pax_global_header00006660000000000000000000000064135210707260014515gustar00rootroot0000000000000052 comment=5c57c690214ee6eacc182f4ddafe8c1402ff0fe6 treesheets-1.0.2/000077500000000000000000000000001352107072600136705ustar00rootroot00000000000000treesheets-1.0.2/.clang-format000066400000000000000000000011611352107072600162420ustar00rootroot00000000000000--- BasedOnStyle: Google --- Language: Cpp IndentWidth: 4 ColumnLimit: 100 UseTab: Never AccessModifierOffset: 0 AlignTrailingComments: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine : All AllowShortLoopsOnASingleLine: true BinPackParameters: true ConstructorInitializerAllOnOneLineOrOnePerLine: true IndentCaseLabels: true NamespaceIndentation: None PointerAlignment: Right SpaceBeforeParens: ControlStatements SpaceAfterTemplateKeyword: false Standard: Cpp11 Cpp11BracedListStyle: false IndentPPDirectives: AfterHash AlwaysBreakTemplateDeclarations: false treesheets-1.0.2/.gitignore000066400000000000000000000006261352107072600156640ustar00rootroot00000000000000/treesheets/Release/ /treesheets/Debug/ /wxwidgets/ osx/TreeSheetsBeta/ osx/TreeSheets/TreeSheets.xcodeproj/project.xcworkspace/xcuserdata/ *.exe *.zip *.tar.gz *.suo *.ilk *.pdb *.bak **/*.o TreeSheets/\.vs/treesheets/v15/ build/treesheets/language/ TS/scripts/\.con\.log TS/treesheets TS/2 CMakeCache.txt CMakeFiles/** Makefile cmake_install.cmake CMakeLists.txt.user TreeSheets/.vs treesheets.cbp treesheets-1.0.2/.travis.yml000066400000000000000000000043741352107072600160110ustar00rootroot00000000000000# Add [ci skip] to the commit message to prevent test execution # whitelist branches: only: - master - testing notifications: slack: on_success: change cache: directories: - wxWidgets - wxWidgets/build_osx language: cpp os: - linux - osx dist: trusty sudo: required compiler: gcc env: - WXLIB="libwxgtk3.1-dev" matrix: allow_failures: - os: linux env: WXLIB="libwxgtk3.1-dev" include: - os: linux env: WXLIB="libwxgtk3.0-dev" - os: osx osx_image: xcode10.2 compiler: gcc clang env: WXLIB="libwxgtk3.1-dev" exclude: - os: osx compiler: gcc env: WXLIB="libwxgtk3.1-dev" before_install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test; sudo apt-get update -qq; fi install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -qq g++-7; sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 90; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then rm -rf wxWidgets; if [[ ! -d wxWidgets ]]; then git submodule init && git submodule add -f https://github.com/wxWidgets/wxWidgets.git; fi; git submodule update --init --recursive; if [[ ! -d wxWidgets/build_osx ]]; then mkdir -p wxWidgets/build_osx ; fi; pushd wxWidgets/build_osx && ../configure --enable-unicode --disable-shared --with-osx_cocoa --without-libtiff CXXFLAGS="-stdlib=libc++" LDFLAGS="-stdlib=libc++" OBJCXXFLAGS="-stdlib=libc++" --with-macosx-sdk=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk --with-macosx-version-min=10.7 CC=clang CXX=clang++ && make && sudo make install && popd ; else if [[ "$WXLIB" = "libwxgtk3.1-dev" ]]; then sudo apt-key adv --fetch-keys http://repos.codelite.org/CodeLite.asc && sudo apt-add-repository 'deb http://repos.codelite.org/wx3.1.0/ubuntu/ trusty universe'; fi; sudo apt-get update && sudo apt-get install -y $WXLIB ; fi script: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then xcodebuild -project osx/TreeSheets/TreeSheets.xcodeproj ; else cmake . && make -j 4; fi treesheets-1.0.2/CMakeLists.txt000066400000000000000000000032211352107072600164260ustar00rootroot00000000000000# Sample build with makefile generator # # cd # cmake -H. -Bb -DCMAKE_BUILD_TYPE=Release \ # -DCMAKE_PROGRAM_PATH= # not always needed # cmake --build b --target install # # Sample build with IDE generator, like Xcode # # cd # cmake -H. -Bb -GXcode \ # -DCMAKE_PROGRAM_PATH= # not always needed # cmake --build b --config Release --target install # # Run program: # # ./i/treesheets # # Should work on Windows, too. About helping FindwxWidgets to find wxWidgets # consult: https://cmake.org/cmake/help/latest/module/FindwxWidgets.html cmake_minimum_required(VERSION 3.1) # CMAKE_CXX_STANDARD needs v3.1, could be lowered with minor effort project(treesheets) # set(CMAKE_CXX_STANDARD 17) # this doesn't work correctly on all CMake versions :( set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/TS) find_package(wxWidgets REQUIRED aui adv core xml net) include(${wxWidgets_USE_FILE}) file(GLOB sources src/*.cpp src/*.h lobster/external/flatbuffers/src/*.cpp lobster/src/builtins.cpp lobster/src/disasm.cpp lobster/src/compiler.cpp lobster/src/file.cpp lobster/src/lobsterreader.cpp lobster/src/platform.cpp lobster/src/vm.cpp lobster/src/vmdata.cpp lobster/src/vmlog.cpp) include_directories(lobster/include lobster/src) add_executable(treesheets ${sources}) target_link_libraries(treesheets PRIVATE ${wxWidgets_LIBRARIES}) install(TARGETS treesheets DESTINATION .) install(DIRECTORY TS/ DESTINATION .) treesheets-1.0.2/README.md000077500000000000000000000113531352107072600151550ustar00rootroot00000000000000Welcome to the source distribution of TreeSheets! ================================================= This contains all the files needed to build TreeSheets for various platforms. If instead you just want to USE TreeSheets, you may be better off with the binaries available on http://strlen.com/treesheets/ TreeSheets has been licensed under the ZLIB license (see ZLIB_LICENSE.txt). [![Build Status](https://travis-ci.org/aardappel/treesheets.svg?branch=master)](https://travis-ci.org/aardappel/treesheets) `src` contains all source code. The code is dense, terse, and with few comments, typical for a codebase that was never intended to be used by more than one person (me). On the positive side, you'll find the code very small and simple, with all functionality easy to find and only in one place (no copy pasting or over-engineering). Enjoy. `TS` is the folder that contains all user-facing files, typically the build process results in an executable to be put in the root of this folder, and distributing to users is then a matter of giving them this folder. `TODO.txt` is the random notes I kept on ideas of myself and others on what future features could be added. Building: --------- Note that YOU are responsible to know how to use compilers and C++, the hints below are all the help I will give you: All Platforms: - TreeSheets requires wxWidgets 3.1 or better. Preferrably the last public release, or bleeding edge from https://github.com/wxWidgets/wxWidgets.git if you're adventurous. Windows: - Make sure your `wxWidgets` folder sits parallel to the `src` folder, that way the TreeSheets project will pick it up without further modifications - (If from git): Copy `include\wx\msw\setup0.h` to `include\wx\msw\setup.h` - Inside `wxWidgets/build/msw`, open `wx_vc14.sln` with Visual Studio 2017. - Select all projects in the solution explorer, and go to properties: - Set configuration to debug, and C/C++ -> Code Generation -> Runtime library to Multithreaded Debug - Set configuration to release, and C/C++ -> Code Generation -> Runtime library to Multithreaded - Build solution in both Debug and Release (if fails, may have to build again) - Close the wxWidgets solution - "treesheets" contains the Visual Studio 2015 files for treesheets, open the .sln. If you've done the above correctly, TreeSheets will now compile and pick up the wxWidgets libraries. - To distribute, build an installer with `TS_installer.nsi` (requires nsis.sourceforge.net) Linux: - Using the version of wxWidgets from https://github.com/wxWidgets/wxWidgets.git - Follow the instructions to build there, but add `--enable-unicode` and `--disabled-shared` to the `configure` step. - Build with `cmake -DCMAKE_BUILD_TYPE=Release .` or similar. Note that you must currently use an "in tree" build, since TreeSheets will look for its files relative to the binary. - There is also a `src/Makefile`, this is deprecated. OSX: - Build wxWidgets as follows (inside the wxWidgets dir): - `mkdir build_osx` - `cd build_osx` - `../configure --enable-unicode --enable-optimize=-O2 --disable-shared --without-libtiff --with-osx_cocoa CXXFLAGS="-stdlib=libc++" LDFLAGS="-stdlib=libc++" OBJCXXFLAGS="-stdlib=libc++" --with-macosx-sdk=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk --with-macosx-version-min=10.7 --disable-mediactrl CC=clang CXX=clang++` - `make -j8` - `sudo make install` - use the XCode project in `osx/TreeSheets` to build treesheets. put the resulting .app together with the files from the `TS` folder in `osx/TreeSheetsBeta` to distribute. Note to use the "Archive" operation to create a release executable. Contributing: ------------- I welcome contributions, especially in the form of neatly prepared pull requests. The main thing to keep in mind when contributing is to keep as close as you can to both the format and the spirit of the existing code, even if it goes against the grain of how you program normally. That means not only using the same formatting and naming conventions (which should be easy), but the same non-redundant style of code (no under-engineering, e.g. copy pasting, and no over engineering, e.g. needless abstractions). Also be economic in terms of features: treesheets tries to accomplish a lot with few features, additional user interface elements (even menu items) have a cost, and features that are only useful for very few people should probably not be in the master branch. Needless to say, performance is important too. When in doubt, ask me :) Try to keep your pull requests small (don't bundle unrelated changes) and make sure you've done extensive testing before you submit, preferrably on multiple platforms. treesheets-1.0.2/TODO.txt000077500000000000000000000462361352107072600152140ustar00rootroot00000000000000[ check google moderator for new feature requests ] - when pasting/typing in new cell, don't use average size or size above, prefer size to right or below. - a way to set vertical spacing on fonts - an auto-import/merge of an external file (e.g. a text file) The contents of a grid would automatically be replaced by whatever is in that text file, and maybe auto-exported when modified? This allows TreeSheets to be a "dashboard" for external files, generated/edited by external programs. - A preview (particularly for images) of external (http & file) links, either on hover over, or even in-line. Makes for a nice alternative to inline images if there's a lot of them. - bug: if you paste a size 0 cell into, say, a size -5 new parent, the cell that is being pasted is set to -5 but all its children stay at 0, making for weird rendering. - bug: if you press "run", it can affect text size, and doesn't undo. - maybe should remove "run" from the toolbar. - OSX: figure out how to do retina text rendering. https://forums.wxwidgets.org/viewtopic.php?f=23&t=42806&p=173880#p173880 - bug: reordering the tabs can disable editing (no selection) - similarly, closing a sheet can make others uneditable. - bug: insert text in cell, then INS, then Ctrl-Z: undoes also text - bug: F6 does not find all occurrences (see Evelien's example). - Have a version of F6 that goes backwards? - fix lack of change detection on OS X / Linux, since more people use it with DropBox now. - Check if wxWidgets file system monitoring works now, or emulate by polling. - allow dropdowns to set their current value also on single click. - allow key binding thru CTRL+menu item click? There's no way to test for ctrl in the event. - wordcount feature, for all, and also shown for current cell - make LEFT on the top dotted line go to parent (and maybe UP and leftmost dotted line). - should save custom color to the cfg - OSX: [github, Yang Xiaowei]: IME input doesn't work No events arrive at either OnChar or OnKeyDown with e.g. Japanese IME selected, and no idea why - Test multi-monitor off-screen detection on Windows & mac. - Windows: [Derell Licht]: unnecessary debug messages, especially in non-admin mode. - "Wrap in Parent" doesn't respect the border color much like INS used to not do. - Alt+scrollwheel doesn't seem to get recorded as undoable - CTRL+g same as INS on other platforms - promote/demote keys? Ctrl-Shift left and right? - if it fails to load its icon files, it should check in the current directory - would simplify command line usage. - non-windows platforms: wxIsalnum not returning true for certain unicode chars? xxxxxx xxxxxxxx xxxxxx F�retro xxxxx - some people have locale settings that makes it break words in the middle. maybe do word splits based on ispunct etc instead? - a fold/unfold all op. - easy way to split a cell at the current cursor position, e.g. shift + enter? - should be easy to add a simplistic drawing tool inside TreeSheets, since we already support bitmaps in cells - it be very good to make the XML import/export closer to the native format, plenty of people would want to interoperate with TS that way - we now skip PNGs that fail to load, but would be even better if they didn't fail at all. - could switch to using sbimage for PNG loading, probably more robust. - spellchecker - print options: scale/position control, and some form of pagination - add a way to copy only the image from a cell - when importing from text, make sure tab is also seen as indentation - make g_cell_margin configurable? - if we support OPML read/write, then we can edit on iOS using e.g. http://carbonfin.com/ - CI with Travis - LINUX: undo log fix in local git - LINUX: (64bit) typing faster loses characters? - LINUX: Ctrl+I advances to next tab instead of italics - LINUX: window resizing sometimes leads to a red blinking border and the program becoming unresponsive??? -> also a problem since 2.9.4 according to http://trac.wxwidgets.org/ticket/14871 - LINUX? recent files list not updated after Save As? - the bitmaps used for rounded lines show up using black for the alpha on some printers. draw them using actual lines when in printing mode. - the fact that you can't just copy a X*Y selection and paste that into a similar selection is a bit weird, e.g. if you wanted to duplicate a spreadsheet row. - shift+down/up while selecting text should also extend selection. - ctrl+shift+tab for changing tabs backwards - modifier+enter for going left rather than down upon exiting a cell? Hmm TAB does that, but doesn't go to the line in-between. - a way to navigate to previous selections (like alt+arrow in browsers) - shift+F3 find previous. - when zooming out, for any parent(s) of the selected item that also fit on screen, make sure they are shown on screen - When a subgrid has multiple columns "Flatten" should iterate over rows (and copy rowns in their entirety) rather than iterating over each cell. - file system watch should really just poll on currently open doc filestamps, which would probably be more efficient than the currently broken implementation and work on all platforms. - LINUX: apparently on some systems only every second keystroke arrives at the treesheets view? how to reproduce this? - LINUX: check 12.04 for libgtk error - VIDEO! on the site, showing how the basics work - if you open a .cts file from explorer it gives a weird error about not being able to make a connection (on Iara's computer fresh install) - export images in html see also html_rendering_bugs.png - if we continue or change the internal programming language, one important thing would be to give it more database functionality, i.e. do something like a SQL query on a treesheet to get a sub-view. - JSON export - should retain image when folding. - improve drag and drop with CTRL to use current selection - text alignment per cell? - what's wrong with font rendering when running from within visual studio? - better fold icon, clicking on it should unfold? - make the folding icon more basic like notepad++ - When I fold or unfold the cell, the embedded image disappears or is replaced with a 'plus' symbol. I'd like to the image remained in the cell. - insert a date stamp / time stamp - stephan: way to mark a grid as numbered, and have sub grids have sub-numbering etc. (2.3) - bullet pointed list / numbered list instead of grid drawing? - auto-save whenever window de-focused - people would really like excel style formulas - easy way to have external images, such that file/loading saveing doesn't take forever. preferably loaded on demand. - option for auto export - export should auto suggest a filename - if your top level grid has just a single cell in it, changing formatting of that grid (i.e. layout mode) is unintuitive, as you first have to do ctrl+A... either make that more obvious in the docs, or provide a better operation for that This is generally a problem as some operations operate on a grid by means of its parent cell, and others by having the whole grid selected. May well be too hard to change this now. - should "auto reload documents" be default on now that a lot more people use DropBox etc? - keep track if an image was originally a JPG, and then save it as JPG? - date & time functionality: - allow a range of consecutive cells be filled with dates from a picker - allow a cell to be set to a time, with an alarm attached if needed only works if it can find a date cell nearby, which is found by: - cells to the left - cells above - same for the parent - load all files relative to exe path instead of cwd? - the new sheet dialog confuses people into thinking that the initial size matters, e.g. http://treesheets.findmysoft.com/ however, starting with a 1x1 would be confusing also - calculation language sample use cases: - tag cells with a progress complete, and then have overal average progress complete figures in a hierarchical fashion - treesheets suitable for code editing! - export + run + showing console output + jump to line - syntax highlighting.. might be fun even for non-programming languages - could have a feature to upload to google docs http://code.google.com/apis/documents/docs/3.0/developers_guide_protocol.html#UploadingDocs http://code.google.com/p/gdata-cpp-util/ wxHTTP - the future for synced treesheets is to backup to an existing sharing service like google docs or simplenote you could simply: * convert current TS to an indented text file * upload to google docs * mark upload time in TS doc * when downloading: * any cells with newer dates than the last upload overwrite the google doc * any older cells may be overwritten by anything that is different in the google doc * keeps the formatting of the TS doc * should work even when the doc is shared! * can do this over google docs api, or even easier, by putting a separate file in gdrive except google docs doesn't let you edit text files online, and it doesn't sync its own files (just url) and the only way to access google docs thru c++ seems to be thru a huge library (includes libcurl and libxml) http://code.google.com/p/gdata-cpp-util/ unless I parsed it myself using the wxwidgets html req etc. but there appear to be no docs on how to do it yourself? even if this works, the docs talk about uploading new docs.. what about overwriting existing *shared* docs? actually theres ways to overwrite, using a PUT request.. http://www.codeproject.com/Articles/319435/Michael-Haephrati-A-Google-Docs-based-Backup-Appli * simplenote requires one http request per "note", so you'd have to put your entire todo in one note really need a sharing service that has text documents you can edit on the web / your phone * actually, there's text editors for dropbox. so we can do the above idea for dropbox. * would have to keep track of the last file time the file was imported or saved over * gets tricky with the real file, since if the text file gets modified, now all treesheets instances have a modified file they want to save and auto saving after an import could get you into a loop hmmm... with the text syncing the whole file syncing should really be turned off, since it complicates matters and text syncing is safer, because it merges changes, so can be done even if one user has unsaved changes (!) the merging algorithm needs to be somewhat smart, besides using dates, it needs to detect inserted and moved items hmm, should start with changing the existing changes detected algo such that it does a merge instead of asks the user this should make that process much more reliable. must also set an undo ONLY if there are any changes during a merge file then gets autosaved problem with dropbox is the .cts and .txt may be updated in an unreliable order.. ideally want to do the .cts first * a simpler idea for now: just allow a .txt file to be opened directly, and when saved also as .txt just shame about the lack of formatting, though who knows we can introduce more of that, like markdown - 2 pane system: a left pane with a windows explorer style tree, and a right one that displays the regular TreeSheets workspace, zoomed in to whatever is selected on the left. That is simpler from a UI perspective, and familiar to many users. It would also allow a neat extra: for the left tree to show all open documents in tree form, not just the current one, for even quicker switching. The TreeSheets document would remember what nodes on the left are expanded or not. - make a free-form opengl based representation? - plugin programming: lua/squirrel/cubescript/lobster.. - the current visual formulas - excel style formulas - formulas where the cell text is the formula, and the subgrid the result of evaluation - a macro system - a scripting language plugin (e.g. Lua) - generic plugins: export cell as XML, run external tool, load back in the result - [james,Iara] spell checker - figure out some kind of "template" feature for common structures, so people who don't know how to design their own treesheet can use it? - the 10x10 grid can be a confusing start for new users, because they don't realize they can shrink/grow, should do templates instead - encryption? (truecrypt) - [superboyac] custom keyboard assignments - outliner/todo/powerpoint style hierarchy - [superboyac] easy moving up and down in hierarchy of items (like pp or outliners), not sure if these make sense: * indent means merge into the grid of sibling above * outdent means becoming a cell after current parent, and taking all following siblings with you as children - a way to linearly go to the next cell instead of hierarchically? * currently, pressing up or down for furthest selection in grid simply doesn't do anything, could make that mean go to parent... but ESC already does this - BUG: the fix for seeing selection boxes at the wrong location after a forced resize now doesn't allow selections at all - [james] is there a better way to choose the relative size when zoomed in such that resizing a sub grid does not have weird consequences? - an additional way to exit fullscreen mode for people that forget F11. Maybe esc should deselect at top level, and then be able to esc fs? - a way to hop from one view to the next for presentation mode? - "Save Selection As" or "Save View As" - not frequently needed, and easy to do with copy/paste already - ALT+mousewheel causes the menu to be selected, disabling further mousewheel actions - [james] import for text delineated by double lf for paragraphs - "filter on tag" -> can just type tag into search field and use filter on search, more flexible - [karl] keyboard shortcut key for minimize to and maximize from tray - [karl] portable version (for Windows) - [karl] interface translation via external (text) file - [d.lynch] the auto-refocussing in horizontal mode (or maybe any) should only care for the Text, not the sub grid no: seems to already do the correct thing : get as much as the cell showing, focussing on the top left he wanted to still see the parent if possible, but that is not something that can be done in general - put grid color & width in XML/HTML export? - tags, search, and code need to have configurable colors (as well as default colors for border/text/bg? gridlines?) Must be on a per sheet basis, because otherwise colors will clash with cell coloring from other people - paste without style? - replace treesheets toolbar optionally by custom panel on mac? - directory browser inside treesheets? - can add GC of images now easily - "mental stack" feature for treesheets: some way to really quickly jot down what you need to do? - ideas for features or presentation: http://www.omnigroup.com/applications/omnioutliner/ - convert examples from http://www.biggerplate.com/ - some way to allow to print on multiple pages. Maybe allow each cell of a grid to be on its own page? Figuring out how to split up a large treesheet automatically is not easy. - do search with something other than red text, same with programming language -> maybe configurable? "set style of current cell as": "search result", "operator" - mac: set a minimum for text size / note in docs that ctrl==cmd / mac specific shortcuts for new grid (Cmd-Option-I), merge (Cmd+Option+R) / enter on cell says not implemented / CMD+scroll may clash with zoom if we'd convert to doc/view, this may help the mac version, but that would mean losing tabs - adam: add text size, bold and other features to the xml/html export - wxIsAlnum returns true for chinese characters... so they can't get line-split - add more formats supported by file drag & drop (treesheets merge, text/xml etc). - figure out different way to do * hover drawing * unicode events - wxOverlay based rendering (does it support blending??) - alf: copy to all selected cells - fix icon in installer? - vic: bug: I have the mouse setting Smart Move set on ("causes the cursor to jump to the highlighted button of a new dialog box"). With all my other software, if there's a default button set, the cursor jumps to it. But with TreeSheets, it jumps to Discard changes. - selection extend with the mouse on current selection corners? - leib: RTF export - borders still not working properly when toolbar and statusbar turned off? - Can we do better than the 1 size less printing hack? - tim: unicode example is slow... must add partial refresh - more icons in the toolbar | icons in the menu - work on VPL - errors inside cells - a "comment" cell that gets skipped entirely - ISSUE: arial unicode ms has rasterization bugs? - BUG: rotates characters needlessly in unicode fonts? - optimize typing speed when no re-layout is required - shrink to selection using Paste()/CloneSel() - Grid inline/merge in parent? - allow text size change independent of contained grid - make ios/android versions? - make undo bounded - FIXMEs - paste file - cycle selection ctrl+alt+arrow? - export using wxPdfDocument - strategical zoom vs context rendering - overview vs treesheets - directory browser based on treesheets? HARD TO DO / NOT WORTH THE EFFORT: - remove tabs if there's only 1 -> appears difficult to do, swap out notebook for a single scrollerwindow REQUIRES SPECIAL TEST: - scrollbar issue in ubuntu - aliased text problem only occurs in release mode? - linux bug: Error: can't open file './examples/tutorial.tmp' (error 2: No such file or directory) - outputs in debug: ..\..\src\msw\window.cpp(665): 'SetFocus' failed with error 0x00000057 (the parameter is incorrect.) - test: left/right mousewheel doesn't work - (not reproducable?) toolbar becomes white sometimes? UNREPRODUCABLE: - seems to do corrupt toolbar rendering on windows 7 -> maybe related to my use of dgipp / mactype? - BUG: sometimes it can dimension a column much narrower than the text, resulting in overdraw it seemed to be related to a column width identical to a single URL (20), and text in a cell beneath * http://bsonspec.org/ * none: messages constructed in code - BUG: UNREPRODUCABLE: wide column layout? -> there really is a gigantic string there, no idea how it got created - changing font from farago to arial unicode screwed up rendering - F8 (Hierarchy Swap). This crashes on the swap of the top cell in hierarchy or the swapped cell is dissapeared. - clancy: files not loading on other computer?? -> likely a transfer problem UNTESTABLE: - BUG: untestable: crash with file updates when multiple file system watch events cause multiple popups UNFIXABLE: - improve scaled rendering: increase VIEWPORT_EXTENT in dc.cpp to get more accuracy? -> not fixed in 2.9.1 -> tried changing this value to higher numbers with no difference FIXED?? - wxStaticText hack not necessary anymore - the toolbar text rendering hack doesn't work if you shrink the window to make less of them visible. =============================== FAQ: older versions of logitech mouseware may interfere with treesheets operation, make sure to have the latest installed =============================== treesheets-1.0.2/TS/000077500000000000000000000000001352107072600142165ustar00rootroot00000000000000treesheets-1.0.2/TS/docs/000077500000000000000000000000001352107072600151465ustar00rootroot00000000000000treesheets-1.0.2/TS/docs/donations.html000077500000000000000000000050561352107072600200430ustar00rootroot00000000000000 TreeSheets Donations & Sponsors

Donations

The following generous people have donated to TreeSheets:

(Highest donations first):

Pierre van Male de Ghorain, David Lynch, Michael Wicher, Alexander Deliyannis, Hunter Elofson, James McGuire, NameAction Chile S. A., Dietmar Bellersheim, Infolution B.V., Xavier Masson, dotpeople, Matthew Probst, Francisco Gracia, Roland Breuer, Yechen Mao, Jeffrey Goatcher, Eric Raible, Günter Marangoni, Zdenek Krejci, Daniel Kimblad, Carsten Heitmann, Robert Mischke, Nasul Magallanes, Yango Pohl, Tab Hockamier, More Addin, Alistair McGhee, Thorsten Schwesig, QuantumFlux42, Frank Salinas, Tobias Skarman, Olav Schettler, iliis, Adam Glowalla, One Man Talking, Amy Young, Ulrich Benzing, Dragan Espenschied, Alberto Egidi, Kevin Whitaker, BTWReviews.com, Dénes Harmath, Aleksis Doma, Arthur Koks, Matthias Knoefel, Tikhonov Aleksey, Danylo Dubinin, Howard, Daniel Rosenberg, Asish Kar-Roy, Keith Haddad.

(+ quite a few anonymous donations).

I am no longer requesting donations at this point. While I am grateful for the support of the above people, the total amount donated so far from 2008 to 2018 is $2007, or about $16 per month.

treesheets-1.0.2/TS/docs/file_format_spec.txt000077500000000000000000000034771352107072600212260ustar00rootroot00000000000000File format spec for TreeSheets .cts files, version 16. This should be enough for a programmer to write an importer/exporter struct File // not really a struct, i.e. no alignment, read an element at a time { char magic[4] = "TSFF"; char version = 16; Image images[0 or more]; char ident = 'D'; // marks start of document, and end of list of images // the remainder of the file from here is written as a zlib compressed stream Cell root; String tagnames[1 or more, terminated by empty string]; } struct Image { char ident = 'I'; // marks start of image char png_data[len]; // whatever format wxImage::LoadFile uses // to know len, you need to parse the png chunks // sadly this means there's no way to read the Cells without doing so } struct Cell { uchar celltype; // enum { CT_DATA = 0, CT_CODE, CT_VARD, CT_VIEWH, CT_VARU, CT_VIEWV }; uint cellcolor; uint textcolor; uchar drawstyle; // enum { DS_GRID, DS_BLOBSHIER, DS_BLOBLINE }; uchar cellcontents; // enum { TS_TEXT = 0, TS_GRID, TS_BOTH, TS_NEITHER }; if (TS_TEXT or TS_BOTH) { String text; uint relativesize; uint imageindex; // or 0xFFFFFFFF for no image uint stylebits; // enum { STYLE_BOLD = 1, STYLE_ITALIC = 2, STYLE_FIXED = 4, STYLE_UNDERLINE = 8, STYLE_STRIKETHRU = 16 }; uint64 lastedit; // internal representation of wxDateTime } if (TS_GRID or TS_BOTH) { uint xs, ys; uint bordercolor; uint user_grid_outer_spacing; uchar verticaltextandgrid; uchar folded; uint columnwidths[xs]; Cell cells[xs * ys]; // row major } } struct String // as wxDataOutputStream::WriteString writes it, apparently UTF-8 prefixed by length treesheets-1.0.2/TS/docs/history.txt000077500000000000000000000540501352107072600174170ustar00rootroot00000000000000since development moved to GitHub, the list of changes here may not be up to date. The most accurate list of changes will be GitHub commit history: https://github.com/aardappel/treesheets/commits/master 2013-06-02: - TreeSheets is now open source! https://github.com/aardappel/treesheets - you can now set the background color on a per document basis - you can now turn off cursor key navigation of in between cell locations - integrated new wxWidgets 2.9.4, which probably fixed a lot of problems, particularly on OS X (which has now graduated from Alpha to Beta :). - you can now pick a default font size when you pick a default font. This is a per user setting, not per document, other users will open your documents at their preferred size. - Edit -> Copy As Continuous Text puts a single line of text merged from any selection into the clipboard. Useful for merging the text of multiple small cells, or making a paragraph out of many cells for use in another application. - home/end now move within a line, whereas ctrl+home/end move within the entire text of the cell - improved rendering of line rendering style and background color picking - added additional space to cell margins - hover over wasn't updated during mousewheel scroll - optimized drag & drop of external files to not scroll/select while dragging - fixed ALT+F etc. not popping up the File menu - improved editing of search/replace boxes supporting shift+cursor/home/end etc. - on Linux, the Fold operation is now SHIFT+F10 instead of F10 to not clash with Ubuntu pre-defined keys. 2012-04-07: - fixed bug caused by right-clicking on a modified selection - possibly fixed problem related to "automatically reload files" popping up multiple dialogs at once 2011-08-25: - fixed missing UTF-8 charset header in HTML export - fixed fold not being preserved on copy/past and undo - Now detects when a file that is loaded has been modified on disk by another program / computer, and reloads the file automatically (if it hasn't been changed) or asks (if it has). This allows multiple computers to seamlessly work with 1 treesheets file over services like DropBox. This functionality is by default turned OFF (see Options -> Auto reload documents) because it is potentially expensive if the treesheets file is in a folder with lots of subfolders. Also not working on network drives at the moment. - HOME & END now change selection in a grid - INS on a cell that already has a grid now selects the last line of that grid, ready for inserting an item - fixed INS not unfolding a folded cell - fix for broken OSX clipboard functionality - added menu keyboard navigation accellerators - fixed bug in CTRL+x on empty text selection - fixed bug in CTRL+up/down in text edit mode 2011-04-30: - fixed bug in horizontal cell layout text selection - clear status message when switching tabs or entering search - fixed bug in shift+cursor selection - fixed semi-colon CSV import - fixed loading a tmp file not marking the document as modified 2011-04-19: - folding of grids: Edit -> Toggle Fold (F10) on cell(s) hides the grids they contain. Folded cells will be marked with a '+' symbol in front of them If you zoom into them, you will see the full grid, without having to fold/unfold. A nice alternative to shrinking text if your sheet gets too big. - Redo (CTRL+y) now works. - an image drop down menu provides a convenient set of images you can quickly add to selected cells - there's now a strikethrough style (useful for todo/task lists!) using the CTRL+t shortcut (previously transpose) - added alternative (black & white) toolbar icon set (see options menu) - "Sort" now sorts on subsequent columns if data in the current column is equal - "Sort" can now be either ascending or descending - outputs statistics on file when loading - added commands to reset text sizes, styles, colors and column widths within the current selection (see Edit menu) - [making a lot of edits to a single cell will now set multiple undo points rather than just a single one] -> removed in favor of Redo - added shift+del, shift+insert and ctrl+insert as alternatives for ctrl+x, ctrl+v, ctrl+c - double clicking grid boundaries now selects that entire grid, not the parent cell - option: render document centered or not - option: minimize on close - option: single click maximize from tray - images in front of text rendered centered - improved toolbar dropdown rendering & size - images don't resize anymore when changing the text size of a cell, instead there's now a function to change the size of an image (Edit -> Images -> Scale) - cells shrunks to tiny size don't show images - documents always show a small gutter, regardless of size - CTRL+f now selects any previous text in the search box, CTRL+a selects all, and HOME/END work too - made the interactive tutorial the default help (F1), both it and the old html tutorial available from the menu - selects the parent if you cut or delete all children & doesn't allow deleting of all children of the root of the current view - fixed filtering making everything tiny if cell text of grid was filtered, now doesn't minimize cells with grids, just greys them out - fixed text selection not showing correctly with image in front - fixed failing autosave popping up endless dialog boxes - fixed undo causing file to be marked as not modified - fixed horizontal layout being reset by some operations - fixed line style rendering sometimes having wrong background color - fixed style settings on "Wrap in new parent" - fixed statusbar items not showing fully on some systems - fixed bug with badly formatted indented text not being pasted/imported properly - fixed selection being off in scaled presentation mode - fixed bug with international characters in filenames 2010-11-14: - fixed selection box being rendered in the wrong location after switching resolutions - fixed it repeatedly trying to save temp files when temp file saving fails 2010-10-15: - fixed crash in "Hierarchy Swap" operation on leaf cells - "escape" did not exit edit mode, as it used to - new cells sometimes would not get applied the correct rendering style (from siblings or parent) - changing fonts could leave other open documents with incorrect rendering - transpose now keeps a selection 2010-10-10: - fixed bug where text background would not be set correctly, mostly resulting in incorrect printouts 2010-09-28: - fixed bug that would stop certain operations work on single cell grids (such as browse) 2010-09-20: - Added new rendering options for grids, see tutorial.cts for examples, or todo_calendar.cts - on multi line text, cursor up & down now moves line by line before moving to the next cell - new tabs that have individual close buttons, can be reordered, and by default are at the bottom (use option to move them to the top) CTRL+TAB cycles thru all tabs - CTRL+mouse drag moves a cell to a destination (like Cut + Paste) ALT+mouse drag copies a cell to a destination (like Copy + Paste) - minimum size of sizing cells is now relative to the current view rather than the root of the document, allowing you to make extremely hierarchical documents (important for presentations). - Some operations that work on grids and required you to select the parent cell to operate now also work if you simply select all cells of the grid. This is more intuitive to some people, and also allows these actions on the root grid, which didn't work before. Affected operations sofar: transpose, flatten, hierarchify, and the cell rendering options - when loading a file that is already open, instead of the "this file is already loaded" dialog it now simply switches to the open document - Added option to swap mousewheel scrolling and zooming operations (make scrolling require CTRL instead of zooming) - Made top toolbar more compact to fit on netbook screens better - Ctrl+W (close) or closing last tab now simply quits TreeSheets. CTRL+q quits / closes all tabs. - When clearing a cell with delete/backspace, it now keeps its colors/styling - Statusbar now shows date of last edit of cell you hover over. The furthest these dates go back is cells edited since the 2010-03-10 release of TreeSheets, since older versions didn't record this information. - Fixed pasting of NxN grid selections into grids, it behaves much more logical now - Fixed potential crash bug in closing tabs - On paste, cursor is at the end of the pasted string - New grids take default column width from their parent - Added column sizing method that does not size any sub grids - Improved line-splitting for ( and ) - Added remove image operation - Added seperators to the menus for clarity, and moved some lesser used editing operations into submenus. The roundness setting now shows its current value. - Fixed rendering of editing in presentation view - Status bar updates even if mouse pointer doesn't move - Fixed refresh error in Hierarchy Swap operation, and one related to scrolling - Fixed filename being reset on a cancelled Save As - Single instance check file under linux is now hidden - Rewrote some of the column width related code to make it more robust, fixed several bugs in it 2010-03-22: - "Paste Style Only" pastes only the color/style/image from the copied cell into the destination cells, not the text - dragging middle mouse button (mousewheel down) now pans - Scrolling smoother on "continous" mousewheels - Fixed undo resetting column width to default - Fixed zoomed in views sometimes not rendering correct background color 2010-03-10: - Grid borders can now be rendered with round corners. The "roundness" can be set in the options menu. - Added "tags". Tags make cell texts that are used repeatedly easier to assign, recognize, and keep consistent. See menu edit/tags. - Added "Toggle Grid Below/Next to Text" (F7) for yet more flexible layout possibilities - Added "Hierarchify" and "Flatten" operations that simplify data portability between TreeSheets and spreadsheets - Added export to "CSV" format. - Added "Hierarchy Swap" (F8) operation (see tutorial) - Added "Wrap in new parent" (F9) operation to easily create new hierarchy (works on any selection) - Added "filters" (see View menu). A filter forces all cells not included to be shown as 1 pixel characters in lighter grey, so its easy to focus visually on whats important in really large TreeSheets files. Currently, there's filters for the search results, and for a % percent of last edited cells. This feature works because TreeSheets now records the last edit time for each cell. Since older TreeSheets files do not have this information, it may take a while of editing before this feature becomes useful. - Completely reworked how columns are sized. Now every column in a grid simply has a character width which can be resized by SHIFT+mousewheel as before, instead of the clumsy per cell linecount. Column width is shown in the statusbar. Resizing affects the column(s) spanning the current selection, and any grids inside of it. Layout is quicker and more predictable. Default column width is 80 characters, so you may have to resize some old treesheets files. - When zooming in, now shows you the "context" of what you're looking at in light grey text (the text of all the parent cells) - Added "Open File" (F4), which loads a filename in the associated application (paths relative to where the .cts is stored). - Improved the UI to be more like other applications and more consistent. Primarily, the mousewheel and PgUp/PgDn now always work the same, without modifiers they scroll, with CTRL they zoom, with SHIFT they change text size, and with ALT they change column size. - Support for mousewheel scrolling horizontally (on mice that support it) has been added (NOT TESTED). - fixed bug where "replace" could loop indefinitely if the replacement string contained an occurrence of the search string :) - Tabs are shown on the left by default (change in options) - small improvements to the builtin programming language - Option for faster line rendering (useful if you have very complex sheets, or lots of 1 pixel text) - text rendering is lots faster on windows now - Fixed bug in CVS import where it wouldn't parse linefeeds inside "" correctly - Fixed bug where closing some but not all documents could make TreeSheets confused as to which document it is looking at - Fixed bug where it could display the wrong filename on tabs/window title due to autosave - Linux bug related to unicode keystrokes workaround - Linux now has wxwidgets 2.9 statically linked, so doesn't require that to be installed anymore 2009-11-05: - You can now change both the color and width of grid borders - Added fullscreen toggle (F11), and scaling toggle (F12): useful for presentations. TreeSheets can do better presentations than powerpoint this way, as you can structure your presentation around hierarchical zooming, which is much more coherent than a sequential set of slides - HTML export now sets colors & styles - HTML/XML/TXT export now always export the current view instead of the entire document (much like image export) - Now remembers all open files when you re-start TreeSheets - Added option to show file tabs on the left instead of on top (to save vertical space) - The "Open link in browser" and "Go to matching cell" functions now also work on text selections rather than just whole cells - Added SHIFT + Home/End operations for text editing (extends selection) - improved grid rendering to have a tiny margin - improved the way it scrolls the selected cell into view if it is partially out of view - find next (F3) zoom in automatically if cell is too small to read - Extend Selection to Full Rows/Columns of current selection - fixed: deleting the top level grid on a zoomed in view would crash - fixed: cancelling new file dialog would crash treesheets - fixed: icon did not show in the window frame - fixed: filenames could get confused when switching tabs - fixed: text editing in both search controls - fixed: possible hang in rendering with truely huge treesheets files - made the automatic text sizing at zoom a bit more reliable - removed the "remove grid" (CTRL+r), "replace parent" and "merge with parent" (CTRL+m) operations, as simply using copy/paste/delete on the cells instead is much more intuitive - window now de-minimizes from tray if app is launched again or a treesheets file is launched from explorer - shows filename on "discard changes?" dialog - updated the tutorial with some recent features, and the spreadsheet language - shows on new tabs - linux fixes 2009-06-30: - Prevents multiple instances being opened, instead files launched will open in the existing instance - Will notify you if you accidentally try to open the same file - empty cells are now at least square in size - shows a "+" instead of a "*" when a file has been modified but has been autosaved - fixed ENTER stopped working (caused by tabs) - fixed bug that could cause characters to be missing on a multiline rendering 2009-06-25: - Multiple documents can now be open in a single TreeSheets instance. - Tabs allow you to switch between them. - copy/paste between seperate files within the same instance of TreeSheets now retain layout and images faithfully. - Search functionality: - CTRL+F focuses on search box - press F3 to cycle through all search results - Replace box, type text in here, and then use: - Replace All to replace all occurrences of the current search - Replace in Current Selection: either work in combination with F3 to selectively replace search results, or you can even select a hierarchical subset of all search results and then replace all in that. - autosave: saves files that have unsaved modifications to a .tmp whenever the file hasn't been saved for 5 minutes, you go idle for 1 minute, you minimize or deactivate the window, whichever comes first. This file gets removed if you do an explicit save, and you can restore it should your machine crash (TreeSheets will ask you). Does not activate for "new" files that have never been saved before. - made multi-cell Paste (and Merge with Parent) not insert unnecessary cells when blank cells are available at the target location - added "Minimize to Tray" option - added Sort operation: make a 1xN to indicate what column to sort on, and what rows to affect - drag & drop of files now works, currently only image files supported (anything else appears as the filename). May support other formats in the future. - added seperate XML with attributes import mode (for formats that store their data there, like OPML) - CTRL+SHIFT+cursor left & right now select words at a time in edit mode (much like any text editor) - automatically increases the number of lines in a cell if text insertions cause it to go over N characters average, configurable from the menu. - fixed status bar not updating when changing text size on a cell - you can now undo past the point of your last save, all the way to how you loaded it - added menu option to disable writing of .bak files on save - "Set Print Scale" allows simplistic scaling of print output. No multi-page output yet. - fixed image export not clearing background correctly - fixed tutorial file not being added to the file history correctly 2009-04-26: - fixed thin selections not centering on zoom - fixed cursor keys & tab on linux 2009-04-25: - can now copy/paste and drag&drop images and text directly from other apps into a treesheet (e.g. web browser, photoshop, etc). - you can now select a custom color, and assign it to cells from the color menu - improved color/style picking for new rows/columns inserted - automatically scrolls & zooms out to bring the current selection in view for most operations (keyboard movement, etc). - optimized the screen refreshing - when copying a single cell, now properly remembers number of lines, color/style - ENTER on grid line selections now inserts a cell - SHIFT+leftmouse now enlarges the selection, same as if you had dragged to that location. - Export current view as PNG file - fixed bug in printer scaling - made mousewheel accumulative, to hopefully make zooming work better on continuous mousewheels. 2009-04-19: - Added "Go To Matching Cell" (F6) function: will select whatever cell has the same text. If multiple other cells are available, will prefer to go to a cell that has different formatting (color/style) than the current cell (this is to allow this feature to be used as specially marked "links" to a reference cell). - as a temporary solution, added a "simple rendering mode" that does not make use of XOR to show selections, and thus renders correctly on on MaxOSX and Linux. It is slower to refresh and does not give hovering feedback, but at least it works. More advanced rendering on these platforms will have to wait until wxWidgets supports wxOverlay's with blending. - fixed several issues where the linux version of the wxWidgets behaves differently - Made some improvements to make the UI more compliant on the Mac (about/exit menus, drag & drop, some shortcuts) - fixed crash bug related to right-clicking outside the treesheet - added a "traditional mousewheel" option that swaps the zoom & scroll functions - removed the floating grey cursor (it was confusing) - the text cursor is now a blinking black line (as in Excel) 2009-04-02: - XML export now writes text size/color/style information - XML import now recognizes its own XML code (grid sizes, text size/color/style), thus can reconstruct a treesheets file 1:1 - rewrote the line splitting algorithm to allow more characters to be the splitting point - rewrote the column resizing algorithm to work more accurately - Added semi-colon CSV format as an alternative to comma CSV import - ESC now selects the parent cell of the current selection if not in text edit mode, and SHIFT+ENTER selects the first child - added underlined style - no longer creates a new selection if you right-click inside an existing selection - added keyboard shortcuts for text size and line count (CTRL/SHIFT + PGUP/PGDN) - double clicking the current color assigns it directly to the selection, as intended - fixed line rendering bug when using colored grids 2009-03-20: - every cell can now have its own background and text color - shows an edit mouse cursor in text edit mode - made ctrl+p mean print rather than replace parent - created work-around for cursor left/right & delete not working in the search box - fixed bug in Undo coalescing that could cause crash 2009-02-27: - automated column resizing! SHIFT+mousewheel now finds the widest (or thinnest) cell in a column instead of just resizing the current cell. Even works on multiple columns at once or hierarchically. - added CSV (comma seperated values, for use with Excel), tab delimited text and indented text as import options - added outline html export, which loads into Word as an hierarchical set of headers - Search box in the toolbar: shows all text that matches the search in red. May add more featureful search & replace in the future - ALT + cursor keys or mousewheel scrolls the view - Undo undoes all edits in a single cell all at once, and ESC is the same as Undo + leave text mode, when in text mode (just like Excel) - Select all (CTRL+a) expands the current selection to the entire grid it is in - shift+tab goes to previous cell - added "launch in browser" for cells with urls in them (F5) - added F2 (rename in my many apps) as an alternative to ENTER to enter text edit mode - fixed bug where backspace wouldn't delete entire cell selection - fixed unusued special keys (such as F1) showing up as random characters 2009-02-24: - doubleclick to select a word (when already in text edit mode), and doubleclick+drag to select a range of words - TAB now advanced to the next cell (to the right, or down, or back again to the top) and also selects its contents in text mode. - Any cell can now have a "style", sofar available: bold/italic/typewriter - increased maximum font size - fixed XML export doctype 2009-02-20: - initial "polished" release (history below this point deleted) 2009-01-25: - initial public release 2009-01-19: - closed beta release treesheets-1.0.2/TS/docs/images/000077500000000000000000000000001352107072600164135ustar00rootroot00000000000000treesheets-1.0.2/TS/docs/images/check.png000077500000000000000000000031151352107072600202010ustar00rootroot00000000000000PNG  IHDRĴl; pHYsͧtIDAT8mowǟݵw^K*kp̑ cd͘0 s!n ;cXf,P fۻ>_?&ǧQ ArzvvxẋvCFBf[c-[?MK5(V&Aj% U,b/ ysI+'lPDP>8,bZPPݳzу=Ҹ"E`f(%HScS3]5s( P> Iua-RgDq*Gon{kc2EHۣo=2xfW}1Au!=tG׊W:N?I=vQQS3G68z#F䂔S(DL^|""%) tDDCۏ㛎&ml* 2w9#"Ex|if}E"CB ,U@×* <45a{ӎg?^go< x7$WT!p} ppP_GnDfmrvtP^Ly=Rs04!&"/eDI 1؋5[6s\pn(hZZBg4^BLbdP.ί.pD!Wm;pW╱0e#PYǤx!Lx'*crU`qODId,Xcq*Eh Cah&2:)YZvm=z/B8!Bwl2@>k躘_@c*1!Z$9.DE@z De ,lAnv^!C(.J#S[5~\ q+> ry-|t4NFC T,DY$>GaY6@q $] T)Knyߣty[wEBg1dΟ0JPRQ8um\M°ux+%f>?<%cǾ얏GWU |}6h"w_.u\|,W"4uf˅6!$"R#mzx0^Y2sEU`@p 6L3Ơbw)?;}lE5X0 4&A,4b_"Oiӊ/mͫ?+;jZJ)Ď~z4¦DJ}O/=]?jLjIDrdȭN-$exuxkWOx`go}]ÌRK2;2(\mv;IENDB`treesheets-1.0.2/TS/docs/images/documentation/000077500000000000000000000000001352107072600212645ustar00rootroot00000000000000treesheets-1.0.2/TS/docs/images/documentation/deleted.png000077500000000000000000000012721352107072600234050ustar00rootroot00000000000000PNG  IHDRG`{IDATxKR0 m%c2֥ܠ vKDž5"ز~ȩU_Kd:19JVUL2qZ=Z)5#X-sfuJcU1c~?q%Bq5Ha=ChkEHUDqU5T+΁qB yB @Bkq@(fگ =KN]N9oLuwtC([LR&:!څE S"W@Z,BrF-=ԇJJub5bUS2ucM ~>4JD`j@ x$sո:i0Z)O^;\q2tܘJ~NcV5[gH~{W4j9lN@+B1+NbQw" #DbOhm>βdeۤؕSˀ% $\C@K=CX{a[+[/2qo$8?4:U9| |g"W *G\W[~ BIDATxAR0 EmSp" k땲B/D!x8v$qަi֒$Jȁۀ<7߽K܆s/M=3+scUfDmχYŎ3Z1=3g L1ײɭCRv 5KZn폔WLvM/|xXu\K$0 3C?ۿmzCHr"|`qFִǣc pPZx]Tx&|Ո4Su[%zUx듴"[KC5NloiCioKK,?,%sS:q$Z7hiZa'HGl:%3}淦T3JFo#ms9#I,嘞3g <'KG74{l>cpYTIENDB`treesheets-1.0.2/TS/docs/images/documentation/grid.png000077500000000000000000000021071352107072600227220ustar00rootroot00000000000000PNG  IHDRu~IDATxAn!E(lr8^{)| @Ae(0T5Z4m7m罿Uh_ @}>@hW~|bÏ+?=>ׯL-B B^G5 BBBBBBB{q8~\±] YfYLS#9lB.ceE 55 ~1r¤住痿c@0M@w}q Zwm h 0 Џs֋؅{mIřtc ;M)DS "vЧj=]_MzVG*)#ߋl'_]'߲-nl .ﻮWh/sB6pdd,s<S@!ȓM L L L L L L ,z¨~w8 2 _IENDB`treesheets-1.0.2/TS/docs/images/documentation/gridline.png000077500000000000000000000010771352107072600235770ustar00rootroot00000000000000PNG  IHDRXJIDATxMr0œNOn`鹼ڭcBrtgao ?ϗGHTJ " (B (xu>fc6>ߜtp;BIs"\O#<#fV! ͵UHGf#3t`F:0#`L=J(e8|[ q)2ї'YGHXF5&6,3$t#J)߮tRJ}6o}9#~ﲃf3k >JhSÞ/o g5\_^YJ,0'lk˞>ߜmt%fVR"V.l!y'[].s e^.Sy{jozIQYUdf[)1fg=vyˉ#D`'3#vތ4˯#V2"t`F:0#@Gf#3P=%O#P@!7To!IENDB`treesheets-1.0.2/TS/docs/images/documentation/gridline2.png000077500000000000000000000013131352107072600236520ustar00rootroot00000000000000PNG  IHDRV_;hIDATx\;R0}b(ih90p|Z2LB%HKCo O쎧;Z1:\0uWE@т9Hfq3E\@0G@@ vSl3xAȳjNat]qٯ6{qSƘH=.[>BOӹC.VE:&Քj #c*o\9ߦ=Ja9 9>TTf鰫Gp-/sE.ctzW,\ MEu #Q]\cI |u12sa0`B̬ts{U"O5":GYW "0Aₚlpix.&t eIU7jJuרdE?dbrAe )6ibOpيm D4YQ,>9¿h?G4/ٲhٲ##SLaE ` @ 9 ",͛rtpiP(PO ^IENDB`treesheets-1.0.2/TS/docs/images/documentation/gridsel1.png000077500000000000000000000021541352107072600235110ustar00rootroot00000000000000PNG  IHDR|pI.3IDATxq0Lp)É'(:Hj9`YE,\N0O Jk|}G9......Φ_7{\^vP/[B.tӃlӕRJ!zVDW}Nkf}/N]x:rO>gֶ@ a)J)>1tfQ:f!|H# G tzGIF:kWFewOx.NKڦ;.f|%VouϿDZ5Kfk'&F)ݺ:lwo#5#ǮosH$@X#-YDxO;=Жiػv + < :v<"t ]WxH :HEEEEEEEٴ(埠t[q7HDD?Nv4IENDB`treesheets-1.0.2/TS/docs/images/documentation/gridsel2.png000077500000000000000000000022421352107072600235100ustar00rootroot00000000000000PNG  IHDR{B•iIDATx[r!E!UxES);ߨRBP/Cn5fA #m2.?????anD!Qr8n}BL>Ԥ?O/O/O/O/O/O/Hvԯ߇_XL"x{}e? vc$ NuRX`p<}+Z)֙Nq!|pYj}=Sh}?Xжm-7x{ $޾6SW{1:Γr^} G]y(GԩKp}r8J8O)U[~ߧZ>}z|R[w3 \Fvd oF+) @} GuZII3$n|np@4pr^9Q9SR(7'WNKhanMڢR rpt%~thgesOJ~E﯌+&'0:NZ_91B!Lo$ Sْy(11Qdʹ[k~|~%;IDpɮB~SZk󪇧|2}-KhmFJK #Zm$J(5eBXV9 as9~VܑxWem̘pϿo>~أ%mz̍UtI1-j 㩂ht-:gz(0,Z%=X߳@v7+Z~Ӑ} *3Xe*ϔLXr:gw?` і|vOv3[^xw* w;E|T QAQ>>:sSާxoЅL%OGd/ee>tʿ'E8z虯Fg>JgB:\qUtX.T쟩^c-}PO!ђEY1J}j/ SS/?pHqd7vV7q藧/><~]\{Da$j؝0a|`5!|>0 #N03~.:7g`D3hxU[[F/?ݻ7 q;Qyٲ 25R30{W>pCV ~O4升~̑a>vdovX]1UPwb; ]1CW u+Ca,߆30Ǡ+>>>>oħIENDB`treesheets-1.0.2/TS/docs/images/documentation/insert.png000077500000000000000000000013261352107072600233030ustar00rootroot00000000000000PNG  IHDRW^٥IDATx=r0GI:Si ;u DU@B=־M(( (8( C O߿K 0'Aӷ/5Ոe!>2jEp&?#Jg7Z033ktK2DadI+2hOBw;=u]>|>oyD\YcW퉢dDIdc 1u\WUz{M4k w[kjh^Hmb ;2֎q_g _Z1)KI=rR[KIqKS?>]V,9}t{J/^q/xQeܞX4gp݈#i !Q ƜxY. t/ԧ/a`g- S+YV_>jM9{M -8H |G҂#_i/d,7?R;!( (CXjzwIENDB`treesheets-1.0.2/TS/docs/images/documentation/long1.png000077500000000000000000000040021352107072600230110ustar00rootroot00000000000000PNG  IHDRPIDATxK(dbN9c;Lçkhh0 6Uz΂Tr˲zl![LW}r@w_>JW`KecS|pu)tY뉈;zl!ꉈ_]B!XB` QO` Q-D=z%D=DB!XB` QO` Q-᜻:"ggGތz~^f( ,kYeV {$>}3}.`eQ9wzZ轢٥7$K (`Ϳ8 Wi^NiM:{T՘ 'B^}@Sk}iOOݴ[u/`oԘʩ&>jqʜnWJ˛7Aw4V^0 W_[5*o(5-h_7wZҗfK?y"aGuou UCUF9fZl m5^XҼS.WkthIyvu ˯-QoVFP撦~ڊ:{%lusc+S+_E y'-ORknl]z<9U;oyƷn7|S/.jhކ]6^λUɎ`5.B'mKt?UϨ0ASnBv^CU"{9^W|%]5[89kCF--M@BRS(3*ia\Q314ĚV9d$O]YY< W_C Vrp(l?B0?Iܱwh{1gA~I+P֘d ;,&5kByW* u Z*;%YGIfl*1=>g#Q͹yomXĿ[ +[_Ė#fOL~ԶgجTN&@#³oz0rdo#¼53*l !Z99u9aOȲʜ|N ڝ NtsOϹǎhZ9/;'Dk"/,c0sr&y.1 5J6M*!.PfZ451@.3[WOm j~\M{6هA$[z,#ʩw+OyF)F[Z(74?7*(@r`Y#o̶ ;[&5 )>ET0M5殘gڢֽ qܭ|@+l2Y<`P,9Zi^K.UhRQ.D7 ym M+AyNoX@x_O4 j ?,ٴ_-yXBcmXFk`s{Cdx#עY71rc7U<,U\ɖ sDs`N[6909O9t9cO훊WCT]ݚ&Ye7WAs.] d z)'kg4a𲌛9"9e9-+N?@ h1{Aw9K+{“G$-JgN?@3/8ydנ{QIW\(S|s@֒^"^ʾnCpƍJ@<{<7<.-86<5 3!H.]kDƠc._jʌ>g<9ٳP<m'bZ˜ko/` sn'鲚\6f$hM_ An5@1䀖ZϤ窪ׄhM<4[_ rx8?:SXp gvRsϔt  Y]Jy?yh9{ q,0&Bzv)ˢ!U7 <^w|9e[95϶ݳ爐`!I =f{Y:=lO%rr Z!=(Ѓr,Ѓr,Ѓr,Ѓr,Ѓr,Ѓr,Ѓr,Г}MW&XH<6X@9P`X,AobIENDB`treesheets-1.0.2/TS/docs/images/documentation/long3.png000077500000000000000000000040711352107072600230210ustar00rootroot00000000000000PNG  IHDRJQzIDATxM(F?M>x'sFy!5C UJ%!#IAn7H]]H6l @ )׿ Hyy}եGc$t)R׷p=H6 ))H 8 ))H 8 ))H 8+ò,Wa4k\Rj3W걷Wf(Z,&h h[ۗO.? $$ O.²,5,̵ޗ1X9m+H c vH`X..WK:@lr`6b+wPk`'&W Vd<7M܁To˙{ABI;U* 8c~8h'ay;}Nzquzngn)/Đ =ɍB㳒 ֓-|Q¯0(M<5ˤ0 '(7&z. xo$I|k&Ͳ,r+H b&qXib]d͞q'grԘ _Acb@ M&GܞsmٺK.ߝպ0:%nJZgxA2O*6JK4ɿA+nI Wj1@ J.yI%t |sUU]W~`W{m`0d܂se+A=< $ybd.MeOϊS4mytvZ~r!CXvV;j*p>:r]|̠da*LJ `)H+HB gxHA   _ArnP<l?YGxIQF95K [U+Ѐ]tM R> AR'Ɔig  b*l~VAb BRRj 4REˢ+- ))Hw+)R$1ۘ$*ڍeJ6yGXsCC~nG")T`%unh2@HwJĕ3XV aN|ҩF1@ @ ǹOm)' \nm;a0N$RLSr&5a|{~mJa>iK`6u@/yH~QB/&vUF@ OJd<V_q?gO0D1 qXuZ) ,k9E"oֽWPU0^03U H{ 0g`T>@¼ݗiBPyx +_HAWdH͏bch{FMHAW$$!p  _ARR$!p  _ARR$!p  _ARR$!pd#u_=H6l @ `X tIENDB`treesheets-1.0.2/TS/docs/images/documentation/new.png000077500000000000000000000004521352107072600225670ustar00rootroot00000000000000PNG  IHDR;D3IDAThA @Nቤzf\X^ƴdd@ S$e;41o~8s^F5+[oo=/[U1uszk}{ǩc:c:c_ttnwK~UVuc\nwtIz=x:c:c:cԷVs`'ꬉfGR<=y꘧y꘧y꘧yudO'!=ļBDuIENDB`treesheets-1.0.2/TS/docs/images/documentation/postgrid.png000077500000000000000000000012621352107072600236310ustar00rootroot00000000000000PNG  IHDRHx#yIDATxIR0D-Sp" ǰ*'5BKhpZ$~TWKOv#7[@+$l4$l4$l4v+6ny}~H_SO?~ydEHYՅHxnDB$L &A cD1"ap2&ܣ.C$l'1 3N:[qcZ;?A4bkmjnE]Ins9>]rCOM ?fEX~a}XnDx?}I(AIv([;ngEkgqpB'ޒ/Wn:ew|#t8V[YoDr >T^JTl,ZnooZK(e6R]ߗҨ3=0A$]8P$8P$8P$8P$8P$8P$8'D}rP;JTߴPyI(IENDB`treesheets-1.0.2/TS/docs/images/documentation/textsize.png000077500000000000000000000020331352107072600236520ustar00rootroot00000000000000PNG  IHDRm~4IDATxA q_OStnx̶Y1ztVmGfZ+͇ t:gvtB=ڣ{laK첰GJ9#Q]@G#$#$#$#$#$#$cIx??)LyAЯy=iɣ QY5a?xoGd>k-78VlO iY8MіPM)iNzns9=5=23l-q!m-2wǾ=Zqkǹϼ9%M|':yR/,rS3A`<ۣ!~2Jnf$)b'%T=v1hlchsT/)cN >эeN׹|C+Ay?IogDd ,KLo6iH,B|Jٴq@H9E!lʲY@tJ>&)߭m+8YJ2dd)RIHwuJ2(RpU_==N)%RX,hy3)o4Lp h?W2 p?WqB/bS%?Z4 HJ̢mTQ5#wΰRSJ7Vrt땩Wp: *Q5{>:[D%/|Ԍ4Uuce7X +N®4V,9?Ǚb\cZ* Mzա=ƥП*)z^IF,%YJ2.bz'IENDB`treesheets-1.0.2/TS/docs/images/excl.png000077500000000000000000000024321352107072600200600ustar00rootroot00000000000000PNG  IHDR}\ pHYs==U8IDATHilUo:R[)"e H@E1EA1 %1HJq!j-K))Tj 33߽?҂7~砪z/J*ޜoVh\Yx`DU;g xyw~.&2OFW0M4dcx"ן_y'Af3aքoQmWEt֓G(쩶5s<:g<5ed$FI䗾l1^\X]lfgEk[ڄ?~Lm)цo ՟ж/`*际}TQ[+axY!5Hc<#fΦҩ{?-3 3UK } 1X63aWɂ)ǝ%@V 㛉8{mַ7}zn{IHfLEZ盽' S) Bgk5A;@z\JFZiΊ1֯-猿xjXL+I@pm'5ތl_5C zɞuK^q(Z(.y8 d;3}T~rQf Vat,bsvi7]yC,:[\ q!IU6)_85,)/V,8p z yGik$+8 J;7OW6E ;c|_֬|梫G"NS4$ j@4cH(.#?/_-,ݱ晫ꝕtwNɆX'3x,٥{0NݺD.#j6Ы" 9ې(;˴;`w+S懅f>:5YP$ Gǖ()P b@)n_[ 񜸽V0x=һ"J7'\tl!:Bϲb,d/1ɆC' ]uT=D!j*N0 /x ʐBt54e6Lzz(8uU4Fz\ ݋@AS Sިu>udsoyR_@xdڋ❠i3=d"h1=/s_~VTZPҗX?!ST .}f¹!mHyV\a$ջIENDB`treesheets-1.0.2/TS/docs/images/screenshots/000077500000000000000000000000001352107072600207535ustar00rootroot00000000000000treesheets-1.0.2/TS/docs/images/screenshots/screenshot_personel.png000077500000000000000000005670101352107072600255610ustar00rootroot00000000000000PNG  IHDRU IDATx}y`U3fY!(V& D~n T$(h.D% drsfΜ.IXk8gw3C(e.vp@!خeGKfCV~* !S|q+`sgQ?N[>u@u_8(((((((((((l6iשPJu_&^x'p `3״;I @ ;X߶HAAAAAAAAAAg |>B) u> |!+(((((((((&=2|>c,x'`-a\MRN ~`t,()u얹˼_WPPPPPPPPP8eh20G~AkkkkkkO8QSSS]]-ؿW~SAccccbb%E:"GĉG=QSSè 3cbbI<"J3l)Ǐ?tPEEEEEWQ2QPPPPN_s ꟔Ժu@ io ?nkkjj?^QQqE+ӌGei'56!G_9X5Ґ5nإ%-[fOTk MO O F\nK'+y&p{{8:ch,3q!yS'$dT<.#7NLz˾`AcR7>a޺Ȓ89};%:z+vKD}ayy%BͩR:I\<4+X(q 1VXdthAWH =^{U+hڏ?x!duûw-1gD| @osE k (E"}G]#Q3~hLpHxlI?Ǐ=zlQs]w}GD#{d|QYf=y{ ?! Y /%P1,#(1FaB01,Vj$AmɢQ`ry,qɘ,=5NprMB_jt.(5:#lC T %F$VABC(.lExӤ Ӎ5sҦ|pO14G'!*Jy"N>z131GeHHṥA::mx-nR}W ݐPGQnX(}7]7(66}K>3***|(ۦ@ N򩮮>rȞ={:"k0XG!)(`[fjQ&u oNLwwD&qf \##na#jM<&bKWVm\' *q;{\"9ca)1)rW#(Jz"1t( f"V%2aHc(1چ;%b4 Fj>ۅɦh_B/ !#{1F Z"q락K6Hd) Jmi !&"[9-nƣeQ" wN9_LVd3w4,xWdlʹ<$ 0p hP@ s6 -<Ŝ؋ Y,a5}5oצ!0(s.2u]1vK(F򻏊~rNm=wWWG`zC%w1Q>={Zj=}>_ 0ȇxVWW;vl˖-ӦϨ:aM^(Ѡԟ}ʴ"d '!\䟰pKKߖ+%ES1&f|E6~AI_-$M08 wPVa!3}Nj>cPT.T0ۿa" zAԳV`ݍ;#J]6Jf#LsQHC]-g/N?uXPm@pPS,]2]u[D5,AI:g.=wlI{đOm ]ð|\$yTSK&A"E7=>|C C#ʄ Ii`Hz&V|8Z3N1u4@ Ҁ?mڼj򇏜t6ǀ~]m5Glwt!0QOrGNƒ2zr_ z `C՘6}—#!!!66V->.q55vN0s0 _qke/^\+An7s! ƶ6 ®x.qVKOH,!Ex8{ )M T5e +c3|8/n8EK 6W5r9xSχ*4fP­*Ęa<AOB>Sac2sգԞ# Y@=; [,lHf4"$PbNd7Bp(!:(t b,t!5)~1(u ņ Q :ڠzqIZP0|A'X9$O5·N[j5ίˎ[{F_ ,O>4 mӦ?Df7!Z > ]P;7/|lOIyDSE&R;ɺ5mwF9*B<M'xb52n$ /€ M؉5*S)-2ACB>܄`uPISU3WT)@) eͰ@ l•#b7`.9pOؗջ@F|E{&=sI}VE1ǻh&|d ,^V ozb (_ dbCCsُ.6- %P%:ԢPQﯮҫ~whu~umGG񡮦:P[WsƇ1 Tle|nkLQ!yI/hz[fcGFMԸ_ 8 H(:; >%\ũ>eohئKB֣Z׸t(RI8qhN|= 3~Bp;VػU"0aBs#D$tzQO'U7R"ݤpW3-β(Ģ0sg-,' "sAVau=O_XKxۓ.i&* 0CE0c&b@ַ%AƁpF*q̒Bd(P0cKGNz']lp[ǖ blg0ۥ:FgM1#J6mF%n'xUVr˗1>ULuƭ;tMڄ '%v67\-;eٺe}.nƈj?r%0@hCձsb盡*2 S\ >^LMu'_xMj%M|{cWg2B^Zgݟ|1~OҊb{Y L 8V=z%߿oW[Kּy d›kǞ}eȱ>iyqȱ_XY^6p-~(PQ o&'<1k]V߼c#I`EHJyAՀzԤ k/=mb[{T#h:kCq.NVdpgxj? %_ew[ jIT.k[dKasa W;%@XށlJVGVfyׯx9%1 mԅ7B*;g|M7CH5凎bzDڊCGlэh}Uc||lD>PF|pF1rB/.~?[C  jbOmO*dX/~q0"?eVޫ1`w}799+z&/hْEqFl@C!l@}ˤr#QYE-5wÇ>b]Y(gQ#+.xc}g<%%|_Uݮ] ᷏_Ŗ#ƌg%(/?ȼVEee{4bx>|!]iǻXXU pqыv]ݧ{+3!Sr망vI))V'ܞQ\*.\?8u@&2&,rPw{?񸶢Ezqʧ̺vwQa1Zl]b TKm>1%znbws^<nӘv?Öbǔq>R ,veᄅM:wJW{!bK/a:%7.$$vL!1xds>uLCx4wJVRe=ÕI)G{`0%06jחx*XɘsqLߕ6Ax~]$肮>B;&ܺkk]vM^Q~P?8Nv{v%{i/n[\*F,ya\ {^TUe$jЫmX&{c)[~ݫmž݅X"Ww,S%0t؎i,lfNLN:rYbZ8jtUyY x|`"Jı09ݵfyn b5޻cg{Wֱvyd;۵sEHV]7޻za?~˨q,3ry][ǰWr9N/fy{'toSY^VY^& ֚W˭w9@jƚ9C2{voêVk)*sۯGXY2vE17 U݅`%=[Gj:6]ғέON%>J"8oG`RxN$?|a,P\ʫ8Op"*pK 푔\ܒOV.FNszF$J*vSӾ*>KDtA{F.0aAU(w$&\? @hM#4i4p1;Nyj"Oͻw\>$!h'Ђ<<8p_]]m۶͛_r%V:%+ƭCHJN(/UZ[?Vly+0+_ϽoU>۳UQ^`Ĩ>/wk';kE)h׫z.yO1z5V9_Q~Eme]P8!AS\eq,N-}Zp^\ ૭ӯǃk G{pd XV`羂d&%EC2{ݒO5Khִ6H{kSY"w\%i枨q7u-' f 'hgpF̔-%O\% /)ZJ(KM#[|C ]r*'+epˁyIo&$"a)?/"P#sbqȬ.4tJuJ8 D3 ,p<6J Lu!\(-. 1$>q1$>Ɨ">% 61 z\G|<yx+PO y"*G|<-6'xj  !FTwy5\pBl߾8|/o{݆k1<¢]ӞLk6|DԴg7uREyLз>pУsiIF}EW~dee|l7Ωi#FeEOOzSfI𳯙꿾HXKkTԼa__\lt:1׶)J&A76Rcmqڍ&g ;wg)pP'_\ܹK7s*VpeDEX:Smݵymݵa?kE:xy}Sӆcɕe;JyQzc/wzzx!iz19dMӨ/DG4JhKhߔ5$iƑqqhOģI]uDEԑ:yx4ך!!NkGƑ&qZB/D7S'qɔ^T|hVfW0`O<<hvk4:S 1{#q@yĚ>+_ϭ(0bXym)y*jm)>7\ `]RDL1HJJ!|嫃xgwP CN-#Fg3G 6J&M48bܪZ=SJ*OS&L|OiVzܪÇ*"Y`:+?-/gnYDAUys -蓞:?*۲ !yϿ)] ʊ2`Z4OնOoveD^}3֭\a3Ĥ˨LFvKYzعĬi֮pI{;ew=[Z kW1ʾ~2/aaYco S4cʰSv `΂E,2e8 $1,k,1l2Y#[72*{XV3,b]Jpu-3̘ĚkVΘO"HE"/s{yIUmtsl(ag.)L_9ی:W+v:  yDkWU助n5N~|>{ze҈5:[I,]ekB C.۔ΈSSU 3v\x5LeY׼@D7NC==iJ%R @?Gݺ.N.o'S}MGl;NɱcWC%@-}r*CBl-ex\mp8\} |fgY߉[!31'_X:?GLcOwV&R}l?,7ݙFeyYn:?bf; y[u .W;N 7 =/L`+y`ðCG1vد{IyalNLNy|+V1uϜ w3"{gV$$L9W,/c_`>FC 8 z׮:{̞m}n!=*F)5mXV{'£dɚ9UtF a]$z`Y`>,/lŭ6n~xW!19Es]y"=_2ꆫXt>`?>7|J;+cs/гMK:|^vE:gXVvDVfj맷de%& 9vɃ>199>>!PP?w&{ Pع9 Ǘ[7{ҷ>o,/Ѧ{vpqkW~w?")<"e{{鑥MAhlFA)t(_/6iNߖo&o0!ޓ[&%={ߔCf}b,޶CF ä|=eOLz#?Mpۍז]T9%n(śPS[ 3(Jiqy]I2PR1fmU}3u߁Mض𲗞ɚPD߂Ϻ`|}ݸEd˹!R.@Ap}+_Ͻo|=霚voV0|Fo[,or! L2=FBo]; 5v>?;b}KWXzk?2{'?xy\L c<CTM l+3׸tN4-o;w&7__"ROG6[Gc+j"gKvuJ5샛Sj7&0 `{?is:} *@]-◞`; ;vI6s.;CTfqAܿ{|CO;kW+erc4uջwnfym@dfO8}^t}?JZ,זZs5FӃ7Ȩ,vf;ƍ D#} kԬ׾8rSmf:IK/yQ3'lzA!F}-Xq]6mfP^V^̋ee#nϖyG[霚#(S:}vq$|'J_`r8;uI{⹅{,n{⹅Ge6'7'l^Wg <0U爬[3[E1oM~ŗsGw/|*&M|K'^x v1?|{?3*.*9}ǽ3pEI\SJ1IڹY#:op)sY}3X{gaG(kߩ\M_2)Eҳ_%q]R\Ⱦ+"~Sz"`#'y]҆feS|䔒/|dYٔeRJ׮ȥ:r,S-r~}I#'\W[ƶu޺z0T9 ^t5*ʆey{UkG<V _#W(]Ȩ30gaQerΟj{H}=׵rõLN׎\`amW0WwL96ӱK%(R E+oGeb'Dة,Z&|XKy8` gے{7?Ә$ڸn`lT+gk 35]#Y+ WXC3(>wq,5K\ȳ@!jb H`0b{^@@Q6YC'};/;܄En 4VUը -ɑ#GjkkO8q񪪪ݻwٛ_0J[|yaaa&Mڴi㕀@iicFPB4c ? PĺxРOj yP80׿/0Bӥ=NKB_RK􇆛]j r#YU=DL8YwԽ8;H8 سc47 î~laY㇙/QiݵnE;܋ EϐIgU}J3@)1o))CU=p?i;Ku0 ZqqkxFSM p̑G][ו .3N?69xOxeW3UJNTv&-k,<Xn @Mmň*J-kD>4#4~>E.)%{g ?!:mq#֩%+2\ҢE͛'$$4hҤI|||x)ǎ+, k bpcmSjTsa^]`ϣӦ 9V^,)2M@7_IH(x]uLF_(4-G.hzAI\~9$Wn q]atjQ Urdlj(WwN)9n7jWMK!8 Kt253~sgMG,xv3VCa'"6׌$h7A;9ir7b P=Pfmjq)?ӌްbVԐj̽\5c9D$N2RQH"\C`5IuU @{JfNAHƌ%VR򬃸Tlϯ5r208Ky]0[g1_"-aלX5"Y`ϥ㸈D IDATd}LnЎ]`~)ea9 cU,9⚠85dcλtǤV_j8WP,z}A+nwMr,Ro;XJ=)II $ Ɨdӓ)_$vmnk _' SH>NA0k_@ 4>zgD0+O\+Kx&"ꏰFot"8ÍS蹬CC`2_PC&p]{|}<]3(3C}&BU[:+%<_8+t ~Nj 9ԤaC.C0MFSb[T0qJ#g|f:]̋8֨Ȃ%/"~">I~5o!T,W[ /P64ƄP0~Z3T1DRsk&$D M[L%g&zBRmiv%_bt7 jIT.]P_>PI?qdY=n@JhwE[`"iy0 ayiJ4S4LXu65?᠞Ͳo#l>[+)5% } $53+*{RL.|$D(/1jB'ļ/6ѹ)4`+^ `k e /|~n17C(xIj&62pMS51h4gU8 RG(m2s63.s{>,)N2ٺ-\buQyݻBsJv>Ee "<26$I[1J Tzp,,z -6L?@N( %`쟘 ~`d)$r3?pkn`n"24~&&yP:1*djgYr^b/ԝG;wpK/eݜsO^1N Z | )g6 n !:H#FkSӋϪzؤ:^Z mm.lήFjhnϮҞv5HR\ewq#i#=P^\JI^о!+֣ETfɎ@)S?ZtHJ&jҨ:^q0 Iq!ۀxޜ `X} ?ڼ> *((((((((((d 7փG6#hFB=u 7Xa~7 Ljy>:" >Ŀ_T~Sc) 4M  [P~SWPPPPPPPPP8EpNxNn| Mo49޳ фRNZƏKo3Cg܂駿 942XJeO}E4 M ƪ$n>_5ba 99w]6~ZP|t?FБG'8@y2YN}$ %*gy>m"mO-=h鲜=PR0~=p:Ʃ'WmA?s>Ʌowe:97!wݔ~ٍ}SۃڸOBP]}_u~u'j\ ~>@MmHiQWꪈR-MB7f!R!xUWW{~eWǿlbۓ{%c{f5u񴊿$xzQ: ponk >#=陭]KމfmKӸd~ۓr^Q·2#I7s9c'˫D Trg!FN _yӦʝEcgX¢oz09cʝEK&l|tV޾k{nq69 l22_ZQ}UýmnظgqKluXz+/n)հKM/EcCv<[v!ZӚ .ģOg.`zo1=g,XK5Q ikõOOOԯQ~ׇGˉ;Og^#5GjbG>r~U|&ݖXKlo%t?!|x$ 03C-4 !m֚o?y̼Gj7,Q۬GcGndWǿx?v.{D#LƼY~c\/nyg/dN#~cz,&yg,^z˖Ox}ϥ4BУټ#(KioM\꟦`=3YZ #5q0V| vnauJ% 8yg9Fxz{K[>"J !Ņ9)m(! o"D2їt/Y2tŰkhf>%@䷼Sr3c ;,%FqGd/ݼvo3)KH͟,2 yJ}oIFRuF; S3H.D_65=s圴 X-yf3K? `fK? |6ޚBiϰGϱT!ܩj"QDEV| cJ.ny>pWKvWx?L='{ I|!sP#X]JgWxhSn{m^6%R#30\[tSG)/o?jw6֊a4G!go@ݻ2SǐAwݰi/#c]"F 6e]vЈG\DcB]jmKݶ7Z\y'$@eVkh?꟦=8j@v}yZn)((((((3jV~f5֣۽dG} Y`dp3':$la uKJ *]t8\h] Vg(FX#9϶w7ǑNs0,mp m i{͔uƋCdh7n{X[S0qvF@?v?K{c]9mnܒ=W9•$>\;V!=Ǟ]zN=ݞͧ`xMP=Yvaƙ?9//\cҶ̾c[! 4DK,XD?qg=QdrflKCy'n5`oep]>zǃ:BH'S'"};ݐ&޶yX4úf~v3ddXArTXe6OcȊɃvW_?г:2sH^! (=lOW)((((((4qC}L aR :}֑e8oL{S;GFj$!]Vy?g;ǠS`MO IsLP4+|%ѩ~VݸOl&/nZ9uߘf7[_3qf6DVOүE/Lf-n1pDߡp1e1|臝*>6qC|jC痛u⾣yPGc9eDsG1Gf]y+#.['wiM\ul'Gϱ+wdEr*ijrȑ'N?~{]?ꋢJ.hz~~m+(2缗uv+R3>g pfE{8jqH.>hѢy M94iDjWᤠ^3^|:s{v8W k>~WүfwF9׻֖Ǜd"YQ:/DDBOAAAAAt!lX'((4*?r)@G}H`g~!ͧp^_S+t&pJwS+"qk??;99qG:|3rFEseּ4Ts ԟ+((((A^󓚟8Xqտ/OwF gӝO$eӝ3FR5\EgWj8'@U=pW| z@o߾ǣ%ʷfL%PAדGCUPPPPPPoXv1-bkg(j?;wp3xwf JA|GbnO&XJw>~  u۟<Sw墨ja˶X>o=ygmOFCDTn'1I)DרGRPC4RG(*i_ {y~>Шݐl$EUG'@鉚:=Sο!8IbIso?r?YFnbHqmƌӦMӝ---]lDUکA=h{KL0TЀۮ~)~!?.6N6w-J qޭ5})6@t F5PB@m;~6YzŢ;_b(u:ƫ JuO;z,Lw#~h̖w._=;uݻf*#PCY.}2;=+$&,`BkkGbNykl٬bC?,Y [#y iB~,:Ĝ偟qV~웏,}qqgz#%E?d7j=~(vkT5$- oӢ5fTt$,Lޯyt~N|ꩧ!I}}>,))[i"3W^}U ߺ'dO?sD'{oƆ6uoe&'o_Z$ଏ Ue $MX0$  )!ߖ})7[6|h)0$HU{%UG^1f.էȤSO)'fӭ7Վ7`sVenh00PVڣ[۹B=앖ѭyѣjۛwmV?nqܩ{d;(=`!* wo^uxu{|'wJ*--5bZN}s#$ɑ`xؘZ_}!\fe">s„S/z`)o|3tTCf 8$0yvۀ h0ϛ?d!؈b*ˇqT+++?}{3T}sO;C~V534`44@30 =?u%&/~5yWVi;Y?K?sr,C:6~ ^{} 3nj?r֭W_֒&r{rDgϞѯK+!9-[ U }OJ1e꣩[,>uk5cWX,0 Œd*++GBGBdTxs Å? wdc+5e 4.>zl ߯=6"LCJ0ČP!ì0 )$G"! `ڰ#e̓Sŷ4gg5?h?1hn10ZO7nʙgx_Q2$('0 xo<M'ǮdZSMU&L17>4k[ޭ>_ދcYPw7#jnxW.-[:O6Yգ^[?1of90`~}u+J]1#S?b1M;؊ӹc[Ӗ_^ض7A`);Pׁ7vaLyW<|ݿ]K?tjـ[,EOj$Ҋ޿Qg.L,-iH4i j7}ݍHu)hGAu\ 8CMDDJ甔Qw@$ ,1HK⍧-"Rk TxxO^fm7~3Yкˏ1ы93goۘo>z>5m1}}VoIA UO!G}ԣB\/΀z] X3s>w_N*q1k!yˁfHI#fQnh#P IDAT}aKy#l& t<\`Qs;Hfǔ0`gɓIJPTZ+Bf~QDK_}wHYB/;4̉ јAAs"[;; va|eWښ]jJ} 5w\s r ;V\hk/?fC`w͝5w2B}ҋng֋CqGrC$؏au|S K/^QyOBk`g;L_4;`媝mXnj߶ "Oɾ{ɟp>-_8דE3?|(. 7ʒE a6dK389cu}ׄ릔m N! ch4 Θ'V:ɰQyn}|v@#}Qp!K>8I5L"YT8~f}\ l/A)3hc𔩺lxWVaTp NpIL6кGЂL&QQ2&3G VC4Q5U0"Ĩm?y<_U'ω'gccUl ٻ횇bA=]By_;ReHٱEwt/ܓn{]\J `rm͸KGs7]˯\V=ل  p C/oAK~UG1m62i@ Ȱ%OVl@t>0p|0 4Bc` (םWsΚ:vܲ`ɧD،TǼ6++dL_w|粋?3%"B JS|_:'rZ|҄]phضI4gl`Loq>r};moX,t&U@Of{!qi^|iԩ̆lLyObwtIi'VT6!fȑ9:v 00-tLt!d46p4Pׁg9C5qfzΌU}^>~M/tuOyKȖ̂%םt_]2BՉe};p}/41_| 4t>C> Y KϿ[<njhWPTBOI]zp7(Fшº݌7xlhfll¦]N=dAqGPJ|L iBǤ=xӬ1ʇ8aۻvu>;D)谅 T:z7zbMK NESvΝCEDSnŖ-ؽ;%Omj6<m>PǑ5t/WQ4g|T#0;?=G>1k;X5@:c=csj&+YuHSvF?12EOOf{]gr]h6F>ѡucq]s)_RWe_r=t(dvlx+Ŋyݷi“!4XU?aީ~%K{×1a!xn?} k/Ymv-6f⫖fݱ⪚Wxš o[k^cU]mZ~m}~eԼ}Ys;:?lteݻٗmŋҋ{u]wk#އ3gVuǴi)ڇ/߲;_\sg+Kߺha;_|V)Qww{gĚ;A3u)כ3:A:bAىLpjh54BM&Ä (0 M^^:4ư1&Ja536QL('GiOɗOH>S,j2Z=S>}sOT/^mVq;X975H̀SRްugLD9Lq5̜m&ONjÜzĭ[^;O8HDDKhLn[҄Nox/N>Sv E6B4z(a??]HKgyrGz'__>,ٸOf×^{VݶMzkڗ6}wk;Q,EwNo\vo 3ص<w^~ Wn Xsg5.?RG߃Z~ǭw}z;{r m<=ݶ{/ =YKn!`o}Uի<.뚗nڴi0ZW_8oɤʕ c |vmNVvB+Ze>#YsͻQU-9\N#=v-U :^o,|@i \~g%e~,%H;ؖTG PFs0HxDITCYwRt[׻mƘY36 ID xo$<%ri'||x^)bxm_}BʔB՝":9~_1XnkՄν;;3gó˟lP:Ќ=mt6'MUNYmN۽|csgFmg0HW?̅o_(HS6ǜ~G_ sO+67믯=_snѹ/5; .*=`A06n[:{_ 3>O4ॿ2}/%HW"PhLC9:l{Vڏ/Z|kW.'ʏk%_;'I\|˶vL@-q.xݷ>qoOXlp~G39zi_pY4tu7?Ă| Fpt=˶vߗwm677O 5_>Oe63娍 --4obv5XkКfTRWZ_}=o69aԳokE},\*4#@{NRkhe]ήBze|Dr804R39h?<<nݍ?}}[忾"=vV̫$P$y8;|X2CJJWoظO_^l l$[Xߏ;vmsin2Mǚh!GVNiiJ*0;ĬS_0z׹כv`*vm-T1``@HAoy9Ϲq tmXx۲W.g9|掂2%7bгDZsXcMf3ː~ /~xܻ^۷prӠvu_zV\H !KEMXz5,.[yܻKu& ر⪚sn/ Sc|֍`SN6}] ? vebݟOi!#PuO=27׏;,O}m?/%Ƚ-0-24̹Eաe"%Z*nƗSM|D0 .;KfgasG.qRT|?03[v[ݞIQt߬gdѬy5Mrm_55Wb] #] Ck0 8CS8LK9io9e۫֝q1f{΃t 0羕}sz&BDGʊgM7yϨy_W=jH7l<갊O/<=Q9f`feXbf{'Ys~իDKKhk) XJ(h=u;V2gcm[WqSu9JϼͬzZyVF jD  q}9n9--|mf\9wx‹=ǫn[ܟ-ǂ|+n[٦}$vٗL1@i=MQ=z?~gKDlNJ?$.~Q⿴kӫ@,-Bu2d?I s01O5>Q?hN yw;:Wh@} DY T٤FGXlHYr:4m;8+534oƍ^Ld\(Ǽ\ ^;NgJO~ GagHHIK?Ӱg%;~}WwYgͽ7]GNH^QcݛGanI5{N[?4p #F]#niٵuOv2 ooGuyy'O5mG*D\&Ҭg7|?y)hH:ڕ@kPVkݵ\=0yaGo/^vǎ ff0"fVH2EY1a81(rDpba[[}+~AG$vm݈Knn[N2sgC{oWm n:iЭY{2`&uC/oVʃ-zIM3z_~mڵ[~V9CMJ3i}m~}jvshSݮ6+@#٤ƀ%_yv~GxZY985Cwo wneoiZ}t }} .5104~&m-ʤ3JVU &-&?ni▦5N}OϞ 2MJ18]jy%{:ҪYTUm=)H7ʽkN:3-jo_qƏny?Goik~zǧ%5ֽ5H$ï]_; n^_ߵ|Ū7̖ax2霋/yV}/y+݅9޺; me}1.[z]r^ڢ'F^~2@cŭuΛpǭҦ]Xq^yޢKі \u[jE罟Ep?ܣͯs ^LNaPs7R8z29=%R;iv=[RFL"1*bhm)_ii+&5f 2P-=JHi0eVՕ֣0 G_dKOq>^ lSrZ řN+N:hN羴Œ.W9Y6&r6k/JG-wp?_(sgm>mZ8mrF&flǛ;g^^>B̜=wơsL&I !ɄzD:#;n1'59so[ޡ+s'=1s=S0 @8 V8w\z^wWO wݺ?g;x#Cܹm0:SGi5@)9WU]3 -'ec{/{_xO{,+;mB_cjїPB*֥ BnwIEeP: !8 BT:SyFYڱ$dt%g2RI!IevHB֕6mԶYϝ!*߾?.tA{#Qv\>z-#&it4;7Ҿs06%g ؅drU]t'z>9M7l^xS天kv_qa}_hQ[}NmMg:6nO2:B@\}kΩAe$!@ ຈ'Xܛz3.^п%g/ix~1B/gWf'qN:kzm:1]ve7&õ~O&nVh-4l7o'-&uۈ蹁zQEnϕ{^6[p Zam/xW^q]Pݝ==A~o0C@GPGg6>ݚ qj}mӄSjm>Uqi=ٜ\|tSCCgŔ4UYEp?J ^/CgSSBN8De4ctQ3@ifta]]W.]I#D-x?FBB*^ˆgYÒ7@uz-uh/)S (⿆n.)J0J@D]Β[pc}I:_q;{Ԟ"s'L0Ec6<UR\&)]u1Awӥa*)qjwȩz/9Zrpxgn1%LKC1c{wja&FT)  `2',b76Sz7QS̭UYwK天/S (sMP7)9}-uY QU4S_wLe5-a ^67HMRz%,+9(oAڏ;jKKC=40^sȩMuЭ^T&<_qG ͓G~qSݷM$>;]5slKy+g IDATX % @1ӭ>E??7cœ#ov XwO;/#6ȥZ?{n ޘ4W.tTZ`VrҫCդ{]%VN;n-?vaE~f(-6͛j{QLaŲ/?ψ" >nKK~O_-CA=M얾u=?>[Pan~b[|R:Sc[Zfn> yؐ-^`)cο䝚b?{z6zyXIS~_cÞǎyn"&W>36 :Ų+_{&SpVz}}E [5]'%g]缅-S:P,)w.H$,4@a1{w,byҩϋNߏX|m+cbn5 wF:Utp5n/vdl] Kчn^bX ʗw޵[v ׾|pbȰ^$vX|P}b}@zm'bX/uww)c϶{b9PV A߽" fX,bX/6'opbX,e<\`O?3j kX,bX*ӨbX,b/bHnX,bX, )j^/eX,bX,J6¿@~뵎bX,exͳ/^o~/bX,e^퉢 bX,b^bX,Ao?wV[,bX,ɀ'ߊ~bX,eatJ\0abX,r덌 /JDDt=GMbX,A Xt?|L^E]x*MEtVE>=X,btAؼn}7yc}~i,bX,z<SD/6?$g|33SDovٌ.])bX,` IpWSX9;9I"c#t:^i|S Hny4e?j`6 ~o?3rJbX,b.rCO9vkYy~Wvk$}\TgقN^n?:6gvweUfצ/dVgY>Ch:%X,bl쳮n9?9lQg8M^-" fqϯ8Ht;%g#A}o3ԟWFNI,bX,(+_\Ozs7u>L\v dqS{aP뎮y<{|I,bX,!K}~,@t%v-:3i+\uΪ6CRcbX,2|P.get.0F.g.8{Srn)'3?bY!abX,2 }>tDŻ )'yfA;9`\{?hӟEqQ!fbX,2l|>A4;]f9a;/vVgz'(fpJFNI,bX,E^ z2GQ:yv֛N^Ob]񆞑SbX,p@zS[[[Ad2L&NSTSSݻo~?u p)Ie:k\p;bX,R< o~֛k3+8㎪5jTEEE2,QRRH$Z,bX,~E+ ux^bX,2"U__Լ^.bX,b>EOž X,bXە)bX,bF E:lwv),bX,0‘~x(>bX,b 963(t馛bX,Kxn>fN>V[,bX:";0tJyq>#IwrM7E5bX,r +؁_d~vM>*z-bX, y;0 |>ԙgdj."Ym̲.9 ?39"9x-yp/c;bx0"/b&|> ++-bX, {؁#Q9}Rt|ψ+Ȋp}? Ay$9Bf&""a,HJQJA뺁V Bc4"bfb 302̆؀1lrGN^ {βrFۋ@pr] )=3d ^K)Yi@ 83+h@+P ->zX֎fmqr/}E"E"$W8%/-85D)a4tTkqWnyhmٓno4y*uvvLwd0lll cjkkvׁds[F^I !buLXVqQʡ];j}♦/=nq̇?fBetH% )PaqRJf @d%%cxI,dA^<CY)!IJ!t鹞9+ZkņFq V:UL6%5]uJj3*4,HH,^gR cp=k3v6iSMmM*99Sg$x}*S>zT"imlIx 9IHJfɆlH)* ucD]D`x|@`#0c(q$+qh$ 1FC_ f-\7\WAx1H!2 31/I_tu ,Yk-D :G_Bh HaS} @B0k@<_P3Tg/BSOoXDOh?(uc9Pt1Z10eу &d߫{lEg~ad@!DB`2 kJRZJYMoYQ 8*~’xhb--mnIxrɀ1NkJJZh0M~cn28FMs(J4غSUiL%Ȥ󜌟*-%*҆( J)VJyfR !@JJJAa9J)2L(9&&`)d1@ÐJ"RC#Dxn̬?Ce8t.ðajv4ܹs P gP3!%+**Ja&jk?2z11vU0;<0M4񈚹& Z|?S=K;|Yy%%CN;e23J Ŵ DZD3& RFJK^tRJc $݀%=Nj\ bVZ:$eY+BBGOkY#u]8h è`1sԅ$5`rw v ҋ.hC C,@XFwP!V !HBf !&# @ПɍHUq.XL㪴 d)3#%t]W)L%2*a]RJQ>Dl!"cLJZi6*:|GHe4k8`, "YG1 'Yq ~[vȭ/ fQV;S{|+H%$7Zkdt#"`3?R]TdJnNMc:TffuIXژȵfA$$FI)p\S0ՐVtNaJfDcAO`PKm5RtK̡&f<sd"ƕRRXJ1/&dNTfTL&#1K!Y&̀`!#BzdɤNTKR8h̀QXc I+1 4 X9;Z ("H|&#!# bcB-c eP -0 AI0!]yzYGl?CBVhe2ƀa2cjB P+S~Zgc L跴f:#g{Bc?|AcmmZװ+#$fc(" 0  _f",]/7׵l d;pP:!J`@6A*0Guݘ(#I&A:2PJAC 87SEbD-y)@ C"1Q$GٳB+"I$I9$GGnWw 2LL$ 5`9 K)pJ3؀$ҋsE2-Mq4)cqcN"]0C?*2~no @iͮ&Ғ纒9t@((6 N % MDC!iI@*e 5q=8" |_wpL,!] R+QJ;ƍAkLkmMe`xdҊ*x%ie2pdDYX0 HMabz ƙH$ |X;jlYy*W/FP~`ZS6$6WĒO;1  2!6:0ϵB;{U~]2:tHD&8A\fT]O:$R+VA(D Ԇ*RY|P " #~I# h SR*e4 `4i&f0H"0@p_@2(]oe8^PZ+Z#@C:u\/LB& d,Q*H`Ng҂1q1*Sm? La "N$LzKBWPkBBCePx6Dp+)ȓp.A!q6F]Ю)?S~B儔NY v5Ӿ+ݒd<~q׶֖=&TH)7fc&*&īX:V$#Qxn&˟#D)7m丮K,oɥ UH ҔRwS#{FִN}PˈF\@ A@P2{/V_2QXHe쥕G~9+cO?K/!k׆E+b޸q#hpttr\^8<,"ݶ<ȟ/=,loQD7`0U*L :e &2C0IRc0"*D.nCiQ!DP UQxV~|#ϛm|,c´Gm.jWCiכw]vMYakfqu+K,;kS#B)*Vzfv4XUI bC fcܦܶ}يrppXgI CU$5& gjxc&s&M=unuKYirnS<٬DZO!xʕ_޸~mǜpýYK}79>vjQJ`RQ8c 1GC;Sq cFUTJl1-c)c#""zdp%'}jUT# )81FcL2DSbE0VzޱX!B6hX#5uGb`(<>ԛc" T& 2hEc j$D`,/\<<(g>=.WyTYmhʳc-Rd29SaL1!9W8P1T=+2w<4w ]#kDc߃ffAΚR9kg&Ր2[@rV8 +Y1XւdT1w}B!=77zDe R% Jd3 8Տ Oۑ$"OJdBИ4I3?~+xգb_,UU[VMs19{W^E9H|G> U, Q% Q U_t&Fbg(IDڶ,,i6MMf HD#TUȀ J@wyz IDATŎl^Kw;W/8q @UYv>:iW~`Θ4@C\*a53. 0RF4)v կ_;GmS{BSJS,l)+׮g?u1hMQ.>( 8C4{ߜ >"Dbb@d۬^z˖,o"ZT^?!G3Ɯ~?kA eQVf9/_:BAU afzдeBU"( "P'Ap9K *I" D4qDx :[)!"g "IrI*9`JɘJ2dQMLD* "ya;Q12(#&IAqJ12C (*ZO|¥ڴikEٯYEɁQdk5]n6J^SU) :1F:>'@seB޽{{kzlY$1./ }bp`VVιQ9ՈJYC _sg}F!`@A%@Q(@4UN_IqLҩ)ɘgDF$L 0x&o(q ) F@Gʚ3.dg[JӞS 9e͉k6mΙmz͠.ːY%;Ab}~솱4&,uZ6H$]C($JȄ[NbL61 Zl^{' ]]9繌`)LM8`$ ME))r껚mpU] LTbtD@)d!"[ f~ꩧ^y啔ݻXs>=====aqcD4)"i3<-"O>Of51*\uǕE?*83!猪mۯW'w~n Y9"ʠD1{4Ls{JMI{KS7N6Tm|\NrmzwLUNHzz bD0"f̕DA 1 uڮg{\lܔJVڮ7Q,%[_40!k >Y}ַurtk[UulʲtEqgo{.]Z,{}ɜ!].KU7~.f˽,iYfcK|V;[n6-$m2x  C닇)z; CY{swuta05 }Q N~Doҋ p"3"ZD̨FUTR 9 g@o4{_o;>zՇb2!ɺ,TlNCL3j!d˜}{|z7o 9yWaq,˲,(rYz\t2T%RӆfHCHQ2ȴ{B"bv{̉fD%6xC AS'p|m}J{r Bf>mRϊŬ2BhCvޓ-Q4ò (Eiרm\|sg#jBNd,f4W &|z|~;釶mqNVk(2缿la!"RjqK߳J.K@[O}z1733qafcmRnv 3+bLH!ι,]ӳ5\p9*Y,kRls)|}حW ޝm?~ o/׮&;7o%%jD8'3z5" ccALFrTNNl%bI?`ʁkFtVϮ^SO}~p3o6&N@@4f7ÐWc=6{$Ùk'ymgL f3Wi .ml)#3qiANVSald&$vss;xO~2XQR "bث"29˦;l ,1d[_ kq;{ TBFbl`RIg(%DPe̊!P~V[@&QjAK0ԅ$1[X/Ģ8HQT$t?Y&S9<4@d%g Qd$U%DO…C84DǠ%Q0 c;t[%D2Ĉ)iJEc}Qfu$C{UQbk-x˲l6MZk ˲DZ,K&I1֧ׯ_ogGK h ?Qb&d@41l`*wc`0vvuܮOcFf;4]/=.\5dΜ:;Q6~TS) 10 }׷е)54eFޝ[oݻ}Zm֧m @\fHJ$C YhDK,ޢ<< NǤ9YsTEsMdF @qf8mp=~+0ԞNơmA)3:gqSr쬳dM9v}N1Jh/=rn~rեr8dɷ 9#"L<ڶ}嗟y׮:=>>>cLaGk7@cJIDB9dcLQ W͢L:l5o͓O>br [,$l!,)jrHΓ,)j̬Y$Fc|ISxa߱H98U=Sa98AcB͊,ҏa)rJ]ל(Z닺Z/$Cuف*H"Dy }?YY1)SJm7v>[X{Zp8۶wr7Mߤ0F0tz G]‘)'UtLl@J YFFudއƹ$90m'׮b}|Os, Ghr]7C"k.zypz/d=ϯVc!0ĥc1I1EJl¨Cn/șo\{ʕ+E*Vxˤh}h 10^:9>^.k|VJW68M`fS` Ry\,0䪴=kP6+@\Vc?4 ΑP C @Ǿd͡BJ AQ3Qa4kq9Vɗ,.~zT&cVfeabdOQsm,ܞ0 /SO=믓z#qΩj)|Z!ȎҨH1*!h0[CdlUUinX"k_=әDo?"cdcq(#1&HQh{ꫧ:UE%g;S:4fW1.oO\sEY!qut(b $s.0 qϯSow~?P4.t|||1Ic1Z;`3T0c;hFhhjb: HM0Ʀude%ә/\_I1G. .\{׿~癛7oUQUhV0 ~옭I^Ntu/' Ԕ!kǐ[; "2#8+K.=zE9+Ub+ׯ߼:,qu{oݏi۴](KfuC%0&DȌ1?xW_'>beLQ0&ș1%k}Mmࣟ !h1\ F@#̌H M άYZ~YAP@RPd!BDd96.WŶ>܍v6ZS1Dr^r J%yi 3$0FYk,5j3"]ۿ q$ɜUsT ߘ 1QdTlY>ơA2@"4q3k<g$ȓ*ۈoG|޶:~܋O?¼18cma6c*OtFC0Y皮 $Ko1Y@|Y{{/5/&tR9@%@$3[koٞ Ͻs{[Ίe]0k $e!pa+n9Ywtm췿/?}kFhI)tٛWǎJ`Ir Qh!_OrV^Us!E@U#qgcUP(=aF0nO pI|9"e~"f@HJĠsw[2dff/ WV^͊Dι[n3OwRJq˪r,"˃\"4tn^{f]ח.?(|:w{/ XX@g\ ݰjӶ=9W]} sv &LDA&DEcB  2 5_~}ZUp9ߔhxtX巿{<1lsͦ,~4ݻ]F !<[>BДa o?|??җGޫ˾,M)ΫH+HDڷݲd)% ;_W\ڛ?_~AaM *aC{޸sɦ=9ws?/}q'Ivr7M0e@$f$1<Q˯KYjDQ#Z( 5D-pҖp>(P뺮 Ƿ7u_. WW/&3P1)dTM)HDXCH)9M[͝{Wm{9 }<iCdx)#s7vc&& fbQ"?Çb^B6߄GLv?)o .l=W[nYRJuqZkEdeLe]EAD88 C!,6M(eYJv b!Ʉ cUW_%".E/^&3S?csrK/ݺ~p~oǾ#ؘQP$)};0Sd ².˺"clU Y޸UXwE$<ֿcgGʍPK IDAT71o.YUe:iONfUU9#tl&l; Oh3G dfClQ*m[cKwjv|tn.a9s>Ix09秞z駟蜋a!rF!5!ˆK[uU9]uX\x r{_>v C'*Yugw&Cl+f|ԅtztrݶ)*(98ey̘s6X'VpHjI<}ӿyrڕWVG׭yfs| fBNcz[;1%tEUۂ:6h JttDQHXU4Ңp\wC48P:r-RUʗa"v.M1z)RX>nWðwqa@EXYU׵1&xiOw;fb Wb[Aa̽(ޭIb4!kjmok?C{f9w-XC./bfCBH*DU~!i7M7mA Ldɀpu՗~GO>h4; o >R;&l%Ldz9 (?|?yI!cڶOONV01`딳NOEaK)9s!`ٰY̫T8z/9WŇ~43=O s~z l躓޼8o{kTFB&g }U|YU齟Ԁ@dX _ҽ۷{ L>wqxv%K6iwB-R߶7H!,XEU8XC(@'tZ3fEI5ϸsV!X K64w~ˏ}~yJ*%ǟn}0q'MUF(q,[.feQ!٬`?ok|׏} h{e "MFJ D68 c K)-7TY e^q 1b֜6  Ch,!@I-^j$nݼ{T/.='gee;I N6b&W- !jRV~+vӞnmR T⢘bИFbdUJBabrՅlCv+g[q)x&OdHw>!LcC̄/m O;Cޙ~TT$0VRab1!,I_.+%W\|F$A?Ѵ>Bsķ|^|ڵkf꺻«blO9{Uiv;5 1="*:1Ry+1 8Ȁι~޼ys\x@'@;moܹC̻:jH#Lt^B`THtΖ~Qz Xg׎}Y.SJh ϓusyu+݇ S6ciS8#1ԕg6c EBI25)uF5o dTel m^?ѿ.tW^yA5f@n2lC4لaArH 2 2 11TB*ϊyŚ0t0/.,f$`cd 콉b0,Y2~8ս۾m=W)8)~N PYHBڪp # ĮivWY8!R.MUDsCw6E~! iPD2f 9M$MQ#yĒW5ug-c1sn[֗ j-#8O$&B,Kb1簜U Dh }r AdJϼ8\`CI$}Gg?{9BQcnxvarއj%"9T{dsN)MF]E]9Sc퐊wKi@iO9?O0#;= L톮Grj6X屭c]ujPUM'|# rXgZ'obZ:'ۍMV>>_rg_~@|nr tBDY u-Q"Y)"<t8Yq$ }߃¬H׏'oz~~FBԏ&t< 3//7Er1Es NRJt Uj9.7{綶7C_W4 ɫ ex罏__/o܈GTc2`H@rv=gh}L| "p #Uh=-bL!U!w!"2b$`Jaat"̋۰..3b4pHIU(ȦT Y`dB&s E81Kٞ;кBRBt~-l@ow˒~VP$ͻ~ 8[-CX5H2)(i=!LZHPعfՉ2H6?Ox#j:5mKXV;=:^,ι~_^^N<3MUd*%IɚTVO%h'?}/f0 U?j;1=ײF >nsVO?_7 xOsb ={W4V\1ES:Ëѳsɹ;wNsq"r_=/+ R5* PQrC&fԧ\U0r1xS!VowNϖd<^̱̀=8~\|pR8@妙R\py@ʓM;Ppsyxyi]տݾuSU0Revw(&yoeDͤ١qdUFepC:E)Bl|;gJ1) K){&DRO~Ǐ{O==&2"}V.U4R }2DBd?þ"aD>ZΏ+Rc ,:\{_!8T1ĺ.!磯b]㘰hc(RunO\<!J$zJˍ|}p0*h&L<:$'b=@5`^02T329"ѳ/yyzth7O^)S fW$ xR ũb?ɻ O*2M  arxi=/vYwnwo\@#5E=;Zy_d[0EAʇJk[0Q pX6/bsOlq>HffTg !d8Z?u5v1 SCn{XmW^ٯYQªUU͒bf`_m6|u@JbC*Ɍ,nY5 ѡMlh'NMa3{ѱ!1(*I{6b=3cĈp 4Y9ld%!"-@v(I4uUjyCIq]^fk~ZWMUv8c&@"did1{쵻w٬L5c#!WU{ѣG=RtQFdbHy01 ׄ%0SR֋~n":#C%R'VYUш͋Tsc&H]74nMH1Xkn͎=K8T1BRT3F&9;`Fx_şxΚvDy݃TI>QƜnsӊf蓏/8I<:I'XUa?]׭VqvnƏ޽b= (Ed9ۋ>J`'[+\/*g:5S"B#Q >CvC]׎X葈BD voobD`pushsz)eโt- ߜ_ `cfL\RNN@f ] >@ |P2P$FBFBWEDBqYϞ<6)UU%Uܘ 0dw 0߁ /g?+9_#8 T{bpmZTrqx6L !Sc۶η.:o~?~]JJx|^^M@I0 Z_-F>I1c>|*nb6TːƔRG2sP\9ffB϶^lLi;f\L>ycղͬ}xݿвUa]l6,`E ԁ!UUp]y/?>yZp 1 <cS463E:(ww\ ̆T8e#deEMN0;!OO"_v% F̞(kF-9u$IILFcXLc4~ibX89>T!Q7]mCGb4 q׫U5~þvժYnz?{~zHz2wnNNoO%9gf&Uik85ii8hkf΅nHO.w~#q_ݺuw0 "M_>c2ŰytgEG*MD@!"#U5OBQa.b!5_!R9:Zn=8*4owX2}=V΢7`Y\G|c4DyAS6>0>^9;;gխŲ9 Q̤fdD`3GZ|e1vo"; 8όf_ehYP>jeś6129 b]D.:?"Ra2B2&<{ϒ|> FCӇ珪%@̐h}Iq=9Rr~}ݮu4]םo0fsΒ.PUvtttZ:vr狪Z4ճbZqM3[?y$"F|~Ig7̼z < ԃ;e/c"~?z>IBs4zPt|x[1?ۿD*:u"K*蜨:Ev^ pn63":==m1.mjmZ %CREdf:<9b4N 9bȎѴ2b%9iET d4!]e j9 cI9j=_7*kFDXXd"*0B]^<^/GM)v7c3OW瞺+9|Z"Ϟ=yM?wn8YWG'u;և ݈LJ@LPX3*qdEmSݽsr݇o.Prw?sߌt20P(Cv woͽyc@4d22"N@lcQ`G" T muv~4!:]WHm5s"ARB^J֋'b&-wώ sԌm۬_}W=;բin2@U0<""D6<; w Cp1 Nd14u<p|<5?M>w[o23:f hsY 3GSMHX-2|$| ]?B-KA%Xr,ǏWo~۳C_$`W^cJO>qBhW7}zy<@`Ep`v[ɩ.0<=99Zw)Pˣzjez>S&ѳgy/̏_!P"" 胒Wrnw_Yzf('2)ddRAuTT!*;:2uf(9:5V H'|$je,rݾjD4_.Jιjy=KώֳWu}R9מnupxyL9~[M/~'Ƈ<\.ðADh\\\|Go42Aa 8L9Ghf0D k-!`@6$Cf]TW)"& e 0Ɂ1a94 XJI)4Z׫!LGR`h fJhIXDdVb9u,ʹ1Au۴m;kCC!8@ERF)&N (zXn~o/9@rqmc }΢ 0PM'F@U/n.ge_-d1!79+dkJEi A02RMjq꺮إvUUmlv~tr43}q*IՁ!@p'Xf|srw \H0bfTBtbBΖٔĈbZuʝd#v{Wt'<z5T-<czȯ]3 *gygzw>X׾Di OA5SDMTur#4@5D" !bEdLv`/C s=47x=ad+vcF)*1 E>> 8t"̊f0EP@! MjflFf*R!SV̰K9# TUݶm[Vxr%Dc(N[fi5[8>ѧ&{#dgDHѫ@I~ O]@[eF5_<V L3 E ""9*!z ٶOH\hEFraq ٮ,hʑ+/h)%<)s#fu@X,#ͼxvbVrƮb`M[MEv(K1ݓgt*{3q6M>ϯ/zZefyBD*K)8w~"B윛'LJ{ \p>vMgV*R1󘇦m!W7^>[_oECzAĶP01`4iLF pЁ3+EU_ˡF!MfȠf*(\P"p@aBbvH'fCŊcC4bUdR!bHhSF#"vyt`Ø=bp),i#xmnX<*Uۺ.)j 5d1rLՄقB(c&ZR2"`X,}[7.ԫl{Ajh`p_ٓV P634$ A,*!xb缋S8f(`}2q6C]7Mgصr%C= ^PN08yJ̼w}yժ1mcRaL-s @x=9dwFr+%R R!5-sŦY;$ɱ=;pWIihqy2#!S(^h) D=eaտa`#N{PcΡJ~6 #- {e,(#23ќ9bTQ P$Uu'GO?]?Z?r98ϸ_}\SÇc?8j1MS3"+L}Ą``2R!dfjap )~cnDբu1OvL_]huba5 <#(2&u':l8T2F!:EH T'C✫fw v1/yf9crcNԗk ~ߛ8><{"!g ɡ#ML?]9F}#;0a*YguS<@D=;3RݽC$59nHf6yDJ6@D$80%byWU!lyֳE9ݐweJI{cT;7n?byw2@kK)N"9OM*9 (xUmSCJ1z_͇j=KTKcmME% GfVh>\xcS2w]iW)v$NzV# 6yO6=@鰻O 2@bp,RB46EC*6("OU݋~#_*_ f~=9%l0鍡G`3CR򘺮#cJm u]S(Le]c̾ mKZ%SMH "Ȭmu]Hߨz{'[Jq#hT( đ#BDX)x9I{_ǀ*~%&0w4t>;yr\3L QUa_{7Ģ)i(B8PVc0" ;!JE6"OȦ숽!;1{0bV!Uuƙ'-f#//Rrq3 쫶 u3߮./wϞ=l6cߗlJkaO+3c=;I2Stve1XlE̚ٲj桙7<*ĚP8x]JQB" UM8D@,TW!ʁJ11'RYM`ѻ~{aRKff+Ms{ ЈzTU#$Ct%i,yi^^l/SJ};XQGR<o@]'Ǒ*& J "cYEʘRJiz&i0E̓S+s$"u{b9ksd" ãG}UUQRt]MefUl!82sl\gAD4ĩI%OfO4,9!!X׹n?Cp&NƁ~xkɓG}ϒ&BvJi4üjQ1,̜$αU|:>M}[o4 !3"h o{\>@F?uL4( $ˬ(!;Sھʋ* '˾OY7I7[G]ӧ("!" Kq!I: l߶@ssW~mt P٠$dYaA6d d1$@RD`b6ȌZ x4Q@)+b:+B[);DL6 5&3M+L9Am)i bԻ9ä IDAT1FDf2HH1FT bJT$DHDB4F\J!D}L z}j_l8scH)I c9VPY ERty K`8FoBRۮ51I #aE7MS׵3|m]@_pƁ G!]v> qlE1bRŔR!pFEd5]6I|"1ˇ&}]י̕ڷ~^]" Kh7X@G֚y7/)fy8ZJ;"2HQ5}j #kș/lٰYȐTaPv;{xym馔XhJ̑l:<9HdlS%BPMLhvCDCX9Yx:^o7]#̯wmk+UUP8641ƈP:Dt67q)2fưqd )ITba#A9cIQk]M]L7k_w>_kNV+I (xa >fex>/KFc$֖0͸(,[o hT:cCw]Jgِ BLn>$w 9c*|` P&`ˀ q3uUE5*FEHJk{$S 4*7wM v5,Q$AV]]DæQAVr|n黮ʝ"j<$]DlV5*3"D8! `0B {ENȦ(ˢ(!ý{NB_dM @BT P? " 1@Bl6/z)nBBXkSb#} ]Dc@0y=kW7DR׷gE q(b< QDBc2$EVHTI?XdPP m{N#EAS C\4?݋v0* vd (/1&٧ۛG 5@pF5YS!u]X%Pmóh4%xB$cˆ6}@JF%"DD $RTɪ]nת 10'˲7~#"Ր4 2 $Lv0LS؁$!h6FX#~aQ5nfM(ڮo~wwvn~>_Qt8 AH4/_|qZb$`mnUP_.{Qh6` :BT=Ц%8}{ABD&$Hf(Ec#"X523i{Deo6r~>Z#T%` MÙi:R{UaE 00|A&A@kh|"x "[x<ϟ}7R^ح &Wac#g&k9c,I}hTYBIߜ̧_n#BdH228̀ƤDlPC"!iL)%M)ƘO1&9Ϣ&CчfYys?' QQ @eZURI()J8Q edՔRNDCI}zUChQRIz$[{ M{`/6m{: }I;_G=_}{ 'oVD(&(RT 3*"3Ә*|&C$D,Y$3JA']VN'sE (H_ Zp^S ژF!&4DOޮ}ӷq, d ܣV|2(%>#Zg) 9k]AO⠷FUd 5&QMQaFț)"r=+MĨml =L{X"$l$ljӿ4f@U)vH!_nϟxeySdE9znϊ<("*B*@0bQHF7b<-DUBRR7o*w]۶vCQ$2&I5UQ:yfѲQ> 4h8wIZ1q%j})Q*kO ?P9MUH) Z$&hWRp/~2T@D`#SEIp4guh/?Ʊw[ jEnO>?uq|~?Qҫ#|DHC }klz;oި!ү,2u=*c| ̬$aYC~Ԩ TAdR$[e826%}Rd@TH)w5؇-2L~gca5ۭY PTIQ(۶M!,ˊ*bMEV$#3)CϤ"4wYkCCGBЉDcCӓnyYI1LJl 0YNZǡm-plB9'7['Dq =n2áEd#P5R }1D(]]U"GD0(&ч؏r> C3~-yMC dI 0%T5D 1D4EÈ$FI!>cUP vƅ|AE! (ddȌ9;;Sն@k#%#: j@$}"28 1Q @ qe^@wݮk`-8l߻9 87mD >4H< vG Vw]]|yyR cHT8b6 jWHΖhpSʟ?z߬D-\^qOXFH;Y68^.=}da)}BM@j q P'x>̊jJA:>CpDH$lʹ~|qbF87UCh6|GԶ_?}qbљ?yVT%u'Cbe3̒PBc\o7MT|m:o6̯Opd1Ykٰˎdt:;,SBE @̌d S"{%@Lh4:Ye֛6C}#}%&*)%P&LbȠ1UOA`IRgKp'F%Fff>Bq:_&mWU[w%'}*Tރ2(E.zӧ7]]WU[6F{>tmb2QE˗/|t:6c۶7ݮU>FEX%svve!\U13JR]ץ!6*$ uF#ZA:}ꛮ-So{f'Y{mM'3D,lXל͛Ѣ(*}k}"@'DFAVclE vSJ(DFLB&[}.Fw>nS6u۷)D/2˔0?&ˋgW=zhUGՐ$mÌXҔ< !0cL)@$I M}Iط X0 #6n MPx:zu!X(kAC-Eou/ZZȳ,Cb:UUTB@UQD PS>$]51\߭: nr<7_^9p Ȫr|zlchBDf5g"#!ԯt8#) `Yg#ҾKCOEVdƂw1(dQqt4%mTRFrDlUUc:HrNއկ~_//..:rgsg2kEBQMG7WWmLqkWA {ď-4%XmSҪUEE98E $@D ()j$}'QDDIA$"R9KfP&勋Y5~Җ{ķ]プCjߖJR-Ƴw'O|NΪbhyr4G1ojnS9=zѓi>i>ĬJK5BY!3fT !xwg/3g&erg0ޛJi_oƔXUU yQutr1xtt:cJ)rb d\n] ")"VA-S"fm}u!$c"Fcf\Qna(_MBEU6RDY9( @`m(@HTM<bUP\7TtD2^N.𫾞FMƿ~}\zahA,r\n/_NŔcڬefY9n>}ɬz#E0y^=1fH9_ B=BoRpX5tP a"TJj71ֻ]S$Hu߶e7`3U6-G51jm|O滺}oj :0 `m]5sY_mB,&1" )E|7KeH{t~W77k7" YkA4lTɃ^O]}O~??b4}v}8c䛮g_sI! C(YYm k!jWfq+h(1adIβ5Ieh$efRRF%dbez]DDM>rNѲ*QḰ"s}IMː|\^jdLBxP)%W9$UR]tE鲂b)FAvzK)%MQS )nݷW׷|w]3pX#˲6{ g8fGDI$ă|RI$&M0% , @L.0un"CZ̲1((>1@z}aQdW6 )"CM'ݯ>OV~Sh] |j8ljvh2H錀3DDJ6Y^ (:#HԘ )X"I%""зm߷ٳxZM,wdfYǾʋ9P0Ѩpt:FO/ޮwb^DCHĎ<"?[_eo)hwz\x62/o85QaT2(!E$WsYe2\F8yߕGeqZ$ J|d."8l C wf7+iZo:VI*:/fhl$yB bh )XMO>mf`y^#DB2EQ!h4 ?gHG02#bTL!XrC`0 D7; (h)DM34CEUQ33a8 U|}ϡޯߐ-}R@"Y5%Vw_yn24]ߖECusm'o,xcEQV;~նf.4M(rtrb̲ iTeh&o2*(q5@T@TRLb -"aD0gDŽ[7uQ ]c4yQThhٮ"?>>ʹ Aʙ"I0F7KP%@ (}~ϣ2/ U)C ]vns%DM{^V_\]mb0Wlz[G]zmQZ0$k]\E>+,a 1VH1}g9Lf!!qlɅ,=$fZbg5C(Ah< |T+b4җnNԷ̚| Q!&I)%D~YN~4*>()*!߱pfTSfraQliL ?g4f]6uuɣ'oeEvgϞ]^^N̜E<"h'*1$&"Ed;3.QHLm%9jH" Z$A$Fz'LA ^Ї@Ċ2g!82Tޭ7*sÁ?$UK1@E`rL#oG/٫Wl+h4Fyc }ZWվmf&m[zzv4F{:*?O+k UWqlD%D l|߅.]lTI۞Ϣn پ/,?LzsZmwyQ,O'''6""(B, Mڦh6FͶ6N}߭2}A{P8oC1!tz?xT~cKAeX]ݬw&eY׷÷]EЌhmb2'*sq:1O?~E](DP%ޝ/ M)8j@BBP1!l2&O>IgzW+k:|4x[d/^v]˲,ǣgg'gwt:؟+AUQT]3߭ZV>U`vl2-r6% fKFBxr6~muly1oVSQl6~|1{ t aߞ?}qlV:Z'6i^P8 e6&7& }q!mbRB$٢),LݒA 7j-ؗ{yEZ8įeY20 IDAT!2.D1c |!E; D3~<@((#pe#"xlskLwwwwnruהUL6Mb7屳UUl:}ۭn.//]ȠDj^o.LK7dQ$SQxa>yիϿf"uA@@< =99zrz}D ;/bRb٠ ڦ]]7*+ffK}]ݭwwn-l9/on֭$rFs]qӢW_ſА^ 0IJ$GZ?ާf5v_wjΝ}g㉳Gf$Уfmf}Yg6tZ% "tv @FҤ,,$)Z;j2|vɲH5'|_eN{m&i\Q6UJtqt\J5aqUcr<;m}Jʋl:$nW(0ğV߇|4-vAC:%c8Y/'eB0ɘ7?yttniQftmSUˣx%sgQfUhE÷O]ݍ S.Nm! L&l%)&U3k2Lq٣`y4DPd2S,& I1f<_7h-mzy^>x|y4E&YbZc1&(t*R1OyUvnBڮEMGC2MƒuԄ@_3 E*1pYV)jLu XvQ#cc߂k}2IHhE2e4Mݶme0GMSg}b$]9F=.&xT |E(c4Ƽy׏x}p }V"Q` 4X%~ٳOe?uf/Qu:.i lylM1̹~%Dx6? WHߞ,NivM*+?=xo;,. DQwy=8/?_b6U~ЅG$ffY)lvMYTR2U޿|G每&nCapO!xS{1( ȨdlVZvfv\sp_ܬ?۫f};]z?:Y7/?tݟ]ztvrzz}}'"fL&<^NǓQحdZVh2Y/I[`T\Iz%`KI|;M fURU'i_Wj_:=/|9geJDGK$`]G$d\LớeQ !ݭ7(׻BRۻbqzcx?~\6 1$ ߸&Ӫ* ʌ`(29w}[̸n1:'>{t݄";;;;;9,$STC}f2 zyzͳd4Lfh4 1.3v2ozo.ʯv YbUٳg̜sZ+Ww6/VݳO>Oo_]Ɠ?Ƀw~IUn./n%nv;峧O,c1dmF"qFvFe1|_]ݤQ(~oW@ 1ERYwYn(y_oLMqeYlƀϧ߭plZJ",si :˚/7H*P w}z0$i޻{)˝qv> m4|<)QկnzL&8ϳ0j >bRak,FepIADdTFuU6t Yhb4{yu}伫4}|z*',pr]?>xу' x,@3b-#cƣU>3È&d_nOo^TU.3\ SdHQ-[d<6M߶$wjW7>mIeTVb{~.#2!B+5Ǜ>N&թfzYVNM3UT  bT1{H2]f rDMIqoʂrwCPriÇUa[Șv׶*THr\,fd*ﻮlwHc!}О *$`B] ph3c&y$?郓kvpAA6QҶMOl<Fصz~|Ue6dz|{! aᯞ=6Ж CmIUԫUb2TU۾!Y*38|$%6 O?e7폏Oo[OQb&1Odq]|:#dRDT4yfnHy5kz'?.o>wO/UH>,g3i1g?}x4{S?N7חq\N|5Of_oXt6_}nֻ,fӣC оnЛ!E - # jf^m&IJ27Xs~Ძ?g?{۲L,"ָ3|nX$Djll~0`?pjVKj)U73i ~Y"FU fE|sb}+;s9:9<<zm6F!M*?cDm0\\^+e,B8RֻۛO?B2:P8?&d"j )_7d#Ddi*3M$IĶmwIkҐg_C4J8=<;t4vf]3bPJw~Hڻâ(vE۞>ZZ@dw9ᙉj9kD!Xr&$@ ",IXQ@J[gPgf1"0dO8̛qSo6~_ea1Ãr)" bC`(+w~BmW ~|_e&^NL_>)wejPeRʺn Jm9DHQ]{cs]J#`*꒡Ȑo./Q(CFf]ײb]12DAzuV'2PIP}_k]7l6 ]I4?=+J'+GD)_۪lyK'(ꫯ-VawG=@PDDB Be\$~'D@$)ck/w[( _9;::ڦNy*FHT.tYz>S ĻĮTLuǴمȠ-U9smAP j@$ ՕpyHƑNZEAi lZ$.ęA0VulH ٔp~CNtDfeY/f7noo|})|t`:\gV,A 9'=>=i:9==ۿCLq\ΚY=~=/{/ˏ?ZOk9 /HPbx!c(m%D$grcx_K>UQNAjhx`o|ۭ `*eRhs1vhgy3/}U*̐2*}X[rI PILgniR1ƐS9Gf!IJ%޼~bЙ! QJVuJ뛙Z`XVU>~ Y}zr4q6bJYwG׷W! ݼ'_v p$,@~߮B[Lܳt]'Ǿ}yyQ%#kRB\z_VUۺXjVV!0o 9 N`6)lw8EqxxXWuY$#d^-}{XkONN^}UkSG멬1gBm[_[_zi4"S~=|מ>XZR8]- !'" ӓ`ZI)< v}cWYS.b!x'z#s89>xJS$DT:Lx7sfQz?Kl]Ηv5j6eZiN%+/B8 9gaaUC7JO|UxJwr"333!22;m.//tvތ'8=zi[Zk)a6$2Ytqis'©ocLAk̠A)c$bVU@( KKHTX }ƇQ'OCco]3IE8Id&Ԉ*Ƙb?jfJyQML +ΚiҤ,9!Y$=:rssv/zPjt6_ٝ "jiW^ye1$7/Пۣ#`eY2+\u5zniuttTW}m{PNܷ]N}os>\|_!B )w ARHc?{uy۱0˜ p0<{m?oo>}v !HN)ECw`))eZ˫ONPWâ{wOY6kGfRZOYO#WI H^Iql&FcN])2`noc9w9C%΃noo020 i n9p\<{ÇR0 cs¨${cy`93e d`vy]U[ăij?[mBdP"HC!i Ph+[.4"q S08W OXND 'T&&T0Nζv~qq;ӥ-K7ohf.۞ռ* +Ce`˯?bl IDATaF$.1a9$fnѬT3% Y@4,ܚ"dMQrF3h4؝?}Z{R9o4#|xi *]yj}N#}`:::XeQ7ggf v`D+&/cѐ#ZLdlf$9ycR0Zm}i9n.`-|Uc)xzxsZ0 mn7o~爴\.άwE63>[4Mg$ je::= 4GMKMYJP5uێ1.lMq S'K}Lv˜D)bgg֨cCIY5X$cOώYA>6<h*20"RFhVgLJi>{wX )6pefޮ7\5;g=pЈ!c Ԩ5#U/Ap~ǟA")MZj8=fc.`9:\jsN]oR+YBa9d&PFB()Z+d!F$+֚(S2B:rQc鴅bѸX!D*)E.]9W|ش:vgWcHqewy_kE9"cLkD'cg:p|r&2dZ D^dA0⦝Q{o~sݵzJ-)欄!gj*),zhIVM!r*8ZPK3dmw7|Zk5LPMi7N2Qnh+Tڙvk;$ѣOOOR[8ngobG'GX\f^Uzv =Q!79`uKįGL&2wYީT`8}J)R Zr:moo[7ߜi c[o:US;㺮˜4#g2 pO(g" 87[MiD$1*H%6!(@oS 4fQW< !ehN)41ya(s|}" "ABۇey߁~(xl []~GSxrtC9;7Nm74HW33AKc||r\.ch $q`!7a(g%sN<= Ȓa6 (&`e?eJ\(!V)en8TXem6cmsc*כm@+7PJ]Cb}ݣ#:k 2҆{n1a]V2s=kkIIkJ_or9yvFiPi D4/'!E: eaUS)FDĐL8w]7ɽgz o2='M2[̟^6__zV6!3zUecr&9爨YRu0EaϪf5W3~}Guf{n9CoUSwI<?" ce-؏IKg4!gFhf]7޻)*ъ10ƐcNCXVeb. |WSY)6hmQMBL9˲e40Ʀca2=%b HkEFl$笉@H^7}ʃR(wpV;EAd @ "jȨGKS߭ sJ) MܷÏەq c02g@ ՘)JCW(%{3MS/=57XdYzWUլgihUYè#o!sZOFD .̚hV봖CJF8}/ڽc쐓W Xp9&dш0hpsJ8epvaHjMڍAh!va^cE8ofKԛ=q}trRU)%4 y3+ټ6Ƥ$s^-K_pK]7_2@MXP[_yEbuc{onnodϱsJ$Rkt]7㳏VqeQ(b8>>nnf~z5rǘFRF$<(! 3}aVh<>\ agUQU1y~NCI52W~vzx6. `~}΁6S (JDX!r y̨&e,鼴֊8˺H)Sͪ}RaSqKk ak1(Y2k"W8*[!8>(B"A9vMvYҘx~qyRRL)CM,Kc1&hNcC-be"wXvUU(HQcQR8 Tϯf齳ryttZNC)O J "Kø\679T %ȫ_RAQ~8Xg&[(82}}T^VrJ[!j B%?z8 >Z&6 LG;FB̂f~iS)> iXf7/.2g룓|SH8J6CH0ƜA;ijg 4_͖u\7~ߧC+"@`QH4ICq,q?xnVq@2Œ"d.j^9'YowVnj(Λ́ )XRֹ,?|~vslU33kܢ)3g"(B$;g `~stKQCQކ~! *3vݍazuѻjVR9Q]7!`q0Eݎ9;HZ䗨98O̪_J ] rrJ1&A@QD4pZ(3(rPXާrεw"BXXH%QDo Di*IڛCnIctrsjwFQ PwWXwn}mn4$/l:a\777J ,g1dWWt7#1$dIǘf}v;0HcvoFh(ڮCr)z?ëۛ^p^60]75~/BԎ 1WT6f1POONJ4#03}J9["-XMe dجqvXc\oo/..or}uy=|pۭԏSYD*3f9A!Dy+̚vtaK -hs$,΍rgKF("O\aCCFD2aBλ{>vCDd6~VVcH! izք15j{ŭf31 h _B`GϞƃlF /! ®1'OIq0(0($NgD9IJ (C1>&^o6GEUgr:?}J66ԯk`Z5Hgc {10sq]0 ]ߣRk]ln;?8tUQ@#A i" Џ7G}|Q~;< A@ c,.vrq mQ2ցҮ6|xq}uv|T5 $"@E2X_W-P-"q/ a&e;1"$w®ݣ>!'XE~׆R9ɬ &aDlw7q ,+q[_y$D(1L deNz@Y?"*)3 0RVhL{JnBTMf?w>~/nn#jkyctu##1ʌ"2Hld4jR&~ dB8XN)DsJ5EXc ! ("$|G' #8B0K_8c sqdOONNNQ[~HR9imLtVR\-jQXvJBb>+ӄ9)EfDhwi]1Fkb6 wޕZSkti013<ˊ_M * !HX$'ɓ''CQzux9f1U[;'};9::zo|do=z;?zw^}KuoRz]450(VHLӢwah]>~|ɐƐXK @M)vb4JGϞ~(ο?j~'PWFRC^$13T@)Qq gO|I QÃ{nˋ5ko.Dt0_,VG'hz|QqNYDQR%)2˜PXd(F݌נ.*ھ(o}Y""("D!p$E3* D(Qxg%R9'.q h9fBȐrJͳd" 6#+wm>z.H٭h|,1&@ZEUTӴJ` cgͺz>3R_jM"vjзnfEBTHrB~(G~~Ҽj>Y&FZK03gf"b&"b( }JlQAʜ2""vaUUHf8UsN1ƮaEs,`kf\JYQQ;ՑLq !b3t) d`feuh3glV+f[6uQ7B# ƨ5$9#hI6gO_DI#YEN+!fB9m ?J )2Z!sƾw}=[;cHlnwwuSMf>s]^)9 JiYD@k=WQ`Q^9OΛ3'J>%:8Mx#l#a'i.7_y Cdi-13t:P^6O!bLI x3" d$H&%&)|9A&C$ ߟfnI-gnG>IeZ DrF EdJ?ιOX'qED)F/>|8*~?H ~LDX>KZ@HϹ.]wvV71Fd24Uk}Un6WJua2-_ַ\΋?,r]To~`D:w]M/?EBEE&0DAA , Hּ_<}V3 Hι߷1{!\\]=95Gi+QhKjLS^˶xws/%A0O>xCo}75mz90 Nͬ,KU0)닺~9 @Nyi3@xճGrmG={&v}f3"O;3rΜ+b95n%P׶B/qyq}yR+4:`(}t7׷~xxt~PJk†qR8c$sJJ}{(upp\.7d!s LL 93"hS$bHU#۠$29eDEBVDDMӴuc߶`e"#1ڶnb(ibɿosRT4Iҁt,dTdb,0dGbR$p{{{ss~$!WgQxyxd0(Q R Yӻ'J/9(,9%HѐB]}Kc1㐆_atHLCBtG|vube3)Xa4#DDtv̕lU!aA9M @4'8iƸ>}v7|.xzvrn4J;W ?z Ҟݯ%ÓB(sQ/0AeE̙9 3 @LPróGgᆱ-:ᓏjg3}Lq߆635?}H ckAUu~mgm[j~%A q5TUXmi4sbBFN)l&nP!8'qn>_%x\¡ Nzik󳅮%* YRfPáR)rI+ ļzrхQqړ2)eڶ|}QUq#L)%+|朅81lze~=ٸ.aUF#"b,_Js?$ab?d6ͪ~owĹ|}*`ȲnpptRϚmsmMxEYI4)8r}Ej^)^y5EFiRyLI p"R'04s.b{s}YC! X 1! #J@nw*f>S8 ~Ɯ"H.r6 m MB%Ɍ) I5><ڮޕ$!)RJRD`&pS7YLm%زs~.9QaNJ)A$"e-q6뵓Z3`=4I IDAT<6ƔY[c{q}v W:I2hl9qmYDzCBN&N7һp'0p9 c1tdHS12Rjyg vW7O`y<\6N+釻π]7/^1x'mRRJ1,"f|7}KD $wDPZǏ?`x9DB@D (!!q@H 1X)j:g@d$aM~5ˎ;89Ve*RHjhe2ڐm yG5^xٶrCVKԖXʬw:Se14L@&2{߽'N1AGӄ(sGRʅX$i28+I-)%mNښ ARd!]pQkuQפbߓ:68!=ynZva.CZ#( 4&!^,;B,m*d:R?x_kF Ŗ}is\'L ѡɋRFX?eFRB @ 1")dŻD aU6}qyz۬&K)Eam^|>ݶ?x?+}aY/{ )v/_9y+!8&,K! {,$D,PP%Oi! bbWR 5)(fvKDVuHJDm P83Z۹(nZӕ_)_J鏈,1F QhA#FVY_&D^^_f?ػ{n]׏/)q !Q DJ,#~8O ^S!"ѐ%~]׫6*FV9%)1#V)%C;Ї 28GVh\W֗YL\ {"Ls 8%{ZIz6ݜ$I9+D"`8v|Bι;DDZ)NI8::? Cɇ4͢R& :A[1˲0y? .xTDPFJߟgг$9YCBJ)3DDIYVTO?aӵͲ swrE`BkO7}V7oe'y?Y6of ~ݴӢ.j;V:fjoZ3.BHևׯ}j>!>\J,HVi̫ߛ?n>1f@!| EI)[M%{_|'/IuMDiG+s%K>_ ЈA7>l~ZOqQ´nq` 3@'vۭ.WKz޴~\^5]8Ҩfs+1 AFD&eՑ76] H #pL)> m( 1+= ED D`hCi1[c0^ Dk[YHQDRLlvNY-mӧ /kh5QDᯰ? c1X60N_)¿w'rtx wMͫ|6[u~tß[UnΞ>@RϦLj:GCp7n!x]b1u씁MQ($Yt%ށֺm٧̉!i$L?8ˡ( k0RJ;74)ڰZ<|Y=LBtX%Tus0D./W:K{.٭Vny5+Dnʗ+>|BԋphF:<3@%"ZCY$%a"޵}'DÇG7o1'"QJAvMM^ 9Ĩ%i2Zi# զ(ll6 [0*%KB@mQtFZGf뛮9iZVB%$Ab;@4&c7 `:~"Q 02xmD.똵bE)")u) 3<|޽OO.vv1 Ð}_&iz , ^b  )Ҥ%abRcIX^fy&wĐH0x*]HKS #{^oɳ] Ðҕh!2)3iv ;tjOd|^hDA%DĢ(u}Sͦl-~ιɍmf&D"ѐ"%7 s1b]wGdu2*xbTXUl1()2F͋ǸB?O$K JB*j_~w1j!:&c0~=vMӻA[o/7Ï>ޛ,]W-n'k ێ%n3'!}%\a ƑUAWt'[  1$ JwnNsD#-M &"MJCL.301(a!$ C|Y| UƢ|Bifۡo]>Pż[B o[UN@ص}aHX#ݐUeૢ_ /}?j/V>!rk~Ӣ%m[W}U]788&`֤Dj A8>Lmlr:[1?[ Y^$gjDt A 2:8>Z?9xݔm@B|}?/)n2B/?~]jzwxxwrYdfܬ7>bE1r*:q}?LLf|%Gơ0Q(237^D1'֊\n3^2/Jf#Xx2vC֓b1MB> 6RDjwym!O*jzba&@Q%!Q *sRUFp#DS1KF`a3"be6J?z> B0I3NgP~ts95u?>#T(j IN.!EN"ڀ)y&\@QH!KT}Iqu̒]md?0RFfKr/V1nVkb>Ϫ+S*v`ѣGz+nf_=V4w1l1ߛ#0@1}+_%SUqNH)QP=?5ncCFh 8dՋ.LLY]yy3I(FLb f낷YAZ[c$H<¨ qb9 |Ƿ/~˿& zlr46g GTDCPȤ !Jo6{Sk9uiv7ujm+bR_tCެ2s:hHOO<-fSdȶ1kw@1`mU&⻮kJ3U-RUiywf"Rʛo*L^}c?\ɪ?75  $f6&\63SLyV}GEVic$LQ!$~&@e 8v{*5w||h_ֻ[u9)?j{ f MӶ#`CB㜷eIhS(g?!L()!$J+E^Mg}siA5>7śo zyD)}L\n;PEY"eJYcЙ 2j5wf&M/(^+Q4xkSFD)z&tӲ0:FE6d F@򡮫*dqX6ښNjY-1.|Bl)(V.?xhV7Km]Lö=Mvݏt5@JD)v~ o-st͐/-a]S_6WSf 0+&W M" sv2ʓO6+]"&{7 V$,Ye@k@6*$120Ռ&4h HWM7&I!QQJEUI}q~#j$ jM"(dX' Y ,;͋{8{t_ sy ,LZkJDXP-[bQ֛qieYc|^o6|q֭A h:%A+`lD0N{{$^ij猟# ĔW{G7 vesn+ A@YW} CӲXP|:A6mGoF9_m˿2;<ci`IHJ|z72$m &y=$d0'ɲq203ͲlVTEo޺ʫ^/{{.2A"`DP=/2^b&"o 1(]Q D CSRrιؼ*%*/ D#Ozp>H2#(P r(1nuVGo|߽?}?Ɨ PJmj%\JI#`PiB9ġYҶ>ںL)Г֯޽7MSԓ(`Yg{{weEV庰 )tB|2g{N Ym? s:d/o. Q ! *8q|: x?J]ۡ$x1K0VKU?9 $4OlכZL㣪*.~5MgV@4m #3(F fF~RV@si ^>~yxztMwjrUWS"⍃oir:;JXCqEq >dEmuCd-#ynA*Bڔ{J%f{qAF;H)a֔I^VQG']n=M,#wCcz= >sk8qju]繭jۡ6qA{{'}4J,vg!sGr;;֟Yk>¤CvlvQJ5}g*Dv>w}gSWLJz-97ZY ј]3̏~JEt^hu;GQG+܏@cQS bN1Fksku: 8;M,{lbycda 3. ̑o\ЮPWWD@M0ss: *Q$3v6nQ BujQJPFB#ڣgmYE-W{o|(m9!šBs9/1He1Svqh'V qflJi\nkҪA"l]vtqv]^tqM%4n`({...X@;0\7oO<={rtm,G׭~pY ]J#ͫpx88s}Gy^g|ﻓz/ݟƝCc3pLM;tݰ-1"_Y3ܶM6D\;)%vT~fQ,3،NB; 'y)$,!j$YaRY6vE ZjRnZmKD]MW^y%Ž>[#JeҐlOH,2k㱇bȘcL|tF]]3㳁 8BW3R>'ʻ !I3N<)kæ;{r>mR?*CʍI; ΅]ӅȮaJdǏv=U9ʲ4 p`ĕBDTHJ(%1"'ц\7E*tb,Z#cF뢘36SrKz [B)$/L7}27ơL asDEwlwR\@9Ka_IM<9[ԅeODV}Vwy>H]jv"(H* dJ3 1a1;d׋Ó] Aj]2WK^{Dn ^g;7n$ HkL1&-3g6) "ʪJiTnH IDAT 0"SJ!TD[~WA)o!DV q|9IJ:j,A( TZR3z=$!Z@At}߻Rlו*[w_N胣$clFaeFƞ˲{fd(w '*Sw??}zrZӦ횦Ƙ̇7CJɣʘ6mwmJWYY#{Mi"$dȀQPH+N)%ϰܵo3g@Jp%IWjӣ#wHpUdQ"'"J1:XmDP5)fݠry~~\i33N! E6gK7]҉~H̜bή߂QAzOJHF)[  (P_V bwo&"V )BAaCkdzAP:3vVOr=ytvѢ.oOHpœBL1bJSf59 DɗŰn, fQ\۝'Bb>ܤde^k+0v:i r T˺mx3Wg7of''O>ѓDŽ@ƾ8wR"s]B" @\@mnpb:Ti=yAms\ĈE]cҴJ).Q) qm}#:426/D)23RB0ce"hMp2[l(e8 mvedYvܼsVvyb[(J*U$7nu pEM"Kf<1MtH2w1:^V>iA{rMIZk>!+r^׆:}ӵC)΍!ݿ?FbLVEU׋|#"/G|2 -IgYY%6m 6HVdnݚ>&@eє67 LkaEZ Qk%Z;t%zv=?:De@;}1}b4 UWA De{.ֱB hcLw_b ,"O_3mPŀzɏo>ECRd$*0'aj2 !A^eY޸y[dY l1iI BZ^\1x%V!|~ v]\~'}!l6!p|e_dj0<:;N&yhfFMh-aT,.uPGׯ6>جH)jH7(Fט k'j0.F@ #(Z{b )$ZERʚkޖɋZVfݻbqW{"K78(+rY޽~.beř"@ۜAnɟV7zu?.WZxÏ~+_e`@dIȠD&0Sd4kH PQ л~7E^qzw}o~t|k#)BV%E`y}ZpB"B(ҦL#`< TEQuӧ>B۷_Fgc .uެz8Rl>͕"cBȉ<;>>βs+I_ѴJEgJa8Vgr(d`[Ay@ ?\tveL'р1t޵]Cr^ھw!TL2S*ߛnk47G"BW#t߽Kx*JTBQR"|>Eq"J)QZqmfrjVw[e)!!e? $80h˺rE=\>~(Z_g=&G|0*}sMjuv~1T{jJAD#3 C]o"l}SJJ) s0ر RJB轣ba\;@: *%-BJz7 !Q@>뚶 )^ڢ(˺'fc| Z;L\!a$὾iGOES~RHge??5"ル6zÓ'Ou]'mI5}z`*˃ޫNNS.~+LA0'D[C',//mF՚R*:Ą{GE^$۶C36@A<~MUN3gkufj Kwy ?8ߝJI2RžHm~[aШggz6*I"3&UNkE`Zk BHBpnmIaX=ʣ7 t}l/eihds&NĐGd9,+gY9mO1@Uɢ>wu"mf*%+Mӵ`/wھs!Bi;Ͽw!HZb0nWr0|&?x% B$"$A2J!ĨжT)apmM:}>r|ubnYk7l~P 3_!@KI&1#"`-B "RBb`IChZmC|O~-n!E)_yw<ok Efy##*2L'mtoi]dcQ!Źký(3maZ)aakrKJ)$US7¯!0i5;?t2hcFldkw1pJ) kcum6MB4Ơ2Ƙbo?z"ƨJ)RZ1x!%s"RJt*{x2HrQ*DWϭ !t}|:UizkoM10ݛk% Q(5@%[_6'K/7}Ok_5])e& a ļ,)ĈR@HY:,˪I](d:ek  %w,|f$ܺHJ, ĤO|kwpml![u^US >͚Si[="kY$ IP.Wm۽d^!$B _po.:Q8˲ڵb:Lح`Un/ֹK3ˋs2RYu]O)NsfZnJ'a2ϧmc'7.J)%nkt2疆- uw]6+5Y'޸u?Dk}ڍI^7~G童㠲 6Uj`O1J,kM3 q@dl#߸'~K_EnDԍVX+ga$ !ŀ $fYV͎DtR(??:؛TӋLgkZ E#'>]T[RAS$v4wR\.t YcADTJ} _x^PJ8 YkRCf͚&yXf.rgz!@DFeӖdGH ?w _طq^4E"%"$&bc{{پ-VUL=L<9SVee> DZYYk 2vPSUD0-K7ިۻޤXl6j}|u܄'.>Cwv*._Qޟ{l!!9$& utw3oEmڐG$,@Ċ1%V1轵mmw憮n9Rʙ۶[mQYoR? >t_,AQ!rSޫju]u-[EI!xُ~^Vo߸CΛ6'WzӢpjEp`CY2Nd '?Yw^tᙧ???˗k 3qĀG~A{_=E}V`U(Β5#0VZǍEQXym6 d!1>fT@Pϼl@ df5HC(oo~a8?){;O6ҸiQ4n}cY,{f6Ls4  A $,d6ϖs>hQĬ@hCF*[[\0N{8ek.]ד*8T0?tB6zVvNVk8T3|ktse᫒_Ǽ^՗O}ӟɟY&isQ1~>13G?6?aj[ H4;|s.!s[SWfSpح'mJ]tC?>缉o7}S M{Bcg W|<3zY\HgCkG7U0m1<ě7_MƠ  j s3#]P# *TE7W' U Y[e]ObNy_8LD]׮KcQ֎=Ъd~xpvo[GCt~`,Z*9LANCl}(C=??l/}SI= qaqtbjH4NH>]ON->0wr gr6\pv(\]Qkre)p7yw^~&<_Qyoo/2YWUʱJher5dmF8Z6 <Ǟ&rtkj9 1aPU˵dm27O?cW˥FU+YR2-Gי("Al58-|󆹴}ahsˀHq`ڀơê(9+ƐܳaU"Ҹ.^~obUQ@$41FR?3  " ę*Y)iU2E![eeA3b;FI*D,kR\ +EASPbvkꭻ˃xkۙk֯MA  fNEHqtn&PPUV%d@oK-hTx:?Zf@ATb:늁m+txantGstrxkȃse5Fn|T?4i!{klO˧t9Ƙۮ/|_KI1(svU>\A'Y_ח.__O.kޮƍw/_;$N9^y{nvt!GYlVrjsӝ (i=` IDAT/1& kmLq} /?3g?>1(mCFѦif[j0Yk'}/dL #gH0 Sh(tC83*8ʩb=ߚf!eT1FoX߄|afQftʒKD˿+/m[PI/?7~yt2I+dBg[vGB>3BQ!ifib2=C@1C @T@=igEM>ح锈uR2POJP իWOn\ "#AGJiNɼ*ѩ4hк0f Ct+{o0y^}a֐A< jFC13tևQT)"74|q\<|oETP .?r򕇧n!uXv@=$}ǜDdI/2Ȝ_{Z3+\!IX9G?]%{h1&g`eEyw̶Kc!!NʂX9J *sBE=$I!'@SebM \1r|O_ue}SJuU}_!8{i?O9s9e9Yn޾y}yzɫIs흫>COSJ]ML;^n6u JB XE  •+d{pvP]v d,8g(@De6-/^:yhkSY RȑЂAl4)sˆŸr՛2@Qw)G7ys}|Btd>i@ 0(4)nV=]vyentyHɇ4 9g46ۢOxڦՐ@Zk,b`pcIU*"h|ܞ?hE@QI)ԧ|o|W_JRG -q 8CZW')qb]n{o}lHVWuM'S8yeKl(1f!2udl̂>#$BC欇*z_=cߺvppι|]eY.^=eWMu}(G7Lm9880ƨf=#?%21<+O£$% Hcٽ/ҿu7hh6nاS]ޥwBYWTa:1hB~S?I ":SX1kvzR![7gg>31sӿ_e{OBplv0 pZDW>YD{Cy㜫8 DTz%X9Bd'[pY f_XUUDE4Ut΋H:__}ywut`ش֝_#?ؕTx/P4y5-@R쇶mJj k@&طfP!Ĺ ̶(9i 2AUyF1) R,rBWa9G c𣏼9M,J]koƬ@d P Z} vgpiGzWu)!02@0 D=kFWz}Qu},Ld(&9)==e>ENɘ,]6xP>"xzt\mf#]Ds<Д9E?D2Qn d8vh E4Tj*g%HIA$gqYT@]>(֡D341V v1:_N9Z0Bt~Hy'M߳(&s˶\'?a S&?DLt!٤l6EQU5t}Qm}_~7kj5b}Ӷ;CƸ~fY/6nn6MJ1sB%wa7ZgU@1t1H<LQeUag0@IqqQp TXx;̊R<8ZmYk_cH!d-;sNKfVP5k7ժ4`UTG16QD{m^g~"e KcNRck@]jfs;mbyݦkA cY۫]uxlMQ9uGwoݽңS=)PK ݪsOSԬdH'$B+D5޿Yr:tYDKj W-=AԬ"$@h)[aR>.>$B< 7_3 yR@Svd8;LrLVUED?s! n6wwwfnY,|Y8_dԔYɄZ6-اTGk{ vgt<x2MpcΖhsxxشfO:ʲ$ʯ//Wscm|>,WE/ŗu[|+圇^Gsd`YeRĦmɘ fŅScm'S'C eFY&`9h"9&"?{7mlvuZ/ڽ{_|c[iNj@@sX #8!vqM4:Gb̷v)z4HX0z0*!w+U9kk49!i8wvvrLsiG=xMGdf8?6N` U/PR~k^BHVEQ..K'h`kɽ}.v0D2 LccZm"7v^nqGfb#XUe7^i=)"(hg9{T2H/Jk;ϫ"UXa@Ӛz)(kNvcU{zpts\j$1m."4mK@;&9ujqy(%,fCJU T!Ehu]ggu٤28+9kACCz2YoHJxوAT=;~g?w~8#|_ @Ṭ@^ΙU@`_$ X8=9y2nڝ_{v?3$笎"y"|jDg_^y2QH`snm۶}o3T`?t,'ǿ+|LHc 9Cstt[Թ92 63C[E5lRI}(릪|{Ufl(ʪEE3"$p۸?fAzOigJɤ{ßoyk.o ֛vQ_y?Yؼ9e -`CmRS΂`ˆvH)ƘrN2;7O=ٝsm+X- J;;Z'U1LsbY:Nɒ!@RcE2gSib`8IY>NNNRd .buY,nL`$ZrD=K𫢈 gkƣa$yxODCꢺx>bF? ՌZ\{Ë_>ImyG]ĭGͦtɹsb Er(PO\Q1)Յd2k/mu=ޞoE?bd:" 5XB+s-}}e50ޓE_EaU[c`Y)e bƾ+hJqn'?_Ku]n'??}W>oz6}IҌAʘFĔr HCw/}Ko&pj]V㍻Րfm7S3B=CU0`bsR2E2ևKW{E?E~3v,~>P|(;s;<`|i7);W;+M(Vŭ_y'?h;i[A"bEa#L-n@[4݁r roaF$0A̳ ~T Q0  L LRƉy[zs0>x+G&.d "c (#f>{k!߻4d T#>qM*T%Ag,'uXRYu NJEe0@d%F*fu2Wg~M51[8ch=ѽ?LkmJH , ηvj"W2sNr)3 (8g,2U ض*uhlYW| 1w]b%v?"! lϿX}~{G<0 #IΪDdH|f[?&T_ ȒȎ9VQjL{⩋>;7V+B5*p9nz@đ4]UdX,Dui_{_il @c/rp/| giswΝ8Ӯ XMꔒ@zfƌu>8P׿{woni]d.3hav6+%&H"@f ZlruC3; 7g>G{ULc8 Cscb@ H;B@IAGUهr 1'EǛ />_~<@\csw^J \CR,7D],CNyӦMMnK)q(kBi; oN!pVssjn͗^yg޸ {QS E C۽}W[7nV>̶ n3o* s 6gz1R!~^, x'"dG}nwdz^Wv~/jy0A? (j Mj=.C9N+w&R9xxo,rk7nζ.<| 9wq{u '{8,g*T)UyڵkmX Fa<}X`rUS UYUR hKf|ͪ`c # 9h./66̎P88wWCT:r"c "$B7.dv=m!RQZB PPc'1f0:0DĚ yhbQ'y`Iiଋd{?s?$Z<#D,>DkXCo-!ĞoO糣ss9iѧ8g]"9>1}EP2( B8 4fU====88`fT{=v9*w~Ё FfPsfHeYAT:{+o~s 1'ԦsO~ZHΨ0BY(uW?'M1=?))uu+ ZE㑣\dS{9F} @Ug!N4)IV P:9όUK{ac݃UY7*lE( :V9]\OfrH@dq~?k%& =MXAIЕiVp&[~#>W>=;wnwR5YY8$c,s) hf&QĿmK&Wdb213Y3 +MqPHhVy IdPFӥ-qT0H1Rdb{ɒ:=9lac-P$(Xj\zq: *̃QA2gUɤOˢ4" ;p,sbtUr('ު|g> .pXEaH95}s{9U[\ jmpݺs!dT,,RB%D4`YUP錤gXA{QQUiYR@T 5T"/wyz1^ms@˔m ȹL5bmI g5(9zy*kR$EP"DFG)LaTmy~'ƠW I$JȢ̪* (%gœYDR.'U"ڬW{!݀p?f1D@Æn59=8bNP'Ħ*2NUmgc;ˎP BpPhm9BYPRCglUfٿuk۳욪Ǽc {;o@cTC¬ǔG&cLJi찉CWܳΪyV5E9[_~ݦ!CYMvw._}φnj}gE"P]i."^ʲ\ƘNҷCQoooz^VO=0z{<e_4]k cXD$ Et޹>QUw~Sd;?¨8z:^@?`lQ1l'=?uhnl:GFY 25.g;ӿ_" ֢̐s hX]jYmL|El#~yVMߛbZ֓n< ]GN $[2nڶmz꜋E(&;P]|W3Rf|wMӮASbZlUQ]pQ֓Ʉ*svFBd`D3VF;WD(ʶ>mb/#PXwCFCS2zǞB*vE1wno`B,9GaLi` Y `mQTTVMf.r:_Ff6mNd=[S̅ \ IDATx+CNH!(@Ɛ5 HD c/*"mOO^'GIr-  >]>9]b\1s~t\}tYƵz4HbA_ʛW7FXL!^xWUq6f3I915yQ^~gWyu"-J0L֘q22`6ƶcn}3ӝ5}Y-TXMu|0֩5LF0Fșț,!aQ57j&w^q=쬎 c:Zڽ\!c׻16&fe[8O?c|;L1MBItX̉RjuYzT%猡N&w|j2ΫZE4Ɨxqrப5)3m%IGZfߜw.>L( zsdN@TA!qYGy'nzP_yy嗮wn }mo\O_|#[ϟ@vȠhV$,9k1猄̹(F(ߋX,Mw?j5-7t:m"qEdEq~7߽r`n[b7~aczE|(']0Ν;`I ΫYT[@Zu ) ~5D%M[Cʀco:vL9Zo("f?}conoLŢtAWfӟ,O!FDtIylxHC7D!HJnӧMdD1?8Z)ƨl568O`ٖdqh !߻x#FAP 9@qoRL}恥2s/lۛ2eUV%JR!M dT5V k{![xkL)}ӟ. =}K] o'LCUqΙd2;R=9xnRZBYE@p&*'[n PY"Kf(#ʀJ$N`DŽ}+E "($aGFI ȨId24Ȳ8sVht&"c(H%g849_[9lGLP2- )jPEH  4@Zݵ,q^D6(̱uWs9_~e&@zcAg_MzUL\WH8WOk{҉AE+;zZq.[%sP^ j}w}wmsv)F"%>n޸9NFNPQ@$JjS_?_S+x]AhթA3neBdw8tn[o>fgC`kk{A|VMӰ K~"Jk&id(eL{هBJ A@Txg ˷ pebcڝ斏I2u6 ,!ح{MŵNLp(H""m0HRߢ'0 "bx4 ApSgB,чt{wu#fT. bt֬!K`c84l< H40:LVmX mʌ+C1J ^#*\)Mvm̈́l>BfiJx˲J5kƄ,H g45]2-H*lNէv_ߛʊ{;Ⱥ)rF%Ut"r^ǣDc(NN(Q6@$'@E$3vW5j0nSQPDž3FUX3T1|f??̇a )C:hݢdWZ9<C*IA5'?gUfݝ/|_9{ɸRJ'$@,{6ƈ@P""RD$>4K1믿 /\x,N4dRݺudNHii3ÏoHB^3$פijGWx1$nvss ,vccQ\NZkz7x<@aW^>wΪU6$eո??_VeNjk+F$UJL=;Ӝ %I0ksh=uV2 0<N7$:D B $Ƙ~*,ˢȗ7w6g;Z8vl4+[dD+B&&&00$Y#T 1r A*D t lw_|23gǎw;9U^:6ܞ7. ǓZTVZB*$gCHSbШLbItej@ĒfjCHlZ뺮! )kaYhB$$%jۀS=133okg{ݏǃ5shl35 Fwi̵m,[9rl~e']4t$5GUR=яЯ<4NTWnߺ;d4j%>Hj!h@}O~9 9-~k}ox+2P,vӝhR[dZΙdpǀru%>I:pjm,Ӥ5*bRE\`_ׁE +ʡL wF. F5[&/fckps.5@ &䵓͵JuN< N'u( lxow*ԉ1fqz+>CF`EԪ D+gI' I&ion1+GIp6# '<N>t"Fy Yݾ:7k{{;wܻw/˲v{~ZuNg{fށ LJ߽sokuu5-Vlp/b[iKܻr_ǩSyZU73;Xw˕#d dbk뚰#vuSl=v!"!a +Ϧ'"AG~05MkgM. *?z+ISu 48Xo !Zg|Tҟ鯬gAP L;+ "wʜ"2J2,wBkln*m%Y vg/҈Ḵ> RڽXk41"EI2p<*e2(ӊF&DetlL#00ierx?(Z9GDY1f~{+yGR*QJ1{ݙ^Sg_z~wgk46+K_Wm 4NMy־^ژZd)1>Q߻ طe޺n[%W꫓{v'@ KfGV[ϗÝ͵Um}A=vo^f_ >Ǟ;X*1a2,7%[B""ٔ<i d!<)'A<,Qmw쩗ֶ챢ݞYUk;;EJMN쨲 w(Ivwwg{{[DsI};( 2-Db A%Np'Va儥bKZˣb״n.[QXK7ծ쮍v&jivDŽ^ AMF@Xu7jS 6]N*K 3Ɲ&m{=ˡ]]3$[yh"2 c5)M4(TTqEsA+ediA oMӛg*:7nyxgۭÓx44 BmObL@*lemzA#?͊[M&h4Lvvvֳs4M&V &g'ƦlJh4}vYv48opݼh\z!n~lzIVWWKe@۲ `E9=vѓuY*&F$ [ H%;D@VS?nj.--([RͽᑅGNݸ2l%5\mh(fK$,oy A` X=ՍMy#">JTjvv՝/!EsDD0!!>S4rBOZ !2zXx0IT @EEȠɨx?Si !2XiOyK>``W7Tdhq:dniyb׭vn9hYF0QS_Ͽ4RBhi,%LR ~O<+l9I4b~ίlܸq Q+sS24`Uhl$M?x"ޖ?R{?3NG}yp=ˤwLSbccsg] Y9|d'ޗݻ;;L7YGBk$;xOfg˦QZ'6;M91z(ߑ AX!D!%", &5m啫l9.dmɇ;Afdeik/i>n[H>1!2 ?B>ߨ{4FjL^xd-Ҵ$\3Zhƅv)X2˗f$Iy^e9V:|T^ʇ'Ҩ&0"ӌ=}:8qpRR Y"ID޺:ݎ5{w*/;ɓHrkL+,D)&ue Kݝ[ݑH:n,=@O]``n(Dϧ3Gk=!y瓌I9@&Ak-J{DP43yI+v>D}DIy$UH$INFh@_!ҨE)Ћ]RSF$<) 5ƍcG{fmSKGVf(UHZ߹~>hR` L 4'(D 777]. F|`P v8a<ϽEpϜ9G )``&/fZ>WټySm=4wxcs[x;S'WUL:̍ʤÑ+kc]q.^\tfSSivwYg$dhTryJ1۪+ojoVן D`f ~25U{/瑩[3IãgN$$tbȘ8 CC@9@(>$noiu]yqDLt{{OGO>:M 4VDN$MM̚BB 3s1Dںi'tUG_?O񇺳<сQ u~"Rqc|Ϝ}\U Wmu1iT0s`BۺԥFy %Y'Nӊ T:&vwTMԠŔŠ`@rPcd=s~DZ"H>G$-`]D/<҇akcοS5f #OCM9ܜ=th8X>vI"iuDTv?|}QDSFƵa⡕r4'ɤ;]\_Xje3ϟT 7-HܭgƘiV޽|VjBoΝ@E {iLk}}oom0E}hB*M=:.~kKvq/'?-VumTk@»a_N3)T/]j}]z_ 0g:I| IDATv{ʕEJ [0 =VEe ܸq-yFrM)%lݵo?z#fXgL{jk{?۞:M{1qqppM @(!Pa8D]ˈ?@~d;oy@]1|ʕ/}Khsxiʲ$)2p۝J:Mu 02J)cs^BQjoW^??ӏ?xjBf\0&5~;gꨬAQy/7Ui}xokfVd$is 6v6bco{98pB"xiptƍWu Lɟ?w1c pwfyqsoJJ${Er~8Gymq8 eMw¥+믜yxY֮"h{J'f\3ә] j4CYMW?%f2X_eBdfP #׃R 1fL?"L{IDVMC+K׮];)hyI б;BLr0"a<wEC7߻{wug/y(^Y~3O<ć>vRDZҹo?.<8:K-XdF=""TUyk:Sף,4M͈Bވb0WJYk<F~g{vg'uU5ͽѩcG<tkecf;iR֕&k(+z{5Y8ҙPT; @mڂfM JX}O9wprB҆i Vk-" Lb HH1̌yQ4@*B+Հ$I`hZĈJ18FD(p b2XEQDo[<U]w;FEѝ孢?7;?7<#}Y6:M֍ݢv pPQ#|^|ŭ-q$v'saf^'u$ L{!e9L/=Kc˯˯G[ 㬵Ly+Teƽ8%x߫=HswHY_Xqr451g{m}&h#!]"-v.`D6 Xooݼvtx7 2BHڼ?A#$"(Ȉ" õquFW_Ƶs]+\+]ymntBg/_}OY3?lݦIA!(/ 4I2}o}(^>ETr'h=M u0` <H=th8_4j, `Dw_<4_~L3PInI, /9hղM.:K+&I6@#E i AYH"jl:r#ٹqkUIƦ`,J ٤|z0])O܏إ8~bo 40mag~"|h>3o\k_.-z|ȸ̒dY uSZxR I%@ /DDEꦉ4$"ujGC!pp~ZS`ПɲN|4K&n|#ۛ'ggT&M8jτgA?燖KKKOqʽX'ZtbF,w8YsZT58'ĬCФ* i&4yų333+=zAk6^}tgJkkI"kw>OHV]E΋ͽ\%Im ˑmlF^4T%,KhZoHYY֪.T<ΨQ2i UUiCMbRnomObkpC#׮]`*B1G:L:v~,oY:#9dYVUdsN9&Q:aABpyphe/Ul_ $7{/?\^_=1םmw߼w{ᑓ35s3W>iWnuhWU.ݖu nkOtB2dBbT(ya¤޵"+{j>u!DmD1ʱkB]AIXM"]1 ^\Lq. DZZt0ɹZ/tTT: B"=3i l#L >[/|_^]]L g_h[ ks3dп_v4s.ho^r$v8R*D4K 7@\~ZysJ'i:yўuT{yWffO톺@{f6˲_>|õuh%+`=0hEj{@r?Ԥs~bc EXMu%9V{8(V1ȓF;le-,쩝mMiLMbJ}k M6VJ"›g ~yyi[UJhos v̘V3XRc%/]'#;n۪XzޤQ] UKŒ`i BQ}˜9}*8^z͍K H gaAXqyߕ3@E+p@DZ Wlmޮ~j,.ZkkHNCp~%I˰ G>=)ɰOqם[ppCg"ٙl* Ƙ4M2ȬQH%m& CF geD?`$:׸ ^cKoPn~7iw;sK52.$Ovms/|ezۛLo6Mrf(Z]O&;qB[k[aE`pIZiu4VLMZ3RjRUTMҫHA@ͫ.ݏڵˡǖW^y'?:۝M+Ͻ^VܾtߺzA Ǎk@!r؇FaHJiB;I$ (T%DE!PA$MMb^oa0IͮMO6Z-bf۞UnmRtAtZ+MPYcg+A([v{@!"PCܜ$#t{H(> KW.\x+:DVb].߽>333&ɓ$ɋy$ SyyVYJ~$AWXQdM0SO=zG3mRu]SҺ o]z[C'~d;h{{nܮ#)=" "$>}f~$3HF"JPA#KQ7_/}̣ZkCw\vroiwcyέ.f[  :3=R*4M ޭ~ jk{u ӻ?󯽾YDL,4i,UmUMV+KۉFVWkaR(̡xn (D4 8!짃*OQ= @bvbO,;m߼_}mz%c4VK3{΅rRtEޝ]Hsk7V"Ck-=㇏ݹ~%8kZG!TUa>:;7L8uf[[;<[ߊZ!TAD)u%;  `bc3$ H,7 CV ,g&w[ s59(#Hӯ,3omm;wn>,NvO8-ϝ;ܽs*/Lȍ2 $+L(N9 FTwYϴ&oeMSX֡HgNjo9?>Z,BYEZeYe@JXzGʘT߼s>яvzG_a7)+R*1x} +.hʌctpBp \H4̜)J"=:DsLDf>3"A, #@)@Z&L7jDJQ/{#qx~zkdZ[d[%RjGq't?=ej상A?ŀ. Qnw,K"%J$bf'gzfQUYG*3++3;~Ȯʨ/"/>HrxXb H))>l㣵kok`g}vɑ#f,I z}l,J0a0 U%p*g,cɽ]oR)`FA X) GoM_G/ ߸*^A?Õ{zC`tb<ܾ͡{ MI d8ddZD`mQmw3 bY,FHRn]O<~qe'Sn߹ON?JbdpO>%5zeyIdH2ʑybc3, &G15067X!*2GRo?yl\yxw㧆SG|ww$HԸ@i~zsƵ7>槛ؑ3gx YI Yb$lɝq*y,KlCaR#G`B?O?HBǎTdd΃ݺuG:=[Ɩ$ ǁ aAbY];h1BTV U$I~ZTտW*(Nѱg~ZMj\TFn\ԩSdL&= }w5VW7;M-Isq!1:v dSF,' \\: p*cCC#lEתd9XyfkXb("İ&4 \{WևX%,@2V1IQz P<& 6,~{W& w oommmU*6a&0nHhryx*ʲ_{ۿzN8L,$vɄi- IDATpm|+Z尪c黲!2諒w!#YĀ `9)V'\T"k8Xcdf?bYί}ꩩa624 M%C>OV>H2'1gyXcFBn#>$"᭕xt/?q0*aaŲ,HEUOCT04Ub,+Z$ʩTc8)<q$I虯vaX`!xz&Sd9O_}jJj æB^0!+UE!}g4T* by(l C}l!$ɫeT@P<[;Ȋélq 7<# tGN?3?uscja3<`~P Qd2XpS óh^O%c H Y$cd<XHޮoyWLso>̑?=hCg/~܍DŽ;67>RY—d8v޸pKV0pzSQa# rx(y*d3V1F ٙCcB\Rd\k$8H 9!5G}QQ?y[/a{{#Gr[fSRɲlBU  VȎ^nԬ3l\`9Ej*{gg1S#$b€,(y|zC9XiJu9$灸՛cY1 b0F->k~5X\zmچT) bFzW_}gN:'}+ǓG>%>1T߾H$9NhȊhI(ΎŏBdr{{[UUU'sklHɡDCp (B#g2ϐ%wRG9>>R0m7EQ(#õnVFl6@% .`0H!$2>^w妊U@eU':o޺}LǁҬqԚ#Ā,Roo|͛eIl O}mb⩣N_,S]#oE,r;<ϓidb#orkݼy1,0l,rSE*V3&FITU}clvAU,"{"q (X?y1U1z~n>վ\"2qBǟaĨDRJCyc9tx*P%OD<'T*P1 *b$ñᱭ;w@COy8 h`&)=r^bY6&Ĥ1V '8olPCQ*\\.5ʲ[;z]&TMjćSTi8G|F(J24:Bh{{{j4 m41&&+dXl?( (2+7e܏,Ű@C#ccYb_:sHjhO3Un^~Ï{N,u{s귿o}hlܺu⑓(#%ݼ}gW^`!^<$˂.Ei NzU,N$֣\T4UNIPeU̲lwlrӓ~RN,ԒN1"sy9@ 0 (e:id[ǍFCn6h> Oy*HMUiH?O LN=ͧXM1MbSiJL2P_Ʃĉ'SGFkMYeVRm{{ӏyGGvnXEQ?.yI%oQXRzn6#|w˟}z _JR'N5ӟyo|G'y NH㒂?8z ڍ7ofXVV1`BABa84ژ^XRk,[gmգ*Y}իo]Vo6nM7Ό_0*nQXc##?|ӷX!R1f\ؙ޹^}\B̨*aDQ!%òkc'.Mq@XQeUUUUAň|29}w4I{}x?[Ceylb˪ rc4p}?}oLrr*Iqc"q\.1*X$pM6&F*d11hRF7nn|K R3 ϐ'LEP1{D EUT+MYXa6ML* mA23'Z8L6#v6ypQ!.>eY.Jݿ{cxp%ywXX|(~V qNQ X,vdlVadYV&P\zMōD"!Ç=ۿ[OMLG]_웿|[\pb EV@S=2|dt_9~lIVFFGǎ~qcUMF "Ȩ+*X쭉ut6 p2kJB*xk|(y=gͯ\]7l=zȱyݑ#<,˲rbB/$G$ Ð}3h>Z&XTQU) Xk~x'NDcp(1Ac7ЬߺwW2CC\2'S}s\"βlU"ɞ9GWW݇RC8UURM2sP)a?x"]?ܮ;a{R6wk}q'Μ:yHJc/ߌPBٌbzr>66622"I`<22֘=ztM, &`dx㧯BB<'o<ؼ/J2{pLo}_3,8zȐ$+F=( v%z B0V MH%!9YI6d,C xra B<62v+d.5Fպp8 |IJ, J)Cd8&~*͛7CcJSLUCfΝ;026{jdgh2B4eEV 㻊t,~ H JN~[ƽ{[RmrOȱN6AM&B25zG>[[;;IْTY!kAfYqSg/V1Xl,(bFFԦ,p 8I*I3Jz`:&cuEQcı~۠x2Y6LIcccM HUy'>?~fS $$9* XHƷ<ظwGyv<, 222#5d@ 0LSQrjxȱcdپӘakWKMr_䗾D$˲O߸qcwo59~aXK?TёSN ׯ<_oA8JMa17ymY@{x5oU ,m҈Ixƈnb=}M( RO?'һ I^xؔ,c\mǧzincǮ}ryiaY}r`gw!=ś?`CY3T TY  ʍs;hݝڭ㼚I;$#7LrDƊҔH|$*؉\,VK59 C)r]13yڵN8 j6$z^vww777766nܸW^'¿wr|ܗ.]2~%FP 72$WrD^"o!QH/\ɻ?}Ʈ?s}[~ĉcccdrH'J%(\]!#5j{ 2FFWJ+B:~8$*5_Lsn>#FC-|DF(^}y[SDCf[( BP(J7`{tӷEF BP( ~ BP( %JƧ~?TgZ %QvۋhQ *Ipꍰ˿Gs n/~}{l@,M~M1.7biͦbi:T:tS+ցS}NU{`:\&3~:eH~dhzZt@ Il|"VWVi:#cg5ai&=hy(QVW)h`u* ./ ߁ /m^0<N}k}<&i e+bs5S[&b~ՕgBP /;n陥̅G~"*gyr9o?X²8hADzfi}>W_vsI; %3WTK3H7 $XEMd"<Z g@b2"o:KV+z0,_dEd ۵k98N䅗\omn֣`ɴ-K([{gppz%__8/S6DC%n21ZzG e_Z>6XPXRC0t4M1nlP HfP٫P̎^l \MO格*BiT`)j:MVsǩ%bjo!ssyк\hC,ͤIn=9ɴv_Ke ɢ==.0b9\)܄b73KS`]1sl6I_萙[IP1ƹ\>al ߵɢrBr0VEeP,=UFV8Os3NN7!MZB;lyCqm%W(tk+)F>eεۖdn,Qûٯ n4}`͜+@N/]|XCXoS8Z>u7PVÚ9Glv2J3P# {LLeHzK3#qh_ePm1]88;B1;\>+_Ѝల&ESBy܅9i,k2b͐¹Af`<OMJ@~;?~ŕ3f洙=[2Ola >eш d5b%=ueoY'ڦ\~6Ta;e}y],ϦQs@,u&`fW}4q\8IOLAqk:Z[mFne&ƹY>ThQEUZnlWb?鉩'EKIϞϕWJ V KrYXAJӚO5޽l*I±9{W#ȞMӂg^%p0nwp0'iak.siqeMW~qyl=91QȵGkgן`f .nI~Ȫ'TgiWg;$ו9[E՚D K/蛪6371g*mt<{پ5T'= &=i*cٱY#/nB]B{``.^ObizܶHx+Z+Μ+^\t(b{Gdnthr[t8)X/]0# %M<'jA,hK~ IϼdHK\,e`4Vf>kg634O+P IDATlŖVP"eP'zu:bR,T,2;+b_@ȁOZGJK>sP.\\vquSɭ\(d݊V{jZmM 5]o hkklJTzVظq^y?kx;}owɥK_|AFm=91#[Q9@ "P_.?wkdz#=o='N&!T*H$hBqa+(6T eX):hDd?_P(ᆌt`P)Y8krA :hD ߏ{ PBMzf Z R(} 3ܠ8 5&< BP( Bg|ƀ5hBFk,@d"> 'W1upHT>$j Q@~8k{#ԫC-|DF(^}y[SDCuOBP( B>Ā,zBP( B Eߏ S( BP(ܶ ׿vA+T{<\yaYK#7COQBP.;G~%(8ا,*ˉ*Xx x)pQY4] s޷u^ȟK([k63x~`MZj-7Kv;컨>XYu)V˹|rXn߱t{ܪUikݳFn2M)X-Y-Sk)\,Xmmle$3Ϟo}ʹg֯vl>p4k`k> o]k]gl+g / ou.J"y N>0H1NՋdz?WΏ#d~t3NT̾/ߣXOUb֦SY \6|=~ť9W#U],Dfb<$eiMp-î\]>*΢,Tkq@T\k#}?.[FWEpk3V*Y 2S[LJl΄vKh3@ѧ<$+\8V2sf?7?_p:nYH6!"!s̭_0޲+{\o&XHrǭ+m2sd}U(h,tXjuu]K9M,w?mR]mp%(O<>B:e ryF\\,~K̜+J>U17l3s|\$ ȺReЁ:Cn;DŽnlgy§?nu*|.-܋\l6%IzVnnnnllܸq{o&)! 'W1ug<^q-kЃC/w_z*> KC_sz+'NM&C:T*HtpnIP( r-J@ُ| BP(JK@]\bp#O BP(Fzf Zix7OzC^ BP( %Btx?07umk"PXE|^}+O"` 7bᐨ|H+$!?6+5U"odIx >-DL߈C!Qs^c5/χBP( B Ȳחz1P( BP(Y^3ǟߏSBP( B]+XKNmY+rﳡc1t$T].A/$i \2X{Uj k?(p05ƭu"tp"Z8(@SpeF*R;~M.|0~~xϟPU@V k9q!rIXN%HMp7mP?9_}= Q`jgCڀUaz@0](Ct+PT6NC~<.Ptz9 G(`9`[u^KB*cs=_%1p6D.`:9 3iKf_2\Zt8ypASM\2E]̜My;J NLz /S\"\.HiX°!QY2b%u'-z1} ϾYK5zo9CUKh<_r~\8]ru|4O*5 2_"i8b?E#IHG7> <nw\KvG|)FoKcAeq+8]5%>!@\^(LM:Er~8bir5w-h.f-$LpʁN~Zt8ypAT-9(fkE $Wd :*f’vTa<=`j"`'UY(ZfBN,oWPvg5φ5G*x&"5 i񠓁h!u2ۦ: 0ޒbIC ?ʚqbDZŢ#_ad< ]['?,}>gR+:%L68^"B )Zp@HG7> ժ9k:0Թ\hM>gavTi s+Onmy4YȪ\"O,s r~P\()mPd>dK=pQ,ڂTĔ\y\uP. (@dP jYW-z% @!UU-:k*85D^ YN~lv 4U,[HqODUe's-R|$Yͺ93[w߷olh?gkv - (3W)8`\T^rh+(Xguq+##""F3\TSyM KiX|Y fPcވ{ĒÔ󚆋:TI?6uR8T`]έ [ yW"br[;.U`.r`i 6-3KZ2`lr㊭f)IR^Zmwwwsssccƍ{_k5@G^nO?}k.]d/J0rumɉAK#"odIx >-DLS='t!Q9:j=8/r'^wWo u1o'׿WN81666::L&tRT"cP( Bu[!T[6(BP( Gf`Y`=Ya>9*Oqɕ~dBP( GEfH8*Oqz. zm/BP( vȂeU~~|_]!#5j{ 2FFWJ+B:~8$*5 Qo8"|P 72$WrD^"o!Q OP( BdLP( BP wkBP( B!!4[:K!͐; 9 :Z 'T0J9rx458$*GYM;ίӵ4mP%qkeWj;c0K B1Q*xa=ժGtlH&Ma{e@_KR|&ݭ>VtêK8JՌZ=8|Xο@=Nױ\tu6[rCbiz<e\}:G̒Q 9(.VWEbz9?nzP, 4ts-z|5*[K:2lj7jvQHLւXF˘=!U|^M7XSaT]ͨ>y **TP0% ;lPyؕ|97L|U P~;'VĶbr }z,Y2:S\yU$¥x\J儸 c|N7~RA7nZDK/a~}I$J6BNM \UJ$6GWw>aqL)=7۬K;YtJ揦gg3Hdb)qv-@ٴy$=33>δi-6sxL΢h~s$Z/\Ւ/ 05v<7k=dήb;:ffҀ<EP֌@}[,T%4&./g©,XnH55P9]f&'\@mxuhcw64y|,qy C9?Z֩T[,y173KR0ψ5a8B9?>zќFX-Y bx!r| 2; aAHoX\3e3:zq4]h<ZSj^$u7q z!b:ckԙH|H۰|2sƼ` Cz졥Yw.nzfɪt9?n:+rbd 4=U/Si>SmaG{ $C9hNH*r4Be9k!}|*2ix^"!3%cde2Ft Yxo#dfw(LDaQ_M7vGxU>|m/;]k޿L.e o'3W)Y$aFYKVZ2Fi>Sc언ҁ:ƮnzfĆ5xu=-4-vm^i0< }-aTfEdNt¢;~n5|p2ޏ|.89}UʚH~+|(W#.Y#Z&K"'[WK[P~Aӌd))|aR E[}h8uXz!_'-ac*Hv I7"ݜ1F()eFTS/r۰g_X%^Si-(e\;#_;&O#[Bն[y*TH0e-Vu̜k{?i-!|q"ǩKLBf=eySp,- )bZ8:}}}Z$0pw7C+M7v R*6쟮,1j-Wp.R9[稾X\0 /-=:&9WrJU/Kx}e [Ϲ^IOYKi 9@,Mw)O>y(tFe9)uĔE'Жuy@j)/!y_ ww2Z*{Ӎ>ʇ H%[>7N3sc0wEV`]9!K=?v'մ\o̮ǩ^B̒O>uT:=eòҟx3/ͯd] vCY3yΩeCK IDATuRA7ZDFȰ(j>JM ]D$C͈ὢmmm5MIz^j7n+?nG^nO?}W'.]2~! k[ON Z =}#O«ox%Il!bFL?о埼W03>grĉd29JDX\pG) BP(g{CΙ BP(s~ n?E0%<3h!( BP(GZhBP( B xյA2B]c"odIx >-DL߈C!Q v xoz|E|^}+O"` 7bᐨ|H/~) BP(?P( BPI7iȟBP( B PwxO^guÆ:VTHoW~CΆxA4(A1*&%ߟ<+_oV+Z+Q,/e۩>ʾ".ELXJwAxA\[d_Wi"Ϡ&^:/ͤRwu4\ĵtul,R NKD\-C\ZrSX0B8\3_]HAFr!"4;>jr㹳{(4=Hzf&cyH#wAxѡ;U4ʮ&o~B//P{ 1r]Lr.b[6U*I~ zXF 6=+0vk+ﭗ}xK- SXϗ ,rÐStW\b}3Kc<ө> o?Y{X\ڃ$F e(\i9#X U.h?dˮ5=eJl6KUgk+ /^JLKVmVI~AN(['!lu-ӗ5QܫשL'+J[[vj?ZVG`:5$:5o۪Qe*mA{^&m9M?=Su7NO/aZk+ŬV 6[1{4:vv.f8dmSfڶus`b8 n>2^ɗCd5n7kў#֙9lP)-R(TbrtJ!7v)?="o-EMUP1d&UANnhϵbŜOd1z6t&cK3W^NUzYWEBwȽ |A\-Cy^ӓ֖a !z 1[t0-v$=3~qy0hu+ӗ5f=׹L,qyj2t(WZqL6e%9cUBsq*^zb ۚgYjꗷflĩpߣwVio-ސ?}ŵpsz4jo)nV|rqRhvٳtXxQ[ {&Ah?nsm ͇k`/=ў [#%uvÞZo(.ߋ̜9j]94l7==3N銭K٭,g=rٳ<_>eJr1x?$cx# Gt:lxgKΒUI&`m]c$΢j򝳊NzOO:f_ρqyS陥- ZJ- kUm%!pd{mm}o=h;οnRe`Uv+xwD{L) ?.5B>e؟Wmc8e4뭾{A'A"-&0IO\9թ}b6t%կNոVZ,ΟMCzb Vbђ{{%8~>o d5{X=.@4.\|悖o)̣|muXl+)esxR0ԟ=`F㔥 LY_RȜe gۅw_E?Ѕ|&.{vt\β9heVa*<`׭8륑wv]:^єnJd's'QkoNb褗W=x*J~v٫Jg < MJu#[dC?7M5YCuS_''yʕzW~j6$z^vww777766nܸW^}Q2RSpws|矇q%_|׍0sai'W׶=j{K w᪳(]m'W@ۿ%,<}1 L_+]:/r'! gy[~ĉcccdrH'J%aIPڱ\ 5bi:[/eR( / tw+WBAfA A }.PQm3/B9 Dlx6m} %Dy8=gBP&rm@w58$y> BP(ʡƧ\ BP( PGSןB[l\י(_k_ƫH]LK.I$t5#g"|pz <Gp? Ȗ}&3&IEӊj΁'II.luX}YkC]XUEE%{^{V{mB!+k('B!-Q~?!B!{ϸ}ND!KL Sjzq"=ӬDےEDP-^m;U~6)HMnh}DBJ%gNEZf./o5)؉h3KL½oz[y+inENի-i?|R\oh}My>-5ov~C-36>_0f`dd!Ԡ|j̵f4b*58o2lϛםXN/ڍw{Qk)0ᝆ{h=-jVFZN8:t4(_(VSF Y?/W}܎meL[<M~^z7oE7nk4_aGM!S18:_,nuFMc'LJmvZنc5C!*qy,z4\gC1yR\CKX||x<χ5ce~u*V ڌߧg߼?~NZrG^oH`nZ{艘7-8}~,>>Sz%uiP'<k :zZ<6NEEbbśԫkbG숶^?m%Ob[G+*n^DlؚuM5zߐI8jzrG`j?UjJˏ(G~Ҟ ܅'ʩ_*IYDj.^ ]b: "ə]09RɊAiԜSlh}_IKmس%Vna<>5׆L'; :mX[: D, bfvt͎hks1oj ϧ&ZQEWV7JvYj #c'gDN+-?vZIR߇-OvK<1T{qWu-' Ny樟0|IX|lVzh^QiTފ| u'-TgNKR}ަOŵ.z`iMǵمM Fg<;5}qrf |y=]zwMzOL:\\NpiUZu|ͽ#d͞yCMgfarshЪVZ~֒i HUn rJzZ<={c~*_?/֏(ʳShV ;]~47h{Zqmȓp1qc6z/n̎.ꈶflrd Qd*q}U H|ͿGM_y7Sc)X΂Bdjztd6w?5U^a%5=ZfDE(\I/f|>Vz7;i=g)'g/XZRYXߑc5+J(ӚkCE`lx@NdEluꎊoRz';NGl3M\ cW"YZ oQSc:rxFϾ3[v|VZ~촌RJ)(S:^\\/Sܯ~?oo|ͯI>ʕ+jc7}ӛu?3ގ/Q3ΌCp\[,T _Ӎ#{mf+O}*m[ѸOŞirx+Xg봩wK230>y#nt\5[\ G[zuϑmmC+lF>[QUU 6fVUvV毦3㞷v9B@{ʕ_t_?qJ^~+WW/_~ӟ޸q~_+iql|>e٥߹s7}wo~ax㍝ڌvq2PNooU$5=:xӟi; [g\=ZNvhS }k{5jִ'?I?|G??}+ EJp0<Bܻr؟&]߹E[MhnBB!BvچGNy>B!0?IYjzKw&햘vsRӣ7ۼԶӼ[~rߋBqς„>#"d>VwBTr{1WJ)Dj{w(p(N_}-~ .CDSO AmYo4oZG9?Q~?i_z|w:!@Q ѝEZvOn& Pv獻(\_NLlDL$Tu%&YBJ0N'ٷv_oS;roh7k'`ztp 5}b_ߜ︟.> W=U'+>8Y2qpd~q&GܳW :7t QdQYJAFi*ȯ+.NzRޕ\z笷ao 66hg;9S|z;)ܹ>g DП&@=oUTFOa|F["/)5? !Mx8Ww !~ρ\QU'33#:ܞǓ]ja{OE_8YcnS1?뉚BisU<?Ы:~r,٪=%l1'kwdlQwmm5("Nܫ_v:q]L ~ϐ('p<X隹<]&OU\4([HkN695%^-@b/*FT.jt**G>W [*K 11D @m984z+w_zok487h~s;S15OHuUϸR%Rs̙k K|8D +w}>μ:\xb}:/r7I}=.LѱlElH 4\íRgǒ02[j5>s\6]ZZ;wG)M}Ə~o~ߦZl7,of:vz+6>5=:x|#doQ\|  ~E9DPg~BKJ] Z *_|('B!d(e"B!FǓB!km}(ŇB!]o>'B!-Y/PO9B)͌~פPy; !d QO\b'𐦏 =9!Mwnfޫn}&zJBnOxk!<%'}3t 1?I>קY>/$&pt:->Sz}bqp,AABPUvl zSWw5Fӣ5{rl35]S]/`:=`/('m+5=#SJ)fN]N@ӻ'˧(*x<>vpا](< O _RS1ʿq6G!T;f=J)uiRӣnw̵KM#z}lRgKcޛ7 u~*GGA Au0^8 П~ҦR3|4 g(qy,>>si'?5 TD̳oY]׌A|.y>VgoO\Ǧ)(™r ʥg/zEN|u j~64z'(')88\ϩ鋓#oNIM:YU>UݭC09Rk8 j)ZM\Hz}ͤi^ԨT7FC>lROSӂϘUj.oUjzMyrBȺ ]ZC%bR2x}luZ}63 -QЀ6;Tئ|`,ӡT|3HB.(gZhL^O\Sr>*K\[%Y@[5j?>PL4OSD|qG'PXdaK*zTrv@!OM*9 `jXೇhj]ODEo kޥxq6V&M6S9_xAx4`ƙXۧm~Ҟg)|ӌx޹4 K?W)m D_8+$ԙk9 |}ѳT,_L13^^|dvj~=Q~]Յ{o4:/{2sMÆh5~n_NDZm;\.f߿Ν|G7P_?7b;of7xc6}L??Ա[Em[gg*Sַ}ܧ=_*&&pdvjͧ$oMI5kB@$?Ňzws{P() `x?B! ])K|!Rg٫ m B!×m &&B!,,/>B!s_q?VdB!n1>+'B!,zhi6( !BHK!v"Z v-ܯ}@Q!BiS,μ\ u)oOK ;:oZ?B!}$&pt:@DĉDGӣOL^xyэO+sm^K̔^mk8Zѻ@FK[+}Zu./!BR*9 pJ)ui,RJ)53;_hRӣ82;5Rj̵xq45=Zzb̵+_b#ب ujEH͜wf: T鋓0pckS QbqRжoeB!m*5 7F$.g. ]nN\Ǧ).zx90^-<~XT!™X9+T=؜ ERP'ͩAˌl2U,kԫp7_ѡq83 %o!%O7JM_)&/ )C9ޑar"ա0zv^RopU(B66Ԫg@|XVLūK.rQJ΂}{.Un}!Ү6Vx1G2j05O3|IX|l qGi"d㥷ԸeHM_do-=CMxǡ'礞-\3DBi&/D6c FޘD=U1SE(U {S: 0L."6F~&nޯoB!1eӣAz^GW~py_\hjUI%g{r~ݖY=d%l/zY.ߊ^B!lLK}dSHv}҂ѳ6Xu1@썙r9#S&7c:ETE(_[eDN@ljNk7^[/5;drO?!^/ z7*||x}z _RҦ+XmoDU^je/N FS ^+[m!_ )M !BidёEngB!dlqyϭ(H#@'B!joBZ*_"~4w!B!d`!bi~B!Bvfs'B!To !B!mg q?B!fX !B^ŲNoVw類B!*MNvzP.|>B!Bٳ/CܜmGq?!B!{B!dHLtj=~"QxS~*7PBII\89ݦz{aP?!BiTrN!&/Rjf='5=#S5r%\<1R/kgc7FIM:Ċp^B!BUj.n@t$@__<Ms6Z5™RK(x|PDSwTEZ_f׫Z7B!%{GSY h<58 3b,>0>.TrbSmw !BnP5Þ'c'c'ʏlOaJ?Kμ-鋿_mys!BN_k _ TKSK3B!֫ y=''bU%_ݙ޿|!B~ QYu>B!B/ɸ]K!B~D~ !B ڜB!tXoٻB!B 6󡰟B!ݨxndB!B6R}!BMЛ)'B!dayR345'+B!o~@dsoMh7>Ps ! %.Sr0PBwJc롿~|} B!<%Mx?#۟)(JB&5)r)%Pcp(%9省BDtWddp-1Ɓ+PA B89R *@O ?_V B!,G͌ocϪ1B9 *d e!* Ju ,L)帊!r]4-QJrpP4R QD RIDD G ~T+ 5@LJTu]9rn>_2yKJB`qDTBTڈuz0h*ȔƐI)4@$T 8?r@w"Bٺf|_lBJ0TC ՘וi sӌpU+L.XT9Wu1D(TJ)W*WJ)DԅԤPjsb ]W)RRW J`L!HѸ W_9$_;o!BOT M\[)1l"#2ř@ ب\\D UPD减V2yWHtS`d $c9GfxfRAHV34:rt]3 )+A! (A͚nz{巇)'BQ~X'~p0΀CvO}]G"F,//~Ig_{7P7j9KfhqP ]WfP3LM7#㮔yJg3|.ɬضmF8 MSLC3 0i\1Ȁqo>5|>|WJ޾}Bo]|Յ[oP^k!B)MifO'S4t3`sιaFF&?N~f rs6r B(!e9q5 @约yQ 5dqPCBr뺮c)G SpuJJ W*Y{ן|^zn~+kp/wJ2>,\}~ꭥA!g {`q7lءw;@G޷1xo_??zwG+0o+RsD @@u 90&s\DG [4M#`r`LRzq|j+E0 c `)e0dqc69n~zhhhhћ$wK?wPwGOryyMB!ҌΈc8B,`*@!Wߛ/J!luULJ 8Rr-0LB)%25v92&t]sJTJ8ÐWG# sL3 p,Lb~^7_?=U07CCLB!l'𧸟l]!LSC*ǟ.X8l|&g4."r@@k#cq`\u5Y0'PBg1 k\D2+_,aHB<)k@ |No>*FuxK:'f"BH]97 {fVҪ[\s+i{iyl}z>X{62$\798D ֩A."Ztp8J390!,L)8r9pq2i˹.hdܕ,\!]߼| 9{A㧋K{pkM|-Bٯ/N~  ܐ˂ .fK£lΒfCyv \V9G+SACg\ƅdY!,R"j%׵AI934Dz]G()9r)A),ue(05#ˤ8뺶#\w*SĭN*%Sp i=*G$OzʹsWwZ@!=;vPOZjN*'l]C,lYjimͰDfCyP#.-;bB!{:s#$\2H ] iȸc9ekf. u! @Y\] < wu)[p @Ći>ŬS%u*?چB*M/}\T/ޕ\ !B=,'4)4'TJIhm8}J.Êi+_W5]H(2X9yd`O&ZJsK!xAv\pq,4Cә %5ԁkvva·iBӸPuD^B!ݬ|+Mo\ :`8,0v/eҖW2BpP87p|$sp0B!j|H%JWr9 ]ct>qΣP +-ijyWqM Ft ;BzihF۱*Z"ʑ 4JȻy e]q먔P# il5kcKno߾Yӎ? p IDAT@!|> hm[R7A3 ta]7 y;#Ƥ=zW ˲rBԇB+Edy!T uƘnpCRRԘB2 Y]B(`JR !Ү s}J9WHi Vҫ H 6@ dJbum1 n賏 WLZY\\ 5\\B* cF(u݌@HW&e=8piŏȕ yԟF !mПT9IM0ZZZ±"Д!gY{msp֎1`Oρtzɑ`33tR JZ#Vҫ&5˲H)@:p[B!d+|Jr!BSg-;+ WRG4Gzx@`Ka>}ciPy+\M!0[: E>ru"㦦g-[ ``-cǏ Bbx㍧zۇB!dj&˟~z@PIDRJ)R(3bnJ~Ox|hggt\%X:-l+L[ZJq5 lWaHD!=^{gz_oǏ|.?uΝovtth<|hwZ#-.  -o[ )RSqe9% !9g-SBOu} BvX> FH?R !]WJi 6CP$l fJˡP=\"qǟeݻw/L=z#\BТѣ==;w0+C 4=s Wَ DDT ms][[[{hq]ױy>Om QN!ިG|Ny>H I>R bJ5f@^J.ٖe+_9vo?>o]zuwww ;:Ck;"g0cK{t!*)8\ײ/bN +NBٵ(''`ip\ƘvG G\!7&$D"<2gG}:/biiuuL;l6kTJL~nv¹l>cKtj QIJ2)t:{a!D&maiB!OD69JCkp4M;oq\WpCWHCTli3B g+++1ttc v_˙P(k tMS\wW8^YYb4΅@qLA&g:|>hq 뽫u@! DqtŤBdDL\CB P( <]; 늼(Zյ5e2;wo?^Z:L30@o$u˫+PᐁL )8n.cvutwC-МB|uބO^buq  Ƙ:J ;08v> a t6]]]^\JgG+]]<}a= pf29]AgwGGwp_@..uMd4Xtnta3dr?sg׷lWƯC!v(> }_*W瀀!04 Cu!Z6{{+]!L=LC=^n+ tCB8erSGq|U˫k( ]ȁG^w@w+ Ls@vwtv^B!ۣD$Cq?i`[5#4qف#_>V3Ȋ[^Y|켕4κgG3::;;".]7޻Kh/n߾-#]۶297j-v~G~ݐ&#ǎ@O-4V>MYNUy>40O!AXK@q&`۷dȟBR2#G%H$ Gž|Kg?oZv'&{Up8 -ˉ HwdC 9إf\`_O0hZѡ=desF0dH\V6ͭf2 5gW B!daQ?d;<pd:㈨Ɖ|yϱ-;4xh(zT9_ AٹLDie٬R (st8 2a<1ޮH01Lp@gVg2_ __|Wx۷?'{;cwv8\ M8(e[p"x8dfz $gBH r,+k[PY]0~wz'źs3fr>t%=ej O!BnAl!/\}?Kv@Õ&jRM?qf29pokzX*owwksd\od&t,;_}n{_0p;__[y&eaS ιƸYֵ\;{虣if(m[* PmИɱ3bjNCG.;xQfʚT+}jvB!YƩג)h^*gj͐c絈p:®Q\෿]xt\;g ήpDB(\.cv뀫ʕ kf GD&`@[ǿUj_xQq{0b"-rޘϗ Bi;g~+??~_ϯJY#?A7_?=F7߃+ WxcfB2۷K߄<[~J2>z{巫so$߯pݏߺ|in=|inm|թs/K/ ‡7୿ LʲmMaB˱;"pseiEp;_:::#K+}gz0 o-;'8h:Zg2`߻3BXMyv<9 B!da!Ǹaa!JOce/[ 5Ӟz^[8~+Nzʹ8[研4Qxux@E7F[9y?8SXU!y!&T -t:DJ)۲k+K˹\440 P0㦕 [Gz9f83i ۶n;2\#:j@G P"{zy>+B!Śa~_:ujK=/DK'_[\cSe5(֏_x =熽1~q\ȁ1@!rdJ@sd-PoL L&{zz8s){\Z]_Y^C Y$/lg0 dJ]4q.h\NJWyd<4xW4| C!{hS?x,\}sZ[u]~ 5 +St:ɼŤr_,Y^^>|޽{ɵۏ=2㥇5L<|h Hg@ǵl)؁JJwyP$g9יk̵A5뺚89MEBȞs~WWW{zz|e{hOEPƀq0 3&I cZ6xqy{1$s/?oe|tzMS|ueMׂD:p\[[s]5{+Kz{:z{WB- fL4@ñ9`f|rkr $5p>QB!;_~)e);IH*H= sjIM_ru-hi`DY\^d2_Z.oq _}xﱮ:#tE:iX\^[?kfh\dud=pL* D"o6_uB!d@@UWvJa{hB b#gJ)MqǵH}}}Ӵҙ/=_]\Y;Hf2p9ټ#PWc(mG3usOO8FGB!PWK}B)%PHPJ!(j_(map/ $`ookK)!j=~bm-8ݻwA# ]y%s_;~~WpHq͑N޶r5MKfvus;n(`Z(@~ !W(ROZO*dL*@L Bt=|x`W`@Og25p#y@ 4x w_J_o0tn~wmwkٹX!i{ZP0~Ww[r 5æMC9,[59Rlb"X= '$Wf2FH] \ܾz 1 7Dld/IA&o5qֲ+m+UC O.|P(3#FD/~Gt7YNLD'L$YbOL/3Y¡8'3W+-¢eK.]O}4ظyɂՋRu5939v` \nLpa<0Yn2#@0S`$itbddr`0XO~DL25~! 9N ''c,Do Ƌr, 'Ϝ\Ph: V/|vC׬3K'> ד2N/^ q bArd|lB_ efC<94|ႁ7֯IrxxxxxdJ06?7/GIq8NDb$ ²H&jJ(4˖XhFQVz?'+DEb8b߲$ˢ,(J$Q$dN(3IfP@ ~|`*+-<'IA`c~Y4:.!I Jᰡ`0*- /aYp8 [ kQ0 AXx>9saҋcyh!+ @ԁ>#,3,$,ʲLd aeYVz9N $s3~:6~j_Tf 3&CaUZG.^ >$I2&#VdyxN`HL'C(P dfA Q^PSNW, %Y#G.ﻂxIfbE^"#I`&Hb'b@eYf8C9g ϯ'Rgr($'G/2N/d"Z˭"V~Qd`|?6Rn1VT d6˜,V.]bL9 xIt@) oɂP8p( hxaY @  â@8<AO:3{fO>/$ $UTsq 0xȉĥc#cAH%Y 0gWXʹ$yCH2Y[YtAt~bb|bȨR%0GgO`Bb8 7 HYeh$P0,K\ynSeK֭_bQXb4ZyĀ( $qѥ#&GqMFj+ rD9D^0V]f*88yc,3 n-0 ֮]k ~"IbrttTF^c 2ďO'ʊ%KVuk r08ĥs0131^B$a*I`plӑK&GLee ̌\d/^jq3X+M$I҂jI*|J*DjHɉ1( !M&S $BPX6,+T.Zf+-&OFf2\x$I`8(K'~D0th`|L Nj6Z213` BX0S5M~ IDAT|(h1 pIU8F l$2f`2#"7!lu_YjT>B"B@Ff Ш?4:2d%^&'8rql0|p"39./A9 eX>9{q, L jadY((PxK0[+W$I8ɲV/Xz5+ F(㉗"e? y7W/K%I Oy61rİI0^46:J"YVXfFdb$1"2ejP'` 4˧ 2`.^0cLò,2YEq% , (b2pb0$n<<es%8zܹK|bK"1 C,xMV RHdh Iр,[d]dh3pd2K*++˫FͽxTYQf2(ȲʊJ7K?>YxNe2sa`BVPyd ʴlA1rŪY3gc'ʌfNd(!0 17U0cu_VK(qd84xAؘLFL"a CL I$I@ TVVQV^-LfF3 qRHD|P yqRHNyG aFlaU 燃!I0CAQ&`y1,JLek7\`ɺ!&Abc ceV(c1Pxa)$PPy^ &8sLfD1FqLfGaj2Y1~"$I""`EyHaU~0  D$2x D%Y/טMoReYXEEM&bX,R~(?MF +$pqŋF`EW>*vY^z0>|XʬU6\o4!Yqd)Dc'sɲg$2$F"3rxn?|)d69 0crKy _X+&e UK%Y8VVnYEAx?~xUUj8.Wĸ&D $KĉAxYfWov.Yq 8&eY&%cL"G1bD2)cq!υՕ.] dhܿ;# V_hb/ 2^0b8˲h( cD…ᑑ˗P($IR8&"܇ Pvkeq$)҉>$Y"KˑHLDSdYB,Fȋ5K?9b,̼ 2qfl6[-&Y$DApB8=_Y~ Jlǚ5k֬YgCG&\+U 0>pBf6ʪLSt/q'u/ 0,_^sWY,DDʸP(/GOD$1R:}Ǯyg~Hyt_<7mLfg;/4TGt~FPuuuDT_8HDF#AhYhX&EL4ND;:LZ?Q0If8,d""'"Gqe-Q) #br.lR5[5gg?zy'N8cא@ERZ {Ο8_zh(@ɟY5>J~WԨ":֭>CD_D(]>"r|؜[f]l,3@JޮX/#O_3}[7}@Ffixoқ̏~t,>~ ?:'ODw(a825zqtO,EymZk7w*#쉎ܹ󯔮D;;~6Bd%jHM5_ۗx??q[l;?;uܯN`>+;񵜧dĉ5M7`>~(%s_ f&<fbW裏x~V^=E( D3́q>s_իWϐ.SCg,3G#v0y\vɴpTs+|sKےWa"dYv˓fI/(Μd+lNBUCyq?lWsgkWgR鯶do=֕[ 1/N$)YBוNlYWvyUҊ\hE|-vݙeoDUO+wq?hOwy܍}{bhWz^wDZ>[*==Jm"V754%tչu(uirLJjhmjؚNg;aGZ{,^j3ۥ>Ǐ$)u[o5u֎=>Ǫ$ZNmݩ Yw"0~E#X!co<(wFr46$5lmO)ce#h=pvNJ4sǰ yi/2cLˆ4_ _8qbӦM[lYp^Cښvv+z[u(Uʤ4 %m^o2ړ˿ 8.@`0 /0vڵyEjpAngg]-*9(ۖƄ~ |] zl%svGө1Ƕ7=i_ rAi\Pn۝VJuwv5ouԷGkt\_B[ގcJ^{hVqmhZn7SOٶ4R}9`bP( ~?999111<<|١q u {agoLW⡇k=~(*e;p]a졽R4Ջ/x_x[%KTWWWUUY򨲲22Okkkp8 }{}4馛Ξ=+V #_y򣷧ݩǏ_~ hCWfyn- =ŋeYξZ1ޝ~Aj 9?  t7Ƒ \0ӸF-կ~uww?#iI-8kUM!7iߤ)iCci^:R\-_0'~S-kw*璸l_UK^+KaS뒥)HQ}CiR==]TJw]SWWfy $Ch;Ojz7m_oKFmqzwׄ&z[Tߠg[U55|'ٚ(aXQ|'86ڵk?M6):hpp]9}u.$<.{=W묋y1͔ehh?9! 0}0O9nvg䙙3ۂ|!~lh^cKYG]4o~ o5*Ug@GO0J#wHj կBͶA$=G;w":Urs)D˒Ejq6nIzn@u>&wuumyUj.n\Q QVͼd@Z*(x.gݭS~M\\mIߩړ/x<..c-;ъ!g*ؾ]зEvtkvy&\[jwzyi6RLKdk¿ty||`󸚻bGCkkZ ]m[oO籺Z?LTh߉]B%ČN\/wnoOkފhUSw57Wߓt7gY2} Me/}rzf;YUG[O}dѭcٶRᄉmi$~PU lM{"9 g|'fʚ%5Lt6v83$ٴ9} ̲hhSJypԷVGrm Cݳ,>zFZ*H{5]ͧv4|Q׫qۮ`{<qkGnWh" e?ؚM]~%z`Ba܇`C0a|^wk@Ӹ_eƘ΅@)DPڶ4:,uJ}}}JNRLUHic@%6Vj!Zi&bkm+%m94Zՙx.2ִ=xJ*hi:"%i(ߟ:{Qȼ~TE|{bѾޖ=S*#]L=ZɋmUio'"g%J7yJ%'nsπo Q,mP=D-FrɻdWyVQ|/`8L'¶լPm36"YiPԩAwtA nebmSO}=iiWvyή~YN5O&>mitFj:4mi^HjR=$3U"my̜V94Zқx]ח21*zؚvP \.Dە?[{MDAҕLJڶ4iowvz+FLU:իD5ihi7ۻ4cOc_s,j:H:͐Rl-W[Gќmq'.ADM{;bO)v97*Z۶4R{;um '6CkRx"Ύg2194 m4Ju%Rnw++'5lwhҟuֺhEFGGCP0~rrrbbbxxٳCCCw׏%:-y͟71]垊zԹp}/+>@ᅄW6^y/~~ɒ%UUUV t8,_noSQo+^dk!%eA?܇8@ q^*셸_/ |9e1syDs5|3뭨R|׮]\s6+71߸q B]qMMMmm)zs)x|-vKcT+K0Ǹn馳gϾ+p806)TbK8/ zl zޞvBE2O~xmB~w޹x,VuR߀*vw9s_e\jGC+;.iODF[nWy䑇XH/D}:v6Fgƕ?z[b? T*5MXV=1nwӈO2K]_U.eqE]v{KoO+Ly^to m>>_6~l9D>j[K}y6Mqnnr46wl裎"QW^z[{zt7QFmsIJHFWIIn7졽ʨQW`Mms2!r%6luw7y{ߠش*ےrm])mǫ>0ִCDw&]a7wE&9Z}ȗ-[#ٚvOoOWVMIִ5ĴZ{XѠ, #N Pj0)QjʙOݵۙnoo|ϓVjmh#==nz[Dsπo QLcMZ{Sd#]t2&1e_rvߟGC|քԠY Nmi^HTz;udT~DHlm30QLePDSΔk{&P4:H?O[;4SBa6-Օ0v:6وlM{;5k>n^3Y[Rl[I}_o ePek^ߓ1NM9C^Y4]55ۂkFGGCP0~rrrbbbxxٳCCCw׏%:-y͟7QL E_Bz#{Nr%+~K,ZQeee%jm@_lhg4p_ouuu5u{ QLw;Y~J()}Å =0?! "^`C0!}>''s<_|ũ1o.T9 _~9u(s2K6/_J +q>)IS!4Œ9svtez4N8s:<8.y2*~.]y39AIv%_2s섣Ôe,y2%u)pVǔ‰~5Q|Gth;s( _f~b҉K:Eyx#W\ay^~OXtfLtbt+RTʇ#\܊Iw#$TU* T3JpdY}Z*9X;-X$w{cF"Sf'L/cD_;6)yuUydɉٵ*GDκOM}gةWړ p%ɫg:`U+sX{IK@ǾVjE:qh2KT;k8|ܹrcZ1Fi,~+_S~-Plz}Uطkn~?:1FP;.J-Xү*aOgz_Jt_|&c~t˽Ν;?e-vv[0'9m6>Jk#pp:2=!J~L16h<~bGS7z衇zHDJ;3T;dꞜo'>`SW뱜~q3c+a'ԂqS9R+ju "]E*NgwM3nzC[#'}X7HqWm5'v"~:6駟&]v˥2n൧}Jl}b-MR <].jIUo Xɶ~k_d.q $͑=˲,"{xljg˲,~WQݱ&>FwJTJJ{ ʞ5wEhJ?$"u|V'I=I{J!S)AZVcE"eS90 s(}j:-I(WRsqLCr >uUi,#_dy)z(T|F2ַEDO=qO=}[JFP;GLk#eldo)qj>o)z}KzdNb3Z%o"5"qoe%=E5o 2ZY=p,{OSALhCtA^t^{vȔ=9#H{֒tjڛ}KВ Qyp~'.A'D*h4!l:I)z\?|î"=k>o߾W>VS="K#d_'C5+^LmR\IW6GŠÉ~qBZ%o<D$bڢLD$I2SIDD?^ ?֠^AW2YW~5)!u3*eQRP͉|LӮGPHGډj۳g޽{Ϟ=M;TI;dʞok$\o꽽aѢS?TO8ʱUbV`~RzВxB_RtkNs'K|֯l7?Trh Ko$ua>+Rxg %O&DD>/'$INˁ_}nmm{g}= ɲ:`0o`d_RR)ȒLD$K6S2ڒ ^^O9mtzM;d!>u>LoI/ G]E&C?{Ier-a^+j{b5Mw~}1:׭ݟCthIrh Sf}/ziUPNmPooSIŕ:z˗ADH /h4џhVQܺGe4ˣjIwQwDD孱ό)'N3dZFSrI?>(w}D裏j.CiG}f{rvVYM"R}7v=Uh?;1?~}ݺu.QMu֭[S{1r}r5?W^M>J7[nݺT*htjhU"riGۇ^pjOŠ]xlOw?>H$76jt&dgDRNeTNݟR(z#{W{w{(AcOwN9`5V{{/+g'ԂM׬f10}GDGiFGGCP0~rrrbbbxxٳCCCwW^粅agOT=w2% NL3ahÍ0s96mڴtҔ4~迿:OSfB#oY'+np?<JnWn,YR]]]UUeZˣ,KQ{X&rv_ǵ.yD79m@O^ǛTˋo-E7C)ͨ9fI`>39VkJq?KT:e^ayGۇ<3m~?'“l=|Ss:ŕ%c~Ƀf`/9v™c(;H_H诚Q2C`/9v™c[3JlCK;̱@SHzFmc'9V__Ԋ4׿F#ͱukIz3KipLw)766M%Y}#شiӛo9e6oqc':P<OD_W܀F9-ZQbhCP$r+N)W:,?YƼ|h ٪4 o%g/s~I__W܏a\>s޸C}f3g^{2{r}/XĂ;?tBQ7l^9cNջ7\y7\3|_5Nos$ZRP-TLW3-?̈́nнA{_= 1'\DIlF"JM=qپ}ovۓ|{~ێm#_i=)~~뇎vkչp+UFb"&RK+3e*C@ah]=];l߼AsLiשU5Sʧj؟+Vo:ϼsp59unI(vm+N$勁v|qd2ޓ{ؘ#='v)z#YiÃD6e4x*by`dτL)v<Jna!0kW_kΙzn9|ߖ:7 :rUew;_: R~tշӾghr""lYDkL.VsΚvbjRJi~K P(ףkw}_onK=O:)k4g_v6slR8}Ukw?D\ECru"~5'߫w_w^Kwv=|k+wlߵU?tt9^{&ԁ]V{Onu`-\~׾7_w}k/m[N3%3v;&B9omSͻ2rŶW-;7Fnq;}p ]qͶȯ_e=[TӿxsB&瞾.۞+xEj3/} FzD|ש4V\{8{Wݰ1NuIXu,^ir]~j__U?a3wFc;G$6oT#v(-Գck Ҷ$: #{ΰ"snMEt*?xد<=N2iKC"3Ю6mjp#/8}r0q-%L{i'jI^zi5(Snm=;߾7RM𽷞uirn=H4)ݹy`4xTWkK;OI wv+V1_.PH7Dξ;!r_/0-(zb{0E4ۣ烑Ν9=PY?ۍ?u*M;ݩ|<}w 7~% \_罛#CORzK}ܴy 5FigLwE;{zJ{MUO\.vgWČNx"iT-M֙D|Cz|2TWU%sQlZ=ow\v7F.)D)a IDATh-A͙{ss\t^ eZlxT}=(Sx0=aἧӟ4囦@D\Xuqpȱ77&-b-} "ڶ[6yUݳyjN~̔SSNGECt-7=;*ޓb+o~hqEM]b)yw+Es'7jZA._2.kؾyƤL\lyh_w)LsKf,v.sWؤB-9)6ʶH9ԔP(499&&&&''Ϟ=;44?~w|zҞ|[t$ +[컘w?Lޓ~R.cm邖,K ~]'_R&}ᑽp!a/ڸW^_dIuuueeeYYYyjZS}~?W=KAD<TBy_(>^#)Tvϩ9Q$(穤O6w}.LA1y 칤J/ Di04ҍOߟ^Ro~R<_lxD܆[(hə/]}~}!96] V:骠Ĉ2tK_cOlip)@_W;\ʭ UG+-eYfO2ť]ӕED,qLT>2[c*<10rhFR^iԿ5s rxx$S_=w~岥nJ mYɲ,tm&LuY?[۩t%4?\OQY\bU%BОJ ڧ/DN+(" K2c <<(Ei3a")4rjhyU 'NfT)mB?U$k $k51$}PC/!ĉ \.<ϼRh>1PMLw@QBJ޹ɲ,ïSJ|pj&R# ՜Q8K}F2wu"O,zp19ᘢgFܯHd9wD IW.))΋Lq 8>s x҆sVƋΎDϪa)'#zēQr9Pf&'%3 2<Խ `0*M{Rfȭ 3|8OXɑħ̌%7GtF1N䮃 d՗uVE-YO^WXq]yJ쨮8*W! eZljM%z+J9G?TUgȟC%`f'.;T;NR8ca0/:[X]ie-+<2rILޛWLt1]L%o1DJ="+KN %7L_~?ʊb)KsζjE<@a6xXʇT%G'TY蚖ಒ.O2AΌˑ,iDɁurZD[>im$t4 oOr:bU)Z =3aFwJIdYb~-|l1Տ]H831DPW z?XlhA9"RFl6WWWeʊi=+L?gnWMLۉ0%wk XbN iFcl{wȞYW?FjTq?2?%NVSvWNܹhfUȟ=Ǿ$]&wr:Ei|I{#cQ2KAJkEiXf&`0{=qBy`0+Wsw{[EwﺍQgHjxӪ 0l j([dea ҤK @t ddSL#^t]\XDj(ɔp*9sWE5_Ԏ+z*p1ЃP;譝jm ۪J: Y(SH@eQ%uٖ}Q_{uɾu҇W;s'Vu`N~ kKemjO6U˾acpm{wwsg2ꛯ_woߜGQd Ap៲Zf(EeY=<p훑AK+k7w\m9}n7ndǧw}bֵ{>71rW]6v~rvS9;9lzOwo\^^F(aRJ4M8$H(W}Z5 <ϋ1qa:D '<|v.k 1ht E},[,ʟ)DQ$,|g)[k^ds?Eq>EwJ\\}V~Z*cd!ۥb?r?@/ژ9gϿ<]jZάpR׷dt)A, )˦EI~CC(}ɔ(/Zk*ע?~jB?ԋrNN>~|R絶KyN<"tF_+_U/O{Џz: ?eo%w _E9}NЏY音>N-FYk)[#t'Ƞq.5~x?g}+^+doOg IENDB`treesheets-1.0.2/TS/docs/images/screenshots/screenshot_sales.png000077500000000000000000001351251352107072600250370ustar00rootroot00000000000000PNG  IHDR{7r IDATx]w`E̾B)PR-tr6.P ;)V@SA< $F$ɽ+nt[]]]UUUQQqK*Zk T`6$$$444,,,444$$$88t:GqUfIK)-///+++.....>{?,J{qփ-X[ALŸۨQ#a" e2w6._*///..>sLii颬;N6b0IJ՟Sƒ cBAjp`Qu0+HpkGV;|:)> lW7_ "Ȳ~;p2c܋/<Ǡ!%r~R]Ko}+PPziy Sm%W^fV#rUa oOneGa+fOmWn/U4LB Jj)2ҧ=ľ|t֢U/yy41+`)&=h*(=L$$?Q\yCL Dw"M9\E\%.TKr@E=pHTDpRB$g:N`{-q].Wuuuyyŋ e̞=ۇ?r++TS)E i<VBbJHW$Y%Z@V^-Jɐ.Ht"J_|e f'`/veٽWY6r. .>|Nw-[+NInQ9\pfhUo})'r.Qkj#![#3K߬jTDr~Psz}4IS0g$¦ظOP0mv"(݀f\5̣Cs?(kc.F*2QRRJq0a5_qJaN^Vv#L2yTm]v 5CB@ ($1ꕫeOɽ 9@fMKl$n  [H26}\΍-QX^R*Z(+ N'e8tܹs>ܰaCo^ltAՕ.]ھ}Jʵ*B;/~T*>jrK[JjEtJ ،Kt jR૷ ,԰162q*bu(HـEvݓ̠R8]DaIuaݿ~ T`m= +2~𦴱1lL iJRJ eIWI6-T1/*nf@HEOIÀ3Wjd쪡QS@bF:YC˻۲uͪe?-;xxrm>Tsծ)T[2']K!NSvs2u7k.,,,88$!e4ʺWrA0d#՞9u]`i'*b5BTzu9˯e[cA?|[^uu&Ux9s &iW1U/\k&4L=0IJG(3uT}rOuދ;?.ѻ=[ܮAa@nkuJ ȌzUÔu)(@TtXG-]we2W#l*ѳ.d4sFUvrS'L&˄VPg\U26h".U3Vy>Mcuͪ-i㬬`!p-ꬲjY#;56.*' yUUUiQ\iNLfnuu5͔׾'NM4ݻB1 QYL0gw<ΰ6` F<Fy[M&^#p܃!9n$/:V.LџR`GacT ݮ8`3J׮5 ךbvPQ[ uy@ID] N $+?LZT*gBH)LAm * : ̺+ǾZ&RDsԝ!:.U9&LUuo!UxWïP]an^eji4weʔ:=L@\Dt:4\.;uȲU]R$S 6Ȳr$gqaBnPR0_Pa587qrͭi MѾGM7b^LlY=.--xag^, k^ay5 ct_SSe "鲿$֤I#FO?4;;oذlʕALmʬ'A !eq† d!ow|46H*q8MM}(.:{Mr#Fyb{sh ").n?:TL\\\Tإu#5%׬|Ń[)wt֬XĤq+?pvѹ{Z)_|*6 D%uDUXЏ!m("4S'̂lXԴ >kY 9OMnRywP3s8"SSofwEE7tLJuBn/̜mlf֊Z蠃yR5|WS $@KkRf1A8TTΤ*˽ FP S옠]EыEeLk 3%^*..a_b%A(^f6ȠG~ Veu=X͓ZxU6(kg>:0/9BjSgYݮmIfd?65SRT&Vׄ*--ŭ[-*c3d ޝ"6vi=]wO:ƒOzfg9=c6bLI**XQ8 /KOvҒ6qmj`C" v> ÍB_0)ЂyOMTuNq%S﫢h ㊖x[)k8ltoΛ7/&&4LzBPp`;8Z\w`4'7^Zp= TUcm_ Øe c6n?v,c]zAhoZ{ǶV{L|<5+2{yfg 2r,IxsMB`lrO?¹Rw \ Og1px̩i%bvWOUJ`ovxy_iRjm(x_żXv1q9oL]%lԒ_[ٶf<;[ݽCa`]S4`pQ7D _y}nȱne&uѱq5OOF`9n&i*`=UOýN#&A13VM09%QO@/5SnWޅ+T Qc7I"$"I$IqQG_ſ81Ĵʝ;wEEEu1((r_168v^hIՖ?"{,fG&_gG+LۭcqQ!!#FߏBu"ndEgMiXi͖]V/YYdƭ\FݹmQYO,.Tu9 ԵRj~ [Z @ Pp -f66jY"@n=Yd4'ԴQi}Ni}Dao}mIQ!@K {wl `Ɋ~êWfy*{T67A䔁G?5m? 2> 4?n4ʰC}#w]f_YJ {uH0o;o\=[5 rc}N)m`:e<֌)Vf ֽBd͘?4b̀ES2dq ܽs4BȺYDYS21L5Z:Bٙ{}g_әٯs7>uѹ` yWeɴ6NAlR] j"E TH蚐 "l$!'}Q($E@]..aK!9H!갅K %!a#L ! Eh}m4.'_ǟ|yn85|(BCiHBHh Ehl u@Cr!M#WC{c9XgϞ?7СCǎq[oR+-6j GfZ 'IoG&еG*'O["k?vOߟzEc?6i{~2dW^_T\Tx}DŅq?3]M&L6j#ʦM[4{U27?s2gdjki nGm8kޢekB{˗t'k-++kZ&f0Njd [l eOwkt*Tl\OLckWdn#Ν5?Тeʠ3MsKw wٱn='Z$0=r(S̜6[mlhЈsfZ @䔁GϜ6(1u:uK?෻wP~>'Pb7|6~u͓S!kWfuRb{W?}n.~ٯvW7wx s`ތ);%E:eX睺[ƭ?h2u9Y%EgٺDL\wƔ> p`N{u+37#ǮQ-[++qg_4pt ~Eu s? ̄BMJV A!!vի?7o޼ sJw[4`t `l|Sg*=:>]sݓr ϷwA?lPB(U) 5`-KU2ϕzO:ۇ]?ίw=cXg^ ݪtAųeqx‚w4nǒԞ]Y~cTkVd++-QEe ^zw9*V~3*>̻h IDATsˬx n=d;WbPf9c q³X!iv H߰j)wꚺ>'971輸NRKq~u릃NR1e^;yn3Gs甹xPOY=w|uπ:8|c>?**0ph,f_FwKL@nF[u(qK8JګlY-!!! 6l111{yϝ;nw]`/÷ց2|f׵Nʚ ])|-EgՄ'8=2E^eoeŒ'6.~?H%Dr.qO جiiYm%KT 6i}Eq㭷ؽc?x>\v/ޱP^[vE&_6JR]oOwosu9YLfMNa=QΚ`Gp9ؿ A *lAvf0JT6[펐hGT=*刈GD#QAQAH[t=*ZV~@~h{T=*鈌rDG9QAQȨhGTth\#942:*ܴL[]u\Oϩ^֐9БuT)ʣŕD1qHqbK`b5Whq%꣥5]{k?ryy~:9'l}u̵/8n%C%|%Tol\c4H`J6ͻpKRn˲1۷>>iV)Ti/z"W^3ййg:SOWxsTPtꚺdž[>m`{=QDޓLl'ja5o! @ձ隸sw[>HS|{d?px YxS?b?V{`,e硃yxֽm%[1YX??wtH:Ukx{8<35m]C+X.~vKIml`ѩ[S{NV?;o}npQR\"tʲ)Jis=(B] .ru}a\Dx`7Y/X9_P1K: ]قۃ$wG8"###HGxDPd#"*("nW/t?GxntDD##HGDdpdDHx=<"$:^q9XTƉvq kzwNyzO:U2GvNu1*=,6]ڞ{0?Iy뵹\sQ gy&xV-]B)fb).< h[QYP#qeK$v|i,R 5V?(jkVd>t;;& 2tTdwTj.{] I?gun(.: B絎oZ2k׭bRdȡ|PtdGk>fVʇh$0&6~d3_2I;t0`Uv6S׀\5f()*d:gXr͓S̘:m( ?RQna9kWdR%47voexTT}]^ۥ'QwX\xRZM v ^5vvkx݊L&>*Ji3dO)-.<ۦa..xJiF6 ?J)!3dpY" s BۖxReA:NCV"hDQJa67k Јb=AQZAگ?ea8*~-L\(&1-bZ&$'(^/4$Q?<&5KQ/44,G{%vнwwݨkFzuݠ{;sD(/sν+{zuݨKF]{7;{}^}eo2|23lFq3UuL.0io$<_$l51g^=\%c:.'놛?W:sꄙS'Z@.͓Z5lU7\ӌܲ飁۴Sdso?{]/n4GEEw>aF_툯8Up}Nzr 7Yr ۩[*1e7o)f<o--;J?օ5ь)bo4&]C]"U2cJƌ)/kϙS3fNuKKLjeRKR}åFkpu˫j81eH{Knc0Gm2s'ju 8cYW7AoG٢Q/Iye eH@rOjE2C*}TfWR@Èz-_>>5 3ss攺̯P5_t**#)wз{L__`J*9Zd϶Ij"G=,)ZLl~ݕqwƚ˯`LuAR֯Sl\z} ω hs%.Ov:vEk ;har`?hK/nl\q S߳ڻ`,pڢe{S]Sz(?y R_ZnP0g `*</@)%qQOƺZ/)*l,nA"Yr wx2ܗWCyl)ɎuԵgO|Er])7=b{v™S3Cq>g[:px:# =(ߔF*9 xwv `jEw#պJtc8b̩YuoR)'EӤVbo Aرܽ#7Eg/zJM_>5YT9؝e0q dCe|^=]ϯ=-S(ohEt׀[yF, Ise$* ʊ3g?~|{]ynfuMdݗ d2bĈT@yCl2%3Oc'-1r J#TSb8Sba\X&O9/-O&&2'/_>IҏUHA'>r0\[;Б|#w]7>;oѠ_[ʴ R #SeT8 MR;j('7 wc㱕D8]LDqƱ&6\DGOi\m#ZWFB>CՅ['֥c|||tttTTTXXXzꅆrVxX.] }+eAkTx[\ôHDY֤OEG(^y&&<5_}5Kn ex摉JQVO0 ~iҭ Z/uܩ%p]Ӻ ZPv (T[L(S>hԌ]Gb*hAh-@00Cjǩ{j}j |Q@uƮeZ5-#CP1.+usCGƸ(4ݰ⊀:t P>rs3U>vsѼj(7;OQ_*#+f8HwyLޙ,^eՁpAcfƮJM-?ByMqGH1 埋"'Uʅ0"SӦd -3|~-4ԉ1(@$I `Gl0HݱInK7DE`nV]u T 7=l "G'ҭ%%䗜? PNTKP- wP^J'*ʩR|/eKn}}ܽ`^Lg7 OjIu!]x !( i줩2/λJHr3Q =*xә왈Puq!%DR$W/q?/q]pIrOBK§0,D qpܢFPfljC"ƥ>T@H2*@ʛB|7{W*]yylh]/۩ 0S%H7Ye-ʾB,#z'EPyNS4tVaRtR@yZ!\לvɷQGKiL}aYQʍ]K _ kzUǾ1)y"d^yx殮IV/^`N#MָSڴJ*_P'2lfcR#sNupD&Ub%]S|j:hjqAiT|PoD/6Dc-\ٯ}-]+/pJ Wc^xyF8U\hu0Gϊg^|1l%Z^>i <꺩7hkƐ)}3C@K)0lU-X`+ҺZn KQ!Fm^]˔-X`+ 铦vଲB.$,X`?*U"lBJ9͂ ,XlR])7Sׂ ,mZ|X-StɾX` W6$I׵{q6o/YeeZ`?8%@ h2Qr!Sׂ ,!=sPh SѬraw}ӘEϧOU6 H3[z{) ~=F=LoUCE@ m.:szʍUY矻֌(1?ښ I;*wAl#ۢ-XB%߁6{3(5ޫ,S0x.ݟti@.kו DEed)W " W [vGnw{N.Ok-ڂ!*lm*Ss.T| U>DMszy]6GBEUy */U7?yM7eLJooj_vOfW)č{ˊ]ap<Ц^ua^m:俍?@Y{ 8|!6cq'Wn*}=a(/*,=nbۏ'r* o-?щT_;&m豯 jI_;ֹԫ~ojP4|A̜Wr|cLouz=}'[ 5< \sQaF:T<KꭏX?wsCqa>W+;ĐJOku1+>g_]Ь!Bp'W}w~_/d߲vOyjX 5b}_U$?P%MDګ{"-tF9*&c1ͿuKɾ}?Cŭ&yobKɾg-aѷJQcvɾ%6?=#dԭDǎؾ^N%d[O,I&&=!56>n~?. K9ϷG{! rDvƨ}ϰYvaY%(MV^^I!ۦg쾙b?X7ձ)Z0,>g_9A@l~M1Zƴk.Q ^宜g'Ϧ&5vc"/$?9dEe"&!$ _ kڵX}% nH"sm:)L +wc?׍%顄I6zAֱwE;哭oz,R$!9 Rs\ܷEӀ`\o}x8tȜ=ioJOx3=kƲG<5OԗV[qSWc2`~CߔZƴk3vUۺ͋ς+u@d@{ 1PٳxxS#YlFO !?C^?=]<^}ܒ+  ұ}f'Ҽ?щ7=YqM[%@Me+  ҵWn_ؐMOlݐŪ8ő/i'$a뗋SLՉ?Yvצ- IDAT@N(nBڢ;HBrk^״ռ}IBrkl`d{}6yL({V7Ɏm-TބKD+xzMj&AY <jmZ|,\sl2(']eõdX%:j<}n=&smȋ<5HmM~=vt䉶-&M%Q"@<фQ .wPOzݏh:[O|Rn_Gb_NR^!$1%?rR$PtEsNԽ&-ۭϢ^n8+ ˋON8T n{/MQ6 VlD?C׺͊ς+ꬲY%q̋x 1k=}J`рVV]{tov}=Ax*gn7kؘ?ChlhDcfOjt_߲jR/nr)6mw @~qzQfݟJM$@Y~辤QZ#R26|ڋތl5\(,Q-wi5|Mri=#[3{ Fܠ4Mj6l>OӚAS+w`dɟ*jbZ|,\;咚t:kjj+++++++**KKKϜ9seo/qwD13~|u`A"Lޗ=>M"Pa ,tLgqOO:^9K訨p <6v Q+Ŷ#N^qdg\e"d؂:BbUks&眘;JGIŸ u0.5!լ,\vxRZ_=t)8IŸ n *jEzer-X`Ÿ$݀x~h=i ,\`|͸D^C-H͂ ,Xbrohnʒtt`,X`τYOm~P_Q=s[+H-WrkuK- f`?%W:F֊q;ZzZߠڋx7Sg{+⏑9S#, 1~K=xOc9. dsŢ%bY2~ڕ{ZPGL{yZ  Atx۾ GtXpǮfC %Hv\Mjf}t)"jsn JA*]5A=ȀۛxK)=p\En 5{Lbsъ{KoXX?ص_?ϷZQ2_]#65)P $8]8ȁ@i^USR| RSEjIQz\酲VTUȎ 7.Bbg==.۵ᗹ-X"P[5afrx*?|hkjȦ+BCds {qY~[`].꒩S(l$Bw$R3XՀ} 4B` 6JeYL2AAlI/}sj"BsMJf65kZG]Bzwj>se &Z8h_M׏tV*;d3!C*+kδwp+Uf=Ϝ**!b=/{!!@ rS ѨW?~cS\HJA)e*Yk*ˠT\.Us-pNz];olhisEHƈG! @ԒqаcWJ 8l簹/|f룷E$4AܤrjU"K[o0&>4J8]ppɐ 6l$ 6 ՗N{l^p1z̶Zv 8 $%ӿw^p?N/GKq\| 7CeEgOOÉ={  KϞoXk{&:, 3'l@! J@D)H=QjsW߄kO_,s@KtmxSHK Lo:^w!u#{s*;-5 ݴMdа߃XAeb󻿪|U74mh \a~w|L[YQtV3ޕEm!r5*I1祗jp$P u<]Cnl|gDN+\eYNe"w͜nex[NI㻟KU.+D+׎Crx$VULduʨv% &&nC$`mҪ}>d`Ry.W vN.j\ c7qþՅE[=ݧpCoz兰XRo̴v|/?M[lR$ٜ.p . ppQBp8=Iw(ޡ_/ڰ|q>+I:Iі#jձs#kXgW7s'c!-u>Iz{'J{<(٣;4;]t˦?OE|\F1MzKg=Z{7=^i)&Aާܐ${aaɫGC"2!sy5GdRJeHqUz2I v8_ҳ(:YsMS8k7+ظ+A23DeI~y8[V~s|w<[ӨJ$JQD%@>Jd $I6b6(_@r%.m`-GVS!$A.${6m_o}&g>Cgnz&EJYoup߼yeA!p6L֞7߼۶%1蘀8lD6JBUb}M}biJNCϗmvUs[0jd&խvȁ));^]qW9d&}}D;vh7Xjim2w bo/~1.oo/roW0Go#zgM;6[Z=)kbj;+i\yA(  L2_MzÏcY1DŠ-2e[‹o|Dr}VODefDfGIP(BTbYOg'_ς! k7bƱ58 - 8(89p@h9h4q58_kiX O/|สs?3KrQq56oin7!р+¥KN{ZtR>yw-\QN9Iycrx7Lų#M\>kscFߓ %l.^ZO].)qCÃ2Uc&ߙ5mJ9]1NuM./t%FJSJN %:( ( Es wA[-QQZȧ}i&͙ٕ>} }AUOl=s7lfn^7_T08GP {Nϥ <? o},8@ay8Gz"|xNPh2W;ZCANh] bpp P1.h9N`8(֑CS=p]>8NH9z1|=?o/´i Nɓp, Z-±cp(9SK֟J o}z r:V-b&o3{9o{X`% *`U;˳)` Ku܀DOQϪ>_z8]?70]tk5~[?xOQ )*Z'kO C:/)h4N088?g'ڟ.A٢ 7|=c#ݰ*ʚ8xOVۢU߶5녓շ=U_.mя~{/;h},4S/Ǐ6jg_;P7h6nV~-CR]TW^q*<>àӀ^>p#M/p  #qqt{{]>G)Q)>8Rc!@}.Etڋ./[937LiE"#o{Wax]8wliܹTVνNط{uue8 ^`(}"xǢ|wOMRߔ|Rq0^"-̺wo*WpwՕ˧-^v\:X|į^0]>RD["&OdKANuF\qIE b%p3grU)S9I MB@ K|,Q2А}Ҕ;=Awu<#gݱmq|pwKcn^ȆpgWŸ-mx[ߙ p[_<xʟM t i뭯t}l[ֹp O>JމU7<2`o(^޲ xEX oZeC޷&GVqX VC:*IT.0YQw~]RFH Nx9])pPV e0z$QJxj5@?YtpQqb- 4% bj@Kh^.-/ %I?~v-[oyGV"rò|n}CCȑp౱@?:Ѧ;_߄_ |ӯBG,PTqg8횽Ǘ|<@0̿7߸:/?Z6R(ֱWO=ỵjuE]q@~ٯq?}_} EgDF yTG+n ճ=.z,y-x5nZ.x;; :prB)A_o5lkJ|4$t\V"-pa"]Iy$&mݻ,mݷ_OWxbҷ;3wL;u­V?X Mo޻* :xl [Ǻ{| 94G}p&Rr$@Dl]v\x9pK;}A=G]g?uiwQ92>~u(*-2|NǁN) p. AQwhxh6\0s9_ ǎ>c֡g֔6qm=`(+*̽,=}w:bu_\/`0c-R^e ?9I;rx9mh ^Z9e7_,2v܄{$ƙ5PQwۂ+5*2Tt#lWQ"k=@uҔݸW0۷{$r7n.qD3u޲+ʈk;ţ43'M+wArܩ;]GaMt q hAS Aɷ:/}nR# ǣ:Ƿ]zۧ-{]}ބ{B_k+7W _L 5SVr__-$ љ? aDP;cL ZAms&{Lg$8aUnQ)&R(6$-ov坟q p.g =OϞ;QQtn̝>召g;=I,'+Z9O~3_tK̹7NJHTc IDATsW[^#z9(|@)Px2G]qp#|6V߲㎃>}#8yՁBݎ+y,.Sh"! ni}_Xf<~ʹk&y\:OO]=d0ՠ-2|zh wA+Hx|JK=@Q)xܾӝow\[͍7ʼ}r=o(j0j<'B @y x xuЕ8Oc<{ {&V[Ա#zӃq;A=Ă}큹wO?= vW̶lQX V/}ל:0PB??L+WP> r*s<5R]ee;>|pW9!p3j7fߟ]A7ŷN8fRo|wyVxΟsҹ2z - q.WB*h<4:8.3pqrƧ-=Bq>13:A*s ]<4Z=}zWj挎xP$/ڤp˃ϬfQ3\ %/}˜ؔlƕqF~vs(媫=iƌ-2->OT=rEh4U>S9ckxxxxHіBo'v}0ogg(XT^i6x p%v~iѣ=Ggr3k$3ZJ N~PR :©cz?ajoJsk#kĞƹvYڼ9 u0'6V ٱ1܉+86*  n7z [T߶hbFfU6WN$cA@r Z݈'tx?+>Fϓb:jg17Ċ P:5P`P3CpžqJJi)]N Þg=|OѸ7̀s4chzɐX<^rq߱luQ -?rHK5h  teM Z쨫zO7Ҩz.Pr ʣ~b1FtSqq(т B_8{_8w7„/2w/K<VSƕ~lykQKJUK,حȲ%^&9T^Yz,`DsyJb+8v`8X~EZaMOOWwpXIv5wnѱT%*l?~`~ 0!s:lt8s/_~+7$(\M*$ZMY1 Za?%)f~'GGk^tfc?>t~>M8w"BC ^V8W-Bhhgp5{L\{4߿]vɭ+*渐i =;O8Luk^vQ3# \yն14рB{5j-qk%Հ{XW| w \\<床̈όS\:65*c&O;[j:aT=r&ۢpf􅋊+ƪoJL J-)h0|)q@KȤqӋ1#mn!H&QhPiT}nSV“[oo;qݚ){?_'ÃcUgi%޳+׻&PW@]hcNyɂ27|p*f]YBmxH|%5.x5O>qvNs/(FCq3㋡μf-Nv߿f]~Qe<+nXtŮ&E0ϥhkptZz{}z󣟝8.MK`9k͍`|jM]8v3G/oZ!יwÇ甔F}e!ђ64=N I*֞Rr`vPDF2|y"#I|ȹKn˴HYɪ:D3m 2FFl_bO-XBw!J齜i)(E' g_̴َrIUՏAD^l}$w=1R"{! yd]#g2-E+6ԎUgLqA$!aEN+LAAr_|#DSrS/eAvW S?. -jN%^]-A!!E|#I  H Khv'tA„w&UV" "m\A)dneAPQsU9f"Aq"`- RPУqEѫZmAAr~\T HB~b\\Y= HaЀD)[%*q) "~t_ æ(IWAUTU?. =Bo.\A #dlv R CTi@RB= 2i]WAA)@nކ  *ӆ4.[ACWn). 4_%P- )*B݋ $aJP>TUƮ\AQa5HQDKKKE@$XzuEg(B @p %wڑ^i" 7JNCZB~װˎ NRHd!$$y${q4Z+ 48&PkehCFA)TAqQ"ɀYVph.Kv J+RRGCfӶz?n>|fԹTYٵ^e eAhmL]6X۴{5Ab#|v4$ !*uvׯUWm&ޤV B8K~ `1i Xh,-Ud-@lNy_3mNl$ꔠASBP+dm ՌlbB..@bH\ⱙ* @h3U'1Ǩbj#  %}->xAOFtѶ}'XSm:B.t J\ch3U}L}n*'pj6T8rįr.[M[Z3p,c_ o!@Diw#jvXĻ%VlD&J'Arl m;m"  L~jKAMAC4kel p4,`^/$ ko DZT4$Ff\?י  m0mP[YIL!ػPG쭄INї՛ 1GQ!yۘ(c4Ip;z(YQ$5M6 %㒾IV0 YOԊ(gb5\ IuJqAD1*5T  9MOW, i&u YR߲5P"  3>jFNM!H6bICBII"'INUCTi\$5̮iRAg!dS)wíJO(ָ$?${`Sw4VV1BĤD"?`㴱6>^OހWBiCfFrhVpMG1r(E[EWc.tTԯjlbD"򦖠lL%)a$H`>@p4});mf,ɰZ)fT=h0XFcPnIJDUd%U{LCۇGXyZRiŸb"(/#dN `1 fq8K&2mT `\??M([1Sߖ>U ۾fkKH2mf`1۷P`¬]^e۵4`Ub&GCfS9 k]cz&EC^;a-`flҗM&hDWk!ANye6nEB8 цu]R1|Z =,6SelpOLTXZ7[Do<66;M[IXoa;mQBj'!j#غG&+>dlKŶAMrk~FE5!+D߷V EGu[.); +~ `.C !TLW"۾ƿr#^0׭kLм]%|-$cH_H_n,TTOC`l]Dq(N$h*l2(۾76S%Ri㧤#ل\$FeFe(VK%o YggЋ3=F$b6&E:lRm&[t ^3fT/\=1}=6`x?cOaҏz; Z Ytcf W̶ſd4xpmJ!UvG 5Hajȶԭ]a[t_!*Gů.؎>|&m/3UZ(w`)7 C"H,ĞZUkyw5W~W=޽*EBN N6FtWYƥd!aunSe#Y N} &=tum 8wڌ+3 bb`1U42h@ >lpIHS6ȑ6P)\"@.<]Q>x=arHu ~;}MZk1`_Jp0~+uf 8L}j ֵzV}G2.RGXN:۾r ꘕ=TǞgh"&`tK~u7vJn.x,YB&#"q]ӁvzG_)Jv 6c@G25P}:[[;wl۹f%B_g!"S`1DT;B:b(h1}JZk+<ֆ6Hr~= E1+N QGu wD?K\J%1p3U5`3ms!5 pͩA_gY, d67[!dsuwwLejFiw36SȤG>Y(*ױd a,cw%%\?9` |+\ܞ2\2ۉxmJS]dJ\Jud.0lJmPޏeCDon6kl1fj10z&tb%B˸$ȚBRtMBtg%"='"kʱh0y2>AE6a]Z#A҄ ! IDAT2Ө" |#WyD2 m堳?"Ey8)E=䄜9!$Yj\@77M̮iÁ7b^ǃ~Lu$ l Ym,̞g!)9dy+deA,OUkN! \AAҁZmNhȞMAʨt Z_7I ![9R,zKPfٝ/%%ń4(q%.d,FU#*Bn "֒J-i!Yk7F7gb`1cИĪ}G1XQndM!jB`m{dJGN L}[-ZK w@-@S@lNT1QbڝTXggȱ Dl^VGӜRv//+DF"m;jю􎆠nmeh ,6h.S 1D[JMvTIj߱6y2uMؗTj$dNcvK\еMj\Yױh6?gg;* S$>ΠK_p͖+V("CHl3UV`|lJ32ğ,]a]Y߁(!sz# uVow}PLU+V=I!YGů.X } >k%~LaHʜ _*&;S;U23;p2s9 uf:.NTՀ!PKȘAr?1U5){ ]ckL6]pq ~L $ RƔX:$dNSvuF'VA7GTbfN'vEULUMxQǬT%JUvbC/HIV$e1b,#!s겣 |Sc+$? aɪvBX@?vJ*ԸYM<ê#1L[63^9ј3." zիWO; HCKa(I|\T HBqMjW@ "]Ђ)  N'j\A`\% 8eA.~\EAob?.  )\W :j&/l5.c =3˻WcPU*w9wǰ{6B@ %6.j\$S [w > {2}ܛ?_!ڸ mh|q,~6.t\6"#Hؘ V}R6.zl~7T-,nq#ljC`c(G[j\$S0kWvm%/X2- $6BT4XY@Hra止NFҸǑiyD5ؘ :#ڸH/k/7V*gZ Q 6!0W)~ؠ ]s=AdZ_ecO>BX0Ipd~Iͻí&ǻ0sB"1zSD.T3;E~Y#]y~Uc}p' v{CI'ؘ mK;ҧ~)l  HBV'UW~\AQ P" "( ld@Ut,#  DV>uL\i~JJ98Fc~LKW:#QfFeZsg3-E5AgJJi3qr13o0@  jPsj\$`ZLVlE@$!N)҄$ky\E\FP:tQ"#nW^7p&IKK 0- *])(p>.a< h"7p V^]ŷԇ*FGq,@ZYЬ#HK"[(H8ZqBU*sU8mVHI ־qv69ۗ11C 9j\$[p4lLU v!۾5UL԰&J5&SjHHr_L i㊥L ؆-U˻EUUaŁ"bl=myyHVtvPEEd7C%ܽ`16wW5&y8Ik eX!T(%ɵ1qzs?B'Hijllu !Y~\U;" B62+lg*I)H;tdzj=A @0HƑqR}6eE$YZMȫ Dŵ0իyuJyɴYx%)dgxZɉƌnjQ d]Jpg䃾eAlC'' !)AD)\CB  \A)@YJ저+# "E6HɢEA u)Pa" H!AB5 ҷ  Fw'4P" By#EAE Ч  *˲5.*AA*]#Dk_EA$2U. E)AA(X CEAD1ל)AAkNv" bAAҁ)# Rj\. (GW- (qA$ոm[AQ70+7  HޢL㢎EAu%wDAF(4*ZAQʱ=   5. W   A9A 6.!MFAWAAj\AI*4.:AD1VyJAA^eAIqA$$qѳ  rAAAҁS!⋉G  M4wߝx$ ͼk ƀ^eAIIqW H"䓯1uJ4Yć ;S`!dXi <:tPUUiYt?:t(dkS4p4f*q.^_VGO3|C[nZظF=o.&| ~BՑfL^r^f UeGJ\/B: xBL2K4̍ekEu_=`u,4`^f V $My{7/̃/J)*S/=/sI 6 qKN.4{qOC;Er \C-,iI'iʉBHMN|@Brhʶt2E?xD-yhD4OOGro7@ӁBcԷX$e=Q U?φ_ ?3(GR\]ᒊrq] a問(T _+c =LCo3ABr#}.1u$!_jn hGza.yW [?{K4B@D 9oX7ճaC Q%ᴕ?b7_nZ9-JV>??m忯RUڵk?ysXv8Bݛ{n&<@ IIo>}V\||  ` ɖ쇏8vr5T$Hxd9 {ʕHbrQ)(O} HdKN Y&l#AbBe0)O[=L}c7XZa7Z7[(zxꩧ@_0}$Bu4jlExHĮ#k~͵Bk1}x-x!4Rom4Ͷm[Vx&DN(RLpd}<(Wm`b8? WB@\c'^ IBu0̬ NZ^8ٟn۰=/ik2!ȕxǒ8zɄg$%IfWU/0FX۱A624Pe"V-rV"_*$aa6)|}##o=+Wl֍ÿ-9IDAT6p!4U5\rǎ?OߑԈj)ԋ.s+U&F ʧBn -A_\c&^*H:" Bfa䝿>|֏ˇbE3x54G ߒGoo..VJظq#_6n5 dhTwwwT#$\9%M+7ڼrƌL3f̘1áwl5z0iaC|36o,f}?Ϙ1cƌ';LgGc=J A*w7mp2^BJ =Co)r'z5=\nqͯxW6Mqyٟr͡Zཌྷ*ZҢVD#Oc?J3· eF1Z3Q >f%ܔ'nǶqДrMy7ĈP>7o`QmUsQ8zhԫ1x<^z<=<<<<<<44488{̙Ǐxy׻ᄈK|Fko}gnwe/q8 !{HJu4WCEp«:*gu={0%۷ևO7>ِ%7x?;nܸѣG3bĈ?š~r`ӌp?]0!#dE{gE!QT "Bq2I#X zӆÇ7=,'sՑDrfssQ?nf%I"9~d![F݌ ƕI~'qӆyLظ6no#`qsԸ8YVGO3'7_ۏlK^0ЫU`u,4(|q(4G7lwܱgϞ|BÞ={4fLb^;# Y+KWD?1R[[֖TMmm-|!dXi <p7x#Ӳ$E嫁 )/~ǜ`!dXi <; b8=cܣSZoe~iOU&yiЋ9RJ;rΏBplVccCGDSS;<4Ū} {߁Ndhqt~|jn} pLd ;&OӠ>έ/am;4nh[CQ_AhQ J8٭#ȡ; \|U.i*:? ayj :f@._'t@7^Ix/yY|$TwkqC@<`o'NOe6[ٳepΨ>nѩ 2)@'d&??df?'cf{^ cU4hW⍱Cqv$.Xt)nI^@vH *:~_ISr*.°\3Ȫǰ9%7g#ΩP7 d^`Ffb` yI2|+(0GL޴W#Kq?./%x#vcAEqO>X6A^;9>.  z!.v96m/Z/8&Na!a椎ʠ$  6ڐ \&*6gg Za8׳<gbcD6e՟OcbGGmkBQQ9Ь^ ubojj_,E2VMeDB̜#M~NY8Q!H%kWze9<[Al&ۻd{N| 3~SMѩ mRcֆ =[Rr. ǰ/4&~r&0ѳmK2Br \|v(g@ 8|;#3O g/^z@F^D'ssrtRڀwGnv[Yָ9[z=!4* CWˡ_g {F:]RE]8njM$b++=™I/Ţ,ɜ4Or.KNq>uG׌U%ili(M6֛}.yJ ]}c8X=vwwST*~Ϸ=zÙ~_6^_>Rpb'dO~oym{YW0BPy[*$ѿ/˟ { ??=/:?ꫯ644Hr\.+;S_BmgV"Sse! #q2_H&ėT~ti?A,c/76m?ZqMR'A[aWWFMWwQA>wkp:p^+sVD\EA* ڭu\լU}/+B%Y*=8v[hyAA~{'ŏd8r*SO64@`k;_WU7 K}T(O qѥEOٔBɆx}͕jnK˞o3\&kIˏ?*:kBym63md{J'V91]O1vҰ\n\]+I@Zv"5_>[SA:MJ9@@!0BAPGA-._+SZO.ȕ&|~H*wDگ:^/oS} wAAI7t 8AT$YqEQk@ܔPWEKh}XŸ36/XP' פL%n4nd9%-=l9:v& (pU`75\ND_Lz[.12ӺeJ$ VDEOuDkH}E]Cs{UN` 2Nx//,#2(dߣ5i]If}5 MS&l6Q]]2qZsY+DJwE=`Q{]\X@q5=+;A._92Yx%nCJ242h<$`B Gs~QdѵdknM\v!p8Xe|gVnNjhhHҟn57&pZ`Խ`Z3D偨hץc1̒xLRI5v/ RY.M쓝k  7FL_ȲPA9ck jƑn$*6hj*4ҟnDVtmlSY&^v{UUzT?zQ"3c`.L0T7 .7<Яo9tOض\&J/ @nj9# ȪWHg@TwSvb+J32Yl&bѠE9spZ'-$%!r9Ϊ*A'DIBJt*?ٳOnmm=yu'oIVoM)xu 2O͔%Ñ`9'_ "k0{3n`D`DrH}}墿T[[[WWWWWWSSC~8tJG~(.+ַ~W_=}cA)&|v(,jnmmm:~ן={v_xCtKF AAA~t媪lԪLpЕ]A2˷DheL<8Κ2&b #tt:58N*Tqp8҉,F4xgggww(q:fFA5&BIlTzz-]v+AL*%rNiRLRTqZAF%D+di.+U}(v=Nid'42EEA^*x0 )4CEj.f kW%+ A7wWK=eq} 4WE"l4$]VnYmFA]~eW`Y2Dqٗ>:".KEA'$t"t*. RJOPeTJn3s\P]Y :J] T\A%Ad.Ju}.Hs]eE]e"ZKbwN!  +˓|~RI֙Tt]NV+ HNDRݥ*>)X!Nlt:MD7@" q[vM Ue+zdXKU~KqA)ZVe$9B~" / FcM zVnAZ_w3IENDB`treesheets-1.0.2/TS/docs/images/screenshots/screenshot_todo.png000077500000000000000000006257021352107072600247020ustar00rootroot00000000000000PNG  IHDR57P IDATxw|ǟٻT W^$H4Q:tBI& OZAP X(%R0-w~ {S);;Y1@ @  @ @ vt@  t@  t@  t@  6e{ @ UB:?8c@ +1'|@O9TB: @ %!"wL] 8hJG֝U'sV @  O Ά* {PZS,,{@ c$PLHרFV+j̤@ @ x !QZ9ш1KGn1v8# =*@ [L#d.c!I 9np8t@ ,Re1L&otf)DD}*NEPN<5,@ UFN4R| 2\ۯXh/ Fn6''qgxQ @ xlD%PAٙ'"&,lԍT1ZV՚mZdwQ @ xll60Х>>>l@n٪`2TVkvvvffݻw9&ӏB: @ pJGv/h!rNB !QTqFFFjjjrrrrr͛7?S))~΅@:x th /W^y%<VQVX1bĈ|<\2" vyo_rE PlYP[H́eY9$(TPXHǻw&''[,YfEGGwҥKvr[McTu[]ҿV ܸtysUP_M5!՜R4*zU&FS %O y\ ݩVBnoZ\hu z8nPrԡVcUzPJG(c`TaڻwҥKh]F)Y/_w)mܯq0Ɲ;wxknݺGViYBs*:~VO,&^d 2Wp2D19cp`|߼ygٷoߎ;M6x`PT#+kUzofVkFFݻwoݺGEGG7i$11Hn<(ۏ_-'䓅.z G-wTe;cNV$0r5TFl;hQx),$%Gpfg$Zv,vLDI' Gl{: Ef0F92Rfv,i+WtyW\)Y$m~ݑ|d2q\\~jfeeݻw履~z/^h$jbt_ѭ{70ݜORݱS*)#R\hrƭHܥ0tajCaȠ1qtCF9u@~BrR"59m# Tu D=R3t< <ޝYYyƙ& jei߾xe]It$566M4XUy$r?gބ?2 |+U۲enrY*90&`9H獃ПezW+&\}0z1 ݐ9FZ$xS;Cr``w1!aBһm^z+FU Yn4.e[vYS>ҝ=e8HF{YUTy;u@^'&zTl6 qZDeA]G'B6M+*#saDzCd2#2ުZK>X9H=Y{I)#;e ve$!+^$pՍt,B1^Jmr(,fΜi0ӧk ř3g㏪۶m;gٕ6lXh9^`A͉7.ZhŋgcfRlʹUmg/]4rHcPts9tەE|ɓ'gΜx+d0`jd޼y&v f||V/9^ط!=rujũfHRߧRT4f-ݨ /.gֳcU qiiC#W4(:tbC!*p tT%=(=PvYY&i3H9"/ ;|1`nrt{)_~ܛjN<pcN2c Ơa9HՍ\B/GPH#z$*ʍKuCpBYwy۷/0=)or.]Zf IpN֔,K>}8o;w^vmXXاOC8qQF\t$W_}:}׬YփN$Z9y]>}>|fKX*I.b?O8qƌ3f믿{5#|͸֭[+ؖ-[ʕ+ǖgq" 9sz]R%n|=KFx0fsWz4E)KD4RZco_)ZT *f N8ZǏgsԥK8~8i#Y˹۷BN}|$&&uj'XP:jY#%6e<ܳ nX CDc?=̚2 ӳ@v>K\>Y{)?/1r/ @ʂU`.>}:=V^T)!Mg 7o1NLL4[8}ر+*!&:N1w@HU5^JJ э111M4ERbEP|||ƍp9bȐ!W6l7|r eݒ35kti<%&ѣGL]-e˔J*/7%7^#W$%%)%b\\\LLW,W m$3LSRR^$r4RHd] dl$TGA:iXUrqkg##:1" YōYod֑Mdd۷oPXAU@ (՜W^䲬r070-7?gS/:dw%% !L$%&`1ln(Ʀh d:nc'bpr80ع`o)j 囍@5PM'iպK:פs.t.k׮]%lS= A|{OԥK>sIyエwP+WߤIh@7|366bf+i Ϝ9QY81Xn"wX,⤣VZCMKKSEj"ܹsPre={1z1rgٳg+ )))Sʰ|J ܣfn&3=vY&˨t{cE#w'YwI?H끽h3H$j &F_f+FROh.RSS^JsGov=LC4B}qΣl)*bŊewfJJJ/Y`3fL_T7bL).^0m;Ѷ;iSTzsdN_Pәe [h#^A!AE:9܊mɎ'=zɓ'5jDܽ{7-8q޽{%Khтe ,gy~qc}#ݻpB.g%9rdZ۶mg͚Ep A\O??زeKϪTrرqƱ?22VZT %%@Us:{:L|խ[[g'N>}̙3D1e֭qqqk׮%b{b 07|GIIIFZn˖-#yܲeKll,F퉊?#""&MDϭ[1İt'ŎׇrQwR—.]IJǤ$VصkX,t7l@+yfCRiÖСCի7bݤ$66j>QFZlI&*MFaaa;v3gNBBºu븷+sNrĉX*ۈ/%%%EEEQF\͚5?hl'N\2ۢbccYү_(ֳnٲ5[ȑ#YCG7o&y\rDDrrJl"gϞLH{X?!VRlE<19{,B@ ʗ/%]g$"TŒ_wUzSU5uYW[{OWfcck2}|̾f IlfR8 Te8pΆ!$3\A }Q*1q"8wчt\! ! I$$@BGſӁegǼK0zOuasʼn$@t?EbYQߊmk9BYG:PjT.{?Oq׮] /KNN޻w/v,B(>>s .o… .;v,7YZ5z̽hѢmRa mڴٷo߭[/߃s$O۷bpSp,9M0.RӦ IDATtn]:ʟ:7<3g*UD*]9tӧOlٲUVWX,!!!.]:uBBBZlr~,KBBuRdE+FDD*PrL&Uc'Lʕ+G2!R#FSy,1>}Zu*SL!JK4 Sv^65k9JQDD΀^ur)[f$<*c it0  A0ca"qU\ֺ.ɈnTr-=zY2\`#MWZ511199cǎ .YOUmF&۷o>@ɝRƉ1 #s6mڔS}etHUjU=88h?@(S z!C3gTXL]vtRŊi |ZaɈ666V91&9 :uR $w \+Y5]zV5]-*U|#G˗X--$$,ԩSBB͛i+={2c"zw9FAqSj0k,*u`eXX1sόLE>GX}>ekהUI˄?3\,ZU.#H_V[9݅\ٳ'8..СCZdz E\!bOϭ 4ȁV$ ivsP5Fzo~\]_:q6 n޼YLݻoӶ_WU o3d2ܞ=;ʖ-KNXuG΅i2%(O!>J>9np,! ba*Wrk%Aa1aME" !X00 Icqa{~H2 S݈A’IѪf̐KGGSPxpHu>*'/:jE_t7)<<<11I&۷o'id_-y}q@BCCǔ^ziĉR.VDkהiTݻC._L4 Ӂ`:unݺ;wH䈰TDQjBʤI&MD^LHHXl=n_eEGTTiΝd#Gr飺v9{lNyMhYP%RP{Fn+V &dٳgϞ=v>;vd)%KWZ mCBxڙ+9)-J=vuO>䥗^4i3<_3cZ(︫[w1_x#k,@E^Z)'p'-%g'&&&;;g^*jDNIKݺu NzYN :H= fIPS[J:dA&d6}ٝW=Md1}%__sȹ 3`06 HsUUH! `ǒȀ#1el$êGOeGYAz^Jӏm۶)#G۷ߥuFvyc!44~8vؤI;Fi@FԄd/66UVI!Uki@شid1>>]vPB1|C @eV UY(E:ґ0iҤÇӦM.=ӘϞ=*ūnA$t`ƍnQ }nXݨ( uMƩS)D@vؑ M?޸qc`agH Mru(hLErKw^A#\(}ݕ,֭ۋ/ؽ{s[N3iMv{pKAUwŭ]@rB9E%UB8HYm6[ttu/_ml--[6::ԩS7oTՇ{}048W\ra*`6>&?zVƯ'e7}Bbr$arlI`"; [?-阳HrW%d9JA!AE '(xӻ& \mfUVE-X@E*|k׮]||@}||ʅ)ٳg6@ jذaU!iIQЀ$Nj!%%:$$$JII߿?VjwAFٳgM1Y8H_V2 ~1UV#F 0L;ԩӒ%Kʗ/1>yѣG62yA9+s$ofwn~ʠQJ HsĮvQ&Ji\qw~Be;j9իW^jRX?JF Fs^6MuO)Z~>JhؠR*soPeEH09IvL̒L]@11;?18rZV,뙤b/]z j44СCߺukm3fXlٲe˨:ׯ8VZ%8@}Xj3`nܸqիW_z5+[hFsO@B4 9>Uv͛xO?499944DB Mf|ƌ3f |eqqq5kT]q΢lސk3fhذ!hӧOo>wܸ8 ˗O޲e {4hвeiӦNҹsg/8p m!]_hْ3gΜ>}:U ˖-Z5+9spѳ䓕&Nȵ@VIc3ҽ{w1tt4[DPї<]F&މKFH认Cڛ*$鸸80::cܽ{X.EQݻ)Sflǵ^;9kDDIJeFE=T3ޕ) ]vh\ŊU Vg͚53fnWXT31^6\1߸ N2z[J QUpIRk UV砚=wZwlf͝;w jPi9*O 4Pc$OJr7$ _HS/_I\1c,@ɢ5טΝ;݄)9W&vn٬VkVVVVVVfffFFbqח/_~ٝ;wr!1Ύ;L&B[mқˁ׍*OzN3jEu92ryT/ʋ<؃ɓ'g̘dɒ7g]?,>(?kfەmڴ7h;떵.Wsc<3ɋg=s4_sG<."lr:2X1d8+=Ubrjժž PUo.Cng$椤$-M65jҥK%BBB:)RH@@@@@l&[y wYv-B=d27U(;d4P*\*L}ԍc=׊:deQ:.g9`N0/:l}j]5ǐc!iܢM 9rEh0]#՞NUKAe@cP \Udaq7懈|fjοT^v7ZC ;N3RIurM ckb5F~'VS\*ʴjZy#=bҝfJg^< ' *SST7#5s)YK\GWmoʫ*(#{#N4F"sHDֿ8fYrebV̔..7z ɟ˰, yhjA'..@\d~SaK\]W M3\ '54!Y9\uwVտ˦ < HN)MBKW\ܱNкTU'8ƘLYA:rr[Rr:c j[y (xGHfDSȞ~tڢKAhЛChT*c'\΃NJFɝ}c#'uky˧,̶=Ӎ|pR zߋ=[P1R(9E"TzJ:/yA?A!)]hEӢвPY\@ H,:ŹઞpsMW;rl:Z_<td/%=n!+ bF:GNqFxVibf Q'-`}{\~ug&wt4r^/2^8pZ8ZQ),:FVdlRqgk P?^~^jPl/s~uꅦ5rKtPk܈HU@rZI)rk ܛS",SRΪwЏY(}Mh%\jEH.6h6t| 7Αe=Y٩LK+ gl(`ZuLa@ (@pnǣ'Q9mwVҧՁ X>Yjj>m𔵴Ց `l:=0E DyVG6e\ƶv/[' Yܩ#`G5rA K~{ŜN}X"E`W_]U:a\7s۶Nh4W%:0@>߻[뎬lX@y^MamuKG@  `E,^}dp[:=Ue %t\8dxv{@9HOH7Oe;d3c-gZ8mٗ\}IWw# nkBσ#m;S彺aϏpNj*`g1ح@~=g6FvWU tߑ\r=CNoU;@ Љ0&'3nIRerƝ,KtvUzoonB{6=--p v5R, wJׇ|ڀޜ2čds'4:2A3S7>Zݩ WnAwd\'i ^Eoj/뻚0;T|iN Xog!J3nFvjәٙl,"aK#;97{ǞIn顅M.-{ɧǰM_~'^#_~/-^c`ݱ~Vy IDAT:َJ*Ym?[NϷX>kJӎSY3~9;vUm}ʔwҔS1<탯_IU7DĥپOnjjQ|t~㯛Pִndo_Hɰt˾? PD^ӚUdҪ[c+~OzaԼһޝǤ9Kro>Ttl,Uu꣚@ BYʙuKU=$^ibl{kzK^)__jϒaGJVE/\;)cHbЮOčDMOdE"z[xɌc7iW\>o܇>ܙG42mj]2>]9ך6SE^Q"c9?O*MTN%“>;ETQ+wL:9o֍͹C[5zo@vt죍ǭ9 #m8b{CtDLꋙowݫo:f 17?}]l{CNImrѫV"%=h/7 ēma)?Zg]IT5.h~J;0eMY寮9մõ|vKZdm7yŕ^t)Vढ़7PSw?O?c,V>Jxg'\[fm\s,†w]4mP 6m>r'_j-kYm^S:=/Lŋ VХ3#d *]x"+T*VP_ow{em*n{+'| ?:8'XzoFY/ r3^d`ihW'uJ7l֡!Z6eC;]uTEg~.ҁ#7?(uP'Ա?^l;nBX>|r%n$iUܸ4i߾7~ JD]7+biBUuY9kiel7Ν gsXV>; wRimլei".Դwj|vZw_Oܑ6-Ȩ{sK|eV܈˶.JeKvn͂SPUzvueӺ~7lSB5Vupȉ2j0oPpjWoq宴@em83݁3usÿwҽ]㦾XC_iU&80i9uz͜7ʊֱuV|H|}f˦InԚӵ UW ݻw w7Ewƌu5@  ԎG/:JG?0nbsV9(!dpnIZK(ܽP{@hȎϱoH]OP4$T8GnP4!qрQs\^R-`?f18P (JHKXhq]'Ƿ-w"*s/0XD6 S~Tlh{ʙ?}=JֿrRKB_H ,g{wW?P$8-!!E n!Qޘߗp'{nY^S6(nHr5 +ZB~?8u"Gף˦_~!?\em?j生u5@  q~t֬#Hj!6>@ !Тj.w+{#s 3=UB9gfdo=$;\{m|)ҋڙ!՟7T 1צ6j6k@PP{w,5 u\Lj";PRI]Ù +?sѿ<ۧ!kU⣿wxS^Mg'{`Oꁟ/@WZEpӌ%ߵCb+a$B݃bB|fp?$C{65gˎҿ.hF2@HmZ*6|ճ>ձ=. k@AswV_to?y>:@P nڏ0^ <'>/Q#Zԫ.NF滁D(ߜ:r. z%blՖi-*VR3Sg^Xﶨv16ܟ)33`ѽR/S]оkӀKR nj7o!vMkۿgϭs'Svٲ8vdQݥ'~rjze:tPӒXd9nu ig\iɚ mԺrdJIc;U$e"/e_ʼM\S]f\D!?)=FUʏօ*gՓoڗd7L勩DNʙsx.ٽIg/v(9.bZ~<{,spj-uqwwt֑vOTvIȹ5teύ[q̠:m5m7Wzi@_ KǒT5ocmZ*RBflx`ELU3ԄY#G,[Ks*\' ḓ7QKu)~feKj\k35;.M\F>٦ȱM,.r>|˦ngIA%=oƷ{,B5|bWL_胨g+qҎ0.X49zqSzlrbeNK2< Z>i62[LZ[ѺTճ0=zm_4yWWs{<;M UG5JTfu*@(GD,˲,vn٬VkVVVVVVfffFFbqח/_رF[[MKhF s}܏'U(hK }>#>eM`ňvzk_|T[\\N7fY<~u*XȐ׏s捪x@ @ F<Ãg1@ @ (xݭt@  퇃ݶ-hC #F\Bv_JMM+hC򝐐`+YSZ<\5Xpl;X??M{,'J /sBRbG! Cshي1h^njʾZq D(w=eXJ-h[ gU }"< wn&I;myjkoGMzå۷ooڴoZG@^.lñI!~VF'I%]{G"~y3~:r2&[Z1`,;26ߞPG1p!-l6¶_kM>c@KUec.$NPE@U"$d;B_xu.X.8נdSec/6l0NO#92,S%(=)Gk{-W%=5=jd^⇅Eu'ƒgCTX S#ڌ ۷~wo㇨)?܋q꫏p+N|szg$m޾Wzs{yU'욝Y}ظhZӊ~s8CvI ar پ?qc튛??մT;, c"0`X6|c؋Ts 56vJ/<+\*wkؽx(IWo=SWL @M!! wEjJ龙wo~]ZhhP=ԫ}3ɀR mqxv-t}h~㎔2-"μ3GPxo^>D"{X| cLkYXccL=fF>;֦b?Uо|}tVPIgeeQyA;;rM8 k"&t/o+ǽP(>6 бz10XpʶXB6;.C l LH$YSْ!Cj5ImY(&&)S?3[/(VeZdef.׼9Bf杤(ukBi|L2 /+)RZ'Bdl9lݐzlow?)R l7\~O][HX@ZZE#zmkھ00 0Dk,w/saKnTISQf}ɈDX2vHېlX|}}}g[{2 ]mϽ6-6Ovk[N}E F}~~(=wJ (ttٌ]vhDM5Ս &e=9Oz}ec˶q@Z*;hҡ ȏd`uC !0I`lIv9L_]e݅{fv9c?8eO36V׹_SXGsbvvlϮ;\ $+1!`1m 0w J|% <uq{' j+sr:yn0?4/"kVgkª[n>Wjw_~y(I22Z`2a֞ƴiS;uE}=*nrjէ057$.*}vj0{r^p)eǒxBjrm6U%lQb{bHF~뉺VPmE5O@0M7pG:xF9ǍY2c׳!5j,9Y(5ON8}v\;uQ44jzNWPlSeFJK[*~~M?kp YYè,T,B,1`k` %0 $$+8N ;peYr,R# ` J`8e/1= +حP+xaþ3\;Yw~pCI$ACC ܯ{x?uWF1RaJG@!ɐJBJtp2.ҧ{zQnt/Ol3%l^KT6>Ǔ.xDDW+7)n"X[b 7 iN+͛f|ϟ9ѤY>Z'[ ɣE[׻*Y͚tˋٺMZ\Ph5Dy$bD}d5b2#Z,>ۚ'sLq!9I+n\5IFF9dp*|ݧLֻẘ_.K6 ss8}@O131i 玚Y$Ij:lV17q߿}s9ovZUtjcpV3`Ŝ %Y\PVr:` ?_l>~ƈppoPrWLyңmW١Vo?+Ei;^hRrV+m}i؇wkىeR8]YZ<(zt:*uw5}Ȓ~0X!UXMU,F+T@bm090A-mQA* `Y 8$W *@daѾ2`  vlWY c`ׁ֬m'>V큂DI:4 IE9O?_2r =zAP.NK|5$8 AeO$ ̲,K<[,gTc5a4.ڬm Òpwe[jhKca(#nj yJ3/gX{Mnpt,iώy~;67ѿ} lܸv#hu>!%qӃhnF݆58WZ ԍ⥻Sz}cm?.n'0ߍeize'5yEMUO SޗCF QE@Z2#i'g@%7M +tTQ_W'Gz4YyePlB8qUyMӡ/n{,`XDUX+â.㗭Ud!JdXEXRv#7x3(5*Xp;y97 (D|/[@a wxM0Xfή'k,'[ 1$ 3yI$Y8&zyK DIx^yjuEW IDATȾ@`U Jw$@Yu0Wj4r(!ۄk< ee\+" $A]ё5O֨!DI*ѣo}|ۅLkX|mD8ߏN_N,bCz?'ȶY}e-?\~痶-(ǻ_VW3yepq&gbw%=șmQJ{.&&[r3 =J׶YͿ}Q>\ϥ|o{啞79d 7L TPV D13oċᝡrzux^&&QOz긙CNTЛ.O+` [>ur/(x^z݁471eޕ˷S\WS?f5B[ lտE*{m7ޓ=ƿZT~iڽ7k*R}e痽y&&])~.:rwi~?G>JyQdd\j,2$V Yb`_! yQGlXXͲ$x(^*&B=@r@iU ' $ ̲,+Iv,IS T ;cH}1,o$c>̯KQ㇀jJ*d>j  W(, ^ Ee{?8*trNk,Q.ާ Kr^> bۆz6stPU 16k~W<`1FC9ȀY@.[;"jF;c:Jڎ=w :,L%, {!ҩV` E^ŤP5,V@aHV/IR٪'Qd.I΃BFm~AmߺXqq>eԼ/1/A_Q̺ggZoގYEϒ翘\xkZ(YؤٌuoSV}O> ϭ%o}Wb){v% @&Ny?ˉ_ߞegDxedV+ߨ3:ykѧG au~kO7~Gf.eϏhxy{[dFS=~ޖZ-Y0M 挋|5%߳}K|dmx~ւ1a2-KMƔ<57j嚽e()VOwZ5gw~ұ^{w]lHeM fJk"N{gƨF?245!K<2}PӮYvQ}TEΤO%lq$@ PUr`o8V Q/!/ȃFL1%pZ(A`X,,dۧ V9|דe!IP)`&gv1Xe) X./܏~vqq8qV+x*4@^22ժ'tߤ+I*~u A*nKpՂ$Vi舒L/;|G(CGQ/nbBGkwnccjeP~fS<]~}dul%T1)kh4zT ,Ҡeki/M&Ic*:w kGtf,(U0z/$<}d< 7 0rGV_mD BGEP+|cʗ|0c ?-}VNxǭ֯h/s fĨ[xaS6ҁguQq^EY7|)>ťl^}5 [PV+$.Y)@V%㲩aGX1k~>}*$Bad 4(B+U8hB:BžarXH2rLJxqlja,\Kǚ^O^XoR/'?nPKTٖR,ld0RI( cXa}zضAI-) H%qVS զ=+&9P ޾l hՆ=n [¼?̘9&\J2v[}nNrvÿmi^Y!-M˕rm|[,I 3jj+3()6CF{yx^Ti0&B2r`U-iN9Đ!:8=ֻŤg} r̒zs%@ί ^0KƶPxIT0]@=]vΞvhuЎ .DEEsTU񓭧_(ۿ_|˴r[uVPJO/\w C0^}0Kl2ϯzvcℍzus'N/GʰoX}M'N=ܦ7e`H[h3'/ቹ{sּ(zt2Nap}D^/B"cP۲ta1%/$?XIB3x3z;[#KHV|@SB#*'@`2,b$Q(زǴndd[J:2d4o${-9#3UǺ(3&N6ik-dN\P4Pr +v%kZ=tGV/ڵKZrmavAꫳf3LnvOUCeF[h'Hd"d[U>ԍdǠG\NLN 4gߣZKRʐNUFLb&<~Jk׸/e1 Ӣ}kW?0?$St@ZUd4fLٗ2/ӼEfffS~Z/5YoNa4k-ayUDQ9Nܵ=A8m$Q 9衰IYR9|llb|:QpJYEH.j)SԨ&@AwUvBcj'Niϣrp{ŧIC-z8اZ@ar*::k}lY{F_?>\6p!\Ul8q Mt@|ZZNrNȣnKۼt3\Q/ ;' q AVi_ D׾iCc{1ֳ%kؑvsm0N4bϜ()w3z}J'~r,{_~}MD΅梫݉gmҮng5QJQGGgRбݤQ0rqF$d@O1[R"Mrb&(7=ѸEV2a$Bd0Zaa흹eLTh~"3EY᪅"*5kTU=1% /e`Ua d5]{,k*NZQKN tpvfzW{D}Z6Fƛy >^UD2Ե}rya@XYQQWԽ*Bۙ.l,}-. EČLɨ9U`Ö+6l{DYńԤ5%Pw_5"@Ezx_#YM |xdSƲ7,;_ ߙ{J~+4D,Jd:"*rrt4//p +ggfedٙ HQLBůQ?A,Ŏ R V 'Q_nmk -'I)_ >} ~hO6e[n~ sϜp£Q o??J o/e $m@k=M?hָ߇/f^vsxD=T I+RƎ5{M3sT[+ډUl9xwܰc$;Aߞ@=aE?9eUCz*=?j˳~rgFȮ~4&-cCN/*>Ѳt{~Y͇|. {_W_%iޞq8tJY~v[[Vz̥'V|.MEnQM}lp~f|kHeR*{7Wҋv~+-#ck^b=::hlq=n,xkO*:2:W7E\t*1m͎A+JT$+ـ -a((.3RA=]:%A2x9d4,"d${M&(DudsŔo~,߿at{QyY` ( /`x+%[+=2io=ۥe^)7fQ`U!٪T2F;T29=#ڢCbl<ͺeRE~y=NZa60Y89&YnҠQcl6XJacstCM4N<}ulE$o7,Ly6$]ۭ=&%e]J8y,IQog,[,#^%IV(nU=%= 5z%?r/$N١e?S*xxu1~e>xtN1cl͟^gwIWoaFKj<ҧo^lVبR}죶){wڿo͟C\WIj{疬r§&].-'4?`έ A-Y*:̢URQQ(ݿDx$$Q@n ӺDtwR栐`E^ )9IEiL('`p5,ju:f:O->ՆVnrc9y[IXGJEE X,KlX`2.%hChxBx78&WRVJ%4:dY9qf'[[}3(;@o@M;dj{AGTdYhz7A~hrq#vu,XVt5j3Paf[7mR<>Sр<ҽ/ Il4c@ϊ[7͔m{꽼vQTVN`wV l43(KV0S.z可X_dqiunfxBChjf>kãO0, Ȑ@@.;S•a 62֎*Tb4]ZmٗS/K)9 Gu Ժ{*^u;$_-u.h7/&wC?j֓e8(!jɔa.9SEh(/ kK>Jc9Щw(ؖkݑޱowPܮ&ZYQ D=.=EÈPy/7 a.AS5Q5V4XE͞;znuX .A!6ahOme$&5t)+_w))h4ڀ@,0ls% .yYs9N)([k2REirfZY6/W:6/:ef]`U(UtÏ9-WfLV pm>MV{swE,Cɂg!˱5 f!Uĩ,$2d5ۓ V|Y\KఔI?otl/.nJɚoɺ͗FQ IfibyFE_Cfd۔4ئbiHe!0AnOܰPn.wd|{B]Ft\z1?^o=6 wHl맾8Yn*1: FM(e,ܞUY G( ٻucjCPMފGߝU0A XCUe`}V}y- øhg8b1i˫TˣgP*8IP2mg7\d N; ,cZ}k@T`Ӫm9}隣[yW8CwY.zj=~b|LZSGّF%]=W=f<';ZU1ow5+?/<ܸc_|?o;:PW=x[QFU7|_zue~ ze]T5a>Z:q3iIUzv Ft:9 R:׸}t.#%ءNw$ٻ/Y)yw:#Cn=c뭻nީеߨ7ޜo,*y6oZT\ݻ}{*ҫz[bGj4$I$ Vl6fd4sssSRR.\x5k֔;v2eʜB!R/9E:ta2W{xxt:...ZVժjZT* B`Y֞mΠ'B!J ;8 GBGa2!B!88 : !B!FIѩӨe.N!B!w=G/=|2!B!VH'Sc:!B!U6RLH΄B!rɲ,˲$I4F3q$t,7҃B!BH(Xp&nZv:!B!7)בQбtnB!r+als!‘arʝB!BP&HD85XB!BAi;UtB!B*RZH!p|^G !B!*m #ōB!sp4V!B!7MqCjJ!BeԴ#!B!/PCEQ$QI!B)Ke[PNRWB!BHEJ;R;uB!R9rt&5&B!BnE#k:CG}&B!::GBGa)!B!\1$I4ªӨQ#x$B!ܪ# NڡcvB!r-P8ZicfB! 8:&GB!.Mw: Ֆ4f!aBARy)h$RGƸwm !љ8>LU=BuwuvWgB}AB! qcY9бt~{3z̖3k=L빢i*Uga"Hoq*ݝ,!B!U0 OMɩEK>h@B"nnZu.;=lp鯻xo\?9afwv{mkKYG Jv"=sCZW~2f6B|:N_M\ѱ U>MGNԲ͆| `Ov3GVB26wNx7T `I9}V=aA:woInȖ{w s)Y!9ǫ7˿6Eqx&;f4S68/z[?to\ٽdg^j[mJ:]0I;\+fol`,by{ T X%dW1J6 !QcOGnQV\ZQknʹ:_ , Ƽ~ ;"R{)ըl^[4 PMCY"*W*~/Hdņew.>=KDŽjHwjiri] Revf녵ƻ8]F:5>cL߭+6faso}x!?5Wo }<.{_#;sI!*S6؝dN_o;>/K".nk-dcU/a@[mByӹ+z`X=Eyig2rNmUE!1:Gjm,ߛRch*MϾh6363ˋ}׋1|Vkʮ{=V>]F\<5x1Ez@ёW1JJ+Jxuq@z_&؝ =W35l;RM(Ȑo9q@NV7B;{vP01bS( bX[/g!;cz@ι|6J> Au~d=kB[CZp>K`&]k~6Læw{ƕd ltM }i3T*j'Uf+ :uQ(^=Ƀm;^T,ɼj42UļTsRΆ@PLBHu8}[d(!/K_Ch-'iioY[s ?/ܬȆr2ӫ!{|xrR}ܵG O/nwWH~͗.C$l5ODOFuXf\shI< oS@f)?c]eFkS'VS`͓Fѽo3doǂ#!T[GrOl}/<\)};r&vEj$j6T*ms;Z j5siX,&kB@A3r0tgFtWx1k"Fjը]%_~FOSQmZaJ?if>hHgﺕpgJ_eɆ,Y/qR>E!`pJH=[>#7'[*y>!T|;R;0q@Vs28(UOphɘ%J,VITP sz:pHCd@ފ.bPp#vdї,x`Dowr-\ThdͷQ_޸`w ό]ؼx!vf#~h&/ e.shK;%SgX&-*tD @ֲ`^B2]ۨӣp9**k;ԉ@fEGm;j/**ċ '!&*eC0HW}O!"z @6yQ >*<~у3 {X:|,O/,G-y!" 9N>Nz}5N$˒_Zj'@ξf릘#At%)C_!G'{6n{ %wt޶ Ky{*G&aC̊7'ec/;zu?3?^Pp:TeA, j4e3c5Zn7]RYsow-_;lƝۗ htt -5-OB0q!>RtmxtgoUKSWXմ *jMQ)}t_g}y4.ěғ`YB hU`U[k{Tz5M[}Y]-^8I s+z~ѭp\Imzܿ3'o +' Ӹi@ -bie`ʗVnh4))xlhO.kQ[۰Y{7qR ^g#]FHZӿg5O= !u3,cO҉V ?o9Yvk|K[Nhúv*3=c埞doZBn&IR~x{YP6W/?[?KdRpF~Ƈv珍Uc>]9 Nkxƹ#y(#$Q}n8}Ga[ܣ ]ϦK֊_j!R>̽\0]RUlB=/I;z$;Bj,Pu,!:F+Y]mqE͈fz/,phu:85N!!Aj'-Bs&T U6\98RXT;֖ܟ?syqy }tJ;xK/T`sG[1bqו-5u6ЭŤJmT<6\KSdoK@¼֍9!w&+IG{$w#j|lj{Ak:YlM0Gt Bȫt5_Hn\m B!CT88LMQt톌Gؙ7j5UX3ƅ1;|\"{=.ゅS1UtdDPPIqA&oǴy15n;\-<&#WAi݃"{d0_4uGPG~M/HY|?wOx܃]t['ԺŒ_o?s3zpCUٮp?>763I9ۊJH?=6MP*nuC!B7 w5i+,]`otn]ɏ\u.(ar㋛&$}rSp}礤ܚΉҋ=sraЁvw>>Ml/S9>8z;?δ̋[?ts-JJJ>7?&%%%%x֠+e.ޚj>^WRE-I;nO/8JV996-))[BH8cL(dpcC$dn?x~Ɵʽis5"зzal"moxjհěD{>$! DCsDh9ώ>SmW.wN5X>6pɄWG9nsR.Qp,ptRokXWt YӚC2';$n kYo~5ү֗5jh_ Ê˭V??rujsT q.4q֑ۡ2.S#7-zcӢ76-zeVۘǿ8Le7 k;kqP$I/86Ӯn= ~\Wy+S-nF]뼟>уmA-ڃ2H|59Y镮Qv3S3L :{xy^(rh!ܵ=Fw: 3qpLo%`5@V^.lcT ZFE,[Q_Vjն:g5_9+o煼)tƃҏ}:izwaM]mߐ0ED ZGb]gjՊZE." .l2 yz99{UN4EQYA,y '#Rv!UPJIDDZVMB; 40TQTw(%.i2Yu,V?֔p Q IDAT0G||aN'n{9( V^uWAt*U\7~v7נJ(jYyN놙i)PdhN N$5 r|(bcflL@͓{Wkt k:6xOhWyz˱PbmmIfn:Ƃ[ݭ, *N`2/cjPE?|l56*ˉ٬~e/[LzN#EIG6hB؎_q7D@ǥ#,R[LpiȪhh=DleyGNksPijG9ΏN_  3Ha_$uĮf.qI<5[tG FgeeϮxߚ?M/&+ص6Zys6n@M>]媱`4rߜ˫^1{],Yrܽ*WmC~rRϥ}./!$ZHϞ=KNN...fYB5=2prN`4߿Z\m=<=~.wS}7Onm3+B€wǾJI7zFn.#;zz+IRdrm`CIt)fH[2shЛ'Xϛ kX $h~:=N X']љ3y,w!ݖ2ݨE|@ &Nد B 40eoidAWЙ}N;@Ek(W'3%m]Ht2d= M"k~rSIݜޫ Uh{'CPonx bfh4 =m3E6D2+,EĶ+%նD"V~w B攸wɁZbB"@Ii2j_ Bv EL:>-=hإh^ yy//оr!$H)))7Xmh2a^v_hy{eUU wg^ѕtI%t{H{ڛg[KL^oadc?Z/^\w#4B!zـ|? ~S9fi{R?'*{zW'Ψ_-KEo>-#~4◦)7Dn[G111|z8UYP&A .*:9\`CCg3PS2ʅ;=2}-ۭx.cy7?gs]BWޣzflvMMMMM Ū(///,,f2ǎkܑeŊ~~~,vOE4:wb{ZQ|j~EH肂}=yi>ajjJ&)jn:^ PW^튇UOç"(Faǐ;vDFF6&z{{3FIIJIIII7P(d2D"H$1111[ +WBP=;;;>)f _~c`+lK9+c\Gsdb.܈.П :In C- eG:x7!40`>@ʏQF k0(W, :xr$:ۤD0#8_0|-\dݰ6ꥰ :ª@ҢWwϓH8's ] !SXY CILZ^N] u& ^2#WcrgBLP d %`2rA@G:V0md{¨ `1{AVrQV2d~P`fvzb vDu$tM եEC{0Q:ɿLs]naZZ9[c6Ա0bU8FNA"P ?_ ^CJ)Åp1#x<sXEaMp^`:ТoGGbM B!e;fj@V5wj4;ĵ|r[C.B!Ģf|ݻڼꂢO$d ℺L(.+r~1O+ـϘzѡ >=ۚjh,8%0aFr2ꆮn6|N96FCF,k5f4F˞| N c~xIio`}YPz<`L'2`4v-v%.ygy1)UA *^f.#-ns:vhC4a&<; 41` msMPG@B88)` Q>߀$pk:Hr(f%?ɆC4àBnȆ^ B.Cw#VQOQ%M&cu.gji=HUVV1DVsa]mٵZRſ`{ԮχmF;q ^]4/zߑM6G\ZӐeFp֘Cl6=|C*D'9(ωT{4]lý a- \//Ãn[S϶Zɣ#^;+ cc  -dڸ%~'"ɧ>t3q''^=/fLpAq 3Ds{O\ݗۡU\k?'5As*,n=uaa]L;cC!suv_Gڡ&7=ȧo#{U^%jkf\*}$5JHJkjv]-P$敞Ue x\; Qَ]"T]tƽS D +͝JmiztW\I dB"w}^ٹw,NO-ge"[ZH ;t6kז Qx)2W_c=w%pI .❩!) ,;v*_FaAt|sܧk>)I3|5OҢZW T~ }Ԯd2ղJ ?QKu:)ňD69N$SP]uF=*_axl*ySzRi62]6:V9ɲ 'T4CS4eQ,A l: };>s_^okk87>Y$<@z l_ e%_a(]KB/yE I= XX[#WZS&U"IBmlg9 )?2ed0*|2"I$Xj%edVJRs= Ŗ^pe ~:w\%х,i-ghG-SQV< ;" !G}mc.X[ԌO5woȋ ]ҷ݁hJ"FcǡABA]]~5)Pޕ{Z76}թQ[Bs-Sw*dUy5SJSc'צh%48u@'+~*0pkRݻl͘|{Pv?PO.VÇ#:p%D'G뤰?،w%(U{WsRUh c;gvyoɁ9,{y8NMcx64 6ȑe_/$H6TΎO@B"sqկb;~Hm7liっ~iom_L7٤&+SD,!AJ 5U j(Ԫe=K ГPtFq̺_]L&W:~ t֒Q6~$CJɻ ˬ2sw Բl j N{UvɏN #OiJNkeUUU > SB= T G~SE0[tޥ F֓epn 2aXnDg﷎>v4U7CeVީ(n EDR #խW)ևPdխfLhUrʺvkފb9whjyo a>P[aN:NO>\Pe.GV;L_ޚ:sݱ$aҲ ܮKHk.u.]Zwݭ mmmcbb ?"00U1'/\B!8GcQ۝-6SsOv8tĵy 1cحmav%O[pn@CgoͲ+Uw^%>WyܯKH3Ek+8#bS[j_?6תbb%i jnvU&P, !fqt1͜{Dn 5^ftOe;|ʼnKj\Ϯ[Rfω6rBvĄ zoa7#*,ڂ:Xi٢y/A]0qJ%NzvV({6k؂3szʼnĚ˚ 'nԞ@hMZ"+9ydE%gfgyDIQ~6]r\C+ydoQ祿1O>e+5&7myCK\閳&;?!wU|L"nOk~~ݯk-}t:ϩ]>??)] %Fde?t 0$ =v~6>SToC$j˲_ OUh }m s_n#BՑX-BsSv[9ls9kI{n*=x '|B^q2uD{`E]f?G[Bon!pczMG F1z夑&vĔF.3^]/o8+,8{ NRNt0g6*FkũkEx2G05\jϏ*#W~nuعV3+L'lo<ЫjXZkle/0lGBdw2NW 7B_=cAҊ)wFqT]^Lvbڊ4bwx*0@dLc/L{ߵ4"T_2ɽA2ϟyW*4]fn?q;WRl$3NpGu[br||ܞٞ֎BPpaNs7KRJs@"jkk4ޥ|80fWCzWw`Wu 4y@\̩;"@$H@#ֲR^~%VT^! Ҳ*U !TG < 5-v\Eq;7'nܻ|̖,R+v1$s ܞ_އ"mTn{{ ^;j p@wiA%oDCGx{ 2<S~ikɺ2_ɧH @]]ݗU7vlubNdi de+|)$eroLUZO-{-dg!`u;=ovD.PD |irBr3naf%Ԗ&\?z]G7#Q^m53| ['qgW<Z0iComkN|;]Y5ĀW6 s\:Ͷ{ɼdk/xd~_ádm[3wN^Zi2o_]WTwN]!5pUCbl6bUWWWVVVVVVTTfgg37oܼy|ssy }W;>e=@^̈́gv-s9r0_at6N"pം%9THQ&8Ek`֬YM}=G|혾ç"^A=YEߚCBBx#cƌQRRRrrrRRR (L&H$ B_[G:|ynwǁD_V1ӃB=n!z:27@5wk~!,ﵾ }QQkH?Η}.f5!AX7_5ξuz@HXp#j;;3 tϒS_^ܝ8ԌOj.NϽմCIgtfh #߼I3 4}H$ Mw]Q G>\]b{"Bjgi8JQ$a!P/cm5z=+ulq]sc2%k2kϒ2?\>#~SֽŒۋޕ"ʅGH7 *$(ٹQQ******xǢz,?, uFtv@U%i.#-G]py1)UA7:1%9 aي0aFr2ꆮnӝթEmi ,v%.y_x[`0HRl%Xlka,Gc ƤҪn,c0 {H&瓴+ e( Zv3u,NeqҕU4*SHdv7x(<;cq&'umCYm_AarrTbҹG^G/ӕ~ !ˣ:Q<x_"mj o߾w^sVWXvӊSKƬ ہ+rŤ5bbbRRRjk䧄@s0ۇ&ɽ9q'& 7[]wOSLg<9']N] u& K^m8)zmlHɏ>rtlW*UoN'{e>`WOYXH?'&6$l xh]9<|Æ_kel8;1j$֩ezWbݰ} k3O [chYɣL`];zzjy_=?s 2pf:"PK** *3aOMuXY/="ņ-PJʒY)IU-s͟/iOL&yy4- RTVͬoȭT)7cqHĺ26OeyY챯5spCWv4e>}e;_\םi쿰w.3qՋ.u@~Ioa׏QtD=~~^ fmOGVto`u%QHz^t6ϷO{l7xÂڊ4BS(x"k< S('_+ÑEPQTR-o۟Jȹ]qqB@>/żxI('k!03+yu۬ *t#U{<gHdP| tji?/Y:@왽9rbbRҒ#w^/v2gwctݿxHA/U@ԅPOCi9UQT}mgӔʪ]W:~ t֒Q6~$ύ60u0(JGt.tǿBi=msHZqb?Yh)i s0[$G<=~2ʃwUz-ny.uĮbg. okҊk,#h4'j%rڼ<͘1 g97lgC".(!eκB~_MϮӜؿ [gvfǬ9T|0?zcszɞ*@D*'۾';^ydɞm_eo Z-1-\ҵ?[͎wu'EQ{wqܞ8U&;i7׏dJn3eWۮaۯs}?FE3x3m??}Ӱ(p¹nbb'ԏVJډF:}S ]-,R#dnе#[Xi6Z*tP ~FEV,ŖQa]gaaaG< ob׷ňۅ_egdg\9@_XyOn{7W Vn'[aGE/f_Kiklf^9iuc+qsy#\mzJ,5X6u~f7\[e sm(T e^X(ot9ghԋ>-uB?)"Cki,Wj̹"mH+ҧ$g beޥUH}skbŎ~RDڠS̛8ψB.4{{`(c)H:2ϟyW*4]fn3X~t0՟Om-DH|o1m@Q\}Z;>Ä d[Cr[ 0@dLc/uY`M2RTTrsh^ yy//к;m#Hj\Gh5> +(STLTR…kB0LE+^<rXdBNEUI:zsnV h\J(ڣ RZk\-F z|>6ԥ_:A.{N4pedUc}j3Wɦ1akj:k B"aLq1D7j{.fغ8od.@#ˠ(z#atΙn).v/0 UgTU dW}a($J9- pXy!WB؏!!!ǎ#4t,--uuunjDR䤤P(IIIIII 2L"H$gu E~ЃᾎR_|,(w|7$V:B!ݱ 29]]> W(ylʢ:UxLHz`P+堞 RorIAs-:FB1xGN>gɿ -$"߈z,=oAc"V9Q@!`p"Gh#BBp\GΒ!Ca\A!z7DB!.:os#= gDo*&uw!ڂ]ӑV9@=bh?sۀB6n+3ҏyw+K^Wy=`M[K-eόGj:vntS MundO uD}#455r;t[>7iu Ηӵ]/34p{Ɍ7v\ڹ%=\YSUB)uw,=+;.-knL8E $(2Nu|&z0!nu;.~ZBm źp#:DȎ3jYUKV>6$ ࠯N(jO]MN^ o^v y EFxlPd+^f.#-ns:l%Xlka,g4FF'1?$7`0 XP#zmV13|0e9Y9*a1|G$3#bee`9m^fX1韴 $ 7e݌eLt3ls4Z܉;llý pq:uTXꡡM*9ÙS׬;T-zP(>mK퓘(%KmOLzRbmg63g8*V2FmגN-?YX[ȧ]~weX};/<ڙy_v6DL]el6bUWWWVVVVVVTTfgg3?&&&69uT??A}*zç"T lw=(>|{"$DAAAǾ#1.uT߫k1wNO=]Ǹz>vhŏϗpɹk1uVp%R6{Pӣ%:Ø?kH %o2c/>1w9)lu6.H:|S_Rg2tZ/r-, Ybܼi[I\gf|i.L)<3cr_!7~ g.:mP\S6*WcxD19۲y_|^"K=rD}}}1c())QT999))) ERRRRRRBBBBBL&H$ę'ׂٍZh a!BD$$H#kjjZt:>@r]Ueu)5MqXdž4fv]zDz)>* [qm[*Z(j#%-9#ږ 1ҷϰVZj9EZ\Q^6Of4RVWSsU-'F)(pdr}; "ycAVBmCKZx.d^_9n|?LTWOҳ -jE6V^AC/pv_z4e>}e;_w6'˜ d>틟+Eĕ9 4&w터/B(:^)37a87#;PUQTR-o۟Jȹ>%Ƞ2@Լ~_3gٷV位<v'&1M<_Pv݆E][ Mul@!c ϩH9mϻXEsSm vuv,j'Z#'vVuB8PQDᾎ"Ku>i>+<_LߺvTLYύ60u0(JK]a \ԅPOCi9UQTlz&ZZO1nJOSwZ+ZzQmt:߬K~t:nϙU_5c edZڻDr:>6C4e4ɲQ/OMcqՠPMǘn;Pu_x3 h+v/544L_cVߥN]L]m[Oz}\Wx5]UjKۋLpF-ABTLUerΜ9٘د_?mmm\&HH^;q{k OI:;s{N_zGN-! ~HapπV|'7frFJR{g{pQ9z.?AOY/U,5|ϭHCw Ӎ:,.>>oc dR6 uNzY%"^?!T\TO_]fb fIJ)*gЗڜ32R*xK rUX(󵯞?G\}XeN@{nOٱM1m/_>d^҈OYIKu3p :J`tʧv%eӹ^^PSUY׫-/+5YE&l7"4Ɉ j%f(}A%Vv&gdͨT`V-Ҵ1}3Jg RTrWw 6Y}hPs I5j<5_Y\J 7@ׂ*Rl)Ͻ5!_o^fz5 fzd۠2V@-/}6ԗeˏG.(xs@[5إg~;ȑ35IHV7lҳzN^]B5ھ`SӞ>ߓ$ha) @M*m4Mye7)B6:}$Β=gIO LS+Tel ¾.m;ȉ;ub4a89,RdGw5MΘmҿQVYg}s\0Y R*ӧ+JMʤX1tgF5Cyt׷.pT2~H[%77`_Q5#[ҳBU{$u3!AvYy% $AJX^jAÂ5oEVK?'#`7zZ'st:>N}"/A/hsCsvzMYbV#-̫2]t>Y]Zy>[c}AL>nK"_cU}Tmc#W-0 _o^sAƞ&O"Hq0E攨ʆL_h?cԘ[?nTP}Y_zu&.^1uGd_뫰pS1귤, mz]g|6(%yrr@ 6_/-'BN&A c᪭:NөT*JP(555UUUUUUAAAYYY>5,ғ&M0g }M٤Q Α?.++._pmդq|b"ж/w-咲AWbd]a8q;oYS0Nur41{n@ duu=c/ѣ7n(((hب0vXIII$""/XO@@o߾}#<<<<<<8Ou|uFzbef{V9<)2Z NHĴi.]lcYWXXBL,\2" :\G:[]B!qssB=Mhhh\[uD!BQXBu<# Yc2X>IkȖ&p,̫FVk>\ͱ,{&JrRy9<ݬ`׭:vPHHB{W焻-uMk9{M`Z4εQz!Vau֗WQq mO$-eru3#r0JPǣ=GAMTTTW:QBcsWgKfeT5mBZ{'_800N5fqsS06^aDIkQIR g`* 38Jsݐ#SS}~}S=[X1XVVVVv#P=eeeͣgeeee1ڍ8atL̻G'B`W/v-w)cz_\Gbf*dX2ވIW19/<KiR k]ZZJPٰcmS u:N`V]^ BגwpP_ :;(2%\5d9+ ƌC@KoaVJRpY" 3ͦ^l7UUXZhS3`j!RLUHQފޞ&̯ Mube-\GV獣iQY얱31I L@?fmKʎXuPP _tz: ,w vHADxwE}o^/ q |JܸQw\@/өh,u{[,8ٷ-l3.l p'J$ө-\GVoCy,p<:InU-Gt1<^.pO` 2t98`jmZ{:MNliHGGWUUQ בBP( B'\\GjE-/I(S PF[L4^Q]WwGњ%rٟ//$K-{^-MVHG ӳӳs>~!pa>[Bz6鵼-ڐ xb_ o^?|}ӳ_SƟ*Xd{My o7.-jG.gY#cH85D *(Zi2bgy'D *!#%_[7;'2xW6D{h*L>xYAT}^$&m/}:^UUUSSSSSS]]ݚMl7f+l?JV E]B]!=3\YJ_^]ڂbJ_<? _A߿wX1W/<|i~̧J ]XʊMҁ泭w?h=< @XXv_114,i]yRi@=_a?D_fB&GKN@^m^m^,d܄7]}9hJ?s=;Rc'I"%##3aMu>rkBxZreS0W.iPbjɬ׫MX\ב׋#rA֑b "$%H.)%%%%Y FOоVE&}bE*3<Juuuuu5x]MOo[uucwlRm0I=!_:VUUᭆjGKHL:O.Eﮬ~ӘL6>ѕin;|1O:]TKA1ǹȟҊVk!&/W"-ž'3<MCbC 3N^,w:g8WFp&Ǩ:6uܒ%M7y$1z1*)PXϦܶ]6au]il-,ɬ.5}x| # \gۭkFˤX/NM.S>|u~Hkc\zwL2P8H;,p퓍U'eWS4LYfA~ϫI*Jn}#5tgdFL2mˆ ?XIm oiٿ"J_a{_F]2{CI~ِNĦnZ{kMcl:}JS8a'C] 9.7tEzWgB@sΙT}UjzD-5 "ro{Rz&в.zUzc)ٍ|aBZMvġFV,D2^]1U@(xw@eLȑr _i1Ub?ljC?9BAMQyx%Կ,%! odzlj^zg||tÁnD!T(gDtBZNaqqѷ/㢷}ӜuQʗ¼GlM G{2] W]O-#c@FP`s_leGduKwiuƁkaSE11(w=u=äF|,I)hjFY،ZRX6]aCլسgѣާi'g=Ћ:le2p o:6s (㥧Zsf2HgSғ=gֆљ}&sa͘1$<΢K)il˳ X?I֟%xgوz9eYQ5E-f-󪑢>NpĎBqc,NT*JR(ʊ젠㝜<<ܖX+pPh(Z DW/^tss*lKJKJJJ8'B[ ZYY)//!/// @ N=>!QK݈BOMGB=_YD+;Y=m-&xD廧Ovz>t O*jLsuԴd w&6xg)ꓩ J"Bwcs{{_hU8*GeκG/߽- @>87驑|fz2L$x7'y$_F [xCʗC-)3cq1++ X!}B iZ1t3Rn7U{ 2hR3f[4(q *PK0_:7UUXZȩIҷxB=VDMIKKKKKk|!DʧĵMSwFUl9|=}FJ)JȯYl`x!Ci-o  BOW '@~~>xz&{].I&@3q<,7IhZRaϗ JQM3) BQ{zkkk@2Bu*I5jF*d&{k "xr,D>QFo|wJA$ /ѕ'^>V HcCٔ>=rYaB!*Bu #|;5:[(d/q=#3UuFz368r'_u]M-=}n=03-=g񴤱+mz&3SzATGl;X}`qQoxNPT db0r_xfXHB(]X6=ͨZ(;BD=e?\:!*Zl+a;^ uIm!=rev&ddy<u-Swt]HUYdZHWDžPWbu_(8_ :]j$OzN-sϚJAG`)[-&lpخŔ{f,:Lw ۥyv_%y+Y7D{6!#<Lr8$Wm. . pq?*@LgiϽz/ơ3?tD-Vp :6%ejj+VD8P|}zz.3̸ZW̻-ӡH'1xB#ڬ l|W){tTUE7l[o;c*'{!C&Mxs#)7Y`e )4vˠ _k=gJWͽT @Ɍd9HQykXp;f[Լ=9FOUYEUa󹷾#5tgc ugRzEs{;A:S֭8xsFWYhR4w;\Ɛ֨'%&!1,Bz9y@r !{SzǏșZBTPXeyf*rtwn$z4yOBč{YrD =ZLS}c?ZBR)4cpٓ'/׼퉨z>Uɗܚw=}{K=(0x]drrԈ9Mƻ 2V@0|q]`rrrr aH;g~kߤ's6^?p\zڻFHs223qy ;-z bG7aKPʖ`;xFב2ܜbq9ꢑ/nX0Q21ۓX\@RZGSl:9fT|yON`'W+V{n},PEE5"l<$&5b3ܪXk~P#A&uuu_ 6~" e^43>wpgt|d<«l//c<ނ!v{F+>rD Uk\u::xBD{zyyu8`VP(,~D-5 "ro{R3菌w?WtqH'l9}:~]xa! “`#|ŵI Xy?{\~؎":V^^8:fB)⾷骻e4/==^]y/ԭy/0vڡJ>\^^=äy%tu&9ڧ<>1 k{fHUAiu π,I&Qr C45*?ѧWB1 h@l=楳 c<蕇lZ낖-$f5bIn]HL0FsFkUUtSY]b.qo,mk].Fo<[A' J tc$ݸ6`ʦ3Nl8T\?o~d-9g_rD$n]qwOiZy-?W@u>aN^^s8 . 3ˤڋ1pNt*JR)JMMMUUUUUUeeeEEEqqq~~~vvvPPP||]wvv0Ww?/|4i 9`Bsˊ})RQQQՁYS0Nurtkj)qPm^Ii\IIPޒRJ|'_M9<ێu9"(X^ ۍ톧u j ˎq9"ԍUB!I*>%֓Y: B=>!B]J!B+' gq>͙ˢ`B{{^;16D9C:[llO]99YQ>.}ߺ:B)\a+ޤ;8riYPJƬ p?:::D"QWYUάJZq G>7~\d-VXEu#*j:"z!(fBuuD!ؚiLznСCX>qOl۩yy3gȢ}lKw(thMf1C!Z=ͨT`V-V!kddY^^N___sb[՟\2tn݄7۵y9o0U:~TL|~9:Jw]ŋFfj@ڵ)A"o̗\RPTz&G]]+1ȿ0q2; XegV#E}>k7אuhw_F%?{ק'!P/Vbl=~j9&ٛt9k]vc=fi1Őߊ_iSD`g|+U >A9#%Mygߓp߿NA gP(}mYJ@M =m5%ĩ5aa) !pS?zUMRr0s6V #%6|ܼgFd$q;S#*o;Squz$fa{{,G{YeB)|RGuu urV&b.TH"%.O7z K.sV@UF_ksLx[K/./3@D%^),f'#7@Sil80?׺m{ǫ4b~VS^,vÔ]W/RyV-"[W+kќ>4`4Y"뎣@n&UF l:" UZݦ[ |;ȑ35IHV7~Λ6F)/+ zS{D݆<߲?3bR Fhh_ #9yyv噟G`P'mWaW!aW/kF:!rڧI{8etGG>,\\,۟[*#6{,xȑn :E)9K:嫅Z$&,̇o==퉕q Qc#[4h&+Oʤ8c޿mۓV ;k "}5G9>NV~ ZpH])^A>nn~`G.g9ĢV:tρĬ.qS7d/O*-t%"7t|p$#lyYc56|GF5h/q){=e4 xŕl<iq,`2 IDATUN+o8,;Y.܄;jtz<ɶhRx%<](#cvPqjW嶴hOI6a;5ށɹբ*whөQZLsKڞ|wB*-zl:k…vŸ/V^>ѫp+n[M'KTz#uy7-Lr+)A"ՙ"k+4zo363嬋@z5_1;Wc6k0Zuy~ًw ? J5@5Bnj̽\ANF^r{ڿnQ7[ Z;KW=VP;m-inO<@nlp7︓;Fܤʭ-tpciz†I8yTi2\q'o'ٲɃOxuGat=8[]L%E1ɓyH l:^ 4ӿ.o?di+Zd!cil}]6'7o'ђ:MRu.yV$I žj=3!͹vN-1vcFýj$a $G be(@TZ8s_p_gK@}D@n{==4y{Y{s\6;^&Z _vjδrai '$rFK/9W?J"$%H.)%%^19 8RL\wVҎF<]`Iuc9@|. Ön9GvjC$* {&POWWL;HiI1Jqaq>1F_KE%ed3WVHMigO"34O![F;}9ozW׳]JiBZbEZ5Sˆ=%kO?@sp4C o^~IÉ 7Rn(i<זc8!)#MS".\2Xu8ʰcW}3"RH_QښuYzTa0rYxBW5f1Q7 d~ ;2dMyQ"aӱǺ̪ J)V/("J"4I)RVZ\\xD8i:S><8,Jg'!Tj uee\\P[ۤ8p̦b޾ao7~B!*2j;eti7$"eE_W*`(+ Mlq- -[/%jb6ꫨhzFվ~7aƈۻ=2i`Kc:y_ʣ^ӣZ; ߋJfa:@mEYi܆dS[%z:%/{^HɎ8ʂyJ_EhYob^oY2^>\B06_uUv sr-2o ^JA!?F ֍oJ0m9~[o_x3ؐ9"_34X M>eae룠ź@Eע r넄WWܹ-ycqpyb:hà *Y}g,VC27M-( Q_ Ե洽OmNX3RKQUz-b &sa͘1>gJOY:Z[GGgRK)il˳ X|D!v 6-1_w kr{q_xuԿ2K)!N{W#?\w]EsG}XN[F;299 <PkZHՌ |˩{[oمz΂0# MF4eDW֘[ҁ*A=eI6l]DEWp,$ _ z_2ns<$wEd=(ꝲRc4vuRVjlWykvK:YLvk8[If{v:I5PkW\PC|{xjcT,W_:߂oF`rL?klVN#/ݾ`b`xGY*R? zR*>ܸ2.%g~ 8(a8uh>Lwv6eÅ宯20XՊzގVJr֛eZ꾕?j<1GNv- <<I۴ʨ^ :"z3l4\C^8_nTKVl{Y>Aьt8vԑS{)5ԍ=ւ x[dY:u4#g7 =A6'3H'&-" ZNt9Hȋh !NO vNjᑝ1!&D"BCC+++544ve`csB!{Y.­c\<!]\GB!I*>%֓Y: ưc݌ȡNefB)xtww 1}hU+\)ӈ~['qkJ!tD=ͰȆPl="s>͙ˢ`B=,jc1AvcBz l ԃqso!P?=>!# uu!PoMGB݉b^)WWGB:tiȐ!=I99YQ>.}Z$T*0B8ױkܤ;@}c1P󯣾:/L0jagʼn5ހV-//:@WY t2k|7PۺJZqŁ}QP#bPx'zK/.a:.\EzDఘwjDU\66H毉ս^ĺGr7wP2,/NZt:JRT RSSSUUUUUUYYYQQQ\\k׮㝝<<< }M;tG?-J5} gMH~1oY9G3yIn{[q阰R(yyO۳.;FO4K>Lװٱj78 T*-)-+))i13Ro;;Ң<ݽSg\;h>ﳱII Gb8LOupUqjޛgi:͡DM-H ]`.W5џwӷ;vϻkD"BCC+++5446'B!ٚiLznСC謒5nNma&YFj^YG7pX㋇šz1qDI\4*u]%3OÓ B噟G`P'mW1?E= L`bYT&ϻ iϽz/ơwS㧿jtX2;=h%$$dfhF;}}}/gCCV~?qǐ6mn:b!#@zfC|.e+e3ISE>F^LƹCe-e/'pSyҏqg]4@xFG<^a.|@.M<[e7;ҳ#;}(rAxRӄ,S-m!g&{>fw_s/WPt堊nw5>bPpi Eւo&4'@AηJ e $O /D|rh)յW&bL.$EsY# 9u)h"f-L2M*֟pH"($JRRRRHYjNwi& ꋏsoP}QXaZLKsHeѪJ5ZZjuu57q‘icFhO|{jO;FŸnI|C,LuurJ7 <Q~]֗Em2MS\J 7ݷUbReW3-|aGlRVOxxX> U7b%Mi/?GDY)1dPNUb슣>WȲY| ]̺ Ez@* Agl_qqyl"R3M*IU^I!J*)oiH*}:8~لT~ϸ\UUU aj߁#?}Ҫwly` %%%%%e/ έޖr gPu)<8ƤS1uZ'[988p.6d:u}k|Ds`缹J||>SHy |/[]o|ː>l\x?Kr}M(>tۼgn_smM͏|NNH=p$x?!5UTrps+TP_f'W\= \{IbZa) *P;1QYu!Ɯܢ7ۦFnc?; KȀP/'-.i_RۊǛf̦X˷.et9nW`"<AϯϖwӉwD!bcW( 꿵VyS@T֔+xԎDWw;4`gwYǽ8޶p1K+h:PPXAJrnɞ]ˮX{tk$1N4ڮe7L*]pĝm^ݪMy#<' 2+p~-IAAS왣J^ydEBWnOY!ϯm@`ܕXRZZ?;9V+M6[Wj kaAI_r›ppwD~{'^kV5lm,7!L{nP6M̌|7rx5ȯoi@ybN%Nq;{',=Dբ":YȃEE;?фU99S@AGA R+ S&(P"qre,D~b5yZB?t˚; ns/"zH'c=4*fNws_}o=}kGs:~u^`@eMx"$Y^2i-tZv0/Dn|?bUSӍfj''u;o;mL G~*䫌vNYaVHxuMDuX5K]M svVjf;%9r*O'Qz eY^m}'1jC}K̨RPuer o <P7*GDfbUBUEOFȝP$yݤ\ }T,fa*}/Jg7ĴE>6*Lj%;FʛB}c78{e~J|e)Ͷnphj ^xc涀<Kr^*dXif(TWY-uOc7Tu;KD*6{y'ouj^n(^{W׺1˘@)J~HbB`7ԴQW^*P'EߒtykD sg; 䆲 (>rvfܤ869"Bof^7ĵڥ4zA!q1iLMpa< :~yv/wgW*Wn9x6SX^tƘ[ vG:$h㿑{$I.z%Ưwֻ:~Y494vu KUF,`|IO I(_ `gL[Ѵ+SJN(?f8{FJ.lFEn^}Om>gcI:@Rr.Cb$&*)Z7P3R jbrdnnvJݳqքj#74jo+o~$M_.z۴>shVʿt%c3]SY@+ԏ@75#-LXU6Q6&=7ţvXmEIqr q5gVz4!<6w'b1Ƶe>^;z$Gzw1TUVkrr%%&.^2XxWwBV'] 2?tF+:lMi> z8E!}Uu-a_0S" ꫪ0+^V>u771 5*I$uP; ՛Y!2n+m+|w"rSGn|np7w]x5vaQ?Lkfz^vq 2s9< B^^?`SmS}hm$ex~i7s+?6"v2!N7cluz?+hS~۬U-O=Ǹ;wA&ܹQ|ss|| /֏p+Lz^b: &Aa% olr !BaՅ2{ogj)>u഑Ck~cek, @䢒M. IUA E!auMYrBv * ~wvKf.-<I " @E%jrhS"ow5\r*JdEy) Aue\x{WYGz>S%ߋLk~gTH`Sq"i 4'w`,} k,Ddw+*t8xB!N8* 18o1#jh.rMb'G(p "@"Pas: Ռ$|f[O, :+*rI+xj֦䬬dYM/ IUie%D" D ٹW, SL@i.\5REyQiJm.3#:QU҃cpp1K+ _Gr@6KqG 4eRRBU#s4kwZ: ZN~l=nt?egOc-., 7l8ust(rYY54Xrq'LMjRܺ?>m0wNgBxq.Q{q~FU#ļz_mdcb1+>Ю-Ztͥ׏9q vʸ %IL D>)r@:y N߭\+xڠC^>\d\\M )#;$ v2*JgxP}cUr6UUr'7sXqz`ژ=?/&"EZ|4Zx+*]}X0ڥ_vNI#uDݭffЕۃgTOZ=qۑBJ\F!,CrFu9Q#n7\0-p$kf.Пgk,b^HW{EFs܏g>خCgV?l &5J$ƌX:?RάYRW V>|U5'* Y4nOHmUUUn{nܱx@gCd~5WJj.m쩭>3]lSc%>gڈaM&GW<{KWFin+ΝڍEQ NBzF>s*hU"}{ԓĴRVT2AQYu!Ɯܢ7ۦF{ nc?; KIxlfntmvC3ȣ]D\ș5vkiQJ\ 򳳘? .- moGHmbPVF>V<4cg6\ r RIv}z_[ϡc9bִ[lOQsQ 3X@g`9cYqV< zƕuD!P5m㾖g3YqT352)zcDQQ#<9bE,wH$ׅ-(Himk2] o?ڥ&ۭlF~:10p˅}IfVPHmt_޽W6aXE!kXّ%fܲQ~f׸D̬Z}ۭQɨ,wwAA p?VLc 69"B׻EmXIԏIM+%N](\E p,>&h_N;m,Ё,&Ѹ42^՝3ފُm&MkoQvKkCꃕUjHN!Gۑ #V=vZ7~{tUP _l'~UayVGLџu/MQܣB!ŔUEOFȝu<ﯛtku?ַ;,LT@WTI,F'FEIM}tgjyyER_"@ۄA&i5-MK}GD5iF .8^7$7>"̷OױE ce~J|e)Ͷnpiogm]/xԵL?^j&dXsh}L?M {鐠FYNg$gk6ZxSM|ߓ'hէM쌻 :V~9  =,F;:-^5I f,b1̊2AӋrrrCCCcbbvY49rٲe^^^fvݎU4] ۩fzTT4H?2 ^% WN-ud#HՁ`ʕu۞a$uaɓbuab<־e{' 5Z/ fH$ c0 EKKB}XGc $n^n^ڼ1q5gVz4!<6w'b11[/:W 5EjwrWWV4qZ|-vYؼ'hj9Ixdk`0Djyr |vmoPGKoɖ:"BEq%sM': .VpRi@Eᇾ h/x}OMNlPΣqF򜂟8Se>\5k Ž)Y>Kerw}T'yGK k̕ALA+;IQqGlllla7/;7g_x&?!Г^=~|\#3-z|0$^gRǺFlxͮDf¢~~q[5ރ?qV;5M\Wɷ>LX`*N8vy~A@ =O!b3qXK4yD>uv9B! 2QDr vpA.v0$^gɁ3`z(}06B8&#J8@ AdW6,񺎅b %2boF~A1hRE2 2m\E)ֈBu칿:zauI$*k&lVgԬb.)ZTRYS0zoN^!ID\@ZZ<[V288~vk,!B#};1. ?8>Pun>9UDbij0Ԃ[TԳV'*V_YjfK.? <O'F~)CLT4Cr]f#B!|ꈓrXG6٣$=$ɬ ?L.c%ҔIJddqrg7Iu6KN,i[b[C6}71B!BTu8P:"B!zJ'gXl{D9!999mBѨT!$$$:qUSGJds͂ԅEQK'5^ BEE_BN\S#`~[M:"B!C@4IM}큪H}DB!?PwNddB![Xذz!|2B!uQoB!uD!B:"BQ5Ȫp02qWn9Lsu!+ѿD6DSAZRZ點^/6 7gHd]~)+.eo49i]GB!OUNq:yk=\XX}~b0lt@֩Tk0˪<9܋]v|>~vѬ;a̮)8k2̺챻mSB!Ov :"_o?;(XY^ ;ɉ4Jt] ΩK<:t`Ph.V3 f,b1̊2AӋrrrCCCcbbBCC1bD``/궃Iu{}g}kA+Wm{s ͇'OՅҊ)4Ψo_Ǯ~_a*+W5G༻Bǔ.=K޺w0dēn9IKȻAz\7cRibo^>Ҏ LI-t[ua?ݓ`hH+h+ IDAT3E9H#/~su{Ҷ~ScN%aaa BhiiQ(~~~":"B][Kѯr5| NrҶS~zy݌Ywetٺi'^yS9`ĨL^zɘfix T<]r8!B݂^_[16м2X6`3,]M!$Ͱyj$QlHOu˙ES$~] ^ٹ 1XU3VQT=;S[omg2l`WT$",B<0۳:W+-ص/mMNOBZ۷<zD}SS_B!WR5慬bwɠn]0#6X@ɳOU}\ʺSaʈΨtunE'w,l: ɨj\Z .}(:bK:^WT(::sMȇ15+ Y;8q:dn !4 xCVKPYԢ6ԟLB&@EŠ 3_j[xӌKsvݥ 3' ;Qފ׈.Wt8cQ/l_5Cm3 Ou(rS}B~W 48lC.%/4⯎!_GLTJ Xk.;HdQrW sZQ%"*DRjaTZ  `ވ~ t_޽W։kVvvÖo'm-(llfffff- BjfZGˣWYnHgLlur4}w<>/^daJFpt9IL;P,}~y)y 7gJ}~9ŕD^a=a44ֱ }7v\gQ?ޏ&No^;p8R..Ӵ= :2PHJ F)M˕1ҁq0Wvf4;y&߅Cv"GeqPNO/_~;CgC!j'r¹\7̷F~4p7k#6Q/ el2I_®gQQ59Eπny]UAOM7;׺wgQhڋj0T; y^Vzr̀]x[p)v,}rX96L7팕8c(,Zݎ &SRsKeLV,ˊȵp=y}J3A*yNo=q߆-}JœB:osni߲r'wQZZsL1z,P^c@!"/\hz}ߐq<ꊳҞ;|:IT.5:USyHͳ 05wͻ(&v]p2mx?%ܔof[77 ^xc涀w15HNgowV|Ñ@u;ҩRԴn<}ee%H|PWe= {E5OxVTQ:T ,'xaߒBy,'noT\kJgp +pZf2"o1 \ :V-C*~.JRWTw=ڰhz¹(8jP *`K}v>8D0ᮿc4ÑsoOՎri1_τJ$yS 0%Lk|r|cLsNw/1.B'G Ǟkc)^C<[n@A$y[ö~-Q3jw}[<)S:p~wwˈʫ?^Oo,9zTyߘmMue u"_:/ШY*P- IwFT1+1w@n(K΀k!#*l!+gmVM&j8ֱ8%\CiN~Bj}V}~QA'  IH]_Ph]_]!f 8rX >J{8fUиpڛW>t#o6ëXTluXZ""m=~iy>q9 \ܼ!B6`ػ53?v9\F,:Mw]rY{杵ƴq3/S)rTəy~5! 숌t0J1'KFݺme%j)̯os/~<;4Kf)K~a',5lVPnWab"/9S  B!:n$2|jo^f(6[tH}q^M\REn^}Om>gcI:@Rr.Cb$&*)Z7P3R jbrd~F<"D*}*l`V B&oAVF.Yϓ$dNv1IQB^VyeW\~y]߄5s&:;^*>E崤i<ߪ7YB N-OhQ!Bumt1 ښ`\VO$A՝,>@U䗗^E ޤT!p^޴]10oVҿ('' 8▿nډofb_{FM۞rZ4Ntf$PcbS+=LB!j QClmӡC뎲2^~/ٸn5o淕7F[?0*^Pߵ;d0ʿt%c3]vY@+ԏ@75#-LX[7M'jXˌ&wvCEA 'ړMd(B ).ω?~lL|wKJKKc''0wuyf=EɎ!' 'zG|/IgHLpPwn;ixZ|q?yyy񧲿_󒗗U{吃qScfTwB!BQUUl6fX,d2+** N/**IOO ~၁^^^f*-ׂK hPk {] əpF- ,>5mP^ҡlN"K@R{&M궠ҷ)2Ѱ۪hzS5SТʕ+=9HB]ɓ'biyyɌ?D0AP( ???jXǞ-lOF;J@P:IL&"B8:Ǣ6ݙ75j́W<~yވB!/3!ߍyL7Q.$J`~}H}}9`HyoHZsB!_S?VuѢڨIf@tOϼ#G.)Ψd|q5ܵ怨hwMڶO1}UB! SGIII,Y2WGBR6YܕKԸۼɻ_"vso"Rn˶^ۗdϞ{D6l6X'{#:^WT B3~te܏O Up.;_BRUNq:yk=\XX}~b0lt@֩`GM:"BDuG89`Fδaww96"B6`Nn.D7caCݪW pK77ګR:6<:t`Ph.qlvvd]RfuQ'n6nw6Bu-AC=EIb47cEԭ`)Y{nNl]1WURU3kg"NCD!@d +;;Č[7y> 'E ua(s)R ,b*@͡RhºC@waO;{etަ+)^533FvRwVtV\]8tgF!6%::cپk4ڔg ҩ ٟr?P2kSTtwTDlvdvwe!P/B_&oϗ鴾+ :Ӓk/ck6LEԖ"Ko~y0bUSj'Kh6uߐ BV̾{:ΩڷnJ iyW~xߟq M(?`F+^`}:sIS]Z*帅BR4?wNɧlZED^7 X4n4H֒>9oɾ;o*mf>d#cB!g ^xc涀xR^;zc%I"'Kn_:DLN@a-Ō||'>Ǽ-mچW*ys8M+C 8˘Ҋϟc /%'ge@HKRy#R$=f qj^kWB!1~ͼAk:s"W*Wn9x6SX^tƘ$5ŽYqmm;ixp{yMkM"6z'kO5O[@0q [~A戩#_ xTT$AW >&" h$i9 nۧn}5"YصZ,R?Wpi45}ppprTԢxx.=u&b\pw߾ ^]e+iWLF'?|I+@5^BڷBѻ# #6c2+}_'}!%dXxušsIw*1r-R-BoNHrBՔPP[%MnjRʰ}} +[8VzIﰭ_~mLhDAWd8՝c2R86, HY)++7O/Gک6TZ¼Y)/ޤÕw]<r.|[N&y$˫_KmXNYC(EJrc+ =D{ .Y{I[_w_%]\KMM=;Qagjjjjjui'\j{ 68luA_τn%|Mpqv7WoKQ/T\⼋}i+dB!;}-Qv{ϮX [QǥHj8eщ"bP2qy\sYlyo&+Om#^Yz4'cs?a! >G?( ut $y ~mpi}gJG &ӧ@KFjƀ!g_ 5݄TM|hgʉCw=opTm@M#&â֒D14im!6e>Y8͙'JB!~W3 [s$Uw :o#3ORo7)l5;uHL jJ^a2|)1M6SKm##Ào9emEԄ$Ҳ#2Ӂd*Ō.ͣuBb2rҖjW.dK|%)+SS{O@^"fU:Te#*ٻ(6K-JJHK) bWEŎl1݅ x-Tawa032sw(2+~26鹕#&ʍx#fn$B!Dח [A3vr45+;/_P`' % :v W  PX MEk 8 %ciR!?.ynE$!VJKOVfz_r4Yb֏T=̴2YRFJ@Bq5 rz`TϒrbߋD)e*ýHĀF9Q0riO!k(jJ¢Md#2`0ʵYȎP/"t- IDATy9_ٸ}!0Rn=ĺu5z=3d3[1=ȧE!9!B` 7pd+?k\I_إQW^?rZ}bW̸q|Mw~Y!.D!&k[WVDS4) R`e}an#4g<9rhu%ikϞ_|SUB?%]:c4>Y+Bl/uEG|FeuD!Buɚ"L:lvaԒ5Š;6lѺW.<w]92mZi2lwO+VƵ--Q`1tĈV9Z^B!xsYT}eUscL0V8=ue(dE9=JR=!@K=$\9T nդ ύJحon|hC(FK_Q®#B!M՜X. {'ɝTcqYG2q炇+=GF67CȺxl.RBCjQlv[ۇ cμ~rʶaaBgK;~A$;Zn^B!TICDfn:l8Wmi`YɤUth$ Ph:::_5+CqR 1E74$vR= SUtY/ PCŝ 5T(llQGB!_d9*V.0цodL9%h*rx-cso !oD(b>j:.:p!BZ Jj[AVԠ:͋ uhf2AR&U^ff޵զMdRT٦@9ʪ~#B!-w@$%jH+6XHNqV9_O[q#jƗ4:Jݣ֒#@4~iJP+?loQa$qySz@ڐx(ȶ$&"Q^ьbCBczCz+mg[ڶJuG/壎x$swl!m徿z]s?lXfUyNk7 uё`'Vd#D%. ;D'Ss2rx9u]ɝqF[Ђ"j_|JYΊV(mۤ 댈jW#c:˼em/Xmlo;v,0V̶P]MDd2?~NS(g-ulKDdŜJ4ю ! l6D|B!~B W?==ܼO̔=f @ )2r=z9Hu7Rr,!Cd٫X)o]UPARұM撍g?H)C~vo{ M+d|IEDQ]"O8n>&ۊ3[O?$痰$dT:uutSo!/.f?Ɗr_ʷy5ը?x& @`‘&M1  fzd߶y>hZk/JUB )i珼M @N{;#9$"QGNOb,!IDfEafJԭW+˜K'nK-ey)o<ז9wY,Q%.U*<~5[nQA*2&@q8ii ]TTTKK zIK;hj&X7?s7DC2Ըfт!08(b\<{WAcB#|T==gyM?K _KCrDǎL!Bg_x{;ssaWI5@w-=<']zdD\?sC^W1jҢ,"D^JJd5L( r~1q3/'wˋs}QFZߒ9׃60 7)-,0l!`xaKJHl* )Fߢvȫ\$/O ذH3<5j}t3&A aeޖ40"~(K''=23r #O(J׬YQ 5s(}} >z#Qt>j䅇>1=?7/9 .%SoDDW幛r\FRG6j\f4t\JO-6"00000p(@Y&hISP;:Gm]2ƆvDKISwڃvލhf\_57yZ,!cZR4B.Zzq(!y.gtinY6Ori.7'Rwm<JYy*X6oMKJ i4[.jtI[i=J^;.֊Nїco:p|3˫_^UH7ӪU':h-(B=nƖdE\<Qio`~U!!2YժT+Jd>?wg-qī.:j4D jL.ͣp ] jZU?jh(bRi0$[#.dMyy|L6SzGms"F欸3Qlß ln 3zz=i2lwOPs5_}:v|ʴjuWf<NCLǥ/x$3!m%\cTV?#/B$$<Oa.63`FHUIFD$ SU zi! @tu4VV")R]ݽ{7 b$"I"[EH=ʉXLYn5v1:"ڔtDuI Nf>s&/fs؏߼ulبԒ5! !U5G\[UrʛC nqB_Ssd"M')?&+=׶dž(v!T* PAHXι,|xZM ܨT˖Ҙ3Y(T FNRMar<7%Y3džpEr/_z} !#{%\mHe#Eϖb~:U鬤5lW\BW%Hu(uea뉁u&uU󧈚)֍Stj5G@K*ܹܽ/ p@yRmt#BNCׯ'v5,ȶ׶B^]~m2gpw"mTUx6|:ލR3T{UoΫ):>k_4|sڜ9ZL; ߐJ?{ij^#sc` r،b!`\Z}.NsJO&J<:-+sDO;y>7Պϗ 3ӍB&"[*8 !࿋@ŋ/[w;No# nK?[m|vxևl;A6ņEKaDK )А؅KgBN|G nΙwѽUf V[u`UY߅B]Yo:0椪*GPka@"l=תWI[hC7 dq9444ҙ qz0%P>E"c9v5::oW>Ħz,G~(AtvbdeeZI|v]oYv-|uXÛ":>Ks*CBgP{ʊO-%5-` +jPEԺk43@):;vNp[1蟵M߻ABZvUZ sd)jՂņE^yê`/3]VZHLjJr\7QW}S+x^Ш+LU:"B!޲{Ǒn TI"]! wؐyU+mg[P |xmU #ۢ9r}CoiFԇ]6 =%y BU`{$loQ}=*$'Kn",:̪r\o~,#O|~G@K%\vrCNdy+eX)7Ig}s K$fXޤ 8>n%0M;cG5*J<srx9uBJv&wƙmC71}ߐ/LUeÐnX?:~vĮW}GB[:Ի" (]rFW ]eXysYeBjsړO2ّ^k{N85/C7g("f8t"+r3Jמ+-ܸf||gGtlfU㵋Ƅ\,耂Sl)hyw)[puZ9Axx~ƭwh_@SSC@]F6O@>μ{&ߧ0?ïgf^ Xx-tC"~"`)N y:K,Cg%؉5ryũ=OrO J46#g#??S?|L{iCd3*~'KL#EEak m:yִ~<0ԐqF1_j@bzjmH:z|bu(#N4^O2Lw/v߷Ԯhaw<>6Bo{y Z<񪇦v ~-췈?KCkk7o޼HH())aXu hɀVk^DY\7ywLW>mbCoϟ6/VjBAV !'#up]N:9ۨ}p}y<4)$.S r{7?,)B9Lwna(7.?pY567|*W-"@gd (zseܹ"1ki$17xS~u~v0L8ޕjF8@UψEQ60גq9cTI*OfxRduUVOj]~)ũʃ{ۍ6nGԸ'"=L~]u?x2ZE;y.;!=Cb!Q|x;!P8%^PF$l& I=1XBT<̬1W'ōމcD%Ap-qō# ヲef~ Xz_?KrkzCBuf/}\u/(~0ut{7W|;yYo(~ɿYFĬ1|?/D 9耨P+ݴEUT #~k,!烁gV{E.:,zGGO<3_oKcXǎ=)iPPGKj?/+w/FʭXɁWl--?Sqtȏ\뾈>I r5.{Үa,iJJJ]frUP'1x~3^򊓗EVjjjj#O\SSSS3ӒǪAw5IUcSOVz[5%i>ǾqjG?_}9eSv݆璆TSe+h=▰@OBўxC3v Zn3۟7>x}/2.~z9 7rkf̕~#Ӆ irF,7N?9ѯ!p8N8$fs$`w6fL&d0eeeeeet:4///###%%%88822r5{zz.^ᄀ;&&&d2K4 [>.5rrޣS5_n~a{p@;v,܉%6$1q#N5sBLQgkJI-_m?C?NyP-d=ݿyǑy"2J*&=V ;Ki#S>YdhY6sC}bY;|AŋvߎLeȨv6s[ۧYt3C Ǐꆆ D$:KCoYjS%Qt::B/N[M#Zc`0EmVP픰\޸3(ȭz_x)Q}r[ՙ)>WGz gm_{8>”s/ں}q E1~`YB~ j]*zƊ;s%cu8éɶe}N ccZ>19ρ'}j4miu$\W>z p,Ngh|5ó^c0α0~fQVfi2hРK_x;5ʕ+;:Nk\eUdIIiYQm}[P*Bq{7U qSP؏^P(v:r4n}'rÓ=LU =ɨ޴IWg\2y[vɡR2R= 0MVX0 xH8%Vaџjŕ*NƉ{,IYuz2kU%H0FGNA^K)$B8q©;<!-- B'q8?qW/'g4,\;MWw]Ʈ["q[޶ҐŽ4p8}s^j9SGmXyw 2qwmQyzp8R&p ^uEzՄͣ,'[}6w_qh /Gc_&Ü8uO_?y]D%ͳzc'5}ۨqۓ,fn_(^qK"TOZl[ϲWl~fĮ#B?M0u3r~!:SaKk?m,j+?<;i&K{p'xS8EW-?о7S*b}6ަ9 $ Rt:i/3QN2O?X?#[p}thpw|֝!X^mfkrazL VBwu`RuY_9WlgqYi1(.ܷKٽ}Tsg7ٺnJYkCڬz &+Olp@eb7xfţ([ }Ibg8"*[ɬAq$"bϗit4i`k_yηN}q2Pk@lGGQE;:vx*UTH"~( 1{W[vqZȏ.v. ˲5yOy}>}̕EV7#-E!@iajQqi;|S8=3DR[B釷ܖ#Y꽦QoRsFߝ=lCN#5f{B傂q~<@;r7m{nDnq©ru߮lB1p=1F^SQ{XG3<⃑$.ua0su/qB=Y:Χ i.MT>Dnw;";$iԒ}3H=xjRbo~,E+G>`*~4^dE)8GL=䥣+ldU?߼{7"Wp궉f7,^0cޞZ<;S%=|kSno zz5+ }{< Vz'.g8l6d2L&(+++++饥yyy)))wYs~~~:?o;{OI)nz8下.wvqgN?ZCm6r[ʺd>yJP~ګ6U|Yafpǘ5PrSvdHPMf.ۺ+m7{ӮzFIXc}w+ bV'c!6eѫ~?^?!n_6H9EFJd䥤;omGL~Uc7<DWsz>۸=QB[Msm~;v,܉%#UZ%[:.##C[%7oAT+C Ǐꆆ DwOX0j.Aa/ EdJH̶nI=1XBe:T<@~]W,`>YyQE]Y +=Z ։KHV~ Hl6@jULLEQ*ZV?*IJ4Yztf! R2jZ2c[ۏ%^+$J7+ZPg/Tio̍~#B6U3: YտL!7G:v kҧe?\7OrSG "te#_~,h>4Yx @Fvn!M#@ 3-L֌&Xh+«|doOTE`gRC/.BD}@_ {.a"BK!u8$zrzKХ5z˿gw=Z-zg{k7d1V %;i;fX4(p \*7t5&,~Þ'ЗoN%e^|ܓ.qLK<վǿoP"kk>Rڵv{֜v\YE!+ZP7!Ժ"~{O-(,D`=PK 4hHް؁d\Gz9Ζ5 *'tmoIc]R$uORQ79Y =Lps0L}t#I2*GԏecS~i6Nw0)։ff䗑;[>d_8g&%IֹDc5~c:KUmF81KNvNH8;vB 0IVbAJ!~>7"InY$Bw`AR>(KZ҆crZG 4Rj=;:~!v?&#ݻ19 \bb<(b0V !;&"I)"L|L彼P<3vߡ=WBE$S'5SAaZA;BVҍ}՝wCk;B!=#j?#A B!!%d۞vq`ɘ)9_&c6B`ױ0B|`z̨;.}J?*-+K^Qy=4Li͜SCiTm`NSw B&a3>e`nyVdž]vpʕ B藂FFLݺuR6XC}wv!qwm.D~&ٛhèrf^#yO>HUàk1LeG_EN=6x`kkkocJ8;V_IcMѕXNM@ô|] -eaC#M-ne7]&aZW&%ml#՜< L"1^?)v IDAT}UMs6),Bi:_&ikOi:: ԎL|쫱j:@yædN|P(Gxoa὎ ">"j]DM ܔtZWG髈+v;Cx{`G=p[é$0>ƜVb9sϠ33Nzbo*#e''N\ܨ̐O7uM9InZYQO>3cͶwܷDҿ盾{C4cOgsOgl``,CbLu۹jFE/xҖ axѱG )<&hjHnNwIο5Vܙ(aGړ#f]H2y5nזiǰp!Q@^*CS9Y<ۋX[ڏ9#`>m!rSV@]X<$Dݻ{.ˊU˗}@ԻKΝ#xː7*a&@6d^.XCI fӵsvf:ߨ {Ha'\]0hÊRjH5z^S4^=Lo?Ag'C:@I6 l`JwvZM|h~>s1TQP9Rq|hTߪ?kN-ZcLābX8Z}ᒔ˶ V:YXH$r_T9>@ pKMޢ[VfĔ>?Hױrd)"Y:T%ύ0!d *}bޓGWv-TdZ x{o{QߨuѬ&)`^o"< ɲ. {'ɝTcBw[=Q{}GA!ՃP2 ԾxRxTlsÕAv#e4g@I^vY͌R:$oxGӰ[ӉBoWPP(gB8{5U͗sŤIoGզ~Bi*o7>=lv''>zoӌi7'̝G෺d} ]?_9V?vD__h ?[zqvZs3OA9fdUON69a}ȶdS}]ݨGU}ta=6m0ئ:uȭ@mÚ׼Nx˅o8l{fZ?vg~*L[NlUc[`ځo7XTu ^~tQhe_Vׯx{CH*:4 (4 쬬I*+$h7WUg~ ]=d5+ms8W8~#B?#sӵa}{Bs_O X[Y[HSήneN>:۽ŸND {ʤ̬sV6dtmv<_jkkc9^[ 5oۃJE0(PXTԵ+eW{] DQ@c^B#M HS}7S-ܹ3Xjq&T⬔D?K6=HJBj0r(V(KAU0f&fgnŽ9*N5@g%R&t?^:D\Bi:_mLe;\XRrCl;NDD0otq@tD =EE{D~l4Ip1-Kߊ3n= S$noô͒]tڅE4gwQ:W7umj^ l9X68{{8_ wD'Lԡ*VI !{ xڣ䪴G e/(^GvUVXzdɣ=:`2@MBFAMugKb++dAVbm\]@Ї) :Ë ,14%G:w׭x_g<*{>σT43m~QR*R6,: bbb b\_z<䪋HϵzwŪ >;Q?w- q߉1Oo Tr3%8s# .IR]D$7҃bpaowIA"hg|;e\aU0s?%sov b^w5$iMR㎢@ʽ$9 ރRdŻ0= |}/װ>ZVba]Buf䰥_D&.^1|Vj%vC9oxPj>z$aʽb:*u;~7ohc"sFӥvx)G1Tq{吸K91}2q@6l؅ &2@~bg& o3J Vjl7}wr94Xk6~ڭGb@uhgg;Ӑ'$ NIIIϯbXJiXOGLit՗Ml קT o#˩`5eGn ްg"??L|r]ҡ m{q/||`(^ro8Q+kj˷:9Z|EhȈC{v]35-UU˘Zd|M,"ÃG q(u@4 Wb0r/%ƷɦWؿ-?ՓߖF=|wɾ,)]p-i#!$ {m՟L^*srZEi9 jCO#=`ɠcX}5&h&d56nߐ6f >ՉK`s7UJ&އaAKvKuYU Aֱw'[tḯGU/fuCLjڕWۿvr MPzyѕ8 <:,tc?n:dJa$5cH.~jQiO[ܻqu Az'Y222QZ ۉwvt8]fzlG'p)޺EoTń=B/zbu^ŲVFɧv!dbRn!?+^s$%H RlbQ5sOQki/Nnx iaSQi5 LGU;ȩmjC2Kd26Vqo: N6 e'L:zA\:#<6ϡ({;LVt=8=v+^+8fN꒢Jsnq0Uֺ**L5(L~ǩh32zW&#vL&.`M&/-RMNb1u9g󸞣nJDQ1ieWDOa8^Kq>[EĔSHsH$##cSi| []H _y*RoO53S2H8Ǐ 0ʼn'nGDŰmذU)sK >#g$fi<5/|V>.*)3g|=c_fLb%h4ZC{6=XM>1䍏?U^6hƖ,^wъSdĮy9 "-ȥ7Uio s"=Qh4-}-A#֜!&)ۍW-Nެx}cJBaTcWVb+KG#NVGfQ\e?KH3g.09ދٝR 8{dAP8@jSG[xMrtfk;TyR"ԟ,$jY]yi2k 'rRܕvD1m Sǀiole8i /gsEIRWxuޛ泚ف hs#Lv3J 8}.})o҇!*u  :QU+N%iN=Й?XUgwn7vy0pxMB=Qu@~N!o' ⅄x1Q)))5NvmH -m- C/' psq2rٓh4v TYlͬFػ&nx؅ZYQumק3YZV.|+cMuu΋.xŌddd6NC.%xlffffB8uu[eV$ 6kh[m3-MBG(uiZQ[2 n%KJ0&҅deEH2raw<d~ť咲 JNA쇢'kd2[fܜϟ5:ڴ#~d2L]hnTbanvy`_K{bA!Ͻrz(AL݄D~d 3%FQ\3r^$m= EcO]tabq@p9*/T@vRWQ8-q `|mUq IBjE9Dxiz﷥Q:|)Y7| oɉE bG8g\UC0a60QG-_Cr#fB7ǯ^<<>hilyITŇ"SY3(d"ײ'7ޣw8\sscҒVU1ܘlz18)61ƒ2s8E%RCB> c[Y&'+!9+VUUU}W{7j tt,GrOBcYϛUF[}Uq'RFm62p(<-0;(?y d[eAF(ɒi)Jz.AǎxMbƙolZwVڢ+W|Eܥ61arez|z%u4Hr͉d. #qD7AvEuM. Lڙܦ!{N}q` &4K0&k.MO\nso9q?m"@y.jiј}.-9;8; Ӆi'mM@XT]ۡM;/=MLH[fb???3<CG]:3WGfd0V]< @p>k*{tJD1 ?4]k*Lm(3"(POmѺ?}sy_"'h>D FkA>>>0pqTd56a׺%"Սw_@sm֓E+LtM#ȹ3{w_WMg>GL&:W}e6L&..h0dz#]3KXy*EyT>?ZUBLb A^:>G0%ulv&!\ZC_ݘNm{XP_xN:pn6 vdbEs&I*UCE.%qˌy @D\ʊZ}ǹ#)w16JOl%Uon#1bmɅ1LJe/JK%M]}Vm[$c:G+|!^,b5777773"z]\\o/_DTgA=l.\sI[hv  wH7kRЮ[(Lǎc!z!s 8:F '.0+y_k.u MęS2VH\Vyf·~g߭1!gNR숙ux""HɱYUJ?G4co$V- GͿ$)8ȍ *fG"좯#<]&D \6ޙ8h۫>8-XCBA :~F.B_t8:RGAzPRG4M}iG}PN2s M Ko\缱=F O@VVPt[wW!t~RID_PQŨauJ$OH$h*  U^V0.|?A%},@A@CTAހRGA٠/A"   @#    4`AA~6Yǟ̓g}*#⡂? -N~4U B69?Lܠ߽^Z'wwDf|zs۷*BOܿd;/J%z'Ȟԗ1JA'Fۦo9l\lwuZġ_IR5~tɣ}m󦭍<{A[B3D,0= @y#= L1WS2ŮjC1e O<0.G|'/o m^ɏ8pr L)=6}0zAA\]Fq&$%moc~y&O M%Ir沽 ˑo+pkpuyaB8c7Mc6} (WB6m?=P  t߉n4;>|B=m3F`X(f˘R:mf؛={3I^;O޿#O[X Hd`=L I 6v\M or6:4`AA.Ȟ[{OxI(㺗&;[n+{*AuN}9M8loUUV!'CƘ5&k ?2 +FmiI珮8(KT+;{#O'Kx~/$MKsʪDABb4UWHn K(m+5Jϖ]7Vן}gZd$Z|cpOmѲAҘ0xavAAAhj[{i U? 3A<\4QV_I[ Sq@ku_>GwAy~cH؜v~o="G.֨~WWT@TBTW3oiE~Xr=fʙ<1Ӆ8no[M[[m4 ba<*hr ZݾiQ$ <ғ:\\%Ġۆ::BwA'VgEE[jAL9{Smv TY%9)8g=y[6kh[mq؅ZYQumקЮia=]kcdQqn.NZ.{zpo٦vfffky2PR״wkqblffffB&z4$-N䆑cw}jٚYwMfz ^Y bxomQSh}Vb?BLka86{TH۽̚??tPR}74PTQvmG~O;`ϼ # c3Gn>Ul*dG^(u3 ]chEݗ -Q󦜖;(3R}9]MǠ}z;c=v0бu17}xzx*ݱ{~?lϡwLGLj^ߴ*"LsxbaE&.KFG{00( ~5Y^'Ncq8h qL}o^g"[ZeswDWK#^;A@3[i\^J(߈YM5sxFbP'q5L'="4Ύ*叿(ᕸ2v}J ȏBEeHWUUX,b4MgEu<ء!/@h9ky9F2=ǔD[s;7w{Ƅq" ?<v#9NA0@F\z޻,Q¨͆BBb3ef<n4'{lΝ7<+G9c5>Jsa۾ Io轊_/Da6ŸloE;`{۫2腙- +(vY:#68T _ۼ˄>c:3]v6N<|n#J$:<Q,xłuSvj:-ܹ{G?@ <;j%;s!j߻WpU#  |dMt6W$򸒹;Pzѷ8{o Aw_S%fJڶI(eے cPTn̈W=/w'Fz2+ǘ3iOnWQyFD\ʊ:)ݑS^.yz}vm kO~)Ғ/7MƋOet:^&/a'q t1aАz^|󾤼c~uGu}:7FR2~N;c }%ͦ>ݰA>aE^%^kr_(uDA--瓌fE[a5+[ߝP?IކqFZCM<_=D(^a>DS[L! 4rL\ޘ16{IzPNٍ6Gcnhb2f txڣ/D,6 (_;-$5暙zA̬Ӫ0I-uB3Ea `6%bog6o|`X,`455544444UTTh|`eKQ<1`5,{Q=ũ4e 7$WX.DmFY꘯ޟCwA UoCݾ4`iGDHHHLt"S:t̙3'3ʪ* \YY=QH۾F|uMᅷwWycnc H/Y__OPttt( @ E:"?=z2YOA??D?ߑ|Ջ!ɻ, 6!3ss "װ;Vy)I5k. 'aox>:"?ֿy8н :;"C[1 7 ;1d dvݷauYǖNӓ!H+-)|):A*+mGx-r2ˋwyT P6(AV Ps(^!CS]~lUaVܯ$V* w0WSeƴ0Oc%Iq I1셊kȈ3³1>NZJ/&XYALFv<,I]RT\tΦ`;9YONLTFmGR-6`tb׻*EŤ^ydy qmbWIH'βnxˁC~X*:wAA49(D}Ըig}z#|Im_?gֶۙV)jUmzۆ Z=mgۜ~UUB*y*/}ݼwEoo:qe}2&NC|\TRfM-ᴋ.˰8󦪲8JH~M>1䍏?U^6hƖ,8ŝi~.j/GWv&)ۍW-Nެx}c34US9G9g^]G[7 mbTM8?j~e۸-Mh[skCuƽU{|H߉Oo!?ߺ;zA%hUdSUkoQEsx]x"|$;K9 _6]縬m=D?;YZgI"$<+44ϻdZ;NHs+1_3>H8849=2C&qĴ2xc |~N!pҲARe$Gͱ^o'I]8յ 9MSL"_Kq>/ջz8u  :QU+Kw+.'HA3;e0w(NrثmbT㑧;@gcITݹ=ءzNmLЗfpe ȀuDrk!bqjU/.BrqǃD-Ӄ:VԴX@㍼{ B2@ #@dȪo½}\S̭f(r M$ވLD pafVN1$<\Ғ埇VUu(-.k.y;0 KH 4nA9OehU§*>yqtAA;cKK ן{4'ਖ਼[RUtxe?(P.F)mxEf~T_0{hV&*klkK!=F,DFjFѬ: qD@@HF̝}>7BAţ3WL/"aaddћ W`={+18ic ʻgO{SuN㸛%55J$<0igƖx0Hyr-`ϭڻve H:  KȞlܾc }{:Ew|đa,WO rUdzQyu#t:B\deeQwA,/l͆w:r5T)5-j>6d']93o$Tך %Lju(POm)>D'c89y911aT_֓SKhQt9NJ֚S Ɏ 61C2\Յ|j䐱\jl2YkS/`Uۇ41V}4Vh f t8Vza w?1vrrrco^H v {Ug̙3'GWVUQlAz CP&4*)];&%U|K/H_z}'*#9-ԂPFZ e0b5NlW[bH&;~]yiL&="tN(om#B }!%%ֺ;GmUq"[QOņ ]Qvw9 O8lK H[nh%W}lwv:Ҿud&Vŭfm. 2}49QLEqQ3&:YT`En^y1GĪyOVjyX%D)L@1Y9H\byץA=@/kKչ)&-ME Ϣ_qUTUU}# fxgo A'VԖĂ[r|725L PvA_L&3W$ 9? Ұ3-Lf}:>ƿ{{7GCA~zG8߈ #0M R/x㈘f&{ei4;]kⓗERB?Z|"zfOUNNCf#ɀ*/JJR6>U`MjF{Rk{$.,t+}9)=_9x+Ǣ!wr,Zz|&7:gs};vZلcмU=2| EiY%u OgwR#z:h%gIYG7u bTVQ"*.fREd+Q!Ӌ隘Yzs_YVa3ct'+J0V`/A;P(U022J2$occK(J6V7W0dȂ__ 3mp3շX^" gफQcFN unàQTm%Y5=Yl,s um-uMy ]gEE[jAL׭k+FhhikimOl.k 4ڮO(8g=y[z)7iɿe3wk2]|/IVUUU$rtNd9H)~&@G~(oE(a5!/ZB'}-r|dY|Pn.9~_}w=LG1 %(ܜwH>a]B%!!*vF@X}„=Wl)5Jy{8 [ySN˝z|y \.5Iޢco^ %y2o贽qltҨўEy~)yCGH@Q\og,:6}.|MbFr6UNochE;Z{ok^\g3I3ߜf}7 <V-GWO l#dhnn.'eW&i] ơ ⟤$ǫ۷o@ݷQ{>}q#?@sM1ϒ|=yl8ىwF_56QHNVʫ$CXΓ(8FCJo|H~0jGi^4qc{7j >}Uq'@2=ǔD[s;7gb^H륚+c+m1NR:?L+ !?"JD< `׫I~'? Pj|6Sn[;NZ:hW|WRZvswL7 @{1#  }^=C @&WVUow႑*RFJ6'6d%Q$ގ_.;63?Ol4^P۶~_LJó?|}~EcŅEET%8HL{΍^̾]K`pvA/aȂےIKp<2mÛjR˻sgu XV2@XZ ZxwTVvRjzyqVuƅZʫ˗ڮ !֩[AAeݿ눲GAKKPqqBBsn@@@cr>${R=m4XL[clK3 WAX r2WƵqO[ρjԻ,01Ԥ3i YOnWQ7|yEFJ]>'ƋT,&YzQ6oyڍ1o߶{l$pGCOӕf*:Ƚo"_)u;u?V =1Khȍ ?AG9AڢN}+GXW#}NFͥ5 㺣! M-rBd_:!ӏ?/MS#\_(Ks& IDATU0f&aXu5Ekԗ jfͧ&8P&AR"=v#o<% A9{x>hUۏ䗔566KFARRիFnnW]Xբ+1>Jzpm󮾜VEG2qƃFGc3RzwҫiKۗ"l鲵CUۤx)3_m+o+V{[Mqu@ԱsjKJC+Y *jTW3"4+p2P:ABy#KA*"BE!,͜hb $MxoWᇵEE;dd ZYrbҝ,p 'H(r95ݴJRJ*_ZWV$3"ȯuκ_rD:" Ȁæњ za?SAQf?]yk'Ӑn;fe蟾G2C,fٸj .?#߻8J# "#"pLqVWؿ-?ڽy.$J{|]??D?ԥy{靋;pBQknhja4Xd4761-LhkaSdP 𱚚 88Jn[/uMMX#Gh; #+W;Air.ˁ:"DZ d%mȸo/-1$4L&t2bqEFhL 4V$_SϹ: d~G?rw ?"::و!%aYUU}^9م*wLSpnu˺\rip#^]pҲAReAƻ8bx>?S1I&*@DuW\$/ő\Ǜ pn3 q6A^&-Ȁu@>/V~>d\mt%.e1$^8q<KB22_H2rv]g AqhU SRRR󫪪X,R1&8ᄍSk9ÜCMHck5#}6qd>q(d"ëojsOz; wq*zo8)61ƒ2sz?TŇ"SYs3ߪrkVJ|@]17Dg%FN'S)57jyML2O2/-+Z  w XEy#«Oϟ0lHOg_FPUtxe0qRw3#NicItzf~).=HU>676GfmQ+/љG5 ā CP]S#dKvhlYOi2q䝰Y@OJ&BR×g2&4ɨ`B#KKx^\Bx0u8jʕ9P|.I=Imf?3 ChVzh3  H]T~xe_o?ɒlS#8~~ס#7_3L2^Ӳc#aAvu3(JWWZpno忳.]2g.'8tPdյM9xҕcuNj}sMq5s6,Ե;[ʼn铗FUrEnۇ<qu{7xW{5<_E#Juѵ?~>ԌNܦD zݵ. wHa< ~ؓSU\ \ͥfJJMѬ:bݎn\;OM6曳X_t*Ѫm}BtZ0#?z2$Cy#1c-˚ ˑo+naFq&$%mo2u&O M%Irpg[D2w_sCS_S,HJۓPϻn=5%_vKFNO"D簒vNqA0}@?kq5A  %#:/n%y$ߘH _y*fҞIfUe)Nx?jŸ 3V2ka[ ·y)oLS/2~Ӯ|dd- yJ%l>ԾYJ[ {3hvZ}̩51Qv`eɧnύ'6b?e[T'+/} w 7qYB(Qi|U M) ~--i!E;BvqdU|'w{XtդԸks]]Hu5aQ2K.2 tڔynwcu`[d7$oYsGWr *0(^dz"o =Iuy -lP}b_6װ IzZ- œ>U:@bJnE6v9(Z8] HΡIWO9i9_{gD *6rH,*'N\'^ԋN 5&d~t2)X˪.Zmii)g7GgLt~ =p,LL)1Fu) CCRކ[ct>C ltsqddž]\g:' τ#)x~'Lol5-!#5Lj>FnRq 19,9. py|f̿PI%cDJxA p:IEnt`llZhj'ŰHpݴ{ Ko{'ҶrcV\ Ed>zC^R#Λm.o{mAG4l(B,og#\ƌ5t }I7CMuu P˅YMi ]jVP>D֙{-42a=iWJK):w5c]MA' Y9aܣXy.5>~.2kZؓZkddd4~W` 'MB`89uL=nN%7jp,e_@تiRMDQUPP0vXH\t#F ojf6ED!;#*>0Ĥ|P1"ե7F,oP__ӡX^ jZo*/_L*˙/-\،kuSlA苹Ǟ:"26Z3ccc)!zD%!9qr%=Y[Gz{iG3bo=ޠ6v{GB{dةי1%o 8GI-FXJDŽq/pꦔ2@qbF}B~5.7'(m߷:7Bq#&}20%%qVVUUFc0׷핟RI& k.;t b#|MB# h簦#+eVPJK?gOp>dqIw kaNk5-=n هJ$}QvZfgk 6QC冏ByN_L/2m6I5Ģ$EkCօicIQ:@kɅ/Z1lPVVdQ= 6 Y,*.CF.UZ݊r\iIEZ7b }ðfz{|=k&-BLba')6m70/l6mELu)ڃ Ŭ㯆hⱝ5GM:)K߾u#lҜ4r]8$5JC]^[?GyJx6h¡#nb @۲"Щ45@ujum=?$__u΂C zo]I)L<;1I 0jJ̤m58DXt7@u=c'>hw[GjAe;cPˑ5[$Y7U%'GiVצ\&n pYoqlct0~s5,њ[tYfF'6z?4rdH"S[Oy|ʠ?Nsm EyD8aqj#FZI΋9z׬8?,NTh! sBYiqsPZVBjLޝ8y!:#x41|sHl36i}',%m]sz[ ߳)aKm‚f{SgA0"6n\k' (kNM+7* ̘Pj[93 \:~idF6`ĵ,r*id^6NY^L}̴ފ -50!2j%;P_\ Y&`HJO @O HQBPiT)[gۙ8 Nmy;|^r|x?/tz;Nkf$gݿ}⶷7fEO @)? :q{yh.,@D Rwmby &10z}uECnc\yu_:Hw[g@؝ {Jk’ ztM3a%7zT]\/]W9u۲}t_m^'dZ."tޤƗE7ٵ3SB=씿 }*pZ:a7w,o:KzL n -: $4W80q[Ӥ:6& ' IDAToDPd)1[Y֝N5#wAz#t:JRT R___[[[[[[SSS]]]RRr۲.;OX.ErՁGg4p9vj'ZSXچR@szo!L^#~_o,*(,k8NQeF ?QG'($.q*[dwPI7'q84  :-zlO&<~"ك-͚/u<\;3~韛/}ll1['͊}|>}i得^![ە{˽%7?RڑfoC;mUoGlR}j\m{^2LS2+yltIk,]@orV V n\- _!@SA 5?s(j_$1~yōYL1f9qir_I>4tadgi2+z|glG|w);xI]7Ή>xyTb@}r}jX~GA (tD(o<366bwq)FMq=4֔1%+u1`yS{oѴ!zG_Bw!`` L{F fY1.k\7FEybR32ӱ1C'?YSo|=8J9ct({oc !lFLs]mL<-Ā5lFI.\&{#{#¯^`?Opy㹘TľyOv ǿ˩zjf/C3$fWgNYݜ"a^n25jOR\̒/"y,6K.@.b77>IE#.Omōo#?xddI [aPUѹЃ&]~vh$߷"N^wTg*Dm v2qG> J98`[7?̭ZU˨Ш7%%?d5ڢUPdCI8J-Z]vĮKfmyd},@@ɿץǍ8aiw]Y feMXFN){z 鈔͑'L|sdqHz r]{dxf? 1KHHh ؽELkis{PndabuM^3Yi;G$D`׼8g89b&-t#t, du\rxFxii';aU\-e!:M=sH0/BCYdCH\^4ʎO~X>L[[]X=)n25j;2gj ]82w쎼’=(G+ɘ/n2o<ȭD"XfE"Ht<^+#{|..tTexI@A[q~\<.:=|xU$^VlߝȮX!)Ou> iq8>مt塪0y3:Rx[`\As7s%eͧLjΕwy^^~ouV>bnN,Ouժ1leq=h\NөT*JP(555%%%tߩKѳ\^^^puEgg粲2taaFFF2)EǟKX t_'~?!+W?-X#;vlƌ@R{زrxm^s]oJMg:DrG|{f?|?6F{C߫;ތ;Qѱ0Ws/JݴqX9QakN~e܌Ï5^qyH,[c O@atyO|f xL[ q.%5{'BơvpYz+SLR6Ι~/[r5m56x:M^tbϧ\w#BP bC2nlB----{p}d=#֞ 9nАԖ|tEGڐ$9&`!mg#g (ޱh7qNо ϶Nf5nMa5u)iQ v<<<\\\|My0q*턧AT\Ԗ+ Ad'.z]|;@">-8rݏ}2\zC~$g lJ|7W}< P!&Vͻ)Rz!֨*((k;M$.:wu#ڪNH~CW3i$MDOK3RZo|/&ee_@ݸ&M3z]8jp۰bUž$]馚u&ֺl# իW999ߛ#??ϟŅN^ySaYI?Aw1l(B,{o9;ڶʲFS{< , .5>~KxhĞx_###: jIGʽz!:{UK{#'7Dc>ؓ4|l])m*飼J2YS)ʇS:svҤ"ckfjaE3YHy:;=uYIq_3މ>t3TW \H1EPg8[C!)zhSjJ˕TaycȊ)I<~th-pX$G]_"D|yG\r~HĦ9ʢ2:d} |&(+lA\~"M,Pbt;")5j尲kuHb [ VkV{΁ v>>>T* +t:ӳ ҟzp..ĹRw=uxߤ?#jȾqX(i& >渝XH.ۘEx~^~k%G'.)81w {KJc5 YKWqVQظg;zg\]&ZO^Zz^vx(M^agOp>dNT,S5%nRZ&l|*~$diAqK[6}h, ?$VclcJD91Fƌ;v!{f 3LMZmԑܸZ0[GTLJӕU(ھVTt8ڈ#Vm5+O -+{&"uvF p@uyis++k{^`;Iǝ|?5̴l$1lh苸P ~ Asqp !K`Yi Бhf$xb3Kc+I"hL*`qUs#k0'J כ4Q'nINoiᚮ=^=*Y{$Y{o0YnV򀃃AuŸ](;6/+%.R7WPc3,77''g˔3LLLօL6 .ΞnITQQI$!,6d ~mjl5G=jV;Q_}(4榺XkX?878! gC*c% ,"lo֜re&@kÙ9̴VfgWa;x,0>:Vi zV @v͟5ES…&;<̺t [ _[mI7xukl #G軸н@_!}|򧨡ì|ytO;ޝQlP2/k:ƌxvw| @M/YBgw/" )1WDuڇxT ,[6#$ $rQ,@kɿ-_l9`;<.?}CcdZM˕k;U#xN1<]OŻ]k=Y. g .ߔKl56tn !@#2x ˮ;|dCc6 Fi@؁b@A }DF? (zDAZ  =" HvppF%pss'%%Μ9u!  Z!!USSс͛7{VKTa)l'zo *$,a*dSZ >Eh(tH0mܟ! C#s,oCCP&嵵T*U]ޱIqy#ֽS5d1ǭ;Ut&>Ql_nn^kfٿ;yAύ p8A:"A__?17t;prr2R?5ؾ o1w8A`ߟ!WzkE_ t{TPZE.DJ,AбYCCWZe`-'.$ $l94K vѕ+_]Ӷ cKX4'eh?H$uVTO#+Yx_ɢ5&OH'8δȸ:DOefD"T)@9(nYXu|pC !Q FD}bk)b0A=ud2AKy?b$f%,R gc%/%oiA#CǼL_#߱"VB~,NPH\NT&tLбyA~# 2ؠ'dB~~~ϢGh`z1-Q~T ܚd \ܴ2QdZ(M`?]V,b-)77~&ݡܗ, mUo-,/J ݜ5UXpj9_c 0-rh/epf IDAT w緝AzOW.q\8uҬh?ʳoL}kV,%azw..{X4UE_yǥ`˕,YX ]29<󊘍'sSOr.kNKzV vcˀ 2"D$n&Rб[|1^ ,/n^Hw,Ob'H09i?8;muu !98R{˼`&+fs3&|fYn4' 6lփˡcRf7y1Or/}D^xKW &u>  Tnm[ϮPrfx56E&><4&jݒÌo? Y;v-&ךƠσ_vxwȑ!4bӝGFxM^qaߩf˦NJ/ t¿V"{]@>`hh8+2xz^ZM0/4-mm*-dϡ{Jb:s&}՞&q9l>uKqWjZ'Ms'tW?Ky$M%@$ { rCR /dP?k$T(02Aq],KC'cZfwy 3OV+HA;5t Rg<3ʒEvwNHk\i7氦%Dy&I ֣r2>\Ȧ̔cT04$;@̮֝6-c; cc3 :"^MO]FO]4/KZ/Fm7ؼfqR2[6Y) fJgi;ȩEϐ_;eusVw q\?${OXsg/!6p)mĬ?NI̳'0 |>u6yurWh{Y|mҗ\NEn / H!i6 Цe"@,/lKA] /-ZVH1RRǩMytqB9WxXɡ;/"zD VT[4?n[VGZ1E% *)Ǎ^~ .GR}tk頚(b hu.嚍eBI /2Yv`[7?̭ZU˨Ш7JYa O n~E:jEDio,I/A]k}A^\7W~qr?>677w*L88oY %Ǐmy/Śqg\;"BLPT\F@rl{-uBucasKgxMa$+b:L{ CǞ_HaãDdL|4hr礜*A?E71td'$$4M@?Nx]Ƽ-,(1ûswZ(ɑT]gpBJtknbG=O׎rkՔ[;ꋸ8?t*JR)J}}}mmmmmmMMMuuuIII~~~nnnppprrr@@@s~ggg///3]hԥY.]/DzŋʀłUopzz###ɛŇ|iiiia0=~bƌ@v@[ID"$ɥ_ĮV`\pYF5my}  akN_<9zԥJ'/y. tmKx*{_g&uu^y>IӠ|OO)!`AflSb^SSC"d2D`:7f"h4zWiY6g9?f8Z򐝔jj+ʺ=ǧ$Y|qheė >[OTJzBؾoTA }2!tE;O H ^y/:/pn-?{Wx(ŭ~cSvcW~'fo>m*;tվVms^(ѹh>>>T*):^WWo Cw7lhۼk1ό\^px綈ٕ8y}~ n>-6fنcIxvCڌWyU$j?=72Wea*^.V[ nIJfϚrv#+,UQ;`ۦ_.5EmIJ rj[Dqªy;A %>I%FMT!w%WWa%*Xy=FʭeiƷ%a7IRVH\T2cB j+PP/}Iz$)7`,xQ܈ tuDAw3ikAe&N;!/\k{o=$ (ed*FG#,h-8}•YJ2ˊnv;_r ]}+EHmyЊmƚbT 8-bێ ~#rA?Gxt:JRT R___[[[[[[SSS]]]RR؜LN]n  HŌ3J,IDbYYY/ o@xxxMM D"$I@@l] ڠAAA AAFYp b2 dw+U#Y~"t#  ڰa+,1 e9/n%'鎷\?6+JTPJABGAA51XgcmhjoOkn|U [P  }"n:~i) Noz>ȷMV]tyvM7&a v}Õr@Ru3+ e~8r O>r+  MpYN ÿ g2@lՂGߺx ';y Accľ:  ,{7"3: 3K]mtˀ~yaωnSmp6hVο%T{6HJ|z5T>yCtA.\7JBDPInyijRj G~ R/1znŸ&1fWo++[v"8 oޭ :!v s䁄4pbv.9^hYK̮֝6-уE膋.˒@{QuNd GdSA>{D қ(n N ݄ׯ_~Wr>yQF?4 1kzRƈT`bųq]FI߯Eڄ5-xj-o|/&ez1sssC}}Mob  Hk*斖6i[}o6E(-h˳8!(/IM,%_>6б0 }:C+,H U7c[Zם1ƴWϲ`+θ5حb2|(SJdkHbX?F?UBGd0Bq#I(zDzhB~~@oPέ[ ~Hh jV;Q_}(4\m Hףqˆ D_c-+ω/ 4?>NnCV[ҍ^ݚ//  b}7Z B(nD?w͐g*-c+!NCmqKvuц:tLGWh˩uO?(ů3"+ZZl3ItEB]_PX[ _Uڔ=̒˭|XϘ!_JCZ~:y-kt<˱v+nY`@@]79cSq64  ͇eK܏xern,}3-uͬ?,mxӘ'{~y-r(Vfпu.@O9GU693rtȺlӚ(lf61O5f̘1_5H:lfƑ*!<қĈ#kF!ȟ _D^=O\(UOD>( @$8r{ aa^؋:i|Z uOw/F)v>InA/I7TsG迻w't9kwM8Њfe5=ѳXwֻ'>3aѩ*ns??U*_|Q ^39}/!!.!.2mݞo۷o>hhS2}w'Y >5N>|$W,+B؇TSlH>9̷iq{g\.rH!ȟ 2XU]ޱIqy#kѳ\d{^1"s!W1b{OýܤFVNtߺo3Be_)TmUrzu}n>L㘍O.4YjJv*VT?n6z1㽯;rc$Cƾ"d_G%IRhѥѾ&Kh"Jג%EHTȴH,~ %f[T>9khμg)H85V}HV_|msƲ"!y餹[ory` AXh㯖Qlq?f;@!)EZE z^1'5[Z. 1O]&_/V%nR+fO*֖3C:F`ÆpZZd]^[,mEHp ;ح@zK=FM0AMJ#(g}>GHz'eePSvtM |rz.;|8/[@{*@KNE=]AQKyV$7MsyAqϛ֓q~qU/ۍ "?jè=mZJ%\>Dǿh|NPL J^Ox!;w (( NN(覸x+I)yVA_uJ Adr3g_fD9iAP\#e5ieU__Xdo*\AuYV09>4.zxeB86 w9[STpnvuH)U8H$R];,4ݓC"%z: H$Rd8ϖ:ٻKГ7}"ƍ$MQr1~/2;WԹr w}2f:KWT_DEXLO']+LG[iVlRt6{ab`k^)a.3%V%XmkkN|u1Uԟ~Y`e|"|Iv]}YEO7at^s$|Rn,;eU97gWԩ*r1|/e2;U4s;^OBߚHc:mqcp37v43l˗ @7A4 pq*z{Wr#ܬfYn`a% ȀB]GdHSPP07gKKóuv]ǺV@*P^^/*N(.!D1JdlsW4]?*a_a \6,lX* JJ)7FvAo2}KvO_1JmL/0>uj'SmK3YvWi5ig+Jz{;(4صdXC$Q-8DE%u5uf8'2Q0CO[PcIh :jƊw %reÅZx%x2hjQx@o s9MYj}~lj/82VXTTD%2gbtf9Y;^>չi*{N OjJ?徎}N%%%Η|+R ۩lE+.m7DRLj 3dy'֩I.!2d[nq~8aLOz:Vgbp|222x> Ȁ?vS( B&+**JJJH$RxxxFFFHHΛ7o2Ҕ|LNJ}&""6JaˍVH ^|hւʂأ7a7\{M^L6kU~r΋DZ>UÓrV ȯQ H~ lll!l91&&(()YU_[ LYS߆N9tANއi_.?)[Wa"D$x<XԱ?Y!ג԰vc hvSx8776Q t}in/^i=k̳Y@ry۲:㌖.ّ޺@&3o 9nO5Ծ|rqKmHjW俠~\l-mݮ}bx߇ q.+?VR&vS&͜TSۯSThcltG*:AD>-AױN19Kʲ[EQ^jRV{OmE#E\CAJR擞v䬳4g<)olZ9'@gu`֧Dխ|dôv&BMmZI{b(!,$h<m}ۦ(H}&+2+ \3LN?VL}Nf_m1n˸SvrhѲ␠1ju\κO!ӠR׶akKIuIٿMAW~+cPs@d(q`uy'̝^:]jSz-{*9wJ\z/$cVp;u wIC _mQid_OzK~# mEA`6%%%?Qb%lrVfb nqL:d+ơ~1ޡ[cS rݷ֏~nc{|Hz ʎ~Quc 5bvCށn^l$H-wo]%ZK!njsd",:b0 ڏZbZWg0`*' w<啶ܸq70T ۴rHn)vK g1WˮV@D 66YQ)A܉~\ tm>#$`QIGF#MYԪ棯gln5;φBG;ρޓ '>dk{vY<j]$HVZ@>~487}*Oȣ:E| >2M]x#aYی;4Mۦ0f_|ok-]WТf]FbsDx b_|Q`۱M"fk8l9odc<ޖB 070xaYY&R+ɯo=fnޓ2`ͮ0^*&w(62Mwe@r|c3m{9(ztYQa PﱗOQx^ *!58#Ud8&Cqً^EVH۸րZꋟK}Q&6?f(FE' 00u 缻jx+Y7gT*>F\ˮRT4fA ̕<аr0*H\P jF'oZ]˸ҏduȒꞖJy5 }օm`QȌMR[(nܕ qlyDtq 7'HzM S? ={K&o&@似v[j|8^ ZmCQrnSzc`AٛGn7{RP\ӹOX',hF던w 7mSY ? ӏpI:5T<* ~hMcd\"3Tnl]5\utFv^I%SqPxp9q˕߷cQ36OئiI\&(u\i>BSm/بVFX㲏[a=Fz:ܘ:`.qm y/%!0Q #)FQ( B&+**JJJH$RxxxFFFhhNNN7o2d!bb2؜/;/۴_aӱ 4iܹӦM0-^>Mr 1>kH[iX%a:zA%^s~=H&ڒ}1m~Bob"_(RnHDDwwwP(()YUGۙ;PG MYo(H ?mC_@TTTCCHTRR"x<j6z innnnnn(`ղoߋ`>f|̓ݏtp-@kd (@]e+z]A Ws1UPRmJX  Ƞ $= [:Vx|g_At莨=s063l0' SMm#3w+&!uDA`#)}Դ{ VOfu{w#2ifX :,~R≉ŭ1Itڢ0g奍=H}|@#uezfkwV͊IKMߘQ u!7A0JvX#[QnLKI>Ie%1~xҍEj])c2132eq%N4UQ9ssT8M_0Kz&?A8Y žA~P>e"ȟF#Z§Ni6L }ޜK5ύKH ZhT>lu=XvZ+gg,jߒ5UeXl#/.( $!>nsPShXɃ Ȁ@ C Fuu AVQPz5i L'c\+ic=Vl_ds멖yJe -ZJjef:c6Tz9?.^\ѬȏZ﹖_h˜#YaEOE/rKLUVj=ORF**(+y  } V,ϑm1eTZnvϣV7چ 9VbSr^E#42X, soJ_4X#4~\v,Ns J^PŬo/\Ϧ1ԻWn+Y)݄e% uAM{kO?:a`Ovo˱]nmi_{?)z<@%&Fw1ݑaN9&r{<`=zվ[c* U^3T_%%g?9؈kT>6Pk DbCHhJȥv: a+dq)YSM֜9Æ-8lg<4sq5(rPAA&;I:N˦ ݽqRX;|< .4ܰ,%yرX 75wAMiY=~N>߽dQiBuh*2DI+^4k4w~A'q@^9<^=9Jtc%6cE8Y6'w{Χ:grݖin %A4v}w P]u,?`A}&&g)Q&.yt%΢`3NEadHhYBOTQ0uD"oD~k 0f9++3+SYh 5l 'UGKFfkVݔ]d(>PwI( _6WܼNy{,A'k l{hocK\Jӓܢ%D91E\=1 vzSQ-q_ՠ*XjVLZjԼoMq]h ^ynu"W(35EfK[e/Wsuf3 28:X(& 9ppiۛp祿;i|] 9XN]xj e+9a o﷞7Æii"[*3go PEF.|*7}n&r(ώV]T9ayu=ZeO`Uۜgy '8EF}?=Z^*{E}ioF2 Ta{1:o% wWPJk:u6-?n[F]O$9Y#@}Ƶձ`8JFRjzV.Kr|((rNN(U{TQ'սA9Xiݷ6bỹ[vv~VKKEAF `y)\0 ?pj4ȓ3ݮ?lO'h74ٸ=u.#u{5ף W]oޟPψv,Ns oJ_4X#4~\ 4{w-ف @yJgf6 fsGMGdҮKg9N~.g)ځq"@$vqEuƿfќ~',@7ѽg3Q/Jz!YbÖ$m^2j,ĭO fKU5c 졧cE5uvpƷbP=^Dpw<]'OZ3ܔ6oGқZ8xopxXfzO&JPی0[Mwo_++DDDgqww ?&JJVUU}OYA8sFEGi%bU &f/'#Rߕ*]=}3S֍F!@TTTCCHTRR"x<j? i'ͼ>SO<#n&zy+ҡk=7{\mDɭ_n8}X7;9)KD"o5ٗ;->tt)e DcʤvƉcXQYMTey_oTSX\!yIТ'xWU~f1v k=P2m!?K,oCuOW0kPZs7s dsq0i&\ݴ2ꪯb,źsֹI Yu=x 6\-78W[{n38Q/L=`s& uA:"CJカ+[}]k EĶz}l ~aj>e;+a]ݔMb[ 괯#l]茥8`i+k,&@}{z6ᙻWn+zg uYA!u!m(r>n#Gli-ͽ^ZA1([9q}V戮RÍc$%%%%%GcozzuG=ǫHJJJJG)Mݗa}m#_UmB\LX\VZvNPRٳ SbddgapeR)F9G`+}e~s&R8 %DM>1yvlܿD~ m#y]^׹IzA`'YW4r ߺqHU2K:K)zƓƎ Φs"h ؄*93n[hbAz]uu5{Ā4ɳ/X8h Qv#,_G+=S5gm,,_ >7D\Xj)T Z\Xpߜ I q!]1=iaR{3EPД | ' vzzq w$vG1+Joߨ|=uSō_Mpո8!W@.{^b)u ̛$ֱA8؈sh/'G#6G;<ӝ;%Gxwߙ?qkHvc҉ [1v7  bat9m@+ʟP{峏}^w> XN5DZ0k맥 Ȃ #dr%!!)Vcݝ-Lf~# IY$>s;QbԱoM9yps2yhw;|:|l} iZW5 맅./CB]YTcIe=~3oYΧĈdKۓRC/l&t9#3M_\r4 M;'ivBŃgs,${xT8Kڴ?)z]dxp>Z2' RU9^/>u$=u,mA⫍֞rZ~--h8llRЩB/,++++ӷoxDM-; Щ´i왱1s*tsag">`0Vw+,ˤ/I_ pH7_?ˈ~=HOiIy)A#Q~u&c [|PKOPr퉑j/82VXTTD%K]K6j(5tKb8)s kg$[4x(n<~ b 84:u~,#"pm~.ldxy VܮiL #.|JVYvN`![ް&&+ }.9pMzΕZ)tR"Õ-4l$ÁnA/}O$Ɖ=u,mIw}>ĤqǕyJc=lcHW/~*B%jTЗnyAc՘R;N =k5g&^9qZ%U_Mz%dcϱۆ!_k Ƭ#2G߿Os782:P?ԦD u^.m7/~eg* /]x6_G~=rCFQ 20-utAc%f82yvɃ gg%MpM%8kV>[I t9#Y7*j" ]0"`P$s#|FWe>'m]O`Z8F>Hd{!7zUUjP|Gu8Z̃|T1\ D2\x@)?ym_ 7oIRTL@L m\mwvs0p~7bN/hSϱ!_j:#2ಲ|xA iI5M4X)U}l48~ɕ12`HM@\vU__e(3A~xaͿ䀹o' d1:`T^ڸ.8zJ&Y0^qڝUnyI9ߥw]*,g= +&Y׬;īD, ܖ0K|pPw*'U)t{8Que.ɛp5e@bPfI4LX߹M>fV) a#ȟg/;u>}oJS6?yrl ]-ٞʛo:m ~r=Ǯ6ۚ,[񯻃k \Pdl%Aa0Cm#*c[v [`0X;N^Kk3 e+9)\[rƥuWYre#VQfODBGQ:bi#;Ⴉ8(<|_ĜJ~n[~1q('lӴ$.muETb#²Zv!W.\g6s"S3w192o^JB`'W:" :X :z{ 18T`L2?Oqwάu=Ւc1Ve\KQ]vFS ҭ=1R* }x5goKFRjF Jyz + c-eE5@@`pMEJ9C8vH$Ӓvvn}ʓKN69s:"X>^tjst>Ɍ U1 = x&DncjkY9>s=cl 4gѯ58MG\$Lh>Fh?&:X/9@cq<^(4{w-ف SB=#^7Eݏ)w-Kth&;Ğ;7^Oq?GA~=uD4iA~i NsVṖœөsZ$ Y0f0~uޗ𙔌K}vԽOKrMStH5TN)5!(d3ZoYff6X9 uD4ss-mh4ZKK ֭[`"TVCpTU D/ɃCa_G5ԻWn+I2o/\Ϧ1ON!P2mS|w gf _u8g7}>sz)NK!L^S{wsBCz65c=G"C}' PWWWۦB3-4D 5J/=\ߦƁ[e^DwAE:<&zf9?P}lUqqqXV Yٚpe|ߣ^mEt3f>;(xW^ * "nG={,;?LbUgyzM|v:cI歷9)#3xx2Ebm±Wr=:QC)}mb{)tBw '&{YjfgODu:7 '>=:gWGoZʪב_Ye/t֞:cNRaڻUSYtm۟E 㵕%Ee\O~(i癶k}w[) q+nyT䤄8fDBONn|rrrrrTP][j.+ c8DvSk.쎉q`Ǫ ș̷2Cد$[ Д4@VX@PXBtUL^q*z̺q\&bR|k "6Nb][EmEʂ'k Wq6dp05-(dҾcͭlll>hnZcZ}eFWv!N}z{i X .oceece9mMd!5.DMϫxEz\9{f!and;ίb\lmLF?u'ion,#T ****2ߌ.~*19LZ՗a9zvᱛf-M0N} 4a뼀Fa2ŭTSf/ oCwFo0uo-{vo`Q)^'tQ;sMx84kQc=(D:"._"Z%f'hU!G:"?|vu6l\Wt**Vq[,y M>E\r+}N/_i Y_ .~@?΢#W6ӋbUN]+['͍ےoڙ Yخm8&59|HOI}Favܒu24\ް2<4u8i\8ax/6|xUbwWս:;SXMKV''>SuV{!ު;o(,T"%GqoV_?:)]Nd d۟}0_-mNˢ/11>1Mڽ@D ; •ܱsXTT76՘SN!an.N7.N?1ˎ2+<*dGD\a:***꼯6^u7"|)G1 _KgVZ[{.IQqaJő[Y{{KO|1r$2d>euD$>3iz+IfJػ*534l2E͍X׋hϛ.y̝f^:w ԍ-@]I5$̍hgirvjZ⤰hltڻ&}Knўk)+"W9W1 sq30n$#)e޷Y|˛Nj~SJ@HPUU|`򨀚hK\9{2ߩaײBPS]vr:n;!0ƘKZbӤzmQ<0L %.?IVҏ5G:2T%''w_ Ƞq\: 0v/Mm.~P9=N=S.Ye9O@9No> KikmF#YL SQv,%eoҢ_I@bSX|0׍gz`_ 2@$2$:s~*aRs5/ r~)<1>E =X<0@ ڮ8+mj 1o,;ڼm)Ji :@~,msaw$#y^5b]eB tuc7qG43JKEp4Vz;+IW;^j6L';Kvs3=)Q3@Tv&O*aYEݎ|ZWbCld+e 3נ7:WhT[h1ʘ~#t-,Q?e2dRZ1?-v[ >KAѷoP2DP,}ѭo [-dY{{3]eE,:[#eM'εlz>R+|{N}_Y[2n/{NOFԱvz+aݰq2*;9= ̘⧯O!IZJ.^-}Ҋ֫p ^ZmI }'nx-Ҟ//)7bU|!7XP/=nUBukg1ipg|Kڞ8R ,_4׷pJ{=ʲU]*8f('Az.I*FOY;3$柍b~}dn?3[Jpi" $٧ni7Yq?g0C8Ow;)tsRo$>Y 7a֮]ŽDRG|;2(DU#WBy- U݃=Ji#YPNbEmXs#@QF;NGldp*NrɗVVXAac?5o1-sF0)(e?ۻV|ћ6x%QL|3G%|,*e 359fn߾IrŰ)FVWXXDҩE1U(/5՜`h4FR+**>}gϞ'Mmio_֙+1s(3Wb0kuu|ɓ88tЍ7CSѱ'-Q/!ǯac[~֞ Ou&ѻ){}|~[XXٳF.cEdHP( //)//K"&"!??LN#s.+ ݽ+ԜmKtGA(.L9a@ǀ (uDw?XL&wˑNssssssԽ.i ^[NxAuDA J -}LYYY&&&T*u$JdA~;Vt  `RGdPSVVhK0yAA~XuDA JAs@A~!ދB_~暛pgE-ӛin tAAAB;%=3OgAx(uDdBNdC&XJ}CE_~lCԸY󉉚W W8ZX Dz\9{f3O$n8ݎd}o >:" Bc .<&mXtӞP.7q~==(ѷܝb5v1@+c ۋnOo,^=J]sʩ0d2K8VGEEE(C Kн # *@&T}Q@`4Uy=oM\{01#=o;[LBW15gxgKpzT[LŽF&@DdQGAA~uL?h3—B MҖČ扎?2]~,sawB"y.0bno\i.8~ /U@"3,LO-S_ uDAT]]#!E(/PHAH]KG\O/_sd݅Z{JzY}{}+M9b!"l\\PQZ ܺb4$~gԌ/CV6\kp!m6'B=# {-,!R4AMcF!s-W),tM Ir)=K-y&3r^~|Hsϰ#)6EmKyno@mw޸V7ĞUnάNK ] zQMC[w𭂍mT GY?A~Lf({D+Sf[J'=:,ыYvfť7-jg+&+r=|E~CVe=L~(b2eӑmm]\RNiu`d, ]fff}e :(oDX@z4~8>gB2kӱg6%%^sΗ3_fv|9  H13/K^GAA6}Ih9S 3Oo'Fzd$SZoZ7MiO#P5$x2XhȠE$v3<",,_NNNNnJx*b7U3oe@]ZЦ~ IDAT4CA^>Qk̋Ø*' k( fD}wm?Mw /j[:@]8. S1~q>G uSfm ?+,RWZq<k+KʸĢJ£?tC"tPtRBٿfyڜGqo\vQ^ e8jΉbe}7LXE $oZ𵿋5X>ߌ7m\%mx *****6o 59|HOI}L紭3WS%5@߹^'?l]=ow|qEnJ&ZpC:inܖ~{vl6l\Wt*x/6|xUbK"1ێnǧ)g})hsZ1 \sma-] {7;?6mO'E߫ѓ-ٱ|ɒ%Kyp41~=(,FguD5:mV; 3g Nc?jT ҢoӰc}W Dz3+|x[ 51MSW^\$avqk?sժyI†}F{3@oD=i^V7/'3C~ 5/{Pfm e#Hn#y XN\c w@O A_T<GUhG?}xB]߽:l wl:TVVV>nUt.D'|zAɏ{I@OL~ cBo 0_*J衒raqi HJ[7VrI1KHKQK˱7 WH0w?ť6,^ vTU圞Md2 H")d ItG !)O@yW 'zw19%4ֈ 2Q>33Y 󯢘PS21TRF(.*\S^ڗȤ#Egw,vc>9bcav̐0 w-Zi4!Dj}e!{՘%%m\Bz% 4vӵ."~/X1;d_t"Kr]OtFImCf\ǰym_^3'inu'ʈa-g6^S/N}]W+tktd\& Tԩi?_zU\\|ٓ͝:y#MUe4N.ݗQ {.sPP5\C(.&R[霱8{A &ԶzSW,rbo487rD=쵧%VC2=E*jآG|PS[!%AzQɻa"1ێ11 ${7=$z//,&qw+?P1Jgi)׷*1&3@e'ivq:3#O(ц3)1k'25qf t\udf?_ ǛhQ;u=T rcv[3<78N5\ALWWkQiޗB1*l>J9>S[JoőJ ^3HbzSN,8f׹ykrԲn{ 68bbCU `Y)~Z$GJw* X!ߟ#ȯرcJ +c@m H\uA~\q?V \?_d*3I.L{ Wc7KfO9ķ;朧P l)-f=Yz-`Ym@l#~@$H$"NH?h۷]()F2m΅ݑzը-_X""N  ;5EoKu{ۓ-K.63nć-޳I8qa_"e-e]olaw7_-!#IV&NwnVV Jm#`IR333eڌ9w֊Oww+{?v5\*h56RTXYxY㱋ډ^ejLX4&0h۝ {(I-o7p0hxW'fמ,Udjsew;cޭs%GL4=SaYFh3'X](M\\PQZr1BBBi8 HCc1ҔЃQmyNj*s"{fUsx̔o~]Pk΁Dѱfs:{/bQVVhgJ3y]Tg[zi3)MG埰D NfqpRi-1 53]L2 --->>>nnn...NNNNNN BPd2L&$H$t\_P(vIMM=psg$Ǣ"333ITɡ%2>7`;Wx^{]ߢ>Dz*O-Ü3ӈ)!Mϯv %pm9M6 e>}$Q{M@rv&]5X2<5Fe@[s$ƏtK{J^|lZjϤ SO ظ&<WCd<7kJ9#kWM)Na,8/ohz~i6gOɜSz3AwPonO2ZD" tDBd?1gP(x\&2RAAAFuuu$$$$,,ӆ9ٞ@dD& z^_P(<<<́Gօ4uDcW҆mb:Ve^$Aqi'k,DQVnP:aޗ ~̑!672 D$co2B mā }tOeQ&%MEd.M+[NvQ춡Y/4O6L/# 9"Pֽ(.\z;C"d2???{n{X""" #ލ>CNNN ~~$W@@@PPP@@ 777s +s9s}@ 0 ~,q@.|xEG(Itn.}|훲^ԈZɴw9%dI w#u)5mY_5d<2ڊ%׹  xi9Q7^i9 ZaJr%[gzz|IW`L7Uk$ƿ_3a$Ad\ǡ/yH?`&6$Jobޠ6EE":6(u('G_ߔmZA~[Ζ5wH+rtظxǁċ{pJ8.>cmZ*=Mu6N۶bه'Ǒet!-zہ $ny%?Y1衟z6ęmػ^uDaZ/[;y# .9n2 ))9xAkR~hUpss۰aZaX+3K Ekbwڭ3WbNEhվ#)):"H߈[ݖ:rwq֭BBB<<<|mxyyVфA:ho72hi]z& *;h&-  KB/@ڈ"oA/gvuVEM3="t# B2ԣb O@#  T 4˓  /ɟъ\ZhȩjۖM~cLllm׷o|yԆQv~ FFv'23g5m{l)` ]umopzwN礗SrrгBB(uWC*{&'\w&ef ";Bf *<.>.OF}٩ $=3vJvA}?gIic% cRoP.>𤭴otfffff#؉~q8=as2zr:<#җнmHe}G^^>m8[o,tXe>缾ͰϻПsm-CvLW#"@ʁIrw YAH1q[Bq>Ꝼc-mۃOo?>djw&lO,k.rt<{jct?B О*2 fpwNx/ bIW'?G-j.1zTAaLnY=ﺟއUh[wtll8w]PFGY,xY񤢱U-\`|^i7νgmIWo\kW;l6HjvFr:h\- @Ju9<@0]gN&?l hf@ Aj]=QGd)S\\񶔭1B޼[ |"%0k5{ ]|c.\9Tt9rͨ_}骨'fNfWxTaUj}D%zfڿg԰8j[^phe{$#=} W3ulv:Xϟ{*gHNW[к5b&>=w ۋnOo,~ŘU^]6dbø@x OjfXrC5ݷyʮ Vnn]+ȹr˸)$sn~*<ί'%=\4qn㯳@bbnHlB~gKLO?mv@F:"?qک/?}lff&͛;dάp7[Ø?ԟma`L[ nlJ2J}w6ZE#0ֽ:pom&#h/(@]Px߼Y wl3골<f @nt?nG$@G#'=15gxgV!an5o `ijSJ1NM&O?q:zUrv>1:Tuד=7qzRČԃ5Ywu (@&T}Q@_2iʘ<}4)?NLԓ|f7ݜuϒV>]Dl?`|eø+.0Z)E+?LG yG\)?vc7\S&#! nl,p.-ěb:ͫ1"+$i`${\ǖ}ٸc7.yb|½>k{F]$ Dl<Eq+6][pĄv'nHjxL!ECˊˋw7y "@懓N|_t OHeGݾ"Ei>?M1AG#2 ύQPCzocN1c|O IDAT<%Pb .{54%ixR2lӷan/}=%\5ljA~\\PQZ{Frku\n#46Q[*@sJ$eZA@ݫtz1o`bs/Լ{[7Z6ϽR Xݶf(.ԷXԆ}MDže5? OH;79|i,=Wھ? Zsw]K#: g,7`!wW[Zm/*5/"6_@R\cA?J83?=ܽKH.kT4(7ciQGdtMW}r½NLh EaWZ%k3ue=B_d['eҿnwt-?dfK͙i~It{pL!\5 <<@eeK pMOn "mstȞuRwd;@'7?`e73J$ @|ցǙ6Nrt>= Ni&EyZV$4uΔ% Q~ߔ$yyŀmcI?hyN1w'*e9ם!1n~`t%@3Zc&s}TygI:Yt9X<'3 UJ> ]H\n 8BKK 0 Ah4Jmnnnllllllhh(...** JMM=p@nnn6l4PV C˙+1s|KQӬF WVNlR|A+p a=#-{6]쌋 V^Ƅ*ϴ<DA+kpp7yr$%%Ixxy/7<__9}PU4(U2预1 :A$)-788xǎnjkhh,...$$$((׆Bd2L$D4GrA OZlD:Efc@'puQ%֚"<&ᤎ H_zcVm$N `>LAIh9 ȯ&])+y׷xnaS-AagkU 2h!`AA~ #:"   @#  ? ЅRG26 1  a^*S>wQoW/t 7Qm1f6qTh`ȯf`377"w#=3_7dž^Xw !~# l@+" ! +~h%G$o8u򐐅$G3ZV+rrZtrR/lyOw=FhPwt/v݉&lO,k.rt<{j}XEQuGw>#K@$9FAb N1"cXg] UN}m&1zui(w8hêpz)im Ԫw!zNۑ|tP@@X DE ,W]U~r}arHFJ0#JOɐ]7՚7L0-nlJ2Jz;z)ǚ afb6;1=۽9ꅐ k{@$~wM?h۷]()F2ZD"@ t:8+ "_^M y(:Ѷo&1N1F軜y85h*Ay#ЧAHyNvR6sJq VWhHQJoW:A :" !(uD~JhxAAʔsА# 9(uD~JoC*ԝkѺ>g+;h#2+QXs>kSav/zkA.uDAJLqq1{#hiWx9QtgAA'RGg5RJ}\\c\BWKEQ{mptZbͦgq\cބk{=;5d͒krצxOpk>P6 Wб\S޶|3uU,\/#GMmN9rHimoT'7ԐWZ;ҐvMKVACE@(qv^ǸR34‹ۉ HA:" :"?1#Mww>܈~tgl޼K|9 ZvSo,=j䮤/=Prksk_~W/z)X(g^x}cc1mߓy{2 6z׺"^zbɧR4ޜrku*uy W!G|6U,}*5Ĩ7ou:ڎg'f6F?B후wuPֈU`Ԝ45Pݓnr,Dx;:*gq%Sr,?6|ɝEM;ZoAn蹎ύQPCPC#9t~j27fyJ$ /~^@HP 6*k5zwqM]mLDAq{j{QV\jum:_p *dq!@}?s='gMcֶmQAۍO7->16k!ȵ~c"ru?AUpGyD=Zn_croܧ~*PVnvzu| *H\v׵gC5r箄I9nw$vn 9DI2Nmq4=PTױrMgWWEsx:Γ>`r(,R""X~IՆEӯ9n/27UenimpH"9^vkW8+IK]m|""6stD^zh{98-L%;[q!Q̾Q#6>H%,L#ћc]O$Ҋ6SSUvU'dV7 #:1ꮆ 0 ^zх(lx(6f̙&gZ*?E_5M?ɢ={p+1 :£~=l-c(5l6>YQLD)#DԤM=C_]p(Uvε*%)p~Q!(3|/<(_=VWD@>k8 RC1b>?tF+Y׫A= ?^TsjaͮO'VPiZntʵy;&IDd7i+WB(|ƊKDD9\rreg[5Uy#r9ޟ2Ֆ˓s깵c7n<W[瞒3V>~|ߩgΜ9 zAUѿsy^ WST=]l]V9_pɐ Ncz|/{8Y5Kb^+O7wJ*;P}]ٌ]Q*`cM /#Č5f]u-z.Z_]m>3Sy"B4r`j[#QHzrW;J^Dn""ߒ=#?dzM@s⍴7+~(zK^IRVcɧMtމ{.l"qj8>"٦w?ҝyz&s & w;Vʼ,gZ7y㍈R pY3{C竧R.Ѧ̜*DI'_M>[qNqrRiޛb#qf>ݾ/kϕOHD<2hو+!،==-gW},l"blZVdUq.NYۣn9 ֎\]`afyf~/K$"ʈ(~M6Zӿ9ߗG_L9vFQTfWɅbFADdmUݫh"J~'gWZ/o&їQݢ.x#&a UHX>%|C;|+:a](:8ր^VIdw_%1B{֤#8^%~)_B &ԪD|ew-Pԭ[ `q:B#NSa [o㈲=\2uؘZ)(0j5܆>Z›[/OF*TRerR)PVr~Im_reG=g{=YwҚs[;U""YC =9<,qU'FQ=%?&O}iR*tIHؕiw5TW&Q6\ {gE6[u NN"[Omt!AT3i[GDg֡":[+6[XuP\0J7I[6][0Gy_c}GX\=oמت#j܄߽v[]ڭU"T)M"}M˞zDݛ߽r_UJ,{xffF*jf!A:N?N=k&Dh}KgBR_ڪQFg|.hCͰ7 DnsGnuCtu8 %E"""E٥X,6)FF%lڳTٞJ(/a*bH*6@g!t^wӗOЋ~*vX 7Ǩ-!7b>;t6K]g/<#"/ӉR<ճAmU)L L9snog6,M "Ju½"ZHRaVX(=>_},eI-[goɞ[Z2Q͗zu'9_}2޲CՎnk%nMD֬ҷk5"ne_CU)h=rت_|wV OR(kYzZۺQU3$Uts|_,fD"HD"H$ YYYiii qqqׯsΪUd;wQOAc#~3Ut˹\"7uYj[䮟y꒥<;mgnEť7;kzw}?P8>yOM-E3Moc87"B)б wƣnn}|Eux("uT>vCN/&YٸDb KNI_ڹ{@#@UdXyJ{jjkm,jٓ}dZ|A9.3OsikoPFQUKU.oV`7suٕT"J 1KZU* -#jT4{zsإ,vsת`zX{;UsQ]XמlQA4⒦s, H0YU$_ x|`j&U{;,}֩Vűf.NHV\\ly9X(==KG?5l6;. =[{8:mwх8{5K㽓99:{]˵CzL'̉(HQgM70SڼJn "sc0M47$"#ntȩ+8T; v3WD̦}!#;/OO-8)u{w{kEw&Kh@zEd-iDHDӸ guFס֞M쏇G4dMm"[9Ws\?b7c3~nI1fAe J=,SjviiӦMD4bj{>F0n0pdgO'/4nGNDiqaMM p玍Bx`ȍZ9ۙF6l6[/ /8U]̢ܻggہNp㘑KƯ(5d#OsM-R/[D:G:Z~xUOz}D:qwd?ϟkso>}xv1_=(¿J])egw߽5w:"Ků&sDȐ碱WQq.}u_cj<FR3u6֌׆ MgZ<}@>5z#_l]{Pj~St%Gaρ|uZ٥QFѦMd[gԨQXj{kј[rr o?ȴc5rdkӢ?56MGZt3vɶ], W:VgB*)Qoխ4֍mjgf V̝kql6q}#VOǖJ%@N؊EVuNXa5bX,58E@5[W$2(D"4s:gsfef9L١jyYeϨ⒓lS桗/JozU@aS,+KKf"$1>()9Ģ<\J)Zg?r:6{l nODJ$9H2KYYl3DD񈈤)?-*Zи!IuHe51EJFʙQ1T)231Ω&i MoKnI5_ͮ;vugرAUk_}֒W*XHoXU, Izj| Rd$}Ҥ- Td:0'B"Ǹ1,-؈(%1^L!Ju^ eqt"Ϳ֣7U'07!"29y2r}W.ZTX또:Pr߿LHuW2t$Ϣq{_ŧ%$$ɕ@D̄s9M۳@jv};)&?0Wɫbʻ;qrR$NOJHHXuEð^vyU:ã#A3Of5bz-:hNjPY&",)3=Ll~_9?ӭTIzJU Nb8F=>(8qC-W7:?vgxDvk(Ŏ0)5dNMl;EDDX,&yݒgn]yZOwԆb}}<.&DD$bd3~v I ^A"Jzzff)$)T*x.W-ZDDg^hQ``ٳ&ccW;!aVµٳNr"N9A^?7Ik pߕn5Nkefm#wB.=7cųzwqxUqtt̿ŋJc$F{o7K&D.Sͮ=i^CO]Xb&G!YI$79[cg%T*H$Dj*j5qlݣ$Ǔ$7oC/×3cc̒;9WX"D"!INoʼG^pĽRnS T"?T`,YBD3gΔH$3g\dɢEfΜ?%sGNv;w:yqF/M+ NĶnD'kV[X˵vG*s;z 6^>3ijvr"R)}e[vbؾ jP-ơjiV&?u IOR-B׌9oҠNeSa_O6P%_޺'^UJDF.ϣוjj: ڪPDGGWREauL1:[c䋩9ը/BYS^$4Ti;3p 2rE$*m7zCMm^\{Bm#?[ThӨXl5h͚~lJk^2KvUyÅW83a~?udbŔ9T,՟KA* "6m,iӂ,Y2m4Ī^f]&oҶ}zٮ-cIrU}jCwFG6qfО7Vm4t" C^3Xփ0YsY*P+䐏pJuc>mR f#f7pk+vߵ M5~xDD{Y$Vta~BbiZ6ϭ#ԭXEЪP888gUc1$D"D"H(feeeddddd%$$Ʈ_۫VܹܹsGѰaCrCCC}||lll%˜똕U)f|>?44;4>#$~ZuSJU]6m -,kҤI^^^ڵ2333555000ahh(r\.qxZA1:8iۻOUUF;c+b1JP I|3gW[}b*V޼y}b,3a㏣yz)y승V%aYy<G{@ЉjYR>|oaaaUkժYqW1(ZL{w-aXuwu ){amڴ9sLqץiӆQV@F8¢m۶EU>0j%w#*x]O8QDUertF!{e2O-@ M1H,ֵm=@dk!R+ p,  t :@„XL)d#G(uoW]AOe~CN(=t^7|q +3Aynk~Η!5:N;T?-&&e[̭Uc^GlP夽8ij>[7=xp&={m{;j(=\eQ5~^nkm:刈gYwǦ넩^1'7}`ǾL*!WYWݑvED$9%^aGP _&ɽGQBA-{(MabLl+pqе,س"+݋dN/oSBGB#>[v ݏ[gMُoGJYE2{Cgfp u8RMy9IbN?7m<>}:}_YY~נ]o7}}/U[{13/[oȚ}윬DD~U~94u'/y̘?%Wca=f;\PٻSpHt;,J]ƣȷ&YNyn65>Lk͚}9v_ \A7~=oBi(er>m6{F}ϸ1.g4`eFm}(Ig_އ(?3wiȣGѻnkS`ؗk:<))"Dn.E%"0cKōDDY<WΎʗ7%QfzQQ'.O>G0-U]m. w?WAڳ[3tQkHUQׂ*އD'ٺg~2)!W;Af2q q;rßf\pIJ+=GhF,e(=sb""yZAK~v};r nҺi{Aq~#ΔOEW걿[4c*_rD3`U([juYK:V&J"* 4wa_J=c9{ɤ}ߣnve7v RWr#8Mε;+WkaƂKy?={r>3Uޢ]zxU{{X >G7s=vyԪ'PS{Ҧv&-)-%%o;[v]8z>2q]94sۧt6HDŽ˫k![J+=2ьߴj0W4"csCC$&HŶ6'k[ JY{;}ЬezdÄވҢ]mU庵+_yӆf*%p-sĀ3Rs(\2gOR;'"[/w"L|a]N1ַP6}cCu9dV=?~ ,)I3?~H44+;WP϶nϹus/sw,DX2z9Լsf0r X@D)/==C 3cSC92\JC7q2!"Wue<}9jϖ*2SZe(Mr>]:k31fihK({wIUp]9W#/KP͗i&ӣ7>kR{Rg7^^}S;e -jlحϨo]YV hTP+]^P6?:¨vU[ ~p+:%=~2BC,!۵*x\r .Pއc_xk?)2/سC!NGt~Tr"Zx z93yr]ϒDaW|1v!z-\Ŭ馮r=_dH}S{ brrbIT#}2wS$eB'~3gQk;oVJQs_A( cB5!G(>?8?+б_:e­uLoҮ[;nʝCW f| jyf)^WY Jy3cDHooqMˇ'VڜOƉCL{լlkK~ySlhUv!Ǣ7 S*?W59>8{&c.\jס(_;Mu\fl}Tn!Pts ]IDATm.?WZ VrkԼ_k{hcJ1N c>ﲶowyD޾!"ZpT@>|1ZZFPTҸl^8Uv}fj?I75{OTI Ccxψ>]-/߸cϙ\U;~Wղ@zypՌl5~wzK-(W^z+/'ֳD(֥gr7ﹲerDF ,pͮ5K +zvZ``u.WVUo}])Vu74[H>4U t Zs/\wXr&k_Mwi͐G":4l-G] ~fQLq@Gɗs(H$H$DYYYB0===333---===!!!...66vw]vvqs6Y(5MĄ|pו ?lӆ=zTlʔ)^^^ڵ2333111444a`````|>rl6Ƅ0bau2*tQ}pS-ЀUq!FI$t`j.G2~:4Pe(kdS߼yE XUR(d*\G@ɆFM+gW 2`Cnop$+ pJ>ݏ/J>?U*JMMM_0mw2>>>)))3S V,CGdW$ k,իW7n۶,O|v~U[y5q! 5.f/B}RXz[ 3RRRop8,dנR:el6xvvvGP‡ŗ/_xl_zZ6CRTUߨ꩚H=|QQ#WmjP}IŨT*+@SmX,JūTT- X|㟯+=zΎb#t+$S>Qጹ~L訧WNիW׫W/bRNc(bPبiA /Z?eDR9# ^%(Լs)}kOSHzjkkk>rG'Rq#733ȸ}h0c5G:Hct)z__]#tȕ+*~/H$X,"(++++++######===---%%%999!!!11իWIII.\(@IibbRR%######c@ zzzzzzɐH|T:ʇbX h`` HRRRlll233BH$Gb)cB}}}feVed#We<@D$42?Jf2LGe5쐭¬RUKQlZ*s\X,fGf,,Lfeez% (dk23nQC2eѣlƣlUA ב GLLdRv|H$ 0`LR%hBlu<*DL,nJG"0|>_;Qv+0]T WuE"J&%sH.,*es&:wK*7Бf62̿+ h,CGY1(JQ>sBPF os0,1.7tMt(eid]Mč5r:Y8B$?lU!7\WVq (R>`̣.&n HH$H:@Б dݏHRg9WH SQ~H$bY#q#(G&,ȑ]G"(:ՒEs 9,h7"t(dѣ,XZ(Y&G!cGZJ$6-H葉eUHÏ&Y! cH1܈b)((K6Q_? ĐCU#ByHG ecSQ_čeGOkDXZ )o??Q>AAV> qci.td($@!C4 HU?c VIENDB`treesheets-1.0.2/TS/docs/images/screenshots/screenshot_todo_half.png000077500000000000000000005744421352107072600257000ustar00rootroot00000000000000PNG  IHDR_Z< IDATx}w|ř33M[$Kr{ơSr9.]!{!!y/ǛrƆ .劻-*V~}H%Yb#ow3e)̳1C0! !&`C00! a!eqe Y_0! a!u9ₔRFsC5غKM厜5rhiRwAaC8/$`1Ƅ~}I2nYu]7 Cu4 KsCb( ;KA)R04M4MQEQcaX! aC `aJa7 _'ᄚ׬\x\4QU\0`rJ FC n_2^TU'y4M399933iY 1E!$$ٳvwuu ɋʚ0aB 0 #R61ƜNcrrrDQT@4HӴXL$#bsoP  L@& Ik\WR[RW뮻B3CVqE;w;vlzzzcc#B(Ĺny10y9}YߛyLNpM^u'QzyIE_K`e59{XxbZdjg` G_O @v1:}1sc3ErwD _TbpЋ"rVD kO"H{v{SSSgg'B(++[@al6ij M!Dή.QfbMӔe9755EQ.D Gkk+_u9d`?\Ίbޡ4]F<& D8EԪ%مQ^A d@&@; D6Wgy]PP`}eUuY iZIIi---yWQHII̬EonnNMMξۡG[K;fO,YRZZz- VUuŊ'OLJJD" u]MMM-˛5k֖-[f̘aOxY jg[ +ib[.El;vdgggeeqQ7zZñk׮Ç!0AEX~AEQ&Mdq~_K )b<_vY+Dij(ZcǎUUW^؇</c'cr{R3ӖyfϜrښii)|n`mR2Kǧ Y @lo~IȲwmـ]5w㖕*@өa&e C":DV.dVOw4_{Zw[c 0(`3i,˲,s˂ٿnݑ3^>Tҍ7bqM6͟?$c,^zƌRI!DUUEQ D0>ilذ^(,,㵵[B?Ob\.qYOgwW]uUggMӸ_?0M|oȑ#h]]]nnn$iҥcǎ;N>́@ kUWWWWW|GӴ[u]N/,{^Biܿp~ƛxvvv~3gLKKS%!˭.ҏ>ku+Up,[JE+WVVV;vlřMMM:t(%%eɱXL$ |P0xn+몪vuuYH[<;쵈Reηv'O{e$>_ f7=1ir'^X7-;= 22ҳۿ?|>_}yY<hH65G.Y~(J\"0t:x|шF,K 555%UQr$vE2!)q=6D,3(`s3zc.]!@@gf ɢd0XbIj>ΉzLVykFQCM<9+#D?]wݓO>gϞ'N֎9r/R[[[qqW_mҥKkkkO>dܹO<ıcc6qѢEMMM1bĩSV\I)5k֨Qjkk/_qƏ3`~zzo(JiiiӸvki bGG+O0AQ0o~ĉ ._ĉ}QQQQZZ[o5iҤ˗O8qٲe<˛oy颢{M:ohhϿ0k֬MOO7o^4_(JnnԩS%IZji]vtʕ}^{˗/_lsݲeK]](7x?uTYYߎ1򓟌37Mii'NBv=sLM:;;/^lƘ1cF/X^|naMMMa̞={ذa[llkktG̜?~ccx8JJJ8p8˗/?}n_`BhŲ,Μ9sڵoƍk=rȮ]B3g,,,x@MMMJJft„ #Gֆ?<3tM7q&I?iW]uաCjjjh$;wnAAC6oCjooBkooimmMOO4ivW_%TVVܹ:77ވF~ʔ)lrW wo?%c=fy/uXWOLT8t4]ӴX, F(h̨oNI =zjQ555X c*L6aLʀ1vn4Yl6+\YE2mD6mJ(EQjjNBe. SrC"ajGTD` ;?"˳gL|ЁMkHTc2V1)L-\Jd>q=HAD"k׮=t,[lOntvvh ~>|K,:th۶m۷oaÆe˖vmoSRJ_|E˵hѢٳg?裆a>eYNKK+..=z /t:o&F8%%%--ME >|ذa---,Fqر޽{⋻vD"oֈ#<Ͼ}RSS@zzzeee4~zǏ۷3;w?~|Æ wy={>u֝>}[oaAOꫯ۷ٳo}EEE@`5557\QQ!„ v)b(jkkZK]]S &ttt455yŋ|k˗/_ż^offM7$IoV\0o޼_~رcW_}eoQUUh"ޚ+VqmmmVڽ{'nּ͛7|iӦǏ/--}g`]]]s9uꔮhg)++Zxqgg$I|6\wy+ʢz[lq\{OӴ[ouɒ% ǎ{7.`0oBzꩢ'OѣGx۷o?uio~YY?f͙3'33j˦Mjjjn喂@Ke4 7oO09J&>Y~|cRӴ3f̟?_UՃB>cY?iƍ۫oG}W_s1ycdžBK}ٳwرjժnAQse5O?:u[[['OxA4^v{YYه~ m6Hm633?kv=vq^;[QQ#>c999cƌ)**>pf3M3%%'|w~}999weYwDQ6lSO=OXjԩ6l:u*cl~Μ9NF"g}{x<êiZ,k}4u[ /$"TUMKKd'%%yW^{챑#G{Ǐ|Bʷx/YCfiܑk׮կ~u1KPi*++KKKǏ}.Q)7xÇy-$ɲit:pXu.\ƪ//?#w8Z[?k`~B< 8vQعU>/u^/aǢ(eaf@0B@Ũ B(fK@c a @0FݦķNijBBn!@(h$oeD& "3 3Q2Zd4#72ih lC()"W1{KD"~-[֭[Gq @).\P*!d̘1.!dݧN+֯_([[[W^lٲk߿,Xޒh4;;{ȑk׮u\7nzӦM[fZ~}$III9x`0\j!d---I|G}4//73f|uTU-..nnn^n֭[ <(#G\dѣG?VkѣGTQ>Q[[[9_ S,+((j;v3gi'NpGuuoQXX9rdӦMׯ?|̙3#zM.j't~?Z60םbw/4 10@^g566]]tvzN:nDӞzD;$BGKԴ䔤@RR HJ$'%kH'|j*?}>yz<<~R CsIf.ʅ|xaa(iii{^M***jjjdYɱl999Xlذa;vp'ѣGBvؘFot7&''YXX )))1+QDQܾ}3gՎ1vݻw8p`UUU$eeeqr󋊊on322<]Kmޠ독B>-//pnp8l69gWTU<ӧ{e ` x<˚wBܦǏ (wE[Vc~fɲKs^(D2EuGZ''13 ic.v9p8) xgl6[<w݉ 1jt:y!---7o:n8>UxJޮDw :1?1<?t:]o'q~ z<z\'bX$"n gu,;p~ Ֆg2:4w JW#g皔{@nʟ&`dG9 ! q]߫-=}[S%3(ܰaáCO<?(\5s#.&$ƀ'Z`mY>zVZԳ3ZcqBHDu&!N}v6M)g648wo2!BBH^N!kZiD9Ѫ'B= Q;'XfbW[xE/͊vDmV^n~0aiE kEH$ÚjZ;]׹g r%%%%"X ^5 K"8q'fhXV9MX%$u i.* Y`~ - +l]f| `ژX-֜~&rDMcO!l8433E=bB0 ׁ;j7Һqw@^5s$f֋ą%VDce:E"kL,Wp79}b]byA|3iaQ^^͉zy@ EHfܚj`=131d}'DsGަ^r%]Y$p󇷻_.إsؒ_hHd|5WXG&$f=qGƘ(<\' ;g%q[H |<zcpDg} >nx} _5HI8xp?,?>x!lzIU`_H_w2ɷrB0! a_+p yɒ%~3qc c5YEHf )(`]Qr`÷^z nΤ$Zwou9#ǂk)D$0eo(95>X1[oE05B uA!L0n 3sժuݘdqM&,|o9OڏmY}H-rt93HuLb2o`sc vWwoέu+]1hh Lqq5eެ*U%#! a 5K_h/}ͷޚ%DQ0u!Aj BHD(P0M#fRbIcQ04L m(ٲwv|Ui{5fϻ+R,JTSubZ=p*%.Kdjy k ]<> *w逇Ub> c$dO~M Rh(uoKNI4ІիZ2]1.lVPzZa&vڮ{Va vOn[%Gi^$}߷ }(]ʙg&ݰPGB*Rccݟ@x{~]T Fuk?Ө~7f5550 '|JΟ2zؤJ1V&2ғ¢ C3IfR*JReRǽY8{kO~Z}J8uƓV~|E5c-9Ӭgf2-n2G[bTLMI`65jbz_t\wcY'ZÕ.&9=#Q zL(</cvp+Ip ,&`Rfm)~f ;׼U,y}R 464Jl?X&y峫|śmGnү3/ _eyUmc&P&륁rn@ABz0tDW[@Ic 1GD^;Ģ Z32P^8bli=NP}=uǎp/kO4ux;:0D uD) pïëv?S+{Q7Jmgoz}QGM׌FgO;2KM2|Go9~)M:ojMq:Vmy7pc}T/7wݛ6m_>LPERi맅F{YrY9)%(?sh磿|"Jҫko#u6w`L]) ӞwmwUtٛfpgQ # ^CGBOjz]0mm՛wnc}xddhCWiqQ+$!Νq}g1ɟ Rֿ,Q祉}X1.0A`W9 `s >|߿癷w"NʐV.y.v?nɳTP//4ϧ|j[7/gr=L9g̝zE"ljQG'3_|LӞRL|&\?շ|yg-^}}x 1wشau80;둌0d4nӇ7o3F@kE }󑴫qu1+3|rQmN7wπ"'/Ow WtIN o64#qɷ?){F f˯~v[@]Ӈ~YaSt3߅,0O_~9e~YW~xe̲Cf}o'wD:YjCV;_ÄO6Q%>g ɝ{??BcLP[~wt0(-oCS}yC%_ze! 1]2Elf#+'= @zJjW[DDx2aC{#)D\ V@& pRaۯ)ƻQc"1q[OWMCI # % f' +‹_}u}ڵAը2uON)͞GƐ`vuN^J eS䂊qlE qQ[aB-iIן{G+Xn&0bQ#F^miEcr{ҟ=E5@M‘{Ht/ 2J*=ۀMSeb{'sg_a- ln'FO6 ѣ;]άW0UWrgC4*-U 0@DԤtҼH\Z$LUϾ}gc>i]HwI öx㎏>m7ؾEaP55" R;[ S bqGJF\5jiprƘaEcKG5$3w_/ɟi\xs邈muCD_[B^^^O2#J*5M,15TeJ}s:nRmK)Qh )N=o 6挚>jRnϙ9yPAdK)-)utgLCo9.\peI^rյŠSG8Y^M9{ṩ2/ctYΞPѥ ④]$15i#QOŒQۓs^uMw+*Kg7kXl s*uΖ@jvh[̪vnhD˯.p GȫwٕБkY%aJ+8a乵WMS@P!A5br旌uOYӣr>EӧM)Ia-;WMz\Y#eb}9e yFmÇc]3E|e3vnTRR1!`#d}Gxw]?DUUҋC=$IF!7Ef ]s-Pc,Dh\g6(01a^MAWH`n.c k|.`ΐ!*SFL69‚+ F!EhDD𺉩i1t%%k׍BFE-B$sT1, ^4 `"nsJv Q&(0e== j8.Ѹl"1#MT" ;,Fmbn50&E ATaG){k/xgd+ &%(H"Ԙj [Hw0ƺ=! X!01%^AwK2E_3sCAhZ杜\˵e˖uU/uu#0!( 0LGLc|`A&m !#` caVhImkmkwu V!BHg;I;($m7ծsZD HˀvSƀ1GTi#5J\D!4)!q`n";)tB'N@ml&W>'>TO !&"YR0!i=L=&V#5mJ\ĄIoo1`_;BF%ӄ瑁00}nWfvC^9 04fvEc;a`; lwL@w. g=& SEIys-42A@M uA`RI):7(qpw|uGJB ̞FF  9uG;i\VP'4L4)NyG|6L0mB`4n s!fhDGOړ7w|G1` KX@LDxgєe`("Lbt O=_ҿjWjiXV/B4 JMg)cv6@pA0TL_9pTUu~="@)cAL19%x!\9R‚ C= !BamMl*5&0JQNn4 ^QwX,~ͅ& ] CO\)7l6LSMC%b`(!谾? } c,`LC7Y@ y! H,(#bذ^ƾ F9$B@E$ "B߁c^,_PrG@0ꈨ6soΗ IDATH\X2M>~qfBCjc! H֠$Mw҆ U];2 G~0Dwu[[?!--`햼QhhJj>nO:I~nRA}'߽V54@&sq=KRe)E;^fMD+gvHtZg4Yj͊U 0M<]mۺK),= "RJG_0h`{|&9?I30tc3󵎻탯hJDcqusI@d皦;j&eO0ּ{Z_yJu@uii.ŢF(ⱨiKq3ޙ,7f<O dlIDH!"Sڿ=>Ő+b?pm)EŽqnu̸(I C*}@^g7jyaN]԰A[.o<` %$$, saϤA$ mM3_軪_QKax&4׻+'k~n5xiU<9j͵фi zqjxImC{"I؀Tק}c` E$–qO@b@dŒI-FXlNj{mlcqgvO%B@}.C@g-"7ۑ`@J+"prS'1Z]Uۮȵ}[CWv̨}Q)pb_"1{3}g0,m_Vrz5+upARyZ_v^{'oؔ]ssD𜋯>qw|㯱qQ%bb1mFn!"_?4t?{&'zd^)F횏v77 =xGC/,Zψ9c⟼mϹԡ_ۚ=5(q'M;}S[[|AOϞ~ܪf\~L&ude?h#;YX:q_k/߃HӢ9Vk]x7n>}wg}wu㷾$!ā+drO|i~}ww[zOl]+sLe˧;56']t95twԷT<~`ؔ;v':'^r!Ȯ#"Jϰ~d5bڹ[jZ*y"66X]zM8uֳ^64uNlY /JzwXǫ1{';s}aE3y~+r1 ?pVb~8zd/ۤP$) !Rzk֞EΨ. kk.>;hX-] t,.(\Wxu(yk 4SէLQ'K[iZq];sS5n٠{<$K];}8KSF\rƈ]\uw3`XR6!!2omiKbI ֦Gw7zsBP;:U5^gss\h`/~{Vo=j xr3کs-"cONDNmFY_#־["0 ko_Fqר2[sLiêX߂Т,R\T>|=yOJ;1,;Z9՛=Wv|o>%Siy2-ZT^c Hy|M@͵۶մ (z{c'_W1j肧ӝq J9Y|֢Ԭ-ߝgũGx$>G7Hgv+/,m.puPNEߪrf4#H[{>)߷s?k,N=tCZN~// ibkM;gdmYWDW۟4o𡇞 G_`k~kvlzs_ҹ~C<.atc mY6AwYֳsV]?Zo=`hF%|Tr̙sT ]x3?4w?RԷѮ%V<&n>9fv;X#|(iH@*J ƢĺOLJ\UCC'uP̆v"KJ۶Z;m"Pf@?ϵ=q[o߱~?|~G.tN y<,2{ Ϛ/`.MBgL}&V,tSp~S84Df>q炡kĸPLS,y(ܬ -diq ˬ>Զ{a? "At^]=HqκO0{eV:g5OOOj =@7! Q=:f& ރV`Kg==IMQ -hM@$Q &6n1Gdg]n@7?K?if2ֲe:oaOESJb%=-|j߻rAJ 믾hr*ҜIWc9}?nSQV*ּ}*M:/2$~våKfHJF[Wo\?~-_1>')L޼5ԴQ)L砤H ڤ6o۸.R*}mlj-RTT4|Ĉ%K躾Wx.Xnyg Zy3?ǖrKFLf~ACVUOlkk uϮYik=_95[7zݩ#6^iWLF}\`$f 2|AI0ZB/:9$M-P?sQ+iMCKmt K`kd?Z]o^񩜽4d=5W nr_4Hz=W"mO:wI\jTj%oTP=f浱:f@"q6L2% KAO楁ݚ71XWD܀n \}g%n۟JT.{J#n8qz 9%DHɔ;$:XVPݵn?Fq*)sE㿹1F }{Jo~WMa@J*w9^qv8TrA&~NךHLQZn鸋_p3 #畩H)JzLs'Kׯ_f6e6Cd\יNZY.4/AV1 ZZU_E Oj `c-|]Xw{knЫH9?t9cN@+r"<8D$9  e),MDmSs@w+E>V>M]D$H!tnM7ʟ~lO?]VSI;f H^;!!jӇ(m-wE}L(L4=z˚#" sW>B #Ɨ-Z%@5#V/:#ExueŢ²~U-A;;ԭO5(q3SR|#pF{jg}{Y;-S)=ښ6lTݒ*ب)Ņ\۶D9Hg]a& \ f5ۢ7g==ē`=;dJzh/p%WxPN܅A[ک .CwIoblNa _[ͣ8[*%#E?@2n6jf&'kv$^^('Os#rÍ 6E\nUy}K40ݟ5JJC_71f&&\uWum@F8Tڑw7U: 43@a3>O;?c4MDž5ex*:K47O-ZC}\ -7뚂:X& zpU3{%kY*SEsdvcזT_0[a]5.s: [^KίNp{%!T__s'}\?X8t]㯸i ]wp^aaWK^ҜEWm 7Lyv|R| ij/^p 2TDFD%7G7ΧJvTLtKN[AJ x\)3ћhiM,h.OQ۲Ur='N;v}Φz9ؖPP|mҶQ ;̣θ )3e+)A.iM %dSNَwcae:gXVndy/_rd[q ۶>?e'3/>e ߆ƿKRs I!9v=rGzsVHBy[[]{6SЧpwnA?[Ř"""Tq-}#rse kS29+(@f Wҿme`*ʲLK`UJZ!H6D-0II'`t]<=H@  BvҤU,W^`) 0]9ARI͗?(\$lJ&u_i[@GND{U)!la[34Ҳl Ph8@mϒR9 LtPJ + -)F̔˟&)o R$">R*ǻ~ 1Swg(l4-D4-*PEN&7RJ٦+ "B:>]@JJ,۶, R+]V#ES7QJ[)RP$DmU6J)Y~I?5m* I*l%tYC&"R9"a T IDATFԯ/ݹ&8ON@ڶBsv_pJ t hWXq~! VB؉HJBDd/ٙ07= P\nik}0vא>I:6g eƂ-4l3ig2A ' "e&?_,s#9h 8|K.Ҷ1$Ύ8X{ScՑB)2vJ)e@}G=i)S†pt HA靚IБx iR؆5-}](mQZTX))]1LAdL3i"\FsKB@ q{87JT׃5c$E6X!EA4 ?/7r R֝`?o J{9t-IkOxg) ._5ME =k1ǒ8plll,*/-;kwLancH{$,erntDPkRހ joKK8p"_ cd~AtA.RɾR"W8 {΋yX熅@2TRJFȆ4pH)x{ЃVD;}8*77d|ΰL){GF V9NH +[W3 7}^A~<c߱8`^Osޙoo =c҄}93 vna {8"ږ) Ƣ*=]{}r.huڔigOV,q %utJeRpN޴Pgo?{OГ `[Mئe=#+9'zjK hg2C4 CYŌ S2erlBNF-v`Iv8ݷE8 ^A~{W?KOeb~_u1ʋai`r]kLd8pvj^2t=޿`@lHi ˶"ĿG/쟇}t6s8X짆8p~4H&k!wQ6,+waYE:Bof?VmY|n)*ձ 8K;Yf,"1Dкd+mDo?*""FO"c/$vCetI4J^Le&H RcH‘w!rL3T"U "R ӹW!2 *]6Dǫ|weH 23{O~ L c y)GYdq$);> @%.6b[1o䓤;t˥-HS2TB@`N"RBRpn&&0DT^̓c&d/!7-Om=s:aX\cgR$Ir '$6pR.cv9H)RNH-އ}\T#V; #߸dhؖշçw^3C7}{ݔ$}4 1W򭻆75+mW>g=z][m^3nC3d aFΈ( dNކy| OffK}?u S:ߓ{c |ƥ~ /nK &TiȄ"c'I1M9Y1}nBJuFtE~ -ּ!q]+]gpԇ]?d|6jntTDLӬ:r \mj>wq*n[D=q_o5ѤٴbR݁i߼#I]'lRDDbK?`wLiO1(|1]~V/~}UxԱKxKYg oXrTތic{ScEi4ޚ(q49-D}ȥ _nڨuv6W.s+jٸ-ZuN.?clrrHWhdŽ1R=y@i!|ּ'HVt_546XvWsag]M;O[^Cʣou `>Xp(; Ks4h ucp_NNR%e0`Ј1l3hذIAԷ$d?xADJRqէtO(=$ )]Flu-6Y9282Dd2ٹc{UY^\^Xz@ ALdGg-ͱ[>hز^|i~K u vqfnsie9E"B"Lc )8"*"EX7,,z9DIJQ<FzN3;@On^$JJmٺuW 30Js-͍iL񸰤L%<#';s[2-W߳FjM#YλQ*ꚶļeZ׮Inwox['r OſD' RU>hߚU[kJI*5հq{$`n݁~Eݵ ?,*I Qȏ _`qO[W}V vlxN'ƭm^w\=ux };gǜ31Tx3UC~Qg׎[FnG߻W62m˗Y)nw]0#ڴU2{`i*yc[o]0f1 O۹oUN\Cړaeg9jfU9qJy I=,+ I)ML$H_6mΝ;꯿")\ ln-edAj.R-:Z'/׎vp_HיaLE %]-!<ܰ pi4ۢ[-`0tOnjm9̶^LqT㳾]9 /kd"tK5;K/ʌGB72;OD$)J׳ys8 "ǽtk>W[[uO?=770'''n]׿CD8jtS1rݎ6j/PUD۶[ACF %I m[LҖ@HJ(Ip痶0r RD 2ׂ#i (eۮb7IiˬS DPt$Aʿ0QR A)RRh. $HnC9fqLqD=T4c J O^rI( L*aq-,St‹HDZ!3?!m)BJ @f*䤘 _XHII)ԑV2t;/쪩MӁt29S@^ 'D_ = ';wI)D AHo޹nˮ5N V4+QHY? ?lYaYcsA RnƖE<9.Ɂ>r1ReYCGB~֢еWlQghLECll爮~14_LihklIE9}$JE$i4ԶNqW"f$w'^[0˘HF =NӕyDm[Y "˵iӦg{n|@%MRM&fZZ2gB<'qrBQVL)1 D+=[:qw#qr;±;3w4Z ?>ɺ=q=ooYŴhQmp `pqz Mk3M@ΓwkC5 qÒvٹ*3Si^⦒q?*l\zVJWYf@8gj%Uc |4]z TOduJ12tm9{ǣ؉1Ht @ !Pa> 6rtp@M " q@P Z` y|oX5>t ֤PÀi)uحvR/~_~Fzᙧh^7@j7.}YϾZⓏ|ۛ D6-hCv] ^-zgxfN}L:+{;}ˏ瑧v6&QuCNTeJ7'y.iw }z!(ߕ=~ xcǏBafk^3΂Uf=-%xy[۾ާD ǧb!t.FD皦k k#2i!c5+W'IcHJ!G/?+8׈qMud[}zg))#"w^ȹ:cyUJ-I!8axQX e[eZI~sz2E+;F#&\|4nPƜw%P=rh(w7v9-m5ۛ^vLJvSÞ.6fdU0_|12k)^jk;8YV7?'\9t$lڳk䅗_9aPݎq~W~)Xgغd蔳&N3%?:\uX?bڥ3&T4o_iKPgf` "eMf/<+'_{ >"7g|-ڥoǏ~o[T"iY֦ev{־fqw~ȜZ}vS4[z6yO>tC' kkVOc!C;+n~(&Wzϳ/΀fbwK><ㄤ$;Vywў\\ٖP2?OժJgˎGs_yyg/~s?#ܽ)Htu q =p].r7|ιsMkcdgkM]t緮z?ݿ|Gǣ,)>w򷯮? -~Wyesa}^qxZ,㱦ះ#"" nް}ͦ+VowV}wyCӝKgo|bΫ//TTWUT Vs<Oʚx0ٝĨ?VTٱE@))`kz3azBcͱD_T^EuIP;;aÿ8z9Bwu rO ߚM˅ %ٱV9LK?숈\ ›=1&=@ٿm7۟ Ҿ[z Kƶwߑ_qF?uƍ#L`~gw!hK6%etΌ!}3F nuຄ_9 akϺlH\lŹ#S||Aq-j`φϫXS[n , "(('Vf/2-kkٳ6no0 /$9"b0pm|yg(xjxz#N?sʵTl uDaQٹ3>OhHqCGF| `溁co0n`q뫶hh'N?{0YxD'߃rH!( H LSsB!@!K?қă@e3 HzcG`@@SNPAPAS."oYgM2횩#gP o 7䗦LopE5QcOuv఑#} H 5&,V ,<=>2i袓CRႾPwK+@{|P򕫽,WLI"W "di\Y2|{DPxry̙ׄ5pD騉`ݜW^9bgMˈqv~sBka* k{YOM>uggMAn1 q8pS#;9 4T2P IuH8e]\lt-нwDutqͣ#!rA"8u$NsDW6;?B8z:$opDsH'+',CQ׳D2BWγ=& "u,P= i/ H 9 #XIȠ_I@B{yP@ctgL7@9Awa$ReۃLH*"V~p 仧zR7o7jqq8,S! 7n+*~'Bpk8նC ^!.jYЧӛ|S|Hc/4.;*ESh"ufBDP6<+, d$z>FLv 2B C 8T ґS^v΃?+%+#{/sSp΢Qs(蜏l!4先+]GMDxn(J2д@ ]Ru%0+V@e5z/ϝ,} K. URp^fQCM]'io ~. 0#FgDp.p>"oy/= IDAT e&D[68 \< \5a<>7/0kwHE8>Axn԰RFSHH=$g3 -=yt"F̥ODSBZH{瓐H  82R {$BIu4GPJy\k54$[خT*2cs]f\= Vx~6he!,X>TH{T#w߆/_)xE3Rؼ0aDZ =vs&o{BCc+a9l1`X|p`@:S&cGnkؑ_:!2qd!:Ns]2nˁQIE=޸SIN &;2XfA@ AR4@LFLG$P^@"$jf :dDeZH:(odҝ#keOI$k; $4G~ h"@tI)@n QJ;#RgH$⌙fPw4$"#% c>4N)|.!8;ƑHH 8gVw~(WO}S,QRڭ¢LA/l^v@b2[yv ߿y7)!PYEx FCG]g/o{ 6 -ϸS@*pUBru/k(AN)^ °x)l*] ,KxtbYTg+!/|)bI#Cyyаy/~2U\Ks bo?ǖ_?NQDI)bXMLYh ?,)ߺ^_|gLygJiRRӴ>ܮU+8gOϣFn_X ;TЏ9.0J?5[6 kW5…[aG]5]w`UE?3s%I(HX]umn\ۺ*uFiA @HBګ} U޼7sf̙sXdG#5Oކ#sENn{?6j;E9r @п D &A`I9Gma!Zq,gl;ۘcټtG@}f };MeƌC.7uuZ|dpWE1H$pnXhҥ5 FZGu]9H*56AG T=GmAr_Bz''iZbHTFI}?ۤ@ڰ߸R37dfM0"8 -Z}E=c ʎ=naˊz9ޚ 脎1fh?ιZV; DH=a@p9BUU^n%`􀢕s>lac/ً/^S9wOlԉZ3o_TI'ëA/͈6;̢-Xew`q&}SK= R+-C]~:lsa|h./txh4ݖGw9ilLdd_:;l`wY=ۯ"j5P`[֘q%Tlr;7+5V`42 @iL+}uFKh}Jk}'QC~w{E:..(]J_}Jj|nMuMB,z>J<zbJk枧0Ezp>&ïOԍ8!.Z8{`U-5ju;{"N=?sK=BhwƏ )r( 0&!_sEڱRB֘W~"`F[9N?{em<ܤ{Y5t^ǫ(+>rߟXn2yj]7}_z#bc$Fede5{Ӝ~KFth'V9{N*cʅ~ MDF)8|V:eoEhZ$jw$g#s(լ>+&ziws>`jGbrBҼ?'=&|4)ң! AߵϖY+j$Nqlm·脡\o>ϷU-_# @@RdKLDARDpPTPANuGYb@gp&l ՗إk!R Ug0(6wrG#m @ f+ w'y"]g_X9cc8 y H3oZݳeS, s,C3ݓ-{c!HpܩݖsDњ;kv%D56+Rk>+aY{ZPmΕS2ډ✹MLȳYDfUXJR[W,j  p""rtA]}`|Xew=jL`{ qɺ QAhݳoeж^KF];Q`PR2bc?+&UAkYbjHlTo"Rԣm-bN=u{hkPs{镫|NAM %=p8\W0ab¶W۶V*wQ/>ۆN=)3H_@U89<a%F_kaLcd =9rΘ8MEetwҾzti%: ~.tb;v8o?g9_qZ*G_ Si ڽYݨsNx & yoԌh=T#QdCaw55-y+ 6)n]9h@cq2 p|k@% J){y{XmP6H|po?D/zf~_+Aq͵5-9%f0 )8(zs^fn=ڹlYE]_{㕽m:R7lU[k8! XV}ߗ W6f1%/gn: Kzrf8H9W&&8@tGIbz) i?"#c۷oJ3 9!$;;,S#5g?7}ǟ!M Lq]T@1t1`IffvG{+e]U6 >5$np(6.j!{i7-;<^m"DŽ'r cno"h6*y}]7n-wMHpY;kmnNz=:ؖ>0(\}Ou9[쬊uJ=Sg⾬t: @9|]mc 60;vTÇzE#0H i]ŋ!)J8Bi @SϞ? ;:3?!E;w:۹s#DQ꒧uQ+6sHRrӽ͝`#JbJ{tT8I@F^@! ONjn5jV<-;Sm/rsڱ] L#] v$DpشW|3_,n$tDf P뗭ow7۽,xʟ.D ?#~ Y=5cNR;BRGvO Fc8c'Vߏ0M젦°NIq\>0Xh\6h,3)Day:X$lQ q[q1QRG :Iۮ!{վƚ@{͇ ;y p.;ftiOƓ$I)$IXR)0DHcT)7uq Sҽ(cLBhDK *2F8S6"D۫#N Qc_ӡ챪v-;4B pt9W05jhWΥ+#:;eT?!{1/m ju9.b/ϏA'( "ATCAd4 1zM9ť8z(},V3k݂ }/Z,} H@-Q[1gQpkr^dsj4"|Ab4S|rlQ&b!39\H De5J0R6Ur[Xi00S|~ta5 -vA B ry\aa"(6UdGmm6UduLC˾R>x (ˁbwXD ` ()W=0Vű8q|Lܯaj{CaMu<_x ;s& Slx%ذ|VZڻ\_+Uwll@fHbGxonX庚IÒXon۷q=G T\N ->_"mKܞ |;bJ,q:go+vbG .۷ym{%}SAI *^p>Iҡ~Hc+oڹ? V U<ԛC:P?L]uܐ]k}[ʼnӷ ݆LY)f܉)iIF1OF_baWMuxii)S>;s"j綔TsbBskLtII&FYrRd4|iW?_lkDǟz >́s~\<̎'d!?x^!(QIeݻ)yEޣOfOcPko07%66byWվЀxo֘TYW/%]C6ZK3t{[p8:&+:glJBlcRϤ}u'n{q.s[WDkvGMJtE=?jhqMO87˱]g5R3_a b+wC-G}_f =]s23QxW~ks2bs "JR4ppn厸؄~zY~q~"->}ˣPnIoi^wq_n=xgcfҋlݶ0 ٗ]spgNgO#,|MvGs HD\=3RCSÔ_Dw덺k̙t+mP(ZҲ}Eȑdx+~>Y^ g`NCm&]D)B*t&o[xڀJ`HVBG3ֳ~6߯1cskﲔPH8""Hx'kjz CʙGIse)&uFixG>55,ު 2TalK1l` ʠZ |@3EսG{@7"2hauMzgKݜϦ}bguwһ4<ڻbT-QA4;WgaH0`bUϧWӹ-iy& IDAT5q֭AF (Q#hQ5w>[cn} vCd2zښCYe#,z?X{pno! 0F'%(xb 3v-.P8h+)S)() c4R r:RpPTY DwP 0a5(,cqUQKszg&ZN0JPۭ( jf\P0pDRxvqP0"cir*rUXr{BkI6f`(̐ {<UqI@КOȊ"=b!STT ڈXF) ,c;S^PTu׾Xw69 n:q:b0f訥GXc@‘H|/ zrN%~pcVTbL|=_3+I{ñ25;K s6u1UF1nΒ֤ni@Ng͆PG)}{v#z,ΠH4rII*+HfBua!$"$iL8!@H1ho ("A$Z< <,MapDNS3tpWB z%M 7Kḫ~0%H/5W< B)LG 8tvӈt.$5сt\f0֪B0 ahԟ 0L&]抪’Y3EӦK/ڶacw>y-^:>9֢e f;~V\Y{p7-okXiiɺذlGKNvbƎbUjh _`w<_9u@0%p98`J#3B@`&\/"pB c$\@' |)\@ c& o1fɧrk2V "7B"psum 8LA(FG` ۄpP(BG:_%mh_O fa ': %<[޾bEaon3\[VM~i꺦֍zuNL UgXe"1@|ډ Ip%b:X»zj`qF$'&M':Cx~ #!Uh T*V!̓ݛ^+O|&Q f_%&:0ggnhi0I @s>KR~F†zƅSg%X{};زIӿiou yuyu*@dϢ(gPwd D0 1Hȡ9Gf! ҌC"Tr¨,`SJZrQGZMO@!Be (Hca&A cZnUc`8cF$-BQ"f (`Fe,SUR^G(eT0€;@sd2ʔwm"8:) 5O *.2x0bx=rY`޿Z'' &;<]lf8R M ,/5_̭jЋ66Ĥt>WN BBQ(wixX`pކH*#;9)џl]xc=j;@FZAdjf_zݍO]UKUF&VWrJ3@X#|d93㽉),o\Wr0d=0ۃиGB&m ~T&^כmg.^h c8E!` 〰>q8!`љurS19u٦ћ_VcDV,_Bqحs*&QcDh`sBXĪ$I*+L.gkOyVey (P)h4=7u͞>{oV2_t&! >wcF_ 7{An62cs\}64G%Xn`s^ys r5c{AnaC8NygrΰS۪'c l_:CǍg<;î")G+g~4훅K|8 bZ?c~cŃz@e DzI9=/Z 6g}iK/ig>}Kn0ghω|6Wz0w[_W֣TXaHݚo6) Lf taU2dTZ]Ua1vH9J& ,ZjQr&1C{5Z+}!Wq6vZ_^< ?PNyrBNS%'{}d0$pucf$FUw=QmIO"8O>c_7.'VB^1qO?#i9@[kU]%j./mcٛ;?3St 3WwY)i{IYtCSۧ6կ]"*oúg)] S5Α1[y3#Fc3fDJ qG{b:ZQbwB{J.s#H.Ш(=猃7tٽGo4op]㳺m9玘dG{*XTlZXys ZG9V:wb898*!UD={ mU5E=[kxojrR\LB|E}zDffzgOcZX7G8 d\eL$TyS JK{ڂԼ$-`t- i=oiC`2ð}alkPyk+Ǎ14k%%'cؼ'J)喫55aoGsS2>`^qwT[/98?Pe@N̶ yi ,v fKv^_] ,ߛO;8Q zLX?Ḙ]б[AG>*tD;d#CDd' 9ip߉|:q+H;P$v_#:8#f$:{O|Mu bhP^~;! 9XCV&Rg}ʯ]: W*r04-_aYj=S7sO}}K^2KxA5&|/f۳ m^$Oy8wϲ7g&Bot7~ouˎ-3Gʿ_xgbE bzsk:7ܮ "4k͘[9ѽGoKfjþ=7x+*w٠fkcW8 |uO;dsHfT*sK=hu0p-,`?]|Gyi]|V5;q펑2Pw0ƴcԚ:O\VmF۱0`277a'^ |~UJN+îJsC|anzٔ|Vr/qů?,R^w}O?.!,`͈+9ت6{u OHM*/9 ;oVݖ_Um-`$]u@H!Č~W(] ֆ&\R7E=_ahufm-lܴ,cD {?&IM6*O cBh;|LBAyA@WIpQY>IsgPwT ]됱 &9$3g\r8T9 r)qpEk0 R(,+~P)%H?0]! ȓR@dYb r*.),1]aKGU$/Ud!ȒɝR ]1 n)ea%['kkh]`2U[ ( kMD X0TO_*CQc@CRLZ7 )gL|+9Ƙ9-E'e_`!ߠȵ3}a<@,g#'< tGϩ;j㩨PTQ,ވP(D|>3U+2 B@UJ tD{۫tUUPpa*w)K!BHA9FLUƊ,wBL1TeUB!BP(G\PckPUՈ_@asOaBrصHA9|@e%??GWDHU%ʓh΁Crupnq(ryyE gӮ/B>>>55*a!֝ɘ%~L}R65e:Xz z%9NJzoݴ1&`EݛefGÎ/+nKSKV`gQv~rnt 5ȿ7FװQ>zh/L$aw¸F5f79  5e ͜}yƍlhZiOy]J[lK7ڷ:^ijK F5*k Uޕ!TIm;NI@SInL?(3U2ZiCg=FDV ـR !>L::QRXg=PZ}tطduȀ˺;\ߪOyCwΛ؜1ٰ_oU&>6C f~Qz_o1_R9_.O-1}3ݣޔ%η9RBiM{3vuǍYTt75%55%ʬkj 8O-qU3×g'yG`qb\xb?#)8矒+ҟ}xcQ9*!nهpLS[sʧvۨk愒ʲ}e"!۷۹44{(e|1M=EO;\ ku2Eyċ_c* Qؘlo˔#XXC5zGuٝ:@'aW;#O{٨WӪ>jG9ٍH S+`g$( oZR^H[ -˒ctf `*Bu}yFִaGj'y,dox/IF0E{ -"U\@A"||,>0/#.,fDCAέUC?rK#ceƁG@TeDI&b]E?::miF .̖ 3$,>w-iPrBh@nyKd >P+sgq*j .gWxr)=g0` Fdr@ jC&+rQhCZ8X FX, DQGȏW{G`0xd1΁qz/㟻9(UU@)JTU4WeIRT}N{)Б (;N.Ϲz8c**Ñv9Zy8$)j$c\J# =”12Uc!T녪H~Ye?uk@8_w=71?Qk)}z}]F  Է*")L1v>wkmOՊE_67(oW `)WUa<׮9+x)U:n?>@u1^E4fe_{ٙ]r2֙-8SdEmKfUڭwh_Z4?_7^`W\o ZxnAU>a8]V IDATOAwwO]$f[C4{(ܶs]5-#$) ,Ry#b |fPHQ&/_㨽B!UYEڦ4LƏ~i8+c;tfvG *n _27g)sDHęauCj/QEBjni8rǬ:HIB KU>]sGs@*nwGˉ\Ot&gSBc7i{ ܺdy1ඖhKIٽEMط[nMHLJomiUFqUw+fw*-uA=8[wсd|.0[jJǫPN?\=.>~' c{[Oav0'wvb6'QvU*[m*ɱ.nKNInk=p!&݅Bmş?賊V0ɿb`_c olZ \4w޺M8pYXSw4xGQ~s8Sߓu=tO6|y/]oYsA?W>j%_2v!p`r@\2:=j +曖rGc]MSYWz˥c9B0"]E]Pp:s7wnl%3?c 6mK,CF1ϜU<ܺg{$0=\Pg_F\YcGm޸;ͺۮġTLST (E{# ByrIUդ"/ƘeHDqgt)J2"8VTUEƨR8E/h Ud#K"8HwŔ@RN;ԛ+˪J2F.`*Az^7* 8UJU.Չ@QTodq0鳪1noQW 8*T_Ν8&*4W25)莧3? gr^dزr;Zt&+̤?zq);BT񎁐*1T=1͠7,r _,>ۼg{>ɉ311-UwcW>^{Ã>FLbRJJrJ$7/b*]bsʈ@}}tu ~1wqS Nb2/._.8RbZgλFe{(.\볹=+wٻ*sf-&7JBH(A^>ׂVײkk]"(kBHm)7 A0)sf=y^s̜'=aFVe>ucF]Iޝ gfK+C#$آж+M[Zy>oFƟv׶{i7K3}Vۙ~ό(ʞ'F0 me }YxF'KG}Cl( ~FgH \G%,j@xdBcs, ͈hX pG/`'%=HI_O3GL{eي"+^OeɸL] PRP}NƎ4z~×K>kӼI%)fw=r`AKԊGKx* f\L4_p~%"G#fZY楔yXnɗ7HO.Yy( *>57Wό`yyO֮ھKvw`[Ee_XcSŷgՊ@ΣYV[x #?_dVbRYp&ZŊ+7wb"WC$}ʒ q9r<!ܜd)e>d@g9֧MN0qGM ?{OÒYˆvb5qa~+j~=E߸K@FN~gѮHFVY2g~K_ŴKqs.@"9w ?Ҧ~=>RF ryS]Pzw oߨ!޹aM=L{Ze׬ЦԴ^,KMTIsHW!ƶ)ӿZc+`H }A膤bĉa.3zߠ-p^?+8#X;١N陙8zO,hQT w56E˷7 󶌵[;duFG!^}Tw.Ќ&~>nR f;+yݥ(KK7?[}"Hrauث2i`2|*!d θ ]V`2A$"x㎬Pq^i%P9e0h;/x|Nj1p{I E8vUɶY<=]֞` R.H9gcݮ` xqBk@476VWܹeYƮqt@L:M͑_!DfAF(N9IlێO}ǯ__ BHss͛ntl؂&~۷p!DL;1 ޔuGx>ctMGGG{8g0 ԯ!!q_DkʳOi]Ξ< vjHPHJpf_!Cu;Uv,嘸].Y8#Q31) 9> C$8%$AarUj ' -S&'bʃ,<]< n P8"ОQ{YO=Pryw56`8pt1ʲĨAfv"q )6"O `ZԤ$/Q^`yKl/L{M#tHK0 }BȲmEV8 IBUs(MJoօFUU'ZzBp.q.FH.l$Ir\u?WSIAnqG!ร%3c )ӦMB] 6CV#5k֌3(2!0!Zc^Jn.:q٢hR4cK0Yݓ͚Y="ظ` ēo nڝQC:|ڳjڈ1#vO>e+3^{n#e􉣵e?]ނġG9 &PleYq}}.3&D{:ajnZ5tЀ2A8"_7bZw gQ$svtzΟݧ;w%:}?"]~mqCPO>Cg'}툝zCW.xV⊁l6v¨LDz&uEg acSjNlJ1BqDթ@9,9T$8eB0ΉP H7 l=ZBPsHrc M] N 4Ð$3&]["+qPJ)QJgrpkArG-79xʕ,WUUРUgUyA<%n .8BQzʕc 7T ݣCxX-sWu_RAyt\w؊k hn0@Gϯ>SMn=רK288Gyw/D<2N็A^]~mcz>̝T"mwy}IiRzzZR´WNbNkj_c.oz9dļ,{X= ѝpMRs\zk ?ҳ8c+_Z.B l3jCu)2gD)qsN&^xrn41iqK VNXYpuҳL4?' }脐TQ䶻ݡ/tIB N5kˆF97cz]ٞIB۶ͅ'!*ŴYVe˖-nSN?/FDZD+wtǹO(mۆa q0!s&eI f3.`Lx=#sW"P|HD31ƀ:t-&FB 8GyJ J;\-BUU]&Նm?q`OAP4bXzu{\ jK'0Ӱ& 3(a@G5Y̖\]G_zf}"EM{$ziē~HiNz a,8D WL:!\ ;9i\_GӼ>#g5 rWmZex*S=v*w'u3ihglLI0hHQ]dK J`0cA@~^u5':z-4&SSF: qw~`G]4* k7vvٴ PN}O5"M:?m/N97^} {%6{8)1ƘhA3OsϝRc恧:ze"mT32}2)-!6Eg椦<4-&:t0E؝7Yyӓօ84ӹ] [/֔m}pF׀ܶo㑅Ckk})cNCfK<}o/{'9M;7G(\M{`^O.U_8tp_b !MMM;vO1 ?=q8cL_qےp̈"7)9WE9''HhB; h) ,.%$(E[CfK HNe pHIjjsBY&.SV^[إd`f.dvQq 97:ЊUk*gJa F,267@qelǼy p?ԑM+s_qq/ִmpRBuf#0mYK'm/& Z XLOS3ƹj_etkڍE蟿uKC6Ҩ`ZT]x! 6@"m pӎ- $e۸ːGcX@#{.Z6G ,Mif>݇P}ס`#9D 0DmF)8oi2RXP #Qݍ9fEBcNC]54% mIJDA7oA*> qstM$h=c YF7$/ k?qEMC0/)!$% O^~W ; E=B?rHk<θ8LBHzfK_Gn6B^$xAb$ J =~Yߩk=ztjZ_n,VdL8V 7]6($iʕ˖-G%pƼU:OD @.}]BH3;Lg73gm uݖcfKjsԆ߯cXDh_sHZlw==zursWj*.N[Ŭ͵<7{b3FլQCz+@@g_yCryqs|a16{kN|r zu{^YuLS_!ݯ)~\]vu !@"x$'w4 I ёR͖KFҤ5\j`r]`MxQu<䢌"]\{~DHK^)j&&&(rvjZ)%"9'HS8?XȨF "s'15!;GDb˜NU1}Sr53(wrC鞓ɵ_wZvg_KM s.ĩ-{PJ$'%7%M;璿t锂)f,_ei^}[,p9f'%54?XoFv"@6bխ dWNwrQ()iwaZB7($19FN4@QkɝV:9ҫ1%H㿿}?A!Ƙm۔+˿wi`?iV#Ӱ^賏k`ѫ.6XBBpDv>O?|o]̋S4}C?+w;z&dְvӶ. u@_RY=JK\ܹwYie3>ٝqܘpHoA.,8c h:HjzzczFEC&J;|tEi={:0sdH^rK-K&>43m/pʽ- MԵObQӤPp=)>I>Hi~;䌌4 !Jzz8W~1uwpvdЏ#M;|L3͐Qջ<X;̬.=why99YYHV nlZsO9i>W\0 IDATcO5gzKH8;CVfѕn=ͫIƎ& $' 5뭷\*^]oF$ktҧ8e۫T}x@3NyAvGdƢs.˥؆ kEU'\}$$0FU5 R-no G(YaquV, Gڔ m83 B j۴u/DbB{ڻil2̶)C H$)e]zaY؊U'\}^Ԏ `MmSۦ6Em6kiWsȢM? HUg<ä '_xmK{o;}+-Bv}quw>ASҧTt+.pӕ]q͜5ANZWtvD"sȶ3q`Z'_e EDVdDBQuD\>W1ZL dwVTlK P ʴM n7[x0uHˣe m˰x^N4Q~InG f3B$vhܝ:L8ղv8nFߔ; &._޽a:3 4L* ɅH`2 N'wXNQ,e+3C)64ò1$l7h~DNWVB,=f OհuCKf.ТQQ/Xi3ls96:nY8R@0۝=(@D!`X7182Զ@`pJHܶ8} iYGA!NjZԹ &^VZ,dڹןk G=;?Ͼ'kn3)QY5O|aN!JJ;ecY5?ƛ]_ӫ#uSo>I2tQ:K(/M28-̎8cE^twN/iL#eh:He%8c4^;nFߔ;aaۊeSA B2, F! $C 0Ek] +)!Ǣ1B Ő ݀Ui4b2KB;FƊh)!D b1 XsŢ a,R%PKӅ0 @H%Iyz^@HBTÙg(̥5+ߟaGGcCQ~L6GnIgty :xpXCN-˪¬/τ3Q#N#ѵt}h<@!mi?B;7 1#Lฒ\ώ>SϾW/lD8,;uHn?&ڔ]sC&|ΣϚTSQ3͎!9Q]fҿm1t >Uu9pTV,ɒd1 XtXغtΡ(.sҿ-auXe~K͋z~Nۯ;d()m&JHtcK踱ƹcy~1b~8b{gҿK C±p/wE?.;s4'P=b9VKsC/)l#Zc8Cu j5Ar] $&m"1Yu9U7NpiѨ b 2FHu(_}Ag夓+kv6oS-\E"H-1,Zz܉h$lQ@`ť|ҴmWBQ__Mkr ,صyM#Kv7:E\0{Wu= jlh7A9u-pj|M$L]K}J)3l4|4b6naYz4;cgBuS&dN)5wϢE쮛v/V[ӐM[xaQvkkغqcvW-Mga>@2GZj}iNǃeK{ xw79FI0kK`x{sW;tLEa:px'# wlCݽ~u݆n Ng~^RJ|in:V͛}uK{;e^]4O:3 HQU|ް'哆%TDy+o.cTl[hƭŒ#[shu?P al3͛m rJKhӜw_zV/c&d9#f}CEߠywK2-&šqհ>Ŵ9FݴsJiZpMMX0ywKÊ#hF+ ]B!Li2n$l fh F,M326MhYc{i/{Z˾X;+'GԢJnFyՃ;n~}Wx̘TUO?݈[w? ux nD8#q *$rΘ=*s:{ЁM)w~{pc[%eӒV]Rig0'.ɂgi 㛩o% H _ާ}h~8>#݅^D)!AW?Chj 8I^;uvްk#IZT gNٽkʹRsy/OOU}}e 9ۭBQec'_yEj1͆>ݶ_VfU1TwZMO"9&f_(ppT*MV9i8ugk!!OpٚZVgϘ.(d@ s@RJ9m`̚d>f\I)nN/߸`awrnX4mbөhG8q=V?7ca~Ni^eeʊ)5saYiXe'x <%;XV}^VM|u[6BLoʈ ʩkbBH1 e=F_3ۗ.D*$rIZ>+׿܇KzSFTkI' M֜yY_2uJ~n0cXrؑ [AGc^\|u AչĝRm(};n߾_ҧG RDѣoY&Gda9X&N`vX"J`o+kc10dc1=D@sc]jUҊ~lĩY^7 |Do<QLJ5X\IaS.НyMupB75o-gGm~0&Å%]4sg_)͡0@m#Eca[>:cQ5+KLLLJ\(tVUdnB 8b@(Nj/BԣWEYEƈ֢$-naҜ.E$ݷ]|B|UVq?uK˾|_z.i\s׫gÊiG%BbWtÍxvH!J|e'xV>氷竦%Ɵ{nQL|e%O:1`#B0L4- 566nݺO>ѣǒ%K ?} cy8p9SJ<dΨ3=K@rێP3VKF3ȄahVk wc.X âkHY9 Gi)IMuurbG&XB[W.83:gW\+y2|Nz2]^\`~#uv):!L[nprS/iݺU$cōڵE#Y\KcnÖ/2Sk gR8' a"hmvQQߴ}ݒ[R JS]c`,+Ye*ohIƵՄԠq fWoѨ[7W7%rR!7! -z#e{?*tP/_KLa.u%֢h-=?l;"@ikøkyYBNUm ruV\MܱPE /=p/:e\oqֵo3P{=bp"Sq͚:oZW5;vl\їkoXTб,#)KݵyʭM%ei~}wxW;tPUU({{pc\+&MA!lnۢtA)Pԭf~ewiyA⚦1!"5BvU-5McϪ2cpy汨So#WFR)mPg fF#aOjN6pRt*<Ř@,C|l˴(8E2֢Q1H!,C3)tM쐏h~,D\,)&vM4a8 5OL*$QP8*nb\RnȎ2u4נ܈2nHѽ45>{~s^,w0s eڂd0-QةZ&.,,!C6fB#Sӓr@tM(<9uGρiXlٲMi-Q !MK"F0#  $56ja AFCh. $uXDZCK) JiGM|/wX1c0E\4tkl р0Rf r0h IUcF+-qCHK{c!C,*9. r I#P0&a},5AhϏo9& #I|q}(aH"˺r9 !BG/яkꛓ}  elJ%X"9aI`"zG8cD)e1*Dې H t{rI qXF@hOT ̩Ņ(m7(~$ez>mqJr:gh4{hO>ťO]2p`Tk;n3ǿѾ07o9 L j[HHs)@ D@6GYu:DK$K8Wۤ i(}V^|`^tK72SR/i;y ˿9s2[nEfVH@\XF-y%V,XIY3!-qnatUkv֊Hoݒo̞%IIo}~c(NQIq 9 D0–34$oPTԴ8g#oX.D|PUj a[?ac s΅`cJgrP 5M A8g919*iaaǍY;ޯ*huV?0HUt8]r.Uu˩:eٙS_oԒ^Eu9TNr.gXuK'[S8=Y,^&n˥8NK%A$G؟#fzhb9?VN5]CvE˗hԕ{ET]{u/^ճeoU|Z{@kjz3t4j@U>}Qh'N;ؽlG4fVoPRm\8됔)Ѯ{uн_^=  ̮$rM~}ΉW0܋&_r^p&.oBB pty<*crTY8›rʐ$@ Q&?9'N-o_fx}~(A`řrr]@ 1'x K]>.tرgo=?vp.J @;~GOo߹>(qV\Ea$#!2@TݾsW +]ar( PG$S"\\ E-!eL Yߛ1ɩ.ArbZeppDU >Ybٶrj'ӱS:)%,vKrm0X1Nۧ.] fK[cLCMVi' IDAT[`7~anzQ$=~``kuMdus0޽CqQp >I뛻^%wNF_N&Xniƭ6fk먯oUϋn *iukg]l_XL-GtKݸ/ݲn)#x.c|q(1zuk7ؖT:pHiߕBW}K uS]8pĒ rw]V}9fd6:UpY+/өwgLQsOv2a@ן}:v|hvv-+(X$dw)N8מ^^c?6rX8l]7_}n !CGn7,v-2gʟxAOOoN nuON㹽's6uӻw)4z~2okL ᤴԍzM޺nȝe=jӂdֲo{\v-wiq/yi!Nȿ︫{ʘq|ɇraVru8޸|Q>hH|=p9̀i',j;3Q3c6Dm 54Dc,\ѣ{s7@ :Bf N!ncS+Ӊ~)سx{gS?ߚnhvǒ<9ufOa߃h7D=xaܕ=$.qva\v <_wٸ|dwu_Qvi1 ;*F]ђ?'~$BĢzTN8]ufs7,۠'y_ijY!=szQ8$ %/f-{^ѝ;t5cGF])/cƟ~ŋ?d7 v.:v`huIQdWU&N؜X6²˒Stg(*N=PzLq HR$a3ө~.,J4"mz^ϼWիBslސ7(`p/̯pߖ4tgi^JA  _._^5,ˢ,E*g]Y;Г3Gw}0/ٿgœ"68|G<礼';b(g[a޽8)~{qr$c ѭ[>5⾽sZ\anhajs\ԭ\ڽhO $=cjJB^}~FM8d5v5LYdˎj,-u)W,q&e8xI1N[kAї~{pC29o-aV>gQF9秵aha vD6ٴHXl\Wcr" ` k]e2"y%*7/hOW @2z,08000jj.QՒeQNĔPmmxE^z ~in[&aW^su^T w -N Bwtwt$t2#.}GQcد X]lF}%WgmB /gIH ]˶ jyfNp>efc)s 2 .;GOM74~mwmـ%WJrbϑn"c9tttXu|po9ˆi=Hioq?lU?9P;DI<{jA)׎0>S*fOڳ6inUK|J"ڇc'r%@lK-\[0{Ύ3w˖Cޣ ?'^M71(|9)<3@2d,1KnNibޅWBV4|➛'.uy[y3wK2sZԑхV-Mp?ޕpC/'φ۞xh5A~0q &)-E]ϑd9 G7OBρF4V-(#dy/^iZ^hv]`hQ% E)8vj nMTݤHLDi Q laibQvDU vUY 2ƜNʕ+m6[߾}9Lre%%x]c"H?VA#p 0xX44fߏ]!ΕrWii29m߸ƓY%"˗A]{Yuk.dgvM}3x4DW 0טiꍕ)%RtMpd8й$~NY=,^vi-d ) Օ{lY^ڴlՖ,͑Rsݺ{'D[Y^6nLn;BszBj[UV.[ OiIa*_~hܰ!ː+5/70NLrA }% ';]p1͛7SJQεhE3 s-&VD AemIME1!]( T-"KSM Aᴴ4!}{✊M9ڃ<18BarnhaSĜEijLG~gq&9CC޽ꚺeѧS89iYJf000H@ ՘ǝ# 禦D ?bAs39mNa0JH@\7KT i:ëF%g1]t%eE)`NpNyy-d7l4VTSM[_iE95^;N/78 9@ 9uzDcG(Ϋd'edž~<,N /!fA$%cҒc&7LY?|((gG$؆]č݆Vx׎p[~{`hc"ׄ1jC# 0`a 2/a FBQDEZ"2̈ G*cA )Sc`G/(9~c~VĘLLE1㼹rP[ pKkr$ڴdI_b#js >H0]:B-O9HQlQ"%t1->`L=q cQ$lj (/ {DQDiXKH ",I%"K Qe,aj,9cc NNuDuO t n%=!`MiC$}%œRR:Us1L[r8DU;VzTPXRO#O5A#P~9ʱHD'Yt"(O96lgp !r4AL  pFXHʳ?xUDo,{c/$棯+Uz=|0:-Hm)nFUD[T 88ula.nq,4jI&C-!͸/k p@邷YN+8gq0FT7\% ^j$<øs2#+fz'd8Fj߂.O@e*D":3U4WYk&[FEA1CrFܼI\ӭ[qÆ 6PRҭ~Mn͠$۪6,o+ kdܿ7nՙBOJ#aa7<̳ElFxа3~?* Ul0 ]5>}˳ (ӷjy♣Gf-+fYt轟1a,܂ey?D˖7c`i-[pFVB.\SF]]đ#u911~?+ <0TQL󺵫}8lRt߮mf. &ځqE1E`KT߹sBʎϗ4GXz)~ b޹8g$ 7oݶq71DCa[q,;zcz=z9kZ8Ϯc@Pf$ࡖML{igݗ;}5)I-7_.k! p>޺icK#SwEwvNLim C]p> AB4ҬŶ++?Y1plYZj6؉=m9"q;oOJBu+VU2N~\oo=hy[sW/^<"a ` +Ll.;3jͲ,JiY]?PuEv\.Q!swɻ=p?pX|eMŀ9ѿ"}V+.|E\NAq %rP|h@)Mj5eg$ɓ0&'U Ǿ#\UVr2-fQfY5gݻ8SMo~x6hT8W<5iw- wyOxeI} 5*۹߹gs']w;Rշ(}Mq#:WceR5;BMSnzߕv=~睝ಶ؟oWZw]W aY$@du.)FUrrnI Ȳt+y[j蓮ǢՒ1f1)y=ꦡv/@dّG}ˊ͏V.\ua7{QgݱH23wm__㙃M ֖\[v߬*HVر#D@9N/wN0ݿ'b%GwmTb ͕O M^݈Rļj C)N"QKJ-Cw F`Σ1Fy< #Z9$hH Q?%[Q"( k8&aL."޶iq9 WSŘ xT3)AerQ45 ,"No۫m"fcjc"iRi"!˲eYe1f# d`1`㬲19- #/oe/wnEl΁L,;w?+pΙewL5lfn[m+mdp7ħ.nղDOZAFc:%4ͽ'y傞ē;qę'̈ %wxS￰o^Ls=m=1%ʾ"/65`rҁBRimՖIMpt) V1<`%ϐDq; :wݻpFsbbpή {=ImPզ=L=c3o$'l\⎑@ a @0}JMݝ\:,TSlXC?0J$wY:bOr*;mzBR-]>>ކFo{U9IF4MDϼYL98N*`PñNgQR[EtЙ?o+km}b082d,EӉ&i٪;-*oY6spDLYmG\5hǟ+ZP5=oȾ ԨC[06af؄Kt&gː.Ę~)N`lƚ~3gBU 4|+|&8S{Lt &2k~>`(޽?:;ǥ~xJQ9 JJr`]eZ=]uŕfMUbƼRvk"Wa^+w@$gCvo=HPg;ּ{ŷtVnذ87'KhڼxGAWD '0sI!f'[v\ISs s+='N^ /)ab 9];eunokժbm^B%zOT7MG„QE#i:-1n]w6x<}P<}1+bq8ҷzԉsX dÃ#Khy nBDf8Y6" 0&!LwIA3ZGԚKE\\787,@ 87(y\"I4+1h# <.A1&70PM z7 P L !{܂x Hд'>h|ӖJ3@`)tH81 [쎚ORf^bkޞ=Wp),T"!%*PK¶-gj왛f͍ f.Ad39`=(jsC}B~V{pѝߚbˮ f~>2n( uu2{It :hTÑXLo e0J({,SIӂ]`^;/0kك2g__opΩo= o qD PEIBQmt+q,] C:DX$AŸ!eS 8$YFq-1ZL䠢Ǝ1>Z-3I˄/;hk[5:Š)X^<ѮdKW]T%bǀ6C BC7b&/>LdajeƼ].BHwL ,&4 _Z]s榐4u,et2?/CML7| :SEN! woQJ{|G哷;wɖ!?yu>DYP_% ;_ +/ɥ׿}]&''=P {mٰP[[{S3wS8V_[`-[:=;CUܫS`i!N~]۔4RF. <4lv{_؛k=vfٚ}g|˥7smת5{7lw/.JkhwXC=rQnpCȜf&;gEo?^uIYO;NIXs@`Y=en?N70~~-o{-=ﮫ<&:<^_b"rN閝pD-o[̭a{_{QY٥]˾oxӓNЩY=1^̨uQtѯ-7DБ*r lHG&Cˑ1h%%@3Cu@X$3&!(".R*8E9$ #ڒ񭮘3u1N I$x( ~28˗$z/NHk6--g0SSTDK"F-8~7p`L㺧EM^ݺf{WY 38'гzGf}{ k7wR4*Cs]x䦋[Y߼O_eskisuQu\dnŬqYKDԹl`)1 62+n_C(v:oX >bg$hg,1†6%ZRjpi׎o&'ѿծo/{y{W7x+bԊ'%1  w0XeRMu˛G zkv|_4͓/Cpv ľ԰Kf}'"Dt/]~qWWye^3L#-Kw٩ah7XSN. 㠦exLcnPKw9nNNqǍ3t?2̙y @;GI)7M<3}7:wOwz\qaX0ۿiooEeQ5&S2ʻ=_*#CJ}OԲAYbX$ ڱ<V,pd$\%DYjmznSYcDDF;g폖71FOb8H(vG6|Hr9mNѿ33r!D@ӿ=g-ntY4bw^Աg~Uo:#P,N`z6큮a@#S㺇\{`Dtx}Aaɮ'c@+1 8mw%s /Dσn bY$ Mwm: \(+)X+o/+lߦs'iC>ƍ雿ዷ^qC,Y^2`1U-ehTk%o /nkUpҘ(#!{JLا01V1˷=,eGk6a5w/tߣ w\yᇳ <8fEKWĐ'-)}D1J~8*tEnE9Iw~t`t0AJII5MأȘeXԫs_"HHi7_z=vUvţ3r:usǞ3*A@F~QnL؟8ʺ%eu7_}Kz$TQ$`L'9tsa7^sIE}2N{b^EE]&6-[+=wL,5f~wo!]7s{ޯؓԫWvrCbIEI>.}<=ȕ\}gddwe5('^2v71WiiÆEwdWd{I=+:'0g9RIENI~E79(bwؙ#섲#S/[.dggk9&C3cXu7'3oVtvJڲKI=3ٍ~ Ȯ/9+G꟒]Vzhؽ?!_kft5+ =n@~:QC8 nr3tMjY=cXTQt? S=>F0,SЃS#D5}k\;~jtHH/*45MGAZ:Z#gȱSE@,(*05{S;!>2-hZDiWaՃӿ--CWc1ļ"K N⺍ХGOS{mt@Q8CE $ Lʇd!f*Q-9ΐ[Td#dz0izAI/eYc~#59| I~GB\j#/,F1vQ|q5)m45-FFovH qEժC17 eIi1!:%.-\p6{~>ߑ|]p5n~=MMaIL]R"gߴUnY3>H^}T${{+F?{WTմp$i=v?PŔﰽˇ; MܽS(C$uØLsl?| [J[7 :LiGY顭618Ę_BG3>Ψ?) ;FNw1> bLrzYxe}VvΕ SBsD  zFdԞ^n߾/tVV^N_ zeve&'[[ǯ^'vU+7MvXQKlah@~Jg2"SCS4cBQ3OV=ŏc+ԯ`u !z 0OO(fjZfznYvI~ɤ`vqF>1f>g3Q"~9IչⱹDD(2=a0La1F9;přOu?M|nIVO]c/gѴx(l:;¦}G,K6QmNJj<0tLPtP  Ƞ,a,ΙE!U!YAK̙($`FDH"rQ~D%pEQܻw,˒ h'(8]EN? ,:ګz}G$}GG p^eh*܃sZzX< wxGGB,>~u ^)8C)5 Pրۑ'q sju[8v1sV~^ *"@`Zx^VߑZ[pJJL + =QK`N;LsN4v,G ,$jd#[b)$D1x>tĉ[vQXi2@$RӰlMLF ١)cbV3+9 @#t"sA^ T #A,HBH `cWƹaw:'a:0㭏IBIpΰ0>0}tNKm`p,Q]&R !agɺeIhD3)ہos%U?OR +i{V7)7K?+]qES'_v}#[a63 rmS"u,uDO t Z:1H8$ߝ!!BH@Zli0q%cfPPl[ 9g0 @GN(#Ԫ~#!$ AG__i!¬ܴ)l0h: 57T]e`QA B{ֿŎT\~tghyIvU: 䏄bXM_{S0P1WsWlqW 7u'Bnd29ēA&Vg0to~6`sqx3 X1)Ln$Z3`-E!` :DٗK8?Qm?䵴$]wxjpL!C~B0pPy_. ! f p 9dIHB,eDDfYuXXu0 *bXȆ;$bX)64:$[a<8u>{߃7^8(|īP, ߋxzΫpZ)\/f.jă o\bg|f[m/-KWO@N Դ\?I7OϷ/nhb&Ѻ!}kk/hϧMp.~ѨqR +>u1no)XK1Q6/WD;jtħŊA$gPM oIRf"Li@ HeMCcWN^}v+Jh^@Cత)=>Ip>UjqonC˅H<Æ/{SGc} DU''Ѓ^]Z3Iyҩ* &Kvzze 357a{{j㺁 =&Q/'^+=yd.\ Č!tY};m3v &0wł& sof#®= !QDq0@XXrR0=) @%U,(AycFB Qp@@HuBRja2єPXґߥ*s EPܭ*KqE=;W@  %q!tĔc(֬ ݗs:rxΕ_߶F*++RacQ uƁ#:eJ8z^a w~LΟ#⋋ C]/4tEiJ*Ό8ЩΌ#ƈ誚xN{vWc[/upB Tđw\IJLr%I6M~*8^?ZhfUQ0tqY2)RVӧ|>wYR}{.'$Oerq^w`FF{1ΨGdMDtMx}u*iG= ;qOQE`_t #"'88>qAI"c#l0f:8RӀQꀅĊR`sBJFZF$CM aOaǠI^tr9Eq<f͢%Mg IwaWCK|Sm";(%S-}u.sffaڒ2jmhq g5DZ״ Sz"`2F)ƌV"層n'#]Xy龛So➇FxW|? Ow#j S& V?_7cvZ]KFT:cnCp\v#T8}n?<4aXh vA3p?[#Fħ;+ C)؝A xHvve^Ng8StAS9h8vDuAvEC ;:50B!6F@R;×>qoڻxn U}K*mٴek2qR×_-caAs}-qKy,t^Gߺ;7eInqt+Kҭx/\RbO;jFS SbDUPtL2zz`tobKC(b`8ŘQB) l rz}hcckd||`3B@0()c1/4cB h+ *ZFY'r[(N P =b4)l39kk.kLH4]ᜦ$goYMpڬgLr=btnߞIѧҫxcO\XYңoC˾yr=:ǜ:`'m$00=&f k'ϩ{n(~xs>? >}[_,Zg(q9x+k.\9jGqh.BҊ}2t[{ p @[癟5yEe`Rjx`3J5WBbԚwc<~`:ȷ|]!0~WN d|#ݵc}-z ϼA~o/6fSu8J@2v~{~w#LraIΒGÍaI3C+vX]IW4] 'V{ޝŠv=t6h" >֘ʼnlmpi 6TԘ1wC6.G{. f06kjևƌًڽ!zнW!׾󯾭7>qw_vf£oo2y-Sg~8(r/ֻ҃v37*FmrrOw } xn^9nJBB x%&T]3J - qxAI|ˎaaWyq¥\սptW8Kl}ȕm0⋱n$ֻtӻTH~3omנ#>5F JzJVYΕ7e/;s:20i 3 4k ƦsmnuksFOŊ0ИiUP/zTd\cލV\[g_Me™Wo#o?He/胯&S]8_==f@%IWzfO^w5=oێߛTO^]cz(|rHj y Wm-=h_&(sO7q_?鼿^X8(~ĘܢD*I!9 !஘*F4Uې.a ya0ɥ.ۘ uEVn*S>xCYˀySn!/Wϵx)[aSKKc#ّ=bd/Ы؍hB\GQV<"lcb{Zn &I 9Hᰤa9p[d rXT aB$3V *6`c$)դ2@-ٕf@ ;lEŹ`lP!181> "1ic,K)PI0Pn)c!P\c@Vΐڶ#=)BN`BV;:$b&6YM۸ Naa.+ .u\VZ9C9oO-Mt`%f$@Fyf v"NZ\c.b ;u*fzx{r^XG鸉x~zѶ|и|C5$8LG"㢍rpHIGEMaڦ}9@sqO3JeK3=1.bBE >L2_~۷oKII }/Iv)eVjݶ9+6_%!Έ̹$i3CSYc:3 (e!TiÌPQtYӻÜ5%'JIs.-uJJY4UUW)/;`Yq<eOEM͢jGxE'駟O+Z"4 0DT9NׂDUba51E#!@s9L<C__=m&9?{IIBBRJ)3\T Jn%cQj3"pm8o83־,lgclD ɀ"!O}Bg Փ_tYi ex):#" !jЉ,0h  s0ɵx:Z0Ga`9(+/+o_-EʶgOU]9A Q-Oؑi<7uDe!t=;#ikI3"(AV?yD4K  ,©#`1X+85 1Qo(ɷY͊zQX!+Q@ؠ"g3;cxS2`PXF~=/¢:ty\=K7lFM; Ql/ۼu`A~>ѥ@sڭi)*9`*'6«bQH( aBf)2P\MauDEBA€R&Zl90J",WG8%00vY Q䱦F)ÜAVԓ[7:BHnz2 (rDq Ba(εZ̚!ˢ`d,G.zAe6A (PsOm=zDjr(XVz|0(xD Êf/oNes{w{? YV}~8Ex"ZPyj`8ۺjY'V=r{?3x;:P5=+=)73%KQ"~4Fq|fڷ~̟oG 846ƌj-29˯uֳg8/_~U|QUUI,)2/_`ΜyV([ۻ7'L}a\kj!cǡ`=Oy>PplM_h ʟ Nv .a_T_rbEz{@xb p/Z{s 3'7uj@˫O>\?i$ k=[?]/-i 6'}3|cʙFp,͡`n߯MBokx -k/;s wٍ&ϙ1{E]̢*ȒݡCZGƨ`8Xբ/(~Q=Qe ̢KTS/V{U/ T|_anأGOG\==8Κ|| =%DBex!qdv_(p3(:(IToO q;> @|eŏ9G#jF=;nJ뿟h,B8~Ϝ(!+pg&dȲ7cPf#ka9ό1]vQрO>rxk@Q:"Bub8~kv0"!0"akλG/f8Fik)ls#$8RO*t+\V0Fq\DP;EhMq?vyݔF@,'}sb'9oWf'QI&wjl۽x2v^|]9d vw ʠ7 %~{b%>oޚNɢa1hO: h߿)kVZ<ża'Ǧzxi2rO1]{`Pa%OAI!E+*-SʻG au)(.3Nj΋xYeRXRJUX_P݅}*ܥDwY0 ` Rc.C3h0".ZՋ _dG֤9}G7uGOY fΪk<0mlGVWv[d.7?ٶ{aRJ_vEu_MT3UƏ:Hwݿ˺S6X'Lc]D 0Te׎ͪnq8c%)m+*fҮފGZ=]#'\2n)F (T-T@cFt8ݷ~@p+{޴iGu^e"֚|2ѾÄDTadC0@BH^7$  Q@fٯSB@X5Z'B顣c= i.7{}*9LѾ( Ֆf )Z^Yv8i;>;%--f?&S^ qir Ā1xXs]S:`nE#d&@!G@԰]qBQFU IHZo7wJDqѧ~(R FpFc!w ~衘nTv88 L4[]63@LUdybȞ]N:!f)L(Ye%qza_ <ư'&+-j}=|HQa"v e3J^a]m֊!ܚF]+2(5 E.` h9s*`20w >ܚ1F(mK4ƨ:c+jqѝ߅grc 6/N^[yUdM~6eQ~tLT1t84j{Lqjp*:’N(h24pHɲ|(E89F8HЫ}.yjnv7MCN)5(h;V\.Wd1 1n( V!dxF9bgYGM u:rf}uJR|wN,Ҍ8+>[on d}^WRi6.[K>M!"Èi λ| i P00B! <64/߲/1_eʛΣ;{Wq{~T{,A SE7ޒFmcyc]wxXz_4J=9=oy?JC:_P>rPXYλrTWk{#j0ZJnt`V&=p|kRf#.fF?Οx>m1 iW=曏\+oxέ]2ًWWV;绶=37]9&Svڥv=䛹CYA'x.m1F5"T\J]v䉓1]~-@R19|ĉ=a䥉5Srɪg39%2kk`?꛺Cwc&\;~0q)6:+AFPg1Ŋq+㾘F8sW]#0sqƠitU' i\~fSJJk5]G&[re]_cFрd 031]&rqod64I&pUy(C|t\wDW\llJaэ#^tk9rۑIY ܆Oѿ!bkOO 2f=Q]\_6 ʁ4̓rB^U=.)aw-Z5x w]L| c_5ݨmL/z+s:A4"+Cܹq ;0Jr@WPpvJs"  TјqVWza0 HHKb^X TMgfA'Xb11PU( 魀иS6 059w{cޞE!& :%EԼL5P5;!3>Q UOr  4ݓ TgJt"PT8A3ޞ!.RU=ǧ8`Н/0mYsLٳ3gf޹{i?Ο9e7B{k}FWN_zo4m<3[㡰dOM]VIWO޽I״v! 0n[G@~CKёg/ۑfޜ*ة* Z.hoʤ sn_o?twxFqX߹Eu^R6&9mʋ&T.():^=ic˃s>]u`۷daR+ԖdɮmBabT̉:^l'#j'sWZ FPER/_O{;WL#cz30!)U<o|G1yQ2~W3REsYAme'%0 f5ɢ` 75Ƥ7pJUf-ZGW=}=Y-D3];g^ .~W^6!!uا_|=;*Sg̪qݷYMNɲ+`zpߟ{bTݘy^7ӑflG8g[+޼K*-;GRߚL|Q_o>pPV%|>[SS3eʔ~ر#%%oxHB~x 2f53:ŸXo &K߿=}`CC YU5fݶا)$l^zWtu EԸν\ f 򞞽{[1A\j&XM.Ut wݷK9A++)ٰdNo3t]ɹNȤ#. r1={q:eud&vmY}a$:߸>s.CxǎCN~wrWHKupnH/Y`c~i~~~~AhidzFcEa]i nH.FuwB˨y˜A0CosC-=_o;R{:j-63A!L0۬fòfXT)Lt 8 Ӂ49(N#$GC`Y1ʁ0; ,&˂apdY"!l\V[@@b$,V"I:nx$-DTVMǍ=u`+na!kdwBzd&sm]7ļN;;ôo# V@ c BcI8 4CI#ՖfvAWC`U/c#frڢƅ `!Ѓ!u裇cFbA8836iO d`7x`#JcFE=Jacz=>w1B[2F)1TQfd6Xa3N ϱu^R 0FGef030kC)a !10ЈJ#DWH{p>169^-^E9l!V%9Ř1f4 k㶧AHF(  9 ]Cum~@7,jvӀ+PĊA"!o3@ܻϷ< N~{}~\hٲKk3n޲Mr`w څ!ІFO2`t=$k5Gj9K7PȫOdC{m}DejZ۶nqe|ӌx9L1Y6ȊמZ0}ﮝAqM!}ݢY3IMgMn@ݸb 2 =VHׁ+ϴͪrpesATDqp| +ǵ ̏&eu1U%\!g-znͿQ.V$/]dGyc+f (:ş6.NUMqh?_[H2폖 u҃W"VJ[-)௛roywsG)؀)%:{q]k`f{ft}f\ us?[=xИ_9b=Ŝ: -SJug_ }aG~ewۯb Y+/pޠчVyqѾÌ@U~pN3(VdUa 88"rR IDAT"Jo”0@-4E%c,6E8!S+22L(a+ҵq&ȁ %4yGL9 [sh0qHJ(Hf*ʉF 0eY^ tfBUpE)h?o1fUN=1BiH322NeEy~6 9i IG\vN&Bdk oYٔ;jܨуJMԹsrB G xhFpd%u0z@²SԔY&?ܥ&ߕcN{!BJVnQ׮] oԒJ2szeL}& RK;[̒4RRSB]K-S**(P4)K=8e~mqIy= b9G,&sgvN̮®Y9=O Kn<3!Jc9zsi靻3`]pvjמAޑ\ګ2$4@?L-7t@V;}zeu+I˜1pf-#]V{. 1\@ö/c$+*CJC@i?6 łV[nmh^6`5 `RUga$EM&[-"[ q;MSw\,9[VoߘWe7&ib5ƉCJ-VNV?_Œ=&ƙ6鍿.gvnWRշd 28gt 7㧯#]{'_}GK4{^t?Vq(/$ҾDN߱u'66##'n 1Q0mܶ'Kj@5]s&v$</)h :&hc7NM@u\{c{'JvY4}fo|pdy*!hqeҟzAC"C19Vy_9=煓;^@Oe"~Q.O뎏ޙ-ٔM6gS ! ņ,^l*EK5HeʽMDO4oLn9fP2AP AR|=6K]B4`EQ&c~1<}Y=ssޒ2o{c[6hPoZBJ[1<ĺN}|t?9Q!~s6zUo>akx֠н%e+0KR AӴkg_3x܌ZG[U7V,}qWiɶmKVqsWlwޠTdZ/wͧ+::}US<}Ϲ.de&H)Y3FoOR6.@"Ѫq6/ߍTIRKx!) c!$ 2L FP""B1Q%giZ@  4A7だ{䛤 E1 Fh!A(RB2B$) ԠSRl\Ҥ,yH =qiǀbF%Obm1Mg92. DAiL6mdgD{]UC/Z[7xnͷ˷]cӇLzi;QoP nm\z_>>aCK͖Ƭ>#r7,ZN iطrԢѡ@s 3pOϚn~nBh7 n9a5>v}A(kTr: ٥iO QTycBeZDZip &BfS_E՟m?+Wt)6[H/w}oxϽ{٣@O=zPsMe. xdwgu6>,R gX&@%m'ZhvwtbW=WR]}G^pӦd#sYC:J@ؔS?*,l54J\ DTiCW?xxo+ݸ<+߿hct30滋{>~QuQI @E`0=TʴG J6jg^v/3T-iV-J[( *m'6-3_`{ũP+ǒΕ<ŨUߐ҃ +Hql2l-1!&-뿵"j*?2m'i|~iEBDJ_|+!1 ~ޮLɈ1bzPDggõMϒ_N1>%1.*.!#ˢh K|Bk2g%F3X]4||07լP#>ٱ 1F;+W$zJLJ OJ# $B*c,"''HfAl-V1͔L#F 9e\RQ/@@a>m<4~مG1,ւ!\_9`ĽW}rqbma BLjLcEIC;|La؃_D + y0 '._Iu FTU.KB..hDB^4s{ȋ.t !5 Q !Grp~$7tCZTNW=?#9Oc@}VtQ<R8<6$̓{"Sg)1if<2DNך\2e9/=}@y}3oZxжxťGj9D0ew>xӡ1ÇKy<ZҶf@a||kL71 lXRy3_{ߟ)g5tᑜs=DPX=\t?vOB! ;'EaQP@@B _lu yk=0 C}`ALJ1@lu ]r`EV4K%S@{|R}c]`s3P`TwTKǛ#C ZQ$!A`P"R`Ξ\t : Wݱmm]>{.. 품gxkSΗW]\{vJH 3 P6 "!AKGkRBhJE_ /Üz -fDV#dT RfUTT6X}C \3H`,`s/'} !PQ;= U |q; I("4(FRbe[N PJ)eG#C|Q@1BH B\} .J68!J)"Iv`@%kA2(1u8Wwȗ*=8#-7A,w7cc"z$gi Q2{IM&asjuB+V90nnSJ68d ]@ 9\VѫbV J B%CZn\FTyg@{1AHP_  hK0"J?W©]ƘJ:Th¬_g_ RT$<*,1q ʄgkG)Řu[#HANS@>c032c`2%HFD3n!iMvh!Wf~QG]ץnXn{ rpȉ#%[m۽8/奻v)_Sy/Ҷ;^}յxgɮ[#rPp4mYfȶy}Ͼ}Q ϶w}GkPk6K)g?i˯-=h;dђ&xݱy^Aٴyݚ=O d{Rtx{P B )O/2m/?˵o=vdzi k?~ku!FFU( Br/`B1,2k{;ZiZD Uj4\ƒ\bed* /R$S2'bJ#@.SvZj) :C!#VX.VJ+P? W^w wl,m˦^qS*J(wgwvDs>OCrQ&c۬5zy}K@;R@AO$:By΋1s|<+(g[hc2s;y1-On9fWUGµ!fovEHҾ?[l{Ӵ)%r+PhftoYBZNɶ6펪USlor{\5-yC;+减啮ֆ:7;zo?f3/ՕiY0?]Z#KN|Yy)ݮe,KWNMS/=a=춫1 sjwޱ9Wf.g/_dcO~o4}>f%E_/WQX}Ļf=ﭛ2}&&}sp{'>zw(uZ^wzd@Eh\Br\Bdm/ӥDGh;c~;L8#) 0@LZԛ-{иhkԠ_I=Kl1z67ř@l1TruɼEM.b~RՇ /.yƄwaqAs~YW GA3٥V WT^32ZMݞAU߮aAs}ibX%N7Xv#"XԔ܂4DP99X=5]@"eH@DS$_/X3`ʾv6]jVK?$Mձʃ;6U;6hl:<廵GPlTͽwdd␋\adPȋn햪=Sl\fGܴGn=oDWT[dkJQ/Q&PEV<[g6bS)Dh5D"$䱻~y$v!t?W_;BoJz?ԎU<X[۰|lPr}ᮛD'ӊwϜqLI/DB1 Z E^ee I9 Hf8"vӖ$ؔNSK䀇0B7 Nb(Qwehח%+BAFr8eFA/8ޥu ڱJ. DE 1tb$b žqv8'FȠ ζ72!b !B ٯ*B'\zMG~X3'@3  /kp1tP3'ã&nԪ.v"a`s.1m_5w=zEiI=7q",C)~( ABScR}Ѡ GGPu;&1s^0/*7ߚ>T\D1T%^v?_}/RBHQʀ%0PVziۯb2R$Q;hѤ+ G IDATKan]V:uƣ #4 v_3keȋ'?aXa 3RSkX?Rd>jsUXqQ TQ=gRbhԽxZyNF&@sޛQ4XiwJRމkc!ca$Ga9k |cCR>!a)\aa( K3 QP fE0̚Em9n@"doXldHJ|QiϿQF״lz c3̈R1 ‡|c"n#(Acj\|B\c}=az: `apQB|!57{%/v>9>`1عcm%K/9x`=Ҷ"H9% &bB( B H$;M)Pȗs=M8 `"QEۀ() LAd$6@) R&BDBCsoAwBAD AJL|~ 'jsS#R<֦y asڽ^A*f/dnW&EC]@5yGve'۵2(@{V/|ᙇWG kv^ . 7E8 o3~YbA/4m ~ؽ3]3u:59ُ#cJ`V9r"!3Ϩ>|>mԫ>~>I:6PJgG)I鎎 &Ru.71&l 3E""i*A:b6n6BRB4I  .NRYt`x;BniQ=BpaYH1׾kg h#*|_Yl2"t9JgC2!$m>YP0)]w2/mmm&/, H(%h:btnRCkX65776B͎vp73؞;aq:_<5'+.&&3/'6Ҩ ʶdB4 qzul%5)mbBc2S!DD% 1"?9R>c/$i؀1_6OQ~ZWN6='*M)6c OΊ3B͖^bڰ(#/IM`)Al-&Y$ /O(= 1u(RJdԟ<:ȱnKn!|ͼeLoY+ܺjsd\<r$m!07wk7+1Ɛmjna.XRR ؛ ZX=qX;`=穷Uސ^ڷ9.%^;%7~?t>yO6bXL|e}N _8Zظ!;R{5ᄏi T. HF0 @gXՃ 8f'Gr ڹQQZzD9RRjȸxk]WyU'ډXE+,T9܈)t68RY`Ɉ =VZD^uG}rUXT֚fWa䑊jc%3Nˏ7}$;t6:߽M;'[{lM{ cO͈Z+w.wws4lҕbJ.'G NA;rH"y+ؾd"8t">?!`xN?V{>0a |.o Pdم} {B8ة !DT0Q!B% \)pDBvGoLj9sf+W[A9WvaXI/{E7l*i_Z[ZK A>#<̺CgdOx"p CVQ}k@1{QMUr:v29Kĝ_޻3.Q ** -|v5s(~䑉١w\pӝy4}R@TV^vv1Ma}_;hGK6lϸVR7dkR2s;!LDkb9@^d'E@HJ)ð Hȏ~4ym!M.B{=zE=k'7<ﹳ/VD+PzhQgl{]U]9Uh 2$-&7ҍ{};;^"GSE+ع~a>9'N'fŮ]p\Rds~J0.E(ݹu68<~󊈶سb}ޠ>nUlںi N Ӂtyۼ)d|>o ۰jݻYAWEunFҲ7^X̀Eڲ_-=nPSGB1ø7mݲ}N 4Ts`C5TX25<^NuHU ke3;0=q\˗;Q qr'iyE -\ʣG:bԅCPc1o訢 isMw; od^OP<*} oX ivː1z[U;9~Q;^(pۅGAPV{S`k=Hrљһh̠cytywZ[Z9؋&b/jw az3s9iqD$]y")mś#2YȤn@k :/f~T8ѕ`PZ_9N!t!#btK&D'&&ŚJc(EHeIq\I*&@,+'_?y@bMU)>nXR\ 1f) iiF5礅yq s-H\bNHL8tZbqPIOK}99M8z!&6)5)Ƭbu ^rwQSl\|N <~,KbLtbbR|RjGKSdEњPsVq SdtRrza,%m?x&dBRt-5,%RP+ ,KHIO0?벥 `X/3]0fXQoJX:P ONѩIYrF(L5+Z8=vȲLc˩䱑}v䦜DՉ6Qק`M) 6&/'5$*z;[~nkG'_|jRJԬh:B7|\fẴ\m3D%W%//[}r^/W/V;Crosb2[7O@E3 72 \?M;c'khMZ",Bزc,4naмo7ߥ%ǘc8 l !,HnDQy7k|=pvyBUDK AK)BY.9"I=egTPB4m|7)D$N@p˥*+% t՗f#HEW q|W.~6]٭.&t5 em[@IWSߕ q.%_=#:~.,( R"Hj-t@@Diħ"aXu yXt!7Knf ÓB 1"ARe`YV"KBv\󓞊 r#B > $bm"l 2Dlw}Up@n%A-?cgI3 ]UǕƃQ &0Ğ2:B?FΨUFQ6ҞF8oYх^+` a3^Q ħnJɢo{@ EQy;zt&,VϱJJ@դ#Hě+ձ>?i'IfD)m=aVP錔n_}auRO(B;bEe4@q4VCoJ$" R|.n<gW-)D=Ӣ7L[}@{ /.Eu2L&c"/<;@G@R TҲx}ARt EJϋC˕*"N'=RP2A)}~^7ʑp E*N sz<ұkBWaVUx(*5=z/<Ōxۦ_h` rS"N+afEϺz<q8E|Nzj<0O,b ںmqtvq@(,|ߟ(4Zeēߴn72叺  iA!"iRCj:cI3؅O:.+ Wo˜2d0kok*OܲVc,G-E9+H 2|q>Y;zrM#xu-(,u֊e^Qn[pvbWY?rz/;1s:kƽh=,( ~8XGM' W>^+5*}.=nxrgGXLrg՜><5\A2}W$]:)cW]<[=KNJΕvbΜOCAPi{V|teϾR u ~-G:V@$rY\R8$Pyvu`"n[ͲXɱ;eWO)i`]4R"W\}caՇ\^̋_Z&LS拭㯺%~W]O +o5n|OT:-0%/{k_\ue3// IN;qjj\ ŨE5vi{͢^2__~4x-P\WUٸQ!3eoeNK?Ci7 SJf0\8^|Y}^>潵kfi,BlM'Oe~/PP)M$Oj`(( L&,@+Z]cOE,0Dmk;::EzoJ\3m7>TZj{b?,FmJQ]}X9lӅ3V|uhqӖoEo?nO8 n{;Zu7Zm΋Zol_eȈGwmٺȋՕWQ*it}۾ eģ0 :1^9erDn7ߥLnZ !#ǺD'W';̂nY {H.n:Q3 2i!Jg}|>n \neH#?/9䪧"9'jj3 rGN},k;uX\E (_X\Bt3k^O|ݿB+lG\6|-|F땫6 *.Vcy1hcjb- Svo0?3N# #j\rH;+u2qoAİ~9c;<6fٿl)\egȶK)J썛U9-=dYY՗_dRoh2-i:V6akB(r6>&Bo'Y?_-WrV򯧇YY:RLhJ2j~G.2ɥgBH伮N!Tp?<٤#!W䢌e| XK?)ip,?nlziмoa.ۅd%6;1PN?|u R%e~Tf{xAjAXYt͌ޖ#Aٸ@7w}g>Ѻ鳕;Ț`cqvQ+s +M^:"b*2zUY^rh箃,Y wSu pRT[;]uVkǦ#O~RR#{4;L-{B4f]:EJ&zϭɪy߀Gsuե;&>1PϪ3mqv95=$;R1㱷i r'vBP`F)ڡ[?#+\NiNkkG #ԅW x\.@ ɺuMtӝt{ p˿,/Cg0hi{nI) Ezņlw5 V0:' L q=n6c7 }քW_:JϵĦzCбnOLοnceӮW[oxH3|@'k*5% 81Ol̲ߟq}Q_ϸ-rCK5֊rسO};d2=vL^~|p?}öSr9TUX, |G*ths⮯Z戹)}rH,0eꠅ,HE~'G w5]Ӡ*v5vH J)ƈ[2PPD3,r1~S}d]0?;}MsfD}mmƑg?*j0<>]dDQ z=@`y֝jS1FL!`Bk>[kҧعpoӧfU8S.}W_z9PLeon//`6wQ_̽[dHOH B 5t*TXP{ㇽRDE4H %B'MASDڙHGҸ:(7uOX˶9[v2vZGREZgFe#xjZ.fv]~wpn=?!z-޸$OoaqE\5yu=Z ᒬHJs5gϘ۱mF۶(R.Az+Îh=^~D)lk ʐ?ӓ誵 Ѣd/F/qµqsB]ιSʄ`q%ޕ:^H;SB^+P8_^hȵJ|w6A [654X7Fj;800&z{,r#Ij` cƌ816:G(0΅֓&(n/=ʃ#P4 &K1i1IͿ1DsXwD@~m<$A軅?)zBTK8h"`k9pM?d\fh"~*ƭT@r јHXjvMۧn9ULj {t/u);XwR&V"pc[WbTWvt7 Cꄱ)GGE=7/ouPCM +m[SRB^]~u%T=cۦEΔ-=uO\q߶M$Kh8p)nK>,wG`B 湚?.`ُt钛pAُb{@Pa#fʊ,؇O eq2sM#S̟BυͫKC|Z5WI[yC)=%XHqCU Etj%MRN8\5^ڻu>X2ѧC XիSu?^[vX-jՀ&|˯M.Z=uOEG Ъ4Zkݣs#U:``t,CEji٩E%*>7)-o*0!Ď202 XɱnЪUkuNJzY}{ǖNjصº޹\8udoi?S&E%( bce/h1zSPkSAVdU}3sf$A"HכAr \5]¹կ9c"8*l1jX)/:8)w@{E+pY3p~ݓ{C8i Ё\zƭ;gXKz"߿pҵSzjPRf]cJψE'?U6kA=y2]^\uֈ_NϞ=N)9bU|AW7 Bq8?D!Q4c!Q !!2Y)uE8X8pkCoJ Δ;W-5Dn){_gzU/8 1-zׄ]sg/Se܉'FUQU^UmٳuCzk]t|6Zzo] vSGb; )ZRr }ߐd:hwk[1!Dd͟"X7!h٫OL."*~_mF[Aҗ_F9Q>*sFkuv灓[Le>;iJ>xm³ wĆn~}Ҏ~gJiPPлヒ1~'vElW*158Eݖ7J8‚QY]^Orud} S.ՁKxeTUG!`sc!G4JFK عIc[]0=,e`\vijؘvub#8Ƅq0v2)4]2^ȣO 6niYqhuU2jd5wݤ!C#fbMi xkmbE JSH/r\//%^q޿w+-as,_Z j})kޖo'g5o3| ~9!-]lޓo90I-vw/n$/{Cݻww: /wzxS-28"K^q¤ dTuTc_?U"Yz:Gg0'&taq٥$dEÎjQ-tTRY͗u쵅'$Ҹx݄N7D16PD i1ė@4 nxJ@P€\h/mmH('>30'LV)zOIy4G fnNie} BƵ7kq&2;1JlߛwD tW8Zt3- z0`sQNIJuq+F'A`hk*WwRp\]:=|(<qN0)r2bqZĂFUvQުc) !Ѐֵ%/ ŨBC-:0V˯NJI\q9EGF}|\}GNd .sBO9+ `KNe(kp 60!-ߢ6l :=ײ_Asn45yb(%t;ڵq~9Ӿ}G{ܹsێKD` 0&GOAsN}@D[3o=Z_ |O2\ s0 IDy0u9?9â[ߛ ό<(TD%y5n)Go?(3 s9Kdb'm)odTbg' n` k{^y”2S2yP34nL` /?ݙ/>oԒ3(|V>)7;N>l㑚>n /w朅Giw_ns yd>QNZ.Ǘ~~ vLmU/S$w(@58 jmp#vu׺Qee'֘cUgpbQi$+ٴCߌ.㲻蘕u;yDrfSLa8p{BH߅HD{[̪= v8FqXs槟WO2SEԐS_4E 㺾}.e/>=ch^w&kux]6Ee\8ZFro5o j!co׶54#DKrIn_:=߽.kC~[Tnjby?5M8|H[ϛF |6Gط]OlxӰKmDSUr`e[MG+8e@0lwGlAhr霐4h0OZ /ۍ81Ҫ]A\:y ww7YsZyZ8gbG+|]RupU.&c"+7sz>BW0]^xQzyV0^]#!ė !Kn#Beg"L*X\@+活9R:1(K@@-7Lr~UjώQ;;r B9hNS$TPVUkj@P͵}㯍_$y{ +m[+{ݞd510Hn"JlXSf|WuyeQ$@4Cb"Qӫe`X`VEDNIXB,=ir{ z 3Y`UȘH=ƍ 9nX`feTI./%`qA7ĚUU!a!=(F4jѥ?a F'^~OI :`x2[gϏ ;nA8 zxEe E^(,84SU:;^"&ݗwH)c_SkR!DaRƴ<_X(\U}!1vAzŊ]?.v\ScdVD.9o*"SEU'!&DsIz IDAT?ba[D~X:ٓ)(:}ڌ'u\]v{l=)]Ǫ׿~K܍Yi\PiKϜ<`Þش@bz؄*-:9ڦ9,:!@GxTQ(~“[oah#.JOC\BaӰE[$7*p6XJN3JW{uiz:h鯾|GV|IŹ<{y79,jdfn߼4soxd+X}֫ⴤH}3'}C6|<ı9?[>z1]B]7q Θ}p9+q+M*JNG/40\UƲ(B fSW#Jnqd !Ut}#rLKꀐ/s8F4"-${]*}_@ *3(ZU[__Ow~˗gff߿qƲ,]9,xVq1Z\s=(~2E(7 j@D&qw!? y`X /{^n5#2F.UU%D@$Ytz,KX BU9&@ǶqU'9FU0GX)8gs!袔VeV'PEV9,8#S_1|@j+EQ 1uI"(t~L`F1 겂ғ2]BpuL)$)/d<nXbjEF%[ "{]ŧw#YU,KHЋȑ>u\!<ֿMӣZ-K3B=KdLpe mCY)6-@4ru: :Le %/b4ETYRşm9c"=h0 :t+ pOG oN1w%[dpy5=zN<0n_1)RC0Ɯ?B~%8V5t: \Ki< #"bJIRjQ{c>B!(*+Q깋5&N,`na b0EQY`^o ڼՏztiҾkF:NQ EN8G> q}a "A9o0+1 AyOzWӜƂ73k1W*: 곁`" c ~/BDu1u|dryƈ 0Yvb ŠJkxSA48>e\ccjl8BHissҷѲ| ?2Hj/c#eG6[ySrTT^S햌E' GlsTݽ#h${rxrjҞO_zmw_wiZ[rp]OV%\'z*KP|A35oNk2)n4i"S-ňR +n*ywL!}QFQ$̱a!,S&gj쎂ДVi16IVNsBHYYYXX؟${㕕SZZWfLCsD'NOhBp, 9!Dr93 %4 (Q|ѤS%XԛNEa\vԸqxxcz&$ZAs|h.+= 3|>`)Ĝ7̪Ywz:}~fƑI Z5Nƚ׷_#֫_G¾cOΌZSգVcyf<<Н;.wްryt5+t +g>U+\|6﹙3+ BYQъҮv+#ԢY'x;?=~dֻ-ȣKI'<555pޑqn2#8 1c8p[/߳^8\V`9os1)رnuqWOM˿A6^rhjZT;zk\FƘojv:ο^~0u$a]>Et*x*?~ۼtNņhm^ 0Wb,l./h3Ӆ JO*(=_B^ 蚯?{*^GN8╛H_]@zpobOa̲+cP|d7^x+4}2٫_zU_i)8k/zq+o8^hn䠬-'c-F&ؼhz=>XGp|{"wdn-ԜI76865iHkn2TA1٘Eǚ$,\?BQE]"D*BHq`~?!x[ӻn=sF\tO^Ѐ̕u`.iMlLi?s犧QOq`g-~b.9X7SK*T `2mA&GeQi]ajW\Jv +[s7s-ͮ*v+ɱ1-Ct&vk)ɇt|`.xT/s?=^[~p[o^b Np'3irȄ@VTAc1rͮͿ!e~-SC,z0I~S̾~<7m]<ӧNFMHoԫv4m߼M|i3^{|{\6Q>}d>n[폙I,Y_PR^(Vp&M[??RM҈PV"{JO窊X Y|j8NLF,!,ĺؙѣY!Hyx"w ŞGEi9bj}-C7" \{:x}VAxnUs,ȝ1uU L2Ƿ<h;-{˫<:Z';#E6[ˡ'vYK9jIk?k-? 6V'\s׫¶4+ָr'9cԎ/>01{摖8#?4bi;&8I gBH$b4A0r*hMegQ`ccLXӪ9t1_4s_J#FsJBc`ilԨR0FQ!vJQo1ʵghN)mUS1u|Et_QbPh +8~b¿/O ;sܓM2GkϞֈ މbTTm -صql?pelݹc v8[D‚Lbd6>SZ5tT>} q pWTAH~Ҟ;Ԗ gr aBT[O<^`4uJ.:'8cViA[7WjM2F{$ņki:ZdJy{VfvHKF-S" TJl-,qۦQm灰mÜxa&db[v-ydȨ%c9+ιQ8#I.9b~D@) 0BZ-O8Ӡo}e>ƌR9бnSEr< iߵAs>3Ff,ب$-?%hv^䟗ߑ<3r}9tPeʎ."ķNQ*˲fGz~x[Ky?K􈦊B`cȑufw߹;79gt|~b5wN42. p"WݮC97,2a(Sdm_Zo _}찇 UCbW?2w]́.!D 5Ue'qƑ;~F7??ǒX E8 5 1<.Lb 1!A,Ǝ_GFi9{fΓfޘWJ "T wgvg?,x){ >}#F);mZdsnV-[j:} +(މ#;>qG;HHw]f*l޴;=cO:yNeDw0e,cV@rb^Ō5A1ޤV\"[BW;Yq!!?U׻}iK~DqfQ'C JϽ^"N!51fՌiO6 0t9[WVUUUU]JKb6/pp aM7_-+ܪӱk@3nj6w`ԏg$`˫Ȫ[QKUW5յkYrƥwmFlu 5dwmܵW?zсs^A6 B15 (\scvg?59Ӗ ZWqwo BUA|7W/NXJM^E@cT|lKs u8}_ IDATH=";7'Le,^R H9rTY|lܷ4%YGX`{v𐦞^[ fNUKN;nՊCF<6o۹mKF"ڱtY0 L***>oC'u`Govc/X]wL[tc N={J/3,j\]H{9 ($IѣQQQ%%6U=SDH@`h30'_㳧2bE41f́:cRJ1eeenYIZx!w!رĘ&1UI&dD~}ϬƑrwCӓbMDkӧD# iLM-)(Ij߭M|@8V=BXڪ}n]Zlݹk;OIaRnMYCb&1(4i }a]u]hj2}Բvpy9=rlBt؇@đ2WgM ;s@q~K%!Yj Qdv u(瘥Q|lXP-=Ve;KLԸEϝ!TXhE艳`u۲dvy 'Δ)Mue9Y͈?}gT.Jl,=\͚73jPg:&?zSJ)e,kF*<~j95!uKۺu:k}xGܸx{g[IUA,3g6;WW`QEN9SW,\W??ld25?KrF\hوOG¬?lWl"w>].źIJcHK/Lz'nֻk>Nj7P7R6X$1ww1"фkk{cm۶{Nt9Wg?]ŋu:QKd9RVVޣGwLk?>7sƴtY;iaZ__SؠI-D-`J9AZ@!B})z񖜪 &xOc8 NUkjXYrԻMN@a 0FTU9 "`\U˜EF" ATmoXRlHIw;Uw-Қ3_y3UR'=0zH-dRcwn_f=«}Jl|=) |5=;{O:Zu$d-95B̘9m|+Тj{tĸaU{-=4!y#Uׂ<Š Z?] N0gZ%[}4B-)F$,9b4K l( bGFVe}浅 Rh W*QUUn#Tdw->zQƥ[v5ycVׅ*c`0c9Mv mQz뤄`EQeM{اs ͅǾ*""yeуgR Q%:()D'2EHeY8xK.UDGPSHoެy:@Cc`9¤gsCU\=PDm5PCoc"_$jk|ݭ׻Zgd!A8sO%Qן0!0Ș @cPlq"$KtBzbQYDݑ6!E ׮sض;YE-(^w`rubL7{Uu9ɓwvK@%JQumj{WkցY BHws~'! GC6 {ϼyx8B'/f8gaaHzNV,Z*ѿ(C@mU_ȓc:RmKL= fV⢢ʲh h |YWs MO*EN)BϞo ?ؾ>πP8u y߼_w5'=;y o*ǵ{_yド/)dcz-y~;nrGt|C 5κP\rcOö/N)lٿT5JְM Ѣn{򵅎۞tǍ}z'}y)KCgH |~4ieeVGlokgw>=;>ش!>9In֬Y,.\jwvPհ"3tƌ>TƨJujh?c<\{1ự;v!D0{b.I"= Wl]2xHտ~vL1HN.\2K!]xqsnټM&G#,G"0g.(VU6b ՉyEms۶/OJPm;Ҕ׻n%}L+i-v49d<<#"8!PUQ嘁|o>i 4ֆ$GMo`EQ#1)=A"JEeO*Mig]7#!>&rf9eӊm Au7$/UTOim0yA}ώ՝)E 9^%6[BބQ Ն%0?;)510/O,(3nTycJœ܂ԄT @DjaY/QPH+2MB)e=(wvnVa9q-=0+9͉w3ܱYvu Lvǯc۹sGNn$wPC;gn\` 698D#|7Lr_ԉДvL:!Q]Өl1*95uEXHb?>3{~z3 B k."F;Tlە g,npI窪IyAL׹"}ӧO(Lrqv,` ," j/ {ٗsE$J Q9laTªF) BV*C(蠚ADh0vt$G;3C*e)f[P;A@PmNړO}g_*c:HiuӮ'\7Ĩϰ:bY9ͿzsyƙIf qF9ϨU8cG1MU~& A;L?5CD0 hAh!"g5(qO 'L6Rf;^=oA R:u*H-YϨ'mC`=sa"pY|xFITTp}Zȣ#nZ=d>#h;j`P)~0 3a0(3!`cAbB % ),IÊ FX爐Z{/\7Ή1j8I ?.<6u1x=1ڈpq$^9&S'!hJ{iMʐy Tsmy?irTsvGv:3Xy pZi?Ύ!@0,Y.Nj6oM#CL !qeswܿD?]-3$Zƽw\ŭ~=e$Ym(!cJ@.* :\QQ̹hEG91pƸ Y]1.,^H![4~=L&.vv2}i!v#!0`qeɵ1KI7]?mݻ/κ3L<4N)#]KsEL[֋mƻGhkoAy7Ojdy#Z_xc[b?BkŖ_%W x[ xS{Y[?:4:S/7#׸KhGV,x/b)Y&_y1Z{Fstϝ'bۻ,!WmĪ/?]sОTFSN&Y+؍nVc{DQ5O:R4~+֭N,,-L(?XِZد05>bqXxHS'k_Z7/5G)*qa2ɵ-edޱk`DL3ֶ4kꚎh9tDSMm=pӰc!&ͅӝ2hP߃kvpΌ^Y)6D_xr_ag(FMb|;o?nn]7FN#o']w,@x~XsKMD,|rs> Fo/<'oBTcSf'mtTY/Ty; FCG ٿܖ'+ZMf:s`4uQcRiѵ<}ǂƝ?H]2B4w0 KJkw+ȕ; >|"KzoSA_Zi_) ̘>yq%y܍L?Zp8`aK}W=;{C֕ׯ[]v~^_sˋ,/.?8ܼ{~fÖ5wsIy_$:7|s:1EtKǮ^ɜ`H ) Bn33 Cu<`o8coɾ== 0F)s&lHWHJCSTQB,R($;6ÎwBrr "S 3#b)WϙnG*pT34cL_:{XaNNKRZ[N/=؞86TB0hħ%%j98&"‚4 D`H'%1M֘D'z| R8m`(y6EƆbٗFK(1;=&:F}!?5Cwgo`|F?cN_:i59ln\;{%1xDӮi{!v<p僗~R%WOj!āG>OfWXp8얎]B@p'zg著3/gA.y9LѨ0t !v&ٔK4>u폌K ~^3tyOLʹ!Kw+fD qI82 8K/9)6c);7n[dG'kݷ`Չi3&D# yC`I}Ap º(6šbj;KdȲs'n?oWon|ӥ?[EܻW]5}?s>]wi_SͺZ::jd?GJ19+wom)>:d}nR=tVG7k.9( ꡛ CGBlOeNI"TQc  t]4C4~#N:/*8pӣ:''; D\XMګg}pq\)s;(wius bF#vD"(yN5:Ϩ ?5s3iu"B!~[uQE6R46gt//ӣwuu{:tۺkG$;qW? D[|Iؒ^g^YhEAT_PڵWQ]yj/5tťqb~b*'h~2j @N2adh#JyEERSĔ4LY-Z8Yߌ>{hb:Gوk e.Bv){x9ǘΘظѣGUo'BЃ>T@ Va83Yei˾>}Mݻ bǪ,]e:păcúؼ8m-אwM#Om hs=h܈}Z4rP^Ec'в՞»!  Aa j(az!L7rܣOqɊ Rw]z}afj!T3YDqq.j5T,vv );aU h&W/ fОrQ긨G;7Tkm82M Px]Y9o? r}k7i7ƅ˖9bÊ(_,5$ݾ`vҼܸpSwɒuѣCX.Q7ဠ3NlTעӊR Á p&p{?ǂ!-G @c႒@aF_6KZ{רQuNc>ĦwTxnң1T k1]vTx+EwW~MAb3ݳpU9sm_i39bA/>G "٣bD8!L CjW旊j搿g jŠip0;v/_tI[:?D?pz𯈓!Wu0fp@0qR5Ue=Pރ-@ BH{)FѴB`T 4&M=bL1C2&OGx6&/!])`'^}UA;FϹy֘ކn"HH-$#Q&@U9lj(E  -"&d7òb( lY9g*ֽ M)?O+?ؗr7. A7!Xf(A M'[8>QI1>$@#ӆyZ`Zϲ};y/څ:avo3W\eމVaFuaCɚˇU(Mm'W>[<9-M Mu'bsz{6 ՝nzX]׿enx~_xD͝= *!aK""B/1`0??5DkʣaOXq/?5hz՗?s|z%)Nf>Ӭo|sΣoתwsgbsmہ5,_1h>ou{/n}柽 RCޭ`0 #66駟X,w}w[[۷q얎st )p_9D$qPJ=/=?̺16K3Gʹѩ3~FEyBmii钻20fo/ՐX@0yx}V_>~s=?{$mU_[tdՊtEO6/?5;4VP6{tìeV ʑ>XQPWLdpX,w_~FQ@vKΨdwL5$BXe1QHH wRqEfp(Xrd)a;1a m""z( , 8e I"F_b)a,1LfߎTgSD1 B3]?NJ>aumV{TZJ"(u8$궄&(yȥSӣ'"0&h@RHJ);s12u,ILӈ$SUM*n|rKYYY(;^8p΢bSIY]ujUl|0mV豣˷tdW?-ҷ9Kq(qv`J 9$b^[YaKHuB@9D;֘D+ 7`PAjKdfڣܞi`}"qC4>,F.ȡVŸ D%- WL,$**,АJnyO6)$-5^(ɴSA$IKz3u#Dtѷ_x<|&_Dq70yg3wǾtVߺÖ_o҂ͮ䄽Ma٠N bj9qpKIB+y⧞ۖ/{rL%HP(4cƌ7mR꺮j8zGIIIihhqsΊ{sX;44yv4ܷsBĄ.9x3"ę:C ;4f8ggrc)axk'1#)Gl.;wnL%ϸcgiᄀ#SzjҮlO~fSChdwq::sND1 xpKAD%%&PP],<E9T`q^?D'$&'׳_gz]Ny#0b,q-Ңf^~gq#`cpmUѪ~`|bB}꿾#=W>$'1Vط7qOcÇN XV3u8eLEF 4AQ帼(8]= .GJ~q$xp-7ٜIYÆ LUV68-T7WZvNVvFv~nj=%Vug;kʚ>1s3*#7vk]BR`&L:mP^^INA9u0Wd; jl*(B𗟾g=aPNLikg͜ 0CgclgwTЫΤ1q%^[=GS?9ې9M9AAyVm5Wxk Xp`;4m6a7,y/+pS12K6y r[y["n7:}EGBZVT̘9EaJb逼py'_͜9ASΚ KC#ākhB%/yMC(r;fX~#c' n.w{k>k79C䕍uk/>>֖+oI$M5G{(ጋ;hB.GT5{Nz{d|q `Bz^V2b }!Schk+c)sg'* ;ꯨjj&@׺7F̈m\ڃ:>?χWLI9"5=0pg 0(AHU,:# KWV02dC:;V{y~'!CCVTT.l'e 3^6UM׃ukV/&^ )4@q8d?tA>CAG& m6mgXmk60DXضkWׇ n^ab@ˉk֝-zW^~`ޭ /SRI>v(l9d-E_ / ى a,o=᎟ݾx>ræ< g}/)`T #c4sZOTGSxy.,bhVʎnٴf=^0cO@ks+71w_h[_q.jnnA560`o_j]u}3Xv&pЎk6V7 ʣGDjk޿gc H413-8afukk[¢5P68nem#"aLI}تs__jSQ H #3J伜48y683#kgN5<t/Jcg P3骫 "ʛ.?%|m-----N1Z8 ! yh]Uh~g߬xsiZ;|N)\.h#[x]bi0旊h7iM>g6.x'l%y,Y 5o ?k1=be@[gة]Ѩgx:ft+z>TSun7>o'54zCOe*]MӁQD5H?mk־pVWcNBDʁsf5Jyk:Y 6UIel(quV/ޑ3&[-8^dDaMg={FegTbGѸ5Z2*ژE׾+K]#KrvY+sK ;1I4~!N#wuݏbF'd!+¨A Eqf~㄀ƤS#?BDvR|9~!L:| Hq,6cdh>GSFQ"退q _Z#L=͗ & ]5C$ͻea*## 83AXxYs-Cؽ٠>|яߨX=pu#fDR5IQe#;oܱoCO󅯾~ȩ6'~9G1{뚡G 2 X@@#$ʄ.i4tPرn+b xPig$=uץB3G]#Y1k52[7}zI=n0v" DAf7q;0T%爛n3M7D][xo~5˾X˳ŷ7n.{(X'_ J÷?'1E0"O#`:X KT_Z%j¥[?^p9 gt޿!kRMw܂|O9Cпǥ˒P#Z@Kow:FaP17496\Nk(U2!: } $O2PMO9ꩿqCD@Q:~Fϡ!j ! 5=0<9CnIEh Ïz82gߒ[j P3ڞ.W)۬RԎ-gc,tsƙ:ݎA,E>onaf&dsYq ]|/Uu-,ɉvcBíG˷m_6A$UՎs,N ʹ5*46 ,ڜQaZ0ۢan=Z}ۺ/+k=X8g#,s.tg c]z 1 q%_nգy+>~o鉠rbY--bO^_,1`1,48 L1c*ן!1YrX`oskPo5HVS}Rz/ЫӖFY&{7}a:@#_ L6ف8lj[o~$%'+z˦]۶nAFpj(j0`ضev#>[ V} 0D  :@U3VWz\s՗,*n=I oX^~ti=vO,5]1ѝ}U uVWv!3AaPFN)`Q 0t]eS`D"xJm9vLE$n5~7mQc\%IDQ(CUU:;QBp,1 D,b D\n*pJͨH;jVsϊ~z^{ݼy=J:NE(&`Α`;Ca X{Ϟw7޶bOMcMs&Sۢs5 q$c#ttq@.E^> P4E|Lj0dh9{5ugK ꎵ 55Vw4UN GoW%urƃwyZCw[Sv Ʀ% lXO!D@: I :NEQt]u]4qyyya~+oV,QH1'z6Gٔ bX}1y@BUob{ϝqyڪS)ILջgf^1GᶺU*,̷KԨ#ӳ'绹AM?,T^4oJONA@ӳTakANڂcU-ys]l2|9C%}ғS *-8r<6#/#8 1*0 k"x^/ScIyyaiii)9YF[&<Ȓt\=a!֣B(&>93-=%1ߐLE}՜%#ƎZi",{򒑹ic'bRsJONJJKV7g`,+ԫ^ PM?>=1/lB!K$[RR)H,aJ{yN~75}V Q 1ժmdM:dbZ0A$`\EI"ޓM%/w.C:\rDwDnpIZdI`0(JbbgU ]1=ѷrꠢhI۲n_pY{_rJq;KK67 E^Ь3 =+s"cs; 15'^6G$j̫NCTrK;唌)<~]Gk|^УYő13"s?}NGm(@to#~lSÍLsynpp@GtѽKX=v;A8QkpW›/7gjk=xѪҩ3{;ƺcy=blĠ beVbBK?uvXq͂ ;oБ/|PK >#<8 IDAT j{LLܥxG3x)1=y)_@Oٍ։eaVZ<^qdVBP$O]q!gBS޹V.|ʪ/1t?^6];&<`juHWHԍ.d{gV]wm 0eLZ=/+?OaVFN6$HbkŞõީ0"_?'%#nFk )7jġ9iS;`xYA=o +P͞rG $0]Iy߶c⊆&`:}T#0 0 J0Sʨks1jPS2Fe@ M48gLTM7LAk5 LDBA7UO*3Jvn,eq2f&2Kc9p{p8#s1~zYm1 g905xfIQj6{7c RJ9𞬬jhbB^ϊwX{{ BµO҈m;闻|뽅qxoyk0,҇* -~C{Eka1$Hן{}[Ci*|oy? Uڪ'Vj{>9/q~4BD`oEܲoϾ:v8/00iec9n޽=:n;{wYQi6[޵w[C{gM5 M70Ƙ qBy7X/'iQ^c}_~_z{'`*$W𼃟p`{wwO?XHYp$`ݻizlqFp傼 U! P1neSFv$b #ҡC!^R,IrS4L6:b; N/0:Q7esX80wztv1sy; I{Cz?5#DN=mg\q2h@)zmڙX4bV ^XԬYE9T7%WnܺhTM*?cO7+S"ڌh"${$IB`z31of= z.M!`LXxuC@dB)GYBĖ'lO]9vC !(Sj@(pl1qVʃI{_VbV&S Iڍ M7 6<0u%n;G;a s\3;| Κc~sLd;s!&ذpϖ5Jtgm򝏍(H bEE7?{l ƈk?OTӨ䆪P+vNh|5MEQ4Bs.BXɶ ԲصrW‘ 1~Uݳ4n\hTH{{[6ەuğ{ 9,iCN.49?ϴdR [dq[ܯ(Ѻ( L 5b%A +F(j0d&j %s\TD,HHTI;=1&`4,'M[b4 aBhxDB-.\N|ϦK-Ɣ=x°>D m_lFđSfg [%#7'.-;5*!}S!$d+;gO#꽛|%Hu|λbE'aG=x#)+ҽ>˲iZgш1朿#F$sf(Nԛ4I3ErH9(BN%E#gi=>dLD~\3gEůȆzOOtV6efra<p wbDߣGѠ0)<30%c7.~D.D0a pjPâ$# θ-%`P\ٚ?UQd A뚆$pJ5-ٴs1: X@5k8φIKeĄZ(aѕ}íw` 6au<Pd܉9rTFL=_{uÜ ̕^H]JY=vA1-'IBC*lhXDQ5`F5쎋!Kt5.aػ)~{  @$ҤEQֵ+~]*7썪R7)!@=ǹ Qqve5/{ϝyS=3Xg:LfjJeYp,9ج2% :"L8 v L0!j,#Os 1`p5 cnk5ƚǃ#ZˣVl\=z_<(H$q&9008`f#$5#3Y#FX=]<ɷߑf@ &^z_0[$ znzruy#W>y5OsF U{K¢#3=(;YS找h趚fk\zFJLmEYvY%8p.Jbáu=wvnߴÕ謫,;FCc߮~{9ȩxK!k1?Dl1NmgbA*1uWzu7֩@ B|{g_wc~'%2HtLȿβ^Vl"dY_%{8{=Ƹ,p`걒m/?x-WܾuΛ{oS/̫h?v`_SoYzk9sb>_6fmlT{ !ocY!lִE|AչCJÖG-@0?ŇaΘhR̊ qi` xb&9@@)ƘvY$MhЛC4žajB*N5Y}KcEAޭ'9= 6Ok&JB3;gVel8Gȍ:x銱|f~)}ӣ |NQN>C_,f-{Ԭp.JrS^%c7V7i` [Ƕ^#B__W\՗Bb0Z>Qt>hk/dŜJ͊AŤ< vY|ހb77ocso~ζeK.f@MיNԾ1\u,J~ʱbc`P= hv25pq8譭SFxs~#)1MXÆBd6ͽJ K ;pP[WvF\j Uزm_d3"q8O6g֔|⋏=wӋ>xOWrlЦ;l2Sh-BX S ^GϺѳ4֭;XwqBᣭ+^y̌K.ŕ7>GA#:V79>wF%2YYvdKWWkL{&;0\.!q2?1xן30F~/UL)/Ă/ĈEW0~A3Jde9sG cFuɖo;7t@!wg-V+n#լ '6wG[[NB#!55Δ:xDN"?{zuUG3%nޖt9SYSc˾JܯbK<W\b\qU@KI%)`H!oIjr-'${Ѓ_ 0?s\[-G`opja$f)@@CQ P8fEaM'pʡpE̊=bfTӁ#$eL&!va9TWA8R58ׅkqߓ_= 4tWVzle55+1{݃'mm7( ӨGi ә'C*GiĦ`ǘP&;U@&TSXbmʰHx=&9>Z>.Wn=ʿ`֐,qQf5ǤO>w>73lV8B@ bȝNx޹sLr=8UŽ3%r!?+0 ASF<,^ٽcdʑ 2$UU1ifDo2$.-Plmhj[Κ@\)e)ÖJ=4mjSPuګ_XRs+~f[s*Qc.^OѸ1 PoӇk9qZ&a7&9q&"Q\û3q[<>-<DH'#Zx+6r#ɧ_m0 !&?q+9(Cw^tL BaM"6AoA@0Fb46qgBȷl4@b:]6zyF:~{>9RucgC^ޞwb3;89`|W_UVrjjlBD8cT.v=yj*lQ1qIigO=+5aYkQ$9pe(GqvG,oh6~h;LX$MeH:Θ(\QX2p3<6g<1X 1# =****%"Dc.0JEjyY!-ўG~IӴp8 nwEEERRRccb9IΥ 6b2N#Θd @Ke "[G ݽW_R^-oH.hkP]SMz r+#N}u#9#9!**6>)5ڎպQ.GTIk?ځ&Ncf#-*66!>!Si?2F0KɪO/jAyYT11DC0wᐪ(ů=/וf Vh8\;b_ې,Ή" qOA(C񾴴4:::==;J=B DKL0&G'oּؼw/z g-;58{bҊ-{YSdv}^3L%_>2nh MELPDБpƾ|Ǣk\ [W(d|: IDAT`!uXQQ8,jdo)*L|so@IdNx;OЁҥi ZS\Fq3Ƹ%_MNrQQ?ZF;oQ ٱOwg͙Utow4;h73}˫ԋ/wnٝ=bT6uۯ8RzD#j;&&ćZR#g;d[TC֐% 0N^qTj w0F1e{\!yP8%{}!&a22 ЧaZd!`LDIE%g@!.E0;;͚!B\bI⒒Ą R]~:jms)|-ѕUVbkumZC7tp#*Tf޽\z}\qПwg5U$͆Xͫ^cgUT6$$' \5^?`^fyǞSF2hh4fj6zʘ8y we!\?v#g3a,}>bued&nhC.1iFN^RpDHp) 5[[!'E{:Ad@$"dĔYi_IJ3{ 5 fAlu8VWfv/9 WlfwNX=4E%33;'d& bccePSD!%JI^~t5ulX4wxކꆌQfִc7%Lyy髞|X'ި7jv5XuïY,mSd4Wa0=Vo>I&uSB(5 L7+5_vRb Uq"Ʋ  yұ:f蚫mՇ q &UM7aCٞm 9τfS20?g ?@qP=T:i&;mtSf{}^=P>|mM7IQv`ͽ~*oKЧ:c] nrLj< "Gؼ2&%5נަW^LIg!4s]_<]zӕ>HǢ.J^ G =8ǁ0!1&BFd6V4>F(0&䴻F0RQv\6bMf$fpEE/]3%ل9fPCo,\ψ )!Dp CUk[~x7K[pO>+sFOuZG3{]s ̿{e;g 93DI[j\r߫iS%s7XN)!IҼyVZeZIDOsQ{bl#_`"C{/ұJ,TwmDc8ۋGW,a$̀{G5%ξA V}DM6BTp-iWvo`wȍ-A ^|Fu%!n6`d*I_ZyYszۮC P@EC^ld;n 6(/Qn̂1WefIBz/mӛOyfbSa61y|p p%vr2B&!2B/qE!(PS|RN+J(ѠұoŶaF keL=oNӓL{ESzc3GgzKWn描wzaggRƍ;E5asE'2VQT(Sv:!1!C~:~2L T$|ݙt7j+gY/.ؤNAOUDm*˸ m.[l<L& *^}EEE'$3v<=^?Bq1`XD{q.0&l!w5O1֝OSNreSlw98LYs"+65sp-8Ǣ2I$ġh1Os,Vc[I/Ć!r9k{Cep:e8%ϙ3n`2ɹd2KwfQ0k'MEC'NXpkvI-[7:v~>esDh_hu $n_βDmfOE{lv$HcL$KXeFφ80l$3,Fega70w]jE3\{CȬտgvFg(v-9n|U8\ iNzu78R-n;RMojP-=:V{1BIO!]C/B' . &,q38sXbu?oD2( ^pγ f!M9޹c ew[g|F9h~ `+55*b\hֶs6?K=z%6kJ?LS2,D8P}|bx]MH0||ZjƄ:'O;^30>V7' ꓢBuM_q睽,/#'hI #l)uוZlq9gfKXFD!Ξ͝!J$ Tt?8 #\t}'; tFg!P(b\g3 Ơ3}!@H3@9 FH`@:St;#Y [abD+VIϫR6@@620 "Θd FC~qdqd@ A( 22 &@ Մ2Kfv Q/Y,X t EA`r4ǩ=D9Ǎק#l6tLM;߃f2]'x Y7r18տzչwԕ8QDMw>x~z]%;}w R2G[@g_g\¨3Sm!` ,*UA2)!MpEG:#"W0"`i*`d," ~шH22-o4mbu]EJ'DT  5 2TQjsM&}udV  p<dSExuM705֑u_\v% gܴ ilxPsF9YޞPi#rH7}*cCc͈yί+݊ h9AP2|~ X ""az EEEU[_uTM2Q jX6m_VI89SVV_7[>QPgmΨu sLcs}yd;x7ՀAӴ0+=L?: !P(XUU ^x<~O>x<1ƝSw=Bm[*7ƈ0_W_  n/3a^ʨѐԻ;/8Qo;c!| _}>B!+53}RQڷyΪv0ZǴn*iw>>c1 \=-J1@xZ6[_Ql6s9`QR̊Bl`@"IfCՆMfHc9Q%Q8TskId6&d s9%*1,fŤ@Mʀ2$KF[4ȢH9A\mj`X$DDe908GXl ,T׵1ƌ!X-&ԭ K&(qƉb HVGb7]$ "Rwn^tX/x?rv(oy]:8߮H\V7(ZE;vm]H@ .yO UqU۰=zkj- *)+ݺ~Si,Gm^P]_Z$Vve`?K ~m֤;%|ٲngUO5l_a];D!c2NJwah4w@ѱQQQv=661 h;˂Sۚm "۽m[[8xxihucՇ>[Vl-ˣ!PS3`>=O 28ZSݽʭ*gt85_w`Kn= Wq.@ۻWUt~5kvS-M>iS nܺu[CG:W'?|㯶U ;VʊʪvPyɎG".gx"6;Y/6zim%A4 UWܽǣ@JCl‡(l6K;Q(cb_i;$3f*WG_G@|mMCme!It4ݳXzr*H.CTW͎W|;KW7Cfx`;M6K -sЋ P-!-zJ IZ]^v'<|bߞTP$ؽ}kmGHEO- s&Vuάsً;?rWlZ~h'3r&m \Mvl++Yrmx}kv8Le 0%lPa^ttyw;ݟ =gU? o|2W*ofSQq,i.5h\t_G=2cK5|y=֘RQkoٞ#m^F[=e?CPJvok9!ʁF/BgDā뺮iZ(bJ)&о=K[wt՝_:E4~kAirdfDٵxb54U>vF_W5Ŵb_({P#R߈kڢm-[6!R0S#Oc>{)\Cq6xs1]T߆j{}T_b8j\@Sg]QY ؔ]-nժ92'g]|~=aɟ~V#G1ok7^8ئ)Rm;ˊ:QXۺ {&ssͭ7Xay ,Lgv㵗%6t3 u Ec^\4gD5㤫ļϽxpԂzX`˦isCힸ~gtcGSQ<:2Q\4rcPؕ3[.W7~u0()j숢g?83V+*, &qbF nJAnްke#آg۳'p.s9 ;+fA_s=TV@13/2ZF3BD(Ҧ1hYv[V;iʨ=I.N گTW3M=#xiFL7shNhp0(, ]=7i>ئ0F%R#ϝT,9Ht}xKBk5(?PhU(ڪvUE}b/'#tUM0rم5%ls Ѫ,TBNyѧ]xoW-Y?xqZ(әx*Jj#fvfAҮ;5wjH Q `rZYtz#Ouonl;(q )vYt.DX/i 0ǥ'q@=&O{̼xDU`sN, %:I ̐"Kl%dQL\qÆ a;-k݇_=޶v?CQ*,,䈲3tD'I>C0LUהmS0ȖP롵kqʀ NjvyE7~0o:w5v_mOH w݂DL{{eDe\@p(PW7@2Z6nTU: kT@xWjp@pSw IDAT#"ڷuu'wUVzŭ;_(~#>spoI! L~s!p6ox&mu!]zn?yϙ슶*)6}IC!KsZwvɇEhA!LՔwI $f4(۶mAuJ(0ڷm:MNل:q~oٹa~k VB +q :kl+?[`@ FF&L,0b{l2Umw8nƔ⬗y=`P9'O9â)\xg7'SOLt[lGLλb?^{ًoXG=`͂?/xŭG s<{}Ǭ}z;cG /ھWlG}Ģc/6stV3oưf&o|udW~] QŠI/3crrrccգ#z_U(IبW\+.ʙ;::V_uo}riSlƌ읜G۠^{vqكIIJJNJISW&F#,Ju;rBA/ָ3FMdH2B޾gOM|R,BNTGee}jn_ Db# :5~^ ̉OLNOJHsasFXZA))ɉɽzjm^W^|DG(.#1r=lrY!:윔&am5~OnX8h} , !Êg'ۑ0$?T ؤ'I4QiEC]ir;)6-^i)9XO5͕S4lHvAX<8{jIߋV;=rܯo/WB6Ͳ pAG V!2*%;;3_ U)OFb0?|b^pܢNfe&HkBZccpG 5{fNrY (YR|ced8e780jKꝢb]w.s÷~cbgOFιIJ-уX]z~ńg-1hH9!FI=99PH20!1zo1#3 Xe5)c!1FY"cLEk`(}Cs0S!# 8p{;:Pu]1TUDZZ8_ȊݢB`%ǰ(bdYVCDLש   !F84jZF(fRT mvY0f%GV9,+ yYbȔRfsASFa\$80_ÍHZ[`ANNΨQ$ImOTΩ,>&x?0\6JKKn݊nk! R`D;3s9gTguR!bGq"(eaB0M!q]ny߫ލw$e)6I08'Qƀ9G9gQD1:PLwr,uJ8ҁ~K!ĨE ?]rc3QJΤԈ|30"NxM/D‰ ʒipV6$XT&D D`X$YT"H(",p(H;3|Q亦3EIY1PXJIQ92'q[hvIwF#{;5LA D €0FVPd<>&u9&Fs{!c] }0 D0 x> 9L91pƄ`>O)Z[}ӈX@#5 pɧ$w(߹p(]p9,#q'!,ːa{2&iՎS^um9q"{^RtEEuo^kcO Rmy‰ѻs{p;5?sD0bc[CE;&W,_yu7N˪n&&ƅ:Z=* [C+5hAkw$tep(ͭ휪ͭ^N3:Zu__isӜ!s -O3`oV۷6s#u &}CL-sBnw9D ,t3#Y]=>zWfR[12=鿈5.Կ Q/Q܍.Qx, ~ݷq5_,H*Jh۹]_?/{ ˇLϽ<= !9,5qԸ4G ;[_We[{ 0`0&!lzd$&$NBil-w[nmo9?$ƴPl}ߛ;3o޽̜9?x護>_xemQb S )53t○I]z4@_^t:~* ds~qmKG *Kh;/]l;ifF7ʖc]tx~cgg_qq>klI'[) gy)#jҶ-v_ 3{U` !>U #HpE-s\o G%v\~ "ƃB8,`IHMYc{_R 1lOl$}fnXEhj>hh݊9x(9U'b@?). ֵ< Q8d5w3c{{*uyovO1[VU ';L؜nT?6y/N81HD"۶?)Ƿ]A%K,OnO+磟JzK/>^׉lpH e<]zuG{LHa' λj0$zUqo|({F9gLYx~د%6gLk$ǁ#z " D8`$e?&m %@裆#쒲~Eϙ7,/}~P뾃nwm7?iRMxߡk}ݽDBdggvmp[V4(HOD۶gϞ`0Ե$Duu&L4͋_dHȀ$a'\$ DaxS"ɐ O[C IQRJo1bVp!d3udP{4 2cd| njA*# &]H cRb"t3~0ͧ@x4(ɕ\^ Cf[k2I-NeVx%Wn m3kÖ`ߌ½|hؾ𞐪?Lr+%%%Lw4^xa\Ώڍ#ÕKO)SDŽ}z"uI po-[ C&ɧ/6gZ-qď$')Dl[O֥ɶ "w9j VD?RH^!+-Y69gWn??{x _HI?^z \zug}>2wH. OS@!U]RWݴB4GW.~Voo2uy3gWˍ{(O2|ذu7>=n_Tf,uҜ%s?F_0xSU\)%r9K.zܿ8Rzɩ#|+c`LUIT$e7~nK}oIU5ҎF[D.OTW4cHEw~=hNVyM1~ӯZ(qыҥK(eYź}d4 @Z{| =PDjGU{mO 3{@d 1yљg@}9c Z+~~`NCk^ﰊ.w<_Ι=7vSY`yf𲋔]{E ?5y'~`I2޾>&`,0bf /}hr7t j g{t-FvC%;%`~%[:FZ֬yԛ/>m_.}m?#yA "Yv*n=CHH"J+ 24S}(2ie\ }HҲU@Gd;KXy>SERJH2IIgb"f1tIYt d %"Ŷ-d3^I)#@ď;=Cr%t\$ιQo^sÈGr~ES<-4.7Ȟl¶z§ ѶÇ۶M#|u<)q|8S0( K< nknp]wgWXXx\HR!-,,-)ɴ ߯"|йPyCޑ6̭{뵪:^8d!>c&ׅDp6C<@J )*Q1)bSОH>L־kS]pؔ!sׯЦtPEF*Բ#{& R2hyIK'5bO+"JHqo,j gt%*wm=ڊ=i5Xë6/B8>3iޘT{s=U~eE֎O<.z3IS330c C#0_01$iT4spSw)=qݗ',[4S dP)ILФcelihqIU1 ֣$8| `Kd"iWH&YcW퇮~@=!Ѱ@DfttWiS@(H-#:D$8 =,[BJ8N/$IksTr 5LmЀ.MBEJ)=Gϭ )^sV<"њX1Г{fxWɴ*6}^N9coa~Ͽxc}c ?4lݴ}꼹Y܈:>DE3sϵcR}k~X+hӎaEj?ӑYAؒQADF 0PTQKon/=z۞YC}e㘙lcNge~'޾xEή{Y:r $4v}x҉|e{[E?g7 +g@#%_3a%$m!X[zwֻ;ڢ7m iFOuj]pi"vF*YGy`4~풓o =ZǓ #IdOȓYDI'2֩IE` kȶ{!StQ:mqiF@S9IǓTV>_ߜ c! WTƘiG*b9"I? 6?B=`(0G2FDf͛Nr?E=h?yi  __,HW}%3CYgD۵@Tu0 2xn/Y};elIZiԲLg]X 6>Sf0bO<z}F@ R޿~ZjBCvDwvr55S'R-YOY~ֿ?Vwrg;'ԴpT0 JśzdSׇIUU<Cq);~AJA= 3@)2μS!3HBHIJJ)3PIWF '=B^ })q >:pjZDp,ڵ>55$E(!TuѴ}u۬hȴ3 3N$34ŵd&_\SIG d2@mcM-^p'I|F;m {™WN?stw) @w˲)BQzUq@`$2}/ sxSz(T 3b#a">hL_+iBlPw뾬6tБ/~ݿmVK I3 c7J "ʭŹYqzVQFv|[텎AAŔw<ϸ''ssdHR/T5wdطֺZ2!4tʐw$1jܿ/!HޮI[dCpcn?ӛ/wLJ%;>ͽZTڶm"Bdn&<4X<&іy9v2uݵ,Ѵ#Cl"[ڴ@V@dF f,7dn[L00і䅓b Uqt[[܈?Z|ݶp=''G#6|>NnG+/|rE}k)3%O9xUGJx IDAT~At,-hْBWgv g'V-w^ݰ)/{A ^4/\RZ:m[[׼˧OڎW6#=RG|m4D,tд9gL')oܽPc5/q%>x e1ϻg_xc&ܑ%SVvM(愳./.U|0in޼JStJ7F _owc VliԂtG,>M幆cpIyIk2(?!Y$/)+ݐG\[E">JDOqGfѬ}ՇI gK+͊q =HS՚(-qqiz` QӴd2;um۶/N(}G0t7&q;@3C]knߗ?^.Qq wւ1mZuɲӦ=SKG^|ܻ[[ϻxkO03GYI  <{3צ76ZTځL8Qnqy O2b=rN>y斺$G=jdڲ}>#J>qK2CcEZ:8Ē$I5A~Jft_Hmi8V <;::p1썯H%sq2N TZ(a)")~'Ll_ )hgR UD %38ҶeS7Cp$\UU qUM(3) K\r ]P8 ^ ],L4 q*LJ㚮t,[ttmALSuk4qF\I ld'U澽ؤ+[0kl6WY>ٶiZ H$++++++ 䧎S:B^W 96C L>}Ҽl&R-mQ߻eNO싿uVFmUn0oe>p>mt}cѮbν[C%R㘎[~¸3LUfl3ǻ@ VDU'HZ4H{z)ō(<ҪWz&IH B g^ " p)x[Z ȤJxަiLcL83:DHēGܣý cOo܋j=%Xyo}ɺ})h vx.@v^UGդ 82M"c=Y9H` tr0tN _`7a=`w2)@k3$Ʉ3hڒ42λHڕ~ʁ3Udn fHv:ir΅P8d*mx氙IҙΫ\{LZ9sE"gL(HPNqN4Oho+h:{w?"NeD"aM75Kf,;$1EUnY(.G@{1:DͧtjꪕN9 Pd2iW~UȐ\P"u{ ]F:[ ѳn KERtIН)ѣ\pr;ǤkQu!S888v!+|wZYafO/N% $$@ T޺Pr;݃n&*o/dGԲxG ̽7߽%DTܳƹ I hMXߟn۽ Z'"sh߮}@Q<z}qJ`ZTw8/( <,Q [ʯ-/R$I& Ihԡ;yzXNǗлZd`D)3M`h~/. r=Fڲ->k~oߺ/э7UWWn<`rYwmޑ\@@ }>_W}VtkVg\nb<%{/H8u^;~sΚԄ:yM-]zYDRrYs^ho?Q|@Eg⡕)[fֽpr&"EGwkw*lxkZR3^kp +g\Æ=_PgߩTh+f,rx}G?}]/}K0+̶.Pj}w1_P0z_}5{L'8Bͮ;Rq޼yɍÊ$!ZU/]F̿{J'+ ڛxŵ^Ëf[HgSlzr:vf@Ev/oiMZ򗿾[~?xm2F-c/zt-r%9LшyhwuƎѲ{Z/7n}6g>~9AQͅkkZuURj޸8-PS ]7xM?;ܱvՎÙ+# #aᓾ~@æW\rwzgk&T٧^ڲqwdb¼ts)dlT[w7mgLIg>X=[;*fӆ>v5x}~Mw\L2Tߐv!fRg[ו)dawWf˔~wkS:oڈmR L!6#O`W'8K0;?־vz(f/RRVZXt{[KSKvx6/ K asWUc1_gN sZy*D꧟ZeS8˽O;d@$Dnn[x GTɉqNt$HeS 麶i۞PRa8֑}3':7 }>_4ݿӳ |G.a,N}S(Pgu(6;W}{md0+{QԄ3OɽE'$b Mdj(mNoIẠ0 !CPwI :ռ,q3TG.BFZ;rF \anA$HeI+x9 J7 !Ҟ"99±mA k aBD( '\iKIdj0y^tf3 !RB$f τNqY>&"IN0 /1@:ӂAL<"R4#/LӖR_v[ױ3RXzV?QwЎd9q×gC!SPT]SGRK=6Kd(..N>!K"" /u]uHވ`RJ/..4Utt3FiHpU5c:ah|*sΎwiJxqHJh{8\ض%@14M#BHBiAN*hFR145u BhOZv(2M͵뀩$vj[xWzg BD͵{ߟtmux=lI=e|q^|l!c@lߴ)jzPM7رÒ$߻o_ukRUٜ5Eq]jSպ뮿eӁ鱆[vVuy)i}-mqOd+zV̉;P9KFgv3hٴiiK˵C+}%0u%S\{/<;iK[ݿkˁ}rM![|ؼnsx}uO+n~w4#_ g%?앜;mikZ Ϲk}|`.UVϬ/.Ŷq۸.o~U?6[>뜉u18?Nfu:KfNYd>zd—.쵎iqE@ d  CD {yV赎zzY~!/xq܈Q Ox vXkSs:ia_<#y(vK|iP׽bxC$\/38{,"ƈ$S!tTs-SͯK F[ygy / flǵAģ?lZZuLRq$]#%\TmiB@Kfr_򲑹Y6[S:'8d8C$d2xGq`G_^D=w<{<KF =܋>$kl*.+ɏ-[jqq"Q"$l(uRc DƘ< 9D1BClB"c^R",H"p+Hҋ)~FRI9Bc7*:A!)1NR@1 1DbunmfёdNq2v*ޑtr=&WBrjkQv^ux[sfV;1rXggS#faNVбl{:/7 E[یp;-WLt#igj9Xs["65޳d< %#1n]$dL, S杭g0@L h'7k 82:*w3>7 w '9=֪76wJ 8QQMΏH)m2w9zoDucrEN}455E"u Iޞ2Fc9Y$@8W7tto}-~#re߷@{4~TYLZM IDAT|m_!DpYOoySa>p#uv,}/wY3X<(/y!{H匛Tg,6t߿ͬtҠH }gC[~(}׽?XT ˁ<韺ou}vN7<ӯ4W;6TrywָM7lLyD+Z[|sgS=v_Ya뙤ڨ۷(2X:~9(k=9 c(R_BA?`n8LQu]%JI\ M%BAy#A"Ẁ%T8J $3XG;0Uu]SII:$*g9 )ݪRj9t%DC:ƞ>Fg=è(JCctaWRZЊf2h4ڷo_47MSuS>? z3:> !s"E)ˮoj@oF{Z^tNmc~3eqWS/suÞfR0yw0_ؐ~o۷soTX"|%x9켺7hv >vҴY+"S\1mD|W[cco .0 7IlZ9tC71پ{C|zC&M0ecqǚWWu9Kxe+ups(;zs̘@,zZpg5M]:;u3mڵ=-4MKEoPfoUKTœqx襷:2K9V*r8C[޼mj:}޸ 5{v$\"->VҖ}\/z~f@pe)} mj9tQd0[VLXy?TWM/(*hi^N! g7s] "5U4ցk^X5dlADJd)- GL3J)()ku55iRMMz H.$[7C?r"MU`VqGJ0`&19}٦rgMchR"y} i@P}& J_'_zw8sc+Pe&Ko^6szi4q&-1hm-o.g'=>돿[V?v5\=G~L B+W=cv5v`vݪ۸bsMg^:V~; kJt z= 74E~BEqUf'ܳ" j"hms&N?mLt- Rvg uTŋvA"bfBe& o1CsKr rC Kڌo Ι?"ҒRgsJ Nshז WABZI?y]eg͊%!CSWWsxĔfGrK sduSrR@ D6mcM]xY80zDEI2SdzL2EUk^c#+9_8\URvhDuy}p3*sJJB `ͫk|[\zIkkkmZoDc2oSkՕk=`.7JO?߇NqH^)k?kt^-u{3Ɏ/yc=SY[UQT(sf'4M\tNT3?:6h>}#u\¼)$+,p A1Hn u]4X$'RLX|Y_ܢA8;HHy [G)%犢(v@usg1IHB@,df2s;sGeLӴINzj>>O9$^- a@rI;+pώ7\ռ`ncmv`-.M}@,Y{ xAQJsr4eS{UT2n#գ@m#i흕qyE jr)GH]4MhXg}x#M% "awI(͉owO #i"ewéc~ׇx +(>0ݕDT-/c[o=r3J/Y&0bMw5FtSݹ}*4B=[+{s٪n"g 9Y|Uqfw7nhع<╫f=쎆0cRu "f F: ٱ!d&e%B$ ԗWq{ "*Xq {29YiOy9@r7l4\2,]Y)X͚yr-`u~3*+:2|O<[eBq@e6NOC[<ܼ= V[fsqCǧdm˗oؾ˨lKurH5|tyVb#39Ͻ³ 74g߬-t翏,?4~v?jU<ܴ srs矫FKW*Jr+78i(X\~e~dKudPk\#9KF] \p-UgH2rآ7nZ{%#W\S?|=w >a <'b=GOjM[?dv\}UUu:- C.J(\SSRSS !N8p3cܙjn`CsJ)CBZ08 "-"LuJ)&)B7#9sB!3sƘs$ᜇB![XX|!C9R\rK[p;mLJ, @HS^R9oI瀀ٶ]\\ }"Sg61qK<!TئƉKa`X0+Ƹ9G3UJ2"<4Ʊf.0z=F!L&]. B84s!|4B&Gz%9yX͸Dp{m9n?jaa5eMw 3 #eΡS\2ㄠby?j1ƭ?((ٌqIeJ)NdL p @zժO?sO)[U%COl-%{rE6X5oGu+8.ߣdnBcD~vvsY҉UJrhmX;{jQʄzw3,rIiQ)1f}2 e1FmeYB0L!-M|Ka9ZNF^jB  r}p;:92'+~7'S3^[8BC7 ezbɖXF.d3_45mSHEŞP$>&@,T]QCdB5z|k*,h,l#r;Ӧjs d#w1fi"uG ],+V(Yl5j Îm۾T/2#e~sk j.I>(`5M m߶=~UW~[Zue|DcMEiO+I4l&ʤlǎ*I’ܱmkI鞚*Kj,7k<͖rYUw$]bw!!!7uHլzW_vߨQ<7i-9ԫ`W܁X }Z}>7^}4O5̻?I|4ޭ_M`x2=')~{^+.WOaN "RĖnrSqnzoU8{E2'~3`cg!sh4ZTTF׮]{'BBQ\ZC񮯗.iȟ8*y/䎙_rmȰk4w5kWnѽwo mU]9QdP$}8犢ڵ+L;6 [He%PxowEC?I.Uq͋oy˶' j<kīK+9?M>fuW\lܻnl\0w]>w/sO=>Α$2&JߙT8|o@ A9eYeg$IY`,_cHc wQ}rCL}榓W‰[6y_ZxCT垿v y'lӎ;~+(۶dYfK{~5sZ/->W.6[cu/{|v9~[xƲjr[3#Nae]^D9wi 7Om:U[PP0|EQvQXXHPU>~Iu;>~>ݬ;w^J%>xg~s\o>s7oPN~Gt^;L]>6/^z-\PUղ !2Ɛ<瀞ΌTW$zj2Ay۞EgTẉ7[1"V=|d~7U|?jdϧ.k6˵ᄉ?=_-ǎ'ԉ*Q 8 =!Irss,:A@2pPP(89匏ּCQWڴ Z6ǜsUUON4i<EфnR#֐LdpdvY~23sJ> Lq߱9yȰ#ӑWI=wb5sݧrSO=5z1c) CukxC YIˢD*œk/CyC_p_nWB?(CO)Qx$mo)_6i'OKKHPGKzK,Y|y0L&7p,Wo]__zwW+["@7~{9_:}Tf[McH&Y,=aڨl:ݳ`P1n۶xi{W_emEҲmMs7ZƧ&c)HFSF,e#F !Xl^{͛-[6i$J)` ð˴fk'>`Vw]w|:zrd[9 zIHh`~x9o7My~Y3 ,\\8ٲU+H!_q(buMdʓh1{8԰zAY;+7]D3'U@p>܏>+ κc;sN),+JDvؑ_WW(EEEH$nmQ4?O䯊nӆ9 Gv{E ?`i#mQ-jC5յX?i`ṗl6`l$u)]7䘙`ɤ$I.Ku $1J9iR$Q<~*h\7e3+Od,S1z,%[ ĬH$j3poP*"(b,555$Qjs_%`vJOpI8Q %ʹ@)5qJ\Ej#xvAO}jOwGdC[]^\nbhфnϝBIU*++vA1بiY]bŲelvݺN4)===777;;; |>:YuzcX*mf*!:9˜` UBajjo>+02P(@H:%$0X;q$Z"aX! 1!D !io)J3CbIԺHD1 (#!@` "0&Y5 d"AqfےRL&)疩 m 9{1"`Sp)TĄ ;XS<Q^s|ċg5Rɰ`&%&yL'Q tb˲;nOr@q˖eysD!lck,QY:v,;38'}ͭ:GZh~@˭p-~{1̲,i.N~oqhm#TUmIo;vu%LT,{8NO+M}iiT1t[u;>UW^%%%MMMG:MGmۙptRs_; pNuWXFFF֎sS@mWJn1wq-Nc51M&`@iXq ,Iد:"3ugү:b|;@q0iFIRUpe6Lt9Cy~ 8}8#m.Pt?,H05[C׏uZc5c< @pH0~X5@oAeI:lsUˣ:> z[tj}`o`7Ǫ8粬p ݃8tR 0Hd0ge` c¨MCۅ0FPT i:;"mۄNZd3M^EVl+Ep,2>3%8cs%*v-:$[[(N;ڔ!0&8cGm!s,0α$Sba ,g,fb͏s(K*(V%q;SVb2jMHS(EKsc8QJi5٩dR7۲2"!mHB#P]ZQ" \f=6%$)o 8w}֓$園 u}g4s@H&F25I%iܥ 3u1V8LD̢\Vj9EUS򲲸ah$Զmj;ܶ8 Q]EiEÚz+((Oa#@ J`ٔK__-߳?bϟ㶞}oE:nt]N{5N-˦eVs6M:yQK$=# iUX6ͼdpq}|C.܇K^xKW xZGg-_B}ܞ$EFŜ[lI+:~mONљ\V'x{̌| 2c~a6>h@.R4ޱF7jǻ?ږ~֩<[@R/}q\U-{Y˗}yD3RLr+ּSOZ,,y;W7e4eQ|^er=\mId=`BD}UiEcf!Dz(tA0F4e;"[ Q>:I=c;?O^k̳.rH^~a jee d}'ϷLEzISG(ݙewb)=ދNn}~cK]5ظ3rc3GϿ5[6/2S7Ez ?6:sV}8Evm[ fd]۪+tC)/'%9{đb{!fڽw~֬{XxR{~Dґ즍ğʩ/>|;0?_~3G>7]增Sf {-o/ʉ4 K역Y\WpegoGoyFcUpx9p"!a3՝w/]n'|aUU!Y_*wI&DO?o$MP}M֯T_UQecxI]7ah8`+y'sa yʥ7|pAV06W!\uMy=|>yOn?$;eqbƚ޽=^4%rzqiCl,[zw3g$+7TwhכwVQQ^O5|L9UV0d7欭N'okFGb.O8i=>=-]eժU[8ܕ_.S3繥`0mSOpMID)}M{@QMr uDc$5P<ᔳGMۺk3KGOuy2A+J774FkK2_|ӧēo/T-'+Vo2v뺞0R(ag8m#2-/w'R#мL?om|@q [׬0q fݪ; )cpm%G~\6r`JKO;hO;‹xkc@pnjR(/+@ ";︵zYs^iA.IݽaKgl6WWV4œ֩>9iK8USB  Bf`g=tP?/*KkB`jv"4ϝr{>7G0hoTU,1v n{@ƀdJn턛:;BٙőhUEmmڜJ(=fmBg+ׯ(8nLU0lxo# yeW]eICCMM]FLČ2nQ3aI9>[/ܵu;3jw r)T1l[PUޠKs} R?+ GfG $eɌn%{; k)Q 80ʴ@Zw_4g<`uou.ٗ1th+~Z0{o =eeg215lTڿ펲_?oj1q.\"๯w5xhE7Ѳ~E}s}շ6ymy7yOzb :䌟 EiR,FO1w k?TRIӯ+t{'I2Ϻxz2=2|1=4"#>^j=?f|9ȥo{ULNuKMk'4WoY8«J{o:AԌsF}◳f^N|=T,v4@D-g+ndZ{;&H$w4i͛{Jēr#Nq+IwfvP -. P/dEBaOZǭI59!-ɝpLRBT,,I2"0R9YD4$E"Yf4t] @ZJ4,++GZ&GD"*MZO.s[o#Jm\df(GǓb)HPLê',KJH%HumYXUM#m#cWZVF1Zx$e`籒ƈ'-R9g*%-۫iѣfj<7]MvTn[$ {<^0JKKO8ᄌܬ,'i]0>T oa7 d✙zn"dEԲgP3I+GOFmj6@0+QD˧Ԍ-٥c61853g4Ź4O@ҔG'3)=lOw1Ό$;u mO cFmD$ 'ZJaN- LF)pEi&B 4Hvg`ǭ5H"K nF`ۖL y)<Pݰ9ܶGQ\,?2S&y yrۨ] w:v3=^.`_Pߨ]|.-q '9$+.g0#usN$Ehv&fك'Z'X`ʲ\}%̒WpT'ᶺ$hYk ?$) 9CH7?Twe\ZUts")Sˡ# \%&F٥4FnvqD(v%l߱E3F5m㷡%1@sQmNt凣 jy&8pyZ'}>_4ȭm]oάBEb1YӃv>Խ<n$JUJe,BwrC3}&d:FBm6F@m9ܓW%>okP΁;Gc1}οC|ǦVS tB훂mX, 4Ms$8ޙU1!ciZ XvI'e˖Lg7˜I=8Bg饵6pB[(G:B=()YNŷj;ٲ(A:`&.8f^58祥-4hP PUUUUG5$Ib9H$Ih O<ĕ+W.^clD/EGa}۷nu>.rכbmYm8r^oFFɓbi9;"MAcv5M4---:*"Irxh;QGc6M|>4)fy@ <2v}>_ zҴ*qPUUGB$n]-jS  GUUM<uQis8ӧUU 0MӶm.G 9b36q;@)cg>;VuJ MYEqQc `, Զm۶: Aa͎]lm rtDU@НqЙ_tࠋz|S9oOAcG@ ޴pp(?MNVIENDB`treesheets-1.0.2/TS/docs/images/screenshots/screenshot_todo_linux.png000077500000000000000000006200151352107072600261110ustar00rootroot00000000000000PNG  IHDRyܬ pHYs  ~ IDATxw|\zo@(bEDE" " aÂ((U,AEERI/Wvw~\w{= Or3͛y3s1q`!d1yw6$xm<g<t0pLH_zi+_t]&rmוI{CWf),28qY^fP>܏Kq@y% \fM-WWyfn7dʈwn$"W-o6]c,.*r0ŔZB.rE>J% #d ezn<ŝA{Չ?0-pqBCNa b:X .m%ֹʽA9r8 d3Q[se{ )y%s-^mX"oe w~)XtLCpى>rB}6[;p}0B#7dxCu'ҍQVWcBMs>*]K(wkUxw? jh%xׇ6rO_|-p$lH}Ů\"7A.Z0`DҞo e Jcv`MC𿵼qyVv=kZMb %y{hk`ZZ`0gMn)!lWg/tG\{#<I<{ke'e'>4[la=qznony- x{4K]s].\)o ?<";} 4B^s&\6NɨY^xD7OW]rJֻ '=ԃq+o\X-D4:vjn8g9 hY=ZgP/Z<ؕ'_tcٶՈ4M+UcQv^+ y2$`&3![gs[qw[[GlhSLqa /=ɜW}^;Tz]TCrR܄ܨϷSnusbAHrTZObp75Nδjv'Ya?#=IHkm@} RXBnˬ+I|~C뱰ނׯcJ 9m߆N̺뉿8O {!v\PG?a,& ZOic8^m\I*8OlkJJGtC%p4 [9'^\b9f-qω'lO{3bĝ5!acشā tY"HQ^!ckegH<<&nGIQy.lבs v|c;®i>G:E- $ܣ䕆9V#:=]ezجIG~Y:{`{x ÛR-9J*$/ѐb3xR\\FDb#T"W1Sr/en֖Gr8<`mb@йq90֡`-yu|̖ 7u%-)ئKg˯mcA{X1lB( !ҟAph^lIvh@z5.Ruozz[Sl-R^1v:CjEz@ޭ%XtOȡv]$0h{Զ~fiJbJh Ry+W1e[SФ$MV0µ䠬 ?@f" H+ցlJaƫn1{w6[nmkKV{qkIM\ҏ}LՆd_h25;qo<}@%`z)d@ @h83O|tD?΀%EX_&xKCtӨ^O @ Me2H}b Jw3Zu)r>WE @ 9OnfTQQ5&y5 _PL/|-# WG@hl-]x](xݵ|cBf!d}C`4)U=Zd M}dsA '9q$*`*RΚ?c>]Fv#\` &q,c\8Gu:+E؎IvV*,((te?*X$ 7nO*cl1P^mP_{aJVfA^ݻj*kI͆#G_"ZsnmA @Ϳ3+k8~xY^sS#n@3,_|kN &H^B*ot%fZ<$tAi5f[G[' tЛ.Y1gklqYjЊip eyj}7T`K黗,[ԗ<ҥM.~J.s!*OqK8=Ƃ=k-\)NV. y?; wmX: :Jt#|gKVyꇏz]svתu8F\:`Rc/_|c(䭝/z̰ԖbmJdzD2c'>Yy - Oin{ZƵ1k7blMUsu ⢚Hl{~nTs8@ >:lE#g '*z>Am,gX݊? Tv|񍡐ӧF7A)(ncx K1I؞D@9[EǷ-d3Irk;'?)TE]4EȪ[X֟.s»?5gt!)J}w켻|i01(p}`ZNB3c Z1Ƃ%dd:uHuT%651erQ 2i5/&LfN zWLIZUV`hn5x^@!:VP,K̙TNh nKwނ-{p}:ګgVCE |u٣x`V;--+n~sg-2j[ժ=o<]si/̲pG>3̞g}:[PQ}_]6ի²Ә:,Yho%${k*7> T@Zi 訁3|lSDrœ%Ij45֪XWƺHݹ*ũh\Y}%×҉0,)dCT<@oL*8^lU)*NޅsNèvLС5=s,e2n!E=0XV.:U5Sz.g^4 :F.9ь-#lkB[jU{!NqIFd-}mFv APTU'%sϿf@kg]?~ڶ`|7KRʠ7Od>oC{u=/ |ͷM߭lW?3aC= ˳L&*0M1ڃBvU_sSTTΒGK`\}z $囬ݱ%S<*'w }\sCyYVPȶPwԭtW&덮ͽm 7}-^l$ U*)+VQkl>}!0V:T9m̾u-RJj".q} S^ L|61pSo(Si>d[3N_qbn,+ᗇ=;:.(LEXcR'n҄ҕ4[Z#Wtڱϣ_fSƺΕnqZHlD.}Jz=?Pg/ѱ~%#Fs<.uQUTqrٹ|jѓ69Ѭcj=c[]pROq89QQg.>SZF\Sr>'ə2ZF3uἂK+9xbvƒ5ѭW4@㿥?^wk|D}DpKEo:[ ?3njy{K`UFft uQt%9w;/VQ,ws)kN1f۳k3 kͺsѓ/9Eκ6kV2 MӲw 8v[ty?SS˿x*T C!}%,ǂ&5TwϣGG$L%`1@_Qyw^Vq3zmfHeYa@C&^ÔNǣJ(a꿥-8<7TA%U6%PKU7 ?0~\5La=#ee3tD{{C刺ưNa̛)( N+kQwfY[]qiU.CׄZ2bQ' jՇ²Ә"{\eڅY9tM'Mw }Aҵ,[+ؿFZu 8*}r `MϓV#a}ʩ~r k9zֻk].JB1h=5f[޺nR-?ĺ"'91BACT X[`9鴆UTѳ]^ٮe(gQbXzN]͸+1E ;9\8\5ѫEjSnI =-B* t9|kl, 0o6rj,Mw2,`J Y 7WV8ڰla9j`5I\e9JUҔaFVkְa qmFVu 5ê4* ^dh,&ш$WmҹPwyj|!ZRU%6yd=5f(>.NY'l;/y*@h*y m8l/2qۓn\9C{U~3Bgʋ7`<&Y4cBZ!)eUP}x!txUQvic6).Q8ęws{?x2ZGM\yi&.iD!õBYڑ|mr4E)9+ZZ{ݜUMA,=z6%U]Q ^#X1[m"ǪJ)9H^vFA7&í=CO : -шQ~Qu-Pv:IiS$FDRZ;O4K<( p\U. bXZP0 (SҴ!-E$ 6&tog3^DuSu;eIpkQ Y۠|3u&3W;`ּGDV:05{C5uOWfY $ 풅AEYPqḜ13NAʕYwG@:>BZm]P~:I2⧼P~:D@)]S2lE&9E^ޏ`վ~ʌSEM&tPBFY[~wR7ONzi%Wj8e7<%nPV]4)Ffs'?;1 |ETE'3gNgvNIv)Ryk$Qݽ+H46*01iغ@h JDBjO N^'-+v1H{vd$d76gYޙL$ IDATw,|W~^6qg.㨮YLy:d+??1fn̂37xsGeeRh:k]|1w˔+P->U{FJ)WXNs29WkZ)XsSZk̮#E/^nɀBo7v ֥>qפ|.,."yFXq2[ _vL)O:ucBes԰S ǫyJR"T4!(П#dSKcsJ: RQY߼*KhuNI iq-kT^y1ΓZa>I, 59yE  `4_-Ԫ, Er\Lݴ5|7ǽp˙BuJ}z(.狷eW*-w?i -QӶ/}jk+*rHG, {Nr}Pxm;@Zuxnuo+رJY x+bTQРCd*$%"W~PbԆc{׻k V <#B, .;뎗[ƔNjgo.`U6uMvPJ4c5|%O$㡣Nivs%~qƗ+Sм am-mF/\5a=Y|6U[^yܟ;ƒߛSqUg`[j}â}蜬ɗ=>w'b+ûr֘\xʅ.{q}yEߞa'?R,LގeЎ"{}3>rL%EwJ/)8y5gơ}1+dra8Bt(HSyE+Sn6\Zy UX6JW7hbzMZA7%q QLt8| :NNH:3#v[PhN5>44!\ rΖ @#Y)q?~4Œh5$)J.4|MLjVkV gm@r *΋4/]wy*GQUv.,. EYd6%n%%S`ɗ&긑+|vT~T< (TeqVRe__iVW8@|Lwu(`n L9JƜ &:82IV.TQY\Yi1-k0(t!A1JJVk*+Tf Uu1z@NyѣGKT.hڔ-[̜tFO3F`EGh}h|xPEZv{Fڳv&B^iUYeuYUC(KZMN#g.Q-Wo" MRP*.n EdںRFEk[f5ڠDf;JVIS%6 o_RѨY}B*.:B Bڇhf\{RDcdyQVb\va|o2,BrZ J`A5G^!GQbEtd^QLsA+p#ؘR Z!r fI>d{Qd1bt*VfN0k؈0a(JY~!JYց)&88Ѣݐa’֘T. X}H2,7a0 hռ1ePεR 8p55 %(:0 W#iE롕ŨvKf$j}75|}O<0195\ԫUs\񩃗IZf &Е^-|DE}*仃H  Qg#[AߤH+"teٚ7/\{BT(:QUPQ- T/W[+B@jr}{)Gh bBV6;(Elx! !VФ@ ghw G-Fr e^x,~p@ DSJZb5B`zi,2΋by~%@ k:m{܌3c @ pM*|b3w8kƈT s ohk-`jP_ @<ͫ/.ޖUvorKKm9ymFF[ʾmm)W=d-)BP厗͋_vzgP7θ峵hbϓw}= 箟dG| ЦMnI/|gu.Z:_>OW3n/'=Q'PqvՇ-B)f3c7F+,J톧^RǢ@lH7m9Yo9OglzVjcZHBrOYN%{og#Kf\SpS.2t*7Es׾+c?4Kv1|gfo}buXsؗVkMOc9#qsTxk@ S[> c Y(;o~bXٛ xw{(_dw6^pOV¶~#ҺHl3jyόO~ٰz)S3ީWB {a@ -®:PvmDka%{ۿv]q5E320d\}Je.Mz?WGΚPWn~{^jSG,b$t&BdFLo66qg4$tk[(I孷_[edCS闌>!lLM׵R kD{7_4N ~ܘrm)j | E ,nENg\>gMyc%Amu RA)}o{x+Vֻt)ŶNiնE|Kŝlld!t3L9#aID& 9Ar GVYC`{cg:bci̙?\˽t ~d8v ϝLv@r(`c֦?e9&.=x,t/Lo{]JO;.8R:ktxxב"-NWKmtĠC٭];-;?܁-kru[?dl۲ ,O iT;sI(ELמ]zG+m5 `˜>/01#_zcƲ^X/\8ת_KqsOw]i>'3haF=#[ ID{?}ž:tyPOPFna;s#hߌy9٘{Tyo ;?[^68}z)ZkWߝg%9ArhC_C(ZPv>I؂>U%2CaOh:a &O;z^8/=[1G}h 8^?}cCW)#]-w[*@'>qw,@2kTo8>c׆t#_ `[e O?{F`SG<˴k0wΩۇ"lqQ:uZNRbMWvf|F%_G/bϮ.A=3[E'_CRǼ5 q}fRT7>᧟>`(z mYfi(EyبN#^}$OFh]rAo}_p)=>'{FNExd?u)e-D\eKI@ 'f1ITsIS}ťbw]8mi,>@ JSČ7yUe;0@xXou CƤ Yk\yv 筺nHR|@ e\t$>1n/B )CЀxM׿?fuR<<)>@ ixP/(|iJcKQ }Wn|4#@ڭ"#@ eL @ 7x="@ wH!Uanv}C @ Lb~ !$n%3U@ @[@ @!@ @ ٽ1(J #{c@ kOa W!:@ @ 4'bIk!0P%:)G@>kyŻzsT!v9mc@eUߒ;ϾH2}O6;H~qC{Ӹ4ϻr{&D7,Hxd7RLRF96 D{Ctx534[k {4u|&i$˜.~FgU%7wƶ<`GAr3(wyJUB IDAT5BbQ(`JxHW~o,_"W/ g~W̢'JNA": ( 9Tsx[ДxCełN%/tb0O=_ZQJ+X2H4NOK6>*3獁?pwl{]ƒ)VgBl4#<)UdrF)M[5|bRz f,ߋ(D DѩFV kH{cE"rkwc}~9Qx٪ީ>b>$Z)}_|Qm^-<(y 4)yӡߋZX׵~8=ޥ}D+9hA[ 0hR3jDP?⁓KXoV̸[`u ~oR?,ߐX o\ b:+ɯdj.{}M2\Z)8fsas:wmt TR\'!u72G/:{WZѥ /=sT)&6[Al_c,>L0֫=/ϡH:sS$}ˑ? !EhRS6NK|"o2qե A QE&= D ϵz"k5*=; 4hYh= ii8N<`y*׫A##*L!qkH^e)qpդ7;U}0 #;ʅw?00uYx]_6q|.~*QSt5-EO+[%$\׷?]\xVxHLlĉ 5&3a c1<`3LS˲7^dxW% 5;M\{n۪ޞ6LOgbl?db,^)4wtNѡXS4iCy Dj=-A' ,7/%W'p{K.i\9ebPjBchNsxxy04(&<ՓU((C%T1! TcN-pRʰrOsjIXI1ѽ ie8|gfFj44M_Z 1eiA\jcTBf3o]ţSG4b s!JYASm2պ8U\ȝCbx GZn Gʒw{hO.U hʖMC|r_;v,Ze4Ų^/Drmt{6vȸO^]t]| KyMIcٺ,4$[1$SF@RP []Bid8C0E  fhc}5*1!RiA]tWkP0 ocOo+'XێUHxGka(6@eU@#*8 aO<3ı+6o=^>O':IbJ_8mrW>~[4[}%Ok ۺ 1jxtvŋ]5K/ǯ^vgw+h-Iqߖ1\X%ڌ6njS=ֽ3J Y kSkzh핋0ⅷ|uREPxv6FCP:$Zl*w=)[fwjVz-c,mFbJ%pf$HPTȕ͗;jPdBnlH XVz E/ewclOE,6K#x ]@#Kן([* ,ڽ_^ :WnZjE\E+0O3XyylvkP'e1@ "D@$>RY.( R Qrg|&0 =ȧ}+\>~hœמg4h`]g_];m_?N_j۪^5xwVeۻYGgד;vpκ1y|L"5C{w=7eXck\y]l}T맿Jޥ6N~DG&Uogo\#Iu̱riz[=g_Y5h;7H;rcŊZ㜌 zWU@6 <73S%_\vաfu+:ؚkw1~T{[óm.3WEZua$ЪӦ ~e/Zyi7wXkM#K\ zή`_ݣ][kk4A΁L/4352@ǃ4Lȓb 3KpaZb`y&)&  Xғa85{Q<50 Ǽ~u>] 74-Go|pA jݚ#v pwHP*A!3RR 1T*P*%*8U}~5U<_x ,YL *gf-߲"Lso<@kqsnFWe$û6xF@1BdHE<{|;&v@؇/߿T}q)\fs&&ϧZ89bckp1ZT,R(ؔd3 +<7-H,H0a EmOcdX"*?@PxZ,qJ!Рd!],#`DB҈:SRN {Ð;wF|2coMlf f9h nV"Ž]fyMQ UX\(wy4)xx/yt71`\`àF\e}Z\ zUƐv=W2ޫhA *4UEc?mϧѼy7|~:B.'iےω|`Yl-,\a@ḇ{-S];`M48Y =1E3T~2'Kx^yw!,\D|~911[WDʣՏeea˻;'/X- Sd ] 2ȔAev\ֽ';8ד8У1R0MAJh9SzǴ^NiHmȧFI{*y`y`RC !UO*d/yO]!ѫDo.=d4mC_,jUO -:aƎzxJJA?>VX4"ԥ#7yRܣ\tROa`y<_wSVm٬7ϛiҭyI1ͭ d6\G}y{vc[Jg3^" WzԂg70*>3JxzlGϜ|i4E9ޤqC]yNyTd6 ^W!EPJtSBQo?G+qM5((Ǻ L^;=l@3wk!،1Y\5N5!Cӹ``8Llor p g`Ef[z;Ro׬:QT ҎjJoGƾY/{}URAI=10"DI z IDAT9zo1Bqtz[Y[r|A6.=18`6,~Q-8jT|HaU˔"pvį#(o)O>"tQڼV!4 b1;֔$;f?lZf}6g ߽|@mɑ M3ئu F!yN9 E a Z-3Wd.pKʲ_ !W~6?'$H(SAM˱6&̞NʘU .qR dRčk~ZD3{,`?zu[̛=֬ F(!AF؂yBXx AGoZUwt7y|i E*{ @3@!Pbg7t3i#z 0@3SVs~.)Vh$W*M˓EM-j(@D)D D*@ssK_tUA!ARt ۷G`h_Qƅk 2KiUSv8e&^B  !.A2.Yk.λ?v[c+\A#Ŭ y 댋EޏPd=kˮfeսJ1C*B,K RqRq%i,#ϣYF!54Yft۱ CXaPqCMdǙn,,@P)L-[;413`0CcPx\vDerU_ֆ !<22#>o߰^P3s\kg |@~c nY:?0]}q ^ rqX(?Tq'`quj|+#/\"31.)7PQWk6W]6T߸qY(4h:;++=-|>7gT8zܧ|R0>SfjZ)R*Tb?|HurZҌRJKKHM) =9,{xx!@0P*h@,ĀJbȳw / p>2KL[g4BX#ʽ?.x?n:+T>vvZiD_[}meDŽ) y3zQfC0tuA0?|^7$R\y]O" !J3ˁ\&YگTiD)#b<[6?̩_B UGޞ=w T X J I #Uq 4yO xc̫>qOCY/=DЖ* B# .%̣" !PчQ me=kARvӴheE Gn^۱}SIA(RnVmӫ+9G5hXeT)FQfn"##W&KJ($ hhFA LL*pA!aٲ,/5EDf@[Tʆ}qm+==R.*WOuc߽T@;~ 1i)ivmA))S[7&XubOټB(aW O4BbH;IDؗjѩz@J$b5*U@(I$Ncez(6[! pSRTb~ZT:mwZ^N,-PZ69U! 0pvԋޛ56!AIX[mʮ^]Sr,UoW/kq1ȸ9mr$fp\LYOpoYײ"d{&_B(JXÇ"0T phlh,#G,U]Pb7 L׫gO&֛V8,Z9 QzLMLMּ4勰jڟOXpeb2uL;e֥rΰ~PZ@3yڙ## s^AIUSU7LUq,Eњ'Tq>& iJk7@Q !D! JORĆ3nqzG Y^ z/礞qebmQ8wEE|'T<|r>CXW. ""_DxQx?R>zZMی$1)!s5!35=[Jv5co]/idAY+[ #@ Hl7y CXrPmL gtVhgv]z34fU:iG@ry !@ -E[vdE<ǩ2U}? (8@(Qz ҎP^kO@ @ T / ,D_L]J @ ұκBeٯEf@ Bk,@ @(sdV2!@ 7OEOB cQ;?sJy K @ Q#R]pen_ x~(@ ԚcUE.ZO|j$@ @@$.k~-w^ @ @ >(.V~-mڢMh@ {baT@ z1sY .ȉ@ ҧ>_@ @  7k/lhѢV@ C)aVcc mQ q0H Ed^bQw)-ZDŋ-B]*Tuԩl@b "@ !FB#vcP) ?h ȢwȌ֢Dpz[e7|P(l$[ʢ:"dv@(_7_bLG@ jLjCԼd !@ By# ժGS- n8B^:Pĭ2 !a SP9Xٍ(_d(`S,[r6F&Q>q%s (]1PR^$jμV& ]MEPN:r#i[ XdU `t.DĿ*dOLg ϘK7YPþQ/z +yToSú8 *((Sߞz׷r)^ǧ|A~6 cyyk~h}ʧJџ@AN..eB1>>x%ka @Ul@Hg zΠNmZ2!==<ꅌZ%S nȞCt^=@R/Z6nz_o8u.8ā!TbDH}NSgˆx{xx7. BU |y6;<\{L;εq)^z&>8u)DvhcY2Wwmk"ғ V),՛Wݼi2~RSBvoҵ\/K?\^devqyu9⼇;: BH`wS'zNT<)D~z)̛6B:g~,2,!$rgz ~BPI)]:@ =n؅g D֏q?:3N@xieOxǓ80kz ^sξ:긔ˆL:lÁ;:2=Gzo=<ѹ-#@fm;0yđ,Μ&/D e>A@@К9G u#m Jnb Kuheu{'sa4`tlj +RL8>g):)[cGtX3#Ym@{rIc]'λd'HԁMa4 6wگ% YyrxݱϪ`* 5AN_ :XsV.XP{ٖk4۩[*OG O܊:lNKƜvxSs:^+iG.48+l'N}?>=K{t{Ύӑ:ZWml'`eÖV_K-Jk~!V(~Rei((zuELFopkLڭ>xQQć". ,J[qIf#:˔^Z^PL"5@:@t.RܳgBhʔ)-[5x4m2նFڭ;;]B7m낯|6zl޸k7op)l>ǖw"ݹSׂjQ >DUe"!(T< 0ZQٺE.ڬwRc+.cbA}旿uG#,f'˯[ک'V/ \2LDj!&@ >N8agg&}ޒK6Q7GyqyڌCj9fֈ\lD[L c6#LxEJjNzeϱWd .P!1i /3[U+ |֬X&<ѣ2 P IDATqI(@ TT/:]Dʫ-@~n?%1!!!66[n*]pƞo<]޳g\Kտ\PX{yx Q8u_Q~|'6pI_V=^ܥɻ`ן47.{v²]+K  CJת:vr>y(']˫[iaՒkc\uㇵ_Kq~gۜ}~n$XcdznθsZE ;38r#B&]hgzy؟}ѳ_@voקuoe?P?'v?l*Xmΐ:DVHe.}}ĐV ~r&at=#ꓞja Goٳ'|Gj,IT{۷[hQ-I5W p8sgǙZjJ#5loƒx溮_XQ u_uͥNJ=޾ O T^(Hoa5q؆Myk6=oa^/7+?A:|߉&g*6|y.F6ny%M"F5ﺰfIZB tXޫ3ڙR@ kPūò;kU eėA>IurA3eR]]7\cPZ-?rO{H둢:@ T]xYʦboToo ҉@ '5._H!ގxɐOߛ(/.xC(y"Vi )Q>\\_S% ZE*Q*Vl nͺ-hy5mh}ZwW [{Dc6hx73ﺺgFb.]rکq/Xu+B6YËZ+??u{3_)#cgB÷:XUUMu70[vs,zx56Vݼ YGɔ^|Et8:\M苉O Jྟ;v"NOI T7 :|7#fz.lp[^O[MB9^"Y2Oʟ 9Z`&LPչ/o*J2\~z*O_#g7*]%|O3Wckkw_T__!1)j _SPGC|"s*ωilKwG&}ןꐣ{ n1'uXst ק- rW.nte>R约 ky$6W1OzQV~.s28bƦEN3l/2Y}k?nWA Ⱥ+[ @Аp혘젠 jժ}f}yX(m v:Dn-zTG@^hA,W(9ky*wN}+D [Mj A[*ZQc)!! G^s8GN&˞4[*Jo5y]ڠT*O8غuk ;A=iy[|ٹykϨ|dq#}W7פrJ-t Y}!.)\LuYu7~Γ*Ls)nq,:0_/J&Z)jHNfI_M(Zڢo`ѻ6G=;Y5F)Clf'*nUY16"T4cAx?ރ'NمJ}5[1Z?ڟwt Y]c%/\Ot^/:.I6e;?R;*Gz]˜y2%V\g i=ՅR%ZA]Wւ lg~_cnnacccP&̏@An6HHH֭[r &K~ghoIkv@GSÓv}4°=W'.o͕ mAi_,vmռVE"EY^}4璿t.%p@9~n9jvϟ?/Ͷ^!Zٓ?ox$g3f/:kABU ٯ>A@P@k~(Z”g QU!t6}zW#*²w JVTC* D48gPL<2}jA"B#T*Ֆ-[n޼Z7Zzxؿmd72y.!!~3g,t$@pzBFV$F=eZ{zzz rΎ s:V! 8s}C_ɪ3Zcm{GXO(۷O> vZ|9]-EY'88s/+r~ِIm8uGZ`.>S_9:n,b'k-=$*q%ݝKP>c&\Y[98ڝ;Wvuro.y-ן>c7c)z*\xl_9 ޓ,ν|A=8<>*la}:j̨39/~fZYą}77حY"Vв+^<#cۃR8;2#ZA|*!вZ jƦQm=ޞe+v{MwUb͙[=gYygZV"9;GƃUä?_viu1أȹKHSX1G MRK.(jaZЮr@Kch N,VweDuq${ūXMtlie#.dU-[Ȍ+3#~X;M7l~%Aw jת2P5^^ )*j=0*U!k 7 GݰaҥK Z-Sc:6M &W ƭK?/p9w\0h4ctmI]WEe:܂qfGs:,ZKB!W#?}=4iRhdMR8A6Dxu[މwO]5n[|ћ3V5U5nEߩÁj4b3w2D{[RĈhNũ|^4XKYTSFgE*JI҈S$GgK\,ݕe,Mdk63)3ru,b(V5!:Kg^ZWJױYXUoSq=)6Û$~ιw-eRΓ#|\yйo'瓇q/ZpٺEsQ6aǼbB5AT8q"66600u666:=_XKԿ]Ҝ`\;],W g+A㬌f .RGS[JkhSO9%T3'->*ɨ3V D,\hnn"6k D |,\՜.ʞc?bB刺0f kfuXu/Fy7)x(*b\ȨIbfck&@ĭ㛳ߌrw4 ա6E$&nkv9)"v9Ew_\PwqƟ7Aww|{IW/ݕO}#;E5,^5njR+;A}|n.{mGsϚ0Gm޲jU\_Fd8enqBuѫt͎6I2ڹyB=pGdDD2W{vJ;V{|_* A2Jd3J=|ޟ~_q{@]B/;uق31 J[>;lȍJ w|wXpnJ}Wl/LpaAkCW6%htMV`x#l fQ糪@\p )*zJ^c?͂h?h ZՎ+wg[l3#'AL#Z&]dhkǍ}~J8. СS!) H/z봡fqqO}t\lmq.N3>c`KYPE1m?_7Ur־>={f>QBKV2{_hVQfz.WifWyUfw0qƫJcvdшcƤECq?rʑ f9xFњ~K_;9RrImmӝe G ܟm0_}]okOMȨ'@*ݠ~-Ŀ)$L\} UTF+VpX֝M5lU6!l׎53uЎ**oS\EoWEqodO,{UBF"Lo\$nKg۪RD'm_1Ӭ@gg#ܗi@S--c%(AlV?z2mnYI_z/AR^ήƚ4H]u%dYUIF6w]L%4*,q|n&W|RmHhbæ Jf/̎,Jt'vNQ9 InUVr)j@  ÐՖik ؆8wsUw & A_qo2[>8wb泵EXI5ఁh~ e>w@[5@"~"D@8B2TY @# ?ٲ£ivӁ;6s+% }VKhXw,9wQȠ^v=W"i7 ۀCLTp"* IDATˬ ߷}r#plğ !4_ ʠhZ[J=JpWO4NU|1cr.YWLsk 2H;trn]J}]_"FyhzO/c۲t::ɪtХr"YB$h?~!D='dvD{2 v+/&YT m$432wZ4Fnvw`\ϡŧE`Bq_= LA@G/u)I[͟=#ۺgwՉt3hLm۵'HdkMp wv(93z DV*G ג 4 '>}t̙3w-v/rZx#m/V%Xm&.$e5gNp#tō ?|R]ą)#:.w 1[?3Eo5DGG;88} 3h$! q'7zqVi904GҺuͦ1qcߨZ5̌gC~}wheD{Z j˻vZUUk'*ZST<29C f.i6aU ^wETc#jN9x7넛_q"ZV1G>ƯFp$% ܟr0Y֒?%՞^MqqCJ*~GXLu4qj誰i jqm@V[柘-Qh% 2n8&wާO-/vq=ޣCfYV9ڛḉĝJc<{rUn']ҠNdD[#ls-Pbuۀ `}8HOOrHrtç{,/{ʥ{a KMw0qcy oQ.sOpa77qsQTR}s8{S͝fRم;p//rϻXxI.Ycxo{K 5FȪ ~ݝlku/tx liq~lkZhHHjupώ~*-!l&'YnWIa~'Xh$DKş#Q|]]t~5ErX9'Ya &H ԭf[@;ȝL_{ܭB 4(;;ٳg<(++ݻwhu29%}BJ]x1 cD#v\ᇃHGwG|b/%"ξbT`Iy̱|!wk Şm5+0S[MoJ)fa6vN ZSqܠ RoYyZD"Z;e~P|U(o{ٕ# ݼCP6K*Tdضr˹t1pY8)nS`jlp@vй-=u8Y;Vo? fCV p<|٥@T07wch#I !W[' }Em/-+^7뒘!8>tRIa!k^oNstsĜ==`K-N:zkɅq-+6.tV'7#P2>67UzZgdCO.gFݰ#s-iU5w ~ɡ]r7e5#YUȧJ*ƖzNtNN9vs*b-;]kV!  h~-GKKk+Wto1Hck^Tn]<~8,m&Fp5MEɈr 'Y*I =*$|么))~0 Tu#cE-;, 0ŻU~cЈ$>3q'R{o_M *tt0'c)3˦ľ'ZUJr];WW|8K-J<|zɡwK'{LYʼs>HJS\(bl7Bn}zu <ٖG\M61L|&{# B`|-WGU%w\1NM5.[3r风s޾s-ȭ dҼ/"Wv,NHs]?lE-,*61ƒg>g"pڥU'6bbZj]m̴(ǿ}sgJr<>G)Y||VwcuFD]Uvg Qsdf^EV)y#A[JR7OsuEi]|S>w}a$9mEQT.zq*pñUDTfZ5쨫yBUe{)Qjr([~Hr˅(DVY3$jDi5jYzQDS&6*_|R!%+r *9@EV9#{Kh-X<|@(E_ty` h >?`pQy*=]CSd4֧HcrV/B-mJPIP^:R>fܨ /E X#Őу>o}65ci̤1@VQ'h@qnQ(0?ZhA7zկT}*$uBMXun kd,)J8c <>@k. ?T5u#ken/&I~0B$N-*`NT|TH {n[)O DlqΌ,x߂q~c[ ̚/ ɲ/Ud`+euV#+@ K2$|oIgI+_w@Ӓ,R#w~qIU'IS3ErM,xW1*fS5dHl׸|&zLpA2 ndدny^FIHHx}qqM|+s_tt%ږzb2;><+ѽބ9EU !,O@o) ?:jȔ .,"LDpJ IyqGL\5>t'V/ ARP(~Q2k"GjuWo}O9a:76 ]̷!b76y6 bڝwkk߻6nU"nDuǡ]M56*vf4YZ[|Q5e~ѓݎr ڲo8\ʮОՋ9PGMg#ILR($Q[XMJ"#,% End Y 4llj8xvAYO.XB!UX ~,eJ4"ThwwFfs8U?ҟ-CRq]6PJ !N;[%XǶ&:5Zɭ,v,V#./D "arKu_6%󜒊Oϯmj`0W\ URR3f(|a|ˣ 4.dڞ}_KxYBoQ!ffn})lmq.N3>ʯi6MmVU򄯻o-1FH\2{7Ju=:bL w`9@t2EnN^&@5;dfg0kii$5MgW8O0)˕Gܚ[񷢉$Zb \ф|&Tnc[~?ؤ9{ɣyq) f͚eee\LWIz%4-t z|A/#"M*Ig&0M&M%lip2R٪]0RW(l& Zw`IN0c}K+٥ gt[ꑋrGѷ:?]_MnIU7{ϰ )ڢG "|I&@d,f7Spc%I BVw;ðÂ0b{ P< .Y T LKϚ3YJw~0rz:eYWrbD+^ƗV|ఁ@aA,&icW hQ߬TDu - m9Ԫ DyvM\:k+gЮJoDz.GP%>|MMLBZZ;U>p4Yzrģ<7F SlHy}ekzleM͸#&N'Ы8#㈽2Z[,ǖ^Xc!JimeZ%ķfE]q]udX9iU*VdVnQ~I5+)vǺ\`}5Գ@K4-p¶?S,.;^*6&7 ZRyrQ>xxx4;Qm΃TlG($ kCO?y4Mi"93{vw\ȻA. 9qtr9FRڸoJ{=7 'xn;m0'{W%;6ǨiaDd}~c2'(o~*D݇h.9hf|^I;Nn|7 Fzgt;kcn;' 퇛g}rf.% kc͐@ &6ڙxqrzB&!YjW[%\| b_K.|!33猙q|ڸ-U@d%P7i[LaG:^Ofw|ի@h ^z 僙u{l Z6A;L"Kwrq|ɚhՅ򢧭ׯEBc6Oǎy*sd|±:cEE|wAuqJ^^Y`DZ?tr@ ->jq~.!,U˾VPu&8J V#Ŀ ~eZ^PeL##"ՃO[ux@cnAh`uDh.hm #G;YCMk@|B Z--6oP@ U+Z$Dkq8Dh G|A@``_p8?L;*x k™//MT )gǖ;Վ~<:# B}廄IkYY[Ғ[:D\$P[@4z}97ܑ?`&ۢ$g#*'3&X%oJ@BGW ʲo/9:e,o@  h!D='Ŀ!b?R6 YToe`$)řY_S%>@XY'ވ9a+,}IKWv,:dȨP|kC䷪?Y&Rܿ] Q6/V Hjuz!h)DК4){n9Pkz:S%- ݴ%>w#ĩ:㧏' jgVK3m~}'܌o5tq5:00*Cq ޝGET˫?E9!Ά^'?~]TYOO*sʻ]dhkǍ}ʚ6o 6oFC "X֟ԣ@1FjʏҼ*P4de$ _*~, 3"os]=wS'PW='DThªQrĦceǩR^Dmw?wUt!~༝!ov?,99999@>j?}8wԋT⿖?`̳;%ha4qğBJK_CQEY>YHYT}MJ`(?'g`77qsQ|`$`֣=,m,=~CXy5w?X5[ p)gกߙ27ܠ#m]WӁiO;AFVgmdc>d 72q8; ͮ*]<\^1Vݺt $jlhz2ko*A4eX_630G ,r0"Hд +^e6 L.$q!FT/ r־Zea~'Xh$DSdB(iFC K D0q=cEWw,$ :b"Fg5{sV޷zV  ܜu5 &u7iIas/,`sJ .qŻ+9WnWw O[Qr _L3mNyymjUg2ҧ>NM})}'%bڽ-;/5x|{~7v/tfdɥ|kEuˮJKO;фI>G6VjJkniyo}Š,vٻ;HZ &"Y)3#cwX4c7rEQsp9b'2!f8=@ ֻ[ P PucX{Sn6l`> boŀa+k/Lq6t~n$ǮpnV\fBiTTƻ pX_3COtP/q '3 HBq6tpag!kn|b pV>nFcKI}-p7N..MGFژ2}%/qxۍ|`1D+YY4kB!k@Ul(HaUB J0җWᰪ̚*aUUVп|xs٫EhUGWڵkݺuтMT`(ORfM}_Y|n.4"I̹=^q]eI.C:adi;I%;y{]PzdjOyIǔ;(4Gq +O9f۩4gM|wB;ի1Ͷg"pD>'-jpN/#¸5#>8{1ׂ B& m-#+11''No:6eD~>;U—Χ\ىb<:5###aK*OBh/8{9jP0F7ZLf0aO_ZY89mp,0Y/R>"##+±ӫτ9OlĴ԰)3Ў7+ݾ2}q|/@&~OKD4 OZ Q f% C~T՜:YasiRV:)դGk!$ #<<Ç{÷l1ũ ǢST b;SU[.g}1NuCUh4"FU$v%PUKT=PQ(*]1/Ll-p%AI9%MF, &BՏ2YK5!`4 d^𼠭x/[7^E3'Wi@g9$@!rR2-Hudeeikk8k>g!c&b`0gOT$qck [֊]O?tq X"Ob4g{0T.>G/1򁦒ZK==zci(x/bHq`~nTFWw*TH`mE[**܌DcŤ:/f)GPs\kbLU҂(hyC1FakMf4mU)j4[m10_TF;Ke'@>LJA+ْT:&ܵZ&GYK4_&ee{,9 0SN)**Κ5Luk}wN:uHXz˄<dٗ*20~䕲:o.$iuj|Y$*Gґd5ؽAf P5!A%iIf|umYIb 4OQ̸ ̂-:v%u/EFIY*@vNNNNBBmllFhuwHSUHb3UV^P ?:jȔ$\Y\o6d\1&ϚYST*OB9 !z%ŸAIyqGzcA# O/;ΥwomjW#$iw~2zƲo> ת2ӝlX(IBUF HjZBOH]u7 Ȳl:[fsxW$,b`~嫽d W?"I} iWTT߿R+K&}b0l=7}ؑeÛ'-0c[nI[8Tv c%Q#yR~3y#NY#Y`Ϗl:G5ُ6:pga?GMraƫ<.D)%28ĵz~ze&;Ѹ"h TL6]ԨR R= )rKjy[~#k:1Ě64 wLS0qoIK9ʧ L|FTi'rGwqĺN$Ѵ  DY<ww]GMhFa4TpUcEG=0" 5ea\;+XDk-)8lxɨ M 3|ã %Lg,O]pjb,WLwtuXa\ydέi= zLa+Zlmq.N3>o0G]L۵pڋ,2V I p33ryu ;h=qrY$k'Sut歉IWamp;{1 5kB nMHaLhK1<+]䬗qK_=rQXw1M H_H:';)1̪H[HtzhrE#-&"nB:a፪g3 Nf\j6[U_Kⴟ-Ŀ|XR {@^z>t׆^u˹Iw~0rzm By0x*ɱ6QcLhc M@Vw;טD+^&a=?erawמK[>;l_3Yp&fpg$ȼP ƤX厄DqR)ldZǻT%S.𾔫YMS0fwU1IEq2u:{EdE(C~i4y֞V6xUNDyvM\:k+gЮJoDz.W7:jڠKCF+[OdtV4"MC~451 incȰr#ҪTRk;/&5U9e]~3q|NE}U_YԌ ;R;Nj`b.O!6G a_~-ok@ԇ}˨ S)ΑC"T ?'E<k 9s"BTڸoJ{=7`Kwٵ"f k6tClG-?IӔ&oIp`Yc'DIˇ93{vw\ͻ<"Aζv}sݼw2Nn$~$Lu{s׉'*<:HLx,ZsIo9{ͽ*1v߱9F?ͯ@ԍ*0QKe#Yϙɕ׃GM:R;J@\6uV\)SSM񋜖(Ĩ}OHj&;vzի%gB6ds]qeD$PX>DH ~gwGK, vڎr~_KG}x@LY=܅.%$$z%JK'afxx6N(Jx[!ͥ;ы d\mykqւ0 #`@  |-ğF(m@arEKzF#j5!KdTUeKmx_r41qMI{vvӦOM`7ON@ :X=G*-~|*4_F-Ds7T $S[ٺ1`D6 Dhq"}O/i9rrr^ @+[ Z@{p^6JcMiU!%z8@6!=E *mN4̤b%@{}XJzCo}eR#2;vDoI;@ ~jk D1HU5~@ff߭T:[8,1K[]^Godg0k9U%w} DQ$ @ԸK,.'B_C;~Q ߶9P|v~<$U53:_+=$7Wd) @]X'K'BZ9{K!kFCS󶉸m7JGy&<:4#0(?-@ @mIV1o o_Ațʻ4x97o7K!(Oe)e.aOj3=gNpc9,nmE}M$&IwNдdN!*K4b4 2mk-6| g^wd}8D3T|⻊"\3ZhzkǠ' ~mEZzzƋ֒' _NE֍8Z Zy3=rS`*N8[,װciz3֯Srܛ9SVzڅz8X8^fFs['9vwZ6Jcꗢ2!f8=N.@h%|Fc 4[堁=߳ 5$Zy5>ݚ}@`B%-f0aO_ZY89meMf\LLK Hpg~g#*;3awrtvaܚKuskAn!}a0܋0`[XTlc%ED1XJ+?43֎q̋ҟ|#AZ>.^p>{sԠ`‹L;~2|7w/OmP 6ۧ^NٜHF ʻ!԰ slz{}l*Ⱥ{[Z88 ^rkt1 p{Ԇ;_4vsy'Xu{/,FNĦz8nb?q,Kƛ{Yo|S /wGX[ zYEY Z:u%.8H_dm1ֆ8no͝7V aö=)9[:㸩ܓ)ꙺMV,D!} -y!Ά^'?+3 mm㸱O\YӷK @Ьf|-Dkxj |MΝIinKg۪RD1Fo|Mo'm_1Ӭ@ggnUzl9D[CvH[&T _G"b+9B+6yYnAZ1>:XvVQ4IZnu[{KE&>**- >CmMt44z[oW.C +B-DeԮ"bb ޱz?|kw."=;c:uԛ9җVFPmPQwQb⃣Tw_k&-r+snR+Oڽ)[Na*?GmvaFzz4QO~sw•g1d2k\[tYAӶ[%⣡1xFE0O;*\ +Gg#z9a\L+?t'VZ-,_eɬ̎,Jt''#휢O@o47goYV PMFzI>SID\VDA# O/;Υwom*&,rJի$ {XXxd__ b)QB k-4~VFIJS\(ɄOT5BTd3͈/Jypm&8kgo7٦H!gMMS72(O$IrNw$Yq]eI 2S@ŴɣGrDI N.bְDMS^/͚0B[[;++/ef2L+-D] -9IZPz~9kߍ[ W/AV[3זf8U}Ly<|A6ͷKIov+,jWD<mJF{4̺AD佸;=u"335p.dڞ{OZIT>]gj;# 5l9rqpph:hd5*G#QFjE/Nn8H`c~_8Z 0J0r66"n:/x^uCw㗭M( TkVqDS)diUiי,AbW2FV|)h*aӭayc)0k{qiX|gBѫw$X02#i'@Y#Q_r:lu)r80j/g۽}< ̌ϫ {& OJ _s&:h/jnODl[l: Cȡ]r7e5#"F̌kk^:C񰀐5r:VNd)1b3nӱlI'W}-#e3vƅdh\}F$vˣs&O/1[ i'/;< &n m$5s7O?3eUqa-P7m|sݏ, m#IWVmVc}rD&ճj`;rN p:~c.I " -/RtĂ!wV@4&ZnՈ?"nIU^Bb\gISLLZ=kBwiCKLBR%M*1"8l5'/ި!x|n8Lͅh@ѷ:?]_Mh,`JMHaLhK1k/(mmT7gިĤNЬi6€SsI }ѳW Uu0U) +dp&yJQ~1"@1("U?!CRz5([l*kY0qGQ.ͫXkRľcz6 Z0Y/R>"##T3H봝 2Sxc+!Cr' @d]r黄UD2`B ɰY9ASMO0[~ ۏ_0kK@;Le} '!j̽S%/n@#^L8w|y}˂ i? S[|] wv5΄ɍ6oY[?)e[֞NgU|1crTi܇}QHNXqjXP/7gn ߴBH>'>eVbLYK)j2"U~i*Šd)k҈p'QJI,+[@ _;:G>_Y\ꗨ_Y׏qV~xD%"BrإvK(d+O_\$O@cuG:^K׌驣g;c@BT;gͳSn⵲Bi|iTjMik険ZhGf:Q$S)] T UxYw61tH7Yk'w?gAç_v=١> osıGң7-24r_vjoG jP ]F0D Fhb a'2rn}|<,yf9hޣ41TѣFwNgӞ[ OY/W(RϹ3YV}r/K Td -tM__ k48ۆ=k Һ=9 %g6zFbPo}q經W Tbc$[$ڮ ݯȁb >C}@ $#:Pϩ&-cc𛾝~pAjP:\D E["Mޚ*ײ'p*s_ttk[]liE3+ I0']7 :.IYRZ~;qOJsOZ7n֔Uƈq'tk?hXqS::&ǫTp-03B<nxt̶˅MР럜rUaգ _-S)G ̥&G m ?a`罝6h9g,WwȔOq1! EΦ'j0T ^ eYa[OǖZ/ 9 dYo4zP:z߰ `憿ҡ.$MLTxG= ̏?*)㓷E:*X/^#IJU!ΖF2AܩivX,y=`b}g(o;Tv.cmV&Y0g8pޥ "bE Xn-[&Fc=ޱ`t8+?q(~Vٛyvvn޾7`RՕ<_* Ud0CPW/FUR|W . RkAubW p.CJ YHB2yr{w1Jbk=bMGDyǪP',ޛS&錮]+׹@`l"t۹ ŏoP$R-hgE᭙1sRUӬLT”p7OBBU^3kSc좇;HjY<ڼ'^NxKmбͥ*u4~/jfi ?e8Tdh)UEos} Q? :\ ֣~` d}k ?K*3bj[2oI:.ͫc_€3+ԔG9{hxdM6y6YFύU N)⥔ bِcPܫN,o'#4zHC itJa>ImY 6U5y9x@֨Rl{o@]6bf֋꽡ɇYĤv#7$s7 H[ kmyz5@f'eQgbjh4pF 7N ~4Ct}ݰJF={FäUk6~.pP;_R&Տ`0+c?]o|qPO;# t{5T+pF&9YA]NPpj`%_d!x7>~IJ! 4Dh> ϽziSLj iXv7ejb5(~In wt#Qof46=jW8`T3 qZ%{-*CJv>{dH_}J\&ՋDf0wIyֶuRr,j?VSѕfh. {݊+*:z-x!hқîڿKXѓGz +7y|ɘV>{8,"^^q7Ottƍ׼ysfG@ $ZH/WnR Svٷ?rmzyfݶZJ6UV=wLf]xb^otznhA(jBmCCbNX~\~7z+#E*'33GQOҪ f9J!Jd-m_c^6qDƎZѶ?9 N.~Շ>M4x[x64/}BgC,,́77mw/ʵ|혛~yТw{yk`0Mb6yh-(0 @"H #%.x^RH"D^v o<1VQkSF‡i(]0\P` +!!VZ|?g'V,^4{& VEa6Kz,؅鶫Gױ{if>l>O 󱄄|EIOR`0 MUOG_W}%Qį3b]j߻gjO[qaI>JE . wIs4j @]_0vQ߆}*ޯpss.oEʏM9o $5l /šze²wnԫIH_@Hdȥv7RܙT7IfzCꏹ 9ja;|:;՛8[j-Hu(,ThHK VpWs/ァUL?Z29!Sϧ%ۧVp( a#f^U~}n]A>WR,v#wqݧWf|=12poӱzazxc_ ,D!ft" yDj|&lka02ZZR\ʕ+٤"#Č_%,y:5xUN^c2Y尮 /dj#Gt (F^r>CS,.Cr Po=cF i4h#9 wg/"g/coIOحNuJhҎPbEʵͻ cJOQQ"d2OV~PV"ըL&Չx(]@^?"&L&sαmX/wg*2z ^RJOÏ![)\PǧCeϏ||o7o:rh=G#D*z{Y9!9uH9wtw2᏶'fVvcS@ S{8w#$O*1PJ^ V'"e>;O'/2 m IMQg?\ޚ{ǔMi#7.jЭ&Uк񏭼>qB{s!S-/\ ymecH}T(w TgNR(H(>YukK@R`}`I'O8א*H~xٞ m`#@ju 5G pFl7pNUI{FX 2O&*՟?XZz+Ŕ"G_GyWlA$mbWNI^H(f\MH(9ƀy;@(uM"MӮTw&jP_}f{ce|-:'Enm/>I켭 Ӳ 2wy &xZR߇ooV$+$xeu+ǠBDFЌ~^s 3ڇG X|GZaMŕCkd2Z)T3(Ȓ;(J"CHm< %؋[9*DRLy!ro>ju[9Rgt9+ $iu_,%h#@0 1ܮu׳56j8I@ qo4ʧ{:O/ ܵz)'Tf=|vf #CrJcP5 M P֊?z3U+ԨR?T]_fc"DGź[uOxrnȥ+OOEP~=Ofn{W)@&6=[vh7rei˩ÂL@2rv_[WQ}>M:HCDdHn%{0_%tx~GDƙÇz*t:Vq^Ixַ Af&5vҘ5gVcTGȼGҪ"[Kۀ0{lҪ6-MS_!nz e@Nit"8"VUQ.oLJä5fΆuz̛\0`ыZ8*:6rQ#掎 0Ovkޤ㧆.&M6iqwP,VZ #dlfc6G74S}>zLbtC7D.w_^\Y$my7Dm6jqj1iZ5sVT1;4qӤ/|XKk tbH;u8v2t١̡L 谌IHj`0%!t=ҰU'[2(ʲbVgn]2₟[.Rġj)u3&Oסq6}cCc 7[iٝ8|`[ `0/#~җ}C,29fE^KZOˆ{}kRm{Ԯ@o~!"a)*`0a `0 S`[[fr,^1_&By>`0!lk}kaL[{=q `O|- S``07>{geO>M>bdV ٿq)S׬iA5AlmNfjam0 v25ݹKWSʯ߾׶K2Tdڴi:e+*^*8}=NR|:N:ՠAwfCE7. 3W /ƿlH2 :{1?1(N"$"!:I{_o~- `R_ 7/`(`όdw98z^IRpXGs=fC:Y<'? O[W4;fѦ1{Cf@`c,ZARI:B[r`Æ ۤgϞ8))|5|S4l_tN$CK.B$tm!NGm2I܈sѕc0SHZ8+& AH*c32\eH1&^[!&޺Im)4sjKkq2לc' ma@RbFW*k$—;%ꠞZ$I⨆OAS0?tKLxndHu ݾOƿr1ujډW4hPSMߖ&{DbMw}.'"OݸL, vAb'@Z%-N܃|q aoT!L?]Z5,-lea>)TsRo#bX"yY}(!IsKgT82IR1j hXKא7r"tg.*Ө.&qh` y̴V*"V:!8"+ψ"HuWmq[ tfnO+nm ln}j+;)C,zt`)j&ASy{7ӣ;]}^ps{}WrVF,Wl]' sd?٣5&HlhjS!ɍE_+2ȂoD^G5l~zeѦ״r(H{VRqȦM+֟nFax(D`cʲ)o-0_1Ȁdvt%ĎkgL츏!G1$u"z\4bG|/ gAr:R=}ٴN=>hؐXtsH_hW$ƈ-~eImu<1<D j;:3bA!"Ah?+vJ&fz   9i_8P'Hr~5Bݙ&^9a0@jK/ռϏq,uW s{:ͻ7ii]9lDs(PbV.ī%##5!XݾSڑ+@zRVS2n+8;l |%ǦdcSC.lqa>' MUO:U&~_i>uE($gpI"aW$Tb>"FpW2pqFnjkD=$b\4$c GQ,ME㑈$s|D\G-*7}цl92B>J"ĭ}7-˷2GdS14~kՙdBÖ"7=7H vpϖWI@${ : Zy~]m$ t m'Uq@ZyfBǰuY}:w[w[ դ\7rwb^9VT|\5pf_^`վ{d!c`Roǽaᐽ֜LW4$?M9reḵ(n_P"CF?8܍3S}'=K]mBLMD<1tĎ4Fn'|m\tIRм\¿_/0S<``tMLuOn}pf.!}ޏ)@$=Lf;EmOhpgG[=(NL%1mh ˋHcs#fUL; IDATbΆB0XH@Y$IK3GD ZH''32M[I$`6.6Gn4.zK*s"ځ?#R+͚ͨB%0''؜:}>@Om)l좶ψbO_5LT}Dۇ\H$vZ(UصmiY3i#ٖjY'GDV1P)qz8"kqo+uC3Jj޼8UccE.FG IlD_T#wN?_AdΜ%ZAk9 J?Yw05D"5{26(ƱlJFoўFڭG-"<Ŀ`ԝ!OAi_=zԮDƢW,a߇P˧ほsaK5~ᘘV3A=8eWskXBj( $"H<]!CQ($r1u۳TwOݷ@ >]z!eރ7xsƹJh'ۮ<%H>A~dfqp(iFC"ب5I(? ^<碢>ʡsXmڜdp㻎t(E >)D!U7i/,uD/ GgjrjOۚkLkKuR%HU|(5E9/>`] _y1hh2a:WCLHXxf$(OtW 1G변$~ B\ΟM:\DϴDŽ0iߪCܨNO+hq:!OF5v];}촮k/ZK079sA*)~ss ز&ި,ŊiwDPSF*A_KC*:yK7:<gBCY.v?[kn5 4\}|bLmm[A}?w&m<|`/ɾ#$g'u{Z$^͝5ysrza+15g&Rdǒc\hɘ@/IzC9 2Wv* R?Yǣ9W7]aF ;=G/Vר}+׉t5YEoNySH3>,*t"IkK÷c" Է>ɎY e{דLDu: $ce֗h[ }a0#-WlW{i? "&d5TufjƦRNSF7jZ]nX#]{B{ ݅쀒ĖҙDH1Ɯ0Z{I!U\uY)ouDPDͺ<>C CBz-gp;ő9]J8t"v DM*;"*.;6&IUS8Wg Xnd ^.+L"+11%Ж@H\̛ZmMmL,'@Ve# rrup -uz[#=<]QĢ(%6wXL]}&|u^ q\ۍ[%$P^nfUO B.|LBODh!#G~+2yp`o';ew X/v` /k)"Խሙ\~{KwvE!SSM=ҫظJ-.;9Wn/BHd[y$u :00.-\L T7C=no:}g;F"ƶi&99yݺuf:nuΫlQ*n>Zk&͛Y4mhE7Cft9o7] Aztiq9:< _87韍Xdh@WNJ=Y*"_Wj TDW*MWJ@E4*MUAoJ]p+5geRˠ"jfGդ\MD!H(T*PE js,T*PM(ԤBM PG$ɠ!BM*" 5АV&؍=Kdr25Bë֊,+@9Κi6lKMp/hJSȹ)JCwz?L7;O}TitRDR@h~Wj@M*>SJnz_..u8hr* ^=+5D)"qŵecÕ`bjgH\idT͋&U] a:_,ɒ! ޤ^jHerıj;sX`Ϛw8 >;}N|V#/=,x#qp&^<5qvK|+:w1M^?qj[}ա{\ ZPڦPl~#enhک)(<4ۭЙeH _*3'{J/ݭ}J/uo=MYezVs?ԸijVxɯOiHmG٧AwNՓ~g v='hɓ?VCCn^ z?7~éY-puMj^Ɨ|C\_q i6y5#{Pj^Bȹ~qg/awwwww2p߯e$[7ߴUL])*zy59X vXe3zt}6DmgXR(vh0zrQx:;U3Z,X`rKk/ԓ]­12oۣl=wtXl_oԼX՞57 @djlW_Tod{FvBGVOQN}J `"Lltxύ.ٰĸlH_LHpr ԃqABǚ=:KN+ĆVpF$m"`X@ a:d]LX!D Xh2s $]ߦ$-%T4Ifx$HR[UV9{"K,AȌIFM|$Fj3g'}P&ID%]GA0B$$" 8mV2"TCPU`hits $ȡF?l$!RaxfiҢXL{SO%l$P'' ĩÀA*{F`z{4"$7mm?d_Y@ $UR !.d^{LV5EIK,MAQ"S(>K$ٓ "?91ٓO^idҷ18HdlaMuGTz -0.çBPwu13|nTW5tr? 9N.|-| sCusU38*71V8XJOs)*qid^瘅:9߱RE7]d nE {W[dfMDfy2UBM$VTL( Ⱥ|ĨU la{*WhfgF=Eb#PE 7΢/:EUS{J $u]QSNHϸϕZT4=rYo !*EMj#'_cJpջm WڳhmC5zpȸE|si3A8!ѾRD7>a a^!ph?}SL`ZH(F(F.U]ɓq@ *2scpݜV uظVu΅\]iT ֈvw;cW;$֡Nvޝڟ;x6Bl# oXyk{30-/+nSPo 3̵7[?@9sJ1[5H:{9U͌;%xy$zFDUV=vx EBaJbw'gr7Q[Vę@21GHnN#_fn:W%R{-p=5ؖ"վw:/& e&lc}eqe{cE2Iڠ*Y {k7N~gS(D62V>EYn;LL(9|߾N3)d1Pgy؈i_{Ws(n2S YG7% ` $VlLq8,. oZOZL?xdr+ ʼn}HTk`-liI>u4AXA,tls, ǵ'3\:rh3 (¥-͝L,7|EC¤&- P*4rQ#掎 0OvkF :4‚L6>ȺCU)AE/vF,!v<Z?'϶n\CZCME"jtN>~/]@v iGd` , PBP,׬Y-7 QDN15XE}u'Yk64N{!}$o\Ostƶ_Fޯ$Cـ~W[q-SO=384Ʉi{&kk nf9]Zn|mĔZۇ2>S)Ab׿)v]2 NlAtHО) |uZ;VW$LWԞfɈt(#ӱ=Y9baG  $B;2ya\aҢ GZlP(1|-ABV:/i͈@ř3NdkVͬKz╼QzZ8K#jj( R=~k%w c<}J(.v]uZ'DMiDiu8Eʼnņk`&F8e F5]W&d1}Lj*1^%Ku1xZL73&Oסq6}c?sr$UM\b[0mHEw{3-Navڃ IeJܹq8IyîԺkN$sܜCgcGB`a.-zJ1 }l2~t4 Bow֭DBaUg(=!::~=3d3)m?}yTxKcq CdsxPxL< 1mtSZЗ18>"]_gmԳqKSe=۱sf"{GhoNlu36 x,%өqgƝb0}gVizs0ƞ)>"f#J0Bw΃Iu'Q) )gAfD>]FҼJ%1~~؞Nֶ#?ɊȪ]w \# v^/δ㛦lCHl}iM}F>=4Y"ryztIT4Hn>J*.vUf1T7GVu"W]Ӛ X_{$SHdwgJܚZdNk}SB"igpRZגbb&mMsrzȳӟ;D^?y3ü7P*](-Dc D$Y'vvYԆ.2EHVR"2q[nԱP rw }tЎy)ZeNLp>!][V0~esoC2nܕ&by0 ފ ܣzC ֥IvNn!Tܻ,1.LjΘxl.}ܝ*CQef[J_!Bg:|yW=<ѱN~*Pn=rf IDATL)GM>NjРA)3s!.oʍՍl?%"ڿ=twqgV<\=Ѕ4z-8v]E*̈́-GӍAݶH(.ЦϲABoD@],_.[c{z»T-&:68O2AZNIǗKױu\`%C!~3?+<ZL9J9w*'2؁|uh.}!f !{yi 4s+a0̻P4%~Io ex~biCc^B6:о}aCCζ"RֳnV6pXy0X>i]Uz.nn_ #m^ȟ]oBɤ:WEzv~Jwg|GZ,?Z6!UF!ݖmQy!oSY6m8Ea0 o[>RkcP*U$%&REE[/Y tmw}(E庰#YBKyMYpט)VU.E%;E=f7;_`I|Gu{wb'N>9rn_r΃ɪ7,EO}Iڗr Uovf_Z ;'w_EIb0_&Cpp.H{ӷJMMݼy3A6Fy ԦfH( O J-MoϹ}z~ݿ ![rv]Oע?p_η߲ <_TO|p7r †;_+?7kՌ)f|F_F֬;v>!!!++uVO=J..U=lDB!H(?Nnja|Hy1 )A=`fa=jRP _훜ܥK*Afd.[lÇ8vȯK yo(kc0<k/ ,`YI9(`0ߴ_4`SSN5M^X8tÇ>|ŷnmP0 }V뚯|9;+ra nî@ Z[~{QnP˗WZ `>؇-ŧ^Df5<+(,,,,,|'U䳠Q΄`Ν;{yh`3÷@2r`>,&&[paӦM߿T b0lkaL@bLc$iA\/vzFVp v>Iz?BV!Cw?M:! 38DΕ+J5pI'oY^.HEҎQMd!_ǵI#ڮN~{ 4z rq\?O׀q0̗yVVVykd>,--@ e>2ɿm!J[G ?bʛK?ZTfghW'GMm=rl~ݩ^=k(roV,2^ɔf3#[O{L:X])*&}Dcv*z8ň9d_t?;f(|sx"jL|y@oAPzeYD_Vd.A^u얇﵎ژu/?Eyd222H$> ώ/SA^A^5Ztl,yKooyP[x5]XiػַW;_Kת}2ֿ[zcR͚~&v7{]DHlb$r?HbB,|Q`DUd#bm|\FN62 >ͳ p+qBVxO H*<+jN#hXsW [% S_5fĺ?.Xw Ւn J'Gt5V.o=Vv>~|| X4* }>N))Yf6 v7MbE)&,qzefkje0ةz JL]o>N2X*an+x_*[քE G*d>5[A ǿ5d!\(=2H&ɪ7l W4񋚳vZ@OC1"$ϿV^{TvlE[i+T}/Uz'l[G&|BmLkEOZMdyXMc l9f}ꐅ ;&U9x|q}zefQ\fo 2W; [[/s 1&,d)lg߷ǣf ǓIٗ9C HZ2_|-Uy9Yx6ٟȊ969>Lllk9g~/{ZDw R)01̫XS6ȴoG5=,T 0v\ȧǘ&ȼޟ/?i07D"mӦMrruf͚uJ"B/O!ݷl5V5+W=#GzVI O|R,F}MhZ=]mg"774I"J"o*i1jY Ah[6?;K6f\Zv"dټWD]9E"x8:jѮG=.P}kQ`Op׾xBc'^xhB9)Y u=*mdf׼9tqcrskx&ɷLf.p܊J>v͖_N^9fI+yi'}Yω_U{Dzyo XqɩKxiY߭ne?)S |^)#zU%O&UT7Eg=i^t};!h2.UP}ǶoW- yy+J |L@r*O2!d?lѥǿZ2_|={ Q䄹 DO=担]TO`p.]EgnDBD1Pl10¼(bwc\P 񂠂H|," Rgev9 :gNkӺdL} Cus (qw2@^woTdTyMZW2F9Ozl&!-5>mNV='xS!IO,d0rEĖ.pTǷ SjFF{wc8`PLW_9@_߸?ZaEm9|_U7U&5=($&#+;T=ǥٓ OOxvfSq8M$jn=m>vQO;uWKJR~t=/6Z3}F7^B5yF-^a,@(Xu7vT M_yfBN3t:l;k`ܳm.Tھjء< .)T9ěn:C/pNo ͘fYHA+#IvLߖ)CĺVj2kM?Jc b|gδ$04mgӽ^8“ ]ל>5cH[uqOQAXL9}]ochg4b Ѕ{kZMi7-hE4C,W!#YE= oҼJ߯Q?Aq-$ úCEAz\#i ƨG8n?^FnՀQ=gvpr(Bʋ+ *4P@8!jH!B5:ܔ5Z!B5=B$/TA]P|q*[=QBgzx:"PCqmȾWިxjZxʐzTkB=^ CBճ&\͐Psc4ewנ6]_7Z\~iPG.7ɧ[%8-1BB5=XתCE/Y$3}_~2ɘyޝ(űx͛̔bƚ(-G]m*'2 d_u)"%:N%ɜ.zV?{uS咗޳w'1f;M/!¶qe VfVtPொ7~w>ͯV*/"g>pVɽGe0JA&JB!`]/A ֝M:D;Ufsއm>loTau2+k77UI ڂŴɂ/)hsk%Iwwё_R2i奾9tucz0\೹=Yq Bk!Wxq9:_^"`NKy1v^qDL0R/~zتˮVIK8hckjCڷ/1p>_k]9&T3Skiݡ1]v-ZhiZт|6byϋ`_` Be³cJ!B֨kxηz,&?~OF? ]I2 E\]#h첕ͥ,l@U^JZӬ B!BֵAi :ATx(T` <,+IX 5Ӷ ςSs_ًtI+#)_ܖoIT̛3n?8[%ˍ]\:tvI3}[  =LGeWU>v_/yiO+mxGsq6WqB! 6B>Wtp(\ #d gnIڪSD$'c5.&T_orݼ4AKvc`pT1)Wߢ Es/7L7 ]z8)OˉG![:vgD5"pӹ@šV:JH56yqWU*ޠ쌖x@Y (eB!4aV"^ēqKnw ?WqGNݻ&N# D3MmI$ps!3ݳ?19iX1y|^BDJ I$Z̢AvpP1#Eg/s?HmZfUS ^[C1gTԨʙoc~( ׄBMbp T'_~rۅ;062󴠮bkt>[X>*bd{VږU=վ,/NСݍYYKɼuRg$}Фi(2 -;cM#)0?1lϡ'I?Z+Hx]؍t'sMvH$g xL83LUTV4gy$#˼/yKQj&ϓźB:A3'+WlL{7n1]ݘ' G{v}eֹwO$CK/b8 kq԰WV=}47Eƨs3y 7G̴UJ=6 YgבK?|)׳=slk}8i̦놮~.χ0eN$ɧ$A{:FS7i>g}{8 JKoes~$)2; k<W^M@򊹤Pr࿫Odhfqi|ferv:z*%-nF\&WEWCpSmۨ>ıĄGR̛!ހC#'ŋJ)T[\9?~MMafw9ɜ<\|J ]nr^e6v)fGf)A!jA_u#{]^>7'_ZEܹy= %wG3x.K}wm|*菨@Qr㵸 I:*)KWVY.x9qd wsO\?i7|"*7gs%W,}=wif˫[Y#VEKP' C۵6/,Yo=pcP칽go(xm|Vr,Uv@YKL<KL[DVƜB?OM;El?( 00[cY$+9!z IDATQQJNȓP,(fEIA_+<.-i_3Awl)G^|v3wOҚ Mk'ݞM6 e[Ohhi#*QSY&G4e{me~}̯*5B!Tk(tFڏXM5ɽpe:YZt,ʰU^Ʊ fV+CX76M̪󂃯w~AՋFw236kmȬՋXY-|him?Ov(=oU7Jw&s/oްN-m&mӟ}uA>.nlciŴfd_"Asԛ42,]#snnz&imfe6#F˯/**kIज[9ߪ -캹sʼAMצ6ߵc{4k`)EBQqՍaFy֤ Z E{Fٵ֐ZGyfB;#ߡYm+0 4k;%_{zl'2s/]*eBZ[S0sv RTU\ia S+9k3ͻ{if2+X{߽-llvFʝUFP[:dյ= r +n?skekin/gs6BP{([&o{[M2oND$&Dyf[]Z& ; 7<:"sO(Rܯ/FP:5Z֣\V= =ۨIWvzʽ;{?M0yuiޚ5괊=^!o(2f[E;)y@۩j=[_tktreT [V m2XCԞD3ŷ~ΙsΔ|G֊M^ϜZSNVRleQʧҢ*أT>NR.E\h!KdjNQuǷWݍYf27;CgK *h2ۣ=7\Z<̄g~y0'R5+_3q>&\Յ^/kNk>J8NFPw0б#ǥJ:`4{:a1]6/_FHod8Z͘}rfDgd>guѐj-$g7Iu)3WkxcudrƷp5}d_Bo@ڭ8V*&Kgߛ2v9N_Ii33==vv*r9$IoZ7T9.=\y2`Aד yQ=sOd^ɬ}4JxB>R7)dzBhlrDI @P(~EmLPf_F*F\L* ˷BPi"')zG\>6Ll5su-Z'E#B!T(fnsFqZ)`Ťtcȱ4 ֆݥOLe`p 36WvK~o50$Q: ո^g8yr3*QUE~߯ 7f(&' imVFeFZwfiá+'%rs3%PRF= %?qg,=?e=BfG$(т%|XQ~[/&pM@kIM -${qid"ٱTn/=&.}qHd\Ldué,BYq-F To߱x~z+8T ucvrPwq>dB;Y"$9o4*^3뺭uFo>DG>s]QfUeQzNc-_\Qm[fq7Š5L񑬢wP6//k:jK A)+DV7BP!F957*L3Ud΍1 p(ܝeݬͷIy%mCGw?>ҥ i;lת!YLO'lP|ɆvzI\UkܻY6,k!i|iM>殚NV!+NϠA;׼8;LK07o/ߞf@v ,{ۑ+/ⲹ ݺ8ϥU}8m59eãJQ" %]]wd' 2x?}K3ʷ8E{w$ӈYGwѤ euor[`]uPe)˸<7-<ޙm,aWTxC^)fe\kUWCkAw{ޒK?Bu-VWfyiKnZ}N$zxAI$)OBduA.ww4!_|ܞk.%մ#A @b'ߛϑ4%8wWVTexEtTnh޿_wWK%ZsNr=~mGeY+uGoeuA5Zz!j w~b a])Ȃ/)hsk r>p"8}FY%%n4{v/ӟN z3=i Nz2$uЅoڷaDJpFgPM8*JfusD{I;ƱsܦwL᪊_!.N#;hH66e5ɔMZhiu>v!D![`B$ Nv7B̗VrÿSz#n*AɷZf&[U9^ï]5@}c[n[>Ӻ(`潡Km]U`2lNn7 4jz^ds8큻(ݶIW :U,e8[D˚MV]gyܼ7]̢!BºVSVk㵸9ifv:2ekMYhDųzMrQpAӓ)~Oh2ůX:^8e,i1RY)!|kFsxl]Rʫ5'<.}i%Flzm eMD)"C_ñ ݔ!B/ulp_mo$/HBQT-sEY:]+S?$}jL5);?*F۪%p2iJُH* GIj)/?Tϰ*MR*Z\ +Z!BCUfer[Ճ]o=ѡ|WnemL njlVUĭPa6dLD^'R- %dq_dm\KV"?Ư*L|nB!617Pf #6@\boS߅}IG.}Ua*>RǏx=,.9ݓnCgN;}ݪ"ҲAwK:eAYWήj-,zW5=%@Vi:%okQV Y"D5_†3Bh\ FU@0p[֖PB>G C߽:z*}F}=YS T7ny;8HV^A}"J;;"> H!B~Trjpn B!}Xjcz!Bzu?K0+(^J`?G5@Z8L !k)k֬*^?խ s=PC5.BlJo#~!!ZVIB/lA!lB!7]* @[!PG@Z B!,"B!PúB!B>k!B} ^Noy^pa}ǁMbp"BB!PŽͺ۞XV~VXsBMYx6VfVm\vi:ht<浩e6fV^.Ydk#jd;E__C@?8ؘa[X} 'WOy択 ^췧 U-g_2]oWVlj6u-B V*JJJ3he"OJʗf: ~N%+=ϵ@y';)0zF>Zz> JqRέ~Üso[u49{Ќ>?qas/ܛ6Z·d%2C_TT#jbY-VZ?fZmբF ؇!P-q aRSOx}f-` 6_?۟o7M]\pk[Lt0UU #(݇JLSTUq `)tO 'dSo&^=wƒ|tQae ۵Y|||}PceddT! P:`4{:a1]6/_W2#O.t]ΨSڐ+GmFm|?eL}B܌ Pw4p_>guѐYo, Z7}WSejpu-$q9?+ZD||+3_umn͈Q&Z!s?RJ6+ճgv 6$Ȗ{\'S&I!-{uxi~[:Jg5Ɖm(I+594~aǽnËUKgu7AE GLV\T ^/ɯ\><2Ek+&Mf #9yw7o+J&Vxmޞ;! =υ[fM^ObÏ6兇 eVYf!͚Utř=e@)'hs=n(]K.NQ1zd*Y#Oͥ]J._,R?qrnG>_WY˦GD0q$z?m 2k/l޽v 6-Vm)WjSJI NPl;vݖQsq]ƻ TO)ylsFzGO8z |"x׃w׻@}٧+-B$A8F '+.jNOI\>t;Ry:I T< 0$ԸQi҂4*A-GP@fdE&I(+Юb΢M.u/Vz~Ar"(q/"0@yxsBe$-%V'ΧyߝOik4i3hE@JI⤛ub26fU3ɴ?ow3&i(yNhgd2[ugtf[1 2VCO}eޚYX)rF:Yl?lTNǼFud?y8Ɖ),NiBOk&4v Fgۼ^i[1S{rv_&iwA ú0L" *Imת̋,N3 "rqQGZ.?yWkN=znҌaQt\\\vR7[dٻv;OY,H\"ȐpjR__# sNQ 3-sUb'Vy8U#f_!;>}xeVrNl$'O\獗n[eviW,?{Vlh^ IDAT #d> <)[„x~l= $>b"#O..jѡ?7+BQuּ$= NfTig<=CDK"mΠ O !PG)7{i]VET.^FHЙ0 I;W mggc2bs>=\f.Vzz.Y?D 9>c^ng}a}Df+.GK~xZ{%*еzΟgttd]:QAJ>}6>[tgܜދBes B ~4^aV="(B@zBN ClP Ap&V0$d+&< 0+ Y[IfZ;.Iւ$(+*̞Lۗ=q-J2]vjw~ʣ^ *= mPDӧ,x<+NBK'68ť˗BmcC[#Iy@EO"νi䣏*x_>޴gJ|y41B#^yw$==LՂAبBAWICY4Ж5w랉K=K۪Y]5iR^$m?`[>SyMZWkI(hmek$waծh-fKjVlxƵt廉q=RUޟ & b$gs oY8B>?;~]OW2&%DS3ף\ѷ;{-{֝Q0tr7R~N&^V^CCy7lV O\k ;YDNT?j8oZ#~e7[pj^dдn=q^|WQA !>Ukxh!B 2M[jY_+=9>UT,Ħ*>>ChT8q>wh t4nD=Oob;Tw;K__C@vpr(Bʋ+%ё >8Ij2HH+aE !B  Pz}!T#ش%6j5RBHR-̬ל殺":GJ*vKHܖZ!TOUV/BH"?WS3dZ\ЄΝ;WkD#ѣGݻw`߻{C5bbu뷱[n%&&oAYYYdS!΍B!P&:ثPrl6Ϗ ;4Wn lt(B NOosgT^ջ"B!T*91[n<4Q/א\C,ǐfWN#Jf=YvF&\^vЁ%+=L,q[m`i`zo{aTa {inE5Zح@\A!jBE#---119j.!+ RyvSk3=k9js잨{┣qr;.ΚyG?a׻B|;z˅'oΊ߱3-BM V*'!jֵjShhhtz5ӑ9&ZwzW WG3=ޟl~EX@q͍Y1Lf~GljmרΖL8U&Lfd2톬Ofn{wb@;c&٪?sr|zynfL6Vj4 I?<}jb~5=^^t6c2M쇭Z%q#kvlg۾̭IE>tN•0rӒA6-:Ѱ|+Gwomny'UVzJvWDxy働.Q%yoϤ9_xTY5muv&N쉫R3.DOR  ^Fɻ6^{w2`ֿ6YxÀ~FWs2c-׸|U`&x_Mݗ}O]^g/p>ޗsgLȪO$ŗRR_&3Zu7Zic/^h?I"h!j(U򔔔~#!UA-QD.J|AqA_y/9=, ޷7'\1E/ گO?ST <4@)w,GM*Ux$e}{̈]Z \~{L˭Sߢ1gSګi~:">=0fbVz3ҵc tgloptd ș:~|Nk6Y*(B^znɜs3Gn>QzXB044($BԹsߜSDJ Ø6ѯ w;2q*X)!EIaZ+H?-  hZVz%a}O#g ~5v4apy4u;;+="bPFYZԥ;^*E{sEA咪ֺRvTbVs0 ڋ/j[;ҵvP~$z@!jm/W~OP锒j$IH ^9FIM6*4aˉ, ?>zO?)k)?|MmEGJW#Eh!ݹsgKpN/=e@P[uzh.62Ν.6n*Rv}#$[%I"mMK/TL,@544K)/"eOJ(W_,b&Kc^۷ߕZ GFFtw}㞶&;`x#<]κa-ABsFFF@@[U=GI}=+wn6L&ij?xHV0RL&ij47]I&B⤛&f2L+ogĕRq:gBVCAЮYKNN:t1nރ )@;UR7$Ƈ?>mݩXv%J4ccH8I횽v\]|hBJ= vEo)+ɁP{Owz֛iQW_,&~ا9Bl#ֿ\jGfHVϤs.S[5 <#+kMk!{Y 1s8T:3 ~DïzRqN&#ם~i੅o7LVK8c|9=uD%tՈٗ`O^2I)EpSbϗ>n^^}s|c3+C9O `クV\VnIud{Ѫ `2u :4w\wUhXv>Oe$IBpꂥ.vu;M`YWn+t;?B@ϩZCvN]l@_bua*T~xSgܞ{MOfc,fLG?xu 4' `WRu=me.~EU['ٔ%/BÕ d:`0\]]SRR^~ŋGv.߄g?+ǻ}CzҕCM2 sP("wgO~ +όc0B^В~*f8\b`'W=Vr;ZNT5eͤ)/㎷?ݳ7'@G_R)0$z 2Ƙ.%?QZu6iJUzeyk7&Us#/$eazW637G̴UJ=62GxKVA4u,t4qv`^ @gzQ$T{<$B oL]noqQD'+}^ʝ281}j*-Dʃ.  ~{د^TNGzV܍f8=0vB^5yG:zQY%2}x tu3?Ա^=nRUwϼW0|鋬Je&dvJ;Fl0p0!jhD]T.bJAMMX9)ўS/PWϼꙚa Uy#np-nf>=[P33V ) MFoV5HjȨPwɩdn'fTuM_yfBN3@c$Hied& ;4m+68] jsn;5489no s9k Z~E.tL6HnnU*/>s*MJA @]Ղu-j+ZFO#vb*')Uv&!R n1t[̘cw\+"O ;" :=pFѥ@P#͊+dh+HZo7TMM^>B?MHAyK7QO?4X2$TP%k3q7=޽{BF=-ea)6.[P)@UVO/*i"]9ܽ/۷ڙ#$Z]Յ6B!PO߃=zn/!l,S%EMU||C R(5A @ ?|-Tg*:83';HU^X#&KOJQX"UZH"-a1 hⲛ Ʈm"ʋ'/*/TiЍ[g엺Ss&\AS\)O=8;,T4ZB]7Gո 7F"-SO"hѝJKM^nebu"o@h:NEi; 3cIelvY7N:wn"Bչm iq<^a}?IC jy5Ywyi-ʦ<`F_~@!Q?W&p<xr,XKeTuK`Upeˁ\÷RnN݉oi^Y|ˬ1Cl6ӷ VńgkLcMzH};~Sv#%eQvטP%]:R۵gU9* z|k{tYG݇y9j2~3eD8]?+1cW'ρ9 &Ph-azEELPl1: c(ewkaWE361": (~&㽐])=\lP_2;ѝh?MgBP4c2}"t'45Mm}?3Q,næ[6j-nE{h:/ečbR^%bؒXkCD'Nb$\xQu=Q{8~m.%!.:C{A+5)Kd~d*rk_z\&ϠG)En.X}"c@uI'#b/U1@:^ 3x#}$wNDӲ=AP1<2ukj pVO{FzqmfX_Ll|JJV1߆]LBJ|[fd0:W(?ߝNҕmoFMMi>tQ/)oQVfUEɓ- x9fI~VX1$sbUHIy F @P2r韓vG NofyAٟc;L^2ٖ8L}}ORb qGVx`u#Ӧm :p{RpoSu*\73쎤 b/]`f>^[Jb*OYRgI]|}/]t ҥS@7dc,> N(FHRYR&ˡnLc.dÍ#ϏɫdʖW9Oʮ{oo ˙5N"t-d-5F2[q~Ϊ;T %\]&0']H JSpo $v񓣫6y!S 98m^\}a{48Z 6v&$߿v́3ozUjMW+{[ѭzo?!_7uLˠ' L֯IGwP4o7M5z{o-8uwOT"Nqw۽QY_&ſ~kjR&7ȇgLr0"uRDõ=]4@]׏H ZzMB_2wDFݍOeȘ!S(i,N' mVy@ J>q2C]Od o" ; H(Y+j9|f'++9qo6'2RQm6e]NS{`bNd&%HX_^g(J1im\ łS{dYYHM LXO2(1LRc' mNs085@ p ޾xֹdžU/[2P@0]2m}q'߶LCQ'>N,RR^!2YYߴZ Mԣt;$k UIzB) d5O>1}!4~z(9.z8/{*cƛMWsw{ @t?p6splDo֧UB1n&Cj= rݦ8隣ˍ~ @ x|-o肧K9u/im^5hhJ ^2vˊvz)b(v4ty?Y_״ x+hGZ`NdTī⃦e91L<5vsc_WW"7 $|wˤ$=g$OUOy)aqa2y`(3>ۻ'䰚Œl jvMZa[uԢ;Ї}0_ %@OM (#W+o}\Ky.`\ ؃.f) OSp"n7%îRM4}UFT"3< Xm»; E.J(j2\'A3CKb<1aRq%̏Q%߆ 6eW^<|ﰡ=ݷ\*A8eq+ @t ziw6 ]FAAARRRvvvEEM***M{ZPf+7wikQ6-ƀݾfR$iK\M_ٗDI{?8 j=e(jV64e :O-Om#R_K$utj/:ϱW7]iJg]=}vգ&L6U]y s!1{Yqt;Y.&%yfg6qHqj&I7e  tiBvW#*.tQdkxs +Jk"/TJJ4IO{H1wUbDԡR6 _]SHHU]$dr/;mΝ7ͯ*o!A0TLʫ^[Jr~Tɧ=!*:ec1$2r*t ,5I>pNJQZT}4(}%8 6 K* ɽV$qEjEnp8^)n3&رk92T0ro]ɨm.l8iHne>F"H ]Nyی~6JC2O*7}W&g'OlT<|+,DAAf6cpb5 73huS1,_^Rθ t8Zft$;d!OÎ":U1'|-į&::ZFFfdrB{me^Xml&O$PT z8om=Tf%z5rˆ NNi='fʇ6EET]u{cj83J>أ7x,5|:ߌm᪼)HS%aXd>tF7`d_9{Suoױ26<84MmUg0AbjqU򺌏z iq<^a}?I@%D|o.b?2{monh9nl(ѧ磅n6΁÷-GR/m/;; 8w<}ӐGk5Y}'8kGZP^vpY@+Q 86ncFz| + nΫ`d!vwn5"~)999~~~ߐdc,HQMDC;vmL|ő+M7YKh-:R.0ds8sɾug:%9UTeˉLT°L%=lh(g?NoE@B'Հm<=kì@Wg)Yq 3DWLCZ\ZJD!Nś]?UhH}.jnk'9Kֺͨ#\WGjY?s X] 77'NDFF4DI ;hXTIV }fc7mx/R$";)fƸ}7'0&=dLa,ҎAQ[\!Hp+BcE\:,);I7"O>ua\z5BH8p18}͢8dW@}8Eu޸$O>K$=K+]ut IDATtj-5rrr$n_ h^ruBSz5ߵe*!~s^`DEE[XXhjjgggJSI|Lٹ_6~m2Wz]+A(k]Y|X]]=I*o}ȘS@ 01 !DZ***$%DF J;;ig:$FWW\7G k!~)666EPSSCDK4c}xF @tPBDuZ O2f{U]W.zN&}-~DvFW^E BtwP@ j樚\6#vPuGɍsթ<~ \yAUw]{qeC e΂sDw!fۢku,-}E$b#j.*ŅED 1|mg-=pZp;GRrP,.9yϗ] w/5;8zytKtM^T /7W(Vv/ Y&91yFl5lݝ9ot[.ov U|ue_AUW8|OUc^92I& ~0tBoVU fۼ2 k9b/\V;SEzaDB/$LBd o7V\@uD[#1ZDkh!lV۪߉{pm vg ;RRpGȡk&aj͢>G;ybMۋLJ=zM/&`ٍ/=ݷ\{:C̏ۢ<:;*s픽B ߋr$]ss^PK(4R1K/?c91Vɹ+;y~\V7y"q6oSsˣ߽{妢 "s̪ɡ"Uխ`|0"G3mz1#_ }A=f7F<;k̘ӶwN3V]b+JU!4[C)K 1HKK_||W_N@8x 80х&/tϻ\zYȍߣL!R/m/;; 8˂Frh)Ҵex |vKcog7?1x4")!FYepЖws!]v](ʞi9zy^}}[`d!jt1BǏ4m\Y)2Ui;88x EM]9!{f[Ę0u|ERYJ$ωv zܘDmA_o]"ߴZf t]quE+|Wmަ @Psҿ'Õ#Imv#d,n&MPuݼ(]@ja)^D!.%8EPjv%D.cCAxe9/<3W~_{E,Mӄ^-Jc~(9uƜ =}[uE-pPZ6kkYo¯ kE[Ph-\`%O)O rQ.<-P@3D^::ƉE2?Tm5YkQ4_B|ڀs _ [ b:ē)OM5iaڛ/3o,S0"ׄ`8Dm/#OB}i5R {9)JE#^ z!4L@VMSp*U0Wй ~k+(a_VazAAARRRvvvEEM***d־ T<^At!S*R_K$ut%m$Yi.eg+u")>ZwDi!@1{YqtW}䀅Se\dnR~.2NW`;",-আqZGHtںE F.{Pp>-LEM1ڹ3ZvIL-q s6}e_BO ]IL]}LkΧZȄRvC t? 3;`w}-(Jl{ERfu_<zmfk(gY{RTV~Xu g1 ЋqN@q@8|opW-cnnnmm-%%qN%,VΨ4+q{o[39MZ~*yD!ڬ䔧m<Q"ؕA6+Hb=PE.j!U^X#f#U? )Y5Z#͵Ҽj sV= >rc˓#hPV;w"M k$,d~n)2jJFD7[.*l}GUqV~L(7`{2ma(y!gĂZf&( 'd SyI#zx)-xʧQ({KzUj]<6=^Ҙ #=;kqWjpH7L;hW'%rE2}H7oe0Coca{D+ *JܨVt=ӶK::ztPG^w|OqS,=ØnU%5AژZ^(\oVW[ 7Sx7Jnk⍌<J;o{U}I.'Mh(i6yMwC¯Ow>8xs_zfCsrrlՑm/2Ӡ9SȦ, naM,v 5i4A*G/6: Z6hq3NG,WI=aa0tݵ] >,S"0Dͫz:U6D3v~5PM .vnJʘuS)\=ּ3!_&Ӊ8f{-:R5juhDŽ@ q_`kLnC' ޥkkEjV:::BvAQr7^:BᛗJ?ۑ!?_o=ߡ lȇ_, @iɨq-_C+$$daaO/^ruBSzu`ǬCCGܛypuD7Ft:oJǾP~{jk?C2[y 8@ 80p>aPPmj6_^fTpC>x2&^dûy{ZܜU:l}O >c 1/k:Sʚ#=-tƺyMo|ǗWni3q97'xZLߜfi3sv;J@R%))#,ہWT׶ H4ZWwGNkuC;pZ@L:xw&$"^h8p~˺p8=/k5Qu'䒥2f{U-c7VAr; S#}+}@Lďoc ^S,@ZKWwXO]i203 P|Jyo͕{'OO_rVYu=4j霫&a_Wdu{6Gw٦su4#)7yU;|V&gj$^m5c-<Ṗ?tܐ*Ra>)`7UMXUumրi>|&+c4CǨ|hg5[QQQdd$}:63r/|vsK:L rH5]styG 4(0!)nK8M _;?f\6@iR,Fav$&ڊ*!.M28,6Vy{l34֋5](PyuVW%yP6po;-1təM$fM|北upbwݵe@i>1&W%y4=sZ^z$:Dݦ^ |C h! 2qĨP MMMqq)'ۓȐTбQkӷw_]#PlݠE"fZ% jBfG/hƮxYIx{HpZ''Cm tkĩzqd?C}L s^yKVX՟KpIq@LHhv;)((HJJήᒂ*'^68_޼Dk)KZH`”8Az $b먗t : 0Yu̦UU^Uzv) P5P {w L'i&УIW=tU:t9FV1Ժh@mq5GBf@PFj//P+{T[: 1r(ykjv?\6uTyU//N;Z$^ qAѩ%{g{*ar@'ҕ(Wq/qL^Yu͠I $M8(Zʕ+a\ukkA^-=׭g'mc.:ѽ;}@t 00???2.슌  IDATڼR(H/ ˏ@iW>@YS޳"( =~oKhX nO"pjK9rX/O mK׽O{QPWRkB;}9Zߚw!w "T#Q/BWxRc-Y#go9'ܿRafK@*F a8#;@0Ib. o`8;7{<) X}nEPDϮ=[wu5"43E4C 9`LttvC{r9yoJz7!j|2DM j$c踅Lj;6@sH?:qoW0ũWyCDj96._LH^rl#LPkyLl:-Bfhs}}E%P{/!+Qq=ɽA䕬)X@'p ZAbn&n4H%ZgVћ1 !j'Wp]"Wǽj/O~yJ:/G((L:֍8d 1~QDWpTt@ `\!/;\fQVV V6qy5[L.-!dW˔Gi5 5Uh#X^&~A)-7]A H8#­U5k hAVxgdӈ4s?uepɚT,DZv/ߚ̶}"Yu–IK{yK+ i#9Ok/C HxUk0"JHb޻Ns:7 AӒŮ6L 9:0T.*s){|v]0~'{ "Mhj Υˆߩ2t6+2 FV9 L}b(oп6=t6˗}Cg]֎մkC' ޥkkZ. YXX~ Yk)G ? \bcc?>{l~~ʄ@MBbLSLSPmʁBn+=!#컹We ?6/I֫a{]F ɿVa/\7> iZfy8`->8#6F #))UE׽_ 'j^.ĉ "P$Nf~!ʘtj%+ 2/9qeSUd ob2@kV(oZջ#[rja0L@F`9kWͨF׷/>C8TZ'[[iA6N?I?$}-Ż볭?d>}SNE4u]k?+G77Op?{g}}bb=[;ݟ>g (( " =y?`8Z̜9{;shSFNF*#ϻ\wnR?ss7Yͫb} eOZx.:^WJյ\"@˶SgEu/7jQu+jwښT[X? V`6=-օ4_%1em9PNsOiΆT]ϊOZx;STӁ f3;hKyݢ~I`u+Wu"z4hootzٛ e)c[J?>?MU`Xrd6Ng:wWyhk8 _~qh5{MZ7ެ=f,\fs5Șj5vвB8k/;W4l?4`孥g3? 4sկj[VKazA?ؚ. ABHGQ/דDGl\ioi:3c 0M"\zXJ*=Pqtc'MAj|H1'ɳu Yo0]m_IޓT<약g}h-De߹+0ԗ'SvM+_nx\P׷f4NhßՉEh MJqxqxTGs)"z:|O7L"xerx`gKrL:3?Wvddz!/$$LW 6vv3v6܁ߑX|9Ջ=V$˙+Ͽp+)jxccݐү/߈j'es'f_ʪ6$=aQu-NroO^k|=o$6Mshcye(5ptk92(791O{ ׮&#}Ĉ f?o:۟A.;iTw"^<.Ph\.9͞>I3'4[폑$mVPu'2">SR9ؠ@Xh #ʞkV?o݃Uf&a%sJ,5pM}ZY-;l,+2keZ aE ymh Y?ŨMxWT4Y/'L]^ Ex Tp C>6Ut?OF˼3 +սZgEg)TUCLQk슙vXT]cD!j۱ܓ$ ιK`!2 |zey5RUcʽ$o4mY]%i*vEѕEԹ'g#Dqf@VFkj Q g𔊑),Ej0u 3(@RZ7`C6/}Y#w1YVGn QDJc4zN|FmV.^ye zʿ*T-~r7zdgU Vk`مc^(@pAE;<聲I[sE 8tABZ$3@%_s*XTGrw$|W1K2 i@/̪V I\Y,:d )JQ7-4"j袑w%$Zq޷!YVsH{JVxtq3?Ё?~>oHU~9 JzIR)_Q+,jU길C)))nYޓ-HOOGVw hD()Sid! M%>~&_<\D~×=Z2e3RUvs# ]Ǚm?6V1^ύH7T{E-u.ϻ,A g= [ aֲWU@4᳣MtL<0aew9n0ѡjq{y!(g-ҡ?[3[l>:T=3o'yt/ LHsYVM@Bg43;}`Bgsn aXiIP&(jNẐE62xP-A߈NZvvvΝSPPި>vk 7|I!7 6VtAkLp ~Tf1"@otS54~y~;=7,.(1 u\)1Z@į^o>eE .͍ rbxU ."=*~"]Fmml9`eo-In>1/gtٜY>GzLn 02`G+~v,c^_v6&xvdi4hj`~ 6Zxۈs\IRVc^^x71 ]Mg3Zb"k)&/]Ѩ]dͿKgk~;=> T_@ hao0#`S3I z*;;aP7џ5~,&g ΤpNRH!q&`eWH&qa$ g["44h,i8 0r+]c>}rwA&z#Db-!~иǀzDwCQ0)x9S[TnH{v^$̆hl?}dm!`$k2*}QrC9xEGNJ UŰ\ʴwn/;*n,^Rsēl;&i>Lkѳ NK, b\Q!NU*eNOpɬk!ʸZѕDEE999#-#==[n9)piI8Kdr9Q}⾍9~3]$7ɚ\^8i 5YY99D}d1&^YiX=NTM+#kN1e eDDdu: r ɻ?IUMg{wz @ ~KZXȬ^u'@9; &{n0 ?nd:?>ց?s#N}a'¸ "z iii?[ @ @xEXXX0j:vdZ+"uĴ7itl5աK Q!zN7LNVt¥Z"s8 ' ?"$cpww?w\HHȐ!CHd@/ʮTjχdI|ӄe=Gvf?.K[^Wg\M'ZU@ E]Ϙ10 ?"z: ;44666mn](ZO޸{@E+.}dL9q~v }3~n\eUA}wͺpޛqc]% i0|M Djb\,vOu].|InM^?1$󠅄6$b9@pCS'nXbK% @}{?gW 9NĖb''̭f"+3<+Ɩ\ JD2ַ;;k+Mz.>׽knM{&9:4}ڌ}H1]K[QQ<`"Pxs/\ݷpK{"RTNLп5oNYzI{°j=9\Q7Sv-}ג@g8v^Mܲow<$,f5<(nAcy ʗ[zP#3 IDAT=u.nW'N^iOс䲏ox</%2='kq@&? @jٲ}55#G.Ydڵμh"M;̹4#5odJd #R5S>0$r_ʰ$_}HJDҍJr \gf$AA0ӬzD kjRn8$@Pq{jW\J>d ;Wjܞ3zŽocwYЈ@sFPwΉϡXUOh.n+\-s&`d)3}I^C~@ (pZMfff+}i:6es&"I$*ʉC踠٢x.SVmY$;uGrى}ZK`UKQ8(Z5Q%tx甑ňIBW|B$602 Kpz[QÆIed뭳mb"#s 9c$ UAoi kífIvR[OY[[7+D1Fhpѳ"dffp@{d޼à ĉM-?awmV']\iLEW.(eBRֆ0~ۂIRUF^};&.}cC*Olcy7c덎.$&R)S R"177'xnR@9(`*BTU1" χ9/, JˈJܶmռ6cƑו G<_b&ye ԊDQ9̼*v6"ـK>xզ(|阰nmh>m ?}7A)l0~أ{òkpYR^u?iC 8xgU\Eafs?/d%@zo[z(%o^u3L6kuk6=?e`#mmygVC.%ww& C 6/lNtVM~24D[c#wz2mb@ z,88l(JslTLdZMu뷣&L4gaDDDN9 ~AWË<\XP׀?<`'Z8Բ|O9Ѓ:QWCtvdN9`CJZ]ۋt&j]wp"KzkG%a\|Oٳ7ynlUc}M 34 צsopc_wsZ " cKqf6\"hF -&L-'pAzsN,UF1CK#zmv0NJpNfӀ%?^!䑔OE,LLUMw<2XVt>kԫE;| dFA]}X. K5~ƥWFl}/9ǶQS6 O 5555 $LIb'-ʡ[Rk>m{C5*.b) $LH`ъ['x]Y dW#1 @DP, }x@|ꋦ'<ֶSL_<@˒Z9T-?1NBo" ޭp旤BgUNW8%4jР{n|*⬠+r z6fxO֜J̢1Q\2JFgWl-~yHmIe[ hA6A,GlIgP/TkАfF wzbj}Io'~kcJ ua얓Ql=ãųos8ΣV+ϸV8a1#f~(sY_5;n}̢Oq֗OK?O7`*X=5_{cw܀_o7@"؅Yf'%lat8YVDO]{k@f[ `D3!n>`͐'I1_>H "? ---j c@W_|Tp=aJa喱e*?ZCuYy'-g1\0q#ـQ_V0}Uݲ95T\Ɲje)Y6+0m 1rTYNA ?ICk#QD N,'G`燥D}֏| WW㐑v)Vm>a@"b=I+Y$$ĭG6ə &',@}`SH$((9CDKPHZrw'`@@fSkBA aL;e E!5e~00resZi55`E;MtE\<+)#l>+㚕e{o) 3ٵd{4"֭h/3hC7W6z_6}z]ڛ0_>9iq'VWnC$0>fkΝd 8(h_ j?3q~VZK7> XD wYw mic4#qW |kُ@ V )mFCc0M~1^ v{@% ZhCD筡;wm>E۴7(2 $ U#kslz 3W+BP1k4'uC[(A/)jfbp-pP}YIFF(,86~k63h*:@ U6#a=$m9o 3ţ q~̝ѲBwoy%&$tٸΧdٜ|dpZ֝v_yQ9cۜ{n#}49򤳛6PvۂAJd`d>Ug/>Ϭ6aa*@ ~'ZZ]nA`Hhg(@^L (Rt ʧ"7'@/86NLSd4HjĜ}YKq`p<|.8F& (D@ 8m !)*PD7KM.Xl\R\ ;k6?(DpKPPE|_%?sAts34xQvJ]Z)ʃZ%+ caz-4(q/S&1{]6G-M`VF_K~Mre4S@ Shu557L8-mjje8k"-b_kGP@uyw.F\ ;IeuC`Ӂx$Sk'@snξ찥d'/KuEkT-ǜ8uѭ10 Ϥ7FWBW_-y v\s}G'^DUdΚc)`9]#DB;av}oD^>Âwt&IBľbm^R@P^zҥ:͐D  (@t5Zv=}e쫄7=zyѕ͢gf%[Wƀ8[*'3N6jo21Ɨ9Xz"yL V =gEhYh>wg&44WVR)a:]3]LAkK0Q={5y :lZESI+-I)R; +(N^8y= ͩ-ťG, F =t֢; i6`䬿6衸 3p'QrM0_ejs~p]_@ /jƋnгS]\>T6G^g~ 7#&&VRR:n"OZyb BbKZYEQ-CQ5} {[4*" yGV6V?G~e/﭅#(>_ @ ̟;cvަkCi|<h?sLKKkY ;`=mޫc?%_ lE#E^xMz^ާv_D>([(]“3d}1AUU$=M+(*.AR0ˁqc?>gػ@#! =QKsF ~!>۸݁g@ -~9v_Dt;q}GVȭd*wb8U27\<&OGG.oڱN-Ct[E9Qa o sXQn7Q ~c ?*3$V^# @3`5ytfD/ɴoGM)4448/""";4׼Z7h1 }UC-q!3[ꨗǍܹn憐OL7o.-Jc\WȞo@וӭ&qà6p_Ɏ\nCv:~$߸qL&{xxpvV[NqW><`WMr ?b~2}bsJv]eKS詻/!HdݙvX3nW0q\T{ҝFk a< i=Nέp_uߪZoXysŒ T>Ĵ?%ikMd#Ͼ4'w=3>|;ЪIXckum{UUNѸGU~7Vfey Mgnn7F>򍴔vɋ T}9go~Қ-CGUj7;lz]m}ɓk,~,7^1@ U>:08<*,z7 ٗV oe\Fy#я;=&ֲ03/,XpxtНu\2LYrBK\ލ*=\8m4憰JR-CoێN&GAuv/W.lݤs߷V~isoG=Xlm/6jf=Ř`+E[)M?_E ((b?C-]a,&js4Ǵ׷_]V+_nx\P׷fpV+4<~cJÔx˭ӷIM9q/*p ZvGdn-;7!>{,4]Zʷ)h|& =z7H-bz+n"z+B_qCq '$DT$S<hw ̹: 甝B Xm=tTU~Gy^5UR&Nra)U[ C 0rэ4[,< X݊>Z@{jǑWO5AMMmȑK,YvmC^J$LP͢ưQ\s yv K]]I@0Bʡ%M}y`ބc+no#OpV$AjW{ن>c1L@RJxŋ}t1_ !&,:)(ӧ|Yz1|Pg'q!dW#1 0uCI$:^6DWeƺ5@̻")a֯,$v)&elE *48g9n9 ;᷿ʏJZ.DÜ{K1_FHDfj=bN#F$[hU̯H!O\ +ʒj>\c%M"I[\xx%{I5NFEW+ˮI YjA@YW_ʷQjrd(7]ҍ>׽=󴚗JL~})o#2˒Yϝ(.*81⫖^EQ%3DIѧ-Hrn3(9uJF`D pHD"F kc3Y8E{GgYg:pZGV7,\rՐmgCAhTkLz8F19u\0P/TkАf钙?f2^!$: IDATL"IrE#9]jox>NsCMVVNN x6EbD-b>]KVGcFD*ϖ,bjM<{}#ǼdsIQ~ͫczʜ\Y=鶴$fč{K1>:M]5ωMd)SUň*>C2/9tAArۧpuM4İ%aQiQ2hW̝gO5gv'7m&l8/aZ&,ut+++TTɟKzikb=BzW6$V"1D޽ $??nI}w:j[}!D$ˮ?|C~5EU)-^jv哽gMboxNG>:1V#~#0ᗟ *%Y{~]Нrޔ v{@-k~9E$EFݥҒ Ёz+ൗs/BkHyXarϐOp祁ω>⪊KBk(OWۻ^@iԼj_c@Jt(ğʍ|*wi4YGTS)/Y`ec ^6c??^]jȫkqs oRdqP" {sx⿌i ֏eoec=jڹB6o~b>iec0g;k_Fmploޥ脌rJ>k] +%ĬV0ewrus*{svS]*hH`Ř}EמZ,+]]K{O<oM吺Du$gPpp;_J:Lj\tw yH2Ϗ)vU&},v[o$-h7,>9m&zOre#NOy&7*вOY~ KTETV)#&gh؋R6EzUFg'N$$?GY4!\n &Јɏ˥3e|](0ؾ>c􎞂@d~ 7{x։cHsZ " sc<Z]g츸說*;;;rŠ9),)ypr ^c(;zsfG'ްs<̤7{,CC@p jfv3߰$=V^n!I8Y7<.{w$CWF &'ƪUQũQaFk7 zB@P0VG!x֊^)E(a4}8I =H!Qpw4 qK(1k9*S { bW( (~|R`5E ))4T)@P|)7ag90}E['j3c@';?|qճ˅>cN?V) Dl'ztd :ǹ'쪌%}L$| ax״dUzzlQ j9A!EW x*Ro] d J>"57FC[wFB˃wpRVĞWΪ`Yj8zT'uf=!-gAzbKC'/{Grd6~5f#x FL=oc^?N[h<]zYwo@D+)6F$@uA,'sm쳊>q"+Y'#^S8U$Tg U))dKʋ`J>ih:u"UY "ա'@ y Ld󼾽$U"oۯ7Q[MgI-k1C+`$W7GZ{b{F+_/l:(䮈kR7;A(*oٸfm΅[( /<:ae(P"Ԅ/^3oT:clդ kpVkIU e;zt ll+| w.} [[pbߝyLW}wN]6/=@ ?WV.uiP3},G^w{1Fơ!w8iCѧp=L]h==cMqkBrj@UѦj蛹M[8NUhY}M5Mݗ+de'YeXS0Zq,Vb`NӫL1e5=*OWM=cM˯Nj1j;t`杯7:HXo<& ؊zZ7UKk5"]Ӳx"-?FǏz*-6e Ul2DTlCv|a#ey5 |"k^y|Ix\} מ:X/:YGHd jP28Z"E[xPe\$Q @ D7} vN ^dz!/$$Lh7oW"OO!\ jU2]y'] t+>V> Xl.q+Gi-Ԙ";`îoMqNG;͎;H;,s6.).lٻ(6lKH (vq.l?k b]1@F$DR@:6c]B/W>yU. xmF.E ۦnc1D A_Sz1}an7qYk/̢G>7H@yՆذw6l9t]Jnϕec)<{бQ}t<4Ka 4xײ̪3"/v3P[Θo0i(QW=~J33H4O߾aVyjH|Z֕kjX!6̢NKDCԜ4q܉ܙ%xw*F Y bqXK m:wqvG#-*qgee>hCRl=>(b@]R+& t&LEEE8؁~f_uT`}yBR0Rn;ޚQOаiKjKzzr٭3r6^g7phg8< V^PYW;6~d93 jVj0 1&|jf<1QGHC;qwl}co(^gGf'iu!wNSu@`tūE6"p݁QCڅkW.? '*;.$MeSLLPƣwK7r^K" 6OHJ|;-%Loog60_K.X;h9G 6y&Nzϣ)* [$L9qgD:Q䏖g|;Gr 0B(M =2志)E_WGVkII ϮL3I:9*s|'Mg]AOpio?>LT72$JYu%I:#溋FffkcXǙ Gٵ@emʼnȊxޛd\ZH G/knŔDV4Rs"5by]&K\d}Ev8h1t ^vDYQ3~>k.x;C8hN p@ Ι~¶Fh3*4JgW醓ׯN <ٜ9N&`eV_8~,U]]tyIAy5t$"/؃LYXX|`^QӘSu>j^<A<~{GNwIO.Mڻ#;ϰ.?FfӲo ,Lz E6 F GZ34! 3]eIrg:0z1.'7-|c [OE?ͪ:x[)iwoVFQ>+dtnOqq@`<3$ + ^d}ku3zqH>m䷗KC@ԗQSһk)6pV+tVX?_y-1ݒBrV ĕd>fc*-½Ӄ_%ҒL-na&ٔUTT† *AQq#Ucԙw-@v? Տ=Zmn\eqٺwI(7ˢ>&5`#UF#(3tR8(}=I~y/y&?aM >Fsݾ!w: >g xu9YFe*5^^7r޶@͚sЬ세Ts6{Zuu] VL%N*eLaV ۤS.ՋZw{ ;FW':ŜN>;( Pv0.{",|VDiΡ7klE٥Wys~K{w>@Ǚ /NSSgWGϞ=+''J"z^ћH7PPkvZGx8i*hNlSTiҕݻf)sMHz" I ,' &Kv9ofM20tL_:x؅b}Nq&('8|W***xLؐue@aD^Oe1Xš5I8.ڧS/@ ̛;KIhqzH@}; C]MA oeHYCb8yդ56 ˏZ2Z|0Cb[b.;_ڲ:^b*eCάuUPfS/*j%jfab˯E-@a8axU$AA:(ͮWn56]UFLL\Rj(,-z[+KyYXU[UW**TצaJG md$ -@}Az\Z\-4<򳠁yG"Kk|No9{ms_(\dz{*"5y_T:wR}R Snds|B=8ӃY^ˢj A5[`G+ G h}HV3JS^$PETS# 9vr4ʾ^ .xjv^ QTA:3!9[Nv-pZiW%!_|eETFVn"<04^ %HA~9BV[f3PȽ(k̞ c)˕;2ԕ2sPo'LqϕS-Jʚ;vAx.xaqݱ'3ly?{[%"o@[xu`JQXB=: H#,tF7͠-cS Z_ch}-AAZAk!ȟٽkr37rnǡn   BAA~ AAA0,qV8 AA%>enUO(Y4bO(9l< J" N58\dA{* ?4c=aؑGl;oyF_u4+nO_Dq~m^G+߷aȬ 6Ĕ w? IDAT-8FSg}>x'0йS5Z84pIĭ-(y#3>7AUn_#ǡW(B~i(BGVV _Ϯs Cʡg_c-l[?@{cW/5+#8/w;$W%nxNZ9Χbf<24$u)"0@Y+Ŏ_rtئwL>Dg;ͽxV EЦOqMc{{^փlޖDFoC*Q,_I]cѨAN۠K1JR),~-:,hGA-]sG}>y/mHfWFnڼC>oχ(b##(;061r;7/eҹ_yʋ3h7gO5][ QD^RmΣZq;#Nw.La(BAL1^_CQ{Uv3h@է6RR-?Gro!̢G#I#Ph<껣&8l#]# }+Y:.%nsK e^f+ C#E4O'" IpFaR>A^GVIHK}4'슈 X3ܛ;OٗT:P͵[:gꥫ l6EO`k! (G7z⺷G'9Ư5ޕ1+##֐|<ݥ_ݙ3t%^ BzmuSf㵦E@ ?l~5ߝH6_)gv^R9ڥ0_es{ ;[Mvv[Nt&. )S˻"7|nS7tC3hhCRl=>(jѵ]:kڄu.㎾ɜk\*+Z]a:T0Z.f: ZrFǻ[=T}d#~NllӋJ5cΝuzPvEWb1u]_gS9)\[.f=뼆8ZY8 LGMBՊ6+[.**zy՘z~ev#^qܖLk>^yMp7A~1U/2s<ʉ7<;8uKêܬh/[wRn$dW鴽Ә?j A7(IJoB]V7u&уz{N '# F~/n4VYbɸ삔B'_c#K"2rrrHkgmE}JB_s"jv凔ƟT,O BNoԊ_0胧*$rr.;1P(ER.J쐝IXYX"&se]g;c"3(g],Y0R8bբ YO~.+Iyu72p}cWpev)`L['1))1!WoI ۞jwsF/gm7KC[- s-wWHzbP凜 J .BH%@BLxf"9p8 \n9RWCZd vS%ʐ~!LIf?T(`/yumI/<.1Q:&[^vÈBs 3pk}xC_o[ȮfU9r{w`},#~Y#c-\'yZ \z%cCMʥ 3?^d &jԋעYY9q8l0_{#CYxlOνPƘr)=N'qٱ9tLkY_o=DZQiL:TeA5s2τODZԍu-g]Js7Χ_ƛb!gM|7(ӼFW۞gԶHSntI},q[6(*@ pn̍-'М+++[[[III+#1+ D$9OJ0~Yew ,"n@> Wc:8X]yr&"r̫JAŬ,i K*DE|f 7|ʫfQHRjmlћ-0P# L~|A_HK2Be^í/(X]Jm2K*%(<IBE"s|tsvqnȇ>eU *i)ގ~FaZoAskV=gGlEϮhE۫#/wp`ƻD% mg@ 99yyΝӧ;9* (m;]Lcg&)YY\fQ|.)_9vADm\x)PY_VSpoRoS gMEιrptU:MP.Z  *~uu:|Vz@7wVmd11;}ˀ +44TZZݽKF:u|Չ/p*OEYm@ۂZ)>VP*U,VBgEo0f_c oOGt>/i<֟8\dsV&Wd&Kb&-൑{OƘ/ޜTp9m`bV,Z@W٪ۃ0XAPXRJa*,pJ֤u_Ty !yY6 DQ%3Hh!cK-_sT}79Fu3O.-aiGdG㶰߭VGeo t0YuzmmkY\j[@$=BX 4PH7]srr ՄhߵGf 166,ȉɒ][hS9 Q3rѦX| 9+ӗ>vaXnk69;՚fھ"^ K0PKKij4ǧV^wyFc}-AF|[%W45517@A~;Amٵ@?O9R&Cw39 H3Vdt1trA䯃[(˷_Y8lзi>onr D9qg+jӄi'RY|DшSlg߻ G>>.8ZP&Ԣ|?#Hw9o??_d7//_ʮ%U640%V;Pk vUqwO^[שMM0QxFP, HskaRۄz|ElqK̗_5KKN N0LcוS;${'ҩGt;Zxh\ٳj|j<#i--\72lOydڍij<&7%d8ׇ+wn?tur.)條]kDG#ƫNi7R$v/8} ^qXwγATR嫃Gb-AA~=DA7į4a{}xYl'&n9L惕ewv򌬚l9=5a2o'5?`#y:&w\w ȏ#/'[zb_J;lx#~C&u"׽Mѣ Z½}Kj;y]N up}u *UfmŚ% ({#)s*=3۸oaV%i7ۅUǟ|?G9ROC94{H/]#]'#o5=i߳r3V`k8|٥89z`]]# .\2[{{w?S# ]#S%Gc+Xg\Y7LCHnμjjvK@V!aJd[rzS5Noȼ)IWSD'Kdѭr #uQv#<8d- x 7g9ۦeؗv&Tp8 w+Uy_ |yI?Fcw`پO8=OktXkl:Ak)'nC籿 {y׮%vei˒6Zv=θ?f"ۄ>%[m66$|Dک]4El`c`5& y&Tu1Km-;mM/*t9w֍CZ]8(k" ݁h*!˛hp"ȋ`8J_kkd^UQXFAr>WZom 0-,R IDAT ,4t4t4 d *1;5oӾ^u8A~otuf:zQ.0a3g9OߚT"?f;, \y;1}Ji" wBf_}Sꎵ] ; yE\ܳSI{Dlro~wYumsnK;:yL@Wp˛vf]pjwA._k!]_0 CD0q`~lBMa/bާ%&$lj;f'69~܁Uf:n쁉䗑g|G7S/V~~~HHȾ}6mMRCzI1Έ⩑4ĀYVr!yAj,m' L!z$:%S/\/wZO3S97&5`#UF#Rpj(fc$d'ĥp`F 'Ъ@ x- vww|lZ C@,BV鳠[y\ԟ8\?8 " ~(t44B0tul5 ;ygt`RfE8qi9)cYXRJYcϥ VEix-fq # 9qEՑlhHnCPUR1x$h_KwN?W %EY23loi '15iܼ;5׽x]ɩ* JT塅L^/&'HrOOH?b+ d qS9ω n'l!9lړ%h*.-&>Ľ @^エW6XU2teHE$f'f}rQħ9~射[Fjc ϲzkFFQp@ 2tL_:x؅b}$hw6.u`C--}:5"1j]>T X&Kv9ofM20R&[%L杨&wقcLM /K!vI;EttVeB"WS*ug-Bu\ڪ m4*K̶?]Cy,SٹdDʵuP[S&6*L:~3,2bBЊy,ox:brZ6⺗cu'9y7bx[Vf%C{ e«o\H/-OkſcQ'&+#׺w>p|չm5~f+1Y)oF{L\NN;spZ') glU@^^^?"伃ѥnƻE'w[?=XRR *N4QI;!F+'L=VZ?_ u3^k0|?ּ"V ORD_3&oែ]/хӶZ8|wu./g|qEz1s gފϹvШ۞/]~SBpv\'9k!H(d":fýP#7?0~\l:\OCVwfs\ q\ ѸNΕ_ٍLzԟ]u DIk۸𨗋SjJDšz͙|y\w)O/2L}~CUa~Ҧkp/?}W 74^;Z&+í ?aYd2׾l?@'w~Z|/^pH;.7ߍ2:H]NUqc'9GA^d XC5S}"ߋ [ 0>?h AͰvL߸oۋ  12hnIW, H[ͧ|@lVumA`]5K"f#kuh޽96=A\/Ssm!mmx-B?۳{,!VVVV?"wǶߨIgA]OO" 0˓  /_2oHg/ gUVeFj :߷/H=6hi?ZM%KK W?`J4%U)IJKH[|3 P} oMkz4EԗW^LU`f1(*cy)cVaʖ[XEWƪb./*"*&gu?鎖?v7O9b&/9p؃GߟJXX7)!Mnu*(;/Sxk9&/>@Z[W ^uzߺcޕ"|:%=?, B .(TZK&i?=LJW=ؼ!*2~Pgs7n'J ?4&3X7,Jtr a+^|7_'3;/e ; W+F_~ .:[r Nn."}*)}#ǻn6?X۹ߺudv|W ̣[Nb^Y+^3km߽;=Z}V5k1bj`xAcy㛞M%\cWg_?|I'fӾcnL{}Vfn6@Zޗ},zǓX>xs=j|!ƢӉ"Dʈ!I.}tͬ%`,L(VCG6q@5S^({< 4VGjZCډiNN.㶽J(zCeЁ{fF-ҽfu+Tէf5S󨉨0'Tr#${*Je=HwstrRF٫^ [?z!O;sUu"-7&O@4ƯǮ矻һaUlnVR? >K4}Y&^xXԀDTiXMXuᒗݢWu=C] Zqu2v eԨQ?~}Ymm-n3R?_xd i/F.}1(=zD`3YX'XkDҞǕ S'՝oIX8b&K`;}<%>lmڨ~Oby+?I) 5.R$ ?10 '3L^3PA_ct]iS='j4YlHܐD!{V=^4xÿcwAz$fvS|&w 5 LQu%SMU㒃o B2zKsB>BbB A 8E+zY4Mykz^l/:w ,W W%H+FSwI}9:dh K蠫7@sjD?CT2KePpIHM:p"U0SUHLkq^\Q!mk"G`}-#Jk?Mژ\PfM'߹1OGQҫ"-5=GO@gg?M{Mc_I?^TmB ߌGc ][2bF#n%7|s˔v}d-aDO?NjƃG@Τ͉81$`_{ozϧJ;4(vٻ2 :N_9Z)<] 98@"֧N{&?]LAhXZ cΏ^z  wecg;K0L2S :]]@RWu+@z r9XZ\4}\w?40f3}0ץ[6,]BAZ<0kK>&bhV@m{w,cĖ7=5Ԩbc=rUAiW ;Tl{S?fv Nӓ2r@iތN"n㵈2oF#}KzP0_{#CiO 42Ij>%b$>rWwFk7 kN{-ꜥ;kcFxї-5Oe*D$i5h,1񠓁INfWxrBކLƫ*u!jkg.:, ;[twHH)ҡ- v!W퇉݊y [1@QT{cc039e9E)EMh%@-(\ 6p܀ ndTuиAz K_\q4ǍwTÀ܇4jY'yzr^_Wu WeF]Y |Q_^@Pi:^2,'G`gf#i7i^W ֒P/sMt/aꔊ$IkFjn;qdi?a XEa^y5x}c./?' b\>0,e<ƿ;7G,2rbtssM6n l6TKB}6`&Ns'MiΎ`S gYtڑ&Ue^&/}k Z0QS7Vu/g릐3g1ƞ:|([q`Uf<ˬ‰"^-1sl p/1]>t 7R\FGE 1)M~Mxwx]TMݣ)r쨐j%~ңʘJTͪbєhi2=r䪔wueū<~FR~8pجzҍ'OCY-{Y6Q1ӤoL[K p߾ko^~Awp.Y7~ʴUr^ы^"]K:ا4r`S~3*z+|447>P D]qfVKXS ^WؙdV(M }YBiȅt"777)));;ں ***% 2x~yn;Z B"N1ѮJϚ5ٻ]Q]}l$B"RX鍇x{㱞,m,[JX[=\nغ' )ԢAgt>3vESQ dDz߈{'K+ )hZsbV/FP4&hLpzt*N20@Ce]۩OwCQ,DH]$a OW%n@W!rtCM a^Luo~*5ƬPt]xt:a =(3++I4sZ`kd<`aW ȏRY: IDAT(ȨRk{!'joVɊcՓs=)׷dɄ'ԜyGJj)דۏ^TJع}~.B 3jly( q7ΝN'w +81J+5/퍍k\&t ̬%"jD`-(|ø;zjۗzT)pPniW|-ߊ<.]?2r[~m9KȸoU䀱ym?ȥK$%% ]r(۾ha@kcNZ,?߅$d$GK~s~+ ;x`6r K9|zM{צL=0kaV"huדݻw] A~]%"(YՍS+be.WאX0uCI:p|ti0NkNog=bTFoؐ D5}7ZcfS*tRLL'X>ԁA3vLdồsJh %De虄촔GζAZ_9"=/&_4 ODA(B_Ù ^r **љّs?5^DQI>^N&)iYxeMX|D~Jr [O{GԼ>RGZN]{릕{9!N:jJʆ#7?$؍f&|:N9USQY]CMr)TE[Cq I1e _iRw ~2t''H@~o fP, Z=u- ԗ(䞄UK+U!eŐ^`dx]8ƽO) Sdӷx̼~Z}BW@q#2淪~ӏ> #kwI+"V*i8v68ז_% \%ڴ^P;;l@- =P\@x->!+JHtI|8Lp} $LD2/K+ficܛX(Ow2@6`C |xpY+0tCOOtu2TYu >|&Z9A:Ap>%$$TVV͓C3WAn0RD,i3쎃޴PG0oJ٫>2JjJKgɹ{ݻw?9aG!ΜG74[ ֫`kU#8@Y(BzZzz.{@O!KG`b#4mN%zyi=AUK}Wg]x*Q~Wॉ9pϰB E{DCO+􋗳[JY%VeƳ*3O>?W~̜5[C?dτgsa43UZb-#k!=Ç Ɵ4 nݳ%lQjs_WM~ b7ӎo3:G:Xh>vM1 YH^|3MkN ꪪ:%D={S5nO')=aDeRzakDjE{gZHRTTTRRr劇ǏR]rK~Rw+wOuqcݯϾ.7mGzn&3NykL(w'6c}-MwoI-_cC9I[L?^wxU>')hݓ|'NO^q+}s,أoo ަ15DG;{ih!}G.^[cgKCCvƽ544t{^d״킽k0 zkhB Aۗmбi.5}zv16\?[WxvgfQ9@ƍ1?oߢ#8HhHL0rgozl3Z[փgLƇ]& ;Z6']-(lvWQ6zܘwu'C<[;=uIǍ5eAVc64fi]7g8[ٹTAZ/ +0e/(kĩBmLsSƾ9xu^G'm?mwB]|g5SX{\ ̼衦N{s!8u7oA( #B3haÆyyy9:r? D##?ȃɒ9UWN^syڋ +'Lw0-.5V>;عj1Ǟ{qn qja" Pjxe^^]$nvB`&nH;Kt8@ç+_z?f~]w7/47&H\ AKju 9/L="^7\(dqO[oVxc~2:*c>VZVuuX#Wۋ:c~82kK'܋]9z[ ٓUsML37MH*phi\>;HbUS=W>x8C|FvtȎi w.MȊco׿jIsȁڄ1txUcW򝌸T:`71|OM:c\j- Z&1S.UtFQ/A,sl3ѿoQ0$v야^ 2Pine~:az0KD_nygOEK>[C(*m;|I}:JOQ()))))urBH#Jm"}uRn'D eqtsq 䅮3O'qM6C!2 >. uo-i+8#PԳ*}{!;g7  ;ϝ`"JZ"Lɠ5SiWBN=t6&Z\K$6SRTᮒW>5ŸӔ!N% $c)" }%$b4HϯGT 痞nZ:UlTLXu|]-S5 : azYLoW :SG*="Vԁ4H/)%:PIbf(VYLL!)Zp;G֠rSr4Ɂo7 z^/sKDգcS1 mw͹W(w֟;Qa>#%0F@.Yd4S#& 'X9!H[j=hߍDHм@~!YO,YX&|1+jwyeV4IuѺv։YK6/*HslWaXcV>x;I&*ɃBa^UXV?70:#a-U{vEX*dv0mн鿛id/~}uSK>l>iކ>]rA}ɍD!ZHTrS|{h7F5֔3.7-sOݾUg;Bk7ke1(^-xTN&X]%;Sw-q-^1 >? 7n([o@2yQlfk( ~O_5[ qEUZ*VǷ_SnWTT[!qSH Lۼ 9~9;?H("uLv`ggr>IQ}}J@k[;[;S]F2O;nR.UkM 5[(JU4Oh[y]%8 }ʖ9zo|mBUa=;.% hdisK"Y?jjQF'E?>G$X?^A372 w\QURR\)icϞ N6.mK=DfwF'5Mz?U|; FUSTvt䘄R(C-H]>om.˗:ItA5ՠeVyk7*@QIoТe_>D-(\ 7z#Yp#qu\CCZ_pp u@~<'S-b'%bWJRB=dڰ@ 5g]u?-ՒS0ZX }&hx QX vnQBDB׬G0x**n1b2I\JDT\wڻs2s+U =;.״_S=?nWq;y{ G.%r1K.>݀:. E% F kO;M^Ǎd 4-sJgux5nO2e(uyǸעUIѴduC3C/ͥ^ؖw[ӭ*{j(=9k|Ȫr:AʅyJ~,Cf{`8FpfcitpAb+ ]^zӠ5x 10rb// [?pss݋-k^8L*a26_o2nz͢}S诖8b@Mti ^n3ݨQ1{3m0n;;)XNssŧ0:n'A9xOä f:XI+xn?OrD[Iؙw~Mot(27nS͖6yN OA, \%㿷կKzD]1t+Nռ HPd,'e|nJv5{ZUB*6wc?F-ڕ/,&URSY@c-{RmlQ1ӤlǷګ#/'EQFRag.o$l:3*y -7GmItھҋ>D >'IdʞSn,AN[:cE`.V!MyǽB\ AʭT3(M,pM+q2SU_L 7u9D] Ջ{̼~Z}BW@q#X4[WN;o|Ckh02vdԵFP_RSX%)^$o/qzrJLJU->H\lᐴҰ^X@u_ρGA<^ruJ1nKG&,v.xOـ]q7cm˯3^x2%n;;-X VlIo~q1nu흞uoBDSx̭=\[ )-r'.Sn-Wťj# ȆFjKUoX9˙ ޭ3ڏת#0 B8j,'&,6- H$Ҍ_h5K] dqə1&)y8I3fMT)gCx.(4aMY"=b(+2]? |,ȥuQ&d C>&2ZSNh:&h6cc%c/4Nr%l|.nl &+Z.M9QWӾjݾ1_jÖ4ym.AO[UK%7ȯ?\rDݏs9 f>{?dOs.ɧ)ql͚Qf\N|pE“b[wu.3vdJ8IQ(x6_C}@ 3Ts g6&x5[ iŊ/ƺ~/@V4F b{LTT%7?iok~ hOɹoN:-]}@DUJRmk=)(|zxC1c;YA~0Zhݿz6;1 0 k!sۍ ~,n!9}Y ~x㵐^v"lb:ﮕ#Q\$i)] (B稩[Xtk)hh(BkZi=s_!¤?qYa103 ] !47 =Ç gAA" c-, *EEE%%+W|{VDiܘ~k8vUo>n'3`2jMjLqz߅^-Av-Az{aaLwΚ,@IS<Nxz?]]gMէwC'kp-{%2n+{4ߢ.І=1 ,k!ңh4ڄ  Ǝ;?~\^^N]/WJe on:VZ`dEy7^t~f#5#5t|8_vɫ"9AMS1=MXei9fk}pu!ɈKBvTeK[]L\cKF\*`3hn'\^.Ne6x_˪嗣(۾ha@kcNZZqS$ACzlLJNØh_m SoK 앗e/&wPrRq+$}n[ QY*e?FF4Xf|}\N)2y#<J7]{z;;]Z êX='znpY˸EǞ>nt7lƣ.'KN_yAO'PTnо]}n@u 9a-W%l|._(E^iuvmdYk' 3gu ^`n'Zg yu7DO4+"k.nt''~Z\vL:}q>#1?rbع J㢆ۑ6*ǁ"o|j?>u{w-ǟ:A~Eş<||p îPZ-nӖD'Y uI/^ #t_(~}hlRv|QQQ {}g75?rgOw3~Za>#%04O:i 2P78:q0TDEsSJE 2P >r%L+s@*(B\~4t״n-4`o_ʬ}qnZZ=`UahDUO0F: (%l޴ /KK~Ҍ%' e›vS67_(~YH]H zFŰ>*<%dPKHEԾ6nQ@gcu뉑"oQ|j^ 6<(. o#HbO^_Н@}܄qC*A ^זEُ'5UU%eÑVI >C\]_>KNAYEEc&[ּpܹW_T A{[ՖY_$ 0^s> .`U*b?uxAdDQY:J⢽Fz|{m _ob[CMYAhW5@ë6^^8]h:#\{CMEiU\s ~?S%9yY KL`fGk}ZP`dVn irEB_CJVe>'1ffOF]Ki$%%xq"apΛ0@6.#|;7*>pkzq*pbdNɷ+Zq'1[6ť-a3￟VjuH$\ׄYt묋PnewK6֧حy'=}-9>ep)P @Uq9yF^ O !n9wչ@9)$E~v|l#H)(,@+w+A5:sCQD٣/n:K@3a.QtvIFJH~u1K ܹiK꟯g4yg= Iz %*j .Ȕ>֧fث=sxUM # mRd7懆s)QHbS<b~4RX({z!tp Idԍ"sOJTm'O/ݗ(G_V7 8^,x`|}-:EFb췤ז+)b]MT)o:h!/46V]2_i{FaW FAT%:pia\hZisɖ}nsG0<ւjh_†פ[fkHڔ HYӧ|QAA~ Vm4-&n5J+erX2ZYx): -|FBoNRFI imz61VYJB:SZMg n.˪.etTIАR۴_rq+J϶,a Fo})$YtLPDRUYo~`#CUUi'G8`"gR;^GD;(BAȹSZ4;tzoZP5ftCSE]ﱏӳSէ鴋&Iy/.,,,,,,ai`;U))iI^~]wH}?bgމn%l|.J{7a^+t|WP 4l q*7}Lz)kZ_LӚB"rθnzIDV qvq&CV y⤺N#! o˞H$<|PWpoRhADEE.߁b:X_'밟] A~WKo#0-  ;=LBo Z !$͍  ȯ#J^7G5-[AoAػءX |5)v'C2mA`Aja88Zx-A hk!ȟ30emH   m8f(BATuK$2WkұqG|+be.WԥpZꟅb$~aaaaaQ̀VnNwwG4,7 n2A6:8BA'4?37 qͶKR?zg3Fa+|f|\T\u}q"hM;sF,,Ok#c+Ly~>ݏO̼衦N{3>A~Q8oݱO~c.ڵPą  ?Q|Fo@hρ Ǘxả^mceeeinl9gۙP?spЃ꒫'\`e=t͋3=iJ݋5/wl'L_yΪm S`p6Qbmyu]ӽ H'5UU%eÑVD;,?aR 7Le5C/3:YrHi9usk[?Gm$M& qut~)CB~{3\@?q2?0u=5"jȻ1e% IDAT Y;Γ ȟ󃇏Lح>(BAUO'Jr O~"dlm4Խ=T,0 5d]zp#6W+R}Vq2H5^_V*a},2N~ *7/aqISi#e>f4 `dV5nSv(H*o|!+3;Fa i[ y)-Oo"_c֫Kd׷-02{uLh]~s֟;wDUy@}Nĝ>wvO&;NO2q>!{^߉)R5ufE?0~[Kh dq!}D05L$Jӊ;n/#x 8GokO<w'sũ C/֦ZDٻ밨6ݥSJRVW;׸v(6gҽ{|eEA}Ͻ̜93geߝ8OUALZ۹MsΛYmx%W^ef@!37}ldpQ-|WyXw}`̫Y癚L*6."Xo0:V$*Yj.$F޾>޾»Wuj손$@8+yg1Sn=e-xVr/3p;"ZO QʣR?M hee¡/~"HAx%YɎ:9J*niC^ !*4tBR c]ŧش?6;z};[Lء̧ ዠDCYΰԣ׮_~lhL;ڋz3/+nm^MD/T{ DZ*K6%o9~%M=+rc N9-yswLnfcVoeL82I9U NӴ^ J+gYGۮNGi\3=joLJ[Lȣ:!BAjj׆G\<|Ǔd@961AD Ė]ԷgQ e:ĔB,*7 z!.ήc@dI)r-@^4uՁog~@*-?V 4=*2E$SWDjPC%{kWV4{U\= 6= 9z &XdFV<"'7ػrz{./jg?M.&]*+kͭ9%8R%и2-BH3:JOuk_UWEn\mD100n<}A H.&BDׯoY}8Yydin t^:>3BʚTO/1rM#Tmv ' 6om̌LGꕜT7WNȬ*VxdllΚxɔ [nug\n]~{\?6ˆ}#3S9SZBRIEetW*B?pv^5&#L[(ꪥdm4f߷ }/."4;3vֻ!UرzU9;+Fd"C{Ɏ_ȝ/PWvݴw(8ݣVz/4U&},TsbwlpY}ZZAN'*dSg%"Z`B!c 7mcƾH'3ڢņ.W gmrW95>ez{;cUTZ]Ѥ]#iu6o$ҔET.n@wi(ԹV ?F0VzF׍hz-B;Z^k'5&6:lOc&h8w)/!BEE[ש\ ׺;<ׯ~D[5}OӀ/B!)3%tu/{$2xsi3w^] 7Bg0!󕅨u~ B"K ٴk >g5 g'4up;Z"YϖPѹ럦SG3s1R6ܽ2M6fgJc\Ktv)iEHJ 7ߟ֠f?/3ޕ7 <c[צJ]4+Y _~IS+:|ax~ck!B8v6tyVeFGFYV׌Dm}xk@_GFE?Zdrhbxzgͽ"O~̎crؠWoD=Y粩'ٲg*t[G'z]m!W߼ QXG>*o^Φut쵠&a!'L- Q*r]1ԇB!z-cFN7z yЦi˶+P""JiO"CG.*kcUosglNw6&aZ6K^3ա<sX}Đ*O__ ?dkBPZJĽUBXkCZ!T(RBpB!}k/^?w쮞v<E C(G)q8xwS.bezO)BP_H )r fy~, d|9 BD!8!BE͊{~בWz9y0a ;؞'_.ɂU78`S^? OU(04 @,t|[1ԇ| ? BZ!*2dע)~3#I40!Qz5@C3_߲.9"qh wS&UԿB]wgܦ*t}w;/}G12YB0rnhI/ךb2F! e~{#{[*(!D!BG Z!B!TZ!*2BE$vmz#V)JuFawo(\ۼR],qu/\EaBB~-+[ZvL䴦lk]h ͎flO*]=|]}65uÕ,;2n[50#M:CS^CXs(ɛmjz%=8ƛJۄKy>u%ܢ"B(J>Ю噔xSK˕dOϲOt*l{ lh=S=[ _֭Ǭ<}Zz7Vak9J≰#]8yΖAμi刿Բ VIG9ןq1B$//Gc2~5lgϘlld|q\|1B!PAs)Ҭn~<*cشVB쨓Wuj손$үn=e-xVrOfew'- qut-jT,Wտ/3Ύkx<*X{c:.ځ=<|]w|"&KSU1q,VW}G7L ,Rl۩G}qcdh8SMeKU48I;iqe&@돲\T`j:GҞl׵ZIɏ~Rnn_nI'J PB!PQi8X@#/ג%]uZB Ҷʸ8:st \b̦\]K9@U*Q˕v-h$re5Q!Y+}@ekUg|)4;z9y8/?إvD[I`ZN/qB\Y}.1|.i ?1^ _=;ُ7>fj l_ZO(F (~ݏ?8ѷ(7g!K#k,̵m=K<sȭ$Dl}E!4[툽 Ok<3eqFe+/3hZw֯* 6@ݏUj;Ua[>u&:[FP[ 0B!P^KԱA޼zeSEOJNXxdTE&+f.jۈ;-yGqUTv=&P5'rMYB";Vd@Nؚ>̇QOO{o6Ǫ[XO$kc51j#wP6ʓ/߱gAӆ'8!Oa֧1?GIW՛p_:ed0?;}Fhh$$02w\;_''lm c-syGy#+ F6]1oJ*%^<#G\۪(y cQKƣِKNG:T(ag׳(n p~q z{9bYE X !BQ34hP7;iAy]ŔXYh{ܹzj$kMy0DN=?J?\i|n>+6hWAC\زˆ!Q*($hWKml=7iԫ 6nelX'ƹP)_`4&2Re?sO*j9Sq O6Bu WЦR10t>G0 MhC?aa0A(Gp!B! ZXOax*lZgU„c{dЬ Deh8iO]PLND\{)WВ[M@!B_G4B0!hZL !0mYNdP (sB Bqқ2X !p{֩] R* 75&;B!B! EYB!B!_8B!B߀R>ޢp\ !B!^Zl8܇!B!l aH>^!B!Ez--dB!B8VxݳP @a(!@ !SB!4?:j>uWqd+J!B´4,09[]ЯΘf&9ܒZ!B?+W/o_o_OWs!1r)ep5]=|]YpgefջNwJuF(#T͞1hML 0B!q9Lӷ]rޏ[(G [[wk|hŵt\$Np@_pK%ТbB!3(۽_g?oW'OyliycZutj2nz{{ !u3q[*]~2"BV ~AOMbQH@q^RZUKAWǭݰ}qɴm+--vO%B!~Soއ.!׊۫4Ⱦ7 B۽ a-"B$UǭH>x${;pdɲ !]GD0{7B-9~+:vH5,B!C()Lis[I€8x>՗O^Н3@rB7Emz#TGDL17++:A!|I/OO_x!ݼ4TET:7 l"(^@+D>!k`aӛN3tc`k8avKWnjnowבjڬ/$lZ+eS1>v IDAThgdh M˳b 1ٻy߹f&]— EEQ "znn?G>b5Fm97 r^o&jd#V[x#RPc~S?|W|@ɝLH(a֦]To2_2θ3Խƺ eDR?ow.~!mmk}Z]4oBhoC[q G֠ [nirX:*Ⱦ(k)Fc^YSPO>9v:NYsw)> Thlmk֨Q&_SԒW^K㞃yoD&g>.6׽qJe=O_S2.ݨm /Sh8@G7)ʾVsWF6<18mCos]cYG:\o @\o[{!O=!>ȵn s:ο <>d`PJHJLɊ'_I syz8|.ޞh][\աF@#`A^tLa,;# aSBs|.G zv_n+ן5JXԚYG7D_)AB? b^#pp`aW_<ݍ+^(hNxe R BKӟעB]jG C{jBC=BDuGzr3j>SW7 ½ԻlAKB!B+ci|6!ļtQ;C !#`˜NK_:<./sYmKar2،̼θ`HQɸԯє9gwpv'q`eӥ 2n:2j,=4s*c 6ry=:OTŹy^V03E66%5idC.?E S={K@:M8+_ʉ>6gwgҍ̩gOOlZkvK\~հO1uۊ$PΏݝm? fU3j+P~lWcv^o Dӻc|du;9۔clysVjo,-yIV)o!r#m)|wtZM }"C?+UB`eMw\:.6w߼~֭>`>]ݻX֌*/չR)X  D)σ(W^7__޽1|e cvwD *J$߹yiU/%'Ȓ\K/{xK<Y_>OCk+sÂG9sO aIyW6i˥K Bd3 BdC}zLmں& 8_8lˑ&;Fab\ p <{.迮T16󹻠1#ÀFm^1ZShZ#_n;Ll:U<3L:qE{g)AOkS~u+ IQ@GFhmynL*Eln+\봎\v&7%$ͅ𺵂 hnxqbǿ{OX}i!1!phDmםRyfd4Qga UR{pIWi)!QBB뵈.kJ9vÞ_`sYǔ>`p3}]22ժs_Yzik¡^Kӵdd <&|i rv=i9$%dljmt}Ԩ\BSNdaU+xMz~v'.%}{8vb_i|V^gѭxPǩ3gTyJ4.WU~X(&;撰yk(=qmk7?!ZM hh&z 2.oXy.ιtbܩU⧻ V2rߺj BfFֶ85?|Y;jQggt>t{PBNJL *;7$:cʈڶz]Ɛsp4ۊ!$bi"=Af>h)]~.v5jnNEg\Hέ 2дMJZtDc@.>?mas1 jWH߆z!BH#)`T~t>.f @^-]D@+wia {{uAF}j12[PuК ,ʔ,mohVoQsQB&="U>:G2Yd|NK,t:cZ9H_vOs, ^q|'Tc/ׄH?+ ʗ(%=" װN~NȩbI# oՠSncS94ȟ?0"H45Tv5YyLx6!7,,,gJx*g>e7jͦ&/&bsz9HefmI1ZC[II+IR#QwAQK)ѢR1B:";q4S9ij!n+BMW۰0{!Fc"߇Pc}<$_N4LVXSt{! 76đ;bq;-u)/.5/_eB4.Gp4W]?slAQƕZ4)^e |pŏb)>VOhR*0 GB'C /L,+COG C?O:\0Pn! YoEf|1+>VR܍!c-Ie DH(59V}N\| 7-3{h:v̌Nf640IKMոf++c%>Z3+;|NmO<]WWϯç÷նkcs,GW?#8 Jk]3usL2Y_*'\L ݼckxѢ9x3J|ԓo8!U_6QĿSW?w0 m<\gn}lVa@y'P_KwMm7e䡙K_u\5&OYњ~qnlJBw.nݰ~y7s60M[0%kX_$:^JJWM}jԁ6+pަir5BQ5WLYtNțCKv߶Ryh5g64*ۑB:עrt-W޺ܨIwoLhqD4CݹX^@0RJ9JǏq1 #WB'"s,#*;ljhDQnӊGF:Vs+q#uïLXRcYhϨ,G?u9[:J֩$d <0Ԙ5HF9Xo{o@rϯ?RӸLjJ,qt ?(|ȻFh9sU^eriQm\ϧFU-`yJoU[טhHmfY_ܲfqG ܺ}PYm?4XorYw+U) yrn_;;0[zlmz~j:uu7L}ч +hRVNk>Xjl.ݎkj/]:`,ٵ#;kj2h]ek4slFT|{$Ks^WP׳Qz!žy-k4* I%*ߝ*ݼX%/Rri+\P/dPT,̌{ZOF7ժQʡdb/\xb`<ΥL~ vT^}"2q=Hs#WhwSg{:o`}Be?P qf* W CB40La0 76Uⴔt 3"263?rpɲqcTZ[}C)~T@)8coB}QBׇ7vN%?ļ%1$ (oiGk(PPPiůJwQPFhM뵾3{\WyS 6E5GHJ> ?Jj\"O ∭M/ SyԨk'*حٯ?QzBi]-~Ȟ2@(0c->5a% _>aWBia+ .8g(Ohkȱ29tUyq;W8EJDvX D#/<Ҷe!rw%_a"/ERo|_BoG*UPRY%yŧ~K}Zksu4P"*XNR:_ |pPt`_ !~[^R @(ߡP"@(?,&!S|f-/=܂lܬMaWM R`_|v-Bq-gaB,E\ @)Jr( 0LՂ)ϯa&-Z?}R$4iњ/6Hþ( ^@!~+ G)w奔r'zSkB!B,-Z -c*>%0.;ohB!/K㽬J:rYC[Brc DB!Bge|9Bk)W*CX #S[oG!B XVJ_N +Pݢk7+P@zA B!B5o[ijS)ZwK!a/ن"B!oJTqV=Ubw9+B! 601~aJwm|, T J8,7&K@ASUZ| Bg=;l+QT벀J)"Yv1 gQRm4p!B!BC) *A)e9Q|ЊP @ MQh89]WJ|aVSBj[*/R|xbE] }+KUkB!cפU}.n=ʍܺrVVN* @uj1,ݷzpx=d Cߗ]LMѵW-#;g-s^2iTi%x ]mo3uoX{}5K[ݱso`[H嗷_s ~ i*&˜πZ!DqsohVv~Ȃ̱7ܱfwN2P,)? ! LJ4cX`s<X4|{߀E}\^=m$ђK :Dy}D/wO4cC;۪u4~}tU{ >v̍[g_4aBrkd_-\\%|QjT>mY#*zX0ڮg/p/#b/򼶻 2P(4(Z{sB!~R{Jyl|{եr%Rr`em[Xp^9~ =:$)Æ G1p9u18> LmL c \zݕMhWS8|ҬJ/Wew-1x-yijȁ,5!lpj]mu}rJNi;V3#` ~x>&Jt x_kFUo][cC\Ԫ98 o[L`ץ! 5]-JrޝsvK>/^Ɍ1׬<`ggCٿu_W|IKB IDATkTїr%TkQB!hC"{KgAҢۡu_QBz~ 옰AuSxCJV|he,-ɭy=Z8Bvd=4/Z@ ;(](DŽcVar^!sϘ׽ɳ>wq/J”3ZJDR! Tb{YonJzVSswqo]dU'Y#Yr=+S!F:*]LW)0O)^1T@!B'"-٪mMs})iUSʭ 3wl ói$+n?=[kĨ}Nl`<`^llAhX0gR[1☻!Y^J 'fNyiVY2ocF_IdkOބJUl2mY[zF1Z/LK8*ZFgdS hʛ%Q^,60.퓯;1e0YwA>uPBy|2 PJ[A P"{25ZP>-%8B!~?blѧv:'bB%&ļy-v)&5 )Jk^tndpĽAb?ƽ{yer6%v]69o?NC% "ym۶.k ĕa/92w{m6rJ>ܼQt#8.=ͫщ៲8zvx֋Bž>cμGLF>fb^q9yweoT{}K{oXXOio|Gjߦ8hҋQ"X1ʱ] SdkԙYf ޾ -iZ+lZSX@ ܝMߧ}S7e#68G,ΑpTT|AINDzTͻ<ګV łٜIIs7-{6l;b@ vWﱲƄt9Ds}?>=`ZwFI7/1ڗo2h?+t27/xq~:=SĹƽ\DZX[Wa{7D\0ֵǯJ^pig[Sľ%Tv41=Y:lYW|sm]-ֿFsr󸔰[\JwI||=#yCG!!SJ+$$C>:x%"BC? <{_}TBBװǥa_ g.kf%{Ranjɩe߽0m 6M)N>CdQt R*.-1)ÀrЅB!~VؠF5&XI+*cQ*{cP0JqBP c (% M r'!BB_ȾF.tqE(yÏ_@Y*Q~Ud^2(G71|!B!:}HOW`I_ DH(JWpK) :u>t|L6uA!*{PwV_1v 7'U*#ZɄ|(%N@s"JaݫC!gt2ORxb._r/U^BN93a]K "cq2!B!T>ɳԣDk:P~ >Rië]IFn l s'DzzEi|B!6Mc)ǪʇQ@B9Nz?2XK gPJroZ:Q, "B!P!5ƜCA+Mw!&pX=s) PtZ4|5NRJbՍsD8Q*}a僪nAebB!K j5%pDW]  P7Tx)B6Jŝa"bDڻp\җ P/PNY.K T e0XDfo4KfY(=8PȭCJB-?cQK @ASɪ/d(!H4[[g(P8QB!C8J)e"`rm0mUk@:}CdW8.7 QVمqITodB(PٷMG(@ތգr'q,2?0P(DŸ>0*8roB%Ṯ,ˊ(C J ! {(62߼!F  (e9J !ʺOK,+/mV+ *]){y(37U y!R[[d8 0 Ț[1 #rXH$BmN/_M9zzzDRJeYe:S)Kd5gUΟHum?h>g J*^?|+ B8 ˉ%r@ g߶-k9%e4Av{p t01~>bB h~1s?Ɵ&*Dr]u].}7K!i~dMF8XIgoSc o߿f:Ƹ4كoTzu\>sN%Usd䙧t6d:6w̟ٵVDBTdb矿?ة~-]$gI1\5E"$41Pff$(q9Ev]h$.c\2 ou9 0!NĥCE f*I3vm&GLFVz}6@0Ϣ\|&ցa5 gXR/OX)¦ ,‹V)m0`jOONu:U *2tϋo%ig'6>!T]@⿾:^onW|0}i@$r R|d57G=sun~>c}Gewט_D9珟r^$R3*[_!-`w.4z|ŅΨnv^~z# v܉-}t=hzS;GS[`cLNW;N '$}Ʒz\O0U?Y/./S|&HM'UxI/_:q:0j,X&CU7F:=a"*brN1XW'q|1@3 QR>ʯ~]t1F얂X_av; etYu6(MYPu:j10J5ܨgF)H.h! P8o3HOF.㭅*޶|.y{\CFhw/Jq9'玲Y3'=Z8$ös0Od LP `-6-Q[6]r#Qpzׯs>e.dcH;5Df9BN 5c$2KfHtﮣGmj,;r,\9,@ -"Rp 5g"'=.>59!›?savZ$|369 'K3_ny:iOK;t^vFԍ6 <=Guו^w/*켊WϪvSP"2"E5 +O;Uc6[ڡY-Gd@:L8Dl$n{Izlo<2F9&"Ě3I@N<<GB#W_>3{d9fUtqƸ95gzU&v&oUyܣ2Bp[0,5%2x5l*؞5BxtV :s]aJ{h—tg+Xs&5oUxHG1K\>|,+d^7N ߖ d΁n׺$!{iԠ,nIA,J0 ު{OJT J\K+^ׯ{51;W\:Uȸ?чs-GQcR7u䊵(r?G,"cEb3Vsv1"Cxl*3iv~i3ɄSOW4}/ꋵJ;Ky VPf~bf,} n/ 3`ΜCYo`kYUOA ;p,9oյVVyK"CdS*{ ,\MZ4PhjCtdX #<8͉\3с˜!` K#"zn=~mv#yV!fX+Q!UQ.6꫏bYڨ%}ANkofׇ=23)Px QǻY3>)V6?xCCM  @^@貽0g/^{G}O^*<'̱3I%ЉC :9ŞQ˯$kAS}䉥's=ŋ/zQ\*X5EO𐖯PEhLlc{dJldjI5'ۈ =3|gW)) \^YQycyg x=SeWu#ךdM %go> ݷdimp')>t`۽@Ua=%/FWt.7_"fö$w@ꉼ*KB[͟1ƫc) oVa-JN+_Cj_T&ާQyB3RνSsr!^ΤRIF+ef𐦤1p*VƷ "r]jDA!ok"A9XX%+pӻTQz,!IPs9H%q쬢^Y;V3HKY.iylEI*Tsfڐok%dk~?/Y/pp8v W_TLֵ Q0߇ jb|4ڄ /nqp _Am*O-H"ވ]c96;4%sFz<"oI{]S#Ɏ{O>dYP[(4U Fp>>W[9Byv_w n ]Y4@|C@ZLwnO|-|FGmSusz41)8 QY.pjo<hS7l u)rVK/>>|v|賻jVC/q^ѣ2?r+hiMkqsŽ(qKlE.2Ef5WVsa)5;txʩR5ڄ:}k gs9D \Q"d0i.ҩ~+N볔po E}* ,aO23,ω3>t=20IM\ʥ`[^kU/ZJXgG.RB3x[e={|bujNؼk?@兀l1ecv3$з_-:d韾DiҘb⥮H }3 i?)K%i ?Jd_u(yL6w7iW=2ud88GEУY0)j8,)Dw_$,YJ/CabR\]#|O`)ލ?%~J͡{oDPtTwN݄sݽc1TGwaB(;pm ܿ`IDAT<Cm{"ZU. O{@{.(eYtX[&^~Y{ f}K0's,ى #c"?F sѮUB4>^0~wr-4vSS UV{s HM]>p|qfV]o(J;A8pfoWuoe-sYt \97-.fGE%SIgutmPUNfsdTTέAӥ9>?ڮ)isC`UbT5H]ܧ aJsl:13,C{b^!*ķf$h?/4UUOgw^eQ]d}Ni]m0kh&KK.$m K!e l|xˋ &¹6ewѽUnHx^1Cwc,]Dw$)ljGG U&vֲ^^1ثW8% H8 B$hnb cyҶ^?0]} :ŋK<ؔN(V)ix w#C5l:7h0O HTV"?FЍ}/#d;7YO Qn5?$H\7uWqq.Y/w * =oNH ) ɽ냗񬘹5ܾᮆ ,2,ts5l{0ADi:˴ 3e[ti;P=44$ =!MPsL[- RTts6;t̜-߸"BwV =˔д \l;`z0Lr耀hPhfIQU#8Y :Mvt--i ZؖfFa=Ƃ.fkn)x3S+66~>Q'p;M-+y5d [ ^:D{DGG!`7h|Kw75`c1vRF:oly~M  ͦá 5EYN= dQrw>lҳ?v< 6V;)"W Z~*ɯh,a b8O$ cl5䛹7x)+I_:y()%ƕPzxY,]]&KR/'lKbC }5tH=aM*'cA 4׷-Q)}<(bNz:u>p7JJ.mъNpS43-XImy8EQ~JsLa#a~.p1K8SN2v}U4!{Xޡz}1y ^Y*恢mN ?.Y !֌zjșF#ʄh0/]I&DZmH q"weN99ݍU(8oG>jf~0*]"= 650ߙ#uʸk`??c4r&N頻CK8kxcKDR!*BP!WQ.BϏu28tC٢2ϸM;+0.jϴiӒWqX ?n Bi{RD8h0 ]N52͔"S5T1 ?|u]:;XTU:DZ:|B*EiQǿ $ɖA@u}D̲(ik.݀3:?=sSKen͛T5%r 89iS:Ku\Pcԗ+Yj~.7sޡ2RUtE3X*\,_hsοqAԦi&SÃJ1Qb |=CpTN`)j]0̝i[FJJ #AmI䞗11U: ׸t$1ִ (=y@1U:$&^4K}ӑɈ0S_mr3-dy.!ϋ%iQn78c T*J">=b)RkI$K96}ι-AU/._ O5 I@=m-̏X}X8>5KߗWlzR2m<冇&'Oxzx c8ę&xϣ_z!\[lѣ,/ X',t_`Ћ z䥅*_J.\d'$% ?⒠ՙQTJv^վhϩl{5S?3@4yldro?"Uvb|S|C?w}P9$TZW Jw3ͣP"߮eD2H ~T^D,TƋL_ַ]t;Uih60x7WRr\w3Nz l+8^|}{VZd+0"rc, JVHT=\xKRb>W.B Bj$s$mp -XVpqnd9uWb.&>(s\xpuT[ϵ3mQDӍu%땬ZEզIQw/6՗{ctA~!e5If/Yx]ƢsmërKifs&ͻ+"SD㕆ڼ NunsW!NO^'$Z"?6@ ץ???ff }K:$6MQAq-nPcWCҲ&rNf6:D>=X3<X+9뺮Y13c `f狭3"k{c#欛`&9۳ȵ/'y$a~&1|>זʱ҈ùREs 0gqI:u`m'M tLb$*[x sΉ ~|\ 355v_ܥ`.nU+KEEhH/ pS"uTɛ, 8WF'RK{# S۷]!PuUl ;>UxV pdYDP[eC ^"QP<+< tzkKEaT|,fm^Ꟍ;4g<ntX7GH袘gbK(Atu9xW弤y=wgw峝Q'cH.McT{"AtvĎ44Uar͸\)ș =`cEc8#%`޿C%ӵh>~tfJrVȧvbgl4B_UY>%%XPڃ[sxT>A6~x^r oK>~%t [sط'\u)0dGМ(PU!M&QeT,3ӎOZtiXK#ӝA@s=ۣAZ(w,^~}_momC(JdŌ@`;*Է@#0_Ǵ߮-~΂'S߼2c1{T*q“:1p}MӒUGe6ҷ.PiFsl]ۛyLjFZkt{Nث'H%;R|q]7J+(8=!\Qa1K";Y CXDj65'l R@|@ Izuى[ ,sv8LǺS;c]1T *"Қ}J߉ǖM~uc S^2T@L330Jn OurfM`x,M3O}VQ(lH$\}8 t}^ T7W{b(؜IDON&I^-ё_']5czCu=v<n8{YPBBH9 iD'bc_̶jnw"zYV踈3BQAb)D/ >$avG>+O+vc&~Av6a}"Nl'OtF#R+mB*8f{w}؉cݟ2d1|yGﯧ)b-sӵjלwȵqְܻF.ɳ:8cX]}=}u/0}Mr!+ ¢&|a>ıʐ3n0/qWGcQ/^o9*s&|*y)LdYִqh<=oձV$GawW?e׻[*k㍧yDN"&F *w/&:: UY;_E"b̳ΟfBDP鮂ÃycU!@r_kq]2OTf$˔f6+%YA^uc4m/( fB0Gv[Džu(]U1UoZ41"uyp6|Y_.ϟaXW "}Op!J?vrqNWщ F Cv*z!:O>=Bta<`YD~ ]55rra6mS^%u]LN^vsT|洟f51 doRx,xuZvz Ogbn^I_(l꤇oKz8U} zrnl-Ls&ԐĄ ~>V_RvF&L@ݐT&e''!go!U?7pꮖ7\,DS㍵pBpzE=T0Vh0g Rw BƪDד]!,݇&-yc֣||Pbdx T"M p&Wu,yق~GHs?MW{/ I.B BHKS iM} éI%V(aBY=-{Eetߌ1oC>X;a*:',bCy^cQ/mTLs9^fJAo޷CobXqI,Y$L}(nSߐOR5^JV3K: hb-]W:"O<V$wV1g7S s]5_KG!BVvT !J H}/{L&rmfZ"XȸB#\^sr7r*Uψ˲짋CR R2%kih*,&V9<w޴l5c^]װbƈ1" (  0d::**fE,bOu Mӓd]}Tթꪧ9U%c I@$I$ H$H@T $ H$ I@$Iod P^^<%$ H$ I@$T;֮]ۺuS6o$I$ H$ IབvSvuM['I@$I$ Hx$PVVgϞc~7۶mk a5!eӆ6ݻM˹]vСCNu+>>~ҤI[nm_)S(W b̙3ZGj)/M657Krrr^M*RJ{ƍMPb%I=&Ǐ/5owݿ|0neC6P~____:nܸ7盖/ի1.' ME*$W&@C:,1cǎ7n_Mh *QH\qݧO gcƌ0b  fO aŘ>k,h5O85I.+Д6 7}&9T4dddPs禤kޚrEћJBdff9spӧ;w1z .-I3jD*7s|A2x E1qDl۶m˖-MW9r=^ M)++ _S}m4k~Q= p\lmmk3fkvу_UVTxj*V+AAfoo )p Z.]L1 < l@7!C`Sw2rPpߡcjc8si!YpNHHȗb<ݰaս5I.?3$``Z*@c7jsS>i~.qqq@~~>4{E<Ǎ)Xbb"`3,ucKK'⌢cq KJJ>Bu*zAŋVKQ; o߾B4jԨ> 3t$M fwgŊN̈́CAg;y$"CnXGphXTgFG{ٔ^C1ikkK@2,ZFŅ^c)BoAt҆ m??'ǻ B^G@1t*Wp t hAd2p2SV[:K;:6WB6.R(SP)$V;_ x ӷ 0958@u9 ?) #n_սHF@j$[ 6#/"k Ĉ́k̻%`/像`4#s$CI}J'lHVp $H,tHQ=jS)(Եe 1^cv P)J®] JÂvAj9gϞ5Mc1v̇L)+AM! h5C`PSz(Z@ ݘR"abD]h &6yOL/ш\^/iaP xHxAҀkhtV0Pw@%ArXBPNm'u{E<P$QZQ;z T `RG<0O o8'JO GiHn!PBU"Lk/>Dr= ?ԙhrFE # 5Mi^sĘHAi3QipB")V#,J(0{ _8IdK82h]Cmi fzxZ4 !t$09YHLih^b!cGp TJH p)`kV68 ~BX0`Gڈ\ERfAӀ#e8HAKA 3x꧅4-\S$)0 rG$IEX.G4f; Aa~c+"PnS60ri:-\SKߡD$:'O01+6#؆.Q$9nH Olđ,?UCW9Ǎ+ A hzl;O`8hhwmXa`s 9H;@\ϐ*/H .?^c u`(J;5#; *.I^ M,Z/j kz mrrr5v ^ d>ŗLpI|G;N.?+ Qr4(bhGBav$AQ~'7n#%$Ilo]Cw %>ހ% P/0_m`\0`w9Eo\$0D²@F)x|n#hOlj7\pIXjG.wL \@x}~x =|>#%:Xs@a`{5چɒz{#5(gQ:}xЄ5v:+Ѣ3Z TrS/Ǎ Ad@oxY~ 1r$[ օJC|N)tƂ |` ]’- A!~o$|Rj [v BQ;;l pkp'`‰S2X-Exm4_lh;z+`oNf" O+DHoFGjLFcH1HH/=\tr~6`RZI bH>@_qzKBLW PaH$5V1@gDrχq1 Z4xP$53*Coxb OK@ +¡醴Fא„ (PGAS\[0bŃs-Eo(zDpp- س;XETJWfRCntm/2Shp  6PWAɇ Kn /حhdp%Ar8E]/!^3xJghƔ M nt<PSJIWdE@dž|14TuL8 E֔-\k(P6NА0GI  >0 5Cx(+xqfk\# |9kѸfʑV a-#; rHBmP!e%B\D FQ zMԍ%Eo4 t$#A\Gh\W4nc/$HwP_: *j]^BLW @*𮅂 ߣ( !0 aŰhBCX>|#)x<—.$ @1UcR%6EC@b:{e!&l6k$ @ KP1hG)X׈A|d~^cilma`D$4odRrIL `E*/ekRa}0`<;v X֬9z ~?ՋZ}5ѭ1׈ipe15>k ;HX_ I@)3֬˜ -/_ꁇf0ג$I%@V]IA$I$ H$ Im R$ I@$I$ Hx[$@ oxH11V^c=?⯱m>#zŅ@/|$K:hgr?HHA4(WL>#vB#/whSi1S ?^)xz~Fn#li>HJwJz_E?> x4&O9"32z5Iܑ5=b: [GS %"/r( #~D/So0~U%*$?W- `<+N fPh$æ#8N7E67xxKŚ&p'AthP.\$cdD/7$yJGBIBz_XEz_QE2>z PG(j,B\\ ׿p4^P@(G{YrFNHR$ I@$I$ pJ.G):,N}[z] .F [J^1OqJA'#AG~c$?)?HA bPob$># `2NcEZ>LyX\@$ d < 2kc~t-I@$I$ H0&Ӿg@{j,}_~VVV_\ЀHO` 7Hq0ǿ؎> yj< N%C@P÷4K\Wi!]z_ER{H+iˠ@sFoCΊȷ@kҊ";8z!?ŵA#<P[H}.$ռۑVsR#c}//$G)`WUH\p[C>`9Bj5WF-%<2=sX||T8.j_j̑#GzzzfggdU~V$I$ H$J ++rm(}j>h8<'sJPE11185???W $ H$ I@$P135j ߌ"6Q$Xkӑi9OK}WQ@?GWAQPP#I$ H$ I@[.@ {I D4k ^4˂Coq7c0RB$ H$ I@[uPʗ@BB2x#7ыlb4|=qwl 0ƚ&䴴}w; 9⋑'~-"& yi׿7V<|QӇhow}[> fsqf S߹f& VfeD5Ӌ^!-ʌF3j̄H?ʆ$F?u#JDLHڸŠ 2 ^A!8>8NO(@oXáC=/Ak \u" 3aa|GXnn"&wXvw?w~77?g?|xd"Jx;]^sѿ6"u,/l LCaIbϺǺ+RLeOmD##ʣQb+9cؙg]6@ʯB$uM(n ?EnOhQ@V)xIEʳllf4PN vl},҉X7˦.a]o qbM<+^z3RVTqMxB K~oNū\+vYa ,v:4v7P=:b ʹ1unT*B Zl r4jE r(?;XCx2j9mB# oC.N=|gO+"EK#pؓш6<$?ө:z9(wu2 аB| ԛ"=%ia`h(Jwԥᢝ"+>fEoh–˹e%̌‡C>$f]?YջR_IIIsBѰ0ZH.^XZZzҥl"ϟ?ݻwIXokNjmg8ٔuA7!t5V7OQ|ŧ[Zڽbڴ)Sovze+D@f0$6?3M6. Y\Yl͵M}JWY@f0Ywc6/a| S~-" 3V7'>|9,IJ\=nZd-D9CtH:mlKCrzאb/Xc[ܫ# ԝh'&0dnr 9gLw~fCGYߍY,zSrRwȲpzLa'ZMߚym:8lH&qވ۞f8{1P~ 7qh;io.5?Z9*C(/q 5 iP̖$B֭N۫혭5Q/R|#/ի#sˢ7]xmSli> sj'I$^w-o+CӰY9}洐$7̖˷{!˃nbk1d^Vl@HŘմ!g7匼wS<§xI-gKkb]lyϘ )E_cu5B7C *7-荦5,qv(͆hX<҉(m~JA[Ycj{̡YBtcL 8QOV%#jC)Δ'N>}{]Z-$Hh/ݧvb5Ʒr"VKeB/Ze +;Y|O>iˊ~n}@2 }8=0~<.H<A &1My-LrH+s)5GA}}Ȍ͉g%aSh~eFFlL=|22`jaU$hA2aӾ8$d{:[׺:N \O&"ǐmub~v2'bH~v2WɨSP䓱e \_FO9paB㗐C9EJ[ ?̉o4e2ܬۗ+kQ2]I:,!%lpl}4SC閬L*O*ZWGg\xq nC{V:rM PB}Іmg3(1L#Kf؊;uu\J.CF&DzB&nIGb;m]y(yVᐹ; >pr2Qlk:@u|S%)4g..fnE546 bqj[IRB;dK.g՝w&ݷ@rɖbDĠ-IFƍ&-'f${&4}CX`j$CRRà 7dg#_Jzo8ۏM;MDD& X@ЯJq0)=}+3w9qۗB&'>Y=G;+U8`8'>U +c/컄n eRGvss#VT0L= %PpH<A^^a `SbmyL Ui;5<"H.V! RP}ŬaBa&n_:G kXǑ*w#%;Q4 T66--3/7d1F"tzcH`!총401Di%i},'crIn)>7Nu;!NKv"Ո'NV^Ŷᐐdtko*E"?ΠKl#&`9_='7laBt rcLK8g'M]_Փcn=R1`-VD Fd7I!(6NlO[Nd;vI4r'Ao1މN ȡӒAr$`Y+z A. x@{i'M#::ѡ`~]]jwI7*vPs& 4[S%AwIuRV fZA[}ycid*C?ude}vLI`JB HB9AN !B7iv_`L%~?Co8 faUߴ}8UOT`Aʕ/W^zԪ#t} &52{uGNqbvyե'g?/G di0zvrllɉg/N(˜ visl̨g0V7'L$-j ĎeCtjZ4:YsfʚӧOeԲHWgox: kpX"x?1. ٥,f'~. Lbjùe+=H;3 pe6J|v` xB@3CucnDq9}[ ..g䞐99jڎ\RFR,OT&)Eo5MF6A#ItT߱t(N…@lh7^SxBHah– {Vu"ҕ+!Sp %}n`Uu ҟ9Yn gJ˖-ۻwz$ABc|r8w)܅Eo|˩껻C7T ozæmUؼ1ѱe@~՛M ɝ< i/M$Ɗ+R,>p~.T{OpdooNe)j @Л."-w/ &ym>iQhTllLVOPy3=ãbQ %sJKIp2) @8m{$XnH1"L#c(382)-w*t)$6D@]Pz& ĆԞ@S3 h))y*=WxlWĂ>: 43o(er^rO; Y~2P:T&['YXDvz㚌%nh#ƺ͜E{PlD$%8$cnP$c;7"#EEy$SpA ~f=tȭc䘒i[ e[ãB=Y~-z4R$ P>)[ 1؅bp2"~^!$kE۝֒FnEI)H1k;֨zf=+rc gJkk2pTv7`!6 wA]hP#ĕ@}CP D76`_TLӪa_}슟i1ȑݴpv4<#2&*tV:2¾K`;J&$o>-@$Hh- @E;T ћaYT&8kP+:`[!#)ɧw#Э:(=/##==/_pC 5#zV NY֙r;}i)ņrl0 !n#>޽z#"n/pr 5:dƒdžl>]U^%푘A Džl6<(oҎk,?3?krLxPkaۃXrcz-هOhNN;D _)/~峓|!/Չn;I{} rI)p68Ic5(^%u(WZC q!..3_|([<ޫaщLz1ã91u"5*>JrvLS9~tY6˾VV%Lw( R @z5k8::.5@2# c CU/AmvƇn&6xxԸKzlOϞ=ްٝ@CMOqs;puSׯGHkcԐ!r&̾{j~Y&9$=ښ8)`սDrczSSu I\wRRxBN0.P@hn@vl^5P∗`@]R$ReAh|l-{b͖E?q*҆i2(CMши$rWHBJ{ =6Ƅ<ͷNl6#!ZHjpOJd˒w<"FW.}5j'xHV6̍7h9vcˮ,sZ2|f|YIdQ}T"+([yp)ְOxg79z3׼3rGSfʝ%8VAݨ  E]v8kXt$/?j^UcW]H<svoZ{|@C7-DEyAmxː>zptB& &=zHVTg'?rXi\ŹioVA[W(OETqR7;;\D6] w!K`}h&cBB | +U!bP#Pq{4zwz]0.!uMUѣ ̅e -YMNntsI$ Hx#$h52|#m۶9O)kFfpWrOĢЍj8Խ{wTO!"5kq+"Q-M^JĒ$ H$%.W/=)G} pMEon+pM=ިӛA7 o$I$ HxxkN7PQQAҽ yިi⍿XxC}P+T/N $ H$ IJ]UR: po?poe9#6nz)X|Eomڴ$I$ Hxx@k{9Mn7@ ,Mt,{MK@(Z!zr)H$ I@$I! P|ߚfSz6S{MV*P֭[m)E4Ja;vyii{%N¦km;ֶ8Kx[?u ,Uf*%I@;)] oߚ =_o3_@Ƈn[ۏ.7)z$H" I=&x&wu l?[|; J0?E/k]\mWawe;&""zҤ#wlcp}pGej*4&L4t;Fuހ$!a{ڍ^3tU]mg+X=tAYuoTtE7^;,[ a3WSWVL,#\e{~+7iB)CgL #޻Ԭ5^7zJ7fAo{ߧ *޸B8Z!|LK#;/YXL5t4[eNK􂣓 lmމ+>՗*^|һU+{~߬|#°}Grˎ!5Si`=`>ffb[bnsk׮-3疓Sݚ-+Ν;<~AUV\~Yyų-';w>x̙3sC_yxƜ~ˑ?6l{p>1+s;jSc}j/(ncԅ;zy_Ɲ@w _ PM z2`)Љ=&i'ʻNt7U_dcv\\.Ծ7KfxQ+~Y sS^ܪ`ane1w* {ihic执^W$)gL% oca332i{NT+L?_}mn ̡0KC fԷ#))aΞeΜaN䴢{y? mHV|qֻ)68@Ud mϙOOʗ̴-i*sW̍q__.S%C/NR4=5C][f1]n1m\'S)mF ՜ͱ~>9Zw}2xK [`kcrS58b۶ŋ}{O~lʛnn_,\d]~ն?9K|TVvi}nQk6G_|k7Xr{ߛ5kxbg+/2j&"s;~zi֯;4mGĞ}3H) %fU<ߥf5X} ҒOxڌ7})xS>Bh ՠ Cc"CoD} =g*RSOKuC 8ͻt#W9",s.Ӷ7Kvd&]ހ,s<%X  *2= O9;"z4O|BsyxFcgl5CͭHU3*eX9#ft ψi3*'%cǎÐg1lR[Ç6_:w/S^p=zz۶"7L,?ەKWV{(⋘"&/9t[EgM K 2peoϚ5Sor}:6blL*/Rߛ{}[=| Hl?l [גuRYZTn{򼺲#oeuW[;^|R˾R<ЫoML=fy~j*iS^\s/k=ڠeUm,nyC ,0KSELY~un _c# G, k0# =&|,M i̬ocFHH4t5;  nb52}FoxJQXN NYfSt,M6f``R/2dGo:/(֍B0^0G7-ÔD1Y}-sW\W2%!.yv3k\S&OڌsIAfb ^vYW=lә?XEX sb͑^}]$k7kg}AuQ{٧]hFoLQERTFFٷ :u<'ap1l]Nˆ'lLSM1>fi:wlPƹ5 z7ot`Sq+cMQ1=&qZ7놙3bM^\Q9%&MgɆ[G\\ÜdI̢ W0_}ti;q~L%Y4JP >&ڲ2y~վC3M#]p&+gO3O0W[UퟯNa?z\NEZԱkj'y3lD.[qqa,a,`Ly4df/1~D̵d Iv7f_w>B5Mwؕ]PX^&ds66+ oQS2CooT,药{6SKjSlt5,n^lJuo؁D7p~pfL혌>bJ3wIݨu&x gi%&"qI=$a)de2/0UU*O3NqL /Z.m9˲}-ɯ˪XpGܿǕw+9f:LU执l(0^ 66ħ~NJV`i3hÆxyMƍDBEeˈmllf a ܩӸnL3h DȮ*FR(Ddt >s>s)XV_B9z_`gzgͺ%ċB o׫E~W:6G&[*o q2otpp4VdYsYhGX۽GwܿZǗ>dy~q+Ad(^aJ ̙%FƯ/,91jƔcIH"~mĸ}{2N :ng0]cB|<9: =$4\dfvyz|s ETi~ld,/;lN(`t$%L-Y]hFoLQ)仈g{VwZ66(lY-,z^LPca^ۂ.D& g!j+N+R ^E k +ս!4 z?swܪ.P jCo` _g4L|ݪ4 JcB'wЛ @o?2 zK2gL:΄ev&-J\.0{WƟbc bjצKeL,fosӵSO?J[pưr‚ӇU>|R'e/ǯ|eݥ J]pJ}ġm{zv_U3پ?nbe[eФI3)zVdwȈg `[`滽LG{bJ"F{ڰOZﻳnޤxCe-Fȕ&۱7>~x םe/4|NYDwxFi+`%fzpyJ+_Wvbd!YCC9{Sx.\7زFt"ȿY6mg\;`CvBL(~S)qep-/-׷W .YQś>z,pfSSN|Me <}@o//3j1!'1Gޣc6 ?N[ cv3ALXL^)w?rØ֯as-Xоm2sܱJgÇOjFx1ezܮ~L7hqtqqQI/۠ut;_\`J7wsxyl0W* swӷe̱;̒Myya,ŋԷ<<59po7\A)3Ip>8.g  >[ư;l[NNk3Xn;7l9l+/}|V˷Ϟv|m;Bie"ҡ(MnvWК>Aa;wOǀeM7tɴ{_w=r]DuZ75[ rT&{w2#39~Dnx-C&韘m B{塄ؘpM{I efmb Y$ap璞II-%[0ҲmsшIi)ˑih-~'䌌aa{wmD Y-\|8ER\cY-1G5J5[sׂ[>[{8j\7^!^!rJk(L 61%.9{Q{'˳V2|V>CMztp%- .bU̶|'cr3?ܸu=W'edb_KkNa&Vp;(v_^`֘@EOه?VvXUs'9Km'G,vޏVxkO3}?ٍ2\Oyi}um t,'fs6yxLiVզ#tLBBBO7DD01=z,#x˂CoPR& KXus3/ct 6b`hS$<:93P @U2[.ŵ3?;;lA}'+|QhY:digcC7\*E3zcZ/$5+>ڋKi1Ҳl#~(z.]T'urb48ȶ_?XQGx1lLl,[̿x1z6M Fcl`n?cfcww 33{L$tz_K~U`{ o̩9z*c;p8QQpX0EyǏF8;)%eoHk 7A3Y5PlތfĈ}wwnݰXʿU:|.\;{V{R2twYVkN ,<1!v J*Y;uyųN45k0DnOhcW5wqwڎ埃=s箝>@o24Q%+R 'ip-9fbi37LywzȢ N>s`" r:3djO% n`Ζ8ɵIAѱN;uO][Zf窙8?mܠqzON޷,-.!}TuNvghWJJo-:C'1qOT#99UFf^Ffn&+.>Q^~ƌbxCq[`E2 (g~e:/pSz,8f疭A[E ,bdhw@V5+l B\ԏnXh,W-j8j ͛9k-(z_t\荃nś Z)jEMјv Y<#QMny(]\}k.so_y ۠SzJ z̋edf0=̈}LoG_S:f&,HcF3Kvܼu^Tz]xk1,oӋv.~6O&f-Lgl6KF66y|i +Wn]zzVwb2N/ZvYD`۳{1];u0 zKO(('%@DFۉ[[ ,l}Z;ו_]avvsܿ[,|/$ aa38鿝gez2m~í[z7wwwӥ05>,~Q{W,_:}%Iͥ .O<7iӣ[a?_m'5e;?ST駲+7J_֞.jK/P6.0Y`3CV/ooQK'l a f?b_7v ;ڟGm݀T+g-K2[$?''3WČZd۲{7;o杜L"t>`20K5˳a˗޼[n=7LjGأK:-:UL8&g[ΰmƌ, __3̘}ZEzjߎ+_к2++1Uz{!~W޾rKn޼xNM OJ#j@osbf~1 U{gs ۂvT@) ܌x9BJVnZBL(?- Ow,Dd,95W)Q!Yɻyy'de RQ]1`OaxV~]ٙ;9;7;%FpWtFNnnnf.C- [;d8mx<%xK=DCJVVzJZ:RƄ"PV EkR7yTu M  R7z\=i7S}z~otZN .8ر#݌fN fiXv *ElUyKwbʖ|xgfV41KAMx,3T6BJk)SlܸtiV2! KYž;=dss599'S8xrQȓt 7y9 ^ {lӬRN_afA/[e1|vuZ"쇭qHm*pJ+' (\%3+3:Fshy:ss61lჶoM;J<ls͖9;)[<_77l0˳i NR31ym&On|VP6uCK~⣏r_bGO`d7аB~aXƏމP\-P6Eh6&i Q!hY;C5861$ls3 u[[*ޢ[p ׂ[}O̢g6~o'eнqrm8k0od?9Y,nVMs1dTq@$Hh}ǧ-aF`\-;rɓk+.&?iC( /[(vixwt">|Bə3Z ;ut|LfL0rn>c_}t a AIb]D6'sOSe!uF}9RU b y-~̉>-E - ao3bynk4k9qp&hlY3j&GZ>_kZpѱaU+ӆ++u 7o޽q~ׯM>aKw7f6c֪zkgE?5M;-SRA^k6m~]ͷ1|i JTT|)սQD\Sx1;&U/ptn^ U lnA )`eUL9x{\?JsҔxm慀 [l  F$4]^}Ztǥ;.[-w(~ʣ?/=م{?;57}rӦ k7/qNOGa7KmKKB,4C:uZݫ>P mk̵̦'Gd޳YR=؊o3hM/='1@lT[X1Eڶqm|~wXZ8m+>HLZc4M.E&vof.-LؘqiU2xfgOY=<YYf#Vf{ys0x`7cqƉNL:`֒;o2S\Qhe,R)Y&o+_Wvb.5.7 7o(z6 ,͈ 9d V-P !7n)EoتDne+*<*6ȬؐQ!/k%ӽ?f~:0iW ):a__k0gax/^z >/:ه0i;{:)8wY]?j+Wsd6~F$ր|mJV$=Kum ߊ2B6`f}.QE FɍS-ߚMpȩSU 7(Lū6~3lWV=lUoCȵ1E-~`arl@V0t(to w G㱸I{o3}_? e~>dJI$$ @oHO;} ~o܂Sn.{Sz)핉RȠ$S/ VP_wXŻD/5՚(z;l(z~oE͵c7c7rJ e Χ_ I$ H$ I@;$}˩oMo95|znՂ17VR֡CTv")H$ I@$I p-P)wP}SQϧӽa7|7wҊ@uTfTxUno8sY_F|[s"нY #8&нq-5؋_ $ H$ I@$wF|Qh,~o8n c* )H$ I@$I ֽ5z6Srjbn)rJT1hrJq$ H$ I@$I^۪q Rgzn}7MlJ- I@$I$ Hx$"=k%{{7 o荚MSNt)Tm,U$I$ H$`B655ܖo5=qA޸U -|ݛ $ H$ I@$I(z9}7N⍃n5TFל{toFOj!I@$I$ P p @ot)BW;{N{RI-I@$I$ Hx7$qeܭĚSӺ7rnQ$ H$ I@_uox))Ղ5pzfp NJ=^$I$ Hx$LOל-}˩ScȻfR]$ H$ I@$Y+:kASkNћU z{TwI$ H$I؎!4ւi˩ߛ售Z@?^{V$I$ Hx% X@ל""YN?TpRwΩ [$I$ Hx$ b;k7 lYNM~o8^{V$I$ Hx%ZN XRnҎ!Mw/\w^իW$I0`4i$I`}],uoCNo]s FVANoSz9===9K$I: `4k<% HhV P7u,MU/ H[s{ V-|ƝRI~ob:fookoܸQ$ H$ `4 |L$If%p}(tKxpkV->k)Md[D'*777 IӶ$IT...tdt U* j&ܭ̞W-l*J'eykٱcG`` 0R_~Qnkڟ `F$ H-/_^xc>"""`BX!:D(I B*CR{fSSOW-`ͩ韵 YNM! }qw0$ a K%7iސ$Tt+罕@7XӄXmלoGom7'e{tobzish(jkX*3nqbS%ťUTOFhJ J+OsŗꝬ ,xs%!#cxs *-@bE(?}1ٽ}S95A7{㟔%r7i^1=q2*ŪG^cӹQIb^}tĄdž a KeUd,v^2{q]e  F&Ч@t\ ]ūURwPuw~R^ 46:bgK+M[&O+nhv$+K=3"8eGԛ=產7SIYX= Z 7e%]:,h-f 6dq~z 㰟9%OkwǠڹs@|ڹKu=9SwU{ZekLn->^o.zK#)m$ MJdXuFaciem)rVYFFnkekkI"t_;Pf] -Z[ DXngi"WDl2DYbKKDZMGop JZlS^s*$Ã![,h<[dLM{5 5,ۓbkq,y{;UOh~? C"A,YEeٷL6-xހJ厲e s:~ZiBs=۟eTၹ'Qttň+wa3N+e! c !zZև-BJTw)dJ)[P i7hSիrˋ.X6jiL^y@gm^ghΖ242hD:bg+k0w Ʌ"EzII-kQU˗t'ȌUr+Ѳ4*BjLfG buјֽ5Sӧ{3j[s "(սI-U:9&{!6#^0ְTbЛQ~n7KOA6ťeUJ4iu: }YO mep=5={y漼rxL[2, dRG5/bVPRE>LT͚%aae<'@oεnp*ҕEo$?|̶k$x~o&ò~oU 0ҳלJ眚^۹7xӖS~7~[ވBݩEo5ǽ{v/~RN0Kl+*Jwފpv=V?GjMs*Qk}l -l=kH-HLIKCKΰ(5T/7/I 7޴mS {`ϟY՗NEa4ԀZ`aYˍ}|ӉF`@fLwi" ,y:lF:5Y CNM9ZNBOZMBofxvs5qK b [/L5LְTfuo5~oFj Hd1Eok͞0{68@: T3{+;H4dNXVB2nxCouW!TgӇ~JZ&(9ОN{WK~HlA$[K`已/4P{]}U]{sSpMqjBfCTh@eV:1J8r zW){(njUѶ|߈g[%ѵim*D::t'S6Nf줬fզkNwłSO/ݪWpRŘн!U |&YN͎;ufg_szn$GW-9 kkNY(~`* 6Mb73 3$*()f,e1`<zcBfM*5{*͞NZgk%Q66u".EyU׮Od)cmաjƲNxeYH4tz3z3Ddn9=`~$=fN[KZ1KD}T2uԬ֍,Vfьɧ4$ Sp# cYmH߭;FH`fGoIYsN97~o MBofYn B|Na As6޸_KݢԺaZ2c1#BkGXp]c9̫Z"[SMV-e.dSo 3 ĄU׷zTiNkU `J'V 79Z;l.f:\ɟ0kRj6iͩW}#Л64j- lRrly #y3#s?sbnUpdL!?>~))E5sN7 f,&нA(YNv-289bikX*g-TnhEd*Y:4v=PUO1'X OH0aXi^z Hl# 왝54p-pJ_Dk,(ҏAoڻv/#9$!I^j !Ao`B0Ru+UiGcK̦<4g䆻m yssO%X }!{)?_W+F%w1+%JM#l>VoA KOʂߛIY& ]7zmoY "b2D-V ր6R/ m#}\)NdYUVd²#=[c|^?U~׆]˱ /g !AoYld]0եhFfK8_,ԚVda%ڛW  oZݗg]Rko9w~:yW#ݮ㣒 VD7zHUc~otCf٭A ]7nuz%kvf躤$I[}0#SjZ*rJ6Y@vAcʁ'FfU3)5걺Tz5KWR msAr cp^#:]ΩUTݗUٳ,kb.GBhsǛ:\6{# &l|;p:,`n6}CGx[Tp4Dz8q~or*|޻SEE"'i,6;ξ-|}[=̈R w$H2v~ pR5`+V<.( Ȫ=$Xɀ2 wjdQ)']@V-pΝiaj_ە+Wp)v y[ޕ_N!4 NoCL韵`v,;?kA߻SJD"^0&$:ߘkl}Mۘ=γhRڠՔ5fbL4%s$(i j l: `/w^^\߿?66VL7U/ifKQ7__SIYS97nz[,SI&E{wJHHd$lCa5!߬zslSVav5JƆ/\k5{C900ݐDhP؃߮U"ٚN)otoTA7c,}˩ޤz̿fwe3}N#F2DrʍVOt1: jω3أ_uPۺQQ =w$Iì@R$4p*B)b Tli*ЛFncTʾ&1D`9kNdTN]?2H/6W8pMIt[NƹqE7i3/!ޑS%؜ Z\fvymq< Wkc<Phrk\hQ+J7u&*QZnI+ʜV,FX<$I S&vD|2+r_fQZZےFeQCXVZZȩ6#-Z[ R)nĘsawVv! ۶m[bŔ)S$˩s8^Z?H7rK: kto%l@hPX -ݗ=e XnQM&q]V6to`eeÎ־t3< k 2\ *LtdvM88p:=mmh]3Oz>v3 [_FiG%ĵ-{-0 JL?57[NJ荓ƌRZcY]pq{+=|4FfOV־a8FA0vxky86KA2Xx 6^`hJmIV̮XuhxAbU [{]vD8()̎- jDOc)܈( WִvY& qѿ nz` ,X YNŌ ~J=:#)av'n9)+=Yc/( MGg5ށ e4U$I{t8Mז߿(ϡQffcTo,:070`; Ӄ FnhD0C@ݒ.L A=G]0]Ɏ%t%DT hiH< %}~]Z+6 IM̬:C:/+fwGlhժ|C ڵk3;995Eo);sZI%NbdnGlUiUgpx䛫%d^e0S{oPw*fpaC)%荌}(=OazcRw$cm B>M7oY2.)GuGa~-,]|@I }u=rtkkZ|X %n\z25`Naz GMtfs*fLX&w0P: VR"7 2I Ӡ"^z2kkH;K[@/ /ч_>/,ˆN rW+Qvx3 ߋ€.^0#dV۲-em'toj:eQfА U- }B=5 c5jZ0(>VOƿX qЍ(tgMsww:u*4mKy[[{J=}칫ϼ<8\Xb_Kާ6ύ1tu&cC g>*DvHE>J9\" :?|_1El \]޸QBI~Og9XZݛz|8GoQVXb`M!FFa`[m0+v͚(xځ7n2dr-,ӕ\P3O5k.v z#7V;Jz#2Pf.z39>JS1Gh‰i>țV$tR{(ƅhXQep-mcg`D!UE\)#Nhm;WG6˞8)5%YY6+ްA $GpPaqT{Uo֞RO⨍/K1iŨ7痺/|v2S-S#j^]VFac(i6@aiOs0Ȩv!ޞ~ `f4h,qvestsò 198;ɨfR貃_8[-tӖ5BQ zKC)۹FƎpv"N" r6؎J5cR(KtK^sQ0 &ϚQX A8Far͆$9ՠC[2Rx"(ս#ԁOil}3" .dү o2ӕWҽI7Gj_oWZ ݄Nz Z{N"yZ$p@vSÉ1& 8Gnp!"++X@ל"Fo"ui 7j-S_c7 6XP if~onf߱ _sgu孛 r|e9u"qVFtW,Yfb:(GF-43s0;H Y9M)Tmy[7 \ 0+vAf"2P:3YwljOEVBoz3;e4m?Tuwk"z MN`K5-ǡ@qNof{b3x7ΠK~ofߴ pƜpf  NIg-|+v͙~ӗRꕵ]`g0G??S|[7AoZ[ "m@o 1'E)l`ܳv'k7iJj桯-G(֒vtݖq&SMl~ A@&Fc~o+AC * b;HΘ핿d{8nɩl fIuz۞C'W)xtlc? BsOO0EQ)ucYNaZįUd[.  KL7F,]az#;LPUDprY *`4&S`bU &z׈*Kx~o00Buc*SE ܭ`٩5to*&Fzs;W(>>sWqBy{RJ v?N(eØgCB;IKAo}ut1-ި?qQK[@rU|)>̚uH%Nit/sά?>]`xބuod.GP,n&+LofIo$V7TwR{3{_*0 ٛY LJnto5ߛَ^vI>PtGyIɧ9a٪ؼ?O;# i_DnL݂a#_r]1>P`~O `';w `QhvFz&Ht^[ J㓉_j ֋ ܦ!T_sJ-ߛ^^Xza;+om ,|Ϙ[OK;ϘL쑪_G? AL!YdzkФ$UC`iXN s7 0-U ܎![w}kNհߛY lz . py)[Gu{q9r9sT\hn3^8L_8Q7Qʕ+XsMĦ)%` 1iFBobD'iׇW+4kߚSckL+>L[N%ݛ^~o6CГ> {Qc11v 3~I}.p zccctE.cFT<K=Ԫb{?qŭwı}TbN$ J@ʾW*6@ rX2x믿_ce{߭Īi^1Box7n޼yEdV W_߂ W-\hߢվ]-j%~]:t&o-6{on_<|}׭haQg9_s*f˾Wͬ4$J?  gVUUj=jJ"Ige/Ngd>Y5^S_1 xSh<˩{DlUUQ*hpt%g`y|,&AU.z#;(dV GIС.2T,D `c=nӲ:ڊ@0o;ЛYaZ96^%zbt5GV:P/&+텸s`%[;n Ns ׷َ\;&kv&V[&,EBmp#q '=녺|\ՠ7ms;|NJnjY,X[qa@` ]Z+%qcR[ImFo0,&v34&EcWcu8;q9y47q]]r9٭{]huu5 |jpKKlݺ ] 8eްc`7izuS K\@y+/7wV)u!MƂ6lTYT;C$Yrj0Z`+K-YtteL4WZ<TtDŽz#6R;VYN TfθlcK|,zcAfaK,kɡZ"%[v+bh׬׽κDgIOeuoіÓY kyu\皰Jͬ0D9+hF ጃI 4A!ys+vK4Y_)MBWR,gI 8]DkO ^W@v-y5,G^.S%XL} ")ړP(s\CbXV@u6-Zv0q6#GVT>/ǷʟyrMnU_so|7n{Cv Iz3+"Ze %C&]V .+ôX]@rhb3+0 W{VM>kTIep=xYG7U$Qt/3/Pñ~> B }A4h9c]NfLfVu"nwrb 8֥!6ڝ= r-&EBHYd#Ze'j:z]vZ ]zU.cDlwg dO U;;^ {{[G"0uHo :qy0wg)$_p@́ȵFLj˜(nI 1|pԺxt_Y(#Dym: Y^uX*I ugR+&o+0z3ZN.rg഻2*v?t7P슽9)[]x2Л qg-HlGЛYI[qV^D(Ƞ_hX3K̑bQmD}Ŗ˘{*xr5z*)VVUzOJ=x՛U_~՚o9/Y񔞵 r toX*%fVD هENZZX8u-ׁF2Y+Ԕ ;{݂ppsG-aծhO:jbjKu(<'dA 16ՔYbA EuTY%ʺ7ި-]gLU$@Vֽ|DEBkn4BlA &2;ZNYdeE0"Jk [bis 7~uXUt )Y SիUX}Go9qYrjMϢ7.'oNaחﺲ6b&;El8p% 6(qs=aΘ3QOO^Q7ڭr?9~cr;qO9zPŠ ՝=qLI~of̊H"$>KEo"۽BJ:Ҭ9"=%-ΐ v 1f= u9ZꡋNpb2(VtI.'gz\6{ۂysN{GAͦ:H,=@»w Wӽ[NMROwvVЬ-~$ HKEow1;~R@j6u诗^~i-jUn-Z|9ouyګXx''ƭ<9~ ,MͽQ~S\sPgP-NJe{ 7S5r*[M$2I[fln 463Ÿ9\ފלctUSY/Уǀߍ\0faFKѽ}vh}pÖdɱ'_^={=| >m9G`6%-]Z v$+sʔyŗ|p9VWrj#Jͬ$7Dp1QȔxobHЗz 9mE 6'_ˏO\z Z| {#~o3{tc?,Tp6NYqr⪓˿9|yẐ YHowSn܆-)vB此 >BwRzFf 66,hJFYU>ؘU5vL\ݘCN!F#a r&6K@mbLļ7ҧ+{'b-P}ֲy'J~ ~MgVFv i^34ۜ#O25F/o~vÎLѩgGμ8*<$Cu۩ye)yeʜ[ Y3o&f{/u'eq;\{3;Bo~mbbh$wmaU}s&1;^r9 荋ɱN$fVA=$ ~(DPs6 z|r>|H {i$]p" "5d~ܸ- ֲݏ=g5ό{?.>2H?>wC&M[0v?krO8.%fJMeC7]OvMyk\K-C87{+RngrZv*ߏvs9>R*۶ԅb5Dј$I^ !]CU @2xހV[F`M_[>EķN)8)S8)̦){Оӏg9C싆.:ß>ca6SU'$e]Mι薘u=>j\:~{3⠖kNޤU ";A",Y]Z+dN EoĄʢ7Z{K#fmX*r*.21S:gN)bX5F9qA Dtgkkk++k[, ɻM kXYY*=T55.BM*ћ8=ZNGP{+(QRuVg@49"KL#Z-i5ܙ-i[VjK,bV-߷C{L;crviVvy2y?-,p1NEc &(xړ*~߸m 9 !4!J\ZilZ)&_:$O;FAO cd95 ZHp qLIK;dՈ=b 7zcЛ&H@̘-QAlj6M?yh\mp}nu8q:f@z2 'YFfT; U*F/JxܫDoj9gX#ӧsq8?aY[6JJlu"܉?:_ ut+YSPNKD ш{ KE_]W!id,ݧwtהl+^aF,)qL5ΩpܲI.oͯgJJ IYUhW$_ NtrS5~of5>9m͸tV3a9o,f/@CŗgCLdaie)b R=q7A 8200rbw`ciI>5 K+Z Krfk4^f#<üT{()g kx_`c6DѥvŅ0 DkGXX" rb,$dIeٺ+ {<&ŵ1rRJ%fºqVsz[:灮hݙ)g'TH`(+Eo r&i}ћs1_48榥Pa*UCSY,g ˰zU:MyNm +%gcTO{kvhɅ&fgZ?>nch=HccVhƭ _qlK=tϿ?|.HebG~w0Ʀg&dNfՂS@78 н,+r^4c奬[/Aotn/rȆIV] \a6І,""z\.rqqI5o ײGou ȶf*txBWX8uodV GETr[[ @P“w5~;g` .5𴯧\U`qЪq.mVLnkekO?V<>G,-m(Ss6+:j]w!tM gT-rX;'Zړ +韛'*ȣbO*2֤j_7jk|aBOʪ[ݣ\mnzM2"eaӟ~_[nc;Okl0!+ˑOnph~-m;nߝ/lӑqvDMrʝ@uo(q׮]~ ISz{4iL]paa'\7w Yf1?|t̶L33%ˍmG1쭭U` -bG|=O4@oQET$UPR]{6lpcg ];XqB֡73N=Eo,#X-B;!YxDRdaiMWm';Y"G\uLots f<-ˤzC"k.r~e~oŧ]zݏt*0w#"R"R(PCi 0(!&))W͂mA8r[zThS\.$Е֮a_, Fo d6y*w@ QDw.U=m5a鞕 `޹[O|*Τ'1YZM>XOFr ȼ褮RzLL3=Sej/Wӟ>,3oG)bB\xfwI,YSl QݛY U z3Yz͊H"h> E3yOn,*ܓ}_L-i$/ tϊwp-:.B»ɪ-QhPQZLi$:Hm5l 3}jLlX1LebgmzK+CWdJ]t}m:=Q;]6lg`N5q-l\C:z+)7$~Y[:\uwogj/f*m=O ԁk &_)Ajr*Tjnl*b- pYA4VYNT0G a9P]}6ݠ7#Ǜ)uج,&v}Bb/ۄSS4O5xnYOnwۺǔǀؔqi'DĜ:+ZΨcpzS}7CzqAZ@ K~of] D| M ĢbFl|%k+ &wgVVVuj4ja ,$;?+b))ӪeK6BfE\ldOx^LeGP5G 6w%W[9ؚ J`2NyW҂)-U~E~oo{H">2;w%v;zsq$Nijmw[ GSȬ?lr_PDtl]V_I`܂,412e&ս0V]o<,M֯3.#o{x=\n1&\EcACTGF#( "H@TQATT$ *頻gg@~f5(V|J!c?Mx%&}0bwQInF')xτNEOnHٺ+'<1ͱ'7Ǟs2lɍQ갭YFћXϽћR1D6 z9QYb0Ⱥ0 QW?FDH>WF>3.FDTg!Șc8t`0BYm҄SЮO;u t;ÐR:Ј]vO::x_ױ[L=Doz"ęBo,x%@o˧6<zc@si/fHf%P/VwɈs_ kq&rScc)6 PϽeS`DSW!zՁs_@~؈&:g H]^*9$GIn<YxBqo<3,{ni*)$juwiގ;͡&|0"Qm_rrޠ^pvcTFX? [26gGi-ȩRRתެ.Ÿ gi ǎa3T8MEc یsN3+WDχ40f9_r~8}7#F{<'C&32#<9v!kpyNeLO Bey(d^$x++#d W#.TB2g ]l^>kA`;b:%.Ex8w-xEb$} 9()eYs' s#%.8r94/C7.tx[ğCCCf!7& ;^4Jj?)jK&ޘ!甗'10m@GYқÒ/?[GtrO/SMqǸܶϟ6j{ǜϊa1/4TXLkwZ`SfsĽY\ z8Ed޾}[fкբfyb:dctN94vP}mnuӎh57`|1uS.zĽY\zSO6X+ALeJ[EMt9M.] ,3[:mI>%?m!+NkY{$݀{G7OΠMT;3j{#^hfZ/s ])?4˗lC_{7Nh_:2ϩ&s >>Mγ?O]>}G?{R@+*!pY1kh_;G+)]ޜ;:Ӥ֛ujQ7+ *p3:6!wȯߣ@^X?5ܩ/& E["& Z?O=?o+ㆍB>NY)|bsjqI=>%ǣh3!"eܒ eMiM Um;J)ҘEm-zSS믐u{H> ]xq sN}{cnčoYDh qhPbDbK10UW:T &?1>g*VʒZ{S<ע-b_Zk~Yf]'dӭn#OJ3,5Im)iOISnN ؠ'>+AtD0ɀ1"V}@sr҂1+f4`X@:{yiX6]ѦEo9f~^7+ 6Ïw!;g9*<,T 73LRA^$L͸M7ƽ *C6FAo{KLvw֦Wo&UnqJWM#R?Ҹ%ef%gA/O(*jIR79$qtZN:=HR2D>9T0;cIHSgv8b8.=*49_<%H=tZ`hKbduCO4H!I5GLNZ2уQe,u'4!?䍱+#'tèl@o 1liM=ykV LrdA|B5*'Xs䛙!Jւ̥𘣷݇n?NUM;#exVMz>`"7В crIj^&'Hh1髫l(+HjТP Лh0 )1k4䪮'Ex{j[ܹXG_uNi[N(d="JtZTD}9+1~G-bG큢f ý5%Szl3 e>m03Ͻ5d-XdYHEÇm۶@d7A^IS`)S9kqFoq{wgT.[?j.:ez.7L*.z"׀Dj6R 8eB7ߜ溾I $sqĚ3C?ѳMd9w7zo8wE%Cg@2ͳqM<<_AP5M 9]=p#{3^yh"EZzBtܱSRj oyԸmqo\7~a6mH!_ lG{6>Ƹ7A&Z`9*Y c޶mwUO=K%o(rs# HDa!CdqGñ'{IP^I,L%0"!m78" Ce !Dp(0'V6y%y9V r=%Ӡs(xg椰\Փ]"ހes&yAkYFD/؋>:;!ZͅB۔wv/HآhUE%g뛀:N "bv΋I(!vE񨸕vLwa "yq4q[?H57-z+//oY޽{EoYq薆jÆG lMa3‘~V 1Vfs`T[ $6Ξ}ڽ{޽{8pܸqݺu79qL2ST A&VWY('z۟7{U2y7`قNs97#r28xOD=8Ôj7z u$E2苯ZCN ܉` R߮31^|yOMZe6YD_-ؐOM&d=,0V x̢`| OF$Co̜ =Is8#4qoQ,'zDo8n_X=] +鄷z;ЋBx.gg'w"ڨL{{(L;,^ړC0}I<܌BD3 &0*t $HLx_#tjXy  ̦Yq;xJ00Bo"iћQs3ÍQIBUӽXBYLX-=I"OޒpM֜%Hs N Eeaz8Ӈ*!NfH('9洊$mcַ$T?DFNIAa} ?*6$sy|>ͤL!KVTo>㧙wR7?W&MG(GfIMγФ{ըwFѩ㓞+~z,oV k圢I7|Rɐ8X2(>*YSWW'8 |).%8Z󸡷Oi=NoUz@iPJus7gqikݔ$M߬Lq=6z#ܛFo ey^L8rE3"y1'2q|荢uB9ܕEDv,CK ;!MGq%.TGcH QtƻhwH~h%vw$G7Lǧ7nEx7OoX' uA䒐8~+c݃2"ЏGt>}? i4 ,YLZL2Af$kt5cq#Gts=q7[.]؍ŽawJ8eRZLkJ7]z嗳ª=;5v/$uWeNaeo3dF'kE7IM86;.rN %ɨn`h'Rjs4^ C!e - #UdX!nD qh瀣hZ^7 Cvd Mٺp ~ }&ƖG64B zc-##Ӥ(?]> aF;taR' wO@)N¥qxJst26Ƞoȯbm ӹ|r:`=lsR\]γp>gJ d&8=8L|~h!sDb5I`ofւ)MPA7ͥa7oX~ȑ jSx|;g\o̜>1G p߷_3gn߾=11/- }S+-\e|1 ^?i;/6BigY3V8m|.x.g̍ ǑE)5w cl8[$@uy*ު7֬YS[[+k<4ʂN!?q !pp0N1Xz8Eo NwK#-4L N?,yRacLᡨ2ƹ}=)z;5uyYi q q'w4PJ sa3Bc% {U॔ޔ(7VW̽uTN1֧woVdm_|1b/Wx/Y:`~*~$_}ٚ? ߹~ޯ_ٳvrDN%ћ zk6K!gۇ8\Tv/g]7::Okx1r-Bo1c9w_e5H@m ،pKi챕̀z߹s۸ө4'-P Ѻ >C3ˍf,o/L࠾x8ayrdRd)&.zj #8Y-Mbխc ݦ'tOO}Oέ;Um\ް,{,{ֽ]k̚1{{)VM6671oA(Oc歬iFX Z 7vc%Oϋ֧d; lFp?( X.~@ҷI$3fLH!{Az^K\a9DFɊ{ٳ$OWSt9M+?ϝ$)*oHH7dK#k$29Gνɍ{ ckSSSoܼ9o޼1cFGEx- tWP|^_ιcwnH䉰c;NoI<Ī ߴ9,\W_\WP׌ʉ{?ѝ.S\SUK嚌6UTUO>Qōjt(Mp\Rqj}yUvh}˰CUv P߹a_|K}z3i/َn=Ê2aBFxCz/-+sXkGNQ*z\E^2yl8___$ޕB\GZ\'qn: Ѹ7 a0X1XЉ 7.8?Ģ>}+\|dq#L`aYDa7o~hޣJroFZ 6TSī-nɕ+W-Zb>My?g%UEYQz?3zcRppT,^TaBk\yiAF2cVڞ[oorY-VW xN9uw:tuƏj{|&yD}zzBWlPݨ^ҍꥡ[nϒ-V/ XoĜUk#C1?{˂!ޛXkA4m~ۃlx?6/ujҟ5ߣO{ZY#w|]G+'$Лk!-P7zV~hpwv٫8 TzHGћo`.>.? X284=A!ӆnmK@+5Xz ;&vG6Kan8 zxZPPfRi*rNM.0i~h WʘKs7d}!{lYOD"MZGhr±'@R@V; yTIzEo˥ՕVޚc XA 8+U|QA5j'g4Ɔi(U0ZyTh5%BozJ ˉR$-,]"X9A.WD9A]YY =T7d0';zD]VzU!4UQکޚc V9/ɁȉZrtKeE;6?|/YF:'=.0+/ڃ(678:+/+3:h9>(; ?򒈖;+BFi9 rD9بF- Y2Wbf X\*Me 7Ci|S> goo UB"7o ߺ׷/N]Row9% 8?aP/̵*/;5篖&d\v[fشC׮];dpo?0ˁ SO"xЋ7  <7[OVEVf&V"|[ \PWBZ4-3ȏѱ;F}Y7X\*Me Z`9hMpo~ƍ_}垸kgDt̐E&;7e~xgO:5.,3Hޝ۷v%su2ם7~+,sxv'L9I=r}plzo)WM̽Z1VUt_]msT'TެQ[`䠷P70^4="HH yvgAqDEQkCkY-yq'Ff4T(7ę'@eiGq荲)iRrA ^\YpDUD.]H&w;q#8[#'-B 4IXO_,墨cw-:mjwB=Jgq4sj2TRޔ 2)͛vw߶m] cR^(\ ASBFF%C#ҏymSKGQ>o X\*Me`ւ$PT``g#^w|hǥYsWJKMWvgؚ^|^wcr;֊Q>5nܸO?{5fF-}۝۾lU͊+兗+o\g[HӦѦӏޚϵPFbf7Vcgpδ~?]*xfǧ z}fµPƃ X\*Me "ސZ@7qĉ׭[7sSjwf%~_Y~4=;ߚX%^~Zy,k6l~B/6_^2YpASp2SΘŽǣ)JYbǶt!&&23;΁n8m5u{VD6{7?t}zoy'cǔ ES?5~ȝ{NP썋{3fk5PۯΫ? 杽i/gKUWcf-qopy 'Q4￿Q\>~3pkeI]".v}ӹ":}ۋ;=|ǡn;|~sq?_plN~};9kg - i/ޚv>ޔh X]W^[ly>}kqǺG]po8nb.;^n g+?3=>GJ]~;sYwnYhQǎ3p|I2z~)Wj<fR̀2wh:S oW W%P{O0K>ַ_ѣG7[׶xol;Kk=k.8*xѲז_0Gn}]Yg݋ة87`J)ޛXkAZ\|_ỳ2x<۷-?A ež‡9x9o,ԧ~zذ VoW.8}vFJ]vJcoiF? >qO?zbmMMRR[of 񋆘Z`Z &s+ܛVf@3x xicBվe>8G;r=MZzg GM:jkn\>(5o\xo'J*9˔?O;{_4`:vd/i̳0f{3`UG{%mFo5;TXS^'%3xao2utY92-n/֬YS[[+=2ߨ$ZɚUl *a/U] L ]\8D =ܱܽf>4=7l, oGg,Y[ߕ?~)ᛔ֢f ܹsV$YPx[||Ν;嬇&NvkiZUl Lj-pd_t;@;t|;t@$ؘKh笭-~=yOʔSrxn7ɸ73ܛ9L7"Pd?m%}+`nC˫zF R]D[p;Iv+`ڙUЛ*iי/,1cvZ0 r҈őU :?R{ O(ܗnڢp2X 0̍.s+fcjjj6ljO-)/  , ,$zh#foRQz3`{kZpo28kmg::~1j,K"3{\1c~ҷ}ԣǀO?ֳ'6Tw3ݸS:H`CK/N |Ⱦ?ˍ{㜤bqH߻ n[oᇎ=zӧC\>6bS^mXq08Ɋ!nSƽqSqΩL_%|f[bwZ vqyq$ $uZ*dL/ &s*ͽ(bsoׄK8V4]Ɩ&h{.`J/FysXy z7OMi );}}zd.[̿m==Xm/;#&@o$a3MSq‚EUjf;NuӄˇKC?4SNalzghX8c8ohyyy\ 0~gm@CW~[MU y=hT/a2S $E0;5 0lv6qo If-`гGo4^ޱcoQ`V8 LwQPk Eok9qmɸ7IfszʅMp1u廼nJX.kvGU7<0-|og{{{gFt ̠c {n*"=<6]*,~HjƶL;Ǔw~R-dG7,,U @r;I$H-s&L_bM6,90)_bWMAoףvq7\OdE!Y ;,Jr( /Gn{W p8q?m\Ž1I9Tꕸ7 zZ:8nF>AobHUGC"9{c\ '!'8}dŐU6I/VCo#"ގ0B?F RO5Ͻ&(yj\2ށrtu'qi+<]c#9ㅳw[7M350 {[y ehuc#@Kg}Z9t HՐ~)B?ـcy$aSΗ߿?_!̒J0B7I&XЀkQg163ܴϖ=s=Kb8LP1D\ ) FkڭfΜ̙(DZ~~97fϗO'ϏwM:s9ezMkjYFl|Tgm/1490%HS{;{k @##q2~Br$~FM$z+pvhU T[d8QtqMO>C}&,ϰgrxZ1 aHݑ̩:;zF @Лdu#/fϚ ƅS^E 9`ΦЖwpm{78X[znxfOwܚ&poVQ`S_p,ʷ@%Z(zX74 *5,lXRl_п >}[t)a+0Ee'n y{'ҡ"տH=wgo×}vtO8N~E~zv9tƵK|.ݦ|)p$7bU1Go"I 'Dx@. S3Bozͱ=鞁nh:7=rK OGo3e ƷD s]H95t/dv!JN6ٓĴwzXyl␄9zzΚQn,:L5.n-H&GB, $$pdclR*(%Rsgo4n{KB*ٲ;ЌM{L>ZBZoؾ1d#Fkc\݁s0`! hq;&f&9{(k50VW~}•qEjt?Oɉ$rECQn\*qo2cijP i4TMFk@o$hL[.U))]4STpGՖ炜Ϛ[\G;Ew:mn+L[j0 -^oBdQ`$Z=Yn)ue,"@#V@ Y\$S~[Soj`͡>ß_x-8Z\bϩޛ)pѢEuuG8{~s?IwVO?omŗO!3sfîR{}~ĵmGJ]cLL>}HZW a9j&ZP<WS1\E4O$wsM&i9;CfV3OO$^7>&n@H999L=+JKKK*LdZ7, š9Dj)IM3 +,&V>?5\9o ߱cgoc׎ۻeqk ]2b%Ǯ8Mts&ۖ{-Z3VFt5[c'`spornRy< עg7n˙3gd~[͛;I.G/tl9zQJ $\3foJ7-Q⧳-q]GnǔNxt[ UY[ʋcc ǼB|ZJ'r=b~+6p&5fވ@o)g2 U(Ui_E]_W2xbEv%$ݫ~Y}8|3U1DfւR/g!>MȴQtNeNTӚ JnÈA7hgeeɱAŷ/?n:pz̃UyOUq=ӯKC<"՘Y\, x,5>2ŮIRw2oZq'ӂGTN T~i' b;y^Ml^!`(,^/|| Ke0(S3_ AɜSH5$][^y$1gV(/fbfyK"͏,^swM(U> Bn{mߩ[reo:991穩S.kAPW">(ͪK7͝Am DR޽G׭[ji kSE3a%6#k-0o8)\ܛCtVA 3 yZ &KV`7ZPeCxs_J@x{USٮGɦy*:vx̄((M_w]v bcL@5 $P{ӫK(pTpo0ݍGS"|辍? Fw'b7W'Vu{I%olH.SRsأ#\|DeIXqUuCXΩM\`=z+O@16$AZ$[y Z%;eA޸%L9v'N [{u;O:Q+a9?yz~ e=<:M֮z?k{{bߗd9N˵gtKroa1TSo+g R9bN* D桜 8c [ւ_\ꮵUWI ăq~ qU9Ms03J5'nG駟7 S7Sj{kZ0gʶ3,diiG*~).xW]xMF/`}]q2dYW{ ܚzm? r,rD ;iŽsNA!"MZ\0x46fMfex73ҟvU>o9yɥ7ࡅGbfdX'*m:!JG}I4xغQk Umi)cq_fւ|B#'(>_m BAÊ`q/Lĵ;= udD-jn:gq/TD &.7_Mh9/Fq}~UykV?M̈́MJ~8|+2(Gozˢeyq ɳ{cW-Z (g@5J |7J B80`2=z-@0'(yP7k^|FoaA䪢 krFs^(zn"S[zz:ב׊o c⽦͇5hm 5{A 2d3$ +Zpmf$߿p̰N{|[-Yi;?8#0$wߝ3mozOll\yA'r bޛRתأ7M3$ l9e ir#*NbZDKahI`ҟUff%,jĒ8⭨"#f۲#%`GogirY io O Pj౷ A]D'^[`"JOUJMqoAsPBq^!Q=zo&XC YPYz4\ϩ|j38:LIa/f 덀~'vǷ@@q z35*- q Ö^=91g^[xyeQV~qwFMs&??qfkkŞm_yyH3f? SqjS;{NY 1Ժ@ћR"{ћ~ 6(xs +Fg"f(=ONk{l Z zc._U a @< 9xbӔݒ4 ީ*bւE0'=9JmZJ|&kcƇf Rz+X462n™t;g G3m^zc~.0ykՂ Y ,M N91c0igyye['@u# Jg;B?m0{_ :u]kwgŝ\ Lz{Zt'6I-aG㩧ϔzcY z<ƦȠjJЛm:D!*ɀ\7F'v+mMA78Or8,{Jf+7M^ԙjt6B􎚪fLބڢqzl~§ z3x[zcAo۔Extҁ7hs7tꥳUG΢ETa,~ c3MEaԗksZP˃Co\ E]zZb{usA<LiM=ܦEo]^*ɎBކzo%"n7 MdJ-dΔV>?..Bf  i95zEjgQ`bJ/`ў3UvIǭs؈[ 0󛯏?ea9uwz=XUT`,4 ;uد_?&(h-1qCoxoP,)ٮsJ:ܧCPi$)[..".e3?)C, "y]rŋ㟈m໾M -&+[DzM-^ځ~Ҟ_חLΩX<)f`6O0dZcqo*ir(T39 Q@j j{o (<m2PǨZO5[6O:.Lol|~T/٠a KSL}\_|| ]sf 1#q )8iWH^۔O±r!kq?QZxJᶌ7<ѧӫܟ?{噤YC֍C>ⳑŅgnOcn5A'55uK+x^(lޱcGqIT[75KZ5q#f }<]9ԱzCpB|O,Ο?B|fCkˮbVrTT@T ެk.ͧ<",D;2 &ط/ >5e/>70zaq"є_Z"Y󏕏9OoLZ{ +P5eqobSƱ-@WW6コ5F|ͥG|߿Fnot>\&syg3o_;zg쿟{a磎=VZRzzq_s?OTTT8TS,. ?~<>> oMrSP,&4%fnS\&At%XDj_z5???//oϞ=8![c5z@m=Ƈ9?.?. }cܛ,xB<˾tg N%}{;~-.?S驩~3};h+_ XjǽϚV_^xWZܭc_mߧ޻b)s7ɸ70JܛKߍuhw ׌6qUp)๕n9򕣚-ImьH2S1jVwލBXA|հ`+vˈ }C8۷]ܻoӠ> ~;v fL=5olbLx_b7_}?dzzS׿oﱥscEC7FIj-0ϩ%s=nmZnc5ƞUzC6 z;w\RRT9ii{K_^bgToxW^y϶ӥLJݹCKRA??SڵUwb^j%Y1+ZP<c,Ήb@gbBzC6<.\ ެ}V={xc/X ?ҿ|/=oCo\Co(3UW:TN YHFo6f$^H:]1en=a̸a}~QW.睋:nI1X=(YٯOU?ڽG_/{NMΗ6z)h>3 J- 1(CdQ33vYO;UЛ99nR\7 i:k%i[fVZ}>NLH޼asHDd+gfL[vcp1_7?2D95po!W|HG_/DoLA:GEA=ٵkCo!L? Rެ4|-݀CobsrNH~9>!fTj({]{Nzom<KTVw߷wktלٓ&v 瓏W&9(eqz!VK{S2|^f5Kܭ$&sj-H1n>3PƵc4T&Y1|j3[>;w! :ժWЛ))@7VW\MRUw }X7~1 "t^+?x}vC`0 pG7 懇̙9}/Ǝ'Mjޟ|f-xN-uEAoUjvץ4by-QQQ>r˂r=}Gf˜7N~ Svi 3Ž -boV4:s 3'$uw*ãOO:+Qhǽ1MxaBXn$jMCw߸+坆m+Mް*<֐ ̿eC>tNwǮ;w3RFs&dg6yw @C?X`Wcxy]2x]z/.p:}&_hM9.O_j[bA>XzMr]ʚĬ*)a7l]:䲮Rmp9&..}"pfSs=l0,Ѓdf [FFQa{C oi(lmy}U>4iroS^Ry7Uűnj grǬHF{Oesj aVM$*?n:pz̃ݙRV#_d]r+[\io 9L3mQ|~iQ(sjfYzcCoQ79WSnƗXg7A\XWzr6ua„}Vؼ)*rKD5kV7۷g ܛd7霊sNzoׁW037`ٽBy\%:rqmJ+`Ao]>8ywŐeE0̪KUm9vyR쵪ׂ#j OުL9&mŋ#~'A?D{A:zڿRE\]y粟]/ɹYu/#f}F8olGQ (8"M[M$V[ܫ&X=vQЛsosoxbuIDE0X`YkHd-<ĵ_J~-jWu==U(T1OZGƮ4Ɂuf-(ܛ o5h-9|*dGliiIUUݵk :}6t(P@f@i .\Huu [y^xX䔡:,f+z,3ӚDɁ~ ]6޺0ݞ+^:fimy H;u^NT;pj(E{s&nW_‚]toW9p@y7nޫ;w|j^Cxʡؼ^qWϾib޻4zÂ| J 耽>!DD:ٱ^bj_cXA~,kEZݝ @\Xq'?hwMl-39:@<؂;Z#GgbgBvI|(_7K ! <8z{duNʕ luC#myCϣܿ3^NjMMY d"fmX2J槨IG qk?w12xqZ;ɸ7DT,~5zSi&P8589N>qlـJ"aX`끽h0` 1ʍm8bE ]4ހKxӶcƌnz<D';90c&ّݎ w!KjB[0Co?Kp>aHuɗu)[iml? 1S]=bVj=QH<`ŲN dXhzo6p._W,V+o56F2T9B >:$H7Kƽ6hpR'Fçwz!:*z܊TNsi+l~*UG,V m^kÔzNu܋_i֞8 +LZsʏ{RtСÇp81e ;Jomւ)fqGo7t?:R)j@o]FԙMzN1@ce{ƫ!S. b nI8໼n77~,2d6p]t#wɟowZڳо/|}z8Ls=G5Փg"6.Lu'[eϿr_{G2zǻ.-ܲlEo䁪^xN-Gk bY beG/+nYCq3)~'z{1 ;iCo:]2:s~xtzGgL@GSS3 SW#W Qެ=Lqoʿmtc 8 'Z뜊ћR1DRXzw~6 9Lp9jăH{L>Oվ؇ 4|~,9zvЛ#;kǁ&ܹzB)Q"s?3M ~2GinTzfxW#ֱ6ET.j3|76V2?+F ׻ץ] h*#WAZ?{C7%kAJP1D\+mZ{I>2 Tq'jp|)RoF)K+"zeAwECɐI3)VjL?TW4hU}T4T0n(K_kvRmQz;HA%Kqo ~fot#ut6Nkc(3s[ 7呀NZ.3uܼA\yi_>yo,q907g})W$\J{j?|c荫f{"H{DoԘ!ŧ Ϊ/Ao혂%*cm".X[׊)f.ҿUm @h#LМ xR!fܿ(SK, FVWRk7>& J fSTx8JY81%ⰺZ5v靕SMMgv)ۺW9,nQ?z/wUH ߦKy~Tǥ3?dc_I! = ,`M7f1hUDVhћIhp@WZ1x3 UZũR@M.]z๬ޛџhKŅgIeŽْw8#~ ^w^|S}})hߜG6r/b5K%Q\",(uɥ]UUlVorsjqٛ2:MPD35 ޘSS9 qo|d79U,.ћM&ض$dY _ >VeV{M߫HMۧs~1p=ygЭ3z[Z8ڂFR"ä߻ܛSyrp' |)t9#Ҟ"/zklPh4%_R<OtՀՙxSbik i#i3ح+#kIey<ͬ4DZjVW54UW </Ư+#T~Qf `/;AkƣKW,@S[z?WiIa#+ؽ^Z^pV_9EY1%T]_1SA)K̜a Hchݺ;ռW|3oaþxGPQ~gqzAޛXkAZ\ zl̜478%ݾ|]J%ݏ 'vӌ͛7[:@33`!F7䜊79@S9:')܇F;5-ȏ{{XsB#gegG9%m95 $SkBac{ޗzhƉEںNeGl1ɩ'H_L֫po20-n_7Lw+*DEiHpdv>} @f ޚKFcV]=9c;mxNUx|;Vlr=ͼЂSڼ敗۴ſH;e>zդ _ 7:rI{ \WWWY+.%=zkɉZPT嬌aZ/o^%+o]o^ѻ\'DM6ə7F@^s*Po*Jl럹M-f-࠶/!)[\r'9\/ICmm-G^z% *HB,T dEFu3[{n/N9ם6wg{j񵛿\VRv̹[l<!5Oq9,NTbZ|<ƍKrҭW]0xl@:%ͪţܛŊ!dzYě Ca4P7/@)K\H甋oDZҮ|12Ư;<$ ʋCr|n\]S\)Ux_7vi9wu{xu{S<2cdΏb̀m3 9uhr{8L`3s}S-<֐ mlz׻v4/|45wG|푑c'MLqtcbt?Fm ۬4ga? ExF`s9~>uwO:?`ѣ"CQ, e5?ehȽx܅_rΗͻSrMuV~㱋~w0;yzA[_G{Hz3oy_b֭[ΩI&&{ݡ~Nxl йtvȦ7~zl'A.#ݰpMqՋCRsrr  \]xv1ũ'p!)N9SqΩR:P7S(3` 7qJY6ˆ7%kNr3Ԓ0JbT`SYST8EZd+qoys*Ƈn[N_W٢uޠzF78p>s׹E}~&ڲ]{;xwz8Tнe >6> wce޸7ԣ뽱Sƽ)MDo5K/O],([ӟOq^f3(W'FF&y@wQ@yy#Vj,zN% x 4WckN+3$3Ims~{~4?]Ö(mպE:V6O*4B?&j*Z 6mLU A qNPk? 9:JZ?$\7|nW[t_l&R@a:[t=nEfo $\EnN7\gߠdɥ_'&ZtޔzoGAo#s'^ z)=CFd{"3[n1OZFFQa{C oiG|U^_{|էMIi訲L+0%;fjjOPcސ4PᔫMւ}EQMv&9_'"54={_. 3Y Vc)#&?d~Z(R`(^x6<Kƽl4)(ܛ"Z`9h͔F^݀?«l&FI[t~8]lfKrv]MMD]FE[|]݀5uC7zRkA)W)e77ʖޖGT+]6N U*޽ZOŸ̀&/DZJ64۫ E{QbJ(te<ݔ ބtNhÙ*96LjS>K5mƒ3p-o6M7oت[_.pnunkn–A.V-3黏^IǫY]kkЪd0&ޠRxN-.vL[z!w^>w Z -OY]iN@H۷l8v 扼E7P;w|j^Cxʡؼ^qW .7Q΁j|u=@=}A$!<;Lr,iՑlo|DvpI3oVH>t'} 粮уݕ=((a[^sMTaDoZ3bm}Yz1mAy-WVnKV!ձg=ŧ۪Ow^>#7:sM9EWMZjAV8˪.P -R̞x{b..?M\?ޖ-;"/ܩJWpSi{稓OIR]aw>]U\\ͤ&)*)9(((H. #9(3%3!\Ʌ|(Ced,.S sVjuZ/+M+pb`A9_TXsP@s:}q\5Y '3>)E[CtڕMqyܦ!0X'5%z@ז2;+ij0,N O4%PO]3MFytsErj4NY| D3T9sT {J+n 1t7jF:~Kz7xKQ$d-MvnFnYzy;oѭ}e 0冺~uz{s'.pqoM+s6-R:,ت Tmc܇n/{nLB7s^y\|Y]QFܠN?u#f n(H--rܶ`@{7RپQө:GE<Tؤ6l8|8nѢECɮ4juN6t7G (9r|V"zXך("ޞka*a!˯♎S`%{KZʾӹQnxTG*3TeZuI?X7?i~ÔzNu qi5 tߌD|WƸ7;\ފϷUNMMa?fhvÇo>?>=&^>ᾼh.d)R)KR2A{ijuZvng]~: ͡ !jVڒ;j8l"6H81͡79uL+!!!S~(J K_^rGC&?qoLҞt--ss ;p<Y9Er]ݕ-1qֽ'=SHf:Ma lx)H8;ւ͗jR.T=GP;"Bψ~ζ)[뗤mwv|wϳo|飕>. +ӽWzkH\zcSC,ˢe$m3)>[CK@i7V ,Pp$ ~fot#ut6Nkc(3s[ ?0_@!~\^sBqSo}V۝xWIk%ahqot%ë˵%pPJ!x]\f"z&9z>a0Z0hPAYH;=X~wNV "٩쑼l YzۚqZ 2ctE$9)U:1*Q™8/޸qC{yW W% 0-{Kh݀!G~d$ʹ^zc;X;(B౳MC?KcrG&nu=Wh{~u ֽ>ؗV$NY)꽉ϩť2Л( Aаe+wq{9,nQ?z/wUH ߦKy~Tǥ3?dc_I! =-*s06^ZMԥ[ L,ayLŃ荫S1&w}}9 rN +zگJ !kUv|lg 7sH7"/N]cॉdNTĔ 6g UW*s-1"Ťèp:jTp5['O:.Cb2^MNSv4&SJZy X7çZ,=V/H;JYA=?9{뿎>~FLK}Z:L/;o{nnu=ih6՟ξ/k-73Y J^9+BoNs΋Su3t>:taٺt9wV3_{ziTl̀uNCEฌխ-SƩXj5H(>|+FMw+Q6^|ySziKД_ZVI'$uNWtNM]AAɜSH!&stAB Ru_ޟub|ߡ^k>Y_ɺ>!ѿm;VԳݏ9M V;aZGo+,|ɾ'm1MPWbsBo|_wDVۗK@UCk;9ؼyUӨ+3`1,.bŐf2faP7/@)diCu7'7+E @,k}̀!u9~+ߟUW?YSR@ufVK]{.+G~A봳#z.u\^ޢڞU; qaܛ sYkǯ[%}W9so Y !FT-ꆬnƺ^u3~pdAk '?7_o Эo?!kuvZZ39T;bvj~WW \kGI朂BDd-X\!zÜ,_߼KV ޺mѻwNo6m8i2gŽ=+NCo\Ω,^}C^|#nݺh-X0& {i7RC_/tCj>Y^Wh[׮Vj{SϿotС믿?Z붲w5~кvW.^5[Z]|{cqoTs蜚_/\p{֙*ؘ$ Xm{IS&ƍKrҭW]0xl@:%M΍C3rΟ?/VʒôaaW9֯~jqo`7Sko/*zƅw Lkj}}~W˿Ͼhשc:v'z/Y]UUV;teݘݧk;-ϫ q:{Èt+Sb֭7n w%A0p0RЛK=Bqo A)MւEԴ>M@7 ?U󐯯dSYYpof k圢I7iC]_lĿOgVJ3$H3<4:whۦ_7ط~nHnZ`^K*?]W?O뷦[1EvIrܛXk7ĽY^j.] t~$Zelϣmܹ~9sf͚5sӧO2e|_=|p;wSΰñgD[TҲ2 զ;kO&nϰAgISAbdk,B[AёR L+WSiȵ-kLQ73SrNm^<ϩeS>ƼJIJZ}%G"f_ Z5_Ԏ^W=&@㶶6wЩO<*]ee^tpz-hV{ض7%Rւ)z%MOaPm~V);*37nfԩS&N6l0~^'0NЬa8SG×o=r׸c{3$o&EZ*9>OLsf8e]e)@t,+neHwFFl\|93z5&kӮMg5rzimLX}BT+HDzd/oXD(:2傊!b ) t }s4{nRDAOVua7ܢgߩ;)3~eh Go&6TRʼ3P~j<ÔNx g͔Ͻ u'> n3Mz;{θ?"p[MeRT~YSC{Bi/]A t˩3Xp&cɹIjZ<ޫFQ<\ʥiWu==ەhķeMٍS'_8T<'͔{{7A]Hj-W0EMIw |Ϸ/ҦSv[3jo=|۶m;u󩧞ej7T9eZ pbJܛ͋CQe39^t8`NJ wB-]npvi.(:0g>"d<f9eFA}G7~AWWp;OQ}9fu\g`0TKtĊbk=zz0WZJ¹4; 8̑\l)cAn~׹vvM[*7W#4 eovbK/mئɋLoj2~Ojug\d<\sHb z:RoRtqg޹hwqǜИ%8P;tIUJǠ-t^1`5MFԒI6~c f bΟtȟEk%au^ɪo|֦MH5CfWm ܦ#z9Sq)*JY6/ eGeh XDo4 J'FbfLz)~>fO _(!b F{ !j U)> +^]?!mD^ex݀j,b2NLp, AҐ1F9ժTiIN@i.}P-c:h6N#&=dlOh4%F\ÔzNuZQT"ѿq ;]WCq677w~Wϩޛ@^N5c}@xƸ7/O_7s rNPMxNe.Leṿ7ٳgܛG "Qd CʯϝGN7g8~UX:qS|ϱ.TLIf t$}\g-pܛd{Z{S<6/eGeh 4!&cz$Tflz'ĥ=8y[[tZ8[HNY1I$jWJ"ZJJCgǨA'엁tei!ǯTg{c:fš̀21zǽ=d$s*5(s$Ӱq!&27"{$/;/in͓_xIn [uѵ`M;K: 4;#sy& C[Mv,'5xK^F\_Y+d4e +{!>at @-/$uN蜚!9:U 3T ޸!n'1MPW"닮)3Xƍ7oδ~UbICc1sի?FP0F%\ZL85<5K)A3|oTbߎ慠lFtZbO|@UqTW; Ν8`}cm+~\@]zcB=6!J]ri/vz㏉N cxۚ½6oKeRk0lFP\jBq +m7RMZyY(;*3@g@>z3sjCܛ(&"/N*xib` Z{ūin)^ubJF1g׺_Uʸg cZ9 qPZ}}I ~Ѱ]ϊQ5/;C>'PUgpNOc鹷$ok)O ]߆lל2K K4^ ƸfͶwU[~w+kČkEEE׮\+5!ߞ$ D"35 I&>:@ףז^/,.5iJ nw[ub֭[.PT-޲ƽqn2olȮ]VB~_q^ee%+^WWǾܰۆ ,ݻ/.!늁W_mPyN9JւE`чV@ V`qq6g1zǽ?c k'Ҙ1@vr](-A7몫kjWWkTR|$Ob'6F^W>[FH ^]C Gx|$ǝ/1S8U때{h4Jܛe`! M%Ye g-?~i&M ӦM2~wΜ93f`x?-^C://<<2d^_XAaaÆ9]r7)7XޔSc>ғRԍ0+).!3OPoFq|{g<ujBB35~>8P- 꽁{`(q :|nq N}I@@[BizU' x{VQVSKP#8ڬVy5}4 D"|SiJp 6މDT'Z-6rIƽxN-.A9EFoސ99k*U6m_~}ئMk֬ Jǎyzz !7&&?[z-[|||B7n\ׯ_v+.\p!1z{so,kAAoEp9f T*89&mHTIZRyy۩bۇeӱR{uVRLMP2sY~fSL0Zgs6{kgd8VU骪᪪7c .KozB6 (c\W9b[jj(fhZ™U1Cuʫ+{bVW!H[]I=tk\D >D6csjZϺqۀΝ;/_lժUxy{{oذaҥ鉟 ,@dիW uſ .\͛7_ .77755qoS@78 s zsjx[bYε$,ʑbT ]ٹEОRSRyNQG6r^8 d21gyJ4{X&$)XBG`"d#g_wւ:S[>}gΚ v ۊW!bڎ=Q4I 'Ž ,1 aw>˖-Ym߾}|71zS*ؼ,;=Ss\#<|[S2e9PY _cgSwaJLMVOrH :ׇBIr&;l%= H6?3#(UpjnNwqqrq'cJWYpbKwsN"CL`se-%4#ݲ8!E??* .ؘ䡟3'cp&%:)جt2^8.ًJ rP9ES_Ixww9B%:ke2Pw'hrrIᡷcW$_X]fMo 4g{Vje%|Z*+뚚{޸qF2(3G3m5N]d> `(FHx\qVSH<\ XcH#< =cUϨI|0iلso% JKS97(Aq7ЍO}'`ю9܅E@p=p $'c $;od6̘ 4˖/4LIIB$jr|c@w[SU姇x;8fE-oGo@-)Yy9>N*YYyI=w | po;ffee1 *G82=5fN &-!-3l `ݬ4ndtn@F ^vJ́? 9STG3qBi8IД^ocN*!"-+OD:KBf?E>II.*oa0ghKHOr!#g;ݬzs"S-%\W5s Q5*j 'jU^Upe'!QM9evgPNsyQ7 sCxAvlP1@d,57sa:C]g-uÊ= ^`l}SbHڀE8ģGoWT'0FPqp}VUW K[H WtwvDz)h:d-`7d A%ՔC3C/+BR)Fo{C;Â|;rfוf-Pfqϟ_5$䜯sj7l{CeÇ{;o1bj w+@{=l=LA)q!6eC^/RHh1pU*JY)YPE*В?< Ԕ4@Ac JDoXz4z苍?6.I]܎BF`ѝx+ Oz!4Ѭ4u^xH ɠ I[(OR o1fYp󎋃c5|Î.%1z Dӣ7YY^& Sy' M7+kDo|S{sT_ǽU#ޫ_YJ|B [ A] -tEAR /#j,쬁BdTcǨC #y 2Pm4w(=w C #荥rCx#v!z%}kCQT5x+ad6o 8O߾H5ōlʕ}8P|?-p5 E vѮ];>zNZ sĽټ,Bo7͐Uq ϩkdCV~(dE)efQcfihTf46Up7OJ<&7zW5.%QN<Їۀ Gl949u{a)gOj f'6RBtL=hщ@p Su*s'],I7-q5=8kDIi4v  \_;zh-+BR_vm ! +._$xW.]p)?J+W.]b>e=猭?Y1vh[z ƍJܛ7b*TXMRxDZ۵ BI(T@>)$N=z9~L|<+2nݺ9:;ݻ׭[+ B=?ޛXkA\ f̌,!pl`H\!Ľ}y3 (VˉxznsHZI0/y'a|dZ0>.ЛT0Zd-k#C3@ ql"-Bȴ8Zq9ecK76:',Gi1IqocN,۹x1p "3,>Ta\Wǀ IE$W%.`wؒyK| ,a1Faq^ς36yE~荫S1f-mlAlZmm}mTT"AJyrZ. ㇑qrfkolES_99b3rNs={A{܆` plf$ >i@ܻvA9Dݽk:uB޽{{}m[`AH/ddQ'X©dZP79sȆ&M}ffB\>=ANvVe' D1`.**;3ۆ֎o/r&Gmy0V3sg0&SЍ̤ȯY紩~?R}\ dB L$f>kAkh˳% PJV")-[d?N@8=WMs _紩(C2";Q  Wт:`*5$8 z'$#\_5xz9EsjjC$sN!u8,ݻٳ='~3H5mӦM{{{h? ~ pY"6mzϧ C&Lxжm[AΩ@)9U*XB4!S _3`JnnbƔSC0>ŧEր.T ^9 rΔ-6d9H~mj{r *\d\?[` bLGBlS"! n߀p?57hd}Ϳ'L8x  tNW Q<EMMۡfT.~'ق GE%et)PIdeQS tV7W俵qDnj2sN-͙po|M_hA%Y7C,oo[ àE_F_9B$mso Y M ֪U2e /> mHqF7 9$_l@I ,zKxW͔) #d-Zn8̋ "'i܊Q(5]4ig7@RHH>ːr@YU>6ښÎ7Z^`* $ꚕ=[-zI~qxEkAKn/9OQJ@`13g΄ Wq͛6A-7h1QTcG0k,1)0k~=7N&Žq:SE:&g͉{GQ r}p*kRË^2Rh`PPR!r> T E}f YFNfqo\lgf0̌LH[ zl䖻#WW2 Jܛk 5{`((PH8Pe;٩C1Σg`px8RAAOx@t(57N䔥"B 4rNjf҈u"z=z<hٲ 7;g*r'DA[/EJr=K4O.'˝ Jʪ%cCݝ<(F@ubHur#70Q#VCkB x :ZLJi6@},1E {|󡛘'xRMa"w8>ԅkiǯh-|3U1@jzo0pސyRD  hΝO> /*0r )Nÿ0; ֿ)!qR*41JD‰=ϩCXV=zU.A`Z(*℺" >]X~4u䋁AHUBLӠ˩ZZBDE8hXQCKÈ@#Nʃ(F,COJR*G'o~:ahL҉+ 4g@mfz#Mee%Co AMւE0' keDG=/GRUX9Vp1 l*k(gvɸ73ᛂ½lA9EFoS  ߔ(˘/aq>>>,_ ܗ_~ꩧwpucoa p !U~WMAojȔYU7h\B399;\ԩK;) *L"1PFBUJLdrK?Uv7h5FNpƈS2%Yp#BaTa%*2:%;gL `e.iffTlto\7V4_d`U{CZUf>0ѷ3P%JOf;ζu])ۤ9ՏmҷU'Cx ϊ*9VL:| [7UYR/;y] jH9gpAnذa?oAϋb/`>"'O]bh荫{cZ =R$pIF!3JN.^qi.n%@om$P4Ͱz$zO|sr,g 3OB'I LQzHR}@M³m(Z 6_JLqopYR}G;w *}3#P PBJ)\kּk|酋/eU@رch9>ƅ)Z ]{dLљ  BAz >%Лi)!T 1M FAewĪťT~ !r!J@Ɉ%@T?Q9%fƝ4RT!Z,'!;y?Tǁ`)i xk4xW 1s*-arSM+RRp-Cͦo00M L"es+fʚOfd䅨TK oz^=U(T1OZwVx.- Ķ7 @9hT {CH+n|f +.x=' U\\y6 <" =ٓS4c#)*$r>CL@L6 Q>jy0zhnFJYIkh ӖNՎ*cJh2=EYqzǻ+9X,VO47َ3'mIGMIw;^~ex?`]6 ;J l4g̯xzT+eĔ7fN>%-MH g̤ڤTp,ƗIͣLS`~vDo ܛUZ"ތ۩a_] ^; kL+9 <{|GsfSvGg);3l?gQf]ކj7MFEMǪ(vj6欉$9QrN|cB8B lކ Qpf, 06iҤ>sjժ_}@9rh($LU 3x`Y':>C!7.k{39U*YV7@f1rZMomaGܛSe! I?n.W%" bCwf[-}6wOF6d蛩ʽfPpu@z5 A9XÚ50zdr//#T>GJdiHZy+vb (;in)\H)ECR !#C#UP^y |ʲPa:A67I9U !Wha3xYomaGMւ{{Nk'8$8$]so\#8Z%Vqd'#'c4&p>%"Mqfj azH yvWjjjfw1k(`WNnq)7.~pUϩޛ)tÆ ^Ng(} A1LLL5 X"{TpX Y1!yBxN%UsWT'2ܖ<{YznW 9bM׉?9Qd- w? })Eo\).$ȬlG1zcT3 ?~Xzӑ$@[VWޫ&Y",;_/#/u.>YWWpuI0Fܢia$/U^35%TIXYsj*w o"&LhH8@C|5a [ؿha̞/' z *ϩ½Y֢ӕXQf8MȽ8;R1!q(xWBoKߐr eRyNY ;Gt3Q C…j1'UUVUWkP?mye uUt{޽5}:SSz&ɆZL{Оp\56j}Ϛ*gFOHɸϤdY?ڟ89+[M CN7~pJւs(s*`0i hXbq=23AکEPЛeGed΀Æւtk eb xvp]}jIkh K17^fG9md2d-!eUZJvSVɌ8L03 5DЀ"p ?upj(j!q8&4*DfpއA5= 9(ܤҎjpNKnLs}LlcK%QPmB^zC35m4pMR+A95uC$sNbJ* ȏo 1P;(eqz!M,~E-G1Pf77C { !m&}CopkfiΖ C4WZQ4( zc5h :#ºa0^U* -W8G=Z F1yTӓmW]cDl`Aj1t8ڨyy;k Y6QZgͶ<2h6鷰O9ֺ*ܛuų(eUnoSSr#zcUܛ95,!DRt%])coh96U79V:Vf51ITR5e<K Y0 3=[ "{'|EWE_=:laX ~%ipz%G{35ܦzϩ񡘤){S qo)M̽T}8S%>>^OF(Q7="uZJRj|'1;⌨8o޿myO_=č/#Rƴ}`cʰj@C1RQ2Wv>* izo&3p3?#?Dq)WTC/8xN4 jpM68hټ1Ν鹲: +Ken $rʩjI_-"ڬ MxF6u/6@6ߛ~;r !qsZ[ǀQk  PQK[jz{|SUGg>?39Ǚ9%Όg/3hUT:*XFR@ BE-BXr@Hʥ@))HE_*mJk%++{$;){?BY{Wvyo~o&gXN^-sEޢ9w}K,Yz5-Ul y^<|mx^ŗa`YkZ0*8(p(rwPR<@;m2Hf'.^g܋3˚a[fwYSz|l9ˉ-2,k r8u\#s8ֹэ7ۖ`ȴf5Kx_G[r |0bjj~ '6 T3`Ĝ&pS7 Tڹs}:=z?+;~XAPހn0Z Q \{Cz? 2C%b`<6ovfX [Dp)<ִt"iyj"/B[isajۑLl[N&KjnTf1Y>q9 `y9VkZ*֒WB}xfX7![_* RMfe7ŜMuv%;)ͧZ&-ہڛr*c(28l-JrT>l}}z-<ٳ'RDp[*FƐ&Hr6vLL[2i&[n)c-W6|&K /O՜钻syJ[*K3۞iɈP@?RTy\z:amh)K5օqxlȔ7>*RS<8,l_[fs&@ ̦LЕd7. anL5'i*5lvДCPβX 1[&gsdaDh'7|դwʠ7xS5xZ>>Yػk:k;1闧⍑/] CޠsC޺jF'$ʣG((3׿ۿ4` -?HAoSsw1Dތlq˘1Dq,eEL[F^n5tX-JB% a8$Cl7՜%]Gɜͬ}lj!b٬thh`Q8yVK(*⸂| awExf[SUY>C. (ȳYV1Na+%zIP4SMEx [Fnfjz^A% LAolrJkXDaKh5N؂En[Lr蝲g2x7Kor'y4°p?ފCGo{g/.m4ukco3şIۊ܂iXN~ojcN j9[/ GO=ԯ=w}A`-o۰韠xZT&pbմX,q014ri- M<u j|hcžN%-xLio$ ( p##M_pWi?Lr꥗rHT.zdzΓi2eCÿ́ʃTeg4^5b'NdX+ˣ)gbLsP&譆:Lw)@ ecț-&):'s /I7;jB5=vc}:h4muS [g4sKm;4EwGqb\ gg6>ٌ [K{rx.pU\Z›=,1I%H&5kر34 #k׮7qD @Wkཉt!X_ B7r-\+ tpBgf@4 Ȧ|ehbN ^͖i3\49 M9 Mu\o|LV{#=V30X`)?aX$+=l+b@Kx4hw9t8'[ jocSNEx ^'7EĜ͜ڀY|4CEYme=4ms +u+SF>az1华-[lYAMwhh \jfj0&dnkb]+р_G`wfkĕJ6j-\.zS9)$.}Q4C*̝ ,@  6m 6mr 0zC<޸s-['78ŷ<=|o**5)Ʌ 2ѕK,C|F$xP 5jtN掟TELWd#V5蓡x zEo OSǴƇ{uѴ;'{jfgjn 2xѲrS8慂{h Q9넷UlǾ#gj{Ef ]3gvUTomul֍c`&-p^M^ڲtӓ'O~o:yN**))VZm6P 0*+E68qzCLn2S^ury)\97axeEjIcͼ\7Sl[{s 5>jGAz{:iZ{4Q-tvyk4kЁ,gml?ށ@C𥆋+TZBߎ #'k{%fi ͫ?GwK?v13#DuUKeÈ9M/Z^h pnC6Ĝ>9vi۝Tӛ(ųj8M2%Q˩Ÿ,LUƹ3pk9_nKy4m3g/X~fBLU-} ta^M;gIU/|\ܑ5b3 ^*[C\FB ւbrVTT؂@4mTn'???r-`YC[О7f+Xg/]:j LZ52q;rƾog@ui/qɕ: ޒ&TTR{ Vl~]]~O b#w8VM1X{aMIIӟ&MT;jpzv׮]yyyUu2AoIf9 ~~ᮧ;ht7=zgQ#DwK?NK?[Z PG{P #'n{yfॹPX/Nz᮷`:[-ӗ.$v3zC\F̩/u[Dw_(hӞ#XSltҲ2ho7nOOy)jSI |лwodK),흆;aZڥvy٫[]N?Z^L y4lhӴr칇R{>pG2@'6O{79j~gb ~4oq7:pݪg»RKF-VƐ<{{űg7T߸qLo § p\_zko7dCsu,^ڛs95c`40fJ̀78!';`[CP'61/[Xw7ӱ^lS9ZbYP}0@VZxl؇bBGNط3b/tf]Q5Wd{[>ҁ>ݾ~z&BX( MF7 x(x\$v*j-"<2`̦Rv%nF ĝK|eߘֿቿE1AнҰKhMW, cdCr-W4j-Mo*e-0r l9-ZN&[Ny~p0Iް`%-7ho({\$7XZn<[/]VoRa9Ӥ?3Ǝ Ao@*Bcc#A 6j4"7~D<~}mNl ܸqtCl#f/G7- ˩Ao~z13FDoO}bD^QΝbY7XHATE8Sހn8NoiiifEY{o1Cb\4^]C^:G-O?;|2]7٩f@KΩNSd Q{֫@7)議pŊpx࠺b 17nrn9Uho 647Mz3} z6EW$#\Cqĥ79[CBVEh/xZZZC>_<[z۞|߰&shQ S@㐏w֬Y(u 2nTp5&,R!uԨQZ \TfXNx\ݞkqz_f^wP>WtLdۄ.XY) G~Y!Ş$5 5I2PFiBo&F0 )m̙x@o8)7E)O&g  īRի!@oj˩9뛦ߛ1$ڕahoK{[UꏋMcB@n^N֨;@^v ~B$n MWL ^+))9rxza9q˩0[Κ/~eɝ"Hn5g%H)=gיeɰe9"ĸ1xhk:wB ud;ƥ7ւ~J"G2Ao7D:*,'O4t~C^E {)7NoSN-..55U)BMz #e 1`5{SZ0,T燁7Ӆ55=^i|R65cEf0qLlHeos;3,fd;\ Onf39Fo kjSϙ0)=A8sִT YJ$̰Zн-vxb2f^ ; }ߚQ SW^eWיj5!7H%!GŊݳ»b`peŎJrXl :sҬiitty 2X'& r0-FraX͑3M{C̩Z{Ӄ\}ڛ鍷8[rbJaʕk֬ԦMl\x}u~3f*<{M's-v6y䢢"Ka9.DƵ7p})-x6kfAn*ZD]ҘL2Ǚ奚%42z:2y+q$P B{k^Y5TSzjʧv%Ho4.Wٔ i0dˣs)MJifԸs,bGAo>/ t h/` ĬO)sxWjLىQibގ=޾{ {1<QlW7C{̘APO`#G"/b VD+4?AAo/ G`ؠ7[@oHIywwk9UM؅{/D+PڄvnJB vp{QkZPƎZ0~o~6Ͼn޵EWxf3K/ywQ%=/7=-;/Ӗf-ȄjD X βXܤȰ Qa̜`1ݍ-LRdJ_E.2.R,6jMwz8`^Xj`)bhfVK›sz$.GbMM,n6$ZkUtJnreCL1 SX5"G Uz!YB{w^No@D횄'zH{Eǜb!9j!|W}Kvve;}L7+elFƐׄ.kWOvrYm})>eu5ޘ_)C, 4&ɚc|Ki%XE>43ڧ/KƤ)<: RPA:Un-an]L{ 4y rr$۠eCkZggϲp -*K>ɰo1Do^L_;6Br 71|dkȱ,BoRC~o [ 4Zb@-nI'bxcS6I$={PrB{􆪘(k˩\% /YNcЛQTˍPTʊs*ƟP6eʔi҂7F~XK:,64fXN i^ 0`!^}=es☳~OneĶ!n>=-Hqf ßy+ /L]d7^6'Üp/ޢ,*s$e@ #3@@eO#;l /1k?2+g"GH{S@(H|EY PMΔWv0(5( zFnxWrGhoD*Xf!g+i'7ZPSɣ |'7{˰¹Ж[tx~`:A뜪-v̩!ޢPOhͳ_P{]9O8Me+<a 47Oո1`ꄴ4\"0]55״rz~o6G{+HS.G [{ WWg ~>/<[s=WW hWDK?7hS2Z&# XMshjaAH|cT -@׶@34[Qa ?~NF \o[Kc8Y5*z-s, [N7mZ~o >=gE~7*Y73u E9UęNo|M_N.CyFuNp*ꜪcN:r%K@$8{l #m#+9V }w/lAxj̀4J:%5NHoj7JYoNjhh755444ꚚjmBolji3^k؆@K]]cSS#-}&L6?uuP6إ5p{ 65m3X 5kfcl4Fs3:ik75=1FCS߂n8V8~*ۯ:JK=ii], y~oۗ}ΧӬT?ոM< [d.$Ưs*c0jn丆6V;-"Ǒ+~) rNho›{4뜢Ja95 a H㡀J?}WsFk6:/L'!*O+꿌#}^}OiA{=cGIÄ78F*T(S9 O2U ƙoǪ_~" JyJd +0A,xS}?~ʕ…:>5YlUЊ-=dn9Sڏ_$:, rfjΏLo J!Hq"0),O`hjK{!-߲;6:ϕPkIy5XvzԥԻڰ ,-3J2 rAt[IMJc9XR!NLP_%88BK#AXd{cqY6t"e Ŕ1~h=Ũg)vi~Q%,!-#3ނG=D(JƟ5k* uNE)4_}l9ͩ==۶\C < 8qo=V64Vv:tȷW}4m"֑s({c957C{3(A1;\)' ! }*7wMûBڛ'\z,rq[S}{3GFSBȚͱlR%֬@srJ)d)M7"?+d\cEDQPd(7KKZn)/Ihm7vdFJU(_n)f,#.+ , O$V@}#Jƺ!0OLA#Cs3bC l87[sCƄ.J#)n拵߆Mrʵ7uԂs:k9U0 !uE7;W{ \PsJ۾}$Vy_{Akwᆪgj/pMOmP̙ӟT7vԂ7( :Ylډ1,]㩚uNqs3葔Ϛ^ڏ8(_OvKWZΖe˪@ Y\R"9ݤjƺE@ȹM[m9 z#-zAz(ՊD]hTp4 "˜Jʬ b#DadYgɴȒֲlRڐ1X L*,!z &U{fﰂxǜb!l 2qz{ ܹa =bAQ;]:AѵkKytݻ>p3("ts)ZPd52e&noAB'< {vzҸ[܌!Yszk4bNkqDžqe2Em\$PBux R[{4taZmrE*nB;92io r,MaItFScU&w~Ћ%~Ӵ>qqxFoGz&oԐEpE )o\Mn{ǎ-[`,۲rOBIu3AoZ[r۵j3pzyʸjϟDh 4Ph6_Çzv9x02'fsn/ lnC;mێXvm޽?ӧkd…ls a9v%%4WrBJdU_/蕚"Mni3N< szPMwOɟwAѓmGBWWn[~oZZ boX1/>0% hRAgmS7U)8mM⯦")ó h`.F0xޤO=r*<D'7_+MCSjN{|oZ3$sz ̯旌^7.?]w|~3م6ݱӾ8C>0ڋ<{ dawk{P :n}C믿~=UUYu,M&,SΩ\+J+Ձ&9kr~KAk%wzhuf۲uXayVKK=yȃ{vg=]MoLhb6RtQOᖾu+V>*uB3*݆(gU"gRDRsOLdF:PqC]r=zĉ?v!  x+µ|}w=+_?^zW~)eSC;z,j69=>gn:05U5TT,EuWr@ey&S&zp aw L\hr\9+sV!U`fA5>v*n`#S3 i_`,5h/ ̓xY#ƓAn GaT5|L5G4<:/KcAoXUDAW8{,4 ڃZ qaN1}JY4:[ 7'[Hx@~)x) z{n9҈VAo?Kf=߈߹s7m]Uvw~ [l8DJm@SV vwwBxѦD zK~ݿ/xt-2}tm3|ݗo`%Q BdlVKz. M-LdM7c7у Ֆj6Y21)mb%LoL9I91zq˩ˑgXSSf&`TlG$*[8-HLGk>9bRWlBF[67[GN& ZffgZ;]|oqoqM>IE\_ڴ+zpz}=1}!7}}B]3gE) ;)˟g [Tw܅.4!L˅.4>}P|ߕWTns&AU7 zKǿﲦ|j'5cZVޮK[Fb3 VnR[7NAxKdd773FBaz(`¡rlddT#!Oɔ#xE>4H=TG,~:Ԫ48՜^ ϏOJ-Otx144n &`;m0]G9'YStez,|zIAWs1W/QNgM=v0PiN ?;Cp7-IN P=VsmZν耻 P 2W}pO>d4w7\;T VB]?@k8_[ǹ(Ռ0?޷w/G}M'\bT72ĸ $.NKB'+hcCv8[ ;oY Qdv~4bELտ&{85U1lX7O{<_zh .º||DB%fi=g@JfP9dEE>\J0G#ΐQ^M-6xwk[pIqB^GOZ'.򾰧} JfTA$=qO;SFDhߎ4.4_W41W*Koq^i{;)_xJAHE> Dl*D"`oRvX^!FC 0 +^@{QT-TCg6THB+u{t1,I/q_0hkUuCjC +d] +Y,9t*4/# lgI[25 ȉv|oVJ:}\0={Ϟ{T+ ƓߛfzSPUC[Tb*d ETMd%~V3ЊZRTb bmQ#UB+^4UQVjpS-dCeuC8:h^唕7E %BtLTM cŮTҌ㳲W1HWE ',*R,l(N,t%sK~o0"E ]1gssN)47'Bٺ3g.YÆ 'l]2~HfREtAI#ݮXa!5Rr;\kD hY3]H(+]=Nۏz~jݪe˗//..fժ˗,8q'k0}*,'O43@RTWRXP?SUo޻aC9\P!i&vΟo8tk׺w8y?}w aeϣSk-ˢ:]Qhawq@ULn8r<:/O~e,\3;26Mo"b[V˺TK~`#yyIT^,K` h׳sN1\lU5Sg]]hfמ*nͻ>v\='3gm0wyC7x8yS=Zv㉓z܆n|oz,HH{ ]Ц$Nv˖-:?$ ,o\O5~B1 .c˨8Քq#׭)vr2^6!hpCVWc׹bop ͮ]y# #CUML]Ǐ!<=x#?Q/#.˃]=(%sr"3R >.X'Z}վwI>KJa^`G=`31Ao.`#jAG[^˩6\HtKk՜8y3k/n'zZ z;yUXKו"c|ɡC x\ss3choiJ}+#!zu=B~8sagĩɅ]3C'. -[8==pS,PQ-b/!sD\tKחDEqߠn"*T?Q5}澈Wzt!'~Dt9߭.װ~ߐ5ؿ,3?{0D{oyϙ v Nob<;Kbh OI7>P69<0 \Z|v ћfSHFhH! )s2ȋ&ñsSg.m/_pDo ==,?׿uŪ;uYSno`8&\DS#cH;PBF;GQwˬ 퓗iyE3Aoo51q?A\-nƐDi3sK JI,YC#vM6Zzŗ1ƍF0Z[İUጠ- zO)לayn>EtjWz`NnQ.?9>?o58$DoFD>VQ)K-\kl np!9'Οh{"('zI@̛<4dn Lܸ7u)YЛnݐ3\-[S}=&?>- k #ny9'l-Q) ~oV<+_#c׿NT*)?{kzƒ!D}vzB? X))^-8 P@2&6cĜ5Q S-6mPz0q\;Ilw/Ե7o޼qs/T`qe#?}o/7᥊SM3"B0x#jA-*-d9eC}LN)C;= -C.*]կ7_"Yé>$ۼ!mFRlvGYǎ{!#-vgEdv…]n2oɤwKocNc  8!5ဴ,+^Vu7_i/ ơl&~o'&!WCPAo] SMz5;1 USHe]]wqGCm8$1F"j!8{6u;+eifzm\ . /}cߟ9vɓw-=rfk??3zРAÇ~ӟr#8\"<BޠldMxDSƇb<Уm)AoEd"2x_nmLR bkc乫H+-~KcYZ\~qz_yF7 $iGP{[ū֕*ʥgG\sE$j|#9uf0D6}GDc"~Jv=Qxx%b\KS[zs\{(df/:z減g!&-\L*ݍAJf-wrQK6ӣ؟d7vQ1fYB},.Wee '.-9 _Z IFdOS{YBa/rP umN68[W,\QwĐ{~Cfff)P ^ KXG7n9FCA~ͰtWn k9+̝s@6h cܣCf,?23 ZfޅDucOmV逇_4mySWn9]l7cȑr:u}?_kè~䷫s.j㬠7nЈ[uu 6JY"7ۼPk6t[:"߷|s^ڦ ecTm.t>e*"i+K:_1+*ƣ/s~olo{*MG62fzx1"?`jh*0ʊObRmmW!7Kly6SEE[jrbho1NcNhӛ"[ ׷=29{飋 B嶷^|w{#F}SܦYG-7`[4z_:Ǻ9.ʉV)gjY)'ߛz_4.<<>D~Hki"jsg5PyzJCF F[avd š7ivf`8V?9swK $82Jsl$MV cLigތS7&uބ`&|ݒ>v;*,"4FS-%M$Ə/q=t:ў~Aohv#MLfjIHW- oiEau# qVz]T1y,i.Tlc-lE|ngIw>0`ѓm5g9#[K!BSHM vn}5 -WLըi[Z*v2啈#ʣS3&ts榳H->nbTV;ћrNDl t^qY 0ӝxݫHD!&g%,cc3YrseЫ&Szj'(bKorS9s m7.ዎ/ [#~77GFfM=dהb –Ӱ8#f] rZ+)X'!!вS2?2"̭a)6Ecm7|'Z IA gUg}㟇ӧOhXE7n?լ-["Z I_^dGlqqNpq+7TBd ޠp.{4?Y*L3YsX\9]S"-*Miϴf0JFdm2ek\|N~ɖWz,e^@ 0NDo8-}c-_QEA9^)")4_:@FZ(1)!ir^Ĩp4T+.1Y\Y9uR-yQMZd%$wi*<7X/N{=(,'O4t7!4*,|oep `LoȐ!|˷7$՜Il6-=t3)QE,dunMn#{< 䱰͓m6e2Bb)wŜQj,MK-edZRI]a(@L!꨼^sDfZ2dXsrAJ'9 q$RPDz AL]pR,4k2JDa17i`4-xg o6\WЛfSFh"cf)!ed{)3h\4/WO܋Fvko R7E̩R DJYFƐ8[ܙb|6pMu |ۼys$*ܾ_ .|Wa>7ܮR Z+I' v2aO"G~]lQ6J'4g $ND/V1st~oJYqo9%$4sX!h 4 @[Э[eMmyN~l~o~oirYM]T9޸d >8le{Y|L]P"<! X 7h~s͠7BU̓ yB9a|֭[W> Ħ7( r^MMeW󺥊Iv{ jEHiC(.%iE{/#2l_'~Z Iu!!dOS{ 'S 7R)9XH铚/GPSEF̣dz3bN zK`9.hAC*8Ĝ>{ 3Io {ޠ鯵f`ZNմ*nmjf4E-֩wA (4.zES6O# 9>w_233Ծ}h8 zK`ಉZłQ,H.HXrЇqkoqoqM=iWA-P:bĜ&%\TVݰi9׿[Nq]B@RS"@U_}Uu S2~oj)Sޒ1c?3b/6}G&Nر 2I~, c~o z[4MFn(jzM.BӞP_~]{}CX_.F믿駟n~v?ӂ 8a@Z75Cb\ MҗW'1+ VIDATi\HQ 7㛧ʦzQOpyut(~5FSMEunll@.~H/5jkd;#t,*w\@w9m\MTOh$t{-n1o_w&J)*9]^W9JT>ۋ3'(Oeyw ZO;'o^?Zib|v&}~o00l`!{nTms+x2![֫1*e 7ͷ !I$;&z(aUp_qsWW9ؽ~FjXq?_dQq&ܲ Ut{2$Cڛۂ!ϠTtY8FsY/ߢ n&}9uTǑ괏;]qϸo}g`}Gg>~'hc/Z̲3&~'gTQ~rʭ,[mE&nQ۩O!s6΂8X{04>?&]Ui2=?soęi1Ê_h9!}-4Gx1yq fF=7V,u8q wh󷊧Mu-z {0|SH.Ḍ>|M#jHh:Ɏ8YDG x ʄsرG ^n.+qVxuvt5˜Re=suUko:3PƍcK3<:$ }ӰkJp8 >L4`{*Y'XPVM1bT0:~B1Y3 rPC;Q/M-J  -'74ŖV0Y[kSLZ[KS]]C%h;ZC4ZolMxN AVVM̈́bMlZp=Bhk7546Pxj_45ف'nߊYFhsR1D^Du^x>y𗸮_jjj/bD%Dfm1D8qMuS۸q{E@QIIf{ 'wuҔ=Ü^V78L&KZZӣ'̓mGb$&5--2{3HgZbm9Փ1$1(-Q Ȟcv/ YsL56  `Aj&Uj931&g=ƠX(l䘿jt\Ⱦ_^x꩓[mdR^YbNF̩vLŁl9IV6Zj`}Ռ*̀Ѝ4is@2hlhn51u? .hvul#pCHYf(o&R`ePm je/hkhoIOJY|o*Ămn=-~ǎRA1B~a;BPe[%p_Zv-"U8|pu̩͈ZqYTtR\x:?$NrJ!8K6y_ODG5îAo%sn:R v|W1O4yٶ,G=$<_Iɜ}`wЛϽαNc+:/[S}J"}Ĺ8qmWX5Yr,%%ou(\יj Shw[f^nǚY$4ɖ қ\\9ێ9)ILwzߛ871T#ƭ)j/]셙X 3?}VX{ސ"$2B32MtX>\;og Vog)hً:ό\T]\:]c`9`(&h`-\NJ151M\f .mF25нzg؂fHtYjF[q-"nzogu#.ۨRnlvEx^a9UǜƎZ%›at$.p:KI7C.Au˅ oHC(jVT,EGɔ^$^ud2gi(tdYv4+L3rKRV|LifEO՜ 4RuEʼk.2CWćRa楚,@\b2=ӒAva@H”[UrbJmSoDRhx>,`aPWʊŭ@Y-9Ϟwr9EdFsV#GJQ5>qq[$𲜜D^nNfR̩돐 wq70ǻ-Ó[zi99Q#Ϝ9ijz;r*r൷_]/S̏;U,@x $7)X_tκ >ӌxH;l~ov5ޢ fӠ唃0 -Zz WhF-Z 5g6S xœ ш('l^'8u>a3g1v bJ X*d̙6jIgXn`րv #-\W2:Vhx'ǣ> wjV"Nu(z֘DxhttYnH z@^ ߻>OfQ栅Tѕ&8\|6t_Z aI2ZƐȞM&6YKZ{=hMzS{TZz!o2E poNzYM0g/5q˪7 teT:fK3O C lyC 3 1XH{#z)H5*LEڛU8bd@'7CAbIyEڛ953d2RAz[tZ(%<&V>ZqUk9r(FQ5gBe(:AF%Ʀh \4J6 "I-֭^ חa9KusE[G,|p5v0[/xԂ~z>}Ɛ!#G3dǟ}yeĻbE Rs7%K= Pp@B4UDrpіdl -u[¢x_<ᘏ"', SȂV `pxvoW,Lx_~q=&u^7uz^5"lՄmp /z#j|^K^28zXP㉖N ΐ)hy3 ?4n:ƠB=i{D1|ƽT6JrZ :2o!E?g~]ሦb+mFy*ݿzjDqs|RJY:? Z{Shox'bhJ!J=\)FSpѲ"fƌ?iSٰa/]aǎo?qca V&yL/*kWz:o s'ԿFU>\jT z67 ~7l蠟%ql-.ga[φ ݎ;qkS /4b 2:Cc4IoM(tڛE3.)Gs…|#. m, f3j9*6Bs/bزghۘ/6JuP?%B;G=l\DӺjho:"]d) 5/ zӉDQ3=_hp?[\1.!7USHKuS=yPӸц*A ] W_q7yСB ƘNor zK'a79T2d'|#\(ĽPC~Uv\dݤI_WUBTTSS/&͜u\zʍ L>o7_ƖEU/O ]z37pS}wAjzhs̀/қZ tGQGZ{ؑM#բx$&r8&B 4 p-lT! 'y.ChHmfWp2~%EoϪ@4*eaFV5r_Uh/Fho{ٗ M71Zr.r yL {1`5{SZc9[}Wr [ǎc>ww*681χGQrZ]|`Dtrt3a[.!5Ǿ`L|N:f䯄/]IN1-zȸn4R3M' 6 $,؂PL,pʄ9:)))C3h1".$Rى[B[jY܁=xJ*h;q\̺{ DH3jDJG%&eeZx]@{ڊċuҷP UBoD&oe`m؎!FǾ۳Wxʏb_DlqH% }= NE1.B-*E`Hh/*+Re OH7k@.lCN*ꀵ0Q!)l r'CigSu|w 'h *cf8?ƹ-!˩~ 7` ;5n{Qq/9S;6pAz`;Noe6j*\jV8~UT΃U븽SIoLdU(HƯB"2cw\Gd 1Qe#T-t1+7 ]6sD%%~VQKWQv,cv%>{$JYxR$.E#&BoԏO\$@Չ!!47A qUL,6[nydrC)BYm )ϟB XOC"6..\f믑Cg 8 .i/ކћX@oUN`lcY ( ,huC <gLtmJJWfxuDѷPi8 Hʱ,jni/"E͘S:!4Ŷ NS~ F7&Wz8q* 1vx 6-z<"[ݍg|a0[o7 \h^/Rh$5weί=_/3̺s7c1Zݵo?8YGo$;HM_WJI5UQgejv^~PSRm*"NVHTvg7>宕QQŻ5wѬ$֔A;kG?u;stn(iZ]]6CJX#';#|lVw =5lZ{66wt ,{֮K^AMks㙝1'O3onIyoaR 65tQ ShCrWdFZ6؈~MZ8YpS*/C5jioXHKQ0օnE^vƴ~:y͗iˏ242vD%9@&91i_gir #SR/WVtAbWHw8qM#j[BS5,35=z9ݴl5'ͼU,u_ zz c}@T q-:cNu Z`-Vʭ?3֧sRiosScwٷFPq6f7EJK7{W}FOV_+:Us 'S|+N[7;U)̐9ef\n[{n  9Y5ym䠇 )\`Fy?{kE;-][i=Lj.1-0a G= (muD8ڷ`X~8!"Vka,|/!cXRR_xB^NdTx֔ώ^,j毊ӑWxD̩a9"ǯFXwy=o]l4c_lǂB{:w!:vloZ :0&a9Uꔿk55p>ߑ^u JD?$F{cߛՎ;KG5>,ٷqUsK'[S> yy&> $=q*:c\Ņ%Do49g{-5(VV6g{ 6;,LkN/JMk!Ǿ8G./^lz AtA# =bOZ8wz]#'4v-LM w?tR7-i>y=|:-L M}#%MwszSO1 x a#{6gq*\!@o C(7؀gMLũ8Qk^x V+NXWl͌ $)tfS་7ڏ,Ӯ]ɑ"7.0..4gHHzIszŰOa-G' zSQ1fX,U ˲eUg`y=/CmVtz ؒҁd6U'apīW !og|޵~PP!pzn}F_VB(YxCl^x/>a{]g_ƛtd57ʳKM{JЈFFtt~Dhj|odlO q p_7ј8q +F74vNo ,P *[g~cAM3Gً{>zlՍ[Q1țTYe7UjۥZ ?08^8ߊr,|32o<Ҫ~Wkۺ KAl B2[esPą3.C{~"PX ͗iA (2>)P+tJ=zoj.]:\2-x3gvRlGfho:J$:Bc i/ @&77lSqM7tP'7-B{SVxIEs.fC7}2-ω1ÌNzCÒ_ s} Os]hUizb N)Fv)J%Hloؠ;p0!^AFsUWIwj|L @L4s*_aKHetd3 .Xܣ.xK:CN{_ع&oHR)>d&Xk |\%q$Vk!ƹMm9U)7G{A{7nh&8Uq[ccZ4Űu}9ȏNd xXkXmʍt1W1rnkqi:m%躇PxaKk<޾OzFc__M7"c:P^+W؋  {̣Q ZrZO#£ 7 ˩֫nxO˨p݄8p1t遴¸]vnTrDVRTvY~= "8$ Lrϰ\}(1h#*v;?]{r4]p7?`Z Do2$ .u),gɰ~oB{s{UXNbY1*ebTOzn z&zoB̺B7Xpe% AoP y^et&/]{>U` c [u`qƯc&7xӄ. q~oB?^293$4{!a;HP9S^ D1z@(W$!,"o~o0{Kzn z&^M8 V^͵7ur{JB|=kaI^!D=iƜz\ g@<D_`ہ4CCcăA b "cxfH#76R4yLKS&,wXf⬱Ydzhig Lo9v'Ӎr$[axSak-{)R#ΨՎtuᩥ({̶Vq)[u&\Ͻ߼3moLqO71Ź${7+VL9 I\7.m Z[k >O ,r*]:r`:Z/я-_55r9R2FT李%+*enm2)k:ƩGx~o ˩{Q 1. cދ!e]0Ӊ9)~drdجT3ȹ-h9& *-\xe)5ȱ$ًzXn"Ֆ'`S%d΅V$zCYA3s*lm̔N+7O5Ծ鍏ѕ-[HRπFq8_ވACh ZSv cQ%=/?ÖWfIc\͒J^_:p3}n%{+z?M($_15)J7YI % $I,<-z 7_aвh Iz"K!C,Fڛh$ᕐ%u_͔ʔHC'SR_ 87_inp"Xr%M1 Fol9z"%&3C -YY%N8kUV Ggm3_1#Ѡ1^kh>wGAyرcXgXoݷ58o9TpفCG;CGi;|a9۷@MCO}wؾ/Tw=̞9Ws~߷r/{Gjf>@;_jٳF=w.~oB-JoЌrӛ pQ \{uN1FԂuzh4m4|x&OR Q3/mOVD5 yGv7̍㇆j ; J (ڈ;07p[#[Tn:u \.Lo"Rbf&ŁO`ߛ#l1-f,ǕAy1d鍜E˞CqV8[(7 ;" |llE}H{cbiHA*4 :|z[)_ZioAzc+>E^|p,X1e7ZNM5E6,(?F~oQLNs.YaKneG2))rKOϞ4;yb%x@ʀ6s= ['q烿jh"7Qq!Q=&KFE R[MrrRA/#\!kJd{oc-ڜƦ7Mhj7"\SM,ސ1 d6uz#[o l--P&L)0wJ6zNPh&S4uʐG{:HХJW͡gfN?}'yC;/ی62o޳6??#oZMLd4yۿ7 d%. z[t)vh3U7n0膪|x 20 F#ҭ6flv-SEZiol ZS B>X B3<%Tu+ 9犩l!JY+ʵzb^7'൝B7)rR2ߛ" g|փ)OQ?!/}//ыo"K:T M~oސ䡇3 VP|nnnvv6k.26-Hzqz^hFaOf~kfL3XKw.g~1;@9v{6hxǧ|oMm4u{{<=krdߝ*tI|0ĝzɒ%؝Kn Sn@ǏAEEt&ES9cIzւ,E9 g%'?!1泃כ~$S&d_1W"jǜbMm0Uv/?~6Haő9[zt. +}6?b?_g?O 3D{l,qwD5uAr}ݠqtCNp-yfnKho*F,HFfӤʄ'̦1ybŠݓVRLw}*ϔU|j>)\:"dA&j-PSC]h&T^kA)DErP^YɬdܞµE^Z4ci N3hB)Z>)fڑxIٖ=u冑#[! ~39_kXNK>ݞ˥Kzݨ>mqCRxrkq/r4 ̠&A3֫sJ:BK5dj//OF{tup~222FH{7I&M2 xfO>g^e֬Y .DɻDiWf8魵_ᄋ_Ik[^9qŮ ]㜻&5޹k ϸ; +w3zfc>tBG5Oh{ kW6|F+P;/Rl᱉ךf07@7˻"c:[7P^u,;|/pˈE fmhޖ*>O! d'=ҁ=b{hE;.p/hK7a?Ukolq&R*-^qwFQGS2amhRv]e=HMC z+**G1 zIqu%n498 'iP1o<q)x7rtC;nݺu=XhNJ @&1KZlmڕPU(*J3bL#K7p7UVV*0*797 Nrqo} Wy\ZKTq Wy1FG?^w10,X&Ը+ a7EE,\aWV-q׶,F+r-6WhlnU޸o}6웹~N[睺jFw JJ=95:No(9u%J\SrBDي (Ĝ>bǂT$@ j&V?dP Wj^ n _&8!EB47]7Q &N\^yݜ˧ok(o&͗⎇ oq51N+rۤMS "E(̭SL{/]b3 _|or3j!kLwSqS0UGc6b#wO{0j<`]XAOn9uho_,0eŎ۾_#iʫa*Sx ̞[:s>Jf .Z 6sӁ_Hrjho1>NETFf;gM,FzVC*8/]q}\~o\SM~ |fT%g]qL-z2t[4a9%魭9zuLjomarO@5L`koF~_5m r, #!,m8fC8<[Qz1on76^lhk_YO"%۩9v`&ک3x/Zם=.-zy{b2uU^ u#)goL^#/R~SCiW/Sl{p8pn)]m{ںaj9a;)={ ޭn³a_)gݱKu=չ|cԵwJlQ/9`l&tì{6l +1u 9 6NƵpk Q .nek<:su_7j:mLuz rPko)7|oFh_ ]Iz%U"vNNhf@sm۶ x,d QǜBf$%1WM8d'6|[>W,跭/ љ9L?;bIoR a2IcLD<&i~t!R6pNs]õfi`t)5nokl($g4r&IR WX+s譹1d3H2]qfMc aa[nN]~ =,i^hcb3OSC9 Iʗl){{ DShowI.V9{&ӈ断4|;sFZ66prm9)=ާqmmM1 >ί늽 ugn e/R&D 7s2ȋlBzopϯ80{޹nڇᛳq߂m 6t\m﬍ikdZiQهV nz))m_ɫwOZ fnvSE,QAoѦ_"3pYf@QTZ171rE-h[NmMyެfcɭW}2akm*-NuqkLo[|WPkpƝ!+[g/"ty.Qח+6ˣAb  L@3`Rdڛ97C0;l~oܤKe1Do{6ZIukd 5l9K􆗉jsѶ󗮟3oP.674 oV|OScOG+L7`l9}x Y +}qO={B#cb/-%bۥFA%&6[֠&VihL/ԭKJ]=wOop ڛ:[J(v}Cq7\3;{#jAim|7cnmX煅%` y~^uںදV`Ǘ<_Y~'S iޢMb];щ1*eijoD}p^g<6D-QB1㻯;+[5{cmO{^.؉6Ou>YӞ%$.ڇ6"[?TP>NAKXNYٰ0 R8A{rC{`@Df?2c&HhX4v)%  3UN(r dm@ 4KZ$$IMM-~?@CA~$7:mYLIMwOS>[? |,7SG^۟Pᰠlom#,wз=/\efno o:Ne X=62Do@o)GqOI4ſatgӞ=LzRfp׮٦gJM.pԂrO箛SqFOȃ q;|?+*+ܴYE'z5$(ݗ]yk5[{|ғre雾}uXuNaH5cb y3дjžtcs+hɽʚfu:8 Q [_MjzcJr׷ݷG%I{WAQ¦^01$m%|],F W8gGokf8G=g" WFxaXb<9Cy<աd$msR P rTfJh922ҀN u63gg8~oM]GŊ# :s/{ކOm7b#(LvVt#O*MUIcrC}8ZDSu̩MhhoɁ1g<(jpێw7.LIUZV[[+io=FfCl?4uUuzx, E֫p8@c 2#l=YK3[g,{,g;pv.ڬcKwh&={ z3-ϧ1䴷یh竖 +͖bV^ ŜR+om1&A5-MG62Pbtr(eTaq\' \BC&No] = /皎  , UMoED.tERLKW>JӷoŻ+bNO|}Tn.pzEm0XMfFl9)P )6 MXi ӝ?T]ޔRǓ'^ zluz>UqN1X#xۧߔCd'v~RL~hUZQ{3s~g@'e{>~Hgj#}g* BnLkn(>zt][ϕ\soQBI.God^i( L[aD^J E Hs?ŏ6#-B5h v)%(xiJr8E65467dqfĥĿ,uE6#C^̩'Pp<g]Ϋ4==lѣ~ﵷ_{ϼTjZ zx})6x~|^7!1)ICPTd׽{O G=szUz92l{pϞ{UGIy;-"h~o|FF-%2xo*{Kvf۷}Rm$j2hZF,raE |v-Ehz!1Arz|%Cg@'4[1l^IDn7K.Ή85Coy_^ySQJG~/{ Zfa7b̝DR脕geDHNI!SEZYWB9(SZe,:lT2-UkhlfánT!ȱe=ࠅnwŔYUUd25Ń2 XuTX 20]'dsaMAB7diˁ4͝Ȉ-H6%eG^vOu<1کP79<-̦pnll9-_4cm#>^u3ͧKYE[>^u­ b`;|֑}&paxh˰[heaEJyjMތlqzŝ"18;uԂN.&iF6LњY=~B<=Z-;U$N5/WʢԻdIyz#Z#zZX;c5YNQ]Ti9moEM ȡ>y W؊"dVې#ޘޯ܊! 5۳g]WLB{UJWK.k6[Dou7CAo#s2"<-Ŗ3ger lKfâE-cN$j{ߨOy?[>Ά/r XՏ7 zָ̫ 6 [!|-C֏mtO1*e 7oybܘרlg$ %&R" &?.uǸ8qg5q`eEdʦ0Y F0iXLo$6!jvEƒmn EVa۬͠nXNszo>{DZv:.Isp-@}fL= r1'M =g,0r@ldJu(:8%#i31۝{t{#G,3l܊*2l<!xZ%cڿ^2tfoD7M ,CW =mFW:,4̾OPz#cHϠhcu4O:X?#Koe:DUuϵ79=ݡ'NGFIxÝ nR%:'ERY^_x?,,l6pw׼Y`[ay <)6+3p@-^l_< YeCUZxQ7MXNaU95 znxAT$vKo"cHܨ֧kWe2`7[o=_NoPyɓ'X>{uzCUE7Eӿ:hQe2~1wh?yAaxWB^6|o[ڛZ&D+9kr~KAk%wљЅU%s3n3R3/Dluf۲KzHc5*bN/?-nt~dz%Fz^Mg x)'!Af{.| v/~7;EBz5޷XN9! z1X(c!?_WiVLȯV.?&ǿOSz!q/\rmt"k϶xs_{M sYL&j\\+e2g+yf>+-'eޒ\Ɍa,NۑnPxt9Z.Mlf\%||6l92hdJu5FCVJ˰[_K8Zj+cZ*h+홖|FL.qfX--ɵYIiV؀`<Ja.@wngAKuZut6ז\~:.W[܌!k$F?A{C~*[+$,Qt!`9u[A3Z#VI}); ix)8 ZH~xFV "E&`?K~J$""k׷jo{#={ۓf6Fۿ;^HK{,[Ao_hor,u7{n nSlW^aETlX𔯈[Bڛa9m[\z۰ s[L{U*m{뱕dr3Waɚ O5oiɜ宩Lz}&S<֌"5)SvUsdYLTCҚ4ȣ|YfSڱ,] /_F! LñLye>fM+`0&s&pɞa(0=8JzA)ǂa ? 8 $VJguZ}n|G&3Iq6 ǙmrSe5^11ho*޸g8رcx&pU\iդg}og743$ݓf;Q"Φ7IŎb&jAq7PQTTPQA FP˦y=ܪ*j^NuԩSUO=JQ^4y{SSSsrsg\Tso3.]>tE6Οh.;}.]8lEsͷլ OS9bs55Μq\UE>qUt>vԩ)! 77i2W4f-.467KSfk)L?3bGnlG,o߿Ol7>AC4ͩͫ7'˟ݪ׎ϑ wD:dW >K\UBd냕NU>0")ϩuԲAgOsVz9=`# ZX*m Z۸B,cCˢJښZ,YRZDF^eIxqw :;[6myʶ es\z]L{W[6uID*&7ܨFMs*kN>ZS 7+_q僋KrA1!_nzZ-Hu| 0q$(4jBj̈́ުVꆯb<0l>@y0tUyPHtFBo^if> +QVaANCV4WRmp VGAkQAmA#:@3oqotj|莰+vo`ԙsPm_{##:z2z#7JTq' 9]kcrf{"O`*H󣲬 mFL*Wd\,p/6Q@d=jFmPԾ~Ksv+9Tr᱿Yޜt,9Q|EUPwDQM R7D A㽁{XIqoN:3E9sf݁#_;3;?9βȀC^CF{xAsZά|aYU*fX6<TC\D.ō y0 10_PiUeh3rN&W1e |K)H {. \T{c Nq6fgnyIttY &T>9b P4ȴca7S˳v e%USso YpFy]SGw|Ac9r7Y[mbGm$X7i̵0@f7#Ъu#5MX xo窫/QĐچ[^{=E yN&)ܜ0N{#&Tn!E+7iS{GF)))- Y5 ݍӟNz1)uuʈ#oqe6 EYE*CzvXf|g(-Va***,++hEjvWLG+-US2. Eyi}u tl7icMQФ@۹ʱ~ ;CGvo׮]H{zCqvBoA X"MM=A,<(6䴺+Ͻ;wtƂY'gy]s/Б#9gr _(*|u円S7Nie2s*{-)mVzڳS;$iKeZǽٌKyNy-hMCo4wپ}… V×M-m"r5N6W["9QzyJcӜ>ms͝bq*CyE |ןݔtQsXN3eu'1?WB@Y'?ubsDl,S*됬ýkfSaN`:SBTm&|NM`&?ivo`\eqY_ Ôݗ&z(']WAȸ^ 6Kk"P(7Cyfo4rJ{Htn[ momGyF=n-0Fj5#=W 58YZFiwoo[АkMݽC槷7t{ Ȩ둻y# hj X75Sg>Eo_)_}uI_աLIMT',>D)(T/qo6ћ|lݠ0uʺѠ)@UŽeow7r xBAӻ7z%r4yN1Lblb2ތ&Y6UlaP* 5C{~Y61y6EyXR`>*eV%VRXh?i#.\ `FB者A;0L,>2`3̍?ͮ^S].º w117홟g<| 俟)ޛnLY81,|rrr\0G=),_cy<#L辘Mv {vo=ooD7nX^kXzoXY@n_ސ 86ma 4<.{^ - !si5؅zU3Fb{>ߑK`<ӽ}t ”lv |zc!m1LlmC')CKP"gx83 y)` E-$(/hI N\04p*?{hjsi-fSWrھ8+AնP!/7b#`v1 Pn 4 0}*#щojㄜ4R| fq.G"Go8Y7fs&ƲS r+ۓq<r!ro04xLj#-]fljnc6o;  ޻M$P\ߎ?qSƱCs^~3vq緜cmwT/*Kjhg[t:נ9eHރDpUvћMs6~öؒ7mSnHR 9A'odr7~|)e)e3Rf(tA[J&m)hBm.dڎ0}#S`5zչ4͉ͩDqon,L4x}K;Fo"|Nܛt[E@ceT8gPU'7ކR&\h-_--ƫQq@^sEX3XvaPtLj-ʭłrP\Rdd9lGgAQ95NIn$pbt>,4lGSݻocl "oNOH0㏇#r< p0k"r߹_W?gvxoJZ%ܗ{x9u?#Wp^zĽc03bzo7ZVk= S ^oQx~[gS)gyUv[^ܪG*7dU?\x'52WgXYb}ecJW>uniN5 v ;4޺u.O56>8%_roNocR7%ңqmj_I%7LNٔ켑=Væ~ce5'5Gqn{a[N]K]W{Tq#XQnN/Y |NV Ek^F/ M ȅLm*ܢ .\P޼~(`'!Uu YQ7{pMM* F'ܛM{ʯn>v<vqV); ߖnkM٧eW&fZ{r5*\}d|FE܁+2W?P{-hz\MOۢ'q h aj!ׂh]d 6]u pW3guG2@g >2z#Ӻc쏰S~weoXZ^P:{)"2Ydgnh+wxhŁN_@8P ꁊeeNYiNL' и{`h4F=Hg]Go}N!Hs7"y\Qgkcq.2YPV>O88FݛɸH΋a|CSy{Iʏ}(/ЛDz 6@ʐWePn7?N:eGse4lro/)K4 ?z@-)m>9nf [3wʝs7[o瓎ޖ~鮂3CZwYlG[~r#gfzSh)b` hz}Pqd5iom?XsƅVs+I72Gm驼ںfeKD]Kc9Co_o< z+1vtvY!I8qo ##;"}N_fg#̮/ U;*_HݽǕ;Hͽ! 5 Xfh9zsb&,'gUn8 wzSp6Vc K OʘivAO 6F$ ^>3P؆C{Vdn1NŤ9zvj|kDSϩTCon^]u}{d\{ΔShʉgo@s uږ1iNK^Uqo}8ZGŏղb[w{i,DC, PtӗR/m-sZE]ا HPFo3gμp:4q\ 64`B)+,dmŗbvm>GN?R_GN_sΆKvd/ڞ %)867ۚ=ku ZN,O/z[l޲{'9DJ!(YQ7 u ͟?radqX`ADDDUi^QEvo՟ҢR}늝˱Cf?HT;~s坨%ӣ{{-O?f9[3!.YFo]-sZ7z`K]Zw\&{?5|h+S~F=`{ZԾ os1u>Tdၬ ZB7鈏%3\!=Gڸ6A:Pmn !L̔'XڸX {k#ۡ)BoYa{Q%Л9u)C^,TΎeCȭGJȬX{, GO;0uY2;w/!9 W1eaIY+MΎ`nmߒKv'=TᵀxooA‚OӜ: nwgUowdlHOOE/RMpv<K-ڭZ!A{q=ׂ=0Afj WaB2uDcQvmt׮ T>O(e-.%WS7NZhI^9WJWtCxk%XkfxtWorlԜ:gNyxrS85xV/ x=WRD7a樒gze- Q8zfwXn G\Ƞ.\3vo&iNk7sDo DY>(~WiIi3O[mq޷P%d@wLN?cMY{08!-z/JdOiV9ɤ۽\ ݛIq:z['+`Þ=n]@48C8f (n4AOY"mE?|#D]Epc8Fgn6=:վ)~jGV6 g LF",Ò٪\{(;9EJ>ACXbso5s(И{4n]Ӝxٽ=3%nʬ3KR-f_eKTrt]SJW)BlUJ,K-h́5d=8&n_ ;"SK"Sm/9tPٽQS5z"8 nwgUowdV e3先7nnP~VWW7xC G뼒JJSBtљ dtiВNd5INM@rUH'8\̬@SumEL:edĎ5<M1BtTdFz%z$Y$@@R/_ZUױ ID*ҎNVEgUH6%xǸ"3+ ٱ8[I5ZmSq\Doj9:;MJi$oUZ6=wﶷA%΢yܦ]{+ڟYvhќ39jJ//ZZQWn^nh8yf[*coz[^X^T&/wbSugL:ҔI+ӡe+3iw,=>.}'Θ ~o)Ev5bquyv˝Kzf\{ uuâ9V5=މf^ 9I # u{ovw[g_G~4hK]g]_-EԮWnQK|mKv͚0tK~hrތ-yaӷ؜37b#eF߄TF8T{s0e4=;|k^t)%%eѢEYsnX7n?ZرcvoUR4Tp 8RQJ4 d;H͔Zb8,c(fJa&q3P6c(ptu|G6my;Gt\ϵ P 荱TbNjԤ|@.~MH_s인"cc}dw{\-Lݔ;}sԤr¦\jPZ46_?O zs(贅 *p"99&dF1MAnn#G(Л3l+W,o*g1zEщ`c&Nͽ3k1YYB~ &"xվ+#WjNM> cgmH*r 0Ό@HZtR тMXERZfk~nΠGmY @C[xVs)j_(2"yd`B,,H3‚qO;j!vhǙ!b|4%;,4XbT#`ucC@+(&\HvN<%,%Zp귛X$̔aG7g=[b:z:SVc`~zO.Ż?ɂ]ZMžB##SajͱׂރICo` i')) @n  ۅ 9t萆 htzzJAPPb sW.;Ȉ`y ! '#&eWڳW:ݠ% cߣcm:k Wk7S~+M*Ti3PKUobXS}ʨ,F bղ̠nuNSƎĖ;[ VXS^@H8YZQKx nS~fzfODp欦blnC# :@?7Ȋyd' K _ZYmY;1pg9NI#iC}<h]czEМv3=,6t'TfSx!2{˩!'xGO<3i v~ovG?Ͳ)&2y _8?,-^9Udz!N@o=d0uʺљa|K@ Sn`?'(l{s1ĝ%hj_m+daLxFIVt+MY:b'X?dsqY5&1 )S5,)YLSҴ߆:ADМD0L`SByDlvƼ(YjQq,ɬ!5;隐ZY Hm!(V .J #ܓ  C7 GoOx HWk;0V俇ƭ9RmC_ ܗwgL\5amָ pxlv<4naИ ~4ͩ (%ןnڒ{K;y-qv@o%ʚSBBl DҽkO8z0E%b7>|hl[w^L<'y4-5*׏#c-k |zkmnߊ56 K ^~ôհ{tP}<#yuOHr\X0oo mN_4N--6v9rY}b'3v3[x{zS Z[-{ίr׼u}Yst g\$oy{SݬeIhzp xg]&yI٩*֜r]B9?0E7OE'%g'N8y>̹7C iSh-k-~56Sܛ%ϩ{S<%u96OdXL*J*/7N@MV2};J7ekIIIvG o-! ykﺞ`-KwԈRKHmNq U :u*& < P*y& % \ll,^ =j;z|9o-95Un $HH/gɍy?3nPOA%26i#9{z呱naB~u^h32ͩ{k9"xoփsبhPkQGo-gz$J*f/SEs*g"Ħb3 |e , \~b.PF 2Sx- o[o2zٛG9%f~ROINEޏ^, `bn7EoIAoTFĕk t@!3}\ A.(|NUtĽ0]>x-yq2+FEzqWz*JzzCu&WZ7FAˉ{CݖydSZ7Br?ʓ=L2 `t/?wp״:Ao ==jЈZ`5ִ|z:&Hq ;9 ՚ӽ{|;mV:^TTq0LS̚Ӗ&3kNWL΢,zkYSƢp}8cwUcY&{-BJpCUgp*ӟ2?Z? PܿWu7kACop`ࢢg7U 2b%&/7߾I g}g);dOw${%Ƅ5_pr2ǎ CL(Axy~Y5,+ #9`&?ӽ؁wq{{`Qj篾^&5'~[v.8PEXz+\>vtd:Hxc!,+em2(9.r+wt Ľ EaG,{kE6|xI&n.fW-"PAWieѾ 6Cjv`.sSȀdm57u³,C5+szC9[<bb[;Ģ K ̜C/-E  UkBRWΗANq"ZQi q?T +uχ7lwofFn?t  C30 V>䬧j|7TgDy+U̸ =֗]ΊWv+S -Ƣpc5Vng陋0ySESpjTcL3Afebo& kzR1Dpo 57|1222,,^(6%XGS}ϰ(y?t̚=A+s;Jw$[:d3y[>w8T>;;j^ohW%JrtRֳAkpޜFT+ AyNa[kolDC#XaMHU򭨭w69퓺,\+Hَc4^N#_)eSj ި fdz~,+;.Eqvq7XxK!(),09yMyʏF")[ m9!@YeeY&Jjvv'C>9~zk2ߠ :O7Tn"Ho΢[Y?ΠʔPoyG-/ðAо5Vh ZiP7o&4cf a,>2f)218Wx*5Bgp\q,fƳ8bE(+כjXRNcog bl/t.:,lq5x?Mr1k"6v<;@CW?VGEnG'Ι>7".blGE%foW75j9صkGaq'O1Ee1x =Ўj״;,f  j[qo1Ĺݛ[2k4 cCBNk k"x{7hN-ޮ9zۙϔ5NKA~Uܛ>|"/__qp2w_uɳgݼiD1Bׯ]3Pi8]Rrŋ kp^SS3ln2d[S{stUHʗP$94^G\NɊғ"#cӋ ZizZ&$*JYx h-(s4r~0`Sl7U>("cbXa׏4zn[KsBݟ{wXx)yn'@.P̊,@ɝw- z;9]2M֕< 3tǀ#Vn 1~~zxۙS;{zޜpo2z}/Zx⵬#9'O\իW)V2t׮՟(;q|54?pm̩See'*+ .^.MMt(,b«T臋UYA.]x%575o9}4lpp@}܌=J/Co7oT܅ 5n]&"7{SD"x5_b5zK )UnS˭Z&0 cCQK%`H  A:$W#ؼsCq:e(lOq`zz3e9UQ-"-hudEYU9`g0@.\?ԝ8yo}$+ 6A1 e2"|@*3]R{ 0ܛ9GM =WMg/EFc3Ojk8];ਜ਼˦KkX-@X {CuU{½=Y6z+ B+_{J Bph`Yuޛf9օ{z1C7Flro<6s$ʨŋj=Sul0_sùUgN>u+@ٜ@@oցK\WĊ{ͦ)(D`PMsS1#}a@ؾ}… X" ) `ȖOǦҔ@ntbRe {3$x0"=\?hNћ>U.,i~auظLsR8zC%#M&^pͨIUJ7:Sc4@7 _PZ+P]t졠QAWd8X+ҔRեיT ?͛ˢ95C+3)hK3KOn7lxx^<5g ^IgB%lzczGt8E`_ސj,^^ɂ}Zu/Ͻl4^5AϹC@GoHpY^cnP8K,I &Jq}ː4*yvUyZHPOګSۓۺV_{s1D|Jp&Nؓs={vſݻp4 776\g,! n1~RSYs9:{>5^c@+VرÒ_X7D=R}*c+, EC^1]GoyɱYkAmv)u.]/U.N(SxޜՔrZ R̻![)BeڕFR 7\x2ж[<Ȭ Vq&7 KB=* el *]$nKF*omEQvkޭT,ׂl ;dI %5p0Ǡ̩Q<c/z2ma" v'ݫ)鈑xCsjϽg7{i]Q/wG9{XؠY{?ڽ~,wxLY2zCS]h_Nc@oƲ ooPhQstHXkH4znQ,wԦAe@o\ R&ag2=#ޖdzN>hؙLEz%H͈tez_+zcaxV؝&W?B;}fy ޛRgb_|(28ȩ.#A*gl^eʲhNeS{TTvKBp /<NbY7ϩsJ^ 4ͩgTz٠8HuÛ)FSٕ, 8) t FmYȕ+"7s-s[E@I{<=j;T^+k#B2/B̴^ wDVЇť_u4hγ;(u)UL=zp/EDyx~:S~ܾc,>ngf nzhMfcym:zy 1`':0`euH;WK0_ccF0*%ih>>?Go},ŚjΏ>/93%U,T/G AY"+RMt?2z2T6_\ {̹ BbZ|ܔkԍoZBGFfI{Л@SwBJp*"""ꯗUNo;w)\ "ϩ=7kACo =-޺6]zvD\)8z=,kܛs9m&!q\1Ȁk1rNքBM?A6ek-P}Q3֌*7z Q VН+|?Bom5-]>mr3^B2ŐScuYz9}y28vՋ~]bNVa tGEܛ@AojSMpîyp|\8z#vok'ȩ@*j3{QޜD>>|bAfSEynq+`jnt9@Â#)cB]Do5wiGzkK R}K9Ԕk;;7~m8y1h!2%}{"&64U4ba!Y8BȌf8KL-2ϬْZ\ Gǹ1w,X |#J[ sn}HKlݽ~] L#?7eǙʊ35 Uʜ .|BoZǽ=⢢"^ rS5z"<sAh\gz%C0xh~mwJrkGRhu 'M` WYv33w;52¦3[Gڼ Q_*7O,ae^nҜQcv[XClArb*۬SQS*C#JLRz;J4r60xUz.Xj̲f۫ <1{-(&NGS.}=1츹dOlQ&VPRV`|MC395F+칹?3f*뫜಼ٛ?5ƺ4XwM䑠֜ݻ2®.HW`K{3"baYm, IzzC¢*L-X0V~^Q~~Rý ͩ޴hN%\WseϞ=n!&8 nr+L[ou*)JGweYt _)JZoHtW0.0}H ۑGu\|os79sђ.KBf4䀆kAA&#G߿N0#gVYF%[_7&L< ?kݢc \WZSu¡o_i(UzK7Fy/]FRt'#AɄ꓌PJzdGOe.NnY:FGpD4?pA@"ꚯ=BobH;q60K< ӱBo3gBh0{Aqo!`-Yꍹz6c[G IUGTpDnV"Nн0ׂ̞T@7-FoYyPp~AA^W}H )4l,>頃s݂К)5z+@o U NTQU c.=?$?GCJ$3ΡW^hD.2%EiѦS=x˩-lfX;l-shY; fPBF֖ vl?-?1CMkXX+Gc+ށZYVSs6/=iM& @oуcڹ\27.:[7zT!v9Kq|=OƦ¸ޝSvbU@Hnl3qo/H.8}B;)Sb;3Gc4n#nuyɵ!|Iw" c,%37&Oyx! ‚C$64a麀P!Lp%^)&RkHtE`HĽ}ňG$y8;c-s,@S̄UcG3 Vz#yV}3yzoPZDoE,S @҅աVUT{DE1,7WiV@HP=PfU7d&9S+**ij̙ATtTq}NݛMM!6lݺE"2&7@|%7;np$ 7ްYMUV TZ d؄B|d,o`O:t_"%B!+^dؒEEze$$1h2=.G뼒Jѻ*$iO An&!jkr$ofGfVU!.N(9mqG1d㼘—}xHnqo= z5Bo GܛMvl ZUYPt`5:gv3gaf[4pjbݻR`2I zd@gS762%,k7ϾF S$2RnRs15y#8z$4~O;='+DS~+ A擕7A N\v#?~˺rxe&JtLC-huV4 sTCFd^RC^JGǤ"1u,-h*,-Dž6v2񣗆7}|F# Hfb4*0,EH' ځ GGc x/lZ2z3ee>|S@7Ҝ¤ͩq";9 *{pUL>NK`IA{efFuB(B {P>pc@ob` a{>ypeP̽ '7{˲ZS7@49d4$mQ> {ٟIs E'1USm6乷 5WBLYMx (B=,yE,Xq9@rAiL9Dq"@S +J &`@z1ydl9@K$K1SxBm*56 XRd {\H}*<eLIsX~ݽ榖;L),*24pͩlH'8C3N)8elkLsʔOZV^3. l1)>o>?a6~~LFkNYO\[MYj:Pe~{+ZM]㇬ʠJ4@-?$RiHE<, bkAoМsjwa޼yLܐÔҘv1LnBor7W4 p¶m"##';G Qn S|`lo*4bV`*3ٴ138f$j43YNvoج{0 0=,.@ @A-JyXޘ*!;|8{RU6y8ot>O#yy^4d>yJWytyP:_ UC*vãK9fBo|!/9 O f Cϲ I-Ȼ {!ގ>Ƚ^(xZ$5`Rev֕<` ^gLM@nT ܊zjQyt([](8[8S%2ˡ׺;YL-Z 6=eŞ}0LYr60`{8=ܝ®BC&͹ha:)?#Y~Kg,;Mva8{)UM{s½MvAt譹8ŋل!{s쵠e逸ÓӎxMug A7Lun'p رce4X3_!or(MG{_آQs@  _moNhn;D,a |膢"C!=ZOw6:,wBysЊ=xvP)„PmxZN/)aB,_ptGJ#<U$28=:kXyy'\Ol^z~IRIw-N6mkXͶ 6i*3,S!ZJbXUTBmk?s8b9mx)oٲ/-f*y Э^.ZK=gp ́BH<9zƅ޼²Z$EYR$XN-skUf6T0mX|N!+[%>xpgtf"[F @ofS0_K"Lx=7?'0@0ؘ$I5dC]RShkgQFfY!@!/b<0h]Ԝ8axtؾ@o.ʋ~y iU> ZTĿ=|x KERQ[@Ziv}xx^o>5?ų/yo)Y/}I9eޘK*57Nc^&sA `@SP135(|@7*AIQCT=aG Cle͵L-z8ytpo=ҾsޘUXWDz`Q0ow̓ 6]5 fV,_U$SGnDZ۰h,Szۃ9u1DmJpݱ{#׍{ν}16xF|욬CN y1D{\ kvoqk{6AP5@ p}@yګ.) dl")̹Ld`OKuGm)ʌEn]K?*у=P~O#R;9NjO98w&0VSʙ x%!]MQ: 7GWsSG߹s;l cZT]}k4 h3+<(]VMfw n0:U^ ZǽADO+`/aʲҒEEx~!gQvv ;_|ϨuP? ZtΌKk,OߓQ27T{_aB!?r7hjܬmЫЛ{#܋/[o!hHTn/jP%?;_"Vz߽6=Z1Fvo_X\oY=c$jk>PA C~e C7!mbhO SQ . Oʃr6֫ӴE,ǰp]~f&@ot>p^Bzza=ϩWsDn;?:Y,4??b(r-8 ޺7Qx#x<)屃b22Q^Qfz^̴4dSt6z#ЮBsJYMH ͱ(HJ$EcJBc/s [lSy pNΪD1Wwt)վ76"󀱑St\76oCqxۦTg"R^lX>*vW9Pޜ}=荢R7vo ~?3k(dI, ? [/OeJz{3Xk\3p`ࢢ`hY{4{ t֍?Ի$ ORl/ yط"KMJV>Ľ;͒9b yzgܢ8Wt3}#-~p 6 + 5`̄yk84mCo m[zz*/*7 vHN/><: Tb o| 5zt޽/Nz(&9k3ꛃN}o2&۽Q7>C5:i#p?G۶m s.C6E*J +V bВCW JզX+ +}BDnZhHEɓ'UApo:!ȑC䵰c֥qJfj*XJG4d&OތŌ0cٜ,՟~EG^rwͩN7Z'ٱe .qy&ƊqJܸGpR7~Yd^MCp^S^x޾M^&19.DEVcmjϸ7x*I=j{#ӹ c7]s5'F{)Jt. r{-O?f9[3! ӓ6n2z둳55G6@`Bj5zk,Ʋbnε}]dwnM?%&޺)ecĽ!R.w(L/`L kۆH6^ Brpо9De zň7h3gMs!a7hN]9绠H{k/G=7nr:Sϩ֫vkh#8*ґtx7wCS$!_B H˶&xHzp =:FAg\Z (U^@>C 73t^}id&ӼS0m^ \wHƷ?{!r۾Q[S"̫!)f eF򙶳xwy6;Is7y-PBoMk)gF^9_qRslƨO` 0".k 5.Bo f?T\˞p p@l6D{C,]ڎ}|әzm#GoNUWM- a^Gi\ T ͩO2muBZpsVGo^rc'?o_fE#߾Ȕ} p8*{vL^ w[ʌlۙvU&/SSJ7n4ԛ0+TDc?"'Ki$KgH*o4L{+yNd\\" +"O)"i;CnLD )Z PJ A56Q>ey Or Dz9#C3j"ޛ+v_e0{[f}=+ RO>(,bc67TmFΞϩ"SDP,-b+^FqG2VdҜ:²4sG'f[R>gt,^zlV^!IhN}t^l#ԪP ЛWCmyɾ^^Gg4؆ _B:ݳb}7+@m(?7@3fOrG]FiadRwA0m)LV6eZOl8r? vHvw1iF)5+rZi Z[Y&|;0t+T&h1qon7oWd7G>]C*NԱB{o6\Zއ>h#G@efJ ߙ)7 ;6!Va_P9eC>(??_ε@i4`22a\$l%Sק,իWkzso@o3gΜ;w@oK,BKoBoB7Ruz!7K l6342?;X!"xT!! 嬬ܼcDzQu8>alW)%TJBkoBgJpiܛ)Fྎ,zñY`DyD l =bpq{Cs!zEsp2Չϓ/\tx0~/_zZA=UxNYiYP|7d9YA7KzL^ М {s}6hh#Ѓ# ޮ]#{qUhnaWAϞOvop^\]{ϩ^ 16z譡2<ɀް@& 6 3,֖,P0$Bo8m2U\?^BokACol2iF@yƽic3i4 ͩ<2&\Mp٥K JO# >7]*jr&^d&G,e &{-0Ě[mZOxFEG95ߤ}?D)2?vj\ k~:Yu@/0p)*P bdOc+loPzSx-ܛiyN{$Ӻ9.7ʦz΁ΎojDv,% qc}\a ]j_y ͍4S~+)mڷ7kxۼ\/{NWӜ:xr*"h &pМ򔔔#(U3Tϟg @jHaJ2$TgoF:Dziz̗vp`gZO++LͣF|Rwڷ ־ ،)鈑x54iYdsJvoS9HƁ{oS`gk@f@cx0+!EPB bK8v\GoP) 'ٽuyrh;j#@wF&ziL uG:匏(@U{f5Q7YVnSYK'X2˨æDO;Fz|(_%}GNUoI%gK(}ϑcX.w\X0o)n o5 … se<5SҎvoX`F @fQ@! `6Q 0 ㈄ )zs+K=E޺<9F;#:ϩz[X^ )[QiT 3Eɬ2V`ԣGF#NvVN91?SY?dzOefLXZE@o7CX+X},^<?(сO#LнOӝ}# 7,Cꘄ{r-ac6#Z&xQ7Z%^(4e3 3*-b1\E A4'CkA÷.@AU!¹p bU{-)E)t\ $F~c&VͽuaPiΩ+27"F ]a4nL[yN vc!X4Vצ$$}zհmVë])q{Mily-ZHdSp\'jp|g?W^O}+b<7{26e9}@aT=(@6Āxo"bM9ޛ9vF@@ro.t EsJ{D84pF"FgCZظVGcYW`WqjP>h @o-9z[Zlb^{s|5^4j޽{](9@o@A{ћ@ @A |YAJ\(6_bG y'\ M~M噡65zI2eu"}N;[a@Whsov_11UC B+_fVkzz2+Z`N‘Oz3qHR +UhN| j:YZ 鄭71>.]L}do|d6s,bwz G1X7ߥK)"(!r]PQxbgk`p lV X#Wx&Pq XF  ߀c"P+LY"Z1䉙ډj#{G)4b;Ơ|&xDx[8kɡʷCS+O,wm?-Ufi)K&S}ձC4 ˌĚn{{& ۾|' S >9Z6>64O9PGpp̽Y+7*0K̀ $G1xyzFT#`278/b9:HTnnhen4zIqK +u MHsF +'q{ϹrY!ހ4SWF! oS?>5 6<@a4 B(0PzycQ{>~xod&}N5W&6=>ؽṧΔ= ~~L\†87^"[fD^voZ,W1zZ MGFF]QJUdh//__._|Ȑ! )$e{C:o2z#1Kv޽gϞ}R9p~Uv7e˖M6%%%aA+h##@ ׯ[nڵ /&&_^IENZXJY@W6> M{w Gp~]{7͛]n݊7>x#pIo+iii~.[ȡF#EDI(I^[~~>Te҇a) @JR⚽ǎ/5IDATr?gvo"7\{@K˰ϣ$(E!j =gb<~N[u~S??mFyvl&Yfozݯ^\{!^B:cSYSʛhYWS1\c`_1^jv_zNm:}d|cuw_j +5a OX=amĄVUƟ-=x.pMOL}ɞ2eĄ~, _J[TqA9㰰{9M?aG{ZŘ C_z5"Э.|]WNV;yi)Xe3S]7oo݂}E ǟ&6eڎ˓/mԭCMrv37UNT1uI V䌍l7RXpkY`b<Z}]7yjҟbX,y.zQ[puظfS}+x7ĉ䯕to߀j،\];_Mބ/-|m];g>+ Vp"pa3_o0{W- {ⵋw1^6_:xֽؑ!O ̷Vg! `@i@g -0Y05HXp-1h`&͵\2fϢeH̝;Ѝ??#{6m8@u7S9b ĥ#;&/Vh9:?(B n;?#F.]{fMW׾רU<&gOHKS?_^_s!.}Wk}KΫ̪_N9OdLK^##V+q 9tSB~= !dB0h4nGA T p? AAa 7<&N&PFo")qor7Ҝʙ`! `1v p1l9jԨ/fW/Eվwouß6tӵ >?a6l _7F%fK㗛Gl:Vk͇'~{?WY~˾+j?0,zXL'1>}9^ymu_{y#T2/-iaFcKK,W7``4[ڰP|si7#.2VOɋo4"ivn[$wo{y-˰6|mo6=?^YT_T5>]6;psE7Bo raífvbk4'q_ٚo~:9`Gmܛt ?\|;ogT2y ov"VgFp`AA  n@@p[);` rԞB*sS.m#ToKl!ѯ4niNe „ۈ>{,`SdM.P]]{];ys^~ΐC;?9v3:Cw893snn缢 ;M^m>Qpo~A,.s/:0L?Ģ_8x_a@ Wqtثt ~Cؽ&!(%'(\A a8@W52[Κe3B FTnxo"K=I6 PbPHU~0!{|(/"(-4s%';cNuFwsN!V93sQygTySL&tglM^m>ؕfg I3<Œm@ZT:˒/*iD=2 Q*ov1Ci KlsN5}Wٟܲfrj\sysC_bk` 6f R OR+NwL5 l>6|;Q]wSg@o,&|t']s.LH2aoC 3q/ymIErýSާ :tNwO[;?J5hqMncxT|xYkU^:y~q^^Â9W{;rbԲ]EICq-^Wpo'6cwܶ!!T}fq57RJߝfލ--te1Cx2ƹ> #^/U<|8zc`r޺6>\OM4vZs{z9_}*dүL]9F/ KZh[~=Uf}U^`v~B !`"WN{5A#6DwC^MSRR bȄ<@ܛkA1A[7^65yNH! 7j~sa'L_>|ذaCůVF@F zn|Ǿ}ч~{w ^(--ub`]8P4 P x;ߠji1L?nsG?;?2z Q x]{w Gp~]C\_pp=ztPP}$&1P+pf0߂> 0bs72ܛͮϩ`\1q- $G:S<%MpBsZ 7TsJyNU)F\s/vr:FFXlaQsBh#6FO 8SZ$ILL$;]DBhX,X`ҤIބL;_jvGq<fqAEF6t={*{ Z{(_+rA̧X~wy>(@F.9wҎ(Ffvw؜wjg3 3#Сr m7Xr/,EO]QCrv2֢3bS}/al.א^]Ϣdz$1te<5yyfPu޾bR"/]L#p_P )_p׈w.6Vex=N$!Й4 +hD(nNбׂ%ׂSA8H" xSpo 7 {V{XKi%6<9 ^<{_k׮EDrCX+[ y Hp '{s1=<ƛXdeq{k)R[1ehX_oNp2Nf6+r-r5#`>Mj`ux Fc=D o0c-5 <[?=0z> Z t^5"6&g_R:AdhAs0>,PȂ`23̓0A^W9#ĕ s)KMd6h B+/MAѩ}Nd{#^Nz^mO#@ ;RِD|:#'E݄ ×N b*(v)#$~ V '7η428͆<{*{1_zaCIKhp஁‘.q7ݧmV@So ٍ N0ul&¿f{vo"ׂѩu[G}z#h@=!OxKdDv `@ ..aqqE sQe5oBo 77P9uTeϧ|0>}3?CvU/~aK'C5f5/VQ`J$T8x6㽁T$7uy_ AvZ6]ҜB)iNasE#>Ԟ"՞>?՘ayz5#tR*p&M'l0e)TS{ԜxٽQo-ٲ%uÒ 3"骩4߿l޼sݻӒ6$K߷}-2ən?te^UB=G(^^Ex؊8x`F"$WZ~8YD |8ˉgv$c 9|`Ge,)J 6>'C8+`.@`6q_w nGH==u(-Ԧ0h62b^NTT4\\ H_𰀵GZJ3B[Yŋ$ T"yãXͽDoh -ząB@(b0r`PA%bGBwڑŹӜ O*bz{<}SxX}q(+0Dp E 總{Kvj(iP3wq4pNA)6͈I<]yuU?c+}{`90>#B~w;Y2|pv,~J2(>v,|oĀr-Pq) 'Fvo\ia7"B (TisraBi>#@npx U.Q{吚9El ء;a񉍍'; wbzx<z,ܶ{Sd"$pB+1ׄB%M $ x;aleI}ne# \zᔇ ˕ `:`w('OFS7յˍB jJ؝E&Ax <#>vWqg8wүXPLW^鮼|4@o(DN7(*zC4Jp2 fSZ]DNOaÄԦ! nk#c3;>9֫V^@? ( X4G|FAVz@=u[ e&Rկ"ϩlxrYƮkPŽf̘ LPqZ `z( r UCXLwߝ9s0&Nw+q8w<5@S՞jG>+m")T;t}#GHy ={jEo1U[X㑇{HO?zwlqGx#77/u[qsy(9b*MD  挂cб 0hw6{{'Q.o w<Žb ZEqbSI{VCبOpk 2ڐ,A zrcאg:0kr oFiLw'hyG փ\y)'WOciu}6S֜א:_3X^Ac~`@*a1SFSPEy hJYD=jhY7FfAFEAnfUvzxzda@a;.6*&g@@&0j _=E8wQyp:i)䭎]מ+ғʾn)%1p# |>7ZN5;HZDbVoc ƚ5*lב̹~d7N-`1 Ook\c Y}~L~b(^MWqk^(3s`&}tNJCowvaƮްa [eXe2{œqħ9׼)9+c#󽿘uL>_5qq<b|dF$X4W3_ !)+x[X7>\l>\y{o}8,yN'6rbN>p\P@!,ÂpA0.ZPOW`y{^ jS)QkʅŷQ. JKas5VAaeL,l͋e_ԋ]eeȑ}q8,@)>m@PpAU,u3a 2jJА!!3TCpR$o_T"I2O(P Mq Às F0I2tPhK7X+1Q!}hhVzSLHm47nF#(t)O6JHHpɜB6ܛ@0'7|{y_ǂw`QA( ™}xιjB<-k뽂4w] A^>82M+d&})IG2Z<ՠM˵ij#.kS C1E)=HS㩵o +_h56YhHm{wYt"p{,6O|Y1ܛ!w?FP8 8YdF!y&J9LFr@S1d&}N))%`35j0@,zEz)\ ԃ"0cH | #5eM)]ĄsĪ,Xb.ʻrѦSǺmvxQ 7܏x$7|)n=Q[exA/QYX!Foc/`X~̼W]/뿏re ={#gUik0ČֶU1@fBz9Ԏ,ٟx8`?πXk9XWMћ؄bWQn:> Docpz01TXnwtyK{+{,{-Xr-sJk# 4MxHaZTd0^dKsXƁԯd7e"@(l&),t9 P['2LPp.8qT_XP]!TO;a+\kPa$I xsHL!& a_*OacA >0"*x,Õ˸ps{M2ROy<nf)cH4vXMv̚5KhNQ'S駟] c&s]#x8(deܛdyt,Zך;rWwE;J?Ӿ1pG荔tN3 M!;S v\HJk R91GnNj09ԈM$Ta<1&7 V?o6%{_l[eq ͯm'dM@+;bmj)lrlBjxbSR 7pFNomWMWd|pI go/bsje&0tOv@h<]/Ԧ gdJU#|Q#bX@zCD^sJ ҁxAdFSE:#+4؊L &bohbxU6aFțT !Փc 1S[z#\Nl%Q+T\E%?6 :Ibù/uk  liI!@o[}29dVߑUso@ooBnni27Oq"p&8Msq|d6s,|3?')rV 7;{zo)CeҜFo ܛ@oB%J0bO荴'T jOɲ2S9ZM7po@荒F/, -WQ&hy`E(PO{ʧJҦ Vh۴C-pe̩]UKN%زKN;y>9's^z 0Ppr2}#kKQ84bcR rnL8꺣jI,r~p~װdoiM-\+^ru9JȬe ³&M(UKsޢ-l 1̛q-*`%XMIj N/D7폻bw1Ğ!u(?(jobHCGx;]4ϱThNy@`)ћ6<) 4$F |J8U\ s*PrЦz\3h\֠CHyJvokܛ"7(M׈f<{1#l%7i{؋OJxwX 3/7#ވr 4Ez9%6)dz$jwН}aŎCwU#. dLB>^) !zmJmBwH9bC-k3{z:\JTɍتR9Cm ߌ!Tɫ H<#d,m-9Hpo~OY,.o#ʣ938skOz7`j8@otwQ^Fo_z3g߮=9kP3Oy(~ [%v^!j X7{9r!JoWKi5K` DYX#j:h!l7\'{-P$Zi#XT˃DlŎȻ+#K^.r,w坶/6OnWGwg*GHX{^~UwQO\96sq{Sѕr}wǤ#xp RR/7oʋ޲Aݑwowp_zq3Ru?bGB+Cnp3ncW ݿ?mǟ?dsJSϩWaud0|YEE >4"+(LJ넯O\*Hr8zh7^8~SmEҟހpbd~C8_AV۫r+{aCa_̐PAhHξnξdAOCd7u@`S_6> )bz8`aS7P*)Nt]CYټ ξ@oΣUA 4&I2\S' "EmH!j a^xPnP"  zD)iNe7s=7LW?6pX͞:nOq{Rn]k6{;n9qd1BYf SmٲEA|: ~4yWW<|X! _$T_pSρ#͐uqm+~i樆VEX+ 5m"4m(nHP*⽉hpj`ԹHsn3M^mPb/I <499Y &N(hWۃodLEAB!Q7oow|N.(ʆqT "V@.X΁K zzCJ@@42F  P'Bb_{T r9uK{f* ma{sμ}2?^&!AyJqCmݺզZ*[$\w-8 ;\"77xS@7cqUs*{-f G Á~C#@7*Q G ~( e&:Zziv8mF#j!}k# #!\ah͛VXYXT<,LQRuB…)Vu@b՘ p/@ H8p_p=R#@YQ`*М) "5Sx3(Ogy'p |r .H~(|Na*'gzCarUmF^聋7 a2+^W˖-CKdU6jVje~VC΅$#mF$nrQw]EH N w 5w[ܳ!LYvs-C7nSԫڔX 9hNG5R*#x?S۽9ބTX6h h#h吿 蝡EO)l)l/8@fo`@aT'V>c#̪6:;aUL^|g~ [o8k_qI#;@QAWldimmqrr C#Fa;kLܹs'?; !%{CZNSzxՁzGhNu,@jkfS@=pz Ǡi#6xgW;vrъGw*M;x|gFю}[lN=1Pb՛X+l޽~hU[ړ}r Uli5RK3`Ym$4]\pQĻ*7^w9š[&p%AlmC#e HZQ4Dd\sN cN-V[ܺUN-h='<ǵuٓ9YGG/|k/yc#k~)۰_)#Mm_GԱ=M%d{&9c2ou,6^y*9>!5ӃުcCYS'MLxCox_?Xy!.>}3s&&&?VcI 7<_%Njn3 [ԙY=q(cEOi}.O)NaIu %:K(;WUo,Yo[6K߼bݶwXTx沊!Tl[bK- [t;SBў]ub5ݘވ z_, P{zltv#ӥΏʗtQZ_"w2܀cˊK-Ǝ5Q^:̂ҍ{!NRLZ~7"ޖLj^\סq}g\UbDWJɩK:G7ãV1ӊ.4V.ώ!SQ;lt uW$$yN"iind8M+;OvJWp/^}^k3X_S9K4OOMHw󍞣8_VIɐƨ}.h{\u9"kM.b,,ҊB.NSW78y(?gJET\dF/Ν4 -9]&9%z+ߵa&ڊm ¥Kl?zO,_Rk{8R@9XhSh!vv<>jԘ㟞6mV~3_0'|ҥ38mǽ\ i;@]?7qeŹ]iWN_LT/-;J_DZ(]WB!bУolA~Sqo;W^ Y+uޠ4`3c2YG9ޠ,2c=$ 0Bw<lho`$ve9_*~l[]HBoKZug|Vwm5&$B桟k4.ThD<>s*9 (AۥhO.ꀜ,#5۽h x۟<MI Zm6?v'Spu}=S5h$ŇQKeInՉp>()͋{+W[~Pʋ?ḷȭ\ܛWthսo+N5-2:s[qHkūqs3(jp]tyɧ׮-h,>ylV EH~*eƔ~v^{EoU8+U"ZqQ%܂Q,݋^In'Vڝ<א XaèZ;z"^э'#7.A ֞V^ñJ‹DL6u$JaiRPV/kAǽ甲( qokޢ>=bƷ*Ը.]mDclnPA6U 1L>V, ="֩u#Utћ9H{fǷ%'w?i^}͚яwt;vD[bb"$뮻n;7qs[1ʕCGJ_}#)i.5vDw޺<>{V^YVyƞ =z[ۏ+'4ʽ_Ge-lDPw(BiootM:TǨ\jJwi~qH8NԾ3FMA.Sio YnA!}VB*4q Ϣ^9!KeSP" Q\HVX[uFaJhAc9mMEoJ(|CI;тY`"Jc78{-6jN Q@W@g:<.Nh+?7 ]t|Jǯw ;z6iҔoQz<^ւjMg-T4EN{ដު+*P{ZowPڠḋ)izz+~y ͞甦*UzRJ(8> Tk_o#/ fx ]믹Ai@;Lz>lNpF©h 3ޜvaF9 p 4Ƕ+3d?z~R[Xrxkbko۞ us9+r⢵ېO7.+;ypO[XrN5ׂ +zØ *iQJb 9ڛVk?F|W7zs@f;A=Rrh}.Ug-`UUU;:Ti[ 8fnֿח^=҈e}uiZiS Xnzv 飔sJ@:\ȗ_} $&wEYBݤM4#o}UEKnfu|Ѥ=NnFrwk.1iċ{]_*G?"80ӞDo6G<9]XJ9=_5;Y 堷Pխ[mqwx$A܈% d@T6jJn8Z{a ŽEsNe:B$S:t}==44v7R1o&~ftM۷QK|5\_{vvR{3ZSC{ˬGo-G#t/w  WZQ[&1AfyNGzR|?ʌ{+z^aФ7|CU1Ӝ|-oio㺆=UDԐsհC8DFM ӣ"k`Pu`ܑe%T-lo~;BN^fhXl>,SEEGEyyp#jȩ=?4Sb`L -ҁq.PZR$֣}J%G^ţ1H.b~l0siOo^$iOuT{CxDA|DrpFR"Jf;qʯMr!)V>@3fAVR?ЛWmMozo^'ޞ w@oF~ecV]W[57pwz"xJ?ڴG?4m_ull|x`\V8Jƚ)ތBF K334}_W@?zdx ܆4vzAf6bho?%9%z7H~rvV.S 4eut$ T.SqoxEy-N?njiik&-HY;.BuJ{{߇DܗsN)JSn9zTAW:EG~Mu9fb.9B٨#?S8 몫kup-C|E}N|uu"ʓNƈ:oBUX[4KO8-c"/9xB9TzNSƽE/Qtˉb#I47s^$4Ž;N)Mߪ+{oزatt6.ݬ/QU1ded#{zI{7,>X6֭ȑ$&E/p3ZmVPû-׷K\gdDov^CsJ}0.Aˡ@L Y*'RDV(ѮQmzS9PwԘkp=w*xx5l k6Irzoi$>mRٯcKUq`I٧egTӻ(gw}mYlA<{[VI)Z؃Z?KQ]3|u1UW[dܛsJ-999d+m2*6,_ݍ7xc"@ ,) )0VQ-_]@nӀ7:-޲jظvh3Xl{4-)KU JUT"݈ooL]%%=:ߜ p!o*Lgeh=4x&izo+P6Lh CCKT..s+M&՞{3t |PY;oV8r!C2yd1OkԘ)rNgJ;>Go%ys͔*$T.Y zCyIo<1[pĵ[NuI죴7)+sߌ9^sGŽ]󜾺]5nrO*Pqg-pިTf-Coi=zLIɻ?plnjy"-- :B)ߋ)07Ov횝=~·[__nᄏl}Q;oކ>?A74 c>1jT#<ޛ z=S=Y{k؝@@$ ިZ( PԫFڛȉIG9ey<'8*'#EZ{ƽSf{SˍނэH{#ܦ4Y n5?~!C8p??}Ҥi^(,|s삏?'Lxt{o=4_~>Lw}3{zs*+HBՍyڌpܛ7[{ZV/}r3]9fO O@Ωe5D 7+Pʂ7\HK=?yСe1b|p9_4h# vmF ߑ#G>c؏1W= o K8!8h{oDoX?E(Lp"JUbJd>y`%-녮4a ڻӺo}JR/(]?~BEV+/́׿{<)y_ r |y߿q8/w}/wΝRoqϷc@7 4 &yzO٣(%*"dPi67rRp Go2T.^TؽgϞ_333ǎCFyOX  >:4 `#$G}wԉNg;pDo6R$C`%HwE, b%ȄXBB(_>?ۤ/L񜥅T #ԧ{#o]~]hL.:qf^+ 9@HJt!̀jd7ho\&ZfWzcJNcz8q"ܠ@ÇπeƌPߡC]@T:lG۰a8q)pzpt p0EAop$"6m |#;{/%D87}~Ĭ?ZS<.]nXK5ٙk6xjԼU*/ I6nd*U-2Jo6yqڌyyY)Wm?xPJ̷)ΏW55;@[k 4EoR>Qo-/2.OZ!l.s222|= ԡE[O&!6Io  z#=˂,ՋizoR3f>?@ RphLm԰o ĆtP9p^0IESqo ardz!$a X#I87޲u;׷+;{:\6ySp_r3t_h_y+ 4WMh1Z姪iKp{09zݸ~XVJHM Q(0ӯ?#v槚V_O'=7*ZsHR噕ǏG#a`]`B7xα?|>s? G5k XJU}>H[qo$Tm2 !'@l҆npb#`Aa#4C#˷y9<7J;7RySvJq0G'ŵK) \8נ7JZ]1q_^6sR&eWR8S0Ml&THKJ^ $;%%'\V Z%MP}]?n:ڽkF%ӜiSb篽BIT1ޡPVXIw{vR3 LPd Z)#x&MY9WxG.LJ:lf2B|=$g-@a7qS4(o.9+ƚ]C3,r{gʒn|_=O M:LHEynS)ޔ&T o% iҤI2gؠ')m `F,PpnR{U46eQ1s)lR=lʔ\|BM,)#κ,uJJG^jkt'UZ̜,{ QITӇZO:*lbQ,3SGbQ9PMozI5c8>7Gq9Io$UTTsFg57JوЍgZ!@7BAop7;ͨ<*B!CF P&A$*Sn90Wވ| R)yN ze{Ii423$X)7JnLpZE`T Se[Cc84}ғo_T 皡i`Be:i]yZPNSQ%PgEWizۈ)i6oX+|<񠦝UܭdU=o%83XJC@/ɳaUN"ioדȧ-Pcy3?4^PeNҶ.2תsOH߲ LYZ3io8ְ҂ƞPR6vtHQ qn Q?apEқ3uJ|؆p7W2oXxtw,;ʨJo 3'F~R 9lX|AlBQq⸄]s rr)Oxӛo `JCKjo4\;ȬgF""s-kA{S. oѻMJlSh] IGDSTc쿦JБ=%9)iS#9Ѽ [|Jlx54e.,ދ׼h:|R"_Rq:*Zǯ;gTu[bO*4TX^*!M} )׫<u"kE"}t }7.`X习S- Yk?$[lo)'ϔD|6md*3  6#kT7T:*Jzz:\* j^{鍲-S9Q@b+pH=ҵJAr|[ӖgΒ'+PY'U yl0dZ߻RcSPdbVѲe%%%X֡ s?XBIJDTҔFN,ShV{43{//bDSK.UzفN/M.Xm:*-{P }KNMJn7j *K;Y)JYDa3'9w>Fg(V*F쮯8|2bZujo.__J@ϧ*!t;pvRmnku =/;8ʍ("H5e(Qܛo`5@< ]m! 9Q) C{lyQB11%.`O>H&%Wi@^?Sho yj'hñXfeUXad]Ξ?mqd)#GN 2B=V0B-#gkD%UwSt͋;:RWE2 /R{7BdϷXu,x!?75Kx)+I1a?QQgISуi*FI+bH54! $U&dh~1G1yZ`bX:9Q}R%_zPe;}"uu=R[[Zz2S؆CNJoB3#V|AFou7mjGq)bQY 6@dL<6D1c GmXK5jlj))}y\Իロ,37Ќ7h~SB7/evQe[}o1ϾZ=[Svi U؇/ G.Tb"z10ǀ햂˫r&d@/BZ5]URR')|x"[gv)]xݮ3=UQpׄOjoOWfM`;41sQYYQTbNU/|`?2Xhd^u5d㚒ښ5E:I(CݾeiRE au[4f6%sT^t:::0qʢUea?_IxSgцrWH[D{#eN%RxN՚J%I狴 yABHt.hOAov[ި{KgOV:æ*egyKN%s^7IN VP] SGGtڽCzӫ{0-}8wݡC%)nKҥW> O+K.AxL R p|1ɪS8_ck#-C+KYawl@/Iy{""pY zwvP ovߎ*luܛ %DrU/)V*o5<b7/2z';՞t>ԣGQPpɌT*H->_o_9ri=vLD߲cJ˜RJRuAzBB|:EVL^w{;Z8ÞPe=a  jSqG}ECөPH=ߨ!I; 㹠2 t3bxr.BiT{3K\j} -W NISutݝ:%λѳWW_LlS9{9X~7c,)89pRcc4^[v{#kZ{f87Z8aKtc}aC&:t 8ÇD7kŲj]6㩯>𢡊}`Xs9|/8zu؉k>?l~:UA=qnr9/K;~7j de[z>98MKeT +p-,LWvOڷбc^u{ܽ{i޻ܓүG{= /wuhTg-ȅ5D(r582U#-n73.YP KbQDN"zL\{g6%Ip,񂉂W9x˂HMƺp7FsRf$~ֻaگW{{{?%F樾/#ACF<4̑=zedb`o7, G'88 f1V\$>J>>-?H.g=) a !&}Ro˵!:\hQ}ޤ&3= nFnvu7*KyB/ioDoBƽ2"DW9'I=u=Ԟu9c=OԻS]Rݧc^zM0s|˒ G΄7,tV<}P9AF±gÜAu6,-Uq}O=x,hZ#&"p(yex(Wlΐr}Zf4Y(@mR7_vq ڤ9 7T7F7g&'65P=& t7| :wK;Nj.7t{x6jA |?a'=W~{p8.9O  gr9 ^1ZʵwggaӍ0 Q30q"],Z,dqmk;Ǐw v3M6Sg,km@a›7Ɋn27O|u6zq H:v+5wp[L3aҴizj?4o^{Mzf崧@7NՁ#NF1QZWc[ #k`Z12 Cp.*#Ikbvi9Aϧ$SucՍЍ)GxAfpQ m\;osz#Pnm m#z؅JjpF<rY4.44Ξ?@K%&"F4آk#E)܍nb'tiz5pMɚ [jnJ#VHxC]/ X~U֭{'OxF#zaaG=z̙3mǓON0 {!IM:O4 )pH{ (c( N2iDopIc+:xҾ6}\p]gO\h9I/4Jq ]0lgOi oƸbJ Fr1dn) )ÔހnQz3d`8jIO$dg?=aƌyf O=ܬY,Ym54:vcnzC=%bctݦr,] zÉ$& r*38vJabYYY o0T3Œ E^no|w! ϔrYu[FZ|H`qյ(M=zcPc,)8J"7W}I:u~CxktԂÌ$6Ñfg00TeNȅjpq'R90'x@$/뮽Kryk|2A$` UyV 5y jM?kR #Gqo3Yswٵ g/z>!bs}`̔-95l|Ds(]s FJlŷ"6w#t#ɍͣ7_2C W pԗ ù3N#t%C! vWn`lan<&r Ŵi pPԐ(|_;vfzBЌ_ȁE9gjRN0*M9`JF7]m?Lx ..E'ݤdž! 6r'Ai2._I\zu#J2)z&6YW+p]I)M… %I)|/k$gIxi<21֞ik[k_q8m*@xngzFКPpIYY8+I͉^_S',_S6#K]RFkV΋F7,I8}?T/ onD[jC 3Xf#&]\gSM oݺu<dKi퍖 m^Tv ΐtBVؑʑpt2)UU,iRunYYY q҅J1)j}zݒ |K)W |Er"ILHR2Y@[@jo?I49: c,a sAlFXRJ#cugJ ћ1GVBZsڛ:pIz# _-"eI62h!ZcH Ylw!*`i\ r+h56sǥ(^U޾LjkӰ%?5,i䚵y x =}nAX݈`H$Wl-et޽{tS`S}gA9_g<8o8?0|C$J# x)iVp0~0ضrzXgCphrYEŽ޸fhh֛ֆF[BLo;Eoqk߆]np𜖖s7wc nk3L226 t$a56f_"?E`:bXf)lU O; kD1bm {9ĵwqo;؇O pgЛx|h/=pZCM!*-{Ʒ,GH}m=Bsq .6qv?w ?'Fv}60$KvE:R6&,`f6䫲'3Oi'*}Aol$-[ R#(TN"2+a_#>^jpNH~;FX#%N]|aHiJ#5)#HT9=v((139ّGNU;x0 f>_%7>ϩ`jk`v8 P)& GYavUt|#5{qK&tky;r]ڋs}[}9Y2LdR[zr5 ~jIސ1m'\DV#] @UP_~n񴗩FLmۧOYfݦNG !:ӛ/u/-`زUO z 2$ )Duo7N{4gFA`BS)vFӵw Lo,qy3r H{j"M" /wc/-rc I$I5NfjRaAvvEd, מ -tiȦ8zk߾mH,9QR{|4Ş&xClumt['z>?%+8F|B"8sHomCXzћd8K߷g>iӦ5 lk ~_ g3-&3.“Ƶ22tҐ^zd!/*bs Sav\o/Ŋ Wgiz7.$P8-||aࡊ ֲ$'1u B6ޘp{p1Vei< 7Ktu@\'g#onc_#Rm>RmGϗz|Re&uoF@"2em,1z_P%xi,@4^&/}&Y_1.gjAZ^gOϪiR)aT7o%`{4\{g~ 끽T}=8M?a0TbInl[m8 sjwR4̿6#;qc}mpt~3Ñь@Þy[i*b(sg 3eፂ &F myhInNHVTۅ}+mN+td i,_*\i$C|X3\ Nm.uw xXT#˄B 0 ZDop!!7ldj1QRkKzzX a$y7nV:JGS0-6JofNI󍊳59NYsW}Zcٚ܌ߵ_ZV87c)͝a P#(N:Rmv[x$oH7}RiJok6I5Ύ n/mµ`1ӵw1~$}.UB"4͝E (%<0{7c&k%/W]uV|]{ŵ'ft}C;/:0dgjL&$I\&eIע/1uH_;dl}biDjgB4|M҅J OzWAXn[BNU,vhBc1>\oA} :3&sK d JhkcX` `@fKqLhu-Cڦ?A'9ʿ ?hzsp ppDr<: o+K#8\ LammɱЍjAn`Mr<fGٴCXdgR}]Γ1wb1/o嘀O&uqi/{DGzG@B(:B/y#ĵqpzD7g,yэ IHmbF263^H&6HdKFoTVa'i4a/Y"ol%gcf g>4pl5P978O pHq Z1/]{Xr9_*%75ҳϔthg*b=: dOdM8KLoapIw$g^,1صnc}}b=VsxO UzQIc)9FkouW,$1!;F7DǺ{>OdVZG&]i \UH:vgOm\LT {1ȁ8ݩ sƒ%ذ>>-HC~r! c("8 ( pƒ[C>ýSΜ9Ιv  J#j!p W_8RJIp2@ٳMW2Hh_2PIBų(C*`so,)((((((((((W|@ u@AAAAAAAAAD@hp! p!(((((((((((ش|fxYR`Nc@(`0(;X*AlM '\(0`Qdc@`0 2GH|k+Բ_ȶM@ 耼D oRq8`lB,Bsb)!:e{@PPPPPPPPPP8Scbb|>g/ `p =zhMMMuu PPPPPPPPPP8ƀ ]C0jkkkjj=zС555$P‰1bcc%@"|q~= ?1J#G_QQQQQ?F>:p A!1U4o޼e˖``0e=_$'@MM͑#G***WUU {N1"G"V٩Pe#gh7dp5)6b)D)P% l Ĵ`&DS+?PGYP#Jb0@#+J@! .sFЎ +Qé4GRB5wA ]G@A$@fhacwVu|1@ `P7(T!~P(}ɹ_5 ..}LAv$0AvPuuw'􆛆%Xղ&XYTkf0S2'd]U6u;bBe%1Iwhi[z ­8z g0^Iѧ0)Jʀx6ࣶDdttCF25Ԉfi彶񗍊=N)1(3W&7z(T>l"8,JoHvlBXUn\ۖL kj j hI˖j1Vp0wW84ԞkbfYMI|ah0 J”.DT LcB YT#/, 3D#5D l7n{oӃ.*ÊY!lZd9܅f.Z>h @ @cSKt.Oo9ή:ʂ\Xİ t=ed5<4穧ڽ{w-]C+R'Fkkk>qƻL!YB@uz :PCsF555qqqlʿ{dDP[[ f>ý{F%VZߟaBd W"Ԍ:G|VB He6comՕu o#=,'Wɏ;5g&za f-;LRk$KTb)h"91c-/'<kNAa@IڍN,a Ptjv$z[!W=jN>@.{P(<_mDdEJ^V]-‰%O̐Rˠv1r}6bkkŹ V&k %<EyroeX4u#ef1c8T$b~fHLM0XIJ)"ə J o@1>dC1և`m]CWY9 T2fmLAy >cXۦ))$&`:5=@JW[d| LۇFDPqːX{-)) B4&SP%Rԑp֝gW!^ʚ]#w㰞 %-dAgp#,ˈ+C\2\S)TTCG U׃h}"$6c)hPPʖ vB|+dLڍ8e X3wJA۪3E~Cv(9LKVͼDfP)}&h?k[4c bolz"_*P0 [6Cig `CU /95s2Rol6 *a1DqX8p: Av 6YGwF ~@6|uOh/q̞5c@ck= h<! t:+Rkf1qTd9>E \ٛ-O<dmfEݱWBļv`J%\1=Acn/OߠMC*Ɉa#j~oͲxA9ɏ;J 9dܦc#8.C&1 Y8yv^ j *,il_*h/Is:}9sNbN|bm.1 ڢE 9ҥK5UMvrĺM_vJϐZO{ު>X<+H+W*LDpN@PQ~in|0qC)~yӒSR2CTh `GŢom {vᐛΩ~JM#F?+"U>0iҵ޹=̆.i~J<,8`e'`|RE"k 1׋9#j˚M\+#,/xpy< %_vk:?Ҡ\Nfys4|Oϯ,/н--?uJyG [FRpWf5nͲj)Cv,/A+_X~7EjMţ ;C<\PCFZFI~Wlq%&AZkcɩ|jsH2goj\mlݮ禴gǼ!YWw"Pi&]4Eg]AoܱDq}AI:VSPF|p_%b?L2KFBA Щ&vŞ~Ɇ"9Wbs3'oVU ]͚ɾY`ct|6c@s~$0W&BLg 8 G2{ &|a՗٢8{-5OM[ïlW鶎]ЛǬ*tㆡ(//cҼǟEМ6y;9fq33[A+xDgq|)JB$?r/YJLW#opNSo_|ѥMv ̻Lc{~Em DlfDc`L1gͲ~QtK6hЬ,/c䈎%!K \0i3l%g6 $÷q$H{فSs II 5P?BLӘys ͛)~9puAnahA8mLqf{:2ȓia 0 }g{߳o]|风4 }Q6ttt+#xtO+Xs`FcQR(J,+kĠFBS/͜zi,uN_5J?_k}zjB6E9bG69hj68'w]o15|T%:SGאָ K8c%; 1#ہ3bG&IE=T>g0 h2̌sz)ߘ<˚-U6TEsClE"uݷ% iQ#η xX2 FCek@'|Ȟ!p h!)v y*zPq D^Bi`0D<0c'RyiS@6 n/æ3eOq6HCN&/PZ-"QZ&%mJ',/k~S'_$j_o%ѱsF>Yr萖>5}j''e̱}j=ƛ" X$]v)qRbKٕIOPQ^&&&&g1y᭵!^3-|s"Cz]UMASӆ%ܳ;ntㆌ4T Z᫝S}S=*2|i.i&F`L.~^ZĞ2nm7wkpA Z% \:v,|VVJ9es޺ɚV ,An,Ž7,Իr :t`An<5S&T|&Uu=;^̔x]憺];J~"益q縢][Ʊg^wmW4ߖ;עy][Ʋ%"w~X۵ xьagƞo;qyAd׎nanoi\j\Y^&LM/{`)2 u)Hζ6_sѵe;*/~N셭.j{q8cAZCP0JRuc5)u^t N-12Db+vG#֘-%ǝ#EʼcBH,:PEkƅv;J)N*W\6qͶ i.J$-DY(58ېp`Q(fUA\\JO,)թETܺƥNmdٚ⡆ B"p`!J5PN0 E! 4M#4i4&9J7|{ES#s1io$#"8})$!ԐSu{ O.};՛7o.))IJJ޽{LL {wy מorS*.h ʚ\YX|q bIǯZwFݽ 6/9k`Oe7~ri%k{&MXo+j̽w |QzKmʟoӅ`)^9Xסomع$Gؼ|#T*piV2#a1VĐ4[61(lzxUOt!#G_y>3N*2m `{y_^ϒUZ RE ( IDATV/#&9u¨W 6}[~竽Gu]]UQvYv{d_Yh|W)-˟9v0jL)cQ3.IIY^ cq7gG ⑹X]kGIUyY ;{:F hYef_ (hY2N~S/ZfkqKZ,o]sj^խ W$nUvΤ(8B]&|,!xn Oۅp~X 4O=K6a k@vVydߴ'ziO9Lԟ' !ODlHIB8_B,bjMwL[෽ v Blٲ7|/ݻw}W/%2xtLvs=&\kb^dO(7ߜCV3i=&\^>!Q#F=aT5OM{V}3̽w+{'M3/8{[Ƭ߼u@ شqC>^3vƆi3XPCzB%-|w ݸ}|ENEK fLݮK3ޱ}\; mZ`oܘWr=8gԃS87W&YKzgXS eD>T܁o3hy%K|JZZwG ~㰌BѲ| Q07W<䬧lE q0kG޵d]猚˕eZ;9Z/흵zi>^/Cw+ZVPY^&gdW{RR>T| ϸk\bbkWiɌ; xOѲ~xWyy|\;Wgu}WRJ(CVsoQX,[|>yjΆ(|++~pif6nɝ=oaey#e\?T{;^Yj4-?RM[!V7]Z ?,`fNcDiw" HXkٝ:L`g{]pĨ$arD67ī;7R{-1g#+lbRܲ3줬~B'@r+l?&!!B@L0UrԚJ2$NA 0n^FK-N45$<Z[ʄ0KW d\pO&Ȃ"Al@4lOƿ6>Yx& УncosDLx]ƅQg|}1f䝐G?{ DI_~>hjjܹscbbbbbybkx`Q^Y+ﯪ2FbIlX_iD¼*f!YgfKx9sO,k?`<)97 ykKUy쯲ΒΚ;c.mN,a ~+si ݘ[RI.ٷhY/Ldw_Wj?rn茻&-+`s[4W; q7#+3Nߢe^ [~MvwsF8"vjl3߮c:YO/`i];[^`!58g[=Ѭ(U68b\>&Gg_6}ޛs\hVAm{W/˟~8--"Ɯ\v#%5UZ"- \+K`E]a.T^fN 8ɯVCQ0Ul]v*T-nր% oa˥R3GFRIogBN2[x<~ <@@ЙĘUq=v#pA TJ%3E@ך$x(wX,oѢKII93lr}8p _4*u 3L]6z_5;J,,h5}j[(/+۵sK عK IR i?;<|񫔧E$5MMK-_*̳%9ײ@Cj6˯|k 6dh;K?ynlGwl,$h= i$9v൥y驾T;oҼT<+x`X&Zzi~bbdRk5ukحUbث&~m[Ft>j+9LJj#s_*Z^+GرwUzv<:e,Zs:} RY^v~8sg)zw(qPhY-boݵ{0{v^ZȂ萞qcN?BƳCBeyy-b/( +XLIM[[^xNeyR'-q,囀IVa;X!F\5bґ"{l }L?۫7si&+hKŀ_*m1"&=<}TTZ 9yA1a@u,ۀQ)dȠS2$Mu5 q1/  + }1>[|b,K8l|\並ɱIdӤ3g&&&'$'Ŝy?/9ٟ%?_4?r?)ٟԟ4iRlrRlrRlӤ3g&4MIJMJNHm'4MN:ë^. xf}b~k>^A#9O}gDY `@3{^m;&w5lh>_{+^OX7%;e|[ 揋\}~DR3Oy$W%N5b"gf¼󞠔u6{ bqׇ<)= `~C|{y ZBVZ᫷\?p?ex\_<Ij{h*=g͝?k|t;'_Ngl{=DV>\|#b ?U,yd|@Ʈ%2n}jO/`lj mE\g/6SY^ݵy-bVE7zYg6Ye-dN hv}Jbl5W叵Mcu,Q"X7&[5'l:ƞuC4r4 1&ntč)P떙iʗ6k"O@ wA!S| 5C"ن~,G5&)y?# /YAʺV +bIbT+gF'SFN 5Ar^ur*\gu:(?1ƺW9q1ZllM?;v@qcBDf4BA ~xF_ѫO}2yb~%Tyal)1]7iPvwO|I?˙ҹmxyc1k7)AʛCƶ-_ϜKz0+~;O+//'/ɓzlx4!aλ+Vo-c*XU!'36Yxqspqz8M#:#n9:񉱎Tq" `ĵ%]:*n(V-y|`mgBV*iKf=32 ڱ[Ċfile,)سwޱM~@`N9v`]e> 졢l .%O<3L`N!<-ͧ?P}J%Ӽ|m*f[l`@)s+@A䮢LLBV'2ydٿR?ne _e./?KzgR6g(|s1EX$k8mZİ$wzY>;ĿtŐpQa¿k KnMv8ғ\sV钢B8"_s)gb>/d7w$BsúP` fm]1 沍CT/l]qr&*XnvZ!<%Jfkٿufs2ӫ^[ګYR{Kٯy{oc@2%Iϡ=4?gԞ2/W=տef֙[sE_X9ov}jRlЛo"zEԻ#'߽|Iݓ&=ɸzvŒ+czư+ /bΛo]+׾gѢD'Lɷ7ٸfea忽cz#sW< [9QXYX`Q{&gXLp8Gf6)W&ݛ,WFKlc."8U#8UܡSqFyjH!0dWcV~,=d?xCF^Uy‘'7UyLdC, \u/UU&cBchױ3ԑ޾cᣊ;o ɽOS,_jݚ.) #ׇ￟lalL.sFY^DԀ IG\vղ,/,5!\]X^<2w~NS.ZV0sʄv٩Q.zv_];_Ae򉂂,5ZCԙY7zi^&¬̸ks{Ѳesrg?2#+Wjcݭ7 qx:vzH&᫽!b1e.ז)3̜2~zgq5L(y0"ħXDۧf4Wr9 a.YfbSq-t3dw)e*30_U{C 0 9Ex..- fr L6PgV`0oPz :O}8H@@õ'=|ü+~;k=$:!,}tQ_s`+4kF%š,0* O* SJu P̢:g6K^\鑠piֻEwبMhgF}bc#r3ɚmvLXˁ[W Bs ۊeCo%m1=ndkw?ʾ2e|gfg^v\;txMWn5F/yn|IY26q=2wTx۪%yM_˽'cNS@R;H=Sx}3*^0Ǟ{yjҵ27tz0W(ZjIqܹ}zowբKK[8 αD |x|ʿ `˞#,v2{wؤ56}@EyY4]3뉡] _xꑹmyRٵ$\R/8 IdxxLw1('N1|K)5O+(zo|P\;kQ,kvg)GE2?wAX؂5_*1g=2[2 W w[W_p}G^0keMGD/ǺQ7 i6KM.srw(wsrg=̬󁑺w8\ux)z#G۷gϞ+矖V`,³K.-))iҤIVB%ػwÇ322FϵoH JWS]8Flqw}pD lJ1kjuvP]HF}W6X/ 7RN_pAٽ@{[GE v(i) :$# o*5G.vnx!2`J?:en<`:ҥA֡_l۴v嵾ex?f̝FÁd[1W3uTmT-#'G 3`'yhD#x_حӶuU0,uA6qZ2|KI 5BfO|O Ќqh$#-"Vw'..+$7 nX=---999)))11 &M$$$$$$ A6la8|pIB2AӔ~ 8mj |a8/6eCtP5Rhޅ"B6CN]aD)BnT?|a4@(]O#0-v@2.l1RoX >Yvh Ԁ͋IS"3H?27m^1G@AxMeP)#O'잆rtN;|mS_MC~92& ;K-|Z7Ds!B8~/bp$#P* ys: ʶ i|uB5NC db+WL3OȚk9lDPz~#t2\50"YE~3+[D.x1 ֘.tLjǦ/5k(shOo0I4L%p`'*!T8mD!q7ٰm8^@.yRRX-pր 'i9zg3/Rdu<[dd].35Oa pY~mjVNMilnPM|FBC'2bR|Xm0j`ȀS'?.%ܻ(W¥C=}`ctZ0X{6T2>Q{ /10f3t~=ڹb(y?3Sδ,ΌgjV4n &!y.n/!f2.v3\5haqNH\[Y@mY5a`֫ytn|%(EJG֦okp,r>NsMO/ P ۇ?ʻS{XM*rL?)w 9Ic%wP]R݌cZV]UJK6 DcobN,q")c0sn"0wllK-!ο`n6V~hfR`P?aYE- b kx/]ҋH/zcATxލVw7wEƠSc#@ Pam[|-ʾ4xkڿJmf)jd NT`kd( m*RJ@8Gw/|`~LӒ3&p }#ۇ 9X0.*f=_F}jZL7 l)/ǫeH>pL5HD:,ڢcǑr@`ӰmA+p]x6ywU-}ڶ BPyb쒉)!˫Ԓ/'M9г |8]gD XH4mB'_fФR/̪5- , 更K}H/Sch@tJ}ID>hx|Y;W p4PCBӕѰ-ҢPJF~cAhNFmļ)ɼh"愃k&A.'@pIuDM& ^FaZP}LPH}05Ɛhv$tIa|kiRuKxю'{r8'$}JWs\W3iXQnEv8y$a*.kYh*L;HsǻE{U]q(Vbwg5uͅd-e;h d_!Y0o%œaߍ9Ɣ}h`M}ID_Ȭ\\91}˺ ݬ49^-SUlvPwwaQ%W]uY@ #Bg~t)r4{ГAsR@=-VC0]C ^#G@ K1SR@X3[y{_.BYDrWP}EiACqR3wReZ/L_$TS[m懫 lSޝfLxMSJZ~M -.+UN9IK6qək.WL1n <jXBRCY` s[%#/Qfi c$t !f/w'2}=!u1`F@C=r{Am!A1U>JNQ MHhZC_qsVzBCwU"e7BDnDRrGT,R~Fݹe]r4D?3(IGi)7Bou\77 ޿QbG~gg7(;i]p:Qm4 F#j}ԕAp HQT 'j}:`t8UPPPPPPPPPPh4Mk,R˨tj}@AAAAAAAAAT4q$) ({@AAAAAAAAA8Bo+}/d̝Ӡ~oz|#e3ɯ??lڮ[zpSm܂_s fKNoF%C+C_PM'KQ$D7OVqXD7Rͽʝrۈ@$q[Bh /@@B:es.E(4#{tt 4Y ޿J u)A)QOk5/cQs;GvNHuRLE+'cy__w^E:HxGK^!Ҥgjʟ ^Xǝsr%~_ϑItkm ш7<1?|:PuuGk\" ~9DMmXjPWˁꪨRVK/Ar-HQsՇԗ@G~eڗwkbc)_9뼻*^|u1\ϸɿvqѿK~?Apm#m oFeqǚ_5+tܼ[:ym+z6?ܠ\QC<~؅ yKӋp$`:g`AkaoJ)]S,Z[w)((((((4A`WϣFIB A֜- s#W=(ß\ĸ{|tڤԺ@"v/Qu8ߵdep͞%@m7*&3c [Kz~]`Csݰxԅ\h=wٓw!N{syAﶎFtQLBաu.Y y|֝` .{Np=gdxZ罻3sWY? {mZu{B1R=fw0hl^5Ă]F9SZ L "ɡFp#~O6*{( wU 3h$J$KnD{$s8b7OE"z6뚁3m͎m鷐]?~U5DF,~/2m̞0m#²t]vآ_yWBd=9&}i Ys#jצIkn)((((((4jV "<.ܶsKa4`svNZ2 @=qμ(3g\3oi,s~|hg L3ξv`:` YtFnO‡iY<}C8ьHNN=3pi4r_+(h ((X{VI+EˉfѠ=~宠pzO=4WSq8IqzGOPJaX": 7TiYJV-4`%={$h|A;~χ:7Ab3_L~<>B䠶F/G[wMN\Q%Ӵow~K$5.'`G?q̄_@(53(<VRJo?pPhк-n[V]6Y O>:?-wR:՟Yal4kԣ@)@aj 1FY-U%5-~G]Ң u5K1 a%)J:-xc6-cDDm>[3|Q3ŕ}ν[ZjUSSsy {.^ݏ9(D#DicQ큯+kf2 4_[aW\X}0>.^6wͯTvpHNnoVg!.`u F5PB@Խ7;°sgu<%ZQ:tJ JuO;t8l@w F}`f-PJߜ;;v*V삋/|,73:sׁ cw(уG|k5kZ8c^}$LUWo'viZ:k{V}hGPhٗ|qfN@ݡwỵuÈ<Mm֔RPJɧ:JuJi0Pz Ϲ Vo/?~DFlLYAAAb _z)|c֯F˗'6irJh;OIPDoPx&vޏR>4.ފpKsϵg>+R'M~wW_D.-!j>|4 > q?_;%y~g~ @󎏪yɤN C =T)*" ""+XEQP(""ʮ+߂*HYU 6w&i8{94 B\AҨ.S {].WVٸ+LFÖ7^1oAa!\ώy"P;;O( @)0-쌎W :/,6i~sn٥ X}+nHt gQEGkvyA.on/D鋊LH_G<ׅf=QZzt?A%ʼn&n`;6ɣ>j2ZµJ$L0Ajy <`ʲ({lDA@[ @x&ػ`ਨqxڲ{z^w& ef3(:BUPRPTPUP(DD$ݼmޱ?h@#R(TբARP(**0Z htpϚ5Mo*UTٸS2N+g|0?8: 1gٴq۶{pPQz/VDŅ.TP) eȕm56B(֝zn"mqp/ONx0~kAF^h;c,͋VB<{FwÅM.r.d#"00&TѬ e @{ R (2IuZQ#y 0RDA@À0P)M_ۦAN! ۖYK>J(JAUﺫߝw޵gǡQ^5 uy *ʀZ7gñ1~ٓ{ IDATj?J[s1}4fvO9v6?%RZ(4YA*jwP(2Ϥ[LnIɶ-* Dl.s_z:ڶsM%> ;XHkI.fo9kI,[w^c8Y7W~1e-ZQ)GJ(L(cTe9`F<BBIsP0Qnk+r%1?.Hs^eo| eۺZBGN1:^}SD_/Nd|Z WO|yҩWOV]ËSV&nyT!:B)2M`Kn6Z./t`E_N5k/-1;WK)2w3>\>q{`  fWKV[~Ƌ^- bm 2A (F6 b hE"N$0ElJ~y<8(|']@T:@5NĔv ZPrM@Q[ Vէ|pEU#p&dQl:(϶aA{w0_*Y.2g '\kH@@XT0[o%GzmWķrVn|EUO¶wN.(PA&w[po򨁭^U%CId{Y3@>7E+ DdJ)TXbMRȲ"b UUnZ'Ns&qP|T3Xd҇+P)![bAv)ķh>ԇzvA:B>xa۴wkVeC5`pCٳYuUCʕ/;_,=_h:T4jCnZhLݼsV~ƒjKFO'*Oɀ20+`QAalˀ V^gU~l&#3Րpi7 @2D`s P D @RLR@j@@#V3O]{C\ySC1MіmuGgVG-()B@@QUe()E≉l%+%ݺ7Oٻ3K.eTVQԝsNp--{l9y_VF @a`VAQ!H )S79z7}6@@$ @`&3@.@Tf؝oth2x(WSn-kvIIQQ&E"=~XXYR\$F(f(c H"$Љ(:#hfhA3QU\! IBƶ?y}#wčwYu1d.{['+{TZ@0wnS8AtHu}z'-a̸ܵlk? ,*+L U5){, N_ؕ{. bt&zTuT A(d`-`ւI&(:wѬV"6W)B Y'}=Z g?+oO;Ȧ}6.*$((,z_pE((K #] ǃ~SmZkw 2!Y$u|)5N9} l\8R#xWSe>N "7ѩ@Òo\.Q=lh} L-E8xu]۬5QEDTuW\5?JI>3g}A -0]9/ZVfdf dPkn!Q TJhV \(g*S(eR]UcL1&0RYc!LWh(f"E9Ewo֨o!̱wӘQX?5Zs~޼?<태XMK됚|M#F?$%/79Ix-s(J0P(e2@y)&jU>ͷ^p謹cR0* SPue_è}*)ŝڥ2΄uuY?2}:|cnI62uulb>7)IޥaZ #m[ںmR:J͚f2{E6N!̣/ ՛s~n.nqXr, (*Rr3 8(]uYPK^YG;uԔkL廿 ^|K{/ }&,Nϧméۗ+dݵWM}}┙WH֪aj} goIqm sp* CtRZZ{hh OԨg2x񩣚aS.i5Qvp?]QWOi´K7 ?<rcqs{ 0Tm4GL[ f}ƧY/g> et]^]wGw͛72? ٿ??2ǺZ&#V5h4IXUf0tm;7ܶ v蔅vG*\\ɉV  NCFyߞ}'98<ڛ!'uwRp{Υ[. J3n|̾`|Zz=wUˤHTpe!l??dh߸g]:{x+d@Wi_Y9nfږ`2*(*p׵G ~-mz0e~<Ɖ @Sj"DEa( bAQxc!<{Ңe+&hcynA2^1uӸv!x?@2(5A([ٙs !w\̏Yv=3y$ݤ5 Zn~۷ZABڦ,hp9f )ˉ*ѐj py]@*0F :<2MA':N݄R!J 0H(-OtcƉZGB~w y\g}nWf;]v kߚ l=)롃BQlTUTQTo#cad;SRUMʡIwIZttГ q̜Kb@]@Ԙ-bv|Q+D@!ξ &d}IOot)f` ,;im!^DjOc zX*P/I@^ W6m:t޽{ ! \NT|;+uj4;+'e]8FRD0T[̆V ڶl־]W`z2>`۾Ny\7gFo??w^s#[$bn ](FX,R:%^fME.Xӎ}_OJ6(l]d1ƘR4haae/UցB@* pDƟtSx~VbW"E֥0vi^"Ajg*w}v@2/؛z*nix10 zm{;fϬk&UNdd})n&*k?__a&kd7Oys0ٙ;tV Jls&e@EZTR`";QxW"}jJî62ωW3tj[ %? P&C JyYV&/D25O Nd2 h.-5SU48,£{8U:ۮGwb*,(ST%&H)/ 8}peNFm}$w.vfcئ7P1Bn/etL6۷;)S':o㏟+ɔ>v!)By*m"Zz5}iQIMAVckJN{4|uܼr0flX8n^(&?(.Ǔ ~ IvaaVi>}7z2̘q5w?&Op(R[gP_7,Ǣ1׷qZ!S`|Zc2חuwR=~jOZ ._P"{t/.G=9fk?9f=!VJO\Ehyt e @D)WTNj3l9VxU-jZcˎŮo׌p|ObTQd &9amr5Dp9Lf%䚈 ͢$d FKN审as֕ 5UL&!6J1 ޣf(br[\sZjR-4}!PlmÄpTySZw@n^eP r.d^B65.>8Ru)A΂noy}ߊjT:˖elTդ(fs|]7h*;.(!;N8ւ@}6̪uhA C~y'vĈġz{Wz9}U๭x=LuizyhNUuzcndi"$NWxEۿ밾0l|..:yni4wzR׽5v)9Sm74'߳w#*]U6&~Ua*)|Xbni,+S>lW܀#+5P]jElƂ<pٓb#Pې`Ї@P8h-$t &VG"$4t:hY@fRcP05YkSXf!hD$I&Luot9Rx0 m/FO[ 0B:7o˻Pt\2_M7o6 A$R-myYVJkL#> p%hJjs./h^Ttl6Ctw0Y>0O`Lh @y%M+֗@@ X FƻRAh4UTmx՝XPPP ^ ; ':LAZ "P^֚W!=:_/r1sӾs oV6G:!AA^(B};+#F\/b(jhhf1T6 ",qʵ*.IJ%y-FKi'v@#FjD,-EIvtmM]v'FYs,*(u,"P"HLh1y2&7I)EV!A,;%y姾㱷 LC-yyכk͆>0g mچ{eڈh_B%Yv)/BjMSPm|H$Y zq7[$ IDAT(fw iANmxqG-**kw>?%Lc;|ؓ&UC7}(S% 9<@ך=m^栃JgZ/9ټ魛|P .5#Gٜ7< V f]&}ֵg١fc1DؘH"U)XS  NIk ^b @bUV= Al;fz`??D7OwkҦ g~Φqu&mӶ f/$?W/ 5d C3~#_|A e/ /; NZ(K.ɽr𵌢v {M;(R%M>Q\&{ywou'9J%{A㳉5Xc^85*২B]~׽J8W>x&ͷAy>Z2&yzEuRO1) SϿyE= Aj[.T-1~   T]5>qGwAAi朜IZ}9WR1(1f !b26oxO*+  :>_="iii=AAoàRB!_5AAV@L=PǦ 4AA…F"x]W{iu  pǘ+}X5Cc!  p!D}b #  DQRjM*  R}peO> %BWP@AA•:}UJ}<1*  hDQECqq-zr/<_\V>>DAAWl@V M4iۦiinBYˀAA(_n踦M5 k۶m4h |}GC  @TU]K)u{6c\̇|dFAATUJr{Iyz;7FAAv( a9{)u@1  4` -xS(AAAj}=x6*  R0(>PP@AAB%WBGAA ߧ_?̊j{ KƏ RaV-Nc=2AAA";;;###//E"""ڴiӢE #*x/d anZ'Ok6^uw H)oiddd'**?L&1c1b),,:wO>fJ^=fh2 v H#'''+1UQFPAPUUTUTX\\$Z]b C%_>!   HMmE?@Q&RUU+^ I!8ߨPVlZC@iAc2 *UTŸbeʧ|)^uӘAA+T,N웿7zk*:""xmhAA37SN o O;!,I   u 9ёǧx⊁ txE x;EJ*xO*v   HuT@5:""OMJ" AAP3־{I2txE x;E ם5neAAA kO5 v 'sdt/>  T J}F5 o LPWAA@H O @WASA@Q._Š Mخc|c'  N`*DeYQEQdYX,&d2FPPPpڵ˗//_gSHxekw#344iǯXƏDŽ xޟ ol6W'Nd*UUR0\TW-l۵1ټy]̟77Mti*=FDDZ z^t:F#Iw+ 5hC  #j*hF x;E['B  RG^ o ŸFXAAA>)^5reJWJMVDQDѰaA/UEA`-HP]}b|9\u]3ϟ?sLIIIiiiII !:tСCll$I'@AaFboF FAljOoׯ'TjO>d||RpeR:s>ky>/0`(ڵk͛7}tnB!~A@אxLP(wB\S1V(\RNqFER*rrw4Zh1cƖ-[꫘T AA0_ވ&F߸qF!\!>pB4ͺux$SAw['5jfލMxm裏feeI$˲NEQ$I)1QF AP@k>;hI%9s }T8;vlÆ <])HUտ3gj)4t3fGy:! Hxj@<L&njЖjXܷOT\F߰a%\]!3g$If&yOhժUfks"qA.>@ø9R*q y@ n?C}饗+ ,Zhǎ.\'''O:uTqX!! .!{_0b!h#Wۉo;x^MU./_sNpoms #UU#""/^Zr~~vx;\ psİzt;E\~!/'?@jRRR(rA=xhÑ'''sM{"Iw"oPML _*uaZO333?^RRRRRbX?<<<22uֱ|AڄUqF{Y|ĉ[lᡥ^.B&M[lټyϕsM60-k4qEDDTAkX/8=zׯJ/r} 99YyݻUart;vsdyȬ~~3))iԨQ^5jժ ֺukaÆgr@ }֭[;o1.. %4;rnݺ|EDD>#G )̫֋) H}0c}=?l6POPUU7|]!!!~]w5zh[o\ҟSN7 }Eap.77vz{s]7o:ۭ[Cyyڴi&%% 6Lyw}zY?|\*|eeecƌ1 @˖-GOܹs嗀w9}tv|s?~_jl毻%n4\vm˖-{Yfa-<A/O{J qIG/WM.#7ZVVVVVbŊ+V;ӦM6mwaWߟ8ūĚܾ_暒P`*0 <]ĽҊfE+} ]njk&EGjtI&Pg#44' UDUUȿ׿^~eJĽ] >(##Wn& A⍿Pş0 3g{k~')))UsnL=wӹSMI*AnnqvءhB6 HM Bxoo($8z3˲,IӧJd|ӂ^|ӇY%%%n{۶m\Kw}SNcǎ9S<|FӥŊx͛g6Ui{7N1bĶm*ky0aBII?~umJII:t,˾ed۷/WY)))֭#~gY+yt^nF֭'|2dȐq >ܘ7w'xCgz};E[Z '!@ CF :&&*: o?OKKgU&!|111|#xğ 0ܦMjX|=#F1ciiiݻw_hQQQ7<hhh,IHH| :l6SJ]eJYYU_dz-J/xw˲l+O-Yu\:u}l 5qF{_jm_x 5\V'NN,R,[HT%O !o^w**qJ+Wo?もѪ`!nB Bfݷ馢6KJJ?\3֭ۜ9s(Vh(xuJ髯̟:uSTlb̙3yTU~…3fΝ;733K@Tp //ҥK@rrr x_ܹ;ק+Aj?Ө^`f:tT88p@ >W^yeY8=pҥK7:t$lݻwf6ZCpv Rg# קOpYZZ bǴAAA+W0`*jyyy_~e^}#Gش+@iA]vLU ^iT R_BVh46WMǞ'D}Cf͚ÇsOt7[\+ ˗/ի̙3/^(Z jrHC"$$?r aÆ ':?YA:xF 4N&g9Opsxȟ~o%Ir<Ť(?gΜ':C "J8p'@4A@@5+M&Ν;.9P uMYpv3, DQ,,,{믿o\sխ[7pه{O?CVA[JBeGtҼ<nj֭[d<\ 4;.z=ܹsm6 . 2_y<"_|ff+Yns|Ykf?s#G{l\\q=<1<<< b,vիbqUqZ688EQ۱޴iӾ_W\\gh4K,1_?kakN:Պ~yqj}c 8p`OW7eϞ=']XXXxKN~ŋJh6-[7 0`ʔ)#FNmQA>ȩ\;z)S;PcDQ,((غukVVVVVXl>ۮZo߾!!!n:rQgϞ}{O˖-06(_=p@n77ooN6?_|ɓ'trcm|qg͚3kiӦ3g8U lBdڼy͛ox'l۶?T"4Ph,[̓Ty IDATcU* RSS"Brsssss:`0LP?aeffz{޽{[nkڴiZZ SYCVF >%K3ׯnV> 'x*_eyĉ/"OM`&x ^HHH]S Z؆+EQzc=wޕ+Wvmx"E>}d]ӣ00AT:xJ(6u֭G~Ϟ=0A)lB իD(|˹Yf+VOz}x:rõUU͛k׮;ӕYK-2 CrXPwfß|l> &[f͚ڊ]8͉k}޽7nJ`n 9 5F)&!1??#q%8·{ ݫP΃W^6mz6RAΞ={!ⷉ a5>85Tؕ_|92x?@UUIGCEA*]+h4b4 NHbᛂt:Wh4QD ӞQ!H?)S8uڵkdޮȀp$I(jF˕k N?~m۶l2j(FDUU'OcNAv Kw1vwdɒ=zfxƘ(?`0DQ4 _~?n%Kٳxܜjxx8TNvѣ̙#IBxy m~駇:tSmϞ=`ҥKoW ìYmJ>9s;֫R:'sGKMMO"##)^-DuQj5&wqw?~|| Ϫ$~mVG9zhDD A&P^^jj]oذl645,?сn-]tyGo7dvݷjRẐ^J]Sk}ܘ}Q~tu„ ގ駟޶mgyTf̘YMT9xG O uwysٰa?۷ӔqK,y=IF\o uV_bwBJڣ(J- 2wxVV?Ps;$8QJeYjJ.s=]t$ًW+h4٩' .٬jTk$#ǎm۶իW0"G~i~~>Pke V]@=ڪ~+w!'Of.eu5p>..ݪWrV۹sgpC)++Z_:;02Al5j/^ W> 4) H-BUwǾ$Io=99V{;;vؿS!$Pp1i++,(d4uuEFF:f)J׬Yx"~m?XiQLwxRڶmۑ#GBŨbJ(999]^^dɩS8yގ Fu420BCC6nq*d)XZw" P@'M< ?H=jv%$K񠫱 If1sanGFW7B fv=b18a쀍 D7b&ĺ[ ~xyAX@Y+*3àm:t_'r!Y=:؛!9ev+cypM7ENbo|wU, {0~7~cffK}#K677%b ŧ\̷~뷮l@6KVWWoKQhc=FM*fd7 el ;KR :Dѻ+DgDWv. }-233M666s~[ڻw/(HN[};}oOM&JDwr۶׾v__ٳgcc#Ff3|~ +6\\ve$q(t2=#Dl?SOU<22¦̃Y\d,?P,b1ĬO?tTWw^f v%\w8;);4|?6yRyJr\ַP(|;!|_җ.٥aɟs=4W$l]2@W12Se8o;6Ao><Olnnjd*={x={0D5k 7qyPKD6X]bϟ?;;z׻~]r%\rH p̙Bqc&;v؍7޸;e1 cuugݳg2 ߳gȥ6lѵ÷r cqwϳ ^Vկ~uOi]ۿM8_zm;bHK/;Wwns/8u[,|6ltt4ۘ^{GT5R`|,͖^?ᑑHakkkЇ߽曟1w v/˟g_җDtW՝+J\s^K۷Cmݦg/1uG?Ci R|q}%Ԇiwyĉ'NLLLD*j}}}ii<'汶g۶oI*=)t ?:(-]oΝ;|Y~|+F///gGk,G^6 z'|Im͐OشiϞ==g>|3 ŗc###jullje`a-'?v"?mݦg2.^dRٶ}ɏ/zы^߿{{{޽?O_??777ET0PddA{lwScgdM0ѷs2vԩ}c] 6ɪZFxk_{D{5AC#y~wBڥcZ(0S+(@vXUxg^xG}G)č^sdZj Ml=69;y=ztddMXB.Qڡ̭9v(GBloG>d殛2 L²ݍ׿'>=K }c7pFĢ8`qc++'EdmyX];絚5Kkko|#Gְ{쳟,]i0-oyKx󞷵bd4m{cc㮻z.2L€$L}K^{0'rU.eFց>O/ߤMc hz_oHpZAd`lyOɩ;/92e}ӟ~&˓, [qGJE\;;}؁3+aYeNW\WW'N w?q̮!Ԃwݻw^Exkے׳C" m߶xce^W_|+_!Wssssccn?}- lz&LɄeG"y{?Ϟ>m?!gyF>6,/x ~ӟF'ol;-8.O^Hߗ]_,R2o|C:yq0Ydb{!/zыcǎ{'h]CrI}|U&sa>l7tU;l}kk['O=WjG?>vm.f+w^}ժ AFA.9ǯ#k[} =}k$̤+/HW^y_bJ2y[o&K_RJ4e_MNNw}l/{ mV{y׻uر3g<_z'*\r^7[o}ӛtWr"v[\CnF(J^SeRcy睞-U*(QUg>Z!?gu4Mn=m1|n??=s^ywqk^{СCc!, d؆7]2Wc/|aZC"a|+_W&_#mۯzի^Wf_咙j*e]{HL{?_~7MO<ď~xŋض=::zp \s_饗Kܴ}{xE%!DtgWiu]w+cR$_{{@ &M7tM7%_S.ljjjjjgygΟ?裏?v ø+nkꪫ,`@#DlMz/+=7l.8\`?טDmnj{^f:"J!;1 x]jzlK/뮻Njnk5uQCH"2H <ƊK6^W^yW^/{˒Eb{>b{ʢފ]RW=r Df&J $c%ɷErTTmۃQH. "!~bA&_lscf`7<̪߲H& ۶m3G0VY N #fmNאn4 .2/Xk`e~; Ķv./E hVǎ N  o{@ £@/3+[w>H&p܅HoX___]]]]]x Ο?㏟;wS_;?E(_Nߥ׼Χ4ri":z3_&?{xp*?>@`Y[[۶zeT}k}}}mm+wN_/ qСCW]uˁFGGGGG۷o߾{|!e` @.`8l[ C?v6=`~v'g\p aؤ;?Ía P N #lFw\r)d'ܫO? 8G?wpx\p y~b0*+T"+Sv!aq~ N`ЌC lNngr).gx?16 `0e Eo@v Fȅ= N 3Z v"3_$p YŅGQ@|?{E@`^@! }SO ?T0>2C$`hɼF!fm@_.gcd'\рձ^W 7= adFԋGF G0H8 t{}+&BE`wړs0ѳ[C X{?0l8~EE}[`فhLؕGwBo S0I6=釡B oSG~ި9&=_Ps%@qw |bI|Kހ% )2 2$`Xؑ:ʠoQ$\e)-""$raGS6+0o&SDe (Ss#;+,2bkhJ71q~i/^#u-3rBp6vĻZϭ3JjRJZbr\ᦫN/Y5.oXcWƨ =H:[#QFC?]u`=̴Ɋ[K8932S֑)>t,m~VWWZm/UXTCDD@L^jp݌ƐD]5#dgWjPA/e""* tOӋSDfrTC:QA\6Mҙq[ -GY{ a07j[s>EY"Z`6.vCzT Lct_dit&F<P(`Hq꽗XGs6 EɛxV)˫^r"gĿ̍veKJ1x٢ge̿#dR:!T \ExK\̆ 29JXj\ju}k@}JDD=uT!72FS@^ƕr3ySX7qj*c΄/Hje(FֆJL .D כ+JW%Qh{THԜ"c,Cc\,و\5LA?8=2.7N6ES QZ=vX;Ǖ2LOJ4kPlPMU8-2F1i1pq(vv1NVKbFԸ iFoȤI""j->v 61!;;.sCYT,v`'R-0o \W^tgoEt'3N1;OBWe7p(\=^tȄ:N;u5VB+f5uF׎U[Cclb#w+x9yo"rWB;& faѬ{L!;j/$C;5wu*ɑ˨怩Y2 IDAT.LVmt~d/vk.`ed QKE$hUvUSQ i`1 ?bO3 d ]&b@l29̄"ΑD՘EL^r2*щBeAE;~P"q'ʥKE.ثnJ׫Cach7ɍܣ ,1 Ȅs⨚ffŒ.WgcflP}_A=|Y -tSyD/I\O yqn*.l$.QT䃉E* ouNV}%P|҂KP—=HR?LɊ'7]q.x Q NL =Fpg`h1f7>B̦e^̥ =oM%+E"፶ ]znrFRQ&ք}1\ٵ7٫?d9Yˉ/M HXV<9'C'U6N)]z~ (^o=gT'Q#w){Z5[l9&|kﲅ2q Ѵ_Q ҥL,vP*ŝeW!yBdčWAF}dF&c (`3$\CDwVNSd?zԺ>z^Fo{.d:4`bR,c*ז.`bjR/^;Dmk#>=$!yBd] #c-{H}v0Ұ?IDZ66/dR4QtBbiXiE9Ԇ| {cW\'F"!U+2=aKEƳ%< .}z1tW>I5#3#Y$C1؛μ׫ ;ΫGZB~Pâlȁ\}Rq?vif9tlC|Eh#&RR &V*Ŭg@ BLnI.jkZ`8;9d`Da)ekҢS [L2rt.J ȔQtȤ#R/l! s)/8E W+ vHÍ]$I//=n{096K/e8|/~q[rY{!toqm]y^{_vMk]}曯<FGG߿gPP0l3 ]"!"" ;#=lEBꖁBCOpo>o IIQ{̨Ȳ?tAF6@0C7~\2*3#` 7z 0hx 3/d֝N0ZK'2մ2&՜2l)F9ːRӃRzXCJ2T{*N7Ǩ!,+pxןTonu]U\x=;Wpb5gTj(p28O‰FթIH?EY}Sl+czR̰rT2XLvoeȧj뫫=w?{?{lum=@S(PϰboQiS3 ۅ1O'vDTm۶mb@C{h,{j/Q?1UBEgzSt{(OU&@3:5(֩h7JܛAP9Y=V%{m۶=W,/fGYeN-D ~=윬J(&k@v5ts1C-<䜱mw@N,Wʯṕ:UۉyxDt|Xg-\zTg| 7ˋ*n>ƒbٶm6mNehۦmܟmufE49n?,̥y9A1X^ZរT/S y'G4/kTPVs*RmzZ|#Qqx|i Ƨ|# hv&hxr~kUZ^LKVy$)#Q%WuE]T=!3UZZ%I!!7!(u\J$z=,V$UŖPXj(ɼQȔĉGe/$D@6oӖ5L{`o{q{`ET04'JDe+[X(JU9h;QުIO|dsiDf: xY^?fo:WiDDdgYԋ!ZZm*-G&n(dD s*Q~`5WZTj][V56[[3[[ʉr뿞綞xn_n]\>>Sm*oO56Afy1s𬑔-fmz[-Όk@j։J`a sUҔyVZ0M[/t_/[%xrkIL_'0 %s"hXg[#G' &l?UPM2J:,뤣;:uAl]G[Nu,/Ŗ!P#A72T"EP;.rq~_\7gJ϶ml|&YT\"Ei:XA=yUBp2fw 2*|n-VƒZ.Qʾ3㷦N9$Hy4bZ kU>\8<] b-lZl=E}̥Ҹhy9:uG\xіWRIK?.(V"/gkwy!^v0tw 2mm#+՜Hf 4,ç62VYYA\Z3cQ.e:MdO%ך~R >I*[^&d+ |9R^DNb{a)~*D)tmll?1h0Ko{yHgb籭V +-[>i!;L?[Q2%G‘*ӻf-쥀]+[^$$uҺJSͱB-{hBc@% B%ph1Yw=!(8C(Vs*hyZa$e~덽+)DHVz4 Fo8{]Z*&6z.cN n&M_10Q67= R-9?S_(oy*5L'ې+LUb@bw-QwmYo OW^% R&+8nCA ^ ]vv=?5?7,\J3; z9=Mv76Tb=B9bT"n 7:Z†dP4Nl/]TS(`GDmM eS0OprOc͉gϷ2/tHb{QYrRu%!Y*uL'󐛐h`D]|e!1`Dџh-~СCW]uˁFGGGGG۷o߾{י@x_݁`y,h6XSda !=y_. S IQ! n:=/Vшz4$BzKd1@#C.XfPOs^@ #Ō2Bz՜w먋]@Fb(\.`WO]SVb !=wV "73 2Bb#V (? a #i0!5MÌ ~]WϋGҩQ[]E· :50VHjN9 qsgH, dyTө"; zO5sXH/aqJЩUZ3Va3¾$"FlAA!X@ZRەX)X~R/zVY: u"m۶m{l%Y"CC$*x!~Q=V%絶m\wNVZDJXazob-A# 6b/!Ӳ Fr0Xg:O#]@ZRQ0-`-x e;r/RbA$P]JOjY)`~bj5x;ȵBc IuUi:{"5 I:XtC;胬ƻXln"!bE:y:^Z.^˒ 2 B@,W?İn n 'lP'*M6?kFĿ̮@*D8Ve,TPk|[`dXDX:M6٫jN*=(V'iS ^|YRZն*c Q>'i*rHjry&ڈb\:HVofTjpd3ˋQy'Tuf%򔗫r͔ O;\9jI&H+Da.r t*VJkM j-J+$C.&i#{ 9dc+$+)"``xQxYHȡqj։J`Pga.qnR-̹\8IZdVm|FV$w_"9g} SDDԪ+ڢ+DDl\Uxr*sz㣐Ԡ9XyX&'-y¾ ,/f6 djNډV\X i؉}d sFd85>VrVȅ!Z 3IAD>1iWY^lW%$~D b瓴DԪj'A!,r&tZN%2Y҇>6qOQ*-*5NR) #Q!^N^?@,ԃXͩ\rYU;;۳v5VMo CߠPm4J1 BYJ)]DaHl[=)8cnNp=ToxHyٚ"D)$$r?Sݽ:*bZ."2'.Cxc)V-)mMސL+ĜFB?kwQ3@]fZJltBN΁8ZֵL$vr%p$O2ř{IU;Tɭ|6wZD3m I[\:IсRKWrwU)5W&U8 NyєO5Jieɧr+TJ1fנ:!Dpve'n,/FÇ@l/j͖f"IQ4z";e""qL#wSjL伺\E xAN$9oHi,Yz%qWnUM߄ARc͒H$A,9u^N,$;ex3w ccccssssssccc}}}uuuuuŋ.\8?~ܹO}5'0珼}+w}w Dt7QO܁mU{`Ox68sm[[[[[[[O=tMgv|;|[/?Eh[^}ǫo>tUW]u_r}۷o޽###B;`v!;iIe"-~ymInb' };,PF04{6 8a&vз P?uFo1lt2ˋvB@o6 9X :_ࣱ?0LmA}z w[ ę ]uBF)^Aϝ`IeH;@0v{}ֲ,1""z'NGD}|,2 }}>wne!.0-IXFqб?sNH?ʦ1?tV`;뉈7?2Xw2sċOa ?y4o^F㱝bhb=玆 _޿x cv8m{;e(# 9V ψoDDDo^BW$G@G~č`GcBΏF" E&o>~lÜ? W"z IDATo'gNP+2{7z_7+s#ض[Jw9൞pO>~&ε3"no^FñÐFﶬwG>+v.a"s$7[DR rp^leIo98MdZ [9â "6Sx?:`H P 2cձ?8SHo^@QCFP; &fszp( Wؽ^C`(Xy6l !К_H8zh_>N>MDN&A^x@QU+nDu OχG07lsz J;gs@_H{` 6 )v 0ԄOR@6QqQ7 FrzaL5 038A }gv혂p N*jNE9;;[@=;a~ \sҪ)E#<4OjNEO2`5gƊm\!aGTrX)#`5R7AqfpJ2|CdP'SEj۶mJԪyxԚ?cEn73UjUNzYg[T)ygox.iN+I/%X* R_ho(Ui3&t3wt>RScr;Wy@@7q({ As<63UBǽR`PDDAdUj(tUIb'b5Rce1hE{1 `2Whcvr܌L{rda0.z/lQv,֚*jcPcB$9cd1،| pį[A-i?.%/# v1"ETÝbv4[/xA@/'&MtGDD's9DcF[˭fJJ41^ sޚR/.I7|kȉXg[ܕtK6bPj/a(ȓ\\#Y9%mk O'Jܒu!у4Z5 ʣ!q))\D穊 ÝJvfy-ۓ#A{ Gb6Viy&$d·) lݮ*cɏQ)Z\ʕ':8 rz`OR}#a|K7bfV yrPD̉.M$+P*EطBrcj RmˆH|zdbkf;ŕFJ2]xp֘m k9*(9:F(*c¯TIĶs,%Q[8Rm>y<:I$ƲTd.ʓR i0b:n. $S3&8|ŋZ=450zRֶγ!pǥeTps[_*"(,P.Ex·߹c_^2Sr*U_F{M |!(CaL8[.5V/:†QJCjѡ 4JN+ sm xX+ *ov"_m~ _O,/v{2xoqfHį$*GY:][-##jpbe9S%[%˓BZ n⋎HNtqlQWIʛS+^X")y'k/K[w!J$+AFJ~1FÝrv+wU s+^ (S=.r;I~ŋ/\pܹsoz_F^һ;cӧP(`!G?cgT956?-: HW C<=!։,_rM{m{kk{kkk{{{kk^Lշo<|>`=.xdn;^}Cꪃ߿r۷o߾}{@`$;j3a2hSppMD4ЙKp3̢c5O )tcHfylnjAudPbA;f0gv fy.[,l{Bb (y!à, ^`{Q\!x oyȝs ^:55E +Vs* RiTzujJ::0")M&tNVZ#ӥg-(Ջ~53ˋ; '%[;5(.5Vlt4 $ V!/ހ Gk~.k@(04d9*}11Z˞8bIol+t sv셺fys!TNKha~.kxUl3ܩSMjNR?+]\*c2H$}gxq#NDcxB&TX%*M̉R;s|A@ 2ʖ"uv),H<x4J8Q3 rwjUH嵅}JT$i*ǭE?Vl,+y1+ۀtq,Kdɞ,|1c^_@N;ՠs*cc3[a֋Ǜݮ,fynWJc@#d0is8OSz1.(ٸ钿zYS ~1DB&W Ƿ&LWg#ߙwasǶWt2Щzzg5G:'+-.d^mspHϒBDT/SOUB"ݩU&ۂZ UGĈo/0ڄ_ْznX/&Q'F؀՜‰FțÛ51!go~UkW `"3GHanl)WBd.٥RZrg(Jv_5~DpInʋM7/CgN^9][QPc%Y-K\fL\ &0HD,- &+Lq+^glp_>>d)tNVZնm2׮Rtk fnS62%\'.t/di(D?ŶVN3<$l#ήܪ)T_D'xԲ 7*Pm"aJ>m "rVroEƥ2vW,NYN[W3VE†YAFv1@_&o,͉R4ӥVd̼ N򗧜 %wxzid.eo'J-)5[{u$7MVHEH9/<GihT9U[ty49'^d'5Ĉo[ "Ȧs׻v={?q" 6 :vjz`}eY&FDJlD‘*--,,9T[g^9Deޜ(ɭ1tj0GōwjBxw@KGųSхvU#?˧Vx<*">iKj2 W!>h>} B^ v:":zh^ wTn$InLrdmm{l~駃m:Sx9}>z~z]iٯ[oW|С۷o޽{GFFTGE _9jjUse2g`P'jbLD>̷{ȀM@Q ;QU,/ c1ˋ,uQWp ۰]/X`idxbv*I: yP<n" 1|H28`asQ>_B$ A  }@s/x,A0Lxb BH1?AGCLd@;^41ay뼟aM3K ^q ?l =0 ssBBs 3Qz=b mk@_(S2 O&^W l=z~K9=ޭW[![$'`yި@ zqhe@0SWAjNNE {t 楚o/ W?ǀ>$EH{5fa(T GQQf0w?JzU3|CyxTmZo` jNB]4sS*^\2+!Y;SEjیF^ Rk0\nDUf{гFfSS` ɿ`GS.^uk.Ak@g~CY^'fyJe˽Ry0 fy.$RZ"䆺=k:D S]s|4 g:C4.Eq1=ڍ/g+s#,i.a 8[DU].n5bU ]~^ h eUD$qEBrTB,@T5"M&􂩦줂EK<Ԓr譡tbȨ@aV +n"^/NB&#V9KNA&A;7mBBc5-yR|d AtZny*5N̉Rp@z8ujԮ|:5Xq."\%s95;:CQe,8ԋc3N-+95Vtz ;#У5,!a',Dbөźզb.TPmk5q0ˋvJTbe%JP6eHTK"b뇱"UlWۢI&ߍ|{A22WĦOhH"2%ߥ[t⣥d%"( fy"<<] u t \\Ȅ~*'q)UVVe0?TܳtXAK9/Mn n$4V])sJ a2#ޢ= -=VxcO*Pm;iݏl75 '}l9[+@anlJ(,/z1'x?9)̵T_9QmoR;le%*M6YVGnwHyk2? &` 10܏Q{`{fyK!QrKeGM܇C$Ŵ51毰 Re$!uv@鰪-l[ ȆU L3*^PcEmkF M(I"nhzkT,7Nwx$t~̷&.93-o*D`RLʤR͔yB۫PYM4S*UzAku`ka] :0w>1 ; cUjW[!:B\2Mݬ.PeY-eE>@* OЖ>5S_)h({ZnUI/t́uf%6#9Qź/LR(>AzVh=SlN*X6^eP'Rx$R}C99U@, :^D.v[|.sdk'AAR-#E$Q\sҡHn+ To =(koyy `{7&l";~mշzǫo>tUW]u_r}۷o޽###<% 0 ^24.!p՜ed5g[/@3? '\koUg@o )2nfylnj:b?f2h&EeP"0eFV`'3,mrœ=wœm[~36/]@}wިA tQ 0+F?辟_ TQ@-Yf wzid̽\r &2ML$H2@Q\`@~𧚿}SFAjNFi z4 BzCfSMOQzQgr5%ݝdi[j 8 6fyѶBy AZ-Q}6 z4 c1Åzs" AfL5-[vheyh5bU crr> |;n/9z G\7Fԧ`%~PO"A yJ'$3F5=ynNgĩFU9)H%Pm~VܒrDWlQ+ dȼDdW7N@K%tw)@'%=@j#alOF¯ENf#Wn-pMhh 2'^ -h\)ILAv,cΥ+r r#paS+m۶ҠX*cw+RF‘*XE:UBE]%*5Vl]Ԍ"YNYVxUm& sKiSX$6Vt#uVellyƑ[Bzk^]x<_(\wfy{Fv33Uj-[D9a5g ‰FI0׮RXkր~RbP'œݮz].%>0TʲБ'T^+-ԍkl3nujNEr[ AdH'YJL2-a0i/-RZ-7Yg4aӗ^CRu[8ֶl_NJS c@F}0!. @rhoəDmPxN9oxx]HA1ej2cmXKpO\qݨ "[_Y˭ꑂ9>Ixzz}@ґ:^kaN\ X-:.3qrK=ztk  .:MKf 4Jq].)il{%)T<hjsl)s蚤e.Ko WTԪ·> gd-36UAl0P/Mϥ,ޫ^oA*Dr|9 cuf帒۶yD2Ւ_׋ZM/ݔ %*dWL5r"bmooom{BƷ%;59!r"J!0;N=EdUj(8"0$L*‘7H~ - 7'B2зJ˝rLp)s[e]" S3dN`~V[}7ݲky~x,!= MXT#O<Ar'SA@f(@Bqиi8IPn؀;at@vR_ޛ%koEDL$ۗi,;5BYiݱҕtFw1 KN: !@T8eXy;I;L%Y8!:;C?wss?:? |?O%o/`=PsfCC'-k91 _$qYf6{cltѬhYho65=49jgn+N&ITn%FdEa)cI^uL2N-"=ȡN,i_ymHl0޲esd"PF}9R6آӑ/%IZ'Fb߮(z[-0CR5uFdTr޽{[[qÇ;=z__=W_?˓OȐr}}}ssssss}}˗|>?}>{۷o~wS'?J7?J^zE4]%x~IAt% ņr`aX ۭ#ĸ . Z٠f‒4>Im!|?<~ko~O#_ӧOx޽$fGQRWS|/M!įtX *Չ.y`Y5xr-^~3o}PC21JPv=jv~gB F @ 5-Xà z%$i!BH@MR{n^f=AIXrخ//%kGz X gxxI3\ lmdCDkʼL6M>CAc.22NSc끍1⬐)L2{ot Lz TX44;JAp~(j1n7ddmLLBI@[d`Zj1ԴB xwZh 'jj$Z-jboKnIo f&BspQ_"j ۬X *{pK ۊnt[IL"%ESC/)Px޶`>PT?{*$.S~gZq0?mX:gd"8/纬"-6E8BMYXof"5:LFniv2;)čpFﳼaM @x6T$XSŽ5S]O)WpyGB]reIj;cua q{~1!pq{N=4nOѧ*RI\Gxv"?1-Ln7}ߟ[B!޺< Kp-j,\휘IˮcN*b'C |f@m8ݖ$IȖZO`d-HI?b]\Bۍ;PkQSJ[d;c2JPh,iNZ8M[ '}V0 $)j1x}T JZ_I]U+39EvG15R֘51e0P`=Pj ِ7[}WmŒ4ްo$UEE qOSqm01E r$,D휘Ĺr%ɢ5(a[ NW; >!+/_91Jȴiq1oE2v'6uK'0ڡ⦔G;5Ϥ /˥jI+IrGohbFϊF$e[:E#jvG ^)"a@WcW5~r19C65;WiJ&K':TEiu|F^!ƞZFjɢr!+`$JN4b˪2w% i%#iW]L-`w?yܺ#KqRvƜMLH[rt)ym5U^E{F~Q0+vP`msqQr R`O𝗉t[bD-4kNӒ-F^u]5E⸮|uψ`wjBJR4PI[agjI}$Vn޼etQwqDˁFjA6caO]$^u,%=U '- wOakjL"`[F}zt{UdZ$dY#r _hJ@ N#7dR^"R:cu%H:,VIS(fQ!5=q^~$y[4^jDm|`(EЉnЀykvjcL78%8L@%^ T(z:`B;9U;MP,wK 0' FCgj*|ưg.x=݊^ g6 2ꫝ3%ЉmWؒi#7He7sA,MYX/)R;cj u*[Zn'x/]zbCH0%)j֑Q1x}/R;$Ԧr q݀6oN%KuaʑZ TY(777777_|ӧO?~ûw޾}_o9Ͽ1~ϟ7IӼziZSKyESxv(hn^~qgXS @҆!'i<|omm=x޽d^Q=֎oo#?x~O>y>^ģGvvvvvv߿@y/y~<X ްoJAjw6CK[JR[J7``'jgZЧ&XRمKM hZnSv~gBA ^ME! B-j!XpzM1l2W=2n?no?>eA.QXA \tb}c}$)ɯoV]7lߚYmE/p{ 9 ۻ۪XZKeW;'&qnt8wy_+,5RQBѿֲpPE!s3ij[c V?|4]ǰ*vKN,ۤ wM_d`:kUCuyö[8VX1I0A@Ǐz6)Vq*utFd"h?3M@/]W$ÿą /S k)|dddKPDzV\H҃cI B{dK):DzbbسX)4G^ݑEY`yBaݟIt[YT)eNXl#T4-6KkB,i:y&P"ɏfը&m>cWTweI e ">>)*ٹeJcS[f<*?%A/.R/QC@&e#ڡI4SisoS,]DݒgX`t4Qu/JF*׀*\(†3핎N>CJRA.kTo$w仰+ rC~4N5iI+[߻IJUCd sۋX9v Tμ(SZ12àJVc9 dU^&ĕQ;M^DFTJF jcYzqR AXԞIvvƜ( Ok-c:^hᵙ]jEޒz:VZ8a%FprbYFXNrF&RٵR'q+~G&UlÞE"\K;lsL ǟG L IDATJTUY]b!0_CYޜu TK#0 gҒ+> Wo*˗/|>Ǐ>|x۷o͛7_'?|y}]իWMӚ*u]Bȋ/*/ ^$,on͒WǚĞ4XjmmM$Gӓﷶ1N(z ]`QRn^*z67'W+6]XC+W3s{z˯`-z΀p}˔av~guhp>P7l+Fjp)xeU\$z;ް!NP\z*SjNg-2R*X~z;3<2=/+Mz |k ,̴$}JUPjt[}b:5uq}Bܞ&pZ2 .wf3'&!H$WH4;?ISiPzTӸ3,[7l'F'+|㗇L4M"dYga%uYKr/;1t4zh &&Fn|˰:jooط 8HIcp/Kʡ[̻$#{(}M)]!D3EH~ @2LKxNFG)JZВTs'7pg)XFj (鶒$Li$$a=#VMH4d#7ҚdnJfn?5X4w5njߟن=u .ʔF3⎬$.»8wI<Ox -wP㤭J 26,"cd^uͷT|2ZQݦΒz$]T=82ldK;(SZ14Z!\#R,=HM?.J/w;IUa9%&p{T 8SUf#drd`bKM+KE=|0'A2٨&qPto6! NE0/ʔ&& pS3':hXݧc%vaxd25Aֆ)XYdKIt+C%k,)KV2B(777777_|ӧO?~ûw޾}_o޼wtο?W%W^B4Mk@u !/^H_E\%f,Y㾛qSOx'okϟ߿{=zEQ|WvP[6_~ŷTg߿g~>}ɓǏ?|{=޾@CPF^}flZpP;ɥnxhd^eP` ~ G`B ||_z`X `=.XD nO) Y-)ɝ:ZbݞSDު, @`=P 똇Zj/=ԩGzԸH11e7ldeW7쓉01-= X.@ܑEr[aЗԃ#92ٵ 2%Z寬'k.X¯ ۭ.gAF^mnk;865rS X`5`=Pr99;J,3H{lŹC̓ qoPZ2V-:xWSb쩩KþeZ.O*,a)up{J{e)Ew`,UZx')XӢDc:7l+EmWWdJNȨL)0>I,9QR5,7dLO,<24Y k,pfuwSjXGJ >\arI5r%q{^Ce:U uN]M U ZE= ~Bw餇2oK3斝g+U$g3۰QĴUAJnݏ ް3t[T6]CL-S)M6l]8̒؟vdRQdYR9Vm^}YrzvҞP]ò}CLs$sIsRTx+L$^gѦu_g G>)a0cBtJnq ^ j7pj,*C|j8@Y$2-P{uW@M㧭I z24}=:a~uwt $ P;'fHAhv*ɠ#yR iF(Ws'LiSsX+(Y43\-k- r 󮦆i1Ǽ &i0_ \(^j[rF7L{5rR;c uvuCi'LnUI)oPYk5OoطRi%ܳ-ÞڪR21~,KjA /,>"ջt3eEQVסyWerYz+vhQtRLz%?>sgqtfczO@>UM!`f-"0D{5R;`sD뼒A2 B@j$WJA'uj*|j8aͦ.l,lÞS3dYZORHov`鋊 4*鶘O>+lqI&P(E9Kޥcj>Fnj95oWPZ|8v&^\!jg^ܑe+ϻL|G=^;ْg.%RQ w% l龌EMT^^ɒ5[OB&#CeOe'lM>P6VT ŀ@u=C~{lXփ#u{DS3ť6WS$zpdXՔ%$yzwO].vhh~2a7uOZN۸o6)cdv+YnOa|$4LWмK( SvHYQ>Yk ¡2<3ك >PaR-%H#XhzORI Mm&qbB|JB!`B81~k.6˜(v=Y\p [tjϪpeL7.d[{ r.RKdZ?kyW\%'U6Ŏߛ(OFXNC%!ttvLZ&)R؂$`!(777777_|ӧO?~ûw޾}_o޼wtο?W%W^B4Mk@u !/^_+hau~4ïʄ!!N4omm=x޽d^QEa o_|J%x{?~_ӧO{nAWQ#Pd{\~!mqEB.ϘgLO z|fhxJ*v@xWSbBC3H58S,"jbڜ>sכޞԎ0 &9AP3g YjJ5[$ R+AyI|KLLBl BHزRmRmC=2y`Ց[*;]M 4"qkiDI&'xnduÕAղ⩝31n+p `C)'ŭ!u<]j7KluPΉL뽋)UA(#wH4$yJ Hl.g|2wSe,)~X+][»8wpMqqK$U2s@W+2`b"@.$(B39ny$ _mo @l#y]Th~&nNR*kG6Ϯ ޥcj>F.V-WtG ΢Ņwt[Ir @2j y}_+;/zkߟن=uT(:JMj?Nʻtܒ)CFMC&Ʒ {nYƞ9x9PdI/XpGlsB0؞,B-[W[9]洌n#X4N'xKvhKwݞ\ AG2wQ ;NNᄘWT멬܉GA**!SXن}o0*Nt9 -70!y7*ʞvhh4 ڡ霿[4[ %E=^=9˶ZZ잛݄ʃێzo+ȅCTTh 1O"y;n4_jOݶ47\HL*Bx} :K-69)-ڂ^yq69(5E8;}#SQc?.WR);Iؼ9I#K:>nAETetR)_Źx|dks'f˗"~I[Y'Efh0ʮzF &?d5mK196_F\EJ+`=P [%@Hg|~cŦOl2IsD|G&mm:uψ~ƭ8vlz8v]15uwX#?M7];_A+j1:L]lAF^`-=Yd+Rۖ*MDe6Eի]:CXwda3.ֻL#ڱmX##+)T |&z񔂯 UmޠxNZ'VgddVhT 3pU3 wd{j.e_wՃ#/Ν;M=kflm8ݗI,hn [5yюӹĮeU(Y91 a CslVvt{6<+evRl}Cd7/uHH 1c%˗/|>Ǐ>|x۷o͛;:"?7O~UI/իWMӚ*u]Bȋ/kNJone?Zs07X Cmօ V`'i2D ;7l߂vh]<ԲW{وzpdXTI JH>) 6Lta2WaHz`=Ei/od$ YܑEr[aЗԃ#ue/&ij% AkbbNre^a.m֯n=(;߂)ba-ܞntʕ,& j)aHzԁ&k)mEqԩg*!=G'Nr2$aVǐ-[[)4y8SwVJ*SSӗYZ<yS׋N3 **/Cx;clnpt]M[}Ҫ3 2eE ,̞kBxF:ǟl,XJtF7; k8!R_&eL}e JbL`4F2kR3T~O8'`Mz2ް f6鶊3bv%aݟ碖NNպ< j0nKQ?-z3$aVG}TLH?\nPJ;4S*-]O(и t/m0J/jJ5NÊtǜB|jJ : ~vlF 6TY$ )5zBh@jj3&iôA c1uwyY`l$͛Ot[#4b_V.DȸJaQ b@FQ8;tobvr6;PS߀l4#Ca!Sr] X!9m ȇ8Tܷ,/w鰣+JE t" pGVTkcsLstҡ7[]y+ w5-gzIjw?wq ]\9kB5X@ےZƼp!m01~YLaɎ#*EEn.gTj螞M)?Ԭe|dThRu5+X-KʤN[›$I]HaͩШ[n O11nKS'o3%kFw阇OKՕ?#+ [w.{`)(HRRs-bX\?]aF+qʖi 끪#{FRz]rfՕ$aTG͏*(Ntxq0_,%Ź|+.] yIAũ[rVjI(*op,cO ^ye9"]$`B0Jo7wVnp4:ͰgT;|XvMDzЫ1^ietg#H/&9UD4oZ<(Q5ױ85hq%L)D~(rO4\ MY?trS-΀ (;H(I'`}Ponnnnn|2O>~Çw޽}W߼s+cϟ?_^zE4]%xoa$4suO77lV_ k.'i<|omm=x޽d^QEa o_|J%x{?~_ӧO6:g߈`㩼$w`=pp{[l ۲#mm۪צ#hMV+;S[`A`=Bz|yveo}yZ4U/'Օ-4Rld ererli?dbbNy{vK9O;wq]q!Vf{mG:v+Jkƨ`:Fn 6|_Ż8w9~jW*Q[um , u[lALp>P^:DU7l+Em%3ËbydN3 TE2sL:$"8j ՒϞXvu\is1K1VUc_Ww F aް!Yύ7PjL$w1k/PSgI5u@ +7FqMР2n,)]I v+I: |iF~R&&2Y$T?撪wyeg/2mz Nߛlҩ43t[=IHФ+zpdU qG!y-S;4Tr|:ްG11ҙSt2I!j$s !e1'Y 3{b!nOѭ0XgBtbLdaf2nbjڱmKg?-%9cB2Nv6 qtjTeySigKUy*'V꧄ J+Fϒ]GH`Iq|U΍bt%Y7Lt[˓XRp媹h4je±YTSLLb}˰Tڱ{h5kwd86'&h%W4<*GK+1._3fOX\0{7[BW55j eo2yϝæ Ǔ)1Ԥ=`z`A$'n.kRŪag.Ν##s'2B0yf/$?r҉>E ţjX]j@]$Fنd(Z,jb8!{ժX( j7zNV kE6wlhQB$_-)z+z24k`L\L,|j)PSf?ROVbVRr=!)u.{ޥcj>Fn-3 Y-;-ˆ 7Q&UiCT+RYg!zv+fAjisb8:~h鬏$ e\ 7&m. ,PSuψ t+>CwX,y9o#|#S?Ww<Ntxq$A4yuһD%co& l龌L-}d,L %[GoX![Lew[ SOdJ?/^͊P:($_tԶ <cjmkq{YJV֍;Rs'*vU3^ RbFRuψ#/ݞ𴴠X&ڡI4C9ٷ1JTzpd-Po%AIۋ>pu XS;gA^v!%U,L %]R^5#Qn[T.[EHPe:o(ZJR~*@;ȦdPMAp$FΩ0F$W7Tg VZYSsHBrYJ&Ƅ;Iܨ3!G6Z57V|Ιm]J'mзðêqx(:lX>ڡXrj&ȩ MS{NP;MHlfU"8uYa0vda"c(Yxqky3w*gAƴ: RV2SA6%ӟjbu#DYmK"U)7J#ne>_, z6X*@F`,T+D1'x[,OGJ} 뛛/_|ӧ?~ݻwo߾կ7o~97?W%W^B4Mk@u !/^hM Da0MA_]fmc~5 ۭ#I5}[[[?˟R ?{ӯ'O<~Çߋx}!Z8 3hoط_[L~J}1V-X[PP ng?( ~cU6hYoKPTǻcO8wV' Sް ="$#(5j)㞇䦋ᩓ)/MbYRa\2a[-Bng8uMi5Ӥ;W7UC mRH;L$ښB(& 㱱/UO qFT%u/l@${{5gIAdluB,]IBI A' 6j^Vil>Sz;?(?eL .3^!7U \zRcpnp8pE ۭ$ <^=*xiB /lmQe:VxofdDٓjZK]vhY.%&&!Fx.S팣TI̗St2IgXkVC5hB1Mʶ֌,sDcBE8̦htBՃ#L"FI&*s -a^*aaZ;%e/)On % VVӎm A!İϒ'DL,ujwdXUmOLV*1ٰP*N+iښ.H~!jj>a,i݁ōUbJy+{8/R;'fOkB*Z7KP"IxW/oY8=VFBaH[& te]u4 _-xr3X"0⅂̛`юmp1Q/p.T#96[=\ݤO1íZ;@:VY(r.XQ*$DL2/jM.zWSZlX!VR fȶuD~Źx|d+1%Tc e[Jdw+yyr7VFKou=vҖƽ= H,k:"qQ/tpIϖʮ NU':wdkuψC݊sYàwjVӤEviB6,Rd-%'B]ԻtCM'mb9|jE8dZJ0N7 s21\L5iWB_ؤd"rҡvE$cFܑEj@dad5z֎mYŹƉ>*(S3-xX(gREBZᇵե\E*8Ls'tYƞ9Kr ~TY"RC IH1*eܦzdM9BAŅjr _T~%)1{`=PuzQ0:9N^"ӿM?e.,]gnspdPAnO鹩q핎T,TvaWSiJB8NC c, &byQFCJ wUgBE8>"4R d:E>g 5PpfRVCJ^ JdIpL귬*"ɺ)v6p$-7WdtxHlt@upUM+zHm|WQӊf_&QРN&-%Bub3 Ȯ &$:9&SjȰ]±O\ IDATc+7(c*YNzni`5Ӥ;k]ǻ*a"UVN fHuTcYzqBYkZiV(,I,{ɖ*%+XRT/k/gс+RIK5id2Uq+\bHltΡ\__\__e>O>}Ç޽{~߿yϾ?*ziZSKy}-Wq@l_ƸZYyroImﷶs1vq ^ͤ8l,~U]w6WtkYI5novn/ۨ[% +|*voVC/w-)ްЬdO6&^N oط {B`s4_/w>% p{G4aCbjZ'f6R=EѧqvjJHŹCw䊥75Η ;njg]5[69V%l鞺nO? wq^x1O4 b7k pzIݕ ̈́ݷ^xNa[-Bn+"SS " (W;'L4<g՛*LL5tZ6:O#$,)cY N tiy:En|uHԴ1%蜘фY8:P EO*ǻt {b%wdBް!N%ȷT&'pfcvfL)5`^ d(mw9V/uu{N&)|vČ)KI΂}OJqGlHtm8ݖ$RPe *XɆh<]a 7Ꮅcې0'aj %}' IJ S;/ S`*ܑEJq\If@j`)6ZDɢY6`ef%,$% 똓xhD͹Xx6j^4m6JҝM0qƫ)1ԥs'wQ|D T ԃWxWS4HՊ3g؁JX)J02[mޥI+|/OȾ%$,Ȋ=Tن3VQRYƞ9 T/p>PG}K'|P=/'<Ւ1 MKTvfyiPVR cw{r/kj!4Yƭ{Uyu1[Al/=l(O8Q}Icm^+e"_-+Ѣ-[yI6!ѺKۙqV$ qCLGiPڡ霿[Gj%7XXTELRoS`Edf 8ތ8yVp֮@~20[[E+]6\(YA'vaRh&̜D͑zV.fkV2=S6>*ޘr#DB}Do 7؟'a:Z% ek;9 #5Kc~(Cӱ,5vh:WmԕkQonnnnn|2O>~Çw޽}W߼__>|U x!DӴ t]ŋ BcD\cN4>I擷ϟ?~kk%{( 3W0_~~!lw~/.]nniŮʫZ4.2Cy(mC&BM, (2'=LpzРz)g;c 0.KX/^tWw}Ȉ8'U~?/Ο߉e"G}߿wޝ;w>3ݻ[[[[[[nؘi=Xum}cr$xL3d+5FDŽ,z{-P'`}@1~q_-@1>W- ?Y X_0`}|jxVj58KXi+jMZ%^D =gXr{x)Q%o |qDZ ZH;0ߙ[f7hsL[fG ['*DxT*C2̃ށ霜zl$kҧN1 в1Mxss㘦n8UeE cE7Z>*@h:;!ۣIt:nmB&21oY[}o %8? r: M]^Z%1 [YB;0q;NbSv s)-EyOQ@D9yv1Fy0CAi6"LJ!7/l9O"KTJK?bRخK*>AbOlzR ,W|4'F⢾[gd<:(aǤ>S,t2-,&aFm$ Y$a3Ӳ̩,֌{qq䙍8Bfi&cobGrd᪮%.$ qeۣH}{q;EۇVpRR $Izԧ-8sL` XZzJ.BP:سJ=!DE5zʕ?C>BLdٵVZI}XdQ]U/O~gf}p|pс {tkR4q U"m^s'{$gۻNO'flVj#&c7O|IfƬVi sR5{Phb$6s(N(1L>IYϚP%=Ka XI\DREmLK Ic%T*.<ڍx*O"âڥ]͢k4%f6*ۚ |ށJdz٣I!}_p@U%:-c(QL]SXkзwpoUqzUA1'CrZbM,$]1זPY=% jS3p)cc ]Z--&tIT׃feVGsXf0( 7HFeƫ^*l4N'z'NFB.7N1za:Jcyvy!\Pr;f*tTwMAcī2sҔGvS, T]cߢ,tƾ<>q's8M8Z ϗNlaˊZ˪4 Wv CnK$ 褖Sg %I*gmdW)O-Ҟ,3AɂE 7z9L-rJn\{ܙsԮppqzu9oPI}X0!,B.•B_ͅeq[s;pM T|>x#D[8/8hQIvK&,]vndM 5iޘOm񮾱o9M@I ^f0DiEPo5I0--֤?]c-o-(\ӾI~&U{ib$yR;}&TZRZ$=EIgAFW`=f QVO8.w}ށiw:jVyqYQ]Jٕj)\1Uw`EѮ޿yyyyy7o޼~˗/^gӟ5-kb~>\B={FDaԕD#Z^ Z{;a, d@qsjRZ3Ȋ^&9LQ~&7z3 ޖw޽zjcc|k ϝD~? <{ܹ)wܼuⅪAFsy-dHJC-oyH-Krk$rY l}l,Zx7C'z'u8#S5h|5Z'ƟZ㑱hQn6`^eiav5y6L'nXc:ޠPT\H l#qW}0֡ GoЦLNO}5:}d>{ӈ=ƤOFr ? ы|EdYzq,Y=,t"O oFc3#qopdt3tM{'YD%@=R^Zt`)O"h7αhIgߏH1sG鑸{qC7h5m?|K~ȩ2Mt D`Cjs"ۂq̟[tf%-4$Ae[3J)qפ+6Doagɮ"4s1#7hiCd7ą}{T&/Ô͍JT3P}hrDGYq[Jʓa2R?)5rO ,Yeu#a$&F}(Nw`O.x'NGR#qrBN@`s[>}0mFgwmMNqv&ot t: M죝[-a{$JHDd7H 4gqW0\RQ7h 4XPWhYJIor٤L%<ȟ0d ]sɍLZun\@P\dX QiDzL:lhƜy9+N{$'d}?5mN N:('ώMY6uvS ZM+$F2{q0rI@hF@txOTXC+:⢍}yr>rW0bFm$GѬQޥHBʚ8xgyfÚJnU.RtMJзw@!EI4N`E)O:8+KByvX0<̴,s~1rVRG-+_+ E/~!#tr]$GJe%gap8W,?d=dݘy8;n0kE MˠwdNU%7<مb#rͦVPG5 =%B7\Z"{Q "IQgULE9,V7}{7wz?90ÉVfkf^oFkBEEAo?O{Q7ntx Seu@o?N1c!xWU|?Hg;&j/toLn[ĎfG2 .~uW-ӈ܋9"-npV~l#y玵oۻdz8*Y Bܡ[a\AC*b2Qszʛ3m<%whB9uːJa*LMDlt"[6ddX@`kRB*TRdGc xA܋G^8tʹ; ;Doyvy*Ӂ98+eGfiuذ! O-ȼ#-CKp;n10* *`^Tn%ppqh@jX%T IO ކc{Xzw&_G5 ]( Wf,Պd^ ZDRgɰgPطl8طGQKp)4`.%q?uZ@b9 4Ǖ]MNg)>U`qcE 2N3bL #E{ B3uVd&{F}3vҒٟ)͎LWI$z}#EE Y:a56ju Q[\:q:_Oקzu>¢ עngED:\oƾv6cr`98+ Svuuu}}}}}}uu˷o߾y/_|w'/jiiFWo}0.={FDaԕDѣ2`] ߡvO6oj3Pºف^5+jXۻw^zq>5M}?sg &ϟ?w~?7/ ~ IDAT>?/0qB>^1(Z᝞8 K,?k:Kw0@=ZXu #E|PBx(73?M`BMxޠUn7# a[jUGtQƖzzch+2S ̔2?C;0'j\>I^.+ޠRufmjS@dhUqbށ霜z(x]x"ӿ$cEEfZ%W䬜ْq'_#-}윜z*f Vң}CgRufmjS<1^.DĄzִNNַw)g p:GsSp'SۖU 6&tY6e p7 N)#."}3^5nO!oyUsl@"d[V.Z2/,٬דu"69J1[ntcRyճ@u5\ı 2;꽌+UQO-.RHf:D5U|@?FdoZ' LK^}yuy+zJYm~_M.z{-"?I;&9^|cz?v)0hL ށIgUwh3Aq6YyV?&?NhgcrZӶIA+'}4E$j*W ՚gALgs+ O<(PE9(\<5igxDz"wk3DhFhٴ9U/M$AR)7q~*$3_56 1wt"Ʉ@* طQW{>.It: M ]N&eƶHȗ*4Iج'CaU)z؏DAnqśYxgF/K3Ӳi678E-bBA+͕(oRTzKXx̃~c?6\+HA| sPjYyGm#B9Q,l"cwģ_ 4)jMm>991ҧ6"dtppqȯ*RI/?Qm9RT__BxO$%|k8T1~{\h&1M8"ط&$ ft֟(ۣh=Woe4WXOQE(.1fwZ$*TE(X浬T<=) 8ڙo{icp)XX85/+`W'vY؞u/rO+s,V,@4Y:/꬝hBˊ!1`Y$ 'Uz5bl;GBT*V#~B]TM?뫫_^^^^^}͛7_~/'s+o~>&Lx}5u]"z ޠ89Ⱦ@ ̅@LC,>@Ϝ)cP+i" ˻w^zq>5M}_4]Ώ~_(J}߿wޝ;w>3ݻ[[[[[[n(?1eVDMYGe1`=x#[c\x0'Ԫk.F9Nq{}r-z{4>ך ^}`gz{LC+,PPr|`y7'n7t!o%A+z{{ VoRK}ajk`t@H%ݥ)} v]cXZs=8־1A#ܡMs-Lh'zS]1CƐVҿNu锂kq6A=x#O|' ,?  ϙUw`2_A+}ߟH)-UadNl[D8MnhIJx'nߠUP| \z{TnIт+ M&/aE!ޠ%M]n|eVQS'lpZk&nEmx'bR'OTX"zۻ$%"@AKkDNNۉ(ϤJĠds`9r͞w-Z.Q#2*isNd$ܢ~F:~ިڦƓ~Nwqd!K"{RA͟pe.Tǖd2ۇt]Q47X=$C"m=l%0Oc(oR(ZE]^F %j@_ṟQDeq>/H*ܚrudBK#0XjpLEQC+%E'<\ PD zcQ,j%zgevJЩP)߮1+eNϹ>GI,s'"x0/Q>WfvѡxXw"ډ!;CCD-ޝE%J%X1< %jEMT fÊ+*kj}OdҲG~sP鳑 #+g jH5Qȫ,UkoWgwɁK͚Uֹd}{ X /#rHB5"~rJRtBЋgrq:LD}njM;Z#|n:JHR"E%?Lteq^/(ۚh 8C;q'"jԛRe+|޹c.C7uY o"$`U(?@wͦ쒕 R3/;ĞLI {9dOI̅➞8@NՓCxAQI*V%P@:WUe{yeq^$Bkſ*Ε4hIߴ.C;6hBKtE$,kljNܡmgwVȷաTF@;0PLuwA|cO3aquAhy+s 2?Mjbط(:v&SrK\R"QK$QDC&Ehjeq^/T5A^ F 44f/՛8h}3ϨZ ($g]#M\/PScߢpƾ<>gi%킎x`p~`gz{|(RqH_ԎvAFoLa !b'ӻgzne)̅*hؐGoȾg*'}J~%mR?J)*QtbER[%87]DXc*5{ rk;SG~IE4cQb3h7„J9OԶ>jhbyށiw:ZBHB5++ycwjPižJ//////߾}͛ׯ_|ŋ {߸駿o~>\={FDaԕDѣ2T/9x ڣ) Z]N ZFPڧ8y}%ӛ(SUf9(dAi"7xSݻw^ظ}k qϝͿe"o~}߿wޝ;w>3ݻ[[[[[[nyTН=+MU:WD]vZNTlM`X Il ^<:v Gs:gF~{%W68z8R Ԁ9f4 ($Uju1zpv/b,Zjb|n.Zh n~ ^U>XqX_07hi]Dn70b9VJX%RDv_ Li(rJE`UZfTu%0{q}#s47{} l?}@ɮAk;'֢/!|\X*mM Up6E 5mk-ӘK}tNNv{zUDϝKKiZl7RG0@bP]EƓIg^J`ۣj`1Ӂ5a iwɴQ7^/ǻ8#sGҷwy줖Sr2F!Zz! 2jAJn*PzgH@M ,,NIRioҚ6i$&@^w.d]5D2d4@nDvũp!2TURU0s3%ozhnSڎ[Q4{UN=%T."( Ke4))r .R}Aԡ '26r!&=HR{ƴEZ'W'B}>iUupFol9FصO}$fGoYcGmط4fu8Hn v1=}ߟM9uޠu3 r) v&cQx36jLͦIL=Q"2[D~ݹkf#8F{BvY?,vLq3,AfW(ųU*Pȼ+ap5,_N'C:Dfk.bS+wlCuGLb'J`MMJ~Rr4It: M\]=e<Rh3,ucNixۻJ1vsqq|آq8>ۇVۣhآoR4- LO]zwhQUBTnW*BR,@AݳX?t2wq lxo ,AIHb=BRŕdٞ+pVb[iQ~%,"_-(6Ȼ83-˜ubGL̃  $>dרfX"CqS}JPd> uޠM>&}4jc~rTP9?q\UDzriK_qJPOÔ qaDF~ Ѳwz(d2zY7bYhtJѨՋ"@X4UZg,T"*J3+ wXKUP=}D11uӎ֕W5\ #`ybLBz /FPi{98Oj`ܡMoCܡPY+.NT ے]Lյ <3c7L*YV5Ry5μ/JOU4Lg<.:p}"+l`>P8(<8YJ>R$KMGƴ5i<\OfutFosi΄@a&QG;F*i)bTb'cjF{ՔI" <KbT(JEjW&{ZcJ2PȪAR˱va"*{RcĜuY>2Mˆ0o!,IRYpTDMT*-\gμ>X@C//////߾}͛ׯ_|ŋ7lџ?S˟aγgψ02t]=z^ ^ Aqr0+FZ(ˆrϝiJP߻,djc(>H-޽{۷? ^4}Mp\7^^^>Ͽ쇇{5M#~~ZmF8+M h$Nں Z;M 5zޠ-Ku:` T_}3jPࡳD6](? ~كE0#Hb'~زsnWӚgQދvҧNFTBIduV< qHg]tntJxVCS0,4.-TQ#Pf=,hf8DP|.ز'^{_G6YcÔF7ۇVq1|s:rx'Yi->֍rUFr"@k~{'}ǜjnyf>ʽ ğGh!6my@Aʸɲc/O{ִN#Zs]3)*4H&\V< Wqx̩HTͅp-UgQpuPRrz\&UF\vCLýoCaqME,*s^,){KGwTJ LJVRl"e EKPL{uby)"9SJ)ɦfK1SsE"@ar:Iv&Т{Fgwz$ق.ΈvJr CԪl8myxVqau)X[^f P5ptLͦ͑ط99t6Yr (2Snr+՞Pt7klaI -&#D{E^=.^|d{ds*|b\7M|LO3Z*VT+]gڬlڱJF93Q"3:۩LRN(2EolJۻX܃ނJU[$D*PfBWd8;vѡTըG? UrE$xz)USB%]xjT+4UoW\-_鉳 NOh2SjEIV̛G?֝B~ICdl9FUc +cWbUƂ3+@k%xVИh-&8{2X(yݦ8'$ZJ4M+DNQ A<5Ѥo*k-b,\!2?sj6+.@y玵oۻdӁ"@ݤ^+4jaEP5ηŽ/LGn&^qB}SnWtdZ`yHSiĭx%XpM1˫Tl}'֣T])zۃR#zP?]E4^]C"23"2 ]%GՕ!{0jy7h5NA%Lf&N"x}&bo޽{۷? ^4}M̺.//?~k C57}}߿wޝ;w>3ݻ[[[[[[n(?O /jy`APժ`qx#ph~| 9Q cJn6'﯄'ۣlh8Z,=*6sa5f$(bj#|4|ҟΘz{-:F{VRZi֠`"B `)Њ _!(wV?~h0`UWRd>KGa`UC1Y-b̺yRo]nf쀮`(2T`>o% S~|y?KD Z37:jaR:%e1z>äo;z:wz0.1nWZoŋX9G'H<ՙ]7 >O\{d&DjOVL>VyJY8 Q&)Fmdgޠ5:4o]5($Nx[KER1ı IDa Lܐ(1s—ipMt_k^$Pon4$"9:xqnWP愺_$:W$aB r5t]WId3d3c5dN`$9.B"pW,!ㅴ@8gOQGd}u"ojtvaزs4vG A+'}42O[} /ih9'̟4OANv3wv&c37( )$qNt4Տ-ɍBϯ5"zs3 8Ii?IwZ?I( _PHдOD2b) ǖY嵛j,]T ˄cl)qa#ކ#W*."]RgU=>1?w&3c rG@ޥx';0p̴,9ygk楷-&~lk)B2/C}{W@܊Y]'쎈¥2= * $(:ilK`+-<:䫣"2IC|3B=zfS-u]{cߢ,|sȮ2&L6`ΠhEIX%UkM_Qn ϐy_MdN%$>_wCe j~7c2}Ye"3P""bUtST{<[6Z/T`wz O=p$ =OӾ֤~_k[ms,q*.ѤOQ`ю b{EIbi#f)|Zg(,|p{b4XdM z{hZT9{\'f2m/0K( dLDPo6*Jʩ"")z$۴zKY @z۷o߼y/_x;O?#b?7>>| xQWѣGAqra4 G >FR MĒ޽{ի۷oAFi_].//?|ɟfOG}߿wޝ;w>3ݻ[[[[[[n(? :Q@ 6 H}=k͆ />GOi @=-Zk ?JDP`JP B"X3 Úʁ́e| xk,8u0XN销``)a@~~`}G,+UG m"/-KjDZ#!F%?a6iZYG̔͠'`-(Sn@;7KuX tq|xDan4ku~=|;yNjF~HDz_R|U3w"am^o&D{+ LjR`~CD|"_&逢 5Wd/9J./~kD>T\w=D?&"?EYA>w`BY`u4O'[~}6bSLШF5|"|+ >Ͼ{D?2,Wm!/A2ϖyQvu-<0 c"ق@10Z ~0+`>Ph2ѣ 0'={FDbJpS`2#X7o*`>@5AKu$ZOܮ KR,)՚PݮP%?V,J_ځł3Eu>i3d5q;oDNmw`GA3oКAVRWaߨ5O\f3f[T.Y;͝V77G7nXu*Qd@0%HmnWk}ǖiDZ;0SOwzXE@MLBM{@M_5eO&MÐo9Ŋ. ).Q uT#y v\R-oFe} Y|vDBvV5H' 7[MwҎP/ ށIg &{'|;FNL.Θ  ߷wE8N AH0ٱqljKXCC+TN=$ӷw)tB8C+4ɲUq'a Ɠ>/>Kg4R0gQnЦ班iUuRY`6&f_eo:l%2&q).~f(V<9U:T_dK%D Ŋ2)pe]4+Hk]n g^k*ڹ+VP@d|3Ӳi؀|eOK0q z{4I|Wǝh:U&> <5#d5oi2 *g조"0ot^<3>yyg$:#YM';wR7^2KLyQF \H$R$qNVCU)-d"J=O/rv$$dOtS 坞8OîᩉQw`;?aJtV|3\mxq] pshFp$ll ;d(rSayps.KYh0z{4JG1%(m]*%/.cFiG(:di?SO*6=ks7]n,S|>Mo' B2("L<V #ݬ{98qjjmFlղ5M{;*T<%r&*:!5c UAT/ZU";nVKj}9y|d1 NJ.$J'7{"$фiC{YT>F/xQBxYI4􅋉J}3,Idn\jv;bۣIIfFLn܌$hg2K'fe"e93D4密Pu798^LU"(~gIRAKзwHٛ]MBGɌVy+Z&`6ar%TfX ,՚]g)ܗsjd[msqޙO IDAT//////߾}͛ׯ_|ŋ~x_~/Ç^³gψ02t]=z^ ^Aqr0Q`!,ۢX]( vqPn7 yQU/iJ0O9WE'#WAkc/opd_MW։EaEd-0;=qhw[_`i(=2'E BhDLqxV6(r0aIqEqnjxir^X ɐ/&ܨX+-R]]|]pRTh&"LR8~*ւUԂpC)/)Av1=}ߟMιFg737< $nWk65='}3mu"oЊEǖdGNq?jAoZd7.y "o I:dPKNiaNrȥo$`,Z^[s̫2k cOFm=-0mLͦ" n,8?0q82ۇ9^:wqaq\`pdu8$ ?1kOsr[ x:f [d3cU4#T0 A QT7}{"TVO{R6eX \ șL f—3gd)35C"M=lH  "\Nǂx;ziL=Y*}dk'1`S[ k OfȢI.*ۻMzܡlNa]Rz>([NQhhNCWE$t%<&f]E3B9IeZcFs{ njbyYyYFMbct^wh1-_ந<9TLW;=q7ݪV-V8n8,˼L<>u1KAAVM[e: =8fF3vv goRQ3"9ILXb5ʭr(r<}S]rX;0XFoL(롷G>MC4ΤЌ^RDm/m3 Ѥ~,)5imRE$gn3TPD֤$yٟTYeCD/73D>ڙLJ7 n//////߾}͛ׯ_|ŋ~_;?+_Ç%gϞaue.=z "D 'F'p,}d5|ݻW^mllܾ}5zM|4a`Ώ~'J ?n裏> <{ܹ)wܼuXYwzڏj`X2\k6NxJXv/Z~oB`A / ^}a@i0X$ A{aV # Dx?_WܢũO},%/FcFP+(e#r㷿mF_H+yN?I_?\ƿ L }K>nʈ|狗{ _>H Voj}?OkͯMG?0to?#ȯTPP{C+~w?[ h>G0?h7O)/|7|h:^/~/(rAAo.6~+sO[El:ѿҵ;(ٿsɿM"y?oLH?noάZ~SrS ^h07F_TI׷f5q+q͗oɒ6, |`ѿO[???{0;'JVFڑA8U!s@$aS=d݌y=tNfy| 1jNQHըȣ'8iM $/ }}/kڗ?6^o][/<ש3^]xAH:N+ݽ"tsuEFWM InN.ʹ\gW$@Dɯsi>Ł}Z&ԣR,:ڷC:Ys?OѾRF. `=P1g|Y>Ի\Wŷyλ{:Ur\V[ʖ` jv}?uZq('G/5Hl([Zk>@)yb/@ɾIzpO`ש6IO͕[+_z460b_#_BNӅɯsAh1?h^#%`]w@{RuGw]#߭\mMˮ) ˇ۱wveW[ho;{`TWb|PlPrnvKOMDDezo e_٧9l]9zi_XI/NQK=!I]a싻,.= !9+!p,l\"nHE~s"~wә{NzrLoOy̪ʝZ^@XW^X ?K~ghm}.+4h )ɕ](n$tCr9]2Ib&6K\H$ SI}$:Q7ȓ; O_Ч/X@ JZ',е_ sq$ Ft2d-qn~oB{Nz;|rϵ'_ ivo,x ezrŎ˞`xheZYcߺ zuɷ\!G6n.*^\VȾ`"[ܭ`Pq{XuDڷ%8v7hMG^C|uotrsMǷ˫wG({9kF;O9\z[:rnn\!Y'1כQ[hcvؚtKn?T P//$I*(;BʽGUЪysP^~༸$ ;IS'mX~!/eQX9@[Zk>@)/싛*ݽsVB=cg^._쁊ש_W{{)"m2=aZ^@td쁊λ{:/nȾ_jB$ڙ9 =P >I"üuM}m.=6u/Qɷ][0{0 NW=6 &u[| uQˮK5/둓-HvH$@E{-(nfxU@ޞZ(='{?4*CDD{{CC˵߇X}~S;ywYB"R o{aeר"yn{o.>ת=5pw; D_5LD]úYN{6>Bûhh~`~oO?U,߸l`p܂K =C56Sd M~=r¶ŋjТȂK6'~<“(zjGBWjڽ7w1驽Hh&jw!^]1nF:ݻI2f")@%yM8"߷s/lX9Ivj};$~\9zޏ{7.Z׻IzAȎH׿9^¡Eu|aV..ٴhmЊD_*g [Z\oرڀjmL)Qۖ'b N{|~&5rv%˥[beYL<ۋЊ=9p,k?L@[(gBB"ihb--U  vD:P <($"dd[bxFbhrkgH g/ p !93xr{v)1CoBTP6+(= Tn$"Y&#ȍh[HOFQtC E)=S/Z9L0KƉ_G"!ۻafTN}֝%ˆтRR$A\щA*ɸא@V&\ۭ>֏scSi>~ӼhF?e1w9kÏM !R^ljֵ5MC B ~k~׹X*js)qHmp.^eNX*(9]C7cx$"jp .`X@$T{KlAF_gv]J$g(I8< !-E2qhrTLZTSDPd> :1H5&㬩yMj iO2y<]g ӂ *JuOդ119K] ex*WxL:7m](%"GHY^V?IǹXRZs 6C+^Ff~8:`5v(*-TrD0.J&X:j[5Fn/FruP쀡2>Gz|E]a3H$dÒH#nwS `џN(Q){'}&Kj u(KIpW0C)S&ynlDe VcsUag1Ryx0sunv7w/:Q&0[(`MQ.N%`5u(U*Ѕ9$3ҝ5j %?_?9BnYZKa}!$tɍEH$d`(1/X=ЉLLn8m-4; |L2jsc+m\Fd>A`7E`'-\2Ӳыz4MTAI>h ]Q+m`5u(U#0X1XpeR,Y9HP4/5 q:N"!ڢz;IsKeƋNʉM7[]̣%OZnZ$`px={,^iw}l3QqaCCɨʔݱQ* yL,="СxPB-,Pxhoc90lj[F3$7l%dCya2KnϮtՃ7NYM,?:)׊tl gW:2mb=UWqb8wM ϲјuKOiBP xέh:ַKG Ǒ &{'P}_nMѷB]`5u(*uYo+Qj/pX!wowü[@9 hiن2,;nIH&}*b3Mb0݀_8Z:JX,Blm[ΚowO/("Jg wGWT<187FdWjλb yjtPHsH$IؗveoC!g N$SETعsΞ={3gΜ>}ԩS'OIFDC]"DDD8sV60LP4V˙8?xF3qJ f(KP+ (fRޗ)!7r(G5s#(f+lhJ;aK";Yb2Z/2Ej]Z&C!;O(FrDᴶIF$%8EA{u+gWzAGjhٕ N8$Mb~7ڢ5KIF]u!7wC (1  pI(%M[n: pa0۳+WYPo ~gnrihjdѶryh$'T{K!,g≈IsOPs6gW lw$F=k*ɩ4PZ\i&N(3X:3qK!9𔏢';KG[;Ce?'}Q֪O ׈`$♭"O zK/w_;Cm ~# . W#lԞkI FGfܸ RoҰooʲt٧4KKQ:bOO$A񳉳Ɯ/Ny$d|#Z}r#(zE!ڢVE/P}eX0Y>\lm mP5’sA4څ/$"vIbKD]acfL&Vѻ(:, [WCF;ƿ͵`[`e:%wrB 1 oHZ-Ꮈa@dޅ"ohOB㔈X:FdXKFy:֒o՘ey4I}TɨuBH$!.Cn)rH/ECt!q[U"ڋdцTOUhC6R-ڞ0~q5c-Ts!<$WXDOnؼ(Ժr> ՗lw>K/6vnʼn[h<*Tjk4rBuɣɨ*]nv}+E8im6mQ,.uX0j] w_0kJ8`+0ݪPJ-Cr# 8?aj7ypWRd쾈t3Z:=ԺH> un냴`vC0;n'y`ȥߘߥ2ȃMa+Թ1nTs}(bu4Hٕ?mǟP7e&zδU~*;0,,o=J_i8rEusM0sWxGŚ.æ~W&R pzc \fIAyS 汦fLՖ+!7Em] W 5n{(ooaU@çIP AP[4 VR"Ksy~b[{@,MvC[MASv 5el;uOܦAq]lߍDcልĞSŒCm}fwm&*\_Tx(6*T#4z & `[Ha4u54~-VA7z(~lznrui=6{#)zscPsh4I̾JigVg2|é[Q5J&SQ1_ إd7 *P)4b׆eb#ɦp 5CQ}R-9.e\=/y7\YA?-12ĩD6eXMc} k-MލmqrUhHjCrZ$=lǐp{G෍2 aÂi,jjWҸcsr8Mo:!;wٳgϞ=sӧO:uɣGno77 ===ѥyށm۶Q8*l6KD֭c)oH5>ʥ[vy[[Խ "aaP)-9U-|TqFVh%6Y-2 !*JୂO>Ʀ$I,Kbh={^Jc#"7ܰtܹs̙={3fi̜9sӧOonnnnn:ujcc#! =x{ail ϥ8M(k8Ќ8Eޘ)`7]h`߳۳+8+&kE ueHSo~|*l@OugD ՈfPZPYPݍ :uۯ6 R~sUeB@TT}FJhFZ,T,f!CAB,{d0/B: {w.?cXBjrг |TcrXdj d<ݥRAT{ T1i:6uʧrTN2L2iΔ)S5'-I\DKG(#+&shnݗܞ]xv8[+ϡP<6L| .\o\bŭ7w߭_~֭ E{d!EsD 1q;(ꦮ>lbJuOdGQ%d왘ػw?5ԧ.O}SfͺK/^<99b7FqVF;G-.eưk[е 3j" tEh 'iS] D Gn!6=H6)j&lԞʩϺd٭&۳+Sq@mHmlJsqbﶧrS9n!"VxՁ[4K5hB =4Z`*&遈˦S7Zͷ.ʼm3**'Fp*(AUw*g> a= e ?#x'g͚5{9s̚5k1011)>G"Z6R}mtg(=9ov0A0Q[d;b uə8Q49*˲v}D9r#ZKG]a"eu>g[wHKq3upthD {0Alt ;mQ1dSf0掗2P= %ͅmZ^*Dqw(mlSșDkD3T]j$6onhhh<kFYxa#QG6΍qJH<y'loK%pW2j*0 0Rxj;6 g┈tL=C]BybT4E[&HWJ_ 'R5BmQA`!iJU6!NBKMx`rkMupǒc> Њz΍Q-!RHz(R} ot-XVS5iMYai9z9 q2[u56l8λk K{[Dɯ2s?d23 U]eЛv0N؅N׻] 2/klna#Pzim^'Ժ ?&î!"R7C.فn(Q[ZG` +3SQNqʭ Y_rTToePUܞ]]X;Ԉ42ᴸ[w~"(p©xm]9C$CyG$b6L~ _IP-3Xe}/Q͹ulcKۯI.%ܟ1,kP6{P[T*# }r([@^%&[(LtnϮ?s~A$k)ޕp:.` 42|g2e;}Zx55\%@4MLL)ƀr0r /T d41`"Bdb!P[TD*^,!+"+W. h25J3z/|"VWik>chʅܰP^$[C+VGӱԞ]S f[nw%GLx)rG5h[H<4 *ǣ쯿eq5"䓏>hV{ej>}^J!ͭoXtܹs̙={3fi̜9sӧOonnnnn:ujcc# Z=󾻷𳏹dgրP5L PBCa)"v߼Au?5,TAOY]b !}Br-+?%?p~ơtD+VPdw}ĉD?cddDaR (#.@%^c"T@Cͦ4U1 ̞=;QBSc g `୨Ո=r R(Px1LB2T);lF ;J^bjL5(93bZa% /15p@Q{ jo*wv4mzm4GK؎7v;W˾WKGL|R!2pRx S/ GWZlz}ߚ7wTnR" 3.`]8gn׎GXLU26QP =WX?5 )|e@C%]ٕ֕c3!ӳ@N5H$544NjţtvADT@hh[.y`>b@zVe (̰`}JO >e";~ΐ32i#2'e_A> c+ /=ؕjW'*,X l=nS>}8NUܩDdwfY2l&rv)`n$"}"bx!k[شiӦMDnSݴ~馆c?.|m[TmgI 7\-5444{[nI]Hbc9$-}? l$I4{+xSnf&ƨ e>;4%~pUljː&._̇onXSX ] $I-"\͘b-ɞkN+գX=z ۖOu.&n} /&DD,; qgdY@M֠CCF&ND̒zzz+&͒{lo ছ~gKJ$Ȗ﬽\yrӦw}YĆL׏5Ǘ]{&-]Qt{@1="%)%yGGD_iED;uL7Z!Ϩ(H>#3qH}ڵKιS7C#ʫX$M ь)!֒ ZamY 7&(/?G3L d5HX' kR ,i?[SY@ݢL.)Rt\)[ceyR&"zqzKK;_$"e;Ǘ, "zI&'eM1ɓd/]],ܸEO!mޕ7ӻ<9w<:z¾DPǶ?yG7i ,]ѐF[/a~-߉y_|#7'QwzwjW5p; *nX|YUkJjnHϏՠo~$=AAO'EvlCؾ}rn?u#ۯUW)b_ |Wˆ>qQ}ӛNq] J ^bjL5(.M>n&!W)Ћ/(%Px1+)P_e}`ɒ%K644 [@_dGiKL)\I!ND(?\@ @% :E |*@ez2`'?.@`;KNjF}l`9T$ߠ&ˎl#_`P;bmːI@$p_HR Տ{@W PixsҶK[?G6>,+=Pn)l`ȃUiejrlWvmҶKV޷o3mlךg n{bͺ5f=p)~4zwvDYw9뉸&҉;xxYnwn)]ܨ@M2 tU_= DK/% "G{{.!"K;x-%Ҏ ޟ{ے%w}Ʊ!8o>|7с7]nEfx1g n~u_2V5Y k:||/mSV6Ҵ0m<mvHjo=n9|Jff6 &sg9s#DԦϠɄfˢF8(@`uID&GVv=A?viT]ޱ:||-_~%=q?zͻd3K/&:# 2q89{=[m|wl{c=~P! Жl"V%h"v~@dVia%i $nI^smwZ:|ڟFO]DDo_jr|ƮA7?;w1)+k|K.-؛?ݻf]"~o=Qwl.vl؟ZHDw*%{*%.- @l$k˾r[ﭱ6~*/nDióKDzIT(o.nP7o1uŭ"PCcX k?Ӊ;v\ѱV =]mxvx֣m;ޚ Fܲy~bPt{?O߻FNWP x~3mx@kݵamYb"nu ͥ_-5$|ɋ31qOHs]3ًt,rK`\b+@wIDnt]ݼxӃsq́Ǖm-M#3n:‘ܝuY=em5D/bC?ϊbcq:6+6V$T ji)y*7_=}ז'SNڻ6}hŖ-nYSȯ%u=[䮽/~K^yĸ/˖Hȉˀ{N$DS\5>*'.޶5OSB>lφir(-W qB8uGA-W^v)Y}.J:|v3V|F0&N!>%դP-86AKЎ~xZye[/{];7ֶRu|v.~8Mup=mNܲL7(,5@ *8 .mrfWEy|ߡCx;E]o`teoݢb }<aѣsȭǡ8{{+f{.+& ׂ?Ζ8q *- ۿg'~1IB%yyg^'>+o:EH\*@8tI$ܤU x;ۅpl/XkTZq>pn "= l&mkhgnt~{-;F`ñ)]qeXs]{f ߹e38.Yrޗ遻mQ(7kv5b7o-?Rbx<4lb7uS‰ U!,EîK5 >*َU{rh؛?ݻfY_s'W{Xk iP6%u4}`zj l)& '2aUn3  ɲl;X;R\[ֶR{ڷlx -DӇ79}3gN:uɣGn~9vCApoYh/Ζ.ʡ\@S{q/DCº\xM7ܹs̙sy͜9sƌ3f̘1mڴiӦ555555566<1(*=q_1@ \a ju8+}n"+nĉ#G4`tf{@S,wxwf@۱rS`VeA΀ooyt? (3LCzIJ+SjoP@ാ5Ii=?HD7 >B5Sgwg!\|Lv5zƎ1d_˅t{PZP_ 7VBQ:,S2 x*3;M {E}ٹM?p+szXЫHҊK?Mrds/Պ_x*fj '>d"vЕpU趯w)2VIRsTR+D>g(qu+3F3C/{CS/0aP洽Aa|FWaML$;H7g @5;6sٝ;<}+XhdVV$BeIg CC."ljLʜ~Ӏ`)@ss… |;I`g]< /Q*Sɕcm)?6jIu|60B4\2W[L$O2ӊb+2Gkɑ@'b۬aLT.WP $smwc7S$`")Kq @o%(oԿ1M mnWō@JmTN/S|zD+hjh9%1i Cuŧ|;^xaSSScc#k@~o0biӦ͙3g\v?cyS)yk_MZ.(wQS*=ؕu~4Tq5 ɋZ0f/,#5We.qT#9zN(dOԝ'9j_ +l8[]#0m|f75e5[%ˎ y.q3&Z^>4@f6&fPFLDBu/]a3jjfGty577O:]%P~&A~oX,Ьϔ)SN:mڴK.s7/ƁkJ5d)4'nfKɾ;282-f?ग़g4`'LD8Kkds+, ~YߌPVVJ3 D&%8BЪJy.jza#6 E?#F^˶amW톽v.+9`a3n^[`~qdLScn]rˬAy׬e|fۙQE$J.C,[#Pk[ݳOK?7|K/tڴiMMMSNmll d=j<>>>999>>>11166vܹsΝ={̙3OԩS|%yq;auZꂝb*+OaI,Ҩ2lSSʹE, Tll}7Ycrw%Ʃ[bɪQ%۟DIrzšN;u0bZ?~g\|+Λ6mڬYfh̜9s̙ӧO1cƴiӚkaʔ)=0111111>>IԩSowwDys;y͚5k֬YSL0s3fL>}͊=L2QY,P֋) )3fLNNB?_|'|r9ŊߩD~bHQiӦNРRhOconnVv)O,#(K V˪r跩BSSnn(!%Ɖ $gpܹ1Nd(6kѻG7 sI.o %$I,d2 c`rrrrrRU4>>I@/@0ޕPЗlIT{@7dY2e䤲DN)(<{w}$P=8*hlleYFDc9\6Pc bYK@ N_"eY1 4XcņngiP1k4#IG/((ƀ~D Շ&j $`/DD)q+ Xe^ X7&/bMP*RPC8O @i`MeoW7L 牉&{`ʔ)%`5`PJع{$Э[{4WLdCCbL2(d1N߲nYǥN=u ;g<3j {V ;C1@a eG%iKN,,PIENDB`treesheets-1.0.2/TS/docs/images/screenshots/screenshot_tutorial2.png000077500000000000000000002114571352107072600256600ustar00rootroot00000000000000PNG  IHDRA$A IDATx}y`U3, ,oAwVVqA*  U+*TQ)j%AmE[7$'* drYΜn <^9sfyg=!Rhhhhhhhhhhh, [n xgO~,)@khhhhhhhhP2v]'WDYe̚=khhhhhhhh H~,UG_X>VIp,ihhhhhhhhhYDYJ8mT6 YiԿ6' $'6 CK\C*]. 'f9_ծM%EB!ՑR5Б7'jhhhhhhhhhW`fƒ!>OPJ%fA! :K Bz=khhhhhhhh*|>c̛O[n0`9 \PϪf' Y7K0˘+ff(`0 nʞݳ ɛ9NHH|~e:bfg'uuuuC5{8 OHHHLL  EBB0 n!1$u;| 1ٳ ƞuNJJJNNNIIINNNJJJLL @@!372СC{V78@>8~q|!۴i B~_%삟ꞥs]]ݡCSSS`g3frjAzqu#GO$'eOowUzج(&ئv{gR?PJ//8b 1?03]!Tϑ;w(weP3[;$.?n|gyv͓lMG^Ԧ V}/-ylAJlHvyöBWG1Mb{V-78PUUUSSO͝#:^dT9AJJj~n\98 9y֔CGQ%%1Rۆvj&oS3w$󖨷$RQ̖ >ND!Ɓ[O٠\ʦ5!|<(eg'hבOB yM?Qtdn?.z̵ǬZt̻n w_NaԙCP}}C8PQQ`ܹ~q]BdEizBd`R,4>.K!6b[Y jgaZ ք5qB"E |o#Жc$W!@(Pc*Cቱ'7.y;!#psG[Zjsx:5 ̓&aHz("]8թ@h(ST#S=ᙤ+ylٶT5#*:3)-+"yN&I- ڃnذ3WiH-aJaa[EAXfB"PEBGױn/y(bz#OD,pCX(۳(<ԀeS|A09qZԞhEYgǿ1"o}Ƚ+od f}c9ɗThPJA bY(!7on zQ@U$d摗{DT:*MiYZ><߰8iUxN-iPSRY )>MP-CRyn6Ϸ)A'-,6mRjS"'RȂ^`tG{W= Bf49!dެ{eϺ#q`׎p ` 6׊q CL@};rd QpW3$&`RrSmg<^z$&&S|>ﯥ~è1_W7ܶQ1>VfKYaEv%NEʉ6ZIyj#;uJGrttn[ (rYDa8k:񲬠Tڹ0F)(sdr" JK3ub1 GdSq݋);˃sfj 2SB)---;Pe-q+^FW4˃B;cU]GvA%ׄfQSe*#cnkڰ&]IpQ:l(VeIKJ*{:gTS7FDUBJTTjB]6 2Ab,mƴL0B!Ӥ!eSO0rt9$̺ZO'HsRD0Cѓ%W^YѓtH f*@ia! 6A@]ruuuό0;Au޽;bm۶><7O @:DhnF9P6K!-~;hYrVVV<Fp_tf&'ov:fTpL-\GnAjm&)<#F@߉/`aj$8WjqrrgڻWrbMDv$ Tښ"9P2ƒM"v$6M&!6-)ÝGؒN2Z5NvOija*W\(O"O֢ZZ~Jm%$`Շe "YJ^* ջ$F!7' `0X[[' ,]W'CC]mn)H r+!{Ep]fC@˘G& $C*5Mʙ1uuR͆#5E9Ȫ4,Ut0hg+ vРck y(͚5k۶ݳg͛ z۽{ٵAeŒ=Բc8uN+iaSS(uzF ěQߐlߗs NpG1C=w.Gr,zbLAPb{2{ ^[)Tq'_wq0ny7{U0iz颻;5cW `NCX1T&6 b' EvшhQ))UTu< A%9I|'Z6R*T75> kUq;C"=1}U| 8Pb@HTfZvu3YjA٤ba2"R{R8QTآ0QP]8PSS#=^Aȟ>K Fwj`= stsZ\שUl P:3Hc { -um۶cƌoVaa<кukے%KTEP3+ }4ڒϺtˊE eբ6m(5Rї. y䒃E\v(n++7{Mje&Fש1U#JK8UU}{VwNiEѢx~c ?1|Ί:qk,. ȁ]~J_~q-n.̴6opa[W3`}Y3n^[g'Gƹs{UgpmW/+d׌wcVWV 92,*WhMQв)?IN]m}bYm|]LTi|;M1ⓒq &7ǞM.l*'n٠0x@CDdQ7>vp89^IUj*b[X:g^+\j%;Ibiy& eZHK͌7W ~CQ:Ja DUM̶C!Nam!2VTTro+:'#Çj*XSʽ?־5QVe=TWo,7|lNi >xkPǽ+Jg l6(B ! DaRCrNkwxo i~*?2Jh x1hР7x####bG)ZjE;ab۴aJ9_~# QR&}٫aP쭩BɍJ;dwhMuE }N{e}x33/QK2QOXQ F \YYjyE=sIvo5viVb9jM։~VڀH"TPdGO68݁ywϸyˆtܝ̹-_Rgz'o%_m栕WzGf"/iLiϘ=o҂nZپG>Hgueܘ,6rXىcNʅp-T׭)T1 3c\FUݼ|*Gvrg8H3aǽ+f':K72A e*5،$^D]zshwR=I*ZT4*^VCVMs8T[Q[%7YSQUf_s8&%=T[HT=#j(QU͏V~vϷ55|=oꇙ@@lד}`26.%Q $trl@&rO!)=zE`ϪY֮A71=y槟~ziiia= .Fͻ<^,_Wȃ}%n_y }}ʊ fI髤3dKNtCද*ɄVYh=ΣƎ_9-/zqߞ}/q֥[ҽiV-xgVyVl++Qc'x`s:QZVEȁGRur.;X\*cd=̜7OyZF&s׬ϹOO>Եmf,yՒ~q9!`l6}E1hʎ ɈĘTzFܼ9_;fgXQ=7"}g}t w,"mhlhanqeA=8{sbr,x@pdePF&vh. sWFo; pBQ*nl8TC"zٱ}uzp(_`]_I<4V-…OBRtqGMEeF9X/(чg:R3eBk u~A)>Bpԛg>ڰ.+=鸢>۱}+! ǩ3Nqj¬i 7mNKuZRӓf61wڦ.v)I!X9.eR⬶)7gdvR7mw`ղm,yxף8 }55I87g Epb^%}\RbuZ~\R<6^^m{I~RRܫM['ZR;uOޫM`ՒEH:u.oW-YԳMvlf:>bx&qIٽu6YNb=de$;(rz& D%YQ0K_vRqQSdR?.)&~;~RR))f¬^Zx ^}݇wsC|vG>^ե2ZZ0gvK5kqs໎BשZZRLnǣK]s?;Kvn/tV{$5U㮹xٷM" ˱J)w;{^ gߖ/{IToW-,LfߖEW.}[bOܼ+{TUBf:iRWd$0UKc[qI'b'dmR3l`I7^=@zƩV$ITo|E(sE*e+?M]̮FJT"JN׾n IDATjTV-lyڎҕȮ= 8UhR$B;Dؖ#ٰIɩQN%Zy`bqZ (4Ϣ , (R,EN%A`pKCRZbQ:D6>?~#)I )@D#9$))I)4)HNb!s}Pṡ˼f|$ b$D#%ї I#9y4^M6ɇZ͛ x㍧~:Nmh풸#O<;0%5-n:_]oo {o`6MJQ~}yws+EO|_{yP5f#O.tu?I5'ӝ &3<4Wy4HH`_]03Z/t'$EAm+ ddd̟??!!!!!t܆%3W  drޚjf5Km ţKvӆW\2j%ݰbz=sedaHGzFe3W \X~&0>E۲Sg5rΝgdN\ܝS'<f ahCOIzcV-ڷZmg<ГшHTU%x3=l2 G֍~%POE~[ऄ2-}Kcn2 ׌<>ٸ~DnӭTw᪥?H8~BPzǞ=[}[L;}3}MEo_* jiXn9g3eq#1y!tpc YZQ̧`s1ʹ#NVҼu73aDned{!0"'XՕ,!)w`֭i1t%zw0ye4-#EjiNJ::!DjLM_¿u$ $g+6ZԄ(,BjݺI/ݢ[/lK"A@ j =هA|~4'bSA(#>& 8U^W!x%j`Bs ?R D4af)>#qdm III[|iiir͛}[4|y1C잙rvHOꐞt^VJw@WUyY8 G8SFr;NPUQQ}O(-!-Ἤ,/*Pa/3 R}R}KH,[IvlסcK߲/r Lx$ dvD]ˆd9rxe[8m%bp|\~mf2x~e"iE핤 ;˷Xd3zpFv[U+ge6vn=^UK lسM,zv%b[; e{M6j^XtnSzl3fLyG7^':=YygV-+cl$ͯ.)Lߍ['3zyZ޹k0H++zId~ _ojiAց9xui@.xٽlpfkԹ[ֈ<8F䲑Cue!d slJ|sgxa\E&SL_wl9RT槏Hbꀸ| ## S8oY_jdT̤Z+3,saF]eEWcrv - X=ٿ 4?%5oHHLL$& KT~@BbMLHLpyKLH|>@ X oH.!!$$čGB?آebԖԖSR[Z&LM8j _˖ԖFKlOmOmOmhhhhpJԄ --3ژ-Z6h;j(1]:Qjr,/BfO좪"mgaGQ^`k.읋ڭ7{Ƶu7L@ PEbҧ/O9ުwp;NzRw@ΊE.xR*uL3цul';5I32H:(Z@yieȶyR \0 =բdw.0BL?}[D{c2rӓ?Aؼ4.O6314wp/.7Y1y^z5G9#F}]8ˍFuҽQ Sfqf*c.-d]8^/^]Z0wͧiwJ-7>ްnĘr;uz q ÇRlkH,5vr`E}G8UR hBУTi< Ԧ%^+Y<*Q.Mf kBrCqGpxAmhNd1 q8SusQ"iށe 1zd)~|[QVD@@ м1`` P`v| }KXà0Ah}o"}~_bJۧ#@XݳܢCob.Lv)H)^^AGQIUشxS Lzfؗ4-#f,{qۧNf`԰+++ZmkǴl\J,q9rOjB?yE|SnrĦ{f,ڱ<cǧMt{#.0fEL]lk _UBY9m⊢Y?\v.28)SܾEUevlֻmJUeELf0W=}tA&ĎzP߷F`Y׌7g͎*s:|vU]Y1gd{MŜmUeEӒʷR⊜n33XYT@)>)@wFbV2-QrZ]Qe=[+*(G**yUrrh܏ã/UO5Eܓ8gTJ)dc1_p洉gN`ItեlnyYʢE(*ZEٵūZG(ғreQG /čXMh?OT!_, zrH[,Ԉk/pIDBXϳ{(uwOy&s=Nˍra{ӻO![ߴmӺK.K.mӺmV4KNN hױA j?`pO0~ܦ~O0(ߠ N3U!-/U3$30uA;Mm n7`pCzižz$wc{khĤ{&$Yn/u@m򲪢BF¶h:Q(]?J-?e0P(2NÿdwVͅBVhڛm=wJB6\T]|ۜ,\存 "b05?#AbC(EA2%B,7҇)2#Di,A+&֧4;~C!w,7µ˷uIlJ{$#tZ[.aA Ʌ$Q@UeŨTΆ]eTBTLnYo Ǚ㝾V< `sGe=LN]~;0Oֿ^[QW\ļ=oeuiX2g@НSnZ"fT5g;WJY ~32a1f|yVw=VqQVM׆/~5?3 ;ʶ⿿{~l(HArE3_y!JǮYx\.M)nye{UmVc,?ȣڄvl/-XvrgΕ{~N]ͣ"ߤgd(+ezxnhYW85V/-Pݯ{}f*mp˶HzՕnjW~BPN]{l}*+nr ^*k)]o/=kş㖇sa݋ ?tWۘ H{_njx\Szm5+ˣD 6GWӼأ$4o><.vdU^ iUqv؉܃@3>&EhGQvwb ;^p!tP$5xXفB>o ";3eAP(N`JLǠ1_0EV8 ٣6$\FH5hu$ohh=|Cjjjٳk׮O6mG4 K,)--m֬Y۶m#'`3f̗?b 'TzfXu 2,,[ A)##ptsI:;in8o.Vxžl+윻|5.+mg΂EzFd/;Bhms{iYwg#JIIk.o ;TbDB;I=*. &xqoُc{eue'H{CG\x :<I5NPf&̭u6u{acuB!\^JF@O6obDl&Oc8xI4[]z t#Ml #/c@JF%|>!:< pr%ة)mʑԮ Lshލ%O&~ʯs[hzT,(l`*d|@%"rLڊHU*JTɾvT$/+=2Wqq5E.ODIx\łID$2 D4##[dY,?;>4$l4cLEL,hj,JAXTUUB -vQd7;5 q6Bgn@)_϶p)7[c\@vؓ*s Sb42sӗu@ lIP"os;m  aoDlLSK;5$Q8ǨjܨeRw19T>%_3B&v9~>_Ef(RażI GEDgQKJE"?((ظ$$gXfvb mEpdAp9]9BQuTb`F'6oUBw1:EQZSu{+)TiDUNW} ;Trq8lG/s'fhyT„8UXM$ЈF|Y\SP$ +i4]SxMݿ'πOd#JH$m { vb1Kó)TfB-[Q5@G @"> %\H(o®2oT4?K-8X)EdՄ%2DZ`2H]mw]'U(#{DڒpzUGuxbv yA4SO2R8;rlvXw8aUw<Yufk$Aû6삗IXn@V!yI<;{9O3\h⍰G; \n{ wbURS=r{ԗ– Dnx1y=qLRrK vt & yYnߓr_߮>tOsl'oٍo_wK?oKo==ZCCCCC&KEks`z+<=|{wO}ݞ6tŻ`z~=qD޶k۱wv/n-kGJ޻=)!vR4&,ZkepڿyTT .W0y/<.e%(+we\WӄaCFMGYsUH[~W#%O[-?OOOnxDq߾r/߸Ͳ-e[^uϐ76\Zlz(7[17%Lt rz]93vE7Q}[\v{[ʪ}gr/Z%!(}Uo)? >׋,{w[1zKY38K[›lf+& :[y%o.Dž߽cO_ɛpE\ڝY ;ʳlr+[ꭔG.̿a˟YtgkGMTڧ30-כs.!lyy7&vXzZէwn<=R'-W^$Ux,;/oS6 <H.BHUboDXɪĈ/MBi, --]Qs.QkQ_N syxTd /ןݵ5ɞ?nAowe\wEţѡ{oB6Ԅy H"L˝>[[pvniMx>jOCCCCC8Bhi8tgn& =ypzÛ/b-[y^]v]=S.C8pĉvyϗaÜ3Ǟsk+'rv/~̵5gK=:uoxEL)|oLroFvؽnuϋWGʙc!}!]G?ܝ7rכ/\vx;dwhyi׵lkʲK޻@]XJ)L Uδ]MĞKg Yvy^[0R!bt   ]4qՕ6>raaOCCCCCxkJn{fh0p}xx*+&KzsKh& ofУ+hC-g;VOD"q c#8&N{zwcnx}o~J::d_1tuw߾5 $gȤaw_}}ŤqѾ[6V0Hr(7""(-{ݮ jyfl[s`LI`|}ZDl9ĕVZgWނihhhhhCxHqic`϶oL=n8GClf7qrxNI\1ޱdS`Ih ݱp9US49sݹ`b,~6^/즱~XBX7旬бTGAó(XÞhGHK{k]h?i7.^wAsi؞N 'n{q{BବH*ߊCۻJQzǿzvYFRIؖvרVٕbi444444c'ЍPZJ0$0mUu_;+gF 㶐ymÜz @eMtwyݷK{S#wV UG݅7l#brjKuzSoq>+˿ԡI0kM e)q^ 4k%am`7}2\`GeE|TPޥaH8S#צ*U/}n9$B\:LyuXw> Yix1,!a;.0{K8冎O0WKEWt_h9L\1`7q y-C-.5ecX`.gswڏ!-f{˦+[8]?8ԥV Kb/Cu &)_|9ʟ^໾^tה}m+tyE\t,U^3c )yKSzoEwyхqgī{f:~i ۧS;8e 8,y> Md >tPMM͞={v|GeՖ25nj?^0?71y 6]9</N\48YѲE`ڬ?=m]Zs̖-[4h֬YrrrrrrbbbgS75QAT'.촢]O<̪OCCCCgXٳ'us8ٛֆ>#-=/sGLOCCCCE'X3q]]G>8X`;?8qaxyḫAO4444444444N$4@7=߸a5444444444͔΢Ϟ/ӭa5444444444 v9Ƴ}jhhhg;g}Z-[νϳX rĠ=|B 외,BF4.ILz9Cվ<ւ1z'_kphf0C|TV8m*>Rjh43t6~݁c-Ac{E--SiX1$W:fɉ1IzDa<%9 pRPC!߁+FRm۾C~// 6l^pm@՗Xsѳ56?)oݔLj>@z&1 B)@(_iZm5u3B&ߺ u: J6Vv<~=)Q_g{-*Ј<nh۶m]]ݱ% wK/V܆ɱ=[*z@D?T0n|^w9K5EIIft~#s"-|v|_Ή!$B4dҠ P jaG{^jW\E',:˙m:1R4aR_PPj|Ɓ d˼fu\= giq_ֳ/Wx$ʿ8ss7WHT}Z .u/sm}9{z_ϡ1lhw8=?ka@3pg'%%?6 Qozc $Ѫi⚚&(5)P@X0ddww^Ic G'?3q5FTVV.[,Y'OЅ"nK%$E)Q&Y0UNiwjifL2%h~W_Db)駤&#lLNih &B& À@bw"ά@ˉszI؛ӭ9@@kt@Y7`F)ݣFsόpk /޽8|oٺz)2cyӄ &(EB4̞kcmªnwN ~[k}i(, *=;?9_~+鿻,-y߾|p[g%nLNN94mʶ%'u=:5OXKb̴7xc`qhCQTTw:+!A"(n30 :%Q~x$3.;:w8&:ͤ(j1j@B%$Hz:?zNg?W^UuT{yzS \G?;._4㘟`#һ'ɹ+**zNJ﫧O` Jg=P+d4 d/Z+mߍYV=;=<:و#DD_/σ88 HQ8yT>N#{y~<JaJAJ*>džs>>Կ9ʟ;}lv{ frfLֵoFGg `̾L6~~ͱݷ=n\v΄}?>F}^'[ZN=f; (NsiS{i3?sK |}-VnwNNN?Ƞ^P>9ǜ :)妳/qs?ɖ P83`4@ xB P5 &CQC)p`D !/_jntjOnRΦ sgV7C IcNڅ\w? ] D]_B4SBzV$f{_}me{鮶i7R`(uë 7#u s.0ٝ4kiA~8>IE f8Mޯ=`vيij:UŞ;R\lQά{rrȎ7OvvK)<3;O< n{{KF\C;#YGbo;yUD%@q ]NuD'bw}w{߲UΛ<, c`4(Y%MiZG &&+~ FR]^ ;}۵x)tEQQ*'. # 0T@B(BB QN@BA 0eQkl|5>& ߘOJwqxg]4c> njGWOGl#7NTc Lc]^""}Qpx@R?~ggoBf M˃00aʌ;\Vwg^#3_o> .~r?}<:|ṳ>t8B~l3H8[<ya_̚8  ~JG4vxr@<-\{愐L.-{.9ϝ6bGM,0lyҩinySn 1o;Ÿ1k[gGotfeϞ/}%qwJ4W߳ʆ;L(ZmϞˤ'{uYVv=▐7nnvH{v`bJ=AA,xQଢ଼[\nH3&Ō}_~f L8B!d S a2O^b%5ʟ~mΧ z=ςkĤ|? ~d7<׊q ]hqV IDAT{'A we-?㓂px vE`?mIϛ P / .J.ɫ><|DZ jL_= x.zBlΜ”cg<@ V~`p}/T F:i23Jh|y_.}IL)t#xpGdkMux/ |8%:Aq,8&N~kSwŌ{==m]76B+WTYU]U\u=_\kξa݀hFFsS7睳96Fh i7 ㇗"EO}C{0e:.M=.>s΋isg*`N!0 @qz$`s)WO_~k?]8F׻LK@b2M^(Yy$g΀p~_>Μߏӧo4isb4#zчO9t#Ԧ@Z!gt#_2e]8 HQSxxy_0 _ 9HWgۿkN<@7Z6lmΦz^KC٪~GOL@A~a:qPo`^/09fh@.t6#N!. ) ǘ֍=p;FxFn{_'7}go@ѝCό˯Ȍ^~;-xF8ƣOwzw@C=f8 =]kP|~un_IFZm#D wpƃS.x_0 RгhhJH=cN{vчg]Qꇮk/v*A.ox*)3r2$ cΰ%ߧpNotC٪`w/TTb/wvS)t9Inn?V7_8A'rFp\wX Nvx'Zcuxډ=Pw"ee.P:O}ŋ/^xAqu0k|L\}Ou= ><=`ytZyeE}?{6͡C&'v761bS|ƺ_Ik#E^^ިXL2sQIg?k^GnYُҴA" pী.5'G>:0r D̷ҳ2@dp@Sɷ}@AyJ)RQlJ 6!ϝlxb4tt| 6Rկuxi_e=:g楮g)Μ])&{~Uhiߛ6͜={jn}gO>0iذܸ۫jpK @^)Y8H>^H>6Ӛ\13R\~)L_4 iӳϦ]kx3/qaNǫ|g\gNN!TEg<Vc𓇾z<J5)ID6Mț.LKqϝ@nj;/|beWj̜BSW}UZVjmOOЙɉ~y(]'(G5+N]tӎ=#5[޶j  t֏ug'?wUo>>p҇㣛ϒk gKm_uBn~zXJ u~p]8R7*e1O;ʦ1"ڶ۳g\plؚYYYMMM1c+j{{_޸y а},r衄 !zBib |``Ќ D/Vj\j~MM'$[g#/Tc21"dbXs{ճ5p ;M9?L @Th$8vLz2v;̑ :毼_|],4*B.Yr]Kk~YYػШ{uߛ{ђ /+3敷yoԒ)>v?x-'gT0#J`r7geޖ5qdgb˟~qA^?N7SM'uL64nyypu?zc{s}}T9ҩ\C8ܼt<pscst 7XH⸨yKҾp@74?؂" f+ڽQmhߵZxqgg')3\CIQg˯H@PZwh\)H'r8su>o҅Ntt|7W^/vI&}Z~zs5wğ>qբ88]~3׌2x* o_M1h`2j@qʅփvqӖa:r8^?ᜟ|S:gZiC2&sDoē鯽9<;gUg_?ͳum/<)3ğ_䯻!nPt9B/F _Kn~K}ӯeۦ 00m:>sĹ3sis9j7`3|VJ9Oߩ:Vo p_{\Ὕtoۄ6Bz%B{ 1r&Mvw!cJ^0i7ɦ|獽9~!bAL]ф~ըqwo]dK9E=x͢Ň7m{>ٌe,6S'ޯP|HHҨ_ OTw$ٰ)~ bjY^eELQo.ڂq\"T5o/]"n޴}ܻzBi$v3V[3> ?. 'jߜ4[[ɽjoܛ _8Y%>7t"su^Ny4\c]eSq*D p1cq&""ERRכ>P1@S3@q֍9_;vDKsҒ={Sݹ ZV3sS yMjokl2{xoRO+3Y׮!]uY>Mhp`V7!0 ?uS % >/#~v4EjLq Ѽ =#W̬y- '^Pi&‰S8=tc#Ӌo~6LWUӦ;c~;=Gxo<'xNK='a*5{YKI{<>/PC1:IPzE,)m\uP RP頔ۑA9y6l׭7<Ho 1 8otVoCFdu;%8P| G |<)dS>=|Fa bf:C/3~UyFϿ6Be#G~i^7w_~hA=4Yp^Z  C奅u4 h><< o}2,ڹ V)N{qڃ3?/:}Ӌ.4Ãjssn>q|E.tħ_T{m~?:DZ"$36=#2gܯFN6iԨCԊq\,n~^]_tv5~+|[[=i*OU4O̿2W "==rƑ[_/V7mjs&q3}/v?vA= ܥyX5u(]lp^́9NG7WN(φR&,]uVUo_2>z~͋s9.V7Jev)&,_Y6JSϫ${VTTsGͳ_߃xzL{CDiz,re7Kv{b3[E;/#Yܘ=)d֔뎯ϭQu<-NLM:.7/-m1R2HE߳&Ⱥp~Ͽy]sܸyl9)) Q)H7<]8ӎíּ;wM{ v8ΐb"t]Gc3c3~rE6WqeD5K1' >zAӧz-l=xZ ӊ1[JAh& P[KH3!5 >i%F衎O=?Λez?yjpT X9K(_߃[pyܪ[>ه~}_h(snoޢmc{Mn޴=zunUb ܵc=nYc/m6b*g=_^>?~ʧX~ʒ;@܆gZ8qp_q` cfuM>}:ފ#Z4 ]%ݯ;qkd]1G/zp:2g61c|?xNpܩm5]i'M)yn4j'gȉ[吒 jz_}O7iF;w5( / .%gCۥ!b!iB#A혰 KG0QL[ MM0@6K֘>J [%;wohrm J К爥b!!3Eߤ]x쵧>E_9ӟ7:;.0ZRdyI "jjژQJD,Yi `( FrG8,l}g]iHǐa5F7tC K1\Ga!c8]n{T\b\w}ݻoGP2sp@A @AlHpJ'N|p%q 8fׁuMg<$rʔsfL>YG9IBQ'wX(p/O={IIϖ 5'2j0eMAZKq-8qLshm~w_kW+Z --}wŏ%>o3Ol3R[8"|]nLr\wx\6h=s~kL1 BX ~Ɵ$.I^)QbI`s3|bq>H~ 0p  ^^ J` n-b M[Y M/ȞdΜcJörLr&L>r_kqRoɓ'&d[Y?_=ȝ7mč?2){Z]7PqF)ٺxJ1vbU0r.# :O̙`Xf?  Ɛ%y鸣vttk}+(C_-*:CY[ѽ}s-9&Xtĝ;j/X=qF)_0an{&L7Lnny3x@pSS\@ul'@ɂ2K;|GJ/s k~7?iC#.w0-a$jaԄ-Mp=j5d$E0uΦ(F=׷7[җPF^ 1ӱ*S /xV4"W {Vyx?m}+^ (F8`84iuоfgvL>od΍域ۂyk4TT}Ψ ]|d5M5lSo>>.0 FwEpp}-TQgHCf3 H6YXRG32'$@' ~ܾ`0X$m6F;Zt )rX-qɎ#Wj\XeP{/t*J#Wc0 :?~#'~O?}-E`A!E|9I ;/{x_K1d&kw vg \ 랱.8W/`0 cĕ+wi=W8(EtMZˑi&%scp8\2Y2 ` ni;ƛiii1&IR[ʸ++B!nu 9w"e= IDAT&;`0  Vum8)s (_D1PX:ѯMHJP3QjJvV)%|m.7ז ^2RX_)} z>>)a%<%1CņJ^X2l!Z6lŖQFA7ЦJ+PZK) 8BJĭ$7aT {I.!M[auV!zJU*,E'":*2R@-E+A{ +KOIJ}4xLZPLk!HL]9߼hR*G !3A 1i/Bj٠/P+T^;dHN-}^9n[(- SU`h`0 Mz*{~9ϕKkW6=i'RQSl5[9Zo.Lt\T*Ukubˬ"JʇbJ(, X5Zk=9^5O"sM[#VAwOn Žj[*hm#P׀)"[W-e9a]H<˲TZZ\o-(k&ժkY9gjXo--NRn8U'R4wX(V/OWѩ䦳^.n?.sK0 .2asO̹1@6±rbSouv6c?WB7g%*N¿i dr( 'i7mR@cş:Kf=Ӂ0R0.ҷ!ٰs~V(SXB~Cv2GQ;rCbA"H đYMlUŖ0e'1d Kb r:54?})BY IiHln∧F)k& ;֜5fkYZz[ڷ^o PRҒ V{InbHoη$jQ^DzJ/ݹo_}`0~텅ϙ3̝3gܹs?s;q n` ݚ6s__P+ Z RH6\Rr}nCt&助Zj,*JVVZK\tKEm5 ki- ~UN9qB}K.uM7i.(ĦMX,΍rde`0zgUQO/R(w8[7#}`0Kҭ2txx<---Fd2]$܁L)\z˾lwEZ |>'̚}^8f̘#FddddLOOOOOOMM6jLa0A6xY[ XY&V/d0UKźun*[`0EXw5 ƐB{˚Jknw l{@.`0/ J+ZFvs?0 FPR*{PJ=YL^g`0 c#Y/r#7`0 1РәF 86߳ePJ5qTTS%JrSUE%^( 7;"ЙkKĺv|nHI{b3CpYr}9eM"+\\gh>UEn.^[ WcOܱŎҵ-t/Ԙp04+a82$`j9B?nJ]U3샟t "ZJ(#EUPnEUNyr-{I|9 5-|ffCu[%|::s%Dӵ݂Sgc}Hcg?Y^ZhCM3euyb`.krUYgGyzet請|/S [)xOnbo&x'  QY# ".-}XI$E(=)BPK&dR;حk,*f Qi BاLA72Rh>բ:*-) ~ֳʯЧO 1ư6Z%6mۂ6TRo "1w9 {C|Bxn]Ȭ)P>:CPutSΪ"IpY1ՋZT; AJTRǀp56h&(YQE,Ҧ[r=ÈI,Kݿ2e8 m!_ Ք[|gFWJEiszwB}C&zcʟċQY͋VXQȠYh9"ELjR)@958DG8[?C*.M)(=AB賣چ૦֖=Wb/ ^喊R#Pď;O|J sMZo ZT}u, >yEBB6Kj9T(XJS^s^B ()G}ī"vzUIkMÄlڤ*9ZxN# :I%(&/n+D"zsVnP/khdA(*Mӻ7Pe5 ch.-KDUlZȥF%+8WrH/7A!G 1H`Q gc=dCw`~3'O]n{0B*QhQCJuq8M^dMݪLy Z2"$2#DڨrwIQMV{kNéݣTsSoDziA/TFoŃ8mtI?f5H׹c`Vq&>Z]bymjzp8^1#ɟs#uI7\ Ʈ8^ I$VaJ!: rKÿ:jH}s⨹$Js$W\\ӤvkSZQ/aη-B[ $ID2,|{R9jFp5'1cm?Mq|VGXR$YG@H^kAz~$;Cr6[t%N}xuTH;@r{ t㫭"pa2Obu 唉viJ. rh:FtV{sUQ.suE>MzVQooDѕRTZ s{hr;MMzbgG͚o7%Y&X2Mf<34AӆָlYZG쳎%]60pqeOs~hoXVډ9u|"m;Vq"=\I M合Ru#8ʴW}E++R:p:MgUut%a*y+Zk9=ܣj5qVUQXJ>`INn$eqޑ HQFei)ꫫ륶,-oYަf:&K]c*sxPt!^l:F< u9U|~T4UZJ8W 1׈Q_5gwB Q+J39Mnм=Q-jrK7.&qRT9QBm6U!*W}Upu䁊~cE4aZJ.FjDqa(_W͡D.pe&tTHĪptb*M^DdP$ToVPypEh"e{bxifl<ݮkDjRC#$t)K$[D<8LuxVH-[rznv\]]]mmmǏ?r˛~'=t#~z#nݴiŒ1q8V\ )Ϊ-+Po3X:Rd d {.ׁ"FOuIҭ2txx<---Fd2q\LeIė6[ow}+H# 3/K/3fLvv#222dffjsCT  >DUm=2}|B󌁬1'EY~ઈyfh `P׊1n E3bYƹctm oa(cYSiͭvTPD4r*CRZiaw$b.m Dae~灌~@/aB(JgcgBKTRF0yڪ`JRAiE`0g‘6:+ՠ3/6ȴDd0 `0.rGǁ &ƷC I `0P PB@8=˥h2ә`0 1𡔂uS!~iԿ `0 @& TߊښF&!`0 oX!1s5O>G9Ae,Y*;– &f0  ڶ4f=GCтvV[')Fu6~;7\}z1 |$"nphLPWB;ح+4-?<Ϊ"[blMIl-mR#*S[qU*bm`0 E"97lYZ چҥpBԊBb/]/Қkhm)\(%b}pVIPT\͑ Wc5LX %ar(K*֯:@sAOɰwB[iQa-n*@i-"lجMRx OQQo*Q5`0nO9778"6[cRkΪ6 TsqM.5kV\V^$ IA/p^BTWB 4.a06 TXMSUu5!|m;wl2Udӭj`0ڑV$w<mYSiU;ਸ਼l8sUPT>j4Y,6UZ%98wlK1Cw'.Bt6İyqm]"}y 乪[a&eSV5`0O(Gx̹xV[uUc<` +lW8kx(4 GR"%m;y Il{:P) 4EaxRY*GYB.TwQh5=D"]R4^U97 HWhѣ1/Za@nk*/]ߤG(4 ϧ7TZ%C:]j)cE6a+(C]q `h jC hiyMMz"dR.Jzuqf0  0qə3g|>unruuu?~ȑ#/o'nYS`(<#nܴiŒ1q8V\ { i^^[_YUel:)8Ya<`0#I4񴴴Fq!G0!RJHuODg~뭷컾|]!3ϛ^8f̘#FddddLOOOOOOMM1cZo+5YB6/j} Ͱx`0&Y1MAm5CsqMm)%%ҎRRɌg`0uYhڬgK}-P\\CZ`0PP4mgsg0 `0#grf0 ` 2Ngn|c4`0 c@{&C5Yƅ`0 c3m&T SֆQBn ; `0mlh9}TW"MNg rR01`N:l2lڴ]ܐm `0 f(QY1 \ OFz&?`0 Ԑ?^=g*/`0 pZ;ڬg2d<βrRrRRU{8KuW/v`w;!Tf=ӐGY `04+ս` IDAT<0WW ѧ;!1 gб֠6qgh3oYuXV,[Vu k5LI%y}#1chc[yN( =վv WLIc^彷g+W'1+w(CǶBJd Y" eݾˍbu*֑La:jDmi]v/-2[[q{9-β;^ [c j4nm^.OF AJ4iyyƒg98uٽi>ϐcߺrZ~{)`(C` (tj=K94v䭘 d/v(osTz>`5 <2/*\~(ƆfRz|liN,;AyΒ{ʼt!#1ȍYHH2Fߓ=ڊ[/7<46yx`DQz,S0kjs#μv[s˷%C ֑z"7$lǾ@^R؆朱 `9v9@ uẻ߲.ܭ;ͳD%`0"`Iɟ(f ,#rZ^No=1{5%̐f4[sp>X dعρX0:~Ru#|vH FU$^l"[OQsu=SLYf{ZI>Ub $^Mh14*I6{yyWp5xmBRLR+9OA\g`e7A}C,B{z\C>X[F2}"5ИȜ9J{&G3Ev "s#։Ό,幩BB"3|eնC=gIdG=Ai'0D=νÖ,BB"38=nބY{&G3EYB=VBɄfWgf?I~]_2!ޫoMʨ02RG_}y ZtSWsRО1wQEQM^G5QE5<'[Qsi]Ttߴ-ܞbns/DŽ'[4L&+nG>d2yv/KO%vZg=jz.4E県CQo8_9'S=Fh̢@k37ίJx@6n__YB37>(@eံ( CJsQB >>ﮯK SqEGdmyy_#G-#|ZZjz衆M6w} 6lذ{űgB5c?}xj!BJ2ju:+k#m9|=U-239'K:GQ*pB!&)(| cl]</wBe4z “UPb 1F;vȑ#BT.:bZV#q3!!BՊd8W]е):H@I̙35rZq!JGe3=#j'ܷo_M#֩$u >+jEfpgLc 14=\S8.ׅ*NY#%Eu"HojP'W7n RD3 CU:qr׻+l\RP>cJP)D7BB"lJm( (OȔ'7rQ)v~$7}ΦE۷oS38Wܱ>㸙ޱ6 c86 @poxVлB>ͷq!><Оy"qܢi~Lr8slS.=@x 8 w)9B zިBRn$s=#piS ɺArQ6Lzڲ}I!S8fs^EݰYe6Ҋ~.\sNg]] @k8!䝊gQpUMH(qܮi(޸慛U۷o5z)|[zNԷ;O((z); lM [wH'R-ipc$<4s a,'4LҘjf37PU=nLe'J]n+a4n]`Za4Udێ%<%5'kY3,,,hc r:B3'HTȥϯ:f|JOBč76o\^ 2Py&'3*%c=N0-( jrURu<3mQ-m6~Kq@;=A)3 +*n?ڐ>fgTZawдq?N^EG?@{`ƍZA -l6"ǍZiMce [wH'ZZ|>DŽ:nnt̡6nmVW.:0tI =#zp, f&w8KXweMQB_K꓎= 5SBн/mvesggXkDge*sDm~سP3x$l;๊q̯g ͎!T BγF1{FʽHr($v Ri :C5 ږʽHj966fS)(eiۦ};W7޸qcB(CCǞ9|޸=Nsޱ*zM(6۩㦌(! m9|*:CP 2!m M2tTu}rB~F(7@eg]t(& fFow엩z)[A0M8iZKi[$#n 2U@86tsCv=hBH~F @dM=(zw8WZ RU$\ִ}$4-S^#`i*m\ j-[7v$5Vߜc2t &Qhnep!*~.\sNAf]]'І0Ld`ID IDATE 1*dn0zFEo>8sL;'CV}}}gg۷O<977$# F›a@͘ B16eD,B5*:^Z-O8 +=bJ̙3P#I}}ݻ^_ҥK`޽=0:`]AkY80@t^vä2/̓!s!WEvć#SlVαW^L@H1Qq۷ U5֣"xQؘ1Eρp ca|8*ql0gIQr9zhN}hgTT&o`N7Wx㸙ޱ6 6ivM|qi}>A:Dx 8 k4N"TBwj: 8Z{Bzлzǧ.ΐh.@b:(7g3%5d^;lA&֬Y^x6j;*^>ѝPMy;, K5agL1oEm [ YƶXx~N\zDZ QUOy>fo F}}3? x5^vPW"hj$t;YrP33?FM|ܐY ;lc"Ƌ*D,Amm(jy&PW"vmI8+g6nxƍr!T`\[E3boro>\wͷO䩙l@!nH DP455js 6U' 1XG9n4c5\ٚPCxZFUuU}!K_8`HZE8DTO>O'x{)w_Bđg 8S*ܠD(B_ٲeKccc8.wG tur"Daj' qdι5k߾}gΜ9s̾}ݗ9s )W l6o޼%#2qǭܺuki n*zLGq27EMry΋*~ +kOǛ݋RB߽{իW_\t޽{ EƎqՍza8>0m6e˥bh'c2t t .4xx65CNN"T~| 9[YY]VUҢ畕Օ;+/?l__ur/Oogn55=c\JR3hGccccccml#>a:xvw|`aa&!5vxL fĭhzh# zpa e9u"2S 3ITv! ͫ"nsʝ?^^]Y]YYI/=S꒞AuQV"M!awD7]]yg%)x=@{)D,Rb`5sFExn ]a|lV٨$Bq*|Sww}| n4:y6BAσGm@!oa턕5wBhު*@[ush*6PZZ[zӟ$''>/,/"WK 9cg3B04D`?k&i-u;:oV*8DJ_?)-zްa}WWevf N:[ZfngBHD眜Z0 =S3[94e1@@`awEjxzćސB{L| "ϪZG!ja363_뜓3[2|9k4V4QUD:pǥ9JY9(*5yB!B#y\ *bg%3!q"B`0& ]@H*Gs:x)oZE 1tF!BڑG3h-m$n(9S B!@l@3& _)y!a%BG3Abf*p3f~=/ gn ÛބU|AMaN*V&mp|݅yٸܽ@HF9kg@y7!B :[A~yϜ$-uuZBqs.XxuxnZ]]]YY]ܾsΝlakZ%ԼJk egΜ)wjξ}0?tqq+⽫Unu[ͨZT(4zN>KG47 #TFx~ܽ@㸕;wrwsΝRHl@p+uB{0Avχh(wGL2tq#̙3x%rYIsss;*g-`>,--mܸ 2++inuuc&i\ďJVL̍%:lߖo$K˯n訾]_;q_:\0z+}-lHΨ47cC?裳wʫ4lj>]xOfn&ݨ̍c4}# O>Q0a:\d3P)Bǖ-[z}}}}]]]r#E r'BQEQ˩37P=1*<^J*hk PWc>=<|O|hG+L,ybOxMh;&0ᆒ> m?::f' UG=cAT2mLMn-\t iQ~2ۺmft01aJ,@4*,['Hێ݃t'r1%˲oՌݛ瀓#U{%Tsl0[䓔e\{0m灞ZIg5֎I3E%'knx"t4JQ^ֵ+[H+:|CZ@H~Nig;^N$$҂ UEnݺ#$~FPL 3Vз;@xjP ]|mEQA2GQOjb PE23sZ?3LdghbOu4^], R\䶐 I") G{(_&y#7J5OȔ=exBo{<+i7}Φ9َU %G{SOy eLk#-.+J_vΦUU3l mjcU@j) Jg:?e5nͳ&E.翃F{ &XC;K[)S\zHM} q~'N Dž2)ƩU@Z! S 3 Qkel`0d0:>јsSdj0<At@_cϩ6efkZQaȩ 2S 3ߜMƩS+TU=P=Eo@ ?bJ )w;ĐM[ @ZPmcrGmKf |iv )0Җ-R$z"$p"+͔5%yϥ)lE=)SlCmЊ QRk49| !4BHb -d=%Bd=nGԭg+ͣ)4x`@6gTMa/,A%7 2ɔs67nh[կ 1ZO ȮZ!>C 04N3L?DFcG>mmth2\Z:'oh"Y[3ͨU _qCMt=|v}Z{ܢ$'S^o KOLIK79B/V[h9c(D ]>K$tu:udt(ͧb()ƍoܸyfEmk "7Y{<b7n(}pikh“3}#~M鳦wdLћ}fL֦4;C͎Z!_ Ӻh3}#~elFy9aeF/9ޔwUx~ڿyJOB=TjզS'nYhYG9kk+ĹD$o3dHkO!y["=jB!IuPޢ^c&: _iOjޚeȱ^$Ǟ^#7ꓢٗ=?Ox \0%*T>lY-Do{ԭA|:9Y'66̕nXR0xEɸs#&,:^t/w]Ukypj ǖ-[*sqB mǞQ-A>>}Ѵq⯚&鵀 1# mtSsM|PTb0ʜ Ο麧~ٳ.\w]H $˫6m0 \Y0* B\k$+,$ ÅיEDj:U(9()m R']M0+l] ) *xk"Z#} RMJ52QwaIH+&0ɡC̢ pKJ>JdhW,Lt+l67G2.%CS>v\TZzd0u[+J/d!6a8O:hWM-MJJd(he  {.[7ڵBR}}}gg۷O<977)? I^s"/KƐjrUr-s;ox's(U ?o#aI8O@ l KƎ'.as#oH 2M&賑B|o\ȮW@awPF{OEG?@{`F +|SrU8[4͏e@9;>7>l,bC;Is#AA:8Ɨlx%LVas}a?yCZHy0-loɢ OŦZl,Qsov0,\'572Xl>dF:9|CCӱ*07yym<˭.vkhbap-Q l`p:za/i? p"*l0Z7dӆ"E!9D-30$=NU)޽gaYvrr?19Ҷ"!ᣍwE D vy1LtȰ2!Uza&bg;JbY5#(p >NHӻzm0:*powܱ!ow`{Ƹ2|J$WԓB%ǀ3ccT*MX4|dy6d$.q%S&_h]9wp#Y#Ζ>[ @vt%_vZa~m0+.(WXh)I!z^'ǀgi@^ TX;;MCrVVaGI  ێ]}6pz2I>vg4Li IDAT<31ep8jyvU [fk3De fEQt69 ds 9+q7ݞfde8"'lxf^`>4YP2')awrjbazgRqx=޾u4Vݓ(9 7J3rSYgV>i :$fMCgMgS1^_x׽?8^'YQs $nO2(xrn=lVs#e, sTcg*څw=$㠩zdmDyQ~'%c2(xF4굄6żZ#8mzb6 Yf+ɺ{$tB~+]TћlBxFFodN~sSF:/ uOt YZ 2id=G@oe zNAU(_`n+١zߦ38!wYY&Vb֓unks$t) dz騂*P `]١l:rFϘ\qisl2Q(}­v1!9?>'iϞ[NEG0}<3³4|Zx.?0"U?a'0̍tNtw8:ZeйAKNľ=I|-+:Ӟ?w $FS2U@86td2LG`YlW1in],vp:[dBB"38='iφs|t{(}E3<{’1ިl32KD D "LއyXjQJw.J Ӟ S`q!$DPc`z!H3}&r, 'D=KyOf>5ᓛ,y$$:oܢMHDX 8 _ v8HBiZA݄Yg/Le,sd2KO%vZMduUȼ9|1GZ RU$$Ds2]y13Ř945;IBq,=*ož Yf:KO%v-٣:i]U=G9ɯ#3ޚKėk ցRR7o޼}[?>ƍׯ_vsseq豣?y̙3`\-_4M} ޾D iA| ƍ%<`ooiqIA-ЭqFNƙpW% t/ƒ"Bhn]x<w?rA , ʾ`3ǎ;rH;SY*bl3&0*Sh='|}uu4 86"jtyy_zRWӧ[2~=PCCæM 6lpb3BCӦQi/V| 鯓{X>Tk bP5f^!BJdπ+u"лB8a۷ U4se<] p's%^dEpT Jx󨃟WRB -͚ǵ(3h o ng"B%3Bxmu`V B gk bjNY. $:* (ZÃ=SٯTTڀFƞg zTh#RFfBr ˱D rC#e=!P ظq7 JH} i Ojj96Z}]kj96ϺZQdA*;CQܽ@U'|2޾}ADǞ)R*(ǍZ +je˖p8\ *"qqzj37Pm=@ե=﷧o%c~Orb*9Do{ps"<}Dž\5Je'*Jwvv޾}ɓsssKKK+#s? TF|s*gL@$ w !рuW/)8 MEQT3V&R3YAQ٨$*ݻw?3,NNN?8&GZ-5 Qݨ 7+lzorb*BlJxQNjZE}h~Qn$dMj`2p~{ݿr`ͳ6$n?;X8=ڨy"ǁtvZ"<3"xV*;1G'@WG|88ulL:u0>00;VC3#qk4}82GF;@r.7g=Ddft#WY$P+*WϨ:MY|fg)]s/N;+T.Dz8us艄.ݞ)x=^ E"h4vC1>Fiį9aǶk5NGT+uwfbT'Jٛ/^l>~@Ȼ"4Ay6odĵj`O fwhW} J2B1CxZFUuUJUFe4HN[ Y>t;q:TbȿⴃJ஻'r]w)hCݬ8BZ_ sNN--b C],|rwEj]wݵs?O}O?pŃפ;Sy۷oݺG7nܸ~k7>w=zԶg}6C!kFϲ[w9efn B SWƴʓ:Ԫ9,wB!P)|Gk Y7G|Tq(=.]ʣn/ABZPLq$ O|VA3 9>o9B!BiOZvnՋ"B!5Tr^ԝ:4xhkB!4R;Uo]O n۶M˿h4 0 y!,._G)/*Uv:!m|v늞e F# eٴiS{T~~%Ī삋O!J(snOQ\8e' (%VejN!TYY7dp~˂~%ĪW͉ * yhS7=,;CYq^ cRY֭97`-\uY2cޡ; ՠO<=Ti:-81{0<} pupGA?G {:i,B]23b=#J;C U{MVC.70F|KRBnB^wFǵh3kb1Y|R z~(/=#RIQ=m]ƞfUһB+u(N/:ba(yNtxM=QWG.稖f/x7dޗTA.x1{F?.^?u=T; v7QݟNQ%o=la-jl=7dcU''7<8_<#%gI6jZąMWϏ,?D{ĕwXp)RisXl?UwigE}2+,ץ;Տ}h8u$Usϱ_>#JʤW +~V,wϾ^pN! D$}]3n=M۩1ц6I$<֖?)wrرcǎ)e|T~o~cuuu?xH᷾v^zv@`Z 6}o-l^S:E%>Ͼ ( NP7? ?kvXpEAm[r?CY9_?O&R8T9[?.˱>{y6 O\XZ!.x Q"YD2\̊Af<ޟy˵VAIlɕUR=gkXaQng9.w2La!w8vElE.i+ fz.Ґ#G8V&voKw_kn/KEQdKg+zg͜2]̡ J=!k]:N=ku6ޕ5:pMKCɓ'[N<  ee6+S8}Q zuϞTO'ۥz5 H]J>G q%'+#9;y?A IL`oM[ϾitB(?&92☍xұg<_bgz$GV{F p\Z瞣@ֿlĶ,p5іkW.ö[dv][]K!-w +9Th(PlȂQڗ9JORRySR'R!Wt\ܱ4){F‚ߖI?>ɩ+S+rtMY}.⊮mg[3}}}cuSZWVVx.T#2j/l,mqwGkF.ǜ=TԻ'TQ+@%vwḅxsa%`Ϟ=ԚopQE|<7Ymݢ!^jU .%/[d wr~r_?bm g?itB(oX3 IVB>sC<@_l8ο?ſ9?=?6}~/|+ grxVW7yKvV(x^+8-`y0:*6 9q~K=D'H5(wH**?M^pg_4y:!>.:)|ś7o޾}֭[G}tƍׯ_vO^]D\ }Qێǿ򕯈O=>C1{.<;*1%VeN!Tȿ7V񏏲94Ҳ3˸X]*;Q@POO)ͣF%ĪW *:q__u=mVYr/^{58J /xU'S^ j?fYa,Q9HSOΪ8~uٱcG]]nB NGi/*tBtd˦({@xfg?ٯ_"h ~%#P ̘B!j9mF!BAYO… B!X:xduB!*!+(`.R.ATB/Mb5ŮH^v̜=3:̙9s>Y/ h9 k$7^bg_qY #Ig`3od z.)̩H[QeVF#fRDIΜj&hؤl^jyqVe}l%i;K/_clعłYs BN]VF`V]~/i+9sl"ⵖtoyyɥ|·zNFozun^eCی\Jg϶41:pw,h"?љXtֱeCϹ:9ޯV)9sI},K2|NB[2/jʃdȍ#uD١9NyR[I=u̩K4ad62jdBD\f>~:_əSmw"\KFsPP;2% ִeѮh#6 ^l<'ҏ+3x%bev糒RӡZg4ϖ:tl\k]hɅ~f:Ϭts:2p1e-U2ehFwk+V t<7 s.M5cvÇגD#g|f:GkKM &mS lHg[F'zGoL΅)9sL$48ZKg[Gbg^ h(x)Zq*OF /4kْ};.To39U>/#Wwngo@v5~RCFDD'mWڻ{Dtͨ%YOts~pMY;, |<'r3>[>FxAwX/31841cgnkn-wع͟4?:Q5;Qt쁦PYه"NC(-I|[*=E?/W^|"~dp@1#kL/LL(YO\O8ƢX^[":z󂩩e sa.=ͧVOD⑓hǧzN.LP,Ϟ9p !]=r"QmAn?tiqmJ6pd 9 Nr޽d2yݧ&g'gI°F= WƯ̬a5@-yF:UX0'o;*]Gbm\':)˪h f<'a^yxəb-YX=z-t>fT< #_F;'>ʀ N93N%(^p-iWm""Z%i3Ykw5/LrXiQK!/kz!q4ln6 t+"XZ~s]M#0XzQ[P"6,WLE,Cimxn5Xrm,S*oG֗>s[|,rؙFez2ZP-DlWς-wڎnyŲyz5-ڬ;i#|ȪWc-㞳"@DKC0E[;qj.G͎HQ" |!{&zn ;U Wζv'"ꜘuP?/Mnn"kf:KTdRˢmhx{Jg}.k"Mã}M-ԩWCN㼭er8 J\F}Qze4yWy}ӗ_"Fe wux4bN%Z/VS]-Q4ӫ9%nIj+ϰ Is͙eUϫX&*dÇ[[[ۛ?ܼ;wn߾=_~yԃd~O<#˖x"Pw@^!8?o2rTςwxo@KC?/믿曗MDSLʓ}25 k_>3V BP% y`}vb񭫐'oL"|s5vlߊR$d9oeDY2PHO/  Z_?r'MVj^H&$pLZ=KQ((2 s@Iu{䳩0f>ns vv 2rC$)/zX:=)s=,}xR'\|3kM5^AoRkNL!PB^onN{dPkf&|gaSUȭk.wfK8GUrI0C"Ó+2|E^p4)saّf嬡dM;ێ9J2Ɏ CGWb?R]Q7&āTYfNG Y;7\ɚ Rq53WfvCf?M5NQ3K{Jv(zx85- Z$B'H-}1ՙvS:A:GV.TI֗}y,t gv9SFm.:.P؇m)$TQ&}RSN}3 e%6#YF>Sj6_Br4YLǒ#5*ىcM6 +{ըl ZU/?uu(&cb7̑]]Hʮ+`{n=(N<띤^,-k" ۛ<)ɹWit~wVdmVʶ{>ퟙȨ];a-AA_Mvi}`0D-fbjxl(`(:p,QYk.1 KWDJKU%NP%L[$k*'+NVop?ōLtk4E<7BWL{x3E[ؗ+gKjUyVr;6]5Bj|tQԋw~&׿#K555eeemZkB{h~.'񬿦\_ovj'4A y˳bs-mL)](m~hUHX@L h}޿!U[CP%<%BDWZO3֥=6b6tb 0)եh=4_J]&>hׇ+ MګU|2 ǩYPzi|{}BP0,++  ʹ1o666vww~͍G=xݻu_$׿9i^UcQMMbyDW,6+. PJF3I ƪ=YR4yNEf^;=Efin-L+:/+/= /ԄBJP(T^^ ݝE~T ?Y3555~Re,2J⋏= Ï?/O"uy%^iP(TVVx##%I  E(//e`EEsQ3wx{/ \FstZ=3@`ww h`f0=Tu*__3ˢ-f\@gKQ Q@3JR+ ;;;\=3M{e;Y3w?Y!˲OR E2P`buQL|{RdY!ufר/BtGQ=L3-LoYx1hY[ 0,~qw@$6 hn;]&#P`u9uuJZ'%IR"7ZgFwvvX?Ou8pAB,3;#p[&v2|>2w9k8p1rn7 lŒ' x@n'X]MMMMMMeeeuuuUU yN 9p4,sQQQ^DIsQQQr@ jjj+++O>}1<9p;0zǸ]tttLLLFbbb^xuO,yGWoӦ A5MSQ!δaѻӧOUTT<3?tЛΪ|ԊJX`܁ٝYb#llGzD d FǓ!9vF_5sNʁZ7&0-.9R➚[K,  X((wPB#ZXGCq1`a0,+pd j,PfaL1m>A(d3|z0.\ժꫮ!.( p*vKã @sYK:+FJe(%S%w>Y3o?tP˖-.Zq;-r OOE:sΝ;g̜U^iJKgnp]g:HsD0Kk=Q]Rd!S5E[z}{"FIAy4@= MlWf9r@_CkAZ *(%jI3!T5a ,C.Ru}yi.0\bZln9i2EHxFt3tTp;8ϣT'yT`x5ëZ@:kk"y>s hh W e*ɽ8 uNUkjWJUS $O֯]2|xY jTWSޝ^7.ω~k19a#&S[qۂ[bS*T_H 33g-{fI.]5jzwQQQ(\.69k:Nc5g60~2՜A)OPE-³5<ռe Cfu:w֛9R2s4g;HԘj?̜,m)U40*뙈@*qL02&.\b8- <z{g mZԟhf:#P92wPL Dd$p  e/AT̜r(D>Z7_4ښAR~J)j}>P󋷎]xZJ{}4gu>N1ZWl|؈ɂ̭_L0l+6UU(%bG T:'RhP*k6C0K0(5 !"b]~iCt[+&:hI=$FPn#U؅ HC gxQԓEZN!y$'[`cϩtZ:Jm ¬+P.Wɤ%s" "篪20 L_[|bCZBqJ_mm3 UDiA~ ibimቸ ;?PAL3Qxk5qQ~TX8X<gr5SG-,,C; FLsǬ0y6~C*D!p@E:RU5}[6޶bc,w>T|>g0Z  鳳ve4nܸu{۷ottѣgΜa:#PX;"5D0 ͳLرE#J.!1 ŦqҔ|h kl1دPܘ&he4x_`@;2̉0i1' Cc{=BirnH J 4& !HjÞi3'=!#YgNܴ&wƭʪ:溱YaݤUA|M՚-ۓ* Ap:3OўSXg48u +O_F,-?~>J-x3OAEE=IBNѮ=1h9ViٟP2tEEʏ !)(#6b=\&N7e ĺ0SB1XnȘ`Fzg;Nmݺȑ#ロClRzjud5r{OL̶/;K6m^p~ 75ȯotOPYK/psLRݲU[JKzp@vU+~jŰ[ ǴD7~U/7m:]\ܔO!!8W:Ԧ"N@]!'-mش6YS^e=3m`ڼ~X 0Mk #,xbyyiI**G  /7c5oCQ7q؅OhZ]S]襔~x^c>(bw_tMl*mލq;m4|T'CC+f1SH`f_fccjV 8vq*> O"%!zY)qB!tD(]aH=}GSf+J+N=*8)o$ Uǿ(/? T!p.Bax.3TpǓgLWOv J| ~ j+T m*O))X.ɶ:N|Bމ#gEyuM߾}z ~R+A;ys Q5v`vo Lk>)<_ Ө+ mc҅~c5W\~-%%$&}\Y\/%p~V~spNJKKX)z#ϼ dΞ>?{##KnMx* rvh͐,+5g>Ӭ?Qumc0Nlkߐmڼ%3O >%̛9Ep;},xbys xu a!:*Ɗl3.޸&>8%a蝠 jcj/K0"-AAG%'q.=XļJ"{rޑ ,=op+gSX0`lI@#DŒ~s# t|ħVT$b \gʼn)FW:AuL٪Sf3bݣYE{\ǿ(?ƹԭ[!pn|coE,*i"KOǓ<"QmT(T ??OOlhکٓ"M-q,cݲB؝$g={Yz$(RR;]qJZ'`w3*',B,NJ{Z& #KE#'H6DQ+~yy͚BXKK6G{:ظ&ʫ!ߟ~! cO]FavQ%$& Ι7cM&v#2>f^\\Cnf 7`DyJ]j' 1kK°uD&pW\!'%Qr 7o%#w%fہaA/3?ʹ)ߔ@xe3|uHg8Qa#&߭V?: D>^PT俺:ﯾE޳w%:$ŴOn|Iyi sl}״Iz{eer79ޛ-_Jw}߿Gm{_nPVZ6666Υ|OxﺙCj ,ۼ4qLPVzBHډuNj&W_yOP^R4(-0l8ƿ/wB:x`_Z1 . -EvkZ볝;ғi.U/%D%JKғ\INI:!*߹sKŭs(uwmӭunI}6֦:X(,//an4odܽMMk [ڼco|ҞOUqĘ위$VCsϜ\QZbyA~ ╗t ZXzvϹC_p@a ٙߥw7ˮ `e1[ޥwꕦdOI.W~ wLulrlXVn7^n\ླྀgwA&|29wLzSF~m7, U¡}jvmݥϡ]Zysquش&OזsعeTZ ;&̞>;%vAv츸K kKe@4<`G4DFss&X: j25P|p"(đ@!m nЂ.RNlFN(-P huEn}av2 iA@qE!..B.}{+gQI,[D1wFm\p D( !LN F*}ݪ]vv=**݊Zdui@ Ie%Kopy]_*o+u9u[+yT΀^JK 9{oi~me~ vUVZrEm*ںWW{%=ZJ]&ye7ثkiiR[n3r/_f-j8D WCsW殃1)pp.cFׯZ ^}_"8߰QIQ6!yl%x 7PT6j3YVZqQ+Koz\?Xڸ&ozfM;*tw`Ŧ5ygMWG1~c:zv_lZ҆5qu޼6!W:PHӇd?kJv'6iK,O}ӣco`ɲ?T-|b((-ۭ-% 0=A͝1l\;8з[KeD^+ʼg4X88_ysgL]Jv f`+X7{D,:%'Nv1+>GZG ex熋k _xOe/,o aT̏J 0q>0[.DX Da;PφC)DQ8at=_vTDz.7QJ=$*^DGo s'!:Z71(CbUbh(14(7ԥVӹ}AV5سgO^^[ov֭{sO୅YcO-1z;NږRRo%iS'+أ3?g[o%[{״wM{3 9 >WVZs}sɃɊM#n~UkۿgU@tvu;w읥,+s>3(ѴA&-!!W^;Kq374;硧Vlzӵy:o\7w.syW&[2kڼ94;EnxzHnkI䖗ȳ;:Ĉ~i@¹wLpyF[޵auw 57ꐚ^^Zzdsx缇"|0$;g%].d#c׷1}1!>]Z]}À-/-}9ŭ<۰z%tYD|~'NU@t).ثm6 SfzB0cl' ̙ʼWfrJ .g>/A)鐖##/}~ب?S>[䊡L)>+Y!ϐ ~鷏-aEoޥ~I9)>;&k~P)E/64u) Q\byW͓ZL`h4EBHyc H(&Q.W&DѶVS *3s\fW${?EFFVTU;P(Tqv}Q$*Z ԗ2Zks[¾m۷C=dɒ(t2[@虑ܓnX#nIHLja=[ tLMu'& ׯfiS'e (m;Gaff:0@ό,P@-dY I^]᧞g)N=Xj#Oy঻F_Ɋr>3+6=Gy-dVodrظx1wYWd#/{eʧ,\,c' .#@@X,7\|Cs6{I}6;uJg!)K խxEO,( \+ssgL޸&Yۭ,ynw0MX۴6oѓlYgu0dء#s@!%c"T!c)>zeCDzx/94;gÚ&BƲwH6b]cqCnX+4^ڳmlip2y%J(QV<7o/$c]֐dHv1sZ4vrĎ`*ל 1vÚ&M@T[*H˯ocbSuֲ^YjCؠӈ6~ WPGީG34No00 } &Z=*UM ]5d u ql((n7NGրYTOtM"%R~Dqq7m( p sn+:6P.29R磊Q$HSpƍ܊ȍX- )<9+,cttt˖-].W|||ӦMs=:u݊qpˇ Z?H߫[,ve%%xuU^oK;e"lόo˪2{u>Oi,v%%xuu[wy6<"I_ P$$&mkm\\k^~Q|yA%7wss%ǹ־"ij9.^M^}-<&4l8;}"x.Ё3Bf|bOxmJ6JCxmDWj{mʲ,WθɻoRW}N-: mXѶ moشumݨ_dv+=bF t`H!c˅1].'&-\ NU),|b*"8}߻^ l ُ1M3.BKK.nev٘`-=dui > !dvӹe :9NL A0gdV6~`~KO Za@Q[F1; KkzqS'+.sgLf%eZ(ՄIO0^S\+ksإҥrh>Pz0UUa㉚ fr'aw51*S8Q H "RY>ФYW8z<^xQ^x<(vxk.>r{Rtف?FEy<(&Q^',4玍s74u7u7FF5mm抋s)qq8wl;;Y'.i446YlTl'6.&,.\r[k53 ֊`fn8\^~ߕWWV-[o]Y5U}Wf^wEgZ=?J+K+K+TTYֽ~qi*>TZML׸EJo+|Uֿ]OXa_~Rg|^bg`ي e'Dl鲥RJY#>۹idj,b, F9/?Ͳca}\ķķx V?~yL]9C/hWWBvo'K{d 6[s&"Ĥ!9fN峅wE17jGP&d>V㫺o2| slc{U}fޣ:3td;wX Q.F&?.zm{Z eqd gαU3Б9_XYv˪2tO9x(-_'5!Y1}%95}UGo7_7=}DZJ[JH&]0Co+7bx ݲU+;Ļd*g7Il\mX JB=8.>"x`W2_qѾDWYn>\Fsvk욐^&4둩]SʸWߗP4OHqX2sgNaׁ8t`}*D&wL=vެ)3'Ғ3'%MΛ9ݲݲMnޱgHvƵyΥ?@ަTM0 ƇPzi>0E;مH]YID^3a塃)͹c"7Dq(}J|#JiYɉ'PJ,Y̮,g;xۧhӰ YJ!>^U/Ξ~+ 萒Nٸ&oNr,/0`ċ )ŐqF0UVX6e'>SWRۢG _*oF4RQSM0PdgϴOC\{h!/"GճW{ܰ&ʙNɉ ғҒғӒӓ;%' .mƍ=Mc:%'''KL=$%'_;K6Ғ%KJmV ޴ILӬMr}em۫o~fgV=/g}{M쑕py5\xE+%أoRFߖ&תGV=&_.^~-)?+";> |`_[oe( TP0QDPw}%imx\JUWNIMyTκUvҗ#nYJ޺Uq7JzuU=ӧ3}jU?\6|TΫr{tn'0 {uUQcwMpTda IWddÊ&'O;y|V-:%%6X\mбXޔ$a 5ޒ4jU+JNMt_j~]NF[jeEGbⴻׯZy MײecEى )i3S҇qm^KE}sHvέ3kڼn?6imޕOLbr;o|tYSd5k.H`Q^mNY$(/-ٸ&O^_#e2`qYI X6ok +~luo%:tL9g5fNNNIm!QtїfK<<Iռ׷69#|6$;g,~ mn:r܆+M4ӺK͝1i܇S̝1)mpm:ԡ#s~0O6ٲ+'ϝ1I"aws6!kﱚM[Usj(kyB3&ϝ1y˄Oe3([iZxA0Apх ]5CX`n~j<8sXP;1sK)~R>wL|R^8pwS6 y_[mv`m#O=?񪴞T/"~;wEFցr=So]JN215T\Il$&A"jؒK}Vo`W:r}&2 `S/PUΏ?fhg¿xvp+fs?]n02gKQe`ϑ,i 1y1 1{_VZҮCGy9N&:;x-XܔFH5@ar[͟9l19b>?= !9yBR7e2iwEfLp~amn1VdBަ&+!M=Vdq2{ n:re1{彲͘Da^yo:+mc5]/W"2wv<1飯yaW-K.p6nܸub=z̙3#GD.v_ʪXFVl)9/.!QE]eCb\lPum(|`cM…t6U$†o,(&2'/X=,wЁBce )Ёߐ 䏽qK3;BÏ5(F2["*@)˳0k>d qx{U\z?gʝW1\erdR?V OR4rifD&d Mj,B.Sp{D섞TMsPI]*LACyxv'(P>BKj[:}#wOKY='%%6jԨ Gƍcbbbbb^ `)M 9svհ0CAoQ42lP7lY B US^Z]LQ [˃*o[,hډ"@!c' r MOb @ʮˑiOxunL}n#Ri:*` a  7ȓscne@(/Ο1b4OlvOfgsӼ Iz{*N4Nt;"Q)&G+ ʫ%ɳh4Q3< A?Yff{I)L?&w"&=!B㥦p? ӾgxL3B;Jo&jRS[Tuil, @gtFbu˂8LHd 0+Q! dsn [Rk|jS+)cX"@%"6]X ݎA}[~ܯ§Zob4G< ,h#F .YO2ns;䩦&lmX _yŢ["$id[ȼz)j2jwˆAAwfh҈ݘ2 ?_)K$jhz@ _m͸@}Cō U1-'EFGb˞ǹ7D;rc ,>HQUh '-28s˳F|5}LchXhbbc X2!z3² ޓ:FU/  0A"a`sR`_Q")\ Rl"H*~ngE;"-+즋62 }j"\hy"l\Qk 5` /`͊HtbdH󖳜 b2*Uٓp*Za\=oKxCP z}֖ƸI1?pY\d|A[ƟzAarpPEEُtbKӲkD#2$V2ՃDVÝv&'fMPyvˈMTȏX3+C m4 KjeiM%s/Zq+}LaVV=2VLSr#vc۱pnƃɚNXASbm-ᕎ$`aH IDATk ^ƿؐh4 6m&YDT ]O59V)S;X>WF~*VD55iM}džF|ڄ7V[.9fԣG|VPs<'BOPa.)Z;}@lB"}#9KAMIfs% XS=2ʠ>$O|gX9"L^9* rgUY pi Ҁ#ne 8ǜÙ':P_m]=/@o+e) Q͏u~C Ö́dX2XxQѥ#K}~eRl@!UhuM74&8p!10ǟ7 K E5WS8p;LsuGBhzgi98pY_mb4+.78ppåX,%4u(R9p4,䠃;+8pBQ'Ⱦ9+@U΁8p!A]V wQ,Rs8pABmaOSU귚ͿMoot,8҇2=c8K0qK[+oEw{PS>0/b}eX;ޟU7 fcCF*O׏(0ܚKQ;*6TUHI-SjsVr׫FUdJJۖurDi_TV[/M߹n\n:zBWR. WMǐ>ow)Ujȍ.h} -?kX'D@sa+ᗯϸ:u?G mm7ځ/0}""qkEeTUOը|c5!z;Cm͏*"=e#R!l-TU-܋l~GzȬ+v=2x&<:(Gu~uo(d @M{.?֫^|Gy[vnEF9؈^} dw=0iK߯5-kZB״aX߮4^:b̮;g!-o1ۋX?Nї&|y>ڸ KKv.s /#s.w+Vnkq=-B]˙Q!0 =<'EtyXX ʽQ:i+~s2@uaJx@pVݏ]9?'"Bz'}u7)*[T.X,5?{R|[џlC2+zeEo֨EV]/z{ޢE_<@#;4M {Ebtydh~V8as0z /}?@Oo5v;XJ`vMd9WfImhϹxҔWe\[컾w4oyۆ(MVAYM!yEV:e޲䭞R8os~/_}X@Ls.%5KO_寞aw'=̇6bm`<.^B?\1@`D,NBi9tIH^0-sm7MBk&Glʂ^mޚK^cf(>Ru{_={`ogWBGB>K35?\1C`]o&"m7|Ox&Iyiu"B%e{C\~%4V.$XQ1Foa*wI"v8.bڻ9_B,b"ngr4w^6k>_7>㲭# ѵ]R(&f7MΊ99o}YStt͐fo\H Щ};-(GI6ڷ8<<'ZŅ.65!mv|bj?93;/f^HN B!YҦc'Q׌eԼkIM cqwe]/ Z\6Z!h4+lSs Eth5"UPPo->8p&iZJ.&uP_KFSB59RL&eRB}㯍ؤh#Gҭ[d^gH PRSs;(Q2m=mJMOvW'Ht(2Lȁ}PM\Da|Z-r/Hu =64 á.$h!qգV[JἭob8p 8M&P] &EgV2Z,-$D h6ɛajք/¶):zp86w_*t]p04[wl`[;#?]mwpW&\; !c{'omCrd:7eҶ/09zizY7kS/NV?UE5GB^q/rQ=YB+yZ 5NX-\4->8p6B1;;-~n;Idۥ=8⼈W@Iͥ7f|X1<7~Q_ 6w_wT ;mBU6??J]ɞ?s8ѶENsrMdBio~8m@o)2luRZ;fY~#;͎G|xh>{x(9GA\>BjYɃUہ [V~OիN0vƒXшϽc5Tx$;댹?bY",\w{{#cEO ldoT}&/etShn؟h鴗[VpYEll~Oo_;b%aLZ8B~0AӰ †QL ܘٖm^s&̭ˊ~}7et4q-Er>-)6KOGq%k sGSkO4yebU-JF/u4 9f B<,Iցy[eMlρ4r)//<{lEEǏ9ϊuH `ֽs.5N\َ{FTf y~`# w%=)))...66QFM87nz^o;v8u2NzQ˦:[IY9p9kR# Yr;wPvzڹqsn#?|I9p9E%a$ӆ;hp/π|;?_Kρ)^Ep8pА#xמÁ8p}]:һ>W-8p XH>ϱ],9s?y/yE ￴" olll}9pkxZ$Ҋ8QviE N^CX>JVa8^]bb&)~pUA{H&\#R.7gP7_N6'_5?7@YO~1/wa&gxJO\#o eC w qA;?>晦1O(Օ.(?w M_:XJΞݮ Zg.O7oSqm~|5g{dGJ'~IH"Bm e(珣_+ [D^ֲ m?VVWժ-%>F#IT Wty]& \8p)}ѣ[n]]]Kb F[$ C.mZ4.=zK{[VE{UjrWʱe VȅT^?~?ԧ.* 7K;ldu!Tz颲)[燋RUURF(((U].(w9i#~|淾T|@)}k«/u#pn^pN2w0ZSTҽWj޼Y߽ؓƴqUU':(SuKKNsG *N7VJ+KO2]sjOSqщ߫)↑ ?%6oF)(ʯRRT#wWq+^x7/=id'*ǁ-&MB;7`jHϾg /1lzGb49*TDžW${`]6mӢ-M3?^s55JhFjmRL4UHm-SWn\.( \ 5gYhq]|Eɬ&>\.E7? nűW}Lb+ӴPUyg͸}ITVxıu$5}bMU *(EMطE|gX\wIj<;XϖWEOQ\qz _mCwDȻZǜ:Uǔ&ڵy|>.OjDw+۵t5Zڪ}_IƌSUY0@ZK TQ<gY?=vF%1q}AYVfOAműt:p@lf`gϞ ߳fcD'&3ҩO<@p;UDž"V۷kL/[2~n3T~I@߹qAs()Qiu5|^OE~>?~T(..nwB봲/ x4θ5~|*~A)9![o<.|F Y>HfXp)[J^P ?P ?(T _ْ*KZ>>mI=T~rvS$Y3SJ}j{ŷ])N^)NJݫ=e ƻ.J.*zq]-+Sʼn)ign1+Q3\! JE!HtAM^cN%'6?=_OۤpjOk1SOOKMkۤVH]o(!XI61333 ~>x`vf>g>2/S-0Dd|l6z׺93Ϟ=yۙ3g|ǎ??G}^فaϛ&>)g hbby8}fNL2ۏQ${uqA.1>o`2XWV\ n`Ry}G/%wΘsϿ9&7 &PJP >??Ya1M@1mOdJ:Br<0&d  I~uJ=DhѺgɰ:^x?~yyfͿ[S~\MjxDx%DRx/ɻބgNž%S+O<$M[8jJy7&n?|u8s L`/y_7,ˮ> [ kncxc1dl?>pqώf֍ˮgO:˿}wpx|>_/ə3<8zm=0o7Oz'<{SdSHJx C0@ `ĉ=vwd/n?򽇯f#\^hW#{EB_w1LPQIyh0>Gd`` kfm}L/ ]3K @,6\S`LD`*j B7&A5 D 8bl[~o9 }`ڊ__?..xnkVx^!+3ygj̜m>O4'||ekzE,L/;K)0/u(00vO.~ϑ5~KM ,{9(L0oa_y7֧Gq9ku{mūoNss4[({z.lͺP驳 f~tb2^?t0S)}>3 퓿YqBHR* nƝqxسڴ{YI:8 n['9xw}&[M4*Ϯݏqbk]%it#!(,~|啕7J2N8ڦ pߋ{WީZƦd4쇈$_]o'qဘEvΕfo ݊γ”dH`+ 9XL11=oj?BY3X,pTl,!#. >kʠ7,3Ά3gb_v~w }k_<8gQoh11w]v{^?HQR^DR֮ZqvC^S>+g_6S=uzo]b!jOで7&[ܵ􉉳 JDop?O= pߍgѽ[qoƬH0D̳t$L;wI㳘;x>8?$R8}f}goyf%%I1T]7~[|+aU_h˅{%Uĕ;_ܻ׾ JϾ_Y&mTvdF(<~r৐G ^ |gsէ%`릞a.u;/d[+)0&!t @x<S󑂳;+|z%V1K<9H@;a;1; `"C} P7Z?zC:u(@rNީw]`<JyOD륃 =D]uRb6AMC0xy{M wvpg.5!lf^(+c?ƓY_sSoo?, Ϭ5}|9ړS/-{7}1P.DTGv<7I:nS~.x֣?ꇣ}9wz>4dF;C@}@}&Zc Na f&>rbY,<3 ĥ w:Ux88p7`ϯ]K̜?}a Nɓp,L&cgΤ|)I- ?lW?6 Ǜ IDAT/ϛ&1Nt@Hx7kAh\޹}baXucsqjOႯde/H"0THi]_6>ߤ;qF$CM~OL܍7q\w7˛\uo>Ð >w4z~6pc3f`.3g[π16avrۦڏ[ΏEu΂EuJ6[nu՛v9Jx0{Hgxyqu~n;W2,^xk>ϮzDT+F_Gw^{f}%aEךgDC̜;.ૣ0?0wC͉p~x9qf=^zqQ`(磘e0;-8n:\3Ѓ/smtûH͟cC^ !^yo9ϼgITzgV <'~{^#k#ğW0qYg8p_gĻYqR!E@9T <\}¼ _71(8\p 03><8rhN-&HMIi$5&$𹽑͚ :pH…AEG 0`1``A/Ā,K=ĔiwX֓pu33q}oi`ҥ#@/[-[A{is{W?y+n[߂_|3~w-__eO'(<B p(0Rtə0 ,NON9Ab"fAqŐapf#Aгeɻo;NZ}Y ;6q2j=6z7 S!d GO &LC0 D` f۰{+U_]Xy |Oo\- f byJ~X0W6^~'OXyמZW6FGߖ+V>%"<_ Op}sD|7C֭>x{k82pXg^rG?ў_֘x$@vk=i-G]G ]Ër& '|ȹ w$88a *.{v'/ONe6`SZOw^ᙋ-0#6>cnzOެ?>q巯[~?z?Jyg+K_7N;o@𒪣Riࠍ-J\!ۯy:q13 ~^vw zi%bڌ=s+ܵrJlª '2{@QvC#KY7b6wϳn^-ImLyMS/p%Oէͻ>f߻v2p Б.=Lv  x<'>x'nx`ug=vݏW`|59UI+*V>`|)X>G2$nXeڬ9R~_/r? ҊiӦ޽[Px~e%`XExVB)(8sٖoow̌}o;˙G.pQm$3횩 0 &];gBk[ xݐ5[Z0sB`J_obr.a}9})Lj¹_dN瑪a13L8]f`IL//~ͤ4Nܳ͝[TUNH_ϹJh9䗟@y< W: enZsٷHt]94 /k/~[<=%_V޶?qWY&//}wozPpsw NJ?魿 3yNKG.ҟ|'$pҒaϚX̺l&")?5e۝}awDonDMJ@YzΝMM2s_`oNp /i;gP'+?a@DOHHMkb1c {B ﱮS u-)sps/1̆ŒnMJqP;Pi qQxVJ;} CN 2Wk,w^qUki 9޾ k^"B)S{=5‹-{G.E?Mȶfm[ל9a:yy\ }0!Xrs4<8Dngh34xsiSq%=Irr!<|ho\hɊ^wߠOJf"͘^q(mZM;|3@i/tmMvP_{o>5<?Ԯޭeu}?}[C1OErrJ_eǫ6j~Z]S|'__Z9w{ؚs˚|K.ODd /!gzzOt;N~?xi]liuK3dg.^4kŋ_=}lgD0R`ϴ)0}: =h2[;J>~xxMs}}U8@ud ࢩJTOcYuʼ %̓;NE~-*^y_x-g@ h1omy/Fxψ@][E߃dUB=n!搔4fF:p'bI:jsw3;|i )8(h&_4 yL?V8~~h0 BP<19m!a0sֽcQ|gx3?Zd%u(>9c&>/$3n{?57ߢa~9qӎ@,G-/UH ;] U\@ַVߜ`9?oojfH6p p>9ٝHv~f&sҌk}!êGޓW@<p?ֽ]+$΋Am@Le8rtߴ-먻X<ޏv_n2N9_?6 ik-aN`0ӝ@Zdx:va/|`ґ d2|<04= ^ϩ]@I`n73c {l\01w/\8o)ݜ`?|!q몇zX %]h3antx9_;{޻F~ $N2_q9 ,dWhck6N|p?x2LvvH3aᗯ~追yVTӤ6wdr/g=wrjbo[;X(7k9{'{b90 $40M!{;|}צUK :wTJDx~!*41H &q*NZό$Cϟbً'Sٮe3W_mǏ>;Gm© ))y9nBtlylG;ɩgL re= i!72LwLTywHI)z +nD> eû0y1aqj=L>pL )$0cj, 8~ /)ˀ<Jπ׭gӸ}];VK[SNjI6bNij|SLH}==gXǛٴ5'/ WLFF+FmJAl`!8JaVz"`"d &% Z؝{ߚOH[\yq5EQ$Yِ\[§-3-1Ex Q_~@lKTm_ [zq2[8= H]s50g%U#W3 _2i$關; x_l9DICf&ӱM}}L+5]&7g&&ibН:lDgsIls[0o/sDK87{yp7OY71p"mu87/$X@5|WE&':dXW$-]7֣$΋)mL^F " jT\!(J 5PZ!p;BAACu?QψSF׬Yc'Yz ܹsfbWTHB!n7\ f,*  .!Vf坤c^ZBcc-AMJJJzZK\H.<`XAAE! Uø} S)N`s.n4. í;j`e}Av|Z: @~S:nˮшȒߓb "r}m߾_zȑJzBw! oDD7lCM-@A}<5?okgR=M6T@mmiyZuXPm(̯ȏ( T+b![0gE6jm3JosK3H!+k7T@m֭yy_ 7&@eZ'Aka:۪Uٺvc%#}A]]]999SN> :-RR*lk?yzl/ dVY(~6ZGj&y[; ^A'*q(CpUlv =+Z#Q ʊQAh&!mhF|#_a6XlCMmp 'B]ɔ9b5"ȽӌZ9w(~?^А[gpp {NLgNlP"@hNӇ]"檍I+Fm+3Jᵟڊ:=l缻#-Ơal F'5LY` C4~ jry8x<^x!TpQaћ{}ZCpiKMxRe0 귅)kP6X T}DHC8 k=Z* q~޻Vʋ();U q]$3%HRlea5i}&+DmCkKKPѢaVRlY_*U6^7B5QF |=0eÓғ(P!/J{zVi4/htc}AC%Ϛ\Dw,j;dn&I2ĺpRM=ɂK~QYE[F]#F؂ WB?0$UdLxK=;w-_  ՐInAd4Dp6$]b]ʢr]4\,bTV3NWPߩFw5okȞl-/YPN*=Qнry#`Ý!]NE+bV-#}Ax"+!?z^vΜ9r^~/>x3|pec Ėn͚5ଶC9GYm-nik,z]%UY"܎r:(kb78m5Mw\/6ڧ$ cWsInCEퟚ9D˅+֐QF_\j&_rq{?0 ~zgҤ ovx [?;X.KJʽ3fLF~qV+=  Ew1be]KĥZ ʚ%*v@YShRtu;Ymn \ʶ>1=uU<Ay-U[M[Fu5lD{Dž \P"n[AR(N0FhL@0+VJu8\]ՈaߢkvVZl5m*4R{1Vk@kҋ:Ź*|jlߒ O3 ζ 2=GHBP;FH9g3nyfQe:xJJ=:/0#^~N="   9G t{ӖgcDI  -D+;CƥY[(kbGe]  2vPJ 1-Ҋs54AA" ,5(Z!+@AAbD _.~a lU|,l`n@ A k]EGixm($cjk'B`YuL (5Ꝯ  ѢC ֵ n/ ҡ yc8 AD qGغ(FAmNwCvu>VЭyc7AD"jAW,rS2A.{vMc=AD`X=kæ{gD9j1ıy'ø h$B gU)5Z-YraZK" ;5#5Sn+ظ֪F)my2}6[ԛ"J+4)8ﲭ!'b/%[@ s%JCy*ZZ" =K 4g@cj*F/U_"MA^f=8K@*tXxe3Vc(kbYeAK_%!׾(+/R;Ĝ:BUAgĚs}+PWl{˲,TuŒTjN[eIIz-0\%UY[PbVcl ZG%؆Zwj)%^Z* apʌRgq6#Gtc"ףaA+BQDfgЈa*3mՐQuVj6 Ӌ@jjR5m t:v6ɠVI" CvE!qQkfdQI]P݉6T-ɗЧ=ZGa|Z(>jZ0tTkvʇư>S7}4D~GbAق(geWeKJUUwΗB:e(6RURTp 5VVrC9w qQEY*qJ( eipM\Gw5e7{ռN+0.-&DE6.SJrlr?ca@f)otCׂ"]l)ҭE)N$ݮgK k"ȭ*C+Vj%{˯TC/ ^. F6WF LnP+ܻF6B:Qmm"rUC)1u'waPBVS.'[eMծЮ*]jQvLFfX(QPߩgT(cvMy*Lpb՝wy[*ZyShVڔcD xvF5@zzfj+x cp& W9^7%#R٬:OX\y@gڠCŝx~>WQQ:K@٭ܜ#4tV[!\]jᣫVVEp5lt;KU(+׺ Dy[):k"W oK} Zs-XنŒ0}DOǘ\i:A=_ bzkiAD Ji N+;ʠ*Z%,$jeMa ҋٙ[kմl)yr6մ[km5m\(kjbU:UP) #%*IYDAH@xB ;,YQ~ڹ@3,+^?*>I9ERMyYa  2%$6m[ㅻ! ru@d|T:Q}8uwIHtԍnAтH1"k%S+¶CFA" <Wxrxy7 c=A#F|lrV}deN! bgXA9dpVGk+b[[AA1¦ CLWp02  r F$q;AAg$+w   /I~XxAYgZq  BiGcQxv2xpu-,E|:B1H6c=+aA T ";#voX&{\{ HHe%&ȯ [i]q #2 ?彃Dh]K伻T]B'~؎B'Q!v;..[Z+QBl]1 #2,\BhqMceWs5"Y-.ivvmYm4EkSfQwGa' e ;Kg}֌w͚ *6䍊=AJ)m4]_޴V 3 ȸD2 ͽ \Ʋ,˲m5bI*9ueM‰;FL]] ဲ;rĀk]u[f˲lSm$Y\%VṺꊋeYh;3j޵cpQZw?7bFd\sdg!ā&#׾I9l5ms FLer@Y!pj3mpA\[Fsx&vs\6d[k`,<"OniW7I.l Gt݄&4=OU*Z)0ʐclCVo9H6EَZ]itʺY!O`荈C{k߲˳zzgXiw2dA"@Q[ȕ"UYM {RcN UBPV`/U9Q59 a}DOA+sWݟ<җZQjWn0$I2D ۳69fX6z, WVZhÄUbDcXEE MHzQchzQxռ-lMC&9YykzQsVs s:GJv?A؆BQTX7j NWzJqB TwT6RkfdTkpp@_2S_3R{$cÚ]+e:hPYG3 ~Dz\=|kU+W\,;7}m؝ޅ~ԅ*8$Q%$wz/?l)MW݆;.-i}+ǷĀ`W,P#k ,l֦Ԯ;Rm(Ըh}aDкvcAH]4P8; k::aR"x, HdqS!ؗ5֍i\/!<>:2}Uu(TP\-%q5y[Z)JUVPnK3 )(Gu啡0Zx7+QjU+€`$v'.p-DaŐq BvZ->wH~"Bp|Y똜h3XY%, DȽKNN.&fw̲JV!]tS6!gQ}@)tx9BLqSGvJ+:4#_lB:͋&z_QGfVڙ](Wam- gE ݰcdxy[)ݠcuSUEMBw\ɉ: Fe{| 7L4I~0999%%%ZSmG J0F"#(/J/4IQ%zežb 3JoPM >""^er &mX, Wn0 +?%QW/ ^B*FAt",w';!/@AƘsWZ5q"8;gΜ f/]v{4ڨ19{AôEq c( wEF=Sy[iLE;#3,./XvA8[!A_,AAtzy8`@~~M8en4+gଶCSp5VAM[\V"z r#.H'Ef Ȼ3[Pj.ɭR*̓Z:e7萕9a mc=ABm;K 1]w tudi0W ؜u IDAT=hԡr67ֵoCs  c "lFӾa%/ so]q*-ݒAAd Fvw o⃳*Rl.4 `v:~4Ws)3]w'u]v\8*k2Z#4CAˢgbTEwa笶ו5,˲MYUUAlA4]fJkN[e,6A6h̺K5mfښ[% mt|mw*F\bݹM  ȘS`@Su#.մp9 T0;AW(Mϴ8%v|ayZ*6+k*O4BWy1kNv  %b$Kwr)2\ ׾Q*\]eEr'6qu/:(T68BKUpTJY\ T*>6c7 A*HeF^yQS!W*8ӬH٠ReBLg rR '$A5] ve1 ruAjgWSh)KUJ]ؚT)hTw"`ydZT1ZQb]^A!$*6A CwaVEc>1ͺaa˹  ;RBlA]&?\R =%'?j.)Yu{ŹX7OPcS9 qRLs Rc/A/6;m$Kpwh/Pɬpݱ MTK؆R;od snw?'Ɍ}E7=Viuf/}AnuG-6WA@yZ+ YV^KUtp9 hchncS`;AOu( bK9o*9Qq@4 }'k @ﭞ Wa&B;d4ޮAJdq.;5譡-[[d#@ZB2J3P;!XH =Z_P[[K 2JnM~/҅,.;%c0O؆(C*4E݉IѤT|zh(\FcX"䐍n$"[C}7{iIcl ΚWA+ /! g$X5lCVlQ ;`Oa>P2У EmP)[㐒HYe1_BQ0bT#( 7MWJeѧQ7%}Tw9zr\|wA2 z0/dK7G--CE:;K J:z+EN DQ*Fmt'tUvk/6|m( 7=&軚X \WA[oJ BH]W&@] .$P B A$}ͮ&m GYT~$dd23FY_#/"ם_zj8Z,h5M1%,|:T)DSd\^ /_+kS; ~o/_xq}}>=/={۷_۟g{9O}"4"7nkWx_ۯB^}[nݺu͛7oޔ]ѿ| _ww~#^~>$zKo&_K_7?{ߨVCڰ#뀦QKLJ޻?G|Qs?qc%d߉.z?}kc}_oKoݚ6P~oU˻߼ h>8zoq_/]/O&J}_~o?}|)~\h"3T-Q:w9 \ݿ|L&^ңqwy}{oq}G{@>;Go^g^3&Se姫6X_4|+eW,Œ|bӏO}GFD+;?J]TbʕWd*r`_£Vm N5^l|͝wsϿ,s};51T`_.&sh/Zu3K+|'Ɨ<7?>y7wޡ'=~ }_ٯ>56y&Z4یg-B"z91o;?5;~ۣ'/zuyL`-тT#ﳗ߉;Dy7!,w>K|( /cěG`]-8CˆDoϹy&.XdNUwXgTҊSgˑP MU[4K<2N9Nv[E%.vƷΏq&=MӆnMo&T]׎0hlidZ: ql")L f8 HRnyWb-.;wص!l'Z╢S\Z@-g69gNl4Z(Z@eɑMd).Y[u!]#~ea TahA򂰐")Sà,>i"C`xiWmPt^J4ifgD *(ߪP^ylk }vྥg.":u]߉I0ꂈ DtF8c{=4M"bGX3Z)Ipo[ƨ S%eG+U)f@Az^ f@,'5p*+),j]EsM"8U"b6x"">I>􈈌Q,Eq937z=<<ܣa2_qDVJ-$VD;d̏咨`֫P&&wN,،y-ojV[tkZ;I%1 u#?Wcu`w<)c$1Q|tD6HѭivWm鎗#U3; Ȫ`<EA!ݺ?xys6ʯt&=6 9LY+ ,8[RU!a 7}3qq0 "5;<۷r%ڑrWRMoKd.mvq ɭ`<)X 5A?.pE7qT, *XKX4RREr1MGxI1]ayȷDsІa4`SR땪Ԃ& w|읒-Gv9z*L;bwk\Qr"o0K[Ӥc"&3؅#='Yj"@yM1Wrߙy,@@>2Q q z*(jRcqIT.h\*X%O@hO}zk{=o4OD_}_o߾}W^Bȫz֭[nݼy͛mm) X1ZvVEb,l1X!l>,_ĦC^\u2TYAg6ˀ>ŋ<,_Aݦ"41Ѫ,6I<_^{饗^"Wni^9~=+CU V,Px ;wt&uݕ)/FX!>X^*5 yh`y܊ Om{VW@@8Τ `N[օHB \;^*N}6< V6 f~l@DdṘ2Ctk$Rre[xv|n6a?lE"˻p]|-=Kޤp@$',i6 "슒N!s6 yfz?lBj, 4(VP >GDIDE2[Nd;h%&`Qw6QxOI4낙[wDK@[w>{`k)9 E(ypd%wa}IӠ}4; !5; U]Q[d$7(y ]5`@{/ q\(h&am!\(\!ȃnz#yn;ҩv`ЭiC`ؖ#0@#k Efh4 "]!-SO5|)z1J5wII^ؿe|?iM/L6; UriO([M{/۵ezŷv^#ѝ;wVmіM}o7uB-6[6<0ӧO?7n/is_]]{b n'/?77x_}+W_}֭[nݺy͛7oZ1xP@ F&g :ubyCY]4wP'G5- 6 j.#Iͥ 姫6,&5rx-]:h=R@ abl Pu8@["S? w`{G;'"2{VQe#!s<I7Mzi%"8 ^JYInt9oʂ DfrV DdF?tbY D {wjCV&P-%v/˪% J )򜊚{;2~ g-D#-}|J_{f9IdwNUg:s,j.kԝޤumd.ag"7R.6 IDATk/UU$|&:l8ޤ%F?ԩnMcRݚ4?vWk whp?\ޅ#x&w1 &g.\lAs:1ldmDLw-\^ |kq 4tk:G;~PGdwkxF4/uL ׃h7ⷀts<6s8I onݚw9>EBby~~]Tڈ7ui6 f0k-ݚ.VX6nݏytP&M480)X9>i>߼xo^)w`Э?2X<\Y$rNz9A|hw323dEl|ŵ$lhlϹ,*aX,MpY,WA6嘸QeB#,{Cq5%-.0,ȫwh08*~~ u8M+wo?xBy}/>ſ/>Dx+se\<酺.N2lϛqvu26.Bw[7*;L;{x,SMמ_fN[;e3$ۈډ2 Pqޤ'ٖwHjFZκ8%J]?9Y_s|?W_2+g'}VW&ٜ7p4-bk"l/hr.Bm cX .r(ә}ܜĵIcv(% KV-=;^,4h.,QvYmߚ&r? `ߗ(KrGĭI@=l.uӫ+512k_`X y': mC8eb+S.1-l4;q1̤3hp0:J^XlX/-DxηJ1l8YnZfF,Cc4n%B*i j[> y¢*j}@r7zEngVW3g,9 5,­Q F|L1$F`7?|]쬠>=+FafF3ޘwkgjƫ@TXM)=R(qTWFW,nIX8dXxNT)'=ZWr' Უ0'0II*Y. R(D&K \O)$Μ k6i8֔u-43s0BAQ66$؏Mɽ iV?(*" !Df8xkAzA;JB&L5e,Ȉ@QTBz\gW5VV3N>>.yjulb8ZaSQ;soNղ3ncP+?Hm69ulĈ ]lHϽ5BaPvq&ehw /RvH!qsILjJYЪ} (@8YRٛE zxp~rY_dTp؆ (\!n%~wg煱K]4ߜ, Nsqs_0֍])Qh 1c{Wz/Ej?L$4,2lECWtiDUb{_b ]T"\~E'g,SG=K,UW!(k'^ׇӌ1eyOv9 @iZ#fPnZE[Uĉ/S ""sd2,\ϣD*7K14vɛN.NOef\ WHvf~ўO>Ǐ'?q9_/۫2z}zŷv^#ѝ;wi9 J Y 2T/I3$kaSE}R˷B{]j]Ywf[O>oܸ/iZ{=_?%'_77x_}+W_}֭[nݺy͛7WbaV=?&tgz*D GPr1J>eu;\vR3A,2WpxOCLvo(X=w"Pw',:.}g|DM-IHNtl l80E-PV4I b7X*O-p> K?NX\ wyL (z% ݰ(l?gA?ڍQ+fPiƨhWҹ m@BaXhEgw5~'>͛bnYQn^n SJ1ayv7nMy/R&}R4 7O0jk ȶ9}YɟZ@g0xMz삢m,hrKb|@v!n~rԔ-WyGXy뉜#nAx/=2`bnMw,sEeR'+#!#9~*@&5rx-`iE[Ub wڏ>ֻ:vl=(tq`6c X @ LζPݥj.u9vѭi2 N\Hcĝ.Y'A',U2OHcJ <;\vy 6FA%1 K1&%6n%X @@QbոdwU S+b LX}'0}AhD:lW=;mx"lAIokU+ljDX-,+dY-` ^CՆnj%,vk+}܀\ӝ@.wDfIwyӠr=EW}MXRMx^ dC]UsYm ;QDl<埐ޤWn2~3K;Û;:Nj@ӱJ(H)p7EEKe[Rey#;c[3dE8ۅI84A+CT [/\< Jқݔ@ T_u Ҍ6݌S6.X ""wص)~nHg^t@Fx"l@QȈk 9e=/S3yY|YaRg %+g7}^'LZ _8)d[],Ld&nVuJqem! ޅ)%22 dMMD%f Yܽ Bi OTs)m ֈ"y cb\pcOـ7A4[:5 _X {cယ*0+Ȕ%Iq1<8ä"{EB]bFO٣}Y|er< g{0Q؊kn!17̋"𺆚("M-U]R;;̰ۉ4ƽqLUy-玀掂'bḇAR`UxxJt}]M!SVaU!,,qSTu= #wP|erKW&un_#ڱ u\lS=` ˚o%t]N`rv2nRhf-a#hT\fcq @2e8~[t(1N9'RլmGNal8̇<- 2 #,F#Ȝc^5(fJ7*4EI"& bCsU)吨J$ZTՖ4{84,P0dNQn?: |fm+{VwPx` ==`ӹ߇hsfPRcb; p)\HbuX9ӥ"Mz2edGW-Gn3B:,ZT&kN`bh29|ed\HHNWtM\5kn["*_5 "q0pcFu&[b0GQN`dS01%NJȔќUKeAM 'H%+P9OS.WhbH6|ed䓏>?OWt]_~WgroFD< ;wڢ-&/aeQPKOU[Mij]ϧMzsRԺB!izӏ?ƍ/K/-he1{'__׾oo~Wꫯ޺u֭[7o޼yNӰK Znr0R }[5[&gh Sg{c)K%q΄xɅL xR]wˁ%OVL#QaF(X1ђҭo5]2+ު)rV4-TjlZo hx6 x姫6;MjU[,FyQ@lE'Xj;qڒ&=M4mFPlkHWn wc$ڲM\2^. (s5]{wjCȻp.RPmx0/3FryNda>)Zsfm,GjȪ?+Hi0? 1%d|l]MD{;z|=2sӗpwsdm:U߻h~Dn__֭ln-I8J\  xy7{zX:DID$}$f>h~xکiխL9L1'+93{f7?"{Nʼ{^fSK!2s? Ǜ2~) ܶ5.clQݚMr(AQ&c\P^ K%\̋_WcAf]FªD>4HSf&G|'--q0 ";*&'Vl݉'ګPD{>fi\/=/}Z- QM ƛ?\P;Q }pe-c9XM&GvFnMXJ(AM71MpY wn"5qWw&YgH1i0 B3;^g+Yg:/5λLW\ba&̏ (zHw(9bY0W="zH$~phw\l|8E%ɡ66c_zp-X<$]jLL̑0F 꺉 9-]ٮ3tkg ߁ݡֵa;.L L)rinqo_]qi(;D&e_Rلx.1_KŸQ8jc> Ǥw[1kXci++zWu`+rH>. ji%Ik9۫PG{6 -` ~FPA*(iD7Q(q\wۥ(&E.:ZIZ89Z+1+,NPc(Lm-J nTJ:2ġX5$wrYqn k,*+rTmv;0sRבT ,J"d,(̜^ @I h(><'Ha0"@ttdr#w slH }DEe$7ʐQ (ղ~2JwHYٳ*M.(2)yߘT+I9ߝŒvor5w*89~K@I:}4Lv}ĂE";w]vA $HߵLӌ;Flc3H&,2y$kfϥ\+c5wb @ZIشYg̰`w70+٫Gj+@DyCNlG4x^Yޮh_ [hHԑ(ϧӓĂ]kIgl&X$ѓŚt:RH(A]7Q>(ׅkh6(^@#8߅WTGc'L2kAU6#dK fh4 fv{5&('O<{?>䓏>?OWw]_~۽m\=[;уΝ;hKY^CkԢIw}lM0H|U Ffk}-ovlÀUo>}߸q_~饅M45MF{=_տ{~DDD__׾oo~Wꫯ޺u֭[7o޼y&w4Dͦj`naվ8p X %|xP>aMG. aa^r=ZC1TCXj ]>:,Px<6I;T$wr>K.ՖYJ!xǴohEvWD5E9K,B /_hbsQg,uQ:R%WjJ) Ɖ?'p@ށv]_giuKZO ]wwڀ@N&Mx#]=;qV 9qdf79;n?vJ56;4]3c> LAIҡ{˛z/pf~\?:dcd^Mpol]']%gOvW,/Δ%yrUlwr;pͦ]8ir<\t=T@čUcuk[ׅ9$e]D|#FK CМty$ɽߝO-F5IP%+b,w}gadh-т n5ݚmaSK"]3W)!p-7 ݛ3P:}D`;-׃n/6st6&=&h6 Yqc47l%Nk R@-4Sӧ\8%㛟b)مBŻ%D`:0j*RC{ 3cIoH'twb8=d j;*[(k5,Rx}σ9D9H?4Y8˛D{=0F~~ʼB&?a9}Y.H;ڝ#=ngIògK C`1_:E$hc PD,M3ҺG{1dw&gfW+6 )`@(>ih:->ݚz ۊF8J #tvx^|+ +hwV$% ̆];[phߎ Q57SXV_l}DrFx'- ݼh;Ãk/&1Bp}0ߛMm\&J!DGGh0eݭ Fг) X" F W]w,Q8SMzC;2 >0}Ԃo7u.<"қ6e¿ VSF@u Z1QވN5jZ >m#Cwrtv0S[txȷ$gގ _pXVji@Է>4F?J~z'+ԅ7uh򶇩b95ҸC kE@6lGF=燧5]sgX{@:4kp)4hbX/ h5^z3ĚF%nY;9yc~T$_o.yh$/OkG;R /nW\^@KwAҰ4$(K0p[9Gc| g#v{} ]"ݚruɈKU7x1ڪDˤK;{I9;W5YE"e$o;ws4f0v ὃi֥Yq%$KW&$;A7&PrN8I/54FSV@A٣Zd]DZf>f8en1yvp40;<kIwy{cjSi|l]I[y$os~l`wY@2T0:,5*u i~t h7$i;& nMkߪ)مw;LHOIt Gs)=)MS(\oa@VfhA8c/OƧȟQ%è7i)Yl1OUKM,c~ӰRiPc?˛;3@y=}E:VpZԡ}^QSo%61 yB mQx$'Ě2oT2`%6h:cȸ7^ʜ >gR?83,(;DN Ԋ'";:~.B.#$Q,TlyQ!Bu:}'݀ Com!yc('e(r@Z,v$31P鈎Y|lݡ:4u8dc͈[\8 3!Y->Ar٭ANooP?ݚ&2ʿ97 @n*6l,([ t3wv(݂ *Y3u2~g1x#;X&?mޅvnM69=1nݩ)1 Lb @x^4Y|g },u]:/KU>@wXD}g/'We8țVbW^.6.?Gk&TzQth){GR;{\[V4'dh3w`Tg a{ېcdud |ZV Zn/:"i7{"dE+Z @6$q6u #!x8?+2ʬE@6GDou]Ru /~%_Mj}2ؐ_.!JfTA6|s|q4Qm a_mvetoUH֣Qp*D}e>s%N8w*+oώ'""sUh6к=ѧ27aȔyJ[.P)R.= $2)LYf_mZKm{$U=t]j-tk[=o2.q1 3(@f\U,Kv:*i%P^+@ށy]!{~0O>Y{;ECM |ğo2ޑWe(l+F/ҏt4 l@PtJؖ;ShF"8.=*7(iD a]2 t48Wg@+}Y/򿉛e*46PU w`?5爛K΅(eВ\W§dvkZ%H"Ynv‚ϡpZSr4oԌYE-rNW Vnw Rz? v W,7 \c44Ep ++Tx2ڍO$vO@?l09 2SLfJkiϝ9U&z2{sH%4MɽḀ>#ٝj!QEt4Yk<݌oPʭXKS+P- l<PpU#a2#V~§|V J5;0ғwdwŋ+?2j2>jl}9x~>Pb%V4`_ JAU,wk.S"4Fm]1}-^jӫ YEW1COR3.\F6|kmT B&Qj\YutlXYKbV8)Ut93ҝPQ /])QW 7!!#Y zY҆ɜK:{;jZlH 5 뢍9E˯Ժd@4!( K\'l9?ixcenp`iciÕ8q ]'(WM.U_Ui XWs. Ӷ~P}.4 >>ĭ,ܥ#qɷI.BG{BS+NE_,WjM V}ky~e=d.mR !ڒ&ZxX /8Kṽg%LjV%>re_AY ᄚF5K+dLv~6)Vrd j3j!JH^Kuaa]oݍw?xDP;;𧖞qwqzگ@6I'H$YתVH^UM'JWVՆ&JH*6,`k ? Z{w4,krBk;cw%&֛* Om[c(< Shuk[˶i0o8 vxwÏ8s4ȇ`-Rhv^[ @Gڂb+(^jZBɄr))‹uNw,4S՗K.}-^hvUf6o[{h?OX K,*7o,uEX2ssK>SuugUz#oAW@ށ"3~ cI&%_H ݬ>Y=ϾJl;T$ECk5[4m"}ե*6{3W 9,@P} Ũh/"Zy ZIc]H)H<, 姫6Xcʭ k6HU[0w-F}c$^BSg#O[&|0YKڪIֵ2yӴw{Z]u\!o/ 5#mw`Pz gE,gR0"]UK0@['+ܢj/J(5ޑ|nz6| xwi&ު-Y94YNt,w/jsI^z}i[VJDDgn'mo65O~Fa $(KQ11h nL^].5~󳒔tY&zu}09&ܡֵo9NSK/ػZw-o^ -}:ݚdGM,9viO&,OZ&2_ Xq v麔(v }$ ٶ.0k Y; |]hd`^sK&G6~sg~b$;XUVFZB"t&\Щm@6L@XN3䡑@1؝ чD\7d" h  5CTא.K%92Ɉ 2||12]pś^+b~,%R M:K&3:&/L|f"ƒɲd}P`/zѤE)RX)Ck#"F> 񿛌WaXZ7%ٗN7H (P_|,ŧп݉W&JX ㏨c<]CZ8q ; xP4_ "K(otAF.<שЌ%j!Rnh Qt:*nW|#h1Ѐ=oe)\>#!ƒ2`nA@RCzD?/ W{[ɥp?c'tB?h!X9KQ/'K8cmdunbC]h|1~+fFY]RY"X8)gPo/2D3ܸdaT,u+? ;G2#,c-$[&|{pJK4,!H^ђP#H51%b|!*>6$.t4ݎ{II|D6E|ZjeuJךOQoX6bYEyD._qzMAebRtIĿ/|+-iO6uQ/Cxw@|S6D &QD:Cx`ay:o?f}TZyH}݂dK"䝳jDS{_)}QƖ^inKJhKߡK~Qpk$JX'KY踥]A YntA ȱEɗ+GOWkb+JKe i_+n1*ydM8Na]qAUFuѨ1R0+r~s6;BwPk1_ө 6-d4Yg 6zȫM%QuFt0H}|@WA A K.* Jc֝agbt՞M%ǩ%3Q?`QF%B_/r VVvYsI4R.c7M*_9diSaK,[hI?k4O˒-B%Xisd/՞(c,cݽoxT[+sjV]%;h/hT`%';=f<4QoQxpϛ5?/xd#:,顃Bn(VR|8eJV׿CxnJ*(W.mTf/cjy==\n_ge?7c +?Tt%Sovd*(;` X+Z'Ug` ZYA9 VwkT/~fs_;5ezj+Bx5Ÿocݻy*z(8.@}@ a,T AXfw G&Y-TF}MX?G/ZG9+VRQYΒ 甕LJ^1*Y_9IADOiߕ1HMde7)IFU϶Bxbi S³йh.?èE1FK1h wPgp#S|#b$팮Ue9wA-FmE&c**hoVsC&@:2JY:5PpZJJ\Ae v Zit@xՈe߯p-)Լ4|h.wPy5PgV'㑵nxbHM$;({aE cUΈ_L:v]yJL7-p8l.N[UPB%q9%f^iN zCxeqi9-RDwbҠ|:.}d}ia@inU48+"(C:fJAs\y公;XUEv$fG K!,E-1?Z1 *4ay?jkӈW2`1&NFu^{ezEʄ*3]:\=i"DEu,f O%L{Vwk&xX&9A@}lS 3g(o[!KtGPZY΂ !K5wkwU'Ug »*ZYt^9:g CI7~h4p(蜅MX h]lĉs@`0O@yЛN H2s@%\DPkX` q{QuF`Ch|ߍVƵS+Pw`=?{GS##Lbd)TEQ8VR+Zͩ(»:Pvsn[""˧K% ek3ӦK1֙ߥ,ؿ}k5V@6q󄈈k#ty4+Oq'8%Ls=<3MUΏEWnf]R:֖8L+1˸Vsj~4Msֵ^ZB=oݳQF[B6ODVG-k {)\=;wHfw_u-b\GWz$X?F$!m:q9)r`I×/~syyUH<0kuSa.ތ,nH.Buݗ6ۘ.T`^:3=-0" Y5D;ӂOH s) e9mcXʞ0mv* 9ZgpD {G2 Ia֓iV]YTq\k$-ϟVF8 Yag\Zq9zzI)i_r\OQ6{GBtzLoO({[V~-3愰]LOYs,7(s]#JH Ϩe`9sQ+4'ݙqW=M;;7[4X[BPR~ Z5L_!xxKLdM= ."Wzw?tu[YXDdu㴽ʁxyu <ˢ^!l'D69*90htiVa~dhk%Q)4CENaV`L!dROڑ V]winh!F~;]О\͸V^Z)}ZWX6pbbH]#XˆT ""RZOd]QЊ)=zs3J'WBmpa(8!뻔+CN8R\qaL?Nˣ5;Mnսb$"Z !wM`y\-e|J6Eߍi6 k5j,ֳ6Ih5+v;ʬh\6YQj݆T5\n]<3}Y~L0 e rlhXhvb $ |P{`xs4s>P-޽-L\6mؚZmiGrht_!wJۼEmHֈzP+XSj YiB8P$@xRg,Nл\ZL\>v93Ty+YSkڲY4XS(zbOLCቖV!adaY7\h9&UڬQ\Ւri =ӳ& DΎTl1Iݩ:B3Z>YW: -nDDݙX8G!+*I狅S pGDҹn"'VN+š9qwDX#"YErE)B2PB;υ VIyjv͵<ŷI+L lڻّ^GYqP\p˻Wl({wof?,LG͹V[|6ϧSOb ~Wzٳg~_?|GO}oO'/џ'7QU/DoW8fFTֳVÒ֏V ܪ IDATZeX17C!Y">'(_/ӟ޹s祗^ukMqiq>VƳgϾ} /o׿򕯼_>]xW_yW^y_~9 ucM^ֳnj7 a%q3J eـB?+M+ڵJo6:suQ"um6Z4fUY!ژe1VߺW芀֐Agv9"Ir ~$zG;Fa!CخE\Wt?NصIۊY*9Z43 ǺG0;0gM dDD4>-Z{BL%ġ+{ҹn^DdL>_"!qhC |e_h|Ujo]eOO]L*Yra@)s"YsD&ؑq0m`s 4~6 :g!om=lXh`tv0)0`J?,Z~?/Ug]z?V hW)Wl4З_߫:yC,ZA>: @DDA>@~筷@;+!#(`XuqZၛ+.^ w>_w,{)ˮFP4NO:#k_tP ZuUJgGv(IX"Q 2bm>d`Èh,4.v,QUǽw}Oެ8jߦM{B.Ol fbP:JR6j=3%"RRw6K]EǺlXJlk9[@aIʠeJPNZNe>,@0a$#ȴގ`r"'(DtIP]*dH(Yxgbm$Y W"7^>=:F@scHc:NV"/^>mr6; @`Dt<z`zsr Z,P^[#<ܞW}@M!JG֣?J3m1@ wPC9 Ynmک[ַ+D@=ԯy1t< ^PNO꠵rQE VF.]hdYWØdEi0JoK^[k.X{ EXKu/R!P5!E%R<2Ž+DCaGv\mEAC8tMA6ހ3}>Ӳr5'XOݨ] $TƵ괗VKDp.۞6s6Bx5O'm:v>W A05?m7@_:Iƣ*Jr]P ,k Yi=od3=qFQYRvʸ:b)IzC",KoKL;+ɲA<5&3 ;P:ѭV5aAMuHȸyJRەD{2O0nJ˟O;Ox7ԝU["^~M] d//z.wC{wqGXӧJw&]\>ߖhpm!^ƶ|C6eU(soVм&KȞEBda< .f(a3"VNZu9q5!kip"CyxAҽ]Ie-攈zz_fwP*{.B|tcmzD`{[K+O6:g!wE8ԝNA'^XKKZ-[WUHk3c r o&*X;(8uƘ$T{ESOe %Cd ^{KlxTOs̚C+ +QP |}A;OB?LUDlxua͆kjBD@7Fi9 u#Cm+j" w5 Tu`CDDĥ83gv 53zGߡ3,Y/Q ^:AS "a=LBZ$QC]:zqrgf;}Ex1u$ 徽*|L {tl2ei'Fz]>^M.HieiĥiH*P^>ulNl瑹`V3Y,tbF䈡ε2 1]uN`#j3}B|v3/Oe1C笹5xwXWNCtq=a1N3 ²YDiVzNƼ0vRF0aX.jp@=0l:vl+؂ C99:?MSڥ҇o&r`b\t/8vyABxt&".ø:SE"+C9Vw&d:<ȃ豺 f]hpu#6~-Ue,9q%{DZRwƍ3nƶyZ\*r WE!ԟ9S_j\m|ϸV]FDD$uP7w廆/iT\_w+yUW3=Q`"2Vx'IS ku@#,kLd⹧Xa v:=Ӣ6EV=i nL2AӮy/:FETM]>d{K&΃WDk}zUPP?).(L"L"x|-t=7=˼ r;bobB[jչ_W+e)c ;s C},޻bؾ8㙔y&̖~j$"[CfrGX"*8bKl3M"59.ykG7T0nۙfAXG_z,UF1Cل+aYM+Ags>9`5:W"(WsLEEW>?T ):1_BX@{@hre(q*͸jjJ )٢_Gu Dtw;dGX Lz;b1X"h>M%W0o`Oh=d;~?:03zGߡq%uk!XظL#)^>A씏8a%udI&GXSoP3cD='`9h4ƅQlh ZO8 :`+4K笽fa}C]G\k Q,@:v\@x1UܦD\z0.j}6[D,c}[M̀H륊 pZ)՗ J]+ qs}֕0f2N]ƣǪ5bg> dI%MYjE6<֯:hE㫾D4rȤ8z~qуDGFqe ;PPwg}Z]JDݨ] $TƵJ8~+fy%`qX">y"^>izDx@k,;ЙS #4"+4kޖa[0N~/D48AvyAtg_Qf0{a;p5aFagI٩3]@ Q1[RL$IU=Gšp^% "tBF҉nj RmEBSڮ$ړytVX|޹:mg;DZɸVqSD;q.apGoK46tUc[L>šwJ#0$mQ  i+'u" < .O5DlVw˰dfE`km}7xJoM7|J$a,VEPwpxa-/il_W"!^;\1/ZcP":Fzh\` *UbLeȱD+ajdc\{!/iYs9k`J O/|/IPw׶^=]U(~Zu^~Q9 di\95 <(tVO$u}X36 2@DsVJ٘8;H?=gB yb/T+I 34Ӵ!ibzw4z;"zi 33zGߡ3L{fD$yΌ;"4lYȴM thm#1=#xNSze՞Fhw~*\nzXXg(a'1u$纾ZoG2I ~cMD4=,"zJDMδwrr8u~Rf TDA,v9%jOљ*o\bx>1ߺ˘q8#+V=ි$,VwqR{_ }vn@ 9mAy8 O/giX!㠵#;Sr5߅:i.R {b0Q r唈'oѝܣH4%@;N,$JǞ4hſb'tQR"/':j #0]>,9qE؛!8wI$K Z֜v _qt[{<DYWrFJ'u}sk~<8LғWzT>yyz˧}tflF0tzW;F)}%\D!'*I٩'a^+dM_qĂ4Iġɹ-Pt1D18KtZEDDgX7Ok@D\> IvIX͇ 3-X7O{)5 @M!$I"#~.e&%R{_' ȁncve~ގ`eNFɕ+0s^;߼SR?zG.6{!/Bg:| nNPz[pxa-gҲ[!.sh&%BzvtwoDY{՞fÞ0a('-M664!u$ qОDol{(pԿyф+3V|2ciL'g]??R.[6*Cy׿:h `O@$xm1fi 屧Ξ <`1wbj~."gQwlӃ'{U g 8)v:8 .$C]G7^=2gݿ o'h;䈈CWY~["RI}w퉻ፗ[=a'~ /] Tk:f5]n:לstE =]Y$?$/dXyYV " G}sݽABJwtJk_@͔4$b4뿫]t|g\{BCB{i{|1.tE:~ۺL_z Vwܮrp< 0ʩ ( *~dD,HAE۝qa~6Ž h꠵#;=yV^0~L7O&U~K V.5c}2Od( VNxuE@l,6[wP,l'!ECǘ%GԿb^j^dYDaG.,⏓ DXtOz C)]#wdV]Et'=`׊:ِVa 疛Ryɾ x9xsٶsD?;CbM&@Ϣ)`DTWha- 4;G"^>~sI?DL&`DVWQ@R9pyAT W\@h,Mg9'hRa?P:%%@sch""jYw9}@gRqԟ7ǺOA>#O?pPׇLGr04zi唤~X3XXWث%Z`]#k/~Ð {t="gZԒmC9P:iNNCT75YAtr;=oL2zŽ ڪ.GLj*lwrs /Bg:wA]NWPwz &Ct|0݋Cc}73-5l]/ VEhDfB8q{;<.B`(V#`b>ڥj\Kz 87/`KFkXZWRt"'Ug2W_s3[Wu^ X{{i|m뵪P>x?:/X㏪@Bf΢P:3sLݵ/.sh*k-D® s}rA; :qf`sHfƭߡΔHJ٘__C9i Ȼ h ;PNv0nLK-qϭ`j-1N󿪟P]΂6 6,k1wFAQ!E\Kao#,"j/'t"B"Q 2bm~bο䰧c?5/{pkIs*k/Ӊ PvǝٙVE6Z{x?lg &Լ|׻(@D,G* g!C!및Y|H T!A`9J]x,L251w, !4P0C;}+E|4#δG~lUy8x>k-@6%oIeHg\$Ee(|h'-?K>b<c^[2t C@ B)1v뢳O5iSW./lV )% ^k:ɮ$,3͕ԝ/Bx V6o5مr^4= 9!ciƐִ{*(p5aFçxq(8YKx*; ,٨=al|Tԕw}nFқt'9p~Q9]L2S.IzaS2e(Nd{S{O?,GuAv>}tRwJ=7U]}ޗʸyJR{ wW_HO%^VYhzCMPMPaKhÃƵ=-MD~ $=/Zrfm|!zh;4΂Jw&cwyDgucM/,AK0ʀwq48ٜ^pmIk&X$3MZW WM<3+EntօDkJX,{] kseMYmuWVkxyc&m`[q]?$" Dk ?;V"7:k)YZ]+၃ztxTwݬ:*զpee9Oݿ o'DF?>|H_?.?|򣼯Q2k[UHz߷_Z768u㏪@B,3g9j d4gj'梺5j=5=l^G1PzADw' RZIk"9XlTA纞d_pߺ{q(n4Z`Oh=^l]wߛ]d ĺ({WS?tI8ݪS;!jiEwmn_N'-S`R-d}ov AGظSoJ>Ls(tU/i0ŦrP<+C=:vAF#lގv59(j$%0Nxwm=|<޿K5g^n_AQtCU%Ep>!#l 8%)ZO8P|WaOnŝtՠ[՞G^*f2sn/M)/O})G㫾>{OC+yIKkX\3^P{,2v2AzIoI 3wj"k ?vg uk{l7Lsv#/4CD:RH2pr`O{NjGqz*0][JSͼ1sk1ȩ Y$LYOmTP%̜r,."z*ugO!giQ D84<!jO}o'oJ+#.(`lvrJqKrTCo{zg#i|%Gݚ`cj s~i- ?Xt')g?ٯ~gϞ={?'?G}oѿOʭ??o~#/DoǪ3>~筷/NDDm@=СЧg2szM?q 5j=i!zY9e*\?-=X+_~u硜K/ӟ޹s祗^ukَqiTqqϞ=wggO&q$Ww_W^/| .ꫯ+/{6< \jˉ2nF0$%{gvqYn[3@r:Ӫ[w.1$ۥZ׆Axy^? iXC*bR{(1.U֟ks]5ND`g.B$dRqJ)|`pɽ}P׳D"ϭZ_ܯx2_\:(/Bg:L+æ@m|2K.%rcOB,jC=pҾ8E.q뉟M ⧝{ZSP{'vd-Rxz;Ȯ'ql8/ih=-]#*O @`dg/MD<zqOzEC[.=CxE;@=C,@d@"tBV<_ `Ox߱c]&"=F%٬;ȁ8瓶djx\hd~筷~?y-pc X7?",Nah<5/:h(AbV6"qiF߱wzC`82#aw)5/>Bpdxg'{q02]{W|Bt\l?: q83)q*»*ZYt^9ci% ? P48;LQ;H߯: exl4ꖵ.XBxLF>Z/a);X͉}G"M 3#X]s)s6>P-n9.ʹwz`3Yy Kp="xj #e]Bjإ0s^8Wbl@ػV Dw蜅1=8啒۞bdbXYz|fih j4$7f9"f#FZɫGBP8Ng7$ P"6BMt.;rD=?r31eMXYt/6s)c>YU,/MӼgI^XQ .I:K*˻;q8ѓ#m 3_XW9eM!ztlJHB;Wģ d_..eN$^~-"3nd;T%Ώ|ώF馮+^:#ɲ0 :gFˑ:]ѻݵwSެhh!.GGx|.:}ddLuZLms=s->cR' yDpg湸*y30}6}}eɎ(d?CwLֳgtgy.W7/u sZ;x7{Fw5w2(WΆqfzZ,]`h fX5+gW$<P=kYDdwҹu8 1+W<|6kq6!;xvi; qhÔ+SN2N_R,>spdRET4@q83MD'8Z+Ū%MIY`ÛO,DM%7^=|{Hi=IVQP\9 VweZu.\IDwPV|t?2tR+"r䳸ZV+'q~ޚNnmc1Y2^479ޣB?KjgC7S9xn f)>a=*P[ C\zZ8tIl%^uSf[hZ[ٳNq7ba9E-[ , !Hq‘~e# 82w,:ܗ߸3"zp_=t_%%BtN^eM6{Tg V\1wGDԝ9m!.GF.@iJ*ygibQ~g @v4t&\sf}z۷oK/gWn߾rN/'֙ 4:R#;Yf5C| f,ɚ)Qi&0KU|G5qoN" IDAT|LDg_}>}ջ~_t:/^ 1gYkҹ~%]>ܥBӉJ$Kmk#H $ŋ?◾/K_ݻw?".YHEzj.[ ?G\Ҧ,1@ܼ^xŋ˿{_ݻoۅ7eDx?X*ݐ)4Sf9>K-d$x9,23V)#+?u˟}7ٱ^,ŋO?}~gg'.'{P8++:#wspw:4@n߾ b$:x%w74%Y4*w[n% r:;`kÛOrOi"}{' "e܅(w9[ޅw9uksZ$?+z~r/IDַ\qYrOi"5ٳghD҇w,g ;wp~1yQPm/~_Wժ>?vc۷^?zױ7%T?wUg`Ąwnݺu۷ePnݺUm#}|&Yk/~~j;C4MO_ܽӧO}@"9Zc,ߔo58$"O? -zPO?5?-j$:;'&n yjBg>s/^˿KA(i/^|j:/ hܣ&< !$W,41-40Gs| ,uu1iP@/fi3*4iBpbLGt"KL3S޺PygSGj<7oތ$H $  d#,{G}_򗯽ZwD$;땚hg(UhNMSZZzȑwov@HN>]]T\"A;AAzmٿo<O<M.))tĈ#z#ҥll;6! qmegg1M#GRSS~KVȁ  dezYYY?yΜ9| ] xȌAA\$+I&R.Bנlڸnd{,Ȁ!'''33p;-- МEAbLFfƨQ߷HzxE=z1B}8K$3f-r [hNMe $L<{"ZcM>=HN$N|'mQ/_AsBlB% Qxہ N}?!Wz{{{{o3bׯfbQ$@@Abʀ6w8/ob@$x Ht!@(q N >g!bxn˗/ 64{_ׯHII(3' Xˢ"{'{wZ4 Ip+aFAAA{{+Wb- ;bĈ2s"Ȁ@c]\ uy"#_0VkAA rB#{Կ#,GEV4AA9_"{wWAAANCCv9vҕJAA^w  A  Naq',&N>}&eJ+;ӧ`„ %9{,H$  ӧO_I_DSPW WBe}0a%A dž/ф?A#ʠcBXx {'8BEV vGA%DŽ9lD`㓿J; ]bBX8F]L X5,uZ63fCK<튨RBG d펮#=oQs3?11WaTUfx W& @mzLg $;R w/̳nf6?|Lnq!t%"D]DA`8Ǻ;:Y |C2&#of'D5Ωm ;,pA0u6D$WC#tEHVOtJot# vGp?}:rп7Ʈ· *4WkBF=4=]4jԨ=GȻTu6y'CN_?ʨF ׎"Fg[ ZIˇ!j(,嵧.D6nNۃ¬;<(ҩ9c6>Ψ-+s?Ut0'R2x?FiD6*\дZ?TCRi%K [иlIJY^8aŴ37vױ:L398Kp7闊Fu/̪9yu Kl= Yy5m Fʴ~pQR."G*ioo4щ,m5A8`e3/GdD~:,]9;L  V /_x_4\3i{w=ަHV+2wYȎy|ڂΤ-+sxfb<>~t#Y/gx\p~\afU-EEeMU62U.֭[nD0pC y!0g9oN3\Y F-힣;+u]3rͬm#e|}|/+;ZuDEEl۶l۶ ***lN`qtGnU|W0 YDѝ+˙}RoPY X$+]V3/lޜJwP$k;>'39n|BP5Ti[A̕5v):BU{ظ`Nm;!I9иlk@e.G]Tmp&D3Y.85-{5(n^xxgnt &: g!q9"Ms5Aj!=9+/nءp1\csSʧڧs#ٔnUv8'[z<[O>=HJ8 5YR9Kh9vh+, =AsV8\95[fjaiZؼ9m:ą`㔟cEycf͟58XYum3WcȢrWxȬ7`l#7 ItXX1~xΞ?Ot*MUtTPaZV{,Ape|;uc ۧ y q9"s#4yAk<9ӣMܸӓWn5Zs&\Sve45.t_xu[u^u?LW%X]ǟ,v)Gk~XVw>Uگش9m@ݡS`!9P%3[,Si[U]ƲqAp¢(J# ʶZԻ.|39 6\_~ΟLxC޷%c Ŷs=K/Q3uGA6HHcI PΎs獟s~q&[VӰn#yWqR4H'`O?"p|w87՚ߊGvi0XSU;",!"(CBDQg9'j{>|laZD޻Ǎw卵i]kk vNEѺ^___G+Na`5kFs49MWTJziRΥyg|lq8^5Ζc>~vfqiG޵FyzػYc14m `qttZy5H'{J[or:NsBw<^?f9`b~^(TՎpr Y [< @ y1G@`]zCtO|xi PSJMU>6@@ w-nZ3^^xRNذaY6l`,\o}ڜh"Hb,]!!dž{GG*);'XA 6̨T{dgݲZ_AAAAA̦ 3}Ug6 f6i<{ ̂bݲGg~n2(jbՐWu)`f#Տ`[TxƿE*q {7OX:U#\ܾ8Ԕkm~MI?]t;]o\4w<}"hGDQKgE(HYU>sJcfb^V]jX.7nիWzjظqa7vG8\p.\`W@Ξp徾7o޼yƍ===_}UGGG]?cF =k׮Sr~O>(KM^7?ǫ oqo=& \2hNA9|{K?oR%pog=;9;;{ذaCMOOϔHKKKKKKIIIII G+d٭ǫ 4ܡ<pYG ő=:/(O=hӀEH0gYL>J6hbQΜzqNER ܽw$$Jҥ&CAg;ͽw > L7c TfAx¿бI6?Jfp_xg\exn0Aņ{G$wpz+; S5FIX8Xh#@4g5B\e1zMq"8N~Ǩ ~>Z|V#ftv~ƨۻWf}ށ NGZ7ɋGKt-e;2 JwR>?lq-Z~hH3'BZJvΦ.,Qӹg6/r(\3vRX\mc}ќi-i(e׸:ִ͉)`&8- G˷GS>o*Ȏ,;:=F-aeSi6nS{(kŐjlƂA,-}ߤM/U!w ZRN)Uܺ5 *@TN#GOw(ZWw^LknPeLlk6-m;ߖv;i9ҥK}ݍ7z{{{zz꫎Gv /xѣ HtF:$;Om)3:kcj2:}ș3gΞ=;o~YQiSzaÆeeeedddʤz^|AdҲ{}xf׀ Hۍlw Ǜ725 Msfۮ4$=Z!EA ̶mbDA `j2 KflFBѽ.H  )).Xvqx{=sfC6Ȅ}IA[NpR}P!_ IDATFIڠ$FV(,aY3=._ph8xk.! aV1-rش([Ri3?ϣJm?xH+3r88zG~;sԘдqYAVK/A˒@= EMP'"6cd@$ * z[5ß;rP|I: ɴz}+d,#ʖD^mQ5l.,DSe&&i!m™spq /^e'<' EgEnlBcPd3vPPyYG3hRw%&0:2KۢN4 D`dp "N^!tDX a>: !-AL>*o?9HJJyzXmxŘp"h삇xDdT&Լ#^orr2[};כ:l0?QYXЏ 0wC̯TDfXk P0;-Z懟*|v'+b! VXA0fmHTw~0\Ж.@\mqan0FquJoQ~H[&^te5LF,h0M<RP{yP _T[W-{O!C1<{xoǼ?)))iȐ!Fz`F~%Vɋ$iA>yw+d;뒜m,*5JN ~~;%k$K-@TWCp mgcy/DP3"|>y ZG{ʲ.J3k6B2OJio۟ãlFGnįg'XFg\4 pk|N7{X[؁=ճwߑz xy.]w7n|W^믿ˏ>nIHʧ? #I%YC"?]l*̊\nƨP+|n.|sLc[dHCG0(؛C/d4 l:{xi#j Ϊ)Ɲ~{=bPMA"{敗D1cdeefffddddddPOo~?sxڵk===W\pիWN;R  򲳲2333339222RRR{̖eYpBKOOEqر~mNNw}{&仜\x  ȠG_TG!Cx<8KIawIQQHII@fziii4~?ʛFA$ 8Mz;%@ @=<P}(*#  n^4C}3nc.ga# $$pf^ʷ]|kuIENDB`treesheets-1.0.2/TS/docs/images/stop.png000077500000000000000000000016001352107072600201060ustar00rootroot00000000000000PNG  IHDRĴl; pHYs99|2IDAT8MhI2 MAeAA<)ಐ/9 arT0]o=/ă(AQ8q3'İ{I_Uޫ3VZ\.MsK1,hdp?ݵ@]< 7 C/,9{eh&;NE\'PJBccH*8qR_($ߏ56vM U&*)\זɤDpf\ VՅ-ts,\*AqUUJA0,k e0<MNJgZ [O&'|~%rjK9##p${<1ܸ:;?~0l 訡VAsa.8=Ç-Ap]h4`b_!0'ik5` .cc(ؿ.;w=}ذ][aF{ س8Hj A[@ -GP#x A j|Ao5> A[hWz@AT3y8%?CA[PdJƐ7'iAAS|0:At: ނ?sM}o ❸B -AA?> #x"GAԃ~|AO zЏ ≠AQA<# A?> '~|AD=GDЏ  H#3%pR3~?LYP{Є'*9Aq'Ng#x GD\NG0Ax|AD=GD\N9 .BK4|)aAt< X²,va@3LS ?vx"\w]Qb)++rʥK*++ܹSYYIQ`ٳgϞ=CCC9QA< 5~%0Ӿ,]d4M1mڴ!7e).e/\;拉֊uTf_zرc_{HpG}~|r pDz,>Z8#.Խ >z 9񡁁l6ݻ7''g .9%zx'fBK5lFn+WOQ˲ LI$@,Z~aHH*}x|S믿FGG߿`044M;i`0۷'eFIXB^2L ־g-**8d2I4M,rq\aWzOd0;6uT,*}ěqw0<>|ر=z///9t f3q֭0a AСx-LJ>lٲ~ؑ<׿ԩSa}AL2uTK8h К[ ,_l6,k<`azeh^~&%%}Rљ&))))++o :pwC5jT?ʒj|P >D X.\ΆZB oVbb"eVbA4=z!CfeeY ^>Bܝ(dy^f_eUUZ8;;ƀ [:8]Zl+|KX ׯ߽{x[w'8 B^CB7cT܁ѣ_~ep4 P lܸ1//ի}xxxBB̙3TvdKh nI {xVx<2(O#*yoܸqqJ,+DMy>000%%%&&su}cT65ҠbGUu8* gPKZۏiSlU'dmiSeeT?`XWX8;nܸӧOJT &/^XYYYYYY__ݿe˖AAA]t Rt~`?|0,9u90mڴԩSv{gMHy 0v.]ȿ5;;IK({LG^pAOlݺSO=` ϝ;۷ڵkvܢEǷhтLg%(`gZδ,%N}}=!瞣$Y~ĒG366Vcmv}ѝ;w0U!ϤI4\?9sE</+J^~zy˩[Aprx5ѨDŽpeȗ+rB$|1Bw^}b8qȑ0+|׮]] ~딥~|ZÊ)t҂ CݮNؕPyӖD!2(UVD\(E9sljX&*TMӐVǦB,߃ j|G!+r6h_fҥK)8N>vQ`eԩSћ6mi<oJ@B?, :thO;/зO:U^^n7a۴iҥKa~o`OB C#q7oޜ1cƕ+WOB<]SL CYYٔ)S@>]ͥ)JL!Rw!e7n_~Ŋ&ѣ5<1-ƴ0DE#Q3ٽ3]HK:߅ ֮;-AO4)//`01U~|G(9J&ٳgfFxY{{NQq!\G;BШ\BbYvժU{8qh(Au:TMMF4:^~_QQa')ȳz꺺:9>>>mڴ~(((۸f`8yŋ7n?.*iuuu*a,//ZDŽ<###77wذa}vpC߮< 0Lxx8v%N]?!cbbrssm*ZOSL={6! i5գGѣGL&gLzҶ'`01d扭Zܹ㏥&8u~Ν&F.L[B,˂ȉܕ4LGNիWqrs̡읾*G!ʂ53tEA]"p'ORsN=8Frڴi(δ|PEtm>BGgnܸ(`]D4Mhy!+++aku}A,\Х8B1Uq7(Sf04o/̄alPP[޼y3!b_81@ǣK}7o޼crx>}ƍڗpCBBq$O p=+^"֭[LL !D:%"@7۱cG7nxm05]SdrSAPTxDZ:~_WW'Rae*-rrrIŋ3d2Y']p,>o>bɵpԩ9se)C\sk XdVrǏ9r$OGCx|޶m۞={ڭ~-hz^tc='AoF~fϞ}Y@\G>q;c^Wm|M grIΝ;TzL5k>dOQlݺ522g=wѿhn?ei؏# Tc~|MP:! 5B{4k,333xe"4MCΝ;###'wHSB̞da ΆJ ](]/?Ʀr "((/| Q&@,X뗘XRRx Zlb%,Ϝ9ҥK]QYCsmdPǁfy~ܸqǏW(Uy{_\zuԨQoXD]^)vitT<=%05>|OIJ|Ǐ?#5M"h48qbɒ%ҽ݄???{A֒VR#>>>F @kˣ :k֬9+ J>y !+W;wr>IE4ݬY3(:Ք FQESzwY6U۷/22r֬YYwBb^\Iխ[lٲvZX%m'Nݻ7!ܳ˭[-–-[ґ]zGBFcXCk֬7oޖ-[>_~vQ`0l߾]K](v<i7B0gΜ:th}}7D&,YN8Q^^<o8s޽{?s)))v)w۶m[ZZڐ!Cf͚K0lxέ#??~֬YvQ,[VVvѢ"J!OtCϟ?_D,;Az!???4A}>%CWZm6Deo>tP)bޮ]ym޼yΝ?e1!>k׮oTw۶mS3 'ɴvڈ]Z0Mpܚrkkk5vcx~kZ,]tѢE|ƍϝ;GH.xWXѯ_PQQQQQ%ׯ_yvB_U'OƧn;v,wu֥Ξ={na(/b,' ĉۨ,^ C,y@pJaF#f3N<޿q]Wzfy…Q!0 u qT|}}u@/`%ٳ'8V% yΝ?0::zĈ{-g8+ ȴ'4iX7o^jj*Խ^:Xҫ:."L6Xo4=ꫯBA8ʕ+[n8@QEh|ӽTqhTÆ ;rȣ> d|ѣO{,w@ܸ*K%+א{nΝ@ ҫJKKǏǷ`0 xNpbWG e7ocK2ڣ3B4qH]nݬYw`l޻w/yC5B9&8eYѨ^xMgc-xɓ'>|x„ :Ax9sf^^D|(y(~~~ BBl^j_|a !,VWWoݺqy|}}SSS d#U<ϷlL'xbٲe\CylĀ-n[ gϞ={} JrBٴiEQK,]Ae˖ #@(ƚWxw'w2tЁR[)Dlc Ì9rȑ/^ܹsgZZD(1baY]]3Ϝ?>00 Ps .::Zڟ5Z7,,8_ۨ(osn59j sϞ=%88֭[DϹU!XzzH«M9BCܮ]֥H2?ӷo_RXPEE'|2tPv9zjMـjX_kfsǎK;氨/\RyPUU%`7O>CAҲ2OqFjjj4>T o߆XMh`Z'N2dT799%G?.PE̙3eɴ45:Gȣ*q5*k4~ʞ骪*OiXDU#0Λ0a±cRRRn~_TTR q#F a;}4jБn[ؕzٕEQ0KHHطo_PP4:333Fj<͘1,"Ν;IXr(z}}aÒ58!`#'N46Џ еkS\e[n ;NaԂ8RSSQ҉!_7ҝ]:ZLbTҴi({8nݪW5o"")))YvmΝ!N)}G8pʕ+ 5d^F_i0jԨ^zD@=*v̂0`%KFsRRRXuꤤ$iСC!<HfYԩS cIxN8BC:9n5 7..NZgپ}^Z֭8p%K]]d21 |'O 0&b?w# %9vXO(R8<|wynzBݻ @hdF(MZڨKNN8p סpL&Sll?OxAA-@_bENN#q°#Pܪ9rӧkTh@.]ӧo^?nz۶maaa.\`uT, N6Mg?w}_9ɩr 4]WWaÆ͛7?<@HHLwܹy޽{ϟ?OYvEHŠ(?11)DjƌxG~ePIDAT|||6lnDiӦ\Tu0eúVPP>xiW!VG;Cp| ,.]ɇ~;iO`ߢE hb_uFF RSSSXXXXXhRu}O>񡤦\Nrw5вMq$O`gee~z ܋Edn<2;:\aު#h&iȑ}^V[-uMŝe˖]L&YO&UdܪU?ǧg0WtKJGm!OO(0dN>> \ѬYFw w BBB>l6K"Ē%:fLjy>}*hz%<&CS56J 6g%,G;sT~CStu)Lg͚1ʒYE2(#m9f-ZXfMBB`{jo!6w^f0t<Ւ"i0dn(jxyDDD^^8@YXd4&k׮iiile$㇄L&;:=dʒB!_FѮ];ȇgs|J {Vu*fݻw?z {+WX?2];vҥeBx-ZBxTnuuuJ~Ltee+ڪ8E*Ryyj ڴioڴҥKu JLkn̙- rVݻq6>s[[988x#1_}RGb /9b.ԩ%q@e˖EFFJUݛ]Ӄ?7o7wh()=a_?|CdF#e}}}F9aȮΤk$**JBwI z뭷4ܭCln Xlls׷ƌC6yW^ձ­`\MC<;w[NÔ?ȑ[?~:__߀zhԩ%S hR+=ɓƹ^t XIE____\\?^~F,mB׮]۷oo4Oⶵy sGQ_WTTTTTOt-vGS{שS~+r<߫pԂionVRَkpڲ8[+؏Fm۶m*ފ[I[iJ~㊚5;% & `o߾O>"8 aF}d~7O0HMuA\AHa c9Xޥoؤ;Mt]$# 0A(x3ցa-xh<> A`~|AO# A?> '~|AD=NDgS 3~Ϟ)tβ=' ԩ, GDЏ A<GAԃ~|AO zЏ ≠AQA<# A?> '~|AD=GDЏ  AAA?> #x"PNs x&s- (A& j|Ao5> A[@ -GP#x A j|Ao5> A[?̫<[IENDB`treesheets-1.0.2/TS/docs/screenshots.html000077500000000000000000000045541352107072600204070ustar00rootroot00000000000000 TreeSheets Screenshots

Screenshots

treesheets-1.0.2/TS/docs/script_reference.html000066400000000000000000001663001352107072600213640ustar00rootroot00000000000000 lobster builtin function reference

lobster builtin functions:(file auto generated by compiler, do not modify)

treesheets

ts_goto_root()makes the root of the document the current cell. this is the default at the startof any script, so this function is only needed to return there.
ts_goto_view()makes what the user has zoomed into the current cell.
ts_has_selection() -> intwether there is a selection.
ts_goto_selection()makes the current cell the one containing the selection, or does nothing on noselection.
ts_has_parent() -> intwether the current cell has a parent (is the root cell).
ts_goto_parent()makes the current cell the parent of the current cell, if any.
ts_num_children() -> intreturns the total number of children of the current cell (rows * columns).returns 0 if this cell doesn't have a sub-grid at all.
ts_num_columns_rows() -> xy_ireturns the number of columns & rows in the current cell.
ts_selection() -> xy_i, xy_ireturns the (x,y) and (xs,ys) of the current selection, or zeroes if none.
ts_goto_child(n:int)makes the current cell the nth child of the current cell.
ts_goto_column_row(col:int, row:int)makes the current cell the child at col / row.
ts_get_text() -> stringgets the text of the current cell.
ts_set_text(text:string)sets the text of the current cell.
ts_create_grid(cols:int, rows:int)creates a grid in the current cell if there isn't one yet.
ts_insert_columns(c:int, n:int)insert n columns before column c in an existing grid.
ts_insert_rows(r:int, n:int)insert n rows before row r in an existing grid.
ts_delete(pos:xy_i, size:xy_i)clears the cells denoted by pos/size. also removes columns/rows if they becomecompletely empty, or the entire grid.
ts_set_background_color(col:xyzw_f)sets the background color of the current cell
ts_set_text_color(col:xyzw_f)sets the text color of the current cell
ts_set_relative_size(s:int)sets the relative size (0 is normal, -1 is smaller etc.) of the current cell
ts_set_style_bits(s:int)sets one or more styles (bold = 1, italic = 2, fixed = 4, underline = 8, strikethru = 16) on the current cell.
ts_set_status_message(msg:string)sets the status message in TreeSheets.
ts_get_filename_from_user(is_save:int) -> stringgets a filename using a file dialog. empty string if cancelled.

builtin

print(x:string)output any value to the console (with linefeed).
string(x:string) -> stringconvert any value to string
set_print_depth(depth:int)for printing / string conversion: sets max vectors/objects recursion depth (default 10)
set_print_length(len:int)for printing / string conversion: sets max string length (default 100000)
set_print_quoted(quoted:bool)for printing / string conversion: if the top level value is a string, whether to convert it with escape codes and quotes (default false)
set_print_decimals(decimals:int)for printing / string conversion: number of decimals for any floating point output (default -1, meaning all)
set_print_indent(spaces:int)for printing / string conversion: number of spaces to indent with. default is 0: no indent / no multi-line
get_line() -> stringreads a string from the console if possible (followed by enter)
if(cond, then:function, else:function = nil) -> anyevaluates then or else depending on cond, else is optional
while(cond:function, do:function) -> anyevaluates body while cond (converted to a function) holds true, returns last body value
for(iter, do:function)iterates over int/vector/string, body may take [ element [ , index ] ] arguments
append(xs:[any], ys:[any]) -> [any]creates a new vector by appending all elements of 2 input vectors
vector_reserve(typeid:typeid, len:int) -> [any]creates a new empty vector much like [] would, except now ensures it will have space for len push() operations without having to reallocate. pass "typeof return" as typeid.
length(x:int) -> intlength of int (identity function, useful in combination with string/vector version)
length(s:string) -> intlength of string
length(xs:[any]) -> intlength of vector
equal(a, b) -> intstructural equality between any two values (recurses into vectors/objects, unlike == which is only true for vectors/objects if they are the same object)
push(xs:[any], x) -> [any]appends one element to a vector, returns existing vector
pop(xs:[any]) -> anyremoves last element from vector and returns it
top(xs:[any]) -> anyreturns last element from vector
insert(xs:[any], i:int, x) -> [any]inserts a value into a vector at index i, existing elements shift upward, returns original vector
remove(xs:[any], i:int, n:int = 0) -> anyremove element(s) at index i, following elements shift down. pass the number of elements to remove as an optional argument, default 1. returns the first element removed.
remove_obj(xs:[any], obj) -> anyremove all elements equal to obj (==), returns obj.
binary_search(xs:[int], key:int) -> int, intdoes a binary search for key in a sorted vector, returns as first return value how many matches were found, and as second the index in the array where the matches start (so you can read them, overwrite them, or remove them), or if none found, where the key could be inserted such that the vector stays sorted. This overload is for int vectors and keys.
binary_search(xs:[float], key:float) -> int, intfloat version.
binary_search(xs:[string], key:string) -> int, intstring version.
copy(xs) -> anymakes a shallow copy of any object.
slice(xs:[any], start:int, size:int) -> [any]returns a sub-vector of size elements from index start. size can be negative to indicate the rest of the vector.
any(xs:vec_i) -> intreturns wether any elements of the numeric struct are true values
any(xs:[any]) -> intreturns wether any elements of the vector are true values
all(xs:vec_i) -> intreturns wether all elements of the numeric struct are true values
all(xs:[any]) -> intreturns wether all elements of the vector are true values
substring(s:string, start:int, size:int) -> stringreturns a substring of size characters from index start. size can be negative to indicate the rest of the string.
string_to_int(s:string) -> int, intconverts a string to an int. returns 0 if no numeric data could be parsed.second return value is true if all characters of the string were parsed
string_to_float(s:string) -> floatconverts a string to a float. returns 0.0 if no numeric data could be parsed
tokenize(s:string, delimiters:string, whitespace:string) -> [string]splits a string into a vector of strings, by splitting into segments upon each dividing or terminating delimiter. Segments are stripped of leading and trailing whitespace. Example: "; A ; B C; " becomes [ "", "A", "B C" ] with ";" as delimiter and " " as whitespace.
unicode_to_string(us:[int]) -> stringconverts a vector of ints representing unicode values to a UTF-8 string.
string_to_unicode(s:string) -> [int]?converts a UTF-8 string into a vector of unicode values, or nil upon a decoding error
number_to_string(number:int, base:int, minchars:int) -> stringconverts the (unsigned version) of the input integer number to a string given the base (2..36, e.g. 16 for hex) and outputting a minimum of characters (padding with 0).
lowercase(s:string) -> stringconverts a UTF-8 string from any case to lower case, affecting only A-Z
uppercase(s:string) -> stringconverts a UTF-8 string from any case to upper case, affecting only a-z
escape_string(s:string, set:string, prefix:string, postfix:string) -> stringprefixes & postfixes any occurrences or characters in set in string s
concat_string(v:[string], sep:string) -> stringconcatenates all elements of the string vector, separated with sep.
repeat_string(s:string, n:int) -> stringreturns a string consisting of n copies of the input string.
pow(a:float, b:float) -> floata raised to the power of b
pow(a:int, b:int) -> inta raised to the power of b, for integers, using exponentiation by squaring
pow(a:vec_f, b:float) -> vec_fvector elements raised to the power of b
log(a:float) -> floatnatural logaritm of a
sqrt(f:float) -> floatsquare root
ceiling(f:float) -> intthe nearest int >= f
ceiling(v:vec_f) -> vec_ithe nearest ints >= each component of v
floor(f:float) -> intthe nearest int <= f
floor(v:vec_f) -> vec_ithe nearest ints <= each component of v
int(f:float) -> intconverts a float to an int by dropping the fraction
int(v:vec_f) -> vec_iconverts a vector of floats to ints by dropping the fraction
round(f:float) -> intconverts a float to the closest int. same as int(f + 0.5), so does not work well on negative numbers
round(v:vec_f) -> vec_iconverts a vector of floats to the closest ints
fraction(f:float) -> floatreturns the fractional part of a float: short for f - int(f)
fraction(v:vec_f) -> vec_freturns the fractional part of a vector of floats
float(i:int) -> floatconverts an int to float
float(v:vec_i) -> vec_fconverts a vector of ints to floats
sin(angle:float) -> floatthe y coordinate of the normalized vector indicated by angle (in degrees)
cos(angle:float) -> floatthe x coordinate of the normalized vector indicated by angle (in degrees)
tan(angle:float) -> floatthe tangent of an angle (in degrees)
sincos(angle:float) -> xy_fthe normalized vector indicated by angle (in degrees), same as xy { cos(angle), sin(angle) }
asin(y:float) -> floatthe angle (in degrees) indicated by the y coordinate projected to the unit circle
acos(x:float) -> floatthe angle (in degrees) indicated by the x coordinate projected to the unit circle
atan(x:float) -> floatthe angle (in degrees) indicated by the y coordinate of the tangent projected to the unit circle
radians(angle:float) -> floatconverts an angle in degrees to radians
degrees(angle:float) -> floatconverts an angle in radians to degrees
atan2(vec:vec_f) -> floatthe angle (in degrees) corresponding to a normalized 2D vector
radians(angle:float) -> floatconverts an angle in degrees to radians
degrees(angle:float) -> floatconverts an angle in radians to degrees
normalize(vec:vec_f) -> vec_freturns a vector of unit length
dot(a:vec_f, b:vec_f) -> floatthe length of vector a when projected onto b (or vice versa)
magnitude(v:vec_f) -> floatthe geometric length of a vector
manhattan(v:vec_i) -> intthe manhattan distance of a vector
cross(a:xyz_f, b:xyz_f) -> xyz_fa perpendicular vector to the 2D plane defined by a and b (swap a and b for its inverse)
rnd(max:int) -> inta random value [0..max).
rnd(max:vec_i) -> vec_ia random vector within the range of an input vector.
rnd_float() -> floata random float [0..1)
rnd_gaussian() -> floata random float in a gaussian distribution with mean 0 and stddev 1
rnd_seed(seed:int)explicitly set a random seed for reproducable randomness
div(a:int, b:int) -> floatforces two ints to be divided as floats
clamp(x:int, min:int, max:int) -> intforces an integer to be in the range between min and max (inclusive)
clamp(x:float, min:float, max:float) -> floatforces a float to be in the range between min and max (inclusive)
clamp(x:vec_i, min:vec_i, max:vec_i) -> vec_iforces an integer vector to be in the range between min and max (inclusive)
clamp(x:vec_f, min:vec_f, max:vec_f) -> vec_fforces a float vector to be in the range between min and max (inclusive)
in_range(x:int, range:int, bias:int = 0) -> intchecks if an integer is >= bias and < bias + range. Bias defaults to 0.
in_range(x:float, range:float, bias:float = 0) -> intchecks if a float is >= bias and < bias + range. Bias defaults to 0.
in_range(x:vec_i, range:vec_i, bias:vec_i = nil) -> intchecks if a 2d/3d integer vector is >= bias and < bias + range. Bias defaults to 0.
in_range(x:vec_f, range:vec_f, bias:vec_f = nil) -> intchecks if a 2d/3d float vector is >= bias and < bias + range. Bias defaults to 0.
abs(x:int) -> intabsolute value of an integer
abs(x:float) -> floatabsolute value of a float
abs(x:vec_i) -> vec_iabsolute value of an int vector
abs(x:vec_f) -> vec_fabsolute value of a float vector
sign(x:int) -> intsign (-1, 0, 1) of an integer
sign(x:float) -> intsign (-1, 0, 1) of a float
sign(x:vec_i) -> vec_isigns of an int vector
sign(x:vec_f) -> vec_isigns of a float vector
min(x:int, y:int) -> intsmallest of 2 integers.
min(x:float, y:float) -> floatsmallest of 2 floats.
min(x:vec_i, y:vec_i) -> vec_ismallest components of 2 int vectors
min(x:vec_f, y:vec_f) -> vec_fsmallest components of 2 float vectors
min(v:vec_i) -> intsmallest component of a int vector.
min(v:vec_f) -> floatsmallest component of a float vector.
min(v:[int]) -> intsmallest component of a int vector, or INT_MAX if length 0.
min(v:[float]) -> floatsmallest component of a float vector, or FLT_MAX if length 0.
max(x:int, y:int) -> intlargest of 2 integers.
max(x:float, y:float) -> floatlargest of 2 floats.
max(x:vec_i, y:vec_i) -> vec_ilargest components of 2 int vectors
max(x:vec_f, y:vec_f) -> vec_flargest components of 2 float vectors
max(v:vec_i) -> intlargest component of a int vector.
max(v:vec_f) -> floatlargest component of a float vector.
max(v:[int]) -> intlargest component of a int vector, or INT_MIN if length 0.
max(v:[float]) -> floatlargest component of a float vector, or FLT_MIN if length 0.
lerp(x:float, y:float, f:float) -> floatlinearly interpolates between x and y with factor f [0..1]
lerp(a:vec_f, b:vec_f, f:float) -> vec_flinearly interpolates between a and b vectors with factor f [0..1]
cardinal_spline(z:vec_f, a:vec_f, b:vec_f, c:vec_f, f:float, tension:float) -> xyz_fcomputes the position between a and b with factor f [0..1], using z (before a) and c (after b) to form a cardinal spline (tension at 0.5 is a good default)
line_intersect(line1a:xy_f, line1b:xy_f, line2a:xy_f, line2b:xy_f) -> int, xy_fcomputes if there is an intersection point between 2 line segments, with the point as second return value
circles_within_range(dist:float, positions:[xy_f], radiuses:[float], positions2:[xy_f], radiuses2:[float], gridsize:xy_i) -> [[int]]Given a vector of 2D positions (and same size vectors of radiuses), returns a vector of vectors of indices (to the second set of positions and radiuses) of the circles that are within dist of eachothers radius. If the second set are [], the first set is used for both (and the self element is excluded). gridsize optionally specifies the size of the grid to use for accellerated lookup of nearby points. This is essential for the algorithm to be fast, too big or too small can cause slowdown. Omit it, and a heuristic will be chosen for you, which is currently sqrt(num_circles) * 2 along each dimension, e.g. 100 elements would use a 20x20 grid. Efficiency wise this algorithm is fastest if there is not too much variance in the radiuses of the second set and/or the second set has smaller radiuses than the first.
wave_function_collapse(tilemap:[string], size:xy_i) -> [string], intreturns a tilemap of given size modelled after the possible shapes in the input tilemap. Tilemap should consist of chars in the 0..127 range. Second return value the number of failed neighbor matches, this should ideally be 0, but can be non-0 for larger maps. Simply call this function repeatedly until it is 0
resume(coroutine:coroutine, return_value:any = nil) -> coroutine?resumes execution of a coroutine, passing a value back or nil
return_value(coroutine:coroutine) -> anygets the last return value of a coroutine
active(coroutine:coroutine) -> intwether the given coroutine is still active
hash(x:function) -> inthashes a function value into an int
hash(x) -> inthashes any value into an int
program_name() -> stringreturns the name of the main program (e.g. "foo.lobster".
vm_compiled_mode() -> intreturns if the VM is running in compiled mode (Lobster -> C++).
seconds_elapsed() -> floatseconds since program start as a float, unlike gl_time() it is calculated every time it is called
assert(condition) -> anyhalts the program with an assertion failure if passed false. returns its input
trace_bytecode(mode:int)tracing shows each bytecode instruction as it is being executed, not very useful unless you are trying to isolate a compiler bug. Mode is off(0), on(1) or tail only (2)
set_max_stack_size(max:int)size in megabytes the stack can grow to before an overflow error occurs. defaults to 1
reference_count(val) -> intget the reference count of any value. for compiler debugging, mostly
set_console(on:bool)lets you turn on/off the console window (on Windows)
command_line_arguments() -> [string]
thread_information() -> int, intreturns the number of hardware threads, and the number of cores
is_worker_thread() -> intwether the current thread is a worker thread
start_worker_threads(numthreads:int)launch worker threads
stop_worker_threads()only needs to be called if you want to stop the worker threads before the end of the program, or if you want to call start_worker_threads again. workers_alive will become false inside the workers, which should then exit.
workers_alive() -> intwether workers should continue doing work. returns false after stop_worker_threads() has been called.
thread_write(struct)put this struct in the thread queue
thread_read(type:typeid) -> any?get a struct from the thread queue. pass the typeof struct. blocks if no suchstructs available. returns struct, or nil if stop_worker_threads() was called
log_frame()call this function instead of gl_frame() or gl_log_frame() to simulate a frame based program from non-graphical code.

compiler

compile_run_code(code:string, args:[string]) -> string, string?compiles and runs lobster source, sandboxed from the current program (in its own VM). the argument is a string of code. returns the return value of the program as a string, with an error string as second return value, or nil if none. using parse_data(), two program can communicate more complex data structures even if they don't have the same version of struct definitions.
compile_run_file(filename:string, args:[string]) -> string, string?same as compile_run_code(), only now you pass a filename.

file

scan_folder(folder:string, divisor:int) -> [string]?, [int]?returns two vectors representing all elements in a folder, the first vector containing all names, the second vector containing sizes (or -1 if a directory). Specify 1 as divisor to get sizes in bytes, 1024 for kb etc. Values > 0x7FFFFFFF will be clamped in 32-bit builds. Returns nil if folder couldn't be scanned.
read_file(file:string, textmode:int = 0) -> string?returns the contents of a file as a string, or nil if the file can't be found. you may use either \ or / as path separators
write_file(file:string, contents:string, textmode:int = 0) -> intcreates a file with the contents of a string, returns false if writing wasn't possible
ensure_size(string:string, size:int, char:int, extra:int = 0) -> stringensures a string is at least size characters. if it is, just returns the existing string, otherwise returns a new string of that size (with optionally extra bytes added), with any new characters set to char. You can specify a negative size to mean relative to the end, i.e. new characters will be added at the start.
write_int64_le(string:string, i:int, val:int) -> string, intwrites a value as little endian to a string at location i. Uses ensure_size to make the string twice as long (with extra 0 bytes) if no space. Returns new string if resized, and the index of the location right after where the value was written. The _back version writes relative to the end (and writes before the index)
write_int32_le(string:string, i:int, val:int) -> string, int(see write_int64_le)
write_int16_le(string:string, i:int, val:int) -> string, int(see write_int64_le)
write_int8_le(string:string, i:int, val:int) -> string, int(see write_int64_le)
write_float64_le(string:string, i:int, val:float) -> string, int(see write_int64_le)
write_float32_le(string:string, i:int, val:float) -> string, int(see write_int64_le)
write_int64_le_back(string:string, i:int, val:int) -> string, int(see write_int64_le)
write_int32_le_back(string:string, i:int, val:int) -> string, int(see write_int64_le)
write_int16_le_back(string:string, i:int, val:int) -> string, int(see write_int64_le)
write_int8_le_back(string:string, i:int, val:int) -> string, int(see write_int64_le)
write_float64_le_back(string:string, i:int, val:float) -> string, int(see write_int64_le)
write_float32_le_back(string:string, i:int, val:float) -> string, int(see write_int64_le)
write_substring(string:string, i:int, substr:string, nullterm:int) -> string, intwrites a substring into another string at i (see also write_int64_le)
write_substring_back(string:string, i:int, substr:string, nullterm:int) -> string, int
compare_substring(string_a:string, i_a:int, string_b:string, i_b:int, len:int) -> intreturns if the two substrings are equal (0), or a < b (-1) or a > b (1).
read_int64_le(string:string, i:int) -> int, intreads a value as little endian from a string at location i. The value must be within bounds of the string. Returns the value, and the index of the location right after where the value was read. The _back version reads relative to the end (and reads before the index)
read_int32_le(string:string, i:int) -> int, int(see read_int64_le)
read_int16_le(string:string, i:int) -> int, int(see read_int64_le)
read_int8_le(string:string, i:int) -> int, int(see read_int64_le)
read_float64_le(string:string, i:int) -> float, int(see read_int64_le)
read_float32_le(string:string, i:int) -> float, int(see read_int64_le)
read_int64_le_back(string:string, i:int) -> int, int(see read_int64_le)
read_int32_le_back(string:string, i:int) -> int, int(see read_int64_le)
read_int16_le_back(string:string, i:int) -> int, int(see read_int64_le)
read_int8_le_back(string:string, i:int) -> int, int(see read_int64_le)
read_float64_le_back(string:string, i:int) -> float, int(see read_int64_le)
read_float32_le_back(string:string, i:int) -> float, int(see read_int64_le)
flatbuffers_field_int64(string:string, tablei:int, vo:int, def:int) -> intreads a flatbuffers field from a string at table location tablei, field vtable offset vo, and default value def. The value must be within bounds of the string. Returns the value (or default if the field was not present)
flatbuffers_field_int32(string:string, tablei:int, vo:int, def:int) -> int(see flatbuffers_field_int64)
flatbuffers_field_int16(string:string, tablei:int, vo:int, def:int) -> int(see flatbuffers_field_int64)
flatbuffers_field_int8(string:string, tablei:int, vo:int, def:int) -> int(see flatbuffers_field_int64)
flatbuffers_field_float64(string:string, tablei:int, vo:int, def:float) -> float(see flatbuffers_field_int64)
flatbuffers_field_float32(string:string, tablei:int, vo:int, def:float) -> float(see flatbuffers_field_int64)
flatbuffers_field_string(string:string, tablei:int, vo:int) -> stringreads a flatbuffer string field, returns "" if not present
flatbuffers_field_vector_len(string:string, tablei:int, vo:int) -> intreads a flatbuffer vector field length, or 0 if not present
flatbuffers_field_vector(string:string, tablei:int, vo:int) -> intreturns a flatbuffer vector field element start, or 0 if not present
flatbuffers_field_table(string:string, tablei:int, vo:int) -> intreturns a flatbuffer table field start, or 0 if not present
flatbuffers_field_struct(string:string, tablei:int, vo:int) -> intreturns a flatbuffer struct field start, or 0 if not present
flatbuffers_indirect(string:string, index:int) -> intreturns a flatbuffer offset at index relative to itself
flatbuffers_string(string:string, index:int) -> stringreturns a flatbuffer string whose offset is at given index
flatbuffers_binary_to_json(schemas:string, binary:string, includedirs:[string]) -> string, string?returns a JSON string generated from the given binary and corresponding schema.if there was an error parsing the schema, the error will be in the second returnvalue, or nil for no error
flatbuffers_json_to_binary(schema:string, json:string, includedirs:[string]) -> string, string?returns a binary flatbuffer generated from the given json and corresponding schema.if there was an error parsing the schema, the error will be in the second returnvalue, or nil for no error

parsedata

parse_data(typeid:typeid, stringdata:string) -> any?, string?parses a string containing a data structure in lobster syntax (what you get if you convert an arbitrary data structure to a string) back into a data structure. supports int/float/string/vector and classes. classes will be forced to be compatible with their current definitions, i.e. too many elements will be truncated, missing elements will be set to 0/nil if possible. useful for simple file formats. returns the value and an error string as second return value (or nil if no error)
treesheets-1.0.2/TS/docs/tutorial.html000077500000000000000000000302541352107072600177060ustar00rootroot00000000000000 TreeSheets Tutorial

Tutorial & Feature description

There are 3 ways to learn how TreeSheets works:

1. Live In-App Tutorial:

Try out functionality live while reading about it, in the tutorial document that loads up when you first start the program (or press F1).

2. Watch this video:

William Ranvaud kindly made a tutorial video:

3. Or, read about it on this page:

[Note: The text / images below are a bit out of date, but should still give you the general idea.]

Start by creating a new grid (menu File/New, or CTRL+N). Don't worry too much about dimensions, inserting/deleting rows and columns is the easiest thing.

To enter data, simply LeftClick inside a cell to select it, and start typing:

Once you start typing on a selected cell, a thinner border will indicate that you are in text edit mode (similar to spreadsheets).

Select a grid line (LeftClick):

Now start typing to insert a row or column at that location. The new content will end up at between the cells you clicked at (the thicker part of the line selection):

Similarly, we can delete rows or columns by selecting a grid line again:

and then using the BACKSPACE key (for the row above, or the column before) or the DELETE key (for the row below, or the column after) to delete:

This intuititively works much like a text editor, try it out. Don't worry about accidental deletions, there's unlimited undo on any actions (Edit/Undo or CTRL+Z).

Saving (File/Save or CTRL+S, File/Save As) and Loading (File/Open or CTRL+O) work as you expect from any productivity application. TreeSheets automatically loads the last saved .cts file on startup. Use File/Export As XML / HTML / Text if you need to use your data outside of TreeSheets.

These are the basics of editing a single grid, but the real fun only starts when you start organizing your data with grids inside other grids. Simply select a single cell, and use Edit/New Grid or INSERT:

->

The cell you had selected now has a 1x1 subgrid. Edit this cell, and add some additional cells to this new grid to get the hang of how this new grid works in relation to its parent:

You can select multiple cells simply by using LeftClick+Drag, much like in spreadsheets. This even works across grid hierarchy levels, where crossing boundaries will automatically select the entire child:

or

Some operations work only on single cells (such as inserting new data above), but many also work on these larger selections. For example, you can use DELETE to clear/remove any sub selection of a grid, and CTRL+LEFT|RIGHT|UP|DOWN to move a selection around inside a grid:

->

Notice that with every editing operation, resizing to content is automatic. TreeSheets makes organizing data in complex ways really easy, and this way you always get the most compact layout with the ideal usage of space. You can influence how much space anything takes up by using SHIFT+MouseWheel with any amount of cells selected:

This changes the relative size of a cell. It is relative to how deeply it is nested (as you saw, a nested grid already had a smaller font). Using relative size is a great tool to make certain important things (such as captions) stand out, and less important data still readable, but very small and thus taking up less space. TreeSheets has been designed with the philosophy that for very large and complex sets of data you should simply be able to shrink data (down to a single pixel per character!) rather than using excessive amounts of space that would require a lot of scrolling around. But once you make something unreadably small, how can you make it readable/editable again? This is where TreeSheets' zooming feature comes in. Simply make any selection, at any level of nesting, and then use the CTRL+MouseWheel (forward):

Every click of the MouseWheel will zoom you in one level, so even very deeply nested grids are instantly reachable with a quick flick of the MouseWheel. And since text sizes are relative, the root of what is currently displayed will always be the default font size, making it readable and editable. This system allows you to create TreeSheets containing huge amounts of data, where only the overal structure is visible at the root level, yet everything is quickly within reach.

Zooming out back to the root is even easier since it doesn't even require a selection: just flick your MouseWheel in the backwards direction.

(Any use of the MouseWheel can be replaced with PageUp/PageDown, which may be more convenient on laptops).

TreeSheets will show scrollbars when the current data doesn't fit on screen, but you are encouraged to find out how much easier it is to work without scrollbars, by shrinking items till they fit. You can shrink unimportant text down to single pixels (!) which is then only readably by zooming in.

Another tool to affect the layout of your TreeSheets is the the column width. TreeSheets treats each cell as a single line of text in terms of editing, but you can have that line being word-wrapped across any number of lines you choose. This is useful to stop long lines from stretching the layout of your data. Simply use ALT+ScrollWheel to increase or decrease the column width:

originally:

smaller:

very small:

Cut (Edit/Cut or CTRL+X), Copy (Edit/Copy or CTRL+C) and Paste (Edit/Paste or CTRL+V) work as you expect, though the destination for Paste is expected to be a single cell. Copy and Paste works too and from other applications as well, with any selection being converted to lines of text, with indentation indicating hierarchy levels. Similarly, if you have any text that uses indentention for hierarchy, pasting it into TreeSheets will reproduce that structure.

Other fun functionality to try:

  • Use the cursor keys to move your selection around, or even Enter to move to the next line

  • Import from XML, or copy paste any ascii text into a cell with indentation will create a tree structure according to the indentation

  • Set your favourite font to view your TreeSheets with (View/Pick Default Font)

  • You can add images to any cell (Edit/Images...). The image will be conveniently stored as part of the file. Once you have loaded an image into a cell once, you can copy paste it to any number of cells within the sheet. Images are always rendered in front of any text that is also part of the cell (and above any subgrid), if you want a different orientation, simply put text and images in seperate cells.

  • TreeSheets has lots of styling and layout options (check out the screenshots page for some examples)
treesheets-1.0.2/TS/examples/000077500000000000000000000000001352107072600160345ustar00rootroot00000000000000treesheets-1.0.2/TS/examples/complex_eval.cts000066400000000000000000000020101352107072600212160ustar00rootroot00000000000000TSFFDxXOAv0`K`߃uKAm)Fd"8pA=4#P 9|ާRD @'5X78t8B1Tf*deJ* h&krU\Dꩁڃ :]!7{JF! U*(X8R*<gR07]\,Rbaa~5d{#k{^e˶Y/1zT2H3fwGݪ1GxXr~nи6e2a vNۘdY!ra$st{>G|3rj,HY:ƮJyHꖍ|9 cuOqzȡO@g 0yIxC&W̹ۺS)},)Hc*\pa`p0#ޚ P"DL0 , 6VhOayjh_{A"vrn+KWF/ ߨ+|㕢i s76`H(&iy!/OOuRw{gW액 ?y(2Lx}⩵8w)YP i ӨniJIYNWCRơyLY %cǞYOVa1>THGukoo=04D1 (TC Oɨ3/Cm4Xg3''!treesheets-1.0.2/TS/examples/contrib/help/000077500000000000000000000000001352107072600204245ustar00rootroot00000000000000treesheets-1.0.2/TS/examples/contrib/help/treesheet key quick reference (by WiM) 3 posted.cts000066400000000000000000000075171352107072600315210ustar00rootroot00000000000000TSFFIPNG  IHDR߃XsBITOIDATxQn(Eݣ3 { G$q5su¸L8I^Pc_?M3_zC+m;GqM l[_i?*I'1ϗ cK0?$?sgrI~ l;m-;78x1 5u0b) (P<@߯CٯνpHg< ?gWu8U/%8Xj${׳_mV)}麲~ah mJ_9mV >FJm [Juk:s5k̦upB˪q`J?J{yaS} s%&[sZ++{{f)JiZ:^B]_]j |6DL$}J ;ء`0P ?@A~2~PVO1G`/5[=mne[g]wc@A3UF|N:KԂ6t63d~V>8' H$N,fNHZ=E񃆬Hs_y(H s#Xu"Y73~Y*HYfS88 ?@A~ (Y0|Aǖ-,dZ++QN1t QtC` M Tch&! whY` M LBA{ˇ4 1tA`(P ?@A~~%.jy>!~fv-+Q4E?1AsA:m?h>Q M tfk?쯃 (P$E-'=@?hAM >64~PVe&?hQfҺ ~ЍuP ?@A~ xu{ĝzZJ_rbT䏩`?H(~Pboti,+ n܀-7CCW'g;QLɾT&_BP+5M ]*X꧞CA 09@Kf񃆯H|pxR~`4·M}; :t?hoYdCӊ:^G7t|RMg~(_ (P ?@SX|3kYԍC6P'A@ً'ﴁ,NU_~ dy},DmDķ,!R7 dydV.N4A`(PϿM-IENDB`DxY}lG;!8ui.IB>m݄%MӸ)9toong8A R/j$RLD ! P]H`L)U3{fg/6,{}~wg>ȻYO,b`ws@"s;\Okk@yXŲdPRt3Crn>QK(IPSgZ|sjX8;Y`_K梄q6 u?rAd|VN_6t9!t\iXӳ%mK-qNVבǎja\_j.x.HƌBхip搗\gBUnA B2TUu;_K\IUrXot(M/ޞdB N6,U1|+46EIHFSTY{A@n4[xГsݞd&ww4 IF$IȤ jK{wr9@+ m0yJjmêQ +,rȫ1 ?T_(3-j3v8qb_+!v7vkw7~D2i:&P-01= <6.Pis}‫\U HG.gb=1hmf][hL = J8/ 2MK b'\ZYCxTZ-XRu,Ծ&{MDccQ4r":r$SWJ5g2RA"ӕ7l#:7 7}&#qw5-z cy7G2-^8TMhk[M=kc&`._] VO~R}e~.e*eL… !KO&UL^:.!'T'bfhF:eLސ2G=z7s(A8Mi7BT(D2eN~Xp$_=#d۳(0n$%M)I>_u&TƪQI2-"eoQ̌nY[yI*LU:KU"M&_~A2i1``Ef؃NMkq[JP_0fn{޶xK* 1ǀӺI  `*ܽ0Id%F—*8<0\RN94J(jq92f*DM^b pzLnƻhvd,F}mxʔjGzg-dW!ki {R{[x$򵫎epJVnfUQG|+] z7` vn{`M>o:l'>K\mů5_];k B-2!:X cNPl`D,r5wѺ^]o^7 Fj]}~k+SFt[- N=BqQ2KFx4W|{gN]C,#?#oU}|Ti5M~ō<$=.PּVnJ޺qE$wI[?昽_Яm}C.%1 k*צR,L/l%#bJ:'';zyAx+t#-qtreesheets-1.0.2/TS/examples/contrib/help/treesheet menu including images landscape final.cts000066400000000000000000000325651352107072600322370ustar00rootroot00000000000000TSFFIPNG  IHDR갧sBITO$IDATHݕ[l QgfwfgǥqjWBJT$H=AD/D)!øBq'.2sKVL *G5#uqyIn뗞mZx6q3Ͳ\PzO2i\fGkvxeԆ0O*qƆ2VC!BX#l Һ>:uΧ5|nTGh~`h'."tdRڽUd(v:VdRkry擘%걢#JR4H;0u9[gW#T*J CqIENDB`IPNG  IHDRK4sBITOIDAT8͕;U~_vgK3&Y`ABPNZd !J,uF1`*"jF7kfTr.?s=|oP؃0+|C_}~4QRBB#֮It?#VxP@/Q֫ѵ @~]zW2:mV0G0Q.@/L?[?^=g~"![kp2dyoI$9N,UZkrezfو؈hv!t{_vp}t*IJ<#nVil^nb iM0H(HPPP (hPy^xh@E%9'g_Z3Cq %иcU.f֒_ =GowpJ3V"b"tYzXkO˗G,̣8ICA@%ޗ?h3l]Xd*9K1cBH|ۏB}p;aE.̍d#fs=ӎiǬg޻Qm=|S5gzIU$ if#Q4jRUə8T!  H DD'`Tꢠ*# @DZ'xIGzuDH;3g\),Ԓ%XZ6{&G{DED{Dh\RD>ܭ3w ,0\Ai舔,h rm3IfG@ ?_/8YyjwNc󷮨6A҈oYMD`;ł ٧m~hx:imMhf}W'A>?tS>nykSCCReUKmVbPܭ\нFrGeK.UZ\h^/^0ItR &7^@DJdm]ytO3at O'ߤ3z&3[-KsMw<?dRܥ0bGIENDB`IPNG  IHDR?;sBITOIDATHkHSa.ג8jP !HE7 $.b~RDeE$XAIID%>tQws99},j{yx^IDCPqO҃UUq&1ZI|AY +4( *a]nm&\\VvlH^W7[TVǭM( )mXxEqc(+Iˮ hW5 je[jNݱkň_H'h(d)jlED唜zѡΞ:&D"p8Q`#oN@ 6(*$ޙ9o# ?!2O|HݼJ tÝR5YӇQE-53xXqkIENDB`IPNG  IHDRsBITOuIDAT8͕Q:=ʲ11ɚh$+/``h" h m>Ufh鮯??:ҿ W& AJd_׵KwO>yX@`v]ٹnZ\$c|{+WV-;k2\T "X{vB 4%a1e!A p)Ա|jgw( x w\|}u"wm坜rRXiOƵѧ@6ᎄRcX4z;EJܔ_Pfɧtq]G+g}jF]kNfRiK319:j.br+:]nxB u nLf@ RGbh^DUZuE9Qcc4E]~o܃Iˊ2;W<|dai5` w_IENDB`IPNG  IHDRosBITOIDAT8MLeoвײB:mL84YbbBL#%Ƌ.O;</߶f&>/V8+-9PF)oֹ FUYi\)Mdg׎w9&kJ//}`cs8I0(Ot'yO΂}ǖUAYh\&[̨ZtaD~ӦO޽6 MS9\Q/gNtw0%{fbL<-L{GBU V:ws;ghE߱Hۻbzܻ"rY_[glog-P4,B30}tfb2+SQesB%)AܠC.&!aQEd<9)t h2F`]|g&-1R6!CRAt AP2.[.sSB5z?"DAա=nopo7xG /Km A% D겄>jI. TQfr&6l)Bt't7ۉ@/ݟgmun2,s<8f IFoUI;泝rPxy:~,cyY0L_uX=37n>vR_~Oba% heJ^h=b2c=8xlk.$pԭu&8@`GkIyo'<]`PE(aqe $"ޜ륎WN'"0*.}l. h1'KُSU->Hsɛ_- ΙŰdﳴnw֚VGpg&8SXdfBs(TX$7o #In ,ϷU@*纻m *@#gE`ZyÚ(HEB1 ҒC㽏L}2Y@ ?| *Lh"k x{ PB̾O+UIYem {}Ѩq+%5Wcts TrvkAx <;[B!qRH<́< xg6G@I:A$Y/3^C(hv\ݖ[`s$~!S~Z9~9u`" a77h0؜ٳ_X/=2-g˭gf EѲ-~>J s*ɀ]$\IENDB`IPNG  IHDR"sBITO9IDAT8k\u{;7ɤWPSD-)ňvDn] >6]m#C LyTwnsdtw>c<~o KeU, y*l~ݞaS##'cvc16^4I7B(4W\>H@f U[YRRVW|~.8J3!t5Mخ"\>7BkJQ)T $[\r"쓌mVkT^ymW\KQ)Dyr<}^(TJmZRF˘PkOOeվ^3DsS^w.̲vJBnTtb2mKxs}^| >{?Z$檦PIENDB`IPNG  IHDR#sBITOIDAT8͔MkSA3smZڪM*VRPAZq77R\/t%hK)6%Ғ[&In9.$M|39ϋ8@oԺ/mDDD0ԃYDXDj./^n{X,V.U<"a2!Qʲ,g676r_~(b"4 *ZNV+zF;)7ۊ ODMnzk51Te,. x"?DlgtwԹp.tiL|ǬeB1Jr ͒z~"v3gv]pNPڧRdZؕnS Ņ$ 3~Zˤ2QAӟ0 d]ҏH>A)|{$t/<03R蝺 /y`mPeCF]{Ǽu|_Ő_`e,.#lRo1h5A +k6t-}u"`$x  ##^4^uEqMe@0|@"{/UMSfK,J;]^0pgO_H0?FIENDB`IPNG  IHDRfvv  єdo? :+4V 091jiF( a]ߊjScg9ֿE\`ʎql9LY0TX:'&mD}]AɁ 'R.э 8Uuk}>-!?VٳgcV~5s\Cڿx %!G޼v[hMg,bl 2$6(9!E*?]Vbm{N/u[uH/!__k߶ܻK|dI[jg!^G~fbSmI ;N @e`zOk"b ^ Wn[sԇP-'#۴\:u]DK۷G[jk Ϲs+4!BV[F]~U?$UḄsX$ѤE.[~d9^8L`IIQF_ aZ)x=Yf)9 F)lUWr=-4f(D⫂R̲*zUPW"IÛ̜ͩ[h+:8] eJDV$ Xޭ&ёɺCe,1m)kFUBG88))٭PTZGKӠDBu}RIFy*%-BP]aDl7})"R{NP#+H }~[G&?]21kS*U#^/w-_5o0W`&hr>:Q>%q5? V=uMz =Q\,CV,lK;Ѥn}c/E6[ŇnG0)\RkM qQ~ >i<3>8 ZC+7"j8sy-^┘x'4x9pmgF9*x 3QB,Eɺ.204&ĉ0Du ڞz^x }8p2ms?8uuuE%hq׽N 'cxc'uK!O)KN+TA XBYA#R;Aļ&lzic.&'JaUEbٹnַY>|HRM2DP CE׶5gr)GhOZ~Fiƍ9NqPb0.Ts;]cLTsV#wp/#BI6 bOYCǯ`ò@.C"sXpfqœ@)6C.|J\ 6d#WAln*Vd [s I5(er5"9~0 ē2II-b^io?\-QĎ"7F(9YE#66<َxΙ/F3/l,!J6&zǝ8EkHz}qXjtC9kUZ"o*'fW0旄Xw^B%, "ϟZ@Nbjosl"RԼ}QTSX>•KFې~3>7 X[У}R>j85'h6:o5eKRA[4yc̷ ;N33l[O/s)IZH6"C%lC.$@N2[4UѦ +̂x|j$p f5'oipǀX%j4dZW qS#S x2 nq񤇮Qp ;5&?&m M'%Oo\q 2v5X8n_*~gtq}m(sIdb';h)pbS^LeUEf%\3GqlnnKoTE$IDӧK* vaDP]y(XPM""pK,D`_^aӶ0X\EUqV6|柪%V%ֻ fpw'B%,s=(bЇtp"*Ƿ2 q y0IR m,UJ]Zf.vZmf֢[#'5"2ᶴ%U,PdվZ=pmתLUYTF-p))2᯶b&kѺ5R9C'+ !BF 9"4nqa(ViR4ɮ퐐~ Qk l Ƣ[UrEMއnKց|w"8,NYrnXx9)E*Ukqhs} عdmtzv4I,EԳ}CFO6 a+|$2cFvbW‡z̿<,9}sO^5h?}/L(<"a_Eer\hi]_NYsl4QziMυӽPaR*ә2{\g߷8khlc[Ͻr89 qk79HHj}E6:di{^vY F{!lK*%X~97U \%]c_SW̬lΡﮜe,@u7D$i9k"vY QESJP ݐg^>oCF";*,@IhAh_0-2rCd&Z#A*ͼW>& ˰U@>h7.gvyUDs8a``=+O/e>)}tU*_aک[7X^WT7a TG6E׉$̉ |^omz䤦w^4ClGg=dn/L}ݟur~>χaM~}LkˎEcNJTQI6"dʅ+*l!%5CFN: 9o,J!r>̐ގ?S;w*)X[{Et8CtU treesheets-1.0.2/TS/examples/contrib/structogram-quicksort.cts000066400000000000000000000003551352107072600246060ustar00rootroot00000000000000TSFFDxڍj0 Ք^e=kh.&SZ$dЧoe71,, M-Oޱ n zAgj"V[MAUQl4h0ə[PJ|hLF䊵⿰&?N59v+vsV*H<jNt1etiO=З~$.:W)`̇?7y;$9treesheets-1.0.2/TS/examples/contrib/weekly+calendar.cts000066400000000000000000000013331352107072600232540ustar00rootroot00000000000000TSFFDxYo0x?]v MIkY%(*7qlg(`m#yV"{7#6o4QT+5p}vNympX%2;{?^/clZZxѾtD?']qߍ_J7y̍7{k8cMa?aof},߾2.Q .ͬRzbJ~αފvb<.ʪ;j !piV(  Vjۓv)yhW( XoBJA (aQx\-rNڑhh͒:@>#@>:+4K$' I2)|J&4hM w.JatqڻqFG)GDhS:8 ,ʠ 0L[m2&7%H"p!r?yX[m'קPS1+ЀNJ#獳dJoJ:v/EȘߔ4kzBu zH,@,`0 &@&51K戩k~,Xx8Epjn92\Ss~Pϝ\_قP[DD:[C /tQ# V'[gnݨpJLLtreesheets-1.0.2/TS/examples/contrib/what-i-like-about-treesheets.cts000066400000000000000000000012371352107072600256060ustar00rootroot00000000000000TSFFDxڕVn@@IP!a$^HE"^H)}`Z`OQ;ǩ@>}=Lөrs\{g8:\WDaoE%u4h³=-mw8D\!0]3#JfADCf 93B/⥴G=F2pi-͛q)4O%|".@.,a~o0{i`W0g@ML#yj,f 九 $-fT`W{bHF2sXG˔j(%Lڄ6r*gc! KkMUX?y;CY$0L2$(~UY`e0Fþj7] x,r5dY-H/Y!4& }/xoWH= J2vPN| !D)f%&Q;u.Ud́31%wjۡQjw^OЙ꺡s;WE`U"6u{ 6#+'xM#xowq ^9+[emDG ip*poW%pà64)ݥ"vjHdWߍ- HΕ6SRqm^6{itreesheets-1.0.2/TS/examples/imported_from_xml_examples/000077500000000000000000000000001352107072600234605ustar00rootroot00000000000000treesheets-1.0.2/TS/examples/imported_from_xml_examples/books.cts000066400000000000000000000024461352107072600253160ustar00rootroot00000000000000TSFF Dxڵ[oGCM}C#U-ؑsޒ%)N!wzG,36O_nEX{g=;x݆|}޾}q]^#Z\ۖ2<|v9:tb,y24[x1c6f3Qs^ 4sCWMd҈Ovw7n/x/伽^ŋt-i͹%^yVQ6:ó&C YlT:XW!|vsGeJ@٨t"KݺZ(S|ŞJ94 ʜW/W 5C(ePp>c!ʩ|7L^:St4ULfK M7y /=G,zVV)퇌m@Wou f-s:*θ2q +vd&M] 󁌚D=Oɐclzj|RBeLjW&Ҋ9kD:%9. Nd!mK5Qб4`jߡbt'+m^+g7?N8U>AH"wT-!Aa0s6zgd\A]0x7Rp^c:t8-cЎ͸%rW;CyuQT1O}I[䃙Pm40]j\̴Uq$ MRV!V'ړy)ʫe(QJ"9sMZVh#}ȧc=86x$ j 䢘E}lD.mnҡW9AZ2YQǞ[L;Rd58Yw9 7)μPۀZIx|\Z&$ʫܽF -S8j`4@g9Yx^B`*HVCց-̈ı0F=o DL?s ~cq7ڻ*!K=MOn@EpӶTeWD=nUU>Sܹ f`Faԁumd(%9'0sz.N*=Z~S PAܣ}ahe\Lfg'Z .(5񘗀ufWdMq`#HpBegSNu %ﮠTG8L?/y5_ў(+ wdϒ\8{X']+"tNTYg;u ϳ[ NGrqϮl)9´!]]4o0aiF<c㷴42sn 71!:L=@;k#-B@ (= +Ҹ;ߑ}:tiY7C^| 2 ȟӌ y63ٚ"}mdc%T=WbYAF] ɹRݡ=ٔ':QXQ-[&*NI}*K'Y7^ v\o9g5㲬dJB)XC{ Zg(XEcI4Cφ3$ OX7GI ́ ;BcdD;vOf*C`YW^\cV݂ft/i]麃g 9 "2RT&|N0Ţ!$ t q?h8s%rSQKjokΩŪŒHiPZl{^]zآ;!;*yK$a|cr}#,wu[:F-܄,Pg #rMtVꇚSS& ^pWzۘ?s?-X%M0`2qifS.LK-}F:(^Et>oW] treesheets-1.0.2/TS/examples/imported_from_xml_examples/menu.cts000066400000000000000000000005731352107072600251440ustar00rootroot00000000000000TSFF DxڭKK@S77 -mG+GiyM6l m"XMlavW׵gep)vkCsօQcJAfhLi<.Ք Tb P&&\T6R "*]mo%Zt^'K\ +8E&QlMZ; joM؃[$3Vڍ2t\Q0hGNa,lG"Mʣ Y̏M!/vv_R RuJ z NG\pu_sOͿ!B_e4.[ */$29hUf~|&;treesheets-1.0.2/TS/examples/imported_from_xml_examples/plant_catalog.cts000066400000000000000000000023061352107072600270040ustar00rootroot00000000000000TSFFDx͙OFUmKWq*r|K-m*σ}xǞSsz, ha(ِ 89ٸpg;j|k$.ꂫ:jjFps PƝI>:{n٭=u$>lt0yPZ٩Ppck GKq 3薻!7u]L,eSrVqi,ɤV)4 ]4|_&=AG\۶W%x D16R]ftvGn#kv"(ه8ՅiI|x#ru~PWI53*w`$ɺbm0ȇ/uT:VVGgr.487 9*I}]x⌗}3 ;GPJ&#I+"!Y!qb(ӦEᯯQGzbAJjfo +3n?JǒP  3ʛݙk)JGR _|ctq1Pa.ͨ1-t] \' ؁Fy֨E^~Xh k#J̛Ҡ#Uq]8FJmخ̂٘F կ  #M65>xc]*i_urKli3? 춠XWغS9ud]5uC؈˙pO+n b'ӄihfgh%wbF vhkN3?0>ufe$@0b6w`6 zC`TGra76|׮kT L!\`'V0s 5k)]1G6qHIPo-L~Tjut/!_(s߈ZS]p u+{uXݰȺ~9Kϯ7-6zDG{OM\]֒O..sUvSJ:M&ڹ2+' .%(c.7YiY %hB1b􈿺֩K /(U_);lǍ,o[s̱Z->* ɄSP30NWT" IR1v Bq~':Duv)_//0"9(ytreesheets-1.0.2/TS/examples/personel_file.cts000066400000000000000000003324761352107072600214140ustar00rootroot00000000000000TSFFI@PNG  IHDR00WsBIT|dsIDAThilz}88@.rR4Ph)TEP@H)jM!@"=hIHBp؉w]1C9C&y?hF3ь<\l!ᗨQ.f/.אyjXd`KK@/_\9kR=w֏@"I(S\2=ce^T^S-M ,b}wX_ĥha}f%!,G,5N qwC ewz>bcibνyzxZJAuMs ԌQ8N@RTuX3h.f'|ϕRnoƊ ;^vPоc>{luu`{ґOVgNS P tG'b/^?dKqiJpuO1MHjkAD?74%ħP|!:wDi䙡j(T~ySX_sKRO oAϝj4a] V<Ɖb(ip( -@u!-s9yH=^(xr2Es/ΟWS]\ḐRЙJc*p&;&\,f rsMSy`$>Qwtף3 }|S PE!0Iúô$1smY"ɂ LkhΛӕbzs@4yzhFB!x];©~:f29Bla`oY$La+wg(YV ޙTx*}tkYQ~ykM"EB) b,6aӺ)n4[~|?W +\xyY4ʲL,z}t VT2in@o*Rx1eKX‘)ecqߜ%6Eן/ Mbء6 .l5]N݈)L&0"-z-&:CWr!M/;BK~j  ݀J_I%E4LBWJ0 bI䂕6)5$K0yﯸPC nJvV,)uQ ` nj!"-N[˶.InWc/m|B0&]I:"G궚+k…B:88!pmc$j(ґ>}a$>:77R+B ) la\6Hp0qvKbk<{{sAӚ:$K\< zcob KZrIz[tik>mɑ a w''6YBL)/h8) M,HmۚңKh7:Vnrw>9};}֑ԈۈtxHFH$Y'Cɒ):Sٶ_!9=RwZ@_۾ٖƫiJ q,2=l?m)DvKU:G~ Urtd FG$ME(GNEs/n_}O9 8X$\mtMzn lD5k_=Hfد@t^30p蔛$ M(<v{?F2$]b-A=>ݷkP9tؖ5ODDXF{yHf4hF<6#αIENDB`I?PNG  IHDR<PʨsBITO IDAThm{Wlq^NO^\d R$Md[ W/T ӔD@@ ^\|⤝VÞsp)iթ}fջ_?;`hHU'|eUUBfN"1F UHDD$"**+ yX@ 1O>2zxiC^ :+"e[cD"(HR"@"""TIIEDA(@ş~xT%L2l1`\AH)z|ׇO)!at٢̊-*b PEA)~~_="D%X8xfDRH\NbU\i6uS!5l$ι*tEcg1I""*6øn @UYry|Z Q@4ąκ~pՈ8%&UL_6P7PDu)& bd1e.r$>nh6"gTUDUHATAAi*^kN[&ɭ?~jmKkIŀNѰ,ˬILh bR2bszYUD HDz^(@*F"EAEBE`#N&fS3{ǧf%DdSfavl2ga$͜%fkraL)mBW fdU'0ļy.1m6̺zByAtLPlq:1Hd,13!DTPB`i}aS;2βH \R̰ P{+#ӏ?$'ڙ3h2!! [kY3#"ZCD XЪ6,T;󀀄H̅: mHRcfMgGO?{An8Z 3A3k k,k1Zk1133!$@R*Ddm26uݠ1xdÃrqoK"kcal{QY9kD$! #31! dfClQ*s릵NmΖu'KJx^p7 bc/U5Br9` Pgd [Ce&cʘ*֥w~x~۶G f<]/C(UZ;Lʲ$Bxl*nА|lYvJTReƐEp;euy2צ'?[/ܸp.^ N$NlPqل 1ȫ,T5$R OcQ" @$eغ@]@dTaqo2}֍>7+LCBP5x@DF#R}&HܺM@(2lD5Ꭱ%lnc4,Q YFS,%Lwv&jq 2 w8U{nQccR@dk2rgihH5#!H$227e82fg#M1s/bC&RVċSJLm>E9&h$c&92Γ@ `8*0ec1ff" Bf9|xԷ]Y8/Ey""2!qQ9F!2eUMIRJ8tJ" hk 2l *]. TtBX<,$ǟCC n,!"2̚"MTU#g'1$ @͋&xD<$* 0Q2/M I2c#1FCsh^Bd !d"'eYdl5GJR$QT ¶={fXk/<(""Zk֘#*mᣡ"6&ǾA{K\ RN!M (!1t~sqxv!($!19绞hBݲ hXC!vU+ZHI-q\p״S*⻞PR21Y \EJ)ϲm69&9ZVuR ә#:9zl[U!Ê6sI%v! ; MDE9*KCTl\V ).3ĒR״{R1MR1}Y d hP̈pTG>Y.1bX60(,[,W VhT:cCuJg b1B>$/8D1 IIT -m=M$I÷zAG_0*}6&e.Y bѷl>FjND(B>b$mnUoTPZE( mfK @ bak~r÷~QwN\]l/LIC[;BG(8\mgo߲QMZfy.ժU Qc [1E%LW]+2u=*c| yI@Ԡ–E(E.<ݻk׮zJm}D._V䮮G4'Ohg>NC۶=>;]7jǨYU{o|re^T(hM @:X+jq`keUZk|pXΖfABm@(iR5fYeVUȐDp zbwf՛7?^mM-!\OOgo|ˆݕK"cGSd\e<3><I"?O WHo騺۴]棼;3@L%Qh-܆5͜32%L";{Hc/{ﻎHɸZçLJEUYkˢ$@T!C7Mw'3pK=~t/~{{{ )0QTU=T 0!BL1Fak@ l(&() !5̀ HEtztJѧ*86jX M|<gfj4̜eA ("3I(@IHAa4}( 'EчK6)(1)u yn*UQg%K@EĪlBm &xpl-EI "cDH fd֘6!Ŧi E@ll\!y3MEI]ޝb*"+T7nE%0kPZe v czҦ4Mߴyq^wggAS.ɘSiI;;;{B"O1/`ߴl''fR&ߵea˲L(ٴO?{|z\^U"!J!1~/) P=OŨg]L-]&^1η‘N'n `8xyWA$O ~BUM!",+,ޟ-_ͻwmzYH 98ܓ{׿^hv'Ũ̫ftfr8g'I!$OFW'8ouh/Ё" !reem:v.w"&k0}~lھ3ξާwwn]T󃫗u#vv_|鹺YNFjx[E L` `a([LH9E5o~b!cX2h$@ʚ2#:4?gwⳇoכ:|Èp@A* Å*_z;6еmUUY%%&rYfƓɭ[7o\Oj6W]cG'އ Q5qLѰu˅l"L{Է1y;Z`^RV}gϿY,l43_nUl2 8DѨ{b"{2;{x|w~\ˋb4NBf)SHߴݪ᝟~*G&/JΝ[|CU5*P$sLd:"1d2sX+;I9:;93U5r&ѦmWlyt|hYjl3Ll kJ1$DlZV#tE9~g_§o~3STI46`ۅu0c2LUY՛ZDl(B}rT1!*!b@L&%wWE)Q37}jpYSt6M'EdzݝWfO>Ͼ=@M)avLLL,"f>Ř0aJ>TUS{o.F֢&[9f! ,;?zW_}m<)iT|6~i75hgs泯MwWZ (ɸM$VP00ϲbek#* DQF dR+bH.ˌ7ngyԍx4^~ _|拿{ֲ@7o1 4>5* 1RL)1dPA D U\E{Fba [Dt Y/>AE囷-e'+/V|22__"! ,*0TV,jD#D4Φa~.:G5O>;r-,oM7?Ώ_Obw!![k-DD&I|BU…-GD9L VObS@R콄wѧѝGy|Q~kWo]R43D JƠ$( B)Ʌ^aL$ uMQ%(!2ì_O?G|wu'Iw~uZ5}uicvoQY}gYH2LaA)e@13a8H6:S÷Ew-9=]},gGz6۹~JƸ^.v X ҂`>E%42|W~>bk\K! G X;~g ^}O~z>: EJȽ "^zy2.b{kE%B@dDN4& Ch䩜Au4zLsi$׵i]CD.FK?I=٬Nߝ[-۷o'2 ! S4PR s AhKd.;)A".8ZJiW: !˫N5-c_{~ җ_|7YGd~]lup㙗EޙͭAI=`4999YwX50Y3`BBA:7Į1&s\U7jJ)yK)VbCL63w{GIݝ֭lMljg7]ʭQj6,$c_F$4*)R oܺ2w}lD" & 4x{5(;ݼx6 )JJ1yQ>s/ld֕@ El6l Ҷ* 2PUUι,ʲ,˲L&1Ʀi9km,sC5!"4CiRVLbA;>fl@чd1Ɓc.+\ zu8_CM|{O~:tm{xx^ Qf-"&Qd0BbNj]IXOObN'aΪó߿h<[h4yp1&"!UQ lwB!k=xkD !x'h4ZVQ([q*"Fx٭zrrb\"Ĵ8=],Y5><:?(Ƙ;Y|L]eE>f8S*<ԆSVIENDB`I?PNG  IHDR;P(t}sBITOIDATh{I%וÛrYU$bK6`a/ {卽0`{f^ m Lk"EJ85*#x/ERE2*"wO| o 8VUD$@  (9dE2;%3lPAI|~\>|ߎη13'"BRCN4` H))Hl",BFDUBRPP<XUA7a}^zAmGiV J!Reu:f{۫jLIDQ !8OVUDm@պ_}#_"7i] C/bWf5_x<ީHbTERc P7㗈ܔ/TU5 1–b}zGAclޙ\sg8EѿGH_z/kxi]lȚN/~>;q- UulʲtEduFh("MӨD2|kM|_X :}O~z3z("bvMtgkr֭`$}(}_A QEWbͯ~w(TQ5w&jykkXĆ IbrfW''/t;cRkME@7ܟADM1z:.ů⋔*cm-$NMf[gxP$y6=].gӧ1vw%Ԕ!ka@)d~d~o49zcfmKU58gjPxfȘ.6Fu=iw~ѣ/߹}܅`-MUUp76̄+SJ{_WmZvR[K˪r,"j˕D^5Geju]^B@sKbj5޼oA?+Ю^%n7֋Cyﻮ0D9g`uºhP9L\" [U>zx?,/Wonr7.7ڿs4o֓'O9 R@$-]1,MP E %0C{{;A1MQˢ(|1WmlM,t~޷lm-,DDdPeTPB3"O?$1Xk0-3p8qY^;ع}+[!P`TVC@ l;[ɰU3]5U۶`Դ]Ua# *蹯P@Uӳ?}ADD2#opTZ*3uˢxtuw{gPWLF"S}]&f1^jɃ5! ^݅@ֺ'}?;ZDV ylUrl]ۢ(<(&U5, "@"`얉v~}H0`..V 0\x }s^t:E Y @iit; LuP@˅ Qse=.# .TMv2>==]Vs^@<0׭?I9h @04<)lAjI` c a!6sRR邦l8_ҝ?gӭbyd0H§s|y0<'@a\׵BA"?J(9H))#@o `H{6rLE$U 9h-Cj%):&,W]3tupwtmԣM<9甂0#!LD HHd DҤ1+e,1vN%o[ W!^ͭ5+"i!V$@DUbfκ,ڦ6Xaf$Yk .sB;[6$@ݼ~/O^ !n>D$"ܿ)EDdB2lsιu f,3f)%DBXQ1HD1vB| EPA:5)H@F@)#%Tarqb$59!d՘Sr1umhbb 5Ƙzu=9zKe жmΙ aB P Qg935n6suMr_8y޶z݈hY1F~ bUY̞jc܍ju΁ a&TJj5`nlp(C^vy=n XOW_a8f!G[𥨩WkH 4MJ QQIc#[D„&#aܥ[0v^ur{4],CJ(z<ٝ_gUYuGvfgTHBd>$&A=EB tY2u\!uJ#t6Macޝn2NOb_w/ݝ[o~}m%fyTۻvWm'L1Dr"S[Ea!3Ѩm!$VzJ9U0Rz'$se;\ iL͠*B<~nݺ5qƴz9Ïz}u&"$R$PP!UP~3\dTאreLAò],fںʍDDvd¶,VyQUF,r˷`<uognl\z0mnby[r@Ѽɇk_1*@E@b9Ե5qg8v57r_` U%#R¤L E(SIW]ai&P͆)v[Zd;ĬHj" nf?7ۻ /ln s$uP"`P='0 J <ȠA=eJm.VgI Y@-C*J$DDDD&VQ9WUƭdՌe[M5YԺ0Ȩk4PɳO?],RQ3 @] Y޼~UfK g"Dd4D2oUl˵F1"pJozc֫yZ&)cg1ŰΞ=yzB|,26]KsUfDETQr%7FDE֫wx姏 Ys$PAI%j1Z6@}iD/>rSX.d:Nۤjަ҇`T/FDUT *ocUӄ1)Pϟ&f9 1'g=zڵkOm[Wx_c*0%Yv))膲ٲ>}уSA@Q@P[rPA;We;k,) "gT&,T߆b"F^ym4'ƻj8)-MȚ"\UR%VD% ~At?MOylbhck@ݝ;>*Y=CȢ@E$eD31J]|2;{*JŴgGOv=NE۶`.ibVyyY'YJsᠮ+B׶]+:D9bJ-[W3歝,]Hӳ)dp8 LȚd2P7^/hhrU$̢$i[dR庹ݗoݞ?.昲el1)H#!eg H!Nӣ'ư;zٝQ U1!e1}]U/тQ!Tz A>!!KRo}T[(c۶0*r:gbf0'̙!K11SZ#`'S"BFR$T@}K7+P (P*b0$FdE2zEá:`XEQܸv͗F[ p݅rPG!4ؔ^3QL(Q!%?!1(`6Dy^x?k"9\Ix :ChӭǮV}}sPkFR*VTŨ\ +d@"#kRBc#ffv@mVmeQIQhU(,Tz'e r~YTHP ˺32!`ι)\I ܱUUBhcU 1hC ~PF*]6r`R$3`R"(p0 TZ"Fڔ۔OE~S_sV\60(lyFx!!L ,U1K_IFfe1笵Pâ(9.fլ@b] h)}㒢9($9˪ iٴ]H)kLYcN1'Q ]L!%r>==].9ǜsy:u] )'j0jbΪYZ7h]l3]Vp9_D}i 1{S E ̜m $tm)(b8TabӅz<Fh.ePV3A/Y/N]xo v@[u|m@d@"!:Ŕ*{TAD,"/`E!͞NCNW+b_@D\0C?e=L$ ԅľ $sNƜsZe4r:@]٘UփxsFDd ow1^)1>!% !xcr67Ϟ0_.x{o{Е$ (2ԆO7.+ܞlm[p4REk|QTUY#ӯ{_o]L,`ж1ݻwo4Q֗`% X үbžZ[5u}ƍ7n C)nmmrPj&p'sNq%2:~翨b61 dt`XUQ@{~,HJOW. Gܼ}(_ꎼ?G|QIPTmGi]Q)|?|>ջ]A&b5_<=zbLlU(GA9Bu\&6(. z_V^D/lj r)i4KoyRPȪ` `48zӟ?|>WUf-p !+xړNj( 2].3s\)_)i y|{oo 0 h?\T &1Y YDLߵ)g_8kr>{op8njʤ(∍DMa޸ړgOBsWEh6<{,7^v0H O>7 Lj:vvvp޺bLOq!C`}ֽ{={vrrb}QUzvmUQ:cC2Ȇd2͗ng'Oty|1d dw[1YD,>z; Ç?.b0lmm㾫ZkO?w{wrr6ƸhV tttTꯆ$ъ[c-q]+/ 8o᝻ee r9F+׮?:zSa뚈VU_>==5llʼo~?rTm昬}g9gժj8lVMrĎ kg]'5bW9ۛ{7ǟދ$?pEiה ;(͛f}v6;991{;{k !~̇ݍ߻wo>ms26}93D(JD̠\p8"fUYMVAhཛ8o9;|sҝuOW_+ʺKB!'m׭;G6SJ!9#s˲d612Z"'YD+_mleX2$Z>==y~wt~d!C?;?zz4l6_gl65H" c5Lu݆ƈH_!TU6VU hp4" *`QTg?zMk~O>~ ?yM9(p̸\>9~(}YoeY"ƺ;1IcD$Qw13!YﶶHX/(VM;=WKOXț+pgb|O>9ݹ l?裮1&\izBWUUvl9] xc~D*"PQȸ/ӊ(A1G5Ƒ3㽃fv{mFgQYmݼuřٳO>i[}{RJ1FUt$ z<Ÿs&\I-ڶdixٵ;-V7ܝf )==o^zpHJz|rl:+E$SJZ5=fQUMIB={**E`r J]^ul) {4p:b ?~ǏF$?Ҷ5@9|fp]CVh2 \!EX #2sSہI-ClQp{GOӳY4O>Y}\0D}U  دHU93w>tYU#yld](H֙z\ڶMŸغ ݨ1Yº1i׵&Dl%@}"_v= UsCDޫbp芪vQhjsF.[ڔOߛ/mzIO1z^ UEѵqH1Dŕ'tSJժ<dHY(5Ddw6 '@ )!fJIZwF0:[0 3uSuyZF5ƨJ/ncQIENDB`I?PNG  IHDR>P]Q9sBITO IDAThUYm[v49jvtܾzʮ !V0 $"A`xt**I1O'&DaLm (l޽,:D{{ɫd]&H {Ec_z)'eUhp|4C/IA 1d JAɄ`L]@Elܰ6jtfZ,r$W6ϞFᠪNYY.8fDp0^^^\,z)It !',"`ŀh`a$ QRÎ9K* jpq/'C]:(`iB癹*Mz\l;(~`ٚ J G[ӻw?O(@SES8,kUIJ"$Ɛ5hfSp IV)b2d>L ʕPӮy}h1MdŌj 1(8 ˷-7/x;P%sQ:"Xַ]ˎ6^sBR0 "(3غ:::s|{\,;*fz߾gq%.Uh<]7v+BYU̶mml 3 (|;<>w>ݺu˖,>E>Adq%(Yk%bU(8b"(N`4:"l5j'zo1Xmz@;Z>kU(K`2"'~mэ~[M\_[plp%7Mӄ^zP5Y*Ѣ*CkK@Ⱥb`3DKF_4Ͳi/U6BFB!(!"" a* DŽ QHYٰO?*/2L{G/WE`|c\ DKh (8Per(rMʡ1bj\.^/M3>,Rp ̘s6Xw0 GBW֫u}^y#ܣ{-.X;}jqJfRVcR*LDZ 1(JcJKƤ^Lh hњPFU}gа`Bc QrHBhqvzl;Wq;Ϟ_GSjfŤn1+C5)+;C&ddf&lجrctmoJg$A]0A{@ GeBI25)uF5vBɨDV4-@z?+/ W{7n{ggȶ[  "feRɦа^lm}"vo6$jr-AYbi;WWr5d 'Ƙ'uU%ͦkv6'E8EJ YXu*3A84*UdEwZ.`˪Ya~6>'P I9%z5ekȲaǦp," bJi\YKŶ-. LҦm..WŪhi=|F$)rPOX! !c7`7gBNe];kC׏d|QI}!)%TaI W7Y}sI32󓓧E9`^}Hڈ WTPdB4&&A6V7eeE1 >R! 9@sg,)S8Wp`ԥ0fTDĐjz1ߺ?HA-u9g#DD-J0 uBVȰ 6m7 b)bN`dD$6{&ɨxQkH" !f juba$EYZ!={ڷYxRݼ9`X*09WĔF@ɸ0Y6Q UQCU nlVk %L* b!GҘ5"(# jBJ BHި1!RmgH_{tԏ>002T IB$B hl9c}dfݕu@m TDPA@!T0 "Q5g@UV%UB %Xݼ#lCB}7S^oCSJ u9g"2LIX'$I.z0(zn|QY%0ՑR J0'Ue2{odl*Qʔ^|镢 l5Y bU \9]\\>cL];dFqN5焤P$%H4)13X6HBP A+ rA1eNɏnߘFv|.T5HE#EL"$Aj0F9"0N SUjNYeB1YBV`ClXĞق4K8.˛G0v}f<EA jٯQֵ1!F`[jW1FB&"CȌ0g R""S,}I@ A"Bf@(*$ % p6&IzՠpCII!kj툍@T Y2}o 9_-p4J-JUDI0+0LA A2eT$'4ŠV{ hmq6<6HZ=f\*.ܠFaT29 e6R @Y!"d2h֛}AAI5OI:QT4Ld(J,hMLHIĚBAdfgWggg[J/Mr)TM :Wc *) 0`VŐRu9ƽzYF3 +YTBR͌@ĉȔDDdE EDކcL!^D \w'͊ʺ2ӇG,R6) "2"#*YkCJz=3@5xPpوd `H}ߧA|ROqm7MH̜cvkͫ^Q9vRx:N?:-è!,)g"2D Fl(*dj<! s 9m]hf^nX-.Od鰪ǓÛ=:̧??ƕyt<N]\=x51X&D0@( ! 3WƍeʽA4<˺PS!~ˍud,q/Ŵn|x|~j !eka4nO|:{zl4Y+fd{cb(*K 0#cSh=˧}37hsܴ'6]m6zPGw ju^wrDG<;ͧO`MÓ7f- *z C[L١2 llUsUn+Vr'OnT `o}p-u\m~{usxx  7o\^#bQx<{Ѹv˘Oz@2b2ʠDVE z6/\^^? {xO\,բ@bz8?\/6룣}C`QbfhXe p4 pPUUaY[]<>7@U#C)Nb?k '? ɸ*4HU'rѷ&ٮXtOAU97R{qf kUU5`X:O'>~6N"H"TќGr6[@%H@ !"Cl~;7X[oTD~xӗo{Vx7+vuco2*xZipWkJ$p>& 6e'ɇG,` À)^CC=AY 5d ""T`P18 |z|p?}+f Wge9A{q۳˳wnfB}WY6L p<Ľo woWG>Z8屑% ){ǣ [CHmk$ JJ)gkتdP6d23( (Ur(ŰW1Bl#Bփ_<_n^\MwJe0T9Nvl֛nONNή6mUEw2X;w]0t#2 Pڜy<h* mp5[t0ώoNRHfZ,dՄ|AΕ7VA= E@Q48-'˕+>G( $SJQ'tXϯsJSoXvh)mKCH9co`}eJA@A= 3B7dLD@EQ-[%׾ӿ2=߽7 K);rֿd-Ҹ\YHUoI)t:h WEĦmp4Lfx8$pPiQHhEE(3jR,1S?LJ{) B-}uDDc,YXKR͠YUh͢iP K "#j:(*6( 1ŐB4ؕ"z('?YѭɃ-g\EdQ@dĝ5_dV˾on˔j:k&xoIDPUC$`hRVP5l)~ZK`bHd,Y{qyEUk{ _|[SIJtd99FD)9&7nCsudޔrjHU }O }Z\-<~xX(0ܹCD‚ ľlG~dSX7zֽw>9?'_gj8 j~R*% 2~)Gэ/\?zk{&մl~=kM>J6l2TCnD HL9㪲kkY^Ų=^^76*f9iW#ac = B@S r~p:;}|$#~u~կk)I7nȧՠFp@;LTSJrγyadf|{?la8B#";9ix9*f7sG2zPJmp`e5 }lVKɹm`:'"Y9$QT$EZ,M>u]";Ę !fl8lvK #)ge$ffEs~?;c~jײ؎ʺ@N?yY,{2<G l $@!%xR!fV1gBSDU#yH 1#"h&8;;;&w帏)BTa@rv7k[T{C'".mB2w^i~ORܶG'mR5|Er$9U,I I xwOĦ ~÷_,RsVQ)$BTӃ= KYDrz#QQ,-}L@!AP\Hh|Z]߻?Ǐ<|˳fM!!ؓg~1Zeg*{7FS7b¬\. '_xOLU˶ h"AT>AY_WOAɖy27}rvw*Gsj5Z+3l\OFnTJ mQL&fUc.ŦL^~5.M XېwKA5g8"ڹ37|pY۳Uhz|Ω,2sCvtD pYrQ \_S$3|{RbM}!P݌h-@ w0"‚/K'OƷaι ~</l\KڿbJB$ءGBMm- 1lM7ju x?z|g~Kf>D.]Xt)@@FE甅pz7=z\^o6+у 4H%(G?Y]]rYcL>crcyu}|ﺐzrھя~#͈s*c}V@4H3(A!"¢lYh:KY5UBŠGRcSj:9AE׬&Y6m\z?QMs9I@UA9[1UD7`|1һ޹w2eJ8FbٝTJIDAȚ>mJmsbQB4^-!3!lѪuup>) |b@R>nm#P&C 9 @I|ͧON¦+%,SJdk]aƿM˜j+5)*b9QJ"9Wzmnܼu>+a]o6@DDMۀ/HhvjO$AYO|zrrR(n*Z:/LFn5Jf|~د۾bN*lЇ/߼}xvZ~cI@9kUx.DDFRFf4bhl(2;,?~ܶb84MLa5qUG/ſ7ހ {F۪Dtrr7z4*]FCr4=~*ӷ-C "Dd# X,B@EJ$D4 Q!T݈ۆ )" [l1(sgN o1Dfl{Sl?ꂔ2^{x2DХcNDز yiU 0(9F ET }lߵ]us z 9Jh!fd\Qm鳶l:[t]O\߾[O折S$yPA̋bZTn9埼H%aNDWH Į kX7ۤr;I$}# ȸ" &1` EcJ)䤂B8y64Ͻ'It!- &QYrΙ8B]vd6ZіjJ. ur8Ƀw;3jQQ5߆j##Z6uN^\\,V!2dc! 7$UH+,5]ƈ@R"pkOhʃ麋Bv~v3<|_y鵽ɾSzv~>]_]m[yn-"juUE%(YBHq JNsۦ9qs{^ ;Wӳa=8MS6l{7݇z_z;!?¹,ۭuN/>> UҜjsΔ$YrDo-el{sd)uYƨueUH@l\s?sţV+Ưo~NAm6۳gO?l6drIYL,@|[Z !AU13:T86u:s.'N1ղ`&6Fzyl)^y< ;_׵ͧ|h@bO_w.MfA `+-31D$"삋rXκsə D2E״ϞE1NV,Ȉso TUUJ2M?{g~闿7uW^Y _KwF+:[W!x2usH*VUHETw'i5{#aҹtZ_-`ĺLSBSN"c˲Yw^%Uv&れ-~W_tz ?샯}d0uQ:84Mh$iԞe8YkIENDB`I?PNG  IHDR=P%j:sBITO IDATheIdI&&|===n /sny!0AU5US]黹t)iI bȳ١PZj1:fdDDf˸ 1܈ժ|T -4$WD w4w!H(] ع+9u 0zzC])S~v28 NA)njYoߗ9iV8@F@1fF$0tp$DDt @G}:NP:hlDB!u0iȇX&jn`brmŵUhZ53yap{w{7=nUHx.3#4F`G"gBC@$LP,óuI +B!1$ ]#ȕ(@qlnwؕB##"#\oAjs!n眛sd^Z:-nl4B$@ SD$")%djjU2Wdu[k~3\qaVK1Ctf&7ǹMa1t4 Aky.S1kTЌȑݼf ( S) !$@؈cGg)Ӝy"SonϟI ::|o~kЉN`-^[v@#Fvkn Lj8FC'՚Ĕ|I8P: WxrrU23Z#@"踝vwm˜͐Td !aFΈZzF`;߬t>0"a"A"`A dS(ɉ>e0ݴiyju ,Wi>áCVS T#!!yc1caDkq(u28#QG3H,dET̴4wpWwFq@WnO U vfܙȫ$G}bɂ>)WEId B@aUUuGb0]]'͋ü_]~b "LnHDdnOw:bD@:!S )̀с؍& tTl3ö$h h>:??_1L\c ybfv-Z/*Pφ͗^TnonNN$aLy"w"W@^X7$hK=Q7}!~~՛g/NF{$GD fs%@fg(9V>QNTԛ7pw5Yr2x6ZNN92i[ lzw7:t߼{o^}_B0'UL"2Tv[shog׭F ɫ!ҩ9C;v眙yz s˗p孝ng柾}[ )|0Gvxvs{wzz^si~oj?|jmЪZmKO!#z 5{H]΢s^ &ic~_o7wW. ً筵n?՟?^eJ)9z Oo#ɗ_~ُ}n~"*j[D Zpm > finjL! =# HTgٗ|}g/OWƾvͥtiʇ[O8g/_a|twy}=O9s\ukZkVUjZJkf[+Yy婁Avupr|2fuYw9c]}֎7_W9oq(o|8Zo^}Q7O$Bss%pFta$gЄ\TwtA(mÇU0 OD_OQLt"3}CCEMj|j:Mg7W_~5=3_Ʊrn,b`r40GW ZL$ O@#S25?]Ձ/OV}k%q3Rhj  1ô[%cuْ>fN/CubDF"GPUXx@UknN)ud)a\cJ8H V眙1YuA$T\KsDY n3Rf_Wͺ,b_oMv# 0+!9#[swvGrȱo PCWwK#YR*9GfUA('IOAdxS5N?lVGSAE̬;4s4Dx353G4 !un\ 2Gq¹y*?]n5AnYUlf~7g?~Ofq?!-&Sw7Dt,$;pSC'fT+ 6aJ~3J NDaƮ~!+Rk-t];5Ucyatt(uΙ1"",3.",_6W,Qck (\Tx]Z(}wاԇ jX3'$'N8|{wUws?}xlfXjNJ.{ Q$::!-pSZ6}{PyiJAHVQ< ;r), \$]wCi>DeR@o@9; ׌ܠb[ ͊KbUWS-VZM}]_mNϤ9ŒMs-yu-I5rcS%ΨJiD fȵ C^ktvt EO֫üt7 N^OMQe5pNM+ 77 vrg1vFC*^ yZB9Y#"ԩL;F1vw3AUsHz 8ҩ.>\AsǪ֭VG١Q8yz<9i4X1$3r9odfe2zxl~ 7nNV$|xD±Kϯ^VOΎ6'y<dq՟<{Rˎy>?w?~ÇnoosbE`nikQSDLg(YIːh1U<Aͮnn8PK;C<Ǔ0noo覆цuê7fh~uG/^zkf777_~4EݶfZNymcaEµ#$9y S3?۟i抙Isn?[&G6 Q*ʼlPiE@E"82xqrrz~r"l@ 00-o0Hl%|6GUݽ<|""CŋzCg'Ac'Zb4D֛\ǻۇ]z-ug܍#]9zvo~7/pbv:Zn`UUNs>{_n67c_xz& ؇!JZՐ·ttEvwWf8:?>2:&F xut+zcžC)>xf7o˿{oJ)s."]-18::ͺlV/_\fgX)Xܽn0t]7bfDt8M7ADkLr^cPkbWf4%F퟾KQ4go_m%h1322JHa#90veg?ջ/V's(L-g0'-Ykm!Uu͌֫m\'ME& e4&hޟ?n}޼xy|s-E"7"qCgw~n/9yZ9) = (8 b"Z`5.FZnZy0vggg}qH9ׯ!E$&8vvrRJ>oiVqn$P"07}(/hn"("pjVG̓0F Z 9"jzkAK]~Z"<j9qxw~]GHnf0 Gq&:i LfRB0 L1!y?i燇P ZIIw/_ NO/>|ixtʇ~c"!p4{r=%.2 ւnHjclf պcdZU݃$&Zqk`M//t >Y>pn$.$IPWj@k ]ܴeG\n {OQcך3XbS?a!b̺+Ӽߵn< _|b`SM{18\I6GK8to~\V@!%vPeuC'!R["C$-ZJqUoj@()DyΛͱ]./.}G!@+|խsӬ6k5zsz;d~}q9掦$w 7ȥ)z#bu B$f"`S׶8aaIZ+3a/..Am6_?{ן=̍E,mtφqd $Yi5"-`'yr-9Q~&'d6/KL|oDf5?|% MչaPse6^k.H7ӌ''^ia؜:#;.[Yp   $3.)x"EX\Ҩr"ŋXRt⫇pzo,mP=Z6vK4\\\Oϻ~|#:/`%"^mf v Dp.LӢBRiZ׫M_ҜSVuA>;~~̐°G2 N~8ҏY-DYf`nH{,X\JnIR #<'c:{xŷ- Fl}CjSoq7?m NAFn& ҴT#I?wPIDAT_DE4I1*=)֊] n1\4E ޼}w9)=(>YJog/_E֪ jAX8is7dRsAp&tDkk_IB@Rwj,1TMa?9O㇫?:>Wps{od |~_̥bjZ ~FbVDP7Zd$A]͚:wEKh4"8@0C5SbRkq?OSεsӒB vUY8<.`ZvbiƓrXKs !a5sh|򮁑!2SUV0s) z@5\>꽯IѦEaz$]ߍ>gQ3Z][&lg LQAA ȁ  䔥Dj hں`%9ٔbh ,Kb-NJ,P= 5c<ߩ>9} i}@#EP@D3"**L"UDH*uəȫ7tdƑ)\^n^\qZ,4lZT2X4.1b'U&5t>6j7Hߜ{0b3Q3pTUDUZXӵ,9Y*R Ŗ`n^]Jb*I2ʲ,"s&:|d i Mm4D7*P  )9$py<wJp#].w谤D*,fRL@~FhR3HM `곈;e@r@!ĦҺօwI ԝ)*2M8br}y1:6Ji#f64BpQ4U9vBfRT!W#;V,3.04`Z):3DDB0C"&24$cRkݺ@J*ˬ1 xlSiJS-LiRj^0L5-JM"kX(fh@NDD"U )yF\+4 R ZiV]Rr-EJ6(U@+-Ӵ$z&mkK@ REUc;cCTe\sRr)%\D*nj-rGhڬf4etMZe2%:&ey kyc)%fu8`2KdWy! 6pVMaJƦ$G&6'9gixجͪ)99gl(YacNvR97sB0i 4.\#9B30UR]1-L 2#(ZRJ1K=;жmBRCtxl*RKY|}-sgjZRa>ζ!pl:r%QfL $e>J.H+  2RsFhd֊R!/=Jѳ8O,#Լ .a_AURUvM{Zo:j`]f+tCJ$w$P38j0a; ̞hA%O$KLFi ,&1p|1;;Qs>c֫YUuj f?%%$@ L4/NIr"9 j+%/}[4MS  ml !UYʃ ̮i۪ۮÛݶ+%hywo 3SwrHz2A@ 픛f&(y"4j&jIw lf]YCUESu[X Z\juz10N5)y{DLfϱ`] MAL 1IN5'&$RV+yI*ƹTFbw}߯`9P\P)ĻZpހwz~ӧqϡy8Ʀ@CDg{ @N F0qH`2G޳ՒJNp LZUW]ڦ1vb&j̈t`lBu$biM5R@)>AXhf$i9/1 y&pLӸٝ֒߬}E1B# TM-&f5њ!WCf3cvaYΖl"H|/USSAT2N$Ӓ< p bp>833R$W5pHΈUU#&@0\4C)Jn~~Zyye(5("")"DD󷷯s^UK`ѻ`Ral:%2{{foF @D"FH"HMS-8s<;Z$R뛜OlfN#9$Z08L5y<4[߄#Z!QZkh VLH!S-L4R5*8 ӑ505j%;lJ WRY%-I%4 Z$evRkF2Pd2cT2 ,AqcmjZAⲨ:tH*{,̦&2hQA%0)he@Ddd3Ddf"ET% T@(:Oi99Er`b4 49gS%c`&5VMۮϺK#A:%G=:&`tLOlfQ#D)TLj ZJ9IIK&Iɪ*!Rp86pV2AnvwⱢe.s:ߵ9LIa߷>8΅mb\Z  ̴VT#54X]2|¢b4Iރ0 ϮfZq?|t^e^um.eJCo^~_!yWJncCDѺ_=}Dۇ}X?9OӴ̳wj,R͑:BaǾi]>΁QpxbeYa9ۦu "Į m,O"ӔJ~}:qpsffveuj4IW_y?z@,ELj~= kDsy^eI)5M^̜Phi6l6p Rjrկ a|!)WLCYUl;,1rg6ff"qJUD?zγ9,qb@vH_zuk9볳3"pi>zk)޹D*ά40*(& Vp6 ZҲ,'O>ܛn:? DfsJioWߔ,KB7s9_]]obpRZJ)*`"ie3FҪdi hU&R6\L4\j-*mJՇWnMDR9׶}۟kZ9ͱi“gO>ׯ_yZm7j# p8\wiSe #9D%"p<ⳗ_εn ͳEQJm{}hsK5!MK[O8G?7oϞ=}}@@. {8w}/9ҮmTYʜҜ 'kZ~cv<ةs:3p|hy/W?_7Y%Qцyz8ݦ4o6'O_bazκp9e\j5i@JWsw{{kٰfڬ9!pq8]PI!e8OT 2~5*y9㏙?}k@Aݿ=hBJSʄꦜM[0{S54s4wôl6^,1M㟼'v+7o @n.!R+%=}sNek)ιW 6[jI&j\97CF\yNK1e}^\\<=XXO;޿"K8~7E7moo`U?jVUG<͓!  q=q}ۻ={ͷ'YRM'b \."i4p RR6Q}(Փ L77~gǷw/bO8N9<2Kд Vcnw}(z\'7oBѷMWђ+S`j`ժKy`׵1gAI'!:)4=}f]6|7?on^޼7?{m/S[owwwwVpuja{{ z=;:{j㐦t?|͛!x%CrD>-( $\ɻY_JaW׻˫4O4|o~zk}RUtոiZy6նw 0RYD@*ГgnsћousMS)B@\RB-2p[-pYfeٮVnR_~˯cC8Z]e EdN%̡0f+XBYa&N؇ZmW(yI.BIUU4.i\j8̿?n}+?ꗯ/;,j LdɔTE02yDj9ǥ j7}8>9?~}Z jhX"qd _:!_ml׫8pVW[o4isV2އJ) $t<4yYw lgZ+(THBsl]㋫stM޳䓳͇>hV=z9l<;:3{t2沈4ݒųxjuCԢ"EIU ?{6R6MO2PhGó/| oDϞ>{1/ g"vaò8(VZԪs|Pvݢ9%;^6Q \RS=]_J"`M\bAy#m}".0&? x45SU6cڴmANfKmL_vtsכ#HSTH)\۳[m췆A1jS vՍ?_?5qª.)uYRZɱiBp.07Mӭ֛KaIu"Daث*16]RŴZq 2gՠ hK&vW~go_yg'w޽z,?oa'";]RG" "v_Uկceڌ,ypw۳n=;W w.0)>OCBo.16mw޻'jx}~0jQj҂Uba)Aчm~PUTSrFio}e1覂M}\Y\9/2O6WWOnSEW5*Z!5m@'(蛶3qUhVk㔒o" rVM.rgsh5o_R.NhUb77^v BHR֬$ *PsMQ*tz" }%8R9`L"%)@ӿ\X~׿ o*4']R1pMR+?yGu %G`UCZ]Coj[HtdhUjaoBBܯ~O?TUճn-]>yqlqΙf, ;DLji'$E!+8dV/@ 5!RRE @s9,IiVpkZm~9RUÔ R88!) 3"cGGdhfޅd8HLf9UuΝuMӨ;u\a5'ΛЀJU3jJHNT صKE*9c5@B3v55oc-U1iGT"UB N*QvȀvR fݯeQA`fZZȴd3)Eoݪo. " jVAUoIDAT8,$B 0Ub"fvA O|}OY;KJ9U͙!Rk%6F%!ZAD;ڶ-"mcTUfb92#@3=v Ta[Wtߑ=LӦڶ&|po^շ|wgW{fbٹXsSv8p8Ŷ jh~z"b 1AUE02׾_u]oml:zsEZʲr;EQt]Wq6 ˧Oeo/~6:A1G{ ";Ϯ8kEDD1R*At4E"_ 0  c"Dz*˺jۮj*o},|lcV%YH,޻>cYI߬H4񸪪s7x0DcuBZ;>CdChS,h4ڶ//s?Xnj)f1` :wE~{#| ZUOcONoݺ5 bj><ϳ,˜ !,|."y-R5D, mז??_.g666&!6D hto<7"<SVeZڜn؝.1D ]8?;M][,۲j:-0!Mc ώ~޻]L,Am3M%k  :h ϫܿ8(BӬ?],1,?oll+VU+"A|!V\El h.N-Qb[c, p8$fwlnM&kmV2dm32n]9R8EvgÝCJ_ݺs\&m %TQT@REhB[+;{۳A1$kb0$ƒ BC F'm'YeΗ庪ZV߾zp/~oT,JmjRz뭷WnQ7?=ܘmnmq>:?Hmݡ!D!0HQUFN]bg YQPp߹}kqh\C׾v`w2[^bG> ׂߒ?x탽AyWۛc.("@(" IAIP %,9$%R l6ۙmL!t]7'Oc~zp#F?ݝ@^Gm]9Cg^}ޝɀR@[{eE "[E@TD'!PJ*bf *(Al9z=zHbf(W 3trxkϓ 8DGQJJ2(SXDX@U@Y8 8@A>jmz0t]N p\/mpk6˽ um58opdf`%Hzj TՈ2*b ɨQ!a:d׶mz@bcjC7 ETLǵpӶmONNI[%40V]fR״!)UM8ж A M[5R֝ƔS`mOOO󼪪4j) *$k,)K) ADP pF>qW 5Zqk#tMX`.[c ATYD hkkk[`€Iߞ* 1Z*)"F$`̡ 1@0 [ EQ>wCWk;{w몪I=.\$$Q8$`cclk=2g-YTY,%2_U@U̕xC2kUi9g"*Z0D֠* jDQTT "hb2 0) aPK2o%hv^eI3)> fD4gK1YUD2k9`A 'ARcZbH!DZMhYYWvn@d৪?V\c/`0*ԇ*㌳OD,Uz>1)G@fmY"  !0U[65%D-|挵(!<4duP h )*15,,@yGV"U$ G$!uMJJ BBGހF(A0@d#dyBRd-/./F{{?lITITUQQ#lDEn61h04Gb4(1y 嶻X//z*lkc4ayQ.UE"ad8.pZ >~wyoum@@-M-h폜cy *ROf];wo7'QDE㣣?~P5bl 1r^jˊ3Y,"qxIVhWUd2Ƙ *A/k3Uղ,?~l{M1./e..׏<|p㋓x^~pG?|$-kGYf%2BfD2ü4(4c2EQAՊeEцkcLUEyJmj'G?zO>9>>JL Yܞܼ|\"&rq flZd i#4峍`xެ H Ts`T/X>xݽ<'1%P6E֝{2ONOc,G[]^ ,jfey2|$*"]GZa4UlnoϾKgXU8n/ߺ{xώN/ Ogc[s{o5T~o{d<LƣXWe݆t|vԮvxUc ) 1o")ݒM5;{pgL+f4OYj;X6F>51J$!1$6wnxgcmܒ6irnn*O*l#;͐UOϺ&d&5$(ъsv:7&$ ZeljIYź\$umh<tFӭMgIz} @嫾2k l+.Ycp>SHT_ ҵ[EQ*Hk&4,Ž\S_\6g TVB,"zoI +2:$*R9;7EEsΑDEsfmD p4l۶n4FyO&Nm]j G  8mۺBDm1Qݲ`')/ E@D 7% PPa"c"(jL1byJ@bd*()pS]Ӷj8,EAh8DuN*)XWzUm' bL(xcVQ FeUVeQI,RU(SI"c,Ĉuǣ9WU|>'Pg,wm+(9t1WUۆlb6~K_h$)ƾ}S(FLwFpQmI3 )fk*b]B=:ԀfH 5*]6eUUٶ'47^̛4JI *efT" E0daz1$ԩ:Bn:o|&&.zy,3E}^lO_{BLq/WiĊd`֭_ػ}ɺ,PQm)kM 7qMyEQx۶*1 RwmvQt<A1*]۶)bAg\/z]êw~TRb(}O `-hII"`4d!l^=ZM=mHm~W~I9-/,DBm '<0)q,}gG!/|˻[p ƅ?~N 40Os[8;*m&!2 ~0 t[;j :cRyc4A"@D jT%@d'PsW<]h/Wrd޼wo[g}G. %Eb\,/ܸb8xYͻD[N` ȚWڳ +"TEBd-det}ZWZ?vwnߵVUEDu]O&>(}Y~-IuPd=UM)u)f")껔7 I)Z^3EZD r~3n˥1NjZYje/ٻ۸\>~VSVMS"1E>huV1 \9_"ZkQzyh "Hb!@c`YEH)*H}Qղ( ) >XR\kRSLN Eaglw{s<_lf6#0uYuu[9_}13ڶ^_fteS5mWuYMƹ:Ƴ?7j @sL]h,bqyZוA媜Ns&n~LēAcG0ȋ/j*mWƳI6@bf`8Y,ג`2bX7^r֨wKL)YBfVQ RWsq1 3ƘDE,loNf1URFEN`,ZgJ*sDbf.26rȩKEY!fl8!u1mgh.&ĺ mH]dVP$fL ! kzH6޹}EAQkCLd 0plc9mw)m(˦iculkѣ_};e!,NdRbQM@:NA@ C/0Q"*Ѭ(ͬg[{ S rUP(Y & 2EM g?~zt/}~e3h(d8|XX"b"K DPPE4ȬDd1:Au*ǠPlKrƙ{<* H eff`!a۶QEb4mn5Q;o{w8,f_{[w2竖 m*&NA@Y%Э;3%&a1Dƀ1,qbRJMeծf/˲nb쳂[p:{OQKW?|+ܻq&Pƾ`XDA `,Idsc?PxE+bTAnX/櫳z>ʪFg $XYc4 b@9%ض߹G_DjD 11d C?ưgz~~51"bIAicݤbX/mW^wҢ\pz_UU;B*߿G<{,t%E! ;2JJ@F5D" W_dM?~{lN&A4ƶ*cUOΞ??:`ބС[޽r٫9 Y4Sh*$M#cշ:ȝNƋi&Ƹ^&aSV @{E:Y/,`sΡd-GqdrgC 7>"":&[o^}c8nw'<}[Y?ܽsXl OOȘ$ *LWSaYD$Wnoy|s')%UuΟ|үʯzﶶ6¿ouG?ydkkkccskbkg?39croS Df&Ɓk !CiJӍGo1mW޻ӧw6>."2ٻ/݋BdyY.D!`@A%@QBB@DD4GEUDkaEDTДM3"# (17TXpBa*x*HQ%3QEBΤY""00S>N1.tL @k£ *0`Vz~CqlVU֏76 ?5d]PV9vfRPK m0E\Bzis h w5Dzp&\UM!eٟN|…3})59K$zCHڎvF<9ynG+TPVr~ޗs:(%>W_~2AR1WMX|qm1?'{/W͋>7(FQs.m[˜3!;1@ Dd/mh@ղpݷ~×/]3g95~YuԳ֨9*Mhwn_}juWXkC LDD"b4xbkjT+?w~{$ Tdm)9I?u;gsl;ְwpܠtҍT5ŜR޾s&iQmcv 贡ǻ(~n  #'XO?y/=wLA5ߞc.闾_[o2 ƣx0)mպ_ 쳗)Fk$ܼp<9ONäh8SG*YV#G?{g|oMDdLa5m g P`!HNgݠvYjRզo=xhK_H)ch6 Yc cʪs1EDr)&T˶P3W=l$>‘ "Љ@lE$Ju ɱ)z&ۢlC* 昚u L `ݼI96  (tbg " |BXVT@D# !RW|E-Yk(A)GakmA#q2`RCXg6pqZWmJ.ݱ%Њ@dT̾K{EB 6F4H D` 9/YMZ6GA$x jzǓM -xF;TA0LAlH Ir΢\AjYA0 !>ڟ]w g g5Ƣeiy/ƃ'OjvP͙xZOt) 2!DUjQIvrf UIURFdHr0_]k^yc6_.CCB I)BQ 6a0.l}?Eܧ:() B;zCmUeh[5t`070@nvvx8u֟;?ܼ^]W}˺svkBL,řo3[Al0VQP3"Dr y17Ub9>sW^{mk{:"VG1y o|%ýýCcwq? !ųd$z*>֊HΙsOh@Ou'`眱EVXTe2hs}ϫ`wfs/ѣGn}`0޾ts׾Fa:|XRK HԴ-ˢmbՋs'@v'xP@YKEb2آ̪â0"r}|^/a7$U|πnOǓrX_|b>;ERW@)+3:Z'v߿;rFYʞ !:WdU ;Nڶ5}!.4o਷ѕ=Bseb<tc{tc4aY*;B Rdɠ,b6 bYETT^sEb޸03|:U|zEP01C!+&@4x9Cۆ"yEG`l|U[ۮK.W"df O5G1w dbDV`BP AV),b&dM9{Eismu\.ٚ-e)Gr0l3yr^r >tr' *ry^c$E@d 1tk2b*,/G| f1r{_| IK]mCbu U?_˺nsVPbfB ЄP'΅ tk%H4XWgkԉFj*ں)zeUoֵK]] 0xU6mXVr׫jZǘ2 !Z6RB%3M%[ ЁtQTb{T؇w;j1ȢPobb£'Mh3h]m&dDB75'ι,1g)zL/#Sl]lɱ+J 1cĶ~ׯ~xд_YN=];7ok[׶z*1j۶Ƙ`KYHV`пwϞku#DH_i@eUEvtv&dG'1%P<%psr"U-?<<)SJ']~Q$K)\.fַ^d^HNWo_c3klo.3þZHRJ *xc{))j9sbU{ n5|޽h_IYT76ΖOoK [&/R(/lO7޾{R[7ocSHD M'S?C3B6e'4T//~ $̪xo+/WjWӲ,hk;c|sذ֞D28%GSMiEMr rP Tr'*2'nٟz}H Z|pwƯZ7}h<̀h~R i O>t覛NЎQxlG#`"d'IDATzMn^=ޏU\(c֢?g2gLx(IENDB`I?PNG  IHDR;P(t}sBITO IDAThmYdIv63oYY]3%EA{@FJ(= pkά\"2cs9Ue{??w)}Y W8u]G}f{jt-@% Kթ.Zywe:˰LV`RbÂqV~ۇ)KѥZRj)ESJ)%1݄rJ4uLcy.dz)e%S"ģ XMgץe4,qyak!Uӯ7kS NKL?z*kMRҶm4MӜmMmii[Hj%㐏ss-fHDP#"lݭjZV%b`qyj ٙ͏o޽?qnBrdt"KVñmv;mjڐ3G_k L~2DD qS s6!rVyYiKU~_?cIcj bjc`NM8 cy0پn`@m짐`'̈ Z Kb\)JІ8"0ܝ]\_?jVYBH#f.L%3ٮ-w=I[cz;iYe`&w2$A/U\,/B 5w??|/k ]}rwU͵"FDP7t$ l@sXjWɠu@$Ee h^ ,!1s +貔 Q7߼4U.f5 1p! `Դ UwM8gyxM[-$@d^ݍ̑AQԽj."ׄh_~9""B1UuDVtҜۋydJURۻ`ժxlSf,`f>/:y.DDH(f3eZ44WۮH<fڭ)Gǡ,EEQ[m<9~r돟?tWM!21HMHQ 26Z&})j.#=^AJqVP|mXrq !2Q>8{~Wϟ_v..76RDY33vwtuw]b ͑S :#x™<)b_w`a&_^;EZƾ6??//m @Dhh/^>>iiLΌ1JJk0jUע`ghlBh(Ęba\l~]ş#8-stgggO??~oCĈDĎ' " ɪs> y%`(E*]Y ڶ S6ie_={b]eʥlwqq{&$f&""A `Aj@'N .ζ~f!s4"S%#va8yyU5IqeejݬMjҴ)7]GUc Mjھ6F`` c$TGBĴ[m\<a !"B'B!c1`5Pa f11t’p#mr pv/6-u9Jjch&ܪ+J"d&9E2$Ff"@wwGrt'g&l? 5P&@)ŶC@hY4MRJ)K4hִ7O..>zLSD$GEdpUUU3;i\ B@hcH,HM]]+C,, iZs)K9.,yu2)HH")R lhENs 1&P "`A ]pCb*$K-t]BPHE8M24-ՙ̐  a`6pwW7S030<4MJ  BBd.>6}yAGmE'W3pFGrHf:/SɽRyh4̓{1G$$ ֊%!95 "91#Urpjv)n?Dy:nzniSkl9\k5n2DAT-Zʂbhnju9:bK!8#!YuG$Ak5E"ffǶkÏ!.jG *&Tq.sܥ9R mlZ h(خVH2.s.EN."""KXId˲R))oV㳳eGvww9NZK5t@A $8Fp & )z߭fh'"MN #Vjf̈`5VL+fDQ#@RkEfsyv]O ,OK`^4X8l8A""'%gtQU dBLnPR DVխ""ղL4 0-?xZoMH!bԥo#8zjcnwJgǯ_LlKQtmd V*y\rmy;3#) ˴TC1eiԀnh2`fZjR*\ lZH^lr{?خ@mu$ Ɣ<䉈̪8^]H>E_ 6ƶ]rbڶc.e &q-Vٮ :=KL!H$! & z80G 0!z<,Uu.B F2߾&j?t(E1p궽gFNb rYoVu+y)f!pJ0rFDlAooq ֻyvEr7w-?{UluӤF\ Q8HYLb?U눌QxݧպYLyQUZlLZdv?ew9U]<|w9gr&fׯɩ+-sІإ4u7&:1VwW9LL|kjbkVLPFR&Q##!C F Ӽ?9uإǩjx $p i> ^j5.J ==JibŪ\iva',"R ::S$KZH}V jffǫZPݻϞ=9o!2/zVDNjit!`JBB$OCsPJ"j LK)nVH.ȈEB6bR X(ETMӻo|sûu٦~6[wl.(1ܱV,Ia8<*$!24ÐkEfW(l,yw]ߔy$Sjh@o~~?{좉 5 5 E$ QMѲ9DAB,ySF2/xf&pwx8.ӸZDeVm0jq3̬v7k~t]éJjZu&ǹik\08tfyB@2yi<@kpKC9uv8+3tj-eqS[=:<[dzu, Zժ  -%aEvj$=ZkE0dEu\*ƔA(dj*"T+j$дLH!8‡A)1?4lY5]+!pfR|2?{3n ā! 0 1H+!"+" Ң P۾WSRjT"]3EBL@T\+嶓UHhuZ㒗Rr  !"QB۵(bW8yZ%H`ITjL2J6$$`0@T5 X~&8 2d f\\~aڜuɅCB!Rlc""Ñ_ߡ/? "@" FlFŴLfVyK)χeegag! DWUڤX=i2n!a] h?R Z}٣O?9ۮjm(HҜ0C/^mW;Ccy?NXcG$B唫QTkL ^8Ogm:?x9ǿ䗟}㔂0E&"z壧6f?{12!K4Oy1& P8?޴ ꀞ[~a\$CO/"4 URK F"gE*nZfo!oyPa+,jT՜ ąh)I j)XCpǶF0 ġgW'fF"KbBp)JM_hq]_{ \LaJ=;_PU*jH(Dfl +xD'  T8/KR ]m"DAAaVwC+oaP=(RZ˜Ǭp %#8;3WX " c-X4wM?Y#GӾ0 S~(3S "ˤcigx=WW߷  0U~s;-Bh%e@0"*p۵ꦪV":t4;k;wò2.;.DҶ/eN!7T6L^0dw.My}n&~Hi[0{=_}ͼ߷ Nx~h`)m6f8JLbf9/5M׆mc\!n~J'X2qhL]IuXfa7`ؒ+ǯnn"9#8:: ZLM :>>_m)&ȥiT5&ٴܤiZ%{/?KGlcfF<,h樄8 eGshW/_-%f`; 1ӪgYۯ2zּ©^S9F!j^9εNy!D`g<Px.6]SuJ N`OOBBFՅCߥ]mZ64 ȩZs`tE+O&yAP7[2KQ}HŔL޼~wsC„ Z]@4+8ҜO Y aQbbU7)&J3Ԫϐ;S5~{38\MeM*-@bxW/ղD"'?+< ѣ8$)xsn ZUUFHd"7G9_~Mկ3.aOF U7/wlĠ 1"bZ @A0Dl#EγW436=|alT=\ϺM-Kh޾x_Ӭ"ɥv<LJuc(  -D0/j8WCTs _o߾Ojf~ȇ)zhs4f!wr˵ۛwgR 5|j1a?<|\m,rEV ivHл}_|Pkoo&n/r"Q'"1/ޟ?2A>`}H?MOgzju> jfK)0sȥ{z_7ٺ__93jH 482t74}@DJΧO>O4c.AZ8OsG._߽SfԶ_a|7:jɍj(ܭS̬<޼&p0UK)1ӎ?ǗrUԚGEjnno~qY͋ %4`j䯾%\z>TJAhuq^ӏ+E LPg'o"gk\1{9S}1+w_]Q炘duj0 4m!R,5!OyQТl5wqpx#@4CFH}<6}.k:Ǜ?~q¢ !2:@ۅԅCo< #_(:ְ;IDATX^Cb,˪<},T+}?#CDf|"g4G; Z/ׇAzA R+]v7b:@Ht2ĥu]LbV]wWFn'~3'O ipoonKbz zo޻Z';H\m,O}ӮchdoD Z[dfD:p$7p8T=$# VuiB|/hbtZ !4ݮyǙHR%n(ĬAY6<7'_N;{Ð*CH__}T*r] Fm%L~mc?1TV E\>~4,,`N=l'{@i7d'4e7 m/yL!VDuPYݺf={t)pC[otjZ̀cMu{ <? ?rj_O_=z44ɽ{uU%cq?¿?xu!53j]fsCd"v Vs״Rj5XdB 5T#8.y__YCʖNH(1Y]$~wGmCjC ZmbsvJDeeɦ9tpNWO|2FKIENDB`I?PNG  IHDR:PǶCsBITO IDATh]zYdYr/-̬{0/ 1!Ak=͝3""îezc0HȄ¨H$(*DB+5*2\D cƙBRq !ɐ* E@@Bp̔8@JZ/ IHLUI H,20)ࣉC9hQB!h48 yUPP 8䈨1hf )!""İ$d@Q PXԌKd\ y619vof=+ RȊj<. QOs\D="@cS`0 X! $dCλu w^#yO.6},ɤ*J\:}^^Ic@6[Ƿ_|\ܛGEH r$Qd6`@EU j)vmܠ_+4c7oO_Wəh NiΌ2m8=Y:{o|tc*"F`PR$%P@`_8.ulQ H rcф~Uh^^ǡTYs:6'o5j0a>{ph@l .R~6ȱ&l{UuGT93Ai!#BӺlQ;d M^⠱a5\ڞ}Ӯ^oMSdZ̧A}#<Y-Q5ͰLa.&\yIms/nGMȌD~/n}(GDUe|k,xc4Btis3]ߦH6<1'`a Y<σTQU >&D Cܰk^]& f⌃zEM_'w}:?xԋOF"fWCP90iCh^֧O/_f1ˤ%J[&?'U޺X,1& >ARi{}npM7_%nP7 -6߼z;i@Jm?1lnp0Jק=-qy^ ӪGGEQs[a׶.I7dFD>흪ZCRHJ9)u;<?qHcv7*޵\v)ՍӮz7|у{!v]c魊 `^3d@tb@$jɉ Z_(jX{|B`|@T$k zʹi7}R~we&aԶnjt($L@0BYcYm:V͋6]f(1( $ McN6eL0Dr~ _G[N4J}H!榌1X M"3 <k5yH#*1&qQfY^0Fʧ} &M1R"AH9 1`q|;e5],D|i^}HXMۂ0ScDD$2)ED ek<&3Vdv{^_TI)*jSJ`r.ee{>cfFl2dl2̆-QE%Ic4 18t"!*BH%EIIF@1^^~-+JB1=#@$T62l`rpv}z0 ]$2W6Q Sdeɧ䃄("},3Yk0(8(IPPUHID@u<3@Q٧(˲d:̆C L:{oUv` jCf NjD8V1[>[74EQ*x1!"bJI QLI!* "2aef\ "^'A"cb #è23l.2E%ӄ!hLP\ ϪY9%ĤT BB"I$!d%f!h5yfWgn u,']J'͇IS "!Yde٠N 4%"0%h4EhTd) 5F~>h0Xky,fKCizkHTWMȠQTEHv^*#!,Eefq#m۝^\S̋HU ͦi^cZscQN3sU88X6l7ȐTab2@(BJ ]]AFPBNξ~zurVyӪ,cD*9CfpZT,cLgw+@t(FRQEdUP5yPĚOϷٹc0if 3*D\CHW_}lx~jzec! L` ӭ@t:ۮׇ=JRLV IIu 7QF Aʓ֓_ 3w-_ow^}v`fnOŗgD4xG š,rܒzG(fc춋ۨ$ $*%@v0(3րWAIѧ?xﶸ}A r~bP Wnvj6=uwu7<ι 2,U IdܘiU*漩wޜ噙W);HEn "D$R|n.q; z]_Gn,A58_o/<|B/_W_ߺu˚ QaZV(!E$M,vd:-gtn+$(6# SZ,|&/yx{rvmkTGl]\mFădVTUYܿ{^7=|aY}׺hj]bL#uE4iмΔ@$ TW 'c"@wLؽtuM 25IUf!IQd`R;>ʲwÛ7^h}jv4]]_wW2L.l%)&N1iݾ*Ő "1@F0|#@@dux*D_՗z/>Ϫ-ʲ,QG"5xuuuvr&!ƔeIyI0ZˊQv~<syir6 QY̌"*H`[Honܚ)% MTI "bl̋rMӬ7+Pa<ID$J(""Rd޹[.z ŋh@TDHH:H5EWbض&vۈ褘2bWl=yy~~)mMӨjbRbk3F@6bn7oQI'UA@Z\_X٪})I!0(AD΢s>-?ϟ?G?O^;̈ȰX&)1._ee5]0&QJ{H cˊ.rRnPٌ"!]^]6u'뺩<]Χ8t0tN3+)"Ng>$k :tv\rںrHY5cTyeQٝZgmzz>iu_.VkC"^=} ޿ZDTw-r|$Uay "2 F&O(Ju _E;ޞ}ɳA|1ȭrchaZ[JTNoގ8@"Q DE5 D(DlXG@uPH&204 ktۻхf>y{yzʛ!ܾsťm'Ue޾W32Ix}rjQ*s4"5Œ0`PM"),$EBP{ SJ! J@̃f[=8nZyhy<^ө 3G_}u L y q6goO~[oef1BRe\ct:dO@I#L  &W1;j}TV ~٧ˣ~哧]߬/֋9:>&D!eȲlje9I"#DFTEҸ2Ƞ@ QL^rCLPTT5`)2Eׯ?}vzvU`1 Ca`6>lvuIRJ1dE%&C(:[41!ȈY`G(bARL .-ϛᝏA^M^ÇnߺomBj^o7>~r3$@&6tn6 !ɋjVY~ׇQ3BE%hm:T&.m43%1WNW??Ζ]yH,Ap2Ehz3ey"l*d>j.fYB0]ya|1Mw^("L2AAXlzztk X_@>^ŏ A!)A+ܧF  9I%=Gg KFJQ>F_|QO.ϷM|a퍽S]4є&nlDM΄o_|K6(Oݻ[o7{SH! Dp3ļҪJUm n]7҂Q(|m\^_zbPIj|uVhrHH 4&v:ʧ?d>{3GHlÉ.lh+M h rf! jخ[1EM̀N/vA٬<ų}~[9>(g}4'!NH8lD:: PUQTQłr UQT ]wz:m~b$iNM2ZfT]d.luuɯ45(j I%7FDQ ]q$M j@!*9η:h,\˩m֛>{u۟m6'$Rb&b΅BaCY]@Ib@tʯ?[QĤ &VRM p%U3߱hS:6~ӆ۫@Yk̠f&pe +W(Z=+"0ImyTdχEQEE;leRKtnsqzZonC+ l'H],dE۴qJ@B_7׻EM2!KD WEpFGAIԢ* ø&jj]su'볋׻#gyfr扲zHoD b[vY=W@FF%!8eDWGJ*X :Z7]H̜&r]ͰS,ۭwKPZ13"#!#* %-2@*H:'79TTU_R@TpL-7ˣ4{2q^F?D.Q49e9Y ֭q dbkazwYo$EP""@yH#U {`Q؛d"d("5G:>B&q^Bh]d-ߵ~9\Ѱ-&L$ ]Qd!3H m~ݎqtf) }$iH"J`^)PB#P5m|𰸑_>?=kx2)+ru3G>XkƋmWH(l>wMV0!8H0*DH0%HQp\ѻp`Y;A&Wx/l2jMnכN\v9*FͶzn9Wmoۨ4ѐ(A`$CVRRU%"f[ # yPbUK{3D'Rrb{/w<F-]燨yYowJ!!I)f61*"q; {@у*B zEU!8<ק?̝?_Y>Ƕ8 ,Y@R%9AULT c@D1Fkq FSUDZm Uxۻˬ]={__u>W׫`_7C/bƅIaPI  Z$!3^6bpoػt]x5XH$穛HO/_}wsH;Gŝ%f}|>/V?ϲ峓Ȍ}":MTF0D%EfRM7*HDJ[6A@tR b" 12qV8{ԃ EQQUŻoZD1kAu4^d.af/>yRz%yaJB`< gO_<{q0G<&FD," "/aՔRbDb%H\sP6??SIɡ-D%O 3- ,/={q/; 0:Ͳ/>?:*tK Su[Z(3%E6Q@ 1OYdH#a8/-`VA /lm2- t(ƽyqmd]O0tAXAJhE$HjRJ 0ykc+4mDl^)" Q5R۴cp^tvqT..g7Fa !qeD#03eÈ,IENDB`I?PNG  IHDR>P]Q9sBITO IDATh{%q^-gw}ˬ\GI-YNq1%?O0 'È 6BbhR,Q q(r8gr]z9KU~?%D@Q!ƨ9gɱ{R眵SNKܨUZuQ )$ѻ2KFsBbUDh7i%$tLr9朙:&ZV]׭>x<>kiZka']\Q93&AAa / JDDYTDDREc]7xq>߽{nEQ8ʢXߏ1kdBA$"w?B"Hl-j ~ڇ~<~xXr\.]}#!1*"! g=["+~ȘAӪzyߢ+('}~n7NN,˜|Z !"R4㰰Ҷ/!hu_W=wF1Tq3͢l noOǛfmCںSmHUc)%1䐍1Eq=E3{c H]^/^yb:L@E9- 2 ,äs>tzr8T5c>>=ED@D2H1*!hg5`ت fmSB˿;۷o"h 1|LD}r|7z뭤✙-3?ydG''f&2UUj4}kx\UHJQ,FuIJ]ׅ%b}u wQ^ :M)u_n;r("@hl6o^>}dek Dde G=x21 #/UW۔LS'"/|a3UuHUug0Eݝsެ?я^}ՇZRJjz֊HzQU}}u!tJYe^m;xºcHo'u d2yQyECCe0,9 (n\}g3DRƘMONN b4Et<;;k Ea?I)9s!`Ӵ}ٰ|>ٝ;wtu~g K sH)?޽ZWxUnmۦntcB۶)%D>Rhb-;Wc00Dt5M5Q0KRB&b)m{޽{1(B\JDMwrb2ֺlsZ$caAIviCV ɚDMӄ9o %6w*ݪ&dE3"`J?'O>ãy.eEɨߴv9* oGQUtyY7T!")eypG Md")/V(re4Lf,BRJL L c4ƀBupO6u]>朝sM3e{yRu}.+{mȼ(jsvu}Y:?,}Qm6M3ͦlڳMn޼|uZ#9j̯w4ƨ;sΝiyUUMӜ֎MC9s(\lh؟MM#IQLd:^-dzxrD4b]cqAU5ܶmzy|e6w qYFQJBu*棑":cg+M&l,3[ 293W.&ДB1DD)iR iSvd`1"gs!oyRA~>>x*׮[FV#XtTdY-{~0i9/y"\snn6{\Liݖe4+l4X̦t3F{90.5*9`!gHzrDDt<w]Rgy, 8KDѨm۔RJBuΉ:Dgɸ. Ĉ>Dcj#f&l!!ʂx0/Wgu#)Z;@70El6a9n?[s9t괮İK AT"F9 *֕yY?Z-UA< qaFÙzݶ.x"Qp6'z29B(eFF,`Р¹^fQdkHsU)cBQYxk!!!t]63ds{!$" hGv4 %ﻔsý}}Q *2Suh4!Uc"3en3v6.W3b9]aSTL+lQˢ16d!br"o֛mu1fc"97B3;ʲ jFh-"Aι,EQY*73VUOOG]CYD+Y=61I4ŜRڴͦ|M XEUՈdʲs`su}?D "c̀HuY9cNm񮨦3WoWgN,ّ7HATBΪH1r}r|ޕ۟qUU> 1F<ت, ҫA䔮|K?۫զ*kbiȼ_~mocd6.5L)嶳"!vؔZ#O+E*kq1,صf[%?GCcFdWL[3) K|V#bYýb} ]s6Ƃ("EѶobV2zO&RN)|꬚Q5lQ&Գ?wGDz2l<[М½?:^^2v\ WNn|7b?&tTt6_[1L9+9]v7s=5mZ CEaL]{mxJdv4{4MnE=/ ԣɦmVpRQmlVmd_lsޟ/{oߘN!# 8t4͊@@qEQ|[ڬͦk۳rt,dV),b9ޣLbbݦZ] 9Uzif\W6gJ%e*v o_y9gv}%QPy!"" ۗ}FtwsP8c 4!VCT4tuD dT;RSjDDblq9Bͨ*ʄ<շ1GdT0.B"tzOߛ_})4)Dɳ}АelْҐJI03CҠr`H)«*[ր@d8/!)p/n޼iΦý,<3hy[в̄*9$R(3 AD캮iC\|2c!.9zʕ+<%u^K|="^qc2!OzCx !`HQARꁘ{+"bSg)@hxyH*A !O7c|UxDUUJYb939Gd$C9%g$9UE$ YRw)3 $dz"f!|`RзP2 ޺u9C`oo\׃YcL1s!8U΄(&D*s IG(f@QФHwi&#Cd*LB߫iu5JI,e!k1 sCvm׵1v}O}]ByESC9 0Ux2$)!DB yua.<.OK)-eUC:ScjPa\EQ[vu' d@؇c v1ms X; /ŜX,o6J!+dD")D(&kus!ضB4v@U)6=&Igg=zceJY gЌsP4,+ٛN [+mȶMH))(qD"+[n=sp^@>?5AȽ<],xbb.IF쬀6}d4Ďy۴mw1Įר{khj@FQ97M ֶ ox>QUdRr <%c* 꽏*x|ܹt\;7h)IHEᲊ- @>==}hH3QUUTv<+J_ϧڶcy) \ڇL!>L̉ڲ̳_޽z[[C {6b_eH dÃboh Y?=zo]A lAEڎko_/%dY,z<.[데]c)[l~4"ϧ3Cѓ;?yU=Um5Gmu&-8kʕ@r s=-T5VAcNxo[_W*Y{[(4CD4Lr +AR&$9ׯ)s)Ea~n!]1=6X-BHHzw$O =WnU/+ [\r0)D .m gW^zJPR.>a#1E%蝟ϧ~wcH] pِP!o=[ǘcF%M9ɬ@j֮ɝvӹz߀FݿY3dח$L3B"4"@|6).C?O4i1T jPQ![ .ho}P_2 2i*Z9kPd:*3!f [k dkeV))%f65!>mܭ[TD$s~?(9!l~kT "͒1fh 5EU(} -֥I}d\xo'2:V u4iÍ_2m~Qg2#L ʦOBH۳ը*8 &A)Izs%>'=]/_o?WrV ^(1s_ 31# TH I0n&嬪V(e%ld䜲$mOVh^"(" 7;15ɂD rQ5+EĠY1$-)*M&<ʲU gU%~ƙe}ÖD*E $ q P h2R2k'U :@" 9c̕/ҷhכBވtmQY31$xeC-p\lk~h O>رm۽j $A@@S}Jv QE Y1])E( Ei6Ũ+CCPD@F˽$DB1~VP0rεM9{=Ļ4Hnu]J9&4M Ĝ`(YFT DbC=[L]w"c6ֻ)N}D8|_齬g1z?Ͽ0K}a뺎m[1f7gUD$ޗEn IQ5$_{kH$ecy/#w]74ӆ"__+>@Nt"l\"P30^U%{ci Zw xbNYrVSݽM1loCɟ/u}zʷk^(Γ docs9&Ue"Er*c1J %Ó7oֳ}_~y4eupp0ͬ5)ek\H@*O }93loG Ϳ,E3[SU;MD8gS䨚7?{mjmUUE]/k7fy`=9'Cgl6cz~RYdD&n`]TUsocߪiȚt'|wo%6+WeYx_Ao|r??RDo}[/v)Yh9f AD%TU/)9k19trwsCrΠJ̈rO=]t}ge|*)+7}>ʌ"XP9c8݈A颒 IU 12ʹi=Ff{y饢S{}1/N^??}7r}3YBΪL d;?KF )Zd`@QEɈHmpR,ɢ!۬AAEs*OF{-d"y/>3(_YO>yW{RpSIDAT67U?7c۱QAUB$ᥚ!f!""*# FM@0#ʸ*a'H5>E׹G???{W=z4wT;"p>( *J~޹[Ӫ(ʟ- ^ɨQ! ¨dM)[YC0*| 0t6o[o o]\pcw)2 DW.E_1Dryϟ  YD**U db9'M!^$(B{"R~OڔRQ`mΧ̝6*cS}rDR"f6DH×vӀ[;Y;>= >/.HF Ӳ,},*_ {IfC84t.:l!0hz`adMѣGw޽` BfFT|1]F4H<w+=*3*ۗbBD,ܛ~ϞW1mכp7p Yf44R)%ija(P6^5DsVU8]>QxP~}nrwϫwja/={VحpҜ]DaIS "茄L1bRJMӦNu:DD<_l,Go񆥎8կ...mV4zRstmNZoڦyU"B!2sRJ"RjN)yy.fv^ۜ뻫7S Ć߷1 %Thߏ7ͶVѫ:u]שYUMSUM1B $!:,mJ R߼_ެ10btGx8}L&"DBda}0 0@ё0!nVPk,"hIsB]ǔpRLu$Q1F )TV4c*JR>I7ׯ\!jQ$wv V"&S(L#/ "KM+jU&pDOp(!&&**.nc]DJ)TJ?|C^' Ă"( ̀ H@fVJ! @þ~s=1քݪkW`#{@ AȍYH"śP1MGO?}]Dv0êiatg\|g~=~$2^ތKÎ_ |wCjݴ&J)QQ2~_ك~ i-)0oڕqp!$m۝7f&d[7IItwwP`ŁQR$yiݴ?f>J1oTQ2a޼9{R*@uj%" 8Xarsuyr YmΚ Al;-Q hg+{6ݚX܊vm@1ͪ뜧M>AohnSa.Pq@pgiaX{?d3#<&Uמ=8}jy0Z)k1Fr^8&Iqu-Wi?9ZHi"Bi47^*,4;CӇ4s)g"j*Ĝ8"Fs}g-DuXMӮv{Gb S5cuS.9g7iχ}G ~y1g#nyUv;!jw%RC/i ! ó=ץ !fyAuW]F1NH(\LMBݭ[,H`H-" : RGP9gu_8 QDaݜm7LOͺkC9Rp8G 1p}b|=~2 T 1;n޻CE]JcSQ@,s1d8!,*}yy!jU/9Iuu~~޴VBTꅣ@p_^^2{ߴQ Y r. ϹqBK!F@(sH4D4D vuvvn欮PWB z.ĩjnqcHgˎ fL#NylM͔8ZJ(]ͪSQB(3#Ӣfj~L)7o{3cc;}NO\MDrVGRpWcd$lO릵<1RRJ.JDd-ŴM sԙlGw7ݮwDdWnXc6N8e{Ca !TPun2SUhaf-\ǒysZ8HH/Y8x+BBCȥp#|sssyy"f0sQ%a@$@zUO|ta02NcF웦!nô; ! %zSJ!EIu"uۤz%UL1{q_I + "C)pP?3??a@{OKR4\fC@'54u8yΥe-+gSuriog_>&f3] ZZȉQgӟw8BD.O?t$s=}TJz@ꄀŀBi>B >Y&6fjn(!blڎfu5+BnQݐ pnD$5esqk{BTFU=v v{"+Lyqa?xGPvYoO碷;!O7@XvO"!$GPMp\~ZUu.WM˗@dn\$Z4:j>裧}֧gT$H3@GpN2'@`w_dt@dۛ]*VsQ3hU4H" LH,]۞oOO6>''m۬ 9 nY`qqXGav}'nbU)ѣ]_,9:TҮ Զu]fMZw'''=ޞIUq(T]ZRbH -.CIV5qb?oC%"Jȶ!'O''iKVo.VM^uURB1bUV: Uӝaֺ;;go˛]Bfn  |8-,L˺4s!JHO{um]7M"1avgg'8vs0|ޓ׷?˿r\}ͳg/1/,qqet$Z1#R)9H__m7' CheO]uw"]5,m[VjUVP${O?Uwv7_c*ƈ̈fnʫUŒhlϹz+7v>[oCJ)m!cOf< e΁9hcTuU1%ˣ'{O@I * *[cD("ePffAQDDcSΪ$64fLD Ur@*9G`F,DU2&&UUT$3)kf}ُ1)!ƘUU@6 )䔓dXDi@A A@Q5#("JA C9y )4o}U"7mBc1RV&c1ѱbfke2CVEQw/kRE &&XUT@E iEXU1h<4 îaUE "FD@9( `@D"ֲeI !HP5 "#!B5 )1%!2֧ ɸ$MIRnm(CTHEU5&M6(p᭵J)*2( )dSZDR&5j"M(IJL謯F,}r)E`d$3#9 Tdl[_g1 " P@E``fbIQwDUDaWM841FARSfR&QȢ9cJI jʔGk&Ӻs1 Y@E (Y5֐:Džg}Yffa5e?4ES]V&-0fX:-*8BRxkrJ)aL3"sV!!!#oqR$IQ@U rQ8MCwsӇiUVMݐfˆE iZ|Rىweκ'C 5!%@-sQ}b*BlQ5+(0"$chkmQ1Ѵ993pR0L.ܶ!b}QBB(UesR*2 0Ŵ$ϧw%g_ڇrɦgo֔i`;d9 uI" YĜ~y7 !g `R 9E`e zI 1H*ا$a$-~qwW/qPrĻ6NK^䲎)ko1(J4ԦUȒ&v~0b`6f_ɽ"HL=ir7W7}?$cc2X9[wq1h'㈚O߻crMAr1,1 0""ETe:YE1!N&Ùw:We/<9)b6 cz{BҔu^ŬytY]jc |>QUTu Pc6D e9+(JN9!Ƙ"1L LSpݽw0UucF벜4 ^AđRwL9z}8EAΚӳfg }u8r1ь r [6YD""*]_8Upֲ1w?xzc˺kus_у// ˈ֘EIH"8D )T$Y$qYCL8VU3NS@@G{LSYCnTDS_ω53FMYٴJD$BBC 1p҈2!fQq$cE~WڋdG qRxKT:ݴ70N ,NgnݻEY,ooaWWզi&EQd:O˲Bb84!*ٔBbNYS,A@-Rʀ \:눼]{{J)i(|p>- zfzf춛7o>{l\Wo_.+fv0H.m !*"QR$N)!qELbLXD4" [$d%"֐` }ѣ{o>/q2.|>_TC2r 1cvLK}R0cٚ޾~8>%T9Q,*9cL$kH1*qQ+'amv{qyߝ fӃl7/ڮADA%[Ƴłnh`R4"ɫI$PۭJ:?;9]g 5u]z02[&qQ s&D HtV%z+WWbJ)WOjw]BVUe XctY]딃aj@-!''ƀaR$H4M!朽1 U#r߅z02Jnowmӳs}Y,J")6zRmC+H&ɒY ;D8L~noou 1(/KR1&8k=*Cc4v]٬nnWlrq.ږMڶ d2/Ύ.gGQ")Ůk~{wz22 sZ/ڬoRk٪v-?5Uի7}"J>jq0(M {~ttQC!a尴 ށ]A,cB5ecsuc1ɠM;u_o8aŝ˳iYd:4d7ֿ|f뷻׋oϟW"QRcۧ~3٘7}t?, e.{8=9sEYɓ'MӔu^_/mxl7!?}dqXo7l:NhZclΎ모yj뷋AYqdb=zٹy3O'w.m|ص]iۭ dQU}F7oݭ޿1hϟrDзGۮh@8Z̚ITn3Bvټ) CiC(訾<~tӣo_w#3(nw  YR*?n|]^<ꫧ|GN5b(:1ؔ%+W9oJٴ)JDZ c+G Qϼ/~i>޽mnOxzvj%!Q!熙?.y|._zN>vfu<öfҔ9`fCl4^E["cܞ牤FHj 37&_Lvw+֗cWUYcLN!Ձeu~ͫެӳ?lR)bS{x0-u9gq XΎM)iB2l|I;d,l>`[f4dUv@]C?r8qINjß''vs[/Ï.N˳b1NO˂nZ^nzQ3SDZL$HMٱ%,€_IȦS"c6 c Qcﻏ9ۗ?TDdTPFDs \zTX6B|֙dM*_]UrW=tM;nn߾]n%mDL0 Y c`>]|Y Ȍڜ""fagwjyUtm1 " 8e< ~O'/^.w]}SR5{_DDDJTe3+jns k&2$ ;U՗_1(Gǎv}wDs /Ln?/M1(c}@-AdDF`%rtLC? (?y7zzԌ'UaYLgO߼ytpw! u + $+OU}rqyp:{qfv)% $@\AE%☑ד3G''''_<_'k0ϡm_kl6ƎiǾv7DFqץu ndy Yۗ!HLd]2[@Y<{fz=bc2>{xӧO^xdzrN}<4EMj)M*?9Ciw0(s$tRO [ճ[GVu-r4v3!eɀ )#cYQ߹3'cuu4?MY/Dh@0n@bR9 vc " @iL<Ӫd 7o?8=?::YWι!ؗPjV$RV̊؍q髗o|Z2뛛鴙5~n ޶m-ly}\oaH*)*';JWZsr|||ptdE9$wC~3=@dBL_ڧU9aࣺ"k>p=6Ʒ7kEMa5]fD&!1mnsCcY0C3,`Zn?5YLcUVâ*"0 x놱A"󘳪d$ېKoՏ~0LoQcoWHa0^*24*2uȸ]nݦ)dVXPENONwZݶmڱ (lF)80fo0'aSyLs;J`ͯ !żall@&@)1quXwn܅!jS"_5nhIl؋;_w]vEŘ\!4qFfACRᲘ]0SQ/=>;?ݹn:dtsa3޲eBvn t֫ns۵!tW%7S[6}Hcfwe*r&:-͕]n5'&$DU$vCtX_|?l!lo[F ƌ:("!$*vl~Ի꺘L\Y(튏ߟ_,LK/"H6tw;D!m7m c1jfOi3C+Tg*Ws(mCcX$SVs\(\c./@N(9=<:\Kn)H~jSuɤ_OI۷on}ʳvkǡ1EQl*,|LVUQh<~Ǿ:h 5n=ifftR{ 2t:L "vP2U((e$4]wm,(ËBr!X 轟NjM(m"z~ܠ^}Y5=ڡO?>v;;VDDzR7sQTtD 0^%#o9-Dϟlk@6Y c^(ƻBpIr1 ^G?IDAT>S;f/ꐲQ5ƥ<89"2/1Df?s.E5I;:C9QT BFN/߮X@,\AG۾kf~}ymNDrr,r~>}:ͪc_!* (wKUT@z1IOϿtzSM6?^_fB6E)+YYDqsڽcq5'MV15poP%UQwDkc:""E]oBJӋw?:{ٷWl2gDgI]Ymۢ(r:E{6Η]u  o{H"6s3$+ RpY3vON~˃Ӏ.z̔2Isbβa[xu-oGZF0!2eY_>8$ »Re/;Llb" /[wo'Eozn~x΅L\3cJ)YaVIL&1w_c͏_UD rL c.Oe?vwѪL-~{|ptjv`-W{lXr Z8T~vlRJ{yp{#?eY=FPڋ$UhJ*ǹw ~_V(Mal I((WaC{|A1)d2N7/~W8(D(|'w(PmQvśO(zC ƘR4ثjEC_7â([`'?-0 "K^qk5Q O1ܼs|T<ⷓE19ܥX 8bN` #bVy{q9п`yf7% (d,`^}ι|N $q7z*->XBƵ.>﯅AZ꺫Үן4C6͐^h\U7C+4C׽͐mz[3d{ ^CCWf->x0I;%"in Îsel؏RD;R(liǒ= Iڸa3reLCO a("ǒ v]@ -e$6NGI}Lr,"6ZI}V`;ê +9tDoٚpc*N: D[ƌ(602ґ%<. vFl@ Ɇ^R&N 5рZvPiuBuGR7]q8+36kDuxUnD޷_}>l"q!6hۨ#EQr~c)6G]O‚g~>qal`;6E}@6S 1iav`}K8nS ЕX,;URRDC?٢R󁏹&!f1" +@ {/K?Oޓ*3l!5tow$p=|wxAgc?P DLX3?@m j8CzӁA;Y 3mv}.4R'r`xqYn*+#٘+h֩rR 2uDIg# éɣmxz7kD]7v,t &!zX۪Zo4:]Q3EJ,ewYR&%죔"Ytdha k<8-<%ZGh0bad1)c&b<:4 Z&$ϝ.ԯSS6H sWEfW ML" 4}?tǦO}Ԛ'uSrgf،罹6 |<r9HEtcǷ'MU J\w` 0ER"*" (>`jePE*q9tЂT#5. / yxy.xdG}4r,3AamQfn8)@LYB$q#Ű˜EC>"gK:R!:ϣ* Tx:&)#q".>I8Pǵ.`3B bX""#y_4F1#,r܅x.{8T8`S֧%5VG f7ϵ,OXn&d~*,jML(}Ų )JMhD% یOvw]RϦY<ըCMPTH|?9d^ڄO'1f42FD#Av42ѣ:"X㔆8 U9'|97/w#Slؑ>e1_ߌ׿_treesheets-1.0.2/TS/examples/sales.cts000066400000000000000000000003321352107072600176540ustar00rootroot00000000000000TSFFDxڍR0ZP?óByOcbDk(valҐLtA, r $@>Okύ^!v)D+vDpՆtvFD/) |'Ả$B3 Fk󸄠 o ؖwkcNU]S|m]`l=4$L*3t+[*h83cdEt½A`L_p0\ treesheets-1.0.2/TS/examples/simple_graph.cts000066400000000000000000000002551352107072600212230ustar00rootroot00000000000000TSFFDxcxt 02 ^`$aP E  Bb'2b1EVcU1UX՘ 9ЄU9>j$LªY,FV N/J,@V`#D3Xⱟ'c#je0K d$av@qtreesheets-1.0.2/TS/examples/todo_calendar.cts000066400000000000000000000676241352107072600213640ustar00rootroot00000000000000TSFFI@PNG  IHDR00WsBIT|djIDAThipgz5ftI@ d0r.ZGReg]rjH⪭Tr%l7Jf,8t`0H5F̨c$!1?Vu}=S߷ @Q! ;!  4aR% q471_}g OiJgy))pOzj*9No@8Ǒ긤WQifRXHB4J͉qx^p*zWh9ym?)g4ˍe_ %!!,͊i\ 0`RQ\MZ_<ޕ+~-~nG5",VE9wh**2fgNbaG DdҲrJrn(p _Ci ޵ )1 }}RӉ6ODJNqRܵ1,N'#;ꀯ۵H޺|%=}ZoC| AH!wVUn,5npEIJeu80L~n:bZYI;`˛n K;nhm%1) 6H^ jT8Ue`UF}u55bBNe%E;w(.~`I(PXXz.X@ϝ2Pױ~p[RD)+==Tܺ >N qSiL`Xg*U&'k>,g%e5돂ܼIu5) v;Ks"^O'ɘm;;wPFYn.rr_j#&3Af{; nКܮL]W^0z` 2]Cw4},bfzp#Gq64PPKьX{%8O;쁠>~;:8s .0 /X[t} 9! cDuLDF4¼r?yO:eKFUZil:g4Ii!g E20Dwl5$ڜˡ;&^"70"d/&PYc?DӁNO}AXlyZ5AX`8=H _Ơ NSI_0'76z0RPYu;&a&&qjYۄ4]C3"Dt A],۪=DNf/ָlegQlh}-`J- 9+3/6fq'Lekz)K0L+mݦ`aq#h6.L)?rY1_7PyfGpy ù4UaKƍw?tR7Ƨ+=l")%Y[Dx6b2>AOym-wh\ өA{ ݪ+ -x a&XDn{.\G,{f.$#K2VъUa8"zd#AZvgM8Mn_d32b3?Al iI l"2VIF1ϊ`' DJmމjC6^z쩇М`Ʃö?zt|J˽8ȒCvܓS_P} Lr(uUPQaCcchGCCB̪<䓮@ A "H]]T{qׯ$ P,@xp UaP[C"1W69Z?66tuu)#뮄 c\M ~"t]sy<ρRMVpxD"VQM޻GuΉѧpxlx"XWab}}=, @x l6;沈D+#UO5|{^DJ D|= [ e2Y<U%:nl]/P\Ua`}ؾ}$ݻww,;.!}ԙ3 I"x<ρ8JsG0$b|<Uհ>ttNC ffx*-$Pr=>55u nbY)+ ZaɠY0.Iq7_Rb 0 q,\.;Z]USSa,KTVVKt󼹺 fdp_4ApE햅k%@`#le0xiuNb\zP]]=0>>c*]$@"/mDQ (uDנ $yG?⎵kn1bhdf@i}8J)TUG2L,<vN\CC:׬Ysի8w<ׂ.x0xatzEQQ,RhE>_hH&3B 86X%T s_ B^p_|rFgeY,Ʉ\.1b>hh $fdePb3|UEQVӧ [[~ǁR`v& UӰȲ zz/ Ĵl>q!8*b.E&[[ zz᭷~a%4=UU,qBjo] O`x|/ˊaV,oy`0FiU:55K]>7RA~vR,q7~ktt4Ni h4G~eYq}Q(6,sh(X9x^%Q㯼}督#;va3A½s_j,%IENDB`I@PNG  IHDR00WsBIT|dsIDAThilz}88@.rR4Ph)TEP@H)jM!@"=hIHBp؉w]1C9C&y?hF3ь<\l!ᗨQ.f/.אyjXd`KK@/_\9kR=w֏@"I(S\2=ce^T^S-M ,b}wX_ĥha}f%!,G,5N qwC ewz>bcibνyzxZJAuMs ԌQ8N@RTuX3h.f'|ϕRnoƊ ;^vPоc>{luu`{ґOVgNS P tG'b/^?dKqiJpuO1MHjkAD?74%ħP|!:wDi䙡j(T~ySX_sKRO oAϝj4a] V<Ɖb(ip( -@u!-s9yH=^(xr2Es/ΟWS]\ḐRЙJc*p&;&\,f rsMSy`$>Qwtף3 }|S PE!0Iúô$1smY"ɂ LkhΛӕbzs@4yzhFB!x];©~:f29Bla`oY$La+wg(YV ޙTx*}tkYQ~ykM"EB) b,6aӺ)n4[~|?W +\xyY4ʲL,z}t VT2in@o*Rx1eKX‘)ecqߜ%6Eן/ Mbء6 .l5]N݈)L&0"-z-&:CWr!M/;BK~j  ݀J_I%E4LBWJ0 bI䂕6)5$K0yﯸPC nJvV,)uQ ` nj!"-N[˶.InWc/m|B0&]I:"G궚+k…B:88!pmc$j(ґ>}a$>:77R+B ) la\6Hp0qvKbk<{{sAӚ:$K\< zcob KZrIz[tik>mɑ a w''6YBL)/h8) M,HmۚңKh7:Vnrw>9};}֑ԈۈtxHFH$Y'Cɒ):Sٶ_!9=RwZ@_۾ٖƫiJ q,2=l?m)DvKU:G~ Urtd FG$ME(GNEs/n_}O9 8X$\mtMzn lD5k_=Hfد@t^30p蔛$ M(<v{?F2$]b-A=>ݷkP9tؖ5ODDXF{yHf4hF<6#αIENDB`I?PNG  IHDRasBIT|d'IDAT8S;n0 }| 9D_ҹ/ 4 x*ȐH,dP%p =Wkbv;3 x<~t:2{<8z8HDB*Dd]DM U_J[kR Uh03(etwyei:2G@ʑsҜR.陂|;< O ~SUeMaTX fUUeιM̨u]c|>ڶeA'J, q4M8;UgZKe^?58/*uIENDB`I@PNG  IHDR00WsBIT|d IDAThyT՝?-j X춇]@\D4 &!5dg2(G=9$jbKc,U@MhzꮮWU;jhr2 syݺނr^8ugZb/,ʕ"񄊵vaE>xZy?\ֹ13T.ve"&`Sl(A ¼LxxkZa$ :**ǸIoxizsOH>$ vû;Nvp3Př7E ?9] ␐@ /"y ׿l޺y쐝nA8>{ֺd\ݳ*§0oD΢g/d"he{&818tP+嗃qLG6L#Vt#+r1V~k{sij;`~7?_Ίz)o2 p Apl#TDbdddz؝d&c'%G`*0<I͏{\Wm6q6arqtD(ӅGt!7&]vj8{ºQ/cw/㰉ĀO!fڊI<Qגj7X`⥛~^ї@zS’a8-Q4@u&8Q' !hF%@:"2B)(6|" 5h+o.$I$e/QtH`۲@ NA빍%l4hT0Vw0fK =`gT"RųP $h U_lGOZ1[)ID8ԊxTR|U}iׇ$05cxuzݰνAh/E+^hxEX?^*aܿ-'!\;Bڄf _6D"jbmZJ^ZMj}Z?mm5ٵ:݇d.t JQTU_L A1fDN.qP xP K> c.q,GmJ > UǷg-]q7޺ƫHa ]imh~ H)$$ r\k>7Rߏte 8 5 #h(R݄ض j+ihg,1+o,a}.ɏ+.+ g̠m+W`ATs@ՠ蓧"Ӯ<ҕ#KXLד;7=~.+w@h,wiŇ# :rcXMEݗ_1A#$> sUD_lxTB貼3 1eTPl5&yh0*Md(,#;+Y~]!2gX^$)Q!2yEXT _E%Xty%h^{䗎ևG w/aOߋJ$N>xDJ@d*s @@9[@ H* TzXegrmw <3ͪUats鏀&!#49g h 3iƩ:0 /PiTV@.n0>x; y&[?xh[=ɔ9&8`ewO#1[Y tdg3 =9fnf!g:MR$#c5N;ҕ(!;uvg)B"!58eu㍡0IIFвcCDZuC/xhs](@(R~> q)R!eb7pH6W[$I[z߂{&mq3N#O;*?Fzڇ7d5 P G(`3Fa챸utI p 5i:Y#> ?Gss;ΆmX@z|x$xHFv|Mf.Fx$;Tn`X! ٙ}_r0vx Q]놃1L Yl,zGt"9qq͈Gư0л{Z/@C3w= Ӡx ayx?&4 KA8ZkhavfCGH` e :oF-0LcmݵD7@+;>= t 4n5o#rӂ7sJf5yգ~.]"</49˖@_#E?aUx-6ܶ 4MЏB FI hw _HA_ܰ;Kg7l;<[߅z?mi ]P@Z VJ+( PQj К`,jĈt!m)e(t{.{3 IN޹{{ν >d B6(X b|b/ RSKB!>BBxNvN;k睋Ю; ,' ,m2K s>m\BQ*<ՅYP: :p,Dgtz֗PµtX0OA\%Et\yS̀Դ>iP~ o3[9܅˂a0{rl,B-B Sh\rx.5N~f+>gwA(MԦU˱xaDh;})!Ӱ}n.iAsA޻n lds5L .|j ]Œgk u<Ď-,X.6n܈ig HJCćlHHfh*<  V5w:{f``zzر.d^:F|9v<Αɾ9syT2 ؍IeKxݽ3{}GhLO @nZ.;Q6v&7-mPj¼H$B:&8_+r^@_@ eܶ|9P<S?rWT˱W^pYk|av;3p|\|yW`9 ߲p%K2ꣃWJ!=t D"q+q))Jba=Vjᅏ @*lc]+x5%gEILF3_kLӤvx<)%sؾHC+*B.^GPJn鋨H}eZ+נ{Wa ,C}vRt!HRJ>*b> h$Qk`0% oBC WUT/+ݻ8o!H2 P۶m4,P(իWm6رceeexajzp(L:= ðA1: E, M<on ҶZw5~4ps?Bfٵk׈}ߏRjdxy.t Z@8b[MЀ+ eꎫMY_R5wu\_~G5(dUhTp aDR*h RS _)RkTg7m: Ӡsj9oĆ_7DcQmQ >GY+M ͨNjN֏0cYoCk=yͶ9$ =9u 1%h Q u>oh04c1ìƍSy_* 'T,f=xd`SBJw VeU[悦rũ+!1A(p$}}3 Gerg ᪒6o[\ik K%XTb~,p蛀׆]zz8, >(oۈܟZޱ2]YNk`i::y/B>>ZiR])8՝О.15׵ޯ0a +ieݍ7-K~cmo2/XR*u41u9diQj8ԅ%ZkXYVda@-XW;+пqes״y,k ^2AL%||Rh(x]fdI^pGs~ ;Mm'cĨio:zK]U2ooDÏmo_A9̒$9/K>P%|y/%)=Tx Y`U .wu2_  }9Wp͡H{Hյ,0i+ y/Ju3d4|4H9Irnpח8CC<)Nd}sfNv8+gruR&_ok.LʼtpĶ:JWXfHp5%rrLD:c&ӹifSoSYҚz^9o lx ir{ |Jnug;kRrBE*i!j`!(\('<)ϊ <ϛ}%MsA[juw 3_RP$ݏֳYZ_E(Po%D! St&EGQ&,ד^lI H'}%jRb(zZr:eؤgV/1=DJHT]M$2%lLf( װm,]D0(e115ryqprCn}#DWoGJ/__+~C_|zr}jh,E Uj*}z F&CJż#L NO!J×_y}x,EWTxbh M(0 ƅr# gRxqplR)@S XR)z04v]B#EXf019R i c:<>:*:^k4۷Ck䳏瀙"߶5-wŞRm"=5B]4:` V %@yI@7R躁ö @ !!uQvx'33=f<@.ɯw?=~hM{Sztz@@M8XY\ .s3PHDAW]GhēUQhBsCt 3uh mRqEֵ;~ p4.ђ2f)^ҁ`H֞CʁP2 ?"Aa&ɻ.\t4S0<1<卫U'GR<Ŕf23un-5 @5. kh^v-g|nDZ PB#a,3[vދ뤰0"+8 t2)ڷyG]aZ%V (r2tvyU@*-nH04O_N] M$^CӲ[h\h"JE%+6zO|3B B044lD:XbZVOyV&F{8uKnAG$<!1 T7R8(>AM^/-˷rоR#Bw@(ta|LϳvySQ DsmXǝBhf+D纟 I|T¼.،44W72əhuxD" .L/7Z{-uh&t&tt⦣U ?h`j"WtAh:HB.]BvM['XJ>Kwӌ EeJ].q0#ikKf 4K{~QFPR GB D* /;/fl|_Դx Ջ4g,oDmE%;`V@冧FJ*``K4Xf4 "2UJ[1G\\4\OKV @:3t[6}ߝ4g~ }9K~BɡeMSۼL@r+V~)Aʢ%|Ќ(C!zݮ2G$^I`pGII%2 %1!it "Ӭڈ2ES 447K "t9?Af*67l}Լ`;5im])d_ǃLc`}gX:okHOdGR.nQ9?yʫ(+D Y!Q ԂY**Ps~Ne8̫H4"^GCmGsfRw | 942X?E7d_2#(2,oE[eJ Q]Bi,]^)GJiABR[F5\n#tud ٢=)'c0lLh]5²`a B:`f   6SW$/&IT,c;ٰ:D-Q&m4Ej]W ?:A!A\_?];Ķܶk/oē .% =t* $<şbQUغii#bhs8;5׿?8Fe0vц{'G~ɞu9KY9jf!k;ONѧx 4ҀUwa94JeL͟#jkf۲]~~k'$6MsoP1';vڬg-ͭqŭ} :oI=̈́ AU%k aO;Iy %N%mk2X'?A^)f]zHYBX\$!m>)Pi,!l "f%6xNzp'{c(ts!DGϙM [x=VUo".ChA1"YE삅Cf?k1X/No_~ xKPpnRCf_v;:TEȰ1 6,niET1.̬ŋ#TظP(~ %1xH/ݷazI$H8r:xwk#ǰ\7*>kb[sJGLXsG-( A</v9|Kӻ~Fzx LPitځC3w&Wմճzyl,tHVXbs bV`yKk}~w'//8 2p`v]ZeWSSUlH(8l@,N㞧 ?EgN| rY@()8?{X5- Ll!+IKC,ANF p`hHC rd9x^k?R3zU5mwj64=(a"Tn|2V-#fI5( N<\Q:"dSOЊqQKol^=w.g&s=Lf{yL!:"JQ;%GEn:;w=|`*9S!ol|/Bg$ 1KN0?ĥ YEGgbƎATY khhtK"hH&Yd`Mq!XXƑ# a1e7;6k4le(m!a݃OW׶_4:62# xh2[aB31ɀ衳sk  甇yt=*sinca6Ş218*Qst"^GצsH+-qgUA?oe@,lDdJ)Ns|}N/oN6n:_8=կ@eBWh!q遡7d/PT< xD@<"ϭd9Klx W{3’_E^4OƟ`ʼ.\ XH zn&(FDemZ6w_suKAIþ? T3yQ!Tbu)/ 0"u\w).)ϵMlަx'c:ul}>>^qk.Z)YBW.T]Y ˹x$xht0`'Ys~3rbf7i<} /P5ڣW .Tw 8^̳ei *cɅnZbKW-bW8Ʊ+roGF'\gp8Ir9~Eޡ[l8!:~ٯk]]ȼ5k Y6.i v/7[pc/v˄1ޗ%*.ũë -Tk:qmR>|VK44bSÌ dhŃ"2 guoy@2A"Xl=;{y~o<1$^-o\6]79ٹB(!() ]K{\1S×fM1̩9īį볷|^ϒj%̀ Iѷb;qtC~054 !+T[(NVN3d)?ՃG2 h(+7_ڛ^^! ,.G[FG>ga`/ UH܅hx+!,u"րp}AĆ1(k qYM{m ~\`[2X57u7nb}Xz('O% Y.>T]6 i6dbFkƠ&J_4߶_rEM7\G֋I.k0<GOLY30 3₎dcM$ 6O;_a;)aq{[nACX JgI\m5t$/_x~ů) &[WvTEehbh08˶om!BPk h Zc+/)==e+1 WET"+ThcBVYM(4weS?kQ2FGG "_Mt#AX!րDu4ֈKīO1Cd*I"N , ctyoщfv8~!{o!*DGERk5F)l f.*VKr$I5?`s?w?eκo4};>||/M--V!~P<6N1(nXFad`&ihF_@<[)6C((\fh֛B?}'zwwAPnW|0^9%AYp͗0ss?[.ǒ`8'}Eb^.`X9o |(ȍOO1Wp|mxm5s':AX6^K.Ə hoM3~ ٕב?rLE j |X;1xrdxbޞ*;-KTq7Hކ^<'/mW]GiV]y)$![䍬[CiOz2i#XFeMwte3}xۭBj<e2!چ FleW]͒:\iܕ6xL`*nZauJ(ed!G<嵪8;;=+Xn=gي?= ;M!j\چ1Hf˻9-=rs|t&/GZhr16z2|OK[ߙ.v8_yq2[@o+.\ݕSHNcjUhQD"D2R'>-c9v]'p7]l NhRd SZ72ܷund͊rePEÆ.8CW[]{Os ]V8֏nݲٸ%M4T| b& 4>ZNrwyrj=0Ɔ5m$$NpaQM5d_ł,Lt$B@W[/)u:Yu\0s`?p֖$Lc?}feG.rzj ︹S9&B uiA{IP6K44AC, B %e--u*ӛ!IGݳ?څ.)S}KSu@$H$L$qq#$BH$l#$˳k/z 7*pZ>˚V6j P|E$] Yl\>k(q8Js75(KB& E$pEG&8/D H0=qK4̔4L90Jp58"2Q Mm 9 Jx~cɤCUW$H2f@ I-p$hjNiT @*L+ k}c=2,麞YXxqz~8k s&.R$pq8>[%}KOG9/rLUy)" VA(S]$3INִ/'] F&ӴCg{"is/K"Cw3 @ B mrz:H:/K6fFBH0 hnL&@CeS<"?=/ǏٸMH:h#A;H5 )SX|H@BG59P:m+"%.sekyC3-9dAt/7t ZAN @H>R%%E1|nyI\,̂@ 8 0~6hġTn[Ȥd2R:^ELiQ`D'X˪tΰ`2P@悁P KfCxR]͞@7ЪlQB#eɀH Xke*5^˭=EZdm@&<)[`08"-t*aGKh$ 0ށd~Nd/_IZT OJ"k;@~8;<_\(Nѕd+VRy'9m_$AP+^@+OtL?@\HGqك~Xy׬1y%M.-i^DkPUihc * 4*^^q'f _xX)=H?u]MvɸV@ؚYFZP/L =3ᣏN- 0(g976,^6H4NtS'9 IlsRcn4s#9=4NnhDBrG'?4[ k5)DsHdӎSpRiG26FFSVf=)4bh{(p&zUhہFHII;yʹOT D i_X@ՕwIENDB`DxڍWoE8  Qh5 Qou,h(IU^ǻ2;oy> )wS /Ƴ'dY'wsϞ U gee6ɏDˆj+jS9b !8]CY^^Fo{,AF꩓0eX| 3ӣ.epEAB$JG oReRxEC:$M~kno>3sbkFBFc%bO0R؎Z" !!MƂ[D5Q5@O@Y W}~n iKH}Devvat㴋vf3f0EDn0J\?Ñ*@T'e`̎1ߴ`Qٹ y[*vJr\Eޚ}qV` %fLgS*Օ67=*vU`Rل_m\/6EpD+$}ITfk:5X/E)&|6iktz#ʬT[.L⯟XeS7亽UÌ!eKec2 bI2k$*i5tm"^wE\{C6 B{\k\hʚ6ꍉamG2ƀ}th#@ڔ|3JMt 96&Qk\nۃl'^ Q)w5Xи`Ċk>SkBRhniv=_-c&_)J]i>_Ku6Ӝ:$b4acW鞔Ӏ΋OY#LՌn>k::돐?2Xוnuj4)ux7 %(_1cfn-%' Fv?aj~¥Ǎ eFXp,i8r\SZG܌%dbb?<^H)n^B\zxrJ_(@,ޫi~[ƣy鱀(|Xݤe0kKq4sRiKΛm‡]Q)^Kyȕ1 .hr)>rUp! @PN Ӱt`!#X$ P(? Ʀ|)ϗY)`S4 q44ݒ M X䶉Rʺ}Q%:I27LIB@R͐)f IuF[=*x_treesheets-1.0.2/TS/examples/tutorial-de.cts000066400000000000000000000567601352107072600210160ustar00rootroot00000000000000TSFFIPNG  IHDRosBITO IDATxy|U !!$!m[$\+Ɋ n]DAYT.Y D% 9g8= gLwW^zф AAqA j|Ao5> A[@ -GP#x A j|Ao5> A[hWz@AT3y8%?CA[PdJƐ7'iAAS|0:At: ނ?sM}o ❸B -AA?> #x"GAԃ~|AO zЏ ≠AQA<# A?> '~|AD=GDЏ  H#3%pR3~?LYP{Є'*9Aq'Ng#x GD\NG0Ax|AD=GD\N9 .BK4|)aAt< X²,va@3LS ?vx"\w]Qb)++rʥK*++ܹSYYIQ`ٳgϞ=CCC9QA< 5~%0Ӿ,]d4M1mڴ!7e).e/\;拉֊uTf_zرc_{HpG}~|r pDz,>Z8#.Խ >z 9񡁁l6ݻ7''g .9%zx'fBK5lFn+WOQ˲ LI$@,Z~aHH*}x|S믿FGG߿`044M;i`0۷'eFIXB^2L ־g-**8d2I4M,rq\aWzOd0;6uT,*}ěqw0<>|ر=z///9t f3q֭0a AСx-LJ>lٲ~ؑ<׿ԩSa}AL2uTK8h К[ ,_l6,k<`azeh^~&%%}Rљ&))))++o :pwC5jT?ʒj|P >D X.\ΆZB oVbb"eVbA4=z!CfeeY ^>Bܝ(dy^f_eUUZ8;;ƀ [:8]Zl+|KX ׯ߽{x[w'8 B^CB7cT܁ѣ_~ep4 P lܸ1//ի}xxxBB̙3TvdKh nI {xVx<2(O#*yoܸqqJ,+DMy>000%%%&&su}cT65ҠbGUu8* gPKZۏiSlU'dmiSeeT?`XWX8;nܸӧOJT &/^XYYYYYY__ݿe˖AAA]t Rt~`?|0,9u90mڴԩSv{gMHy 0v.]ȿ5;;IK({LG^pAOlݺSO=` ϝ;۷ڵkvܢEǷhтLg%(`gZδ,%N}}=!瞣$Y~ĒG366Vcmv}ѝ;w0U!ϤI4\?9sE</+J^~zy˩[Aprx5ѨDŽpeȗ+rB$|1Bw^}b8qȑ0+|׮]] ~딥~|ZÊ)t҂ CݮNؕPyӖD!2(UVD\(E9sljX&*TMӐVǦB,߃ j|G!+r6h_fҥK)8N>vQ`eԩSћ6mi<oJ@B?, :thO;/зO:U^^n7a۴iҥKa~o`OB C#q7oޜ1cƕ+WOB<]SL CYYٔ)S@>]ͥ)JL!Rw!e7n_~Ŋ&ѣ5<1-ƴ0DE#Q3ٽ3]HK:߅ ֮;-AO4)//`01U~|G(9J&ٳgfFxY{{NQq!\G;BШ\BbYvժU{8qh(Au:TMMF4:^~_QQa')ȳz꺺:9>>>mڴ~(((۸f`8yŋ7n?.*iuuu*a,//ZDŽ<###77wذa}vpC߮< 0Lxx8v%N]?!cbbrssm*ZOSL={6! i5գGѣGL&gLzҶ'`01d扭Zܹ㏥&8u~Ν&F.L[B,˂ȉܕ4LGNիWqrs̡읾*G!ʂ53tEA]"p'ORsN=8Frڴi(δ|PEtm>BGgnܸ(`]D4Mhy!+++aku}A,\Х8B1Uq7(Sf04o/̄alPP[޼y3!b_81@ǣK}7o޼crx>}ƍڗpCBBq$O p=+^"֭[LL !D:%"@7۱cG7nxm05]SdrSAPTxDZ:~_WW'Rae*-rrrIŋ3d2Y']p,>o>bɵpԩ9se)C\sk XdVrǏ9r$OGCx|޶m۞={ڭ~-hz^tc='AoF~fϞ}Y@\G>q;c^Wm|M grIΝ;TzL5k>dOQlݺ522g=wѿhn?ei؏# Tc~|MP:! 5B{4k,333xe"4MCΝ;###'wHSB̞da ΆJ ](]/?Ʀr "((/| Q&@,X뗘XRRx Zlb%,Ϝ9ҥK]QYCsmdPǁfy~ܸqǏW(Uy{_\zuԨQoXD]^)vitT<=%05>|OIJ|Ǐ?#5M"h48qbɒ%ҽ݄???{A֒VR#>>>F @kˣ :k֬9+ J>y !+W;wr>IE4ݬY3(:Ք FQESzwY6U۷/22r֬YYwBb^\Iխ[lٲvZX%m'Nݻ7!ܳ˭[-–-[ґ]zGBFcXCk֬7oޖ-[>_~vQ`0l߾]K](v<i7B0gΜ:th}}7D&,YN8Q^^<o8s޽{?s)))v)w۶m[ZZڐ!Cf͚K0lxέ#??~֬YvQ,[VVvѢ"J!OtCϟ?_D,;Az!???4A}>%CWZm6Deo>tP)bޮ]ym޼yΝ?e1!>k׮oTw۶mS3 'ɴvڈ]Z0Mpܚrkkk5vcx~kZ,]tѢE|ƍϝ;GH.xWXѯ_PQQQQQ%ׯ_yvB_U'OƧn;v,wu֥Ξ={na(/b,' ĉۨ,^ C,y@pJaF#f3N<޿q]Wzfy…Q!0 u qT|}}u@/`%ٳ'8V% yΝ?0::zĈ{-g8+ ȴ'4iX7o^jj*Խ^:Xҫ:."L6Xo4=ꫯBA8ʕ+[n8@QEh|ӽTqhTÆ ;rȣ> d|ѣO{,w@ܸ*K%+א{nΝ@ ҫJKKǏǷ`0 xNpbWG e7ocK2ڣ3B4qH]nݬYw`l޻w/yC5B9&8eYѨ^xMgc-xɓ'>|x„ :Ax9sf^^D|(y(~~~ BBl^j_|a !,VWWoݺqy|}}SSS d#U<ϷlL'xbٲe\CylĀ-n[ gϞ={} JrBٴiEQK,]Ae˖ #@(ƚWxw'w2tЁR[)Dlc Ì9rȑ/^ܹsgZZD(1baY]]3Ϝ?>00 Ps .::Zڟ5Z7,,8_ۨ(osn59j sϞ=%88֭[DϹU!XzzH«M9BCܮ]֥H2?ӷo_RXPEE'|2tPv9zjMـjX_kfsǎK;氨/\RyPUU%`7O>CAҲ2OqFjjj4>T o߆XMh`Z'N2dT799%G?.PE̙3eɴ45:Gȣ*q5*k4~ʞ骪*OiXDU#0Λ0a±cRRRn~_TTR q#F a;}4jБn[ؕzٕEQ0KHHطo_PP4:333Fj<͘1,"Ν;IXr(z}}aÒ58!`#'N46Џ еkS\e[n ;NaԂ8RSSQ҉!_7ҝ]:ZLbTҴi({8nݪW5o"")))YvmΝ!N)}G8pʕ+ 5d^F_i0jԨ^zD@=*v̂0`%KFsRRRXuꤤ$iСC!<HfYԩS cIxN8BC:9n5 7..NZgپ}^Z֭8p%K]]d21 |'O 0&b?w# %9vXO(R8<|wynzBݻ @hdF(MZڨKNN8p סpL&Sll?OxAA-@_bENN#q°#Pܪ9rӧkTh@.]ӧo^?nz۶maaa.\`uT, N6Mg?w}_9ɩr 4]WWaÆ͛7?<@HHLwܹy޽{ϟ?OYvEHŠ(?11)DjƌxG~ePIDAT|||6lnDiӦ\Tu0eúVPP>xiW!VG;Cp| ,.]ɇ~;iO`ߢE hb_uFF RSSSXXXXXhRu}O>񡤦\Nrw5вMq$O`gee~z ܋Edn<2;:\aު#h&iȑ}^V[-uMŝe˖]L&YO&UdܪU?ǧg0WtKJGm!OO(0dN>> \ѬYFw w BBB>l6K"Ē%:fLjy>}*hz%<&CS56J 6g%,G;sT~CStu)Lg͚1ʒYE2(#m9f-ZXfMBB`{jo!6w^f0t<Ւ"i0dn(jxyDDD^^8@YXd4&k׮iiile$㇄L&;:=dʒB!_FѮ];ȇgs|J {Vu*fݻw?z {+WX?2];vҥeBx-ZBxTnuuuJ~Ltee+ڪ8E*Ryyj ڴioڴҥKu JLkn̙- rVݻq6>s[[988x#1_}RGb /9b.ԩ%q@e˖EFFJUݛ]Ӄ?7o7wh()=a_?|CdF#e}}}F9aȮΤk$**JBwI z뭷4ܭCln Xlls׷ƌC6yW^ձ­`\MC<;w[NÔ?ȑ[?~:__߀zhԩ%S hR+=ɓƹ^t XIE____\\?^~F,mB׮]۷oo4Oⶵy sGQ_WTTTTTOt-vGS{שS~+r<߫pԂionVRَkpڲ8[+؏Fm۶m*ފ[I[iJ~㊚5;% & `o߾O>"8 aF}d~7O0HMuA\AHa c9Xޥoؤ;Mt]$# 0A(x3ցa-xh<> A`~|AO# A?> '~|AD=NDgS 3~Ϟ)tβ=' ԩ, GDЏ A<GAԃ~|AO zЏ ≠AQA<# A?> '~|AD=GDЏ  AAA?> #x"PNs x&s- (A& j|Ao5> A[@ -GP#x A j|Ao5> A[?̫<[IENDB`IPNG  IHDRasBIT|dIDAT8mMKsW{on"tS &$ %t'p& H+jrM[-O+vU[@weUh BmxEMbf$~L&Bc nZy`~~>O0Ƭt:KNMMR)vvvj(T*8<<x/˜iS˓2J)yZى{vnrO/333G377'Ram"υX^^qF߶/X,V$JR^=~y!* XDGGG8@%m\EkM:P(099) zr8(D"B!h9??GJ#- !*D1 ~!%Tcccd2BBnoo}J)8m& >v:X,1677VضM4ecccjZRi;9ZkY[[# BFcu9== *wVR|oWWWaoRWWWZw3###o-" 9bK$qf7ͧN.A^GZEKxlL&&N;m jC^!O7NIENDB`IPNG  IHDRasBIT|d7IDAT8c`0A_k#_-&50$+튲`BJQm*): ]DfqEJ1q8D]qO&j~tPFCc1+CDlo^D? "up53 ^&]p/ܬrPK&Γ^.m͟__?x>d;>SA!'ښ:FtWN ]i!F! << /1lrO0K={MJW ǟ~J)%VGۈx)3a k־O9Koed8-m9[j+3a V9KoЃڵ**?o h W ^cX̾/{-dd`k%MW c:ۛ߯Y,{W 33000s篠/R_nzŰs/pE<ڰ.p$Lo[~}!qu4c}6 _nIUS ;7)$IENDB`Dxڭ} yDZltFy`S!oœ/Dup¦άffolEBlbrB[)XJb#myLTJ1QJ˱ٽB"{_?_W?qw1|ꫯ߯Ss:o玨&R lå(ƔO_[He*t偊bf6w4FqkN:`0]YTl)277U,,v(Uz?;24Rx:XJR,NH-q8{=n%1;!GpBz}O G5{a[n[@j@SuBȠ"U& }ü4QB c|V%O&!]I˖voDŽԃ{ zPRw(",җHV}wp-vQJ5GI±zID4 S=E[BƁ^;/8%TU ِ/nf9OØ4T { ZZGˊqΜK#YkQ.PǙaC֯։ aVcma헒=$*}~W4 Eݥ {,|*ˆ0nEq M vӌ_4(R~Wô"0m<*3c*j4!hgA…E\vh94z9-3{ TÌooLV;Oa6c°S(c3̗`Rpowa0qn[ސ3Cq'! 2g<g-H&=ʣLhb8YS'qpIo(ު*G b=Ül1(l/e.oe:*MF> N4ܡ`/6m5(TCQLtSpsPen- +XZgX:g˴]GV<鈎ZtTuqp,qaF1o35q'iȇz0S{ rUb`D`Y:26;w꿂Zo>̈́.z @ ^ X/%,4iFzGpT Z"]G*XgAl,Hz7WyIRB5#0靍æZUƖMW1ql'ia` kC5\Hbn }?CUclYدʅ+ly8H{Cg}XYN!D6rfO#[V2=>llm$zo$Iߵ=:a5m}˂Rha>̓ $YCVp!]H[( ҕWa}FC-W>T:JւœE΁HlEF<vs r"l=t¯U2d&X%c3ۂ[!No":K2k$+i Rkb~ۧFJ0 ]aOZy[ѸfÅ_ b-A2ij\f.x4W%hovfs0lDY$yM\ Qن790O0xqg1~Y{Lh,`E={:I%cwϾ{&&(\=*D p-c F0@AbLe 8ˇ-ZO걝&PCXrZ.suVY%Lne "þ 5b{[-͵(EDHhSߴg)6#&,Z=3UvQ=*a̝hC lfXJ>„%C?H/2Tn Hq@Oa7`q`''iьY}tqI"caF_MC3/ 5o*;hgv {g)>.EmBG}IFfjLA,7:f1 \L|[np*ko|֟ AD!|곿R&9/dk9ft\lTVIڌw/g$w7B^1 qf,uRs[UY+kDNYV~HU+TCt k0k`xh|| hhf4#L[Ze s΄w_?o' 0S1Ry0(2F^m+ި*,_l _P$#Z9(?A'\ly},;Yis@¸DH\"˧6Vk@`v~怽H#F@S&ż͡YhN5mHbhOtiޯ=e+c$k;nFn ]%-vs(}`&8~}JI {IMoxcpHzd}\0zWUIAJQ9ǵ2E:É?xO| :VX5췘0.;9Y:*#;W cb!gl'%"#9b `.y˸ߏ-i9 +HHX.b`Ij1@)ɣB1dl:sR1Q*- 1|2kX|# rG4ɬq(< s7sE"/IKW'4'FQ7DzX,wNc+˜]g:MJPΫ:I-vՀ[4DE6~gp3]+[֯Yw:}3K2xvOЫm?Fidc,pL _| M $X+aw2:=-bx!qV6g$ZfIz8G9&{# zT, dhtmʇ:3dKfo4]3]1 me#n7˼̉gء hwo~3N[fg6֡XAzn*V`aWsdQlmɂ-^D\Ջ~olJ#C=w9hln*Μ Mty _>:|FAv¦.}KdnM|Pʺ&[uk3ByS?}~JMg0dk) 4 _Dv\ Dv/NC)ny(bc_镛^RCm/M5h|BGӱ_>˚| rox|"$j<$3U E+64km4M! `iO 0!&D&dt,ޮ1G&}s+* {Ԯ(< ӰݵkeJJ>!ܣhJ)Ǽށ~ηQ8֘q~A98Ʒ$SxL+M9։ Gl [ʸyD7!=7z_UvIQ8z 3z7Zgt`"1AS2Gt0ƑՏ4R5M}p 6nS+{lԈz% dBg9N^*UmJ>ΦfpO eS2 $IA3٠DbjZLV}Bߚ5ʦ)l"Q?Vܥ8\`dīdȍqHN@DI[v9=Ռ`K6 ];Ve]_m@l)4]iG:?KtDK^҈—"2?N1yΑ[-cEq9iLܱJ%lv$+gMU&Nii("sP۫rh>1=VbAƲܠW"T hyoPĥk_C0jA.ɰf^!rt"ۉd>D" ms#QPmMņ@Tgؔەk]2_٢4Z! 08l|6I*Jl)$kiҩ/ΒVEFc't'Vbs:JG' v%)#jGQurZ,2YXO2t_us*ĢVHhӃ`I`RhwVyVQ&Xsi Ѩ4ɝsmq G4ơ'MOb|M K67m͔C9R3FpoSlrN uwԿ_؏JRuSt#q|['4 f?rGjԇG<$6E<~H!lY {,c7Ar;'(A`4e:V+.\<|H_ Ouz&Zg}^'.Q>;GRaN%/'>)mD ٩$AXFx@M)F])IQ+Z8ASU<> @ BC+!fT$4Sk܄nN16n-Ao8l`3xob6laMgWuRg`rZbRgV>rGJ # |~ d .ZCk9TO7'LYQ: ])Z*Z$ԥv~f*HC9[M"RbT5AF1z3cY_EZ^t')'f9L U৿2]( Oأ_pVD.==Q&;k[ǤPulݝ寍B!z}_ū!}e:>""D a[i#o~ e.cy+D 7Y=td^~u:ּtA$RH-s:2a_ˤJgɕ1nbH TB[*X$uX%:\&"I9GqLNygţOA棐%~*, [bS%u^6L\VKdk54"$VK ͧ.“o nz73ͱ~<%+ j}Vca@WWH~hPBZ ,psEr8 F瓓78{qEqs^|fK !қC%N[̦]DJE/ZU3ՃU6aɬh葨,1G Cт>> KG@1 K9\I=aLZ%,JOYrD I>zI9wI~f1DL] -2<@ꄴYΛ-n/2k/)ޕx! )d1 (;X].X\@xf歨O0x)61'Icy:hk16OCAo6&$hG}eNlΠ $E03&#a[Pc&XV3woyRgJD:Hg<,xlo XS@VN\;~U!X)_f&CkKI$e{ܠ"EyE)S_17}z$$hȓK(jsCVxVk^]ɯ5gx$ukTrJ~.E*|211Q-۞0&LѶ-:/( Lf)X/|]5fN?Tv@Ų4wM1o<0"F;216ip{/LH__'/N玸=˃ V;ϔGn>eA@wW?3%ʟ(K8L8 0o~첀[pzI>xD0wy@_zigt<pz ϭ6)ҚlB|" {` ƪ֏#d;3Ջݑ.M.r;]Ka6/M_ yb<#eI {4GERu/#k[A ?-ۯE[(w8HU8ZfI/w?4Ӻ̈\S=kp"u$.4oA1zNh2nT!LM(}٤,a2R*sr;B(NnWӠUlL ԭ`: 551QnSS-u׷5=לE+dݸwyi?_}2x7ϴ^*=e?yD^,xC^!mPʕz.CDNw}N1[ :n,=Ҩů8R>csl^4MQYi-@InfT˰!q<#&dkh鵘S^-nW"}6ls@ޅU=}Qފ|By|rG}}(g}0^ר' ׶J-˻=d#Gnjb4$^qqNҌvw8ǥ;0ɀp@8j(5|N> /H3Lj]ܯ$2c\9}t#ӗ;'?&w@7E?ӿv6b3S%uqBC5բh*r'hȆtNwmǧa9`XvɠNV{G}OPI{55Vk&7)'+m'aه6Bv#jr2>Z8V;Ѕv޼֮ݩC%~6,|"5 B_ Wuti2kcAp U:e.Z4 m&\%#ЫlE+ڟw8q!޼h[aNjq`@]\+Ԣm_S؊>q橣_%};js?f;柌{1c>0ywǼ<1?\z4뿫+` ijkWۆh_0&\Xy Dcw*_J,vC 2ؙMh:4BݞjsV*|"_B\| b05m>߆cCzx9^sFy5K=x ^4B[_upϤc/EG̬mr}4*F"3mȑM^ ƅFy!,d&yדI/9 }7[1cnsR<_ǗnX21f v*B]:LH%;SA%Oٛe8R| |`oD*H{UO 8RNecU˳'߮iV6iMQE&n{ϑ2v2EW{O1V dF'ꪅ_cx4A%dحn1`_treesheets-1.0.2/TS/examples/tutorial-fr.cts000066400000000000000000000572071352107072600210320ustar00rootroot00000000000000TSFFIPNG  IHDRosBITO IDATxy|U !!$!m[$\+Ɋ n]DAYT.Y D% 9g8= gLwW^zф AAqA j|Ao5> A[@ -GP#x A j|Ao5> A[hWz@AT3y8%?CA[PdJƐ7'iAAS|0:At: ނ?sM}o ❸B -AA?> #x"GAԃ~|AO zЏ ≠AQA<# A?> '~|AD=GDЏ  H#3%pR3~?LYP{Є'*9Aq'Ng#x GD\NG0Ax|AD=GD\N9 .BK4|)aAt< X²,va@3LS ?vx"\w]Qb)++rʥK*++ܹSYYIQ`ٳgϞ=CCC9QA< 5~%0Ӿ,]d4M1mڴ!7e).e/\;拉֊uTf_zرc_{HpG}~|r pDz,>Z8#.Խ >z 9񡁁l6ݻ7''g .9%zx'fBK5lFn+WOQ˲ LI$@,Z~aHH*}x|S믿FGG߿`044M;i`0۷'eFIXB^2L ־g-**8d2I4M,rq\aWzOd0;6uT,*}ěqw0<>|ر=z///9t f3q֭0a AСx-LJ>lٲ~ؑ<׿ԩSa}AL2uTK8h К[ ,_l6,k<`azeh^~&%%}Rљ&))))++o :pwC5jT?ʒj|P >D X.\ΆZB oVbb"eVbA4=z!CfeeY ^>Bܝ(dy^f_eUUZ8;;ƀ [:8]Zl+|KX ׯ߽{x[w'8 B^CB7cT܁ѣ_~ep4 P lܸ1//ի}xxxBB̙3TvdKh nI {xVx<2(O#*yoܸqqJ,+DMy>000%%%&&su}cT65ҠbGUu8* gPKZۏiSlU'dmiSeeT?`XWX8;nܸӧOJT &/^XYYYYYY__ݿe˖AAA]t Rt~`?|0,9u90mڴԩSv{gMHy 0v.]ȿ5;;IK({LG^pAOlݺSO=` ϝ;۷ڵkvܢEǷhтLg%(`gZδ,%N}}=!瞣$Y~ĒG366Vcmv}ѝ;w0U!ϤI4\?9sE</+J^~zy˩[Aprx5ѨDŽpeȗ+rB$|1Bw^}b8qȑ0+|׮]] ~딥~|ZÊ)t҂ CݮNؕPyӖD!2(UVD\(E9sljX&*TMӐVǦB,߃ j|G!+r6h_fҥK)8N>vQ`eԩSћ6mi<oJ@B?, :thO;/зO:U^^n7a۴iҥKa~o`OB C#q7oޜ1cƕ+WOB<]SL CYYٔ)S@>]ͥ)JL!Rw!e7n_~Ŋ&ѣ5<1-ƴ0DE#Q3ٽ3]HK:߅ ֮;-AO4)//`01U~|G(9J&ٳgfFxY{{NQq!\G;BШ\BbYvժU{8qh(Au:TMMF4:^~_QQa')ȳz꺺:9>>>mڴ~(((۸f`8yŋ7n?.*iuuu*a,//ZDŽ<###77wذa}vpC߮< 0Lxx8v%N]?!cbbrssm*ZOSL={6! i5գGѣGL&gLzҶ'`01d扭Zܹ㏥&8u~Ν&F.L[B,˂ȉܕ4LGNիWqrs̡읾*G!ʂ53tEA]"p'ORsN=8Frڴi(δ|PEtm>BGgnܸ(`]D4Mhy!+++aku}A,\Х8B1Uq7(Sf04o/̄alPP[޼y3!b_81@ǣK}7o޼crx>}ƍڗpCBBq$O p=+^"֭[LL !D:%"@7۱cG7nxm05]SdrSAPTxDZ:~_WW'Rae*-rrrIŋ3d2Y']p,>o>bɵpԩ9se)C\sk XdVrǏ9r$OGCx|޶m۞={ڭ~-hz^tc='AoF~fϞ}Y@\G>q;c^Wm|M grIΝ;TzL5k>dOQlݺ522g=wѿhn?ei؏# Tc~|MP:! 5B{4k,333xe"4MCΝ;###'wHSB̞da ΆJ ](]/?Ʀr "((/| Q&@,X뗘XRRx Zlb%,Ϝ9ҥK]QYCsmdPǁfy~ܸqǏW(Uy{_\zuԨQoXD]^)vitT<=%05>|OIJ|Ǐ?#5M"h48qbɒ%ҽ݄???{A֒VR#>>>F @kˣ :k֬9+ J>y !+W;wr>IE4ݬY3(:Ք FQESzwY6U۷/22r֬YYwBb^\Iխ[lٲvZX%m'Nݻ7!ܳ˭[-–-[ґ]zGBFcXCk֬7oޖ-[>_~vQ`0l߾]K](v<i7B0gΜ:th}}7D&,YN8Q^^<o8s޽{?s)))v)w۶m[ZZڐ!Cf͚K0lxέ#??~֬YvQ,[VVvѢ"J!OtCϟ?_D,;Az!???4A}>%CWZm6Deo>tP)bޮ]ym޼yΝ?e1!>k׮oTw۶mS3 'ɴvڈ]Z0Mpܚrkkk5vcx~kZ,]tѢE|ƍϝ;GH.xWXѯ_PQQQQQ%ׯ_yvB_U'OƧn;v,wu֥Ξ={na(/b,' ĉۨ,^ C,y@pJaF#f3N<޿q]Wzfy…Q!0 u qT|}}u@/`%ٳ'8V% yΝ?0::zĈ{-g8+ ȴ'4iX7o^jj*Խ^:Xҫ:."L6Xo4=ꫯBA8ʕ+[n8@QEh|ӽTqhTÆ ;rȣ> d|ѣO{,w@ܸ*K%+א{nΝ@ ҫJKKǏǷ`0 xNpbWG e7ocK2ڣ3B4qH]nݬYw`l޻w/yC5B9&8eYѨ^xMgc-xɓ'>|x„ :Ax9sf^^D|(y(~~~ BBl^j_|a !,VWWoݺqy|}}SSS d#U<ϷlL'xbٲe\CylĀ-n[ gϞ={} JrBٴiEQK,]Ae˖ #@(ƚWxw'w2tЁR[)Dlc Ì9rȑ/^ܹsgZZD(1baY]]3Ϝ?>00 Ps .::Zڟ5Z7,,8_ۨ(osn59j sϞ=%88֭[DϹU!XzzH«M9BCܮ]֥H2?ӷo_RXPEE'|2tPv9zjMـjX_kfsǎK;氨/\RyPUU%`7O>CAҲ2OqFjjj4>T o߆XMh`Z'N2dT799%G?.PE̙3eɴ45:Gȣ*q5*k4~ʞ骪*OiXDU#0Λ0a±cRRRn~_TTR q#F a;}4jБn[ؕzٕEQ0KHHطo_PP4:333Fj<͘1,"Ν;IXr(z}}aÒ58!`#'N46Џ еkS\e[n ;NaԂ8RSSQ҉!_7ҝ]:ZLbTҴi({8nݪW5o"")))YvmΝ!N)}G8pʕ+ 5d^F_i0jԨ^zD@=*v̂0`%KFsRRRXuꤤ$iСC!<HfYԩS cIxN8BC:9n5 7..NZgپ}^Z֭8p%K]]d21 |'O 0&b?w# %9vXO(R8<|wynzBݻ @hdF(MZڨKNN8p סpL&Sll?OxAA-@_bENN#q°#Pܪ9rӧkTh@.]ӧo^?nz۶maaa.\`uT, N6Mg?w}_9ɩr 4]WWaÆ͛7?<@HHLwܹy޽{ϟ?OYvEHŠ(?11)DjƌxG~ePIDAT|||6lnDiӦ\Tu0eúVPP>xiW!VG;Cp| ,.]ɇ~;iO`ߢE hb_uFF RSSSXXXXXhRu}O>񡤦\Nrw5вMq$O`gee~z ܋Edn<2;:\aު#h&iȑ}^V[-uMŝe˖]L&YO&UdܪU?ǧg0WtKJGm!OO(0dN>> \ѬYFw w BBB>l6K"Ē%:fLjy>}*hz%<&CS56J 6g%,G;sT~CStu)Lg͚1ʒYE2(#m9f-ZXfMBB`{jo!6w^f0t<Ւ"i0dn(jxyDDD^^8@YXd4&k׮iiile$㇄L&;:=dʒB!_FѮ];ȇgs|J {Vu*fݻw?z {+WX?2];vҥeBx-ZBxTnuuuJ~Ltee+ڪ8E*Ryyj ڴioڴҥKu JLkn̙- rVݻq6>s[[988x#1_}RGb /9b.ԩ%q@e˖EFFJUݛ]Ӄ?7o7wh()=a_?|CdF#e}}}F9aȮΤk$**JBwI z뭷4ܭCln Xlls׷ƌC6yW^ձ­`\MC<;w[NÔ?ȑ[?~:__߀zhԩ%S hR+=ɓƹ^t XIE____\\?^~F,mB׮]۷oo4Oⶵy sGQ_WTTTTTOt-vGS{שS~+r<߫pԂionVRَkpڲ8[+؏Fm۶m*ފ[I[iJ~㊚5;% & `o߾O>"8 aF}d~7O0HMuA\AHa c9Xޥoؤ;Mt]$# 0A(x3ցa-xh<> A`~|AO# A?> '~|AD=NDgS 3~Ϟ)tβ=' ԩ, GDЏ A<GAԃ~|AO zЏ ≠AQA<# A?> '~|AD=GDЏ  AAA?> #x"PNs x&s- (A& j|Ao5> A[@ -GP#x A j|Ao5> A[?̫<[IENDB`IPNG  IHDRasBIT|d7IDAT8c`0A_k#_-&50$+튲`BJQm*): ]DfqEJ1q8D]qO&j~tPFCc1+CDlo^D? "up53 ^&]p/ܬrPK&Γ^.m͟__?x>d;>SA!'ښ:FtWN ]i!F! << /1lrO0K={MJW ǟ~J)%VGۈx)3a k־O9Koed8-m9[j+3a V9KoЃڵ**?o h W ^cX̾/{-dd`k%MW c:ۛ߯Y,{W 33000s篠/R_nzŰs/pE<ڰ.p$Lo[~}!qu4c}6 _nIUS ;7)$IENDB`IPNG  IHDRasBIT|dIDAT8mK+W?ޙdbBP? ŮƕnBm)*U$E̘>AMԅFiNv|gq +f2ST̲,c!quuZNOOKyi777fqqW9`0|fc\mZJ)X[[T*qpp{fffRA|i dѠV"Nh RI&y399}xx0RI*F8mxǻF,,,|8v{`V"issr 롵~O;2rY!*7V:`!vF(gkk uZT*d2aa8}Z8(͓&s֬Tn@eҢ̫ *U0U'8ITЊT0 ۳Kn`0Vtc'xeߗPy?4y*` κ@0 @VyL 󬟇J0 "?a*w$UL\̪"Xt cTSx H\j≶xН v0^]laA f`dqOA / nJbcTi6B} {,R jQ-p4Ur*p^ZF8K/KGi΅EG; *3]ae#t+Pŏe>l( oVї[]wY[@dfLUe]07biRbP/h&N39X1f[$l3%Б\UrF8p#5=kZ̎â0C@lmm%xA:'0uyx 8%HO[`4ߏyjwS"\":ƭ"j0'8gyTsikP !8ha\EנqA7 .s5sP75(Ώ3tj- R`h9n'H0DLCS]P"ON[2)90hI)rla\R3\J#Д`W` )`쬥i$[fH 3PK/ DY5 $O Q!@Q3]Yc88gAx &Xm)L]=H +e~?lk A̡IdZYȈLb"pU:r u9nzC[;x;ګ{ ,_)$|X`Ng (BxpŬ@gcR7kpОhP/<4dEC| =Ӭ1#?$Udb#LOF `,WDqIg ӡ&^*VٵѢOayg> eH26_uyЀ % FFe=seS)wW;8;* 2zzK bLU_34 Wv*|D}Lrax. <zC"P#z(ud&LJu!A#wX/ON4$Ea%5 ΰ. }}ͬyeQ\ ]XIЮQD}zAselfQ`0*d<" oYpB D`gh|8LfyYwymhմ)#Z_{&\&zdj9@ᲙATE 0" 4#[1 v80 @kcБ[n=v.~؄#~Ys/h\ຮM&*723Gİ'trNlC0QI#`f[F"**!md!by_ZBXNM s ]]ymb_2lb8A($sYUrBFo[1OX*z0<~D,wǛ|ŰRwό̉[4gv7IMm2:`AIc5FL@%P7\wx 3Ah6Ki}l'!}EJj]; iҹ˷5#Yg>F ^M[v1CuqXGL|@}3HsYn69Ұ>6A' d- ޠmsZz4Oٝ3,/#ub"o./+ð~㘅BVBA 3^_?[p~=._ὟY$ӅDhc1!(0n0HԺ˝%,}༌<ě m !XRPI*:+K25iLZXEqS@p,706PW9jsXg:t8>&/LBrUMd d8 eUj$YI* ǗIa&}xQ&"5wK!0(A11)5v׾Wjz-x ":WNscsLboɎ (3Y<{dEb!<\wz=UoΠgNTm1R(ɽ 9b J {~jVFCڮ2 ̬aB{%XT5*Y 8*>J`Wk<{ק.26ts<󼐚#WAզ 2 H\!RLAaIZdwׅlӉ.oI.(!ZGl:q3EC1I `hf:S~4&YZx@6qIPb(l (Əj^fw.gS"փ8.} _5KEOur~8JΎzi*d1;S[&%<t$y:ѭfhU}cq+h-dy S<`Nbj#+};_BsEJ 2 z^?jy(>~oJ,<{Hgi5 pv_߸61ֺ>raUIǨ0fm59 Z!J$IAg =ޛEC']9Cypɨ Z=j6vuohl[?ց8%)zTxIRG1 n/H*sm0ZAoԅ׳aㄸD,9` SoN咑:DhpSN12 JMԓNd^j?gP2u-kVGyऌkƣ7Pa=ݙN<>Le6s<3oS?z;h\坩F>7/BK o)\<&~J!!tχaecavD:g.(^Z⯜jK}bs eKԒ̠ԛ)ԦG8bv'_vKcr<>$u_ԈFCur@E\lDy(y^'̯}'w]E<5^n dFN5k#N ףʢEWp͞v)ˁH0K.[&΅TȞAgBqL۫$!}Եh6dxvϟ}Erxo!h!T7mݾ(kf 8S :<[vz=b`RBTP0{߳L}/Ѽ{{D*njUzXsMКI^nnŽ.3:_WMQ&'aPeu xg'p E(hTKr6Gҙ)9 9Dv|M;qv-ͱ[)HsȫYV o@-Xm28YsC/mGs9D՞ E݃m9w)Xt `9m ܪ VA+GXFi6t3є6c `2nL3@+ga\% n Ɓꛆ'`rN[fl(3Hӄ_sM?|SVMgY`0`4 "ə\/`.jOdqs =HzFକLipYo~lS芧Gz#1r㽜8]gndWOWDFBo>ι42ZG7_]*i/o4Ti* hvY]ڨb"Y5#=`+ߌpeIN:W?k9R&NN-rL:6o-5P 2Ou+Ĩ? 2YO /òBD/*>Fn l9FVYmE E:[6H9{&ra54߆ ףTNR2A+=f[5DdgKT8n]+bK4/naFFt4OG9'y1du5wm Iu#9mFFH޺FgCz0 a 2\)^y?~[V7qjubCFkh{wOk9>; D\i-X+6{\Na'@<c[RJ4aIIKh4֭=)="/}I&ԘR^[j=cil `WC)Na^:.E" ȰnU^i4 D!eXPuUO a%tk4yJm3z9Do2#مAn< ?}iY4i[D?vq -3p )q*03U= :@A>mDW꫽~9Fga+U^SFqt*@>I1gQ*0{6+B.Q (P>Y:\q[뿢ۄ<{xժdgTM$ ^ܯ!hU9?T?+ke+\,/] ACŊ35qS4@{Gvnꫵo"r@x};q h%&tlR+!E%i@sȃ2StS(LG9p/]NO|*5-^ȥPmq #樐lрEEn49O65y>1<v |Z-?zE%Kι N d|i/35r}^_ڲ_Lzh Z(k.d9HF玄}=a@5ǜ!BݳOQyL=I! ~,Vula*^w:6 lz?7mX-푆̛,s*1>pZ8LT^_S~/QܯZ ^'8z/\˰@ЙѰ<~bW|s{qWMU  cyߌ|@Ȫ/|:Zcw^lγ~{_w j"+v=8OĉȺ?۔8˛*IQ*WV4:,G]A| el#[`M1mi7\;|By0& K3Nc~93q8HD jzY%;f#ȍ,oT{kg>SO+99_7&<5TpZy]֡_NgB݁mJz]Y%ln>~e? 2s."s;QJo 5_佯ډۛ)΄^+k粱s\ˡ9n;Whz'nl8J<-.%UY[v=\{IԅF =rkt-L{Ǿؤrڻ^Q@_ۏNʘ܁_d} Rm㏟lZ `;'FF3ٝE xIDv&Qv :3R N W^Nf7iDH4׵e~Lk(GKmFpbo#U+)5=)狺at-u|{dܧCj`G=k"6f +Z( ȝ_㩜A1N ×<\OώÏoBg+o|\>;W#wZẉcc>iލ3kQ$[6l: ""o3w-u6q]6`5+Vǂ]~C;} Sibڼ6\/42O¢vv `\ġ.qr_"(lTn/dǂ,M۱0|c$;uvQ>CX$nkL8^%Z6 V\$4IlSUZtz[Js-V \ĠX=@f^tob' u]ܥ"ITte,<]?SCIbawY/P9O lW8O:#|̈́x0 8U gVxNʧEv4lMuV &Ta_;wed16 0.u(d!+7?8FNsW  `1aB:&;טz zQfht}s.%uRqΚBvScrvD:Ne39DHDs{2)]Mċkqo6:_a9AX_#)L+)ءʥ4_lJLJB7 `IzFij5Qtw4nьN>uS([īH^I34{@,MjN!$*CsNTnǻ3UH;rŊG}Kx:]qhba˩}q)OsF" eZoe^[-g4Rh! u6ҥrR $z00/ؗN F`9iQY*}ޕxzxlpº<^Ɔn ^Bn^{s HybTJ:o8D {?v[%|TgoTD8Kkg_>EӈtX>OtX\{]k!1o8䲱]5 =$?f;棣6Y4nw/4ϳ󠌹d s}=]O0ˉ*775+7@$~IqyזCd y1{Xpڳ>pP`W:5)]2tŻ(ҭyoQ)2{Pt"7]\mIV#qIx wvWNGd}L8:F77>sYSız&="y֠*fm>2tä[_Ib(pb 烍cRëWf=Gӑ~F57| x9bT}4i,_ cpj T)yF6_FO>м 7`72tLs1G/+nˊ7s^Unje5B21f`mQTdJDɀW-ڥB; ьKy$^0p )E s(4a!')&`/ql6UL_FQ#_zQ5*r9fTsǿkj5y;<^(AV/=w]>i4Q͆{WZvPURWAsü'd6"TkEcX[?,9~<>K! g82q $_ en9n< axnz"+[]5.ߢxjz5:1BpSivNƀْ=>hl*9?gkF r!!JɲWǪjP!C ΤD7:bgB=Fz/ -%ڃ^U >&a~>hw9:4\ѪW|eY;*nVyJHS:3tGn҇N!]"`}Y);>dsFi8'gcXdvzOTRS j5,l+_ircEy%&[A4Ph otL&K8 e=lBjXlJXR21 R sMDs=?lx"kmL;VLNor%tAA(OѾAA$tॎ󣅿!|+ì,t~ 6{QӢ{kɮ! ^c(۔9r,g9pM%;!h< ʳ0@-2:Le=om $n<;IRE6ZB@d|T-r m3 H TP0AQ9:fDѮk7 rP\fg@X{`tV"js]dAupA8 :.3-~X[SaK(r|(sxa`B̓|:B%nh,gA~a )AK84<$@( qƱV`5D fThFVGwS(p(h<R . ~r3dDZ{@t<=vZ]9@T^ +`0  pp0P194q$ Z_kx`@3wCʩew$:`pbyQ~xۭGǏx3jXۈ%(ʌOuP M `}*wm򪮿 :zDa<*t]:H+ٍv%$"+ͻ]K.N_q)l" <, ˲h CcB+Oo{b~a.rM#Ƞ_šSyA $ '2!mCt>>-]} gՠ$w9^gTL;u!6!߱풟OB=~\/n4f^Iw䄪`鑝.i<3BԷTɣND}][=eTtIթ!ooLd"ȸ翊>iqIENDB`I?PNG  IHDRosBITO IDATxy|U !!$!m[$\+Ɋ n]DAYT.Y D% 9g8= gLwW^zф AAqA j|Ao5> A[@ -GP#x A j|Ao5> A[hWz@AT3y8%?CA[PdJƐ7'iAAS|0:At: ނ?sM}o ❸B -AA?> #x"GAԃ~|AO zЏ ≠AQA<# A?> '~|AD=GDЏ  H#3%pR3~?LYP{Є'*9Aq'Ng#x GD\NG0Ax|AD=GD\N9 .BK4|)aAt< X²,va@3LS ?vx"\w]Qb)++rʥK*++ܹSYYIQ`ٳgϞ=CCC9QA< 5~%0Ӿ,]d4M1mڴ!7e).e/\;拉֊uTf_zرc_{HpG}~|r pDz,>Z8#.Խ >z 9񡁁l6ݻ7''g .9%zx'fBK5lFn+WOQ˲ LI$@,Z~aHH*}x|S믿FGG߿`044M;i`0۷'eFIXB^2L ־g-**8d2I4M,rq\aWzOd0;6uT,*}ěqw0<>|ر=z///9t f3q֭0a AСx-LJ>lٲ~ؑ<׿ԩSa}AL2uTK8h К[ ,_l6,k<`azeh^~&%%}Rљ&))))++o :pwC5jT?ʒj|P >D X.\ΆZB oVbb"eVbA4=z!CfeeY ^>Bܝ(dy^f_eUUZ8;;ƀ [:8]Zl+|KX ׯ߽{x[w'8 B^CB7cT܁ѣ_~ep4 P lܸ1//ի}xxxBB̙3TvdKh nI {xVx<2(O#*yoܸqqJ,+DMy>000%%%&&su}cT65ҠbGUu8* gPKZۏiSlU'dmiSeeT?`XWX8;nܸӧOJT &/^XYYYYYY__ݿe˖AAA]t Rt~`?|0,9u90mڴԩSv{gMHy 0v.]ȿ5;;IK({LG^pAOlݺSO=` ϝ;۷ڵkvܢEǷhтLg%(`gZδ,%N}}=!瞣$Y~ĒG366Vcmv}ѝ;w0U!ϤI4\?9sE</+J^~zy˩[Aprx5ѨDŽpeȗ+rB$|1Bw^}b8qȑ0+|׮]] ~딥~|ZÊ)t҂ CݮNؕPyӖD!2(UVD\(E9sljX&*TMӐVǦB,߃ j|G!+r6h_fҥK)8N>vQ`eԩSћ6mi<oJ@B?, :thO;/зO:U^^n7a۴iҥKa~o`OB C#q7oޜ1cƕ+WOB<]SL CYYٔ)S@>]ͥ)JL!Rw!e7n_~Ŋ&ѣ5<1-ƴ0DE#Q3ٽ3]HK:߅ ֮;-AO4)//`01U~|G(9J&ٳgfFxY{{NQq!\G;BШ\BbYvժU{8qh(Au:TMMF4:^~_QQa')ȳz꺺:9>>>mڴ~(((۸f`8yŋ7n?.*iuuu*a,//ZDŽ<###77wذa}vpC߮< 0Lxx8v%N]?!cbbrssm*ZOSL={6! i5գGѣGL&gLzҶ'`01d扭Zܹ㏥&8u~Ν&F.L[B,˂ȉܕ4LGNիWqrs̡읾*G!ʂ53tEA]"p'ORsN=8Frڴi(δ|PEtm>BGgnܸ(`]D4Mhy!+++aku}A,\Х8B1Uq7(Sf04o/̄alPP[޼y3!b_81@ǣK}7o޼crx>}ƍڗpCBBq$O p=+^"֭[LL !D:%"@7۱cG7nxm05]SdrSAPTxDZ:~_WW'Rae*-rrrIŋ3d2Y']p,>o>bɵpԩ9se)C\sk XdVrǏ9r$OGCx|޶m۞={ڭ~-hz^tc='AoF~fϞ}Y@\G>q;c^Wm|M grIΝ;TzL5k>dOQlݺ522g=wѿhn?ei؏# Tc~|MP:! 5B{4k,333xe"4MCΝ;###'wHSB̞da ΆJ ](]/?Ʀr "((/| Q&@,X뗘XRRx Zlb%,Ϝ9ҥK]QYCsmdPǁfy~ܸqǏW(Uy{_\zuԨQoXD]^)vitT<=%05>|OIJ|Ǐ?#5M"h48qbɒ%ҽ݄???{A֒VR#>>>F @kˣ :k֬9+ J>y !+W;wr>IE4ݬY3(:Ք FQESzwY6U۷/22r֬YYwBb^\Iխ[lٲvZX%m'Nݻ7!ܳ˭[-–-[ґ]zGBFcXCk֬7oޖ-[>_~vQ`0l߾]K](v<i7B0gΜ:th}}7D&,YN8Q^^<o8s޽{?s)))v)w۶m[ZZڐ!Cf͚K0lxέ#??~֬YvQ,[VVvѢ"J!OtCϟ?_D,;Az!???4A}>%CWZm6Deo>tP)bޮ]ym޼yΝ?e1!>k׮oTw۶mS3 'ɴvڈ]Z0Mpܚrkkk5vcx~kZ,]tѢE|ƍϝ;GH.xWXѯ_PQQQQQ%ׯ_yvB_U'OƧn;v,wu֥Ξ={na(/b,' ĉۨ,^ C,y@pJaF#f3N<޿q]Wzfy…Q!0 u qT|}}u@/`%ٳ'8V% yΝ?0::zĈ{-g8+ ȴ'4iX7o^jj*Խ^:Xҫ:."L6Xo4=ꫯBA8ʕ+[n8@QEh|ӽTqhTÆ ;rȣ> d|ѣO{,w@ܸ*K%+א{nΝ@ ҫJKKǏǷ`0 xNpbWG e7ocK2ڣ3B4qH]nݬYw`l޻w/yC5B9&8eYѨ^xMgc-xɓ'>|x„ :Ax9sf^^D|(y(~~~ BBl^j_|a !,VWWoݺqy|}}SSS d#U<ϷlL'xbٲe\CylĀ-n[ gϞ={} JrBٴiEQK,]Ae˖ #@(ƚWxw'w2tЁR[)Dlc Ì9rȑ/^ܹsgZZD(1baY]]3Ϝ?>00 Ps .::Zڟ5Z7,,8_ۨ(osn59j sϞ=%88֭[DϹU!XzzH«M9BCܮ]֥H2?ӷo_RXPEE'|2tPv9zjMـjX_kfsǎK;氨/\RyPUU%`7O>CAҲ2OqFjjj4>T o߆XMh`Z'N2dT799%G?.PE̙3eɴ45:Gȣ*q5*k4~ʞ骪*OiXDU#0Λ0a±cRRRn~_TTR q#F a;}4jБn[ؕzٕEQ0KHHطo_PP4:333Fj<͘1,"Ν;IXr(z}}aÒ58!`#'N46Џ еkS\e[n ;NaԂ8RSSQ҉!_7ҝ]:ZLbTҴi({8nݪW5o"")))YvmΝ!N)}G8pʕ+ 5d^F_i0jԨ^zD@=*v̂0`%KFsRRRXuꤤ$iСC!<HfYԩS cIxN8BC:9n5 7..NZgپ}^Z֭8p%K]]d21 |'O 0&b?w# %9vXO(R8<|wynzBݻ @hdF(MZڨKNN8p סpL&Sll?OxAA-@_bENN#q°#Pܪ9rӧkTh@.]ӧo^?nz۶maaa.\`uT, N6Mg?w}_9ɩr 4]WWaÆ͛7?<@HHLwܹy޽{ϟ?OYvEHŠ(?11)DjƌxG~ePIDAT|||6lnDiӦ\Tu0eúVPP>xiW!VG;Cp| ,.]ɇ~;iO`ߢE hb_uFF RSSSXXXXXhRu}O>񡤦\Nrw5вMq$O`gee~z ܋Edn<2;:\aު#h&iȑ}^V[-uMŝe˖]L&YO&UdܪU?ǧg0WtKJGm!OO(0dN>> \ѬYFw w BBB>l6K"Ē%:fLjy>}*hz%<&CS56J 6g%,G;sT~CStu)Lg͚1ʒYE2(#m9f-ZXfMBB`{jo!6w^f0t<Ւ"i0dn(jxyDDD^^8@YXd4&k׮iiile$㇄L&;:=dʒB!_FѮ];ȇgs|J {Vu*fݻw?z {+WX?2];vҥeBx-ZBxTnuuuJ~Ltee+ڪ8E*Ryyj ڴioڴҥKu JLkn̙- rVݻq6>s[[988x#1_}RGb /9b.ԩ%q@e˖EFFJUݛ]Ӄ?7o7wh()=a_?|CdF#e}}}F9aȮΤk$**JBwI z뭷4ܭCln Xlls׷ƌC6yW^ձ­`\MC<;w[NÔ?ȑ[?~:__߀zhԩ%S hR+=ɓƹ^t XIE____\\?^~F,mB׮]۷oo4Oⶵy sGQ_WTTTTTOt-vGS{שS~+r<߫pԂionVRَkpڲ8[+؏Fm۶m*ފ[I[iJ~㊚5;% & `o߾O>"8 aF}d~7O0HMuA\AHa c9Xޥoؤ;Mt]$# 0A(x3ցa-xh<> A`~|AO# A?> '~|AD=NDgS 3~Ϟ)tβ=' ԩ, GDЏ A<GAԃ~|AO zЏ ≠AQA<# A?> '~|AD=GDЏ  AAA?> #x"PNs x&s- (A& j|Ao5> A[@ -GP#x A j|Ao5> A[?̫<[IENDB`Dx} dy̬dd Rmv۳/iW-i_#-WvF# Wsֽ$K,LU$8IbB-„*#$W!N9sg[53.|_^m}=3e}Å.w/ݺ @EldJ*/Ãto+F?ʃn8ڢFEEa ֣0IM8GI/㔾}$AKey%A7K"HBm`v ;fQ.4 㨘&&@yO0H>c]7<8䟠o@O@?/{Óz1!:H7ѦB {{ҴtLSل`pfp6l5ƿ5_ sG'X|~wRBmM/FgXAHZqiQYf 7b2 [ilk#9$0RI'FCb.0MUЧ<^F2meyJy#+*CITrȣ!cCՠcsfS7$oZ,ڍ`fA*gA""xHS-t"2l]]dB?TץEsS{N9 ^%ؼFya0%LRV&uqw<3 $P[tXrNo!5]J)X< FPe]IgCJJ9)嶺{3Moz&SzJG~L_iA`4grF ik!v::$襅ڶAF `NnPG(*7GIX06A#Jbz-vT \D4f"a6ـGiu%`uǓp+Da;K +;EL,ZQAaL$63A8q s"D(cJ-a?Pjpe̥.XtiE4PG M@c {#fb~B7 .M\MÚS'O߷~鳸L 0y3:VK[agΞ?1ٖ"n e׌Zraڣ[@o˓LuhMkoxi6BK* qLQ'4 .L4VH`?vףsYM S|,i:Zָ]CDStqINo\9a:%Z*fBme8m#-s$)tդ~HB1A=bRAh4`N:N8" 43|=@aڴ)}ub DjDsBVԦKIpi=x!āh4|6"p\P!ݓ7 cj K7]"?ۿWvoy*_[ K? NvG=83 gDeL̳5fCڤ0a&<o;$Q|!񏖌r+Rg(D)~a*(ŊXh9HF;ӥafAKG@ ~2w=!w̧~4y>9ͭ:t%P.@ ,5hy+`[+ &g}abh \>$ 'Cw VQCڍ;D(=d-[ꥌuUQRDqvZ]>$X*X1[fF/M@Gv;Q&Z7~ij F| bIմm ɭ> @LƣA WQfjFM{%f[v vl:<Sّ`JlИ| X7 D&DkW9h(=Lxi(QNsچ+wM~lE!]/N_֯e)v:2+DH6d$ tC亵C57 Fq3}E^KvOK\9JD%찓0&7*Vx5Dj1,CEKh=(500aE7( [*ФE+l_0ȺQ0[ t>0k}yϧC*i:p'ʮKX`:LFC7#v j"aHĐ>`H<{C1N*ܘZa^<)Off */aȺ OڅV泒l\Ҹϕޗqm5+ g{[LZw-W2sބd)bq)1!4g/^źɔ P{ PԘgw1MiH>7.,g{y#4jGH k`4,5>aM"+Mb-j12ہ$ TH£wG}R_6Xq+ + e vq?}Tmm\\c08*}sHm^Ԗk{x \nX㸍\zPlUs'H-Hat·a[J.첣aMvi-;PJ ==S Y1]6 WG O)" xtZ=i;2k/ֲٓ`LR9}%ׂq\%zLC *p IIjW\1wov;hǵⵯEn+R~Zzc>gEXCF !jDo&BjL {#=&njWETs>Ө>` ׮N @tD 3|*4Sg);= `5!XVInLb4#<#]H)2es3ʀvӽyKjKL"VR*ס8\eB$vRz$/F6]i=K0pC! u,ӥ (T)`Q]18Ϣ;uI^bEhpւPc*ӘmV+xнeJ ZUTFz=ۿ/#1"SB!5$L萅2JA1bGyBAp/{7 :pIg-Ȣ@&HV Z]pC?LaHo|Zc+#b$y1nc|׷B=膃(Bd aw"&ϢdSV$ ؈q?*E~ {ݔ>?E߮#Np> cː˞4JO3fy쏜f4sڮ~E `;X:zit[8 Lz/ͅKYEWLpsr'xᾗ =aO `ѱ1 ;'^pĥFvFo*4] -4dig.*si zpݏ`vÃl$y2b:>3IL@ k^}nj_ @D VZ2Ce"짥#X=CLIAqq) ovG8%o8N*l{YK~Ʈ5I*꒔Mz]0Kg.q-zNO@/yҎZ= }&ۼV](bdQT>nWܭDt:`aҢRBauHf#X{%E u4Do̲]FuZ㔌ys{8_ߙN:{}[E9KW6^q^o^,)S&& b3nWuOcH3iݫ]!%̢X_=b;đ0&mpdIp+駒ہEΨs~ *֙WN>Gyp1,;0sT5:u9qK>vsr܏F/zoeHY3gʼnE|Q>B>H0GYgɴY e6d)'9ْ]Չ9rLu٤>6s>lt \尛#F";ۏNsPLl6Q{zԎ3>u c;=O6 Uߜk>0=oL;w]\OrQO5]ӣ,Q|Ʀ$KhRzZiPZ'[9Pۘ*jȘT83s^urŹZ VTIg'DˊVK%nrGҘ8*MP]҅C"w$s-4:iPrZ'VщWM` tHj,e0ZIHvpN)Dt8*HqT2',{0ι}>LkE]8ұkM ?^5-o'm됄eJ,VpX jNtT'|怜E쨥F驌.:z04J% F%^>iP/UچDt8b u' .:+dmýOT D>jv٢薿׷V @3\̆u 4\d-9ՕSan(J-c.Z5,-ZPLkpӔGEJ?'=Y9T@/:s;;sx=uwgnhq9^U)SR4dzU­博A OJWG6 LOکzpriO\Άs7nC{y8]ݯFNgOѱS'K3$.](}#G& jUvX?#q9x}/){m%q.WmT흨IXt{[tгe6\~c \h'ɉ_SXfEb r(&`5KЭIre{,93ykʟXX<,շZǕHHo9úCX 8pzD5 uq2@m0N}6kVm¬BTnZ4˪qN ^VPT}u[7(Q L}K iL ;̝#pS/{alZ$[]{[6Eb,dݥ\1xKt7nU~V@y 0`PAXS? ޺WۅGOR)-$ uJXz[؛D둊α3@Ekm2VRT8r,FPV-bR]YdhϬaUڪyMf<*\l i .Wss~n1 @mŌ`HA#Xj,[ r {܊gvm**G8y]*uOd\[6LP0#$5R  |J5mT $N9~nWEu QioUO%<=jmA?={EaX݁?5{>܌;[pGu݈gdSxvXZBj}:iȉҦ I,gjq (%WͦשXWR%25qܞ$LN"W:%a#lhi`-)6(wn1PQs<8wGґq`aі7wڌc1G1=;s;ib9"khzV 9DXjt9@3x ɝ.wG%7/PeW?(w))n>ki켜W*)(Swٙ97N4ꪒR$so}jP;1ixvGt,u2rlwrrLF1tgy2Ke)w̳ZcRMD447 q ;؋-3+&5=Hޒ<C)A4HXD9aDz: =mʮ"AS41$՞;՚ʮ>dDyr߻g<.Jz6>c%k9m:e$7x%y_zaY5>pg ſO։)YwnL;A!^٥l'd.M56z{A;%n-rly |8ܪ-|36ɀEz=5S1ۣ n_ot.fƦ8<-.5|;حb[-#$e !EmX;'*YZ;Cw@` ~UoG[tgT1t.P`/Bwu%6@lۆZ! *K4ɩ;iEpCs_e_gDp;)!Uҝg4B95fl؊ĞF؄4't=LϾQ'QɁNnvk6GlwZlFG"h `YǕyq"" \YFU1:D6;V¼g\ !xtsک]pZnZ 1-j(i.5.ZȜ \.ja@CN=JJ-ie/)@ lub Hu,&Ez;̕}h::uIv$a; hZoNwLk' ly-wͯUZϧ^J.ouOX kuԻs/sqQ1:|YpY]Af^9v,b 2)xE1x^jR s,ڷR~hDyT'_Dj?鷛Oi3pZV~MV)In+85(mnԤ"R*27Mn_f}N>Eda<"8}YCy]ٶ)W˂[vok P6WPFIԺJQ­ݒWkطA +_BolwHc{{-Z1FvWfw_=w_c\K4{?=r)^%%#QdO)HpQ0G4 є`sBRO"Ĩr0}eH晼ڹ*/̾-wx(!Hگfpyωq*5$`AS1PL6X̀hTE$2-׽"΃ `_j};b.{Û~28V}HkΜxNǎg7<έ9|4s0|zɮ G'ۼ =vwL:7K^ k91x&c6g+h 1 :sĴq1&0 %lk7;z,E8-8S MNhCBySl!%gAmnq5ڍ-fwLw11ouǴgӚ|AAwz=mޠGf fy٣:Q깣=* Q./LF]wۧCsAB'/?ەn3QiPt_[8o~E46omٴoO8Svk.;#;i)ix<!7'ئN:;ůSlA /{M !ϏygFZU' dilkt;'nm9.kSa5=7ĥ33[Z]CۈIS=:F}c|f?H%|eDI7)դXbetreesheets-1.0.2/TS/examples/unicode_test.cts000066400000000000000000000357301352107072600212440ustar00rootroot00000000000000TSFF Dxkpcy~؊ssJ\y\8%˶%]iZq{N..rA7  oĕ`7py+^!dqX}0R/T>ygw%)%Xzg 꿪[WyO_w?ko_q-_TߥK5>??3?Ji>Ϳuڵ}?OW)>xYN};7wvwvwv;Ώ0g-8|Vcvoؽn mn>>Svv#|y];M /kUj; <;Oǫ]w pK}GX0|SfhL;O|/\}?u?Oz3^N,0{_ʅ9^]ןɗ!M~.}mwb' 2sA7lwkď}޽Rs?ʿ+(P'ֻT7+>ԃʝ~I9o_s#. npv;w?ovfV{Gs;w77X"?> s{/Eww|8pCMlȿ g>a8a0V_xP[g a4|837g7Rܗ#?)~/W__=;2@Ç!ˇ G0θ/ZsAoɟ8S~%7N,qP}˜Ѹ{'tJ{w0jyҞ٩'f B0;]V?^qO_?W7/mǸu:x|xi_begb{׮%?:?7}>{M;ϟ}ӯ}ξgKdS'~[ϽcYS [}ݽoo:[|Կ'?9pc/77K/__ѯ>U}A_ԯ%W[zC_ow?^.-kE}C.뺡o[zOg/_o޸|t>={glRrz׳)=+ٴlFfge=YUjzV׳9=kټ=ֳ'z3[Zz3G\=[гZb@A-֢ňhj1!-ԢŘoi8ŸGbR)-i1ŌhZihiqR9-8łXgXbY-VXb]sZlhq^DM-ZliEG c_~pi@K-RXK"ZԒtSKbZZ҈ZRBKI-42ZZji\KZZzZzI-崔Ҕ ZRQK3ZRIKe-UTRMKu-iy-=-5dk-ZZS-h9堖CZkrD˃Z6ojrL˷|[Zr\ˣZNh9唖ǴrFw|WY-kyB|_Pˏ<圖ZrAZ.jyF˳Z.i劖Zi9-7GzR)i=hֳZihi}R9>2]G]x.~bUFu%ӵzU5׵>XOԺZwj}AO16i# Cm jFTCڸ K1mmm kcDqmj#6RFZm]md1 m}m_j i;mCmjvTCھmK1mGNj;%m!YmO~GڞvNymOiim=Ym]vEUm״]]xYŇ;NX;v"ԎvsS;vniv3vFNR;)i'j'qLhvkvjv&N^;S)hgZ;EhgV;%픵SNU;5siy<ΓޫvZi=ە Sh7ݠvC knD5-ƴ{K;ݸvGnR)i7݌vhvڝ=>#Nj7ݼv[vڝvK-kݪvkڭkwN kvh][-hvg{wz'~߯ޟ ?'zmm}W/^@{A텴֞gj/!Ԟni7F^R{)ewG{w޸&wO{P{7ڛ^A{+joF{+iګi957{l֞=W{ {~HaڏhPQao^?G~R)i?hih'~^S/hZEhV%헵_~U5׵?XOԾ_SЋ^"a֋Ũ^$")Ӌ^d^Ջ^ń^Ӌz@/#ԋ^bJ/ z1EEM/z1s|'|x`&uA^S:(`ZE`V%uPAU5u0uXOtԁtaP!uh0A:pH7uh0&Ő_Eɨ:LpR9u8ÂuXguXaYVuXa]s:lp^uDM:d:\S (B: QDG:2uѐnQLGtt[G:Q\G:J(tQFGwttWGYhBGtt_GtPGt4:QAG:*hFG胠>郰>0AD SDywt)r2N: $C' IT'C:K'1m dD'q$NR:IZ']du2 }e @ @ @ @ λ9{ q2,C\ qm&U[]fe6YfeWf{e6WfkeVfSe6Uf2̐n4i,2)lPf;e6{)2 0_*Ua 3UDMbp*p ׫p * µ+\µ+\R?_WDUU1Ve*U*UtUU1W\sUU1W\@ɫ<Xbc**6جrƪ<<bcʱB] q*UW!B\*8b*خbl5j85j`UW 5j0Ԙ|51OyjlL5f1SjT-K}/w::huxuƫ:h:#:h:h:Xuh:`Yb:uh:h:h:>9`Cls6\sp͡}9sqiP?\s5\s5\sg|K}wyo0`o0` @~! `o o0xo o P 7k@~ 7h @6l@~ 7@~g h h h g,`yamyᚇk>l6,2,Hgygygԫ@:?}7LMfjr(xo2[ٚ{d&5q}M6M7߄&o⿉&o c&o⿉&M07an܄ s&MvdMvdMvdMvdMvdMvlv`^^^^^^^^^^^^^^^^^^^^^^^^^^;S*ls6is6is6is6is6i=~SW/*OF8q0`#F8q0`#:0è:0è:`8pN p8'8pN p8'8pN p8'8pN p8'8mBshcJ¸ 0. ?_rgT$9'S@I̐YR"eR!UR#u2Gd<&OHؤE!.Y OHIAQ I"c$M2KdL{\!Bp +W\!Bp +W\!Bp +>ùf/]Kw`; vp DI,#m0z '0z '0z '0z '0z '0z '0z '0z '0z '0z 0k0k0k0kv~~~~~~~~~~~~~~~~~~~~~~~~~~VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX#F`y{7>[_r9EhM x4& 2{كgA|gA|u&?{KgECQP;ECQP;ECQP;ӯWK_rq?Rءva~ءva~ءva~ءva~ءvK4P:ԹuCP:ԹuCP:ԹuCP:ԹuCP:ԹuCP:ԹuCP:ԹuCP:ԹuCP:ԹuCP:ԹuCP:ԹuCP:ԹuCP:ԹuCP:ԹuCPN7{7W\} GK#jZ֡uijZ֡u n[֡un[֡un[֡un[֡un[֡un[֡un[֡un[֡un[֡un#oA;6a6DLD0DLD0DLD0DLD0DLD0DLD0DLD0DLD0DLD0DLD0DLD0 &11AN '|> r9ნAN '|> ? ? ? ? ? ? ?`-ޏ/ճowYoPDŽڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄڄd&[7ٺMnuld&[7ٺMnuld&[7ٺMn(QG? (QG? (QG? (QG? (QG? (QG? (QG? (QG? V .]r㚾:8oN *-TZPiBJ *-TZPiBJ *-TZPiBJ *-TZPiBJ *-TZPiBJ *-TZPiBJ *-TZPiBJ *-TZPiqb8J1c? 1c? 1c? 1c? 1c? 1c? 1c? 9Ǧ׹\~ 5"-jO? [0Άq6a lg80Άq6a lg80Άq6a lg80Άq6a lg80Άq6a O}ُ3V+_|P:#@9#@9#@ .]һo]NՋ{fmF@8pF8#a0q8pF8#a0q8pF8#a#<Fx8pC:u8qPǡC:j6j6j6j6j6j6j6j6j6j6j68q?8q?8q?8qQGQGQGQGQGQGQGQGwCN|KM#  $@H!B  $@H!B  $@H!B  $@H!B&Xa&Xa&Xa&Xa&Xa&Xa&Xa&Xa&Xa&Xa&Xa&Xa&Xa&Xa&Xa&Xa&OŸ? $I'OŸ? $I'OŸ? $I'OŸ? $I'OŸ? $I'OŸ? $I'OŸ? $I'OŸ? $IOqg.] }՟=IO}) 0` ) 0` ) 0` ) 0` ) 0` ) |BwԨ7{={(:)즰n )즰n )즰n )즰n )즰n )즰n )즰n )즰n51܌f 7cnp31܌f 7cnp31܌f 7ctq8]c1Nk5tq8]c?c?c?c?Y?66YiDFDiDFDiDFDiDFDiDFDiN8 p4iN8 p4iN8,<,<,<,<,<,<,<,<3pf3g 83pf3g 83pf33,<3,<3,< 3g? 3g? 3g*+[ɛ}g=l{ž= {,Ypg›7 o,Yxf›7 o,Yxf›7 o,Yxf›7 o,YXfaš5ˮ:ˮ:ˮ:ˮ:ˮ:ˮ:ˮ:ˮ:ˮ8;8yqagzqayq8aqqaqqamqam9Ǚs9Ǚs9Ǚs9Ǚs9Ǚs9's9's9's9's9's9's:|G/~80$HB$L !$Q2Dn-r '$A$EHdrd8 }<$$ɑ<"2Md̒) 9 1yBū}_?O\ &n &n &n &n &n &n &n &n <0j &j &j &j &j &@ؤE!.Y Oa?~ AB>} AB>} APB:us}8s[VcA9MAQDYeAQDYeAQDYeAQDYeAQDYeAQDYeAQDYeAP |Xc!0PBYe!PBYe!PBYe!PBYe!80!Lu!ԅPB]u!ԅ80!C?0}!CЇA>}!CЇA>}!CЇA>}g{+_;]+0h )0h )0h )0h )0h )0h )0h )0h )0$0x )0³缣r>{wy}⥋;-:00H#90H#90H#901#f48cg3g @<y 3g @<y 3g @<y 3g @<y 3g @<y !38dG?#G?[o83"X`-"X`-"X`-"XOo߯B޷/_|3Jwh"D&"LD0a"D&"LD`ddddddddddddd|,d&7ٽMvo{ݛd&7ٽMvo{ݛd&7ٽMvo{ݛd&7ٽ GA9QƏ2~Le(G>Q2}DYHc$ I($Qq((QPDA% J((QPDA% J((QPDD!211 baC,l !6†X baC,l !4u}G",b(,b(,b(,b(,X,`q,`=Xb{؃,`=X>X,`5Xb kX,`5Xb kX,`5Xb 'b{8Q'DY$~ 1cnjcǠA>} 1cǠA>} 1cǠA>} 1cǠA?~ 1cƠA>} 1cǠA>} 1hcƠA{ [ނVS^߾M?o}Qs@5$HB$L !$Q2Dn-r '$A$EHdrd8 }CLɓ)R ӤHf,)2*:# 2O'Il"m,6>mmmmmmlmmmmmmmmmmmmmmmmmmmm}aGzK޼΋'WU$HB$L !$Q2Dn-r '$A$EHdrd8 }<$$ɑ<"2Md̒) 9 1yB&-&qy߫_ 5k_ 5k_ 5k_ 5k_ 5k_ 5k_ 5k_ 5k;GiHOLu'$HB$L !$Q2Dn-r '$A$EHdrd8 };="$Gd4)2KJL*JjNH̓ iH8% 䩺9sρ?gtsA>}9sA>}9sA>}9sA{@ρ?~9s9sA>}9sA>}9sA;@/,^} k~^vξG(E,JgQ:YN,Ngq4YhG,>f1Y|c,>f1qE,Bf2Y"d!E,Bf2Y"cȘE,2fQb󖘵Ĭ%,////>燍SOGQ*qJh*Jh*qJGQ*qJ+J+J+J+J+J+J+Y*J+Y* y$XFcQYFeYFgetYFgetYT 2e/_ 2e/_ 2e/_ 2e/_ kWvkWvkWvkWvkWvkWvkWvkWvkWvkWvkWvkWvkWvkWvkW</]y_)ny{]o<ջk44.MK4.MK4.MK4.MK4.MK` إv]*` إv=w{|m|d&٥Ivi]Zd٥Evi]Zd٥Evi]Zd٥Evi]Zd٥Evi]Zd٥Avm6LR!<9|:aܫ}R8Hq A)R8Hq A)R8Hq A)R8Hq A)R]'N88qp'N88qpp&΄Ùp8gL8 3p&΄Ùp8gL8 3r&\]]]]HׅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅޅHŮ (jtreesheets-1.0.2/TS/images/000077500000000000000000000000001352107072600154635ustar00rootroot00000000000000treesheets-1.0.2/TS/images/icon16.png000077500000000000000000000017451352107072600173020ustar00rootroot00000000000000PNG  IHDR(-SsRGBgAMA a cHRMz&u0`:pQ<PLTEFwtEXtSoftwarePaint.NET v3.36%3IDAT(Sc`B L( HX'##X$*!|33}|،3IENDB`treesheets-1.0.2/TS/images/icon32.png000077500000000000000000000004431352107072600172720ustar00rootroot00000000000000PNG  IHDR szzsRGBgAMA a pHYs'TIDATXGQ C{t:,6IMnvR-i*OX~NS{W\ 2F+p,5 Fђt&.YL S,L(36 d2UB3tQL%ocnB-Hp`hz/H`vw zKR;IENDB`treesheets-1.0.2/TS/images/nuvola/000077500000000000000000000000001352107072600167675ustar00rootroot00000000000000treesheets-1.0.2/TS/images/nuvola/author000077500000000000000000000001041352107072600202120ustar00rootroot00000000000000David Vignoni (david@icon-king.com) ICON KING - www.icon-king.com treesheets-1.0.2/TS/images/nuvola/dropdown/000077500000000000000000000000001352107072600206235ustar00rootroot00000000000000treesheets-1.0.2/TS/images/nuvola/dropdown/apply.png000077500000000000000000000054471352107072600224730ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?P8=@C4=@C4=@Ca^N.sPym?w%}`>0i(Y`!e}* h-Ao 44O>(x{{xw0Lpx}` Y`^NAqQ3aQaC ]@ Rǫ:3g޽  ߿ ]\RZ* * >}fx5:h0y@ߟUed6?ưU7޽dbb)D@o)\"| nBb K_^!S])`%iC>A`-ð n1<7c+)ȸ (򚊎z34C$73 ?߿g~ @bddej/0  0c,`egfbed01d/ hz0C,0n}ʰC10~gvF&aUgSe}?`=!C #+s2(C<Ͽ0?&` Ņ@1afbL~|Գ Z Z *& ,|@O7ҋ90eSg3f1daV:X(&0>yǰKs>1}33s(Ʒ4'No]҇ ~ q &\ ,^3uZje. C:1/o~1  46@ax aƷ3yr ‘ R܁^ H47A6QPEsnP+CN$;o߯6{pW_`b`UJ0Ê^3Fzqgau[!oZk}o߿`dVdcgapWbe`/ o :뿗 d8uï[232G3Y`҄K\ 0+*0H 0j13˃3ߠto^1z_ ߮9?^B @a_##.Z&4"(0x ^2^No dC0_2м?ߙ!/x 0c7 )ü2aPe t(#т, /`e N6 oCȹ@3guJO $!ft>3>zI#q)`Ì? :yG+2`#-KFR*[ ,S~=`a l^ae`´1 ؾcT@X3//n%AOP`?p,BT~a) `;;O_L غ3c? gA1 0?,*02=q/)1}c iӽ &l1 310Obz#G_2|e >a8{I,W"Iٔτ30aʼ-N/2|6Ⱦ,0m&7'ebfvG X?c~{ g l0c g7@|o7xu+}f#=^W'nfdtP%UYcƧ Ipz Ok?Ggw?Z""C>36},(rWP 5&WZ@%@712[5cf^X@C~ h{ h{ h{ Cu:IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/back.png000077500000000000000000000051771352107072600222460ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?P8=@C4=@CL}HF& ̌ IUhTWyBSXx 1BϤt#@8ǟrn&?HDCǛ2z]Q3=$Ÿ>ǩd-<23̌9 73)1L yvF- j'!i;QflcPK|+gc@Q^ MfV4Z+QaN`o\ 謌d[ @@÷?|"l} ɱ`?0Hg,Y @z@6`X0۝@?`Zb& #A$3Y%XXb320Ba ?w?eb<@HA rc),Jv & ,Pgayf9 ?"8O#@ ""i0Ϣ^ ~ސR@3lİQ~vhZվP6Py 1DB~^"-ϲh7 8tI .03}Η, { Xh/&,K)( 1\%R2|,V1XYH& 008p#' $6,@_ 5z#a!.ccx;Ås/s@Sk qo`b,w 01~ I>,L`>P? Ơ Hz/b"`68]˿Ղœ!%ab JJ 8е6a|*p @@+0Zlm2)#`l03cfp_s@=ch)-b/ Bf`ɐ ɰ@_}0 A8 q(݃?@/CB Fo1/ v?@ǰ1VWKPB}a ,Y1U 9)JX D :0ꊀ#30"3 M6"=Q H6@JBnNBJ? u _6&8 $5 ?H%$|w ~. +F??Аz A rJ'a `eu Q$|b73PD5 X%2,Ν鉍ۯ N t1Z ?h2U@deP 7+2? _?a'D(ddr<` _?`U;#ÒBC34AMQt_p]G!߿LR \;^@, vE~wnddC37+C_ > ?0 1Y15Mf\@vh10^!8l gx؁} , U "\ _A25$_@}31r3q1pp1C1;9Exc RZ ,'>ʰr$';`XÏu[~23{6m%v^1u5(/P1gF"ghXk3Z͠!`?Ç&z˰GТluF%ұѓ |^G&31 Ȉ4Y D2>@} H>ֺ^ 0}u!`Qh[4hĹ.?:Sp!8~SAQ^X1S&׏22{[1xocX.x g7')+#x>"%Cd~{?ƄΟ!!-{g9m~GϽ6FqO2W@$ Zϑ]g,80NhOА@ yА@ yQ#IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/bookcase.png000066400000000000000000000073051352107072600231240ustar00rootroot00000000000000PNG  IHDR00WsBIT|dtEXtSoftwarewww.inkscape.org<WIDATh TTg`y("AF#zk}5ͽYmӘ4mլ$+mbmJ`G5j4Ey`ay03t!Ѥoo}9>eYn̿&~02m@~u}H ,tuzfa ~gj?0$u]ik`zh(DF#X}t|GCss`N[gC̷d7Dh1y6`ft4$\pYaais`\tb_&yHHM 't Cc}a`UQڊ#G`hEr`ϲ53L>n[ r9aa ǥ[p,cޢo>Dz ".^870DgQ#`D" Y鬡 ò=w NO/͍\z5&ӗ'"^&X/qĉˬ,{n÷(BB#}tv w({,5uΣ$f :GӰ`<+h65x(P_pvkG7i)@%4X^Ɠ@ +J ⻯@]JBy7oƇ{8{N09_*n3ICMO ̄x),ދha+ rg΄D+57G{Gt4DՇ¯G ]$vʎCuzd$xfh/:R(i ۸J ڮu%oI{w@dr܎$Sx>M!0A3$ᢄ E;֮žCvK 5zc =$!ܦ 5(#[* Ht'wW¹cG0KrM*eԕUcs,/|8`ïmYr6]hA8y~>>#Hjy~\PhSO! KOxq m7FVv|<2 p#p+}S$Mg*(W$rYn5.)C:-g!5xZ*q R209s)%W8O} YyY0KäNSNb`Ywc>\&RLXr=pnr: KV!|:;iCrz  -mGIȉO@rLLf#} d,ڲeLѾ^)Yӓӧqn@m@Հhh 2uʛ1 sGF_5K$\QT:vJ. BKNQs2S K.-O/\kY $Nf#]u5sbwu=t-,G#'}Gbh'Vb+֔= C!7Bs u{%LSe1AVyRcb"tu|[_́6ٳJYVR^ Ke\-8FC#~͝C`>ϝݎNغmC&w2CH\?~ʗ&'\$īh52a4r\hWFų0$xW}*O }ŏc!Λ;qCVݔZAJK&g[HIXM>?vKw3pYg'Ca=le sA 9Fl*;` ]%]+ cL/2Be34)r,D; Z(wҕF{O""4Dss[xÎ}E=8p2ۋFd28sv,[ֳfkE=:Q0̓JdA2, YdxB0⍰7aνW',r0\5Z/![A;'J!1lbPa'7|Duc&NR|ILM0 eސ/[<TPV_tgh/$T3A MB$ >$Am BÎElX8{̃[) TYo[fHDvv{I 2M Fb֋\uK.7w5L_A#2 m2%r0aj"3).ڸPb{x& HhxQF&7&`:P_lAWq$, eUs jA>p4;bcrl qsomv}B/榀O9<jT3pk>3 0zks)9B4B5>pԨl[.e"+X9̛[ݜ#]fTC(| W@Bܱ0Z04}7"C Ib\_ggo`>q"ٌyx"cxCr/vF9l~ǽ> 9 /211vptW"c3BSj2_٘0~hkp0t,@Y_;*᫧2@5fKۋ؁P8MYE+6.S &y2JV5a87)mܾLHt/Ux/ X MJB;H:l\#z?0 S,q* epL_ERcܬl%m }{vpԒk\kI#i6JHě!劃EF u$$> @4@@| g_=2l[r4DhH97S7.';2#1q-lXP'-PcƢ(ħߔ,PW[gײJrp]X+bmb[yKDEh@YO@^FSyRJX4⺯S1.ˉ|0^OͨT D"ɿSN574J&69@7KY$@w?vՀ,Ɨ 'jJN`o/gS0IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/bookmark.png000077500000000000000000000066631352107072600231540ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< EIDATxb?P0@ yА@ y `dd$0@/&&&% ?}ϟ=G=z R"ؘ32 >ϟ3~8޽@0@>a ,G* .AL4L3E9qt^s ,r/13120 ,:1|~&qgDE 3cfL߹$߿g0yAP>55fUU1`=?_>OLLW|Yt@}A @?fe߿zCC % H?bgLa`Xf?!/+c`Ѐxq $ưnéyVsN_*>60Ouu9!WT ݪ*]]!h00> /`B$ P̀bOZZf\=pH>'u4&`=% r, O~𸦆a"3nw/׿L ?LLG?}b/-e`` p6éj/_e L 0|Jž|awf`06, ( AI4(Ȋ .{55Y5rB )Zݻ ?ׯ/~KBn`3Ի$w۫a4G>Hsӧ:c`2V砚LXQ } U6b 0{&w<X|DHJAУ\X,,@!ID@;($@yq`}nd##F O} X*'337PH ĸAE) VYQ09sǮ] ߾eu0!j}p4 `:?(T@VtR hǿ+W~4l1 MX  `Iffp;بע,ԁ%};ܑG )@A2߿7-GjHItIA%f_`c?4OT+c- _ 9-0E5fc g,ey 9E'77J  @`|g7S͙Y=@500߿'`bL Hm)lH`_SǓ?A=w7og=a(<8T/+:_w`: dPdG3 _$6(!{jB@߿C@B?9X: XV?m6` p;( b&Fh_ \I2H$0cg~߿@G?NdbZx߿i&}^bLR<.$+i P:?0:߀u82WPoX)*2$,!·oW@1@F P~mLL(j !JK&O`H&@GHUy@Z^q[ ǿ~}_*ޥdeg7wZ ̬l:$dH3B37x9 z?/LR@ǃC`{[~=y@%;2y@:R,,fXv/y Ov3"9 Xb? d:XdA]? \ao'h _  7+#?'g* K4S 0Z h0 '`ip'K`QUe?v#0y^o@?נ*o` kd98 XX$x}4@`9XԾL8?v#2 Xm|ԟ?q,*DhdRYY%]k@=o3`mXВ <Bi1`J$h_~߿OB4?@ZSAffK'V*9&& C`930?-} 3 %v`, uII`a8)Q`rϟ{~>ؼ:| -0@{xX%T!T9P]An ]RHy\AK X}z|qpï_sA:/YYS 3%yOGFs` Έ%#Ҽ0kj20c l t/_Ɓ?A  R<<$/a$암,_3WApOWL &L뻁I Y߿37; Mg)\l ,_)21Y015g ̌e`z y}uOk J @5mP?~>3Fw=A҈D`IU u `e6;~Ûσ`~2Ãwgd XTJ@4TD`2LL剰s{2(l`g>z`dtP:?M2@" (r7eduJÍ% ~0?&`tQC縊Hb@Q|!`,tu@4*0W+DԪWE$Eo0M10T;4|`:!5 >d@QQ}и%2ԡwMG&HD+QmE%8{А!hWu ׽IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/bug.png000077500000000000000000000074741352107072600221250ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@CFFFĢ@, Ċ@O GA0w=_ fb/ > w+]HjY!=s| ~ b ~-?0O @LP >@  >u_ǾC=~&``` L#z@ j h0|0RP`agcǏ;ߋgBC+#=@T@,'1k%nncY e%%>))F^^f..`6/_?xSLL 7EE(*2qr3~[w. 5c'!)t+ S#fSVfxׯ888~=;o0$880S~0;ddfŀꙀZ+ -v H@C||‚ bb !(A%A=C0D\p<1`/БL@?@ڼ|p5@30 ʷ`sA݊b!2%:'ed { L !zF?c@y` =zPf&1!!nP> [F3}-`#4`m i]%j`ѧY9?J@ϔ3_0t0c`,0CU4ʀ@3bzXrL@J P>P,uC+/je89Mzo<1R@deR`G>P v40̓i$J @σ0Ճ< 3<<`O2`:Q}wĂ 벀U(`9فd@@c~8 @AlPr9G 1 k,@6 0I?0jUhEW U] ( t+0?&(T !RTHP`(  2LfΫWj0~l~wCV@X=,Ҵ+&(ACHxZP'  L~| SL~ /\`X ,H\A\ ) :X3: T1x& fy@93$r80CX0|L"a+yMx@PKh&'(! ?%QB ?@%'f?`e4L r<(99pR{=p/80b( (/4A#' :^_tԟ_ y`At4$ i`=,i:# WZP#xPlK3|z ?@  -̬,LLF^7B lIH@22r:ȡ@@5xp PrhAxXyA "?@saI)`xT,2^G(exbR. boPchcY@! P :At@"Pz9yAAdJA1t0@ d'//\0_}|` mR4tM`a- +NF@k&`( @80Y<r,,ԑ@c(TjZ@1`Hj@[/Ƚ/^bBP?;. ğ/Pt`Ҭ[`` lX9ð(gZ xH%V@O?Vx ]Ahu{+0ee5(9PĄmc:<,]؀f\O@( ,*4A50Ê\ˁl3>dm՛ׯ*+)x!51C-p"B324A = ',& 9 vˣG zB6kC }q/}=哐׃SQQccc  KcK!t{$2˃TiAs?A'` ;0/J`1A!y԰5?@ P?Rغz i"f@axh W`T} fPLA P*=@\$XAXރCФ8a=>?}pE@%4/|BCZ&>Fh]x! :):+:~.y͡4> MgXp9d^g |v`wQTT@K0FήsRT4ZT O 0To,>= 5%PRղ0G @G30<ǁ̳ g@+WLfaa\+̽lƧ'Lk> l4ՠ t CX\PcNPDJ&6`v;.!g5R { /]$tX2@^@a '!/ϕ2_`ܐ*;Сl@ *n@j`vP< uHy Lv|tT1ݠd l[sT.`UPyd /Ұ/hl>"?`X9 : 771U6hX\'6C3T ,ljF9@Ma,, 'Jp٧On{NBmd y@ ZzA߅ ;`Όraf(,TV| X17=~`b5Px/z 6BRZ`Qc`q#u` KJh!rg`x,j?&&mvAAˏo΃B]64Phl8k=!!9@OTؤYUT$[w\ڸ 1Ͱ@Kz HZ-t.3 @:wٓPGA634;eefli~)&0~~вa#P3M@, !6 LH vfOo9 }*঩AQ*`4#>~30pE) =P "#gK?2|˰{/EHb`"~F X0˩q20? g-ΩN}gx~$P$rn+#"-(oIN3|}i% B"K`QTy9mwH S+{p[p b8 O)n <@EUIAJ*@\sO1)y =I (Qd斓d`P4:|_m;$݃B2!R <% ,E"_$f^2ߴ ǀI=$͟#'@Uv&Saah/ '`81H9."  /0A/-32>70``x *b IMFBAwd`f`Ó|{. Ēd 0KFc~#Ͽ w$'"cedpTf^A p򆤂حx~'Ff&Fd{ٸX.oz҅TH: Q ȍsY>Qq#`o܅-; 3ZZW@E( R<@zA@E{Na. Bc'0Xw6HQ 0K:I9(k_`@-}'l203ի '0d`}ҔIJKmw'"A5HXi2pٗ[mU9 <lԾsߞ;J!M`PZab`5cQOJg#i1O4n @d{ ؗK1߯ )C:O.b8z[`P [TwEIh7Cwģ *Hhl ,N %V##P f`И a@O2" lZyu@11P`5>` "hLPџhD=L-킄)h-\#s DmX@7Jcx3J>ɭA 2: ,uxDL=%20< ldtt jyL@"Oq?041_l6?4OC~Qj7@Q>d j]+d`X6HF b;$L BfЬ)cv3t,H;5=2 JK!+xob`O8 =@{Xk&;+ Ct ( t;@пμýdsZc2 JUx9 ٿ`zHt G]|zm?>`M_bP%v.d?Qރj@ oMhpo2 ; @FAOǟ3 )i@`gd`C,bN3|SGDv o#êl _721|t OaPG- D_9}[t  ҏ&XIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/cancel.png000077500000000000000000000070701352107072600225650ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?P8=@C4=@hRP߿ad`4q!P/#3`#3; d03V9LE@c?yyx L@n~]Vj"0p曘0p 2ܸrӧ:ll@-utdp|?6V֖?USLSl |BC1~b"xv.pC2|bPt! f斿x-72R'O`z+2AHJ*' v|7޽ ?!ý~=M DE䁞&)6<`z_l˩_*w#9AHV6 l`:[@%:*AA_ k0030-[᫴4ŋ hz}$8>!iiqj233|`onf`a`3-ῑ02}5PYĄ%ZSx Wع7`6 x>|```3Ồ(9=D y`4UYMꟀ+.f`* _20|WVmQuu￿~a@W E… 6F;&^^2]$",Z20Tq|00,tfQ=06XA! PO1`&'x&t3 K ~300{AdD@Oܼ'@섅d&JV`( nK @pKW1" dfnў40qC<#I+h"g^^.Ubb b (6Q h߾1^ l՟;? @5q_`  jnF!!eh\T}*7`rLt+`-ZL.2@bnj8X"?og`v3|zh Gjҷo?V ̠gwmm ' ,AA+W~fc~wJK:Z@GC k OO,aagOP@O0=  Rpx0AUz?iWط@s** vb`yᇦ&ٳ>"9 &faai5kS`\WKH[:>kk3p-&W |]4ٲ\[vK?"; 1kQ0扟 XwXcg{ ţri)QX 9e>ׯ͞VVDJ6j_8{3y2?`H338J @*z%*@Ta1%֞ݻ>}@axXC0-c=׶-d:%@@aPLռ:X@N``AA 0P'0*AI" NLP&3= ~ӼXD3f{0i}oe`x?_LﷰUJDI( .%/ӫ@ږ ذpO`C^c`J_&@| LPH F L6>,#lMM%`y ſ DzX2+:pL=Ma@ǂL`(' ,u+'O[=+܁gA{!h =21-rR:0/ P6t瘁VJ 였4{ `ϯ_5,@Ov A ͽDR)r?P7KPCA1r,8ԡ>\Xnk߿I R-,8ݩSz4ƊD{ 3m3>(x<VA^@=Xc9& DC ?*1Ñ<=0H po ԹzBa8xy A.,g`?Å`ʨP|Xb? 'Nexre`RlBW@Jnc[nZ᭷7ftuvK7>,$CGE@OԀ:2JАg&1G&Ndxr`A7 p߁Hk2|`&X/؉4|ϥK ?}Z%cD߿k;7CLfdxq`I  |5O`*ӧ wnX2q= &w>GI{y WVf`y`=r%k@!r \fF`z[X662b`VJgΞex5Q}XX K>+'W.gu7C 9y" Z`3`x*4$x0Â̿nhOtА@ yА@Jsք6u5IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/clanbomber.png000077500000000000000000000053631352107072600234470ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?P8=@ @_aryhx˯zs7K?Ǹ (d ^`I _4X49~gJ@AH@ 0r2x3 2/ _B/!}4x< d͟ax z Œ9͌0qæb'Á}(0v˼UA( @,ߘ49$b8g@gG9p g&&_"/~chPxg_wuGk) ,!3X3,S3\?@3R122@):lL kX gz7<`]P ?41 H'} g33{N\c} 7JJc }~k  ֨xte.U\1x Wc ՅeлA?A3XE@zTш,Cg ~͈9''g՜"3O\R42 Ye .V!D؁8X%ؠI&4@Ѿ䑢 XTޟ1W/wA #a3cϗ>]u鳿;6>e\?y H(q>Lhfgii7))̙3 b b063e8vCoWצ^ =SxjDXq ,0< +^b(Dl&n TVVf022b``;ëW>|pM>= 666&._Ϟ=c8!((ĘAIIΝ;@-dr- ꜿ4~K|`(/wT[113x@'O0l߾ܹs֭,7Õ+W#.ʯ_$) r$ %Ƞ!`v ó {v< BH9>.. r, g0+S {BLLAUUƍ o߾E(@ 0,[6hZHH@iPl@P\__o/_2|b`>A?~ؓ.\;)>,*"/C3'&h*JA%@j)q;0sss޿x(O <,xxx և AK?!8 0+$RSS,)9aG` Tq (X"4|$G vwwgy<s4 {& VVV FF'@1 @< JZjjj QdB'z ~m @ 9` Έ4 J a30qZwb@;,5G&>@,X*,poAe<,a1˰1 +^IpY"mm$V#@{T*1EEEy00`4 qP #(') thÚSQQPXB.aaH""|@ŋd&H(%a R| @,h yA9ȑ#;dàbA g,,,zf2я=bx777P,(f`%)܀2gFt1 @1p8C@%1Z'94UhAڔE~Bya<@+ ke ǀi=PAdD`L0V`oH@{06C$T9MGӧ|{DB(7VB*'͒,?H ,SpD`?hׯ_@=+w@ @ B wXZZ*IM-'7>Dh\ B:`@̌ 65@E-g@UP}j9s?TH@C8_2U^@Č́Mt a)++ 򈔔8V` @g@(}߀6W  " eA=&[3e`fdg`efg4;0_A7 ~2)[A#Ï B30p2%jȦ@y *>\޹t0XB12g^ l81a=rCJ_ك1Ȩ:xF_`O\A X01q1pX=M zgJomRZ33a1`^Rqbwe0dfpw)Ϗؤ~3|D1C70ö)}`|'OfN4܀؅A]T![хZA;wf=b>2bU9>)+߿X`zg`a`PcPg`~:G?^/gJdLS6F-+b73I6=?%K1vpLz οb;C+O 〚6"@40Uְ̽2m l''&`à/ m pV3<,__tbQa#llfn <+&pByPf`ca09l4pA ߗ3\i??2j:*2eDGpی?b|,18.~px_%a8t|40ʮ `YOY5Wf`maax701 1Hq{k3\ffS@)0Æc%> %]2H~c` y6pXC 6~fu'`8IBG@ Of` u<+VMP ACAlzpf o3Xp]Ș twb, jx |x\0f`:x`5+|($0"__pn*7 Xob- J=q"OVa Xe:/ͩ! *]1~|agt|&Bnj? -L?x?0p ;؀$`3, ƀ&,.~]pQ^pD@dz+0QW1b}E$&y]E+ `8yÎb raWtdr@`a0'1~g`lPg8ltLÓ瀵2W0Z$97`bϴ"lF_h`\! xf7`dffxåK\°}$÷o4)-A$/ l|10;! #+4'37Ãoǁ!a WI0||خIqP b<̖ `P:^'\iDά@؊{2  \ a8e þg0uj՟XBXx0H``P8?9YXwa0:^̜@d8i)ÞOg]L6v T06 n6E J.4r<q"60bu;ϋnpz( kN#@]'oGen [\5 3'x &|˟0l!P3t'P-(?Ң@= +3OgPe0:G7-6Ko21 Zu # !07lਪ`'l$lR202+2X2(i_.~pb1\9l4#h*;g&(=v>L, 5 >d`4ϫ lby myF׹_/)F`ImTu|~00wQn)usuò;}pJ]wV2\ t-.`Фz 4<lI2 J0|73 ),İu!s:ONfxÛ, __r6M) l` f,`x( ]6N^1s ~exhcd3V! LL>3{!5JiV~w3,= ]nVγZkt% 94&f`q Mu`U [pǧ/n;{ S$>o7ecGá'@ǀ b 4T'` v=@ïOOzc(&r`a`d=0gdُFC~Hc D7@ yА@ yOJIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/configure.png000077500000000000000000000055211352107072600233200ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?P8=@C4=@©j!:###7P= PP9-m6>@Ubbb"" BB" ,, ~dx޾}_3ⓀyO < aNN70--uAA~vff&1| nݒ8spÓ'w1r < };yaaAVV&O_| etzb-=@LDz??ssӦ- `O?3z(: hb—ly?k]PgcgX`bbd+;;3 ߿'€V &j`r߿0q -ao{ ¼,@0cABBϟ?1@1a)*UYY"|T`߿<_a{knp ř`{D,9H[:b 0<F@@YKK!<<Ƌm)~:@i߿mϞ= eb̌ m EZx 0_#qpb`7;py$?~'߿se'!P $6$$l@@)F% 1  ?=/ȴ0|ׯ߻wϟ`23,@1Lb" S@`0tR~\ڸ8w߾uP 7TEI`PBAfb@6 ;PTÂ<e?33H מ=d12S166Cs<$ׯ_ _~\ GP@# 6\{?7_|\`/P^9^RR^@<NJ?~ ={7di)rT^997KK'55M11q`;TB\x 3Ф[)S&3l޼ 9\L _xb@3= J3v+9 bcVܔ%5 Oa:u Æ +0n"`+CHـM+T166 %577}m`-֬Yr@x XHJo}>f M+cD;33Vě9PVV#ګW/ Yb@LT+n-ؽ{E2ܿX|#T< ,~8E,@1Qe`~҇ J>S=7 5@O,z" 'nJk]7o&O=&@#PQp-$PTTFPL H<0&WZpcΜx+\ }Fʨ: .'H6KND@=LNQ+V{ 'OނYaU~0ObX,cb<@4t, ́K~8g4pGx J:Xsp22$$dc6扳x񬫳fM&?(& ߿`xlK= ə?<@449X fk#%!.m- uu1L`g͚tcԉ$PL R" 12COÏ#{?ēXdetCPGd9 X(ML`0pL0.@l`bcfDq = ?@ k'`{$3CSZБ10,/(/:2#4EQ j&ν`bVv?}=Qf`r&7*@Il P/#a`L X@|P31gzd`??h> yQd3(0z?IB\eA *$~x KH @,24"]'IK.3sAP (ZV+1p2x0ݻ2$J=ALދ eNrg`)2r@@܎ gae lrEI6`7$z 0Tb S|? m`_ФrLl$XcU]`r5/2>Xh,!&@:,0X&;{9C ̈ r 1I>E(RL̠& L?1( 0,~a<Ў|R@kd3ힵ:CۂK b<83Cs4?p<$9V?`;bY`ڠ{Gc Sf3 Kcj aR R ?pis(s/#p7c L}T#K[?sZ2l?AX~Vh2 =q ç | I baxC|a'O0 @Q2 Q ?ddj"G`^gdxmO  R z<`K/0j)%fp[S (Ā<ƺÁTЖ'th?óB2\lpiyD `@Q>8?~#t?Ü-w$8㡠qPvvV%y!w>1`~q5blmwfNe0Pd=} @X2$yK 5=@L6'yXtԅؙ.WP9~YcPX<@l~~z ,vwŗkѰx @ yА@ yА@ y#J ">IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/edit_add.png000077500000000000000000000044601352107072600230750ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@C467 _fb30c`??{`@q 1B10c|i ?ڟ`3 J<tc5ïM@0ps 300W?10qbRf8 ϐW$CkX '/Qo0ʰ6'@R85  <q| ^dUF͒3WO20s%tfhCb@ 󇼧َN6BfV`4êLx3@Ob*aA_VE g`fdF`Ġ*wԱbd33J2D0/~'PPPa˝- l,@3212BMjL 3"@UT2?F!f'_ C=?;CRd@,$9|IF!1Q%v@1^A *U0@b@#q {A\0PNF`{/)22[x;6 3' vFDF)+NDhoa(~;lV @P]Y/@a@аA^X#yC6f6DWt)v<;'I AeDjr@VpvnPU`ĂQu>>ܵ]iޚdeae&=!%C l@N@1e@`YZQH.)@OabfLV55e`g@m|CE'@1f*fXh˫2*3\yYY @v&&&vv__B0|:ZY]pM/PR[bbD19JҨ^^ppsJ+7 F @KCģ d`da;B+-hsȼ dxfoRXRv 1X3LH1ZR Y@i0@ yА@ yА@ e@*]FFJ"p1 h1B!Du9AP)DLk X26U,(( ?nVnu7 %"VMvÙbXCJߒΰ.V.M vfvntDt0b@/.T32" (?~xwL^KK4:X+,IHrK1)ۗ w>```ay,@Lhqbm 7$9Cۘ#q 2|lZ0A (a~T0%!)ܡ썉3RTs] Pr×OYϰf`  ,#A l|7^ϛ .]#K|elb/_ap f(tQT@~;/`s^GQ@a+FAAʶXyK_ 8<ϟ@}Y*o@G 3epZc,2ppڹ _t8G SF;h?C=suu\o#}oƿ 8G  /l\h_-"@f#p& Ǎx:%̀} ":/h7 ȝwp e@p^ I'0?FlLf(*aԍO5knFDEJ 08VQ@^gDL5G +VΖUo_?B<=# &UD[n9܎5~gЭb 8@1L;1es`=͇ b<1 _?e(Wϰv`%}h,_D  JYy:f2Š* nv}iVXy_/3|x1u@ 4+@Q:t\&ÿ hcMt3>a`f>T3(v4А@ y(PWEIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/edit_remove.png000077500000000000000000000022141352107072600236350ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@C4=@C4=@C4=@C4=@C4=@C46? !?(#iQ !*f6}H|F ,kzw3@a7P2߿@00123=8|o OEO"{ Xc~Sb(.cqgb$6B{"SՇfX  ZFF[}<@1bXK{3KgpSAv3@1ʽ4$Ufm+bf`Y Pz JfH1`Pa$& 8Y8XXlaqeiW)  < .r$?3BfbdAL (lS$l\j ߺ[Ʌ#[B.@!;w?#yx -BLT01<M@L CА@ yА@ yQlJFh \P1Fh  ld9 ڈb^Y_ ,t @ 0jbt_0lIv#E t:8@߻w0aPei3~ ##Kg=Ï? ]a Z<@ӷ/ 7gh]2X0d`bi<@aR=+`PWe`cfC"%El}b.%>$л]go_2<} c 'E!w)W^ \O`B3-Hz&w LlǁzwhА@ yА@ yА@ yА@ yА@ yА@ yΊ/oIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/email.png000077500000000000000000000065011352107072600224250ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?P8=@C4=@C4=@(`dd嶶 eO< Ŀr;@ Z/PyY!6~^ [w2~I`fׯπ| d@PA3ׯ~F*gcd``bb```dc`X;*+  gOK} }`uq_@P&+[@TPAFh  &p/18X2=Гl : o^10,S (߿?βrں zr `ǂYA G}c4b + @A $\x(.187Ÿ@OB }QƩ#'/Ơo̠ =~Fca!‚\dwwt -0~K]MC%Ʒ<=߱ϟ/HBZϕ!vm=EPWPah'@ff}N?pW3'.vVfY` Pp|dݻs@oln <;fVUgQ`PT;`V 3w . +CYA @gjjp1Zh2Ԟ]D7/c(";R2aC$(ҌLܿ/ ~aB@31p88C<4Uiy~qq'GV]&#@^߿fd* B liPG<}鞕AXx&>TL5޿vzߟ?x^B@``g: f$6Ñ3'esAedB.\<ŋ  P3BFspvg8{JIP{ .Ckg@6X28;21m?Z@_XbfL|dJaRpsౄxAʠ5++ߟAF~eس&EU 8 p1pp0C U'0'`,26Ñ< . W/98  Wmx@()f \.\|p; 020c.1q^1Q` | v&w~+N8 @#utE7fNllD \bQFƓ' :ə ~k?Ϸ ߿`w?]~p `T3g Ѓ  , [6addPT1π,m.畔zpeh# ׯoބӧ/αcSW88Ş 3,d@.o _?=×O^{߿f6`#,PBWG`l >X;zupOb Ћ?7onϯZu]l}}7@@E?,bXrGRR߿~z/[bb(a|Pņh)%*`n/pr~zOͱkO)ܛ77h7f_ĠoL$ }Xc{1:|' վۯ_wo] ~ w\H@@QԬzIV,p| av%o\ps'0<|x^{wǯP(܁v1?osÇ{:Ϟ`41`{xd?~97á/Ճߟ?_y+?94@u tvۯc_[o n{eXV`ðRҴ&?p}`|\ћ_< uMh1 j}' };wN=_͓XQa/uypP=:ĉ~p#7o//-=[ <Qc`b_]L X 3Gpy\ܲ@ߏ Ç7߽|yg/4m߇z]?q8,>10/|ɤGO$f;`fRW@@og8x8/Auhvpd@DNsr YYUmRw03fXh2 v=ToOy{n}{4CŠČ"zXw?~sǔ|u/?.|c8t!ÅK%oޝ{[>܁51"@:Ӳ6jl2c '>1;5/oޜ~ ~} u8~ ͘!?T0خ05fxO޾cw?;yϟ °ob<@OcawO>x5hH߆(/H*D@@i 6byhyWfLR=@Α ] ꑿ t Аf!!8\GIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/forward.png000077500000000000000000000055141352107072600230050ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?P8=@C4=@C&8@ ?7`ː) { dʿP&Y uX- & t9~@љJ#"`bf|1 e?P=@L$MJl8=$:Y0^iʤnp GDb"[']!~V {2hkcK@P 3=Ā`_2ؘJ2]Đ,0(RUKrzZ23g qN9} gy0 r3D32D/.bd /0`ع>(ÂyX>12L9;t%ÆZ `LC.8@?,/CőX@ n^ԃKb֘muDAmBt`;5QcD$ȈRǢ'@!#{v \\6\ I1/4-e0%6_#< h032$Y13{^}opBB|? Lh)c6OF3;k NVl @3 a\@$$!H?~3" .FAE& H/890?V |lh1 ;#C9#4#L6 G?cXfԳXǃ c6 @e<Uf`6ffUfqPÏ ;fXpB# 2Jt<;C -?//;Sv27*}mDt 73؈n.CM p s7_bu ï$ ߁1(#P`"t?~N;w?2nb} Ysws@A @u J;(|V`?yH. L  n\ *dAP/ ;6S XGrkp#"IP1&! -p8Ǡ6C3W`ȋ0\!:~;ӯ'}\LL `e:{'[ &ؐ DkD:zK W`d`Hg @ϊ2!%cxYfPJ ,: w`9 Lg1@OKY[JXK*vf.PRu۟YzHR!FDk< T'L`Aj?c~41qVoƠ0-Ȑ]vaKؖHxzbay:bc(n̐Szaz( kX @0†ñ`9v`S=]>k&-~%w*FBA@ "&,rk1̜s 0y ʒh0 q[b [?an;p r`9}Ї穑r|fjze~qqii/3koF@y?HaĒ1hbl&/VLlW!_ vj ̻=cX>O@`L53kZ IfqO *޼ t `>dr 4+@ yА@ yА@`!JIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/gohome.png000077500000000000000000000073671352107072600226270ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@C4=@`0Kc BcA# r r fre^6~3b4.1 MH_)kqrqp3\ra> #S((i@1Q M`yN^Ő{3ß?jDi!@KB"(3&!F ) 1y(I?ï? MJ`ͨdCy M&006>bN$@<!`y1H,3i1Ӱ=08R 20~9WPp &h<L!&h 1ߍCBB[g8ڷ X?C3I4%| +&!H xFx+uVV0xõaz)@"p2boCw Ï3TK$ɹhD")1̘5 +&35a3x F2Dd1p0q1020!wBKʼ5 I @y@= \S] W4`^z0o  r 20ۺ!ۗo88v$MBpH~}c.b3[J9ôu ?nfpJ6c?;ù/'3\|/3ßVMdbm(_R@aꉟ_d$65,cHs+*C13pC߿@'#09|P{7a盍`Oʁa{,u5 9`TlMRg e]]-\va ?01p@p(Y9>~aՋb׷_ J=ڞǷ) h<3!LHI-|lofДQk~)ä |q,0?@r@> P?C߃F _|a$Î Z&5<@&38] r~3d0,- +VwoVA,P@Sȱ <+^,c}aB~CGQ ïہV@AJxH)nQkVm?NHerCTNbXף$ .&i,A%_O>1pi0M`ea} U ߾ypz)@2/f'!"j  X|=t<$";p0Op Q ^c`0:1iF%5 8xAm(p6Ue-k@>&`) B ߁m@KA!% Hm*h A`&`1x~{l l, ̠V#COÎGVm` X^{v(av:77PP 142p nL \ L@c<[ v< Y/c|_e?L 6-g8~;+;aS~$@02فf2*{ "E3 n1Ge3?{҅w{$䚽?𕁝0 :. K-@NsFp _l|y /L jiiF<}$&0~?x1 Œ@X2@3- ? 4+/`.309xcfPDnbx)umvT@ ܼ vhK\ <`o@y1e;[OW\6'c` 0Abܥ #8woȰ < Oͨxk;$/`2ŘG_~iM{fPcbl?GC~n`F N4`!`ςF(bd;` y`A@헷\0` 2` |fÇ /& LBw]6fHl?@ wl0>B& "L,`OG E p/xQin^ f~H ~ ,ﰦ?`&@1< L|| [ExyV,P0`C|||`[P / fG a%@1}10_||O@?B9 j*H??,@.?0@k9I'A  BIT{?89XBwH)uxV  tA2_8 / `f~D< 3 Ço9 I?|:#3[Pdx9Lgg` y=pfF`] 9ClC0(XaqX1"0}x}`IX0$ܜޜ%?(fB፻? x`g?3|T#d@ۯԤH`edfy?+4SǦ|F!f;`uU%"PKj=~ ,, _|v UVDb""#59X8 U gyL'H3k0>@,8f-| twq|q&|BlpM쓰^3+YF' M \(1`ቅoB%(?21\~rسcGf^2.2Y!)xX0zT{o@AUD.Ҹp_X8ѰFҟ t_?`=< V>`H^0А!!qI D5IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/history.png000077500000000000000000000132541352107072600230420ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<>IDATxl 0:6`kCԈ*q "|qӌSQD'A@wW,Z lSծ@U7p dOJ+SI}y Jce ٺ,\| B |, l ? /dx'Ï~=xic;J1@A2 F2b NZ$,f`&`.Ƞ TR!S71ݏ ^}J;4ҭNJ  {hPEꂌ &B | DhF  1 pfol#C^{h& { (,~-SRMRa2:;_@|xÛ nfxv-0b`cd_p23ȩ1H11=4K70Xsܥ7/@G'"2@3X٘c|<y>2}wn? " "B bb ga'^Ɵ~11`dfP3aec?v!Âw޿~4@Č ҭ0`0gx;_1;yA+,'30 'Ý{; 0Y2|O Gg3>:40*3i2O?ox Q@Bbf.ĤfI JKiYa'?phyGv1D 327, L^l /_f+%Sc`ga01`P{v30 2`fgpdz-GXN}~3 G$ Ւ\ l|| ?00\=Ē ΎΞ .0X f^AYҕ JR8pcgfg/`fk85` DX,jc`gx w2+220lݺASS\)NCAWXXAUUAHHb˗/0_!22XAOƱ[ fP`n+pkO?95@}Ev/@13C j󯊍1؊[B 04 CN: 09c X 2 ~ &%SpLrp1 0O * 1f.pC?Cj {A8 _ f3Ve O0f3 iP:SSScgNHڇyHHu q< 3B @@12_GO." NYQvSG>j>s3@13= ̴KdT-nfax;Ç'ʲ$i#AA{Hx"6w/ԣPjyDKk / 3bbx=Ó[oIi>н?@ &H ĿZMXû oWb}%TA8# C2*,B</y93#hfg!jX|>A>UOg/2|& c`cKF @@=#Ve֔'0~81'3}\rWX 6ѓ$@|ꍃ 2g_x=P098)  }A ''͙>r 3}l;Cxx 2\S"B  wO?q@E/6`5` w!ßAX\T;>p,<}ŋ j⁊JxRyI7YaϾg 8~d},u U&?:L%>à>C8;''<%ՁmF`l;8i /0+_2̺@f ؀no؁3g3\qކw`PTGlƔA[[a.PenǏ?2ܔaf0O}99!`J` pq ,D^dϟ?vpu|vV`373'@=l5@ rާO|_?`i"((0mR󂮊 7n`ყ30ĥ :d>.'Pr{=î93h,?ؼdy ؙ3Rl#2rT'@髩fPga UU,-]O`3 hP+0Hzl? | Vdd!)X2?hFĉ yxx**AE pA x+× r s3?X2q1pʊ1<{' R4!Wiİr%×ӧg7H;?C߁/@wP@zx 3×Oߡ ! 4{7͛2p7 (Qc'G~1+Y`'o@1z qo& ''mPhxοhx xr<\p=Xy 4t1k* h]zۿ@EgXN|g`g'`I ,+W&(=w,E ï3~fe3E!K`'8YY.^x 32pP=t]u@Abx4ý 7o>`X|铁ͅ`π c`(V;;YZ2:800x ɓ' e5k 7;XR* 7> eR<0Ƙ]d_mSIf6mLL̠! =P}IJ'9dxz+6Bo$X?0<X7;* b ֖6DU aMNU$ydC ,''j 'g~Õc@t_@@0313vLOl_@_3؞yKٶmavÿ `kiP k`+`H ( `XeCbT~xa{ Z ~}e6^'d lCqJ3hZi 1NOgwç^1`q_$C!bMfVͰy]`s]\G;v;`7aag) @_ɼyݘWZAFAVX/< , 1}bٺUP8灝/v h >d(H@.5P "" Űe{,}cd8{'Υw 4; @ ™Mh׳$$p] 'm^#co2LL  5ؿ, ,0808q%yf 0<&4+w?.ϰa#6IÀn|^bfF#`>qrH1Ȉ21ZH0|Űf[)fgw;o¢(kPPshXuA%Qen1I3x:u ó:;K(m@x(~~t6Q^)`L 2I1l\{ʕ 624349@c1++G&w}X\}=bd}?] \Vc+ 34w8_w ( `i%*po˗?`wPʕX /`vŠgg !pÊV/{6h)h,اA"VPNbð X9h31x00K20| gO=fLǬ6?' 8طx nϿ| Eex?|! *-K ˷~cp u%+Sÿ?2/1%uD?r 6| Al  "$,<,^3k?~{o /]V`7ԄL$Sȉ a8[_ @/Dc3LJf&%QENCnSKu10;d>ͱ#&D?o3<ضa*@ iLF60Yi0Hɲ11h)032H 10\쐮OݯA3Fwp/!9>x_b<@x@'2z1czƅQfbb``bd/?yT~ 3w2u@{B;P1*LF@t"S@6 UH~@G?׀PdI@e­ ]IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/kalarm.png000077500000000000000000000107251352107072600226100ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<gIDATxb?P8=@C4=@hfdH13f"j=fFGVFV^ko~3lÛ?@l@  `$~~w'#@/@ B7ͽD,6ᎼLrzJFLL28~30|]f C*P~d=0HX_((" X``t݋ν " ; l`~C_>@@SNd+ ̜ ?hlP;O]֚K4 \ K]9Og,_}`x^0z0#+ޛo[9+O>rf {d3 0 686Y~߽🉉O 0},=?I bl= ,_W  Sټ{0{1T9|y8&}5QYYi% w/]a`cg%xz($ NjWo m3P,eE3Ie ~ [^–y[`x P'C/ah%sK -+?2Xz{10g3\9vXdC|AX8Lzd ᛴ5OP`1 NDÎA@P=tGt@m7Ф4*V*O6v`(o~`Ͱqd[63| ~` a1 c O %vq> [`, # r L -~-lxѐ῜1 L a`b8{'Ñ޺ey4?19e~쐢X+3*.x f&+7T!+,y;tZ7Chië?˰{l1ܽ vгJ)PR Lf$OHM ,;*ԤFY u:b"5!xWPdfXTSpKƫWXYXĿbhINK)p*<=m I_$!V <Hm4) },E%S?~cP36e0a` U Y aعsc^B1(969!G!X .iPa$qX͊;77Xٳ|`f+02@c[]S@6#N` q1 8Dޡ l1+.%An":=  3NefcpwbLfF[~*#ޟ@&0|ag %|}+ L?1lH<Ơ ͠,JCOt}Ll<2Zh߁Kŷ8@/l'$d}pdr>0xf0;1&"a4 0|~ %`p'!;@nxy?~~?ZC =XNlZgf};GotWp=p?F Nf7'$W#`xW lJf3 /.b&,{`^y5M`o#謜 <2,C=05=pȻ<@8c:*` U"_1I@| I߿p;^`hZ@ dԵC wB% \2p !### @w2þ 4@MBc8j6ln1p*dཿ9`{[.^~ IAGx8~~+`2^18B`M/ϰŻAF XYa)_?`߿P&ebav;۳ ,,;o?̕} ?.`FjlhAs/@ax`*@Ghy1CI'2 ~14b˥C/+?8ę|a`,"f`r XfkYÊ@^5C<0"rC5{ˈ4N4;ׁ=$ AZ1ӷe L_3~6!?} b_`X0K Sπj~"0MbP{ Qd XL[30%ՠ3H(Dxt` L@O~{(Ó[LY@a ͽmp+h t}[ --LV o߾eشiömێ( @yPj<<<bP qP20111 c̙3 [ly֭y@~` *M,CTI """ ߿gxϟXXXt 111?~0رa׮]~ZLnʿ :<==MLL{9Ó'O}aɁ<  =p w,'j_7@Z\vw(Aa``ά{ to.\fp3fd"ᄇ 7Y~.PJY7Vڲ0x `p{72s4|lp)zP8<8݂nP%)XB(@ Aur3ez2fC?L wj2dl8a)P`3Ь'U݃w޽J>`n{!%% ^1;2C؁`=6+W-",rHyTD:0%W78|fp:,q 瀕_[+@S`2|@(?}dg{MǰeS 'Zm`Icp6n~ Xɀ*<`… J1 *FzEEEWw,͙\~pi ~ ,D_?(lB/F? o>e4YA3p^$ȋAK _1Nq^_0ܸ XPc#@L-<ʳ=,Xs#$Hj1+0a d8,*1(jh2a% P:Xo<Vi/ Bo0z0$oo:21pLxe ×ϟA^B, Qn"V ߀jXx22O7rsih 0K0h >gX}YXlā5I T π۫7 3bbe! `d>7o[&@G0p21-[.PX6FwX!VV`v~v >3#??~..XJle_Og]n\@ҀڌX'&Pl4`yKt`q V/δI 3`0 lf11fftEAD}z ~i& $t۟iGbzx&/pWbV4 ?5D~y/f*aY`͊ (FA}{3<A-`]Ȑ*A܄a?o_V`L2o?,{>[XyV3Tq  Pb Xd{zp5K%l A$WN`F Y=oCwdn W0$$2xY`*:)ï?3 s VRO;H8Ӥ ΃53܇vp ǐy*_RX?=lgefec`xz1߾˷ [G3hdPE3ܿyAЉg˶;Ozy`+`=ծBK"B_h @Z2A1 @Kϻ |xSAā3Nj0L! ;Mc_8a B0 K1ѳW ӯoR0ع ]@G mTgZa >] :gg(sqc=c?0UGp7yf6 `/GT{.0k020p 0;|o30>ཾ-&>`>@GnAP H1.0\r&yex{Dϛ_?8{ 02hB#lϳ=1Wu9bqSb+0hA t~ 9cx P (i6WQO LǧAEhĀhxMվv`>c`yC [{@҇!6:"ud0K*LE=} ?O7-7QȌL(A蚭jNav3V 1,/$BlD4i g{p\ `ql3܀(00hm  [~0*-OAORFס:c\4W:QK ~ M/Hq<DFh'?A/MC'٠.I@ iV!0, 9aZIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/kcoloredit.png000077500000000000000000000115211352107072600234730ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@C4=@,p02Rtv_TeA ⏁ W>009"G0t@&? 084UO[2| 7~? ?10,ph > !C ~\ ² 2 Z J ~30x0߿o ?c_ҩ7 μdxo ko20tb`"_b (@ ߿ 1031(K( 0Z)0:*0(0 0\%#@ PP?0y4+?6-Űo}?PraP'tOY(fPv""J&* > * l@}f`x? f<P蓟|`8HWyXĹ1ha0Sx LndXw00_b`4='$01c!^H<ܞ0ȊAL^ f`fff`@=L@|ñ >+1}d*$4eIї* X$@=3Џ@iN`?F ɚ K kWC+ 9@Aol ^%a 0(00\p$0D1Ȑwz? z@ 2320PuпXap[ ׁE@AB GGM Ll_`a/Πf, `P'J8Ţ  sxDNgpne`,.dll ~e@ L*2&_&~B?xL< ? 1\exwÕ_ٝX7C\'`䝿xls/).6)W96bB~12N?A7"XO֗nVML ,ZDE33<>rݗ ?e`Te#]vyE`+ fU[(1d?Igx]`pXH 4NI.`g?Iɱ10Ajs  ,i_|s&1y2=.Q.Av` ]VPed`derfzaǹg 1p1gV  ?11H_Hga`cБg|bos& %$~gS?Ϡ#,XNB\Y3DKq2103: xzA\UWMNڕ!['10= %+?a00|~nO׀uH:R!,vv`2,m EOVmn>%_b@w`zol ?31|} j + b QdpL @g AjfXqL, @A` ǐ(aC_%?M*`Ȳ_Y!+Ww>0H'бV8*fbV=&3Pp~~ ~agcfGb/ Q!#s= <(gxu+Dڎ ̔o^`PV =PC?amvDA`x6 d$w k;a'O*P4 M+:Pc`g,R8Fޯ^2GAAX A@@XX,8#'~ .ŀXá f) _e;W/Fv 0(؃}_g` ҖE?,]?0[3C= Zfh )jP _d`:bfYa (xexkmnxdx&)er @cteDZ /̤.ˠ_{W2&` ,ɾxS ɇ"=/WW^3CC9i+e O=İgeFw~39ˏ_Bzl O D?,0 8a,%9}pg Lܼ < @et)v@#0f1hV69>O>1@ mg(~r/^cgxr[o@)?fegWƒ fr @ СLl.0@?PX2-TA&h̀; ) ng`A;p >yp,'Û20i= ޷?`2sB1c И!!?4O,LP gI FD%k(apf%{^30;?l.H3H0(Z|cxí#U2`O5 /H2bbEX.y$ TǢ H`W3hFEN00 t=^~o~-!.P=Õۏ\gxU&kpVa E/zzwMQ0PfH0_=6Xx,č@܃4 B #1 Q~QuH51@1ãdã^pxI o&KcxrR0/7 ;[/n{]_c 6eX EaH z2Էn?fc{#?s s ? d|m.o4 {7`8b?G>=dחfsm}^cQD0 _0\cdxr é Wa 3\> 6exa۽  UtU70rš O0SVg'÷C #!mÃCSr h؏op;c/`4d|;1_OݰF݉60No60o+;3vSAQyUЭf@1cw.}pcz7P_V^(/pXmD2}s] gA {\g?XxE!FDFń=zsz0A?{ ;4axҳl_0 M3@yPT^W>!6`WN RziXHx`YÅ^fxu>Ã_?np?S@EG 5Ol =o+R]ѷgzpX)B*Š!& `wD oG8?R޾p{g79ޞp1z8͇-#6.uKIAgРxj{ ` ,ء Hyf2<~rԅ n2|z?WL_Gja Zi6Q0oֿdL܈aDX, 'P3W `8wM`1v)| c 9hRyO3'``+1h)AL ?/ ҌLh]g?2Wʩ$qeXl ( {2ml ^_޾~p ^mzpV& D K&Li$BwY#`T>1찼{p[;2|{'/_3|ρ a8 lgg%{l0{ك /_`'s$tݿ6ɥ : c`bbbxz8ã[׀A)a)9gX?bpUaedg`ŏ@G CAtObX ^x k؃gYDfM0*k{:K%v圪 -֛h5A){xw,.K50nB&dHAKv#Y0ޟQ ;._wH=R =X/>M(@}`B>F%9Il< -,"D j= YJ]+0_fhFtoJ<+Pl SH`mo0"XM,1 NDw I;ˉn1iRWRN[!*U}s[Ma-<| u0T4H[H`݆u67N"ڪRωIb<޼yCl{u..d֧1OZ&.iċ#eY :l9.eeK=ۣhHC"t_%mOEYJxiq ,q݉ń۽Q2/*{(? M'b,bL0uӽ73gl%1MQq:6M;l.bGgk6Nuoh>|S]dj>. N@E^`.- F^^ uY#֢자;,L@p$10Hb?~0H 3kʖSMՁk r* L \k bR ^a&` L_^>`zTPBX0|{ G_LJW ̌@yy/>2%G[ G8?:Xg2WVFԟ/_Yt! xV`da adW|Y /K ooX='/ 8çWFsX=%_Ϗ.2K=a#+`!$cPt,@c@wŋ͛ A.(`#5&fpǠg`t\  9`e:Xaq 2H3|:T/{WLX=-NA O2GwT5>*TV K>~%%v`y,X{2&hg>:#xW? dP 62 02̴ r#+?q &o* `(X ʨ330PX? ,޽tW-`/ { LJ܂@k`7O2p 1~r/ûO+KU5 `ǻwj7 e0 wb ( )|{FsJ 9ovFL5֖k zvx_JN/fpwJ򇒊(ApJ)3OLOu}Ps8WMe!08Vm$BKz3Ϡ~ b A"tu>KXb;d侜?}̪mP;YnYOL=#&sy|>+Y(<LSZ>VE}rD)g*;S9 hp@, FĽR\=p$1&`'ep>.`/lm l 1_ l2@'1R0'pC@+e@  j`T ;1ƅAU/ l:`bþ~T l ao2| $~aFS`Ty]f4)>C_ĂĈ( a?32B;Pؠ"CMʐi6`#X0{/ȗP57?`oٶ 7~1ae<]_~{) y Ă r ̨>>ax=(6.ly2pde4SnŰel׹qJ$c 160v0v'`gx^FS`2꘠AO@,xPO/1<_9?0I3؉L>88Yb HprLT9@8Yc!,LD9$ٌ;_=gf8 þ\ UUƠm ;?m`,~9J!ITپS)U X,#~@8=&@BL^ <f#q Mz$ lgSfd8 v@ `)4/`F,M P&{@ \ YW Xy3.ETs )baX-H8C3AYd .0V! L .2×,`o`( &IZjP@ ch^$* c@A%`נPAO0dzCW?<~>ïF@NBL YzX9Tifv&$ :#0yoAP?v>y lJqepc t< Тx33@/X  ?z<{o3|=P ̣1MN lL>?}pM`,8#` ? (s?>OhN@8!q ?@KU>` Z_-gb`OYU%lsā-q`MAU``M, M3` 4gEt0?vj3( i"+iFD%ŷC'bP/Z2@2(SB ӗ ? \]oex/&`2bί<H h +R 031VV!b0yh@b%û{/ aU% #ez+o:+3fbep^?W`02CFUu#C>3|{ wgxx"ë; 2A1P pPι T s럳-K͵ ,N 00ss2C(@ 3p#~{Q2<3?y#`yX X<:%:} `ADXNPрCYD!2AX42J 憖_>OawÃy0?g@89l}~CD (RpUa`pd`RaPqc5d1fxw<Ãcǀ,y30Y2ЕOC/Q-("zؠ"`g UcaД`x' L π!ıP~u;O"$@x=VU*0(V#fR4ȷ~ ] -M`t@z[@'CF8{А! D=+IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/kgpg.png000077500000000000000000000131231352107072600222640ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?Pc0228_ @o1y7P Oܯ on30̺$F 2'A=ȗCL~ 03' ЗL L w.t*'UY>_/?Kf```T)s%8!4Po L@or9!+Ñ s;2G0 t~LQba`l&u?~ݳҋ;@@M@K 8Ai   f11\/SO}Y=fy'7,%$E޼{pMᅳ8\ͬ8 c+ۗόnO撻ov·I#Ă7WVbzd3Éu, +S`xz ,ADh$0F1y (tU.֌tZz[2H*00cW@}Î+|G |džUb x3ZNV? W߾pHȃo2 uAeudx0Hjp'I 80T~ o&6CeIm;OtO b  -- yF3$No^>n&Q%9` q cJIAQF//A @If.|F`0?P XR1X00p3oY=jz+9@/ 20 A e &L|;hR\AL BpO>聿@ps21pz@,HxdG3bA ,Ϡ* 4h8A @/po`~}c&Fvh`1 R;G/; ߁I'3+ !e=#6 0`_p t_~}o0T. &W`dgD<Б_?ÓdUāy$b??&>#?0|姿?}ڲȝ,cA6 _u&$@"QK'w/$4K$;kjZ 8PT^R8Vd w߱udnRp}>'!+`6LFJ`6lA-ԝ0u/'`J  @~fxXH U B;L Т!fw b8& >`kǁ)XK JB ]10s;0\޾ "ַ%Nk ;30d0} "kVcxߙYߑ yb-Jgz]l_h6"X/ lD֧2_YWADџBϛa- ߯lbػE@ /l,[~ n`X) $=+:z 9& 콿 1q~ #REɠ,-[!pG`epa?_"V4xO]-w|aprAכ0Gق;%/4Cl,ãǟUM FV B9@K z~=O:< v<] o~av{RWA?@GIc`b`q`p?eAFF f0QAX 038XK28A!?X!q3)@,86 G~|C#?R9/"#_4^6d ?''#;SEۏ ?8=fV<?#O` Dy\pOO7~{9£~u] Ąu( *!Y3fbA0kzAA#߀l0|b^^FP_Ûް=ɬB@|PchO>~adf~04mc"Yd=@mǮdu|- j<LLpaNN+ĀWf0a`0H ⋊q2 < b2| v.@k%@) z`ҧ?NiTR޼K_&' /o4%1VV`w w~50>5< ^d`efPU5̔ Xy A NW Ԩ?QmÀ!$L矁I AA݅7>}'vY8@\F fK>| /4/"4R H($m|7,,~t0p0=h #Pǃ0WL o_a5accKācG`>;|)`(@+’@@@0fja 2`{ŠL }VcACh7 ˰0[p r ,. ,wWP B4Û5"` !>'_?Mʗ_>3<V~o2~%HFL xP p`Jn`Ykݯ@t 10$Y+M`{gW?8RP%!6<jO1s0Oan: e8'sLLp&_Ȭ"A(^<nj7C?"5<~wKhǞ R1# 2W` t$8+4AiP,fdxnȰdÇ G^څ0𚙂cOhb A4Z />ӣ d̄~ B1 c2lv@;à w7gR;7>` `b3@i2?#<n?é9y%A_e:O ʼ LJr oŒo/)1h2jJ1paHH!1(& N!#3$#;;2| Ϟ8Ġob32b20[뭵[ 5Ph00( '+-9&`0 t`nl.V__}dxw> {.2 r8S@^4f3%uC|c  W= zز@B6+3 +## l/1#rkf??~1_P-P}3.=] Ġ6J@<Wg`6&{3'MdΔ72]D!3*c`QA` x`w陠3{2] Ԑ戏@0>B_B@I4S@<@<ˑ[N^v;udСofhŋhHXh}Њ7t@;t +4PF@'bdAcG($A-Ʀ `O&,?<J#4@z`˂>Մh9mh 0)&Y.IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/kgpg_key3.png000077500000000000000000000120571352107072600232240ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@p022H_ ? R?,30xu8^00@|;L fTpo4>. bXYDȆS@Z]YRXA]_ǟ naxvëG/>xoM @|y z@H1;᠐`crTSԳTeбPdPVd {~|aaxKV+3l43*xd7 _<@px= rtLHapW/21j1)A '7@+`{ _at5Cǿ~VfLd{T #d̈4'nANÖ= k2((2lqY' zz  r0bEN-_~_`a'70 =šYP % u @$ @p4?ӿo eXwP]Myy}u?0xa߾ ke `o/`j*  |\~> 8F @wga%㯿?>Z 1Ă=0#qjFnF[YxeTUXXXy ,,+3|ӧ ϟ`hi9!3_ 08cϏ? B?3(a#cЬ, N~eX3߿UNGl{j_% -l g\`ضm7_m^^>}}-5k2DAD:s$(A03BP?0ȉ33(*X0j2͋Zs7@3qo}  @3pK}?ý{.^fO_LLt֯_Cjj~PA'PPjF$ &F/4J1>y/'v}U=x:{i wQYv<\8G!,YW]ȑ >|Ƃ<0)I3 caɒE NN _|c8xc`|C߿G  <3@!$/Ҥyyy9 03`h''@ `s œ۷nxpI%u>ëW<+íN:Ǐ99:_2 ,WN30=;,CA!hi{ ""׻Y 3CT:4XQlll`|%`,b3LJ˗ ]Uɯ h`?߾00~ v˓_: AKի@|K @@ ,R5$ r(0?()h $ d33{@Os0HJ c!<}N?~EXL@?~-{=0\=Ea۷_ Gr@}߿vJ3Yo_a~P3rfaa>k, ;@K3g`L0󃴴0:ف@\\,v30C73] /^90Yz LJr@ Kg .ԔAy2엑QB@B/֜.ce9O 0@1Cp++pp5!% Z@|o{ÿށCҥ 0Yax09]LI (0px i%i 9P4h k.`tZ3cEEY%yy!pEt0A! ƚXzO kn̈FHj ,uW utT= t ~Ap`_X@&H*]@Lk:`c1ׯ׿{O +Ã00C=0rK ZFk7;I,X~@{W28p `QPi"H]P2""8?u..22Rl V UU5O?=t{t #Ê@㪌fsrrRTf@NvXcٯ^=&pON489v#RA馦:}eb+ /_;@5Yz %(%Lk` ,O0H`ŊXG _5S`wqX1sPLu\\ r>K3PЂIPF{n[;lQ`Zl@w @3]ʇ͇Q==Ø_hxpC`xZ}\`quz j=}\*󯀝Ϟ} K>nppp-ŀ *DV 44 :Dtumx +05KDPgaa6M1߿ IbbJ;WJpp}P,2=??;al&8߂+JaaQp@yV$} 5 li*o\uWނ _Kr@Qx6z5޼y lЭgT Yp_ ,lV ޑ|@QO6\ r-FF6`{^0f`m~kC@%5vU<^t< \P=nxxO<!p @QbPepSAi? ?3 L?~0@ᾂg fPI6h[1Sd1o~x-z*!ES_߿)@QamP`pPӁᇰ8ד+ 00Cᨲ>N?Ctb (Đbq힟Zw ~Uc b2 {f`rcVp,TDQef898D%$~Ӈ f`fjF1‚ ߀++ï_?:JQ7%: ;Ƞ{j}3`c .0Rpc "D z_&[{<^3pfod`6>a>൶Σ 84E"cߟ2eLtO`];? +?jC%r{}aRvMEsX `%&COBDq L 9U֟@ ; ~dD_1aeg/1H. Z s2\ ty@ؐn0(bTQV  AX✸kĠtr'=019s%Oj3񙁛($ ;ì ]dѹ#ãB k72{#Z|EJAİv!êU 1}e`a4C~ c@*(lİ!7oc y,3H 2<~ׯ(O@#ժ^{0r`=bd,6°lj' ܿ!eZD`aȰc{2@h%j $°sk:uܔb 0տ32|f }[Y'!+V/l?V¦RH'8/+ ;  aO c&eI`Yۯ/*0|:-/@3`7 xdy@}/;! n/OqS P59 ө:/' ga(eS1X0(z%'G4( J31r1؉979.>a` H /ë/yREFy *dbvj]@~{La;6h_ ]J#) bք+Dll BB" X 6ޏon...31@`?ȡ (i)A1\T"͝&XX3cY"Ϟ=֨ !L(Am_z5!Hu@1[% q@y$_~4 il tC@s^G h*+E&&&;;;{=~~~ӧzP2Ĩu# @#B9bV ,(m0@-@_R@x́' R:ȁ_3@110zh { z֭[߳p ]Cb ())b w@|;bG;k] IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/kuser.png000077500000000000000000000125361352107072600224740ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P"RO;ppro?~?>b/Pw G#(@3{>* ÿ?M{Wuo.;vPWZ{ @pfmd. +/` 7?;ߏ:pl_'?d@V #Цdd[%0L_<@6oO1l_a49<@L$kVzv2pHCfn b6`f!&f @?~20xG2xdqDEE E. 豊_?M@Oo>aa3W{6geVfFm0-<@8={ ,0TI?0$7D_ zXBMDM[9($v- K֑b&)? g @g?Hry/0VxoP)3+vY V@ﺳ瓐beeہO}d`8}a[!?!QV0(.#" J`3CoD ?c?0X8O "6V #@ٲt21v\(2y {9>A_H8(0b?G?pӠPkF:)fpFN`v ?37;#{BS҅7nȔ3@ l?  c4_`O?PkN1|Rf0Mh_@a 0&- ;g޼k%| ~[ -cz_X 8Y5O`:&f=Ê˼ /YOb76;Ì}/pk`,_`dT"E@Kg"ן!avfX* lN3)U& `I WXVM59vG/04na,~Uq3*׾X3[/0a?k. f >|w':-\$~0X(JOǟ3h c>L`Oi`q a˄1 3Va7dqd2 r?;2p?# X E)÷wv½`p[[ wkP SJh+rR 9LExt /?22C3C @1Z~`aظ  ?G`3D3[gB OҜAX%0p1e)%/ϏZ LL< &Vo? l` f&d(L@j0"E llp Ô2!/2|!Mp_Q`[HLFAȊK+7>Q89Y<5x9E 0zHPSa > *@A<oŀ=Od }p %`6 3{}`sH!IAa @```n<ˠ* !  ey`_? Ҭ "/6e&pVe83É_ܭxvۘ轻ZP /x0䀵+a> ,hPbX1`dVa2f/^Ii."W..F]%f 4Ędar|C?'cf$pi9_G!C??ܼ_o/^?^YNXL~ft8+8ٰK K pfxT?C4޳ J| @Z_ GaOPWhEHnڮ̛wfdP2V~sӝw2^}𣅾)/h>+803*>E(  \l B ꗀJ]c ax 4/&(>`x؀AI .s@A<ߓ?|}PL14?@pXɳ12 2<~hُ`@g: &v8=i{Jq0} 9'# iPG xa&'״6@ R?<21BBT03`xpGAXD7E%(çCLF!hP}N^@96`>ADX{hS`felY3sDk`X%.J _+ v_ _~baIaI/R,02xAXq ]^b7n僿|vH,cm,, *N@!ą_ v2#?QyFA` t8$&p0~Z 8Ї%VY`gz5Òм-S_~jI˷W.oqYU lӣ!mP րA 3<$*O _y.:e`dK $a4@iиs x(-/ӟϖ\y\DG 7> #f xVT i9?|?z DD7ZfQquZVi /HS[5Dt`>3wbgv84@Q&)b rj #NpAI>~g -Wwj jz_TRV`ƈC lۅgLwPI >  j{N]=Q`@QBaHX߿?׿?z@7e`@@Tj=fq0h\:. كil BВ  *u@oa ,mu IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/kweather.png000077500000000000000000000117051352107072600231520ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<WIDATxb?P0@ۋ/7f߿}/fU߿c?߿Zo!L jРUx }@,:v)Ć3=MP`? _#00seU!#!eط߯ ~c @d{ Ge?c(<%U{H@Od`'Hp@.?2kR@/@R %g/(#E!1GGO0Eb4݃c?(=]yHy10b+H!P]/7O|-X3#jcE0@sX>1y oFD( = ȐȀT1(I܃ |1 \e&L e$y)o"_caq3/x=0hY G`J@Ur =A 7 @F{P5 E/V[vsF A!.`bZ,]i w_G[G2D\h -x%_#"#TOgd ,@AE+0zw1g|3S, & B٘q- 00@{@R=A@h ;0b6Dub!j^b`P&o`ar`cQ#1pg>+lk?;q(K:$X l ٟ^3}clc&q!O4 Sw9 -ʞ?=x@+/`qBB?x8;yVAlP!Wlg"$4Pl @5%?ýk,JYU@L'?@GbPj#P pY,% %7PQ*"x.fG @a7~`@(K!}ap)jK@dZ2*I1BI J>ȞȎZ0O@bd.9`# A7[Tc`h`` @FI c°tR, yxv  PȂ$f ]?^\?@`k]ιp Q* ( 9联_LءE ( t<`;D/%!؀`g8/43&3P#lN`95@30TyCXqfKُ1ii/8@!t7o0c8?nM B?A_%_<POb/7[LQ@& fЎ ?h01D+]x1< ϐ}w;1$lxC>W CrH1 NZ?! u<(jep˕\Hɕ@g!^7a?AN`!1V56]`#[| :>PApCe L8B<;`2cq8(cJ"ֿP je~x~S{A=45 @ #C%#$@yKeXj4?DV{ ),l ,G oH %V;ôS+d,~~^bH̠r/~25 Z/0V` .92$DAM@{8^h$)I , i[!y oœ<`X3c9i/Ã>i;n6ULLR P-76W@%ϿsPa"qyG0@HG@1C>a ? O٥40U:h 8bdF%_?lLzO/Hl@aZD8! u<_G@`]9PV䟁# W31]7Z7d[f_~ ,2:͟ =DuH};@_yaG 1Xr\9x 'O wGRL oj~ދ/PVDJeͷy/oZK`$(3rAԂP,E+=@g bpSg2U5G3@cSݘ0geӭ2,M?!?/EԀxZ,($A }I`ғ焰B=J^sC~ wɋ hǚ_WY C5R 0~cc?g֥[ޏgsf0S`Xߐ ]Pr@Kn-&n'`d {Pȿz90xE<$Iԙ{ Ryqݷ g]/Zb;?X^<+WMaa$,ͰxÌL _"8 ^@Kus'< y J πb A(@4(@~P_JO /K\}+&VV: ॐRf60?`" [eBl/~bUW<Ց -`8wXy S쀥֫ϐTH@fP =:PW>nӗ9_0׌fٙ@.@='' l }:'L/6GDEh׷_|c_ЀK[n l.ßBo7j0de;8.71wg7K6|`)k-3dv8yt{ 0w<EC!Q`Lf86C6=`2B__ _@U*]F뷿 ?=y3&v^^ o8pwM03fE'-ǿ8=pSW|ez6k; '; ,lЌǽ @XGyxl$`J~~ˣڐA(:K? & _k8;/L] '@} ơ>S@C~ h{ h{ ǻDT$IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/ledblue.png000077500000000000000000000077761352107072600227710ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@p022W)`\7w  ʙfW??P' tbw LK@ >ğq[8uDT=' _Z1|% bce``a`aafoG? ]8D/K} 30< tx03>Ta --`'`f   - ?3 AI Ŭ@Fȍ_P=@=t ,c11ip5`[6de`&@/10|0g M{~Hkhy:r3% 001lfäЖȠ) JIi~: A@K6a?PUP!~XxF( @1 \<‴"0mla_H'SARW#`J?,#@a7E[~uc?$ >4䁧o3?%TUSg`8 &ʍ Xr 0ga/#{'`lP>VHaS ܅@îtM ݐ =_A@T`J3cx Zf4@a4A gAÂ* 3\x^Pe+,Yl es0@1< LR 5s0= #֋51oH&}Gr08H-Fgy@q1c+PKY XIrsAĂ0 gka`(؁A8F}E00SXZA0(jbPFgf@6#rؠR~ õdAH22||deBx-Rb ]  W RԉbԤ mJ03aEohbb:)0q3KrFD "2s2ˊ= j,XYy~c>8X &VV@#&Dc!GL +0K`;+. 4Q5E)) q+q=9t8Y?r<0ف*8P ~= l:)+vc`6A@Iֽ ll^yi qBbjw!!'(s+UrfgjFvF9=i.]y { Z1%`PPRy P;Tpȃψ>3gVFُ߼2 sf&AVN@-6`59A%)'gCj2#'[l \lu 0B va8jg2h 2% d01?YQC )Xa AIOH/h vV,#4`I IX `0,@!) $0__o>78q0#B ̨W2X`Bf_h-,y eWAǷO,ܢ Āhc%!'!d Rb{-ӯþ W@j ɆxkQ^6 zzgFug`xЄG2\^{A^R226 +>3aGT$a ^J@GGၯKm@`%R жWfx7?6ePTHG zfHtG ?? gne8.PYй( @hB\q*/`~f`XLv:p"ʂعy x1pLĴhX1bڲVzy d~; SL<6t?b3 \PMm|Y7@1tNִZ<E؅H2(0P^ΰrAiJM9r;@#cR r|GZ ``o`caw Ra 5e`fɸ(i)~!'v`2]|^P 0X6u00F.qy L5 ЩuS6e>.kWywMQ5i0 r$iVo }#[1<v&`1 ݠg*/C>:j@b brB << )0-7g`;-Ý/_|%_qá 6 utK @C0ϡzk ,5ravYؙɉd1Q@롯D&LzX>)I k%1R@'@.xЉW3 4I:#nÚhȯV0hIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/ledgreen.png000077500000000000000000000100701352107072600231170ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@CH!l*X30? @7+O6 ҿ؁D*@Z(A &!3F[@&_@G?s  / $܁8 Gy~=lfg ЭeZC[>. rc@[ /(or @r\ b &Z 2Z R @w22| f8}&{} 4䙷@lďH"fΨ(  47V1dxå?İnC; "\@420)b$&,jb +et!##303103IFHz[ aX~/݋o3 R ~B.q3Ĝ!%A_܌Lt3{ f'ï_ bh1W|6+@m@|932%2L, L 4ed_f`҆@p~ď?/`İ^w0 4@=> #:@[8|~wp!(y0 AXXZ2d0+\ r@z/3* 6>6oS%{߁! =Ph?( (jAbC-0I}AEV!ВA\XtT|=@y {AHAMڀ_ I /$9!|2ʃ>KJpy(;=a8<'u/a0P&sl Q@e> @,hEm.<\ ?ؾ0|xb \ \R?$ɂK`1N o=GO3EW  o^|ls2@պF r2|c v8 ]Ow08I:3Hq 4/fpSRyV,yK#3< lڂe$AMO#`s#{ <_/ek g 30S`,dʠ̣ l$(D: pfbXS E.NoN9VV:P Z(~]4I4 t T<32cef: 4 g>cx) &0(3/P +a6`|TZ wb63Yp1c7_ \ _|Bv2@ @9Mv.8ED<0Jt!1:.w~6]UVk c^£.F*|F) GiA8c 'y7Rd2͙J *o^e%;-e m{.$mt] HL;Ģ!EϯADJ"G+JL.0MW~iv ݬ=> \r@͠ r0+Աp6 `<( TSPahgaAx)d9_ @`o ,J|6,,(1`Fv (p!K60=hf23& #; b߭+/`ÂX x, gD!P v<0 ]CgCee}--ϑ @Iؼ60ƗtRRRb:R"I|@/,#BBh! վaX2rFv2@',V:F\`X^@/`aR\b+@@z pPg<Y뻯|_E$āgxeA hJe\hgyhP9S@5ܜ |e=ẓL #,p-Ň r>?B)6؀rl 1fV8062A=Ά1X[_5n>bjЕ/ @h` >ܼ~0/eb-ir ;'yVc9!f N @Ycz x1Uaxx 0@Fڰxh8AAa<,Yf+D%Ae'8>{(> tqtC`O410r=xb |b`54 9o(GRs4\rA!é=wA\Jи `)0ebط*73`Kl0d >,)sR'\>7 pg oI<ҩ0e7@ 7j,C#{ ( Bcu E7 π6t,xND7@ 4=@CuKh6tIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/ledlightblue.png000077500000000000000000000066631352107072600240130ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< EIDATxb?P8=@CFu*X؁? ~(24|u债A&n7"_848 X( Q v0 0|z]̌3_ _8+(~O= w+'Dn C(~f`dcdaeДe``? >`É /n{'YY=hS?"5j 310YJ0(3H2?2:aŶ wo '3pb"%1T$7C,C4+(AbI5 1r o2l.3 T)?!&X| J4$Y!Y@Y&`<}{y.2dh DDD@W$Q!V ڠl,Ld0,2 z7yܷɽ l/p Et @[R mD~b` ?TҼFz ̠XpKĄS,)%/_EoHy"?Bɑ?ل|P ];b1Rm1 t0ar`жVdx ,eu7?B,Y ď?'[E Pӧ20pJ^d`Sg`Քn@v0@9]Y~I1|1@>=AAZ*'&H R%Ȑ*3XiKd3_샯 |?)4/˗e-9 Pch?A~[8!j`?|*NA4:`0 Moi l`l : N `fd/𖙉 h(!ĉ7 :- jF3$!5%@P :"0 (`#/#2 ,WAf#; < vv>ptL<rO\K١!fFܭH51(0A1*i|BlAY`JF< \, ~Bv2@yI Bّ _I@+ܬyp,y5$Wh&z'@iVA,`5 r8hyXp)ڬfGT\ AA~2"5aI(H ; P= r v`p(Frrwq!-Pڅ@rQZjjqfFDcD<,L zl?pr`a%a!ψވD!aܣ';} 5~3. _ T1= 7;$A r~@6hƅ:Pe73(z/á?#_2yl&@'[\p-#( uh#g^FCGFPAr88Cj/;=$Ą  W}d }Žj\&h,Ì06L 25!1A`j-Pp @~l6X uBC+ <6-kW/`3'h@q jQc b;{?Þ]w@)P|@VCπF3pt%Cw.?3@|P g>}?ʖ ?c3 i8eF(Cn%:_h'hE)#x?P$ r``v>Ì9ʴLhx T>.sS`aPӔa*U @f\$k;2~7*. 1D=@ p038;1(I1 pw) h T'{p]M0|M4"0`ȿ>*ޗepcPWez ?>}[1}l5,3Q>R'\>壀 Z<lp0NLLLв? 50x* 0Am@|9cdAh h[<Z| ڞmp57^27Xdcy & ~Dj @775Dť}tDY%qYko28ya w/=&$`lq>*)x H 30H1$90DX2hhAp XAI7Ó0gVK'V@R ~Bq3!ŝƻAˀ(L@o0<"uB |C˼ f+@M@|?ySruXg@ !`; 0{|'~? @O71-bxr3 '_bBaD\@_}.>6"Vo`!B?\ !W ;I} 2< _~K _ r@z/3*CK~7'PC8/P#BLXF7APX|Uj* </dfafepSwOpB8 †X|!@Z!ބC]VL0 S2 #!dCcy& hWa`]U@י`@&(f,fGAFEV oc-wo(7\y W_3(`ж\@c@ e"(YJS毿|.0Cf:&?QAo>(!}7 ?|e0`g`d ]tZ HJȉ0hs#X3gx8dB`TGp fFV6Vv}7P?P ~!CK/1&V_?3s10n@v0@5JV^Nv\^_y{WRYh) PEJ51)R?W3\~{ǿ@1b AH2@_Ys2@1պF p32Agbb?ν=J;ҟ06Fb#c^pÅ^xX3S/# iP2L5VGK/7H< l~U>qv`[?8~Üڐݔ 3N3|xv` wL?dT9 } ud'Z|lL "30C= L2z ^_0Hs0qishҠ >2yُ:7@3E8b?8$'ßߥ @h1W,(G#>;n~~fQ dCj2%+2{nL`C w<g&cf? ; P=Ml.s8v0= O{!v6FN`_H,.2VlL$I60( !b\V.$ ; bХd;  4T##L A/A:@hGhcA c#y X1zPIM2`cv{ ) J\A]!2Y P=?FOf`b ,Ar nhA:0,H`vQK!zҜfv0": (1y`M PN w >d`cf %Óɬr<3̋ڥD0=sf/#jx /ޱ3g~ В `p5ߜ"g`վl@ f>;όGQ1D Zq9x^dBPX>e`&JyxfAˆNh 0$ApN@$5H"c| GP000pP)=dB,ۿ8#22$CF XQ<! h!Ί,Q pl 6^(Cv2@z+ɶ 44%]ؑ<MF|P<ʊx^B$Z^/ |:+_ ; <›>\z \L0 < 4zGx3ݳ`$=&!'Þ"(-@/fX3a[ݛ0pfuKAdQ#;$ؐB|ؑz|7D+UF k0|{4P| Thb`~~5+31 B3#,21frB"67Q002r1ŰgE`m2TkC4@h c? >ffm2cf!"YP23j`Hu#fa(04ߠQly@jjBV2s_&לbD =e3$/̆T2#9 q H%W% JgZ`GF;k30tgT 6'PG#`gXfPF @/Yuo? r@XP_gvu87Nj1tfzWPg> bA]c4ÔK f@x >ol %~ 5 6gf/J{ ?E4KPzae4~+]'Ax(o&8B^732k+q߁*ш/RA\<aDjA<3; OYI [W]eXD3,,Lp)`) ~0sr1ڙ02h2 p/X"Mfo?n_|ph-M;3|\lM4"0`ȿ ?~q1;?Ғb 6F ZZ ’\ "v F'޽pKG/0}o(ts|D`@Á1IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/ledorange.png000077500000000000000000000067361352107072600233100ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< pIDATxb?P0@ y 2OC#01// /3oV?,@H(Y4P 4>00260t|& (  &  ?~/tJHq0LM0xp cis`e`7(5E7 JRV7gHLeP,ՀΚ T03"co`2cpbvo@:h? .ai](AHِ!Mß >@@%!&ff)ؚAQ_~A/1ld1/^>8hǯ b~3h (Tg=02_ARZ? yj~a; ~a:ZX%VFL ߁,5=c`8w&XdB3&++F;ļ?4C^[O/_{/ W_7t]Fv3@ᅭ 2!zE_`,R(bߐđar0y&_`(}bQ6V0 11@0z B!!Ԙp8 z"*VG7 >|e0l PJV`H| 1 ,p0P?AHIZkbhcF$ZCLx +#gyc30d-;`N &v8D%8A,b03 O@?A,ld _!ltg`;] ;!4Tx@Lcd7J 0y-ap1BUx f-`@HkB3f&H&gDn!վ Ay_ !_:1-gD/*fDpqb`?DQ$_&J!z ,m GLI;w)d7Xh_Ga1/#,<␼ fAB10}|z_ l30s5 b3`syVh 0F~H]a6DlCl$)x @130*7.O #hˆ7T Gr,Đeh&7 su?pgBb<_@m"_ $ϿvmldDL !EXL]?;`R*{l,@1[ VOΉxd>eAY4 ] Y=2fđTA#?# 2 }<6'f!Yx3x$;ĿfB/kXdRFPdFCΔ$aDJ:yԚ`e~5+ 5٘eg>wGoŭBtό#9cŒ<ό0bF%^AY`]ұW 9e1C8q46O0!ł$|+(@v3@z4`κ> Xό%#3I23*H3AcE ,@Zf9XV'a' LD&#BgƑw/"p×NE{ P<&41?u; |XU*1a)VYu\ 7_f 1,᭐ rcAmd t qF~H38%S`,4y9=a&wN@O0c<`q$NjY08)pU66fP|@=dFpNdXf&nrTrga%: v >+iP'޸0uD!!hrkaE&/g`p2g l&8"v{}y*pْ/ Ul$?7@@x9ane'oUb8u:ßɆT\ b@L1"BYXYYyEuT$0ps?*v~F jr2. _oٙJ |i MR Q3Ј=~ (*Ġi #A5/`hbb7!gnk`aB>: @RH>;[>|&h#&fVN^f4tLO 2 f7 03"w ('MX_62b3 7xw,̌XoC) ơ Z 0)HIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/ledpurple.png000077500000000000000000000100071352107072600233260ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@C⁰N LLL ,,, p ~B@1e 6Ɵ?ԁ  o7"_8̙p6@P@;0'3= ,X_VVP@)* u@a .addYXdEy899Ow2`of -/_Ps02060=7?ÿabxM;6V+@@|Xzr?=8-]LY~@1CA 0 ˇ'AFZ:@;؀ a. ^ t>C ߟg*26ȱ!,Ag}`gh JgWPfPv%@xX `㛀e/C$Ï?Epl$o(gCk *0~UMWk$ D!# 9'1_~29A iAGOZPo@OhdHUb<@<  )2i3owp( uO(9JDc `4[B1I820rRBy&<@@10$$Ԯ3|@5??0?\ŀ@ugP?' -/.y Md`bP ?>g߀IC@??y Az rPI X_d߻_3r .]tb￿" 2%j}u#ÿ/H:+ c<y+̓hrP}~22 Γ#Ƞ, C cf c"Ua`lgg`cx{+{ _AsBߗ e!#y_`x~#V6`t_ zUX( @v0@^Hps3He+##88/1~e`|Ll$Cjcм+!E)&f^x|F6Fv  E釂 B o_afbfbBk6gdb{ @AopY OAP bX|V# P | ;d'v`/);0*cd7 4à(&`c 1# 7 7``d7Fp;A- MP )A ( ߀8Л@ S33VGv3@'!5V`+gFp0C=a3=>`%{?>ft>~Pa%!a m t /Fmy my_q0s3[ ra1C @4v-33|~bd`*쉉HY=_gC`r|5# L &$8Fxp~  e a7gE$Sv&"x aKux5 # ɻض\V L;7*yL6zٵx$@zBo.W#ciAFh##}@%+( l7U{N&4#,17 ÿL0N2@֍}? tA`ܖ4=ԃ J"II6#4SlAER:Nâ&W V)x릤ǥ{k[>pQ1,,E3q3 l9Zȃs@BȠ9 `d INЖ',h&P2>KL 9~12==n<] /0%á:#'Ё  bK ᬐf8H-ȓ`OfHq?'Uk?A)97X&=@J "G@ 9Alh2B!%p3xF'p`C!V6g? \l, @??O?f*/ip6$9<L̈<C{cgBՀc@2= vyd7JS}SW wv1)U@43 d 9f^ al8aA &L̐  1 J'.8 ~n߼ΰ|?$2ba@xj,V= M:| KFωP UYvË wS꫄:_ G]wo=ߧ lzc 9F`!u<<đ̀.b:W/V2g9S@ؒKw>05'LgdFh \ uxs<(t0oãYBC+ zX\zCi /\0p3CbdC)Ò @` !;C<}N0p0 2 z?B8;Nj24p77tDC`7+jg@ <`_'y {ga h|cD4hۓ w1l5<qb7t`qAo@tNA u.9`79ǐ{ P Lq2?`j}bF@PVϐ!DʙE ߠd(.uD;;C$g`Ā< _g~(ï@e f H4ac1gPd0Tgcp{m1A# We S>XփJ |iD` { 4Z )*`ià% " ,ȹA?Ï_?>;3t ?2B>: @z8(?ygYعXXP<l;,@'@8jf2eZ b0ZlvcŇπ x9`ynLEPzA44M09X+>ar& 2?~͠3?rzI li T$,0 '&'yAb @Th'KS>>@ ]i @<FR@\aRP89tK`(6"gXhRcP0@ׯՀ  /"pKPǠBU3jbl_nb2dr d + Xx<?33Bvh r:BhoV%cx08$%$`|yHٻ0/,V~"4(`bA ~#&1`j>|`xm ,j B&FI9C~cxFh T 4MP]YRhÑ߾elE+VMM`Ar@'0 e?w2|o` u``ƈ lfPfBȱ@0pd۾} 330=5p9V!{ L˗~3-M u8H@=lkC ؀f4&3Zr L$ yF,`I=z聿@OT0B=@= @1d Z T %FFLV l3kOptX u/_|F9HL@'!`2cd`aAȎG((SC00yMNL؁qy?jE =X2Z t? =đ=Oa}&P2B.N`4Ѱd@MN_ ׳?~P ̬8bprya@`Xf:3P(!Ȕ@@#z Q@Oz\{l3kdF; !$O ;00?6?E5n te`̘Lh< B h1tq[bhmd7z1z(ً,<Â,9xd {MG@yVAAKZGv0@wh8ti@ a0qtXcD*9aP?fB0L=0N?~SFk&ѓ1=+ZX_z(2Z@f{:y*N=f!xl4 =pUX(U-@/U L>}b-c>ñ%)C 9L{y3@ѓ@WG@'!(rШWםn$B=?\fz$9ly4*zp@XDž&PuE'&& }"[Гr2B/́^@{\v`O (2*9X Ը3X83 0 -JXZ2: < } tjkwIi҃ #`@ia d#73`O2g0C 2 C@ H/\07ANA!ty& l|47g Geox >ol u{= V`"ޞԗ  ]A41HNA,X~$3`r傄8 9 Pדp@U(q0An ^A 2.{ K#r qQQ {_d8t` R\ b&8&kʁJ3x؀6 k`WRX^#ac % @Oʟ#G>J M4"0`/0;z t0?0A8̟4蹷@#`(_fO0zT~2I>"0@(` l\\ @O1A1@Mb m2g0 CZи27Q 1B&oC'A͂NtP_@C~@ y&gIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/ledyellow.png000077500000000000000000000061701352107072600233400ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?P8=@C= XX ~b2ɠ:X9vA@7"_8:m&Ba; <aN?}is (w ( }r@z@Kb? 6!+;X`ӫK /\`x @Oc @eM@T9nCWAҙAD5Oʜ](̓CO0||~ 7ofx(0Y1 SHq @t#_'/vf+P&yd a;[pJǷ00[,'"@z.Y!3=P$C f}g0yv{O0J2\JD%B &"bIǝ0xCHɟAֹxgpHB)id .`W *'Pb'r@xh JF. Pm_B/A Cb1! .Pp%@xT!c`/ +'4 ƿ8/plZ= !7Pp* P<*7\/kJ1@-!ѿb'Zlj=a H`9..`$,EuO$DsO;+;nHm (h+=/͠,M~+OhA&5oEU^P3T#c@aPŒAAX4C,;9l|'~R/g̠Xp { @OH r3HJ3@J P1 T4A+.K?LX ) j" +tP(0p|8T?n3*Ŀ;gu-v` vjʇbAu="ra`e ɸ,Pa ~f)V1 *HE&Rz' ~ ne lsq1|E" К F@W7y䮿l~8؟đ1Z@?=A  f "ؙn 4hZR)$њ6 lpMR j$ Zc(&Q` k:1B3l W@Ց @IH(t-OHnyb-Cr@1>h4kb@j7e߯CoV$%R <X!fB9F80D$X IA}1`!Б4tPrg`fbfA`gX@}PA<ьo! PɀȉI33#9/RGnCҊ[ T0<!fH)-bo`#{#'!0W(BMB #+ s+#3ZŖQ[U)r1Ce0+= =7ß?8 whȁ cbd0z3 _Y 3; 0y=;:@O=܅D>?425"U0<6376cx, @ ܯoS?0ߢD4h<œ:{0< 긨Bˈ44M<~{2 wΟd`x(o&8BUT;ցdoƠ R\ J~" cxn?̰A=,Lp)ʁ?xXiK^ςARAJi јD!y /|a{ÝӇ4=@z̠z 4+" m (# ̄l~ R;w>~)/'`%u:iV"0@(`oI/;0-0;>@>)w cfxm,C#{ ('MX_:R4< % LLМ H6m>P934А@ y IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/maybe.png000066400000000000000000000020541352107072600224270ustar00rootroot00000000000000PNG  IHDR00W pHYs+IDAThMlTUgiZhW%SD6hnBbJtaR4a!?bFPw.l4QI+-ԅ% ` a];u,د7!s}s|_SdA,*z8`8fr'$E-K6 qE"./KJ =;$fj`h3P$R=roh K@m$E@ `=Ux%3|d/l_lw0\#`+Ȁ֌j@: jJf% pB6Sw QY CS6|B~NNQ`+p/pw)GlXLs9V9L~脪cf|zO%v{27w9Z"Ih~C_UntG X G$pZ=i epYୢ_}δR?ҩ  ;;s͞]qaw{<1p0#$Jag=Ѭ7\ZH3;3|97/:\0i2i@Yi+(TkZk\%|LB81WiN+æˋ+ yxJM{ 6#[|S*`iW5kr[TM"6`I(d̘!X醗aA%+^w5#P;qPHe ; fLHw|Xh-,bC%L(0zQ!@,T# E1# 4XXX~7ÿ?@i0fd|ļw%Z@xZ_عԴddD$Ex8_3z oU3i GjG#z@ LUtd$L0 ?dҿ;ow0ܺ|ɵ; EYc(/HIBDd`Ǜ_\ݜJA``v?}  ?_ wo?b(û|pDx HIB@SW̐!"ȘASAL6/|c8y6÷_gRg`afd_G7@n> ۀ(hb@z@ 1wb 6dGX!n{=WICuU .fX70 = 3 +'G$|Ҋ b< 4hVR" ف! " J􏟿=xБ #+cD9ZV8f3$û$30Hh3(XY 8D@ x6* l;/43? FVyg ~c2cPY4({ {f Nt ^.v+~0 & faectõ'?8XjQb //%5+7'0k W }Օ\d<$++V&6^aK cw C Bdq|g&yUuPԋ0  Qg12RgyG`|i1~#=b7DE$8X4 G  %o`3KNeeؚĂb%WDOR'!N"eN߿ * X1 GF3 #'?ç +m.; px1D% e6N÷082+)t : >;O _ٿl~>`=?4#( @|)%)#&y py@c`JMGndx?C)4 # ax Ç/؀!vVN66 ̴L L gsOX2J g7>wnnNV~>F[ͩk255} ,`Oȃo?dP`( U{ _ ni&6{1z1?pL'?20s00ߊ؜ @8J,L  ߀ao .fPr`)##p k%'?>}j%'p{T~X@ǃL@OBS G 233TL@ת Ӷ ֟pX:L_IFAJBa١O +b`c@oBH&$Ai/@458tjTUAkHIX a/ 7>?#01_%0$)`] kz1P^}Xl(j`HwABء}a`caPga`gN`ef8pxL0B[#~ L2?d@ ,Pf l \@򍁝X| }g [~0z y`?M03PہR{3;!]7X2dLہh, pL'^`_WG̒pO_2|F4,!1E߁}ϟ203" s<0)1=3+>'8as(@WA /e "`CQR4C=.*A^FkCw2sȇ&5lN \`}xz0X%Zjy?, _I= %6vn_ $IlN \1{~| ܬ BXdGdtFH GȎx, ,ï'ρ;ͩ~r.88 274<1=9c.~`ae@SWy0zv. " -̎ QТ ^|bfȃC7c#|co_%W#J+T@:4>ۯGNzŸxX #V$H @C*.x(j&on05+zp p$!X2kʠ!l]w34xd? E!5?`d20 s12_p&%SnI`t~?xr 1_ pFX([;zZAJSAK^棷 _?ebeegp6`Hud#P+$J/>24EH3 Շ^0x`3B^@W ]y3\?<@؇eЅm],lbJ6 b* _5^F30yI=ue04 vNpFz'O^` 7/'(0:`ψ9 xjlp+@;2w QXv{a_*002|?ҧ'JV =+>J<80Êc'`v  1x0@. e) >篿06!QaW`— ߯du!(|&$ G N:>}+!%>y``P~Ƞ=ȠrԶaL~6̬ECXisK8 HIrИ);$  Lz @|RI10'2, J<J?oX # \"̐)&P{I jUA!y`lʮyMtsd1*CV1m6)W1p74?0S07; ?B  ơ <1@ yА@ yfWIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/messagebox_warning.png000077500000000000000000000062731352107072600252260ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< MIDATxb?P0@ yА@i.+c`0vfڀԲ wFt|3++ûՁiŃ;5- Z$![`hh28Μ8g:OAF DmĠnmʠ DsR@@)B2eua`{e-uw'$g1\+à30|z`gP `Wa A j@Qid ̪~oXD<XhV jy@ D%CC:o@'ܩZh04!Io${A @0Q N Tf:$ o |pR ɠan3'B)i-,,L: ̒r _B /_bb xXlyAʈR>Rdq:pPHgn`Ą᧦&J 3[+C2%I (IB[Π)yWQ @76 (我$⁡o }^]CpQ Ĭ<<  X9M %,Az ȍA5 H;8yf`PSc`cb?Y+"--X`k((1J*D Dn 4 QVreT: tC[au fff --- L@1zx쐤;Z L@2DT&I2(30v 8dQNar :::>|(*`l l#}7%A!5UEP,8Mr%Drzf`&0AE\'0$..fXΎYd0QRd`VATD{ Hk`%j1H_!<) 6XI($$$98 P,xz20|u2Ơ^)y Hn]L~ L<&4ĎXJJ J0 ,?}a0d @Q@í,RsAW***p ZÛ 1^C `?#6 Q>Ъ|>~`[<&b:FtiYYY,-(`ݡ#2rq3H+7`J:R)Wwf`yZQ ,6#'(P&PU`Ma&sX2(2 @/N4[>.n^Ïo`LR EGk3\ѳ b\ *bX I8HX10>c,@{ l06PD9"F#^q12/`z $3+Pb y% Rr ?kL^΋w?`QAbP Op kf Y;$d= ,PAŪ|@0HXY5 2 r@yn6r`1 `/%%VVVq$``We1yACTT6ŕ OjaY~ݼ,‚OwS߾0:=nF0BkapC̒ b{`A1KAk\ Zv d,3 NF0G 9 Aʊ Fv4r /x؁;$^<ʠ%$ Р>(<@PPkb%H1eol 8XP RTKFY`>#P䈗h\@ ҟe 8@vL|;90/ hc4Px6`Ұ2fy4ß4 }A`w ,k_b`68@gE0h1\?a dh1pM`0A Y!9?J ˰4`Q͈ 9g2.O VfgHd[~zAo |h7_$?466Z=5Pf@'` 3o_y}AXJb9g3"M|Yfâ^_ @șx0޽EC TLGK҃f$_Ȝ ơV L=@ yА@d"?VIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/misc.png000077500000000000000000000117331352107072600222740ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<mIDATxb?P8=@`022UdnR @fff%}hI+@u?dr;@R^ t_t@0VV?~301ԧsssqrr1|Ǐ@?o}Tz&'0}… &  ~D%~jBh ^C @2@~dx?}l8 J 0I22lݺA]]A\\lڵ z pz!00QtX'<Ï?!` PL(]S`  #0P1<ر w*~ N23dfV0prraY8=@X=7G]߿@v82 ÷o?cfs^߿Ih(b@Nj`ab [UUm;\%ùsDZz 0<`iN Àr<>Ao0d(1(**& Ǡv44?2FC6_~bオ&L@KDD\(tI ;`rvCdVʰoV3g@(vZ L:_|_3ps3 ^FpA`f 7@/9@P?SDDL`L;¦#[@2vyY( -@IXQ d`aay7 y)Ae9(&@1}8VV& -IIa`yCΝ[I?VG|2`bQQ%Y0(t i 8,i cAcgy$ ̠2 RR"d!,~2ʁ=L>po>O$Pmܸ!..aٲ`V?z#0jW80z^|P&e<'@qAgJJ `gETht:|8@*V@L=z5ϟ__z N> BB|`π 0 ~}!@Ev<第A |13 p DGgK`Z:r۷t/4LX0C֨{×/?޿ #=\,rr3-7 0+|nPXK(`*!7ÇSQQ!V3.V={p'OB\,ԁe3`ǂ r(!PH܃π!O ,~98!40ܿXpHLRׯ?;ի'@ۻvmj(>X@t}`a`-K\`|,yb`;136+PGٷn߾>zjz:::)H @,v]>>~pUJRD(̓<+2AL~Íޜ9slÇwO MayC`,@YYC DVpL| ,ٸ0<*0@۷oh_UU `k .P1 ]X`yFxMK/ g_,7 l{v`9>,ޭׯt ++ 6@ֆQQ i``*,,z@-PkX/jr0_6)~>nnNp X *@?~gxP/߻poo5S@E+(&~Lm(bX ;6VV.߾}5@/J 4A%PuAI2T|oAA>x3&mɅ޽[ )@$ 2@O;>OAuɻwo~6߿?[͜D-׵@v.P@78V@sӬH/AUU aE)t ..5G={ S>QQ`O!268XY|/^PV/߁IxlMV;TT4B|>VF߿Z΍@1  emP~a.0@ H?<@=7HR6 o߾= $@,ܴp@0O0\tٳP O@}F) 5=d/Ϟ:4Ǐ_@|| '`J`Y'&&1!7 ')]]99)`9O>| e>QQQ`~QK@;iVӃؠP;!5=@f~ X*~@,8wv2DD$|*ǏRMMcF~~~'Ea(&@Q-( 5mmp)٘U03v_{f|X'\{]@=`dd0EBB|"@2xAFF\" 0O@"2t͔B6/kA?8}"27?ނ%cB PzT{T3{d._>ɓǻ@#\@F #qU}>$}vV>Á% hPO P 9&}HFn"J1'`C4<( tQ`=|_$}}I@E,wBy}j{ 'ZN:"!I/~sP  < XA1 J0?w*``6@_ '\zb0M֤޾}woCYiRǰ.\J@e ѧNǠo౥g^[>6bp`w //>nYYy`[ܟ$`"T<r,ZA%H7q߾ Nz$x+_bOa;;ak ++8{X_3@c{%/}TP~@3^pXBG!qH;N 7`} 4G<o~eM۷o.y͛Wnݺ>{#`I_>z{w@mq#/^|ہ@,ffwԁd|v;wF,ټ!bb& P03<L P+/~-0_]arB{<LnK^W\'`ӛ4Wzd %%@e>l,g)((/' tݽ{G/ DY>>>w݄6@ 3DADD4ÊK p`6I51!~E |-0Fx=@ ,y= JVhRU@SP>4W%)-rUIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/mozilla.png000077500000000000000000000077571352107072600230230ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@C5<%ɐ.˿\x ; VK-U<@1W ^ywݸ20Բ120p0Q/*F90ȳ31ͨdׁQ/?Ž _z) Pp0{1Yfgq@Ifg KY (0803 whRd 3G`z`LFZ@QGeXFF2@ܒb`8 mזŞ =I>CB,9 7d_g`dacvv2 < L>\ ?@4 f6fwL 00 rL# }! _$,11S5EL@K> ,@UqXs0bddW\ Pc3c m ` ,*Y# jz@ OQUє%'WH3Bj:Άwlx;?FҌL:|dڀ`?~ J ֬cC?>v;9L >VR[j/_._b,B0 ֏0 x0=>dĄ!L?|.q10*+45ec`xLFUMX* X!AAl&Z0{d+#0& LF / ,6&o3~ [;?g,42I?"g``6%>C< . ]b````ff4 é%,  x[sxe`h#+PalȒ' QWa`6# .Հrag`0}G$.,/Ad]FC o@!2Kzi}7 f1? 4X㜁X7bh&ST tԎ =@P`R!lP ?8/'?HneeXto ߂ @.iI5gUg hBvK:/ssFf66 пE % Rs>`:7#zߝs \ 'OF!PÐ o_b 8鋟 ,6l) @A9{2Yp~30ٸ0TС_}$ ;/@ߐt=ßK> T t_ @%{Ye^ B3s@ؾɐ°_TTk^?B { l`l̘b ̙5 j#{A@0{rυ ?foV 2;?bah;M?3~ tkhnw)@8A=_WF[_4EzsbZL0 hof@BN`__?^9q`Xf?X*Ùe/rCPVEL ,V}ʀX D^L,* jEJ2335&߳9ebRA5+r$ '~aP;د~[`Zgc`Y}3[, 31w{'{ic&~lQ;=nP7xXX8 %6e/;,tdb!! ΠP Jl @K8_ _p[Sr):cs< <4aİ|V=pv澰|@ %߳dW0;00I)]`RJ`r3O{E|y6b^;?FxUk;\lj=017$4+/! @0) OڀV< \zjA1?g jA z$&#w`|E.<ATB*ZXIX(g25mj㷸 }awN4+3HB*P{>cFd~&sT6ny 2RBC o0? g(w0)5E4hrg20խcui8KV@8=`L:m+@ %`G !ڏ|O,00FhM @0Ke`;-P*` o#b A{F z pzKK,\`` } TzB@|a|nP T^! 2,ױ19Gz Ȟę 1GS { p>& ?Rgbe5eW^BN 9N?000t1 ~?B(h zF5G8!#a731wb:o%4K}BlDF T'4v#ʩ߅]awkf\aݼH")} chK DF4t5h2") 1A.R/*ghDp v0iA l6Znr?2Xcn) b`a`P%&<}+x?l8VgˈW trF%X0BB$SNDYIgocx"?r7@a츠CRp@/G-p4zB @y,Se9`|tcK! h{ ւj IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/no.png000077500000000000000000000057661352107072600217660ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?###5@?2b( .=#@1R@9@lD!] Gsrq1033p0jj203|5o/^0:s a;ȸ@'3D:^@MW9(LJALAHh06Ȟ_=c`x {bu.4yx0ݝ!ΎA?nb I9 ~̔ v0<@C@|i)  lC21< {Ob(>pxf'!fe`q0 C`rC&P,;;Û7 s`=f] Kvsrs'L OU}TXa66 FgQpO1X*<$O,ػ10{Xg,(莇^ eAz ɜKp0N0T~Ȱwn߆ GH O18q8d /0(c q/b P6 ȞҜX]\ `0,Ǽ87A@Xh^`Q R@ `aOc`@ [`db>002(CpB5@J!5mmHu218 J!.X7Coq1X*!'`l|x1C10qxb$'m @0(20| ia.3]oa!AOpk{.h%ɡ0 `t)#@LpGO>80goANL16`|a R@ XPBXbrV$ڷ7?oLt<0ss!`㑓` TB9`IB`PQ RSI 7[dST˅t&h! # qtB*P@=a`!@0ISWcԮB}b=@=@\/3<6E*1P } 4 of38YlիD8=p Gr $  .<>A 7l3>c  ==cP!<yV%/{ F/yᱤ$Q5,15}}Dx_@* `WAFoKKK2Nx(xҺ@(8̐( r|1,PE79'@u]$G;K  a`8+)`o TO3)7) '@Q~4` @1H@!6}ϘI)/ 2<^ P5,s%Dq#.!XDg 2`+4Ăd_"bL_'+A V4 @Z@8<O {;4^6vw| R扙Ձu  030Pf M£IfytgXl ș:L6!c b Iy[ |`=45#@QVBXA!d2A7 .8̬C< #VA#מV̀E//zJ؛X|7XG4 Fj.%1hf" !`!F~IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/noatunloopsong.png000077500000000000000000000076221352107072600244300ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<$IDATxb?PĂMt؄T8Rgٽ@G^$h &52  wnVo[0^8R^u Fl"  unyځW1ܻaѢ ??|c`b (jP51;l#D@]V~I .?    i . Sc8;ß߮ jځ&Ĉ_&/@&5X]A)tYSwfWX@͈I/P5@?0,L A f \bp/Vn`:Zpb: pH!ĝ 1%ɠ) , "ʌWWexLز 0u0>px9G@,LP 3i$=䢑ӻ8}~%*,`h f$  L~b#0Kˈ_2t /enޡ 0[4' ʥlaK߰yJ|Av {q `i--.2\#Ϧ٢ yxK 0lkƠjp(߀bapd|}<xW?Ã{{>mM-@@Z,i |`GKJAޒ0ExZ@ ?̤ : 11C|z O.^&`J`e]mzǦ pytPۚ23kz,5>pU׏_2N "b _&;0Y~pG.1 (vA߉/Ȁ  P@ud|pKP&7,bL<],NB'ޔ0l02/Aw } l < [~yO6F1{|ff`nU*u6Pbd@qI\"O jAzk lATCKmb`D $Y@['R+fEө$Q-660|so1(Xɇ5C;k.d4qf[V/6z-߯Q12@a$A}xFV& k-K/#Iy_@eO^ 4h?PGa7В0ŀMn-'}o Zecs*@ajrg?rATRCՄAFM\ >}TfZ 'Ǡjɠe ȳH@/`}jlLΠ_9>)q5[c9]%p [)Tpps21`kO1[< ,@[^(aDc&3 c :[ķ_L f Rz$@E7? 3AtϘ6?~%`0SX_'ΌIňWX7/ ,,Sx 72c|`,pLu ~6x9p@IRDAA[ f*yX57X@jNjZ`  fwRep8!jqy (`7$?+6'yX3{adž ߃H0rA /1 \1eެ2Yـ<5` Ps`jcDxx-`{u`' Τb B@Ԗ)?> g`$ w.y1+Cqr`@FHځ ̓%06_;z {|1"C-"@% hiï?b F ..* >aD&(ׯ y|H=<`{y :?1:=7 I>QXߟr y-Aq59Pc)|,~="LL2$@l\ė`I|s,%@5APPW4R&Rb`lNʳ蚫0$3'@ I~}_sd"(po&p 6YJUhT҃])d`/6Cf>V0cY ̗; N᜵V"~[#܊@ZF!=9Cbȩ tL.g`e`<yYbahg+20Va`*%aΜ:E' o {B @-@1Q13Eq0H 2vcOLj/73_$(XtؕAQTbvJ j8>t`ϧ/ .d` ?1@] &W1  # U<bG_oo._etO@.a`x(k7@Q( F&`L?w _z$ߢDxH0#l 4,ـ*q4e`n:,7PPt@~$N/~ЈMD``) RK < /ChIBRF` Ny1h-AiԵ~p /J, jy?QW^`1M?N30 y ~KI Ĉ%-,] 5%$y|_>v߿@O];ׁ39@v3@Q-]vnG_flll+r)@ x j%!٠Py?P]J3.2 XYB߿ Fhwhh{ hFFb@?dD.Ah3x@T+X /_fbNAfregg3߿QZ@MMMkB@ZPy /`CIALLLꬬL@dׁُ Pe9z)",@䁖-4@=Dc8;Tl@ ^E``b/P=;P Pʟ?|f}`ff>OEm JC e @ir4rr <<< ,,@̂-l33sP`+Ï? 9(n7` f:mB@p|Q[@9A l&aHD?bATTl ba'ׯ_3|7мI@z] @hkd_FF0;`I]Cd5 1PB)(9O:z֭hH׃8 + F)""" P 0Z `1;`Xyd4Y1c͛ >@|@^`xyyf(A4@/^dX| ëY3]6P 2dkݻJ> Ќ XpЄyAX2E;HgN0_0 0:V s@Bݻ ߿;d6(-YCCd6AyZ@L^=bz!݃A aPY;s.0<zD@W7Ќ/_@zP( `Pl zAAytPy1` EǏz:l 66 _ӧOn@as8M x'c. ڀ"PFAQ 7С connp߾eb&@G_|pP *B, @*jC !9T*2 UGG@9t V??V++ O3ܹu  #81C@,x4Bkׯ^3B\@n.(@]`r̤I pl%7$(ٜF9`~U} @]ɷX (v|7tuŋ>|b f#@?P3!pNW $P{d0=yOA0=@Z8Ybbb14+@%脅5(`@k709h{:]@" ]|K`M\\<:ᑖf7'9`:9ٳg~d2d8<" | {*(( %{l D%-L p='߁*5 Ƒ_hl|f@\I*  Tb ԪUZ(s| yPG :D @v)@?c)p Xy@C$ ,,,y[ղ ׯ39sl @Ϝ&Б D%% E=hH!Czzz2 RRRf47/2_D07^ϡ?Dif`40Ml٬TTTͅcǎ۷o_Y`z "k&%:(9 qL00?(cDC~/AhryC$%+P\XA66GF@Q@pCc?_n05 IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/package_network.png000077500000000000000000000127351352107072600245100ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<oIDATxb?P0@"0O2h303222qM*W ~c`se1](#, >U_cDx4eEYe$y%8XË_^'g{˿_mf`|#@~|J<pu1d(PvUaeP`>Aa5_10ŔY@[?#D9X83Hc{t1 3~ t40|(Fgzn/ycbxr!Âytz>FTa0uspOwRcdĠ#?`vpra:0F&/??3|:j>#/ $3Pߑ' 200k7-ax> .O~0vzsps,ͭ%AKß?`& F`j9X_}(W7 ٘?xp(:ÄM On^16O *ÌmQF7O B3( 2| v`fa6N^>2H,e`'O//"?3axf'g2<&u { J 4crdçW+L$_47&&n9!^a ~%gYr \ 쬜`?'w  fseJ`3L-2v^eafpt'Q`}W`fddT`fZ).6~` J2q3/?~?Q߁ILCe&7Ȇ߻2uP-TN}.",WMV2 gggZ ₦ | jp}pm_&@o?8dsׯ <" F KwU0:22Hd(T{GspxOIATĔK?#;A_=d?8%)0yq8ќ :<+`S/`_? ~1ڋ  ܆ <@a 4t(y0;ADa ރ L> _|(Tgx>ß_>|piۏcARTARD۷o o=!q_`7аv2L^~rg B̐,jv\|0?dxW^iŧ> Ïo/<+>1c>.-~yptז3J1d0K{PDSqe6ϟ^' y}3/MVw-no_]k"cHUcd`eF<01dacd3I k t0qUX _(ςc؇Va?phFדg>zlxG?(Z×O,ldU<<ɠi + @(s c z6`(J ]] '1hf1psK3(ě T jV0RGBޜ Ll=C<  ?<l[^f; *z ʦ@s"4 51q e`f*F`!~po9CZ?9!1q 3®AAc??2ܺ3Z3H vP^"xw->a``1kjơ 8IKIF`Wa`?J1 + +}'718[3&i~n^Q `+` Џ_|wP:/00@ܿ&J^)XdH 0dbD;\ j! j\afDy@CL<: ~C@U_B/~d0tV`R2py LbNJ* u| p6 a&}( %nà*np]{g1t V~3"ǫ/ay'   , N@, h&Ab4% ,%-yDx%"JBPXڱ3 v2/3ý~: bWϥy%p^(Ơl;CPXs2+I\(Y M$3ؙ}VFTyt * 9-?>1(0|p+`3X W.| `c /aiTΠ%k *'+$4+<Ab0>Ŋ 9O`,*``un~ l']gekĄ< Nw <:ܡ۵V#``b';~>('<ـ2 z)C@ I{=`_Ǡ#kV#̠g0PcQ=çX6lgx֡Yp+ G67 g8t6) rzqA,C+vAR2 Fr*0ee3<|JlOq,?;+ vxl\M6'ހl >10g?jB_ 6Ȩ3pn( eͅ&A0 N6* 'P}"P l1,\qG0zNPD Ą94g㧷wv/z!ɼAÅ 6(fb6$ Û5ہlS<̡OJyꤥ N NwؘG`z?`-S6X< ١4;tCJ')5K_j29 ë-BhE B,6IVW(E P i >3+8Ld}X3ɯ@;ƶ /xz^`4 pd7 LS:>_ח\S~_|/a(/֟A(TT 2p9 #c''&}jF 6 7ٶ2l{ X0p2q3hcf|f'~C3g~3# }k)D5IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/package_toys.png000077500000000000000000000132301352107072600240040ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<*IDATxb?P8=@C##ld J^}g?J d:ҟe3ϟMRZE@G@ k[k_;5!} wO{-P:b1 EYY]\l0srE@E 98NKϑ穬R G8th`( NYyՊBexVr8sCCCcc#/ Kk(1p7ٸ~p5)V6Aj jLo|f`yb"Pߟ]-^<ޟ*a%$~6VfL b6 /7f0o7iO~YvJS[#l%p]߾1 1\g/˗@=/@)d@!{o~/%}9a>&.F^&&?qT#c?Ԥ3fTb`zAs~Vˌb<|, Ƿ_ _~w&[gg3?d/KCw< @(1Ç’W?d򟁝(Ir+) 21#3'_nF~V63T&^!ivN&&1̟^d ||exLm^:00 211>'ß_ XP!F(!?{ks2t' _>3)h31 00Ä )L3pdd 3 ?b]A(/OAbx8Ǐ R1|3ZHbC&i!nAM!]n9\ ``  `L01&. n2 3(*0Ky@?0R;QiڇLDĒXfکtcN=?t Sf~Bn^⌟[ Vyt x# H1vFip򆫭(ۨgaA?tbse i⯓#P<IE`{{ ~x4|d8XE8%XYx@r-0r?/KGna?*J _1HK#-`f>  _eS"dx;ãw}!$#Xpr01p H0 б̨߁n &`h(@>!0Vp>aaa``PePUZ'4cc/R67gx í, >ӧ[@%O/C[Gh0 rXفy'#MKFg L@2p 0:0<~C_哐w?~IW7g2۟bw+@iPP%d`,bL-8gd&&9n= +&*j1O  ,gEl Jj *oy`/@mVƶ ócc#`p(}_jRT_Ff&@\v1++v-'0.! `GDo&;_06H,g#Pa K9A9'78+{}pw'>:y&,?@E7Ѝ f rbFV6^Vo1(+10I1p}AjB2 ï7?#A`S70f?;/P@ aӤH#K.o#xtBP5O\nт4GRS1iurVUQHja%#Hx<9mװ'1NSQ;AR=0P6%֒T#pT6;q+Pm|^8hU$Nn8z$^!48nL21>_qlmDao<"S4^\ a2sp[ ,wj"EKp&T 0P l7>M\_y)ʴ44aT\ $)%OQev)2!iQ4 &J4lBb 644SO?w$J]TG}W{4˙ K=s`V[H5ADV{x\:/R s1! ș L2T8-w6!j EIxB$0_9mT10~r@`ӧ@ѻ}uMX|0rpp?`k`x@`zl| R6VPhPC^|0c^B==\}'A dU&%, *U > 0?`y=0[Zʂ% _gPu4_2Ɵ@ac>0,?ΰkۍ/G=9@ 9~ @L?<0;`I?3б! -PbYyw1`}8(13c% one0 pd`cF%7Pl?}#G@|2 R&z l@?gxzÑs y6נ} PL6o`Q&=6yY*F*Fpr;6 QFnY9p$0?| v +_u7 rB\Б/@= gafvـؾ%f`5ë˧?ϰ_0[-ç[?g`dh|$Xy8D^=z P_&я x%B^6| x/j,LlO,!8Gnpk;/~?oޟGFsG^3*+1ajf<"FZx?0 2p1xt!8X2)~~rs_u1sґG]P80] m%`{k6Aaw+X≠N +$=/#Mw,IO_|Ė1 _>9ū {.mhioG>}rbA_r9UBX#8O|1ßwOͰb'|UdgRz LcDŽ8X}%&9@ 9&0agff`x1h=t dS`|>K_``SԑAHVx'^{m{;5D?K@W%Gp~E,tϿ|@A D^'Wg뗞0|4Z@z}>\j|EffxW.? j}{ XtV'd]YԓWǡoT3tݷ׾~C}5ߐdNB |,l|L?|{G;nݿ̬X\'&N 2<|ӗ7.>})Q@!ߏ[@(X t8(@GВۏWN_|ఠ-y~2efPՔ`^ܠ̠dWs2:7" ~6B@,XB336I{}a  z@az? ex<b }? O:聿XB _=T7'BbdIAˊl@þ?LB_"*0 @IBhy `hֿT` |'`bAPt؟ Q)@x#`= Ì, ($@8/&%IB~krA,y x  .ʹv !h{ h{ h{ h{ h{ k[4N uAl1AG<@`See%a<8X}|&@Xt.w*x4 =2&,  Xt8PQ9\^1Ăegedf$5J+4 1%y pbx$<{-kx '#,̐&B` XУ]C <@qIf,@c4,R`+F{`?<4`F1b:L @XK!( OI)-FL31@avh %TIB̐7@1Xx ܠ$ߌؓ@aMB@Ih`B)I.x2_ܟ?@ψP[C3]uǬpL/_U`&_|#N°a%;oB!޾ *#z !h"##  = ,7_YnPO @ۈR-IENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/player_play.png000077500000000000000000000047761352107072600236730ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb? 0R C ?gF&F|~bA Kq2iqfPUfb+(v( | 8Iaߜ<+nn6AG? l L}1!6,# SXT~^T <ן _303mvdǠ+'Jg@{ 0=3[3P *W:gAE@y 0߁?7`lq11j3,k&@_2z@x ( _ 30~z MY,)A J@X ly@H 02@W_iA9 zȰvEW10900)@ `x~8 0}_~a` ނ<Ȑg/W)8@FK<7$ߐ#$f`8YA&X] {fCT;@ Xp偟 u43AAI Ҽ \ *% .1lpV4y7Py_;PL0+ 10x 1h(2l=ʰwO^=qԾjw@a` %!&"/H> rL >J d8}27X}//x {74 8 XtI003d10lv~ H:Pi!%vby/g(~wAyce.t'8s#p&ga^j>@ b),?@f^~p XZ30YJ2j1=L?^Nd`a d`b: Q X j1R{$VEB]TT%p߾V= HPb lP 2/Q0*(7;CD!my.3|w[U ,b3 f 4 <$0 13pqϞcX*mǕ? WAbiO`,4X!ƏAT`(*bnt% ڌ.lcyj0229k1G1HZX00 35"{"@X= g1Fzf %71VH>#20T{Kx 1v^n6@`5q$!l8?[>+Xׅ Xpv0210>>~I/OB?+LhY=@a(AWyh&`o@fxw&ÿ_tz (]{ l21AD{?U_ށ~Av1nť X Ff fJC0C n?bxp+66GBf`a< m< M`s$0ޅbv >OYĂ^,J ÁpB:$ Տ>g^\Prhqv6@8Pt;o0>~kJK@` ,pek/0T,*WQ@ IxNp%> z7,t 9,uf@զ/>g8;O@?1& ~J Xu@1j́^ͬt{{.29s` u3P5 f(v8޼p`%s/2M ބ&j~+0d@<فmNxȰoiO_2'6PF@aĠbyA#O`رËO@'El `ƃ/=p80 J.ǡ kNـ {`8*חAfO5y!=2lXz jPO f1 n^ϰv'_nfEAmeF&&f`:<=LS1HU=0@i^\lIENDB`treesheets-1.0.2/TS/images/nuvola/dropdown/player_stop.png000077500000000000000000000014341352107072600236770ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@CtFF; 5`t,[ U501B a7=z@&@,Ȋ!C98iAa߿ /MsKK7g%1u $Y X9T`HF 9O L %@`Ă-o?(<4@a@߿X|8X=@ 0/F) IVց y? ˠpb̃qB=@3&A#bd4 p?. &!h{ h{ h{ h{ h{ h{ e( !@,|x Xpi`dC L  əc<+O$:WȑHcΝұ 99 L] D-2M@CqTw%ٙXq*0yMA j"@O.)= 1Y*(žn? y_ ??44|7^ٸvS Ffde{A` S03cf󞑁÷?vb0e`h[+aĄ3c7_JZ`e)x&C# 22Į{ l~ %ŰjWH0@d2Sy.d<_02t`f(_I'3?G`p3ZŠ ,~I0а"'_Ţp?g`I]c/?|g0eWc`'hW%A IH XO`g?۝AZ@3̻p9#8ÂBoh P feOmo-P~2Lg5eJ5V@1`, {Lv F`31g|aFNP7(ɀNA?X|6 |g_`~ ,^Y;0rp'3L @C*0Dut% (a5&FDP$HP;0o8a=s}/^~a6ch6@j 3V 嘆+,`Iˏ? _a4'T L*?3|Uf7O [_2X=}!1JSX`:_& #ffhe ,s-,%J> 0~͠& ',<wpR59,\N`pk؜ @= n1|mm | '7`"jL=} ù)CL !G58+>? H&? {C} CzA v`^ ?SG`46ʵZ*, TLL`@还I:7(TAf0 geac6Vffx CB*C`2ͩ=/gbj(1gee'&9HRϰ<t< t$;7`Ю)P|*ǀW ^| /Aw*@a 7`hck<3􋡱Д4݀ӇK"pt0t#p|l\1}ѡaǏ?' &r?|<s_D$@/_|i>LlXKJ@I؜ @:8O-X,I!pK) JG0C2@83@>*`0@B?= -%B) H#F|\KÒ? \{쿌 D@!ʄȂ#" cVTza`Hm a@h43b͈KP¬c?J0y'Yq814}Ha`P@5_p @< 7?*{ Xpd8ghWqƐ j`b2nP, [lH'SCc7-#  K `dc lqp!4F' : {ر jtPC M آԀ41~Gp3HB1ݩ9ML t3(1 s#Uh O@5+ف̈́ϰÈEh (l1l80nbg_IH (8Ƽ7ν tiU(H5   m: D7@ 92!!0)+DhIENDB`treesheets-1.0.2/TS/images/nuvola/fold.png000077500000000000000000000033361352107072600204310ustar00rootroot00000000000000PNG  IHDR00W pHYs  ~IDATh_hT?;әY!Ê APRb[a-Qt}*E P[ El,O}*XJC}X`V%+݄CF&i2s;܉-6_{=93Vu 8[yi"- h"@)'"2f,_Ǐh8c1)G@D(ﺮ1}0R%f}OXk 'NTˣG|2ׯ_1cເGDbW 6KZ$-fkٲeK*ţܻwc$孵5%RhzykwرdrGqDJR> R TmֲqP$r9Rr9mFGGRiRXFI&5 +ҥKܾ}|>_#i֒fni-"[{^L?B:G hy<\.,`fM0>>"4/Zˋ/ vM#X&!Y>u *'}iʲ?EM+oy^c@8G6BMn9)A{{;"Rw҂VNS\VCР ֙zkڸpB|~9޽[;Gɓ[lPz~$2gjuٴi+WU(\.pw]]] T2}<OG7_R\rb>KQ)J(> G?ֿyOO:޽{-Zk) -[2 (*\zk׮a]#6ƼU,:-<* ǎȑ#E`vvCQ*j(HRy㌊~`c\ iۊJfgaaL^1P"oSq>}ŁOO=CП fͰǔR!\q+ma/Bbq %bM3ʅM 0J)7m]Z EZk<uNk~}?UBA9M P_]طo;w ufժU@Dz/RLLL7op81;;Rj^t$1_7oެ#*YXX#l`Mn b0<==ӧs zO?? q kT0KyOu9Ú5khoo}s? ,lMLL033K? ߾'.1=y"S Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! treesheets-1.0.2/TS/images/nuvola/readme.txt000077500000000000000000000047131352107072600207750ustar00rootroot00000000000000++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ This copyright and license notice covers the images in this directory. Note the license notice contains an add-on. ************************************************************************ TITLE: NUVOLA ICON THEME for KDE 3.x AUTHOR: David Vignoni | ICON KING SITE: http://www.icon-king.com MAILING LIST: http://mail.icon-king.com/mailman/listinfo/nuvola_icon-king.com Copyright (c) 2003-2004 David Vignoni. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 2.1 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library (see the the license.txt file); if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #######**** NOTE THIS ADD-ON ****####### The GNU Lesser General Public License or LGPL is written for software libraries in the first place. The LGPL has to be considered valid for this artwork library too. Nuvola icon theme for KDE 3.x is a special kind of software library, it is an artwork library, it's elements can be used in a Graphical User Interface, or GUI. Source code, for this library means: - raster png image* . The LGPL in some sections obliges you to make the files carry notices. With images this is in some cases impossible or hardly usefull. With this library a notice is placed at a prominent place in the directory containing the elements. You may follow this practice. The exception in section 6 of the GNU Lesser General Public License covers the use of elements of this art library in a GUI. dave [at] icon-king.com Date: 15 october 2004 Version: 1.0 DESCRIPTION: Icon theme for KDE 3.x. Icons where designed using Adobe Illustrator, and then exported to PNG format. Icons shadows and minor corrections were done using Adobe Photoshop. Kiconedit was used to correct some 16x16 and 22x22 icons. LICENSE Released under GNU Lesser General Public License (LGPL) Look at the license.txt file. CONTACT David Vignoni e-mail : david [at] icon-king.com ICQ : 117761009 http: http://www.icon-king.com treesheets-1.0.2/TS/images/nuvola/thanks.to000077500000000000000000000007421352107072600206310ustar00rootroot00000000000000Thanks to Mark Mahle from Hosted.as for hosting the site, support and for being a true friend. Best wishes for you. Christian Schaller to be a big fan Nuvola SVG and to have made me part of the gnome-themes-extras team. Carlos Woelz for his work at kde-quality and to give me some visibility with the interview at dot.kde.org. Also big thanks to Swell Technology, Kde-look and special thanks to the guys of Nuvola Mailinglist: Marcel Dierkes, Christian Szabo and Virginie Quesnay.treesheets-1.0.2/TS/images/nuvola/toolbar/000077500000000000000000000000001352107072600204315ustar00rootroot00000000000000treesheets-1.0.2/TS/images/nuvola/toolbar/editcopy.png000077500000000000000000000037051352107072600227670ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<WIDATxb?P8=@CI:!u 20300h L Y @,Dw\_P?Tt au=#Çdl\;[R@J"98rb*Bp[@1c#1=/H ~wvn+fbE,u"a^Ő_?vZ-`Tb@u8rlAr@A<Ȥce`)O< `` F六Gh1 @,(rJXCbG8 u0GC(d‡6B K\ZCk h́h=@PZC?jf1DH!WH XJ%x@ XN6H9MπT:g b8Cr&T3B'lebd@,$d  ه ,K& zdE9@1lBX?ђ/ HHd6n  8KH1D(+Õb Xqd+PE#J |rw1CZa>CȖ# _  z ĈF33buè4#Zc) dCaQ3?bYFyd%cZD@`aai<[3rR{`#e^\(4F e!-1bLȹ €=1uF]O$1W0#( T ̨̰Zu d?DYτ`:#733VF0aEFt$ߋ}@l[ϝ<2AA a|c#B٫7 Onc4fh(1K]gbbbx $@nZÂwQj_~4А@ yGTOIENDB`treesheets-1.0.2/TS/images/nuvola/toolbar/editpaste.png000077500000000000000000000052201352107072600231230ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< "IDATxb?P8=@=`i2900Qdtg`w~A7 _~oF?u/W>ĐC_ 3 S r}A џ^0=OGUVԋw gğAw~33|*a`b`WL@?UIFZ?s#xI^_5Jcb&Pe,@23(=esaM;n/ ABT2AB7cI" ?H3ݷ7 տ T HB< 3Y&PTE- G?0l|1@xc#/i@#^=IIgY$U:Pz㘘@Dφa) PccB3p @0 iP3"<2/иo!yd63ȹ,?-8@! XOqHr$27gPłTHAv nb[H)xaș9V0n@( 3 "CN6e;(ؓ?L1G#\K qIai?\aq8zA_hr<+F ?؎?Fv?!l/̈́G@x? HʔÉI6j9M2@k #>,II6p5P[ 5 gbfz<ؒ_,lV70ȘDKɈ9 xǬk\&@1m2gXN@OZ螇QpMwT`BdbЀ3#F)B@txT0pM @ITRb;O܈$)CEnC-b #/^T_"DĨ#ig!gI>3s03^8c_(  7u z& B1@&dX~"ßfF?ah)ҮBgV^E?l~킖@EpM L+, e?!NئbP h6HW E)okx@ďfQp=q? @h+Q ww \ (!t31?( ,-w 0{+$PC3/]HXЀ{@9SGO$1`v>7hMXaCg Bl!V h{ h' ( dIENDB`treesheets-1.0.2/TS/images/nuvola/toolbar/filenew.png000077500000000000000000000056751352107072600226100ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< OIDATxb?P0@ y 6AFt)}}>_ ̈쬐9ɋnaly& Z3ap]<}ovhkpȿqZ])A Nm`ퟟn \'hKQCJZ0=K%t˳^9,]YD}c0҇KEhy)~mi 7ó ԿeΠx ҩ.3|83 0X7 2K3bP4g`6-~3#0蚲0~+iF9>Q]` K5g{GXY&(a`Ue2gb^eOf5d z ?l2m \E>B1(@@iZwiHzIgP#5g9[8 J w.bn6Ҁ_߀%7&`C L?3((30H( rO1w&vM 9y1g Dt=ݑ=ݫ^p3 :0Y8~uv5a{.PnE'8?;o@ h2d B| = ?L, >c[`Q ^V393?30;3Hp_)YNJC%/~ &|(&&GC0&XX?pП%bIX.s.u2~f~O1*J0/QePXk._]n 3$1CsD(1a?ԌJڟb`ȠR9(N]Á gc8qç +2D\˪>9)Ҧ`g \E%4π%C=INkLBRgE?Cy*` |gvrc(p.x#014G=pyl8+ž4:j0@b0h431 j .21\< ?i%/`qgI Ro1{2s`r4;U @8Y} ,kF6~i0bb8qZAa&o< ?ffDD/``LKdV%76'3_:er p%Nmkd v=^UY tPkAI 3>AWvFsXؘ0f6`F=nci]F?WD" `fK[np!K(b oz79f/7?=9!WT{]_]>2`b> `Jʟ_a/ 3bLؗNL1 ơ .MIENDB`treesheets-1.0.2/TS/images/nuvola/toolbar/fileopen.png000077500000000000000000000051361352107072600227500ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?P8=@C4=@C4=@`0@8r`y!, 4#آT K220+W{v;fA?~L>Y>FR"?"\‡&Ɩea3wys) n0d0zCC]` @_.1QQ!qFvJ*&5IN~U<@<ffffp 1| T_`X,;`C&Yq φa}!+?HGe`M;CH]2ax{SGu>o10bbqpcS%  @}7L4 (f`@A<h~zT:\> :z`>H| G?qIcD xFJp D 1(| !~cx#@xK"H0-L O1| Xh5^C/n.VR=U$*;/gI|ٿ8}3?X~P p+>#X/0 a`a 0= ,@Ud*?C~@ /Ӑ1 *%~ QVRdxIap,} @@,Ȏy$ 1|% n0%DflPyôm7XYYo~2|AAXDHB J;0 M\l0~/ _~Bb _/;RF!(AI h1d@B`TtP%4^V= XZaX H6 *RM0GB* ~3_40MI0<A\tǒ 0(@4 c` | !(.&`++H Buӿ1< ' @1>Cc1:'`^)a)a5?%9H}<@OB' b<Q Jh 7cI:1@(1j,x0##4N@MplACa4I 4IK`B"P"`IH&h @B3%r#TA04C1BiN:'RR  ad@o}Cm&<܆BIB @$Dm*`LzFD[31@JAfv^@-W r<,2LF)@!< Pi*~ *'7@ga @_b9đC;cO_+Ç ,8RJߟZ_@WcK-& w"=.ظDzmg"8.OLqACǗ wd`f*@w`r >@1 @PT +`2`ī/A>UIv1LBШ_yx%% Q/i)a`rdz01}Z $!p/orI ;0__IڳbʬV:Ă P11@b`6#k÷; :?P#3ƇXmc,50}a- @,\0_,g1<~]FE̐AyCc9Z`Ɂ??}`sx't, '2Ťj/ 0A ~&hOА@ yА@ yА@z$QIENDB`treesheets-1.0.2/TS/images/nuvola/toolbar/filesave.png000077500000000000000000000051031352107072600227370ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?P8=@C4=@X=$i00-d``01|ʈOR?π {/ª Xt< 'si)]"o@A2??,h@G0h4W;W1(Kjݬkĸ>%yAR\_GyfCu0##,V` Ԃ? /\dV +?_F)",wg&+uUIN?jvv!!r@abFF.Th0 NJCᾐt@=? ?cCRb P !403 :ߏ lʄ# 1g Q !? u ̈́OA= >#81BdW&^?̿A2.* e$ p(13c$('1? oT4!#\+ \8Jit`eDIP&pXbA8 Hlcz Q`; $ 1Cc?4ȁhfhMB  Fx O>L@A.1Ą3` v#`XZabg"'#ʂ' P8##R' PRHQψ(p@DfDx#$ (FF؇&Q\ pzryPLy?#f@DF9hx"D{5R T e2;<ŗ"{aH$QOĂ E̐Yf$ G'!yAX m&F9F< p'!<E1(/#"X# ˆ@!P̌'oCC5ТggoMdd(&F N8?HU2$ @x?x 0kDFCP,JV mÊL=THR X$"c~@;Ab`CDL-LL&,1+ x pBham>DTGI  8XSQt_Lf9b 7?^}cx,/Ǒ3?,l^/4y%& IԔeQP5l, ?#R?jŗgbֆ0J}1G6Jd#l#AXA@oٰf>(j]#Ci#?%0CǏ 90 #bj/& =2V:y~cw?Q^eϟ3gadjRRYKi7P7TF*XC/ 3A'~ .Nz231/ o$+@C[6,<п_, ?R9߿2=tQ XRBkc"X' 1{b/ßx_` Bz’L @x{drȰ.:?'@qb<? , }P,|c$X/R=DCi 90 cDa >(d pZV ȣdѡ d@AH6r F@Բ'099 ?>RED ^4%@yHhTX*1QI!1\ 1 QX&&I G/ 㗿~̟ '(@t J2` p@R逸?"Ăo.^ G@?FH~ZZrv2y} A%#ߨ= @,E*$D|ȠH7b q '0sK2oX/<4 z_^{%ë 4tIIAFpC^[7 ,  ÷_N}fe`lj0c`B|L?N٧E <| dU#?_WMdb N,L\|ͳe:/$Ӭ4=@C4=@C4=`KU8IENDB`treesheets-1.0.2/TS/images/nuvola/toolbar/filesaveas.png000077500000000000000000000074061352107072600232730ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?PĈ}Ve; Jgd)_LYXGV=0 @,',1|3_]UT.s|!s|Op`d` XSr;E9%9t~`3vpVlB 8e pϒĬj -`YY'3@c pyTFqd)Qf L/  . Md*A6삗 2Fpf`:%ק!ý\1W@8<;MY5٭2A[? /x@213̿A@D Bp@偘㻯 f/gu gj 0=@_```fc(b-M?eo l젌v?@F`1K`!hFÙ F`> #? j*"7220p&- L n` #&y0@="&,0.~iFA;PǣxFVPaiK'b0}C〾: @3nl^Ý% +<2B#jyn ;1ZQĐXW)(a4-ttb`ϰ{_.}Č o;401x=#3< >d=%!.n8PhÏ@)q؀ co$ 3r&FIb  ˨ynOu'3(*fpIlYゴ,?n6B 90L`N1K߂@Gg730h'`VW`0cw0eJNT@j0c)* ;o`p!T (QきWk/1Z$͠`l'H1 jB=@( <KB0C,bǷ@Wp?g6=3;%'c!LX?)`([/-˷g1maê B B ʆ On^   `) =@İɏ 'Z7@Ll=fxm&Cj670 /'8zUrcd0in]? U@ʔx'Vpz跽'bM?fW`G)y Z?v`XI.Ík03X8 Z0 K!@e/fI<3 πmА* c8pa"n% D?n& C2<@xcfSXz: #B&622fdc`~ p uõ[ .G0\R``=Vfg9PbPE@+2}6_3(3Xc R"@6'0`-d*`/n6)/CJ^F3LbxXEK2zj,JB"C` ѿ J< 3@ g>2y2w z% \" w2\>aC.%uyp /Q o03pԋ#T%@O`z?ß K=ypC6Pÿ_w@pz"=CxK ÷EO'V$P F:>`?FVG0G c  fz3,uT.;';0} `ccaxUC3# Qd<Tex É80?v r\ Br y *Fdb÷//0cb(eV0p IFFP򒁓"YTt5>t4='[ K!"Xh /`1؃ ' 3&{ 5-*EWPh7RcV$Wh?(z #1@=?LSJa|)U@G OJG' @F;S2H$hTW})"RrIpwsnf Y{2րWk]wv>}F ˁBs!NZd`e y ~R@L$#6%ba0(^ f 1iX$)!A T9L82 alr<  ; ޽cfFx m\فgaaa8 ql%"@ :İi&~~~˱E9X酰AÇ NNN R{` Hce(9p461P?66v 2l޼g[!bӱj`1 o~3?89 g@ KXfh;WZ ؒ@*qp|5 _ae`dQp^ce&X!d? P AZ  H"\!bW㋺/?0M`Tg`)1@2ï~~1| _eWu321[Al@R B 2*| \ !70C7`H3?O@,ձ%!›0[HOA Pz ((70f~z/gb 7)IyY?3 B 7̐,NB@pB@t$(~w"N&`7IjW@] {h10(XV`?X0ſ12p|ed`q0 ?AGcKBDR=@! L\Kpg/F_8>0A2'@u?233#+0óg`ec'- &ByW<J󠒇 3p02HqeP N> ,m(cxw/3 3,(c*@n[J.jN24+o l\ ?:=.'ǻ~T'9 DCln &\@$%`:T90;(X9޼zXC 'íNM` r1 +A&FbJCl @d@ 11.e01\xX`Id||S1 _^=F? VdJ _32C~%ChZ^!!vv1_ ;f3 /^.`s`c@iJj`KBDU G lUvo~o 70iɏ f7~k |3I @$Ȑ 9Ty ;ԓa@]]R)Y 6a@v$@dǀC@@0sr=hW==]߿I@BJ /?]'O@tj)dfo`?#Jm-pZW`}P{fe9,NPUND `@<*s0##Cѻ_~0fLT+zFx$8c!@T")Kl.G(&]L#4M)1 $UDT1 v'++#IRyT"٘e@蓑a8zÃ[AYbBWf/^d Jؒ@UhHecm a QPWQ]]AQQlzy dK544)yON y:9bQb5*Aɤ9' - 34xPWX{@mQk#߿~*8Sa)Io߾ψ@@`i^lX#R'U>37W<@a[jꤥh'7O>@w$@<#b# bhi5PB`Šg. >@a]MZM`/M6=u8@HMIENDB`treesheets-1.0.2/TS/images/nuvola/toolbar/newgrid.png000077500000000000000000000061431352107072600226050ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?P8=@C4=@Ci?(N_s mʳ20rd,n zz~õ d{13[ s˫1zݻǙZI0 Xh`V`f`6bVeVRb`a8cÝwz:Zk@P& ́i' %. t4ϟ =cu"{ -T1pX;<  .C``b`ccgfTUePgPbNNNf`ϧO9?2 |@yNN_1@@ᗱ TZ:jt`sM͟<#xd5E_tӌ,PY wzp4F(+TD 8}<20j0RTT31e31f47ge{6U ^f ÿOq߿`P=gb9..ޙŘA@<*-h?;;0d?01ad)Fk& Paff 2˱2|K )(D)3[Z23pIH200CB_20 "@3\@Z_@z@a00J:80Xvt <tohXE8h00?I*6 ,rb&WN1$mHOb:/ _00_# (`1vۿ3$#4C݇f'#fy KJX g8@&@_ujy|PcJ/_YY`q @1r~ fee@1(:'3;=-}1)YԠnh('. ;ܟ/_Dyh,$@8=_y}#Jh#B96Ԁ*)9`>d1PM000w`.`SB1@,8ËϜqV'e?`3 z ܌Lj J L ̦$on(" që[̀q_SX2{}3eըP1kjf0Y@1Q1F/_&}C5$sD̐~f61g ? 0}my{\WXxāI/J|j:'36p%c៌,3 @1(C܈RAEA@woo_xB kEe&81v43,=޼v 00^6%Nd` L ρAy  *M~)Y5 z=0Pk70 >۷OS-,ڗ =VPۂ7F`Sһxs 3_-n0 !R&Un.q`+ޅ,x<@x4N+?m|.`;K]:`lePX^A `PcGa0 AqPq?KUx"/om+?`~&P8F5p c-` //" 8"O!Ʀ/e;cs@᪉ϯ_4u&P~v?6vvlL^*JZ=aVS! O hn|:p/ t%x#  @TqP*?!P?>PssU1#O=cJ 9EW0\̢VI XJ222 &!h2:˗߽vCn!(?d 0ٱ`a4!AQ`-%($gCln bK`fdwMb 6!˗ ZAoՂ &`BBFNn5P>30"*@y y-oPEDA OъLpIu4>߿gx9G{ V"z\|}7ld#2fd`~ 8߀F?O`&wn#1l =_}{[Y%!!ԌB_e`wb'` ,.{q}e3ÇW]:XA~yo&FR"X#/?+Y̒ #Cc; ,~cᏲ*O` ɟo0|wбfto6΀,!k HZ5+^BA|&P_@ @0A)z _od'pǻ/;4I6{訟 @ъ4ـc ߡ:3 ?.3@Gyh`zˈ4A  Rc0=$ `29(AҠe@6H_~`N@-H@@::h;'}m JR?c_~/$I:#d FDR?&BԴ t80v2L_,HDqˏL@bYǞ 4+@ yА@ yА@s$)IENDB`treesheets-1.0.2/TS/images/nuvola/toolbar/run.png000077500000000000000000000047761352107072600217640ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb? 0R C ?gF&F|~bA Kq2iqfPUfb+(v( | 8Iaߜ<+nn6AG? l L}1!6,# SXT~^T <ן _303mvdǠ+'Jg@{ 0=3[3P *W:gAE@y 0߁?7`lq11j3,k&@_2z@x ( _ 30~z MY,)A J@X ly@H 02@W_iA9 zȰvEW10900)@ `x~8 0}_~a` ނ<Ȑg/W)8@FK<7$ߐ#$f`8YA&X] {fCT;@ Xp偟 u43AAI Ҽ \ *% .1lpV4y7Py_;PL0+ 10x 1h(2l=ʰwO^=qԾjw@a` %!&"/H> rL >J d8}27X}//x {74 8 XtI003d10lv~ H:Pi!%vby/g(~wAyce.t'8s#p&ga^j>@ b),?@f^~p XZ30YJ2j1=L?^Nd`a d`b: Q X j1R{$VEB]TT%p߾V= HPb lP 2/Q0*(7;CD!my.3|w[U ,b3 f 4 <$0 13pqϞcX*mǕ? WAbiO`,4X!ƏAT`(*bnt% ڌ.lcyj0229k1G1HZX00 35"{"@X= g1Fzf %71VH>#20T{Kx 1v^n6@`5q$!l8?[>+Xׅ Xpv0210>>~I/OB?+LhY=@a(AWyh&`o@fxw&ÿ_tz (]{ l21AD{?U_ށ~Av1nť X Ff fJC0C n?bxp+66GBf`a< m< M`s$0ޅbv >OYĂ^,J ÁpB:$ Տ>g^\Prhqv6@8Pt;o0>~kJK@` ,pek/0T,*WQ@ IxNp%> z7,t 9,uf@զ/>g8;O@?1& ~J Xu@1j́^ͬt{{.29s` u3P5 f(v8޼p`%s/2M ބ&j~+0d@<فmNxȰoiO_2'6PF@aĠbyA#O`رËO@'El `ƃ/=p80 J.ǡ kNـ {`8*חAfO5y!=2lXz jPO f1 n^ϰv'_nfEAmeF&&f`:<=LS1HU=0@i^\lIENDB`treesheets-1.0.2/TS/images/nuvola/toolbar/undo.png000077500000000000000000000051351352107072600221130ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe< IDATxb?###51? @ٳ h@n Fjyx}?6(ܺ /5?+<~!-=@LT |ϟ/߾l`33 g4~e\AasC@1Qd(޼*#Ps<7@1x_(_nϐw/ß? BN)#e 2@$ O %;w칸dfXawpsBIfÇ PgpoMəf/ h@b @ yS C~~hׯ34|ɐ\X`LX*.hEsafs0gx+.x $?v0`Hct(6# HӁ_ /dd@葏x 61g[N&& a%% \BBab?2,8a߽{ H@>|l1 30`:i\pxڵ 3Ξ% I@aܹ 2ZZ  bB#;d[yx0<~aݻwTU,@!x_'߻p&,E)0o H%l.Xa32|,)urB1@(%73bH6|a`ݠh[AzA4>G;8X ̼`sg L{Q`µk[y 0QM1>^/^xi== ŋ ?Bs: Jj &jam&lr@3gnhk?;z qy 0syuyUgxy`a?ABa!  : pY@zTXW Zаcc[pCXq1q.00psc`@)$/k_pr=r4 ߾5y-l1@s$˻V*جURb`␛ꭏRP d!  OÃ8=Ǟ=c㚼W桗J ̦mB_[ V w00\ IH|wMif A9`Y=؎۵sfPT I*F::6R80I13`=0c{߾ɩf뗱w@6$ P=ǀTKLUȱr;@<."//),(^cs]`m *DLp+llw>1;QWea X3 QQX7R"y0U DDr'/\~(#3042\8p=F ..|Z͛-ǯ^uǗ~IZYcЏYY0?~RA7 V{||QW%%?2l:X0107oz-e;7=OIHh0  (!@̝|y難\[ lР6_ UbTA:PHۜLL6D"0I\?zCYٕ*` s تLJLLltP2 *b%$t*c@ԅ υ.|y͇O"ր  @C :1pclA:L 9@VRR^UA! 4bDŽ[Ym`??i vbb2 ~I*u0ੂƒ `O`.,Re S @T,]jAGplY`o/dT0D+P@^ ,6,%04(=KJ/de]]p* ,@m+NIX}@ .^,G4ή@  oC`6S p.TV`p,| F==>  p '#pAPS8w7IIpF[&@C!@(@D`ߚ)@ l +54@C:c{p @C%~qp\701}a耆jj{A Abr:P?SZ<-@WIENDB`treesheets-1.0.2/TS/images/nuvola/toolbar/zoomin.png000077500000000000000000000110571352107072600224610ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@pJ022011a@? mbή @y?~|/_\?@/ _ 0 @,j:ZPNNT999YYYFIII!!!..Nm>|pΝO> 7h.Z@:MKKKԔAUU_ |e/ Oa8z(3C 5c z؁***Vzzz2ii32cxٛ=gfxgf?{ѯ : 2R" ~gسw/Î;^y&hFjy pzf 7Ë7ec8AMA]AFAa`'/0 0 \dŠS3gyf30op`bgb8qaq~Ey>+e)Vf` 6Kwv s3 v ?d2e իW˙(@axfVgg窠 ^.&gv^`s`WaV8\b`ط6 ~Nj ?`w66xիW` BiKUUY_a8\!ڏAY7gEſg`,zL!8X@Ö}y~) *fI0@LH$HǗILK.=-8u<?P5_qz38rASCIXր` &h&5 uU? [/q31pr@B7,tApOA L/hRoc`бgM֍ vv bbbX'<<؀%5P1TJ)1|ax!b&[X@ɘX @`&?21A ͡H0yH,$Uu7 qQ)))Ma 1VVVc` X Fp/R’?$Ò?Lu (c#.)fd;6@sh6!>bbdd`87:LPCP *al>f-!A6 biW%i@ppppHCF>.e(b PiQ? GxpP X|c7]@ *!@ "e 㘙 )a-Eo%9;b}ox c~J_ K-yPY IIȑs31p@}PӀ |`GFFD,B=̌(@~=P0~e%  REv 4e ,}B `MXVhx59A  /gnǏQRC`8>}bd}P`a@Jh91!P /-gW3s3J308AAJɳ .]ξ7P_<%@}1\M^Î3v02 : Ͽ;iyY_!IORZǿ " j_ln[/`[a>1 s #_bB`{C#f_`}!&&~V/5W|Π!}˯10hrr]N?8_Y@Y# v?`G`˯/pO0BٸMfpf5ini+ cDp=5>0*{jϗ&\L7^dg? Ë>P".aqpϿ>JN^3+\O`=fc`du3 Qaz&`aCi=Cn>w2/Pb^ (` i5S0:p:op'kr"pew?T`lvj2 lj&`R_x=?U'RgL bVM fCe_8Y'^D N`T$i^1Ka=#+}{u{( c,5yΜ\KA}D4ciCEL02Q`x|ן ه6fxy 4+_}O ρgĢ812/z"=kPC|Xy= nVnVV5X7o!b 3-+ XTt/H}zk> ~MvO5^!UyY^Ï0= ~ L@֘,'8\[@3>dxa㯅䴁y0p21XYoA<2ц00Z_d`<( p{+jȱq}~qm ܓXە7/ ?0C ~;~/d?:-U ;k[}lIENDB`treesheets-1.0.2/TS/images/nuvola/toolbar/zoomout.png000077500000000000000000000110351352107072600226560ustar00rootroot00000000000000PNG  IHDR00WgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?P8=@pJ022011a@? mbή @y?~|/_\?@/ _ 0 @,j:ZPNNT999YYYFIII!!!..Nm>|pΝO> 7h.Z@:MKKKԔAUU_ |e/ Oa8z(3C 5c z؁***Vzzz2ii32cxٛ=gfxgf?{ѯ : 2R" ~gسw/Î;^y&hFjy pzf 7Ë7ec8AMA]AFAa`'/0 0 \dŠS3gyf30op`bgb8qaq~Ey>+e)Vf` 6Kwv s3 v ?d2e իW˙(@axfVgg窠 ^.&gv^`s`WaV8\b`ط6 ~Nj ?`w66xիW` BiKUUY_a8\!ڏAY7gEſg`,zL!8X@Ö}y~) *fI0@LH$HǗILK.=-8u<?P5_qz38rASCIXր` &h&5 uU? [/q31pr@B7,tApOA L/hRoc`бgM֍ vv bbbX'<<؀%5P1TJ)1|ax!b&[X@ɘX @`&?21A ͡H0yH,$Uu7 qQ)))Ma 1VVVc` X Fp/R’?$Ò?Lu (c#.)fd;6@sh6!>bbdd`87:LPCP *al>f-!A6 biW%i@ppppHCF>.e(b PiQ? GxpP X|c7]@ *!@ "e 㘙 )a-Eo%9;b}ox c~J_ K-yPY IIȑs31p@}PӀ |`GFFD,B=̌(@~=P0~e%  REv 4e ,}B `MXVhx59A  /gnǏQRC`8>}bd}P`a@Jh91!P /-gW3s3?`|ҩS1(1 )B=/FH30hy y&C308AAJɳ .]ξ7P_{޽{544t@~t#`:`H30ϟ?~|[K? < o޾e8t8ÕWOə3 $Iie2H0}Q|lϿmU=vzT_5,bEDf.`t8l[ pg'0/1ܽ{?}[]CTBe_f.`LAChxHݲ 2+b `JcG30KbP% OT(gϧG2>jU~㇏ o?_U?wj<W a'? L 2@Og8qϿv{15n`e?? 2%D 1fabbϢdĦ, _~˟~\/AG웷@ eea>/ELċ`Nl β09fؿPO`@!y `mwfa?[ +#8)} b7P??FFϿ*.`PT &3s0L bVM0 āX )I'Op_?bbo, \`bÍ \&g`@~lZ0AGtw"jlF`abkˢ~3~ =vv~\bڬ>-@Nj=WTĈZ gD=_`>tCλ~~/u_cVL o ]N`2:\bxÁ}߀f1@9/o䨉 2O3\}x[y <.q3a|X-_3j%e`6@,R סË~ ,.(-w }4̐ b`9&+CV:Cj$<0U`x_PrïMqfb搗'Rece:TrlO? l|_ʼnG'M Hp{pfF>- ip2P3ݼׯׁY09假 >@brԤ`fg`g5H.`76h٩zb Oz?|Pc o6l6doMwf @`2[>N>ہ?6r>kR3"A /447t}gY|`dfxrm=k3aj~qĽSJ݋t%t.BA雗Yk3H0bp}XG.qպ<|?DO:^/IZRzl<쐚 ^a ߿^`1  y ;hxIefhȯV099IENDB`treesheets-1.0.2/TS/images/render/000077500000000000000000000000001352107072600167425ustar00rootroot00000000000000treesheets-1.0.2/TS/images/render/line_ne.png000077500000000000000000000003351352107072600210650ustar00rootroot00000000000000PNG  IHDRgAMA a pHYsj։ tEXtSoftwarepaint.net 4.0.5e2e[IDAT(Su !]?=Vu`!\$Kv) "B{oI_0|]co R(NUXkADҩƅSJ;PvIENDB`treesheets-1.0.2/TS/images/render/line_nw.png000077500000000000000000000003401352107072600211030ustar00rootroot00000000000000PNG  IHDRgAMA a pHYsktEXtSoftwarepaint.net 4.0.5e2e^IDAT(Su {@F"f4Iȭ T9gv)"`fY/lR jKL7d0zƳP }s =7_9,\FIENDB`treesheets-1.0.2/TS/images/render/line_se.png000077500000000000000000000003331352107072600210700ustar00rootroot00000000000000PNG  IHDRgAMA a pHYsktEXtSoftwarepaint.net 4.0.5e2eYIDAT(Su 0;5hx $/Ɩ8"ť^d we""Z1`f+RUW<+rg 3rWhDIENDB`treesheets-1.0.2/TS/images/render/line_sw.png000077500000000000000000000003371352107072600211160ustar00rootroot00000000000000PNG  IHDRgAMA a pHYsktEXtSoftwarepaint.net 4.0.5e2e]IDAT(Su !]oj6=x^"$K@)%XQ1ѡ9'ޖAUQkzXkY GDoO5sl}νc RsSlIENDB`treesheets-1.0.2/TS/images/treesheets.svg000066400000000000000000003564341352107072600203760ustar00rootroot00000000000000 image/svg+xml a derivation by Deevad ofBinaree Tree by TheStructorr on https://openclipart.org/detail/216277/binary-tree treesheets-1.0.2/TS/images/webalys/000077500000000000000000000000001352107072600171315ustar00rootroot00000000000000treesheets-1.0.2/TS/images/webalys/readme.txt000077500000000000000000000001411352107072600211260ustar00rootroot00000000000000These icons are thanks to: http://www.webalys.com/design-interface-application-framework.php treesheets-1.0.2/TS/images/webalys/toolbar/000077500000000000000000000000001352107072600205735ustar00rootroot00000000000000treesheets-1.0.2/TS/images/webalys/toolbar/editcopy.png000077500000000000000000000004361352107072600231270ustar00rootroot00000000000000PNG  IHDRVΎWsRGBgAMA a pHYsodtEXtSoftwarePaint.NET v3.5.5IIDAT8Oc`+600{QA H1 fp 0\v '$$Cp5 Bg@6 &l54a!d6k70p.ߑFxS841$AA!&]a8r?OrQ >E}!HIENDB`treesheets-1.0.2/TS/images/webalys/toolbar/editpaste.png000077500000000000000000000004601352107072600232660ustar00rootroot00000000000000PNG  IHDRVΎWsRGBgAMA a pHYsodtEXtSoftwarePaint.NET v3.5.5IIDAT8OՓ EhMj`Pd8#fD#>tP P1րR iJ;'Qs&: P&G ẑw'#-)=*NawVkOsi$nIENDB`treesheets-1.0.2/TS/images/webalys/toolbar/filenew.png000077500000000000000000000004011352107072600227300ustar00rootroot00000000000000PNG  IHDRVΎWsRGBgAMA a pHYsodtEXtSoftwarePaint.NET v3.5.5IqIDAT8Oc`4'y$1&B^[c^}RJpb}""  k,B*1,Z$a 1GM"7̨~/~_C($m1n `(Sެ{]g kqxqIENDB`treesheets-1.0.2/TS/images/webalys/toolbar/image.png000077500000000000000000000004761352107072600223750ustar00rootroot00000000000000PNG  IHDRVΎWsRGBgAMA a pHYsodtEXtSoftwarePaint.NET v3.5.5IIDAT8OՓ} ' H@Rp$  H@Boḭk#!!=>vawuP R{O)Aڡpf8RJ Pq5{1 6{-ƃ@wR&9jֺ[BwHGAlGfM OAWv"0IENDB`treesheets-1.0.2/TS/images/webalys/toolbar/newgrid.png000077500000000000000000000003331352107072600227420ustar00rootroot00000000000000PNG  IHDRVΎWsRGBgAMA a pHYsodtEXtSoftwarePaint.NET v3.5.5IKIDAT8Oc`&4l񤊃 a$UJ\T2^#5vp5B;kx!D 1ҸIENDB`treesheets-1.0.2/TS/images/webalys/toolbar/run.png000077500000000000000000000005651352107072600221160ustar00rootroot00000000000000PNG  IHDRVΎWsRGBgAMA a pHYsodtEXtSoftwarePaint.NET v3.5.5IIDAT8Oœ  QQiZBPy?PӪy9ƘܸPDCB۶ 5֚}/?T#!2Y|װRB(bխ4ysJxg*F .,-.+D|2nj !dvM={kIp5~ oDb<ѣvbܕP[ T=/q eidf4F\<y&IENDB`treesheets-1.0.2/TS/images/webalys/toolbar/undo.png000077500000000000000000000005061352107072600222520ustar00rootroot00000000000000PNG  IHDRVΎWsRGBgAMA a pHYsodtEXtSoftwarePaint.NET v3.5.5IIDAT8Oa ' H@  H@ z,Q-Y6}qpΑސ,Z{I)5zq H C1BcbݠUwaAOoE?+s(Z2e"PfBPJ<A2z4s+Aw]iQَ7ٟy1rIENDB`treesheets-1.0.2/TS/images/webalys/toolbar/zoomin.png000077500000000000000000000005561352107072600226250ustar00rootroot00000000000000PNG  IHDRVΎWsRGBgAMA a pHYsodtEXtSoftwarePaint.NET v3.5.5IIDAT8O E z%!LI>>uy֒ܶR(/a ÐRJZSƍ1m E˲C1Ts.+?|89!˜(c 5 p?*赧f6zHT;;<؀&"ܔ.䶖p+w9%ixRx]IENDB`treesheets-1.0.2/TS/images/webalys/toolbar/zoomout.png000077500000000000000000000005451352107072600230240ustar00rootroot00000000000000PNG  IHDRVΎWsRGBgAMA a pHYsodtEXtSoftwarePaint.NET v3.5.5IIDAT8O E$  HL$L$ Opwe,!e_}ZKyr8HJJ߶mC" {c H1CeιkknL Gb:U=[nWVL=6` Hi rgKN8W7U^0)1([OIENDB`treesheets-1.0.2/TS/readme.html000077500000000000000000000203561352107072600163520ustar00rootroot00000000000000 TreeSheets

Open Source
Free Form Data Organizer
(Hierarchical Spreadsheet)

D O W N L O A D :
Windows (xp, vista, 7, 8, 10)
Mac OS X (10.7+)
Linux
From your package manager (e.g. Ubuntu or pkgs.org) or directly from this site 64bit 32bit
 

A "hierarchical spreadsheet" that is a great replacement for spreadsheets, mind mappers, outliners, PIMs, text editors and small databases.

Suitable for any kind of data organization, such as todo lists, calendars, project management, brainstorming, organizing ideas, planning, requirements gathering, presentation of information, etc.

It's like a spreadsheet, immediately familiar, but much more suitable for complex data because it's hierarchical.
It's like a mind mapper, but more organized and compact.
It's like an outliner, but in more than one dimension.
It's like a text editor, but with structure.

Have a quick look at what the application looks like on the screenshots page, see how easy it is to use in the tutorial, then give it a download (above). A video someone made (on Linux)

TreeSheets is exceptionally small & fast, so can sit in your system tray at all times: with several documents loaded representing the equivalent of almost 100 pages of text, it uses only 5MB of memory on Windows 7 (!)

TreeSheets is free & open source. Enjoy!

Visit this google group for discussion, and news updates / releases. Grab the source code from GitHub, or file bug reports there / feature requests there. The past donations page. contact the author (Wouter van Oortmerssen) personally.


 

treesheets-1.0.2/TS/scripts/000077500000000000000000000000001352107072600157055ustar00rootroot00000000000000treesheets-1.0.2/TS/scripts/Export JSON.lobster000066400000000000000000000026061352107072600213200ustar00rootroot00000000000000// This example script exports a TreeSheet to JSON. // That is not super-useful, since TreeSheets are not key-value pairs, so instead it exports // as a recursive set of arrays with strings instead. // In each array, the first element is cell text, followed by the children. // For each child, if it has no children it is just a string, otherwise an array. // Also a nice simple example on how to recurse through a TreeSheet in script. import std let out = [] def add_text(s): out.push("\"") // \ and " need to be escaped in JSON: out.push(escape_string(s, "\\\"", "\\", "")) out.push("\"") def add_indent(indent): out.push(concat_string(map(indent): " ", "")) def add_cell(indent): out.push("[\n") add_indent(indent + 1) add_text(ts_get_text()) for(ts_num_children()) i: out.push(",\n") ts_goto_child(i) add_indent(indent + 1) if ts_num_children(): add_cell(indent + 1) else: add_text(ts_get_text()) ts_goto_parent() out.push("\n") add_indent(indent) out.push("]") let fn = ts_get_filename_from_user(true) if fn.length: ts_goto_root() // This the default, here just for clarity. add_cell(0) let ok = write_file(fn, concat_string(out, "")) ts_set_status_message("JSON export " + if ok: "succesful" else: "failed!") else: ts_set_status_message("export cancelled") treesheets-1.0.2/TS/scripts/modules/000077500000000000000000000000001352107072600173555ustar00rootroot00000000000000treesheets-1.0.2/TS/scripts/modules/color.lobster000066400000000000000000000031301352107072600220640ustar00rootroot00000000000000// standard color constants struct color: red:float green:float blue:float alpha:float let color_black = color { 0, 0, 0, 1 } let color_white = color { 1, 1, 1, 1 } let color_red = color { 1, 0, 0, 1 } let color_green = color { 0, 1, 0, 1 } let color_blue = color { 0, 0, 1, 1 } let color_yellow = color { 1, 1, 0, 1 } let color_cyan = color { 0, 1, 1, 1 } let color_pink = color { 1, 0, 1, 1 } let color_grey = color { 0.5, 0.5, 0.5, 1 } let color_dark_grey = color { 0.25, 0.25, 0.25, 1 } let color_light_grey = color { 0.75, 0.75, 0.75, 1 } let color_lightest_grey = color { 0.9, 0.9, 0.9, 1 } let color_darkest_grey = color { 0.1, 0.1, 0.1, 1 } let color_light_red = color { 1, 0.5, 0.5, 1 } let color_light_green = color { 0.5, 1, 0.5, 1 } let color_light_blue = color { 0.5, 0.5, 1, 1 } let color_light_yellow = color { 1, 1, 0.5, 1 } let color_light_cyan = color { 0.5, 1, 1, 1 } let color_light_pink = color { 1, 0.5, 1, 1 } let color_dark_red = color { 0.5, 0, 0, 1 } let color_dark_green = color { 0, 0.5, 0, 1 } let color_dark_blue = color { 0, 0, 0.5, 1 } let color_olive = color { 0.5, 0.5, 0, 1 } let color_teal = color { 0, 0.5, 0.5, 1 } let color_purple = color { 0.5, 0, 0.5, 1 } let color_orange = color { 1, 0.5, 0, 1 } let color_chartreuse = color { 0.5, 1, 0, 1 } let color_springgreen = color { 0, 1, 0.5, 1 } let color_dodgerblue = color { 0, 0.5, 1, 1 } let color_pink_violet = color { 1, 0, 0.5, 1 } let color_blue_violet = color { 0.5, 0, 1, 1 } treesheets-1.0.2/TS/scripts/modules/std.lobster000066400000000000000000000115631352107072600215510ustar00rootroot00000000000000// standard functions that build on top of the builtin functions useful for almost all programs def map(xs, fun): let r = vector_reserve(typeof return, xs.length) for(xs) x, i: r.push(fun(x, i)) return r def map2(xs, ys, fun): return map xs.length: fun(xs[_], ys[_]) def filter(xs, fun): let r = [] for(xs) x, i: if fun(x, i): r.push(x) return r def filter_indices(l, f): let r = [] for(l) x, i: if f(x, i): r.push(i) return r def partition(xs, fun): let t = [] let f = [] for(xs) x, i: if fun(x, i): t.push(x) else: f.push(x) return t, f def exists(xs, fun): for(xs) x, i: if fun(x, i): return true return false def forever(fun): while true: fun() def fold(xs, acc, fun): for xs: acc = fun(acc, _) return acc def fold2(xs, acc1, acc2, fun): for xs: acc1, acc2 = fun(acc1, acc2, _) return acc1, acc2 def reduce(xs, fun): assert xs.length var acc = xs[0] for(xs.length - 1) i: acc = fun(acc, xs[i + 1]) return acc def connect(xs, fun): return map(max(0, xs.length - 1)) i: fun(xs[i], xs[i + 1]) def reduce_reverse(xs, fun): assert xs.length var acc = xs[xs.length - 1] for(xs.length - 1) i: acc = fun(xs[xs.length - i - 2], acc) return acc def find(xs, fun): for(xs) x, i: if fun(x): return i return -1 // return element for which fun returns biggest float value. def find_best(xs, fun): var best = 0.0 var i = -1 for(xs) x, j: let v = fun(x) if i < 0 or v > best: i = j best = v return i def weighted_pick(list, zero, wf): // FIXME: "zero" is an easy way to select float vs int implementation, explicit template // arg would be better here. let weights = fold(list, zero): _a + wf(_x) assert(weights) let pick = if zero is float: rnd_float() * weights else: rnd(weights) // compile-time if! var acc = zero for(list) x: acc += wf(x) if pick < acc: return x return list.top() // just in case float precision causes pick == weights. def sum(xs): return fold(xs, 0): _x + _y def product(xs): return fold(xs, 1): _x * _y def zip(xs, ys): return map xs.length: [ xs[_], ys[_] ] def reverse(xs, fun): for(xs.length) i: fun(xs[xs.length - i - 1]) def reverse_list(xs): return map(xs.length) i: xs[xs.length - i - 1] def flatten(xs): return fold(xs, []): append(_a, _b) def split(l, f): let r = [ [], [] ] for(l) e: r[f(e)].push(e) return r def qsort(xs, lt): if xs.length <= 1: return xs else: let pivot = xs[0] let tail = xs.slice(1, -1) let f1, f2 = tail.partition(): lt(_, pivot) return append(append(qsort(f1, lt), [ pivot ]), qsort(f2, lt)) def qsort_in_place(xs, lt): def rec(s, e): let l = e - s if l > 1: let pivot = xs[s] var sp = s + 1 var ep = e while sp < ep: let c = xs[sp] if lt(c, pivot): xs[sp - 1] = xs[sp] sp++ else: xs[sp] = xs[--ep] xs[ep] = c xs[--sp] = pivot rec(s, sp) rec(ep, e) rec(0, xs.length) def insertion_sort(xs, lt): for(xs) key, i: if i: var j = i while j > 0 and lt(key, xs[j - 1]): xs[j--] = xs[j - 1] xs[j] = key def insert_ordered(xs, x, lt): for(xs) key, i: if lt(x, key): xs.insert(i, x) return xs.push(x) def randomize(xs): // Simple and effective, but remove makes it expensive. for(xs.length) i: xs.push(xs.remove(rnd(xs.length - i))) return xs def nest_if(c, nest, with): if c: nest(with) else: with() def return_after(v, f): f() return v def do(f): f() // Useful to create a scope where there is none. def for_bias (num, bias, fun): for num: fun(_ + bias) def for_scale(num, scale, fun): for num: fun(_ * scale) def for_range (a, b, fun): for_bias(b - a, a, fun) def for_range_incl(a, b, fun): for_bias(b - a + 1, a, fun) // HOFs that work on other HOFs: def collect(hof): let list = [] hof(): list.push(_) return list // coroutine utility functions: // generally don't want to use this function, since you'd be better off directly using the HOF the coroutine is based on def coroutine_for(co, f): while co.active: f(co.return_value) co.resume return co.return_value // builtin for() made useful as a coroutine. def cofor(n, f): for n: f(_) // error checking def fatal_exit(msg): print "fatal error: " + msg return from program def fatal(msg): if msg: fatal_exit(msg) def check(val, msg): if !val: fatal_exit(msg) else: return val treesheets-1.0.2/TS/scripts/modules/stdtype.lobster000066400000000000000000000011441352107072600224450ustar00rootroot00000000000000// These types have special status in the language, as they can be used with // mathematical operators, and many builtin functions. // This file is automatically included in each Lobster program (!) // vector types: generally for float or int elements, but useful for other types as well. struct xy: x:T y:T struct xyz : xy z:T struct xyzw : xyz w:T // Specialized type names: struct xy_f = xy struct xy_i = xy struct xyz_f = xyz struct xyz_i = xyz struct xyzw_f = xyzw struct xyzw_i = xyzw // Booleans are an enum. enum bool: false true treesheets-1.0.2/TS/scripts/modules/vec.lobster000066400000000000000000000065621352107072600215370ustar00rootroot00000000000000// utility functions and constants for working with vectors import std // Convenient constants: let xy_0 = xy { 0.0, 0.0 } let xy_1 = xy { 1.0, 1.0 } let xy_h = xy { 0.5, 0.5 } let xy_x = xy { 1.0, 0.0 } let xy_y = xy { 0.0, 1.0 } let xyz_0 = xyz { 0.0, 0.0, 0.0 } let xyz_1 = xyz { 1.0, 1.0, 1.0 } let xyz_x = xyz { 1.0, 0.0, 0.0 } let xyz_y = xyz { 0.0, 1.0, 0.0 } let xyz_z = xyz { 0.0, 0.0, 1.0 } let xyzw_0 = xyzw { 0.0, 0.0, 0.0, 0.0 } let xyzw_1 = xyzw { 1.0, 1.0, 1.0, 1.0 } let xyzw_x = xyzw { 1.0, 0.0, 0.0, 0.0 } let xyzw_y = xyzw { 0.0, 1.0, 0.0, 0.0 } let xyzw_z = xyzw { 0.0, 0.0, 1.0, 0.0 } let xyzw_w = xyzw { 0.0, 0.0, 0.0, 1.0 } // int versions let xy_0i = xy { 0, 0 } let xy_1i = xy { 1, 1 } let xy_xi = xy { 1, 0 } let xy_yi = xy { 0, 1 } let xyz_0i = xyz { 0, 0, 0 } let xyz_1i = xyz { 1, 1, 1 } let xyz_xi = xyz { 1, 0, 0 } let xyz_yi = xyz { 0, 1, 0 } let xyz_zi = xyz { 0, 0, 1 } let xyzw_0i = xyzw { 0, 0, 0, 0 } let xyzw_1i = xyzw { 1, 1, 1, 1 } let xyzw_xi = xyzw { 1, 0, 0, 0 } let xyzw_yi = xyzw { 0, 1, 0, 0 } let xyzw_zi = xyzw { 0, 0, 1, 0 } let xyzw_wi = xyzw { 0, 0, 0, 1 } let cardinal_directions = [ xy { 0, -1 }, xy { 1, 0 }, xy { 0, 1 }, xy { -1, 0 } ] let diagonal_directions = [ xy { -1, -1 }, xy { 1, 1 }, xy { 1, -1 }, xy { -1, 1 } ] let positive_directions = [ xy { 0, 0 }, xy { 1, 0 }, xy { 1, 1 }, xy { 0, 1 } ] let octant_directions = [ xyz { 1, 1, 1 }, xyz { 1, 1, -1 }, xyz { 1, -1, 1 }, xyz { 1, -1, -1 }, xyz { -1, 1, 1 }, xyz { -1, 1, -1 }, xyz { -1, -1, 1 }, xyz { -1, -1, -1 }, ] // shorten vectors, e.g. xyz -> xy // FIXME: subtyping should take care of this def xy(v): return xy { v.x, v.y } def xyz(v): return xyz { v.x, v.y, v.z } // lengthen vectors def xyz(v, z): return xyz { v.x, v.y, z } def xyzw(v, w): return xyzw { v.x, v.y, v.z, w } // create from vector types def xy_v(v): return xy { v[0], v[1] } def xyz_v(v): return xyz { v[0], v[1], v[2] } def xyzw_v(v): return xyzw { v[0], v[1], v[2], v[3] } // or to vector def v_xy(v): return [ v.x, v.y ] def v_xyz(v): return [ v.x, v.y, v.z ] def v_xyzw(v): return [ v.x, v.y, v.z, v.w ] // flip vectors def yx(v): return xy { v.y, v.x } def zyx(v): return xyz { v.z, v.y, v.x } def wzyx(v): return xyzw { v.w, v.z, v.y, v.x } def xy_rnd(): return xy { rnd_float(), rnd_float() } def xyz_rnd(): return xyz { rnd_float(), rnd_float(), rnd_float() } def xyzw_rnd(): return xyzw { rnd_float(), rnd_float(), rnd_float(), rnd_float() } def xy_rnd_norm(): return normalize(xy_rnd() - 0.5) def xyz_rnd_norm(): return normalize(xyz_rnd() - 0.5) def xy_rndi(n): return xy { rnd(n.x), rnd(n.y) } def xyz_rndi(n): return xyz { rnd(n.x), rnd(n.y), rnd(n.x) } def forxy(v, fun): for(v.y) y: for(v.x) x: fun(xy { x, y }) def foryx(v, fun): for(v.x) x: for(v.y) y: fun(xy { x, y }) def mapxy(v, fun): return map(v.y) y: map(v.x) x: fun(xy { x, y }) def forxyz(v, fun): for(v.z) z: for(v.y) y: for(v.x) x: fun(xyz { x, y, z }) def mapxyz(v, fun): return map(v.z) z: map(v.y) y: map(v.x) x: fun(xyz { x, y, z }) def vecfromyawpitch(yaw, pitch, move, strafe): return (xyz(sincos(yaw + 90/* start from Y rather than X axis */), 0.0) * cos(pitch) + xyz_z * sin(pitch)) * move + xyz(sincos(yaw), 0.0) * strafe def rotate2D(v, angle): let s = sin(-angle) let c = cos(-angle) return xy { v.x * c + v.y * s, v.y * c - v.x * s } treesheets-1.0.2/TS/translations/000077500000000000000000000000001352107072600167375ustar00rootroot00000000000000treesheets-1.0.2/TS/translations/de/000077500000000000000000000000001352107072600173275ustar00rootroot00000000000000treesheets-1.0.2/TS/translations/de/compile.bat000066400000000000000000000000411352107072600214420ustar00rootroot00000000000000msgfmt --output-file=ts.mo ts.po treesheets-1.0.2/TS/translations/de/ts.mo000066400000000000000000000007111352107072600203110ustar00rootroot00000000000000,<P QV_&Close CTRL+wProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2017-03-26 16:49-0700 PO-Revision-Date: 2017-03-26 16:52-0700 Last-Translator: Language-Team: German Language: de MIME-Version: 1.0 Content-Type: text/plain; charset=CP1252 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); &Schliessen CTRL+wtreesheets-1.0.2/TS/translations/de/ts.po000066400000000000000000000573461352107072600203340ustar00rootroot00000000000000# German translations for PACKAGE package # German translation for PACKAGE. # Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # , 2017. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-03-26 16:49-0700\n" "PO-Revision-Date: 2017-03-26 16:52-0700\n" "Last-Translator: \n" "Language-Team: German\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CP1252\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: myframe.h:1028 #, c-format msgid "" "%s\n" "has been modified on disk by another program / computer:\n" "Would you like to discard your changes and re-load from disk?" msgstr "" #: myframe.h:504 msgid "&About..." msgstr "" #: myframe.h:270 msgid "&Add Cell Text as Tag" msgstr "" #: myframe.h:298 msgid "&Add Image" msgstr "" #: myframe.h:374 msgid "&Browsing..." msgstr "" #: myframe.h:501 msgid "&Clear Views" msgstr "" #: myframe.h:169 msgid "&Close\tCTRL+w" msgstr "&Schliessen\tCTRL+w" #: myframe.h:145 msgid "&Comma delimited text (CSV)..." msgstr "" #: myframe.h:330 msgid "&Copy\tCTRL+c" msgstr "" #: myframe.h:491 msgid "&Data" msgstr "" #: myframe.h:188 msgid "&Decrease text size (SHIFT+mousewheel)\tSHIFT+PGDN" msgstr "" #: myframe.h:340 msgid "&Delete After\tDEL" msgstr "" #: myframe.h:518 msgid "&Edit" msgstr "" #: myframe.h:182 msgid "&Exit\tCTRL+q" msgstr "" #: myframe.h:517 msgid "&File" msgstr "" #: myframe.h:292 msgid "&Flatten" msgstr "" #: myframe.h:386 msgid "&Go To Next Search Result\tF3" msgstr "" #: myframe.h:371 msgid "&Grid Reorganization..." msgstr "" #: myframe.h:135 msgid "&HTML (Tables+Styling)..." msgstr "" #: myframe.h:527 msgid "&Help" msgstr "" #: myframe.h:288 msgid "&Hierarchify" msgstr "" #: myframe.h:495 msgid "&Horizontal View" msgstr "" #: myframe.h:149 msgid "&Image..." msgstr "" #: myframe.h:373 msgid "&Images..." msgstr "" #: myframe.h:187 msgid "&Increase text size (SHIFT+mousewheel)\tSHIFT+PGUP" msgstr "" #: myframe.h:348 msgid "&Insert New Grid\tCTRL+g" msgstr "" #: myframe.h:350 msgid "&Insert New Grid\tINS" msgstr "" #: myframe.h:372 msgid "&Layout && Render Style..." msgstr "" #: myframe.h:500 msgid "&Mark as" msgstr "" #: myframe.h:167 msgid "&New\tCTRL+n" msgstr "" #: myframe.h:168 msgid "&Open...\tCTRL+o" msgstr "" #: myframe.h:492 msgid "&Operation" msgstr "" #: myframe.h:521 msgid "&Options" msgstr "" #: myframe.h:332 msgid "&Paste\tCTRL+v" msgstr "" #: myframe.h:177 msgid "&Print...\tCTRL+p" msgstr "" #: myframe.h:522 msgid "&Program" msgstr "" #: myframe.h:170 msgid "&Recent files" msgstr "" #: myframe.h:337 msgid "&Redo\tCTRL+y" msgstr "" #: myframe.h:271 msgid "&Remove Cell Text from Tags" msgstr "" #: myframe.h:301 msgid "&Remove Image(s)" msgstr "" #: myframe.h:387 msgid "&Replace in Current Selection\tCTRL+h" msgstr "" #: myframe.h:388 msgid "&Replace in Current Selection & Jump Next\tCTRL+j" msgstr "" #: myframe.h:189 msgid "&Reset text sizes\tSHIFT+CTRL+s" msgstr "" #: myframe.h:266 msgid "&Reset text styles\tSHIFT+CTRL+r" msgstr "" #: myframe.h:488 msgid "&Roundness of grid borders..." msgstr "" #: myframe.h:499 msgid "&Run" msgstr "" #: myframe.h:171 msgid "&Save\tCTRL+s" msgstr "" #: myframe.h:299 msgid "&Scale Image" msgstr "" #: myframe.h:519 msgid "&Search" msgstr "" #: myframe.h:385 msgid "&Search\tCTRL+f" msgstr "" #: myframe.h:370 msgid "&Selection..." msgstr "" #: myframe.h:272 msgid "&Set Cell Text to tag (use CTRL+RMB)" msgstr "" #: myframe.h:450 msgid "&Set Custom Color From Cell BG" msgstr "" #: myframe.h:190 msgid "&Shrink text of all sub-grids\tSHIFT+CTRL+m" msgstr "" #: myframe.h:278 msgid "&Transpose\tSHIFT+CTRL+t" msgstr "" #: myframe.h:336 msgid "&Undo\tCTRL+z" msgstr "" #: myframe.h:496 msgid "&Vertical View" msgstr "" #: myframe.h:520 msgid "&View" msgstr "" #: myframe.h:353 msgid "&Wrap in new parent\tF9" msgstr "" #: myframe.h:132 msgid "&XML..." msgstr "" #: document.h:1013 msgid "1:1 scale restored." msgstr "" #: system.h:197 msgid "A temporary autosave file exists, would you like to load it instead?" msgstr "" #: myframe.h:570 msgid "Add Image" msgstr "" #: myframe.h:352 msgid "Adds a grid to the selected cell" msgstr "" #: myframe.h:298 msgid "Adds an image to the selected cell" msgstr "" #: myframe.h:476 msgid "Auto reload documents" msgstr "" #: myframe.h:479 msgid "Automatically export a .html on every save" msgstr "" #: system.h:198 msgid "Autosave load" msgstr "" #: myframe.h:473 msgid "Autosave to .tmp" msgstr "" #: myframe.h:486 msgid "Black and white toolbar icons" msgstr "" #: myframe.h:588 msgid "Border " msgstr "" #: myframe.h:322 msgid "Bubble Style Rendering\tALT+8" msgstr "" #: document.h:1493 msgid "Can only move this cell into an Nx1 or 1xN grid." msgstr "" #: document.h:1660 msgid "" "Can't sort: make a 1xN selection to indicate what column to sort on, and " "what rows to affect" msgstr "" #: document.h:601 msgid "Cancel" msgstr "" #: myframe.h:257 msgid "Cancel text edits\tESC" msgstr "" #: system.h:266 msgid "Cannot decompress file." msgstr "" #: document.h:1611 msgid "Cannot drag & drop more than 1 file." msgstr "" #: document.h:655 msgid "" "Cannot export grid that is not flat (zoom the view to the desired grid, and/" "or use Flatten)." msgstr "" #: document.h:1455 msgid "Cannot find file." msgstr "" #: document.h:1448 msgid "Cannot launch browser for this link." msgstr "" #: document.h:1491 msgid "Cannot move this cell up in the hierarchy." msgstr "" #: system.h:211 msgid "Cannot open file." msgstr "" #: system.h:237 msgid "Cannot tell/seek document?" msgstr "" #: myframe.h:582 msgid "Cell " msgstr "" #: myframe.h:448 msgid "Change a key binding..." msgstr "" #: myframe.h:300 msgid "Change the image size if it is too big or too small" msgstr "" #: document.h:600 msgid "Changes have been made, are you sure you wish to continue?" msgstr "" #: document.h:829 msgid "Choose CSV file to write" msgstr "" #: document.h:826 msgid "Choose HTML file to write" msgstr "" #: document.h:828 msgid "Choose PNG file to write" msgstr "" #: document.h:827 msgid "Choose Text file to write" msgstr "" #: document.h:711 msgid "Choose TreeSheets file to save:" msgstr "" #: document.h:824 msgid "Choose XML file to write" msgstr "" #: document.h:419 msgid "Column width decreased." msgstr "" #: document.h:419 msgid "Column width increased." msgstr "" #: myframe.h:158 msgid "Comma delimited text (CSV)..." msgstr "" #: myframe.h:289 msgid "" "Convert an NxN grid with repeating elements per column into an 1xN grid with " "hierarchy, useful to convert data from spreadsheets" msgstr "" #: myframe.h:563 msgid "Copy (CTRL+c)" msgstr "" #: myframe.h:331 msgid "Copy As Continuous Text" msgstr "" #: system.h:243 msgid "Corrupt PNG header." msgstr "" #: system.h:295 msgid "Corrupt block header." msgstr "" #: myframe.h:471 msgid "Create .bak files" msgstr "" #: myframe.h:354 msgid "Creates a new level of hierarchy around the current selection" msgstr "" #: myframe.h:329 msgid "Cu&t\tCTRL+x" msgstr "" #: myframe.h:238 msgid "Cursor Left\tLEFT" msgstr "" #: myframe.h:239 msgid "Cursor Right\tRIGHT" msgstr "" #: myframe.h:193 msgid "Decrease column width (ALT+mousewheel)\tALT+PGDN" msgstr "" #: myframe.h:197 msgid "Decrease column width (no sub grids)\tCTRL+ALT+PGDN" msgstr "" #: myframe.h:343 msgid "Delete Before\tBACK" msgstr "" #: myframe.h:341 msgid "" "Deletes the column of cells after the selected grid line, or the row below" msgstr "" #: myframe.h:344 msgid "" "Deletes the column of cells before the selected grid line, or the row above" msgstr "" #: document.h:601 msgid "Discard Changes" msgstr "" #: system.h:361 #, c-format msgid "Edited %s" msgstr "" #: document.h:1503 msgid "Empty strings cannot be tags." msgstr "" #: myframe.h:251 msgid "End of line of text\tEND" msgstr "" #: myframe.h:253 msgid "End of text\tCTRL+END" msgstr "" #: myframe.h:255 msgid "Enter/exit text edit mode\tENTER" msgstr "" #: myframe.h:256 msgid "Enter/exit text edit mode\tF2" msgstr "" #: document.h:671 msgid "Error exporting file!" msgstr "" #: myframe.h:105 msgid "Error loading core data file (TreeSheets not installed correctly?)" msgstr "" #: document.h:667 msgid "Error writing PNG file!" msgstr "" #: document.h:165 msgid "Error writing TreeSheets file! (try saving under new filename)." msgstr "" #: document.h:672 msgid "Error writing to file!" msgstr "" #: document.h:167 msgid "Error writing to file." msgstr "" #: document.h:803 msgid "Evaluation finished." msgstr "" #: myframe.h:179 msgid "Export &view as" msgstr "" #: document.h:646 msgid "Export cancelled." msgstr "" #: myframe.h:146 msgid "" "Export the current view as CSV. Good for spreadsheets and databases. Only " "works on grids with no sub-grids (use the Flatten operation first if need be)" msgstr "" #: myframe.h:139 msgid "" "Export the current view as HTML as nested headers, suitable for importing " "into Word's outline mode" msgstr "" #: myframe.h:136 msgid "" "Export the current view as HTML using nested tables, that will look somewhat " "like the TreeSheet" msgstr "" #: myframe.h:133 msgid "" "Export the current view as XML (which can also be reimported without losing " "structure)" msgstr "" #: myframe.h:150 msgid "" "Export the current view as an image. Useful for faithfull renderings of the " "TreeSheet, and programs that don't accept any of the above options" msgstr "" #: myframe.h:142 msgid "" "Export the current view as tree structured text, using spaces for each " "indentation level. Suitable for importing into mindmanagers and general text " "programs" msgstr "" #: myframe.h:227 msgid "Extend Selection Down\tSHIFT+DOWN" msgstr "" #: myframe.h:228 msgid "Extend Selection Full Columns" msgstr "" #: myframe.h:229 msgid "Extend Selection Full Rows" msgstr "" #: myframe.h:224 myframe.h:243 msgid "Extend Selection Left\tSHIFT+LEFT" msgstr "" #: myframe.h:225 myframe.h:244 msgid "Extend Selection Right\tSHIFT+RIGHT" msgstr "" #: myframe.h:226 msgid "Extend Selection Up\tSHIFT+UP" msgstr "" #: myframe.h:245 msgid "Extend Selection Word Left\tSHIFT+CTRL+LEFT" msgstr "" #: myframe.h:246 msgid "Extend Selection Word Right\tSHIFT+CTRL+RIGHT" msgstr "" #: myframe.h:248 msgid "Extend Selection to End\tSHIFT+END" msgstr "" #: myframe.h:247 msgid "Extend Selection to Start\tSHIFT+HOME" msgstr "" #: myframe.h:484 msgid "Faster line rendering" msgstr "" #: myframe.h:457 msgid "File Tabs on the bottom" msgstr "" #: system.h:270 msgid "File corrupted!" msgstr "" #: document.h:705 msgid "File exported successfully." msgstr "" #: myframe.h:1047 msgid "" "File has been re-loaded because of modifications of another program / " "computer" msgstr "" #: system.h:421 msgid "File load error." msgstr "" #: myframe.h:1033 msgid "File modification conflict!" msgstr "" #: system.h:217 msgid "File of newer version." msgstr "" #: document.h:208 msgid "File saved succesfully." msgstr "" #: myframe.h:434 msgid "Filter..." msgstr "" #: myframe.h:365 msgid "Fold All\tCTRL+SHIFT+F10" msgstr "" #: myframe.h:366 msgid "Folds the grid of the selected cell(s) recursively" msgstr "" #: myframe.h:234 msgid "Go To &Matching Cell\tF6" msgstr "" #: myframe.h:235 msgid "Go To Matching Cell (Reverse)\tSHIFT+F6" msgstr "" #: myframe.h:321 msgid "Grid Style Rendering\tALT+7" msgstr "" #: myframe.h:138 msgid "HTML (&Outline)..." msgstr "" #: myframe.h:286 msgid "Hierarchy &Swap\tF8" msgstr "" #: myframe.h:273 msgid "" "Hold CTRL while pressing right mouse button to quickly set a tag for the " "current cell using a popup menu" msgstr "" #: myframe.h:318 msgid "Horizontal Layout with Bubble Style Rendering\tALT+5" msgstr "" #: myframe.h:317 msgid "Horizontal Layout with Grid Style Rendering\tALT+4" msgstr "" #: myframe.h:319 msgid "Horizontal Layout with Line Style Rendering\tALT+6" msgstr "" #: document.h:934 msgid "How many pixels wide should a page be? (0 for auto fit)" msgstr "" #: myframe.h:591 msgid "Image " msgstr "" #: document.h:1463 msgid "Image Resize" msgstr "" #: myframe.h:180 msgid "Import file from" msgstr "" #: myframe.h:192 msgid "Increase column width (ALT+mousewheel)\tALT+PGUP" msgstr "" #: myframe.h:195 msgid "Increase column width (no sub grids)\tCTRL+ALT+PGUP" msgstr "" #: myframe.h:141 msgid "Indented &Text..." msgstr "" #: myframe.h:157 msgid "Indented text..." msgstr "" #: myframe.h:106 msgid "Initialization Error" msgstr "" #: document.h:1558 msgid "Internal error: unimplemented operation!" msgstr "" #: document.h:1049 msgid "Key binding" msgstr "" #: document.h:1063 msgid "Keybinding cancelled." msgstr "" #: myframe.h:323 msgid "Line Style Rendering\tALT+9" msgstr "" #: myframe.h:505 msgid "Load interactive &tutorial...\tF1" msgstr "" #: system.h:288 #, c-format msgid "Loaded %s (%d cells, %d characters)." msgstr "" #: myframe.h:281 myframe.h:284 msgid "" "Make a 1xN selection to indicate which column to sort on, and which rows to " "affect" msgstr "" #: myframe.h:326 msgid "Make a hierarchy layout more vertical (default) or more horizontal" msgstr "" #: myframe.h:461 msgid "Minimize on close" msgstr "" #: myframe.h:459 msgid "Minimize to tray" msgstr "" #: myframe.h:222 msgid "Move Cells Down\tCTRL+DOWN" msgstr "" #: myframe.h:219 msgid "Move Cells Left\tCTRL+LEFT" msgstr "" #: myframe.h:220 msgid "Move Cells Right\tCTRL+RIGHT" msgstr "" #: myframe.h:221 msgid "Move Cells Up\tCTRL+UP" msgstr "" #: myframe.h:217 msgid "Move Selection Down\tDOWN" msgstr "" #: myframe.h:214 msgid "Move Selection Left\tLEFT" msgstr "" #: myframe.h:215 msgid "Move Selection Right\tRIGHT" msgstr "" #: myframe.h:216 msgid "Move Selection Up\tUP" msgstr "" #: myframe.h:209 msgid "Move to next cell\tTAB" msgstr "" #: myframe.h:210 msgid "Move to previous cell\tSHIFT+TAB" msgstr "" #: document.h:1060 msgid "NOTE: key binding will take effect next run of TreeSheets." msgstr "" #: myframe.h:468 msgid "Navigate in between cells with cursor keys" msgstr "" #: myframe.h:557 msgid "New (CTRL+n)" msgstr "" #: myframe.h:569 msgid "New Grid (INS)" msgstr "" #: document.h:863 msgid "New Sheet" msgstr "" #: document.h:864 msgid "New file cancelled." msgstr "" #: document.h:1460 msgid "No image in this cell." msgstr "" #: document.h:1567 msgid "No matches for search." msgstr "" #: document.h:1481 msgid "No matching cell found!" msgstr "" #: document.h:1563 msgid "No search string." msgstr "" #: document.h:999 document.h:1293 msgid "No search." msgstr "" #: document.h:1252 msgid "No style to paste." msgstr "" #: document.h:1114 msgid "No text selected." msgstr "" #: system.h:215 msgid "Not a TreeSheets file." msgstr "" #: document.h:818 msgid "Nothing more to redo." msgstr "" #: document.h:810 msgid "Nothing more to undo." msgstr "" #: document.h:1011 msgid "" "Now viewing TreeSheet to fit to the screen exactly, press F12 to return to " "normal." msgstr "" #: myframe.h:308 msgid "Open &file\tF4" msgstr "" #: myframe.h:558 msgid "Open (CTRL+o)" msgstr "" #: system.h:335 msgid "Open file cancelled." msgstr "" #: myframe.h:305 msgid "Open link in &browser\tF5" msgstr "" #: myframe.h:306 msgid "" "Opens up the text from the selected cell in browser (should start be a valid " "URL)" msgstr "" #: myframe.h:309 msgid "" "Opens up the text from the selected cell in default application for the file " "type" msgstr "" #: system.h:308 msgid "" "PNG decode failed on some images in this document\n" "They have been replaced by red squares." msgstr "" #: system.h:310 msgid "PNG decoder failure" msgstr "" #: myframe.h:174 msgid "Page Setup..." msgstr "" #: myframe.h:564 msgid "Paste (CTRL+v)" msgstr "" #: myframe.h:333 msgid "Paste Style Only\tCTRL+SHIFT+v" msgstr "" #: myframe.h:449 msgid "Pick Custom &Color..." msgstr "" #: myframe.h:447 msgid "Pick Default Font..." msgstr "" #: myframe.h:451 msgid "Pick Document Background..." msgstr "" #: myframe.h:862 msgid "Please enable (Options -> Show Toolbar) to use search." msgstr "" #: document.h:1462 msgid "Please enter the percentage you want the image scaled by:" msgstr "" #: document.h:1048 msgid "Please pick a menu item to change the key binding for" msgstr "" #: document.h:840 msgid "Please select a TreeSheets file to load:" msgstr "" #: document.h:1272 msgid "Please select an image file:" msgstr "" #: system.h:373 msgid "Please select file to import:" msgstr "" #: myframe.h:855 msgid "Press F11 to exit fullscreen mode." msgstr "" #: document.h:943 msgid "Print Preview" msgstr "" #: myframe.h:176 msgid "Print preview..." msgstr "" #: myframe.h:437 msgid "Radius &0" msgstr "" #: myframe.h:438 msgid "Radius &1" msgstr "" #: myframe.h:439 msgid "Radius &2" msgstr "" #: myframe.h:440 msgid "Radius &3" msgstr "" #: myframe.h:441 msgid "Radius &4" msgstr "" #: myframe.h:442 msgid "Radius &5" msgstr "" #: myframe.h:443 msgid "Radius &6" msgstr "" #: myframe.h:477 msgid "" "Reloads when another computer has changed a file (if you have made changes, " "asks)" msgstr "" #: myframe.h:302 msgid "Remove image(s) from the selected cells" msgstr "" #: myframe.h:482 msgid "Render document centered" msgstr "" #: myframe.h:578 msgid "Replace " msgstr "" #: myframe.h:389 msgid "Replace &All" msgstr "" #: myframe.h:267 msgid "Reset &colors\tSHIFT+CTRL+c" msgstr "" #: myframe.h:198 msgid "Reset column widths\tSHIFT+CTRL+w" msgstr "" #: myframe.h:572 msgid "Run" msgstr "" #: myframe.h:172 msgid "Save &As..." msgstr "" #: myframe.h:559 msgid "Save (CTRL+s)" msgstr "" #: myframe.h:560 msgid "Save As" msgstr "" #: document.h:601 msgid "Save and Close" msgstr "" #: document.h:153 document.h:713 msgid "Save cancelled." msgstr "" #: myframe.h:395 msgid "Scroll Down (mousewheel)\tALT+DOWN" msgstr "" #: myframe.h:394 msgid "Scroll Down (mousewheel)\tPGDN" msgstr "" #: myframe.h:396 msgid "Scroll Left\tALT+LEFT" msgstr "" #: myframe.h:397 msgid "Scroll Right\tALT+RIGHT" msgstr "" #: myframe.h:433 msgid "Scroll Sheet..." msgstr "" #: myframe.h:393 msgid "Scroll Up (mousewheel)\tALT+UP" msgstr "" #: myframe.h:392 msgid "Scroll Up (mousewheel)\tPGUP" msgstr "" #: myframe.h:574 msgid "Search " msgstr "" #: myframe.h:231 msgid "Select &Parent\tESC" msgstr "" #: myframe.h:212 msgid "Select &all in current grid\tCTRL+a" msgstr "" #: myframe.h:232 msgid "Select First &Child\tSHIFT+ENTER" msgstr "" #: document.h:1414 msgid "Selected grid is not a table: cells must not already have sub-grids." msgstr "" #: myframe.h:159 msgid "Semi-Colon delimited text (CSV)..." msgstr "" #: myframe.h:378 msgid "Set Grid Border Width..." msgstr "" #: document.h:935 msgid "Set Print Scale" msgstr "" #: myframe.h:175 msgid "Set Print Scale..." msgstr "" #: myframe.h:407 #, c-format msgid "Show 1% less than the last filter" msgstr "" #: myframe.h:406 #, c-format msgid "Show 1% more than the last filter" msgstr "" #: myframe.h:403 #, c-format msgid "Show 10% of last edits" msgstr "" #: myframe.h:404 #, c-format msgid "Show 20% of last edits" msgstr "" #: myframe.h:402 #, c-format msgid "Show 5% of last edits" msgstr "" #: myframe.h:405 #, c-format msgid "Show 50% of last edits" msgstr "" #: myframe.h:453 msgid "Show Statusbar" msgstr "" #: myframe.h:455 msgid "Show Toolbar" msgstr "" #: myframe.h:401 msgid "Show only cells in current search" msgstr "" #: myframe.h:463 msgid "Single click maximize from tray" msgstr "" #: system.h:358 #, c-format msgid "Size %d" msgstr "" #: myframe.h:280 msgid "Sort &Ascending" msgstr "" #: myframe.h:283 msgid "Sort &Descending" msgstr "" #: myframe.h:250 msgid "Start of line of text\tHOME" msgstr "" #: myframe.h:252 msgid "Start of text\tCTRL+HOME" msgstr "" #: myframe.h:287 msgid "Swap all cells with this text at this level (or above) with the parent" msgstr "" #: myframe.h:466 msgid "Swap mousewheel scrolling and zooming" msgstr "" #: myframe.h:418 msgid "Switch to &next file/tab" msgstr "" #: myframe.h:414 msgid "Switch to &next file/tab\tCTRL+TAB" msgstr "" #: myframe.h:420 msgid "Switch to &previous file/tab\tSHIFT+CTRL+TAB" msgstr "" #: myframe.h:160 msgid "Tab delimited text..." msgstr "" #: myframe.h:379 msgid "Tag..." msgstr "" #: myframe.h:293 msgid "" "Takes a hierarchy (nested 1xN or Nx1 grids) and converts it into a flat NxN " "grid, useful for export to spreadsheets" msgstr "" #: myframe.h:585 msgid "Text " msgstr "" #: myframe.h:375 msgid "Text &Editing..." msgstr "" #: myframe.h:376 msgid "Text Sizing..." msgstr "" #: myframe.h:377 msgid "Text Style..." msgstr "" #: document.h:429 msgid "Text size decreased." msgstr "" #: document.h:429 msgid "Text size increased." msgstr "" #: document.h:875 msgid "The Free Form Hierarchical Information Organizer" msgstr "" #: document.h:402 msgid "This operation doesn't work on thin selections." msgstr "" #: document.h:403 msgid "This operation requires a cell that contains a grid." msgstr "" #: document.h:400 msgid "This operation requires a selection." msgstr "" #: document.h:401 msgid "This operation works on a single selected cell only." msgstr "" #: myframe.h:423 msgid "Toggle &Fullscreen View\tCTRL+F11" msgstr "" #: myframe.h:425 msgid "Toggle &Fullscreen View\tF11" msgstr "" #: myframe.h:429 msgid "Toggle &Scaled Presentation View\tCTRL+F12" msgstr "" #: myframe.h:431 msgid "Toggle &Scaled Presentation View\tF12" msgstr "" #: myframe.h:360 msgid "Toggle Fold\tCTRL+F10" msgstr "" #: myframe.h:362 msgid "Toggle Fold\tF10" msgstr "" #: myframe.h:325 msgid "Toggle Vertical Layout\tF7" msgstr "" #: myframe.h:260 msgid "Toggle cell &BOLD\tCTRL+b" msgstr "" #: myframe.h:261 msgid "Toggle cell &ITALIC\tCTRL+i" msgstr "" #: myframe.h:264 msgid "Toggle cell &strikethrough\tCTRL+t" msgstr "" #: myframe.h:262 msgid "Toggle cell &typewriter" msgstr "" #: myframe.h:263 msgid "Toggle cell &underlined\tCTRL+u" msgstr "" #: myframe.h:364 msgid "Toggles showing the grid of the selected cell(s)" msgstr "" #: myframe.h:400 msgid "Turn filter &off" msgstr "" #: myframe.h:562 msgid "Undo (CTRL+z)" msgstr "" #: myframe.h:367 msgid "Unfold All\tCTRL+ALT+F10" msgstr "" #: myframe.h:368 msgid "Unfolds the grid of the selected cell(s) recursively" msgstr "" #: myframe.h:493 msgid "Variable &Assign" msgstr "" #: myframe.h:494 msgid "Variable &Read" msgstr "" #: myframe.h:314 msgid "Vertical Layout with Bubble Style Rendering\tALT+2" msgstr "" #: myframe.h:313 msgid "Vertical Layout with Grid Style Rendering\tALT+1" msgstr "" #: myframe.h:315 msgid "Vertical Layout with Line Style Rendering\tALT+3" msgstr "" #: myframe.h:506 msgid "View tutorial &web page..." msgstr "" #: document.h:862 msgid "What size grid would you like to start with?" msgstr "" #: system.h:359 #, c-format msgid "Width %d" msgstr "" #: myframe.h:240 msgid "Word Left\tCTRL+LEFT" msgstr "" #: myframe.h:241 msgid "Word Right\tCTRL+RIGHT" msgstr "" #: myframe.h:156 msgid "XML (attributes too, for OPML etc)..." msgstr "" #: myframe.h:155 msgid "XML..." msgstr "" #: document.h:187 msgid "Zlib error while writing file." msgstr "" #: myframe.h:410 msgid "Zoom &In (CTRL+mousewheel)\tCTRL+PGUP" msgstr "" #: myframe.h:411 msgid "Zoom &Out (CTRL+mousewheel)\tCTRL+PGDN" msgstr "" #: myframe.h:566 msgid "Zoom In (CTRL+mousewheel)" msgstr "" #: myframe.h:567 msgid "Zoom Out (CTRL+mousewheel)" msgstr "" #: document.h:434 msgid "Zoomed in." msgstr "" #: document.h:434 msgid "Zoomed out." msgstr "" #: myframe.h:807 msgid "change will take effect next run of TreeSheets" msgstr "" #: myframe.h:279 msgid "changes the orientation of a grid" msgstr "" #: system.h:420 msgid "couldn't import file!" msgstr "" #: myframe.h:334 msgid "only sets the colors and style of the copied cell, and keeps the text" msgstr "" #: document.h:1514 msgid "only works in cell text mode" msgstr "" #: myframe.h:338 msgid "redo any undo steps, if you haven't made changes since" msgstr "" #: myframe.h:336 msgid "revert the changes, one step at a time" msgstr "" #: document.h:934 msgid "scale:" msgstr "" #: document.h:863 msgid "size:" msgstr "" treesheets-1.0.2/TS/translations/it/000077500000000000000000000000001352107072600173535ustar00rootroot00000000000000treesheets-1.0.2/TS/translations/it/compile.bat000066400000000000000000000000411352107072600214660ustar00rootroot00000000000000msgfmt --output-file=ts.mo ts.po treesheets-1.0.2/TS/translations/it/ts.mo000066400000000000000000000756601352107072600203540ustar00rootroot00000000000000] 8y9      %218j|    1&Xp       8 $I 0n     ! !!#! 2!$@!e!*!! !!!!""D" a" k""""*" ""#-#5#0R#\####$$\:$$$$*$$ %&%,%3D%:x%%%%%&9&R&j&&& !'/'G'['q'=' '''/'2!(T(Jg(K(( ))6)N)c)))B))?*Q*h*****bN+_+V,h,, --- -".2.*O.,z.!.$..//,/NH///// //20H0&`0000h03111e111712 22/&22V2222(2 22 3 '3$H3Rm3B344&4@4Z4v444444:5*?5 j5w5 555555 566,6C6Y6Ro6 6 666Q 7Q^7Y7 8 8,8;8Y8o88689859(G9p99"9 99 9 9 : : : : ):Q3:'::: :: :; ; (;6;>;M;!];;;;;;;<<".<Q<Dq<"<<<=!=!7=Y=p==== =!==>>*>;>V>Fn>%>>!>+?B?X?s_???? ?@@01@/b@4@$@4@ !ABA)^A$AAAAAB! BBBZB0yBB BB4BC'C16C/hC/CC,CDD-D%CDiDpD$D%DDD E E.&E!UEwEEEE6E&'FNFUFB[FuGH'#HKH ^HjHH&H HHJHI +I 5IBI HI-TI#III II I IMJNJnJ!J J JJ JJJJ K K(K%;KaK,vK;K/K'L'7L_L fLtLL L L7L-L=M8LMMMMMMMOM:N+LN+xNN4NN O2*O]OcO9OpO*P!3P!UP8wP}P.Q7KQ2Q$Q$QR$RG+R?sRRRR#S'7S_S(~S"S%SSTTTTTBT 3UAUYU=pUAUUV VZ`VV V)V W)W,BW)oW'WuW)7XZaX%X%XYY8YOYyZg{Z^ZB[[#\%\&\'\)$]N]7m]8])](^!1^S^l^{^Q^^ _!+_M_ h_r_:__-_`*`=`P`3`4 a2AaFtaaaaGaH1bzbbb,bb c!c$=c#bcZcLc.dIdXd!xd ddd de6eMeRme'eee ff!.f&Pf%wffffffg+g`@gg gggQgT?hkhii7i Hi ii!i"i]iC-jLqj)j)j-k8@kykk k k k k k k kZk+Elql lll2lmmm %m0m?m*Um&mmmm&m$n9n?n.Wn#nWn)o0,o]ouo,o,o$o$p#5p$Yp~pp+p'p qq*q=q]qRxq5qr)!r.Kr"zrrr,s2sGses%xs%s7s4s>1t)ptAt5t8u8Ku3uuu$uv-v1Kv3}v,vLv+w"u4:2PV^X# R6S0J4@ /(BRN7_,tv=#W'1H*$DfmFC:3U)/F.}opB>;*G<H !y7Lk= [9A1@ &h 3M ?bDQ+]IC8,snUX-c?-.EOQV!KW[iNr`Zw] %s has been modified on disk by another program / computer: Would you like to discard your changes and re-load from disk?&About...&Add Cell Text as Tag&Add Image&Browsing...&Clear Views&Close CTRL+w&Comma delimited text (CSV)...&Copy CTRL+c&Data&Decrease text size (SHIFT+mousewheel) SHIFT+PGDN&Delete After DEL&Edit&Exit CTRL+q&File&Flatten&Go To Next Search Result F3&Grid Reorganization...&HTML (Tables+Styling)...&Help&Hierarchify&Horizontal View&Image...&Images...&Increase text size (SHIFT+mousewheel) SHIFT+PGUP&Insert New Grid CTRL+g&Insert New Grid INS&Layout && Render Style...&Mark as&New CTRL+n&Open... CTRL+o&Operation&Options&Paste CTRL+v&Print... CTRL+p&Program&Recent files&Redo CTRL+y&Remove Cell Text from Tags&Remove Image(s)&Replace in Current Selection CTRL+h&Replace in Current Selection & Jump Next CTRL+j&Reset text sizes SHIFT+CTRL+s&Reset text styles SHIFT+CTRL+r&Roundness of grid borders...&Run&Save CTRL+s&Scale Image&Search&Search CTRL+f&Selection...&Set Cell Text to tag (use CTRL+RMB)&Set Custom Color From Cell BG&Shrink text of all sub-grids SHIFT+CTRL+m&Transpose SHIFT+CTRL+t&Undo CTRL+z&Vertical View&View&Wrap in new parent F9&XML...1:1 scale restored.A temporary autosave file exists, would you like to load it instead?Add ImageAdds a grid to the selected cellAdds an image to the selected cellAuto reload documentsAutomatically export a .html on every saveAutosave loadAutosave to .tmpBlack and white toolbar iconsBorder Bubble Style Rendering ALT+8Can only move this cell into an Nx1 or 1xN grid.Can't sort: make a 1xN selection to indicate what column to sort on, and what rows to affectCancelCancel text edits ESCCannot decompress file.Cannot drag & drop more than 1 file.Cannot export grid that is not flat (zoom the view to the desired grid, and/or use Flatten).Cannot find file.Cannot launch browser for this link.Cannot move this cell up in the hierarchy.Cannot open file.Cannot tell/seek document?Cell Change a key binding...Change the image size if it is too big or too smallChanges have been made, are you sure you wish to continue?Choose CSV file to writeChoose HTML file to writeChoose PNG file to writeChoose Text file to writeChoose TreeSheets file to save:Choose XML file to writeColumn width decreased.Column width increased.Comma delimited text (CSV)...Convert an NxN grid with repeating elements per column into an 1xN grid with hierarchy, useful to convert data from spreadsheetsCopy (CTRL+c)Copy As Continuous TextCorrupt PNG header.Corrupt block header.Create .bak filesCreates a new level of hierarchy around the current selectionCu&t CTRL+xCursor Left LEFTCursor Right RIGHTDecrease column width (ALT+mousewheel) ALT+PGDNDecrease column width (no sub grids) CTRL+ALT+PGDNDelete Before BACKDeletes the column of cells after the selected grid line, or the row belowDeletes the column of cells before the selected grid line, or the row aboveDiscard ChangesEdited %sEmpty strings cannot be tags.End of line of text ENDEnd of text CTRL+ENDEnter/exit text edit mode ENTEREnter/exit text edit mode F2Error exporting file!Error loading core data file (TreeSheets not installed correctly?)Error writing PNG file!Error writing TreeSheets file! (try saving under new filename).Error writing to file!Error writing to file.Evaluation finished.Export &view asExport cancelled.Export the current view as CSV. Good for spreadsheets and databases. Only works on grids with no sub-grids (use the Flatten operation first if need be)Export the current view as HTML as nested headers, suitable for importing into Word's outline modeExport the current view as HTML using nested tables, that will look somewhat like the TreeSheetExport the current view as XML (which can also be reimported without losing structure)Export the current view as an image. Useful for faithfull renderings of the TreeSheet, and programs that don't accept any of the above optionsExport the current view as tree structured text, using spaces for each indentation level. Suitable for importing into mindmanagers and general text programsExtend Selection Down SHIFT+DOWNExtend Selection Full ColumnsExtend Selection Full RowsExtend Selection Left SHIFT+LEFTExtend Selection Right SHIFT+RIGHTExtend Selection Up SHIFT+UPExtend Selection Word Left SHIFT+CTRL+LEFTExtend Selection Word Right SHIFT+CTRL+RIGHTExtend Selection to End SHIFT+ENDExtend Selection to Start SHIFT+HOMEFaster line renderingFile Tabs on the bottomFile corrupted!File exported successfully.File has been re-loaded because of modifications of another program / computerFile load error.File modification conflict!File of newer version.File saved succesfully.Filter...Fold All CTRL+SHIFT+F10Folds the grid of the selected cell(s) recursivelyGo To &Matching Cell F6Go To Matching Cell (Reverse) SHIFT+F6Grid Style Rendering ALT+7HTML (&Outline)...Hierarchy &Swap F8Hold CTRL while pressing right mouse button to quickly set a tag for the current cell using a popup menuHorizontal Layout with Bubble Style Rendering ALT+5Horizontal Layout with Grid Style Rendering ALT+4Horizontal Layout with Line Style Rendering ALT+6How many pixels wide should a page be? (0 for auto fit)Image Image ResizeImport file fromIncrease column width (ALT+mousewheel) ALT+PGUPIncrease column width (no sub grids) CTRL+ALT+PGUPIndented &Text...Indented text...Initialization ErrorInternal error: unimplemented operation!Key bindingKeybinding cancelled.Line Style Rendering ALT+9Load interactive &tutorial... F1Loaded %s (%d cells, %d characters).Make a 1xN selection to indicate which column to sort on, and which rows to affectMake a hierarchy layout more vertical (default) or more horizontalMinimize on closeMinimize to trayMove Cells Down CTRL+DOWNMove Cells Left CTRL+LEFTMove Cells Right CTRL+RIGHTMove Cells Up CTRL+UPMove Selection Down DOWNMove Selection Left LEFTMove Selection Right RIGHTMove Selection Up UPMove to next cell TABNOTE: key binding will take effect next run of TreeSheets.Navigate in between cells with cursor keysNew (CTRL+n)New Grid (INS)New SheetNew file cancelled.No image in this cell.No matches for search.No matching cell found!No search string.No search.No style to paste.No text selected.Not a TreeSheets file.Nothing more to redo.Nothing more to undo.Now viewing TreeSheet to fit to the screen exactly, press F12 to return to normal.Open &file F4Open (CTRL+o)Open file cancelled.Open link in &browser F5Opens up the text from the selected cell in browser (should start be a valid URL)Opens up the text from the selected cell in default application for the file typePNG decode failed on some images in this document They have been replaced by red squares.PNG decoder failurePage Setup...Paste (CTRL+v)Paste Style Only CTRL+SHIFT+vPick Custom &Color...Pick Default Font...Pick Document Background...Please enable (Options -> Show Toolbar) to use search.Please enter the percentage you want the image scaled by:Please pick a menu item to change the key binding forPlease select a TreeSheets file to load:Please select an image file:Please select file to import:Press F11 to exit fullscreen mode.Print PreviewPrint preview...Radius &0Radius &1Radius &2Radius &3Radius &4Radius &5Radius &6Reloads when another computer has changed a file (if you have made changes, asks)Remove image(s) from the selected cellsRender document centeredReplace Replace &AllReset &colors SHIFT+CTRL+cReset column widths SHIFT+CTRL+wRunSave &As...Save (CTRL+s)Save AsSave and CloseSave cancelled.Scroll Down (mousewheel) ALT+DOWNScroll Down (mousewheel) PGDNScroll Left ALT+LEFTScroll Right ALT+RIGHTScroll Sheet...Scroll Up (mousewheel) ALT+UPScroll Up (mousewheel) PGUPSearch Select &Parent ESCSelect &all in current grid CTRL+aSelect First &Child SHIFT+ENTERSelected grid is not a table: cells must not already have sub-grids.Semi-Colon delimited text (CSV)...Set Grid Border Width...Set Print ScaleSet Print Scale...Show 1% less than the last filterShow 1% more than the last filterShow 10% of last editsShow 20% of last editsShow 5% of last editsShow 50% of last editsShow StatusbarShow ToolbarShow only cells in current searchSingle click maximize from traySize %dSort &AscendingSort &DescendingStart of line of text HOMEStart of text CTRL+HOMESwap all cells with this text at this level (or above) with the parentSwap mousewheel scrolling and zoomingSwitch to &next file/tabSwitch to &next file/tab CTRL+TABSwitch to &previous file/tab SHIFT+CTRL+TABTab delimited text...Tag...Takes a hierarchy (nested 1xN or Nx1 grids) and converts it into a flat NxN grid, useful for export to spreadsheetsText Text &Editing...Text Sizing...Text Style...Text size decreased.Text size increased.The Free Form Hierarchical Information OrganizerThis operation doesn't work on thin selections.This operation requires a cell that contains a grid.This operation requires a selection.This operation works on a single selected cell only.Toggle &Fullscreen View CTRL+F11Toggle &Fullscreen View F11Toggle &Scaled Presentation View CTRL+F12Toggle &Scaled Presentation View F12Toggle Fold CTRL+F10Toggle Fold F10Toggle Vertical Layout F7Toggle cell &BOLD CTRL+bToggle cell &ITALIC CTRL+iToggle cell &strikethrough CTRL+tToggle cell &typewriterToggle cell &underlined CTRL+uToggles showing the grid of the selected cell(s)Turn filter &offUndo (CTRL+z)Unfold All CTRL+ALT+F10Unfolds the grid of the selected cell(s) recursivelyVariable &AssignVariable &ReadVertical Layout with Bubble Style Rendering ALT+2Vertical Layout with Grid Style Rendering ALT+1Vertical Layout with Line Style Rendering ALT+3View tutorial &web page...What size grid would you like to start with?Width %dWord Left CTRL+LEFTWord Right CTRL+RIGHTXML (attributes too, for OPML etc)...XML...Zlib error while writing file.Zoom &In (CTRL+mousewheel) CTRL+PGUPZoom &Out (CTRL+mousewheel) CTRL+PGDNZoom In (CTRL+mousewheel)Zoom Out (CTRL+mousewheel)Zoomed in.Zoomed out.change will take effect next run of TreeSheetschanges the orientation of a gridcouldn't import file!only sets the colors and style of the copied cell, and keeps the textonly works in cell text moderedo any undo steps, if you haven't made changes sincerevert the changes, one step at a timescale:size:Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2017-03-26 16:49-0700 PO-Revision-Date: 2019-05-18 19:28+0100 Last-Translator: Albano Battistella <> Language-Team: Italian Language: Italian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit è stato modificato sul disco da un altro programma / computer: Volete ignorare le modifiche e ricaricarle dal disco?&Riguardo a...&Aggiungi il testo della cella come tag&Aggiungi immagine&Sfoglia...&Cancella Visualizzazioni&Chiudi CTRL+w&Testo delimitato da virgole (CSV) ...&Copia CTRL+c&Dati&Riduci la dimensione del testo (MAIUSC + rotella del mouse) MAIUSC + PGDN&Elimina dopo DEL&Modifica&Esci CTRL+q&File&Appiattire&Vai al risultato della ricerca successiva F3&Riorganizzazione della griglia ...&HTML (tabelle + stile)...&Aiuto&Gerarchia&Vista Orizzontale&Immagine...&Immagini...&Aumentare la dimensione del testo (MAIUSC + rotellina del mouse) MAIUSC+PGUP&Inserisci nuova griglia CTRL+g&Inserisci nuova griglia INS&Layout && Stile di rendering ...&Segna come&Nuovo CTRL+n&Apri ... CTRL+O&Operazione&Opzioni&Incolla CTRL+v&Print ... CTRL+p&Programma&File recenti&Ripristina CTRL+y&Rimuovi il testo della cella dai tag&Rimuovi immagine(s)&Sostituisci nella selezione corrente CTRL+h&Sostituisci nella selezione corrente e salta Avanti CTRL+j&Reimposta la dimensione del testo SHIFT+CTRL+s&Ripristina stili di testo SHIFT+CTRL+r&Rotondità dei bordi della griglia ...&Avvia&Salva CTRL+s&Scala l'immagine&Cerca&Cerca CTRL+f&Selezione..&Imposta il testo della cella da taggare (usa CTRL+RMB)&Imposta colore personalizzato dalla cella BG&Riduci il testo di tutte le griglie secondarie SHIFT+CTRL+m&Riduci il testo di tutte le sottocategorie SHIFT+CTRL+m&Annulla CTRL+z&Vista verticale&Vista&Avvia il nuovo genitore F9&XML...Scala 1: 1 ripristinata.Esiste un file di salvataggio automatico temporaneo, vorresti invece caricarlo?Aggiungi ImmagineAggiunge una griglia alla cella selezionataAggiunge un'immagine alla cella selezionataAuto caricamento documentiEsporta automaticamente un .html ad ogni salvataggioSalvataggio automaticoSalvataggio automatico su .tmpIcone della barra degli strumenti in bianco e neroBordoRendering stile bolla ALT+8Può solo spostare questa cella in una griglia Nx1 o 1xN.Impossibile ordinare: creare una selezione 1xN per indicare quale colonna ordinare, e Quali righe da influenzareCancellaAnnulla le modifiche al testo ESCImpossibile decomprimere il file.Non è possibile trascinare e rilasciare più di 1 file.Impossibile esportare la griglia che non è piana (zoommare la vista alla griglia desiderata e / o utilizzare Appiattimento).Impossibile trovare il file.Impossibile avviare il browser per questo collegamento.Impossibile spostare questa cella nella gerarchia.Non è possibile aprire questo file.Non posso dire/cercare un documento?CellaModificare una chiave vincolante ...Cambia la dimensione dell'immagine se è troppo grande o troppo piccolaSono state apportate modifiche, sei sicuro di voler continuare?Scegli il file CSV per scrivereScegli il file HTML da scrivereScegli il file PNG per scrivereScegli il file di testo da scrivereScegli il file Fogli Albero da salvare:Scegli il file XML da scrivereLa larghezza della colonna è diminuita.Larghezza della colonna aumentata.Testo delimitato da virgole (CSV) ...Convertire una griglia NxN con elementi ripetuti per colonna in una griglia 1xN con gerarchia, utile per convertire i dati da fogli di calcoloCopia (CTRL+c)Copia come testo continuoIntestazione PNG corrotta.intestazione blocco corrotto.Crea file .bakCrea un nuovo livello di gerarchia attorno alla selezione correnteTaglia CTRL+xCursore a sinistra LEFTCursore a destra RIGHTRiduci larghezza colonna (ALT + rotellina del mouse) ALT+PGDNDiminuisci larghezza colonna (nessuna sottogriglia) CTRL+ALT+PGDNEliminare Prima INDIETROElimina la colonna di celle dopo la linea di griglia selezionata o la riga sottostanteElimina la colonna di celle prima della linea della griglia selezionata o della riga sopraNon salvare le modificheModificato%sLe stringhe vuote non possono essere tag.Fine della riga di testo FINEFine del testo CTRL+FINEEntra/esci da modalità modifica testo ENTEREntra/esci da modalità modifica testo F2Errore durante l'esportazione del file!Errore durante il caricamento del file di dati di base (i fogli dell'albero non sono stati installati correttamente?)Errore durante la scrittura del file PNG!Errore durante la scrittura del file TreeSheets! (prova a salvare con il nuovo nome file).Errore durante la scrittura sul file!Errore durante la scrittura sul file.Valutazione terminata.Esporta &visualizza comeEsportazione annullataEsporta la vista corrente come CSV. Buono per fogli di calcolo e database. Funziona solo su griglie senza sub-griglie (utilizzare prima l'operazione appiattimento se necessario)Esportare la vista corrente come HTML come intestazioni nidificate, adatte per l'importazione In modalità struttura WordEsportare la vista corrente come HTML usando tabelle annidate, che sembreranno un po 'come il TreeSheetEsporta la vista corrente come XML (che può anche essere reimportata senza perdere struttura)Esportare la vista corrente come un'immagine. Utile per rendering fedeli del TreeSheet e programmi che non accettano nessuna delle opzioni di cui sopraEsportare la vista corrente come testo strutturato ad albero, usando spazi per ogni livello di indentazione, adatto per l'importazione in mind manager e testo generaleprogrammiEstendi selezione giù SHIFT + DOWNEstendi colonne complete di selezioneEstendi la selezione di righe completeEstendi selezione a sinistra SHIFT+LEFTEstendi la selezione a destra SHIFT+RIGHTEstendi selezione su SHIFT +UPEstendi la parola di selezione Sinistra SHIFT+CTRL+LEFTEstendi la parola di selezione a destra SHIFT+CTRL+RIGHTEstendi selezione per terminare SHIFT+ENDEstendi selezione per avviare SHIFT+HOMERendering della linea più veloceSchede dei file in bassoFile corrotto!File esportato correttamente.Il file è stato ricaricato a causa di modifiche di un altro programma / computerErrore di caricamento del file.Conflitto di modifica dei file!File della versione più recente.File salvato con successo.Filtro...Piega tutto CTRL+MAIUSC+F10Piega la griglia delle celle selezionate in modo ricorsivoVai a & Abbina cella F6Vai a Cella corrispondente (inversa) SHIFT+F6Grid Style Rendering ALT+7HTML (&Outline)...Gerarchia &Swap F8Tieni premuto CTRL mentre premi il tasto destro del mouse per impostare rapidamente un tag per cella corrente sta' usando un menu popupLayout orizzontale con Rendering stile Bubble ALT+5Layout orizzontale con Rendering stile griglia ALT+4Layout orizzontale con Rendering stile linea ALT+6Di quanti pixel deve essere una pagina? (0 per adattamento automatico)ImmagineRidimensiona mmagineImporta file daAumentare la larghezza della colonna (ALT+rotellina del mouse) ALT+PGUPAumenta la larghezza della colonna (nessuna sottogriglia) CTRL+ALT +PGUPTesto &rientrato ...Testo rientrato ...Errore di inizializzazioneErrore interno: operazione non implementata!chiave di rilegaturaCombinazione di tasti annullato.Rendering stile linea ALT+9Carica interattivo e tutorial ... F1Caricato%s (%d celle,%d caratteri).Crea una selezione 1xN per indicare su quale colonna ordinare e quali righe da influenzareRendi un layout di gerarchia più verticale (predefinito) o più orizzontaleRiduci a icona in chiusuraRiduci a iconaSposta celle in basso CTRL+DOWNSposta celle a sinistra CTRL+LEFTSposta celle a destra CTRL+RIGHTSposta celle in alto CTRL+UPSposta selezione in basso DOWNSposta selezione a sinistra LEFTSposta selezione destra RIGHTSposta selezione su UPPassa alla cella successiva TABNOTA: l'associazione dei tasti avrà effetto la prossima esecuzione di TreeSheets.Naviga tra le celle con i tasti cursoreNuovo (CTRL+n)Nuova griglia (INS)Nuovo foglioNuovo file annullato.Nessuna immagine in questa cella.Nessuna corrispondenza per la ricerca.Nessuna cella corrispondente trovata!Nessuna stringa di ricerca.Nessuna ricercaNessuno stile da incollare.Nessun testo selezionato.Non un file TreeSheets.Niente da ripetere.Niente da annullare.Ora guardando TreeSheet per adattarsi esattamente allo schermo, premi F12 per tornare a normale.Apri &file F4Apri (CTRL+o)Apri file cancellato.Apri link in &browser F5Apre il testo dalla cella selezionata nel browser (dovrebbe essere un valido URL)Apre il testo dalla cella selezionata nell'applicazione predefinita per il file tipoDecodifica PNG non riuscita su alcune immagini in questo documento Sono stati sostituiti da quadrati rossi.Errore decodificatore PNGImpostazione della pagina...Incolla (CTRL+v)Incolla solo stile CTRL+MAIUSC+vScegli colore personalizzato ...Scegli il carattere di default ..Scegli lo sfondo del documento ...Si prega di abilitare (Opzioni -> Mostra la barra degli strumenti) per utilizzare la ricerca.Inserisci la percentuale in cui desideri ridimensionare l'immagine:Si prega di scegliere una voce di menu per modificare la legatura chiave perSeleziona un file TreeSheets da caricare:Si prega di selezionare un file immagine:Si prega di selezionare il file da importare:Premere F11 per uscire dalla modalità a schermo intero.Anteprima di stampaAnteprima di stampa...Raggio &0Raggio &1Raggio &2Raggio &3Raggio &4Raggio &5Raggio &6Ricarica quando un altro computer ha cambiato un file (se hai apportato modifiche, Chiedi)Rimuovi immagine(i) dalle celle selezionateRender del documento centratoSostituisciSostituisci &tuttoRipristina &colori SHIFT+CTRL+cRipristina le larghezze delle colonne SHIFT+CTRL+wAvviaSalva &come...Salva (CTRL+s)Salva comeSalva e chiudiSalvataggio annullatoScorri verso il basso (rotellina) ALT+DOWNScorri verso il basso (rotellina) PGDNScorri a sinistra ALT+LEFTScorri a destra ALT+RIGHTScorri foglio ..Scorri verso l'alto (rotellina) ALT+UPScorri verso l'alto (rotellina) PGUPCercaSeleziona &Genitore ESCSeleziona &tutto nella griglia corrente CTRL+aSeleziona Primo &Figlio SHIFT+ENTERLa griglia selezionata non è una tabella: le celle non devono avere già sottogriglie.Testo delimitato da semi-colonna (CSV) ..Imposta la larghezza del bordo della griglia ...Imposta scala di stampaImposta scala di stampa ...Mostra 1% in meno rispetto all'ultimo filtroMostra 1% in più rispetto all'ultimo filtroMostra il 10% delle ultime modificheMostra il 20% delle ultime modificheMostra il 5% delle ultime modificheMostra il 50% delle ultime modificheMostra la barra di statoMostra la barra degli strumentiMostra solo le celle nella ricerca correnteUn singolo clic ingrandisce dal vassoioDimensione% dOrdine crescenteOrdine decrescenteInizio della riga di testo HOMEInizio del testo CTRL+HOMEScambia tutte le celle con questo testo a questo livello (o sopra) con il genitoreScambia la rotellina del mouse per scorrere e zoomarePassa a &successivo file/schedaPassa a & successivo file/scheda CTRL+TABPassa al file precedente/scheda SHIFT+CTRL+TABTesto delimitato da tabulazioni ..Tag..Prende una gerarchia (griglie nidificate 1xN o Nx1) e lo converte in un NxN piatto griglia, utile per l'esportazione in fogli di calcoloTestoTesto e modifica ...Dimensionamento del testo ...Stile di testo ...La dimensione del testo è diminuita.La dimensione del testo è aumentata.Organizzatore di informazioni gerarchico a forma liberaQuesta operazione non funziona su selezioni sottili.questa operazione richiede una cella che contiene una griglia.Questa operazione richiede una selezione.Questa operazione funziona solo su una singola cella selezionata.Attiva / disattiva visualizzazione a schermo CTRL+F11Attiva/disattiva la visualizzazione a schermo intero F11Mostra presentazione a scalare e ridimensionata CTRL+F12Mostra presentazione a scalare e ridimensionata F12Commuta piegatura CTRL+F10Commuta piegatura F10Attiva/disattiva layout verticale F7Cambia cella &GRASSETTO CTRL+bCommuta cella e ITALIC CTRL+iAttiva/disattiva attivazione cella tramite CTRL+tAttiva/disattiva la cella e la macchina da scrivereAttiva/disattiva sottolineatura cella CTRL+uAttiva/disattiva la visualizzazione della griglia delle cella(e) selezionateSpegni il filtroAnnulla (CTRL+z)Dispiega tutto CTRL+ALT+F10Dispiega la griglia delle celle selezionata(e) in modo ricorsivoAssegnazione &variabileVariabile &LettaLayout verticale con Rendering stile Bubble ALT+2Layout verticale con rendering a stile griglia ALT+1Layout verticale con Rendering stile linea ALT+3Visualizza tutorial e pagina web ...Con quale dimensioni di griglia vorresti iniziare?Larghezza% dParola sinistra CTRL+LEFTParola destra CTRL+RIGHTXML (anche attributi, per OPML ecc.)XML...Errore Zlib durante la scrittura del fileZoom &Avanti (CTRL+rotellina del mouse) CTRL+PGUPZoom &Indietro (CTRL+rotellina del mouse) CTRL+PGDNZoom avanti (CTRL+rotellina del mouse)Zoom indietro (CTR+rotellina del mouse)Ingrandito.Rimpicciolitoil cambiamento avrà effetto la prossima esecuzione di TreeSheetscambia l'orientamento di una grigliaimpossibile importare il file!imposta solo i colori e lo stile della cella copiata e mantiene il testofunziona solo in modalità testo cellaripristina i passaggi di annullamento, se non hai apportato modifiche da alloraripristinare le modifiche, un passo alla voltascala:Dimensione:treesheets-1.0.2/TS/translations/it/ts.po000066400000000000000000001107711352107072600203500ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-03-26 16:49-0700\n" "PO-Revision-Date: 2019-05-18 19:28+0100\n" "Last-Translator: Albano Battistella <>\n" "Language-Team: Italian \n" "Language: Italian\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: myframe.h:1028 #, c-format msgid "" "%s\n" "has been modified on disk by another program / computer:\n" "Would you like to discard your changes and re-load from disk?" msgstr "è stato modificato sul disco da un altro programma / computer:\n" "Volete ignorare le modifiche e ricaricarle dal disco?" #: myframe.h:504 msgid "&About..." msgstr "&Riguardo a..." #: myframe.h:270 msgid "&Add Cell Text as Tag" msgstr "&Aggiungi il testo della cella come tag" #: myframe.h:298 msgid "&Add Image" msgstr "&Aggiungi immagine" #: myframe.h:374 msgid "&Browsing..." msgstr "&Sfoglia..." #: myframe.h:501 msgid "&Clear Views" msgstr "&Cancella Visualizzazioni" #: myframe.h:169 msgid "&Close\tCTRL+w" msgstr "&Chiudi\tCTRL+w" #: myframe.h:145 msgid "&Comma delimited text (CSV)..." msgstr "&Testo delimitato da virgole (CSV) ..." #: myframe.h:330 msgid "&Copy\tCTRL+c" msgstr "&Copia\tCTRL+c" #: myframe.h:491 msgid "&Data" msgstr "&Dati" #: myframe.h:188 msgid "&Decrease text size (SHIFT+mousewheel)\tSHIFT+PGDN" msgstr "&Riduci la dimensione del testo (MAIUSC + rotella del mouse)\tMAIUSC + PGDN" #: myframe.h:340 msgid "&Delete After\tDEL" msgstr "&Elimina dopo\tDEL" #: myframe.h:518 msgid "&Edit" msgstr "&Modifica" #: myframe.h:182 msgid "&Exit\tCTRL+q" msgstr "&Esci\tCTRL+q" #: myframe.h:517 msgid "&File" msgstr "&File" #: myframe.h:292 msgid "&Flatten" msgstr "&Appiattire" #: myframe.h:386 msgid "&Go To Next Search Result\tF3" msgstr "&Vai al risultato della ricerca successiva\tF3" #: myframe.h:371 msgid "&Grid Reorganization..." msgstr "&Riorganizzazione della griglia ..." #: myframe.h:135 msgid "&HTML (Tables+Styling)..." msgstr "&HTML (tabelle + stile)..." #: myframe.h:527 msgid "&Help" msgstr "&Aiuto" #: myframe.h:288 msgid "&Hierarchify" msgstr "&Gerarchia" #: myframe.h:495 msgid "&Horizontal View" msgstr "&Vista Orizzontale" #: myframe.h:149 msgid "&Image..." msgstr "&Immagine..." #: myframe.h:373 msgid "&Images..." msgstr "&Immagini..." #: myframe.h:187 msgid "&Increase text size (SHIFT+mousewheel)\tSHIFT+PGUP" msgstr "&Aumentare la dimensione del testo (MAIUSC + rotellina del mouse)\tMAIUSC+PGUP" #: myframe.h:348 msgid "&Insert New Grid\tCTRL+g" msgstr "&Inserisci nuova griglia\tCTRL+g" #: myframe.h:350 msgid "&Insert New Grid\tINS" msgstr "&Inserisci nuova griglia\tINS" #: myframe.h:372 msgid "&Layout && Render Style..." msgstr "&Layout && Stile di rendering ..." #: myframe.h:500 msgid "&Mark as" msgstr "&Segna come" #: myframe.h:167 msgid "&New\tCTRL+n" msgstr "&Nuovo\tCTRL+n" #: myframe.h:168 msgid "&Open...\tCTRL+o" msgstr "&Apri ...\tCTRL+O" #: myframe.h:492 msgid "&Operation" msgstr "&Operazione" #: myframe.h:521 msgid "&Options" msgstr "&Opzioni" #: myframe.h:332 msgid "&Paste\tCTRL+v" msgstr "&Incolla\tCTRL+v" #: myframe.h:177 msgid "&Print...\tCTRL+p" msgstr "&Print ...\tCTRL+p" #: myframe.h:522 msgid "&Program" msgstr "&Programma" #: myframe.h:170 msgid "&Recent files" msgstr "&File recenti" #: myframe.h:337 msgid "&Redo\tCTRL+y" msgstr "&Ripristina\tCTRL+y" #: myframe.h:271 msgid "&Remove Cell Text from Tags" msgstr "&Rimuovi il testo della cella dai tag" #: myframe.h:301 msgid "&Remove Image(s)" msgstr "&Rimuovi immagine(s)" #: myframe.h:387 msgid "&Replace in Current Selection\tCTRL+h" msgstr "&Sostituisci nella selezione corrente\tCTRL+h" #: myframe.h:388 msgid "&Replace in Current Selection & Jump Next\tCTRL+j" msgstr "&Sostituisci nella selezione corrente e salta Avanti\tCTRL+j" #: myframe.h:189 msgid "&Reset text sizes\tSHIFT+CTRL+s" msgstr "&Reimposta la dimensione del testo\tSHIFT+CTRL+s" #: myframe.h:266 msgid "&Reset text styles\tSHIFT+CTRL+r" msgstr "&Ripristina stili di testo\tSHIFT+CTRL+r" #: myframe.h:488 msgid "&Roundness of grid borders..." msgstr "&Rotondità dei bordi della griglia ..." #: myframe.h:499 msgid "&Run" msgstr "&Avvia" #: myframe.h:171 msgid "&Save\tCTRL+s" msgstr "&Salva\tCTRL+s" #: myframe.h:299 msgid "&Scale Image" msgstr "&Scala l'immagine" #: myframe.h:519 msgid "&Search" msgstr "&Cerca" #: myframe.h:385 msgid "&Search\tCTRL+f" msgstr "&Cerca\tCTRL+f" #: myframe.h:370 msgid "&Selection..." msgstr "&Selezione.." #: myframe.h:272 msgid "&Set Cell Text to tag (use CTRL+RMB)" msgstr "&Imposta il testo della cella da taggare (usa CTRL+RMB)" #: myframe.h:450 msgid "&Set Custom Color From Cell BG" msgstr "&Imposta colore personalizzato dalla cella BG" #: myframe.h:190 msgid "&Shrink text of all sub-grids\tSHIFT+CTRL+m" msgstr "&Riduci il testo di tutte le griglie secondarie \tSHIFT+CTRL+m" #: myframe.h:278 msgid "&Transpose\tSHIFT+CTRL+t" msgstr "&Riduci il testo di tutte le sottocategorie\tSHIFT+CTRL+m" #: myframe.h:336 msgid "&Undo\tCTRL+z" msgstr "&Annulla\tCTRL+z" #: myframe.h:496 msgid "&Vertical View" msgstr "&Vista verticale" #: myframe.h:520 msgid "&View" msgstr "&Vista" #: myframe.h:353 msgid "&Wrap in new parent\tF9" msgstr "&Avvia il nuovo genitore\tF9" #: myframe.h:132 msgid "&XML..." msgstr "&XML..." #: document.h:1013 msgid "1:1 scale restored." msgstr "Scala 1: 1 ripristinata." #: system.h:197 msgid "A temporary autosave file exists, would you like to load it instead?" msgstr "Esiste un file di salvataggio automatico temporaneo, vorresti invece caricarlo?" #: myframe.h:570 msgid "Add Image" msgstr "Aggiungi Immagine" #: myframe.h:352 msgid "Adds a grid to the selected cell" msgstr "Aggiunge una griglia alla cella selezionata" #: myframe.h:298 msgid "Adds an image to the selected cell" msgstr "Aggiunge un'immagine alla cella selezionata" #: myframe.h:476 msgid "Auto reload documents" msgstr "Auto caricamento documenti" #: myframe.h:479 msgid "Automatically export a .html on every save" msgstr "Esporta automaticamente un .html ad ogni salvataggio" #: system.h:198 msgid "Autosave load" msgstr "Salvataggio automatico" #: myframe.h:473 msgid "Autosave to .tmp" msgstr "Salvataggio automatico su .tmp" #: myframe.h:486 msgid "Black and white toolbar icons" msgstr "Icone della barra degli strumenti in bianco e nero" #: myframe.h:588 msgid "Border " msgstr "Bordo" #: myframe.h:322 msgid "Bubble Style Rendering\tALT+8" msgstr "Rendering stile bolla\tALT+8" #: document.h:1493 msgid "Can only move this cell into an Nx1 or 1xN grid." msgstr "Può solo spostare questa cella in una griglia Nx1 o 1xN." #: document.h:1660 msgid "" "Can't sort: make a 1xN selection to indicate what column to sort on, and " "what rows to affect" msgstr "Impossibile ordinare: creare una selezione 1xN per indicare quale colonna ordinare, e " "Quali righe da influenzare" #: document.h:601 msgid "Cancel" msgstr "Cancella" #: myframe.h:257 msgid "Cancel text edits\tESC" msgstr "Annulla le modifiche al testo\tESC" #: system.h:266 msgid "Cannot decompress file." msgstr "Impossibile decomprimere il file." #: document.h:1611 msgid "Cannot drag & drop more than 1 file." msgstr "Non è possibile trascinare e rilasciare più di 1 file." #: document.h:655 msgid "" "Cannot export grid that is not flat (zoom the view to the desired grid, and/" "or use Flatten)." msgstr "Impossibile esportare la griglia che non è piana (zoommare la vista alla griglia desiderata e / " "o utilizzare Appiattimento)." #: document.h:1455 msgid "Cannot find file." msgstr "Impossibile trovare il file." #: document.h:1448 msgid "Cannot launch browser for this link." msgstr "Impossibile avviare il browser per questo collegamento." #: document.h:1491 msgid "Cannot move this cell up in the hierarchy." msgstr "Impossibile spostare questa cella nella gerarchia." #: system.h:211 msgid "Cannot open file." msgstr "Non è possibile aprire questo file." #: system.h:237 msgid "Cannot tell/seek document?" msgstr "Non posso dire/cercare un documento?" #: myframe.h:582 msgid "Cell " msgstr "Cella" #: myframe.h:448 msgid "Change a key binding..." msgstr "Modificare una chiave vincolante ..." #: myframe.h:300 msgid "Change the image size if it is too big or too small" msgstr "Cambia la dimensione dell'immagine se è troppo grande o troppo piccola" #: document.h:600 msgid "Changes have been made, are you sure you wish to continue?" msgstr "Sono state apportate modifiche, sei sicuro di voler continuare?" #: document.h:829 msgid "Choose CSV file to write" msgstr "Scegli il file CSV per scrivere" #: document.h:826 msgid "Choose HTML file to write" msgstr "Scegli il file HTML da scrivere" #: document.h:828 msgid "Choose PNG file to write" msgstr "Scegli il file PNG per scrivere" #: document.h:827 msgid "Choose Text file to write" msgstr "Scegli il file di testo da scrivere" #: document.h:711 msgid "Choose TreeSheets file to save:" msgstr "Scegli il file Fogli Albero da salvare:" #: document.h:824 msgid "Choose XML file to write" msgstr "Scegli il file XML da scrivere" #: document.h:419 msgid "Column width decreased." msgstr "La larghezza della colonna è diminuita." #: document.h:419 msgid "Column width increased." msgstr "Larghezza della colonna aumentata." #: myframe.h:158 msgid "Comma delimited text (CSV)..." msgstr "Testo delimitato da virgole (CSV) ..." #: myframe.h:289 msgid "" "Convert an NxN grid with repeating elements per column into an 1xN grid with " "hierarchy, useful to convert data from spreadsheets" msgstr "Convertire una griglia NxN con elementi ripetuti per colonna in una griglia 1xN con " "gerarchia, utile per convertire i dati da fogli di calcolo" #: myframe.h:563 msgid "Copy (CTRL+c)" msgstr "Copia (CTRL+c)" #: myframe.h:331 msgid "Copy As Continuous Text" msgstr "Copia come testo continuo" #: system.h:243 msgid "Corrupt PNG header." msgstr "Intestazione PNG corrotta." #: system.h:295 msgid "Corrupt block header." msgstr "intestazione blocco corrotto." #: myframe.h:471 msgid "Create .bak files" msgstr "Crea file .bak" #: myframe.h:354 msgid "Creates a new level of hierarchy around the current selection" msgstr "Crea un nuovo livello di gerarchia attorno alla selezione corrente" #: myframe.h:329 msgid "Cu&t\tCTRL+x" msgstr "Taglia\tCTRL+x" #: myframe.h:238 msgid "Cursor Left\tLEFT" msgstr "Cursore a sinistra\tLEFT" #: myframe.h:239 msgid "Cursor Right\tRIGHT" msgstr "Cursore a destra\tRIGHT" #: myframe.h:193 msgid "Decrease column width (ALT+mousewheel)\tALT+PGDN" msgstr "Riduci larghezza colonna (ALT + rotellina del mouse)\tALT+PGDN" #: myframe.h:197 msgid "Decrease column width (no sub grids)\tCTRL+ALT+PGDN" msgstr "Diminuisci larghezza colonna (nessuna sottogriglia)\tCTRL+ALT+PGDN" #: myframe.h:343 msgid "Delete Before\tBACK" msgstr "Eliminare Prima\tINDIETRO" #: myframe.h:341 msgid "" "Deletes the column of cells after the selected grid line, or the row below" msgstr "Elimina la colonna di celle dopo la linea di griglia selezionata o la riga sottostante" #: myframe.h:344 msgid "" "Deletes the column of cells before the selected grid line, or the row above" msgstr "Elimina la colonna di celle prima della linea della griglia selezionata o della riga sopra" #: document.h:601 msgid "Discard Changes" msgstr "Non salvare le modifiche" #: system.h:361 #, c-format msgid "Edited %s" msgstr "Modificato%s" #: document.h:1503 msgid "Empty strings cannot be tags." msgstr "Le stringhe vuote non possono essere tag." #: myframe.h:251 msgid "End of line of text\tEND" msgstr "Fine della riga di testo\tFINE" #: myframe.h:253 msgid "End of text\tCTRL+END" msgstr "Fine del testo\tCTRL+FINE" #: myframe.h:255 msgid "Enter/exit text edit mode\tENTER" msgstr "Entra/esci da modalità modifica testo\tENTER" #: myframe.h:256 msgid "Enter/exit text edit mode\tF2" msgstr "Entra/esci da modalità modifica testo\tF2" #: document.h:671 msgid "Error exporting file!" msgstr "Errore durante l'esportazione del file!" #: myframe.h:105 msgid "Error loading core data file (TreeSheets not installed correctly?)" msgstr "Errore durante il caricamento del file di dati di base (i fogli dell'albero non sono stati installati correttamente?)" #: document.h:667 msgid "Error writing PNG file!" msgstr "Errore durante la scrittura del file PNG!" #: document.h:165 msgid "Error writing TreeSheets file! (try saving under new filename)." msgstr "Errore durante la scrittura del file TreeSheets! (prova a salvare con il nuovo nome file)." #: document.h:672 msgid "Error writing to file!" msgstr "Errore durante la scrittura sul file!" #: document.h:167 msgid "Error writing to file." msgstr "Errore durante la scrittura sul file." #: document.h:803 msgid "Evaluation finished." msgstr "Valutazione terminata." #: myframe.h:179 msgid "Export &view as" msgstr "Esporta &visualizza come" #: document.h:646 msgid "Export cancelled." msgstr "Esportazione annullata" #: myframe.h:146 msgid "" "Export the current view as CSV. Good for spreadsheets and databases. Only " "works on grids with no sub-grids (use the Flatten operation first if need be)" msgstr "Esporta la vista corrente come CSV. Buono per fogli di calcolo e database. Funziona " "solo su griglie senza sub-griglie (utilizzare prima l'operazione appiattimento se necessario)" #: myframe.h:139 msgid "" "Export the current view as HTML as nested headers, suitable for importing " "into Word's outline mode" msgstr "Esportare la vista corrente come HTML come intestazioni nidificate, adatte per l'importazione " "In modalità struttura Word" #: myframe.h:136 msgid "" "Export the current view as HTML using nested tables, that will look somewhat " "like the TreeSheet" msgstr "Esportare la vista corrente come HTML usando tabelle annidate, che sembreranno un po '" "come il TreeSheet" #: myframe.h:133 msgid "" "Export the current view as XML (which can also be reimported without losing " "structure)" msgstr "Esporta la vista corrente come XML (che può anche essere reimportata senza perdere " "struttura)" #: myframe.h:150 msgid "" "Export the current view as an image. Useful for faithfull renderings of the " "TreeSheet, and programs that don't accept any of the above options" msgstr "Esportare la vista corrente come un'immagine. Utile per rendering fedeli del " "TreeSheet e programmi che non accettano nessuna delle opzioni di cui sopra" #: myframe.h:142 msgid "" "Export the current view as tree structured text, using spaces for each " "indentation level. Suitable for importing into mindmanagers and general text " "programs" msgstr "Esportare la vista corrente come testo strutturato ad albero, usando spazi per ogni " "livello di indentazione, adatto per l'importazione in mind manager e testo generale" "programmi" #: myframe.h:227 msgid "Extend Selection Down\tSHIFT+DOWN" msgstr "Estendi selezione giù\tSHIFT + DOWN" #: myframe.h:228 msgid "Extend Selection Full Columns" msgstr "Estendi colonne complete di selezione" #: myframe.h:229 msgid "Extend Selection Full Rows" msgstr "Estendi la selezione di righe complete" #: myframe.h:224 myframe.h:243 msgid "Extend Selection Left\tSHIFT+LEFT" msgstr "Estendi selezione a sinistra\tSHIFT+LEFT" #: myframe.h:225 myframe.h:244 msgid "Extend Selection Right\tSHIFT+RIGHT" msgstr "Estendi la selezione a destra\tSHIFT+RIGHT" #: myframe.h:226 msgid "Extend Selection Up\tSHIFT+UP" msgstr "Estendi selezione su\tSHIFT +UP" #: myframe.h:245 msgid "Extend Selection Word Left\tSHIFT+CTRL+LEFT" msgstr "Estendi la parola di selezione Sinistra\tSHIFT+CTRL+LEFT" #: myframe.h:246 msgid "Extend Selection Word Right\tSHIFT+CTRL+RIGHT" msgstr "Estendi la parola di selezione a destra\tSHIFT+CTRL+RIGHT" #: myframe.h:248 msgid "Extend Selection to End\tSHIFT+END" msgstr "Estendi selezione per terminare\tSHIFT+END" #: myframe.h:247 msgid "Extend Selection to Start\tSHIFT+HOME" msgstr "Estendi selezione per avviare\tSHIFT+HOME" #: myframe.h:484 msgid "Faster line rendering" msgstr "Rendering della linea più veloce" #: myframe.h:457 msgid "File Tabs on the bottom" msgstr "Schede dei file in basso" #: system.h:270 msgid "File corrupted!" msgstr "File corrotto!" #: document.h:705 msgid "File exported successfully." msgstr "File esportato correttamente." #: myframe.h:1047 msgid "" "File has been re-loaded because of modifications of another program / " "computer" msgstr "Il file è stato ricaricato a causa di modifiche di un altro programma / " "computer" #: system.h:421 msgid "File load error." msgstr "Errore di caricamento del file." #: myframe.h:1033 msgid "File modification conflict!" msgstr "Conflitto di modifica dei file!" #: system.h:217 msgid "File of newer version." msgstr "File della versione più recente." #: document.h:208 msgid "File saved succesfully." msgstr "File salvato con successo." #: myframe.h:434 msgid "Filter..." msgstr "Filtro..." #: myframe.h:365 msgid "Fold All\tCTRL+SHIFT+F10" msgstr "Piega tutto\tCTRL+MAIUSC+F10" #: myframe.h:366 msgid "Folds the grid of the selected cell(s) recursively" msgstr "Piega la griglia delle celle selezionate in modo ricorsivo" #: myframe.h:234 msgid "Go To &Matching Cell\tF6" msgstr "Vai a & Abbina cella\tF6" #: myframe.h:235 msgid "Go To Matching Cell (Reverse)\tSHIFT+F6" msgstr "Vai a Cella corrispondente (inversa)\tSHIFT+F6" #: myframe.h:321 msgid "Grid Style Rendering\tALT+7" msgstr "Grid Style Rendering\tALT+7" #: myframe.h:138 msgid "HTML (&Outline)..." msgstr "HTML (&Outline)..." #: myframe.h:286 msgid "Hierarchy &Swap\tF8" msgstr "Gerarchia &Swap\tF8" #: myframe.h:273 msgid "" "Hold CTRL while pressing right mouse button to quickly set a tag for the " "current cell using a popup menu" msgstr "Tieni premuto CTRL mentre premi il tasto destro del mouse per impostare rapidamente un tag per " "cella corrente sta' usando un menu popup" #: myframe.h:318 msgid "Horizontal Layout with Bubble Style Rendering\tALT+5" msgstr "Layout orizzontale con Rendering stile Bubble\tALT+5" #: myframe.h:317 msgid "Horizontal Layout with Grid Style Rendering\tALT+4" msgstr "Layout orizzontale con Rendering stile griglia\tALT+4" #: myframe.h:319 msgid "Horizontal Layout with Line Style Rendering\tALT+6" msgstr "Layout orizzontale con Rendering stile linea\tALT+6" #: document.h:934 msgid "How many pixels wide should a page be? (0 for auto fit)" msgstr "Di quanti pixel deve essere una pagina? (0 per adattamento automatico)" #: myframe.h:591 msgid "Image " msgstr "Immagine" #: document.h:1463 msgid "Image Resize" msgstr "Ridimensiona mmagine" #: myframe.h:180 msgid "Import file from" msgstr "Importa file da" #: myframe.h:192 msgid "Increase column width (ALT+mousewheel)\tALT+PGUP" msgstr "Aumentare la larghezza della colonna (ALT+rotellina del mouse)\tALT+PGUP" #: myframe.h:195 msgid "Increase column width (no sub grids)\tCTRL+ALT+PGUP" msgstr "Aumenta la larghezza della colonna (nessuna sottogriglia)\tCTRL+ALT +PGUP" #: myframe.h:141 msgid "Indented &Text..." msgstr "Testo &rientrato ..." #: myframe.h:157 msgid "Indented text..." msgstr "Testo rientrato ..." #: myframe.h:106 msgid "Initialization Error" msgstr "Errore di inizializzazione" #: document.h:1558 msgid "Internal error: unimplemented operation!" msgstr "Errore interno: operazione non implementata!" #: document.h:1049 msgid "Key binding" msgstr "chiave di rilegatura" #: document.h:1063 msgid "Keybinding cancelled." msgstr "Combinazione di tasti annullato." #: myframe.h:323 msgid "Line Style Rendering\tALT+9" msgstr "Rendering stile linea\tALT+9" #: myframe.h:505 msgid "Load interactive &tutorial...\tF1" msgstr "Carica interattivo e tutorial ...\tF1" #: system.h:288 #, c-format msgid "Loaded %s (%d cells, %d characters)." msgstr "Caricato%s (%d celle,%d caratteri)." #: myframe.h:281 myframe.h:284 msgid "" "Make a 1xN selection to indicate which column to sort on, and which rows to " "affect" msgstr "Crea una selezione 1xN per indicare su quale colonna ordinare e quali righe da " "influenzare" #: myframe.h:326 msgid "Make a hierarchy layout more vertical (default) or more horizontal" msgstr "Rendi un layout di gerarchia più verticale (predefinito) o più orizzontale" #: myframe.h:461 msgid "Minimize on close" msgstr "Riduci a icona in chiusura" #: myframe.h:459 msgid "Minimize to tray" msgstr "Riduci a icona" #: myframe.h:222 msgid "Move Cells Down\tCTRL+DOWN" msgstr "Sposta celle in basso\tCTRL+DOWN" #: myframe.h:219 msgid "Move Cells Left\tCTRL+LEFT" msgstr "Sposta celle a sinistra\tCTRL+LEFT" #: myframe.h:220 msgid "Move Cells Right\tCTRL+RIGHT" msgstr "Sposta celle a destra\tCTRL+RIGHT" #: myframe.h:221 msgid "Move Cells Up\tCTRL+UP" msgstr "Sposta celle in alto\tCTRL+UP" #: myframe.h:217 msgid "Move Selection Down\tDOWN" msgstr "Sposta selezione in basso\tDOWN" #: myframe.h:214 msgid "Move Selection Left\tLEFT" msgstr "Sposta selezione a sinistra\tLEFT" #: myframe.h:215 msgid "Move Selection Right\tRIGHT" msgstr "Sposta selezione destra\tRIGHT" #: myframe.h:216 msgid "Move Selection Up\tUP" msgstr "Sposta selezione su\tUP" #: myframe.h:209 msgid "Move to next cell\tTAB" msgstr "Passa alla cella successiva\tTAB" #: myframe.h:210 msgid "Move to previous cell\tSHIFT+TAB" msgstr "" #: document.h:1060 msgid "NOTE: key binding will take effect next run of TreeSheets." msgstr "NOTA: l'associazione dei tasti avrà effetto la prossima esecuzione di TreeSheets." #: myframe.h:468 msgid "Navigate in between cells with cursor keys" msgstr "Naviga tra le celle con i tasti cursore" #: myframe.h:557 msgid "New (CTRL+n)" msgstr "Nuovo (CTRL+n)" #: myframe.h:569 msgid "New Grid (INS)" msgstr "Nuova griglia (INS)" #: document.h:863 msgid "New Sheet" msgstr "Nuovo foglio" #: document.h:864 msgid "New file cancelled." msgstr "Nuovo file annullato." #: document.h:1460 msgid "No image in this cell." msgstr "Nessuna immagine in questa cella." #: document.h:1567 msgid "No matches for search." msgstr "Nessuna corrispondenza per la ricerca." #: document.h:1481 msgid "No matching cell found!" msgstr "Nessuna cella corrispondente trovata!" #: document.h:1563 msgid "No search string." msgstr "Nessuna stringa di ricerca." #: document.h:999 document.h:1293 msgid "No search." msgstr "Nessuna ricerca" #: document.h:1252 msgid "No style to paste." msgstr "Nessuno stile da incollare." #: document.h:1114 msgid "No text selected." msgstr "Nessun testo selezionato." #: system.h:215 msgid "Not a TreeSheets file." msgstr "Non un file TreeSheets." #: document.h:818 msgid "Nothing more to redo." msgstr "Niente da ripetere." #: document.h:810 msgid "Nothing more to undo." msgstr "Niente da annullare." #: document.h:1011 msgid "" "Now viewing TreeSheet to fit to the screen exactly, press F12 to return to " "normal." msgstr "Ora guardando TreeSheet per adattarsi esattamente allo schermo, premi F12 per tornare a " "normale." #: myframe.h:308 msgid "Open &file\tF4" msgstr "Apri &file\tF4" #: myframe.h:558 msgid "Open (CTRL+o)" msgstr "Apri (CTRL+o)" #: system.h:335 msgid "Open file cancelled." msgstr "Apri file cancellato." #: myframe.h:305 msgid "Open link in &browser\tF5" msgstr "Apri link in &browser\tF5" #: myframe.h:306 msgid "" "Opens up the text from the selected cell in browser (should start be a valid " "URL)" msgstr "Apre il testo dalla cella selezionata nel browser (dovrebbe essere un valido " "URL)" #: myframe.h:309 msgid "" "Opens up the text from the selected cell in default application for the file " "type" msgstr "Apre il testo dalla cella selezionata nell'applicazione predefinita per il file " "tipo" #: system.h:308 msgid "" "PNG decode failed on some images in this document\n" "They have been replaced by red squares." msgstr "Decodifica PNG non riuscita su alcune immagini in questo documento\n" "Sono stati sostituiti da quadrati rossi." #: system.h:310 msgid "PNG decoder failure" msgstr "Errore decodificatore PNG" #: myframe.h:174 msgid "Page Setup..." msgstr "Impostazione della pagina..." #: myframe.h:564 msgid "Paste (CTRL+v)" msgstr "Incolla (CTRL+v)" #: myframe.h:333 msgid "Paste Style Only\tCTRL+SHIFT+v" msgstr "Incolla solo stile\tCTRL+MAIUSC+v" #: myframe.h:449 msgid "Pick Custom &Color..." msgstr "Scegli colore personalizzato ..." #: myframe.h:447 msgid "Pick Default Font..." msgstr "Scegli il carattere di default .." #: myframe.h:451 msgid "Pick Document Background..." msgstr "Scegli lo sfondo del documento ..." #: myframe.h:862 msgid "Please enable (Options -> Show Toolbar) to use search." msgstr "Si prega di abilitare (Opzioni -> Mostra la barra degli strumenti) per utilizzare la ricerca." #: document.h:1462 msgid "Please enter the percentage you want the image scaled by:" msgstr "Inserisci la percentuale in cui desideri ridimensionare l'immagine:" #: document.h:1048 msgid "Please pick a menu item to change the key binding for" msgstr "Si prega di scegliere una voce di menu per modificare la legatura chiave per" #: document.h:840 msgid "Please select a TreeSheets file to load:" msgstr "Seleziona un file TreeSheets da caricare:" #: document.h:1272 msgid "Please select an image file:" msgstr "Si prega di selezionare un file immagine:" #: system.h:373 msgid "Please select file to import:" msgstr "Si prega di selezionare il file da importare:" #: myframe.h:855 msgid "Press F11 to exit fullscreen mode." msgstr "Premere F11 per uscire dalla modalità a schermo intero." #: document.h:943 msgid "Print Preview" msgstr "Anteprima di stampa" #: myframe.h:176 msgid "Print preview..." msgstr "Anteprima di stampa..." #: myframe.h:437 msgid "Radius &0" msgstr "Raggio &0" #: myframe.h:438 msgid "Radius &1" msgstr "Raggio &1" #: myframe.h:439 msgid "Radius &2" msgstr "Raggio &2" #: myframe.h:440 msgid "Radius &3" msgstr "Raggio &3" #: myframe.h:441 msgid "Radius &4" msgstr "Raggio &4" #: myframe.h:442 msgid "Radius &5" msgstr "Raggio &5" #: myframe.h:443 msgid "Radius &6" msgstr "Raggio &6" #: myframe.h:477 msgid "" "Reloads when another computer has changed a file (if you have made changes, " "asks)" msgstr "Ricarica quando un altro computer ha cambiato un file (se hai apportato modifiche, " "Chiedi)" #: myframe.h:302 msgid "Remove image(s) from the selected cells" msgstr "Rimuovi immagine(i) dalle celle selezionate" #: myframe.h:482 msgid "Render document centered" msgstr "Render del documento centrato" #: myframe.h:578 msgid "Replace " msgstr "Sostituisci" #: myframe.h:389 msgid "Replace &All" msgstr "Sostituisci &tutto" #: myframe.h:267 msgid "Reset &colors\tSHIFT+CTRL+c" msgstr "Ripristina &colori\tSHIFT+CTRL+c" #: myframe.h:198 msgid "Reset column widths\tSHIFT+CTRL+w" msgstr "Ripristina le larghezze delle colonne\tSHIFT+CTRL+w" #: myframe.h:572 msgid "Run" msgstr "Avvia" #: myframe.h:172 msgid "Save &As..." msgstr "Salva &come..." #: myframe.h:559 msgid "Save (CTRL+s)" msgstr "Salva (CTRL+s)" #: myframe.h:560 msgid "Save As" msgstr "Salva come" #: document.h:601 msgid "Save and Close" msgstr "Salva e chiudi" #: document.h:153 document.h:713 msgid "Save cancelled." msgstr "Salvataggio annullato" #: myframe.h:395 msgid "Scroll Down (mousewheel)\tALT+DOWN" msgstr "Scorri verso il basso (rotellina)\tALT+DOWN" #: myframe.h:394 msgid "Scroll Down (mousewheel)\tPGDN" msgstr "Scorri verso il basso (rotellina)\tPGDN" #: myframe.h:396 msgid "Scroll Left\tALT+LEFT" msgstr "Scorri a sinistra\tALT+LEFT" #: myframe.h:397 msgid "Scroll Right\tALT+RIGHT" msgstr "Scorri a destra\tALT+RIGHT" #: myframe.h:433 msgid "Scroll Sheet..." msgstr "Scorri foglio .." #: myframe.h:393 msgid "Scroll Up (mousewheel)\tALT+UP" msgstr "Scorri verso l'alto (rotellina)\tALT+UP" #: myframe.h:392 msgid "Scroll Up (mousewheel)\tPGUP" msgstr "Scorri verso l'alto (rotellina)\tPGUP" #: myframe.h:574 msgid "Search " msgstr "Cerca" #: myframe.h:231 msgid "Select &Parent\tESC" msgstr "Seleziona &Genitore\tESC" #: myframe.h:212 msgid "Select &all in current grid\tCTRL+a" msgstr "Seleziona &tutto nella griglia corrente\tCTRL+a" #: myframe.h:232 msgid "Select First &Child\tSHIFT+ENTER" msgstr "Seleziona Primo &Figlio\tSHIFT+ENTER" #: document.h:1414 msgid "Selected grid is not a table: cells must not already have sub-grids." msgstr "La griglia selezionata non è una tabella: le celle non devono avere già sottogriglie." #: myframe.h:159 msgid "Semi-Colon delimited text (CSV)..." msgstr "Testo delimitato da semi-colonna (CSV) .." #: myframe.h:378 msgid "Set Grid Border Width..." msgstr "Imposta la larghezza del bordo della griglia ..." #: document.h:935 msgid "Set Print Scale" msgstr "Imposta scala di stampa" #: myframe.h:175 msgid "Set Print Scale..." msgstr "Imposta scala di stampa ..." #: myframe.h:407 #, c-format msgid "Show 1% less than the last filter" msgstr "Mostra 1% in meno rispetto all'ultimo filtro" #: myframe.h:406 #, c-format msgid "Show 1% more than the last filter" msgstr "Mostra 1% in più rispetto all'ultimo filtro" #: myframe.h:403 #, c-format msgid "Show 10% of last edits" msgstr "Mostra il 10% delle ultime modifiche" #: myframe.h:404 #, c-format msgid "Show 20% of last edits" msgstr "Mostra il 20% delle ultime modifiche" #: myframe.h:402 #, c-format msgid "Show 5% of last edits" msgstr "Mostra il 5% delle ultime modifiche" #: myframe.h:405 #, c-format msgid "Show 50% of last edits" msgstr "Mostra il 50% delle ultime modifiche" #: myframe.h:453 msgid "Show Statusbar" msgstr "Mostra la barra di stato" #: myframe.h:455 msgid "Show Toolbar" msgstr "Mostra la barra degli strumenti" #: myframe.h:401 msgid "Show only cells in current search" msgstr "Mostra solo le celle nella ricerca corrente" #: myframe.h:463 msgid "Single click maximize from tray" msgstr "Un singolo clic ingrandisce dal vassoio" #: system.h:358 #, c-format msgid "Size %d" msgstr "Dimensione% d" #: myframe.h:280 msgid "Sort &Ascending" msgstr "Ordine crescente" #: myframe.h:283 msgid "Sort &Descending" msgstr "Ordine decrescente" #: myframe.h:250 msgid "Start of line of text\tHOME" msgstr "Inizio della riga di testo\tHOME" #: myframe.h:252 msgid "Start of text\tCTRL+HOME" msgstr "Inizio del testo\tCTRL+HOME" #: myframe.h:287 msgid "Swap all cells with this text at this level (or above) with the parent" msgstr "Scambia tutte le celle con questo testo a questo livello (o sopra) con il genitore" #: myframe.h:466 msgid "Swap mousewheel scrolling and zooming" msgstr "Scambia la rotellina del mouse per scorrere e zoomare" #: myframe.h:418 msgid "Switch to &next file/tab" msgstr "Passa a &successivo file/scheda" #: myframe.h:414 msgid "Switch to &next file/tab\tCTRL+TAB" msgstr "Passa a & successivo file/scheda\tCTRL+TAB" #: myframe.h:420 msgid "Switch to &previous file/tab\tSHIFT+CTRL+TAB" msgstr "Passa al file precedente/scheda\tSHIFT+CTRL+TAB" #: myframe.h:160 msgid "Tab delimited text..." msgstr "Testo delimitato da tabulazioni .." #: myframe.h:379 msgid "Tag..." msgstr "Tag.." #: myframe.h:293 msgid "" "Takes a hierarchy (nested 1xN or Nx1 grids) and converts it into a flat NxN " "grid, useful for export to spreadsheets" msgstr "Prende una gerarchia (griglie nidificate 1xN o Nx1) e lo converte in un NxN piatto " "griglia, utile per l'esportazione in fogli di calcolo" #: myframe.h:585 msgid "Text " msgstr "Testo" #: myframe.h:375 msgid "Text &Editing..." msgstr "Testo e modifica ..." #: myframe.h:376 msgid "Text Sizing..." msgstr "Dimensionamento del testo ..." #: myframe.h:377 msgid "Text Style..." msgstr "Stile di testo ..." #: document.h:429 msgid "Text size decreased." msgstr "La dimensione del testo è diminuita." #: document.h:429 msgid "Text size increased." msgstr "La dimensione del testo è aumentata." #: document.h:875 msgid "The Free Form Hierarchical Information Organizer" msgstr "Organizzatore di informazioni gerarchico a forma libera" #: document.h:402 msgid "This operation doesn't work on thin selections." msgstr "Questa operazione non funziona su selezioni sottili." #: document.h:403 msgid "This operation requires a cell that contains a grid." msgstr "questa operazione richiede una cella che contiene una griglia." #: document.h:400 msgid "This operation requires a selection." msgstr "Questa operazione richiede una selezione." #: document.h:401 msgid "This operation works on a single selected cell only." msgstr "Questa operazione funziona solo su una singola cella selezionata." #: myframe.h:423 msgid "Toggle &Fullscreen View\tCTRL+F11" msgstr "Attiva / disattiva visualizzazione a schermo\tCTRL+F11" #: myframe.h:425 msgid "Toggle &Fullscreen View\tF11" msgstr "Attiva/disattiva la visualizzazione a schermo intero\tF11" #: myframe.h:429 msgid "Toggle &Scaled Presentation View\tCTRL+F12" msgstr "Mostra presentazione a scalare e ridimensionata\tCTRL+F12" #: myframe.h:431 msgid "Toggle &Scaled Presentation View\tF12" msgstr "Mostra presentazione a scalare e ridimensionata\tF12" #: myframe.h:360 msgid "Toggle Fold\tCTRL+F10" msgstr "Commuta piegatura\tCTRL+F10" #: myframe.h:362 msgid "Toggle Fold\tF10" msgstr "Commuta piegatura\tF10" #: myframe.h:325 msgid "Toggle Vertical Layout\tF7" msgstr "Attiva/disattiva layout verticale\tF7" #: myframe.h:260 msgid "Toggle cell &BOLD\tCTRL+b" msgstr "Cambia cella &GRASSETTO\tCTRL+b" #: myframe.h:261 msgid "Toggle cell &ITALIC\tCTRL+i" msgstr "Commuta cella e ITALIC\tCTRL+i" #: myframe.h:264 msgid "Toggle cell &strikethrough\tCTRL+t" msgstr "Attiva/disattiva attivazione cella tramite\tCTRL+t" #: myframe.h:262 msgid "Toggle cell &typewriter" msgstr "Attiva/disattiva la cella e la macchina da scrivere" #: myframe.h:263 msgid "Toggle cell &underlined\tCTRL+u" msgstr "Attiva/disattiva sottolineatura cella\tCTRL+u" #: myframe.h:364 msgid "Toggles showing the grid of the selected cell(s)" msgstr "Attiva/disattiva la visualizzazione della griglia delle cella(e) selezionate" #: myframe.h:400 msgid "Turn filter &off" msgstr "Spegni il filtro" #: myframe.h:562 msgid "Undo (CTRL+z)" msgstr "Annulla (CTRL+z)" #: myframe.h:367 msgid "Unfold All\tCTRL+ALT+F10" msgstr "Dispiega tutto\tCTRL+ALT+F10" #: myframe.h:368 msgid "Unfolds the grid of the selected cell(s) recursively" msgstr "Dispiega la griglia delle celle selezionata(e) in modo ricorsivo" #: myframe.h:493 msgid "Variable &Assign" msgstr "Assegnazione &variabile" #: myframe.h:494 msgid "Variable &Read" msgstr "Variabile &Letta" #: myframe.h:314 msgid "Vertical Layout with Bubble Style Rendering\tALT+2" msgstr "Layout verticale con Rendering stile Bubble\tALT+2" #: myframe.h:313 msgid "Vertical Layout with Grid Style Rendering\tALT+1" msgstr "Layout verticale con rendering a stile griglia\tALT+1" #: myframe.h:315 msgid "Vertical Layout with Line Style Rendering\tALT+3" msgstr "Layout verticale con Rendering stile linea\tALT+3" #: myframe.h:506 msgid "View tutorial &web page..." msgstr "Visualizza tutorial e pagina web ..." #: document.h:862 msgid "What size grid would you like to start with?" msgstr "Con quale dimensioni di griglia vorresti iniziare?" #: system.h:359 #, c-format msgid "Width %d" msgstr "Larghezza% d" #: myframe.h:240 msgid "Word Left\tCTRL+LEFT" msgstr "Parola sinistra\tCTRL+LEFT" #: myframe.h:241 msgid "Word Right\tCTRL+RIGHT" msgstr "Parola destra\tCTRL+RIGHT" #: myframe.h:156 msgid "XML (attributes too, for OPML etc)..." msgstr "XML (anche attributi, per OPML ecc.)" #: myframe.h:155 msgid "XML..." msgstr "XML..." #: document.h:187 msgid "Zlib error while writing file." msgstr "Errore Zlib durante la scrittura del file" #: myframe.h:410 msgid "Zoom &In (CTRL+mousewheel)\tCTRL+PGUP" msgstr "Zoom &Avanti (CTRL+rotellina del mouse)\tCTRL+PGUP" #: myframe.h:411 msgid "Zoom &Out (CTRL+mousewheel)\tCTRL+PGDN" msgstr "Zoom &Indietro (CTRL+rotellina del mouse)\tCTRL+PGDN" #: myframe.h:566 msgid "Zoom In (CTRL+mousewheel)" msgstr "Zoom avanti (CTRL+rotellina del mouse)" #: myframe.h:567 msgid "Zoom Out (CTRL+mousewheel)" msgstr "Zoom indietro (CTR+rotellina del mouse)" #: document.h:434 msgid "Zoomed in." msgstr "Ingrandito." #: document.h:434 msgid "Zoomed out." msgstr "Rimpicciolito" #: myframe.h:807 msgid "change will take effect next run of TreeSheets" msgstr "il cambiamento avrà effetto la prossima esecuzione di TreeSheets" #: myframe.h:279 msgid "changes the orientation of a grid" msgstr "cambia l'orientamento di una griglia" #: system.h:420 msgid "couldn't import file!" msgstr "impossibile importare il file!" #: myframe.h:334 msgid "only sets the colors and style of the copied cell, and keeps the text" msgstr "imposta solo i colori e lo stile della cella copiata e mantiene il testo" #: document.h:1514 msgid "only works in cell text mode" msgstr "funziona solo in modalità testo cella" #: myframe.h:338 msgid "redo any undo steps, if you haven't made changes since" msgstr "ripristina i passaggi di annullamento, se non hai apportato modifiche da allora" #: myframe.h:336 msgid "revert the changes, one step at a time" msgstr "ripristinare le modifiche, un passo alla volta" #: document.h:934 msgid "scale:" msgstr "scala:" #: document.h:863 msgid "size:" msgstr "Dimensione:" treesheets-1.0.2/TS/translations/readme_translations.txt000066400000000000000000000015361352107072600235430ustar00rootroot00000000000000This folder contains translations of the TreeSheets UI into various languages. To work on these translations, you need xgettext/msginit/msgfmt commands, which likely are already on your system on Linux/OSX, or on Windows you can get them from e.g. https://mlocati.github.io/articles/gettext-iconv-windows.html This generally follows the gettext standard, see e.g. http://www.labri.fr/perso/fleury/posts/programming/a-quick-gettext-tutorial.html To recompile the main template file (extracting strings from the source code), run ../../src/genbot.bat or similar. To create a translation for a new language, run (inside the tranlations directory): msginit --input ts.pot --locale=de --ouput=de/ts.po and replace "de" with the new language code. To re-compile the language definitions, run de/compile.bat, or a copy of that file in another language directory. treesheets-1.0.2/TS/translations/ts.pot000066400000000000000000000572371352107072600201270ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-03-26 16:49-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: myframe.h:1028 #, c-format msgid "" "%s\n" "has been modified on disk by another program / computer:\n" "Would you like to discard your changes and re-load from disk?" msgstr "" #: myframe.h:504 msgid "&About..." msgstr "" #: myframe.h:270 msgid "&Add Cell Text as Tag" msgstr "" #: myframe.h:298 msgid "&Add Image" msgstr "" #: myframe.h:374 msgid "&Browsing..." msgstr "" #: myframe.h:501 msgid "&Clear Views" msgstr "" #: myframe.h:169 msgid "&Close\tCTRL+w" msgstr "" #: myframe.h:145 msgid "&Comma delimited text (CSV)..." msgstr "" #: myframe.h:330 msgid "&Copy\tCTRL+c" msgstr "" #: myframe.h:491 msgid "&Data" msgstr "" #: myframe.h:188 msgid "&Decrease text size (SHIFT+mousewheel)\tSHIFT+PGDN" msgstr "" #: myframe.h:340 msgid "&Delete After\tDEL" msgstr "" #: myframe.h:518 msgid "&Edit" msgstr "" #: myframe.h:182 msgid "&Exit\tCTRL+q" msgstr "" #: myframe.h:517 msgid "&File" msgstr "" #: myframe.h:292 msgid "&Flatten" msgstr "" #: myframe.h:386 msgid "&Go To Next Search Result\tF3" msgstr "" #: myframe.h:371 msgid "&Grid Reorganization..." msgstr "" #: myframe.h:135 msgid "&HTML (Tables+Styling)..." msgstr "" #: myframe.h:527 msgid "&Help" msgstr "" #: myframe.h:288 msgid "&Hierarchify" msgstr "" #: myframe.h:495 msgid "&Horizontal View" msgstr "" #: myframe.h:149 msgid "&Image..." msgstr "" #: myframe.h:373 msgid "&Images..." msgstr "" #: myframe.h:187 msgid "&Increase text size (SHIFT+mousewheel)\tSHIFT+PGUP" msgstr "" #: myframe.h:348 msgid "&Insert New Grid\tCTRL+g" msgstr "" #: myframe.h:350 msgid "&Insert New Grid\tINS" msgstr "" #: myframe.h:372 msgid "&Layout && Render Style..." msgstr "" #: myframe.h:500 msgid "&Mark as" msgstr "" #: myframe.h:167 msgid "&New\tCTRL+n" msgstr "" #: myframe.h:168 msgid "&Open...\tCTRL+o" msgstr "" #: myframe.h:492 msgid "&Operation" msgstr "" #: myframe.h:521 msgid "&Options" msgstr "" #: myframe.h:332 msgid "&Paste\tCTRL+v" msgstr "" #: myframe.h:177 msgid "&Print...\tCTRL+p" msgstr "" #: myframe.h:522 msgid "&Program" msgstr "" #: myframe.h:170 msgid "&Recent files" msgstr "" #: myframe.h:337 msgid "&Redo\tCTRL+y" msgstr "" #: myframe.h:271 msgid "&Remove Cell Text from Tags" msgstr "" #: myframe.h:301 msgid "&Remove Image(s)" msgstr "" #: myframe.h:387 msgid "&Replace in Current Selection\tCTRL+h" msgstr "" #: myframe.h:388 msgid "&Replace in Current Selection & Jump Next\tCTRL+j" msgstr "" #: myframe.h:189 msgid "&Reset text sizes\tSHIFT+CTRL+s" msgstr "" #: myframe.h:266 msgid "&Reset text styles\tSHIFT+CTRL+r" msgstr "" #: myframe.h:488 msgid "&Roundness of grid borders..." msgstr "" #: myframe.h:499 msgid "&Run" msgstr "" #: myframe.h:171 msgid "&Save\tCTRL+s" msgstr "" #: myframe.h:299 msgid "&Scale Image" msgstr "" #: myframe.h:519 msgid "&Search" msgstr "" #: myframe.h:385 msgid "&Search\tCTRL+f" msgstr "" #: myframe.h:370 msgid "&Selection..." msgstr "" #: myframe.h:272 msgid "&Set Cell Text to tag (use CTRL+RMB)" msgstr "" #: myframe.h:450 msgid "&Set Custom Color From Cell BG" msgstr "" #: myframe.h:190 msgid "&Shrink text of all sub-grids\tSHIFT+CTRL+m" msgstr "" #: myframe.h:278 msgid "&Transpose\tSHIFT+CTRL+t" msgstr "" #: myframe.h:336 msgid "&Undo\tCTRL+z" msgstr "" #: myframe.h:496 msgid "&Vertical View" msgstr "" #: myframe.h:520 msgid "&View" msgstr "" #: myframe.h:353 msgid "&Wrap in new parent\tF9" msgstr "" #: myframe.h:132 msgid "&XML..." msgstr "" #: document.h:1013 msgid "1:1 scale restored." msgstr "" #: system.h:197 msgid "A temporary autosave file exists, would you like to load it instead?" msgstr "" #: myframe.h:570 msgid "Add Image" msgstr "" #: myframe.h:352 msgid "Adds a grid to the selected cell" msgstr "" #: myframe.h:298 msgid "Adds an image to the selected cell" msgstr "" #: myframe.h:476 msgid "Auto reload documents" msgstr "" #: myframe.h:479 msgid "Automatically export a .html on every save" msgstr "" #: system.h:198 msgid "Autosave load" msgstr "" #: myframe.h:473 msgid "Autosave to .tmp" msgstr "" #: myframe.h:486 msgid "Black and white toolbar icons" msgstr "" #: myframe.h:588 msgid "Border " msgstr "" #: myframe.h:322 msgid "Bubble Style Rendering\tALT+8" msgstr "" #: document.h:1493 msgid "Can only move this cell into an Nx1 or 1xN grid." msgstr "" #: document.h:1660 msgid "" "Can't sort: make a 1xN selection to indicate what column to sort on, and " "what rows to affect" msgstr "" #: document.h:601 msgid "Cancel" msgstr "" #: myframe.h:257 msgid "Cancel text edits\tESC" msgstr "" #: system.h:266 msgid "Cannot decompress file." msgstr "" #: document.h:1611 msgid "Cannot drag & drop more than 1 file." msgstr "" #: document.h:655 msgid "" "Cannot export grid that is not flat (zoom the view to the desired grid, and/" "or use Flatten)." msgstr "" #: document.h:1455 msgid "Cannot find file." msgstr "" #: document.h:1448 msgid "Cannot launch browser for this link." msgstr "" #: document.h:1491 msgid "Cannot move this cell up in the hierarchy." msgstr "" #: system.h:211 msgid "Cannot open file." msgstr "" #: system.h:237 msgid "Cannot tell/seek document?" msgstr "" #: myframe.h:582 msgid "Cell " msgstr "" #: myframe.h:448 msgid "Change a key binding..." msgstr "" #: myframe.h:300 msgid "Change the image size if it is too big or too small" msgstr "" #: document.h:600 msgid "Changes have been made, are you sure you wish to continue?" msgstr "" #: document.h:829 msgid "Choose CSV file to write" msgstr "" #: document.h:826 msgid "Choose HTML file to write" msgstr "" #: document.h:828 msgid "Choose PNG file to write" msgstr "" #: document.h:827 msgid "Choose Text file to write" msgstr "" #: document.h:711 msgid "Choose TreeSheets file to save:" msgstr "" #: document.h:824 msgid "Choose XML file to write" msgstr "" #: document.h:419 msgid "Column width decreased." msgstr "" #: document.h:419 msgid "Column width increased." msgstr "" #: myframe.h:158 msgid "Comma delimited text (CSV)..." msgstr "" #: myframe.h:289 msgid "" "Convert an NxN grid with repeating elements per column into an 1xN grid with " "hierarchy, useful to convert data from spreadsheets" msgstr "" #: myframe.h:563 msgid "Copy (CTRL+c)" msgstr "" #: myframe.h:331 msgid "Copy As Continuous Text" msgstr "" #: system.h:243 msgid "Corrupt PNG header." msgstr "" #: system.h:295 msgid "Corrupt block header." msgstr "" #: myframe.h:471 msgid "Create .bak files" msgstr "" #: myframe.h:354 msgid "Creates a new level of hierarchy around the current selection" msgstr "" #: myframe.h:329 msgid "Cu&t\tCTRL+x" msgstr "" #: myframe.h:238 msgid "Cursor Left\tLEFT" msgstr "" #: myframe.h:239 msgid "Cursor Right\tRIGHT" msgstr "" #: myframe.h:193 msgid "Decrease column width (ALT+mousewheel)\tALT+PGDN" msgstr "" #: myframe.h:197 msgid "Decrease column width (no sub grids)\tCTRL+ALT+PGDN" msgstr "" #: myframe.h:343 msgid "Delete Before\tBACK" msgstr "" #: myframe.h:341 msgid "" "Deletes the column of cells after the selected grid line, or the row below" msgstr "" #: myframe.h:344 msgid "" "Deletes the column of cells before the selected grid line, or the row above" msgstr "" #: document.h:601 msgid "Discard Changes" msgstr "" #: system.h:361 #, c-format msgid "Edited %s" msgstr "" #: document.h:1503 msgid "Empty strings cannot be tags." msgstr "" #: myframe.h:251 msgid "End of line of text\tEND" msgstr "" #: myframe.h:253 msgid "End of text\tCTRL+END" msgstr "" #: myframe.h:255 msgid "Enter/exit text edit mode\tENTER" msgstr "" #: myframe.h:256 msgid "Enter/exit text edit mode\tF2" msgstr "" #: document.h:671 msgid "Error exporting file!" msgstr "" #: myframe.h:105 msgid "Error loading core data file (TreeSheets not installed correctly?)" msgstr "" #: document.h:667 msgid "Error writing PNG file!" msgstr "" #: document.h:165 msgid "Error writing TreeSheets file! (try saving under new filename)." msgstr "" #: document.h:672 msgid "Error writing to file!" msgstr "" #: document.h:167 msgid "Error writing to file." msgstr "" #: document.h:803 msgid "Evaluation finished." msgstr "" #: myframe.h:179 msgid "Export &view as" msgstr "" #: document.h:646 msgid "Export cancelled." msgstr "" #: myframe.h:146 msgid "" "Export the current view as CSV. Good for spreadsheets and databases. Only " "works on grids with no sub-grids (use the Flatten operation first if need be)" msgstr "" #: myframe.h:139 msgid "" "Export the current view as HTML as nested headers, suitable for importing " "into Word's outline mode" msgstr "" #: myframe.h:136 msgid "" "Export the current view as HTML using nested tables, that will look somewhat " "like the TreeSheet" msgstr "" #: myframe.h:133 msgid "" "Export the current view as XML (which can also be reimported without losing " "structure)" msgstr "" #: myframe.h:150 msgid "" "Export the current view as an image. Useful for faithfull renderings of the " "TreeSheet, and programs that don't accept any of the above options" msgstr "" #: myframe.h:142 msgid "" "Export the current view as tree structured text, using spaces for each " "indentation level. Suitable for importing into mindmanagers and general text " "programs" msgstr "" #: myframe.h:227 msgid "Extend Selection Down\tSHIFT+DOWN" msgstr "" #: myframe.h:228 msgid "Extend Selection Full Columns" msgstr "" #: myframe.h:229 msgid "Extend Selection Full Rows" msgstr "" #: myframe.h:224 myframe.h:243 msgid "Extend Selection Left\tSHIFT+LEFT" msgstr "" #: myframe.h:225 myframe.h:244 msgid "Extend Selection Right\tSHIFT+RIGHT" msgstr "" #: myframe.h:226 msgid "Extend Selection Up\tSHIFT+UP" msgstr "" #: myframe.h:245 msgid "Extend Selection Word Left\tSHIFT+CTRL+LEFT" msgstr "" #: myframe.h:246 msgid "Extend Selection Word Right\tSHIFT+CTRL+RIGHT" msgstr "" #: myframe.h:248 msgid "Extend Selection to End\tSHIFT+END" msgstr "" #: myframe.h:247 msgid "Extend Selection to Start\tSHIFT+HOME" msgstr "" #: myframe.h:484 msgid "Faster line rendering" msgstr "" #: myframe.h:457 msgid "File Tabs on the bottom" msgstr "" #: system.h:270 msgid "File corrupted!" msgstr "" #: document.h:705 msgid "File exported successfully." msgstr "" #: myframe.h:1047 msgid "" "File has been re-loaded because of modifications of another program / " "computer" msgstr "" #: system.h:421 msgid "File load error." msgstr "" #: myframe.h:1033 msgid "File modification conflict!" msgstr "" #: system.h:217 msgid "File of newer version." msgstr "" #: document.h:208 msgid "File saved succesfully." msgstr "" #: myframe.h:434 msgid "Filter..." msgstr "" #: myframe.h:365 msgid "Fold All\tCTRL+SHIFT+F10" msgstr "" #: myframe.h:366 msgid "Folds the grid of the selected cell(s) recursively" msgstr "" #: myframe.h:234 msgid "Go To &Matching Cell\tF6" msgstr "" #: myframe.h:235 msgid "Go To Matching Cell (Reverse)\tSHIFT+F6" msgstr "" #: myframe.h:321 msgid "Grid Style Rendering\tALT+7" msgstr "" #: myframe.h:138 msgid "HTML (&Outline)..." msgstr "" #: myframe.h:286 msgid "Hierarchy &Swap\tF8" msgstr "" #: myframe.h:273 msgid "" "Hold CTRL while pressing right mouse button to quickly set a tag for the " "current cell using a popup menu" msgstr "" #: myframe.h:318 msgid "Horizontal Layout with Bubble Style Rendering\tALT+5" msgstr "" #: myframe.h:317 msgid "Horizontal Layout with Grid Style Rendering\tALT+4" msgstr "" #: myframe.h:319 msgid "Horizontal Layout with Line Style Rendering\tALT+6" msgstr "" #: document.h:934 msgid "How many pixels wide should a page be? (0 for auto fit)" msgstr "" #: myframe.h:591 msgid "Image " msgstr "" #: document.h:1463 msgid "Image Resize" msgstr "" #: myframe.h:180 msgid "Import file from" msgstr "" #: myframe.h:192 msgid "Increase column width (ALT+mousewheel)\tALT+PGUP" msgstr "" #: myframe.h:195 msgid "Increase column width (no sub grids)\tCTRL+ALT+PGUP" msgstr "" #: myframe.h:141 msgid "Indented &Text..." msgstr "" #: myframe.h:157 msgid "Indented text..." msgstr "" #: myframe.h:106 msgid "Initialization Error" msgstr "" #: document.h:1558 msgid "Internal error: unimplemented operation!" msgstr "" #: document.h:1049 msgid "Key binding" msgstr "" #: document.h:1063 msgid "Keybinding cancelled." msgstr "" #: myframe.h:323 msgid "Line Style Rendering\tALT+9" msgstr "" #: myframe.h:505 msgid "Load interactive &tutorial...\tF1" msgstr "" #: system.h:288 #, c-format msgid "Loaded %s (%d cells, %d characters)." msgstr "" #: myframe.h:281 myframe.h:284 msgid "" "Make a 1xN selection to indicate which column to sort on, and which rows to " "affect" msgstr "" #: myframe.h:326 msgid "Make a hierarchy layout more vertical (default) or more horizontal" msgstr "" #: myframe.h:461 msgid "Minimize on close" msgstr "" #: myframe.h:459 msgid "Minimize to tray" msgstr "" #: myframe.h:222 msgid "Move Cells Down\tCTRL+DOWN" msgstr "" #: myframe.h:219 msgid "Move Cells Left\tCTRL+LEFT" msgstr "" #: myframe.h:220 msgid "Move Cells Right\tCTRL+RIGHT" msgstr "" #: myframe.h:221 msgid "Move Cells Up\tCTRL+UP" msgstr "" #: myframe.h:217 msgid "Move Selection Down\tDOWN" msgstr "" #: myframe.h:214 msgid "Move Selection Left\tLEFT" msgstr "" #: myframe.h:215 msgid "Move Selection Right\tRIGHT" msgstr "" #: myframe.h:216 msgid "Move Selection Up\tUP" msgstr "" #: myframe.h:209 msgid "Move to next cell\tTAB" msgstr "" #: myframe.h:210 msgid "Move to previous cell\tSHIFT+TAB" msgstr "" #: document.h:1060 msgid "NOTE: key binding will take effect next run of TreeSheets." msgstr "" #: myframe.h:468 msgid "Navigate in between cells with cursor keys" msgstr "" #: myframe.h:557 msgid "New (CTRL+n)" msgstr "" #: myframe.h:569 msgid "New Grid (INS)" msgstr "" #: document.h:863 msgid "New Sheet" msgstr "" #: document.h:864 msgid "New file cancelled." msgstr "" #: document.h:1460 msgid "No image in this cell." msgstr "" #: document.h:1567 msgid "No matches for search." msgstr "" #: document.h:1481 msgid "No matching cell found!" msgstr "" #: document.h:1563 msgid "No search string." msgstr "" #: document.h:999 document.h:1293 msgid "No search." msgstr "" #: document.h:1252 msgid "No style to paste." msgstr "" #: document.h:1114 msgid "No text selected." msgstr "" #: system.h:215 msgid "Not a TreeSheets file." msgstr "" #: document.h:818 msgid "Nothing more to redo." msgstr "" #: document.h:810 msgid "Nothing more to undo." msgstr "" #: document.h:1011 msgid "" "Now viewing TreeSheet to fit to the screen exactly, press F12 to return to " "normal." msgstr "" #: myframe.h:308 msgid "Open &file\tF4" msgstr "" #: myframe.h:558 msgid "Open (CTRL+o)" msgstr "" #: system.h:335 msgid "Open file cancelled." msgstr "" #: myframe.h:305 msgid "Open link in &browser\tF5" msgstr "" #: myframe.h:306 msgid "" "Opens up the text from the selected cell in browser (should start be a valid " "URL)" msgstr "" #: myframe.h:309 msgid "" "Opens up the text from the selected cell in default application for the file " "type" msgstr "" #: system.h:308 msgid "" "PNG decode failed on some images in this document\n" "They have been replaced by red squares." msgstr "" #: system.h:310 msgid "PNG decoder failure" msgstr "" #: myframe.h:174 msgid "Page Setup..." msgstr "" #: myframe.h:564 msgid "Paste (CTRL+v)" msgstr "" #: myframe.h:333 msgid "Paste Style Only\tCTRL+SHIFT+v" msgstr "" #: myframe.h:449 msgid "Pick Custom &Color..." msgstr "" #: myframe.h:447 msgid "Pick Default Font..." msgstr "" #: myframe.h:451 msgid "Pick Document Background..." msgstr "" #: myframe.h:862 msgid "Please enable (Options -> Show Toolbar) to use search." msgstr "" #: document.h:1462 msgid "Please enter the percentage you want the image scaled by:" msgstr "" #: document.h:1048 msgid "Please pick a menu item to change the key binding for" msgstr "" #: document.h:840 msgid "Please select a TreeSheets file to load:" msgstr "" #: document.h:1272 msgid "Please select an image file:" msgstr "" #: system.h:373 msgid "Please select file to import:" msgstr "" #: myframe.h:855 msgid "Press F11 to exit fullscreen mode." msgstr "" #: document.h:943 msgid "Print Preview" msgstr "" #: myframe.h:176 msgid "Print preview..." msgstr "" #: myframe.h:437 msgid "Radius &0" msgstr "" #: myframe.h:438 msgid "Radius &1" msgstr "" #: myframe.h:439 msgid "Radius &2" msgstr "" #: myframe.h:440 msgid "Radius &3" msgstr "" #: myframe.h:441 msgid "Radius &4" msgstr "" #: myframe.h:442 msgid "Radius &5" msgstr "" #: myframe.h:443 msgid "Radius &6" msgstr "" #: myframe.h:477 msgid "" "Reloads when another computer has changed a file (if you have made changes, " "asks)" msgstr "" #: myframe.h:302 msgid "Remove image(s) from the selected cells" msgstr "" #: myframe.h:482 msgid "Render document centered" msgstr "" #: myframe.h:578 msgid "Replace " msgstr "" #: myframe.h:389 msgid "Replace &All" msgstr "" #: myframe.h:267 msgid "Reset &colors\tSHIFT+CTRL+c" msgstr "" #: myframe.h:198 msgid "Reset column widths\tSHIFT+CTRL+w" msgstr "" #: myframe.h:572 msgid "Run" msgstr "" #: myframe.h:172 msgid "Save &As..." msgstr "" #: myframe.h:559 msgid "Save (CTRL+s)" msgstr "" #: myframe.h:560 msgid "Save As" msgstr "" #: document.h:601 msgid "Save and Close" msgstr "" #: document.h:153 document.h:713 msgid "Save cancelled." msgstr "" #: myframe.h:395 msgid "Scroll Down (mousewheel)\tALT+DOWN" msgstr "" #: myframe.h:394 msgid "Scroll Down (mousewheel)\tPGDN" msgstr "" #: myframe.h:396 msgid "Scroll Left\tALT+LEFT" msgstr "" #: myframe.h:397 msgid "Scroll Right\tALT+RIGHT" msgstr "" #: myframe.h:433 msgid "Scroll Sheet..." msgstr "" #: myframe.h:393 msgid "Scroll Up (mousewheel)\tALT+UP" msgstr "" #: myframe.h:392 msgid "Scroll Up (mousewheel)\tPGUP" msgstr "" #: myframe.h:574 msgid "Search " msgstr "" #: myframe.h:231 msgid "Select &Parent\tESC" msgstr "" #: myframe.h:212 msgid "Select &all in current grid\tCTRL+a" msgstr "" #: myframe.h:232 msgid "Select First &Child\tSHIFT+ENTER" msgstr "" #: document.h:1414 msgid "Selected grid is not a table: cells must not already have sub-grids." msgstr "" #: myframe.h:159 msgid "Semi-Colon delimited text (CSV)..." msgstr "" #: myframe.h:378 msgid "Set Grid Border Width..." msgstr "" #: document.h:935 msgid "Set Print Scale" msgstr "" #: myframe.h:175 msgid "Set Print Scale..." msgstr "" #: myframe.h:407 #, c-format msgid "Show 1% less than the last filter" msgstr "" #: myframe.h:406 #, c-format msgid "Show 1% more than the last filter" msgstr "" #: myframe.h:403 #, c-format msgid "Show 10% of last edits" msgstr "" #: myframe.h:404 #, c-format msgid "Show 20% of last edits" msgstr "" #: myframe.h:402 #, c-format msgid "Show 5% of last edits" msgstr "" #: myframe.h:405 #, c-format msgid "Show 50% of last edits" msgstr "" #: myframe.h:453 msgid "Show Statusbar" msgstr "" #: myframe.h:455 msgid "Show Toolbar" msgstr "" #: myframe.h:401 msgid "Show only cells in current search" msgstr "" #: myframe.h:463 msgid "Single click maximize from tray" msgstr "" #: system.h:358 #, c-format msgid "Size %d" msgstr "" #: myframe.h:280 msgid "Sort &Ascending" msgstr "" #: myframe.h:283 msgid "Sort &Descending" msgstr "" #: myframe.h:250 msgid "Start of line of text\tHOME" msgstr "" #: myframe.h:252 msgid "Start of text\tCTRL+HOME" msgstr "" #: myframe.h:287 msgid "Swap all cells with this text at this level (or above) with the parent" msgstr "" #: myframe.h:466 msgid "Swap mousewheel scrolling and zooming" msgstr "" #: myframe.h:418 msgid "Switch to &next file/tab" msgstr "" #: myframe.h:414 msgid "Switch to &next file/tab\tCTRL+TAB" msgstr "" #: myframe.h:420 msgid "Switch to &previous file/tab\tSHIFT+CTRL+TAB" msgstr "" #: myframe.h:160 msgid "Tab delimited text..." msgstr "" #: myframe.h:379 msgid "Tag..." msgstr "" #: myframe.h:293 msgid "" "Takes a hierarchy (nested 1xN or Nx1 grids) and converts it into a flat NxN " "grid, useful for export to spreadsheets" msgstr "" #: myframe.h:585 msgid "Text " msgstr "" #: myframe.h:375 msgid "Text &Editing..." msgstr "" #: myframe.h:376 msgid "Text Sizing..." msgstr "" #: myframe.h:377 msgid "Text Style..." msgstr "" #: document.h:429 msgid "Text size decreased." msgstr "" #: document.h:429 msgid "Text size increased." msgstr "" #: document.h:875 msgid "The Free Form Hierarchical Information Organizer" msgstr "" #: document.h:402 msgid "This operation doesn't work on thin selections." msgstr "" #: document.h:403 msgid "This operation requires a cell that contains a grid." msgstr "" #: document.h:400 msgid "This operation requires a selection." msgstr "" #: document.h:401 msgid "This operation works on a single selected cell only." msgstr "" #: myframe.h:423 msgid "Toggle &Fullscreen View\tCTRL+F11" msgstr "" #: myframe.h:425 msgid "Toggle &Fullscreen View\tF11" msgstr "" #: myframe.h:429 msgid "Toggle &Scaled Presentation View\tCTRL+F12" msgstr "" #: myframe.h:431 msgid "Toggle &Scaled Presentation View\tF12" msgstr "" #: myframe.h:360 msgid "Toggle Fold\tCTRL+F10" msgstr "" #: myframe.h:362 msgid "Toggle Fold\tF10" msgstr "" #: myframe.h:325 msgid "Toggle Vertical Layout\tF7" msgstr "" #: myframe.h:260 msgid "Toggle cell &BOLD\tCTRL+b" msgstr "" #: myframe.h:261 msgid "Toggle cell &ITALIC\tCTRL+i" msgstr "" #: myframe.h:264 msgid "Toggle cell &strikethrough\tCTRL+t" msgstr "" #: myframe.h:262 msgid "Toggle cell &typewriter" msgstr "" #: myframe.h:263 msgid "Toggle cell &underlined\tCTRL+u" msgstr "" #: myframe.h:364 msgid "Toggles showing the grid of the selected cell(s)" msgstr "" #: myframe.h:400 msgid "Turn filter &off" msgstr "" #: myframe.h:562 msgid "Undo (CTRL+z)" msgstr "" #: myframe.h:367 msgid "Unfold All\tCTRL+ALT+F10" msgstr "" #: myframe.h:368 msgid "Unfolds the grid of the selected cell(s) recursively" msgstr "" #: myframe.h:493 msgid "Variable &Assign" msgstr "" #: myframe.h:494 msgid "Variable &Read" msgstr "" #: myframe.h:314 msgid "Vertical Layout with Bubble Style Rendering\tALT+2" msgstr "" #: myframe.h:313 msgid "Vertical Layout with Grid Style Rendering\tALT+1" msgstr "" #: myframe.h:315 msgid "Vertical Layout with Line Style Rendering\tALT+3" msgstr "" #: myframe.h:506 msgid "View tutorial &web page..." msgstr "" #: document.h:862 msgid "What size grid would you like to start with?" msgstr "" #: system.h:359 #, c-format msgid "Width %d" msgstr "" #: myframe.h:240 msgid "Word Left\tCTRL+LEFT" msgstr "" #: myframe.h:241 msgid "Word Right\tCTRL+RIGHT" msgstr "" #: myframe.h:156 msgid "XML (attributes too, for OPML etc)..." msgstr "" #: myframe.h:155 msgid "XML..." msgstr "" #: document.h:187 msgid "Zlib error while writing file." msgstr "" #: myframe.h:410 msgid "Zoom &In (CTRL+mousewheel)\tCTRL+PGUP" msgstr "" #: myframe.h:411 msgid "Zoom &Out (CTRL+mousewheel)\tCTRL+PGDN" msgstr "" #: myframe.h:566 msgid "Zoom In (CTRL+mousewheel)" msgstr "" #: myframe.h:567 msgid "Zoom Out (CTRL+mousewheel)" msgstr "" #: document.h:434 msgid "Zoomed in." msgstr "" #: document.h:434 msgid "Zoomed out." msgstr "" #: myframe.h:807 msgid "change will take effect next run of TreeSheets" msgstr "" #: myframe.h:279 msgid "changes the orientation of a grid" msgstr "" #: system.h:420 msgid "couldn't import file!" msgstr "" #: myframe.h:334 msgid "only sets the colors and style of the copied cell, and keeps the text" msgstr "" #: document.h:1514 msgid "only works in cell text mode" msgstr "" #: myframe.h:338 msgid "redo any undo steps, if you haven't made changes since" msgstr "" #: myframe.h:336 msgid "revert the changes, one step at a time" msgstr "" #: document.h:934 msgid "scale:" msgstr "" #: document.h:863 msgid "size:" msgstr "" treesheets-1.0.2/TS/translations/zh_CN/000077500000000000000000000000001352107072600177405ustar00rootroot00000000000000treesheets-1.0.2/TS/translations/zh_CN/compile.bat000066400000000000000000000000411352107072600220530ustar00rootroot00000000000000msgfmt --output-file=ts.mo ts.po treesheets-1.0.2/TS/translations/zh_CN/ts.mo000066400000000000000000000704401352107072600207300ustar00rootroot00000000000000Z y ku     1"4 :GMVs   1(=X am }   $ 0& W v     $ !*F.>%u>>!>+>??s???? ???0?/"@4R@$@4@ @A)A$HAmAAAAA!ABB09BjB {BB4BBB1B/(C/XCC,CCCCD$"D%GDmDD D D.D!D EE EfE6E&EEEBEp1G G%GG GG HH;H MH6XHH HH HH"HHI *I5IFI WI eI6sIIII IJJ 2J =JHJZJ oJzJJ%JJ#J/J#+K#OKsK KKK KKK7K)!L/KL{LLL LLLAL 7M$DM$iMM!MMMMNN6(NO_NNNNNYO`O$sO3OOOP P2P)RP|PPPP#PQ8QKQ^QtzQQQR-RCR6VRRRR+R,RSA'S;iS S SSSS%T"4TWT?pTTBTU*UCUSUgUzUUVIgVRVkWpW#WX:X#YX}XX%X&X(Y,.Y[YnYYY?YY ZZ8ZQZdZ$ZZ'ZZ[[M2[-[-[-[: \E\M\`\+p\,\\\\#\ !]+]>]W]'w]P]4]%^8^K^k^ ^^^^__5_%U_8{_'___ _ ``!>```` ````` aF&amaaa"aRaH bVibbbbbc.cDc7Zc4c-c%cd2dOd mdzd d d d d d d dYd!8eZemeueeeeee eef$f ;f\frf"f fff)f%g>:gygggg#g#h%h>hWhohhh$hh hhii-i@Gi!i&i/i5j!7jYjoijjjjkk+k!Ak$ck0kk0kl#l.>l)mlllll&l#m:m)Wm&mmmm$m nn--n-[n-nn*n no o$5o(Zo(ooo o o-o*p@p;Vp$p=ppqqTRtL:+'<7_3GoOKMW|JL@Bz$(}7?08e$!1-!)Y;^/ Cs9PK.5"m@# "M6d; 4+>%cFNRV,DIb\> U)-0AlWBE&QVFy/Z*A[g Xvj8HESY 4,`' ICpP*a&{<f. w=3N#5J Zn= k?12TODiQ( x~u2hGHq:X9U r%S6]%s has been modified on disk by another program / computer: Would you like to discard your changes and re-load from disk?&About...&Add Cell Text as Tag&Add Image&Browsing...&Clear Views&Close CTRL+w&Comma delimited text (CSV)...&Copy CTRL+c&Data&Decrease text size (SHIFT+mousewheel) SHIFT+PGDN&Delete After DEL&Edit&Exit CTRL+q&File&Flatten&Go To Next Search Result F3&Grid Reorganization...&HTML (Tables+Styling)...&Help&Hierarchify&Horizontal View&Image...&Images...&Increase text size (SHIFT+mousewheel) SHIFT+PGUP&Insert New Grid CTRL+g&Insert New Grid INS&Layout && Render Style...&Mark as&New CTRL+n&Open... CTRL+o&Operation&Options&Paste CTRL+v&Print... CTRL+p&Program&Recent files&Redo CTRL+y&Remove Cell Text from Tags&Remove Image(s)&Replace in Current Selection CTRL+h&Replace in Current Selection & Jump Next CTRL+j&Reset text sizes SHIFT+CTRL+s&Reset text styles SHIFT+CTRL+r&Roundness of grid borders...&Run&Save CTRL+s&Scale Image&Search&Search CTRL+f&Selection...&Set Cell Text to tag (use CTRL+RMB)&Set Custom Color From Cell BG&Shrink text of all sub-grids SHIFT+CTRL+m&Transpose SHIFT+CTRL+t&Undo CTRL+z&Vertical View&View&Wrap in new parent F91:1 scale restored.A temporary autosave file exists, would you like to load it instead?Add ImageAdds a grid to the selected cellAdds an image to the selected cellAuto reload documentsAutomatically export a .html on every saveAutosave loadAutosave to .tmpBlack and white toolbar iconsBorder Bubble Style Rendering ALT+8Can only move this cell into an Nx1 or 1xN grid.Can't sort: make a 1xN selection to indicate what column to sort on, and what rows to affectCancelCancel text edits ESCCannot decompress file.Cannot drag & drop more than 1 file.Cannot export grid that is not flat (zoom the view to the desired grid, and/or use Flatten).Cannot find file.Cannot launch browser for this link.Cannot move this cell up in the hierarchy.Cannot open file.Cannot tell/seek document?Cell Change a key binding...Change the image size if it is too big or too smallChanges have been made, are you sure you wish to continue?Choose CSV file to writeChoose HTML file to writeChoose PNG file to writeChoose Text file to writeChoose TreeSheets file to save:Choose XML file to writeColumn width decreased.Column width increased.Comma delimited text (CSV)...Convert an NxN grid with repeating elements per column into an 1xN grid with hierarchy, useful to convert data from spreadsheetsCopy (CTRL+c)Copy As Continuous TextCorrupt PNG header.Corrupt block header.Create .bak filesCreates a new level of hierarchy around the current selectionCu&t CTRL+xCursor Left LEFTCursor Right RIGHTDecrease column width (ALT+mousewheel) ALT+PGDNDecrease column width (no sub grids) CTRL+ALT+PGDNDelete Before BACKDeletes the column of cells after the selected grid line, or the row belowDeletes the column of cells before the selected grid line, or the row aboveDiscard ChangesEdited %sEmpty strings cannot be tags.End of line of text ENDEnd of text CTRL+ENDEnter/exit text edit mode ENTEREnter/exit text edit mode F2Error exporting file!Error loading core data file (TreeSheets not installed correctly?)Error writing PNG file!Error writing TreeSheets file! (try saving under new filename).Error writing to file!Error writing to file.Evaluation finished.Export &view asExport cancelled.Export the current view as CSV. Good for spreadsheets and databases. Only works on grids with no sub-grids (use the Flatten operation first if need be)Export the current view as HTML as nested headers, suitable for importing into Word's outline modeExport the current view as HTML using nested tables, that will look somewhat like the TreeSheetExport the current view as XML (which can also be reimported without losing structure)Export the current view as an image. Useful for faithfull renderings of the TreeSheet, and programs that don't accept any of the above optionsExport the current view as tree structured text, using spaces for each indentation level. Suitable for importing into mindmanagers and general text programsExtend Selection Down SHIFT+DOWNExtend Selection Full ColumnsExtend Selection Full RowsExtend Selection Left SHIFT+LEFTExtend Selection Right SHIFT+RIGHTExtend Selection Up SHIFT+UPExtend Selection Word Left SHIFT+CTRL+LEFTExtend Selection Word Right SHIFT+CTRL+RIGHTExtend Selection to End SHIFT+ENDExtend Selection to Start SHIFT+HOMEFaster line renderingFile Tabs on the bottomFile corrupted!File exported successfully.File has been re-loaded because of modifications of another program / computerFile load error.File modification conflict!File of newer version.File saved succesfully.Filter...Fold All CTRL+SHIFT+F10Folds the grid of the selected cell(s) recursivelyGo To &Matching Cell F6Go To Matching Cell (Reverse) SHIFT+F6Grid Style Rendering ALT+7HTML (&Outline)...Hierarchy &Swap F8Hold CTRL while pressing right mouse button to quickly set a tag for the current cell using a popup menuHorizontal Layout with Bubble Style Rendering ALT+5Horizontal Layout with Grid Style Rendering ALT+4Horizontal Layout with Line Style Rendering ALT+6How many pixels wide should a page be? (0 for auto fit)Image Image ResizeImport file fromIncrease column width (ALT+mousewheel) ALT+PGUPIncrease column width (no sub grids) CTRL+ALT+PGUPIndented &Text...Indented text...Initialization ErrorInternal error: unimplemented operation!Key bindingKeybinding cancelled.Line Style Rendering ALT+9Load interactive &tutorial... F1Loaded %s (%d cells, %d characters).Make a 1xN selection to indicate which column to sort on, and which rows to affectMake a hierarchy layout more vertical (default) or more horizontalMinimize on closeMinimize to trayMove Cells Down CTRL+DOWNMove Cells Left CTRL+LEFTMove Cells Right CTRL+RIGHTMove Cells Up CTRL+UPMove Selection Down DOWNMove Selection Left LEFTMove Selection Right RIGHTMove Selection Up UPMove to next cell TABMove to previous cell SHIFT+TABNOTE: key binding will take effect next run of TreeSheets.Navigate in between cells with cursor keysNew (CTRL+n)New Grid (INS)New SheetNew file cancelled.No image in this cell.No matches for search.No matching cell found!No search string.No search.No style to paste.No text selected.Not a TreeSheets file.Nothing more to redo.Nothing more to undo.Now viewing TreeSheet to fit to the screen exactly, press F12 to return to normal.Open &file F4Open (CTRL+o)Open file cancelled.Open link in &browser F5Opens up the text from the selected cell in browser (should start be a valid URL)Opens up the text from the selected cell in default application for the file typePNG decode failed on some images in this document They have been replaced by red squares.PNG decoder failurePage Setup...Paste (CTRL+v)Paste Style Only CTRL+SHIFT+vPick Custom &Color...Pick Default Font...Pick Document Background...Please enable (Options -> Show Toolbar) to use search.Please enter the percentage you want the image scaled by:Please pick a menu item to change the key binding forPlease select a TreeSheets file to load:Please select an image file:Please select file to import:Press F11 to exit fullscreen mode.Print PreviewPrint preview...Radius &0Radius &1Radius &2Radius &3Radius &4Radius &5Radius &6Reloads when another computer has changed a file (if you have made changes, asks)Remove image(s) from the selected cellsRender document centeredReplace Replace &AllReset &colors SHIFT+CTRL+cReset column widths SHIFT+CTRL+wRunSave &As...Save (CTRL+s)Save AsSave and CloseSave cancelled.Scroll Down (mousewheel) ALT+DOWNScroll Down (mousewheel) PGDNScroll Left ALT+LEFTScroll Right ALT+RIGHTScroll Up (mousewheel) ALT+UPScroll Up (mousewheel) PGUPSearch Select &Parent ESCSelect &all in current grid CTRL+aSelect First &Child SHIFT+ENTERSelected grid is not a table: cells must not already have sub-grids.Semi-Colon delimited text (CSV)...Set Grid Border Width...Set Print ScaleSet Print Scale...Show 1% less than the last filterShow 1% more than the last filterShow 10% of last editsShow 20% of last editsShow 5% of last editsShow 50% of last editsShow StatusbarShow ToolbarShow only cells in current searchSingle click maximize from traySize %dSort &AscendingSort &DescendingStart of line of text HOMEStart of text CTRL+HOMESwap all cells with this text at this level (or above) with the parentSwap mousewheel scrolling and zoomingSwitch to &next file/tabSwitch to &next file/tab CTRL+TABSwitch to &previous file/tab SHIFT+CTRL+TABTab delimited text...Tag...Takes a hierarchy (nested 1xN or Nx1 grids) and converts it into a flat NxN grid, useful for export to spreadsheetsText Text &Editing...Text Sizing...Text Style...Text size decreased.Text size increased.The Free Form Hierarchical Information OrganizerThis operation doesn't work on thin selections.This operation requires a cell that contains a grid.This operation requires a selection.This operation works on a single selected cell only.Toggle &Fullscreen View CTRL+F11Toggle &Fullscreen View F11Toggle &Scaled Presentation View CTRL+F12Toggle &Scaled Presentation View F12Toggle Fold CTRL+F10Toggle Fold F10Toggle Vertical Layout F7Toggle cell &BOLD CTRL+bToggle cell &ITALIC CTRL+iToggle cell &strikethrough CTRL+tToggle cell &typewriterToggle cell &underlined CTRL+uToggles showing the grid of the selected cell(s)Turn filter &offUndo (CTRL+z)Unfold All CTRL+ALT+F10Unfolds the grid of the selected cell(s) recursivelyVariable &AssignVariable &ReadVertical Layout with Bubble Style Rendering ALT+2Vertical Layout with Grid Style Rendering ALT+1Vertical Layout with Line Style Rendering ALT+3View tutorial &web page...What size grid would you like to start with?Width %dWord Left CTRL+LEFTWord Right CTRL+RIGHTZlib error while writing file.Zoom &In (CTRL+mousewheel) CTRL+PGUPZoom &Out (CTRL+mousewheel) CTRL+PGDNZoom In (CTRL+mousewheel)Zoom Out (CTRL+mousewheel)Zoomed in.Zoomed out.change will take effect next run of TreeSheetschanges the orientation of a gridcouldn't import file!only sets the colors and style of the copied cell, and keeps the textonly works in cell text moderedo any undo steps, if you haven't made changes sincerevert the changes, one step at a timescale:size:Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2017-03-26 16:49-0700 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE Language: MIME-Version: 1.0 Content-Type: text/plain; charset=CHARSET Content-Transfer-Encoding: 8bit % s 已被另一个程序/计算机修改在磁盘上: 是否要放弃所做的更改并从磁盘重新加载?关于(&A)...将单元格文本添加为标记(&A)添加图像(&A)浏览(&B)...清晰的视图(&C)关闭(&C) CTRL+w逗号分隔文本 (csv)(&C)...复制(&C) CTRL+c数据(&D)减小文本大小 (shift 鼠标滚轮)(&D) SHIFT+PGDN删除后(&D) DEL编辑(&E)退出(&E) CTRL+q文件(&F)扁平 化(&F)转到下一个搜索结果(&G) F3网格重组(&G)...html (表格样式)(&H)...帮助(&H)层次结构(&H)水平视图(&H)图像(&I)...图像(&I)...增加文本大小 (shift 鼠标滚轮)(&I) SHIFT+PGUP插入新网格(&I) CTRL+g插入新网格(&I) INS布局渲染样式(&L)...标记为(&M)新增功能(&N) CTRL+n打开(&O)... CTRL+o操作(&O)选项(&O)粘贴(&P) CTRL+v打印(&P)... CTRL+p程序(&P)最近的文件(&R)重新(&R) CTRL+y从标签中删除单元格文本(&R)删除图像(&R)在当前选择中替换(&R) CTRL+h替换在当前选择跳转下一步(&R) CTRL+j重置文本大小(&R) SHIFT+CTRL+s重置文本样式(&R) SHIFT+CTRL+r网格边界的圆度(&R)...运行(&R)救(&S) CTRL+s缩放图像(&S)搜索(&S)搜索(&S) CTRL+f选择。(&S)...将单元格文本设置为标记 (使用 ctrl rmb)(&S)从单元格 bg 设置自定义颜色(&S)收缩所有子网格的文本(&S) SHIFT+CTRL+m移位(&T) SHIFT+CTRL+t撤消(&U) CTRL+z垂直视图(&V)视图(&V)包装在新的父项中(&W) F91: 恢复1刻度。存在临时自动保存文件, 是否要改为加载该文件?添加图像将网格添加到选定的单元格将图像添加到选定的单元格自动重新加载文档每次保存时自动导出. html自动保存负载自动保存到. tmp黑白工具栏图标边境 气泡风格渲染 ALT+8只能将此单元格移动到 nx1 或1xn 网格中。无法排序: 进行1xn 选择, 以指示要排序的列, 以及要影响的行取消取消文本编辑 ESC无法解压缩文件。无法拖放超过1个文件。无法导出不平坦的网格 (将视图缩放到所需的网格, 或使用 "平展")。找不到文件。无法启动此链接的浏览器。无法在层次结构中向上移动此单元格。无法打开文件。不能告诉 "找文件?"?细胞 更改密钥绑定...如果图像太大或太小, 请更改图像大小已进行更改, 您确定要继续吗?选择要写入的 csv 文件选择要写入的 html 文件选择要写入的 png 文件选择要写入的文本文件选择 "树表格" 文件以保存:选择要编写的 xml 文件列宽度减小。列宽度增加。逗号分隔文本 (csv)...将每列重复元素的 nxn 网格转换为具有层次结构的1xn 网格, 可用于从电子表格中转换数据复制 (ctrl c)复制为连续文本损坏的 png 标头。损坏的块标头。创建. bak 文件围绕当前所选内容创建新级别的层次结构削减(&T) CTRL+x左光标 LEFT光标右 RIGHT减小列宽度 (alt 鼠标滚轮) ALT+PGDN减小列宽度 (无子网格) CTRL+ALT+PGDN返回前删除删除选定网格线之后的单元格列, 或删除下面的行删除选定网格线之前的单元格列, 或上面的行放弃更改编辑的% s空字符串不能是标记。文本行的结尾 END文本的结尾 CTRL+END输入/退出文本编辑模式 ENTER输入/退出文本编辑模式 F2导出文件时出错!加载核心数据文件时出错 (树表格安装不正确?)写入 png 文件时出错!写入树表格文件时出错!(尝试在新文件名下保存)。写入文件时出错!写入文件时出错。评估完成。导出视图为(&V)导出已取消。将当前视图导出为 csv。适用于电子表格和数据库。仅适用于没有子网格的网格 (如有需要, 请首先使用 flatten 操作)将当前视图导出为嵌套标头的 html, 适合导入到 word 的大纲模式中使用嵌套表将当前视图导出为 html, 这看起来有点像树表将当前视图导出为 xml (也可以在不丢失结构的情况下重新导入)将当前视图导出为图像。适用于树表的完整渲染, 以及不接受上述任何选项的程序将当前视图导出为树结构化文本, 对每个缩进级别使用空格。适用于导入思维管理人员和一般文本程序向下扩展所选内容 SHIFT+DOWN扩展所选内容的完整列扩展所选内容的完整行向左扩展所选内容 SHIFT+LEFT扩展选择权限 SHIFT+RIGHT向上扩展选择 SHIFT+UP向左扩展选择词 SHIFT+CTRL+LEFT向右扩展选择词 SHIFT+CTRL+RIGHT将所选内容扩展到结束 SHIFT+END将所选内容扩展到 "开始" SHIFT+HOME更快的线渲染底部的文件选项卡文件已损坏!文件已成功导出。由于修改了另一个程序/计算机, 文件已重新加载文件加载错误。文件修改冲突!较新版本的文件。成功保存的文件。滤波器。。...折叠全部 CTRL+SHIFT+F10递归折叠选定单元格的网格转到匹配单元格(&M) F6转到匹配单元格 (反向) SHIFT+F6网格样式渲染 ALT+7html (大纲)(&O)...层次结构交换(&S) F8按住 ctrl 键, 同时使用弹出菜单快速设置当前单元格的标记具有气泡样式渲染的水平布局 ALT+5具有网格样式渲染的水平布局 ALT+4具有线条样式渲染的水平布局 ALT+6一个页面应该有多少像素宽?(0 为自动配合)图像 图像调整大小从导入文件增加列宽度 (alt 鼠标滚轮) ALT+PGUP增加列宽度 (无子网格) CTRL+ALT+PGUP文本缩进(&T)...文本缩进...初始化错误内部错误: 未实现的操作!键绑定取消了绑定。线条样式渲染 ALT+9加载交互式教程(&T)... F1加载% s (% d 单元格,% d 字符)。进行1xn 选择, 以指示要对哪一列进行排序, 以及要影响哪些行使层次结构布局更垂直 (默认) 或更水平关闭时最小化最小化到托盘向下移动单元格 CTRL+DOWN向左移动单元格 CTRL+LEFT向右移动单元格 CTRL+RIGHT向上移动单元格 CTRL+UP向下移动所选内容 DOWN向左移动所选内容 LEFT向右移动选择 RIGHT上移所选内容 UP移动到下一个单元格 TAB移动到上一个单元格 SHIFT+TAB注: 键绑定将在下一次运行的树表中生效。使用光标键在单元格之间导航新建 (ctrl n)新网格 (ins)新工作表新文件已取消。此单元格中没有图像。没有用于搜索的匹配项。找不到匹配的单元格!没有搜索字符串。不搜索。没有要粘贴的样式。未选择文本。不是 "树表" 文件。没有什么可重做的了。没有更多的撤消。现在查看树板以完全适合屏幕, 按 f12 返回正常状态。打开文件(&F) F4打开 (ctrl o)打开文件已取消。在浏览器中打开链接(&B) F5在浏览器中打开选定单元格中的文本 (开始时应该是有效的 url)在文件类型的默认应用程序中打开选定单元格中的文本png 解码在本文档中的某些图像上失败 它们已被红色方块所取代。png 解码器故障页面设置...粘贴 (ctrl v)仅粘贴样式 CTRL+SHIFT+v选择自定义颜色(&C)...选择默认字体...选择文档背景...请启用 (选项-> 显示工具栏) 以使用搜索。请输入要按以下方式缩放图像的百分比:请选择一个菜单项来更改的键绑定请选择要加载的树表格文件:请选择图像文件:请选择要导入的文件:按 f11 退出全屏模式。打印预览打印预览...半径0(&0)半径1(&1)半径2(&2)半径3(&3)半径4(&4)半径5(&5)半径6(&6)当另一台计算机更改了文件时重新加载 (如果您进行了更改, 请询问)从选定单元格中删除图像呈现文档居中取代 全部替换(&A)重置颜色(&C) SHIFT+CTRL+c重置列宽度 SHIFT+CTRL+w运行另存为(&A)...保存 (ctrl s)另存为保存并关闭保存已取消。向下滚动 (鼠标滚轮) ALT+DOWN向下滚动 (鼠标滚轮) PGDN向左滚动 ALT+LEFT向右滚动 ALT+RIGHT向上滚动 (鼠标滚轮) ALT+UP向上滚动 (鼠标滚轮) PGUP搜索 选择父项(&P) ESC在当前网格中选择所有(&A) CTRL+a选择第一个子项(&C) SHIFT+ENTER选定的网格不是表: 单元格不能已经有子网格。半冒号分隔文本 (csv)...设置网格边框宽度...设置打印比例设置打印比例...显示比最后一个筛选器少1%显示比最后一个筛选器多1%显示上次编辑的10%显示上次编辑的20%显示上次编辑的5%显示上次编辑的50%显示状态栏显示工具栏仅在当前搜索中显示单元格从托盘中单击最大化尺寸% d排序升序(&A)排序降序(&D)文本行的开始 HOME文本的开始 CTRL+HOME将此文本与父级 (或更高版本) 的所有单元格交换交换鼠标滚轮滚动和缩放切换到下一个文件/选项卡(&N)切换到下一个文件/选项卡(&N) CTRL+TAB切换到以前的文件/选项卡(&P) SHIFT+CTRL+TAB制表符分隔的文本格式...标记。。...采用层次结构 (嵌套1xn 或1xN 网格) 并将其转换为平面1xN 网格, 可用于导出到电子表格文本 文本编辑(&E)...文本大小...文本样式...文本大小减小。文本大小增加。自由形式分层信息管理器此操作不适用于精简选择。此操作需要一个包含网格的单元格。此操作需要选择。此操作仅适用于单个选定的单元格。切换全屏视图(&F) CTRL+F11切换全屏视图(&F) F11切换缩放的演示文稿视图(&S) CTRL+F12切换缩放的演示文稿视图(&S) F12切换折叠 CTRL+F10切换折叠 F10切换垂直布局 F7切换单元格(&B) CTRL+b切换单元格意大利语(&I) CTRL+i切换单元格删除线(&S) CTRL+t切换单元格打字机(&T)切换带下划线的单元格(&U) CTRL+u切换, 显示所选单元格的网格关闭过滤器(&O)撤消 (ctrl z)全部展开 CTRL+ALT+F10递归展开选定单元格的网格变量分配(&A)变量读取(&R)具有气泡样式渲染的垂直布局 ALT+2具有网格样式渲染的垂直布局 ALT+1具有线条样式渲染的垂直布局 ALT+3查看教程网页(&W)...您希望从什么尺寸的网格开始?宽度% d左边的单词 CTRL+LEFT右文字 CTRL+RIGHT写入文件时出现 zlib 错误。放大 (ctrl 鼠标滚轮)(&I) CTRL+PGUP缩小 (ctrl 鼠标滚轮)(&O) CTRL+PGDN放大 (ctrl 鼠标滚轮)缩小 (ctrl 鼠标滚轮)放大了。缩小了。更改将在下一次运行的树表中生效更改网格的方向无法导入文件!仅设置复制单元格的颜色和样式, 并保留文本仅在单元格文本模式下工作重做任何撤消步骤, 如果你没有做的更改, 因为恢复更改, 一步一步规模:大小:treesheets-1.0.2/TS/translations/zh_CN/ts.po000066400000000000000000001034751352107072600207400ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-03-26 16:49-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: myframe.h:1028 #, c-format msgid "%s\n" "has been modified on disk by another program / computer:\n" "Would you like to discard your changes and re-load from disk?" msgstr "% s\n" "已被另一个程序/计算机修改在磁盘上:\n" "是否要放弃所做的更改并从磁盘重新加载?" #: myframe.h:504 msgid "&About..." msgstr "关于(&A)..." #: myframe.h:270 msgid "&Add Cell Text as Tag" msgstr "将单元格文本添加为标记(&A)" #: myframe.h:298 msgid "&Add Image" msgstr "添加图像(&A)" #: myframe.h:374 msgid "&Browsing..." msgstr "浏览(&B)..." #: myframe.h:501 msgid "&Clear Views" msgstr "清晰的视图(&C)" #: myframe.h:169 msgid "&Close\tCTRL+w" msgstr "关闭(&C)\tCTRL+w" #: myframe.h:145 msgid "&Comma delimited text (CSV)..." msgstr "逗号分隔文本 (csv)(&C)..." #: myframe.h:330 msgid "&Copy\tCTRL+c" msgstr "复制(&C)\tCTRL+c" #: myframe.h:491 msgid "&Data" msgstr "数据(&D)" #: myframe.h:188 msgid "&Decrease text size (SHIFT+mousewheel)\tSHIFT+PGDN" msgstr "减小文本大小 (shift 鼠标滚轮)(&D)\tSHIFT+PGDN" #: myframe.h:340 msgid "&Delete After\tDEL" msgstr "删除后(&D)\tDEL" #: myframe.h:518 msgid "&Edit" msgstr "编辑(&E)" #: myframe.h:182 msgid "&Exit\tCTRL+q" msgstr "退出(&E)\tCTRL+q" #: myframe.h:517 msgid "&File" msgstr "文件(&F)" #: myframe.h:292 msgid "&Flatten" msgstr "扁平 化(&F)" #: myframe.h:386 msgid "&Go To Next Search Result\tF3" msgstr "转到下一个搜索结果(&G)\tF3" #: myframe.h:371 msgid "&Grid Reorganization..." msgstr "网格重组(&G)..." #: myframe.h:135 msgid "&HTML (Tables+Styling)..." msgstr "html (表格样式)(&H)..." #: myframe.h:527 msgid "&Help" msgstr "帮助(&H)" #: myframe.h:288 msgid "&Hierarchify" msgstr "层次结构(&H)" #: myframe.h:495 msgid "&Horizontal View" msgstr "水平视图(&H)" #: myframe.h:149 msgid "&Image..." msgstr "图像(&I)..." #: myframe.h:373 msgid "&Images..." msgstr "图像(&I)..." #: myframe.h:187 msgid "&Increase text size (SHIFT+mousewheel)\tSHIFT+PGUP" msgstr "增加文本大小 (shift 鼠标滚轮)(&I)\tSHIFT+PGUP" #: myframe.h:348 msgid "&Insert New Grid\tCTRL+g" msgstr "插入新网格(&I)\tCTRL+g" #: myframe.h:350 msgid "&Insert New Grid\tINS" msgstr "插入新网格(&I)\tINS" #: myframe.h:372 msgid "&Layout && Render Style..." msgstr "布局渲染样式(&L)..." #: myframe.h:500 msgid "&Mark as" msgstr "标记为(&M)" #: myframe.h:167 msgid "&New\tCTRL+n" msgstr "新增功能(&N)\tCTRL+n" #: myframe.h:168 msgid "&Open...\tCTRL+o" msgstr "打开(&O)...\tCTRL+o" #: myframe.h:492 msgid "&Operation" msgstr "操作(&O)" #: myframe.h:521 msgid "&Options" msgstr "选项(&O)" #: myframe.h:332 msgid "&Paste\tCTRL+v" msgstr "粘贴(&P)\tCTRL+v" #: myframe.h:177 msgid "&Print...\tCTRL+p" msgstr "打印(&P)...\tCTRL+p" #: myframe.h:522 msgid "&Program" msgstr "程序(&P)" #: myframe.h:170 msgid "&Recent files" msgstr "最近的文件(&R)" #: myframe.h:337 msgid "&Redo\tCTRL+y" msgstr "重新(&R)\tCTRL+y" #: myframe.h:271 msgid "&Remove Cell Text from Tags" msgstr "从标签中删除单元格文本(&R)" #: myframe.h:301 msgid "&Remove Image(s)" msgstr "删除图像(&R)" #: myframe.h:387 msgid "&Replace in Current Selection\tCTRL+h" msgstr "在当前选择中替换(&R)\tCTRL+h" #: myframe.h:388 msgid "&Replace in Current Selection & Jump Next\tCTRL+j" msgstr "替换在当前选择跳转下一步(&R)\tCTRL+j" #: myframe.h:189 msgid "&Reset text sizes\tSHIFT+CTRL+s" msgstr "重置文本大小(&R)\tSHIFT+CTRL+s" #: myframe.h:266 msgid "&Reset text styles\tSHIFT+CTRL+r" msgstr "重置文本样式(&R)\tSHIFT+CTRL+r" #: myframe.h:488 msgid "&Roundness of grid borders..." msgstr "网格边界的圆度(&R)..." #: myframe.h:499 msgid "&Run" msgstr "运行(&R)" #: myframe.h:171 msgid "&Save\tCTRL+s" msgstr "救(&S)\tCTRL+s" #: myframe.h:299 msgid "&Scale Image" msgstr "缩放图像(&S)" #: myframe.h:519 msgid "&Search" msgstr "搜索(&S)" #: myframe.h:385 msgid "&Search\tCTRL+f" msgstr "搜索(&S)\tCTRL+f" #: myframe.h:370 msgid "&Selection..." msgstr "选择。(&S)..." #: myframe.h:272 msgid "&Set Cell Text to tag (use CTRL+RMB)" msgstr "将单元格文本设置为标记 (使用 ctrl rmb)(&S)" #: myframe.h:450 msgid "&Set Custom Color From Cell BG" msgstr "从单元格 bg 设置自定义颜色(&S)" #: myframe.h:190 msgid "&Shrink text of all sub-grids\tSHIFT+CTRL+m" msgstr "收缩所有子网格的文本(&S)\tSHIFT+CTRL+m" #: myframe.h:278 msgid "&Transpose\tSHIFT+CTRL+t" msgstr "移位(&T)\tSHIFT+CTRL+t" #: myframe.h:336 msgid "&Undo\tCTRL+z" msgstr "撤消(&U)\tCTRL+z" #: myframe.h:496 msgid "&Vertical View" msgstr "垂直视图(&V)" #: myframe.h:520 msgid "&View" msgstr "视图(&V)" #: myframe.h:353 msgid "&Wrap in new parent\tF9" msgstr "包装在新的父项中(&W)\tF9" #: myframe.h:132 msgid "&XML..." msgstr "" #: document.h:1013 msgid "1:1 scale restored." msgstr "1: 恢复1刻度。" #: system.h:197 msgid "A temporary autosave file exists, would you like to load it instead?" msgstr "存在临时自动保存文件, 是否要改为加载该文件?" #: myframe.h:570 msgid "Add Image" msgstr "添加图像" #: myframe.h:352 msgid "Adds a grid to the selected cell" msgstr "将网格添加到选定的单元格" #: myframe.h:298 msgid "Adds an image to the selected cell" msgstr "将图像添加到选定的单元格" #: myframe.h:476 msgid "Auto reload documents" msgstr "自动重新加载文档" #: myframe.h:479 msgid "Automatically export a .html on every save" msgstr "每次保存时自动导出. html" #: system.h:198 msgid "Autosave load" msgstr "自动保存负载" #: myframe.h:473 msgid "Autosave to .tmp" msgstr "自动保存到. tmp" #: myframe.h:486 msgid "Black and white toolbar icons" msgstr "黑白工具栏图标" #: myframe.h:588 msgid "Border " msgstr "边境 " #: myframe.h:322 msgid "Bubble Style Rendering\tALT+8" msgstr "气泡风格渲染\tALT+8" #: document.h:1493 msgid "Can only move this cell into an Nx1 or 1xN grid." msgstr "只能将此单元格移动到 nx1 或1xn 网格中。" #: document.h:1660 msgid "Can't sort: make a 1xN selection to indicate what column to sort on, and what rows to affect" msgstr "无法排序: 进行1xn 选择, 以指示要排序的列, 以及要影响的行" #: document.h:601 msgid "Cancel" msgstr "取消" #: myframe.h:257 msgid "Cancel text edits\tESC" msgstr "取消文本编辑\tESC" #: system.h:266 msgid "Cannot decompress file." msgstr "无法解压缩文件。" #: document.h:1611 msgid "Cannot drag & drop more than 1 file." msgstr "无法拖放超过1个文件。" #: document.h:655 msgid "Cannot export grid that is not flat (zoom the view to the desired grid, and/or use Flatten)." msgstr "无法导出不平坦的网格 (将视图缩放到所需的网格, 或使用 \"平展\")。" #: document.h:1455 msgid "Cannot find file." msgstr "找不到文件。" #: document.h:1448 msgid "Cannot launch browser for this link." msgstr "无法启动此链接的浏览器。" #: document.h:1491 msgid "Cannot move this cell up in the hierarchy." msgstr "无法在层次结构中向上移动此单元格。" #: system.h:211 msgid "Cannot open file." msgstr "无法打开文件。" #: system.h:237 msgid "Cannot tell/seek document?" msgstr "不能告诉 \"找文件?\"?" #: myframe.h:582 msgid "Cell " msgstr "细胞 " #: myframe.h:448 msgid "Change a key binding..." msgstr "更改密钥绑定..." #: myframe.h:300 msgid "Change the image size if it is too big or too small" msgstr "如果图像太大或太小, 请更改图像大小" #: document.h:600 msgid "Changes have been made, are you sure you wish to continue?" msgstr "已进行更改, 您确定要继续吗?" #: document.h:829 msgid "Choose CSV file to write" msgstr "选择要写入的 csv 文件" #: document.h:826 msgid "Choose HTML file to write" msgstr "选择要写入的 html 文件" #: document.h:828 msgid "Choose PNG file to write" msgstr "选择要写入的 png 文件" #: document.h:827 msgid "Choose Text file to write" msgstr "选择要写入的文本文件" #: document.h:711 msgid "Choose TreeSheets file to save:" msgstr "选择 \"树表格\" 文件以保存:" #: document.h:824 msgid "Choose XML file to write" msgstr "选择要编写的 xml 文件" #: document.h:419 msgid "Column width decreased." msgstr "列宽度减小。" #: document.h:419 msgid "Column width increased." msgstr "列宽度增加。" #: myframe.h:158 msgid "Comma delimited text (CSV)..." msgstr "逗号分隔文本 (csv)..." #: myframe.h:289 msgid "Convert an NxN grid with repeating elements per column into an 1xN grid with hierarchy, useful to convert data from spreadsheets" msgstr "将每列重复元素的 nxn 网格转换为具有层次结构的1xn 网格, 可用于从电子表格中转换数据" #: myframe.h:563 msgid "Copy (CTRL+c)" msgstr "复制 (ctrl c)" #: myframe.h:331 msgid "Copy As Continuous Text" msgstr "复制为连续文本" #: system.h:243 msgid "Corrupt PNG header." msgstr "损坏的 png 标头。" #: system.h:295 msgid "Corrupt block header." msgstr "损坏的块标头。" #: myframe.h:471 msgid "Create .bak files" msgstr "创建. bak 文件" #: myframe.h:354 msgid "Creates a new level of hierarchy around the current selection" msgstr "围绕当前所选内容创建新级别的层次结构" #: myframe.h:329 msgid "Cu&t\tCTRL+x" msgstr "削减(&T)\tCTRL+x" #: myframe.h:238 msgid "Cursor Left\tLEFT" msgstr "左光标\tLEFT" #: myframe.h:239 msgid "Cursor Right\tRIGHT" msgstr "光标右\tRIGHT" #: myframe.h:193 msgid "Decrease column width (ALT+mousewheel)\tALT+PGDN" msgstr "减小列宽度 (alt 鼠标滚轮)\tALT+PGDN" #: myframe.h:197 msgid "Decrease column width (no sub grids)\tCTRL+ALT+PGDN" msgstr "减小列宽度 (无子网格)\tCTRL+ALT+PGDN" #: myframe.h:343 msgid "Delete Before\tBACK" msgstr "返回前删除" #: myframe.h:341 msgid "Deletes the column of cells after the selected grid line, or the row below" msgstr "删除选定网格线之后的单元格列, 或删除下面的行" #: myframe.h:344 msgid "Deletes the column of cells before the selected grid line, or the row above" msgstr "删除选定网格线之前的单元格列, 或上面的行" #: document.h:601 msgid "Discard Changes" msgstr "放弃更改" #: system.h:361 #, c-format msgid "Edited %s" msgstr "编辑的% s" #: document.h:1503 msgid "Empty strings cannot be tags." msgstr "空字符串不能是标记。" #: myframe.h:251 msgid "End of line of text\tEND" msgstr "文本行的结尾\tEND" #: myframe.h:253 msgid "End of text\tCTRL+END" msgstr "文本的结尾\tCTRL+END" #: myframe.h:255 msgid "Enter/exit text edit mode\tENTER" msgstr "输入/退出文本编辑模式\tENTER" #: myframe.h:256 msgid "Enter/exit text edit mode\tF2" msgstr "输入/退出文本编辑模式\tF2" #: document.h:671 msgid "Error exporting file!" msgstr "导出文件时出错!" #: myframe.h:105 msgid "Error loading core data file (TreeSheets not installed correctly?)" msgstr "加载核心数据文件时出错 (树表格安装不正确?)" #: document.h:667 msgid "Error writing PNG file!" msgstr "写入 png 文件时出错!" #: document.h:165 msgid "Error writing TreeSheets file! (try saving under new filename)." msgstr "写入树表格文件时出错!(尝试在新文件名下保存)。" #: document.h:672 msgid "Error writing to file!" msgstr "写入文件时出错!" #: document.h:167 msgid "Error writing to file." msgstr "写入文件时出错。" #: document.h:803 msgid "Evaluation finished." msgstr "评估完成。" #: myframe.h:179 msgid "Export &view as" msgstr "导出视图为(&V)" #: document.h:646 msgid "Export cancelled." msgstr "导出已取消。" #: myframe.h:146 msgid "Export the current view as CSV. Good for spreadsheets and databases. Only works on grids with no sub-grids (use the Flatten operation first if need be)" msgstr "将当前视图导出为 csv。适用于电子表格和数据库。仅适用于没有子网格的网格 (如有需要, 请首先使用 flatten 操作)" #: myframe.h:139 msgid "Export the current view as HTML as nested headers, suitable for importing into Word's outline mode" msgstr "将当前视图导出为嵌套标头的 html, 适合导入到 word 的大纲模式中" #: myframe.h:136 msgid "Export the current view as HTML using nested tables, that will look somewhat like the TreeSheet" msgstr "使用嵌套表将当前视图导出为 html, 这看起来有点像树表" #: myframe.h:133 msgid "Export the current view as XML (which can also be reimported without losing structure)" msgstr "将当前视图导出为 xml (也可以在不丢失结构的情况下重新导入)" #: myframe.h:150 msgid "Export the current view as an image. Useful for faithfull renderings of the TreeSheet, and programs that don't accept any of the above options" msgstr "将当前视图导出为图像。适用于树表的完整渲染, 以及不接受上述任何选项的程序" #: myframe.h:142 msgid "Export the current view as tree structured text, using spaces for each indentation level. Suitable for importing into mindmanagers and general text programs" msgstr "将当前视图导出为树结构化文本, 对每个缩进级别使用空格。适用于导入思维管理人员和一般文本程序" #: myframe.h:227 msgid "Extend Selection Down\tSHIFT+DOWN" msgstr "向下扩展所选内容\tSHIFT+DOWN" #: myframe.h:228 msgid "Extend Selection Full Columns" msgstr "扩展所选内容的完整列" #: myframe.h:229 msgid "Extend Selection Full Rows" msgstr "扩展所选内容的完整行" #: myframe.h:224 myframe.h:243 msgid "Extend Selection Left\tSHIFT+LEFT" msgstr "向左扩展所选内容\tSHIFT+LEFT" #: myframe.h:225 myframe.h:244 msgid "Extend Selection Right\tSHIFT+RIGHT" msgstr "扩展选择权限\tSHIFT+RIGHT" #: myframe.h:226 msgid "Extend Selection Up\tSHIFT+UP" msgstr "向上扩展选择\tSHIFT+UP" #: myframe.h:245 msgid "Extend Selection Word Left\tSHIFT+CTRL+LEFT" msgstr "向左扩展选择词\tSHIFT+CTRL+LEFT" #: myframe.h:246 msgid "Extend Selection Word Right\tSHIFT+CTRL+RIGHT" msgstr "向右扩展选择词\tSHIFT+CTRL+RIGHT" #: myframe.h:248 msgid "Extend Selection to End\tSHIFT+END" msgstr "将所选内容扩展到结束\tSHIFT+END" #: myframe.h:247 msgid "Extend Selection to Start\tSHIFT+HOME" msgstr "将所选内容扩展到 \"开始\"\tSHIFT+HOME" #: myframe.h:484 msgid "Faster line rendering" msgstr "更快的线渲染" #: myframe.h:457 msgid "File Tabs on the bottom" msgstr "底部的文件选项卡" #: system.h:270 msgid "File corrupted!" msgstr "文件已损坏!" #: document.h:705 msgid "File exported successfully." msgstr "文件已成功导出。" #: myframe.h:1047 msgid "File has been re-loaded because of modifications of another program / computer" msgstr "由于修改了另一个程序/计算机, 文件已重新加载" #: system.h:421 msgid "File load error." msgstr "文件加载错误。" #: myframe.h:1033 msgid "File modification conflict!" msgstr "文件修改冲突!" #: system.h:217 msgid "File of newer version." msgstr "较新版本的文件。" #: document.h:208 msgid "File saved succesfully." msgstr "成功保存的文件。" #: myframe.h:434 msgid "Filter..." msgstr "滤波器。。..." #: myframe.h:365 msgid "Fold All\tCTRL+SHIFT+F10" msgstr "折叠全部\tCTRL+SHIFT+F10" #: myframe.h:366 msgid "Folds the grid of the selected cell(s) recursively" msgstr "递归折叠选定单元格的网格" #: myframe.h:234 msgid "Go To &Matching Cell\tF6" msgstr "转到匹配单元格(&M)\tF6" #: myframe.h:235 msgid "Go To Matching Cell (Reverse)\tSHIFT+F6" msgstr "转到匹配单元格 (反向)\tSHIFT+F6" #: myframe.h:321 msgid "Grid Style Rendering\tALT+7" msgstr "网格样式渲染\tALT+7" #: myframe.h:138 msgid "HTML (&Outline)..." msgstr "html (大纲)(&O)..." #: myframe.h:286 msgid "Hierarchy &Swap\tF8" msgstr "层次结构交换(&S)\tF8" #: myframe.h:273 msgid "Hold CTRL while pressing right mouse button to quickly set a tag for the current cell using a popup menu" msgstr "按住 ctrl 键, 同时使用弹出菜单快速设置当前单元格的标记" #: myframe.h:318 msgid "Horizontal Layout with Bubble Style Rendering\tALT+5" msgstr "具有气泡样式渲染的水平布局\tALT+5" #: myframe.h:317 msgid "Horizontal Layout with Grid Style Rendering\tALT+4" msgstr "具有网格样式渲染的水平布局\tALT+4" #: myframe.h:319 msgid "Horizontal Layout with Line Style Rendering\tALT+6" msgstr "具有线条样式渲染的水平布局\tALT+6" #: document.h:934 msgid "How many pixels wide should a page be? (0 for auto fit)" msgstr "一个页面应该有多少像素宽?(0 为自动配合)" #: myframe.h:591 msgid "Image " msgstr "图像 " #: document.h:1463 msgid "Image Resize" msgstr "图像调整大小" #: myframe.h:180 msgid "Import file from" msgstr "从导入文件" #: myframe.h:192 msgid "Increase column width (ALT+mousewheel)\tALT+PGUP" msgstr "增加列宽度 (alt 鼠标滚轮)\tALT+PGUP" #: myframe.h:195 msgid "Increase column width (no sub grids)\tCTRL+ALT+PGUP" msgstr "增加列宽度 (无子网格)\tCTRL+ALT+PGUP" #: myframe.h:141 msgid "Indented &Text..." msgstr "文本缩进(&T)..." #: myframe.h:157 msgid "Indented text..." msgstr "文本缩进..." #: myframe.h:106 msgid "Initialization Error" msgstr "初始化错误" #: document.h:1558 msgid "Internal error: unimplemented operation!" msgstr "内部错误: 未实现的操作!" #: document.h:1049 msgid "Key binding" msgstr "键绑定" #: document.h:1063 msgid "Keybinding cancelled." msgstr "取消了绑定。" #: myframe.h:323 msgid "Line Style Rendering\tALT+9" msgstr "线条样式渲染\tALT+9" #: myframe.h:505 msgid "Load interactive &tutorial...\tF1" msgstr "加载交互式教程(&T)...\tF1" #: system.h:288 #, c-format msgid "Loaded %s (%d cells, %d characters)." msgstr "加载% s (% d 单元格,% d 字符)。" #: myframe.h:281 myframe.h:284 msgid "Make a 1xN selection to indicate which column to sort on, and which rows to affect" msgstr "进行1xn 选择, 以指示要对哪一列进行排序, 以及要影响哪些行" #: myframe.h:326 msgid "Make a hierarchy layout more vertical (default) or more horizontal" msgstr "使层次结构布局更垂直 (默认) 或更水平" #: myframe.h:461 msgid "Minimize on close" msgstr "关闭时最小化" #: myframe.h:459 msgid "Minimize to tray" msgstr "最小化到托盘" #: myframe.h:222 msgid "Move Cells Down\tCTRL+DOWN" msgstr "向下移动单元格\tCTRL+DOWN" #: myframe.h:219 msgid "Move Cells Left\tCTRL+LEFT" msgstr "向左移动单元格\tCTRL+LEFT" #: myframe.h:220 msgid "Move Cells Right\tCTRL+RIGHT" msgstr "向右移动单元格\tCTRL+RIGHT" #: myframe.h:221 msgid "Move Cells Up\tCTRL+UP" msgstr "向上移动单元格\tCTRL+UP" #: myframe.h:217 msgid "Move Selection Down\tDOWN" msgstr "向下移动所选内容\tDOWN" #: myframe.h:214 msgid "Move Selection Left\tLEFT" msgstr "向左移动所选内容\tLEFT" #: myframe.h:215 msgid "Move Selection Right\tRIGHT" msgstr "向右移动选择\tRIGHT" #: myframe.h:216 msgid "Move Selection Up\tUP" msgstr "上移所选内容\tUP" #: myframe.h:209 msgid "Move to next cell\tTAB" msgstr "移动到下一个单元格\tTAB" #: myframe.h:210 msgid "Move to previous cell\tSHIFT+TAB" msgstr "移动到上一个单元格\tSHIFT+TAB" #: document.h:1060 msgid "NOTE: key binding will take effect next run of TreeSheets." msgstr "注: 键绑定将在下一次运行的树表中生效。" #: myframe.h:468 msgid "Navigate in between cells with cursor keys" msgstr "使用光标键在单元格之间导航" #: myframe.h:557 msgid "New (CTRL+n)" msgstr "新建 (ctrl n)" #: myframe.h:569 msgid "New Grid (INS)" msgstr "新网格 (ins)" #: document.h:863 msgid "New Sheet" msgstr "新工作表" #: document.h:864 msgid "New file cancelled." msgstr "新文件已取消。" #: document.h:1460 msgid "No image in this cell." msgstr "此单元格中没有图像。" #: document.h:1567 msgid "No matches for search." msgstr "没有用于搜索的匹配项。" #: document.h:1481 msgid "No matching cell found!" msgstr "找不到匹配的单元格!" #: document.h:1563 msgid "No search string." msgstr "没有搜索字符串。" #: document.h:999 document.h:1293 msgid "No search." msgstr "不搜索。" #: document.h:1252 msgid "No style to paste." msgstr "没有要粘贴的样式。" #: document.h:1114 msgid "No text selected." msgstr "未选择文本。" #: system.h:215 msgid "Not a TreeSheets file." msgstr "不是 \"树表\" 文件。" #: document.h:818 msgid "Nothing more to redo." msgstr "没有什么可重做的了。" #: document.h:810 msgid "Nothing more to undo." msgstr "没有更多的撤消。" #: document.h:1011 msgid "Now viewing TreeSheet to fit to the screen exactly, press F12 to return to normal." msgstr "现在查看树板以完全适合屏幕, 按 f12 返回正常状态。" #: myframe.h:308 msgid "Open &file\tF4" msgstr "打开文件(&F)\tF4" #: myframe.h:558 msgid "Open (CTRL+o)" msgstr "打开 (ctrl o)" #: system.h:335 msgid "Open file cancelled." msgstr "打开文件已取消。" #: myframe.h:305 msgid "Open link in &browser\tF5" msgstr "在浏览器中打开链接(&B)\tF5" #: myframe.h:306 msgid "Opens up the text from the selected cell in browser (should start be a valid URL)" msgstr "在浏览器中打开选定单元格中的文本 (开始时应该是有效的 url)" #: myframe.h:309 msgid "Opens up the text from the selected cell in default application for the file type" msgstr "在文件类型的默认应用程序中打开选定单元格中的文本" #: system.h:308 msgid "PNG decode failed on some images in this document\n" "They have been replaced by red squares." msgstr "png 解码在本文档中的某些图像上失败\n" "它们已被红色方块所取代。" #: system.h:310 msgid "PNG decoder failure" msgstr "png 解码器故障" #: myframe.h:174 msgid "Page Setup..." msgstr "页面设置..." #: myframe.h:564 msgid "Paste (CTRL+v)" msgstr "粘贴 (ctrl v)" #: myframe.h:333 msgid "Paste Style Only\tCTRL+SHIFT+v" msgstr "仅粘贴样式\tCTRL+SHIFT+v" #: myframe.h:449 msgid "Pick Custom &Color..." msgstr "选择自定义颜色(&C)..." #: myframe.h:447 msgid "Pick Default Font..." msgstr "选择默认字体..." #: myframe.h:451 msgid "Pick Document Background..." msgstr "选择文档背景..." #: myframe.h:862 msgid "Please enable (Options -> Show Toolbar) to use search." msgstr "请启用 (选项-> 显示工具栏) 以使用搜索。" #: document.h:1462 msgid "Please enter the percentage you want the image scaled by:" msgstr "请输入要按以下方式缩放图像的百分比:" #: document.h:1048 msgid "Please pick a menu item to change the key binding for" msgstr "请选择一个菜单项来更改的键绑定" #: document.h:840 msgid "Please select a TreeSheets file to load:" msgstr "请选择要加载的树表格文件:" #: document.h:1272 msgid "Please select an image file:" msgstr "请选择图像文件:" #: system.h:373 msgid "Please select file to import:" msgstr "请选择要导入的文件:" #: myframe.h:855 msgid "Press F11 to exit fullscreen mode." msgstr "按 f11 退出全屏模式。" #: document.h:943 msgid "Print Preview" msgstr "打印预览" #: myframe.h:176 msgid "Print preview..." msgstr "打印预览..." #: myframe.h:437 msgid "Radius &0" msgstr "半径0(&0)" #: myframe.h:438 msgid "Radius &1" msgstr "半径1(&1)" #: myframe.h:439 msgid "Radius &2" msgstr "半径2(&2)" #: myframe.h:440 msgid "Radius &3" msgstr "半径3(&3)" #: myframe.h:441 msgid "Radius &4" msgstr "半径4(&4)" #: myframe.h:442 msgid "Radius &5" msgstr "半径5(&5)" #: myframe.h:443 msgid "Radius &6" msgstr "半径6(&6)" #: myframe.h:477 msgid "Reloads when another computer has changed a file (if you have made changes, asks)" msgstr "当另一台计算机更改了文件时重新加载 (如果您进行了更改, 请询问)" #: myframe.h:302 msgid "Remove image(s) from the selected cells" msgstr "从选定单元格中删除图像" #: myframe.h:482 msgid "Render document centered" msgstr "呈现文档居中" #: myframe.h:578 msgid "Replace " msgstr "取代 " #: myframe.h:389 msgid "Replace &All" msgstr "全部替换(&A)" #: myframe.h:267 msgid "Reset &colors\tSHIFT+CTRL+c" msgstr "重置颜色(&C)\tSHIFT+CTRL+c" #: myframe.h:198 msgid "Reset column widths\tSHIFT+CTRL+w" msgstr "重置列宽度\tSHIFT+CTRL+w" #: myframe.h:572 msgid "Run" msgstr "运行" #: myframe.h:172 msgid "Save &As..." msgstr "另存为(&A)..." #: myframe.h:559 msgid "Save (CTRL+s)" msgstr "保存 (ctrl s)" #: myframe.h:560 msgid "Save As" msgstr "另存为" #: document.h:601 msgid "Save and Close" msgstr "保存并关闭" #: document.h:153 document.h:713 msgid "Save cancelled." msgstr "保存已取消。" #: myframe.h:395 msgid "Scroll Down (mousewheel)\tALT+DOWN" msgstr "向下滚动 (鼠标滚轮)\tALT+DOWN" #: myframe.h:394 msgid "Scroll Down (mousewheel)\tPGDN" msgstr "向下滚动 (鼠标滚轮)\tPGDN" #: myframe.h:396 msgid "Scroll Left\tALT+LEFT" msgstr "向左滚动\tALT+LEFT" #: myframe.h:397 msgid "Scroll Right\tALT+RIGHT" msgstr "向右滚动\tALT+RIGHT" #: myframe.h:433 msgid "Scroll Sheet..." msgstr "" #: myframe.h:393 msgid "Scroll Up (mousewheel)\tALT+UP" msgstr "向上滚动 (鼠标滚轮)\tALT+UP" #: myframe.h:392 msgid "Scroll Up (mousewheel)\tPGUP" msgstr "向上滚动 (鼠标滚轮)\tPGUP" #: myframe.h:574 msgid "Search " msgstr "搜索 " #: myframe.h:231 msgid "Select &Parent\tESC" msgstr "选择父项(&P)\tESC" #: myframe.h:212 msgid "Select &all in current grid\tCTRL+a" msgstr "在当前网格中选择所有(&A)\tCTRL+a" #: myframe.h:232 msgid "Select First &Child\tSHIFT+ENTER" msgstr "选择第一个子项(&C)\tSHIFT+ENTER" #: document.h:1414 msgid "Selected grid is not a table: cells must not already have sub-grids." msgstr "选定的网格不是表: 单元格不能已经有子网格。" #: myframe.h:159 msgid "Semi-Colon delimited text (CSV)..." msgstr "半冒号分隔文本 (csv)..." #: myframe.h:378 msgid "Set Grid Border Width..." msgstr "设置网格边框宽度..." #: document.h:935 msgid "Set Print Scale" msgstr "设置打印比例" #: myframe.h:175 msgid "Set Print Scale..." msgstr "设置打印比例..." #: myframe.h:407 #, c-format msgid "Show 1% less than the last filter" msgstr "显示比最后一个筛选器少1%" #: myframe.h:406 #, c-format msgid "Show 1% more than the last filter" msgstr "显示比最后一个筛选器多1%" #: myframe.h:403 #, c-format msgid "Show 10% of last edits" msgstr "显示上次编辑的10%" #: myframe.h:404 #, c-format msgid "Show 20% of last edits" msgstr "显示上次编辑的20%" #: myframe.h:402 #, c-format msgid "Show 5% of last edits" msgstr "显示上次编辑的5%" #: myframe.h:405 #, c-format msgid "Show 50% of last edits" msgstr "显示上次编辑的50%" #: myframe.h:453 msgid "Show Statusbar" msgstr "显示状态栏" #: myframe.h:455 msgid "Show Toolbar" msgstr "显示工具栏" #: myframe.h:401 msgid "Show only cells in current search" msgstr "仅在当前搜索中显示单元格" #: myframe.h:463 msgid "Single click maximize from tray" msgstr "从托盘中单击最大化" #: system.h:358 #, c-format msgid "Size %d" msgstr "尺寸% d" #: myframe.h:280 msgid "Sort &Ascending" msgstr "排序升序(&A)" #: myframe.h:283 msgid "Sort &Descending" msgstr "排序降序(&D)" #: myframe.h:250 msgid "Start of line of text\tHOME" msgstr "文本行的开始\tHOME" #: myframe.h:252 msgid "Start of text\tCTRL+HOME" msgstr "文本的开始\tCTRL+HOME" #: myframe.h:287 msgid "Swap all cells with this text at this level (or above) with the parent" msgstr "将此文本与父级 (或更高版本) 的所有单元格交换" #: myframe.h:466 msgid "Swap mousewheel scrolling and zooming" msgstr "交换鼠标滚轮滚动和缩放" #: myframe.h:418 msgid "Switch to &next file/tab" msgstr "切换到下一个文件/选项卡(&N)" #: myframe.h:414 msgid "Switch to &next file/tab\tCTRL+TAB" msgstr "切换到下一个文件/选项卡(&N)\tCTRL+TAB" #: myframe.h:420 msgid "Switch to &previous file/tab\tSHIFT+CTRL+TAB" msgstr "切换到以前的文件/选项卡(&P)\tSHIFT+CTRL+TAB" #: myframe.h:160 msgid "Tab delimited text..." msgstr "制表符分隔的文本格式..." #: myframe.h:379 msgid "Tag..." msgstr "标记。。..." #: myframe.h:293 msgid "Takes a hierarchy (nested 1xN or Nx1 grids) and converts it into a flat NxN grid, useful for export to spreadsheets" msgstr "采用层次结构 (嵌套1xn 或1xN 网格) 并将其转换为平面1xN 网格, 可用于导出到电子表格" #: myframe.h:585 msgid "Text " msgstr "文本 " #: myframe.h:375 msgid "Text &Editing..." msgstr "文本编辑(&E)..." #: myframe.h:376 msgid "Text Sizing..." msgstr "文本大小..." #: myframe.h:377 msgid "Text Style..." msgstr "文本样式..." #: document.h:429 msgid "Text size decreased." msgstr "文本大小减小。" #: document.h:429 msgid "Text size increased." msgstr "文本大小增加。" #: document.h:875 msgid "The Free Form Hierarchical Information Organizer" msgstr "自由形式分层信息管理器" #: document.h:402 msgid "This operation doesn't work on thin selections." msgstr "此操作不适用于精简选择。" #: document.h:403 msgid "This operation requires a cell that contains a grid." msgstr "此操作需要一个包含网格的单元格。" #: document.h:400 msgid "This operation requires a selection." msgstr "此操作需要选择。" #: document.h:401 msgid "This operation works on a single selected cell only." msgstr "此操作仅适用于单个选定的单元格。" #: myframe.h:423 msgid "Toggle &Fullscreen View\tCTRL+F11" msgstr "切换全屏视图(&F)\tCTRL+F11" #: myframe.h:425 msgid "Toggle &Fullscreen View\tF11" msgstr "切换全屏视图(&F)\tF11" #: myframe.h:429 msgid "Toggle &Scaled Presentation View\tCTRL+F12" msgstr "切换缩放的演示文稿视图(&S)\tCTRL+F12" #: myframe.h:431 msgid "Toggle &Scaled Presentation View\tF12" msgstr "切换缩放的演示文稿视图(&S)\tF12" #: myframe.h:360 msgid "Toggle Fold\tCTRL+F10" msgstr "切换折叠\tCTRL+F10" #: myframe.h:362 msgid "Toggle Fold\tF10" msgstr "切换折叠\tF10" #: myframe.h:325 msgid "Toggle Vertical Layout\tF7" msgstr "切换垂直布局\tF7" #: myframe.h:260 msgid "Toggle cell &BOLD\tCTRL+b" msgstr "切换单元格(&B)\tCTRL+b" #: myframe.h:261 msgid "Toggle cell &ITALIC\tCTRL+i" msgstr "切换单元格意大利语(&I)\tCTRL+i" #: myframe.h:264 msgid "Toggle cell &strikethrough\tCTRL+t" msgstr "切换单元格删除线(&S)\tCTRL+t" #: myframe.h:262 msgid "Toggle cell &typewriter" msgstr "切换单元格打字机(&T)" #: myframe.h:263 msgid "Toggle cell &underlined\tCTRL+u" msgstr "切换带下划线的单元格(&U)\tCTRL+u" #: myframe.h:364 msgid "Toggles showing the grid of the selected cell(s)" msgstr "切换, 显示所选单元格的网格" #: myframe.h:400 msgid "Turn filter &off" msgstr "关闭过滤器(&O)" #: myframe.h:562 msgid "Undo (CTRL+z)" msgstr "撤消 (ctrl z)" #: myframe.h:367 msgid "Unfold All\tCTRL+ALT+F10" msgstr "全部展开\tCTRL+ALT+F10" #: myframe.h:368 msgid "Unfolds the grid of the selected cell(s) recursively" msgstr "递归展开选定单元格的网格" #: myframe.h:493 msgid "Variable &Assign" msgstr "变量分配(&A)" #: myframe.h:494 msgid "Variable &Read" msgstr "变量读取(&R)" #: myframe.h:314 msgid "Vertical Layout with Bubble Style Rendering\tALT+2" msgstr "具有气泡样式渲染的垂直布局\tALT+2" #: myframe.h:313 msgid "Vertical Layout with Grid Style Rendering\tALT+1" msgstr "具有网格样式渲染的垂直布局\tALT+1" #: myframe.h:315 msgid "Vertical Layout with Line Style Rendering\tALT+3" msgstr "具有线条样式渲染的垂直布局\tALT+3" #: myframe.h:506 msgid "View tutorial &web page..." msgstr "查看教程网页(&W)..." #: document.h:862 msgid "What size grid would you like to start with?" msgstr "您希望从什么尺寸的网格开始?" #: system.h:359 #, c-format msgid "Width %d" msgstr "宽度% d" #: myframe.h:240 msgid "Word Left\tCTRL+LEFT" msgstr "左边的单词\tCTRL+LEFT" #: myframe.h:241 msgid "Word Right\tCTRL+RIGHT" msgstr "右文字\tCTRL+RIGHT" #: myframe.h:156 msgid "XML (attributes too, for OPML etc)..." msgstr "" #: myframe.h:155 msgid "XML..." msgstr "" #: document.h:187 msgid "Zlib error while writing file." msgstr "写入文件时出现 zlib 错误。" #: myframe.h:410 msgid "Zoom &In (CTRL+mousewheel)\tCTRL+PGUP" msgstr "放大 (ctrl 鼠标滚轮)(&I)\tCTRL+PGUP" #: myframe.h:411 msgid "Zoom &Out (CTRL+mousewheel)\tCTRL+PGDN" msgstr "缩小 (ctrl 鼠标滚轮)(&O)\tCTRL+PGDN" #: myframe.h:566 msgid "Zoom In (CTRL+mousewheel)" msgstr "放大 (ctrl 鼠标滚轮)" #: myframe.h:567 msgid "Zoom Out (CTRL+mousewheel)" msgstr "缩小 (ctrl 鼠标滚轮)" #: document.h:434 msgid "Zoomed in." msgstr "放大了。" #: document.h:434 msgid "Zoomed out." msgstr "缩小了。" #: myframe.h:807 msgid "change will take effect next run of TreeSheets" msgstr "更改将在下一次运行的树表中生效" #: myframe.h:279 msgid "changes the orientation of a grid" msgstr "更改网格的方向" #: system.h:420 msgid "couldn't import file!" msgstr "无法导入文件!" #: myframe.h:334 msgid "only sets the colors and style of the copied cell, and keeps the text" msgstr "仅设置复制单元格的颜色和样式, 并保留文本" #: document.h:1514 msgid "only works in cell text mode" msgstr "仅在单元格文本模式下工作" #: myframe.h:338 msgid "redo any undo steps, if you haven't made changes since" msgstr "重做任何撤消步骤, 如果你没有做的更改, 因为" #: myframe.h:336 msgid "revert the changes, one step at a time" msgstr "恢复更改, 一步一步" #: document.h:934 msgid "scale:" msgstr "规模:" #: document.h:863 msgid "size:" msgstr "大小:" treesheets-1.0.2/TS/treesheets.desktop000066400000000000000000000006021352107072600177620ustar00rootroot00000000000000[Desktop Entry] Version=1.0 Type=Application Name=TreeSheets GenericName=Treesheets Comment=A hierarhical spreadsheet / outliner productivity tool. TryExec=treesheets Exec=treesheets %f Terminal=false Icon=images/treesheets.svg MimeType=application/x-treesheets; Categories=Office; Keywords=Mindmaps; Knowledge management; Organize information; Brainstorming; Hierarchical spreadsheet; treesheets-1.0.2/TS_installer.nsi000066400000000000000000000054441352107072600170150ustar00rootroot00000000000000 !include "MUI.nsh" !define MUI_FINISHPAGE_RUN "$INSTDIR\TreeSheets.exe" !define MUI_HEADERIMAGE !define MUI_HEADERIMAGE_BITMAP "TreeSheets\tsinst.bmp" /* doesn't show? !define MUI_HEADERIMAGE_UNBITMAP "TreeSheets\tsinst.bmp" */ Name "TreeSheets" OutFile "Treesheets_Setup.exe" XPStyle on InstallDir $PROGRAMFILES\TreeSheets InstallDirRegKey HKLM "Software\TreeSheets" "Install_Dir" SetCompressor /SOLID lzma XPStyle on Page components #"" ba "" Page directory Page instfiles !insertmacro MUI_PAGE_FINISH UninstPage uninstConfirm UninstPage instfiles !insertmacro MUI_LANGUAGE "English" /* AddBrandingImage top 65 Function ba File TreeSheets\dot3.bmp SetBrandingImage TreeSheets\dot3.bmp FunctionEnd Function un.ba SetBrandingImage TreeSheets\dot3.bmp FunctionEnd */ Function .onInit FindWindow $0 "TreeSheets" "" StrCmp $0 0 continueInstall MessageBox MB_ICONSTOP|MB_OK "TreeSheets is already running, please close it and try again." Abort continueInstall: FunctionEnd Function un.onInit FindWindow $0 "TreeSheets" "" StrCmp $0 0 continueInstall MessageBox MB_ICONSTOP|MB_OK "TreeSheets is still running, please close it and try again." Abort continueInstall: FunctionEnd Section "TreeSheets (required)" SectionIn RO SetOutPath $INSTDIR File /r "TS\*.*" WriteRegStr HKLM SOFTWARE\TreeSheets "Install_Dir" "$INSTDIR" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TreeSheets" "DisplayName" "TreeSheets" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TreeSheets" "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TreeSheets" "NoModify" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TreeSheets" "NoRepair" 1 WriteUninstaller "uninstall.exe" SectionEnd /* Section "Visual C++ redistributable runtime" ExecWait '"$INSTDIR\redist\vcredist_x86.exe"' SectionEnd */ Section "Start Menu Shortcuts" CreateDirectory "$SMPROGRAMS\TreeSheets" CreateDirectory "$APPDATA\TreeSheetsdbs\" SetOutPath "$INSTDIR" CreateShortCut "$SMPROGRAMS\TreeSheets\TreeSheets.lnk" "$INSTDIR\TreeSheets.exe" "" "$INSTDIR\TreeSheets.exe" 0 CreateShortCut "$SMPROGRAMS\TreeSheets\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 CreateShortCut "$SMPROGRAMS\TreeSheets\Documentation.lnk" "$INSTDIR\readme.html" "" "$INSTDIR\readme.html" 0 CreateShortCut "$SMPROGRAMS\TreeSheets\Examples.lnk" "$INSTDIR\Examples\" "" "$INSTDIR\Examples\" 0 SectionEnd Section "Uninstall" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TreeSheets" DeleteRegKey HKLM SOFTWARE\TreeSheets RMDir /r "$SMPROGRAMS\TreeSheets" RMDir /r "$INSTDIR" SectionEnd treesheets-1.0.2/ZLIB_LICENSE.txt000077500000000000000000000015771352107072600163500ustar00rootroot00000000000000Copyright (c) 2008 Wouter van Oortmerssen This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. treesheets-1.0.2/lobster/000077500000000000000000000000001352107072600153425ustar00rootroot00000000000000treesheets-1.0.2/lobster/external/000077500000000000000000000000001352107072600171645ustar00rootroot00000000000000treesheets-1.0.2/lobster/external/flatbuffers/000077500000000000000000000000001352107072600214675ustar00rootroot00000000000000treesheets-1.0.2/lobster/external/flatbuffers/src/000077500000000000000000000000001352107072600222565ustar00rootroot00000000000000treesheets-1.0.2/lobster/external/flatbuffers/src/idl_gen_text.cpp000066400000000000000000000261741352107072600254410ustar00rootroot00000000000000/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // independent from idl_parser, since this code is not needed for most clients #include "flatbuffers/flatbuffers.h" #include "flatbuffers/flexbuffers.h" #include "flatbuffers/idl.h" #include "flatbuffers/util.h" namespace flatbuffers { static bool GenStruct(const StructDef &struct_def, const Table *table, int indent, const IDLOptions &opts, std::string *_text); // If indentation is less than 0, that indicates we don't want any newlines // either. const char *NewLine(const IDLOptions &opts) { return opts.indent_step >= 0 ? "\n" : ""; } int Indent(const IDLOptions &opts) { return std::max(opts.indent_step, 0); } // Output an identifier with or without quotes depending on strictness. void OutputIdentifier(const std::string &name, const IDLOptions &opts, std::string *_text) { std::string &text = *_text; if (opts.strict_json) text += "\""; text += name; if (opts.strict_json) text += "\""; } // Print (and its template specialization below for pointers) generate text // for a single FlatBuffer value into JSON format. // The general case for scalars: template bool Print(T val, Type type, int /*indent*/, Type * /*union_type*/, const IDLOptions &opts, std::string *_text) { std::string &text = *_text; if (type.enum_def && opts.output_enum_identifiers) { auto enum_val = type.enum_def->ReverseLookup(static_cast(val)); if (enum_val) { text += "\""; text += enum_val->name; text += "\""; return true; } } if (type.base_type == BASE_TYPE_BOOL) { text += val != 0 ? "true" : "false"; } else { text += NumToString(val); } return true; } // Print a vector a sequence of JSON values, comma separated, wrapped in "[]". template bool PrintVector(const Vector &v, Type type, int indent, const IDLOptions &opts, std::string *_text) { std::string &text = *_text; text += "["; text += NewLine(opts); for (uoffset_t i = 0; i < v.size(); i++) { if (i) { if (!opts.protobuf_ascii_alike) text += ","; text += NewLine(opts); } text.append(indent + Indent(opts), ' '); if (IsStruct(type)) { if (!Print(v.GetStructFromOffset(i * type.struct_def->bytesize), type, indent + Indent(opts), nullptr, opts, _text)) { return false; } } else { if (!Print(v[i], type, indent + Indent(opts), nullptr, opts, _text)) { return false; } } } text += NewLine(opts); text.append(indent, ' '); text += "]"; return true; } // Specialization of Print above for pointer types. template<> bool Print(const void *val, Type type, int indent, Type *union_type, const IDLOptions &opts, std::string *_text) { switch (type.base_type) { case BASE_TYPE_UNION: // If this assert hits, you have an corrupt buffer, a union type field // was not present or was out of range. FLATBUFFERS_ASSERT(union_type); return Print(val, *union_type, indent, nullptr, opts, _text); case BASE_TYPE_STRUCT: if (!GenStruct(*type.struct_def, reinterpret_cast(val), indent, opts, _text)) { return false; } break; case BASE_TYPE_STRING: { auto s = reinterpret_cast(val); if (!EscapeString(s->c_str(), s->Length(), _text, opts.allow_non_utf8, opts.natural_utf8)) { return false; } break; } case BASE_TYPE_VECTOR: type = type.VectorType(); // Call PrintVector above specifically for each element type: switch (type.base_type) { // clang-format off #define FLATBUFFERS_TD(ENUM, IDLTYPE, \ CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, RTYPE) \ case BASE_TYPE_ ## ENUM: \ if (!PrintVector( \ *reinterpret_cast *>(val), \ type, indent, opts, _text)) { \ return false; \ } \ break; FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD) #undef FLATBUFFERS_TD // clang-format on } break; default: FLATBUFFERS_ASSERT(0); } return true; } // Generate text for a scalar field. template static bool GenField(const FieldDef &fd, const Table *table, bool fixed, const IDLOptions &opts, int indent, std::string *_text) { return Print(fixed ? reinterpret_cast(table)->GetField(fd.value.offset) : table->GetField(fd.value.offset, IsFloat(fd.value.type.base_type) ? static_cast(strtod(fd.value.constant.c_str(), nullptr)) : static_cast(StringToInt(fd.value.constant.c_str()))), fd.value.type, indent, nullptr, opts, _text); } static bool GenStruct(const StructDef &struct_def, const Table *table, int indent, const IDLOptions &opts, std::string *_text); // Generate text for non-scalar field. static bool GenFieldOffset(const FieldDef &fd, const Table *table, bool fixed, int indent, Type *union_type, const IDLOptions &opts, std::string *_text) { const void *val = nullptr; if (fixed) { // The only non-scalar fields in structs are structs. FLATBUFFERS_ASSERT(IsStruct(fd.value.type)); val = reinterpret_cast(table)->GetStruct( fd.value.offset); } else if (fd.flexbuffer) { auto vec = table->GetPointer *>(fd.value.offset); auto root = flexbuffers::GetRoot(vec->data(), vec->size()); root.ToString(true, opts.strict_json, *_text); return true; } else if (fd.nested_flatbuffer) { auto vec = table->GetPointer *>(fd.value.offset); auto root = GetRoot(vec->data()); return GenStruct(*fd.nested_flatbuffer, root, indent, opts, _text); } else { val = IsStruct(fd.value.type) ? table->GetStruct(fd.value.offset) : table->GetPointer(fd.value.offset); } return Print(val, fd.value.type, indent, union_type, opts, _text); } // Generate text for a struct or table, values separated by commas, indented, // and bracketed by "{}" static bool GenStruct(const StructDef &struct_def, const Table *table, int indent, const IDLOptions &opts, std::string *_text) { std::string &text = *_text; text += "{"; int fieldout = 0; Type *union_type = nullptr; for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { FieldDef &fd = **it; auto is_present = struct_def.fixed || table->CheckField(fd.value.offset); auto output_anyway = opts.output_default_scalars_in_json && IsScalar(fd.value.type.base_type) && !fd.deprecated; if (is_present || output_anyway) { if (fieldout++) { if (!opts.protobuf_ascii_alike) text += ","; } text += NewLine(opts); text.append(indent + Indent(opts), ' '); OutputIdentifier(fd.name, opts, _text); if (!opts.protobuf_ascii_alike || (fd.value.type.base_type != BASE_TYPE_STRUCT && fd.value.type.base_type != BASE_TYPE_VECTOR)) text += ":"; text += " "; switch (fd.value.type.base_type) { // clang-format off #define FLATBUFFERS_TD(ENUM, IDLTYPE, \ CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, RTYPE) \ case BASE_TYPE_ ## ENUM: \ if (!GenField(fd, table, struct_def.fixed, \ opts, indent + Indent(opts), _text)) { \ return false; \ } \ break; FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD) #undef FLATBUFFERS_TD // Generate drop-thru case statements for all pointer types: #define FLATBUFFERS_TD(ENUM, IDLTYPE, \ CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, RTYPE) \ case BASE_TYPE_ ## ENUM: FLATBUFFERS_GEN_TYPES_POINTER(FLATBUFFERS_TD) #undef FLATBUFFERS_TD if (!GenFieldOffset(fd, table, struct_def.fixed, indent + Indent(opts), union_type, opts, _text)) { return false; } break; // clang-format on } if (fd.value.type.base_type == BASE_TYPE_UTYPE) { auto enum_val = fd.value.type.enum_def->ReverseLookup( table->GetField(fd.value.offset, 0)); union_type = enum_val ? &enum_val->union_type : nullptr; } } } text += NewLine(opts); text.append(indent, ' '); text += "}"; return true; } // Generate a text representation of a flatbuffer in JSON format. bool GenerateText(const Parser &parser, const void *flatbuffer, std::string *_text) { std::string &text = *_text; FLATBUFFERS_ASSERT(parser.root_struct_def_); // call SetRootType() text.reserve(1024); // Reduce amount of inevitable reallocs. auto root = parser.opts.size_prefixed ? GetSizePrefixedRoot
(flatbuffer) : GetRoot
(flatbuffer); if (!GenStruct(*parser.root_struct_def_, root, 0, parser.opts, _text)) { return false; } text += NewLine(parser.opts); return true; } std::string TextFileName(const std::string &path, const std::string &file_name) { return path + file_name + ".json"; } bool GenerateTextFile(const Parser &parser, const std::string &path, const std::string &file_name) { if (!parser.builder_.GetSize() || !parser.root_struct_def_) return true; std::string text; if (!GenerateText(parser, parser.builder_.GetBufferPointer(), &text)) { return false; } return flatbuffers::SaveFile(TextFileName(path, file_name).c_str(), text, false); } std::string TextMakeRule(const Parser &parser, const std::string &path, const std::string &file_name) { if (!parser.builder_.GetSize() || !parser.root_struct_def_) return ""; std::string filebase = flatbuffers::StripPath(flatbuffers::StripExtension(file_name)); std::string make_rule = TextFileName(path, filebase) + ": " + file_name; auto included_files = parser.GetIncludedFilesRecursive(parser.root_struct_def_->file); for (auto it = included_files.begin(); it != included_files.end(); ++it) { make_rule += " " + *it; } return make_rule; } } // namespace flatbuffers treesheets-1.0.2/lobster/external/flatbuffers/src/idl_parser.cpp000066400000000000000000003052731352107072600251200ustar00rootroot00000000000000/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include "flatbuffers/idl.h" #include "flatbuffers/util.h" namespace flatbuffers { const double kPi = 3.14159265358979323846; const char *const kTypeNames[] = { // clang-format off #define FLATBUFFERS_TD(ENUM, IDLTYPE, \ CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, RTYPE) \ IDLTYPE, FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD) #undef FLATBUFFERS_TD // clang-format on nullptr }; const char kTypeSizes[] = { // clang-format off #define FLATBUFFERS_TD(ENUM, IDLTYPE, \ CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, RTYPE) \ sizeof(CTYPE), FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD) #undef FLATBUFFERS_TD // clang-format on }; // The enums in the reflection schema should match the ones we use internally. // Compare the last element to check if these go out of sync. static_assert(BASE_TYPE_UNION == static_cast(reflection::Union), "enums don't match"); // Any parsing calls have to be wrapped in this macro, which automates // handling of recursive error checking a bit. It will check the received // CheckedError object, and return straight away on error. #define ECHECK(call) \ { \ auto ce = (call); \ if (ce.Check()) return ce; \ } // These two functions are called hundreds of times below, so define a short // form: #define NEXT() ECHECK(Next()) #define EXPECT(tok) ECHECK(Expect(tok)) static bool ValidateUTF8(const std::string &str) { const char *s = &str[0]; const char *const sEnd = s + str.length(); while (s < sEnd) { if (FromUTF8(&s) < 0) { return false; } } return true; } // Convert an underscore_based_indentifier in to camelCase. // Also uppercases the first character if first is true. std::string MakeCamel(const std::string &in, bool first) { std::string s; for (size_t i = 0; i < in.length(); i++) { if (!i && first) s += static_cast(toupper(in[0])); else if (in[i] == '_' && i + 1 < in.length()) s += static_cast(toupper(in[++i])); else s += in[i]; } return s; } void Parser::Message(const std::string &msg) { error_ = file_being_parsed_.length() ? AbsolutePath(file_being_parsed_) : ""; // clang-format off #ifdef _WIN32 // MSVC alike error_ += "(" + NumToString(line_) + ", " + NumToString(CursorPosition()) + ")"; #else // gcc alike if (file_being_parsed_.length()) error_ += ":"; error_ += NumToString(line_) + ": " + NumToString(CursorPosition()); #endif // clang-format on error_ += ": " + msg; } void Parser::Warning(const std::string &msg) { Message("warning: " + msg); } CheckedError Parser::Error(const std::string &msg) { Message("error: " + msg); return CheckedError(true); } inline CheckedError NoError() { return CheckedError(false); } CheckedError Parser::RecurseError() { return Error("maximum parsing recursion of " + NumToString(kMaxParsingDepth) + " reached"); } inline std::string OutOfRangeErrorMsg(int64_t val, const std::string &op, int64_t limit) { const std::string cause = NumToString(val) + op + NumToString(limit); return "constant does not fit (" + cause + ")"; } // Ensure that integer values we parse fit inside the declared integer type. CheckedError Parser::CheckInRange(int64_t val, int64_t min, int64_t max) { if (val < min) return Error(OutOfRangeErrorMsg(val, " < ", min)); else if (val > max) return Error(OutOfRangeErrorMsg(val, " > ", max)); else return NoError(); } // atot: templated version of atoi/atof: convert a string to an instance of T. template inline CheckedError atot(const char *s, Parser &parser, T *val) { int64_t i = StringToInt(s); const int64_t min = flatbuffers::numeric_limits::min(); const int64_t max = flatbuffers::numeric_limits::max(); *val = (T)i; // Assign this first to make ASAN happy. return parser.CheckInRange(i, min, max); } template<> inline CheckedError atot(const char *s, Parser &parser, uint64_t *val) { (void)parser; *val = StringToUInt(s); return NoError(); } template<> inline CheckedError atot(const char *s, Parser &parser, bool *val) { (void)parser; *val = 0 != atoi(s); return NoError(); } template<> inline CheckedError atot(const char *s, Parser &parser, float *val) { (void)parser; *val = static_cast(strtod(s, nullptr)); return NoError(); } template<> inline CheckedError atot(const char *s, Parser &parser, double *val) { (void)parser; *val = strtod(s, nullptr); return NoError(); } template<> inline CheckedError atot>(const char *s, Parser &parser, Offset *val) { (void)parser; *val = Offset(atoi(s)); return NoError(); } std::string Namespace::GetFullyQualifiedName(const std::string &name, size_t max_components) const { // Early exit if we don't have a defined namespace. if (components.empty() || !max_components) { return name; } std::string stream_str; for (size_t i = 0; i < std::min(components.size(), max_components); i++) { if (i) { stream_str += '.'; } stream_str += std::string(components[i]); } if (name.length()) { stream_str += '.'; stream_str += name; } return stream_str; } // Declare tokens we'll use. Single character tokens are represented by their // ascii character code (e.g. '{'), others above 256. // clang-format off #define FLATBUFFERS_GEN_TOKENS(TD) \ TD(Eof, 256, "end of file") \ TD(StringConstant, 257, "string constant") \ TD(IntegerConstant, 258, "integer constant") \ TD(FloatConstant, 259, "float constant") \ TD(Identifier, 260, "identifier") #ifdef __GNUC__ __extension__ // Stop GCC complaining about trailing comma with -Wpendantic. #endif enum { #define FLATBUFFERS_TOKEN(NAME, VALUE, STRING) kToken ## NAME = VALUE, FLATBUFFERS_GEN_TOKENS(FLATBUFFERS_TOKEN) #undef FLATBUFFERS_TOKEN }; static std::string TokenToString(int t) { static const char * const tokens[] = { #define FLATBUFFERS_TOKEN(NAME, VALUE, STRING) STRING, FLATBUFFERS_GEN_TOKENS(FLATBUFFERS_TOKEN) #undef FLATBUFFERS_TOKEN #define FLATBUFFERS_TD(ENUM, IDLTYPE, \ CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, RTYPE) \ IDLTYPE, FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD) #undef FLATBUFFERS_TD }; if (t < 256) { // A single ascii char token. std::string s; s.append(1, static_cast(t)); return s; } else { // Other tokens. return tokens[t - 256]; } } // clang-format on std::string Parser::TokenToStringId(int t) const { return t == kTokenIdentifier ? attribute_ : TokenToString(t); } // Parses exactly nibbles worth of hex digits into a number, or error. CheckedError Parser::ParseHexNum(int nibbles, uint64_t *val) { for (int i = 0; i < nibbles; i++) if (!isxdigit(static_cast(cursor_[i]))) return Error("escape code must be followed by " + NumToString(nibbles) + " hex digits"); std::string target(cursor_, cursor_ + nibbles); *val = StringToUInt(target.c_str(), nullptr, 16); cursor_ += nibbles; return NoError(); } CheckedError Parser::SkipByteOrderMark() { if (static_cast(*cursor_) != 0xef) return NoError(); cursor_++; if (static_cast(*cursor_) != 0xbb) return Error("invalid utf-8 byte order mark"); cursor_++; if (static_cast(*cursor_) != 0xbf) return Error("invalid utf-8 byte order mark"); cursor_++; return NoError(); } bool IsIdentifierStart(char c) { return isalpha(static_cast(c)) || c == '_'; } CheckedError Parser::Next() { doc_comment_.clear(); bool seen_newline = cursor_ == source_; attribute_.clear(); for (;;) { char c = *cursor_++; token_ = c; switch (c) { case '\0': cursor_--; token_ = kTokenEof; return NoError(); case ' ': case '\r': case '\t': break; case '\n': MarkNewLine(); seen_newline = true; break; case '{': case '}': case '(': case ')': case '[': case ']': case ',': case ':': case ';': case '=': return NoError(); case '.': if (!isdigit(static_cast(*cursor_))) return NoError(); return Error("floating point constant can\'t start with \".\""); case '\"': case '\'': { int unicode_high_surrogate = -1; while (*cursor_ != c) { if (*cursor_ < ' ' && static_cast(*cursor_) >= 0) return Error("illegal character in string constant"); if (*cursor_ == '\\') { cursor_++; if (unicode_high_surrogate != -1 && *cursor_ != 'u') { return Error( "illegal Unicode sequence (unpaired high surrogate)"); } switch (*cursor_) { case 'n': attribute_ += '\n'; cursor_++; break; case 't': attribute_ += '\t'; cursor_++; break; case 'r': attribute_ += '\r'; cursor_++; break; case 'b': attribute_ += '\b'; cursor_++; break; case 'f': attribute_ += '\f'; cursor_++; break; case '\"': attribute_ += '\"'; cursor_++; break; case '\'': attribute_ += '\''; cursor_++; break; case '\\': attribute_ += '\\'; cursor_++; break; case '/': attribute_ += '/'; cursor_++; break; case 'x': { // Not in the JSON standard cursor_++; uint64_t val; ECHECK(ParseHexNum(2, &val)); attribute_ += static_cast(val); break; } case 'u': { cursor_++; uint64_t val; ECHECK(ParseHexNum(4, &val)); if (val >= 0xD800 && val <= 0xDBFF) { if (unicode_high_surrogate != -1) { return Error( "illegal Unicode sequence (multiple high surrogates)"); } else { unicode_high_surrogate = static_cast(val); } } else if (val >= 0xDC00 && val <= 0xDFFF) { if (unicode_high_surrogate == -1) { return Error( "illegal Unicode sequence (unpaired low surrogate)"); } else { int code_point = 0x10000 + ((unicode_high_surrogate & 0x03FF) << 10) + (val & 0x03FF); ToUTF8(code_point, &attribute_); unicode_high_surrogate = -1; } } else { if (unicode_high_surrogate != -1) { return Error( "illegal Unicode sequence (unpaired high surrogate)"); } ToUTF8(static_cast(val), &attribute_); } break; } default: return Error("unknown escape code in string constant"); } } else { // printable chars + UTF-8 bytes if (unicode_high_surrogate != -1) { return Error( "illegal Unicode sequence (unpaired high surrogate)"); } attribute_ += *cursor_++; } } if (unicode_high_surrogate != -1) { return Error("illegal Unicode sequence (unpaired high surrogate)"); } cursor_++; if (!opts.allow_non_utf8 && !ValidateUTF8(attribute_)) { return Error("illegal UTF-8 sequence"); } token_ = kTokenStringConstant; return NoError(); } case '/': if (*cursor_ == '/') { const char *start = ++cursor_; while (*cursor_ && *cursor_ != '\n' && *cursor_ != '\r') cursor_++; if (*start == '/') { // documentation comment if (!seen_newline) return Error( "a documentation comment should be on a line on its own"); doc_comment_.push_back(std::string(start + 1, cursor_)); } break; } else if (*cursor_ == '*') { cursor_++; // TODO: make nested. while (*cursor_ != '*' || cursor_[1] != '/') { if (*cursor_ == '\n') MarkNewLine(); if (!*cursor_) return Error("end of file in comment"); cursor_++; } cursor_ += 2; break; } // fall thru default: if (IsIdentifierStart(c)) { // Collect all chars of an identifier: const char *start = cursor_ - 1; while (isalnum(static_cast(*cursor_)) || *cursor_ == '_') cursor_++; attribute_.append(start, cursor_); token_ = kTokenIdentifier; return NoError(); } else if (isdigit(static_cast(c)) || c == '-') { const char *start = cursor_ - 1; if (c == '-' && *cursor_ == '0' && (cursor_[1] == 'x' || cursor_[1] == 'X')) { ++start; ++cursor_; attribute_.append(&c, &c + 1); c = '0'; } if (c == '0' && (*cursor_ == 'x' || *cursor_ == 'X')) { cursor_++; while (isxdigit(static_cast(*cursor_))) cursor_++; attribute_.append(start + 2, cursor_); attribute_ = NumToString(static_cast( StringToUInt(attribute_.c_str(), nullptr, 16))); token_ = kTokenIntegerConstant; return NoError(); } while (isdigit(static_cast(*cursor_))) cursor_++; if (*cursor_ == '.' || *cursor_ == 'e' || *cursor_ == 'E') { if (*cursor_ == '.') { cursor_++; while (isdigit(static_cast(*cursor_))) cursor_++; } // See if this float has a scientific notation suffix. Both JSON // and C++ (through strtod() we use) have the same format: if (*cursor_ == 'e' || *cursor_ == 'E') { cursor_++; if (*cursor_ == '+' || *cursor_ == '-') cursor_++; while (isdigit(static_cast(*cursor_))) cursor_++; } token_ = kTokenFloatConstant; } else { token_ = kTokenIntegerConstant; } attribute_.append(start, cursor_); return NoError(); } std::string ch; ch = c; if (c < ' ' || c > '~') ch = "code: " + NumToString(c); return Error("illegal character: " + ch); } } } // Check if a given token is next. bool Parser::Is(int t) const { return t == token_; } bool Parser::IsIdent(const char *id) const { return token_ == kTokenIdentifier && attribute_ == id; } // Expect a given token to be next, consume it, or error if not present. CheckedError Parser::Expect(int t) { if (t != token_) { return Error("expecting: " + TokenToString(t) + " instead got: " + TokenToStringId(token_)); } NEXT(); return NoError(); } CheckedError Parser::ParseNamespacing(std::string *id, std::string *last) { while (Is('.')) { NEXT(); *id += "."; *id += attribute_; if (last) *last = attribute_; EXPECT(kTokenIdentifier); } return NoError(); } EnumDef *Parser::LookupEnum(const std::string &id) { // Search thru parent namespaces. for (int components = static_cast(current_namespace_->components.size()); components >= 0; components--) { auto ed = enums_.Lookup( current_namespace_->GetFullyQualifiedName(id, components)); if (ed) return ed; } return nullptr; } StructDef *Parser::LookupStruct(const std::string &id) const { auto sd = structs_.Lookup(id); if (sd) sd->refcount++; return sd; } CheckedError Parser::ParseTypeIdent(Type &type) { std::string id = attribute_; EXPECT(kTokenIdentifier); ECHECK(ParseNamespacing(&id, nullptr)); auto enum_def = LookupEnum(id); if (enum_def) { type = enum_def->underlying_type; if (enum_def->is_union) type.base_type = BASE_TYPE_UNION; } else { type.base_type = BASE_TYPE_STRUCT; type.struct_def = LookupCreateStruct(id); } return NoError(); } // Parse any IDL type. CheckedError Parser::ParseType(Type &type) { if (token_ == kTokenIdentifier) { if (IsIdent("bool")) { type.base_type = BASE_TYPE_BOOL; NEXT(); } else if (IsIdent("byte") || IsIdent("int8")) { type.base_type = BASE_TYPE_CHAR; NEXT(); } else if (IsIdent("ubyte") || IsIdent("uint8")) { type.base_type = BASE_TYPE_UCHAR; NEXT(); } else if (IsIdent("short") || IsIdent("int16")) { type.base_type = BASE_TYPE_SHORT; NEXT(); } else if (IsIdent("ushort") || IsIdent("uint16")) { type.base_type = BASE_TYPE_USHORT; NEXT(); } else if (IsIdent("int") || IsIdent("int32")) { type.base_type = BASE_TYPE_INT; NEXT(); } else if (IsIdent("uint") || IsIdent("uint32")) { type.base_type = BASE_TYPE_UINT; NEXT(); } else if (IsIdent("long") || IsIdent("int64")) { type.base_type = BASE_TYPE_LONG; NEXT(); } else if (IsIdent("ulong") || IsIdent("uint64")) { type.base_type = BASE_TYPE_ULONG; NEXT(); } else if (IsIdent("float") || IsIdent("float32")) { type.base_type = BASE_TYPE_FLOAT; NEXT(); } else if (IsIdent("double") || IsIdent("float64")) { type.base_type = BASE_TYPE_DOUBLE; NEXT(); } else if (IsIdent("string")) { type.base_type = BASE_TYPE_STRING; NEXT(); } else { ECHECK(ParseTypeIdent(type)); } } else if (token_ == '[') { NEXT(); Type subtype; ECHECK(Recurse([&]() { return ParseType(subtype); })); if (subtype.base_type == BASE_TYPE_VECTOR) { // We could support this, but it will complicate things, and it's // easier to work around with a struct around the inner vector. return Error("nested vector types not supported (wrap in table first)."); } type = Type(BASE_TYPE_VECTOR, subtype.struct_def, subtype.enum_def); type.element = subtype.base_type; EXPECT(']'); } else { return Error("illegal type syntax"); } return NoError(); } CheckedError Parser::AddField(StructDef &struct_def, const std::string &name, const Type &type, FieldDef **dest) { auto &field = *new FieldDef(); field.value.offset = FieldIndexToOffset(static_cast(struct_def.fields.vec.size())); field.name = name; field.file = struct_def.file; field.value.type = type; if (struct_def.fixed) { // statically compute the field offset auto size = InlineSize(type); auto alignment = InlineAlignment(type); // structs_ need to have a predictable format, so we need to align to // the largest scalar struct_def.minalign = std::max(struct_def.minalign, alignment); struct_def.PadLastField(alignment); field.value.offset = static_cast(struct_def.bytesize); struct_def.bytesize += size; } if (struct_def.fields.Add(name, &field)) return Error("field already exists: " + name); *dest = &field; return NoError(); } CheckedError Parser::ParseField(StructDef &struct_def) { std::string name = attribute_; if (LookupStruct(name)) return Error("field name can not be the same as table/struct name"); std::vector dc = doc_comment_; EXPECT(kTokenIdentifier); EXPECT(':'); Type type; ECHECK(ParseType(type)); if (struct_def.fixed && !IsScalar(type.base_type) && !IsStruct(type)) return Error("structs_ may contain only scalar or struct fields"); FieldDef *typefield = nullptr; if (type.base_type == BASE_TYPE_UNION) { // For union fields, add a second auto-generated field to hold the type, // with a special suffix. ECHECK(AddField(struct_def, name + UnionTypeFieldSuffix(), type.enum_def->underlying_type, &typefield)); } else if (type.base_type == BASE_TYPE_VECTOR && type.element == BASE_TYPE_UNION) { // Only cpp, js and ts supports the union vector feature so far. if (!SupportsVectorOfUnions()) { return Error( "Vectors of unions are not yet supported in all " "the specified programming languages."); } // For vector of union fields, add a second auto-generated vector field to // hold the types, with a special suffix. Type union_vector(BASE_TYPE_VECTOR, nullptr, type.enum_def); union_vector.element = BASE_TYPE_UTYPE; ECHECK(AddField(struct_def, name + UnionTypeFieldSuffix(), union_vector, &typefield)); } FieldDef *field; ECHECK(AddField(struct_def, name, type, &field)); if (token_ == '=') { NEXT(); if (!IsScalar(type.base_type) || (struct_def.fixed && field->value.constant != "0")) return Error( "default values currently only supported for scalars in tables"); ECHECK(ParseSingleValue(&field->name, field->value)); } if (type.enum_def && !type.enum_def->is_union && !type.enum_def->attributes.Lookup("bit_flags") && !type.enum_def->ReverseLookup(StringToInt( field->value.constant.c_str()))) { return Error("default value of " + field->value.constant + " for field " + name + " is not part of enum " + type.enum_def->name); } if (IsFloat(type.base_type)) { if (!strpbrk(field->value.constant.c_str(), ".eE")) field->value.constant += ".0"; } if (type.enum_def && IsScalar(type.base_type) && !struct_def.fixed && !type.enum_def->attributes.Lookup("bit_flags") && !type.enum_def->ReverseLookup(StringToInt( field->value.constant.c_str()))) Warning("enum " + type.enum_def->name + " does not have a declaration for this field\'s default of " + field->value.constant); field->doc_comment = dc; ECHECK(ParseMetaData(&field->attributes)); field->deprecated = field->attributes.Lookup("deprecated") != nullptr; auto hash_name = field->attributes.Lookup("hash"); if (hash_name) { switch ((type.base_type == BASE_TYPE_VECTOR) ? type.element : type.base_type) { case BASE_TYPE_SHORT: case BASE_TYPE_USHORT: { if (FindHashFunction16(hash_name->constant.c_str()) == nullptr) return Error("Unknown hashing algorithm for 16 bit types: " + hash_name->constant); break; } case BASE_TYPE_INT: case BASE_TYPE_UINT: { if (FindHashFunction32(hash_name->constant.c_str()) == nullptr) return Error("Unknown hashing algorithm for 32 bit types: " + hash_name->constant); break; } case BASE_TYPE_LONG: case BASE_TYPE_ULONG: { if (FindHashFunction64(hash_name->constant.c_str()) == nullptr) return Error("Unknown hashing algorithm for 64 bit types: " + hash_name->constant); break; } default: return Error( "only short, ushort, int, uint, long and ulong data types support hashing."); } } auto cpp_type = field->attributes.Lookup("cpp_type"); if (cpp_type) { if (!hash_name) return Error("cpp_type can only be used with a hashed field"); /// forcing cpp_ptr_type to 'naked' if unset auto cpp_ptr_type = field->attributes.Lookup("cpp_ptr_type"); if (!cpp_ptr_type) { auto val = new Value(); val->type = cpp_type->type; val->constant = "naked"; field->attributes.Add("cpp_ptr_type", val); } } if (field->deprecated && struct_def.fixed) return Error("can't deprecate fields in a struct"); field->required = field->attributes.Lookup("required") != nullptr; if (field->required && (struct_def.fixed || IsScalar(type.base_type))) return Error("only non-scalar fields in tables may be 'required'"); field->key = field->attributes.Lookup("key") != nullptr; if (field->key) { if (struct_def.has_key) return Error("only one field may be set as 'key'"); struct_def.has_key = true; if (!IsScalar(type.base_type)) { field->required = true; if (type.base_type != BASE_TYPE_STRING) return Error("'key' field must be string or scalar type"); } } auto field_native_custom_alloc = field->attributes.Lookup("native_custom_alloc"); if (field_native_custom_alloc) return Error( "native_custom_alloc can only be used with a table or struct " "definition"); field->native_inline = field->attributes.Lookup("native_inline") != nullptr; if (field->native_inline && !IsStruct(field->value.type)) return Error("native_inline can only be defined on structs'"); auto nested = field->attributes.Lookup("nested_flatbuffer"); if (nested) { if (nested->type.base_type != BASE_TYPE_STRING) return Error( "nested_flatbuffer attribute must be a string (the root type)"); if (type.base_type != BASE_TYPE_VECTOR || type.element != BASE_TYPE_UCHAR) return Error( "nested_flatbuffer attribute may only apply to a vector of ubyte"); // This will cause an error if the root type of the nested flatbuffer // wasn't defined elsewhere. LookupCreateStruct(nested->constant); // Keep a pointer to StructDef in FieldDef to simplify re-use later auto nested_qualified_name = current_namespace_->GetFullyQualifiedName(nested->constant); field->nested_flatbuffer = LookupStruct(nested_qualified_name); } if (field->attributes.Lookup("flexbuffer")) { field->flexbuffer = true; uses_flexbuffers_ = true; if (type.base_type != BASE_TYPE_VECTOR || type.element != BASE_TYPE_UCHAR) return Error("flexbuffer attribute may only apply to a vector of ubyte"); } if (typefield) { if (!IsScalar(typefield->value.type.base_type)) { // this is a union vector field typefield->required = field->required; } // If this field is a union, and it has a manually assigned id, // the automatically added type field should have an id as well (of N - 1). auto attr = field->attributes.Lookup("id"); if (attr) { auto id = atoi(attr->constant.c_str()); auto val = new Value(); val->type = attr->type; val->constant = NumToString(id - 1); typefield->attributes.Add("id", val); } } EXPECT(';'); return NoError(); } CheckedError Parser::ParseString(Value &val) { auto s = attribute_; EXPECT(kTokenStringConstant); val.constant = NumToString(builder_.CreateString(s).o); return NoError(); } CheckedError Parser::ParseComma() { if (!opts.protobuf_ascii_alike) EXPECT(','); return NoError(); } CheckedError Parser::ParseAnyValue(Value &val, FieldDef *field, size_t parent_fieldn, const StructDef *parent_struct_def) { switch (val.type.base_type) { case BASE_TYPE_UNION: { FLATBUFFERS_ASSERT(field); std::string constant; // Find corresponding type field we may have already parsed. for (auto elem = field_stack_.rbegin(); elem != field_stack_.rbegin() + parent_fieldn; ++elem) { auto &type = elem->second->value.type; if (type.base_type == BASE_TYPE_UTYPE && type.enum_def == val.type.enum_def) { constant = elem->first.constant; break; } } if (constant.empty()) { // We haven't seen the type field yet. Sadly a lot of JSON writers // output these in alphabetical order, meaning it comes after this // value. So we scan past the value to find it, then come back here. auto type_name = field->name + UnionTypeFieldSuffix(); FLATBUFFERS_ASSERT(parent_struct_def); auto type_field = parent_struct_def->fields.Lookup(type_name); FLATBUFFERS_ASSERT(type_field); // Guaranteed by ParseField(). // Remember where we are in the source file, so we can come back here. auto backup = *static_cast(this); ECHECK(SkipAnyJsonValue()); // The table. ECHECK(ParseComma()); auto next_name = attribute_; if (Is(kTokenStringConstant)) { NEXT(); } else { EXPECT(kTokenIdentifier); } if (next_name != type_name) return Error("missing type field after this union value: " + type_name); EXPECT(':'); Value type_val = type_field->value; ECHECK(ParseAnyValue(type_val, type_field, 0, nullptr)); constant = type_val.constant; // Got the information we needed, now rewind: *static_cast(this) = backup; } uint8_t enum_idx; ECHECK(atot(constant.c_str(), *this, &enum_idx)); auto enum_val = val.type.enum_def->ReverseLookup(enum_idx); if (!enum_val) return Error("illegal type id for: " + field->name); if (enum_val->union_type.base_type == BASE_TYPE_STRUCT) { ECHECK(ParseTable(*enum_val->union_type.struct_def, &val.constant, nullptr)); if (enum_val->union_type.struct_def->fixed) { // All BASE_TYPE_UNION values are offsets, so turn this into one. SerializeStruct(*enum_val->union_type.struct_def, val); builder_.ClearOffsets(); val.constant = NumToString(builder_.GetSize()); } } else if (enum_val->union_type.base_type == BASE_TYPE_STRING) { ECHECK(ParseString(val)); } else { FLATBUFFERS_ASSERT(false); } break; } case BASE_TYPE_STRUCT: ECHECK(ParseTable(*val.type.struct_def, &val.constant, nullptr)); break; case BASE_TYPE_STRING: { ECHECK(ParseString(val)); break; } case BASE_TYPE_VECTOR: { uoffset_t off; ECHECK(ParseVector(val.type.VectorType(), &off)); val.constant = NumToString(off); break; } case BASE_TYPE_INT: case BASE_TYPE_UINT: case BASE_TYPE_LONG: case BASE_TYPE_ULONG: { if (field && field->attributes.Lookup("hash") && (token_ == kTokenIdentifier || token_ == kTokenStringConstant)) { ECHECK(ParseHash(val, field)); } else { ECHECK(ParseSingleValue(field ? &field->name : nullptr, val)); } break; } default: ECHECK(ParseSingleValue(field ? &field->name : nullptr, val)); break; } return NoError(); } void Parser::SerializeStruct(const StructDef &struct_def, const Value &val) { FLATBUFFERS_ASSERT(val.constant.length() == struct_def.bytesize); builder_.Align(struct_def.minalign); builder_.PushBytes(reinterpret_cast(val.constant.c_str()), struct_def.bytesize); builder_.AddStructOffset(val.offset, builder_.GetSize()); } CheckedError Parser::ParseTableDelimiters(size_t &fieldn, const StructDef *struct_def, ParseTableDelimitersBody body, void *state) { // We allow tables both as JSON object{ .. } with field names // or vector[..] with all fields in order char terminator = '}'; bool is_nested_vector = struct_def && Is('['); if (is_nested_vector) { NEXT(); terminator = ']'; } else { EXPECT('{'); } for (;;) { if ((!opts.strict_json || !fieldn) && Is(terminator)) break; std::string name; if (is_nested_vector) { if (fieldn >= struct_def->fields.vec.size()) { return Error("too many unnamed fields in nested array"); } name = struct_def->fields.vec[fieldn]->name; } else { name = attribute_; if (Is(kTokenStringConstant)) { NEXT(); } else { EXPECT(opts.strict_json ? kTokenStringConstant : kTokenIdentifier); } if (!opts.protobuf_ascii_alike || !(Is('{') || Is('['))) EXPECT(':'); } ECHECK(body(name, fieldn, struct_def, state)); if (Is(terminator)) break; ECHECK(ParseComma()); } NEXT(); if (is_nested_vector && fieldn != struct_def->fields.vec.size()) { return Error("wrong number of unnamed fields in table vector"); } return NoError(); } CheckedError Parser::ParseTable(const StructDef &struct_def, std::string *value, uoffset_t *ovalue) { size_t fieldn_outer = 0; auto err = ParseTableDelimiters( fieldn_outer, &struct_def, [](const std::string &name, size_t &fieldn, const StructDef *struct_def_inner, void *state) -> CheckedError { auto *parser = static_cast(state); if (name == "$schema") { ECHECK(parser->Expect(kTokenStringConstant)); return NoError(); } auto field = struct_def_inner->fields.Lookup(name); if (!field) { if (!parser->opts.skip_unexpected_fields_in_json) { return parser->Error("unknown field: " + name); } else { ECHECK(parser->SkipAnyJsonValue()); } } else { if (parser->IsIdent("null")) { ECHECK(parser->Next()); // Ignore this field. } else { Value val = field->value; if (field->flexbuffer) { flexbuffers::Builder builder(1024, flexbuffers::BUILDER_FLAG_SHARE_ALL); ECHECK(parser->ParseFlexBufferValue(&builder)); builder.Finish(); // Force alignment for nested flexbuffer parser->builder_.ForceVectorAlignment(builder.GetSize(), sizeof(uint8_t), sizeof(largest_scalar_t)); auto off = parser->builder_.CreateVector(builder.GetBuffer()); val.constant = NumToString(off.o); } else if (field->nested_flatbuffer) { ECHECK(parser->ParseNestedFlatbuffer(val, field, fieldn, struct_def_inner)); } else { ECHECK(parser->Recurse([&]() { return parser->ParseAnyValue(val, field, fieldn, struct_def_inner); })); } // Hardcoded insertion-sort with error-check. // If fields are specified in order, then this loop exits // immediately. auto elem = parser->field_stack_.rbegin(); for (; elem != parser->field_stack_.rbegin() + fieldn; ++elem) { auto existing_field = elem->second; if (existing_field == field) return parser->Error("field set more than once: " + field->name); if (existing_field->value.offset < field->value.offset) break; } // Note: elem points to before the insertion point, thus .base() // points to the correct spot. parser->field_stack_.insert(elem.base(), std::make_pair(val, field)); fieldn++; } } return NoError(); }, this); ECHECK(err); // Check if all required fields are parsed. for (auto field_it = struct_def.fields.vec.begin(); field_it != struct_def.fields.vec.end(); ++field_it) { auto required_field = *field_it; if (!required_field->required) { continue; } bool found = false; for (auto pf_it = field_stack_.end() - fieldn_outer; pf_it != field_stack_.end(); ++pf_it) { auto parsed_field = pf_it->second; if (parsed_field == required_field) { found = true; break; } } if (!found) { return Error("required field is missing: " + required_field->name + " in " + struct_def.name); } } if (struct_def.fixed && fieldn_outer != struct_def.fields.vec.size()) return Error("struct: wrong number of initializers: " + struct_def.name); auto start = struct_def.fixed ? builder_.StartStruct(struct_def.minalign) : builder_.StartTable(); for (size_t size = struct_def.sortbysize ? sizeof(largest_scalar_t) : 1; size; size /= 2) { // Go through elements in reverse, since we're building the data backwards. for (auto it = field_stack_.rbegin(); it != field_stack_.rbegin() + fieldn_outer; ++it) { auto &field_value = it->first; auto field = it->second; if (!struct_def.sortbysize || size == SizeOf(field_value.type.base_type)) { switch (field_value.type.base_type) { // clang-format off #define FLATBUFFERS_TD(ENUM, IDLTYPE, \ CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, RTYPE) \ case BASE_TYPE_ ## ENUM: \ builder_.Pad(field->padding); \ if (struct_def.fixed) { \ CTYPE val; \ ECHECK(atot(field_value.constant.c_str(), *this, &val)); \ builder_.PushElement(val); \ } else { \ CTYPE val, valdef; \ ECHECK(atot(field_value.constant.c_str(), *this, &val)); \ ECHECK(atot(field->value.constant.c_str(), *this, &valdef)); \ builder_.AddElement(field_value.offset, val, valdef); \ } \ break; FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD); #undef FLATBUFFERS_TD #define FLATBUFFERS_TD(ENUM, IDLTYPE, \ CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, RTYPE) \ case BASE_TYPE_ ## ENUM: \ builder_.Pad(field->padding); \ if (IsStruct(field->value.type)) { \ SerializeStruct(*field->value.type.struct_def, field_value); \ } else { \ CTYPE val; \ ECHECK(atot(field_value.constant.c_str(), *this, &val)); \ builder_.AddOffset(field_value.offset, val); \ } \ break; FLATBUFFERS_GEN_TYPES_POINTER(FLATBUFFERS_TD); #undef FLATBUFFERS_TD // clang-format on } } } } for (size_t i = 0; i < fieldn_outer; i++) field_stack_.pop_back(); if (struct_def.fixed) { builder_.ClearOffsets(); builder_.EndStruct(); FLATBUFFERS_ASSERT(value); // Temporarily store this struct in the value string, since it is to // be serialized in-place elsewhere. value->assign( reinterpret_cast(builder_.GetCurrentBufferPointer()), struct_def.bytesize); builder_.PopBytes(struct_def.bytesize); FLATBUFFERS_ASSERT(!ovalue); } else { auto val = builder_.EndTable(start); if (ovalue) *ovalue = val; if (value) *value = NumToString(val); } return NoError(); } CheckedError Parser::ParseVectorDelimiters(size_t &count, ParseVectorDelimitersBody body, void *state) { EXPECT('['); for (;;) { if ((!opts.strict_json || !count) && Is(']')) break; ECHECK(body(count, state)); count++; if (Is(']')) break; ECHECK(ParseComma()); } NEXT(); return NoError(); } CheckedError Parser::ParseVector(const Type &type, uoffset_t *ovalue) { size_t count = 0; std::pair parser_and_type_state(this, type); auto err = ParseVectorDelimiters( count, [](size_t &, void *state) -> CheckedError { auto *parser_and_type = static_cast *>(state); auto *parser = parser_and_type->first; Value val; val.type = parser_and_type->second; ECHECK(parser->Recurse([&]() { return parser->ParseAnyValue(val, nullptr, 0, nullptr); })); parser->field_stack_.push_back(std::make_pair(val, nullptr)); return NoError(); }, &parser_and_type_state); ECHECK(err); builder_.StartVector(count * InlineSize(type) / InlineAlignment(type), InlineAlignment(type)); for (size_t i = 0; i < count; i++) { // start at the back, since we're building the data backwards. auto &val = field_stack_.back().first; switch (val.type.base_type) { // clang-format off #define FLATBUFFERS_TD(ENUM, IDLTYPE, \ CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, RTYPE) \ case BASE_TYPE_ ## ENUM: \ if (IsStruct(val.type)) SerializeStruct(*val.type.struct_def, val); \ else { \ CTYPE elem; \ ECHECK(atot(val.constant.c_str(), *this, &elem)); \ builder_.PushElement(elem); \ } \ break; FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD) #undef FLATBUFFERS_TD // clang-format on } field_stack_.pop_back(); } builder_.ClearOffsets(); *ovalue = builder_.EndVector(count); return NoError(); } CheckedError Parser::ParseNestedFlatbuffer(Value &val, FieldDef *field, size_t fieldn, const StructDef *parent_struct_def) { if (token_ == '[') { // backwards compat for 'legacy' ubyte buffers ECHECK(ParseAnyValue(val, field, fieldn, parent_struct_def)); } else { auto cursor_at_value_begin = cursor_; ECHECK(SkipAnyJsonValue()); std::string substring(cursor_at_value_begin - 1, cursor_ - 1); // Create and initialize new parser Parser nested_parser; FLATBUFFERS_ASSERT(field->nested_flatbuffer); nested_parser.root_struct_def_ = field->nested_flatbuffer; nested_parser.enums_ = enums_; nested_parser.opts = opts; nested_parser.uses_flexbuffers_ = uses_flexbuffers_; // Parse JSON substring into new flatbuffer builder using nested_parser if (!nested_parser.Parse(substring.c_str(), nullptr, nullptr)) { ECHECK(Error(nested_parser.error_)); } // Force alignment for nested flatbuffer builder_.ForceVectorAlignment(nested_parser.builder_.GetSize(), sizeof(uint8_t), nested_parser.builder_.GetBufferMinAlignment()); auto off = builder_.CreateVector(nested_parser.builder_.GetBufferPointer(), nested_parser.builder_.GetSize()); val.constant = NumToString(off.o); // Clean nested_parser before destruction to avoid deleting the elements in // the SymbolTables nested_parser.enums_.dict.clear(); nested_parser.enums_.vec.clear(); } return NoError(); } CheckedError Parser::ParseMetaData(SymbolTable *attributes) { if (Is('(')) { NEXT(); for (;;) { auto name = attribute_; if (false == (Is(kTokenIdentifier) || Is(kTokenStringConstant))) return Error("attribute name must be either identifier or string: " + name); if (known_attributes_.find(name) == known_attributes_.end()) return Error("user define attributes must be declared before use: " + name); NEXT(); auto e = new Value(); attributes->Add(name, e); if (Is(':')) { NEXT(); ECHECK(ParseSingleValue(&name, *e)); } if (Is(')')) { NEXT(); break; } EXPECT(','); } } return NoError(); } CheckedError Parser::TryTypedValue(const std::string *name, int dtoken, bool check, Value &e, BaseType req, bool *destmatch) { bool match = dtoken == token_; if (match) { *destmatch = true; e.constant = attribute_; if (!check) { if (e.type.base_type == BASE_TYPE_NONE) { e.type.base_type = req; } else { return Error(std::string("type mismatch: expecting: ") + kTypeNames[e.type.base_type] + ", found: " + kTypeNames[req] + ", name: " + (name ? *name : "") + ", value: " + e.constant); } } NEXT(); } return NoError(); } CheckedError Parser::ParseEnumFromString(Type &type, int64_t *result) { *result = 0; // Parse one or more enum identifiers, separated by spaces. const char *next = attribute_.c_str(); do { const char *divider = strchr(next, ' '); std::string word; if (divider) { word = std::string(next, divider); next = divider + strspn(divider, " "); } else { word = next; next += word.length(); } if (type.enum_def) { // The field has an enum type auto enum_val = type.enum_def->vals.Lookup(word); if (!enum_val) return Error("unknown enum value: " + word + ", for enum: " + type.enum_def->name); *result |= enum_val->value; } else { // No enum type, probably integral field. if (!IsInteger(type.base_type)) return Error("not a valid value for this field: " + word); // TODO: could check if its a valid number constant here. const char *dot = strrchr(word.c_str(), '.'); if (!dot) return Error("enum values need to be qualified by an enum type"); std::string enum_def_str(word.c_str(), dot); std::string enum_val_str(dot + 1, word.c_str() + word.length()); auto enum_def = LookupEnum(enum_def_str); if (!enum_def) return Error("unknown enum: " + enum_def_str); auto enum_val = enum_def->vals.Lookup(enum_val_str); if (!enum_val) return Error("unknown enum value: " + enum_val_str); *result |= enum_val->value; } } while (*next); return NoError(); } CheckedError Parser::ParseHash(Value &e, FieldDef *field) { FLATBUFFERS_ASSERT(field); Value *hash_name = field->attributes.Lookup("hash"); switch (e.type.base_type) { case BASE_TYPE_SHORT: { auto hash = FindHashFunction16(hash_name->constant.c_str()); int16_t hashed_value = static_cast(hash(attribute_.c_str())); e.constant = NumToString(hashed_value); break; } case BASE_TYPE_USHORT: { auto hash = FindHashFunction16(hash_name->constant.c_str()); uint16_t hashed_value = hash(attribute_.c_str()); e.constant = NumToString(hashed_value); break; } case BASE_TYPE_INT: { auto hash = FindHashFunction32(hash_name->constant.c_str()); int32_t hashed_value = static_cast(hash(attribute_.c_str())); e.constant = NumToString(hashed_value); break; } case BASE_TYPE_UINT: { auto hash = FindHashFunction32(hash_name->constant.c_str()); uint32_t hashed_value = hash(attribute_.c_str()); e.constant = NumToString(hashed_value); break; } case BASE_TYPE_LONG: { auto hash = FindHashFunction64(hash_name->constant.c_str()); int64_t hashed_value = static_cast(hash(attribute_.c_str())); e.constant = NumToString(hashed_value); break; } case BASE_TYPE_ULONG: { auto hash = FindHashFunction64(hash_name->constant.c_str()); uint64_t hashed_value = hash(attribute_.c_str()); e.constant = NumToString(hashed_value); break; } default: FLATBUFFERS_ASSERT(0); } NEXT(); return NoError(); } CheckedError Parser::TokenError() { return Error("cannot parse value starting with: " + TokenToStringId(token_)); } CheckedError Parser::ParseSingleValue(const std::string *name, Value &e) { // First see if this could be a conversion function: if (token_ == kTokenIdentifier && *cursor_ == '(') { auto functionname = attribute_; NEXT(); EXPECT('('); ECHECK(ParseSingleValue(name, e)); EXPECT(')'); // clang-format off #define FLATBUFFERS_FN_DOUBLE(name, op) \ if (functionname == name) { \ auto x = strtod(e.constant.c_str(), nullptr); \ e.constant = NumToString(op); \ } FLATBUFFERS_FN_DOUBLE("deg", x / kPi * 180); FLATBUFFERS_FN_DOUBLE("rad", x * kPi / 180); FLATBUFFERS_FN_DOUBLE("sin", sin(x)); FLATBUFFERS_FN_DOUBLE("cos", cos(x)); FLATBUFFERS_FN_DOUBLE("tan", tan(x)); FLATBUFFERS_FN_DOUBLE("asin", asin(x)); FLATBUFFERS_FN_DOUBLE("acos", acos(x)); FLATBUFFERS_FN_DOUBLE("atan", atan(x)); // TODO(wvo): add more useful conversion functions here. #undef FLATBUFFERS_FN_DOUBLE // clang-format on // Then check if this could be a string/identifier enum value: } else if (e.type.base_type != BASE_TYPE_STRING && e.type.base_type != BASE_TYPE_BOOL && e.type.base_type != BASE_TYPE_NONE && (token_ == kTokenIdentifier || token_ == kTokenStringConstant)) { if (IsIdentifierStart(attribute_[0])) { // Enum value. int64_t val; ECHECK(ParseEnumFromString(e.type, &val)); e.constant = NumToString(val); NEXT(); } else { // Numeric constant in string. if (IsInteger(e.type.base_type)) { char *end; e.constant = NumToString(StringToInt(attribute_.c_str(), &end)); if (*end) return Error("invalid integer: " + attribute_); } else if (IsFloat(e.type.base_type)) { char *end; e.constant = NumToString(strtod(attribute_.c_str(), &end)); if (*end) return Error("invalid float: " + attribute_); } else { FLATBUFFERS_ASSERT(0); // Shouldn't happen, we covered all types. e.constant = "0"; } NEXT(); } } else { bool match = false; ECHECK(TryTypedValue(name, kTokenIntegerConstant, IsScalar(e.type.base_type), e, BASE_TYPE_INT, &match)); ECHECK(TryTypedValue(name, kTokenFloatConstant, IsFloat(e.type.base_type), e, BASE_TYPE_FLOAT, &match)); ECHECK(TryTypedValue(name, kTokenStringConstant, e.type.base_type == BASE_TYPE_STRING, e, BASE_TYPE_STRING, &match)); auto istrue = IsIdent("true"); if (istrue || IsIdent("false")) { attribute_ = NumToString(istrue); ECHECK(TryTypedValue(name, kTokenIdentifier, IsBool(e.type.base_type), e, BASE_TYPE_BOOL, &match)); } if (!match) return TokenError(); } return NoError(); } StructDef *Parser::LookupCreateStruct(const std::string &name, bool create_if_new, bool definition) { std::string qualified_name = current_namespace_->GetFullyQualifiedName(name); // See if it exists pre-declared by an unqualified use. auto struct_def = LookupStruct(name); if (struct_def && struct_def->predecl) { if (definition) { // Make sure it has the current namespace, and is registered under its // qualified name. struct_def->defined_namespace = current_namespace_; structs_.Move(name, qualified_name); } return struct_def; } // See if it exists pre-declared by an qualified use. struct_def = LookupStruct(qualified_name); if (struct_def && struct_def->predecl) { if (definition) { // Make sure it has the current namespace. struct_def->defined_namespace = current_namespace_; } return struct_def; } if (!definition) { // Search thru parent namespaces. for (size_t components = current_namespace_->components.size(); components && !struct_def; components--) { struct_def = LookupStruct( current_namespace_->GetFullyQualifiedName(name, components - 1)); } } if (!struct_def && create_if_new) { struct_def = new StructDef(); if (definition) { structs_.Add(qualified_name, struct_def); struct_def->name = name; struct_def->defined_namespace = current_namespace_; } else { // Not a definition. // Rather than failing, we create a "pre declared" StructDef, due to // circular references, and check for errors at the end of parsing. // It is defined in the current namespace, as the best guess what the // final namespace will be. structs_.Add(name, struct_def); struct_def->name = name; struct_def->defined_namespace = current_namespace_; struct_def->original_location.reset( new std::string(file_being_parsed_ + ":" + NumToString(line_))); } } return struct_def; } CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) { std::vector enum_comment = doc_comment_; NEXT(); std::string enum_name = attribute_; EXPECT(kTokenIdentifier); EnumDef *enum_def; ECHECK(StartEnum(enum_name, is_union, &enum_def)); enum_def->doc_comment = enum_comment; if (!is_union && !opts.proto_mode) { // Give specialized error message, since this type spec used to // be optional in the first FlatBuffers release. if (!Is(':')) { return Error( "must specify the underlying integer type for this" " enum (e.g. \': short\', which was the default)."); } else { NEXT(); } // Specify the integer type underlying this enum. ECHECK(ParseType(enum_def->underlying_type)); if (!IsInteger(enum_def->underlying_type.base_type)) return Error("underlying enum type must be integral"); // Make this type refer back to the enum it was derived from. enum_def->underlying_type.enum_def = enum_def; } ECHECK(ParseMetaData(&enum_def->attributes)); EXPECT('{'); if (is_union) enum_def->vals.Add("NONE", new EnumVal("NONE", 0)); for (;;) { if (opts.proto_mode && attribute_ == "option") { ECHECK(ParseProtoOption()); } else { auto value_name = attribute_; auto full_name = value_name; std::vector value_comment = doc_comment_; EXPECT(kTokenIdentifier); if (is_union) { ECHECK(ParseNamespacing(&full_name, &value_name)); if (opts.union_value_namespacing) { // Since we can't namespace the actual enum identifiers, turn // namespace parts into part of the identifier. value_name = full_name; std::replace(value_name.begin(), value_name.end(), '.', '_'); } } auto prevsize = enum_def->vals.vec.size(); auto value = !enum_def->vals.vec.empty() ? enum_def->vals.vec.back()->value + 1 : 0; auto &ev = *new EnumVal(value_name, value); if (enum_def->vals.Add(value_name, &ev)) return Error("enum value already exists: " + value_name); ev.doc_comment = value_comment; if (is_union) { if (Is(':')) { NEXT(); ECHECK(ParseType(ev.union_type)); if (ev.union_type.base_type != BASE_TYPE_STRUCT && ev.union_type.base_type != BASE_TYPE_STRING) return Error("union value type may only be table/struct/string"); enum_def->uses_type_aliases = true; } else { ev.union_type = Type(BASE_TYPE_STRUCT, LookupCreateStruct(full_name)); } } if (Is('=')) { NEXT(); ev.value = StringToInt(attribute_.c_str()); EXPECT(kTokenIntegerConstant); if (!opts.proto_mode && prevsize && enum_def->vals.vec[prevsize - 1]->value >= ev.value) return Error("enum values must be specified in ascending order"); } if (is_union) { if (ev.value < 0 || ev.value >= 256) return Error("union enum value must fit in a ubyte"); } if (opts.proto_mode && Is('[')) { NEXT(); // ignore attributes on enums. while (token_ != ']') NEXT(); NEXT(); } } if (!Is(opts.proto_mode ? ';' : ',')) break; NEXT(); if (Is('}')) break; } EXPECT('}'); if (enum_def->attributes.Lookup("bit_flags")) { for (auto it = enum_def->vals.vec.begin(); it != enum_def->vals.vec.end(); ++it) { if (static_cast((*it)->value) >= SizeOf(enum_def->underlying_type.base_type) * 8) return Error("bit flag out of range of underlying integral type"); (*it)->value = 1LL << (*it)->value; } } if (dest) *dest = enum_def; types_.Add(current_namespace_->GetFullyQualifiedName(enum_def->name), new Type(BASE_TYPE_UNION, nullptr, enum_def)); return NoError(); } CheckedError Parser::StartStruct(const std::string &name, StructDef **dest) { auto &struct_def = *LookupCreateStruct(name, true, true); if (!struct_def.predecl) return Error("datatype already exists: " + name); struct_def.predecl = false; struct_def.name = name; struct_def.file = file_being_parsed_; // Move this struct to the back of the vector just in case it was predeclared, // to preserve declaration order. *std::remove(structs_.vec.begin(), structs_.vec.end(), &struct_def) = &struct_def; *dest = &struct_def; return NoError(); } CheckedError Parser::CheckClash(std::vector &fields, StructDef *struct_def, const char *suffix, BaseType basetype) { auto len = strlen(suffix); for (auto it = fields.begin(); it != fields.end(); ++it) { auto &fname = (*it)->name; if (fname.length() > len && fname.compare(fname.length() - len, len, suffix) == 0 && (*it)->value.type.base_type != BASE_TYPE_UTYPE) { auto field = struct_def->fields.Lookup(fname.substr(0, fname.length() - len)); if (field && field->value.type.base_type == basetype) return Error("Field " + fname + " would clash with generated functions for field " + field->name); } } return NoError(); } bool Parser::SupportsVectorOfUnions() const { return opts.lang_to_generate != 0 && (opts.lang_to_generate & ~(IDLOptions::kCpp | IDLOptions::kJs | IDLOptions::kTs | IDLOptions::kPhp | IDLOptions::kJava | IDLOptions::kCSharp)) == 0; } Namespace *Parser::UniqueNamespace(Namespace *ns) { for (auto it = namespaces_.begin(); it != namespaces_.end(); ++it) { if (ns->components == (*it)->components) { delete ns; return *it; } } namespaces_.push_back(ns); return ns; } static bool compareFieldDefs(const FieldDef *a, const FieldDef *b) { auto a_id = atoi(a->attributes.Lookup("id")->constant.c_str()); auto b_id = atoi(b->attributes.Lookup("id")->constant.c_str()); return a_id < b_id; } CheckedError Parser::ParseDecl() { std::vector dc = doc_comment_; bool fixed = IsIdent("struct"); if (!fixed && !IsIdent("table")) return Error("declaration expected"); NEXT(); std::string name = attribute_; EXPECT(kTokenIdentifier); StructDef *struct_def; ECHECK(StartStruct(name, &struct_def)); struct_def->doc_comment = dc; struct_def->fixed = fixed; ECHECK(ParseMetaData(&struct_def->attributes)); struct_def->sortbysize = struct_def->attributes.Lookup("original_order") == nullptr && !fixed; EXPECT('{'); while (token_ != '}') ECHECK(ParseField(*struct_def)); auto force_align = struct_def->attributes.Lookup("force_align"); if (fixed && force_align) { auto align = static_cast(atoi(force_align->constant.c_str())); if (force_align->type.base_type != BASE_TYPE_INT || align < struct_def->minalign || align > FLATBUFFERS_MAX_ALIGNMENT || align & (align - 1)) return Error( "force_align must be a power of two integer ranging from the" "struct\'s natural alignment to " + NumToString(FLATBUFFERS_MAX_ALIGNMENT)); struct_def->minalign = align; } struct_def->PadLastField(struct_def->minalign); // Check if this is a table that has manual id assignments auto &fields = struct_def->fields.vec; if (!struct_def->fixed && fields.size()) { size_t num_id_fields = 0; for (auto it = fields.begin(); it != fields.end(); ++it) { if ((*it)->attributes.Lookup("id")) num_id_fields++; } // If any fields have ids.. if (num_id_fields) { // Then all fields must have them. if (num_id_fields != fields.size()) return Error( "either all fields or no fields must have an 'id' attribute"); // Simply sort by id, then the fields are the same as if no ids had // been specified. std::sort(fields.begin(), fields.end(), compareFieldDefs); // Verify we have a contiguous set, and reassign vtable offsets. for (int i = 0; i < static_cast(fields.size()); i++) { if (i != atoi(fields[i]->attributes.Lookup("id")->constant.c_str())) return Error("field id\'s must be consecutive from 0, id " + NumToString(i) + " missing or set twice"); fields[i]->value.offset = FieldIndexToOffset(static_cast(i)); } } } ECHECK( CheckClash(fields, struct_def, UnionTypeFieldSuffix(), BASE_TYPE_UNION)); ECHECK(CheckClash(fields, struct_def, "Type", BASE_TYPE_UNION)); ECHECK(CheckClash(fields, struct_def, "_length", BASE_TYPE_VECTOR)); ECHECK(CheckClash(fields, struct_def, "Length", BASE_TYPE_VECTOR)); ECHECK(CheckClash(fields, struct_def, "_byte_vector", BASE_TYPE_STRING)); ECHECK(CheckClash(fields, struct_def, "ByteVector", BASE_TYPE_STRING)); EXPECT('}'); types_.Add(current_namespace_->GetFullyQualifiedName(struct_def->name), new Type(BASE_TYPE_STRUCT, struct_def, nullptr)); return NoError(); } CheckedError Parser::ParseService() { std::vector service_comment = doc_comment_; NEXT(); auto service_name = attribute_; EXPECT(kTokenIdentifier); auto &service_def = *new ServiceDef(); service_def.name = service_name; service_def.file = file_being_parsed_; service_def.doc_comment = service_comment; service_def.defined_namespace = current_namespace_; if (services_.Add(current_namespace_->GetFullyQualifiedName(service_name), &service_def)) return Error("service already exists: " + service_name); ECHECK(ParseMetaData(&service_def.attributes)); EXPECT('{'); do { std::vector doc_comment = doc_comment_; auto rpc_name = attribute_; EXPECT(kTokenIdentifier); EXPECT('('); Type reqtype, resptype; ECHECK(ParseTypeIdent(reqtype)); EXPECT(')'); EXPECT(':'); ECHECK(ParseTypeIdent(resptype)); if (reqtype.base_type != BASE_TYPE_STRUCT || reqtype.struct_def->fixed || resptype.base_type != BASE_TYPE_STRUCT || resptype.struct_def->fixed) return Error("rpc request and response types must be tables"); auto &rpc = *new RPCCall(); rpc.name = rpc_name; rpc.request = reqtype.struct_def; rpc.response = resptype.struct_def; rpc.doc_comment = doc_comment; if (service_def.calls.Add(rpc_name, &rpc)) return Error("rpc already exists: " + rpc_name); ECHECK(ParseMetaData(&rpc.attributes)); EXPECT(';'); } while (token_ != '}'); NEXT(); return NoError(); } bool Parser::SetRootType(const char *name) { root_struct_def_ = LookupStruct(name); if (!root_struct_def_) root_struct_def_ = LookupStruct(current_namespace_->GetFullyQualifiedName(name)); return root_struct_def_ != nullptr; } void Parser::MarkGenerated() { // This function marks all existing definitions as having already // been generated, which signals no code for included files should be // generated. for (auto it = enums_.vec.begin(); it != enums_.vec.end(); ++it) { (*it)->generated = true; } for (auto it = structs_.vec.begin(); it != structs_.vec.end(); ++it) { if (!(*it)->predecl) { (*it)->generated = true; } } for (auto it = services_.vec.begin(); it != services_.vec.end(); ++it) { (*it)->generated = true; } } CheckedError Parser::ParseNamespace() { NEXT(); auto ns = new Namespace(); namespaces_.push_back(ns); // Store it here to not leak upon error. if (token_ != ';') { for (;;) { ns->components.push_back(attribute_); EXPECT(kTokenIdentifier); if (Is('.')) NEXT() else break; } } namespaces_.pop_back(); current_namespace_ = UniqueNamespace(ns); EXPECT(';'); return NoError(); } static bool compareEnumVals(const EnumVal *a, const EnumVal *b) { return a->value < b->value; } // Best effort parsing of .proto declarations, with the aim to turn them // in the closest corresponding FlatBuffer equivalent. // We parse everything as identifiers instead of keywords, since we don't // want protobuf keywords to become invalid identifiers in FlatBuffers. CheckedError Parser::ParseProtoDecl() { bool isextend = IsIdent("extend"); if (IsIdent("package")) { // These are identical in syntax to FlatBuffer's namespace decl. ECHECK(ParseNamespace()); } else if (IsIdent("message") || isextend) { std::vector struct_comment = doc_comment_; NEXT(); StructDef *struct_def = nullptr; Namespace *parent_namespace = nullptr; if (isextend) { if (Is('.')) NEXT(); // qualified names may start with a . ? auto id = attribute_; EXPECT(kTokenIdentifier); ECHECK(ParseNamespacing(&id, nullptr)); struct_def = LookupCreateStruct(id, false); if (!struct_def) return Error("cannot extend unknown message type: " + id); } else { std::string name = attribute_; EXPECT(kTokenIdentifier); ECHECK(StartStruct(name, &struct_def)); // Since message definitions can be nested, we create a new namespace. auto ns = new Namespace(); // Copy of current namespace. *ns = *current_namespace_; // But with current message name. ns->components.push_back(name); ns->from_table++; parent_namespace = current_namespace_; current_namespace_ = UniqueNamespace(ns); } struct_def->doc_comment = struct_comment; ECHECK(ParseProtoFields(struct_def, isextend, false)); if (!isextend) { current_namespace_ = parent_namespace; } if (Is(';')) NEXT(); } else if (IsIdent("enum")) { // These are almost the same, just with different terminator: EnumDef *enum_def; ECHECK(ParseEnum(false, &enum_def)); if (Is(';')) NEXT(); // Protobuf allows them to be specified in any order, so sort afterwards. auto &v = enum_def->vals.vec; std::sort(v.begin(), v.end(), compareEnumVals); // Temp: remove any duplicates, as .fbs files can't handle them. for (auto it = v.begin(); it != v.end();) { if (it != v.begin() && it[0]->value == it[-1]->value) it = v.erase(it); else ++it; } } else if (IsIdent("syntax")) { // Skip these. NEXT(); EXPECT('='); EXPECT(kTokenStringConstant); EXPECT(';'); } else if (IsIdent("option")) { // Skip these. ECHECK(ParseProtoOption()); EXPECT(';'); } else if (IsIdent("service")) { // Skip these. NEXT(); EXPECT(kTokenIdentifier); ECHECK(ParseProtoCurliesOrIdent()); } else { return Error("don\'t know how to parse .proto declaration starting with " + TokenToStringId(token_)); } return NoError(); } CheckedError Parser::StartEnum(const std::string &enum_name, bool is_union, EnumDef **dest) { auto &enum_def = *new EnumDef(); enum_def.name = enum_name; enum_def.file = file_being_parsed_; enum_def.doc_comment = doc_comment_; enum_def.is_union = is_union; enum_def.defined_namespace = current_namespace_; if (enums_.Add(current_namespace_->GetFullyQualifiedName(enum_name), &enum_def)) return Error("enum already exists: " + enum_name); enum_def.underlying_type.base_type = is_union ? BASE_TYPE_UTYPE : BASE_TYPE_INT; enum_def.underlying_type.enum_def = &enum_def; if (dest) *dest = &enum_def; return NoError(); } CheckedError Parser::ParseProtoFields(StructDef *struct_def, bool isextend, bool inside_oneof) { EXPECT('{'); while (token_ != '}') { if (IsIdent("message") || IsIdent("extend") || IsIdent("enum")) { // Nested declarations. ECHECK(ParseProtoDecl()); } else if (IsIdent("extensions")) { // Skip these. NEXT(); EXPECT(kTokenIntegerConstant); if (Is(kTokenIdentifier)) { NEXT(); // to NEXT(); // num } EXPECT(';'); } else if (IsIdent("option")) { // Skip these. ECHECK(ParseProtoOption()); EXPECT(';'); } else if (IsIdent("reserved")) { // Skip these. NEXT(); while (!Is(';')) { NEXT(); } // A variety of formats, just skip. NEXT(); } else { std::vector field_comment = doc_comment_; // Parse the qualifier. bool required = false; bool repeated = false; bool oneof = false; if (!inside_oneof) { if (IsIdent("optional")) { // This is the default. NEXT(); } else if (IsIdent("required")) { required = true; NEXT(); } else if (IsIdent("repeated")) { repeated = true; NEXT(); } else if (IsIdent("oneof")) { oneof = true; NEXT(); } else { // can't error, proto3 allows decls without any of the above. } } StructDef *anonymous_struct = nullptr; EnumDef *oneof_union = nullptr; Type type; if (IsIdent("group") || oneof) { if (!oneof) NEXT(); if (oneof && opts.proto_oneof_union) { auto name = MakeCamel(attribute_, true) + "Union"; ECHECK(StartEnum(name, true, &oneof_union)); type = Type(BASE_TYPE_UNION, nullptr, oneof_union); } else { auto name = "Anonymous" + NumToString(anonymous_counter++); ECHECK(StartStruct(name, &anonymous_struct)); type = Type(BASE_TYPE_STRUCT, anonymous_struct); } } else { ECHECK(ParseTypeFromProtoType(&type)); } // Repeated elements get mapped to a vector. if (repeated) { type.element = type.base_type; type.base_type = BASE_TYPE_VECTOR; if (type.element == BASE_TYPE_VECTOR) { // We have a vector or vectors, which FlatBuffers doesn't support. // For now make it a vector of string (since the source is likely // "repeated bytes"). // TODO(wvo): A better solution would be to wrap this in a table. type.element = BASE_TYPE_STRING; } } std::string name = attribute_; EXPECT(kTokenIdentifier); if (!oneof) { // Parse the field id. Since we're just translating schemas, not // any kind of binary compatibility, we can safely ignore these, and // assign our own. EXPECT('='); EXPECT(kTokenIntegerConstant); } FieldDef *field = nullptr; if (isextend) { // We allow a field to be re-defined when extending. // TODO: are there situations where that is problematic? field = struct_def->fields.Lookup(name); } if (!field) ECHECK(AddField(*struct_def, name, type, &field)); field->doc_comment = field_comment; if (!IsScalar(type.base_type)) field->required = required; // See if there's a default specified. if (Is('[')) { NEXT(); for (;;) { auto key = attribute_; ECHECK(ParseProtoKey()); EXPECT('='); auto val = attribute_; ECHECK(ParseProtoCurliesOrIdent()); if (key == "default") { // Temp: skip non-numeric defaults (enums). auto numeric = strpbrk(val.c_str(), "0123456789-+."); if (IsScalar(type.base_type) && numeric == val.c_str()) field->value.constant = val; } else if (key == "deprecated") { field->deprecated = val == "true"; } if (!Is(',')) break; NEXT(); } EXPECT(']'); } if (anonymous_struct) { ECHECK(ParseProtoFields(anonymous_struct, false, oneof)); if (Is(';')) NEXT(); } else if (oneof_union) { // Parse into a temporary StructDef, then transfer fields into an // EnumDef describing the oneof as a union. StructDef oneof_struct; ECHECK(ParseProtoFields(&oneof_struct, false, oneof)); if (Is(';')) NEXT(); for (auto field_it = oneof_struct.fields.vec.begin(); field_it != oneof_struct.fields.vec.end(); ++field_it) { const auto &oneof_field = **field_it; const auto &oneof_type = oneof_field.value.type; if (oneof_type.base_type != BASE_TYPE_STRUCT || !oneof_type.struct_def || oneof_type.struct_def->fixed) return Error("oneof '" + name + "' cannot be mapped to a union because member '" + oneof_field.name + "' is not a table type."); auto enum_val = new EnumVal(oneof_type.struct_def->name, oneof_union->vals.vec.size()); enum_val->union_type = oneof_type; enum_val->doc_comment = oneof_field.doc_comment; oneof_union->vals.Add(oneof_field.name, enum_val); } } else { EXPECT(';'); } } } NEXT(); return NoError(); } CheckedError Parser::ParseProtoKey() { if (token_ == '(') { NEXT(); // Skip "(a.b)" style custom attributes. while (token_ == '.' || token_ == kTokenIdentifier) NEXT(); EXPECT(')'); while (Is('.')) { NEXT(); EXPECT(kTokenIdentifier); } } else { EXPECT(kTokenIdentifier); } return NoError(); } CheckedError Parser::ParseProtoCurliesOrIdent() { if (Is('{')) { NEXT(); for (int nesting = 1; nesting;) { if (token_ == '{') nesting++; else if (token_ == '}') nesting--; NEXT(); } } else { NEXT(); // Any single token. } return NoError(); } CheckedError Parser::ParseProtoOption() { NEXT(); ECHECK(ParseProtoKey()); EXPECT('='); ECHECK(ParseProtoCurliesOrIdent()); return NoError(); } // Parse a protobuf type, and map it to the corresponding FlatBuffer one. CheckedError Parser::ParseTypeFromProtoType(Type *type) { struct type_lookup { const char *proto_type; BaseType fb_type, element; }; static type_lookup lookup[] = { { "float", BASE_TYPE_FLOAT, BASE_TYPE_NONE }, { "double", BASE_TYPE_DOUBLE, BASE_TYPE_NONE }, { "int32", BASE_TYPE_INT, BASE_TYPE_NONE }, { "int64", BASE_TYPE_LONG, BASE_TYPE_NONE }, { "uint32", BASE_TYPE_UINT, BASE_TYPE_NONE }, { "uint64", BASE_TYPE_ULONG, BASE_TYPE_NONE }, { "sint32", BASE_TYPE_INT, BASE_TYPE_NONE }, { "sint64", BASE_TYPE_LONG, BASE_TYPE_NONE }, { "fixed32", BASE_TYPE_UINT, BASE_TYPE_NONE }, { "fixed64", BASE_TYPE_ULONG, BASE_TYPE_NONE }, { "sfixed32", BASE_TYPE_INT, BASE_TYPE_NONE }, { "sfixed64", BASE_TYPE_LONG, BASE_TYPE_NONE }, { "bool", BASE_TYPE_BOOL, BASE_TYPE_NONE }, { "string", BASE_TYPE_STRING, BASE_TYPE_NONE }, { "bytes", BASE_TYPE_VECTOR, BASE_TYPE_UCHAR }, { nullptr, BASE_TYPE_NONE, BASE_TYPE_NONE } }; for (auto tl = lookup; tl->proto_type; tl++) { if (attribute_ == tl->proto_type) { type->base_type = tl->fb_type; type->element = tl->element; NEXT(); return NoError(); } } if (Is('.')) NEXT(); // qualified names may start with a . ? ECHECK(ParseTypeIdent(*type)); return NoError(); } CheckedError Parser::SkipAnyJsonValue() { switch (token_) { case '{': { size_t fieldn_outer = 0; return ParseTableDelimiters( fieldn_outer, nullptr, [](const std::string &, size_t &fieldn, const StructDef *, void *state) -> CheckedError { auto *parser = static_cast(state); ECHECK(parser->Recurse([&]() { return parser->SkipAnyJsonValue(); })); fieldn++; return NoError(); }, this); } case '[': { size_t count = 0; return ParseVectorDelimiters( count, [](size_t &, void *state) -> CheckedError { auto *parser = static_cast(state); return parser->Recurse([&]() { return parser->SkipAnyJsonValue(); }); }, this); } case kTokenStringConstant: case kTokenIntegerConstant: case kTokenFloatConstant: NEXT(); break; default: if (IsIdent("true") || IsIdent("false") || IsIdent("null")) { NEXT(); } else return TokenError(); } return NoError(); } CheckedError Parser::ParseFlexBufferValue(flexbuffers::Builder *builder) { switch (token_) { case '{': { std::pair parser_and_builder_state( this, builder); auto start = builder->StartMap(); size_t fieldn_outer = 0; auto err = ParseTableDelimiters( fieldn_outer, nullptr, [](const std::string &name, size_t &fieldn, const StructDef *, void *state) -> CheckedError { auto *parser_and_builder = static_cast *>( state); auto *parser = parser_and_builder->first; auto *current_builder = parser_and_builder->second; current_builder->Key(name); ECHECK(parser->ParseFlexBufferValue(current_builder)); fieldn++; return NoError(); }, &parser_and_builder_state); ECHECK(err); builder->EndMap(start); break; } case '[': { auto start = builder->StartVector(); size_t count = 0; std::pair parser_and_builder_state( this, builder); ECHECK(ParseVectorDelimiters( count, [](size_t &, void *state) -> CheckedError { auto *parser_and_builder = static_cast *>( state); return parser_and_builder->first->ParseFlexBufferValue( parser_and_builder->second); }, &parser_and_builder_state)); builder->EndVector(start, false, false); break; } case kTokenStringConstant: builder->String(attribute_); EXPECT(kTokenStringConstant); break; case kTokenIntegerConstant: builder->Int(StringToInt(attribute_.c_str())); EXPECT(kTokenIntegerConstant); break; case kTokenFloatConstant: builder->Double(strtod(attribute_.c_str(), nullptr)); EXPECT(kTokenFloatConstant); break; default: if (IsIdent("true")) { builder->Bool(true); NEXT(); } else if (IsIdent("false")) { builder->Bool(false); NEXT(); } else if (IsIdent("null")) { builder->Null(); NEXT(); } else return TokenError(); } return NoError(); } bool Parser::ParseFlexBuffer(const char *source, const char *source_filename, flexbuffers::Builder *builder) { auto ok = !StartParseFile(source, source_filename).Check() && !ParseFlexBufferValue(builder).Check(); if (ok) builder->Finish(); return ok; } bool Parser::Parse(const char *source, const char **include_paths, const char *source_filename) { return !ParseRoot(source, include_paths, source_filename).Check(); } CheckedError Parser::StartParseFile(const char *source, const char *source_filename) { file_being_parsed_ = source_filename ? source_filename : ""; source_ = source; ResetState(source_); error_.clear(); ECHECK(SkipByteOrderMark()); NEXT(); if (Is(kTokenEof)) return Error("input file is empty"); return NoError(); } CheckedError Parser::ParseRoot(const char *source, const char **include_paths, const char *source_filename) { ECHECK(DoParse(source, include_paths, source_filename, nullptr)); // Check that all types were defined. for (auto it = structs_.vec.begin(); it != structs_.vec.end();) { auto &struct_def = **it; if (struct_def.predecl) { if (opts.proto_mode) { // Protos allow enums to be used before declaration, so check if that // is the case here. EnumDef *enum_def = nullptr; for (size_t components = struct_def.defined_namespace->components.size() + 1; components && !enum_def; components--) { auto qualified_name = struct_def.defined_namespace->GetFullyQualifiedName( struct_def.name, components - 1); enum_def = LookupEnum(qualified_name); } if (enum_def) { // This is pretty slow, but a simple solution for now. auto initial_count = struct_def.refcount; for (auto struct_it = structs_.vec.begin(); struct_it != structs_.vec.end(); ++struct_it) { auto &sd = **struct_it; for (auto field_it = sd.fields.vec.begin(); field_it != sd.fields.vec.end(); ++field_it) { auto &field = **field_it; if (field.value.type.struct_def == &struct_def) { field.value.type.struct_def = nullptr; field.value.type.enum_def = enum_def; auto &bt = field.value.type.base_type == BASE_TYPE_VECTOR ? field.value.type.element : field.value.type.base_type; FLATBUFFERS_ASSERT(bt == BASE_TYPE_STRUCT); bt = enum_def->underlying_type.base_type; struct_def.refcount--; enum_def->refcount++; } } } if (struct_def.refcount) return Error("internal: " + NumToString(struct_def.refcount) + "/" + NumToString(initial_count) + " use(s) of pre-declaration enum not accounted for: " + enum_def->name); structs_.dict.erase(structs_.dict.find(struct_def.name)); it = structs_.vec.erase(it); delete &struct_def; continue; // Skip error. } } auto err = "type referenced but not defined (check namespace): " + struct_def.name; if (struct_def.original_location) err += ", originally at: " + *struct_def.original_location; return Error(err); } ++it; } // This check has to happen here and not earlier, because only now do we // know for sure what the type of these are. for (auto it = enums_.vec.begin(); it != enums_.vec.end(); ++it) { auto &enum_def = **it; if (enum_def.is_union) { for (auto val_it = enum_def.vals.vec.begin(); val_it != enum_def.vals.vec.end(); ++val_it) { auto &val = **val_it; if (!SupportsVectorOfUnions() && val.union_type.struct_def && val.union_type.struct_def->fixed) return Error( "only tables can be union elements in the generated language: " + val.name); } } } return NoError(); } CheckedError Parser::DoParse(const char *source, const char **include_paths, const char *source_filename, const char *include_filename) { if (source_filename && included_files_.find(source_filename) == included_files_.end()) { included_files_[source_filename] = include_filename ? include_filename : ""; files_included_per_file_[source_filename] = std::set(); } if (!include_paths) { static const char *current_directory[] = { "", nullptr }; include_paths = current_directory; } field_stack_.clear(); builder_.Clear(); // Start with a blank namespace just in case this file doesn't have one. current_namespace_ = empty_namespace_; ECHECK(StartParseFile(source, source_filename)); // Includes must come before type declarations: for (;;) { // Parse pre-include proto statements if any: if (opts.proto_mode && (attribute_ == "option" || attribute_ == "syntax" || attribute_ == "package")) { ECHECK(ParseProtoDecl()); } else if (IsIdent("native_include")) { NEXT(); vector_emplace_back(&native_included_files_, attribute_); EXPECT(kTokenStringConstant); EXPECT(';'); } else if (IsIdent("include") || (opts.proto_mode && IsIdent("import"))) { NEXT(); if (opts.proto_mode && attribute_ == "public") NEXT(); auto name = flatbuffers::PosixPath(attribute_.c_str()); EXPECT(kTokenStringConstant); // Look for the file in include_paths. std::string filepath; for (auto paths = include_paths; paths && *paths; paths++) { filepath = flatbuffers::ConCatPathFileName(*paths, name); if (FileExists(filepath.c_str())) break; } if (filepath.empty()) return Error("unable to locate include file: " + name); if (source_filename) files_included_per_file_[source_filename].insert(filepath); if (included_files_.find(filepath) == included_files_.end()) { // We found an include file that we have not parsed yet. // Load it and parse it. std::string contents; if (!LoadFile(filepath.c_str(), true, &contents)) return Error("unable to load include file: " + name); ECHECK(DoParse(contents.c_str(), include_paths, filepath.c_str(), name.c_str())); // We generally do not want to output code for any included files: if (!opts.generate_all) MarkGenerated(); // Reset these just in case the included file had them, and the // parent doesn't. root_struct_def_ = nullptr; file_identifier_.clear(); file_extension_.clear(); // This is the easiest way to continue this file after an include: // instead of saving and restoring all the state, we simply start the // file anew. This will cause it to encounter the same include // statement again, but this time it will skip it, because it was // entered into included_files_. // This is recursive, but only go as deep as the number of include // statements. return DoParse(source, include_paths, source_filename, include_filename); } EXPECT(';'); } else { break; } } // Now parse all other kinds of declarations: while (token_ != kTokenEof) { if (opts.proto_mode) { ECHECK(ParseProtoDecl()); } else if (IsIdent("namespace")) { ECHECK(ParseNamespace()); } else if (token_ == '{') { if (!root_struct_def_) return Error("no root type set to parse json with"); if (builder_.GetSize()) { return Error("cannot have more than one json object in a file"); } uoffset_t toff; ECHECK(ParseTable(*root_struct_def_, nullptr, &toff)); if (opts.size_prefixed) { builder_.FinishSizePrefixed(Offset
(toff), file_identifier_.length() ? file_identifier_.c_str() : nullptr); } else { builder_.Finish(Offset
(toff), file_identifier_.length() ? file_identifier_.c_str() : nullptr); } } else if (IsIdent("enum")) { ECHECK(ParseEnum(false, nullptr)); } else if (IsIdent("union")) { ECHECK(ParseEnum(true, nullptr)); } else if (IsIdent("root_type")) { NEXT(); auto root_type = attribute_; EXPECT(kTokenIdentifier); ECHECK(ParseNamespacing(&root_type, nullptr)); if (opts.root_type.empty()) { if (!SetRootType(root_type.c_str())) return Error("unknown root type: " + root_type); if (root_struct_def_->fixed) return Error("root type must be a table"); } EXPECT(';'); } else if (IsIdent("file_identifier")) { NEXT(); file_identifier_ = attribute_; EXPECT(kTokenStringConstant); if (file_identifier_.length() != FlatBufferBuilder::kFileIdentifierLength) return Error("file_identifier must be exactly " + NumToString(FlatBufferBuilder::kFileIdentifierLength) + " characters"); EXPECT(';'); } else if (IsIdent("file_extension")) { NEXT(); file_extension_ = attribute_; EXPECT(kTokenStringConstant); EXPECT(';'); } else if (IsIdent("include")) { return Error("includes must come before declarations"); } else if (IsIdent("attribute")) { NEXT(); auto name = attribute_; if (Is(kTokenIdentifier)) { NEXT(); } else { EXPECT(kTokenStringConstant); } EXPECT(';'); known_attributes_[name] = false; } else if (IsIdent("rpc_service")) { ECHECK(ParseService()); } else { ECHECK(ParseDecl()); } } return NoError(); } std::set Parser::GetIncludedFilesRecursive( const std::string &file_name) const { std::set included_files; std::list to_process; if (file_name.empty()) return included_files; to_process.push_back(file_name); while (!to_process.empty()) { std::string current = to_process.front(); to_process.pop_front(); included_files.insert(current); // Workaround the lack of const accessor in C++98 maps. auto &new_files = (*const_cast> *>( &files_included_per_file_))[current]; for (auto it = new_files.begin(); it != new_files.end(); ++it) { if (included_files.find(*it) == included_files.end()) to_process.push_back(*it); } } return included_files; } // Schema serialization functionality: template bool compareName(const T *a, const T *b) { return a->defined_namespace->GetFullyQualifiedName(a->name) < b->defined_namespace->GetFullyQualifiedName(b->name); } template void AssignIndices(const std::vector &defvec) { // Pre-sort these vectors, such that we can set the correct indices for them. auto vec = defvec; std::sort(vec.begin(), vec.end(), compareName); for (int i = 0; i < static_cast(vec.size()); i++) vec[i]->index = i; } void Parser::Serialize() { builder_.Clear(); AssignIndices(structs_.vec); AssignIndices(enums_.vec); std::vector> object_offsets; for (auto it = structs_.vec.begin(); it != structs_.vec.end(); ++it) { auto offset = (*it)->Serialize(&builder_, *this); object_offsets.push_back(offset); (*it)->serialized_location = offset.o; } std::vector> enum_offsets; for (auto it = enums_.vec.begin(); it != enums_.vec.end(); ++it) { auto offset = (*it)->Serialize(&builder_, *this); enum_offsets.push_back(offset); (*it)->serialized_location = offset.o; } std::vector> service_offsets; for (auto it = services_.vec.begin(); it != services_.vec.end(); ++it) { auto offset = (*it)->Serialize(&builder_, *this); service_offsets.push_back(offset); (*it)->serialized_location = offset.o; } auto schema_offset = reflection::CreateSchema( builder_, builder_.CreateVectorOfSortedTables(&object_offsets), builder_.CreateVectorOfSortedTables(&enum_offsets), builder_.CreateString(file_identifier_), builder_.CreateString(file_extension_), (root_struct_def_ ? root_struct_def_->serialized_location : 0), builder_.CreateVectorOfSortedTables(&service_offsets)); if (opts.size_prefixed) { builder_.FinishSizePrefixed(schema_offset, reflection::SchemaIdentifier()); } else { builder_.Finish(schema_offset, reflection::SchemaIdentifier()); } } Offset StructDef::Serialize(FlatBufferBuilder *builder, const Parser &parser) const { std::vector> field_offsets; for (auto it = fields.vec.begin(); it != fields.vec.end(); ++it) { field_offsets.push_back((*it)->Serialize( builder, static_cast(it - fields.vec.begin()), parser)); } auto qualified_name = defined_namespace->GetFullyQualifiedName(name); return reflection::CreateObject( *builder, builder->CreateString(qualified_name), builder->CreateVectorOfSortedTables(&field_offsets), fixed, static_cast(minalign), static_cast(bytesize), SerializeAttributes(builder, parser), parser.opts.binary_schema_comments ? builder->CreateVectorOfStrings(doc_comment) : 0); } Offset FieldDef::Serialize(FlatBufferBuilder *builder, uint16_t id, const Parser &parser) const { return reflection::CreateField( *builder, builder->CreateString(name), value.type.Serialize(builder), id, value.offset, IsInteger(value.type.base_type) ? StringToInt(value.constant.c_str()) : 0, IsFloat(value.type.base_type) ? strtod(value.constant.c_str(), nullptr) : 0.0, deprecated, required, key, SerializeAttributes(builder, parser), parser.opts.binary_schema_comments ? builder->CreateVectorOfStrings(doc_comment) : 0); // TODO: value.constant is almost always "0", we could save quite a bit of // space by sharing it. Same for common values of value.type. } Offset RPCCall::Serialize(FlatBufferBuilder *builder, const Parser &parser) const { return reflection::CreateRPCCall( *builder, builder->CreateString(name), request->serialized_location, response->serialized_location, SerializeAttributes(builder, parser), parser.opts.binary_schema_comments ? builder->CreateVectorOfStrings(doc_comment) : 0); } Offset ServiceDef::Serialize(FlatBufferBuilder *builder, const Parser &parser) const { std::vector> servicecall_offsets; for (auto it = calls.vec.begin(); it != calls.vec.end(); ++it) { servicecall_offsets.push_back((*it)->Serialize(builder, parser)); } auto qualified_name = defined_namespace->GetFullyQualifiedName(name); return reflection::CreateService( *builder, builder->CreateString(qualified_name), builder->CreateVector(servicecall_offsets), SerializeAttributes(builder, parser), parser.opts.binary_schema_comments ? builder->CreateVectorOfStrings(doc_comment) : 0); } Offset EnumDef::Serialize(FlatBufferBuilder *builder, const Parser &parser) const { std::vector> enumval_offsets; for (auto it = vals.vec.begin(); it != vals.vec.end(); ++it) { enumval_offsets.push_back((*it)->Serialize(builder, parser)); } auto qualified_name = defined_namespace->GetFullyQualifiedName(name); return reflection::CreateEnum( *builder, builder->CreateString(qualified_name), builder->CreateVector(enumval_offsets), is_union, underlying_type.Serialize(builder), SerializeAttributes(builder, parser), parser.opts.binary_schema_comments ? builder->CreateVectorOfStrings(doc_comment) : 0); } Offset EnumVal::Serialize(FlatBufferBuilder *builder, const Parser &parser) const { return reflection::CreateEnumVal( *builder, builder->CreateString(name), value, union_type.struct_def ? union_type.struct_def->serialized_location : 0, union_type.Serialize(builder), parser.opts.binary_schema_comments ? builder->CreateVectorOfStrings(doc_comment) : 0); } Offset Type::Serialize(FlatBufferBuilder *builder) const { return reflection::CreateType( *builder, static_cast(base_type), static_cast(element), struct_def ? struct_def->index : (enum_def ? enum_def->index : -1)); } flatbuffers::Offset< flatbuffers::Vector>> Definition::SerializeAttributes(FlatBufferBuilder *builder, const Parser &parser) const { std::vector> attrs; for (auto kv = attributes.dict.begin(); kv != attributes.dict.end(); ++kv) { auto it = parser.known_attributes_.find(kv->first); FLATBUFFERS_ASSERT(it != parser.known_attributes_.end()); if (parser.opts.binary_schema_builtins || !it->second) { attrs.push_back(reflection::CreateKeyValue( *builder, builder->CreateString(kv->first), builder->CreateString(kv->second->constant))); } } if (attrs.size()) { return builder->CreateVectorOfSortedTables(&attrs); } else { return 0; } } std::string Parser::ConformTo(const Parser &base) { for (auto sit = structs_.vec.begin(); sit != structs_.vec.end(); ++sit) { auto &struct_def = **sit; auto qualified_name = struct_def.defined_namespace->GetFullyQualifiedName(struct_def.name); auto struct_def_base = base.LookupStruct(qualified_name); if (!struct_def_base) continue; for (auto fit = struct_def.fields.vec.begin(); fit != struct_def.fields.vec.end(); ++fit) { auto &field = **fit; auto field_base = struct_def_base->fields.Lookup(field.name); if (field_base) { if (field.value.offset != field_base->value.offset) return "offsets differ for field: " + field.name; if (field.value.constant != field_base->value.constant) return "defaults differ for field: " + field.name; if (!EqualByName(field.value.type, field_base->value.type)) return "types differ for field: " + field.name; } else { // Doesn't have to exist, deleting fields is fine. // But we should check if there is a field that has the same offset // but is incompatible (in the case of field renaming). for (auto fbit = struct_def_base->fields.vec.begin(); fbit != struct_def_base->fields.vec.end(); ++fbit) { field_base = *fbit; if (field.value.offset == field_base->value.offset) { if (!EqualByName(field.value.type, field_base->value.type)) return "field renamed to different type: " + field.name; break; } } } } } for (auto eit = enums_.vec.begin(); eit != enums_.vec.end(); ++eit) { auto &enum_def = **eit; auto qualified_name = enum_def.defined_namespace->GetFullyQualifiedName(enum_def.name); auto enum_def_base = base.enums_.Lookup(qualified_name); if (!enum_def_base) continue; for (auto evit = enum_def.vals.vec.begin(); evit != enum_def.vals.vec.end(); ++evit) { auto &enum_val = **evit; auto enum_val_base = enum_def_base->vals.Lookup(enum_val.name); if (enum_val_base) { if (enum_val.value != enum_val_base->value) return "values differ for enum: " + enum_val.name; } } } return ""; } } // namespace flatbuffers treesheets-1.0.2/lobster/external/flatbuffers/src/util.cpp000066400000000000000000000053721352107072600237460ustar00rootroot00000000000000/* * Copyright 2016 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "flatbuffers/util.h" namespace flatbuffers { bool FileExistsRaw(const char *name) { std::ifstream ifs(name); return ifs.good(); } bool LoadFileRaw(const char *name, bool binary, std::string *buf) { if (DirExists(name)) return false; std::ifstream ifs(name, binary ? std::ifstream::binary : std::ifstream::in); if (!ifs.is_open()) return false; if (binary) { // The fastest way to read a file into a string. ifs.seekg(0, std::ios::end); auto size = ifs.tellg(); (*buf).resize(static_cast(size)); ifs.seekg(0, std::ios::beg); ifs.read(&(*buf)[0], (*buf).size()); } else { // This is slower, but works correctly on all platforms for text files. std::ostringstream oss; oss << ifs.rdbuf(); *buf = oss.str(); } return !ifs.bad(); } static LoadFileFunction g_load_file_function = LoadFileRaw; static FileExistsFunction g_file_exists_function = FileExistsRaw; bool LoadFile(const char *name, bool binary, std::string *buf) { FLATBUFFERS_ASSERT(g_load_file_function); return g_load_file_function(name, binary, buf); } bool FileExists(const char *name) { FLATBUFFERS_ASSERT(g_file_exists_function); return g_file_exists_function(name); } bool DirExists(const char *name) { // clang-format off #ifdef _WIN32 #define flatbuffers_stat _stat #define FLATBUFFERS_S_IFDIR _S_IFDIR #else #define flatbuffers_stat stat #define FLATBUFFERS_S_IFDIR S_IFDIR #endif // clang-format on struct flatbuffers_stat file_info; if (flatbuffers_stat(name, &file_info) != 0) return false; return (file_info.st_mode & FLATBUFFERS_S_IFDIR) != 0; } LoadFileFunction SetLoadFileFunction(LoadFileFunction load_file_function) { LoadFileFunction previous_function = g_load_file_function; g_load_file_function = load_file_function ? load_file_function : LoadFileRaw; return previous_function; } FileExistsFunction SetFileExistsFunction( FileExistsFunction file_exists_function) { FileExistsFunction previous_function = g_file_exists_function; g_file_exists_function = file_exists_function ? file_exists_function : FileExistsRaw; return previous_function; } } // namespace flatbuffers treesheets-1.0.2/lobster/include/000077500000000000000000000000001352107072600167655ustar00rootroot00000000000000treesheets-1.0.2/lobster/include/StackWalker/000077500000000000000000000000001352107072600212005ustar00rootroot00000000000000treesheets-1.0.2/lobster/include/StackWalker/StackWalker.cpp000066400000000000000000001404421352107072600241240ustar00rootroot00000000000000/********************************************************************** * * StackWalker.cpp * http://stackwalker.codeplex.com/ * * * History: * 2005-07-27 v1 - First public release on http://www.codeproject.com/ * http://www.codeproject.com/threads/StackWalker.asp * 2005-07-28 v2 - Changed the params of the constructor and ShowCallstack * (to simplify the usage) * 2005-08-01 v3 - Changed to use 'CONTEXT_FULL' instead of CONTEXT_ALL * (should also be enough) * - Changed to compile correctly with the PSDK of VC7.0 * (GetFileVersionInfoSizeA and GetFileVersionInfoA is wrongly defined: * it uses LPSTR instead of LPCSTR as first paremeter) * - Added declarations to support VC5/6 without using 'dbghelp.h' * - Added a 'pUserData' member to the ShowCallstack function and the * PReadProcessMemoryRoutine declaration (to pass some user-defined data, * which can be used in the readMemoryFunction-callback) * 2005-08-02 v4 - OnSymInit now also outputs the OS-Version by default * - Added example for doing an exception-callstack-walking in main.cpp * (thanks to owillebo: http://www.codeproject.com/script/profile/whos_who.asp?id=536268) * 2005-08-05 v5 - Removed most Lint (http://www.gimpel.com/) errors... thanks to Okko Willeboordse! * 2008-08-04 v6 - Fixed Bug: Missing LEAK-end-tag * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=2502890#xx2502890xx * Fixed Bug: Compiled with "WIN32_LEAN_AND_MEAN" * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=1824718#xx1824718xx * Fixed Bug: Compiling with "/Wall" * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=2638243#xx2638243xx * Fixed Bug: Now checking SymUseSymSrv * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1388979#xx1388979xx * Fixed Bug: Support for recursive function calls * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1434538#xx1434538xx * Fixed Bug: Missing FreeLibrary call in "GetModuleListTH32" * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1326923#xx1326923xx * Fixed Bug: SymDia is number 7, not 9! * 2008-09-11 v7 For some (undocumented) reason, dbhelp.h is needing a packing of 8! * Thanks to Teajay which reported the bug... * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=2718933#xx2718933xx * 2008-11-27 v8 Debugging Tools for Windows are now stored in a different directory * Thanks to Luiz Salamon which reported this "bug"... * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=2822736#xx2822736xx * 2009-04-10 v9 License slihtly corrected ( replaced) * 2009-11-01 v10 Moved to http://stackwalker.codeplex.com/ * 2009-11-02 v11 Now try to use IMAGEHLP_MODULE64_V3 if available * 2010-04-15 v12 Added support for VS2010 RTM * 2010-05-25 v13 Now using secure MyStrcCpy. Thanks to luke.simon: * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=3477467#xx3477467xx * 2013-01-07 v14 Runtime Check Error VS2010 Debug Builds fixed: * http://stackwalker.codeplex.com/workitem/10511 * * * LICENSE (http://www.opensource.org/licenses/bsd-license.php) * * Copyright (c) 2005-2013, Jochen Kalmbach * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * Neither the name of Jochen Kalmbach nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **********************************************************************/ #include #include #include #include #pragma comment(lib, "version.lib") // for "VerQueryValue" #pragma warning(disable:4826) #include "StackWalker.h" // If VC7 and later, then use the shipped 'dbghelp.h'-file #pragma pack(push,8) #if _MSC_VER >= 1300 #include #else // inline the important dbghelp.h-declarations... typedef enum { SymNone = 0, SymCoff, SymCv, SymPdb, SymExport, SymDeferred, SymSym, SymDia, SymVirtual, NumSymTypes } SYM_TYPE; typedef struct _IMAGEHLP_LINE64 { DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64) PVOID Key; // internal DWORD LineNumber; // line number in file PCHAR FileName; // full filename DWORD64 Address; // first instruction of line } IMAGEHLP_LINE64, *PIMAGEHLP_LINE64; typedef struct _IMAGEHLP_MODULE64 { DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) DWORD64 BaseOfImage; // base load address of module DWORD ImageSize; // virtual size of the loaded module DWORD TimeDateStamp; // date/time stamp from pe header DWORD CheckSum; // checksum from the pe header DWORD NumSyms; // number of symbols in the symbol table SYM_TYPE SymType; // type of symbols loaded CHAR ModuleName[32]; // module name CHAR ImageName[256]; // image name CHAR LoadedImageName[256]; // symbol file name } IMAGEHLP_MODULE64, *PIMAGEHLP_MODULE64; typedef struct _IMAGEHLP_SYMBOL64 { DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_SYMBOL64) DWORD64 Address; // virtual address including dll base address DWORD Size; // estimated size of symbol, can be zero DWORD Flags; // info about the symbols, see the SYMF defines DWORD MaxNameLength; // maximum size of symbol name in 'Name' CHAR Name[1]; // symbol name (null terminated string) } IMAGEHLP_SYMBOL64, *PIMAGEHLP_SYMBOL64; typedef enum { AddrMode1616, AddrMode1632, AddrModeReal, AddrModeFlat } ADDRESS_MODE; typedef struct _tagADDRESS64 { DWORD64 Offset; WORD Segment; ADDRESS_MODE Mode; } ADDRESS64, *LPADDRESS64; typedef struct _KDHELP64 { DWORD64 Thread; DWORD ThCallbackStack; DWORD ThCallbackBStore; DWORD NextCallback; DWORD FramePointer; DWORD64 KiCallUserMode; DWORD64 KeUserCallbackDispatcher; DWORD64 SystemRangeStart; DWORD64 Reserved[8]; } KDHELP64, *PKDHELP64; typedef struct _tagSTACKFRAME64 { ADDRESS64 AddrPC; // program counter ADDRESS64 AddrReturn; // return address ADDRESS64 AddrFrame; // frame pointer ADDRESS64 AddrStack; // stack pointer ADDRESS64 AddrBStore; // backing store pointer PVOID FuncTableEntry; // pointer to pdata/fpo or NULL DWORD64 Params[4]; // possible arguments to the function BOOL Far; // WOW far call BOOL Virtual; // is this a virtual frame? DWORD64 Reserved[3]; KDHELP64 KdHelp; } STACKFRAME64, *LPSTACKFRAME64; typedef BOOL (__stdcall *PREAD_PROCESS_MEMORY_ROUTINE64)( HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead ); typedef PVOID (__stdcall *PFUNCTION_TABLE_ACCESS_ROUTINE64)( HANDLE hProcess, DWORD64 AddrBase ); typedef DWORD64 (__stdcall *PGET_MODULE_BASE_ROUTINE64)( HANDLE hProcess, DWORD64 Address ); typedef DWORD64 (__stdcall *PTRANSLATE_ADDRESS_ROUTINE64)( HANDLE hProcess, HANDLE hThread, LPADDRESS64 lpaddr ); #define SYMOPT_CASE_INSENSITIVE 0x00000001 #define SYMOPT_UNDNAME 0x00000002 #define SYMOPT_DEFERRED_LOADS 0x00000004 #define SYMOPT_NO_CPP 0x00000008 #define SYMOPT_LOAD_LINES 0x00000010 #define SYMOPT_OMAP_FIND_NEAREST 0x00000020 #define SYMOPT_LOAD_ANYTHING 0x00000040 #define SYMOPT_IGNORE_CVREC 0x00000080 #define SYMOPT_NO_UNQUALIFIED_LOADS 0x00000100 #define SYMOPT_FAIL_CRITICAL_ERRORS 0x00000200 #define SYMOPT_EXACT_SYMBOLS 0x00000400 #define SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 0x00000800 #define SYMOPT_IGNORE_NT_SYMPATH 0x00001000 #define SYMOPT_INCLUDE_32BIT_MODULES 0x00002000 #define SYMOPT_PUBLICS_ONLY 0x00004000 #define SYMOPT_NO_PUBLICS 0x00008000 #define SYMOPT_AUTO_PUBLICS 0x00010000 #define SYMOPT_NO_IMAGE_SEARCH 0x00020000 #define SYMOPT_SECURE 0x00040000 #define SYMOPT_DEBUG 0x80000000 #define UNDNAME_COMPLETE (0x0000) // Enable full undecoration #define UNDNAME_NAME_ONLY (0x1000) // Crack only the name for primary declaration; #endif // _MSC_VER < 1300 #pragma pack(pop) // Some missing defines (for VC5/6): #ifndef INVALID_FILE_ATTRIBUTES #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) #endif // secure-CRT_functions are only available starting with VC8 #if _MSC_VER < 1400 #define strcpy_s(dst, len, src) strcpy(dst, src) #define strncpy_s(dst, len, src, maxLen) strncpy(dst, len, src) #define strcat_s(dst, len, src) strcat(dst, src) #define _snprintf_s _snprintf #define _tcscat_s _tcscat #endif static void MyStrCpy(char* szDest, size_t nMaxDestSize, const char* szSrc) { if (nMaxDestSize <= 0) return; strncpy_s(szDest, nMaxDestSize, szSrc, _TRUNCATE); szDest[nMaxDestSize-1] = 0; // INFO: _TRUNCATE will ensure that it is nul-terminated; but with older compilers (<1400) it uses "strncpy" and this does not!) } // MyStrCpy // Normally it should be enough to use 'CONTEXT_FULL' (better would be 'CONTEXT_ALL') #define USED_CONTEXT_FLAGS CONTEXT_FULL class StackWalkerInternal { public: StackWalkerInternal(StackWalker *parent, HANDLE hProcess) { m_parent = parent; m_hDbhHelp = NULL; pSC = NULL; m_hProcess = hProcess; m_szSymPath = NULL; pSFTA = NULL; pSGLFA = NULL; pSGMB = NULL; pSGMI = NULL; pSGO = NULL; pSGSFA = NULL; pSI = NULL; pSLM = NULL; pSSO = NULL; pSW = NULL; pUDSN = NULL; pSGSP = NULL; } ~StackWalkerInternal() { if (pSC != NULL) pSC(m_hProcess); // SymCleanup if (m_hDbhHelp != NULL) FreeLibrary(m_hDbhHelp); m_hDbhHelp = NULL; m_parent = NULL; if(m_szSymPath != NULL) free(m_szSymPath); m_szSymPath = NULL; } BOOL Init(LPCSTR szSymPath) { if (m_parent == NULL) return FALSE; // Dynamically load the Entry-Points for dbghelp.dll: // First try to load the newsest one from TCHAR szTemp[4096]; // But before wqe do this, we first check if the ".local" file exists if (GetModuleFileName(NULL, szTemp, 4096) > 0) { _tcscat_s(szTemp, _T(".local")); if (GetFileAttributes(szTemp) == INVALID_FILE_ATTRIBUTES) { // ".local" file does not exist, so we can try to load the dbghelp.dll from the "Debugging Tools for Windows" // Ok, first try the new path according to the archtitecture: #ifdef _M_IX86 if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) ) { _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x86)\\dbghelp.dll")); // now check if the file exists: if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { m_hDbhHelp = LoadLibrary(szTemp); } } #elif _M_X64 if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) ) { _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x64)\\dbghelp.dll")); // now check if the file exists: if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { m_hDbhHelp = LoadLibrary(szTemp); } } #elif _M_IA64 if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) ) { _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (ia64)\\dbghelp.dll")); // now check if the file exists: if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { m_hDbhHelp = LoadLibrary(szTemp); } } #endif // If still not found, try the old directories... if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) ) { _tcscat_s(szTemp, _T("\\Debugging Tools for Windows\\dbghelp.dll")); // now check if the file exists: if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { m_hDbhHelp = LoadLibrary(szTemp); } } #if defined _M_X64 || defined _M_IA64 // Still not found? Then try to load the (old) 64-Bit version: if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) ) { _tcscat_s(szTemp, _T("\\Debugging Tools for Windows 64-Bit\\dbghelp.dll")); if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { m_hDbhHelp = LoadLibrary(szTemp); } } #endif } } if (m_hDbhHelp == NULL) // if not already loaded, try to load a default-one m_hDbhHelp = LoadLibrary( _T("dbghelp.dll") ); if (m_hDbhHelp == NULL) return FALSE; pSI = (tSI) GetProcAddress(m_hDbhHelp, "SymInitialize" ); pSC = (tSC) GetProcAddress(m_hDbhHelp, "SymCleanup" ); pSW = (tSW) GetProcAddress(m_hDbhHelp, "StackWalk64" ); pSGO = (tSGO) GetProcAddress(m_hDbhHelp, "SymGetOptions" ); pSSO = (tSSO) GetProcAddress(m_hDbhHelp, "SymSetOptions" ); pSFTA = (tSFTA) GetProcAddress(m_hDbhHelp, "SymFunctionTableAccess64" ); pSGLFA = (tSGLFA) GetProcAddress(m_hDbhHelp, "SymGetLineFromAddr64" ); pSGMB = (tSGMB) GetProcAddress(m_hDbhHelp, "SymGetModuleBase64" ); pSGMI = (tSGMI) GetProcAddress(m_hDbhHelp, "SymGetModuleInfo64" ); pSGSFA = (tSGSFA) GetProcAddress(m_hDbhHelp, "SymGetSymFromAddr64" ); pUDSN = (tUDSN) GetProcAddress(m_hDbhHelp, "UnDecorateSymbolName" ); pSLM = (tSLM) GetProcAddress(m_hDbhHelp, "SymLoadModule64" ); pSGSP =(tSGSP) GetProcAddress(m_hDbhHelp, "SymGetSearchPath" ); if ( pSC == NULL || pSFTA == NULL || pSGMB == NULL || pSGMI == NULL || pSGO == NULL || pSGSFA == NULL || pSI == NULL || pSSO == NULL || pSW == NULL || pUDSN == NULL || pSLM == NULL ) { FreeLibrary(m_hDbhHelp); m_hDbhHelp = NULL; pSC = NULL; return FALSE; } // SymInitialize if (szSymPath != NULL) m_szSymPath = _strdup(szSymPath); if (this->pSI(m_hProcess, m_szSymPath, FALSE) == FALSE) this->m_parent->OnDbgHelpErr("SymInitialize", GetLastError(), 0); DWORD symOptions = this->pSGO(); // SymGetOptions symOptions |= SYMOPT_LOAD_LINES; symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; //symOptions |= SYMOPT_NO_PROMPTS; // SymSetOptions symOptions = this->pSSO(symOptions); char buf[StackWalker::STACKWALK_MAX_NAMELEN] = {0}; if (this->pSGSP != NULL) { if (this->pSGSP(m_hProcess, buf, StackWalker::STACKWALK_MAX_NAMELEN) == FALSE) this->m_parent->OnDbgHelpErr("SymGetSearchPath", GetLastError(), 0); } char szUserName[1024] = {0}; DWORD dwSize = 1024; GetUserNameA(szUserName, &dwSize); this->m_parent->OnSymInit(buf, symOptions, szUserName); return TRUE; } StackWalker *m_parent; HMODULE m_hDbhHelp; HANDLE m_hProcess; LPSTR m_szSymPath; #pragma pack(push,8) typedef struct IMAGEHLP_MODULE64_V3 { DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) DWORD64 BaseOfImage; // base load address of module DWORD ImageSize; // virtual size of the loaded module DWORD TimeDateStamp; // date/time stamp from pe header DWORD CheckSum; // checksum from the pe header DWORD NumSyms; // number of symbols in the symbol table SYM_TYPE SymType; // type of symbols loaded CHAR ModuleName[32]; // module name CHAR ImageName[256]; // image name CHAR LoadedImageName[256]; // symbol file name // new elements: 07-Jun-2002 CHAR LoadedPdbName[256]; // pdb file name DWORD CVSig; // Signature of the CV record in the debug directories CHAR CVData[MAX_PATH * 3]; // Contents of the CV record DWORD PdbSig; // Signature of PDB GUID PdbSig70; // Signature of PDB (VC 7 and up) DWORD PdbAge; // DBI age of pdb BOOL PdbUnmatched; // loaded an unmatched pdb BOOL DbgUnmatched; // loaded an unmatched dbg BOOL LineNumbers; // we have line number information BOOL GlobalSymbols; // we have internal symbol information BOOL TypeInfo; // we have type information // new elements: 17-Dec-2003 BOOL SourceIndexed; // pdb supports source server BOOL Publics; // contains public symbols }; typedef struct IMAGEHLP_MODULE64_V2 { DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) DWORD64 BaseOfImage; // base load address of module DWORD ImageSize; // virtual size of the loaded module DWORD TimeDateStamp; // date/time stamp from pe header DWORD CheckSum; // checksum from the pe header DWORD NumSyms; // number of symbols in the symbol table SYM_TYPE SymType; // type of symbols loaded CHAR ModuleName[32]; // module name CHAR ImageName[256]; // image name CHAR LoadedImageName[256]; // symbol file name }; #pragma pack(pop) // SymCleanup() typedef BOOL (__stdcall *tSC)( IN HANDLE hProcess ); tSC pSC; // SymFunctionTableAccess64() typedef PVOID (__stdcall *tSFTA)( HANDLE hProcess, DWORD64 AddrBase ); tSFTA pSFTA; // SymGetLineFromAddr64() typedef BOOL (__stdcall *tSGLFA)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line ); tSGLFA pSGLFA; // SymGetModuleBase64() typedef DWORD64 (__stdcall *tSGMB)( IN HANDLE hProcess, IN DWORD64 dwAddr ); tSGMB pSGMB; // SymGetModuleInfo64() typedef BOOL (__stdcall *tSGMI)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT IMAGEHLP_MODULE64_V3 *ModuleInfo ); tSGMI pSGMI; // SymGetOptions() typedef DWORD (__stdcall *tSGO)( VOID ); tSGO pSGO; // SymGetSymFromAddr64() typedef BOOL (__stdcall *tSGSFA)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD64 pdwDisplacement, OUT PIMAGEHLP_SYMBOL64 Symbol ); tSGSFA pSGSFA; // SymInitialize() typedef BOOL (__stdcall *tSI)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess ); tSI pSI; // SymLoadModule64() typedef DWORD64 (__stdcall *tSLM)( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ); tSLM pSLM; // SymSetOptions() typedef DWORD (__stdcall *tSSO)( IN DWORD SymOptions ); tSSO pSSO; // StackWalk64() typedef BOOL (__stdcall *tSW)( DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ); tSW pSW; // UnDecorateSymbolName() typedef DWORD (__stdcall WINAPI *tUDSN)( PCSTR DecoratedName, PSTR UnDecoratedName, DWORD UndecoratedLength, DWORD Flags ); tUDSN pUDSN; typedef BOOL (__stdcall WINAPI *tSGSP)(HANDLE hProcess, PSTR SearchPath, DWORD SearchPathLength); tSGSP pSGSP; private: // **************************************** ToolHelp32 ************************ #define MAX_MODULE_NAME32 255 #define TH32CS_SNAPMODULE 0x00000008 #pragma pack( push, 8 ) typedef struct tagMODULEENTRY32 { DWORD dwSize; DWORD th32ModuleID; // This module DWORD th32ProcessID; // owning process DWORD GlblcntUsage; // Global usage count on the module DWORD ProccntUsage; // Module usage count in th32ProcessID's context BYTE * modBaseAddr; // Base address of module in th32ProcessID's context DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr HMODULE hModule; // The hModule of this module in th32ProcessID's context char szModule[MAX_MODULE_NAME32 + 1]; char szExePath[MAX_PATH]; } MODULEENTRY32; typedef MODULEENTRY32 * PMODULEENTRY32; typedef MODULEENTRY32 * LPMODULEENTRY32; #pragma pack( pop ) BOOL GetModuleListTH32(HANDLE hProcess, DWORD pid) { // CreateToolhelp32Snapshot() typedef HANDLE (__stdcall *tCT32S)(DWORD dwFlags, DWORD th32ProcessID); // Module32First() typedef BOOL (__stdcall *tM32F)(HANDLE hSnapshot, LPMODULEENTRY32 lpme); // Module32Next() typedef BOOL (__stdcall *tM32N)(HANDLE hSnapshot, LPMODULEENTRY32 lpme); // try both dlls... const TCHAR *dllname[] = { _T("kernel32.dll"), _T("tlhelp32.dll") }; HINSTANCE hToolhelp = NULL; tCT32S pCT32S = NULL; tM32F pM32F = NULL; tM32N pM32N = NULL; HANDLE hSnap; MODULEENTRY32 me; me.dwSize = sizeof(me); BOOL keepGoing; size_t i; for (i = 0; i<(sizeof(dllname) / sizeof(dllname[0])); i++ ) { hToolhelp = LoadLibrary( dllname[i] ); if (hToolhelp == NULL) continue; pCT32S = (tCT32S) GetProcAddress(hToolhelp, "CreateToolhelp32Snapshot"); pM32F = (tM32F) GetProcAddress(hToolhelp, "Module32First"); pM32N = (tM32N) GetProcAddress(hToolhelp, "Module32Next"); if ( (pCT32S != NULL) && (pM32F != NULL) && (pM32N != NULL) ) break; // found the functions! FreeLibrary(hToolhelp); hToolhelp = NULL; } if (hToolhelp == NULL) return FALSE; hSnap = pCT32S( TH32CS_SNAPMODULE, pid ); if (hSnap == (HANDLE) -1) { FreeLibrary(hToolhelp); return FALSE; } keepGoing = !!pM32F( hSnap, &me ); int cnt = 0; while (keepGoing) { this->LoadModule(hProcess, me.szExePath, me.szModule, (DWORD64) me.modBaseAddr, me.modBaseSize); cnt++; keepGoing = !!pM32N( hSnap, &me ); } CloseHandle(hSnap); FreeLibrary(hToolhelp); if (cnt <= 0) return FALSE; return TRUE; } // GetModuleListTH32 // **************************************** PSAPI ************************ typedef struct _MODULEINFO { LPVOID lpBaseOfDll; DWORD SizeOfImage; LPVOID EntryPoint; } MODULEINFO, *LPMODULEINFO; BOOL GetModuleListPSAPI(HANDLE hProcess) { // EnumProcessModules() typedef BOOL (__stdcall *tEPM)(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded ); // GetModuleFileNameEx() typedef DWORD (__stdcall *tGMFNE)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize ); // GetModuleBaseName() typedef DWORD (__stdcall *tGMBN)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize ); // GetModuleInformation() typedef BOOL (__stdcall *tGMI)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize ); HINSTANCE hPsapi; tEPM pEPM; tGMFNE pGMFNE; tGMBN pGMBN; tGMI pGMI; DWORD i; //ModuleEntry e; DWORD cbNeeded; MODULEINFO mi; HMODULE *hMods = 0; char *tt = NULL; char *tt2 = NULL; const SIZE_T TTBUFLEN = 8096; int cnt = 0; hPsapi = LoadLibrary( _T("psapi.dll") ); if (hPsapi == NULL) return FALSE; pEPM = (tEPM) GetProcAddress( hPsapi, "EnumProcessModules" ); pGMFNE = (tGMFNE) GetProcAddress( hPsapi, "GetModuleFileNameExA" ); pGMBN = (tGMFNE) GetProcAddress( hPsapi, "GetModuleBaseNameA" ); pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" ); if ( (pEPM == NULL) || (pGMFNE == NULL) || (pGMBN == NULL) || (pGMI == NULL) ) { // we couldnt find all functions FreeLibrary(hPsapi); return FALSE; } hMods = (HMODULE*) malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof(HMODULE))); tt = (char*) malloc(sizeof(char) * TTBUFLEN); tt2 = (char*) malloc(sizeof(char) * TTBUFLEN); if ( (hMods == NULL) || (tt == NULL) || (tt2 == NULL) ) goto cleanup; if ( ! pEPM( hProcess, hMods, TTBUFLEN, &cbNeeded ) ) { //_ftprintf(fLogFile, _T("%lu: EPM failed, GetLastError = %lu\n"), g_dwShowCount, gle ); goto cleanup; } if ( cbNeeded > TTBUFLEN ) { //_ftprintf(fLogFile, _T("%lu: More than %lu module handles. Huh?\n"), g_dwShowCount, lenof( hMods ) ); goto cleanup; } for ( i = 0; i < cbNeeded / sizeof(hMods[0]); i++ ) { // base address, size pGMI(hProcess, hMods[i], &mi, sizeof(mi)); // image file name tt[0] = 0; pGMFNE(hProcess, hMods[i], tt, TTBUFLEN ); // module name tt2[0] = 0; pGMBN(hProcess, hMods[i], tt2, TTBUFLEN ); DWORD dwRes = this->LoadModule(hProcess, tt, tt2, (DWORD64) mi.lpBaseOfDll, mi.SizeOfImage); if (dwRes != ERROR_SUCCESS) this->m_parent->OnDbgHelpErr("LoadModule", dwRes, 0); cnt++; } cleanup: if (hPsapi != NULL) FreeLibrary(hPsapi); if (tt2 != NULL) free(tt2); if (tt != NULL) free(tt); if (hMods != NULL) free(hMods); return cnt != 0; } // GetModuleListPSAPI DWORD LoadModule(HANDLE hProcess, LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size) { CHAR *szImg = _strdup(img); CHAR *szMod = _strdup(mod); DWORD result = ERROR_SUCCESS; if ( (szImg == NULL) || (szMod == NULL) ) result = ERROR_NOT_ENOUGH_MEMORY; else { if (pSLM(hProcess, 0, szImg, szMod, baseAddr, size) == 0) result = GetLastError(); } ULONGLONG fileVersion = 0; if ( (m_parent != NULL) && (szImg != NULL) ) { // try to retrive the file-version: if ( (this->m_parent->m_options & StackWalker::RetrieveFileVersion) != 0) { VS_FIXEDFILEINFO *fInfo = NULL; DWORD dwHandle; DWORD dwSize = GetFileVersionInfoSizeA(szImg, &dwHandle); if (dwSize > 0) { LPVOID vData = malloc(dwSize); if (vData != NULL) { if (GetFileVersionInfoA(szImg, dwHandle, dwSize, vData) != 0) { UINT len; TCHAR szSubBlock[] = _T("\\"); if (VerQueryValue(vData, szSubBlock, (LPVOID*) &fInfo, &len) == 0) fInfo = NULL; else { fileVersion = ((ULONGLONG)fInfo->dwFileVersionLS) + ((ULONGLONG)fInfo->dwFileVersionMS << 32); } } free(vData); } } } // Retrive some additional-infos about the module IMAGEHLP_MODULE64_V3 Module; const char *szSymType = "-unknown-"; if (this->GetModuleInfo(hProcess, baseAddr, &Module) != FALSE) { switch(Module.SymType) { case SymNone: szSymType = "-nosymbols-"; break; case SymCoff: // 1 szSymType = "COFF"; break; case SymCv: // 2 szSymType = "CV"; break; case SymPdb: // 3 szSymType = "PDB"; break; case SymExport: // 4 szSymType = "-exported-"; break; case SymDeferred: // 5 szSymType = "-deferred-"; break; case SymSym: // 6 szSymType = "SYM"; break; case 7: // SymDia: szSymType = "DIA"; break; case 8: //SymVirtual: szSymType = "Virtual"; break; } } LPCSTR pdbName = Module.LoadedImageName; if (Module.LoadedPdbName[0] != 0) pdbName = Module.LoadedPdbName; this->m_parent->OnLoadModule(img, mod, baseAddr, size, result, szSymType, pdbName, fileVersion); } if (szImg != NULL) free(szImg); if (szMod != NULL) free(szMod); return result; } public: BOOL LoadModules(HANDLE hProcess, DWORD dwProcessId) { // first try toolhelp32 if (GetModuleListTH32(hProcess, dwProcessId)) return true; // then try psapi return GetModuleListPSAPI(hProcess); } BOOL GetModuleInfo(HANDLE hProcess, DWORD64 baseAddr, IMAGEHLP_MODULE64_V3 *pModuleInfo) { memset(pModuleInfo, 0, sizeof(IMAGEHLP_MODULE64_V3)); if(this->pSGMI == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; } // First try to use the larger ModuleInfo-Structure pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3); void *pData = malloc(4096); // reserve enough memory, so the bug in v6.3.5.1 does not lead to memory-overwrites... if (pData == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V3)); static bool s_useV3Version = true; if (s_useV3Version) { if (this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V3*) pData) != FALSE) { // only copy as much memory as is reserved... memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V3)); pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3); free(pData); return TRUE; } s_useV3Version = false; // to prevent unneccessarry calls with the larger struct... } // could not retrive the bigger structure, try with the smaller one (as defined in VC7.1)... pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2); memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V2)); if (this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V3*) pData) != FALSE) { // only copy as much memory as is reserved... memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V2)); pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2); free(pData); return TRUE; } free(pData); SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; } }; // ############################################################# StackWalker::StackWalker(DWORD dwProcessId, HANDLE hProcess) { this->m_options = OptionsAll; this->m_modulesLoaded = FALSE; this->m_hProcess = hProcess; this->m_sw = new StackWalkerInternal(this, this->m_hProcess); this->m_dwProcessId = dwProcessId; this->m_szSymPath = NULL; this->m_MaxRecursionCount = 1000; } StackWalker::StackWalker(int options, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess) { this->m_options = options; this->m_modulesLoaded = FALSE; this->m_hProcess = hProcess; this->m_sw = new StackWalkerInternal(this, this->m_hProcess); this->m_dwProcessId = dwProcessId; if (szSymPath != NULL) { this->m_szSymPath = _strdup(szSymPath); this->m_options |= SymBuildPath; } else this->m_szSymPath = NULL; this->m_MaxRecursionCount = 1000; } StackWalker::~StackWalker() { if (m_szSymPath != NULL) free(m_szSymPath); m_szSymPath = NULL; if (this->m_sw != NULL) delete this->m_sw; this->m_sw = NULL; } BOOL StackWalker::LoadModules() { if (this->m_sw == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; } if (m_modulesLoaded != FALSE) return TRUE; // Build the sym-path: char *szSymPath = NULL; if ( (this->m_options & SymBuildPath) != 0) { const size_t nSymPathLen = 4096; szSymPath = (char*) malloc(nSymPathLen); if (szSymPath == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } szSymPath[0] = 0; // Now first add the (optional) provided sympath: if (this->m_szSymPath != NULL) { strcat_s(szSymPath, nSymPathLen, this->m_szSymPath); strcat_s(szSymPath, nSymPathLen, ";"); } strcat_s(szSymPath, nSymPathLen, ".;"); const size_t nTempLen = 1024; char szTemp[nTempLen]; // Now add the current directory: if (GetCurrentDirectoryA(nTempLen, szTemp) > 0) { szTemp[nTempLen-1] = 0; strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); } // Now add the path for the main-module: if (GetModuleFileNameA(NULL, szTemp, nTempLen) > 0) { szTemp[nTempLen-1] = 0; for (char *p = (szTemp+strlen(szTemp)-1); p >= szTemp; --p) { // locate the rightmost path separator if ( (*p == '\\') || (*p == '/') || (*p == ':') ) { *p = 0; break; } } // for (search for path separator...) if (strlen(szTemp) > 0) { strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); } } if (GetEnvironmentVariableA("_NT_SYMBOL_PATH", szTemp, nTempLen) > 0) { szTemp[nTempLen-1] = 0; strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); } if (GetEnvironmentVariableA("_NT_ALTERNATE_SYMBOL_PATH", szTemp, nTempLen) > 0) { szTemp[nTempLen-1] = 0; strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); } if (GetEnvironmentVariableA("SYSTEMROOT", szTemp, nTempLen) > 0) { szTemp[nTempLen-1] = 0; strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); // also add the "system32"-directory: strcat_s(szTemp, nTempLen, "\\system32"); strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); } if ( (this->m_options & SymUseSymSrv) != 0) { if (GetEnvironmentVariableA("SYSTEMDRIVE", szTemp, nTempLen) > 0) { szTemp[nTempLen-1] = 0; strcat_s(szSymPath, nSymPathLen, "SRV*"); strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, "\\websymbols"); strcat_s(szSymPath, nSymPathLen, "*http://msdl.microsoft.com/download/symbols;"); } else strcat_s(szSymPath, nSymPathLen, "SRV*c:\\websymbols*http://msdl.microsoft.com/download/symbols;"); } } // if SymBuildPath // First Init the whole stuff... BOOL bRet = this->m_sw->Init(szSymPath); if (szSymPath != NULL) free(szSymPath); szSymPath = NULL; if (bRet == FALSE) { this->OnDbgHelpErr("Error while initializing dbghelp.dll", 0, 0); SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; } bRet = this->m_sw->LoadModules(this->m_hProcess, this->m_dwProcessId); if (bRet != FALSE) m_modulesLoaded = TRUE; return bRet; } // The following is used to pass the "userData"-Pointer to the user-provided readMemoryFunction // This has to be done due to a problem with the "hProcess"-parameter in x64... // Because this class is in no case multi-threading-enabled (because of the limitations // of dbghelp.dll) it is "safe" to use a static-variable static StackWalker::PReadProcessMemoryRoutine s_readMemoryFunction = NULL; static LPVOID s_readMemoryFunction_UserData = NULL; BOOL StackWalker::ShowCallstack(HANDLE hThread, const CONTEXT *context, PReadProcessMemoryRoutine readMemoryFunction, LPVOID pUserData) { CONTEXT c; CallstackEntry csEntry; IMAGEHLP_SYMBOL64 *pSym = NULL; StackWalkerInternal::IMAGEHLP_MODULE64_V3 Module; IMAGEHLP_LINE64 Line; int frameNum; bool bLastEntryCalled = true; int curRecursionCount = 0; if (m_modulesLoaded == FALSE) this->LoadModules(); // ignore the result... if (this->m_sw->m_hDbhHelp == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; } s_readMemoryFunction = readMemoryFunction; s_readMemoryFunction_UserData = pUserData; if (context == NULL) { // If no context is provided, capture the context // See: https://stackwalker.codeplex.com/discussions/446958 #if true || _WIN32_WINNT <= 0x0501 // If we need to support XP, we need to use the "old way", because "GetThreadId" is not available! if (hThread == GetCurrentThread()) #else if (GetThreadId(hThread) == GetCurrentThreadId()) #endif { GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, USED_CONTEXT_FLAGS); } else { SuspendThread(hThread); memset(&c, 0, sizeof(CONTEXT)); c.ContextFlags = USED_CONTEXT_FLAGS; if (GetThreadContext(hThread, &c) == FALSE) { ResumeThread(hThread); return FALSE; } } } else c = *context; // init STACKFRAME for first call STACKFRAME64 s; // in/out stackframe memset(&s, 0, sizeof(s)); DWORD imageType; #ifdef _M_IX86 // normally, call ImageNtHeader() and use machine info from PE header imageType = IMAGE_FILE_MACHINE_I386; s.AddrPC.Offset = c.Eip; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.Ebp; s.AddrFrame.Mode = AddrModeFlat; s.AddrStack.Offset = c.Esp; s.AddrStack.Mode = AddrModeFlat; #elif _M_X64 imageType = IMAGE_FILE_MACHINE_AMD64; s.AddrPC.Offset = c.Rip; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.Rsp; s.AddrFrame.Mode = AddrModeFlat; s.AddrStack.Offset = c.Rsp; s.AddrStack.Mode = AddrModeFlat; #elif _M_IA64 imageType = IMAGE_FILE_MACHINE_IA64; s.AddrPC.Offset = c.StIIP; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.IntSp; s.AddrFrame.Mode = AddrModeFlat; s.AddrBStore.Offset = c.RsBSP; s.AddrBStore.Mode = AddrModeFlat; s.AddrStack.Offset = c.IntSp; s.AddrStack.Mode = AddrModeFlat; #else #error "Platform not supported!" #endif pSym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN); if (!pSym) goto cleanup; // not enough memory... memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN); pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); pSym->MaxNameLength = STACKWALK_MAX_NAMELEN; memset(&Line, 0, sizeof(Line)); Line.SizeOfStruct = sizeof(Line); memset(&Module, 0, sizeof(Module)); Module.SizeOfStruct = sizeof(Module); for (frameNum = 0; ; ++frameNum ) { // get next stack frame (StackWalk64(), SymFunctionTableAccess64(), SymGetModuleBase64()) // if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can // assume that either you are done, or that the stack is so hosed that the next // deeper frame could not be found. // CONTEXT need not to be suplied if imageTyp is IMAGE_FILE_MACHINE_I386! if ( ! this->m_sw->pSW(imageType, this->m_hProcess, hThread, &s, &c, myReadProcMem, this->m_sw->pSFTA, this->m_sw->pSGMB, NULL) ) { // INFO: "StackWalk64" does not set "GetLastError"... this->OnDbgHelpErr("StackWalk64", 0, s.AddrPC.Offset); break; } csEntry.offset = s.AddrPC.Offset; csEntry.name[0] = 0; csEntry.undName[0] = 0; csEntry.undFullName[0] = 0; csEntry.offsetFromSmybol = 0; csEntry.offsetFromLine = 0; csEntry.lineFileName[0] = 0; csEntry.lineNumber = 0; csEntry.loadedImageName[0] = 0; csEntry.moduleName[0] = 0; if (s.AddrPC.Offset == s.AddrReturn.Offset) { if ( (this->m_MaxRecursionCount > 0) && (curRecursionCount > m_MaxRecursionCount) ) { this->OnDbgHelpErr("StackWalk64-Endless-Callstack!", 0, s.AddrPC.Offset); break; } curRecursionCount++; } else curRecursionCount = 0; if (s.AddrPC.Offset != 0) { // we seem to have a valid PC // show procedure info (SymGetSymFromAddr64()) if (this->m_sw->pSGSFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromSmybol), pSym) != FALSE) { MyStrCpy(csEntry.name, STACKWALK_MAX_NAMELEN, pSym->Name); // UnDecorateSymbolName() this->m_sw->pUDSN( pSym->Name, csEntry.undName, STACKWALK_MAX_NAMELEN, UNDNAME_NAME_ONLY ); this->m_sw->pUDSN( pSym->Name, csEntry.undFullName, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE ); } else { this->OnDbgHelpErr("SymGetSymFromAddr64", GetLastError(), s.AddrPC.Offset); } // show line number info, NT5.0-method (SymGetLineFromAddr64()) if (this->m_sw->pSGLFA != NULL ) { // yes, we have SymGetLineFromAddr64() if (this->m_sw->pSGLFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromLine), &Line) != FALSE) { csEntry.lineNumber = Line.LineNumber; MyStrCpy(csEntry.lineFileName, STACKWALK_MAX_NAMELEN, Line.FileName); } else { this->OnDbgHelpErr("SymGetLineFromAddr64", GetLastError(), s.AddrPC.Offset); } } // yes, we have SymGetLineFromAddr64() // show module info (SymGetModuleInfo64()) if (this->m_sw->GetModuleInfo(this->m_hProcess, s.AddrPC.Offset, &Module ) != FALSE) { // got module info OK switch ( Module.SymType ) { case SymNone: csEntry.symTypeString = "-nosymbols-"; break; case SymCoff: csEntry.symTypeString = "COFF"; break; case SymCv: csEntry.symTypeString = "CV"; break; case SymPdb: csEntry.symTypeString = "PDB"; break; case SymExport: csEntry.symTypeString = "-exported-"; break; case SymDeferred: csEntry.symTypeString = "-deferred-"; break; case SymSym: csEntry.symTypeString = "SYM"; break; #if API_VERSION_NUMBER >= 9 case SymDia: csEntry.symTypeString = "DIA"; break; #endif case 8: //SymVirtual: csEntry.symTypeString = "Virtual"; break; default: //_snprintf( ty, sizeof(ty), "symtype=%ld", (long) Module.SymType ); csEntry.symTypeString = NULL; break; } MyStrCpy(csEntry.moduleName, STACKWALK_MAX_NAMELEN, Module.ModuleName); csEntry.baseOfImage = Module.BaseOfImage; MyStrCpy(csEntry.loadedImageName, STACKWALK_MAX_NAMELEN, Module.LoadedImageName); } // got module info OK else { this->OnDbgHelpErr("SymGetModuleInfo64", GetLastError(), s.AddrPC.Offset); } } // we seem to have a valid PC CallstackEntryType et = nextEntry; if (frameNum == 0) et = firstEntry; bLastEntryCalled = false; this->OnCallstackEntry(et, csEntry); if (s.AddrReturn.Offset == 0) { bLastEntryCalled = true; this->OnCallstackEntry(lastEntry, csEntry); SetLastError(ERROR_SUCCESS); break; } } // for ( frameNum ) cleanup: if (pSym) free( pSym ); if (bLastEntryCalled == false) this->OnCallstackEntry(lastEntry, csEntry); if (context == NULL) ResumeThread(hThread); return TRUE; } BOOL __stdcall StackWalker::myReadProcMem( HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead ) { if (s_readMemoryFunction == NULL) { SIZE_T st; BOOL bRet = ReadProcessMemory(hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, &st); *lpNumberOfBytesRead = (DWORD) st; //printf("ReadMemory: hProcess: %p, baseAddr: %p, buffer: %p, size: %d, read: %d, result: %d\n", hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, (DWORD) st, (DWORD) bRet); return bRet; } else { return s_readMemoryFunction(hProcess, qwBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead, s_readMemoryFunction_UserData); } } void StackWalker::OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion) { CHAR buffer[STACKWALK_MAX_NAMELEN]; if (fileVersion == 0) _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s'\n", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName); else { DWORD v4 = (DWORD) (fileVersion & 0xFFFF); DWORD v3 = (DWORD) ((fileVersion>>16) & 0xFFFF); DWORD v2 = (DWORD) ((fileVersion>>32) & 0xFFFF); DWORD v1 = (DWORD) ((fileVersion>>48) & 0xFFFF); _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s', fileVersion: %d.%d.%d.%d\n", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName, v1, v2, v3, v4); } OnOutput(buffer); } void StackWalker::OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry) { CHAR buffer[STACKWALK_MAX_NAMELEN]; if ( (eType != lastEntry) && (entry.offset != 0) ) { if (entry.name[0] == 0) MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, "(function-name not available)"); if (entry.undName[0] != 0) MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undName); if (entry.undFullName[0] != 0) MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undFullName); if (entry.lineFileName[0] == 0) { MyStrCpy(entry.lineFileName, STACKWALK_MAX_NAMELEN, "(filename not available)"); if (entry.moduleName[0] == 0) MyStrCpy(entry.moduleName, STACKWALK_MAX_NAMELEN, "(module-name not available)"); _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%p (%s): %s: %s\n", (LPVOID) entry.offset, entry.moduleName, entry.lineFileName, entry.name); } else _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s (%d): %s\n", entry.lineFileName, entry.lineNumber, entry.name); buffer[STACKWALK_MAX_NAMELEN-1] = 0; OnOutput(buffer); } } void StackWalker::OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr) { CHAR buffer[STACKWALK_MAX_NAMELEN]; _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "ERROR: %s, GetLastError: %d (Address: %p)\n", szFuncName, gle, (LPVOID) addr); OnOutput(buffer); } void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName) { CHAR buffer[STACKWALK_MAX_NAMELEN]; _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "SymInit: Symbol-SearchPath: '%s', symOptions: %d, UserName: '%s'\n", szSearchPath, symOptions, szUserName); OnOutput(buffer); // Also display the OS-version #if _MSC_VER <= 1200 OSVERSIONINFOA ver; ZeroMemory(&ver, sizeof(OSVERSIONINFOA)); ver.dwOSVersionInfoSize = sizeof(ver); if (GetVersionExA(&ver) != FALSE) { _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s)\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion); OnOutput(buffer); } #else OSVERSIONINFOEXA ver; ZeroMemory(&ver, sizeof(OSVERSIONINFOEXA)); ver.dwOSVersionInfoSize = sizeof(ver); if (GetVersionExA( (OSVERSIONINFOA*) &ver) != FALSE) { _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion, ver.wSuiteMask, ver.wProductType); OnOutput(buffer); } #endif } void StackWalker::OnOutput(LPCSTR buffer) { OutputDebugStringA(buffer); } treesheets-1.0.2/lobster/include/StackWalker/StackWalker.h000066400000000000000000000167151352107072600235760ustar00rootroot00000000000000/********************************************************************** * * StackWalker.h * * * * LICENSE (http://www.opensource.org/licenses/bsd-license.php) * * Copyright (c) 2005-2009, Jochen Kalmbach * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * Neither the name of Jochen Kalmbach nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * **********************************************************************/ // #pragma once is supported starting with _MCS_VER 1000, // so we need not to check the version (because we only support _MSC_VER >= 1100)! #pragma once #include // special defines for VC5/6 (if no actual PSDK is installed): #if _MSC_VER < 1300 typedef unsigned __int64 DWORD64, *PDWORD64; #if defined(_WIN64) typedef unsigned __int64 SIZE_T, *PSIZE_T; #else typedef unsigned long SIZE_T, *PSIZE_T; #endif #endif // _MSC_VER < 1300 class StackWalkerInternal; // forward class StackWalker { public: typedef enum StackWalkOptions { // No addition info will be retrived // (only the address is available) RetrieveNone = 0, // Try to get the symbol-name RetrieveSymbol = 1, // Try to get the line for this symbol RetrieveLine = 2, // Try to retrieve the module-infos RetrieveModuleInfo = 4, // Also retrieve the version for the DLL/EXE RetrieveFileVersion = 8, // Contains all the abouve RetrieveVerbose = 0xF, // Generate a "good" symbol-search-path SymBuildPath = 0x10, // Also use the public Microsoft-Symbol-Server SymUseSymSrv = 0x20, // Contains all the abouve "Sym"-options SymAll = 0x30, // Contains all options (default) OptionsAll = 0x3F } StackWalkOptions; StackWalker( int options = OptionsAll, // 'int' is by design, to combine the enum-flags LPCSTR szSymPath = NULL, DWORD dwProcessId = GetCurrentProcessId(), HANDLE hProcess = GetCurrentProcess() ); StackWalker(DWORD dwProcessId, HANDLE hProcess); virtual ~StackWalker(); typedef BOOL (__stdcall *PReadProcessMemoryRoutine)( HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead, LPVOID pUserData // optional data, which was passed in "ShowCallstack" ); BOOL LoadModules(); BOOL ShowCallstack( HANDLE hThread = GetCurrentThread(), const CONTEXT *context = NULL, PReadProcessMemoryRoutine readMemoryFunction = NULL, LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback ); #if _MSC_VER >= 1300 // due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public" // in older compilers in order to use it... starting with VC7 we can declare it as "protected" protected: #endif enum { STACKWALK_MAX_NAMELEN = 1024 }; // max name length for found symbols protected: // Entry for each Callstack-Entry typedef struct CallstackEntry { DWORD64 offset; // if 0, we have no valid entry CHAR name[STACKWALK_MAX_NAMELEN]; CHAR undName[STACKWALK_MAX_NAMELEN]; CHAR undFullName[STACKWALK_MAX_NAMELEN]; DWORD64 offsetFromSmybol; DWORD offsetFromLine; DWORD lineNumber; CHAR lineFileName[STACKWALK_MAX_NAMELEN]; DWORD symType; LPCSTR symTypeString; CHAR moduleName[STACKWALK_MAX_NAMELEN]; DWORD64 baseOfImage; CHAR loadedImageName[STACKWALK_MAX_NAMELEN]; } CallstackEntry; typedef enum CallstackEntryType {firstEntry, nextEntry, lastEntry}; virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName); virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion); virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry); virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr); virtual void OnOutput(LPCSTR szText); StackWalkerInternal *m_sw; HANDLE m_hProcess; DWORD m_dwProcessId; BOOL m_modulesLoaded; LPSTR m_szSymPath; int m_options; int m_MaxRecursionCount; static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead); friend StackWalkerInternal; }; // class StackWalker // The "ugly" assembler-implementation is needed for systems before XP // If you have a new PSDK and you only compile for XP and later, then you can use // the "RtlCaptureContext" // Currently there is no define which determines the PSDK-Version... // So we just use the compiler-version (and assumes that the PSDK is // the one which was installed by the VS-IDE) // INFO: If you want, you can use the RtlCaptureContext if you only target XP and later... // But I currently use it in x64/IA64 environments... //#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400) #if defined(_M_IX86) #ifdef CURRENT_THREAD_VIA_EXCEPTION // TODO: The following is not a "good" implementation, // because the callstack is only valid in the "__except" block... #define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ do { \ memset(&c, 0, sizeof(CONTEXT)); \ EXCEPTION_POINTERS *pExp = NULL; \ __try { \ throw 0; \ } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \ if (pExp != NULL) \ memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \ c.ContextFlags = contextFlags; \ } while(0); #else // The following should be enough for walking the callstack... #define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ do { \ memset(&c, 0, sizeof(CONTEXT)); \ c.ContextFlags = contextFlags; \ __asm call x \ __asm x: pop eax \ __asm mov c.Eip, eax \ __asm mov c.Ebp, ebp \ __asm mov c.Esp, esp \ } while(0); #endif #else // The following is defined for x86 (XP and higher), x64 and IA64: #define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ do { \ memset(&c, 0, sizeof(CONTEXT)); \ c.ContextFlags = contextFlags; \ RtlCaptureContext(&c); \ } while(0); #endif treesheets-1.0.2/lobster/include/StackWalker/StackWalkerHelpers.cpp000066400000000000000000000132561352107072600254510ustar00rootroot00000000000000/********************************************************************** * * main.cpp * * * History: * 2008-11-27 v1 - Header added * Samples for Exception-Crashes added... * 2009-11-01 v2 - Moved to stackwalker.codeplex.com * **********************************************************************/ #include #include #include "StackWalker\stackwalker.h" static TCHAR s_szExceptionLogFileName[_MAX_PATH] = _T("\\exceptions.log"); // default static BOOL s_bUnhandledExeptionFilterSet = FALSE; static int argc = 0; static char **argv = nullptr; // Specialized stackwalker-output classes class CustomStackWalker : public StackWalker { public: virtual void OnOutput(LPCSTR szText) { auto hAppend = CreateFile(TEXT(s_szExceptionLogFileName), FILE_APPEND_DATA, // open for writing FILE_SHARE_READ, // allow multiple readers NULL, // no security OPEN_ALWAYS, // open or create FILE_ATTRIBUTE_NORMAL, // normal file NULL); // no attr. template WriteFile(hAppend, szText, strlen(szText), nullptr, nullptr); CloseHandle(hAppend); } // Don't care about all the module ouput. void OnLoadModule(LPCSTR, LPCSTR, DWORD64, DWORD, DWORD, LPCSTR, LPCSTR, ULONGLONG) {} }; // For more info about "PreventSetUnhandledExceptionFilter" see: // "SetUnhandledExceptionFilter" and VC8 // http://blog.kalmbachnet.de/?postid=75 // and // Unhandled exceptions in VC8 and above for x86 and x64 // http://blog.kalmbach-software.de/2008/04/02/unhandled-exceptions-in-vc8-and-above-for-x86-and-x64/ // Even better: // http://blog.kalmbach-software.de/2013/05/23/improvedpreventsetunhandledexceptionfilter/ #if defined _M_X64 || defined _M_IX86 static BOOL PreventSetUnhandledExceptionFilter() { HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll")); if (hKernel32 == NULL) return FALSE; void *pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter"); if (pOrgEntry == NULL) return FALSE; # ifdef _M_IX86 // Code for x86: // 33 C0 xor eax,eax // C2 04 00 ret 4 unsigned char szExecute[] = { 0x33, 0xC0, 0xC2, 0x04, 0x00 }; # elif _M_X64 // 33 C0 xor eax,eax // C3 ret unsigned char szExecute[] = { 0x33, 0xC0, 0xC3 }; # else # error "The following code only works for x86 and x64!" # endif DWORD dwOldProtect = 0; BOOL bProt = VirtualProtect(pOrgEntry, sizeof(szExecute), PAGE_EXECUTE_READWRITE, &dwOldProtect); SIZE_T bytesWritten = 0; BOOL bRet = WriteProcessMemory(GetCurrentProcess(), pOrgEntry, szExecute, sizeof(szExecute), &bytesWritten); if ((bProt != FALSE) && (dwOldProtect != PAGE_EXECUTE_READWRITE)) { DWORD dwBuf; VirtualProtect(pOrgEntry, sizeof(szExecute), dwOldProtect, &dwBuf); } return bRet; } #else # pragma message("This code works only for x86 and x64!") #endif static LONG __stdcall CrashHandlerExceptionFilter(EXCEPTION_POINTERS *pExPtrs) { # ifdef _M_IX86 if (pExPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) { static char MyStack[1024 * 128]; // be sure that we have enought space... // it assumes that DS and SS are the same!!! (this is the case for Win32) // change the stack only if the selectors are the same (this is the case for Win32) //__asm push offset MyStack[1024*128]; //__asm pop esp; __asm mov eax, offset MyStack[1024 * 128]; __asm mov esp, eax; } # endif CustomStackWalker sw; // output to console sw.OnOutput("===== Crash Log =====\n"); SYSTEMTIME st; GetSystemTime(&st); const size_t buflen = 1024; TCHAR lString[buflen] = { 0 }; sprintf_s(lString, buflen, _T("Date: %d-%02d-%02d, Time: %02d:%02d:%02d\n"), st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); sw.OnOutput(lString); sw.OnOutput("CMD: "); for (int i = 0; i < argc; i++) { sw.OnOutput(argv[i]); sw.OnOutput(" "); } sw.OnOutput("\n"); sprintf_s(lString, buflen, _T("ExpCode: 0x%8.8X, ExpFlags: %d, ExpAddress: 0x%8.8X\n"), pExPtrs->ExceptionRecord->ExceptionCode, pExPtrs->ExceptionRecord->ExceptionFlags, pExPtrs->ExceptionRecord->ExceptionAddress); sw.OnOutput(lString); sw.ShowCallstack(GetCurrentThread(), pExPtrs->ContextRecord); sw.OnOutput("\n"); sprintf_s(lString, buflen, _T("Please send %s to the developer!\n"), s_szExceptionLogFileName); for (int i = 1; i < argc; i++) { strcat_s(lString, buflen, argv[i]); strcat_s(lString, buflen, " "); } strcat_s(lString, buflen, "\n"); FatalAppExit(-1, lString); return EXCEPTION_CONTINUE_SEARCH; } void InitUnhandledExceptionFilter(int _argc, char *_argv[]) { argc = _argc; argv = _argv; TCHAR szModName[_MAX_PATH]; if (GetModuleFileName(NULL, szModName, sizeof(szModName) / sizeof(TCHAR)) != 0) { _tcscpy_s(s_szExceptionLogFileName, szModName); _tcscat_s(s_szExceptionLogFileName, _T(".exp.log")); } if (s_bUnhandledExeptionFilterSet == FALSE) { // set global exception handler (for handling all unhandled exceptions) SetUnhandledExceptionFilter(CrashHandlerExceptionFilter); # if defined _M_X64 || defined _M_IX86 PreventSetUnhandledExceptionFilter(); # endif s_bUnhandledExeptionFilterSet = TRUE; } } treesheets-1.0.2/lobster/include/StackWalker/StackWalkerHelpers.h000066400000000000000000000001031352107072600251010ustar00rootroot00000000000000extern void InitUnhandledExceptionFilter(int argc, char* argv[]); treesheets-1.0.2/lobster/include/flatbuffers/000077500000000000000000000000001352107072600212705ustar00rootroot00000000000000treesheets-1.0.2/lobster/include/flatbuffers/base.h000066400000000000000000000204531352107072600223570ustar00rootroot00000000000000#ifndef FLATBUFFERS_BASE_H_ #define FLATBUFFERS_BASE_H_ // clang-format off #if defined(FLATBUFFERS_MEMORY_LEAK_TRACKING) && \ defined(_MSC_VER) && defined(_DEBUG) #define _CRTDBG_MAP_ALLOC #endif #include #if !defined(FLATBUFFERS_ASSERT) #define FLATBUFFERS_ASSERT assert #endif #ifndef ARDUINO #include #endif #include #include #include #if defined(FLATBUFFERS_MEMORY_LEAK_TRACKING) && \ defined(_MSC_VER) && defined(_DEBUG) #include #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) #define new DEBUG_NEW #endif #if defined(ARDUINO) && !defined(ARDUINOSTL_M_H) #include #else #include #endif #include #include #include #include #include #include #include #ifdef _STLPORT_VERSION #define FLATBUFFERS_CPP98_STL #endif #ifndef FLATBUFFERS_CPP98_STL #include #endif #include "flatbuffers/stl_emulation.h" // Note the __clang__ check is needed, because clang presents itself // as an older GNUC compiler (4.2). // Clang 3.3 and later implement all of the ISO C++ 2011 standard. // Clang 3.4 and later implement all of the ISO C++ 2014 standard. // http://clang.llvm.org/cxx_status.html /// @cond FLATBUFFERS_INTERNAL #if __cplusplus <= 199711L && \ (!defined(_MSC_VER) || _MSC_VER < 1600) && \ (!defined(__GNUC__) || \ (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ < 40400)) #error A C++11 compatible compiler with support for the auto typing is \ required for FlatBuffers. #error __cplusplus _MSC_VER __GNUC__ __GNUC_MINOR__ __GNUC_PATCHLEVEL__ #endif #if !defined(__clang__) && \ defined(__GNUC__) && \ (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ < 40600) // Backwards compatability for g++ 4.4, and 4.5 which don't have the nullptr // and constexpr keywords. Note the __clang__ check is needed, because clang // presents itself as an older GNUC compiler. #ifndef nullptr_t const class nullptr_t { public: template inline operator T*() const { return 0; } private: void operator&() const; } nullptr = {}; #endif #ifndef constexpr #define constexpr const #endif #endif // The wire format uses a little endian encoding (since that's efficient for // the common platforms). #if defined(__s390x__) #define FLATBUFFERS_LITTLEENDIAN 0 #endif // __s390x__ #if !defined(FLATBUFFERS_LITTLEENDIAN) #if defined(__GNUC__) || defined(__clang__) #ifdef __BIG_ENDIAN__ #define FLATBUFFERS_LITTLEENDIAN 0 #else #define FLATBUFFERS_LITTLEENDIAN 1 #endif // __BIG_ENDIAN__ #elif defined(_MSC_VER) #if defined(_M_PPC) #define FLATBUFFERS_LITTLEENDIAN 0 #else #define FLATBUFFERS_LITTLEENDIAN 1 #endif #else #error Unable to determine endianness, define FLATBUFFERS_LITTLEENDIAN. #endif #endif // !defined(FLATBUFFERS_LITTLEENDIAN) #define FLATBUFFERS_VERSION_MAJOR 1 #define FLATBUFFERS_VERSION_MINOR 10 #define FLATBUFFERS_VERSION_REVISION 0 #define FLATBUFFERS_STRING_EXPAND(X) #X #define FLATBUFFERS_STRING(X) FLATBUFFERS_STRING_EXPAND(X) #if (!defined(_MSC_VER) || _MSC_VER > 1600) && \ (!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 407)) || \ defined(__clang__) #define FLATBUFFERS_FINAL_CLASS final #define FLATBUFFERS_OVERRIDE override #else #define FLATBUFFERS_FINAL_CLASS #define FLATBUFFERS_OVERRIDE #endif #if (!defined(_MSC_VER) || _MSC_VER >= 1900) && \ (!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 406)) || \ (defined(__cpp_constexpr) && __cpp_constexpr >= 200704) #define FLATBUFFERS_CONSTEXPR constexpr #else #define FLATBUFFERS_CONSTEXPR #endif #if (defined(__cplusplus) && __cplusplus >= 201402L) || \ (defined(__cpp_constexpr) && __cpp_constexpr >= 201304) #define FLATBUFFERS_CONSTEXPR_CPP14 FLATBUFFERS_CONSTEXPR #else #define FLATBUFFERS_CONSTEXPR_CPP14 #endif #if (defined(__GXX_EXPERIMENTAL_CXX0X__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 406)) || \ (defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 190023026)) || \ defined(__clang__) #define FLATBUFFERS_NOEXCEPT noexcept #else #define FLATBUFFERS_NOEXCEPT #endif // NOTE: the FLATBUFFERS_DELETE_FUNC macro may change the access mode to // private, so be sure to put it at the end or reset access mode explicitly. #if (!defined(_MSC_VER) || _MSC_FULL_VER >= 180020827) && \ (!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 404)) || \ defined(__clang__) #define FLATBUFFERS_DELETE_FUNC(func) func = delete; #else #define FLATBUFFERS_DELETE_FUNC(func) private: func; #endif #ifndef FLATBUFFERS_HAS_STRING_VIEW // Only provide flatbuffers::string_view if __has_include can be used // to detect a header that provides an implementation #if defined(__has_include) // Check for std::string_view (in c++17) #if __has_include() && (__cplusplus >= 201606 || _HAS_CXX17) #include namespace flatbuffers { typedef std::string_view string_view; } #define FLATBUFFERS_HAS_STRING_VIEW 1 // Check for std::experimental::string_view (in c++14, compiler-dependent) #elif __has_include() && (__cplusplus >= 201411) #include namespace flatbuffers { typedef std::experimental::string_view string_view; } #define FLATBUFFERS_HAS_STRING_VIEW 1 #endif #endif // __has_include #endif // !FLATBUFFERS_HAS_STRING_VIEW /// @endcond /// @file namespace flatbuffers { /// @cond FLATBUFFERS_INTERNAL // Our default offset / size type, 32bit on purpose on 64bit systems. // Also, using a consistent offset type maintains compatibility of serialized // offset values between 32bit and 64bit systems. typedef uint32_t uoffset_t; // Signed offsets for references that can go in both directions. typedef int32_t soffset_t; // Offset/index used in v-tables, can be changed to uint8_t in // format forks to save a bit of space if desired. typedef uint16_t voffset_t; typedef uintmax_t largest_scalar_t; // In 32bits, this evaluates to 2GB - 1 #define FLATBUFFERS_MAX_BUFFER_SIZE ((1ULL << (sizeof(soffset_t) * 8 - 1)) - 1) // We support aligning the contents of buffers up to this size. #define FLATBUFFERS_MAX_ALIGNMENT 16 #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable: 4127) // C4127: conditional expression is constant #endif template T EndianSwap(T t) { #if defined(_MSC_VER) #define FLATBUFFERS_BYTESWAP16 _byteswap_ushort #define FLATBUFFERS_BYTESWAP32 _byteswap_ulong #define FLATBUFFERS_BYTESWAP64 _byteswap_uint64 #else #if defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ < 408 && !defined(__clang__) // __builtin_bswap16 was missing prior to GCC 4.8. #define FLATBUFFERS_BYTESWAP16(x) \ static_cast(__builtin_bswap32(static_cast(x) << 16)) #else #define FLATBUFFERS_BYTESWAP16 __builtin_bswap16 #endif #define FLATBUFFERS_BYTESWAP32 __builtin_bswap32 #define FLATBUFFERS_BYTESWAP64 __builtin_bswap64 #endif if (sizeof(T) == 1) { // Compile-time if-then's. return t; } else if (sizeof(T) == 2) { union { T t; uint16_t i; } u; u.t = t; u.i = FLATBUFFERS_BYTESWAP16(u.i); return u.t; } else if (sizeof(T) == 4) { union { T t; uint32_t i; } u; u.t = t; u.i = FLATBUFFERS_BYTESWAP32(u.i); return u.t; } else if (sizeof(T) == 8) { union { T t; uint64_t i; } u; u.t = t; u.i = FLATBUFFERS_BYTESWAP64(u.i); return u.t; } else { FLATBUFFERS_ASSERT(0); } } #if defined(_MSC_VER) #pragma warning(pop) #endif template T EndianScalar(T t) { #if FLATBUFFERS_LITTLEENDIAN return t; #else return EndianSwap(t); #endif } template T ReadScalar(const void *p) { return EndianScalar(*reinterpret_cast(p)); } template void WriteScalar(void *p, T t) { *reinterpret_cast(p) = EndianScalar(t); } // Computes how many bytes you'd have to pad to be able to write an // "scalar_size" scalar if the buffer had grown to "buf_size" (downwards in // memory). inline size_t PaddingBytes(size_t buf_size, size_t scalar_size) { return ((~buf_size) + 1) & (scalar_size - 1); } } // namespace flatbuffers #endif // FLATBUFFERS_BASE_H_ treesheets-1.0.2/lobster/include/flatbuffers/code_generators.h000066400000000000000000000104661352107072600246130ustar00rootroot00000000000000/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_CODE_GENERATORS_H_ #define FLATBUFFERS_CODE_GENERATORS_H_ #include #include #include "flatbuffers/idl.h" namespace flatbuffers { // Utility class to assist in generating code through use of text templates. // // Example code: // CodeWriter code; // code.SetValue("NAME", "Foo"); // code += "void {{NAME}}() { printf("%s", "{{NAME}}"); }"; // code.SetValue("NAME", "Bar"); // code += "void {{NAME}}() { printf("%s", "{{NAME}}"); }"; // std::cout << code.ToString() << std::endl; // // Output: // void Foo() { printf("%s", "Foo"); } // void Bar() { printf("%s", "Bar"); } class CodeWriter { public: CodeWriter() {} // Clears the current "written" code. void Clear() { stream_.str(""); stream_.clear(); } // Associates a key with a value. All subsequent calls to operator+=, where // the specified key is contained in {{ and }} delimiters will be replaced by // the given value. void SetValue(const std::string &key, const std::string &value) { value_map_[key] = value; } // Appends the given text to the generated code as well as a newline // character. Any text within {{ and }} delimeters is replaced by values // previously stored in the CodeWriter by calling SetValue above. The newline // will be suppressed if the text ends with the \\ character. void operator+=(std::string text); // Returns the current contents of the CodeWriter as a std::string. std::string ToString() const { return stream_.str(); } private: std::map value_map_; std::stringstream stream_; }; class BaseGenerator { public: virtual bool generate() = 0; static std::string NamespaceDir(const Parser &parser, const std::string &path, const Namespace &ns); protected: BaseGenerator(const Parser &parser, const std::string &path, const std::string &file_name, const std::string qualifying_start, const std::string qualifying_separator) : parser_(parser), path_(path), file_name_(file_name), qualifying_start_(qualifying_start), qualifying_separator_(qualifying_separator) {} virtual ~BaseGenerator() {} // No copy/assign. BaseGenerator &operator=(const BaseGenerator &); BaseGenerator(const BaseGenerator &); std::string NamespaceDir(const Namespace &ns) const; static const char *FlatBuffersGeneratedWarning(); static std::string FullNamespace(const char *separator, const Namespace &ns); static std::string LastNamespacePart(const Namespace &ns); // tracks the current namespace for early exit in WrapInNameSpace // c++, java and csharp returns a different namespace from // the following default (no early exit, always fully qualify), // which works for js and php virtual const Namespace *CurrentNameSpace() const { return nullptr; } // Ensure that a type is prefixed with its namespace whenever it is used // outside of its namespace. std::string WrapInNameSpace(const Namespace *ns, const std::string &name) const; std::string WrapInNameSpace(const Definition &def) const; std::string GetNameSpace(const Definition &def) const; const Parser &parser_; const std::string &path_; const std::string &file_name_; const std::string qualifying_start_; const std::string qualifying_separator_; }; struct CommentConfig { const char *first_line; const char *content_line_prefix; const char *last_line; }; extern void GenComment(const std::vector &dc, std::string *code_ptr, const CommentConfig *config, const char *prefix = ""); } // namespace flatbuffers #endif // FLATBUFFERS_CODE_GENERATORS_H_ treesheets-1.0.2/lobster/include/flatbuffers/flatbuffers.h000066400000000000000000002622031352107072600237510ustar00rootroot00000000000000/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_H_ #define FLATBUFFERS_H_ #include "flatbuffers/base.h" namespace flatbuffers { // Wrapper for uoffset_t to allow safe template specialization. // Value is allowed to be 0 to indicate a null object (see e.g. AddOffset). template struct Offset { uoffset_t o; Offset() : o(0) {} Offset(uoffset_t _o) : o(_o) {} Offset Union() const { return Offset(o); } bool IsNull() const { return !o; } }; inline void EndianCheck() { int endiantest = 1; // If this fails, see FLATBUFFERS_LITTLEENDIAN above. FLATBUFFERS_ASSERT(*reinterpret_cast(&endiantest) == FLATBUFFERS_LITTLEENDIAN); (void)endiantest; } template FLATBUFFERS_CONSTEXPR size_t AlignOf() { // clang-format off #ifdef _MSC_VER return __alignof(T); #else #ifndef alignof return __alignof__(T); #else return alignof(T); #endif #endif // clang-format on } // When we read serialized data from memory, in the case of most scalars, // we want to just read T, but in the case of Offset, we want to actually // perform the indirection and return a pointer. // The template specialization below does just that. // It is wrapped in a struct since function templates can't overload on the // return type like this. // The typedef is for the convenience of callers of this function // (avoiding the need for a trailing return decltype) template struct IndirectHelper { typedef T return_type; typedef T mutable_return_type; static const size_t element_stride = sizeof(T); static return_type Read(const uint8_t *p, uoffset_t i) { return EndianScalar((reinterpret_cast(p))[i]); } }; template struct IndirectHelper> { typedef const T *return_type; typedef T *mutable_return_type; static const size_t element_stride = sizeof(uoffset_t); static return_type Read(const uint8_t *p, uoffset_t i) { p += i * sizeof(uoffset_t); return reinterpret_cast(p + ReadScalar(p)); } }; template struct IndirectHelper { typedef const T *return_type; typedef T *mutable_return_type; static const size_t element_stride = sizeof(T); static return_type Read(const uint8_t *p, uoffset_t i) { return reinterpret_cast(p + i * sizeof(T)); } }; // An STL compatible iterator implementation for Vector below, effectively // calling Get() for every element. template struct VectorIterator { typedef std::random_access_iterator_tag iterator_category; typedef IT value_type; typedef ptrdiff_t difference_type; typedef IT *pointer; typedef IT &reference; VectorIterator(const uint8_t *data, uoffset_t i) : data_(data + IndirectHelper::element_stride * i) {} VectorIterator(const VectorIterator &other) : data_(other.data_) {} VectorIterator &operator=(const VectorIterator &other) { data_ = other.data_; return *this; } VectorIterator &operator=(VectorIterator &&other) { data_ = other.data_; return *this; } bool operator==(const VectorIterator &other) const { return data_ == other.data_; } bool operator<(const VectorIterator &other) const { return data_ < other.data_; } bool operator!=(const VectorIterator &other) const { return data_ != other.data_; } difference_type operator-(const VectorIterator &other) const { return (data_ - other.data_) / IndirectHelper::element_stride; } IT operator*() const { return IndirectHelper::Read(data_, 0); } IT operator->() const { return IndirectHelper::Read(data_, 0); } VectorIterator &operator++() { data_ += IndirectHelper::element_stride; return *this; } VectorIterator operator++(int) { VectorIterator temp(data_, 0); data_ += IndirectHelper::element_stride; return temp; } VectorIterator operator+(const uoffset_t &offset) const { return VectorIterator(data_ + offset * IndirectHelper::element_stride, 0); } VectorIterator &operator+=(const uoffset_t &offset) { data_ += offset * IndirectHelper::element_stride; return *this; } VectorIterator &operator--() { data_ -= IndirectHelper::element_stride; return *this; } VectorIterator operator--(int) { VectorIterator temp(data_, 0); data_ -= IndirectHelper::element_stride; return temp; } VectorIterator operator-(const uoffset_t &offset) { return VectorIterator(data_ - offset * IndirectHelper::element_stride, 0); } VectorIterator &operator-=(const uoffset_t &offset) { data_ -= offset * IndirectHelper::element_stride; return *this; } private: const uint8_t *data_; }; struct String; // This is used as a helper type for accessing vectors. // Vector::data() assumes the vector elements start after the length field. template class Vector { public: typedef VectorIterator::mutable_return_type> iterator; typedef VectorIterator::return_type> const_iterator; uoffset_t size() const { return EndianScalar(length_); } // Deprecated: use size(). Here for backwards compatibility. uoffset_t Length() const { return size(); } typedef typename IndirectHelper::return_type return_type; typedef typename IndirectHelper::mutable_return_type mutable_return_type; return_type Get(uoffset_t i) const { FLATBUFFERS_ASSERT(i < size()); return IndirectHelper::Read(Data(), i); } return_type operator[](uoffset_t i) const { return Get(i); } // If this is a Vector of enums, T will be its storage type, not the enum // type. This function makes it convenient to retrieve value with enum // type E. template E GetEnum(uoffset_t i) const { return static_cast(Get(i)); } // If this a vector of unions, this does the cast for you. There's no check // to make sure this is the right type! template const U *GetAs(uoffset_t i) const { return reinterpret_cast(Get(i)); } // If this a vector of unions, this does the cast for you. There's no check // to make sure this is actually a string! const String *GetAsString(uoffset_t i) const { return reinterpret_cast(Get(i)); } const void *GetStructFromOffset(size_t o) const { return reinterpret_cast(Data() + o); } iterator begin() { return iterator(Data(), 0); } const_iterator begin() const { return const_iterator(Data(), 0); } iterator end() { return iterator(Data(), size()); } const_iterator end() const { return const_iterator(Data(), size()); } // Change elements if you have a non-const pointer to this object. // Scalars only. See reflection.h, and the documentation. void Mutate(uoffset_t i, const T &val) { FLATBUFFERS_ASSERT(i < size()); WriteScalar(data() + i, val); } // Change an element of a vector of tables (or strings). // "val" points to the new table/string, as you can obtain from // e.g. reflection::AddFlatBuffer(). void MutateOffset(uoffset_t i, const uint8_t *val) { FLATBUFFERS_ASSERT(i < size()); static_assert(sizeof(T) == sizeof(uoffset_t), "Unrelated types"); WriteScalar(data() + i, static_cast(val - (Data() + i * sizeof(uoffset_t)))); } // Get a mutable pointer to tables/strings inside this vector. mutable_return_type GetMutableObject(uoffset_t i) const { FLATBUFFERS_ASSERT(i < size()); return const_cast(IndirectHelper::Read(Data(), i)); } // The raw data in little endian format. Use with care. const uint8_t *Data() const { return reinterpret_cast(&length_ + 1); } uint8_t *Data() { return reinterpret_cast(&length_ + 1); } // Similarly, but typed, much like std::vector::data const T *data() const { return reinterpret_cast(Data()); } T *data() { return reinterpret_cast(Data()); } template return_type LookupByKey(K key) const { void *search_result = std::bsearch( &key, Data(), size(), IndirectHelper::element_stride, KeyCompare); if (!search_result) { return nullptr; // Key not found. } const uint8_t *element = reinterpret_cast(search_result); return IndirectHelper::Read(element, 0); } protected: // This class is only used to access pre-existing data. Don't ever // try to construct these manually. Vector(); uoffset_t length_; private: // This class is a pointer. Copying will therefore create an invalid object. // Private and unimplemented copy constructor. Vector(const Vector &); template static int KeyCompare(const void *ap, const void *bp) { const K *key = reinterpret_cast(ap); const uint8_t *data = reinterpret_cast(bp); auto table = IndirectHelper::Read(data, 0); // std::bsearch compares with the operands transposed, so we negate the // result here. return -table->KeyCompareWithValue(*key); } }; // Represent a vector much like the template above, but in this case we // don't know what the element types are (used with reflection.h). class VectorOfAny { public: uoffset_t size() const { return EndianScalar(length_); } const uint8_t *Data() const { return reinterpret_cast(&length_ + 1); } uint8_t *Data() { return reinterpret_cast(&length_ + 1); } protected: VectorOfAny(); uoffset_t length_; private: VectorOfAny(const VectorOfAny &); }; #ifndef FLATBUFFERS_CPP98_STL template Vector> *VectorCast(Vector> *ptr) { static_assert(std::is_base_of::value, "Unrelated types"); return reinterpret_cast> *>(ptr); } template const Vector> *VectorCast(const Vector> *ptr) { static_assert(std::is_base_of::value, "Unrelated types"); return reinterpret_cast> *>(ptr); } #endif // Convenient helper function to get the length of any vector, regardless // of whether it is null or not (the field is not set). template static inline size_t VectorLength(const Vector *v) { return v ? v->Length() : 0; } struct String : public Vector { const char *c_str() const { return reinterpret_cast(Data()); } std::string str() const { return std::string(c_str(), Length()); } // clang-format off #ifdef FLATBUFFERS_HAS_STRING_VIEW flatbuffers::string_view string_view() const { return flatbuffers::string_view(c_str(), Length()); } #endif // FLATBUFFERS_HAS_STRING_VIEW // clang-format on bool operator<(const String &o) const { return strcmp(c_str(), o.c_str()) < 0; } }; // Convenience function to get std::string from a String returning an empty // string on null pointer. static inline std::string GetString(const String * str) { return str ? str->str() : ""; } // Convenience function to get char* from a String returning an empty string on // null pointer. static inline const char * GetCstring(const String * str) { return str ? str->c_str() : ""; } // Allocator interface. This is flatbuffers-specific and meant only for // `vector_downward` usage. class Allocator { public: virtual ~Allocator() {} // Allocate `size` bytes of memory. virtual uint8_t *allocate(size_t size) = 0; // Deallocate `size` bytes of memory at `p` allocated by this allocator. virtual void deallocate(uint8_t *p, size_t size) = 0; // Reallocate `new_size` bytes of memory, replacing the old region of size // `old_size` at `p`. In contrast to a normal realloc, this grows downwards, // and is intended specifcally for `vector_downward` use. // `in_use_back` and `in_use_front` indicate how much of `old_size` is // actually in use at each end, and needs to be copied. virtual uint8_t *reallocate_downward(uint8_t *old_p, size_t old_size, size_t new_size, size_t in_use_back, size_t in_use_front) { FLATBUFFERS_ASSERT(new_size > old_size); // vector_downward only grows uint8_t *new_p = allocate(new_size); memcpy_downward(old_p, old_size, new_p, new_size, in_use_back, in_use_front); deallocate(old_p, old_size); return new_p; } protected: // Called by `reallocate_downward` to copy memory from `old_p` of `old_size` // to `new_p` of `new_size`. Only memory of size `in_use_front` and // `in_use_back` will be copied from the front and back of the old memory // allocation. void memcpy_downward(uint8_t *old_p, size_t old_size, uint8_t *new_p, size_t new_size, size_t in_use_back, size_t in_use_front) { memcpy(new_p + new_size - in_use_back, old_p + old_size - in_use_back, in_use_back); memcpy(new_p, old_p, in_use_front); } }; // DefaultAllocator uses new/delete to allocate memory regions class DefaultAllocator : public Allocator { public: uint8_t *allocate(size_t size) FLATBUFFERS_OVERRIDE { return new uint8_t[size]; } void deallocate(uint8_t *p, size_t) FLATBUFFERS_OVERRIDE { delete[] p; } }; // These functions allow for a null allocator to mean use the default allocator, // as used by DetachedBuffer and vector_downward below. // This is to avoid having a statically or dynamically allocated default // allocator, or having to move it between the classes that may own it. inline uint8_t *Allocate(Allocator *allocator, size_t size) { return allocator ? allocator->allocate(size) : DefaultAllocator().allocate(size); } inline void Deallocate(Allocator *allocator, uint8_t *p, size_t size) { if (allocator) allocator->deallocate(p, size); else DefaultAllocator().deallocate(p, size); } inline uint8_t *ReallocateDownward(Allocator *allocator, uint8_t *old_p, size_t old_size, size_t new_size, size_t in_use_back, size_t in_use_front) { return allocator ? allocator->reallocate_downward(old_p, old_size, new_size, in_use_back, in_use_front) : DefaultAllocator().reallocate_downward(old_p, old_size, new_size, in_use_back, in_use_front); } // DetachedBuffer is a finished flatbuffer memory region, detached from its // builder. The original memory region and allocator are also stored so that // the DetachedBuffer can manage the memory lifetime. class DetachedBuffer { public: DetachedBuffer() : allocator_(nullptr), own_allocator_(false), buf_(nullptr), reserved_(0), cur_(nullptr), size_(0) {} DetachedBuffer(Allocator *allocator, bool own_allocator, uint8_t *buf, size_t reserved, uint8_t *cur, size_t sz) : allocator_(allocator), own_allocator_(own_allocator), buf_(buf), reserved_(reserved), cur_(cur), size_(sz) {} DetachedBuffer(DetachedBuffer &&other) : allocator_(other.allocator_), own_allocator_(other.own_allocator_), buf_(other.buf_), reserved_(other.reserved_), cur_(other.cur_), size_(other.size_) { other.reset(); } DetachedBuffer &operator=(DetachedBuffer &&other) { destroy(); allocator_ = other.allocator_; own_allocator_ = other.own_allocator_; buf_ = other.buf_; reserved_ = other.reserved_; cur_ = other.cur_; size_ = other.size_; other.reset(); return *this; } ~DetachedBuffer() { destroy(); } const uint8_t *data() const { return cur_; } uint8_t *data() { return cur_; } size_t size() const { return size_; } // clang-format off #if 0 // disabled for now due to the ordering of classes in this header template bool Verify() const { Verifier verifier(data(), size()); return verifier.Verify(nullptr); } template const T* GetRoot() const { return flatbuffers::GetRoot(data()); } template T* GetRoot() { return flatbuffers::GetRoot(data()); } #endif // clang-format on // These may change access mode, leave these at end of public section FLATBUFFERS_DELETE_FUNC(DetachedBuffer(const DetachedBuffer &other)) FLATBUFFERS_DELETE_FUNC( DetachedBuffer &operator=(const DetachedBuffer &other)) protected: Allocator *allocator_; bool own_allocator_; uint8_t *buf_; size_t reserved_; uint8_t *cur_; size_t size_; inline void destroy() { if (buf_) Deallocate(allocator_, buf_, reserved_); if (own_allocator_ && allocator_) { delete allocator_; } reset(); } inline void reset() { allocator_ = nullptr; own_allocator_ = false; buf_ = nullptr; reserved_ = 0; cur_ = nullptr; size_ = 0; } }; // This is a minimal replication of std::vector functionality, // except growing from higher to lower addresses. i.e push_back() inserts data // in the lowest address in the vector. // Since this vector leaves the lower part unused, we support a "scratch-pad" // that can be stored there for temporary data, to share the allocated space. // Essentially, this supports 2 std::vectors in a single buffer. class vector_downward { public: explicit vector_downward(size_t initial_size, Allocator *allocator, bool own_allocator, size_t buffer_minalign) : allocator_(allocator), own_allocator_(own_allocator), initial_size_(initial_size), buffer_minalign_(buffer_minalign), reserved_(0), buf_(nullptr), cur_(nullptr), scratch_(nullptr) {} vector_downward(vector_downward &&other) : allocator_(other.allocator_), own_allocator_(other.own_allocator_), initial_size_(other.initial_size_), buffer_minalign_(other.buffer_minalign_), reserved_(other.reserved_), buf_(other.buf_), cur_(other.cur_), scratch_(other.scratch_) { // No change in other.allocator_ // No change in other.initial_size_ // No change in other.buffer_minalign_ other.own_allocator_ = false; other.reserved_ = 0; other.buf_ = nullptr; other.cur_ = nullptr; other.scratch_ = nullptr; } vector_downward &operator=(vector_downward &&other) { // Move construct a temporary and swap idiom vector_downward temp(std::move(other)); swap(temp); return *this; } ~vector_downward() { clear_buffer(); clear_allocator(); } void reset() { clear_buffer(); clear(); } void clear() { if (buf_) { cur_ = buf_ + reserved_; } else { reserved_ = 0; cur_ = nullptr; } clear_scratch(); } void clear_scratch() { scratch_ = buf_; } void clear_allocator() { if (own_allocator_ && allocator_) { delete allocator_; } allocator_ = nullptr; own_allocator_ = false; } void clear_buffer() { if (buf_) Deallocate(allocator_, buf_, reserved_); buf_ = nullptr; } // Relinquish the pointer to the caller. uint8_t *release_raw(size_t &allocated_bytes, size_t &offset) { auto *buf = buf_; allocated_bytes = reserved_; offset = static_cast(cur_ - buf_); // release_raw only relinquishes the buffer ownership. // Does not deallocate or reset the allocator. Destructor will do that. buf_ = nullptr; clear(); return buf; } // Relinquish the pointer to the caller. DetachedBuffer release() { // allocator ownership (if any) is transferred to DetachedBuffer. DetachedBuffer fb(allocator_, own_allocator_, buf_, reserved_, cur_, size()); if (own_allocator_) { allocator_ = nullptr; own_allocator_ = false; } buf_ = nullptr; clear(); return fb; } size_t ensure_space(size_t len) { FLATBUFFERS_ASSERT(cur_ >= scratch_ && scratch_ >= buf_); if (len > static_cast(cur_ - scratch_)) { reallocate(len); } // Beyond this, signed offsets may not have enough range: // (FlatBuffers > 2GB not supported). FLATBUFFERS_ASSERT(size() < FLATBUFFERS_MAX_BUFFER_SIZE); return len; } inline uint8_t *make_space(size_t len) { size_t space = ensure_space(len); cur_ -= space; return cur_; } // Returns nullptr if using the DefaultAllocator. Allocator *get_custom_allocator() { return allocator_; } uoffset_t size() const { return static_cast(reserved_ - (cur_ - buf_)); } uoffset_t scratch_size() const { return static_cast(scratch_ - buf_); } size_t capacity() const { return reserved_; } uint8_t *data() const { FLATBUFFERS_ASSERT(cur_); return cur_; } uint8_t *scratch_data() const { FLATBUFFERS_ASSERT(buf_); return buf_; } uint8_t *scratch_end() const { FLATBUFFERS_ASSERT(scratch_); return scratch_; } uint8_t *data_at(size_t offset) const { return buf_ + reserved_ - offset; } void push(const uint8_t *bytes, size_t num) { memcpy(make_space(num), bytes, num); } // Specialized version of push() that avoids memcpy call for small data. template void push_small(const T &little_endian_t) { make_space(sizeof(T)); *reinterpret_cast(cur_) = little_endian_t; } template void scratch_push_small(const T &t) { ensure_space(sizeof(T)); *reinterpret_cast(scratch_) = t; scratch_ += sizeof(T); } // fill() is most frequently called with small byte counts (<= 4), // which is why we're using loops rather than calling memset. void fill(size_t zero_pad_bytes) { make_space(zero_pad_bytes); for (size_t i = 0; i < zero_pad_bytes; i++) cur_[i] = 0; } // Version for when we know the size is larger. void fill_big(size_t zero_pad_bytes) { memset(make_space(zero_pad_bytes), 0, zero_pad_bytes); } void pop(size_t bytes_to_remove) { cur_ += bytes_to_remove; } void scratch_pop(size_t bytes_to_remove) { scratch_ -= bytes_to_remove; } void swap(vector_downward &other) { using std::swap; swap(allocator_, other.allocator_); swap(own_allocator_, other.own_allocator_); swap(initial_size_, other.initial_size_); swap(buffer_minalign_, other.buffer_minalign_); swap(reserved_, other.reserved_); swap(buf_, other.buf_); swap(cur_, other.cur_); swap(scratch_, other.scratch_); } void swap_allocator(vector_downward &other) { using std::swap; swap(allocator_, other.allocator_); swap(own_allocator_, other.own_allocator_); } private: // You shouldn't really be copying instances of this class. FLATBUFFERS_DELETE_FUNC(vector_downward(const vector_downward &)) FLATBUFFERS_DELETE_FUNC(vector_downward &operator=(const vector_downward &)) Allocator *allocator_; bool own_allocator_; size_t initial_size_; size_t buffer_minalign_; size_t reserved_; uint8_t *buf_; uint8_t *cur_; // Points at location between empty (below) and used (above). uint8_t *scratch_; // Points to the end of the scratchpad in use. void reallocate(size_t len) { auto old_reserved = reserved_; auto old_size = size(); auto old_scratch_size = scratch_size(); reserved_ += (std::max)(len, old_reserved ? old_reserved / 2 : initial_size_); reserved_ = (reserved_ + buffer_minalign_ - 1) & ~(buffer_minalign_ - 1); if (buf_) { buf_ = ReallocateDownward(allocator_, buf_, old_reserved, reserved_, old_size, old_scratch_size); } else { buf_ = Allocate(allocator_, reserved_); } cur_ = buf_ + reserved_ - old_size; scratch_ = buf_ + old_scratch_size; } }; // Converts a Field ID to a virtual table offset. inline voffset_t FieldIndexToOffset(voffset_t field_id) { // Should correspond to what EndTable() below builds up. const int fixed_fields = 2; // Vtable size and Object Size. return static_cast((field_id + fixed_fields) * sizeof(voffset_t)); } template const T *data(const std::vector &v) { return v.empty() ? nullptr : &v.front(); } template T *data(std::vector &v) { return v.empty() ? nullptr : &v.front(); } /// @endcond /// @addtogroup flatbuffers_cpp_api /// @{ /// @class FlatBufferBuilder /// @brief Helper class to hold data needed in creation of a FlatBuffer. /// To serialize data, you typically call one of the `Create*()` functions in /// the generated code, which in turn call a sequence of `StartTable`/ /// `PushElement`/`AddElement`/`EndTable`, or the builtin `CreateString`/ /// `CreateVector` functions. Do this is depth-first order to build up a tree to /// the root. `Finish()` wraps up the buffer ready for transport. class FlatBufferBuilder { public: /// @brief Default constructor for FlatBufferBuilder. /// @param[in] initial_size The initial size of the buffer, in bytes. Defaults /// to `1024`. /// @param[in] allocator An `Allocator` to use. If null will use /// `DefaultAllocator`. /// @param[in] own_allocator Whether the builder/vector should own the /// allocator. Defaults to / `false`. /// @param[in] buffer_minalign Force the buffer to be aligned to the given /// minimum alignment upon reallocation. Only needed if you intend to store /// types with custom alignment AND you wish to read the buffer in-place /// directly after creation. explicit FlatBufferBuilder(size_t initial_size = 1024, Allocator *allocator = nullptr, bool own_allocator = false, size_t buffer_minalign = AlignOf()) : buf_(initial_size, allocator, own_allocator, buffer_minalign), num_field_loc(0), max_voffset_(0), nested(false), finished(false), minalign_(1), force_defaults_(false), dedup_vtables_(true), string_pool(nullptr) { EndianCheck(); } /// @brief Move constructor for FlatBufferBuilder. FlatBufferBuilder(FlatBufferBuilder &&other) : buf_(1024, nullptr, false, AlignOf()), num_field_loc(0), max_voffset_(0), nested(false), finished(false), minalign_(1), force_defaults_(false), dedup_vtables_(true), string_pool(nullptr) { EndianCheck(); // Default construct and swap idiom. // Lack of delegating constructors in vs2010 makes it more verbose than needed. Swap(other); } /// @brief Move assignment operator for FlatBufferBuilder. FlatBufferBuilder &operator=(FlatBufferBuilder &&other) { // Move construct a temporary and swap idiom FlatBufferBuilder temp(std::move(other)); Swap(temp); return *this; } void Swap(FlatBufferBuilder &other) { using std::swap; buf_.swap(other.buf_); swap(num_field_loc, other.num_field_loc); swap(max_voffset_, other.max_voffset_); swap(nested, other.nested); swap(finished, other.finished); swap(minalign_, other.minalign_); swap(force_defaults_, other.force_defaults_); swap(dedup_vtables_, other.dedup_vtables_); swap(string_pool, other.string_pool); } ~FlatBufferBuilder() { if (string_pool) delete string_pool; } void Reset() { Clear(); // clear builder state buf_.reset(); // deallocate buffer } /// @brief Reset all the state in this FlatBufferBuilder so it can be reused /// to construct another buffer. void Clear() { ClearOffsets(); buf_.clear(); nested = false; finished = false; minalign_ = 1; if (string_pool) string_pool->clear(); } /// @brief The current size of the serialized buffer, counting from the end. /// @return Returns an `uoffset_t` with the current size of the buffer. uoffset_t GetSize() const { return buf_.size(); } /// @brief Get the serialized buffer (after you call `Finish()`). /// @return Returns an `uint8_t` pointer to the FlatBuffer data inside the /// buffer. uint8_t *GetBufferPointer() const { Finished(); return buf_.data(); } /// @brief Get a pointer to an unfinished buffer. /// @return Returns a `uint8_t` pointer to the unfinished buffer. uint8_t *GetCurrentBufferPointer() const { return buf_.data(); } /// @brief Get the released pointer to the serialized buffer. /// @warning Do NOT attempt to use this FlatBufferBuilder afterwards! /// @return A `FlatBuffer` that owns the buffer and its allocator and /// behaves similar to a `unique_ptr` with a deleter. /// Deprecated: use Release() instead DetachedBuffer ReleaseBufferPointer() { Finished(); return buf_.release(); } /// @brief Get the released DetachedBuffer. /// @return A `DetachedBuffer` that owns the buffer and its allocator. DetachedBuffer Release() { Finished(); return buf_.release(); } /// @brief Get the released pointer to the serialized buffer. /// @param The size of the memory block containing /// the serialized `FlatBuffer`. /// @param The offset from the released pointer where the finished /// `FlatBuffer` starts. /// @return A raw pointer to the start of the memory block containing /// the serialized `FlatBuffer`. /// @remark If the allocator is owned, it gets deleted during this call. uint8_t *ReleaseRaw(size_t &size, size_t &offset) { Finished(); return buf_.release_raw(size, offset); } /// @brief get the minimum alignment this buffer needs to be accessed /// properly. This is only known once all elements have been written (after /// you call Finish()). You can use this information if you need to embed /// a FlatBuffer in some other buffer, such that you can later read it /// without first having to copy it into its own buffer. size_t GetBufferMinAlignment() { Finished(); return minalign_; } /// @cond FLATBUFFERS_INTERNAL void Finished() const { // If you get this assert, you're attempting to get access a buffer // which hasn't been finished yet. Be sure to call // FlatBufferBuilder::Finish with your root table. // If you really need to access an unfinished buffer, call // GetCurrentBufferPointer instead. FLATBUFFERS_ASSERT(finished); } /// @endcond /// @brief In order to save space, fields that are set to their default value /// don't get serialized into the buffer. /// @param[in] bool fd When set to `true`, always serializes default values that are set. /// Optional fields which are not set explicitly, will still not be serialized. void ForceDefaults(bool fd) { force_defaults_ = fd; } /// @brief By default vtables are deduped in order to save space. /// @param[in] bool dedup When set to `true`, dedup vtables. void DedupVtables(bool dedup) { dedup_vtables_ = dedup; } /// @cond FLATBUFFERS_INTERNAL void Pad(size_t num_bytes) { buf_.fill(num_bytes); } void TrackMinAlign(size_t elem_size) { if (elem_size > minalign_) minalign_ = elem_size; } void Align(size_t elem_size) { TrackMinAlign(elem_size); buf_.fill(PaddingBytes(buf_.size(), elem_size)); } void PushFlatBuffer(const uint8_t *bytes, size_t size) { PushBytes(bytes, size); finished = true; } void PushBytes(const uint8_t *bytes, size_t size) { buf_.push(bytes, size); } void PopBytes(size_t amount) { buf_.pop(amount); } template void AssertScalarT() { // The code assumes power of 2 sizes and endian-swap-ability. static_assert(flatbuffers::is_scalar::value, "T must be a scalar type"); } // Write a single aligned scalar to the buffer template uoffset_t PushElement(T element) { AssertScalarT(); T litle_endian_element = EndianScalar(element); Align(sizeof(T)); buf_.push_small(litle_endian_element); return GetSize(); } template uoffset_t PushElement(Offset off) { // Special case for offsets: see ReferTo below. return PushElement(ReferTo(off.o)); } // When writing fields, we track where they are, so we can create correct // vtables later. void TrackField(voffset_t field, uoffset_t off) { FieldLoc fl = { off, field }; buf_.scratch_push_small(fl); num_field_loc++; max_voffset_ = (std::max)(max_voffset_, field); } // Like PushElement, but additionally tracks the field this represents. template void AddElement(voffset_t field, T e, T def) { // We don't serialize values equal to the default. if (e == def && !force_defaults_) return; auto off = PushElement(e); TrackField(field, off); } template void AddOffset(voffset_t field, Offset off) { if (off.IsNull()) return; // Don't store. AddElement(field, ReferTo(off.o), static_cast(0)); } template void AddStruct(voffset_t field, const T *structptr) { if (!structptr) return; // Default, don't store. Align(AlignOf()); buf_.push_small(*structptr); TrackField(field, GetSize()); } void AddStructOffset(voffset_t field, uoffset_t off) { TrackField(field, off); } // Offsets initially are relative to the end of the buffer (downwards). // This function converts them to be relative to the current location // in the buffer (when stored here), pointing upwards. uoffset_t ReferTo(uoffset_t off) { // Align to ensure GetSize() below is correct. Align(sizeof(uoffset_t)); // Offset must refer to something already in buffer. FLATBUFFERS_ASSERT(off && off <= GetSize()); return GetSize() - off + static_cast(sizeof(uoffset_t)); } void NotNested() { // If you hit this, you're trying to construct a Table/Vector/String // during the construction of its parent table (between the MyTableBuilder // and table.Finish(). // Move the creation of these sub-objects to above the MyTableBuilder to // not get this assert. // Ignoring this assert may appear to work in simple cases, but the reason // it is here is that storing objects in-line may cause vtable offsets // to not fit anymore. It also leads to vtable duplication. FLATBUFFERS_ASSERT(!nested); // If you hit this, fields were added outside the scope of a table. FLATBUFFERS_ASSERT(!num_field_loc); } // From generated code (or from the parser), we call StartTable/EndTable // with a sequence of AddElement calls in between. uoffset_t StartTable() { NotNested(); nested = true; return GetSize(); } // This finishes one serialized object by generating the vtable if it's a // table, comparing it against existing vtables, and writing the // resulting vtable offset. uoffset_t EndTable(uoffset_t start) { // If you get this assert, a corresponding StartTable wasn't called. FLATBUFFERS_ASSERT(nested); // Write the vtable offset, which is the start of any Table. // We fill it's value later. auto vtableoffsetloc = PushElement(0); // Write a vtable, which consists entirely of voffset_t elements. // It starts with the number of offsets, followed by a type id, followed // by the offsets themselves. In reverse: // Include space for the last offset and ensure empty tables have a // minimum size. max_voffset_ = (std::max)(static_cast(max_voffset_ + sizeof(voffset_t)), FieldIndexToOffset(0)); buf_.fill_big(max_voffset_); auto table_object_size = vtableoffsetloc - start; // Vtable use 16bit offsets. FLATBUFFERS_ASSERT(table_object_size < 0x10000); WriteScalar(buf_.data() + sizeof(voffset_t), static_cast(table_object_size)); WriteScalar(buf_.data(), max_voffset_); // Write the offsets into the table for (auto it = buf_.scratch_end() - num_field_loc * sizeof(FieldLoc); it < buf_.scratch_end(); it += sizeof(FieldLoc)) { auto field_location = reinterpret_cast(it); auto pos = static_cast(vtableoffsetloc - field_location->off); // If this asserts, it means you've set a field twice. FLATBUFFERS_ASSERT( !ReadScalar(buf_.data() + field_location->id)); WriteScalar(buf_.data() + field_location->id, pos); } ClearOffsets(); auto vt1 = reinterpret_cast(buf_.data()); auto vt1_size = ReadScalar(vt1); auto vt_use = GetSize(); // See if we already have generated a vtable with this exact same // layout before. If so, make it point to the old one, remove this one. if (dedup_vtables_) { for (auto it = buf_.scratch_data(); it < buf_.scratch_end(); it += sizeof(uoffset_t)) { auto vt_offset_ptr = reinterpret_cast(it); auto vt2 = reinterpret_cast(buf_.data_at(*vt_offset_ptr)); auto vt2_size = *vt2; if (vt1_size != vt2_size || memcmp(vt2, vt1, vt1_size)) continue; vt_use = *vt_offset_ptr; buf_.pop(GetSize() - vtableoffsetloc); break; } } // If this is a new vtable, remember it. if (vt_use == GetSize()) { buf_.scratch_push_small(vt_use); } // Fill the vtable offset we created above. // The offset points from the beginning of the object to where the // vtable is stored. // Offsets default direction is downward in memory for future format // flexibility (storing all vtables at the start of the file). WriteScalar(buf_.data_at(vtableoffsetloc), static_cast(vt_use) - static_cast(vtableoffsetloc)); nested = false; return vtableoffsetloc; } // DEPRECATED: call the version above instead. uoffset_t EndTable(uoffset_t start, voffset_t /*numfields*/) { return EndTable(start); } // This checks a required field has been set in a given table that has // just been constructed. template void Required(Offset table, voffset_t field); uoffset_t StartStruct(size_t alignment) { Align(alignment); return GetSize(); } uoffset_t EndStruct() { return GetSize(); } void ClearOffsets() { buf_.scratch_pop(num_field_loc * sizeof(FieldLoc)); num_field_loc = 0; max_voffset_ = 0; } // Aligns such that when "len" bytes are written, an object can be written // after it with "alignment" without padding. void PreAlign(size_t len, size_t alignment) { TrackMinAlign(alignment); buf_.fill(PaddingBytes(GetSize() + len, alignment)); } template void PreAlign(size_t len) { AssertScalarT(); PreAlign(len, sizeof(T)); } /// @endcond /// @brief Store a string in the buffer, which can contain any binary data. /// @param[in] str A const char pointer to the data to be stored as a string. /// @param[in] len The number of bytes that should be stored from `str`. /// @return Returns the offset in the buffer where the string starts. Offset CreateString(const char *str, size_t len) { NotNested(); PreAlign(len + 1); // Always 0-terminated. buf_.fill(1); PushBytes(reinterpret_cast(str), len); PushElement(static_cast(len)); return Offset(GetSize()); } /// @brief Store a string in the buffer, which is null-terminated. /// @param[in] str A const char pointer to a C-string to add to the buffer. /// @return Returns the offset in the buffer where the string starts. Offset CreateString(const char *str) { return CreateString(str, strlen(str)); } /// @brief Store a string in the buffer, which is null-terminated. /// @param[in] str A char pointer to a C-string to add to the buffer. /// @return Returns the offset in the buffer where the string starts. Offset CreateString(char *str) { return CreateString(str, strlen(str)); } /// @brief Store a string in the buffer, which can contain any binary data. /// @param[in] str A const reference to a std::string to store in the buffer. /// @return Returns the offset in the buffer where the string starts. Offset CreateString(const std::string &str) { return CreateString(str.c_str(), str.length()); } // clang-format off #ifdef FLATBUFFERS_HAS_STRING_VIEW /// @brief Store a string in the buffer, which can contain any binary data. /// @param[in] str A const string_view to copy in to the buffer. /// @return Returns the offset in the buffer where the string starts. Offset CreateString(flatbuffers::string_view str) { return CreateString(str.data(), str.size()); } #endif // FLATBUFFERS_HAS_STRING_VIEW // clang-format on /// @brief Store a string in the buffer, which can contain any binary data. /// @param[in] str A const pointer to a `String` struct to add to the buffer. /// @return Returns the offset in the buffer where the string starts Offset CreateString(const String *str) { return str ? CreateString(str->c_str(), str->Length()) : 0; } /// @brief Store a string in the buffer, which can contain any binary data. /// @param[in] str A const reference to a std::string like type with support /// of T::c_str() and T::length() to store in the buffer. /// @return Returns the offset in the buffer where the string starts. template Offset CreateString(const T &str) { return CreateString(str.c_str(), str.length()); } /// @brief Store a string in the buffer, which can contain any binary data. /// If a string with this exact contents has already been serialized before, /// instead simply returns the offset of the existing string. /// @param[in] str A const char pointer to the data to be stored as a string. /// @param[in] len The number of bytes that should be stored from `str`. /// @return Returns the offset in the buffer where the string starts. Offset CreateSharedString(const char *str, size_t len) { if (!string_pool) string_pool = new StringOffsetMap(StringOffsetCompare(buf_)); auto size_before_string = buf_.size(); // Must first serialize the string, since the set is all offsets into // buffer. auto off = CreateString(str, len); auto it = string_pool->find(off); // If it exists we reuse existing serialized data! if (it != string_pool->end()) { // We can remove the string we serialized. buf_.pop(buf_.size() - size_before_string); return *it; } // Record this string for future use. string_pool->insert(off); return off; } /// @brief Store a string in the buffer, which null-terminated. /// If a string with this exact contents has already been serialized before, /// instead simply returns the offset of the existing string. /// @param[in] str A const char pointer to a C-string to add to the buffer. /// @return Returns the offset in the buffer where the string starts. Offset CreateSharedString(const char *str) { return CreateSharedString(str, strlen(str)); } /// @brief Store a string in the buffer, which can contain any binary data. /// If a string with this exact contents has already been serialized before, /// instead simply returns the offset of the existing string. /// @param[in] str A const reference to a std::string to store in the buffer. /// @return Returns the offset in the buffer where the string starts. Offset CreateSharedString(const std::string &str) { return CreateSharedString(str.c_str(), str.length()); } /// @brief Store a string in the buffer, which can contain any binary data. /// If a string with this exact contents has already been serialized before, /// instead simply returns the offset of the existing string. /// @param[in] str A const pointer to a `String` struct to add to the buffer. /// @return Returns the offset in the buffer where the string starts Offset CreateSharedString(const String *str) { return CreateSharedString(str->c_str(), str->Length()); } /// @cond FLATBUFFERS_INTERNAL uoffset_t EndVector(size_t len) { FLATBUFFERS_ASSERT(nested); // Hit if no corresponding StartVector. nested = false; return PushElement(static_cast(len)); } void StartVector(size_t len, size_t elemsize) { NotNested(); nested = true; PreAlign(len * elemsize); PreAlign(len * elemsize, elemsize); // Just in case elemsize > uoffset_t. } // Call this right before StartVector/CreateVector if you want to force the // alignment to be something different than what the element size would // normally dictate. // This is useful when storing a nested_flatbuffer in a vector of bytes, // or when storing SIMD floats, etc. void ForceVectorAlignment(size_t len, size_t elemsize, size_t alignment) { PreAlign(len * elemsize, alignment); } // Similar to ForceVectorAlignment but for String fields. void ForceStringAlignment(size_t len, size_t alignment) { PreAlign((len + 1) * sizeof(char), alignment); } /// @endcond /// @brief Serialize an array into a FlatBuffer `vector`. /// @tparam T The data type of the array elements. /// @param[in] v A pointer to the array of type `T` to serialize into the /// buffer as a `vector`. /// @param[in] len The number of elements to serialize. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVector(const T *v, size_t len) { // If this assert hits, you're specifying a template argument that is // causing the wrong overload to be selected, remove it. AssertScalarT(); StartVector(len, sizeof(T)); // clang-format off #if FLATBUFFERS_LITTLEENDIAN PushBytes(reinterpret_cast(v), len * sizeof(T)); #else if (sizeof(T) == 1) { PushBytes(reinterpret_cast(v), len); } else { for (auto i = len; i > 0; ) { PushElement(v[--i]); } } #endif // clang-format on return Offset>(EndVector(len)); } template Offset>> CreateVector(const Offset *v, size_t len) { StartVector(len, sizeof(Offset)); for (auto i = len; i > 0;) { PushElement(v[--i]); } return Offset>>(EndVector(len)); } /// @brief Serialize a `std::vector` into a FlatBuffer `vector`. /// @tparam T The data type of the `std::vector` elements. /// @param v A const reference to the `std::vector` to serialize into the /// buffer as a `vector`. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVector(const std::vector &v) { return CreateVector(data(v), v.size()); } // vector may be implemented using a bit-set, so we can't access it as // an array. Instead, read elements manually. // Background: https://isocpp.org/blog/2012/11/on-vectorbool Offset> CreateVector(const std::vector &v) { StartVector(v.size(), sizeof(uint8_t)); for (auto i = v.size(); i > 0;) { PushElement(static_cast(v[--i])); } return Offset>(EndVector(v.size())); } // clang-format off #ifndef FLATBUFFERS_CPP98_STL /// @brief Serialize values returned by a function into a FlatBuffer `vector`. /// This is a convenience function that takes care of iteration for you. /// @tparam T The data type of the `std::vector` elements. /// @param f A function that takes the current iteration 0..vector_size-1 and /// returns any type that you can construct a FlatBuffers vector out of. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVector(size_t vector_size, const std::function &f) { std::vector elems(vector_size); for (size_t i = 0; i < vector_size; i++) elems[i] = f(i); return CreateVector(elems); } #endif // clang-format on /// @brief Serialize values returned by a function into a FlatBuffer `vector`. /// This is a convenience function that takes care of iteration for you. /// @tparam T The data type of the `std::vector` elements. /// @param f A function that takes the current iteration 0..vector_size-1, /// and the state parameter returning any type that you can construct a /// FlatBuffers vector out of. /// @param state State passed to f. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVector(size_t vector_size, F f, S *state) { std::vector elems(vector_size); for (size_t i = 0; i < vector_size; i++) elems[i] = f(i, state); return CreateVector(elems); } /// @brief Serialize a `std::vector` into a FlatBuffer `vector`. /// This is a convenience function for a common case. /// @param v A const reference to the `std::vector` to serialize into the /// buffer as a `vector`. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. Offset>> CreateVectorOfStrings( const std::vector &v) { std::vector> offsets(v.size()); for (size_t i = 0; i < v.size(); i++) offsets[i] = CreateString(v[i]); return CreateVector(offsets); } /// @brief Serialize an array of structs into a FlatBuffer `vector`. /// @tparam T The data type of the struct array elements. /// @param[in] v A pointer to the array of type `T` to serialize into the /// buffer as a `vector`. /// @param[in] len The number of elements to serialize. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVectorOfStructs(const T *v, size_t len) { StartVector(len * sizeof(T) / AlignOf(), AlignOf()); PushBytes(reinterpret_cast(v), sizeof(T) * len); return Offset>(EndVector(len)); } /// @brief Serialize an array of native structs into a FlatBuffer `vector`. /// @tparam T The data type of the struct array elements. /// @tparam S The data type of the native struct array elements. /// @param[in] v A pointer to the array of type `S` to serialize into the /// buffer as a `vector`. /// @param[in] len The number of elements to serialize. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVectorOfNativeStructs(const S *v, size_t len) { extern T Pack(const S &); typedef T (*Pack_t)(const S &); std::vector vv(len); std::transform(v, v + len, vv.begin(), *(Pack_t)&Pack); return CreateVectorOfStructs(vv.data(), vv.size()); } // clang-format off #ifndef FLATBUFFERS_CPP98_STL /// @brief Serialize an array of structs into a FlatBuffer `vector`. /// @tparam T The data type of the struct array elements. /// @param[in] f A function that takes the current iteration 0..vector_size-1 /// and a pointer to the struct that must be filled. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. /// This is mostly useful when flatbuffers are generated with mutation /// accessors. template Offset> CreateVectorOfStructs( size_t vector_size, const std::function &filler) { T* structs = StartVectorOfStructs(vector_size); for (size_t i = 0; i < vector_size; i++) { filler(i, structs); structs++; } return EndVectorOfStructs(vector_size); } #endif // clang-format on /// @brief Serialize an array of structs into a FlatBuffer `vector`. /// @tparam T The data type of the struct array elements. /// @param[in] f A function that takes the current iteration 0..vector_size-1, /// a pointer to the struct that must be filled and the state argument. /// @param[in] state Arbitrary state to pass to f. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. /// This is mostly useful when flatbuffers are generated with mutation /// accessors. template Offset> CreateVectorOfStructs(size_t vector_size, F f, S *state) { T *structs = StartVectorOfStructs(vector_size); for (size_t i = 0; i < vector_size; i++) { f(i, structs, state); structs++; } return EndVectorOfStructs(vector_size); } /// @brief Serialize a `std::vector` of structs into a FlatBuffer `vector`. /// @tparam T The data type of the `std::vector` struct elements. /// @param[in]] v A const reference to the `std::vector` of structs to /// serialize into the buffer as a `vector`. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVectorOfStructs( const std::vector &v) { return CreateVectorOfStructs(data(v), v.size()); } /// @brief Serialize a `std::vector` of native structs into a FlatBuffer /// `vector`. /// @tparam T The data type of the `std::vector` struct elements. /// @tparam S The data type of the `std::vector` native struct elements. /// @param[in]] v A const reference to the `std::vector` of structs to /// serialize into the buffer as a `vector`. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVectorOfNativeStructs( const std::vector &v) { return CreateVectorOfNativeStructs(data(v), v.size()); } /// @cond FLATBUFFERS_INTERNAL template struct StructKeyComparator { bool operator()(const T &a, const T &b) const { return a.KeyCompareLessThan(&b); } private: StructKeyComparator &operator=(const StructKeyComparator &); }; /// @endcond /// @brief Serialize a `std::vector` of structs into a FlatBuffer `vector` /// in sorted order. /// @tparam T The data type of the `std::vector` struct elements. /// @param[in]] v A const reference to the `std::vector` of structs to /// serialize into the buffer as a `vector`. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVectorOfSortedStructs(std::vector *v) { return CreateVectorOfSortedStructs(data(*v), v->size()); } /// @brief Serialize a `std::vector` of native structs into a FlatBuffer /// `vector` in sorted order. /// @tparam T The data type of the `std::vector` struct elements. /// @tparam S The data type of the `std::vector` native struct elements. /// @param[in]] v A const reference to the `std::vector` of structs to /// serialize into the buffer as a `vector`. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVectorOfSortedNativeStructs( std::vector *v) { return CreateVectorOfSortedNativeStructs(data(*v), v->size()); } /// @brief Serialize an array of structs into a FlatBuffer `vector` in sorted /// order. /// @tparam T The data type of the struct array elements. /// @param[in] v A pointer to the array of type `T` to serialize into the /// buffer as a `vector`. /// @param[in] len The number of elements to serialize. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVectorOfSortedStructs(T *v, size_t len) { std::sort(v, v + len, StructKeyComparator()); return CreateVectorOfStructs(v, len); } /// @brief Serialize an array of native structs into a FlatBuffer `vector` in /// sorted order. /// @tparam T The data type of the struct array elements. /// @tparam S The data type of the native struct array elements. /// @param[in] v A pointer to the array of type `S` to serialize into the /// buffer as a `vector`. /// @param[in] len The number of elements to serialize. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset> CreateVectorOfSortedNativeStructs(S *v, size_t len) { extern T Pack(const S &); typedef T (*Pack_t)(const S &); std::vector vv(len); std::transform(v, v + len, vv.begin(), *(Pack_t)&Pack); return CreateVectorOfSortedStructs(vv, len); } /// @cond FLATBUFFERS_INTERNAL template struct TableKeyComparator { TableKeyComparator(vector_downward &buf) : buf_(buf) {} bool operator()(const Offset &a, const Offset &b) const { auto table_a = reinterpret_cast(buf_.data_at(a.o)); auto table_b = reinterpret_cast(buf_.data_at(b.o)); return table_a->KeyCompareLessThan(table_b); } vector_downward &buf_; private: TableKeyComparator &operator=(const TableKeyComparator &); }; /// @endcond /// @brief Serialize an array of `table` offsets as a `vector` in the buffer /// in sorted order. /// @tparam T The data type that the offset refers to. /// @param[in] v An array of type `Offset` that contains the `table` /// offsets to store in the buffer in sorted order. /// @param[in] len The number of elements to store in the `vector`. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset>> CreateVectorOfSortedTables(Offset *v, size_t len) { std::sort(v, v + len, TableKeyComparator(buf_)); return CreateVector(v, len); } /// @brief Serialize an array of `table` offsets as a `vector` in the buffer /// in sorted order. /// @tparam T The data type that the offset refers to. /// @param[in] v An array of type `Offset` that contains the `table` /// offsets to store in the buffer in sorted order. /// @return Returns a typed `Offset` into the serialized data indicating /// where the vector is stored. template Offset>> CreateVectorOfSortedTables( std::vector> *v) { return CreateVectorOfSortedTables(data(*v), v->size()); } /// @brief Specialized version of `CreateVector` for non-copying use cases. /// Write the data any time later to the returned buffer pointer `buf`. /// @param[in] len The number of elements to store in the `vector`. /// @param[in] elemsize The size of each element in the `vector`. /// @param[out] buf A pointer to a `uint8_t` pointer that can be /// written to at a later time to serialize the data into a `vector` /// in the buffer. uoffset_t CreateUninitializedVector(size_t len, size_t elemsize, uint8_t **buf) { NotNested(); StartVector(len, elemsize); buf_.make_space(len * elemsize); auto vec_start = GetSize(); auto vec_end = EndVector(len); *buf = buf_.data_at(vec_start); return vec_end; } /// @brief Specialized version of `CreateVector` for non-copying use cases. /// Write the data any time later to the returned buffer pointer `buf`. /// @tparam T The data type of the data that will be stored in the buffer /// as a `vector`. /// @param[in] len The number of elements to store in the `vector`. /// @param[out] buf A pointer to a pointer of type `T` that can be /// written to at a later time to serialize the data into a `vector` /// in the buffer. template Offset> CreateUninitializedVector(size_t len, T **buf) { AssertScalarT(); return CreateUninitializedVector(len, sizeof(T), reinterpret_cast(buf)); } template Offset> CreateUninitializedVectorOfStructs(size_t len, T **buf) { return CreateUninitializedVector(len, sizeof(T), reinterpret_cast(buf)); } /// @brief Write a struct by itself, typically to be part of a union. template Offset CreateStruct(const T &structobj) { NotNested(); Align(AlignOf()); buf_.push_small(structobj); return Offset(GetSize()); } /// @brief The length of a FlatBuffer file header. static const size_t kFileIdentifierLength = 4; /// @brief Finish serializing a buffer by writing the root offset. /// @param[in] file_identifier If a `file_identifier` is given, the buffer /// will be prefixed with a standard FlatBuffers file header. template void Finish(Offset root, const char *file_identifier = nullptr) { Finish(root.o, file_identifier, false); } /// @brief Finish a buffer with a 32 bit size field pre-fixed (size of the /// buffer following the size field). These buffers are NOT compatible /// with standard buffers created by Finish, i.e. you can't call GetRoot /// on them, you have to use GetSizePrefixedRoot instead. /// All >32 bit quantities in this buffer will be aligned when the whole /// size pre-fixed buffer is aligned. /// These kinds of buffers are useful for creating a stream of FlatBuffers. template void FinishSizePrefixed(Offset root, const char *file_identifier = nullptr) { Finish(root.o, file_identifier, true); } protected: // You shouldn't really be copying instances of this class. FlatBufferBuilder(const FlatBufferBuilder &); FlatBufferBuilder &operator=(const FlatBufferBuilder &); void Finish(uoffset_t root, const char *file_identifier, bool size_prefix) { NotNested(); buf_.clear_scratch(); // This will cause the whole buffer to be aligned. PreAlign((size_prefix ? sizeof(uoffset_t) : 0) + sizeof(uoffset_t) + (file_identifier ? kFileIdentifierLength : 0), minalign_); if (file_identifier) { FLATBUFFERS_ASSERT(strlen(file_identifier) == kFileIdentifierLength); PushBytes(reinterpret_cast(file_identifier), kFileIdentifierLength); } PushElement(ReferTo(root)); // Location of root. if (size_prefix) { PushElement(GetSize()); } finished = true; } struct FieldLoc { uoffset_t off; voffset_t id; }; vector_downward buf_; // Accumulating offsets of table members while it is being built. // We store these in the scratch pad of buf_, after the vtable offsets. uoffset_t num_field_loc; // Track how much of the vtable is in use, so we can output the most compact // possible vtable. voffset_t max_voffset_; // Ensure objects are not nested. bool nested; // Ensure the buffer is finished before it is being accessed. bool finished; size_t minalign_; bool force_defaults_; // Serialize values equal to their defaults anyway. bool dedup_vtables_; struct StringOffsetCompare { StringOffsetCompare(const vector_downward &buf) : buf_(&buf) {} bool operator()(const Offset &a, const Offset &b) const { auto stra = reinterpret_cast(buf_->data_at(a.o)); auto strb = reinterpret_cast(buf_->data_at(b.o)); return strncmp(stra->c_str(), strb->c_str(), (std::min)(stra->size(), strb->size()) + 1) < 0; } const vector_downward *buf_; }; // For use with CreateSharedString. Instantiated on first use only. typedef std::set, StringOffsetCompare> StringOffsetMap; StringOffsetMap *string_pool; private: // Allocates space for a vector of structures. // Must be completed with EndVectorOfStructs(). template T *StartVectorOfStructs(size_t vector_size) { StartVector(vector_size * sizeof(T) / AlignOf(), AlignOf()); return reinterpret_cast(buf_.make_space(vector_size * sizeof(T))); } // End the vector of structues in the flatbuffers. // Vector should have previously be started with StartVectorOfStructs(). template Offset> EndVectorOfStructs(size_t vector_size) { return Offset>(EndVector(vector_size)); } }; /// @} /// @cond FLATBUFFERS_INTERNAL // Helpers to get a typed pointer to the root object contained in the buffer. template T *GetMutableRoot(void *buf) { EndianCheck(); return reinterpret_cast( reinterpret_cast(buf) + EndianScalar(*reinterpret_cast(buf))); } template const T *GetRoot(const void *buf) { return GetMutableRoot(const_cast(buf)); } template const T *GetSizePrefixedRoot(const void *buf) { return GetRoot(reinterpret_cast(buf) + sizeof(uoffset_t)); } /// Helpers to get a typed pointer to objects that are currently being built. /// @warning Creating new objects will lead to reallocations and invalidates /// the pointer! template T *GetMutableTemporaryPointer(FlatBufferBuilder &fbb, Offset offset) { return reinterpret_cast(fbb.GetCurrentBufferPointer() + fbb.GetSize() - offset.o); } template const T *GetTemporaryPointer(FlatBufferBuilder &fbb, Offset offset) { return GetMutableTemporaryPointer(fbb, offset); } /// @brief Get a pointer to the the file_identifier section of the buffer. /// @return Returns a const char pointer to the start of the file_identifier /// characters in the buffer. The returned char * has length /// 'flatbuffers::FlatBufferBuilder::kFileIdentifierLength'. /// This function is UNDEFINED for FlatBuffers whose schema does not include /// a file_identifier (likely points at padding or the start of a the root /// vtable). inline const char *GetBufferIdentifier(const void *buf, bool size_prefixed = false) { return reinterpret_cast(buf) + ((size_prefixed) ? 2 * sizeof(uoffset_t) : sizeof(uoffset_t)); } // Helper to see if the identifier in a buffer has the expected value. inline bool BufferHasIdentifier(const void *buf, const char *identifier, bool size_prefixed = false) { return strncmp(GetBufferIdentifier(buf, size_prefixed), identifier, FlatBufferBuilder::kFileIdentifierLength) == 0; } // Helper class to verify the integrity of a FlatBuffer class Verifier FLATBUFFERS_FINAL_CLASS { public: Verifier(const uint8_t *buf, size_t buf_len, uoffset_t _max_depth = 64, uoffset_t _max_tables = 1000000) : buf_(buf), size_(buf_len), depth_(0), max_depth_(_max_depth), num_tables_(0), max_tables_(_max_tables) // clang-format off #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE , upper_bound_(0) #endif // clang-format on { assert(size_ < FLATBUFFERS_MAX_BUFFER_SIZE); } // Central location where any verification failures register. bool Check(bool ok) const { // clang-format off #ifdef FLATBUFFERS_DEBUG_VERIFICATION_FAILURE FLATBUFFERS_ASSERT(ok); #endif #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE if (!ok) upper_bound_ = 0; #endif // clang-format on return ok; } // Verify any range within the buffer. bool Verify(size_t elem, size_t elem_len) const { // clang-format off #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE auto upper_bound = elem + elem_len; if (upper_bound_ < upper_bound) upper_bound_ = upper_bound; #endif // clang-format on return Check(elem_len < size_ && elem <= size_ - elem_len); } template bool VerifyAlignment(size_t elem) const { return (elem & (sizeof(T) - 1)) == 0; } // Verify a range indicated by sizeof(T). template bool Verify(size_t elem) const { return VerifyAlignment(elem) && Verify(elem, sizeof(T)); } // Verify relative to a known-good base pointer. bool Verify(const uint8_t *base, voffset_t elem_off, size_t elem_len) const { return Verify(static_cast(base - buf_) + elem_off, elem_len); } template bool Verify(const uint8_t *base, voffset_t elem_off) const { return Verify(static_cast(base - buf_) + elem_off, sizeof(T)); } // Verify a pointer (may be NULL) of a table type. template bool VerifyTable(const T *table) { return !table || table->Verify(*this); } // Verify a pointer (may be NULL) of any vector type. template bool VerifyVector(const Vector *vec) const { return !vec || VerifyVectorOrString(reinterpret_cast(vec), sizeof(T)); } // Verify a pointer (may be NULL) of a vector to struct. template bool VerifyVector(const Vector *vec) const { return VerifyVector(reinterpret_cast *>(vec)); } // Verify a pointer (may be NULL) to string. bool VerifyString(const String *str) const { size_t end; return !str || (VerifyVectorOrString(reinterpret_cast(str), 1, &end) && Verify(end, 1) && // Must have terminator Check(buf_[end] == '\0')); // Terminating byte must be 0. } // Common code between vectors and strings. bool VerifyVectorOrString(const uint8_t *vec, size_t elem_size, size_t *end = nullptr) const { auto veco = static_cast(vec - buf_); // Check we can read the size field. if (!Verify(veco)) return false; // Check the whole array. If this is a string, the byte past the array // must be 0. auto size = ReadScalar(vec); auto max_elems = FLATBUFFERS_MAX_BUFFER_SIZE / elem_size; if (!Check(size < max_elems)) return false; // Protect against byte_size overflowing. auto byte_size = sizeof(size) + elem_size * size; if (end) *end = veco + byte_size; return Verify(veco, byte_size); } // Special case for string contents, after the above has been called. bool VerifyVectorOfStrings(const Vector> *vec) const { if (vec) { for (uoffset_t i = 0; i < vec->size(); i++) { if (!VerifyString(vec->Get(i))) return false; } } return true; } // Special case for table contents, after the above has been called. template bool VerifyVectorOfTables(const Vector> *vec) { if (vec) { for (uoffset_t i = 0; i < vec->size(); i++) { if (!vec->Get(i)->Verify(*this)) return false; } } return true; } bool VerifyTableStart(const uint8_t *table) { // Check the vtable offset. auto tableo = static_cast(table - buf_); if (!Verify(tableo)) return false; // This offset may be signed, but doing the substraction unsigned always // gives the result we want. auto vtableo = tableo - static_cast(ReadScalar(table)); // Check the vtable size field, then check vtable fits in its entirety. return VerifyComplexity() && Verify(vtableo) && VerifyAlignment(ReadScalar(buf_ + vtableo)) && Verify(vtableo, ReadScalar(buf_ + vtableo)); } template bool VerifyBufferFromStart(const char *identifier, size_t start) { if (identifier && (size_ < 2 * sizeof(flatbuffers::uoffset_t) || !BufferHasIdentifier(buf_ + start, identifier))) { return false; } // Call T::Verify, which must be in the generated code for this type. auto o = VerifyOffset(start); return o && reinterpret_cast(buf_ + start + o)->Verify(*this) // clang-format off #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE && GetComputedSize() #endif ; // clang-format on } // Verify this whole buffer, starting with root type T. template bool VerifyBuffer() { return VerifyBuffer(nullptr); } template bool VerifyBuffer(const char *identifier) { return VerifyBufferFromStart(identifier, 0); } template bool VerifySizePrefixedBuffer(const char *identifier) { return Verify(0U) && ReadScalar(buf_) == size_ - sizeof(uoffset_t) && VerifyBufferFromStart(identifier, sizeof(uoffset_t)); } uoffset_t VerifyOffset(size_t start) const { if (!Verify(start)) return 0; auto o = ReadScalar(buf_ + start); // May not point to itself. Check(o != 0); // Can't wrap around / buffers are max 2GB. if (!Check(static_cast(o) >= 0)) return 0; // Must be inside the buffer to create a pointer from it (pointer outside // buffer is UB). if (!Verify(start + o, 1)) return 0; return o; } uoffset_t VerifyOffset(const uint8_t *base, voffset_t start) const { return VerifyOffset(static_cast(base - buf_) + start); } // Called at the start of a table to increase counters measuring data // structure depth and amount, and possibly bails out with false if // limits set by the constructor have been hit. Needs to be balanced // with EndTable(). bool VerifyComplexity() { depth_++; num_tables_++; return Check(depth_ <= max_depth_ && num_tables_ <= max_tables_); } // Called at the end of a table to pop the depth count. bool EndTable() { depth_--; return true; } // clang-format off #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE // Returns the message size in bytes size_t GetComputedSize() const { uintptr_t size = upper_bound_; // Align the size to uoffset_t size = (size - 1 + sizeof(uoffset_t)) & ~(sizeof(uoffset_t) - 1); return (size > size_) ? 0 : size; } #endif // clang-format on private: const uint8_t *buf_; size_t size_; uoffset_t depth_; uoffset_t max_depth_; uoffset_t num_tables_; uoffset_t max_tables_; // clang-format off #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE mutable size_t upper_bound_; #endif // clang-format on }; // Convenient way to bundle a buffer and its length, to pass it around // typed by its root. // A BufferRef does not own its buffer. struct BufferRefBase {}; // for std::is_base_of template struct BufferRef : BufferRefBase { BufferRef() : buf(nullptr), len(0), must_free(false) {} BufferRef(uint8_t *_buf, uoffset_t _len) : buf(_buf), len(_len), must_free(false) {} ~BufferRef() { if (must_free) free(buf); } const T *GetRoot() const { return flatbuffers::GetRoot(buf); } bool Verify() { Verifier verifier(buf, len); return verifier.VerifyBuffer(nullptr); } uint8_t *buf; uoffset_t len; bool must_free; }; // "structs" are flat structures that do not have an offset table, thus // always have all members present and do not support forwards/backwards // compatible extensions. class Struct FLATBUFFERS_FINAL_CLASS { public: template T GetField(uoffset_t o) const { return ReadScalar(&data_[o]); } template T GetStruct(uoffset_t o) const { return reinterpret_cast(&data_[o]); } const uint8_t *GetAddressOf(uoffset_t o) const { return &data_[o]; } uint8_t *GetAddressOf(uoffset_t o) { return &data_[o]; } private: uint8_t data_[1]; }; // "tables" use an offset table (possibly shared) that allows fields to be // omitted and added at will, but uses an extra indirection to read. class Table { public: const uint8_t *GetVTable() const { return data_ - ReadScalar(data_); } // This gets the field offset for any of the functions below it, or 0 // if the field was not present. voffset_t GetOptionalFieldOffset(voffset_t field) const { // The vtable offset is always at the start. auto vtable = GetVTable(); // The first element is the size of the vtable (fields + type id + itself). auto vtsize = ReadScalar(vtable); // If the field we're accessing is outside the vtable, we're reading older // data, so it's the same as if the offset was 0 (not present). return field < vtsize ? ReadScalar(vtable + field) : 0; } template T GetField(voffset_t field, T defaultval) const { auto field_offset = GetOptionalFieldOffset(field); return field_offset ? ReadScalar(data_ + field_offset) : defaultval; } template P GetPointer(voffset_t field) { auto field_offset = GetOptionalFieldOffset(field); auto p = data_ + field_offset; return field_offset ? reinterpret_cast

(p + ReadScalar(p)) : nullptr; } template P GetPointer(voffset_t field) const { return const_cast

(this)->GetPointer

(field); } template P GetStruct(voffset_t field) const { auto field_offset = GetOptionalFieldOffset(field); auto p = const_cast(data_ + field_offset); return field_offset ? reinterpret_cast

(p) : nullptr; } template bool SetField(voffset_t field, T val, T def) { auto field_offset = GetOptionalFieldOffset(field); if (!field_offset) return val == def; WriteScalar(data_ + field_offset, val); return true; } bool SetPointer(voffset_t field, const uint8_t *val) { auto field_offset = GetOptionalFieldOffset(field); if (!field_offset) return false; WriteScalar(data_ + field_offset, static_cast(val - (data_ + field_offset))); return true; } uint8_t *GetAddressOf(voffset_t field) { auto field_offset = GetOptionalFieldOffset(field); return field_offset ? data_ + field_offset : nullptr; } const uint8_t *GetAddressOf(voffset_t field) const { return const_cast

(this)->GetAddressOf(field); } bool CheckField(voffset_t field) const { return GetOptionalFieldOffset(field) != 0; } // Verify the vtable of this table. // Call this once per table, followed by VerifyField once per field. bool VerifyTableStart(Verifier &verifier) const { return verifier.VerifyTableStart(data_); } // Verify a particular field. template bool VerifyField(const Verifier &verifier, voffset_t field) const { // Calling GetOptionalFieldOffset should be safe now thanks to // VerifyTable(). auto field_offset = GetOptionalFieldOffset(field); // Check the actual field. return !field_offset || verifier.Verify(data_, field_offset); } // VerifyField for required fields. template bool VerifyFieldRequired(const Verifier &verifier, voffset_t field) const { auto field_offset = GetOptionalFieldOffset(field); return verifier.Check(field_offset != 0) && verifier.Verify(data_, field_offset); } // Versions for offsets. bool VerifyOffset(const Verifier &verifier, voffset_t field) const { auto field_offset = GetOptionalFieldOffset(field); return !field_offset || verifier.VerifyOffset(data_, field_offset); } bool VerifyOffsetRequired(const Verifier &verifier, voffset_t field) const { auto field_offset = GetOptionalFieldOffset(field); return verifier.Check(field_offset != 0) && verifier.VerifyOffset(data_, field_offset); } private: // private constructor & copy constructor: you obtain instances of this // class by pointing to existing data only Table(); Table(const Table &other); uint8_t data_[1]; }; template void FlatBufferBuilder::Required(Offset table, voffset_t field) { auto table_ptr = reinterpret_cast(buf_.data_at(table.o)); bool ok = table_ptr->GetOptionalFieldOffset(field) != 0; // If this fails, the caller will show what field needs to be set. FLATBUFFERS_ASSERT(ok); (void)ok; } /// @brief This can compute the start of a FlatBuffer from a root pointer, i.e. /// it is the opposite transformation of GetRoot(). /// This may be useful if you want to pass on a root and have the recipient /// delete the buffer afterwards. inline const uint8_t *GetBufferStartFromRootPointer(const void *root) { auto table = reinterpret_cast(root); auto vtable = table->GetVTable(); // Either the vtable is before the root or after the root. auto start = (std::min)(vtable, reinterpret_cast(root)); // Align to at least sizeof(uoffset_t). start = reinterpret_cast(reinterpret_cast(start) & ~(sizeof(uoffset_t) - 1)); // Additionally, there may be a file_identifier in the buffer, and the root // offset. The buffer may have been aligned to any size between // sizeof(uoffset_t) and FLATBUFFERS_MAX_ALIGNMENT (see "force_align"). // Sadly, the exact alignment is only known when constructing the buffer, // since it depends on the presence of values with said alignment properties. // So instead, we simply look at the next uoffset_t values (root, // file_identifier, and alignment padding) to see which points to the root. // None of the other values can "impersonate" the root since they will either // be 0 or four ASCII characters. static_assert(FlatBufferBuilder::kFileIdentifierLength == sizeof(uoffset_t), "file_identifier is assumed to be the same size as uoffset_t"); for (auto possible_roots = FLATBUFFERS_MAX_ALIGNMENT / sizeof(uoffset_t) + 1; possible_roots; possible_roots--) { start -= sizeof(uoffset_t); if (ReadScalar(start) + start == reinterpret_cast(root)) return start; } // We didn't find the root, either the "root" passed isn't really a root, // or the buffer is corrupt. // Assert, because calling this function with bad data may cause reads // outside of buffer boundaries. FLATBUFFERS_ASSERT(false); return nullptr; } /// @brief This return the prefixed size of a FlatBuffer. inline uoffset_t GetPrefixedSize(const uint8_t* buf){ return ReadScalar(buf); } // Base class for native objects (FlatBuffer data de-serialized into native // C++ data structures). // Contains no functionality, purely documentative. struct NativeTable {}; /// @brief Function types to be used with resolving hashes into objects and /// back again. The resolver gets a pointer to a field inside an object API /// object that is of the type specified in the schema using the attribute /// `cpp_type` (it is thus important whatever you write to this address /// matches that type). The value of this field is initially null, so you /// may choose to implement a delayed binding lookup using this function /// if you wish. The resolver does the opposite lookup, for when the object /// is being serialized again. typedef uint64_t hash_value_t; // clang-format off #ifdef FLATBUFFERS_CPP98_STL typedef void (*resolver_function_t)(void **pointer_adr, hash_value_t hash); typedef hash_value_t (*rehasher_function_t)(void *pointer); #else typedef std::function resolver_function_t; typedef std::function rehasher_function_t; #endif // clang-format on // Helper function to test if a field is present, using any of the field // enums in the generated code. // `table` must be a generated table type. Since this is a template parameter, // this is not typechecked to be a subclass of Table, so beware! // Note: this function will return false for fields equal to the default // value, since they're not stored in the buffer (unless force_defaults was // used). template bool IsFieldPresent(const T *table, voffset_t field) { // Cast, since Table is a private baseclass of any table types. return reinterpret_cast(table)->CheckField(field); } // Utility function for reverse lookups on the EnumNames*() functions // (in the generated C++ code) // names must be NULL terminated. inline int LookupEnum(const char **names, const char *name) { for (const char **p = names; *p; p++) if (!strcmp(*p, name)) return static_cast(p - names); return -1; } // These macros allow us to layout a struct with a guarantee that they'll end // up looking the same on different compilers and platforms. // It does this by disallowing the compiler to do any padding, and then // does padding itself by inserting extra padding fields that make every // element aligned to its own size. // Additionally, it manually sets the alignment of the struct as a whole, // which is typically its largest element, or a custom size set in the schema // by the force_align attribute. // These are used in the generated code only. // clang-format off #if defined(_MSC_VER) #define FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(alignment) \ __pragma(pack(1)); \ struct __declspec(align(alignment)) #define FLATBUFFERS_STRUCT_END(name, size) \ __pragma(pack()); \ static_assert(sizeof(name) == size, "compiler breaks packing rules") #elif defined(__GNUC__) || defined(__clang__) #define FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(alignment) \ _Pragma("pack(1)") \ struct __attribute__((aligned(alignment))) #define FLATBUFFERS_STRUCT_END(name, size) \ _Pragma("pack()") \ static_assert(sizeof(name) == size, "compiler breaks packing rules") #else #error Unknown compiler, please define structure alignment macros #endif // clang-format on // Minimal reflection via code generation. // Besides full-fat reflection (see reflection.h) and parsing/printing by // loading schemas (see idl.h), we can also have code generation for mimimal // reflection data which allows pretty-printing and other uses without needing // a schema or a parser. // Generate code with --reflect-types (types only) or --reflect-names (names // also) to enable. // See minireflect.h for utilities using this functionality. // These types are organized slightly differently as the ones in idl.h. enum SequenceType { ST_TABLE, ST_STRUCT, ST_UNION, ST_ENUM }; // Scalars have the same order as in idl.h // clang-format off #define FLATBUFFERS_GEN_ELEMENTARY_TYPES(ET) \ ET(ET_UTYPE) \ ET(ET_BOOL) \ ET(ET_CHAR) \ ET(ET_UCHAR) \ ET(ET_SHORT) \ ET(ET_USHORT) \ ET(ET_INT) \ ET(ET_UINT) \ ET(ET_LONG) \ ET(ET_ULONG) \ ET(ET_FLOAT) \ ET(ET_DOUBLE) \ ET(ET_STRING) \ ET(ET_SEQUENCE) // See SequenceType. enum ElementaryType { #define FLATBUFFERS_ET(E) E, FLATBUFFERS_GEN_ELEMENTARY_TYPES(FLATBUFFERS_ET) #undef FLATBUFFERS_ET }; inline const char * const *ElementaryTypeNames() { static const char * const names[] = { #define FLATBUFFERS_ET(E) #E, FLATBUFFERS_GEN_ELEMENTARY_TYPES(FLATBUFFERS_ET) #undef FLATBUFFERS_ET }; return names; } // clang-format on // Basic type info cost just 16bits per field! struct TypeCode { uint16_t base_type : 4; // ElementaryType uint16_t is_vector : 1; int16_t sequence_ref : 11; // Index into type_refs below, or -1 for none. }; static_assert(sizeof(TypeCode) == 2, "TypeCode"); struct TypeTable; // Signature of the static method present in each type. typedef const TypeTable *(*TypeFunction)(); struct TypeTable { SequenceType st; size_t num_elems; // of type_codes, values, names (but not type_refs). const TypeCode *type_codes; // num_elems count const TypeFunction *type_refs; // less than num_elems entries (see TypeCode). const int32_t *values; // Only set for non-consecutive enum/union or structs. const char * const *names; // Only set if compiled with --reflect-names. }; // String which identifies the current version of FlatBuffers. // flatbuffer_version_string is used by Google developers to identify which // applications uploaded to Google Play are using this library. This allows // the development team at Google to determine the popularity of the library. // How it works: Applications that are uploaded to the Google Play Store are // scanned for this version string. We track which applications are using it // to measure popularity. You are free to remove it (of course) but we would // appreciate if you left it in. // Weak linkage is culled by VS & doesn't work on cygwin. // clang-format off #if !defined(_WIN32) && !defined(__CYGWIN__) extern volatile __attribute__((weak)) const char *flatbuffer_version_string; volatile __attribute__((weak)) const char *flatbuffer_version_string = "FlatBuffers " FLATBUFFERS_STRING(FLATBUFFERS_VERSION_MAJOR) "." FLATBUFFERS_STRING(FLATBUFFERS_VERSION_MINOR) "." FLATBUFFERS_STRING(FLATBUFFERS_VERSION_REVISION); #endif // !defined(_WIN32) && !defined(__CYGWIN__) #define FLATBUFFERS_DEFINE_BITMASK_OPERATORS(E, T)\ inline E operator | (E lhs, E rhs){\ return E(T(lhs) | T(rhs));\ }\ inline E operator & (E lhs, E rhs){\ return E(T(lhs) & T(rhs));\ }\ inline E operator ^ (E lhs, E rhs){\ return E(T(lhs) ^ T(rhs));\ }\ inline E operator ~ (E lhs){\ return E(~T(lhs));\ }\ inline E operator |= (E &lhs, E rhs){\ lhs = lhs | rhs;\ return lhs;\ }\ inline E operator &= (E &lhs, E rhs){\ lhs = lhs & rhs;\ return lhs;\ }\ inline E operator ^= (E &lhs, E rhs){\ lhs = lhs ^ rhs;\ return lhs;\ }\ inline bool operator !(E rhs) \ {\ return !bool(T(rhs)); \ } /// @endcond } // namespace flatbuffers // clang-format on #endif // FLATBUFFERS_H_ treesheets-1.0.2/lobster/include/flatbuffers/flatc.h000066400000000000000000000054641352107072600225430ustar00rootroot00000000000000/* * Copyright 2017 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include "flatbuffers/flatbuffers.h" #include "flatbuffers/idl.h" #include "flatbuffers/util.h" #ifndef FLATC_H_ # define FLATC_H_ namespace flatbuffers { class FlatCompiler { public: // Output generator for the various programming languages and formats we // support. struct Generator { typedef bool (*GenerateFn)(const flatbuffers::Parser &parser, const std::string &path, const std::string &file_name); typedef std::string (*MakeRuleFn)(const flatbuffers::Parser &parser, const std::string &path, const std::string &file_name); GenerateFn generate; const char *generator_opt_short; const char *generator_opt_long; const char *lang_name; bool schema_only; GenerateFn generateGRPC; flatbuffers::IDLOptions::Language lang; const char *generator_help; MakeRuleFn make_rule; }; typedef void (*WarnFn)(const FlatCompiler *flatc, const std::string &warn, bool show_exe_name); typedef void (*ErrorFn)(const FlatCompiler *flatc, const std::string &err, bool usage, bool show_exe_name); // Parameters required to initialize the FlatCompiler. struct InitParams { InitParams() : generators(nullptr), num_generators(0), warn_fn(nullptr), error_fn(nullptr) {} const Generator *generators; size_t num_generators; WarnFn warn_fn; ErrorFn error_fn; }; explicit FlatCompiler(const InitParams ¶ms) : params_(params) {} int Compile(int argc, const char **argv); std::string GetUsageString(const char *program_name) const; private: void ParseFile(flatbuffers::Parser &parser, const std::string &filename, const std::string &contents, std::vector &include_directories) const; void Warn(const std::string &warn, bool show_exe_name = true) const; void Error(const std::string &err, bool usage = true, bool show_exe_name = true) const; InitParams params_; }; } // namespace flatbuffers #endif // FLATC_H_ treesheets-1.0.2/lobster/include/flatbuffers/flexbuffers.h000066400000000000000000001457111352107072600237650ustar00rootroot00000000000000/* * Copyright 2017 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_FLEXBUFFERS_H_ #define FLATBUFFERS_FLEXBUFFERS_H_ #include // Used to select STL variant. #include "flatbuffers/base.h" // We use the basic binary writing functions from the regular FlatBuffers. #include "flatbuffers/util.h" #ifdef _MSC_VER # include #endif #if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable : 4127) // C4127: conditional expression is constant #endif namespace flexbuffers { class Reference; class Map; // These are used in the lower 2 bits of a type field to determine the size of // the elements (and or size field) of the item pointed to (e.g. vector). enum BitWidth { BIT_WIDTH_8 = 0, BIT_WIDTH_16 = 1, BIT_WIDTH_32 = 2, BIT_WIDTH_64 = 3, }; // These are used as the upper 6 bits of a type field to indicate the actual // type. enum Type { FBT_NULL = 0, FBT_INT = 1, FBT_UINT = 2, FBT_FLOAT = 3, // Types above stored inline, types below store an offset. FBT_KEY = 4, FBT_STRING = 5, FBT_INDIRECT_INT = 6, FBT_INDIRECT_UINT = 7, FBT_INDIRECT_FLOAT = 8, FBT_MAP = 9, FBT_VECTOR = 10, // Untyped. FBT_VECTOR_INT = 11, // Typed any size (stores no type table). FBT_VECTOR_UINT = 12, FBT_VECTOR_FLOAT = 13, FBT_VECTOR_KEY = 14, FBT_VECTOR_STRING = 15, FBT_VECTOR_INT2 = 16, // Typed tuple (no type table, no size field). FBT_VECTOR_UINT2 = 17, FBT_VECTOR_FLOAT2 = 18, FBT_VECTOR_INT3 = 19, // Typed triple (no type table, no size field). FBT_VECTOR_UINT3 = 20, FBT_VECTOR_FLOAT3 = 21, FBT_VECTOR_INT4 = 22, // Typed quad (no type table, no size field). FBT_VECTOR_UINT4 = 23, FBT_VECTOR_FLOAT4 = 24, FBT_BLOB = 25, FBT_BOOL = 26, FBT_VECTOR_BOOL = 36, // To Allow the same type of conversion of type to vector type }; inline bool IsInline(Type t) { return t <= FBT_FLOAT || t == FBT_BOOL; } inline bool IsTypedVectorElementType(Type t) { return (t >= FBT_INT && t <= FBT_STRING) || t == FBT_BOOL; } inline bool IsTypedVector(Type t) { return (t >= FBT_VECTOR_INT && t <= FBT_VECTOR_STRING) || t == FBT_VECTOR_BOOL; } inline bool IsFixedTypedVector(Type t) { return t >= FBT_VECTOR_INT2 && t <= FBT_VECTOR_FLOAT4; } inline Type ToTypedVector(Type t, size_t fixed_len = 0) { FLATBUFFERS_ASSERT(IsTypedVectorElementType(t)); switch (fixed_len) { case 0: return static_cast(t - FBT_INT + FBT_VECTOR_INT); case 2: return static_cast(t - FBT_INT + FBT_VECTOR_INT2); case 3: return static_cast(t - FBT_INT + FBT_VECTOR_INT3); case 4: return static_cast(t - FBT_INT + FBT_VECTOR_INT4); default: FLATBUFFERS_ASSERT(0); return FBT_NULL; } } inline Type ToTypedVectorElementType(Type t) { FLATBUFFERS_ASSERT(IsTypedVector(t)); return static_cast(t - FBT_VECTOR_INT + FBT_INT); } inline Type ToFixedTypedVectorElementType(Type t, uint8_t *len) { FLATBUFFERS_ASSERT(IsFixedTypedVector(t)); auto fixed_type = t - FBT_VECTOR_INT2; *len = static_cast(fixed_type / 3 + 2); // 3 types each, starting from length 2. return static_cast(fixed_type % 3 + FBT_INT); } // TODO: implement proper support for 8/16bit floats, or decide not to // support them. typedef int16_t half; typedef int8_t quarter; // TODO: can we do this without conditionals using intrinsics or inline asm // on some platforms? Given branch prediction the method below should be // decently quick, but it is the most frequently executed function. // We could do an (unaligned) 64-bit read if we ifdef out the platforms for // which that doesn't work (or where we'd read into un-owned memory). template R ReadSizedScalar(const uint8_t *data, uint8_t byte_width) { return byte_width < 4 ? (byte_width < 2 ? static_cast(flatbuffers::ReadScalar(data)) : static_cast(flatbuffers::ReadScalar(data))) : (byte_width < 8 ? static_cast(flatbuffers::ReadScalar(data)) : static_cast(flatbuffers::ReadScalar(data))); } inline int64_t ReadInt64(const uint8_t *data, uint8_t byte_width) { return ReadSizedScalar( data, byte_width); } inline uint64_t ReadUInt64(const uint8_t *data, uint8_t byte_width) { // This is the "hottest" function (all offset lookups use this), so worth // optimizing if possible. // TODO: GCC apparently replaces memcpy by a rep movsb, but only if count is a // constant, which here it isn't. Test if memcpy is still faster than // the conditionals in ReadSizedScalar. Can also use inline asm. // clang-format off #ifdef _MSC_VER uint64_t u = 0; __movsb(reinterpret_cast(&u), reinterpret_cast(data), byte_width); return flatbuffers::EndianScalar(u); #else return ReadSizedScalar( data, byte_width); #endif // clang-format on } inline double ReadDouble(const uint8_t *data, uint8_t byte_width) { return ReadSizedScalar(data, byte_width); } inline const uint8_t *Indirect(const uint8_t *offset, uint8_t byte_width) { return offset - ReadUInt64(offset, byte_width); } template const uint8_t *Indirect(const uint8_t *offset) { return offset - flatbuffers::ReadScalar(offset); } inline BitWidth WidthU(uint64_t u) { #define FLATBUFFERS_GET_FIELD_BIT_WIDTH(value, width) \ { \ if (!((u) & ~((1ULL << (width)) - 1ULL))) return BIT_WIDTH_##width; \ } FLATBUFFERS_GET_FIELD_BIT_WIDTH(u, 8); FLATBUFFERS_GET_FIELD_BIT_WIDTH(u, 16); FLATBUFFERS_GET_FIELD_BIT_WIDTH(u, 32); #undef FLATBUFFERS_GET_FIELD_BIT_WIDTH return BIT_WIDTH_64; } inline BitWidth WidthI(int64_t i) { auto u = static_cast(i) << 1; return WidthU(i >= 0 ? u : ~u); } inline BitWidth WidthF(double f) { return static_cast(static_cast(f)) == f ? BIT_WIDTH_32 : BIT_WIDTH_64; } // Base class of all types below. // Points into the data buffer and allows access to one type. class Object { public: Object(const uint8_t *data, uint8_t byte_width) : data_(data), byte_width_(byte_width) {} protected: const uint8_t *data_; uint8_t byte_width_; }; // Stores size in `byte_width_` bytes before data_ pointer. class Sized : public Object { public: Sized(const uint8_t *data, uint8_t byte_width) : Object(data, byte_width) {} size_t size() const { return static_cast(ReadUInt64(data_ - byte_width_, byte_width_)); } }; class String : public Sized { public: String(const uint8_t *data, uint8_t byte_width) : Sized(data, byte_width) {} size_t length() const { return size(); } const char *c_str() const { return reinterpret_cast(data_); } std::string str() const { return std::string(c_str(), length()); } static String EmptyString() { static const uint8_t empty_string[] = { 0 /*len*/, 0 /*terminator*/ }; return String(empty_string + 1, 1); } bool IsTheEmptyString() const { return data_ == EmptyString().data_; } }; class Blob : public Sized { public: Blob(const uint8_t *data_buf, uint8_t byte_width) : Sized(data_buf, byte_width) {} static Blob EmptyBlob() { static const uint8_t empty_blob[] = { 0 /*len*/ }; return Blob(empty_blob + 1, 1); } bool IsTheEmptyBlob() const { return data_ == EmptyBlob().data_; } const uint8_t *data() const { return data_; } }; class Vector : public Sized { public: Vector(const uint8_t *data, uint8_t byte_width) : Sized(data, byte_width) {} Reference operator[](size_t i) const; static Vector EmptyVector() { static const uint8_t empty_vector[] = { 0 /*len*/ }; return Vector(empty_vector + 1, 1); } bool IsTheEmptyVector() const { return data_ == EmptyVector().data_; } }; class TypedVector : public Sized { public: TypedVector(const uint8_t *data, uint8_t byte_width, Type element_type) : Sized(data, byte_width), type_(element_type) {} Reference operator[](size_t i) const; static TypedVector EmptyTypedVector() { static const uint8_t empty_typed_vector[] = { 0 /*len*/ }; return TypedVector(empty_typed_vector + 1, 1, FBT_INT); } bool IsTheEmptyVector() const { return data_ == TypedVector::EmptyTypedVector().data_; } Type ElementType() { return type_; } private: Type type_; friend Map; }; class FixedTypedVector : public Object { public: FixedTypedVector(const uint8_t *data, uint8_t byte_width, Type element_type, uint8_t len) : Object(data, byte_width), type_(element_type), len_(len) {} Reference operator[](size_t i) const; static FixedTypedVector EmptyFixedTypedVector() { static const uint8_t fixed_empty_vector[] = { 0 /* unused */ }; return FixedTypedVector(fixed_empty_vector, 1, FBT_INT, 0); } bool IsTheEmptyFixedTypedVector() const { return data_ == FixedTypedVector::EmptyFixedTypedVector().data_; } Type ElementType() { return type_; } uint8_t size() { return len_; } private: Type type_; uint8_t len_; }; class Map : public Vector { public: Map(const uint8_t *data, uint8_t byte_width) : Vector(data, byte_width) {} Reference operator[](const char *key) const; Reference operator[](const std::string &key) const; Vector Values() const { return Vector(data_, byte_width_); } TypedVector Keys() const { const size_t num_prefixed_fields = 3; auto keys_offset = data_ - byte_width_ * num_prefixed_fields; return TypedVector(Indirect(keys_offset, byte_width_), static_cast( ReadUInt64(keys_offset + byte_width_, byte_width_)), FBT_KEY); } static Map EmptyMap() { static const uint8_t empty_map[] = { 0 /*keys_len*/, 0 /*keys_offset*/, 1 /*keys_width*/, 0 /*len*/ }; return Map(empty_map + 4, 1); } bool IsTheEmptyMap() const { return data_ == EmptyMap().data_; } }; class Reference { public: Reference(const uint8_t *data, uint8_t parent_width, uint8_t byte_width, Type type) : data_(data), parent_width_(parent_width), byte_width_(byte_width), type_(type) {} Reference(const uint8_t *data, uint8_t parent_width, uint8_t packed_type) : data_(data), parent_width_(parent_width) { byte_width_ = 1U << static_cast(packed_type & 3); type_ = static_cast(packed_type >> 2); } Type GetType() const { return type_; } bool IsNull() const { return type_ == FBT_NULL; } bool IsBool() const { return type_ == FBT_BOOL; } bool IsInt() const { return type_ == FBT_INT || type_ == FBT_INDIRECT_INT; } bool IsUInt() const { return type_ == FBT_UINT || type_ == FBT_INDIRECT_UINT; } bool IsIntOrUint() const { return IsInt() || IsUInt(); } bool IsFloat() const { return type_ == FBT_FLOAT || type_ == FBT_INDIRECT_FLOAT; } bool IsNumeric() const { return IsIntOrUint() || IsFloat(); } bool IsString() const { return type_ == FBT_STRING; } bool IsKey() const { return type_ == FBT_KEY; } bool IsVector() const { return type_ == FBT_VECTOR || type_ == FBT_MAP; } bool IsTypedVector() const { return flexbuffers::IsTypedVector(type_); } bool IsFixedTypedVector() const { return flexbuffers::IsFixedTypedVector(type_); } bool IsAnyVector() const { return (IsTypedVector() || IsFixedTypedVector() || IsVector());} bool IsMap() const { return type_ == FBT_MAP; } bool IsBlob() const { return type_ == FBT_BLOB; } bool AsBool() const { return (type_ == FBT_BOOL ? ReadUInt64(data_, parent_width_) : AsUInt64()) != 0; } // Reads any type as a int64_t. Never fails, does most sensible conversion. // Truncates floats, strings are attempted to be parsed for a number, // vectors/maps return their size. Returns 0 if all else fails. int64_t AsInt64() const { if (type_ == FBT_INT) { // A fast path for the common case. return ReadInt64(data_, parent_width_); } else switch (type_) { case FBT_INDIRECT_INT: return ReadInt64(Indirect(), byte_width_); case FBT_UINT: return ReadUInt64(data_, parent_width_); case FBT_INDIRECT_UINT: return ReadUInt64(Indirect(), byte_width_); case FBT_FLOAT: return static_cast(ReadDouble(data_, parent_width_)); case FBT_INDIRECT_FLOAT: return static_cast(ReadDouble(Indirect(), byte_width_)); case FBT_NULL: return 0; case FBT_STRING: return flatbuffers::StringToInt(AsString().c_str()); case FBT_VECTOR: return static_cast(AsVector().size()); case FBT_BOOL: return ReadInt64(data_, parent_width_); default: // Convert other things to int. return 0; } } // TODO: could specialize these to not use AsInt64() if that saves // extension ops in generated code, and use a faster op than ReadInt64. int32_t AsInt32() const { return static_cast(AsInt64()); } int16_t AsInt16() const { return static_cast(AsInt64()); } int8_t AsInt8() const { return static_cast(AsInt64()); } uint64_t AsUInt64() const { if (type_ == FBT_UINT) { // A fast path for the common case. return ReadUInt64(data_, parent_width_); } else switch (type_) { case FBT_INDIRECT_UINT: return ReadUInt64(Indirect(), byte_width_); case FBT_INT: return ReadInt64(data_, parent_width_); case FBT_INDIRECT_INT: return ReadInt64(Indirect(), byte_width_); case FBT_FLOAT: return static_cast(ReadDouble(data_, parent_width_)); case FBT_INDIRECT_FLOAT: return static_cast(ReadDouble(Indirect(), byte_width_)); case FBT_NULL: return 0; case FBT_STRING: return flatbuffers::StringToUInt(AsString().c_str()); case FBT_VECTOR: return static_cast(AsVector().size()); case FBT_BOOL: return ReadUInt64(data_, parent_width_); default: // Convert other things to uint. return 0; } } uint32_t AsUInt32() const { return static_cast(AsUInt64()); } uint16_t AsUInt16() const { return static_cast(AsUInt64()); } uint8_t AsUInt8() const { return static_cast(AsUInt64()); } double AsDouble() const { if (type_ == FBT_FLOAT) { // A fast path for the common case. return ReadDouble(data_, parent_width_); } else switch (type_) { case FBT_INDIRECT_FLOAT: return ReadDouble(Indirect(), byte_width_); case FBT_INT: return static_cast(ReadInt64(data_, parent_width_)); case FBT_UINT: return static_cast(ReadUInt64(data_, parent_width_)); case FBT_INDIRECT_INT: return static_cast(ReadInt64(Indirect(), byte_width_)); case FBT_INDIRECT_UINT: return static_cast(ReadUInt64(Indirect(), byte_width_)); case FBT_NULL: return 0.0; case FBT_STRING: return strtod(AsString().c_str(), nullptr); case FBT_VECTOR: return static_cast(AsVector().size()); case FBT_BOOL: return static_cast(ReadUInt64(data_, parent_width_)); default: // Convert strings and other things to float. return 0; } } float AsFloat() const { return static_cast(AsDouble()); } const char *AsKey() const { if (type_ == FBT_KEY) { return reinterpret_cast(Indirect()); } else { return ""; } } // This function returns the empty string if you try to read a not-string. String AsString() const { if (type_ == FBT_STRING) { return String(Indirect(), byte_width_); } else { return String::EmptyString(); } } // Unlike AsString(), this will convert any type to a std::string. std::string ToString() { std::string s; ToString(false, false, s); return s; } // Convert any type to a JSON-like string. strings_quoted determines if // string values at the top level receive "" quotes (inside other values // they always do). keys_quoted determines if keys are quoted, at any level. // TODO(wvo): add further options to have indentation/newlines. void ToString(bool strings_quoted, bool keys_quoted, std::string &s) const { if (type_ == FBT_STRING) { String str(Indirect(), byte_width_); if (strings_quoted) { flatbuffers::EscapeString(str.c_str(), str.length(), &s, true, false); } else { s.append(str.c_str(), str.length()); } } else if (IsKey()) { auto str = AsKey(); if (keys_quoted) { flatbuffers::EscapeString(str, strlen(str), &s, true, false); } else { s += str; } } else if (IsInt()) { s += flatbuffers::NumToString(AsInt64()); } else if (IsUInt()) { s += flatbuffers::NumToString(AsUInt64()); } else if (IsFloat()) { s += flatbuffers::NumToString(AsDouble()); } else if (IsNull()) { s += "null"; } else if (IsBool()) { s += AsBool() ? "true" : "false"; } else if (IsMap()) { s += "{ "; auto m = AsMap(); auto keys = m.Keys(); auto vals = m.Values(); for (size_t i = 0; i < keys.size(); i++) { keys[i].ToString(true, keys_quoted, s); s += ": "; vals[i].ToString(true, keys_quoted, s); if (i < keys.size() - 1) s += ", "; } s += " }"; } else if (IsVector()) { s += "[ "; auto v = AsVector(); for (size_t i = 0; i < v.size(); i++) { v[i].ToString(true, keys_quoted, s); if (i < v.size() - 1) s += ", "; } s += " ]"; } else { s += "(?)"; } } // This function returns the empty blob if you try to read a not-blob. // Strings can be viewed as blobs too. Blob AsBlob() const { if (type_ == FBT_BLOB || type_ == FBT_STRING) { return Blob(Indirect(), byte_width_); } else { return Blob::EmptyBlob(); } } // This function returns the empty vector if you try to read a not-vector. // Maps can be viewed as vectors too. Vector AsVector() const { if (type_ == FBT_VECTOR || type_ == FBT_MAP) { return Vector(Indirect(), byte_width_); } else { return Vector::EmptyVector(); } } TypedVector AsTypedVector() const { if (IsTypedVector()) { return TypedVector(Indirect(), byte_width_, ToTypedVectorElementType(type_)); } else { return TypedVector::EmptyTypedVector(); } } FixedTypedVector AsFixedTypedVector() const { if (IsFixedTypedVector()) { uint8_t len = 0; auto vtype = ToFixedTypedVectorElementType(type_, &len); return FixedTypedVector(Indirect(), byte_width_, vtype, len); } else { return FixedTypedVector::EmptyFixedTypedVector(); } } Map AsMap() const { if (type_ == FBT_MAP) { return Map(Indirect(), byte_width_); } else { return Map::EmptyMap(); } } template T As(); // Experimental: Mutation functions. // These allow scalars in an already created buffer to be updated in-place. // Since by default scalars are stored in the smallest possible space, // the new value may not fit, in which case these functions return false. // To avoid this, you can construct the values you intend to mutate using // Builder::ForceMinimumBitWidth. bool MutateInt(int64_t i) { if (type_ == FBT_INT) { return Mutate(data_, i, parent_width_, WidthI(i)); } else if (type_ == FBT_INDIRECT_INT) { return Mutate(Indirect(), i, byte_width_, WidthI(i)); } else if (type_ == FBT_UINT) { auto u = static_cast(i); return Mutate(data_, u, parent_width_, WidthU(u)); } else if (type_ == FBT_INDIRECT_UINT) { auto u = static_cast(i); return Mutate(Indirect(), u, byte_width_, WidthU(u)); } else { return false; } } bool MutateBool(bool b) { return type_ == FBT_BOOL && Mutate(data_, b, parent_width_, BIT_WIDTH_8); } bool MutateUInt(uint64_t u) { if (type_ == FBT_UINT) { return Mutate(data_, u, parent_width_, WidthU(u)); } else if (type_ == FBT_INDIRECT_UINT) { return Mutate(Indirect(), u, byte_width_, WidthU(u)); } else if (type_ == FBT_INT) { auto i = static_cast(u); return Mutate(data_, i, parent_width_, WidthI(i)); } else if (type_ == FBT_INDIRECT_INT) { auto i = static_cast(u); return Mutate(Indirect(), i, byte_width_, WidthI(i)); } else { return false; } } bool MutateFloat(float f) { if (type_ == FBT_FLOAT) { return MutateF(data_, f, parent_width_, BIT_WIDTH_32); } else if (type_ == FBT_INDIRECT_FLOAT) { return MutateF(Indirect(), f, byte_width_, BIT_WIDTH_32); } else { return false; } } bool MutateFloat(double d) { if (type_ == FBT_FLOAT) { return MutateF(data_, d, parent_width_, WidthF(d)); } else if (type_ == FBT_INDIRECT_FLOAT) { return MutateF(Indirect(), d, byte_width_, WidthF(d)); } else { return false; } } bool MutateString(const char *str, size_t len) { auto s = AsString(); if (s.IsTheEmptyString()) return false; // This is very strict, could allow shorter strings, but that creates // garbage. if (s.length() != len) return false; memcpy(const_cast(s.c_str()), str, len); return true; } bool MutateString(const char *str) { return MutateString(str, strlen(str)); } bool MutateString(const std::string &str) { return MutateString(str.data(), str.length()); } private: const uint8_t *Indirect() const { return flexbuffers::Indirect(data_, parent_width_); } template bool Mutate(const uint8_t *dest, T t, size_t byte_width, BitWidth value_width) { auto fits = static_cast(static_cast(1U) << value_width) <= byte_width; if (fits) { t = flatbuffers::EndianScalar(t); memcpy(const_cast(dest), &t, byte_width); } return fits; } template bool MutateF(const uint8_t *dest, T t, size_t byte_width, BitWidth value_width) { if (byte_width == sizeof(double)) return Mutate(dest, static_cast(t), byte_width, value_width); if (byte_width == sizeof(float)) return Mutate(dest, static_cast(t), byte_width, value_width); FLATBUFFERS_ASSERT(false); return false; } const uint8_t *data_; uint8_t parent_width_; uint8_t byte_width_; Type type_; }; // Template specialization for As(). template<> inline bool Reference::As() { return AsBool(); } template<> inline int8_t Reference::As() { return AsInt8(); } template<> inline int16_t Reference::As() { return AsInt16(); } template<> inline int32_t Reference::As() { return AsInt32(); } template<> inline int64_t Reference::As() { return AsInt64(); } template<> inline uint8_t Reference::As() { return AsUInt8(); } template<> inline uint16_t Reference::As() { return AsUInt16(); } template<> inline uint32_t Reference::As() { return AsUInt32(); } template<> inline uint64_t Reference::As() { return AsUInt64(); } template<> inline double Reference::As() { return AsDouble(); } template<> inline float Reference::As() { return AsFloat(); } template<> inline String Reference::As() { return AsString(); } template<> inline std::string Reference::As() { return AsString().str(); } template<> inline Blob Reference::As() { return AsBlob(); } template<> inline Vector Reference::As() { return AsVector(); } template<> inline TypedVector Reference::As() { return AsTypedVector(); } template<> inline FixedTypedVector Reference::As() { return AsFixedTypedVector(); } template<> inline Map Reference::As() { return AsMap(); } inline uint8_t PackedType(BitWidth bit_width, Type type) { return static_cast(bit_width | (type << 2)); } inline uint8_t NullPackedType() { return PackedType(BIT_WIDTH_8, FBT_NULL); } // Vector accessors. // Note: if you try to access outside of bounds, you get a Null value back // instead. Normally this would be an assert, but since this is "dynamically // typed" data, you may not want that (someone sends you a 2d vector and you // wanted 3d). // The Null converts seamlessly into a default value for any other type. // TODO(wvo): Could introduce an #ifdef that makes this into an assert? inline Reference Vector::operator[](size_t i) const { auto len = size(); if (i >= len) return Reference(nullptr, 1, NullPackedType()); auto packed_type = (data_ + len * byte_width_)[i]; auto elem = data_ + i * byte_width_; return Reference(elem, byte_width_, packed_type); } inline Reference TypedVector::operator[](size_t i) const { auto len = size(); if (i >= len) return Reference(nullptr, 1, NullPackedType()); auto elem = data_ + i * byte_width_; return Reference(elem, byte_width_, 1, type_); } inline Reference FixedTypedVector::operator[](size_t i) const { if (i >= len_) return Reference(nullptr, 1, NullPackedType()); auto elem = data_ + i * byte_width_; return Reference(elem, byte_width_, 1, type_); } template int KeyCompare(const void *key, const void *elem) { auto str_elem = reinterpret_cast( Indirect(reinterpret_cast(elem))); auto skey = reinterpret_cast(key); return strcmp(skey, str_elem); } inline Reference Map::operator[](const char *key) const { auto keys = Keys(); // We can't pass keys.byte_width_ to the comparison function, so we have // to pick the right one ahead of time. int (*comp)(const void *, const void *) = nullptr; switch (keys.byte_width_) { case 1: comp = KeyCompare; break; case 2: comp = KeyCompare; break; case 4: comp = KeyCompare; break; case 8: comp = KeyCompare; break; } auto res = std::bsearch(key, keys.data_, keys.size(), keys.byte_width_, comp); if (!res) return Reference(nullptr, 1, NullPackedType()); auto i = (reinterpret_cast(res) - keys.data_) / keys.byte_width_; return (*static_cast(this))[i]; } inline Reference Map::operator[](const std::string &key) const { return (*this)[key.c_str()]; } inline Reference GetRoot(const uint8_t *buffer, size_t size) { // See Finish() below for the serialization counterpart of this. // The root starts at the end of the buffer, so we parse backwards from there. auto end = buffer + size; auto byte_width = *--end; auto packed_type = *--end; end -= byte_width; // The root data item. return Reference(end, byte_width, packed_type); } inline Reference GetRoot(const std::vector &buffer) { return GetRoot(flatbuffers::vector_data(buffer), buffer.size()); } // Flags that configure how the Builder behaves. // The "Share" flags determine if the Builder automatically tries to pool // this type. Pooling can reduce the size of serialized data if there are // multiple maps of the same kind, at the expense of slightly slower // serialization (the cost of lookups) and more memory use (std::set). // By default this is on for keys, but off for strings. // Turn keys off if you have e.g. only one map. // Turn strings on if you expect many non-unique string values. // Additionally, sharing key vectors can save space if you have maps with // identical field populations. enum BuilderFlag { BUILDER_FLAG_NONE = 0, BUILDER_FLAG_SHARE_KEYS = 1, BUILDER_FLAG_SHARE_STRINGS = 2, BUILDER_FLAG_SHARE_KEYS_AND_STRINGS = 3, BUILDER_FLAG_SHARE_KEY_VECTORS = 4, BUILDER_FLAG_SHARE_ALL = 7, }; class Builder FLATBUFFERS_FINAL_CLASS { public: Builder(size_t initial_size = 256, BuilderFlag flags = BUILDER_FLAG_SHARE_KEYS) : buf_(initial_size), finished_(false), flags_(flags), force_min_bit_width_(BIT_WIDTH_8), key_pool(KeyOffsetCompare(buf_)), string_pool(StringOffsetCompare(buf_)) { buf_.clear(); } /// @brief Get the serialized buffer (after you call `Finish()`). /// @return Returns a vector owned by this class. const std::vector &GetBuffer() const { Finished(); return buf_; } // Size of the buffer. Does not include unfinished values. size_t GetSize() const { return buf_.size(); } // Reset all state so we can re-use the buffer. void Clear() { buf_.clear(); stack_.clear(); finished_ = false; // flags_ remains as-is; force_min_bit_width_ = BIT_WIDTH_8; key_pool.clear(); string_pool.clear(); } // All value constructing functions below have two versions: one that // takes a key (for placement inside a map) and one that doesn't (for inside // vectors and elsewhere). void Null() { stack_.push_back(Value()); } void Null(const char *key) { Key(key); Null(); } void Int(int64_t i) { stack_.push_back(Value(i, FBT_INT, WidthI(i))); } void Int(const char *key, int64_t i) { Key(key); Int(i); } void UInt(uint64_t u) { stack_.push_back(Value(u, FBT_UINT, WidthU(u))); } void UInt(const char *key, uint64_t u) { Key(key); UInt(u); } void Float(float f) { stack_.push_back(Value(f)); } void Float(const char *key, float f) { Key(key); Float(f); } void Double(double f) { stack_.push_back(Value(f)); } void Double(const char *key, double d) { Key(key); Double(d); } void Bool(bool b) { stack_.push_back(Value(b)); } void Bool(const char *key, bool b) { Key(key); Bool(b); } void IndirectInt(int64_t i) { PushIndirect(i, FBT_INDIRECT_INT, WidthI(i)); } void IndirectInt(const char *key, int64_t i) { Key(key); IndirectInt(i); } void IndirectUInt(uint64_t u) { PushIndirect(u, FBT_INDIRECT_UINT, WidthU(u)); } void IndirectUInt(const char *key, uint64_t u) { Key(key); IndirectUInt(u); } void IndirectFloat(float f) { PushIndirect(f, FBT_INDIRECT_FLOAT, BIT_WIDTH_32); } void IndirectFloat(const char *key, float f) { Key(key); IndirectFloat(f); } void IndirectDouble(double f) { PushIndirect(f, FBT_INDIRECT_FLOAT, WidthF(f)); } void IndirectDouble(const char *key, double d) { Key(key); IndirectDouble(d); } size_t Key(const char *str, size_t len) { auto sloc = buf_.size(); WriteBytes(str, len + 1); if (flags_ & BUILDER_FLAG_SHARE_KEYS) { auto it = key_pool.find(sloc); if (it != key_pool.end()) { // Already in the buffer. Remove key we just serialized, and use // existing offset instead. buf_.resize(sloc); sloc = *it; } else { key_pool.insert(sloc); } } stack_.push_back(Value(static_cast(sloc), FBT_KEY, BIT_WIDTH_8)); return sloc; } size_t Key(const char *str) { return Key(str, strlen(str)); } size_t Key(const std::string &str) { return Key(str.c_str(), str.size()); } size_t String(const char *str, size_t len) { auto reset_to = buf_.size(); auto sloc = CreateBlob(str, len, 1, FBT_STRING); if (flags_ & BUILDER_FLAG_SHARE_STRINGS) { StringOffset so(sloc, len); auto it = string_pool.find(so); if (it != string_pool.end()) { // Already in the buffer. Remove string we just serialized, and use // existing offset instead. buf_.resize(reset_to); sloc = it->first; stack_.back().u_ = sloc; } else { string_pool.insert(so); } } return sloc; } size_t String(const char *str) { return String(str, strlen(str)); } size_t String(const std::string &str) { return String(str.c_str(), str.size()); } void String(const flexbuffers::String &str) { String(str.c_str(), str.length()); } void String(const char *key, const char *str) { Key(key); String(str); } void String(const char *key, const std::string &str) { Key(key); String(str); } void String(const char *key, const flexbuffers::String &str) { Key(key); String(str); } size_t Blob(const void *data, size_t len) { return CreateBlob(data, len, 0, FBT_BLOB); } size_t Blob(const std::vector &v) { return CreateBlob(flatbuffers::vector_data(v), v.size(), 0, FBT_BLOB); } // TODO(wvo): support all the FlexBuffer types (like flexbuffers::String), // e.g. Vector etc. Also in overloaded versions. // Also some FlatBuffers types? size_t StartVector() { return stack_.size(); } size_t StartVector(const char *key) { Key(key); return stack_.size(); } size_t StartMap() { return stack_.size(); } size_t StartMap(const char *key) { Key(key); return stack_.size(); } // TODO(wvo): allow this to specify an aligment greater than the natural // alignment. size_t EndVector(size_t start, bool typed, bool fixed) { auto vec = CreateVector(start, stack_.size() - start, 1, typed, fixed); // Remove temp elements and return vector. stack_.resize(start); stack_.push_back(vec); return static_cast(vec.u_); } size_t EndMap(size_t start) { // We should have interleaved keys and values on the stack. // Make sure it is an even number: auto len = stack_.size() - start; FLATBUFFERS_ASSERT(!(len & 1)); len /= 2; // Make sure keys are all strings: for (auto key = start; key < stack_.size(); key += 2) { FLATBUFFERS_ASSERT(stack_[key].type_ == FBT_KEY); } // Now sort values, so later we can do a binary seach lookup. // We want to sort 2 array elements at a time. struct TwoValue { Value key; Value val; }; // TODO(wvo): strict aliasing? // TODO(wvo): allow the caller to indicate the data is already sorted // for maximum efficiency? With an assert to check sortedness to make sure // we're not breaking binary search. // Or, we can track if the map is sorted as keys are added which would be // be quite cheap (cheaper than checking it here), so we can skip this // step automatically when appliccable, and encourage people to write in // sorted fashion. // std::sort is typically already a lot faster on sorted data though. auto dict = reinterpret_cast(flatbuffers::vector_data(stack_) + start); std::sort(dict, dict + len, [&](const TwoValue &a, const TwoValue &b) -> bool { auto as = reinterpret_cast( flatbuffers::vector_data(buf_) + a.key.u_); auto bs = reinterpret_cast( flatbuffers::vector_data(buf_) + b.key.u_); auto comp = strcmp(as, bs); // If this assertion hits, you've added two keys with the same // value to this map. // TODO: Have to check for pointer equality, as some sort // implementation apparently call this function with the same // element?? Why? FLATBUFFERS_ASSERT(comp || &a == &b); return comp < 0; }); // First create a vector out of all keys. // TODO(wvo): if kBuilderFlagShareKeyVectors is true, see if we can share // the first vector. auto keys = CreateVector(start, len, 2, true, false); auto vec = CreateVector(start + 1, len, 2, false, false, &keys); // Remove temp elements and return map. stack_.resize(start); stack_.push_back(vec); return static_cast(vec.u_); } template size_t Vector(F f) { auto start = StartVector(); f(); return EndVector(start, false, false); } template size_t Vector(F f, T &state) { auto start = StartVector(); f(state); return EndVector(start, false, false); } template size_t Vector(const char *key, F f) { auto start = StartVector(key); f(); return EndVector(start, false, false); } template size_t Vector(const char *key, F f, T &state) { auto start = StartVector(key); f(state); return EndVector(start, false, false); } template void Vector(const T *elems, size_t len) { if (flatbuffers::is_scalar::value) { // This path should be a lot quicker and use less space. ScalarVector(elems, len, false); } else { auto start = StartVector(); for (size_t i = 0; i < len; i++) Add(elems[i]); EndVector(start, false, false); } } template void Vector(const char *key, const T *elems, size_t len) { Key(key); Vector(elems, len); } template void Vector(const std::vector &vec) { Vector(flatbuffers::vector_data(vec), vec.size()); } template size_t TypedVector(F f) { auto start = StartVector(); f(); return EndVector(start, true, false); } template size_t TypedVector(F f, T &state) { auto start = StartVector(); f(state); return EndVector(start, true, false); } template size_t TypedVector(const char *key, F f) { auto start = StartVector(key); f(); return EndVector(start, true, false); } template size_t TypedVector(const char *key, F f, T &state) { auto start = StartVector(key); f(state); return EndVector(start, true, false); } template size_t FixedTypedVector(const T *elems, size_t len) { // We only support a few fixed vector lengths. Anything bigger use a // regular typed vector. FLATBUFFERS_ASSERT(len >= 2 && len <= 4); // And only scalar values. static_assert(flatbuffers::is_scalar::value, "Unrelated types"); return ScalarVector(elems, len, true); } template size_t FixedTypedVector(const char *key, const T *elems, size_t len) { Key(key); return FixedTypedVector(elems, len); } template size_t Map(F f) { auto start = StartMap(); f(); return EndMap(start); } template size_t Map(F f, T &state) { auto start = StartMap(); f(state); return EndMap(start); } template size_t Map(const char *key, F f) { auto start = StartMap(key); f(); return EndMap(start); } template size_t Map(const char *key, F f, T &state) { auto start = StartMap(key); f(state); return EndMap(start); } template void Map(const std::map &map) { auto start = StartMap(); for (auto it = map.begin(); it != map.end(); ++it) Add(it->first.c_str(), it->second); EndMap(start); } // Overloaded Add that tries to call the correct function above. void Add(int8_t i) { Int(i); } void Add(int16_t i) { Int(i); } void Add(int32_t i) { Int(i); } void Add(int64_t i) { Int(i); } void Add(uint8_t u) { UInt(u); } void Add(uint16_t u) { UInt(u); } void Add(uint32_t u) { UInt(u); } void Add(uint64_t u) { UInt(u); } void Add(float f) { Float(f); } void Add(double d) { Double(d); } void Add(bool b) { Bool(b); } void Add(const char *str) { String(str); } void Add(const std::string &str) { String(str); } void Add(const flexbuffers::String &str) { String(str); } template void Add(const std::vector &vec) { Vector(vec); } template void Add(const char *key, const T &t) { Key(key); Add(t); } template void Add(const std::map &map) { Map(map); } template void operator+=(const T &t) { Add(t); } // This function is useful in combination with the Mutate* functions above. // It forces elements of vectors and maps to have a minimum size, such that // they can later be updated without failing. // Call with no arguments to reset. void ForceMinimumBitWidth(BitWidth bw = BIT_WIDTH_8) { force_min_bit_width_ = bw; } void Finish() { // If you hit this assert, you likely have objects that were never included // in a parent. You need to have exactly one root to finish a buffer. // Check your Start/End calls are matched, and all objects are inside // some other object. FLATBUFFERS_ASSERT(stack_.size() == 1); // Write root value. auto byte_width = Align(stack_[0].ElemWidth(buf_.size(), 0)); WriteAny(stack_[0], byte_width); // Write root type. Write(stack_[0].StoredPackedType(), 1); // Write root size. Normally determined by parent, but root has no parent :) Write(byte_width, 1); finished_ = true; } private: void Finished() const { // If you get this assert, you're attempting to get access a buffer // which hasn't been finished yet. Be sure to call // Builder::Finish with your root object. FLATBUFFERS_ASSERT(finished_); } // Align to prepare for writing a scalar with a certain size. uint8_t Align(BitWidth alignment) { auto byte_width = 1U << alignment; buf_.insert(buf_.end(), flatbuffers::PaddingBytes(buf_.size(), byte_width), 0); return static_cast(byte_width); } void WriteBytes(const void *val, size_t size) { buf_.insert(buf_.end(), reinterpret_cast(val), reinterpret_cast(val) + size); } template void Write(T val, size_t byte_width) { FLATBUFFERS_ASSERT(sizeof(T) >= byte_width); val = flatbuffers::EndianScalar(val); WriteBytes(&val, byte_width); } void WriteDouble(double f, uint8_t byte_width) { switch (byte_width) { case 8: Write(f, byte_width); break; case 4: Write(static_cast(f), byte_width); break; // case 2: Write(static_cast(f), byte_width); break; // case 1: Write(static_cast(f), byte_width); break; default: FLATBUFFERS_ASSERT(0); } } void WriteOffset(uint64_t o, uint8_t byte_width) { auto reloff = buf_.size() - o; FLATBUFFERS_ASSERT(byte_width == 8 || reloff < 1ULL << (byte_width * 8)); Write(reloff, byte_width); } template void PushIndirect(T val, Type type, BitWidth bit_width) { auto byte_width = Align(bit_width); auto iloc = buf_.size(); Write(val, byte_width); stack_.push_back(Value(static_cast(iloc), type, bit_width)); } static BitWidth WidthB(size_t byte_width) { switch (byte_width) { case 1: return BIT_WIDTH_8; case 2: return BIT_WIDTH_16; case 4: return BIT_WIDTH_32; case 8: return BIT_WIDTH_64; default: FLATBUFFERS_ASSERT(false); return BIT_WIDTH_64; } } template static Type GetScalarType() { static_assert(flatbuffers::is_scalar::value, "Unrelated types"); return flatbuffers::is_floating_point::value ? FBT_FLOAT : flatbuffers::is_same::value ? FBT_BOOL : (flatbuffers::is_unsigned::value ? FBT_UINT : FBT_INT); } struct Value { union { int64_t i_; uint64_t u_; double f_; }; Type type_; // For scalars: of itself, for vector: of its elements, for string: length. BitWidth min_bit_width_; Value() : i_(0), type_(FBT_NULL), min_bit_width_(BIT_WIDTH_8) {} Value(bool b) : u_(static_cast(b)), type_(FBT_BOOL), min_bit_width_(BIT_WIDTH_8) {} Value(int64_t i, Type t, BitWidth bw) : i_(i), type_(t), min_bit_width_(bw) {} Value(uint64_t u, Type t, BitWidth bw) : u_(u), type_(t), min_bit_width_(bw) {} Value(float f) : f_(f), type_(FBT_FLOAT), min_bit_width_(BIT_WIDTH_32) {} Value(double f) : f_(f), type_(FBT_FLOAT), min_bit_width_(WidthF(f)) {} uint8_t StoredPackedType(BitWidth parent_bit_width_ = BIT_WIDTH_8) const { return PackedType(StoredWidth(parent_bit_width_), type_); } BitWidth ElemWidth(size_t buf_size, size_t elem_index) const { if (IsInline(type_)) { return min_bit_width_; } else { // We have an absolute offset, but want to store a relative offset // elem_index elements beyond the current buffer end. Since whether // the relative offset fits in a certain byte_width depends on // the size of the elements before it (and their alignment), we have // to test for each size in turn. for (size_t byte_width = 1; byte_width <= sizeof(flatbuffers::largest_scalar_t); byte_width *= 2) { // Where are we going to write this offset? auto offset_loc = buf_size + flatbuffers::PaddingBytes(buf_size, byte_width) + elem_index * byte_width; // Compute relative offset. auto offset = offset_loc - u_; // Does it fit? auto bit_width = WidthU(offset); if (static_cast(static_cast(1U) << bit_width) == byte_width) return bit_width; } FLATBUFFERS_ASSERT(false); // Must match one of the sizes above. return BIT_WIDTH_64; } } BitWidth StoredWidth(BitWidth parent_bit_width_ = BIT_WIDTH_8) const { if (IsInline(type_)) { return (std::max)(min_bit_width_, parent_bit_width_); } else { return min_bit_width_; } } }; void WriteAny(const Value &val, uint8_t byte_width) { switch (val.type_) { case FBT_NULL: case FBT_INT: Write(val.i_, byte_width); break; case FBT_BOOL: case FBT_UINT: Write(val.u_, byte_width); break; case FBT_FLOAT: WriteDouble(val.f_, byte_width); break; default: WriteOffset(val.u_, byte_width); break; } } size_t CreateBlob(const void *data, size_t len, size_t trailing, Type type) { auto bit_width = WidthU(len); auto byte_width = Align(bit_width); Write(len, byte_width); auto sloc = buf_.size(); WriteBytes(data, len + trailing); stack_.push_back(Value(static_cast(sloc), type, bit_width)); return sloc; } template size_t ScalarVector(const T *elems, size_t len, bool fixed) { auto vector_type = GetScalarType(); auto byte_width = sizeof(T); auto bit_width = WidthB(byte_width); // If you get this assert, you're trying to write a vector with a size // field that is bigger than the scalars you're trying to write (e.g. a // byte vector > 255 elements). For such types, write a "blob" instead. // TODO: instead of asserting, could write vector with larger elements // instead, though that would be wasteful. FLATBUFFERS_ASSERT(WidthU(len) <= bit_width); if (!fixed) Write(len, byte_width); auto vloc = buf_.size(); for (size_t i = 0; i < len; i++) Write(elems[i], byte_width); stack_.push_back(Value(static_cast(vloc), ToTypedVector(vector_type, fixed ? len : 0), bit_width)); return vloc; } Value CreateVector(size_t start, size_t vec_len, size_t step, bool typed, bool fixed, const Value *keys = nullptr) { FLATBUFFERS_ASSERT(!fixed || typed); // typed=false, fixed=true combination is not supported. // Figure out smallest bit width we can store this vector with. auto bit_width = (std::max)(force_min_bit_width_, WidthU(vec_len)); auto prefix_elems = 1; if (keys) { // If this vector is part of a map, we will pre-fix an offset to the keys // to this vector. bit_width = (std::max)(bit_width, keys->ElemWidth(buf_.size(), 0)); prefix_elems += 2; } Type vector_type = FBT_KEY; // Check bit widths and types for all elements. for (size_t i = start; i < stack_.size(); i += step) { auto elem_width = stack_[i].ElemWidth(buf_.size(), i + prefix_elems); bit_width = (std::max)(bit_width, elem_width); if (typed) { if (i == start) { vector_type = stack_[i].type_; } else { // If you get this assert, you are writing a typed vector with // elements that are not all the same type. FLATBUFFERS_ASSERT(vector_type == stack_[i].type_); } } } // If you get this assert, your fixed types are not one of: // Int / UInt / Float / Key. FLATBUFFERS_ASSERT(!fixed || IsTypedVectorElementType(vector_type)); auto byte_width = Align(bit_width); // Write vector. First the keys width/offset if available, and size. if (keys) { WriteOffset(keys->u_, byte_width); Write(1ULL << keys->min_bit_width_, byte_width); } if (!fixed) Write(vec_len, byte_width); // Then the actual data. auto vloc = buf_.size(); for (size_t i = start; i < stack_.size(); i += step) { WriteAny(stack_[i], byte_width); } // Then the types. if (!typed) { for (size_t i = start; i < stack_.size(); i += step) { buf_.push_back(stack_[i].StoredPackedType(bit_width)); } } return Value(static_cast(vloc), keys ? FBT_MAP : (typed ? ToTypedVector(vector_type, fixed ? vec_len : 0) : FBT_VECTOR), bit_width); } // You shouldn't really be copying instances of this class. Builder(const Builder &); Builder &operator=(const Builder &); std::vector buf_; std::vector stack_; bool finished_; BuilderFlag flags_; BitWidth force_min_bit_width_; struct KeyOffsetCompare { explicit KeyOffsetCompare(const std::vector &buf) : buf_(&buf) {} bool operator()(size_t a, size_t b) const { auto stra = reinterpret_cast(flatbuffers::vector_data(*buf_) + a); auto strb = reinterpret_cast(flatbuffers::vector_data(*buf_) + b); return strcmp(stra, strb) < 0; } const std::vector *buf_; }; typedef std::pair StringOffset; struct StringOffsetCompare { explicit StringOffsetCompare(const std::vector &buf) : buf_(&buf) {} bool operator()(const StringOffset &a, const StringOffset &b) const { auto stra = reinterpret_cast( flatbuffers::vector_data(*buf_) + a.first); auto strb = reinterpret_cast( flatbuffers::vector_data(*buf_) + b.first); return strncmp(stra, strb, (std::min)(a.second, b.second) + 1) < 0; } const std::vector *buf_; }; typedef std::set KeyOffsetMap; typedef std::set StringOffsetMap; KeyOffsetMap key_pool; StringOffsetMap string_pool; }; } // namespace flexbuffers # if defined(_MSC_VER) # pragma warning(pop) # endif #endif // FLATBUFFERS_FLEXBUFFERS_H_ treesheets-1.0.2/lobster/include/flatbuffers/grpc.h000066400000000000000000000251161352107072600224010ustar00rootroot00000000000000/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_GRPC_H_ #define FLATBUFFERS_GRPC_H_ // Helper functionality to glue FlatBuffers and GRPC. #include "flatbuffers/flatbuffers.h" #include "grpc++/support/byte_buffer.h" #include "grpc/byte_buffer_reader.h" namespace flatbuffers { namespace grpc { // Message is a typed wrapper around a buffer that manages the underlying // `grpc_slice` and also provides flatbuffers-specific helpers such as `Verify` // and `GetRoot`. Since it is backed by a `grpc_slice`, the underlying buffer // is refcounted and ownership is be managed automatically. template class Message { public: Message() : slice_(grpc_empty_slice()) {} Message(grpc_slice slice, bool add_ref) : slice_(add_ref ? grpc_slice_ref(slice) : slice) {} Message &operator=(const Message &other) = delete; Message(Message &&other) : slice_(other.slice_) { other.slice_ = grpc_empty_slice(); } Message(const Message &other) = delete; Message &operator=(Message &&other) { grpc_slice_unref(slice_); slice_ = other.slice_; other.slice_ = grpc_empty_slice(); return *this; } ~Message() { grpc_slice_unref(slice_); } const uint8_t *mutable_data() const { return GRPC_SLICE_START_PTR(slice_); } const uint8_t *data() const { return GRPC_SLICE_START_PTR(slice_); } size_t size() const { return GRPC_SLICE_LENGTH(slice_); } bool Verify() const { Verifier verifier(data(), size()); return verifier.VerifyBuffer(nullptr); } T *GetMutableRoot() { return flatbuffers::GetMutableRoot(mutable_data()); } const T *GetRoot() const { return flatbuffers::GetRoot(data()); } // This is only intended for serializer use, or if you know what you're doing const grpc_slice &BorrowSlice() const { return slice_; } private: grpc_slice slice_; }; class MessageBuilder; // SliceAllocator is a gRPC-specific allocator that uses the `grpc_slice` // refcounted slices to manage memory ownership. This makes it easy and // efficient to transfer buffers to gRPC. class SliceAllocator : public Allocator { public: SliceAllocator() : slice_(grpc_empty_slice()) {} SliceAllocator(const SliceAllocator &other) = delete; SliceAllocator &operator=(const SliceAllocator &other) = delete; SliceAllocator(SliceAllocator &&other) : slice_(grpc_empty_slice()) { // default-construct and swap idiom swap(other); } SliceAllocator &operator=(SliceAllocator &&other) { // move-construct and swap idiom SliceAllocator temp(std::move(other)); swap(temp); return *this; } void swap(SliceAllocator &other) { using std::swap; swap(slice_, other.slice_); } virtual ~SliceAllocator() { grpc_slice_unref(slice_); } virtual uint8_t *allocate(size_t size) override { FLATBUFFERS_ASSERT(GRPC_SLICE_IS_EMPTY(slice_)); slice_ = grpc_slice_malloc(size); return GRPC_SLICE_START_PTR(slice_); } virtual void deallocate(uint8_t *p, size_t size) override { FLATBUFFERS_ASSERT(p == GRPC_SLICE_START_PTR(slice_)); FLATBUFFERS_ASSERT(size == GRPC_SLICE_LENGTH(slice_)); grpc_slice_unref(slice_); slice_ = grpc_empty_slice(); } virtual uint8_t *reallocate_downward(uint8_t *old_p, size_t old_size, size_t new_size, size_t in_use_back, size_t in_use_front) override { FLATBUFFERS_ASSERT(old_p == GRPC_SLICE_START_PTR(slice_)); FLATBUFFERS_ASSERT(old_size == GRPC_SLICE_LENGTH(slice_)); FLATBUFFERS_ASSERT(new_size > old_size); grpc_slice old_slice = slice_; grpc_slice new_slice = grpc_slice_malloc(new_size); uint8_t *new_p = GRPC_SLICE_START_PTR(new_slice); memcpy_downward(old_p, old_size, new_p, new_size, in_use_back, in_use_front); slice_ = new_slice; grpc_slice_unref(old_slice); return new_p; } private: grpc_slice &get_slice(uint8_t *p, size_t size) { FLATBUFFERS_ASSERT(p == GRPC_SLICE_START_PTR(slice_)); FLATBUFFERS_ASSERT(size == GRPC_SLICE_LENGTH(slice_)); return slice_; } grpc_slice slice_; friend class MessageBuilder; }; // SliceAllocatorMember is a hack to ensure that the MessageBuilder's // slice_allocator_ member is constructed before the FlatBufferBuilder, since // the allocator is used in the FlatBufferBuilder ctor. namespace detail { struct SliceAllocatorMember { SliceAllocator slice_allocator_; }; } // namespace detail // MessageBuilder is a gRPC-specific FlatBufferBuilder that uses SliceAllocator // to allocate gRPC buffers. class MessageBuilder : private detail::SliceAllocatorMember, public FlatBufferBuilder { public: explicit MessageBuilder(uoffset_t initial_size = 1024) : FlatBufferBuilder(initial_size, &slice_allocator_, false) {} MessageBuilder(const MessageBuilder &other) = delete; MessageBuilder &operator=(const MessageBuilder &other) = delete; MessageBuilder(MessageBuilder &&other) : FlatBufferBuilder(1024, &slice_allocator_, false) { // Default construct and swap idiom. Swap(other); } MessageBuilder &operator=(MessageBuilder &&other) { // Move construct a temporary and swap MessageBuilder temp(std::move(other)); Swap(temp); return *this; } void Swap(MessageBuilder &other) { slice_allocator_.swap(other.slice_allocator_); FlatBufferBuilder::Swap(other); // After swapping the FlatBufferBuilder, we swap back the allocator, which restores // the original allocator back in place. This is necessary because MessageBuilder's // allocator is its own member (SliceAllocatorMember). The allocator passed to // FlatBufferBuilder::vector_downward must point to this member. buf_.swap_allocator(other.buf_); } // Releases the ownership of the buffer pointer. // Returns the size, offset, and the original grpc_slice that // allocated the buffer. Also see grpc_slice_unref(). uint8_t *ReleaseRaw(size_t &size, size_t &offset, grpc_slice &slice) { uint8_t *buf = FlatBufferBuilder::ReleaseRaw(size, offset); slice = slice_allocator_.slice_; slice_allocator_.slice_ = grpc_empty_slice(); return buf; } ~MessageBuilder() {} // GetMessage extracts the subslice of the buffer corresponding to the // flatbuffers-encoded region and wraps it in a `Message` to handle buffer // ownership. template Message GetMessage() { auto buf_data = buf_.scratch_data(); // pointer to memory auto buf_size = buf_.capacity(); // size of memory auto msg_data = buf_.data(); // pointer to msg auto msg_size = buf_.size(); // size of msg // Do some sanity checks on data/size FLATBUFFERS_ASSERT(msg_data); FLATBUFFERS_ASSERT(msg_size); FLATBUFFERS_ASSERT(msg_data >= buf_data); FLATBUFFERS_ASSERT(msg_data + msg_size <= buf_data + buf_size); // Calculate offsets from the buffer start auto begin = msg_data - buf_data; auto end = begin + msg_size; // Get the slice we are working with (no refcount change) grpc_slice slice = slice_allocator_.get_slice(buf_data, buf_size); // Extract a subslice of the existing slice (increment refcount) grpc_slice subslice = grpc_slice_sub(slice, begin, end); // Wrap the subslice in a `Message`, but don't increment refcount Message msg(subslice, false); return msg; } template Message ReleaseMessage() { Message msg = GetMessage(); Reset(); return msg; } private: // SliceAllocator slice_allocator_; // part of SliceAllocatorMember }; } // namespace grpc } // namespace flatbuffers namespace grpc { template class SerializationTraits> { public: static grpc::Status Serialize(const flatbuffers::grpc::Message &msg, grpc_byte_buffer **buffer, bool *own_buffer) { // We are passed in a `Message`, which is a wrapper around a // `grpc_slice`. We extract it here using `BorrowSlice()`. The const cast // is necesary because the `grpc_raw_byte_buffer_create` func expects // non-const slices in order to increment their refcounts. grpc_slice *slice = const_cast(&msg.BorrowSlice()); // Now use `grpc_raw_byte_buffer_create` to package the single slice into a // `grpc_byte_buffer`, incrementing the refcount in the process. *buffer = grpc_raw_byte_buffer_create(slice, 1); *own_buffer = true; return grpc::Status::OK; } // Deserialize by pulling the static grpc::Status Deserialize(grpc_byte_buffer *buffer, flatbuffers::grpc::Message *msg) { if (!buffer) { return ::grpc::Status(::grpc::StatusCode::INTERNAL, "No payload"); } // Check if this is a single uncompressed slice. if ((buffer->type == GRPC_BB_RAW) && (buffer->data.raw.compression == GRPC_COMPRESS_NONE) && (buffer->data.raw.slice_buffer.count == 1)) { // If it is, then we can reference the `grpc_slice` directly. grpc_slice slice = buffer->data.raw.slice_buffer.slices[0]; // We wrap a `Message` around the slice, incrementing the refcount. *msg = flatbuffers::grpc::Message(slice, true); } else { // Otherwise, we need to use `grpc_byte_buffer_reader_readall` to read // `buffer` into a single contiguous `grpc_slice`. The gRPC reader gives // us back a new slice with the refcount already incremented. grpc_byte_buffer_reader reader; grpc_byte_buffer_reader_init(&reader, buffer); grpc_slice slice = grpc_byte_buffer_reader_readall(&reader); grpc_byte_buffer_reader_destroy(&reader); // We wrap a `Message` around the slice, but dont increment refcount *msg = flatbuffers::grpc::Message(slice, false); } grpc_byte_buffer_destroy(buffer); #if FLATBUFFERS_GRPC_DISABLE_AUTO_VERIFICATION return ::grpc::Status::OK; #else if (msg->Verify()) { return ::grpc::Status::OK; } else { return ::grpc::Status(::grpc::StatusCode::INTERNAL, "Message verification failed"); } #endif } }; } // namespace grpc #endif // FLATBUFFERS_GRPC_H_ treesheets-1.0.2/lobster/include/flatbuffers/hash.h000066400000000000000000000072371352107072600223750ustar00rootroot00000000000000/* * Copyright 2015 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_HASH_H_ #define FLATBUFFERS_HASH_H_ #include #include #include "flatbuffers/flatbuffers.h" namespace flatbuffers { template struct FnvTraits { static const T kFnvPrime; static const T kOffsetBasis; }; template<> struct FnvTraits { static const uint32_t kFnvPrime = 0x01000193; static const uint32_t kOffsetBasis = 0x811C9DC5; }; template<> struct FnvTraits { static const uint64_t kFnvPrime = 0x00000100000001b3ULL; static const uint64_t kOffsetBasis = 0xcbf29ce484222645ULL; }; template FLATBUFFERS_CONSTEXPR_CPP14 T HashFnv1(const char *input) { T hash = FnvTraits::kOffsetBasis; for (const char *c = input; *c; ++c) { hash *= FnvTraits::kFnvPrime; hash ^= static_cast(*c); } return hash; } template FLATBUFFERS_CONSTEXPR_CPP14 T HashFnv1a(const char *input) { T hash = FnvTraits::kOffsetBasis; for (const char *c = input; *c; ++c) { hash ^= static_cast(*c); hash *= FnvTraits::kFnvPrime; } return hash; } template <> FLATBUFFERS_CONSTEXPR_CPP14 inline uint16_t HashFnv1(const char *input) { uint32_t hash = HashFnv1(input); return (hash >> 16) ^ (hash & 0xffff); } template <> FLATBUFFERS_CONSTEXPR_CPP14 inline uint16_t HashFnv1a(const char *input) { uint32_t hash = HashFnv1a(input); return (hash >> 16) ^ (hash & 0xffff); } template struct NamedHashFunction { const char *name; typedef T (*HashFunction)(const char *); HashFunction function; }; const NamedHashFunction kHashFunctions16[] = { { "fnv1_16", HashFnv1 }, { "fnv1a_16", HashFnv1a }, }; const NamedHashFunction kHashFunctions32[] = { { "fnv1_32", HashFnv1 }, { "fnv1a_32", HashFnv1a }, }; const NamedHashFunction kHashFunctions64[] = { { "fnv1_64", HashFnv1 }, { "fnv1a_64", HashFnv1a }, }; inline NamedHashFunction::HashFunction FindHashFunction16( const char *name) { std::size_t size = sizeof(kHashFunctions16) / sizeof(kHashFunctions16[0]); for (std::size_t i = 0; i < size; ++i) { if (std::strcmp(name, kHashFunctions16[i].name) == 0) { return kHashFunctions16[i].function; } } return nullptr; } inline NamedHashFunction::HashFunction FindHashFunction32( const char *name) { std::size_t size = sizeof(kHashFunctions32) / sizeof(kHashFunctions32[0]); for (std::size_t i = 0; i < size; ++i) { if (std::strcmp(name, kHashFunctions32[i].name) == 0) { return kHashFunctions32[i].function; } } return nullptr; } inline NamedHashFunction::HashFunction FindHashFunction64( const char *name) { std::size_t size = sizeof(kHashFunctions64) / sizeof(kHashFunctions64[0]); for (std::size_t i = 0; i < size; ++i) { if (std::strcmp(name, kHashFunctions64[i].name) == 0) { return kHashFunctions64[i].function; } } return nullptr; } } // namespace flatbuffers #endif // FLATBUFFERS_HASH_H_ treesheets-1.0.2/lobster/include/flatbuffers/idl.h000066400000000000000000001041361352107072600222160ustar00rootroot00000000000000/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_IDL_H_ #define FLATBUFFERS_IDL_H_ #include #include #include #include "flatbuffers/base.h" #include "flatbuffers/flatbuffers.h" #include "flatbuffers/flexbuffers.h" #include "flatbuffers/hash.h" #include "flatbuffers/reflection.h" #if !defined(FLATBUFFERS_CPP98_STL) # include #endif // !defined(FLATBUFFERS_CPP98_STL) // This file defines the data types representing a parsed IDL (Interface // Definition Language) / schema file. namespace flatbuffers { // The order of these matters for Is*() functions below. // Additionally, Parser::ParseType assumes bool..string is a contiguous range // of type tokens. // clang-format off #define FLATBUFFERS_GEN_TYPES_SCALAR(TD) \ TD(NONE, "", uint8_t, byte, byte, byte, uint8, u8) \ TD(UTYPE, "", uint8_t, byte, byte, byte, uint8, u8) /* begin scalar/int */ \ TD(BOOL, "bool", uint8_t, boolean,byte, bool, bool, bool) \ TD(CHAR, "byte", int8_t, byte, int8, sbyte, int8, i8) \ TD(UCHAR, "ubyte", uint8_t, byte, byte, byte, uint8, u8) \ TD(SHORT, "short", int16_t, short, int16, short, int16, i16) \ TD(USHORT, "ushort", uint16_t, short, uint16, ushort, uint16, u16) \ TD(INT, "int", int32_t, int, int32, int, int32, i32) \ TD(UINT, "uint", uint32_t, int, uint32, uint, uint32, u32) \ TD(LONG, "long", int64_t, long, int64, long, int64, i64) \ TD(ULONG, "ulong", uint64_t, long, uint64, ulong, uint64, u64) /* end int */ \ TD(FLOAT, "float", float, float, float32, float, float32, f32) /* begin float */ \ TD(DOUBLE, "double", double, double, float64, double, float64, f64) /* end float/scalar */ #define FLATBUFFERS_GEN_TYPES_POINTER(TD) \ TD(STRING, "string", Offset, int, int, StringOffset, int, unused) \ TD(VECTOR, "", Offset, int, int, VectorOffset, int, unused) \ TD(STRUCT, "", Offset, int, int, int, int, unused) \ TD(UNION, "", Offset, int, int, int, int, unused) // The fields are: // - enum // - FlatBuffers schema type. // - C++ type. // - Java type. // - Go type. // - C# / .Net type. // - Python type. // - Rust type. // using these macros, we can now write code dealing with types just once, e.g. /* switch (type) { #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, \ RTYPE) \ case BASE_TYPE_ ## ENUM: \ // do something specific to CTYPE here FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD) #undef FLATBUFFERS_TD } */ #define FLATBUFFERS_GEN_TYPES(TD) \ FLATBUFFERS_GEN_TYPES_SCALAR(TD) \ FLATBUFFERS_GEN_TYPES_POINTER(TD) // Create an enum for all the types above. #ifdef __GNUC__ __extension__ // Stop GCC complaining about trailing comma with -Wpendantic. #endif enum BaseType { #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, \ RTYPE) \ BASE_TYPE_ ## ENUM, FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD) #undef FLATBUFFERS_TD }; #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, \ RTYPE) \ static_assert(sizeof(CTYPE) <= sizeof(largest_scalar_t), \ "define largest_scalar_t as " #CTYPE); FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD) #undef FLATBUFFERS_TD inline bool IsScalar (BaseType t) { return t >= BASE_TYPE_UTYPE && t <= BASE_TYPE_DOUBLE; } inline bool IsInteger(BaseType t) { return t >= BASE_TYPE_UTYPE && t <= BASE_TYPE_ULONG; } inline bool IsFloat (BaseType t) { return t == BASE_TYPE_FLOAT || t == BASE_TYPE_DOUBLE; } inline bool IsLong (BaseType t) { return t == BASE_TYPE_LONG || t == BASE_TYPE_ULONG; } inline bool IsBool (BaseType t) { return t == BASE_TYPE_BOOL; } inline bool IsOneByte(BaseType t) { return t >= BASE_TYPE_UTYPE && t <= BASE_TYPE_UCHAR; } // clang-format on extern const char *const kTypeNames[]; extern const char kTypeSizes[]; inline size_t SizeOf(BaseType t) { return kTypeSizes[t]; } struct StructDef; struct EnumDef; class Parser; // Represents any type in the IDL, which is a combination of the BaseType // and additional information for vectors/structs_. struct Type { explicit Type(BaseType _base_type = BASE_TYPE_NONE, StructDef *_sd = nullptr, EnumDef *_ed = nullptr) : base_type(_base_type), element(BASE_TYPE_NONE), struct_def(_sd), enum_def(_ed) {} bool operator==(const Type &o) { return base_type == o.base_type && element == o.element && struct_def == o.struct_def && enum_def == o.enum_def; } Type VectorType() const { return Type(element, struct_def, enum_def); } Offset Serialize(FlatBufferBuilder *builder) const; BaseType base_type; BaseType element; // only set if t == BASE_TYPE_VECTOR StructDef *struct_def; // only set if t or element == BASE_TYPE_STRUCT EnumDef *enum_def; // set if t == BASE_TYPE_UNION / BASE_TYPE_UTYPE, // or for an integral type derived from an enum. }; // Represents a parsed scalar value, it's type, and field offset. struct Value { Value() : constant("0"), offset(static_cast(~(static_cast(0U)))) {} Type type; std::string constant; voffset_t offset; }; // Helper class that retains the original order of a set of identifiers and // also provides quick lookup. template class SymbolTable { public: ~SymbolTable() { for (auto it = vec.begin(); it != vec.end(); ++it) { delete *it; } } bool Add(const std::string &name, T *e) { vector_emplace_back(&vec, e); auto it = dict.find(name); if (it != dict.end()) return true; dict[name] = e; return false; } void Move(const std::string &oldname, const std::string &newname) { auto it = dict.find(oldname); if (it != dict.end()) { auto obj = it->second; dict.erase(it); dict[newname] = obj; } else { FLATBUFFERS_ASSERT(false); } } T *Lookup(const std::string &name) const { auto it = dict.find(name); return it == dict.end() ? nullptr : it->second; } public: std::map dict; // quick lookup std::vector vec; // Used to iterate in order of insertion }; // A name space, as set in the schema. struct Namespace { Namespace() : from_table(0) {} // Given a (potentally unqualified) name, return the "fully qualified" name // which has a full namespaced descriptor. // With max_components you can request less than the number of components // the current namespace has. std::string GetFullyQualifiedName(const std::string &name, size_t max_components = 1000) const; std::vector components; size_t from_table; // Part of the namespace corresponds to a message/table. }; // Base class for all definition types (fields, structs_, enums_). struct Definition { Definition() : generated(false), defined_namespace(nullptr), serialized_location(0), index(-1), refcount(1) {} flatbuffers::Offset< flatbuffers::Vector>> SerializeAttributes(FlatBufferBuilder *builder, const Parser &parser) const; std::string name; std::string file; std::vector doc_comment; SymbolTable attributes; bool generated; // did we already output code for this definition? Namespace *defined_namespace; // Where it was defined. // For use with Serialize() uoffset_t serialized_location; int index; // Inside the vector it is stored. int refcount; }; struct FieldDef : public Definition { FieldDef() : deprecated(false), required(false), key(false), native_inline(false), flexbuffer(false), nested_flatbuffer(NULL), padding(0) {} Offset Serialize(FlatBufferBuilder *builder, uint16_t id, const Parser &parser) const; Value value; bool deprecated; // Field is allowed to be present in old data, but can't be. // written in new data nor accessed in new code. bool required; // Field must always be present. bool key; // Field functions as a key for creating sorted vectors. bool native_inline; // Field will be defined inline (instead of as a pointer) // for native tables if field is a struct. bool flexbuffer; // This field contains FlexBuffer data. StructDef *nested_flatbuffer; // This field contains nested FlatBuffer data. size_t padding; // Bytes to always pad after this field. }; struct StructDef : public Definition { StructDef() : fixed(false), predecl(true), sortbysize(true), has_key(false), minalign(1), bytesize(0) {} void PadLastField(size_t min_align) { auto padding = PaddingBytes(bytesize, min_align); bytesize += padding; if (fields.vec.size()) fields.vec.back()->padding = padding; } Offset Serialize(FlatBufferBuilder *builder, const Parser &parser) const; SymbolTable fields; bool fixed; // If it's struct, not a table. bool predecl; // If it's used before it was defined. bool sortbysize; // Whether fields come in the declaration or size order. bool has_key; // It has a key field. size_t minalign; // What the whole object needs to be aligned to. size_t bytesize; // Size if fixed. flatbuffers::unique_ptr original_location; }; inline bool IsStruct(const Type &type) { return type.base_type == BASE_TYPE_STRUCT && type.struct_def->fixed; } inline size_t InlineSize(const Type &type) { return IsStruct(type) ? type.struct_def->bytesize : SizeOf(type.base_type); } inline size_t InlineAlignment(const Type &type) { return IsStruct(type) ? type.struct_def->minalign : SizeOf(type.base_type); } struct EnumVal { EnumVal(const std::string &_name, int64_t _val) : name(_name), value(_val) {} Offset Serialize(FlatBufferBuilder *builder, const Parser &parser) const; std::string name; std::vector doc_comment; int64_t value; Type union_type; }; struct EnumDef : public Definition { EnumDef() : is_union(false), uses_type_aliases(false) {} EnumVal *ReverseLookup(int64_t enum_idx, bool skip_union_default = true) { for (auto it = vals.vec.begin() + static_cast(is_union && skip_union_default); it != vals.vec.end(); ++it) { if ((*it)->value == enum_idx) { return *it; } } return nullptr; } Offset Serialize(FlatBufferBuilder *builder, const Parser &parser) const; SymbolTable vals; bool is_union; bool uses_type_aliases; Type underlying_type; }; inline bool EqualByName(const Type &a, const Type &b) { return a.base_type == b.base_type && a.element == b.element && (a.struct_def == b.struct_def || a.struct_def->name == b.struct_def->name) && (a.enum_def == b.enum_def || a.enum_def->name == b.enum_def->name); } struct RPCCall : public Definition { Offset Serialize(FlatBufferBuilder *builder, const Parser &parser) const; StructDef *request, *response; }; struct ServiceDef : public Definition { Offset Serialize(FlatBufferBuilder *builder, const Parser &parser) const; SymbolTable calls; }; // Container of options that may apply to any of the source/text generators. struct IDLOptions { bool strict_json; bool skip_js_exports; bool use_goog_js_export_format; bool use_ES6_js_export_format; bool output_default_scalars_in_json; int indent_step; bool output_enum_identifiers; bool prefixed_enums; bool scoped_enums; bool include_dependence_headers; bool mutable_buffer; bool one_file; bool proto_mode; bool proto_oneof_union; bool generate_all; bool skip_unexpected_fields_in_json; bool generate_name_strings; bool generate_object_based_api; bool gen_compare; std::string cpp_object_api_pointer_type; std::string cpp_object_api_string_type; bool gen_nullable; std::string object_prefix; std::string object_suffix; bool union_value_namespacing; bool allow_non_utf8; bool natural_utf8; std::string include_prefix; bool keep_include_path; bool binary_schema_comments; bool binary_schema_builtins; bool skip_flatbuffers_import; std::string go_import; std::string go_namespace; bool reexport_ts_modules; bool protobuf_ascii_alike; bool size_prefixed; std::string root_type; bool force_defaults; // Possible options for the more general generator below. enum Language { kJava = 1 << 0, kCSharp = 1 << 1, kGo = 1 << 2, kCpp = 1 << 3, kJs = 1 << 4, kPython = 1 << 5, kPhp = 1 << 6, kJson = 1 << 7, kBinary = 1 << 8, kTs = 1 << 9, kJsonSchema = 1 << 10, kDart = 1 << 11, kLua = 1 << 12, kLobster = 1 << 13, kRust = 1 << 14, kMAX }; Language lang; enum MiniReflect { kNone, kTypes, kTypesAndNames }; MiniReflect mini_reflect; // The corresponding language bit will be set if a language is included // for code generation. unsigned long lang_to_generate; // If set (default behavior), empty string and vector fields will be set to // nullptr to make the flatbuffer more compact. bool set_empty_to_null; IDLOptions() : strict_json(false), skip_js_exports(false), use_goog_js_export_format(false), use_ES6_js_export_format(false), output_default_scalars_in_json(false), indent_step(2), output_enum_identifiers(true), prefixed_enums(true), scoped_enums(false), include_dependence_headers(true), mutable_buffer(false), one_file(false), proto_mode(false), proto_oneof_union(false), generate_all(false), skip_unexpected_fields_in_json(false), generate_name_strings(false), generate_object_based_api(false), gen_compare(false), cpp_object_api_pointer_type("std::unique_ptr"), gen_nullable(false), object_suffix("T"), union_value_namespacing(true), allow_non_utf8(false), natural_utf8(false), keep_include_path(false), binary_schema_comments(false), binary_schema_builtins(false), skip_flatbuffers_import(false), reexport_ts_modules(true), protobuf_ascii_alike(false), size_prefixed(false), force_defaults(false), lang(IDLOptions::kJava), mini_reflect(IDLOptions::kNone), lang_to_generate(0), set_empty_to_null(true) {} }; // This encapsulates where the parser is in the current source file. struct ParserState { ParserState() : cursor_(nullptr), line_start_(nullptr), line_(0), token_(-1) {} protected: void ResetState(const char *source) { cursor_ = source; line_ = 0; MarkNewLine(); } void MarkNewLine() { line_start_ = cursor_; line_ += 1; } int64_t CursorPosition() const { FLATBUFFERS_ASSERT(cursor_ && line_start_ && cursor_ >= line_start_); return static_cast(cursor_ - line_start_); } const char *cursor_; const char *line_start_; int line_; // the current line being parsed int token_; std::string attribute_; std::vector doc_comment_; }; // A way to make error propagation less error prone by requiring values to be // checked. // Once you create a value of this type you must either: // - Call Check() on it. // - Copy or assign it to another value. // Failure to do so leads to an assert. // This guarantees that this as return value cannot be ignored. class CheckedError { public: explicit CheckedError(bool error) : is_error_(error), has_been_checked_(false) {} CheckedError &operator=(const CheckedError &other) { is_error_ = other.is_error_; has_been_checked_ = false; other.has_been_checked_ = true; return *this; } CheckedError(const CheckedError &other) { *this = other; // Use assignment operator. } ~CheckedError() { FLATBUFFERS_ASSERT(has_been_checked_); } bool Check() { has_been_checked_ = true; return is_error_; } private: bool is_error_; mutable bool has_been_checked_; }; // Additionally, in GCC we can get these errors statically, for additional // assurance: // clang-format off #ifdef __GNUC__ #define FLATBUFFERS_CHECKED_ERROR CheckedError \ __attribute__((warn_unused_result)) #else #define FLATBUFFERS_CHECKED_ERROR CheckedError #endif // clang-format on class Parser : public ParserState { public: explicit Parser(const IDLOptions &options = IDLOptions()) : current_namespace_(nullptr), empty_namespace_(nullptr), root_struct_def_(nullptr), opts(options), uses_flexbuffers_(false), source_(nullptr), anonymous_counter(0), recurse_protection_counter(0) { if (opts.force_defaults) { builder_.ForceDefaults(true); } // Start out with the empty namespace being current. empty_namespace_ = new Namespace(); namespaces_.push_back(empty_namespace_); current_namespace_ = empty_namespace_; known_attributes_["deprecated"] = true; known_attributes_["required"] = true; known_attributes_["key"] = true; known_attributes_["hash"] = true; known_attributes_["id"] = true; known_attributes_["force_align"] = true; known_attributes_["bit_flags"] = true; known_attributes_["original_order"] = true; known_attributes_["nested_flatbuffer"] = true; known_attributes_["csharp_partial"] = true; known_attributes_["streaming"] = true; known_attributes_["idempotent"] = true; known_attributes_["cpp_type"] = true; known_attributes_["cpp_ptr_type"] = true; known_attributes_["cpp_ptr_type_get"] = true; known_attributes_["cpp_str_type"] = true; known_attributes_["native_inline"] = true; known_attributes_["native_custom_alloc"] = true; known_attributes_["native_type"] = true; known_attributes_["native_default"] = true; known_attributes_["flexbuffer"] = true; known_attributes_["private"] = true; } ~Parser() { for (auto it = namespaces_.begin(); it != namespaces_.end(); ++it) { delete *it; } } // Parse the string containing either schema or JSON data, which will // populate the SymbolTable's or the FlatBufferBuilder above. // include_paths is used to resolve any include statements, and typically // should at least include the project path (where you loaded source_ from). // include_paths must be nullptr terminated if specified. // If include_paths is nullptr, it will attempt to load from the current // directory. // If the source was loaded from a file and isn't an include file, // supply its name in source_filename. // All paths specified in this call must be in posix format, if you accept // paths from user input, please call PosixPath on them first. bool Parse(const char *_source, const char **include_paths = nullptr, const char *source_filename = nullptr); // Set the root type. May override the one set in the schema. bool SetRootType(const char *name); // Mark all definitions as already having code generated. void MarkGenerated(); // Get the files recursively included by the given file. The returned // container will have at least the given file. std::set GetIncludedFilesRecursive( const std::string &file_name) const; // Fills builder_ with a binary version of the schema parsed. // See reflection/reflection.fbs void Serialize(); // Checks that the schema represented by this parser is a safe evolution // of the schema provided. Returns non-empty error on any problems. std::string ConformTo(const Parser &base); // Similar to Parse(), but now only accepts JSON to be parsed into a // FlexBuffer. bool ParseFlexBuffer(const char *source, const char *source_filename, flexbuffers::Builder *builder); FLATBUFFERS_CHECKED_ERROR CheckInRange(int64_t val, int64_t min, int64_t max); StructDef *LookupStruct(const std::string &id) const; private: void Message(const std::string &msg); void Warning(const std::string &msg); FLATBUFFERS_CHECKED_ERROR Error(const std::string &msg); FLATBUFFERS_CHECKED_ERROR ParseHexNum(int nibbles, uint64_t *val); FLATBUFFERS_CHECKED_ERROR Next(); FLATBUFFERS_CHECKED_ERROR SkipByteOrderMark(); bool Is(int t) const; bool IsIdent(const char *id) const; FLATBUFFERS_CHECKED_ERROR Expect(int t); std::string TokenToStringId(int t) const; EnumDef *LookupEnum(const std::string &id); FLATBUFFERS_CHECKED_ERROR ParseNamespacing(std::string *id, std::string *last); FLATBUFFERS_CHECKED_ERROR ParseTypeIdent(Type &type); FLATBUFFERS_CHECKED_ERROR ParseType(Type &type); FLATBUFFERS_CHECKED_ERROR AddField(StructDef &struct_def, const std::string &name, const Type &type, FieldDef **dest); FLATBUFFERS_CHECKED_ERROR ParseField(StructDef &struct_def); FLATBUFFERS_CHECKED_ERROR ParseString(Value &val); FLATBUFFERS_CHECKED_ERROR ParseComma(); FLATBUFFERS_CHECKED_ERROR ParseAnyValue(Value &val, FieldDef *field, size_t parent_fieldn, const StructDef *parent_struct_def); // clang-format off #if defined(FLATBUFFERS_CPP98_STL) typedef CheckedError (*ParseTableDelimitersBody)( const std::string &name, size_t &fieldn, const StructDef *struct_def, void *state); #else typedef std::function ParseTableDelimitersBody; #endif // defined(FLATBUFFERS_CPP98_STL) // clang-format on FLATBUFFERS_CHECKED_ERROR ParseTableDelimiters(size_t &fieldn, const StructDef *struct_def, ParseTableDelimitersBody body, void *state); FLATBUFFERS_CHECKED_ERROR ParseTable(const StructDef &struct_def, std::string *value, uoffset_t *ovalue); void SerializeStruct(const StructDef &struct_def, const Value &val); // clang-format off #if defined(FLATBUFFERS_CPP98_STL) typedef CheckedError (*ParseVectorDelimitersBody)(size_t &count, void *state); #else typedef std::function ParseVectorDelimitersBody; #endif // defined(FLATBUFFERS_CPP98_STL) // clang-format on FLATBUFFERS_CHECKED_ERROR ParseVectorDelimiters( size_t &count, ParseVectorDelimitersBody body, void *state); FLATBUFFERS_CHECKED_ERROR ParseVector(const Type &type, uoffset_t *ovalue); FLATBUFFERS_CHECKED_ERROR ParseNestedFlatbuffer(Value &val, FieldDef *field, size_t fieldn, const StructDef *parent_struct_def); FLATBUFFERS_CHECKED_ERROR ParseMetaData(SymbolTable *attributes); FLATBUFFERS_CHECKED_ERROR TryTypedValue(const std::string *name, int dtoken, bool check, Value &e, BaseType req, bool *destmatch); FLATBUFFERS_CHECKED_ERROR ParseHash(Value &e, FieldDef* field); FLATBUFFERS_CHECKED_ERROR TokenError(); FLATBUFFERS_CHECKED_ERROR ParseSingleValue(const std::string *name, Value &e); FLATBUFFERS_CHECKED_ERROR ParseEnumFromString(Type &type, int64_t *result); StructDef *LookupCreateStruct(const std::string &name, bool create_if_new = true, bool definition = false); FLATBUFFERS_CHECKED_ERROR ParseEnum(bool is_union, EnumDef **dest); FLATBUFFERS_CHECKED_ERROR ParseNamespace(); FLATBUFFERS_CHECKED_ERROR StartStruct(const std::string &name, StructDef **dest); FLATBUFFERS_CHECKED_ERROR StartEnum(const std::string &name, bool is_union, EnumDef **dest); FLATBUFFERS_CHECKED_ERROR ParseDecl(); FLATBUFFERS_CHECKED_ERROR ParseService(); FLATBUFFERS_CHECKED_ERROR ParseProtoFields(StructDef *struct_def, bool isextend, bool inside_oneof); FLATBUFFERS_CHECKED_ERROR ParseProtoOption(); FLATBUFFERS_CHECKED_ERROR ParseProtoKey(); FLATBUFFERS_CHECKED_ERROR ParseProtoDecl(); FLATBUFFERS_CHECKED_ERROR ParseProtoCurliesOrIdent(); FLATBUFFERS_CHECKED_ERROR ParseTypeFromProtoType(Type *type); FLATBUFFERS_CHECKED_ERROR SkipAnyJsonValue(); FLATBUFFERS_CHECKED_ERROR ParseFlexBufferValue(flexbuffers::Builder *builder); FLATBUFFERS_CHECKED_ERROR StartParseFile(const char *source, const char *source_filename); FLATBUFFERS_CHECKED_ERROR ParseRoot(const char *_source, const char **include_paths, const char *source_filename); FLATBUFFERS_CHECKED_ERROR DoParse(const char *_source, const char **include_paths, const char *source_filename, const char *include_filename); FLATBUFFERS_CHECKED_ERROR CheckClash(std::vector &fields, StructDef *struct_def, const char *suffix, BaseType baseType); bool SupportsVectorOfUnions() const; Namespace *UniqueNamespace(Namespace *ns); enum { kMaxParsingDepth = 64 }; FLATBUFFERS_CHECKED_ERROR RecurseError(); template CheckedError Recurse(F f) { if (++recurse_protection_counter >= kMaxParsingDepth) return RecurseError(); auto ce = f(); recurse_protection_counter--; return ce; } public: SymbolTable types_; SymbolTable structs_; SymbolTable enums_; SymbolTable services_; std::vector namespaces_; Namespace *current_namespace_; Namespace *empty_namespace_; std::string error_; // User readable error_ if Parse() == false FlatBufferBuilder builder_; // any data contained in the file StructDef *root_struct_def_; std::string file_identifier_; std::string file_extension_; std::map included_files_; std::map> files_included_per_file_; std::vector native_included_files_; std::map known_attributes_; IDLOptions opts; bool uses_flexbuffers_; private: const char *source_; std::string file_being_parsed_; std::vector> field_stack_; int anonymous_counter; int recurse_protection_counter; }; // Utility functions for multiple generators: extern std::string MakeCamel(const std::string &in, bool first = true); // Generate text (JSON) from a given FlatBuffer, and a given Parser // object that has been populated with the corresponding schema. // If ident_step is 0, no indentation will be generated. Additionally, // if it is less than 0, no linefeeds will be generated either. // See idl_gen_text.cpp. // strict_json adds "quotes" around field names if true. // If the flatbuffer cannot be encoded in JSON (e.g., it contains non-UTF-8 // byte arrays in String values), returns false. extern bool GenerateText(const Parser &parser, const void *flatbuffer, std::string *text); extern bool GenerateTextFile(const Parser &parser, const std::string &path, const std::string &file_name); // Generate binary files from a given FlatBuffer, and a given Parser // object that has been populated with the corresponding schema. // See idl_gen_general.cpp. extern bool GenerateBinary(const Parser &parser, const std::string &path, const std::string &file_name); // Generate a C++ header from the definitions in the Parser object. // See idl_gen_cpp. extern bool GenerateCPP(const Parser &parser, const std::string &path, const std::string &file_name); extern bool GenerateDart(const Parser &parser, const std::string &path, const std::string &file_name); // Generate JavaScript or TypeScript code from the definitions in the Parser object. // See idl_gen_js. extern bool GenerateJS(const Parser &parser, const std::string &path, const std::string &file_name); // Generate Go files from the definitions in the Parser object. // See idl_gen_go.cpp. extern bool GenerateGo(const Parser &parser, const std::string &path, const std::string &file_name); // Generate Php code from the definitions in the Parser object. // See idl_gen_php. extern bool GeneratePhp(const Parser &parser, const std::string &path, const std::string &file_name); // Generate Python files from the definitions in the Parser object. // See idl_gen_python.cpp. extern bool GeneratePython(const Parser &parser, const std::string &path, const std::string &file_name); // Generate Lobster files from the definitions in the Parser object. // See idl_gen_lobster.cpp. extern bool GenerateLobster(const Parser &parser, const std::string &path, const std::string &file_name); // Generate Lua files from the definitions in the Parser object. // See idl_gen_lua.cpp. extern bool GenerateLua(const Parser &parser, const std::string &path, const std::string &file_name); // Generate Rust files from the definitions in the Parser object. // See idl_gen_rust.cpp. extern bool GenerateRust(const Parser &parser, const std::string &path, const std::string &file_name); // Generate Json schema file // See idl_gen_json_schema.cpp. extern bool GenerateJsonSchema(const Parser &parser, const std::string &path, const std::string &file_name); // Generate Java/C#/.. files from the definitions in the Parser object. // See idl_gen_general.cpp. extern bool GenerateGeneral(const Parser &parser, const std::string &path, const std::string &file_name); // Generate a schema file from the internal representation, useful after // parsing a .proto schema. extern std::string GenerateFBS(const Parser &parser, const std::string &file_name); extern bool GenerateFBS(const Parser &parser, const std::string &path, const std::string &file_name); // Generate a make rule for the generated JavaScript or TypeScript code. // See idl_gen_js.cpp. extern std::string JSMakeRule(const Parser &parser, const std::string &path, const std::string &file_name); // Generate a make rule for the generated C++ header. // See idl_gen_cpp.cpp. extern std::string CPPMakeRule(const Parser &parser, const std::string &path, const std::string &file_name); // Generate a make rule for the generated Dart code // see idl_gen_dart.cpp extern std::string DartMakeRule(const Parser &parser, const std::string &path, const std::string &file_name); // Generate a make rule for the generated Rust code. // See idl_gen_rust.cpp. extern std::string RustMakeRule(const Parser &parser, const std::string &path, const std::string &file_name); // Generate a make rule for the generated Java/C#/... files. // See idl_gen_general.cpp. extern std::string GeneralMakeRule(const Parser &parser, const std::string &path, const std::string &file_name); // Generate a make rule for the generated text (JSON) files. // See idl_gen_text.cpp. extern std::string TextMakeRule(const Parser &parser, const std::string &path, const std::string &file_names); // Generate a make rule for the generated binary files. // See idl_gen_general.cpp. extern std::string BinaryMakeRule(const Parser &parser, const std::string &path, const std::string &file_name); // Generate GRPC Cpp interfaces. // See idl_gen_grpc.cpp. bool GenerateCppGRPC(const Parser &parser, const std::string &path, const std::string &file_name); // Generate GRPC Go interfaces. // See idl_gen_grpc.cpp. bool GenerateGoGRPC(const Parser &parser, const std::string &path, const std::string &file_name); // Generate GRPC Java classes. // See idl_gen_grpc.cpp bool GenerateJavaGRPC(const Parser &parser, const std::string &path, const std::string &file_name); } // namespace flatbuffers #endif // FLATBUFFERS_IDL_H_ treesheets-1.0.2/lobster/include/flatbuffers/minireflect.h000066400000000000000000000310061352107072600237420ustar00rootroot00000000000000/* * Copyright 2017 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_MINIREFLECT_H_ #define FLATBUFFERS_MINIREFLECT_H_ #include "flatbuffers/flatbuffers.h" #include "flatbuffers/util.h" namespace flatbuffers { // Utilities that can be used with the "mini reflection" tables present // in generated code with --reflect-types (only types) or --reflect-names // (also names). // This allows basic reflection functionality such as pretty-printing // that does not require the use of the schema parser or loading of binary // schema files at runtime (reflection.h). // For any of the functions below that take `const TypeTable *`, you pass // `FooTypeTable()` if the type of the root is `Foo`. // First, a generic iterator that can be used by multiple algorithms. struct IterationVisitor { // These mark the scope of a table or struct. virtual void StartSequence() {} virtual void EndSequence() {} // Called for each field regardless of wether it is present or not. // If not present, val == nullptr. set_idx is the index of all set fields. virtual void Field(size_t /*field_idx*/, size_t /*set_idx*/, ElementaryType /*type*/, bool /*is_vector*/, const TypeTable * /*type_table*/, const char * /*name*/, const uint8_t * /*val*/) {} // Called for a value that is actually present, after a field, or as part // of a vector. virtual void UType(uint8_t, const char *) {} virtual void Bool(bool) {} virtual void Char(int8_t, const char *) {} virtual void UChar(uint8_t, const char *) {} virtual void Short(int16_t, const char *) {} virtual void UShort(uint16_t, const char *) {} virtual void Int(int32_t, const char *) {} virtual void UInt(uint32_t, const char *) {} virtual void Long(int64_t) {} virtual void ULong(uint64_t) {} virtual void Float(float) {} virtual void Double(double) {} virtual void String(const String *) {} virtual void Unknown(const uint8_t *) {} // From a future version. // These mark the scope of a vector. virtual void StartVector() {} virtual void EndVector() {} virtual void Element(size_t /*i*/, ElementaryType /*type*/, const TypeTable * /*type_table*/, const uint8_t * /*val*/) {} virtual ~IterationVisitor() {} }; inline size_t InlineSize(ElementaryType type, const TypeTable *type_table) { switch (type) { case ET_UTYPE: case ET_BOOL: case ET_CHAR: case ET_UCHAR: return 1; case ET_SHORT: case ET_USHORT: return 2; case ET_INT: case ET_UINT: case ET_FLOAT: case ET_STRING: return 4; case ET_LONG: case ET_ULONG: case ET_DOUBLE: return 8; case ET_SEQUENCE: switch (type_table->st) { case ST_TABLE: case ST_UNION: return 4; case ST_STRUCT: return type_table->values[type_table->num_elems]; default: FLATBUFFERS_ASSERT(false); return 1; } default: FLATBUFFERS_ASSERT(false); return 1; } } inline int32_t LookupEnum(int32_t enum_val, const int32_t *values, size_t num_values) { if (!values) return enum_val; for (size_t i = 0; i < num_values; i++) { if (enum_val == values[i]) return static_cast(i); } return -1; // Unknown enum value. } template const char *EnumName(T tval, const TypeTable *type_table) { if (!type_table || !type_table->names) return nullptr; auto i = LookupEnum(static_cast(tval), type_table->values, type_table->num_elems); if (i >= 0 && i < static_cast(type_table->num_elems)) { return type_table->names[i]; } return nullptr; } void IterateObject(const uint8_t *obj, const TypeTable *type_table, IterationVisitor *visitor); inline void IterateValue(ElementaryType type, const uint8_t *val, const TypeTable *type_table, const uint8_t *prev_val, soffset_t vector_index, IterationVisitor *visitor) { switch (type) { case ET_UTYPE: { auto tval = *reinterpret_cast(val); visitor->UType(tval, EnumName(tval, type_table)); break; } case ET_BOOL: { visitor->Bool(*reinterpret_cast(val) != 0); break; } case ET_CHAR: { auto tval = *reinterpret_cast(val); visitor->Char(tval, EnumName(tval, type_table)); break; } case ET_UCHAR: { auto tval = *reinterpret_cast(val); visitor->UChar(tval, EnumName(tval, type_table)); break; } case ET_SHORT: { auto tval = *reinterpret_cast(val); visitor->Short(tval, EnumName(tval, type_table)); break; } case ET_USHORT: { auto tval = *reinterpret_cast(val); visitor->UShort(tval, EnumName(tval, type_table)); break; } case ET_INT: { auto tval = *reinterpret_cast(val); visitor->Int(tval, EnumName(tval, type_table)); break; } case ET_UINT: { auto tval = *reinterpret_cast(val); visitor->UInt(tval, EnumName(tval, type_table)); break; } case ET_LONG: { visitor->Long(*reinterpret_cast(val)); break; } case ET_ULONG: { visitor->ULong(*reinterpret_cast(val)); break; } case ET_FLOAT: { visitor->Float(*reinterpret_cast(val)); break; } case ET_DOUBLE: { visitor->Double(*reinterpret_cast(val)); break; } case ET_STRING: { val += ReadScalar(val); visitor->String(reinterpret_cast(val)); break; } case ET_SEQUENCE: { switch (type_table->st) { case ST_TABLE: val += ReadScalar(val); IterateObject(val, type_table, visitor); break; case ST_STRUCT: IterateObject(val, type_table, visitor); break; case ST_UNION: { val += ReadScalar(val); FLATBUFFERS_ASSERT(prev_val); auto union_type = *prev_val; // Always a uint8_t. if (vector_index >= 0) { auto type_vec = reinterpret_cast *>(prev_val); union_type = type_vec->Get(static_cast(vector_index)); } auto type_code_idx = LookupEnum(union_type, type_table->values, type_table->num_elems); if (type_code_idx >= 0 && type_code_idx < static_cast(type_table->num_elems)) { auto type_code = type_table->type_codes[type_code_idx]; switch (type_code.base_type) { case ET_SEQUENCE: { auto ref = type_table->type_refs[type_code.sequence_ref](); IterateObject(val, ref, visitor); break; } case ET_STRING: visitor->String(reinterpret_cast(val)); break; default: visitor->Unknown(val); } } else { visitor->Unknown(val); } break; } case ST_ENUM: FLATBUFFERS_ASSERT(false); break; } break; } default: { visitor->Unknown(val); break; } } } inline void IterateObject(const uint8_t *obj, const TypeTable *type_table, IterationVisitor *visitor) { visitor->StartSequence(); const uint8_t *prev_val = nullptr; size_t set_idx = 0; for (size_t i = 0; i < type_table->num_elems; i++) { auto type_code = type_table->type_codes[i]; auto type = static_cast(type_code.base_type); auto is_vector = type_code.is_vector != 0; auto ref_idx = type_code.sequence_ref; const TypeTable *ref = nullptr; if (ref_idx >= 0) { ref = type_table->type_refs[ref_idx](); } auto name = type_table->names ? type_table->names[i] : nullptr; const uint8_t *val = nullptr; if (type_table->st == ST_TABLE) { val = reinterpret_cast(obj)->GetAddressOf( FieldIndexToOffset(static_cast(i))); } else { val = obj + type_table->values[i]; } visitor->Field(i, set_idx, type, is_vector, ref, name, val); if (val) { set_idx++; if (is_vector) { val += ReadScalar(val); auto vec = reinterpret_cast *>(val); visitor->StartVector(); auto elem_ptr = vec->Data(); for (size_t j = 0; j < vec->size(); j++) { visitor->Element(j, type, ref, elem_ptr); IterateValue(type, elem_ptr, ref, prev_val, static_cast(j), visitor); elem_ptr += InlineSize(type, ref); } visitor->EndVector(); } else { IterateValue(type, val, ref, prev_val, -1, visitor); } } prev_val = val; } visitor->EndSequence(); } inline void IterateFlatBuffer(const uint8_t *buffer, const TypeTable *type_table, IterationVisitor *callback) { IterateObject(GetRoot(buffer), type_table, callback); } // Outputting a Flatbuffer to a string. Tries to conform as close to JSON / // the output generated by idl_gen_text.cpp. struct ToStringVisitor : public IterationVisitor { std::string s; std::string d; bool q; std::string in; size_t indent_level; ToStringVisitor(std::string delimiter, bool quotes, std::string indent) : d(delimiter), q(quotes), in(indent), indent_level(0) {} ToStringVisitor(std::string delimiter) : d(delimiter), q(false), in(""), indent_level(0) {} void append_indent() { for (size_t i = 0; i < indent_level; i++) { s += in; } } void StartSequence() { s += "{"; s += d; indent_level++; } void EndSequence() { s += d; indent_level--; append_indent(); s += "}"; } void Field(size_t /*field_idx*/, size_t set_idx, ElementaryType /*type*/, bool /*is_vector*/, const TypeTable * /*type_table*/, const char *name, const uint8_t *val) { if (!val) return; if (set_idx) { s += ","; s += d; } append_indent(); if (name) { if (q) s += "\""; s += name; if (q) s += "\""; s += ": "; } } template void Named(T x, const char *name) { if (name) { if (q) s += "\""; s += name; if (q) s += "\""; } else { s += NumToString(x); } } void UType(uint8_t x, const char *name) { Named(x, name); } void Bool(bool x) { s += x ? "true" : "false"; } void Char(int8_t x, const char *name) { Named(x, name); } void UChar(uint8_t x, const char *name) { Named(x, name); } void Short(int16_t x, const char *name) { Named(x, name); } void UShort(uint16_t x, const char *name) { Named(x, name); } void Int(int32_t x, const char *name) { Named(x, name); } void UInt(uint32_t x, const char *name) { Named(x, name); } void Long(int64_t x) { s += NumToString(x); } void ULong(uint64_t x) { s += NumToString(x); } void Float(float x) { s += NumToString(x); } void Double(double x) { s += NumToString(x); } void String(const struct String *str) { EscapeString(str->c_str(), str->size(), &s, true, false); } void Unknown(const uint8_t *) { s += "(?)"; } void StartVector() { s += "["; s += d; indent_level++; append_indent(); } void EndVector() { s += d; indent_level--; append_indent(); s += "]"; } void Element(size_t i, ElementaryType /*type*/, const TypeTable * /*type_table*/, const uint8_t * /*val*/) { if (i) { s += ","; s += d; append_indent(); } } }; inline std::string FlatBufferToString(const uint8_t *buffer, const TypeTable *type_table, bool multi_line = false) { ToStringVisitor tostring_visitor(multi_line ? "\n" : " "); IterateFlatBuffer(buffer, type_table, &tostring_visitor); return tostring_visitor.s; } } // namespace flatbuffers #endif // FLATBUFFERS_MINIREFLECT_H_ treesheets-1.0.2/lobster/include/flatbuffers/reflection.h000066400000000000000000000501051352107072600235740ustar00rootroot00000000000000/* * Copyright 2015 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_REFLECTION_H_ #define FLATBUFFERS_REFLECTION_H_ // This is somewhat of a circular dependency because flatc (and thus this // file) is needed to generate this header in the first place. // Should normally not be a problem since it can be generated by the // previous version of flatc whenever this code needs to change. // See reflection/generate_code.sh #include "flatbuffers/reflection_generated.h" // Helper functionality for reflection. namespace flatbuffers { // ------------------------- GETTERS ------------------------- inline bool IsScalar(reflection::BaseType t) { return t >= reflection::UType && t <= reflection::Double; } inline bool IsInteger(reflection::BaseType t) { return t >= reflection::UType && t <= reflection::ULong; } inline bool IsFloat(reflection::BaseType t) { return t == reflection::Float || t == reflection::Double; } inline bool IsLong(reflection::BaseType t) { return t == reflection::Long || t == reflection::ULong; } // Size of a basic type, don't use with structs. inline size_t GetTypeSize(reflection::BaseType base_type) { // This needs to correspond to the BaseType enum. static size_t sizes[] = { 0, 1, 1, 1, 1, 2, 2, 4, 4, 8, 8, 4, 8, 4, 4, 4, 4 }; return sizes[base_type]; } // Same as above, but now correctly returns the size of a struct if // the field (or vector element) is a struct. inline size_t GetTypeSizeInline(reflection::BaseType base_type, int type_index, const reflection::Schema &schema) { if (base_type == reflection::Obj && schema.objects()->Get(type_index)->is_struct()) { return schema.objects()->Get(type_index)->bytesize(); } else { return GetTypeSize(base_type); } } // Get the root, regardless of what type it is. inline Table *GetAnyRoot(uint8_t *flatbuf) { return GetMutableRoot
(flatbuf); } inline const Table *GetAnyRoot(const uint8_t *flatbuf) { return GetRoot
(flatbuf); } // Get a field's default, if you know it's an integer, and its exact type. template T GetFieldDefaultI(const reflection::Field &field) { FLATBUFFERS_ASSERT(sizeof(T) == GetTypeSize(field.type()->base_type())); return static_cast(field.default_integer()); } // Get a field's default, if you know it's floating point and its exact type. template T GetFieldDefaultF(const reflection::Field &field) { FLATBUFFERS_ASSERT(sizeof(T) == GetTypeSize(field.type()->base_type())); return static_cast(field.default_real()); } // Get a field, if you know it's an integer, and its exact type. template T GetFieldI(const Table &table, const reflection::Field &field) { FLATBUFFERS_ASSERT(sizeof(T) == GetTypeSize(field.type()->base_type())); return table.GetField(field.offset(), static_cast(field.default_integer())); } // Get a field, if you know it's floating point and its exact type. template T GetFieldF(const Table &table, const reflection::Field &field) { FLATBUFFERS_ASSERT(sizeof(T) == GetTypeSize(field.type()->base_type())); return table.GetField(field.offset(), static_cast(field.default_real())); } // Get a field, if you know it's a string. inline const String *GetFieldS(const Table &table, const reflection::Field &field) { FLATBUFFERS_ASSERT(field.type()->base_type() == reflection::String); return table.GetPointer(field.offset()); } // Get a field, if you know it's a vector. template Vector *GetFieldV(const Table &table, const reflection::Field &field) { FLATBUFFERS_ASSERT(field.type()->base_type() == reflection::Vector && sizeof(T) == GetTypeSize(field.type()->element())); return table.GetPointer *>(field.offset()); } // Get a field, if you know it's a vector, generically. // To actually access elements, use the return value together with // field.type()->element() in any of GetAnyVectorElemI below etc. inline VectorOfAny *GetFieldAnyV(const Table &table, const reflection::Field &field) { return table.GetPointer(field.offset()); } // Get a field, if you know it's a table. inline Table *GetFieldT(const Table &table, const reflection::Field &field) { FLATBUFFERS_ASSERT(field.type()->base_type() == reflection::Obj || field.type()->base_type() == reflection::Union); return table.GetPointer
(field.offset()); } // Get a field, if you know it's a struct. inline const Struct *GetFieldStruct(const Table &table, const reflection::Field &field) { // TODO: This does NOT check if the field is a table or struct, but we'd need // access to the schema to check the is_struct flag. FLATBUFFERS_ASSERT(field.type()->base_type() == reflection::Obj); return table.GetStruct(field.offset()); } // Get a structure's field, if you know it's a struct. inline const Struct *GetFieldStruct(const Struct &structure, const reflection::Field &field) { FLATBUFFERS_ASSERT(field.type()->base_type() == reflection::Obj); return structure.GetStruct(field.offset()); } // Raw helper functions used below: get any value in memory as a 64bit int, a // double or a string. // All scalars get static_cast to an int64_t, strings use strtoull, every other // data type returns 0. int64_t GetAnyValueI(reflection::BaseType type, const uint8_t *data); // All scalars static cast to double, strings use strtod, every other data // type is 0.0. double GetAnyValueF(reflection::BaseType type, const uint8_t *data); // All scalars converted using stringstream, strings as-is, and all other // data types provide some level of debug-pretty-printing. std::string GetAnyValueS(reflection::BaseType type, const uint8_t *data, const reflection::Schema *schema, int type_index); // Get any table field as a 64bit int, regardless of what type it is. inline int64_t GetAnyFieldI(const Table &table, const reflection::Field &field) { auto field_ptr = table.GetAddressOf(field.offset()); return field_ptr ? GetAnyValueI(field.type()->base_type(), field_ptr) : field.default_integer(); } // Get any table field as a double, regardless of what type it is. inline double GetAnyFieldF(const Table &table, const reflection::Field &field) { auto field_ptr = table.GetAddressOf(field.offset()); return field_ptr ? GetAnyValueF(field.type()->base_type(), field_ptr) : field.default_real(); } // Get any table field as a string, regardless of what type it is. // You may pass nullptr for the schema if you don't care to have fields that // are of table type pretty-printed. inline std::string GetAnyFieldS(const Table &table, const reflection::Field &field, const reflection::Schema *schema) { auto field_ptr = table.GetAddressOf(field.offset()); return field_ptr ? GetAnyValueS(field.type()->base_type(), field_ptr, schema, field.type()->index()) : ""; } // Get any struct field as a 64bit int, regardless of what type it is. inline int64_t GetAnyFieldI(const Struct &st, const reflection::Field &field) { return GetAnyValueI(field.type()->base_type(), st.GetAddressOf(field.offset())); } // Get any struct field as a double, regardless of what type it is. inline double GetAnyFieldF(const Struct &st, const reflection::Field &field) { return GetAnyValueF(field.type()->base_type(), st.GetAddressOf(field.offset())); } // Get any struct field as a string, regardless of what type it is. inline std::string GetAnyFieldS(const Struct &st, const reflection::Field &field) { return GetAnyValueS(field.type()->base_type(), st.GetAddressOf(field.offset()), nullptr, -1); } // Get any vector element as a 64bit int, regardless of what type it is. inline int64_t GetAnyVectorElemI(const VectorOfAny *vec, reflection::BaseType elem_type, size_t i) { return GetAnyValueI(elem_type, vec->Data() + GetTypeSize(elem_type) * i); } // Get any vector element as a double, regardless of what type it is. inline double GetAnyVectorElemF(const VectorOfAny *vec, reflection::BaseType elem_type, size_t i) { return GetAnyValueF(elem_type, vec->Data() + GetTypeSize(elem_type) * i); } // Get any vector element as a string, regardless of what type it is. inline std::string GetAnyVectorElemS(const VectorOfAny *vec, reflection::BaseType elem_type, size_t i) { return GetAnyValueS(elem_type, vec->Data() + GetTypeSize(elem_type) * i, nullptr, -1); } // Get a vector element that's a table/string/vector from a generic vector. // Pass Table/String/VectorOfAny as template parameter. // Warning: does no typechecking. template T *GetAnyVectorElemPointer(const VectorOfAny *vec, size_t i) { auto elem_ptr = vec->Data() + sizeof(uoffset_t) * i; return (T *)(elem_ptr + ReadScalar(elem_ptr)); } // Get the inline-address of a vector element. Useful for Structs (pass Struct // as template arg), or being able to address a range of scalars in-line. // Get elem_size from GetTypeSizeInline(). // Note: little-endian data on all platforms, use EndianScalar() instead of // raw pointer access with scalars). template T *GetAnyVectorElemAddressOf(const VectorOfAny *vec, size_t i, size_t elem_size) { // C-cast to allow const conversion. return (T *)(vec->Data() + elem_size * i); } // Similarly, for elements of tables. template T *GetAnyFieldAddressOf(const Table &table, const reflection::Field &field) { return (T *)table.GetAddressOf(field.offset()); } // Similarly, for elements of structs. template T *GetAnyFieldAddressOf(const Struct &st, const reflection::Field &field) { return (T *)st.GetAddressOf(field.offset()); } // ------------------------- SETTERS ------------------------- // Set any scalar field, if you know its exact type. template bool SetField(Table *table, const reflection::Field &field, T val) { reflection::BaseType type = field.type()->base_type(); if (!IsScalar(type)) { return false; } FLATBUFFERS_ASSERT(sizeof(T) == GetTypeSize(type)); T def; if (IsInteger(type)) { def = GetFieldDefaultI(field); } else { FLATBUFFERS_ASSERT(IsFloat(type)); def = GetFieldDefaultF(field); } return table->SetField(field.offset(), val, def); } // Raw helper functions used below: set any value in memory as a 64bit int, a // double or a string. // These work for all scalar values, but do nothing for other data types. // To set a string, see SetString below. void SetAnyValueI(reflection::BaseType type, uint8_t *data, int64_t val); void SetAnyValueF(reflection::BaseType type, uint8_t *data, double val); void SetAnyValueS(reflection::BaseType type, uint8_t *data, const char *val); // Set any table field as a 64bit int, regardless of type what it is. inline bool SetAnyFieldI(Table *table, const reflection::Field &field, int64_t val) { auto field_ptr = table->GetAddressOf(field.offset()); if (!field_ptr) return val == GetFieldDefaultI(field); SetAnyValueI(field.type()->base_type(), field_ptr, val); return true; } // Set any table field as a double, regardless of what type it is. inline bool SetAnyFieldF(Table *table, const reflection::Field &field, double val) { auto field_ptr = table->GetAddressOf(field.offset()); if (!field_ptr) return val == GetFieldDefaultF(field); SetAnyValueF(field.type()->base_type(), field_ptr, val); return true; } // Set any table field as a string, regardless of what type it is. inline bool SetAnyFieldS(Table *table, const reflection::Field &field, const char *val) { auto field_ptr = table->GetAddressOf(field.offset()); if (!field_ptr) return false; SetAnyValueS(field.type()->base_type(), field_ptr, val); return true; } // Set any struct field as a 64bit int, regardless of type what it is. inline void SetAnyFieldI(Struct *st, const reflection::Field &field, int64_t val) { SetAnyValueI(field.type()->base_type(), st->GetAddressOf(field.offset()), val); } // Set any struct field as a double, regardless of type what it is. inline void SetAnyFieldF(Struct *st, const reflection::Field &field, double val) { SetAnyValueF(field.type()->base_type(), st->GetAddressOf(field.offset()), val); } // Set any struct field as a string, regardless of type what it is. inline void SetAnyFieldS(Struct *st, const reflection::Field &field, const char *val) { SetAnyValueS(field.type()->base_type(), st->GetAddressOf(field.offset()), val); } // Set any vector element as a 64bit int, regardless of type what it is. inline void SetAnyVectorElemI(VectorOfAny *vec, reflection::BaseType elem_type, size_t i, int64_t val) { SetAnyValueI(elem_type, vec->Data() + GetTypeSize(elem_type) * i, val); } // Set any vector element as a double, regardless of type what it is. inline void SetAnyVectorElemF(VectorOfAny *vec, reflection::BaseType elem_type, size_t i, double val) { SetAnyValueF(elem_type, vec->Data() + GetTypeSize(elem_type) * i, val); } // Set any vector element as a string, regardless of type what it is. inline void SetAnyVectorElemS(VectorOfAny *vec, reflection::BaseType elem_type, size_t i, const char *val) { SetAnyValueS(elem_type, vec->Data() + GetTypeSize(elem_type) * i, val); } // ------------------------- RESIZING SETTERS ------------------------- // "smart" pointer for use with resizing vectors: turns a pointer inside // a vector into a relative offset, such that it is not affected by resizes. template class pointer_inside_vector { public: pointer_inside_vector(T *ptr, std::vector &vec) : offset_(reinterpret_cast(ptr) - reinterpret_cast(flatbuffers::vector_data(vec))), vec_(vec) {} T *operator*() const { return reinterpret_cast( reinterpret_cast(flatbuffers::vector_data(vec_)) + offset_); } T *operator->() const { return operator*(); } void operator=(const pointer_inside_vector &piv); private: size_t offset_; std::vector &vec_; }; // Helper to create the above easily without specifying template args. template pointer_inside_vector piv(T *ptr, std::vector &vec) { return pointer_inside_vector(ptr, vec); } inline const char *UnionTypeFieldSuffix() { return "_type"; } // Helper to figure out the actual table type a union refers to. inline const reflection::Object &GetUnionType( const reflection::Schema &schema, const reflection::Object &parent, const reflection::Field &unionfield, const Table &table) { auto enumdef = schema.enums()->Get(unionfield.type()->index()); // TODO: this is clumsy and slow, but no other way to find it? auto type_field = parent.fields()->LookupByKey( (unionfield.name()->str() + UnionTypeFieldSuffix()).c_str()); FLATBUFFERS_ASSERT(type_field); auto union_type = GetFieldI(table, *type_field); auto enumval = enumdef->values()->LookupByKey(union_type); return *enumval->object(); } // Changes the contents of a string inside a FlatBuffer. FlatBuffer must // live inside a std::vector so we can resize the buffer if needed. // "str" must live inside "flatbuf" and may be invalidated after this call. // If your FlatBuffer's root table is not the schema's root table, you should // pass in your root_table type as well. void SetString(const reflection::Schema &schema, const std::string &val, const String *str, std::vector *flatbuf, const reflection::Object *root_table = nullptr); // Resizes a flatbuffers::Vector inside a FlatBuffer. FlatBuffer must // live inside a std::vector so we can resize the buffer if needed. // "vec" must live inside "flatbuf" and may be invalidated after this call. // If your FlatBuffer's root table is not the schema's root table, you should // pass in your root_table type as well. uint8_t *ResizeAnyVector(const reflection::Schema &schema, uoffset_t newsize, const VectorOfAny *vec, uoffset_t num_elems, uoffset_t elem_size, std::vector *flatbuf, const reflection::Object *root_table = nullptr); template void ResizeVector(const reflection::Schema &schema, uoffset_t newsize, T val, const Vector *vec, std::vector *flatbuf, const reflection::Object *root_table = nullptr) { auto delta_elem = static_cast(newsize) - static_cast(vec->size()); auto newelems = ResizeAnyVector( schema, newsize, reinterpret_cast(vec), vec->size(), static_cast(sizeof(T)), flatbuf, root_table); // Set new elements to "val". for (int i = 0; i < delta_elem; i++) { auto loc = newelems + i * sizeof(T); auto is_scalar = flatbuffers::is_scalar::value; if (is_scalar) { WriteScalar(loc, val); } else { // struct *reinterpret_cast(loc) = val; } } } // Adds any new data (in the form of a new FlatBuffer) to an existing // FlatBuffer. This can be used when any of the above methods are not // sufficient, in particular for adding new tables and new fields. // This is potentially slightly less efficient than a FlatBuffer constructed // in one piece, since the new FlatBuffer doesn't share any vtables with the // existing one. // The return value can now be set using Vector::MutateOffset or SetFieldT // below. const uint8_t *AddFlatBuffer(std::vector &flatbuf, const uint8_t *newbuf, size_t newlen); inline bool SetFieldT(Table *table, const reflection::Field &field, const uint8_t *val) { FLATBUFFERS_ASSERT(sizeof(uoffset_t) == GetTypeSize(field.type()->base_type())); return table->SetPointer(field.offset(), val); } // ------------------------- COPYING ------------------------- // Generic copying of tables from a FlatBuffer into a FlatBuffer builder. // Can be used to do any kind of merging/selecting you may want to do out // of existing buffers. Also useful to reconstruct a whole buffer if the // above resizing functionality has introduced garbage in a buffer you want // to remove. // Note: this does not deal with DAGs correctly. If the table passed forms a // DAG, the copy will be a tree instead (with duplicates). Strings can be // shared however, by passing true for use_string_pooling. Offset CopyTable(FlatBufferBuilder &fbb, const reflection::Schema &schema, const reflection::Object &objectdef, const Table &table, bool use_string_pooling = false); // Verifies the provided flatbuffer using reflection. // root should point to the root type for this flatbuffer. // buf should point to the start of flatbuffer data. // length specifies the size of the flatbuffer data. bool Verify(const reflection::Schema &schema, const reflection::Object &root, const uint8_t *buf, size_t length); } // namespace flatbuffers #endif // FLATBUFFERS_REFLECTION_H_ treesheets-1.0.2/lobster/include/flatbuffers/reflection_generated.h000066400000000000000000001204751352107072600256220ustar00rootroot00000000000000// automatically generated by the FlatBuffers compiler, do not modify #ifndef FLATBUFFERS_GENERATED_REFLECTION_REFLECTION_H_ #define FLATBUFFERS_GENERATED_REFLECTION_REFLECTION_H_ #include "flatbuffers/flatbuffers.h" namespace reflection { struct Type; struct KeyValue; struct EnumVal; struct Enum; struct Field; struct Object; struct RPCCall; struct Service; struct Schema; enum BaseType { None = 0, UType = 1, Bool = 2, Byte = 3, UByte = 4, Short = 5, UShort = 6, Int = 7, UInt = 8, Long = 9, ULong = 10, Float = 11, Double = 12, String = 13, Vector = 14, Obj = 15, Union = 16 }; inline const BaseType (&EnumValuesBaseType())[17] { static const BaseType values[] = { None, UType, Bool, Byte, UByte, Short, UShort, Int, UInt, Long, ULong, Float, Double, String, Vector, Obj, Union }; return values; } inline const char * const *EnumNamesBaseType() { static const char * const names[] = { "None", "UType", "Bool", "Byte", "UByte", "Short", "UShort", "Int", "UInt", "Long", "ULong", "Float", "Double", "String", "Vector", "Obj", "Union", nullptr }; return names; } inline const char *EnumNameBaseType(BaseType e) { const size_t index = static_cast(e); return EnumNamesBaseType()[index]; } struct Type FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_BASE_TYPE = 4, VT_ELEMENT = 6, VT_INDEX = 8 }; BaseType base_type() const { return static_cast(GetField(VT_BASE_TYPE, 0)); } BaseType element() const { return static_cast(GetField(VT_ELEMENT, 0)); } int32_t index() const { return GetField(VT_INDEX, -1); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_BASE_TYPE) && VerifyField(verifier, VT_ELEMENT) && VerifyField(verifier, VT_INDEX) && verifier.EndTable(); } }; struct TypeBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_base_type(BaseType base_type) { fbb_.AddElement(Type::VT_BASE_TYPE, static_cast(base_type), 0); } void add_element(BaseType element) { fbb_.AddElement(Type::VT_ELEMENT, static_cast(element), 0); } void add_index(int32_t index) { fbb_.AddElement(Type::VT_INDEX, index, -1); } explicit TypeBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } TypeBuilder &operator=(const TypeBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); return o; } }; inline flatbuffers::Offset CreateType( flatbuffers::FlatBufferBuilder &_fbb, BaseType base_type = None, BaseType element = None, int32_t index = -1) { TypeBuilder builder_(_fbb); builder_.add_index(index); builder_.add_element(element); builder_.add_base_type(base_type); return builder_.Finish(); } struct KeyValue FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_KEY = 4, VT_VALUE = 6 }; const flatbuffers::String *key() const { return GetPointer(VT_KEY); } bool KeyCompareLessThan(const KeyValue *o) const { return *key() < *o->key(); } int KeyCompareWithValue(const char *val) const { return strcmp(key()->c_str(), val); } const flatbuffers::String *value() const { return GetPointer(VT_VALUE); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffsetRequired(verifier, VT_KEY) && verifier.VerifyString(key()) && VerifyOffset(verifier, VT_VALUE) && verifier.VerifyString(value()) && verifier.EndTable(); } }; struct KeyValueBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_key(flatbuffers::Offset key) { fbb_.AddOffset(KeyValue::VT_KEY, key); } void add_value(flatbuffers::Offset value) { fbb_.AddOffset(KeyValue::VT_VALUE, value); } explicit KeyValueBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } KeyValueBuilder &operator=(const KeyValueBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); fbb_.Required(o, KeyValue::VT_KEY); return o; } }; inline flatbuffers::Offset CreateKeyValue( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset key = 0, flatbuffers::Offset value = 0) { KeyValueBuilder builder_(_fbb); builder_.add_value(value); builder_.add_key(key); return builder_.Finish(); } inline flatbuffers::Offset CreateKeyValueDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *key = nullptr, const char *value = nullptr) { return reflection::CreateKeyValue( _fbb, key ? _fbb.CreateString(key) : 0, value ? _fbb.CreateString(value) : 0); } struct EnumVal FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_VALUE = 6, VT_OBJECT = 8, VT_UNION_TYPE = 10, VT_DOCUMENTATION = 12 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } int64_t value() const { return GetField(VT_VALUE, 0); } bool KeyCompareLessThan(const EnumVal *o) const { return value() < o->value(); } int KeyCompareWithValue(int64_t val) const { return static_cast(value() > val) - static_cast(value() < val); } const Object *object() const { return GetPointer(VT_OBJECT); } const Type *union_type() const { return GetPointer(VT_UNION_TYPE); } const flatbuffers::Vector> *documentation() const { return GetPointer> *>(VT_DOCUMENTATION); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffsetRequired(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyField(verifier, VT_VALUE) && VerifyOffset(verifier, VT_OBJECT) && verifier.VerifyTable(object()) && VerifyOffset(verifier, VT_UNION_TYPE) && verifier.VerifyTable(union_type()) && VerifyOffset(verifier, VT_DOCUMENTATION) && verifier.VerifyVector(documentation()) && verifier.VerifyVectorOfStrings(documentation()) && verifier.EndTable(); } }; struct EnumValBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(EnumVal::VT_NAME, name); } void add_value(int64_t value) { fbb_.AddElement(EnumVal::VT_VALUE, value, 0); } void add_object(flatbuffers::Offset object) { fbb_.AddOffset(EnumVal::VT_OBJECT, object); } void add_union_type(flatbuffers::Offset union_type) { fbb_.AddOffset(EnumVal::VT_UNION_TYPE, union_type); } void add_documentation(flatbuffers::Offset>> documentation) { fbb_.AddOffset(EnumVal::VT_DOCUMENTATION, documentation); } explicit EnumValBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } EnumValBuilder &operator=(const EnumValBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); fbb_.Required(o, EnumVal::VT_NAME); return o; } }; inline flatbuffers::Offset CreateEnumVal( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, int64_t value = 0, flatbuffers::Offset object = 0, flatbuffers::Offset union_type = 0, flatbuffers::Offset>> documentation = 0) { EnumValBuilder builder_(_fbb); builder_.add_value(value); builder_.add_documentation(documentation); builder_.add_union_type(union_type); builder_.add_object(object); builder_.add_name(name); return builder_.Finish(); } inline flatbuffers::Offset CreateEnumValDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, int64_t value = 0, flatbuffers::Offset object = 0, flatbuffers::Offset union_type = 0, const std::vector> *documentation = nullptr) { return reflection::CreateEnumVal( _fbb, name ? _fbb.CreateString(name) : 0, value, object, union_type, documentation ? _fbb.CreateVector>(*documentation) : 0); } struct Enum FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_VALUES = 6, VT_IS_UNION = 8, VT_UNDERLYING_TYPE = 10, VT_ATTRIBUTES = 12, VT_DOCUMENTATION = 14 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } bool KeyCompareLessThan(const Enum *o) const { return *name() < *o->name(); } int KeyCompareWithValue(const char *val) const { return strcmp(name()->c_str(), val); } const flatbuffers::Vector> *values() const { return GetPointer> *>(VT_VALUES); } bool is_union() const { return GetField(VT_IS_UNION, 0) != 0; } const Type *underlying_type() const { return GetPointer(VT_UNDERLYING_TYPE); } const flatbuffers::Vector> *attributes() const { return GetPointer> *>(VT_ATTRIBUTES); } const flatbuffers::Vector> *documentation() const { return GetPointer> *>(VT_DOCUMENTATION); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffsetRequired(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyOffsetRequired(verifier, VT_VALUES) && verifier.VerifyVector(values()) && verifier.VerifyVectorOfTables(values()) && VerifyField(verifier, VT_IS_UNION) && VerifyOffsetRequired(verifier, VT_UNDERLYING_TYPE) && verifier.VerifyTable(underlying_type()) && VerifyOffset(verifier, VT_ATTRIBUTES) && verifier.VerifyVector(attributes()) && verifier.VerifyVectorOfTables(attributes()) && VerifyOffset(verifier, VT_DOCUMENTATION) && verifier.VerifyVector(documentation()) && verifier.VerifyVectorOfStrings(documentation()) && verifier.EndTable(); } }; struct EnumBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(Enum::VT_NAME, name); } void add_values(flatbuffers::Offset>> values) { fbb_.AddOffset(Enum::VT_VALUES, values); } void add_is_union(bool is_union) { fbb_.AddElement(Enum::VT_IS_UNION, static_cast(is_union), 0); } void add_underlying_type(flatbuffers::Offset underlying_type) { fbb_.AddOffset(Enum::VT_UNDERLYING_TYPE, underlying_type); } void add_attributes(flatbuffers::Offset>> attributes) { fbb_.AddOffset(Enum::VT_ATTRIBUTES, attributes); } void add_documentation(flatbuffers::Offset>> documentation) { fbb_.AddOffset(Enum::VT_DOCUMENTATION, documentation); } explicit EnumBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } EnumBuilder &operator=(const EnumBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); fbb_.Required(o, Enum::VT_NAME); fbb_.Required(o, Enum::VT_VALUES); fbb_.Required(o, Enum::VT_UNDERLYING_TYPE); return o; } }; inline flatbuffers::Offset CreateEnum( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, flatbuffers::Offset>> values = 0, bool is_union = false, flatbuffers::Offset underlying_type = 0, flatbuffers::Offset>> attributes = 0, flatbuffers::Offset>> documentation = 0) { EnumBuilder builder_(_fbb); builder_.add_documentation(documentation); builder_.add_attributes(attributes); builder_.add_underlying_type(underlying_type); builder_.add_values(values); builder_.add_name(name); builder_.add_is_union(is_union); return builder_.Finish(); } inline flatbuffers::Offset CreateEnumDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, const std::vector> *values = nullptr, bool is_union = false, flatbuffers::Offset underlying_type = 0, const std::vector> *attributes = nullptr, const std::vector> *documentation = nullptr) { return reflection::CreateEnum( _fbb, name ? _fbb.CreateString(name) : 0, values ? _fbb.CreateVector>(*values) : 0, is_union, underlying_type, attributes ? _fbb.CreateVector>(*attributes) : 0, documentation ? _fbb.CreateVector>(*documentation) : 0); } struct Field FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_TYPE = 6, VT_ID = 8, VT_OFFSET = 10, VT_DEFAULT_INTEGER = 12, VT_DEFAULT_REAL = 14, VT_DEPRECATED = 16, VT_REQUIRED = 18, VT_KEY = 20, VT_ATTRIBUTES = 22, VT_DOCUMENTATION = 24 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } bool KeyCompareLessThan(const Field *o) const { return *name() < *o->name(); } int KeyCompareWithValue(const char *val) const { return strcmp(name()->c_str(), val); } const Type *type() const { return GetPointer(VT_TYPE); } uint16_t id() const { return GetField(VT_ID, 0); } uint16_t offset() const { return GetField(VT_OFFSET, 0); } int64_t default_integer() const { return GetField(VT_DEFAULT_INTEGER, 0); } double default_real() const { return GetField(VT_DEFAULT_REAL, 0.0); } bool deprecated() const { return GetField(VT_DEPRECATED, 0) != 0; } bool required() const { return GetField(VT_REQUIRED, 0) != 0; } bool key() const { return GetField(VT_KEY, 0) != 0; } const flatbuffers::Vector> *attributes() const { return GetPointer> *>(VT_ATTRIBUTES); } const flatbuffers::Vector> *documentation() const { return GetPointer> *>(VT_DOCUMENTATION); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffsetRequired(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyOffsetRequired(verifier, VT_TYPE) && verifier.VerifyTable(type()) && VerifyField(verifier, VT_ID) && VerifyField(verifier, VT_OFFSET) && VerifyField(verifier, VT_DEFAULT_INTEGER) && VerifyField(verifier, VT_DEFAULT_REAL) && VerifyField(verifier, VT_DEPRECATED) && VerifyField(verifier, VT_REQUIRED) && VerifyField(verifier, VT_KEY) && VerifyOffset(verifier, VT_ATTRIBUTES) && verifier.VerifyVector(attributes()) && verifier.VerifyVectorOfTables(attributes()) && VerifyOffset(verifier, VT_DOCUMENTATION) && verifier.VerifyVector(documentation()) && verifier.VerifyVectorOfStrings(documentation()) && verifier.EndTable(); } }; struct FieldBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(Field::VT_NAME, name); } void add_type(flatbuffers::Offset type) { fbb_.AddOffset(Field::VT_TYPE, type); } void add_id(uint16_t id) { fbb_.AddElement(Field::VT_ID, id, 0); } void add_offset(uint16_t offset) { fbb_.AddElement(Field::VT_OFFSET, offset, 0); } void add_default_integer(int64_t default_integer) { fbb_.AddElement(Field::VT_DEFAULT_INTEGER, default_integer, 0); } void add_default_real(double default_real) { fbb_.AddElement(Field::VT_DEFAULT_REAL, default_real, 0.0); } void add_deprecated(bool deprecated) { fbb_.AddElement(Field::VT_DEPRECATED, static_cast(deprecated), 0); } void add_required(bool required) { fbb_.AddElement(Field::VT_REQUIRED, static_cast(required), 0); } void add_key(bool key) { fbb_.AddElement(Field::VT_KEY, static_cast(key), 0); } void add_attributes(flatbuffers::Offset>> attributes) { fbb_.AddOffset(Field::VT_ATTRIBUTES, attributes); } void add_documentation(flatbuffers::Offset>> documentation) { fbb_.AddOffset(Field::VT_DOCUMENTATION, documentation); } explicit FieldBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } FieldBuilder &operator=(const FieldBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); fbb_.Required(o, Field::VT_NAME); fbb_.Required(o, Field::VT_TYPE); return o; } }; inline flatbuffers::Offset CreateField( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, flatbuffers::Offset type = 0, uint16_t id = 0, uint16_t offset = 0, int64_t default_integer = 0, double default_real = 0.0, bool deprecated = false, bool required = false, bool key = false, flatbuffers::Offset>> attributes = 0, flatbuffers::Offset>> documentation = 0) { FieldBuilder builder_(_fbb); builder_.add_default_real(default_real); builder_.add_default_integer(default_integer); builder_.add_documentation(documentation); builder_.add_attributes(attributes); builder_.add_type(type); builder_.add_name(name); builder_.add_offset(offset); builder_.add_id(id); builder_.add_key(key); builder_.add_required(required); builder_.add_deprecated(deprecated); return builder_.Finish(); } inline flatbuffers::Offset CreateFieldDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, flatbuffers::Offset type = 0, uint16_t id = 0, uint16_t offset = 0, int64_t default_integer = 0, double default_real = 0.0, bool deprecated = false, bool required = false, bool key = false, const std::vector> *attributes = nullptr, const std::vector> *documentation = nullptr) { return reflection::CreateField( _fbb, name ? _fbb.CreateString(name) : 0, type, id, offset, default_integer, default_real, deprecated, required, key, attributes ? _fbb.CreateVector>(*attributes) : 0, documentation ? _fbb.CreateVector>(*documentation) : 0); } struct Object FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_FIELDS = 6, VT_IS_STRUCT = 8, VT_MINALIGN = 10, VT_BYTESIZE = 12, VT_ATTRIBUTES = 14, VT_DOCUMENTATION = 16 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } bool KeyCompareLessThan(const Object *o) const { return *name() < *o->name(); } int KeyCompareWithValue(const char *val) const { return strcmp(name()->c_str(), val); } const flatbuffers::Vector> *fields() const { return GetPointer> *>(VT_FIELDS); } bool is_struct() const { return GetField(VT_IS_STRUCT, 0) != 0; } int32_t minalign() const { return GetField(VT_MINALIGN, 0); } int32_t bytesize() const { return GetField(VT_BYTESIZE, 0); } const flatbuffers::Vector> *attributes() const { return GetPointer> *>(VT_ATTRIBUTES); } const flatbuffers::Vector> *documentation() const { return GetPointer> *>(VT_DOCUMENTATION); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffsetRequired(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyOffsetRequired(verifier, VT_FIELDS) && verifier.VerifyVector(fields()) && verifier.VerifyVectorOfTables(fields()) && VerifyField(verifier, VT_IS_STRUCT) && VerifyField(verifier, VT_MINALIGN) && VerifyField(verifier, VT_BYTESIZE) && VerifyOffset(verifier, VT_ATTRIBUTES) && verifier.VerifyVector(attributes()) && verifier.VerifyVectorOfTables(attributes()) && VerifyOffset(verifier, VT_DOCUMENTATION) && verifier.VerifyVector(documentation()) && verifier.VerifyVectorOfStrings(documentation()) && verifier.EndTable(); } }; struct ObjectBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(Object::VT_NAME, name); } void add_fields(flatbuffers::Offset>> fields) { fbb_.AddOffset(Object::VT_FIELDS, fields); } void add_is_struct(bool is_struct) { fbb_.AddElement(Object::VT_IS_STRUCT, static_cast(is_struct), 0); } void add_minalign(int32_t minalign) { fbb_.AddElement(Object::VT_MINALIGN, minalign, 0); } void add_bytesize(int32_t bytesize) { fbb_.AddElement(Object::VT_BYTESIZE, bytesize, 0); } void add_attributes(flatbuffers::Offset>> attributes) { fbb_.AddOffset(Object::VT_ATTRIBUTES, attributes); } void add_documentation(flatbuffers::Offset>> documentation) { fbb_.AddOffset(Object::VT_DOCUMENTATION, documentation); } explicit ObjectBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } ObjectBuilder &operator=(const ObjectBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); fbb_.Required(o, Object::VT_NAME); fbb_.Required(o, Object::VT_FIELDS); return o; } }; inline flatbuffers::Offset CreateObject( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, flatbuffers::Offset>> fields = 0, bool is_struct = false, int32_t minalign = 0, int32_t bytesize = 0, flatbuffers::Offset>> attributes = 0, flatbuffers::Offset>> documentation = 0) { ObjectBuilder builder_(_fbb); builder_.add_documentation(documentation); builder_.add_attributes(attributes); builder_.add_bytesize(bytesize); builder_.add_minalign(minalign); builder_.add_fields(fields); builder_.add_name(name); builder_.add_is_struct(is_struct); return builder_.Finish(); } inline flatbuffers::Offset CreateObjectDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, const std::vector> *fields = nullptr, bool is_struct = false, int32_t minalign = 0, int32_t bytesize = 0, const std::vector> *attributes = nullptr, const std::vector> *documentation = nullptr) { return reflection::CreateObject( _fbb, name ? _fbb.CreateString(name) : 0, fields ? _fbb.CreateVector>(*fields) : 0, is_struct, minalign, bytesize, attributes ? _fbb.CreateVector>(*attributes) : 0, documentation ? _fbb.CreateVector>(*documentation) : 0); } struct RPCCall FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_REQUEST = 6, VT_RESPONSE = 8, VT_ATTRIBUTES = 10, VT_DOCUMENTATION = 12 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } bool KeyCompareLessThan(const RPCCall *o) const { return *name() < *o->name(); } int KeyCompareWithValue(const char *val) const { return strcmp(name()->c_str(), val); } const Object *request() const { return GetPointer(VT_REQUEST); } const Object *response() const { return GetPointer(VT_RESPONSE); } const flatbuffers::Vector> *attributes() const { return GetPointer> *>(VT_ATTRIBUTES); } const flatbuffers::Vector> *documentation() const { return GetPointer> *>(VT_DOCUMENTATION); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffsetRequired(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyOffsetRequired(verifier, VT_REQUEST) && verifier.VerifyTable(request()) && VerifyOffsetRequired(verifier, VT_RESPONSE) && verifier.VerifyTable(response()) && VerifyOffset(verifier, VT_ATTRIBUTES) && verifier.VerifyVector(attributes()) && verifier.VerifyVectorOfTables(attributes()) && VerifyOffset(verifier, VT_DOCUMENTATION) && verifier.VerifyVector(documentation()) && verifier.VerifyVectorOfStrings(documentation()) && verifier.EndTable(); } }; struct RPCCallBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(RPCCall::VT_NAME, name); } void add_request(flatbuffers::Offset request) { fbb_.AddOffset(RPCCall::VT_REQUEST, request); } void add_response(flatbuffers::Offset response) { fbb_.AddOffset(RPCCall::VT_RESPONSE, response); } void add_attributes(flatbuffers::Offset>> attributes) { fbb_.AddOffset(RPCCall::VT_ATTRIBUTES, attributes); } void add_documentation(flatbuffers::Offset>> documentation) { fbb_.AddOffset(RPCCall::VT_DOCUMENTATION, documentation); } explicit RPCCallBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } RPCCallBuilder &operator=(const RPCCallBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); fbb_.Required(o, RPCCall::VT_NAME); fbb_.Required(o, RPCCall::VT_REQUEST); fbb_.Required(o, RPCCall::VT_RESPONSE); return o; } }; inline flatbuffers::Offset CreateRPCCall( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, flatbuffers::Offset request = 0, flatbuffers::Offset response = 0, flatbuffers::Offset>> attributes = 0, flatbuffers::Offset>> documentation = 0) { RPCCallBuilder builder_(_fbb); builder_.add_documentation(documentation); builder_.add_attributes(attributes); builder_.add_response(response); builder_.add_request(request); builder_.add_name(name); return builder_.Finish(); } inline flatbuffers::Offset CreateRPCCallDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, flatbuffers::Offset request = 0, flatbuffers::Offset response = 0, const std::vector> *attributes = nullptr, const std::vector> *documentation = nullptr) { return reflection::CreateRPCCall( _fbb, name ? _fbb.CreateString(name) : 0, request, response, attributes ? _fbb.CreateVector>(*attributes) : 0, documentation ? _fbb.CreateVector>(*documentation) : 0); } struct Service FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_CALLS = 6, VT_ATTRIBUTES = 8, VT_DOCUMENTATION = 10 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } bool KeyCompareLessThan(const Service *o) const { return *name() < *o->name(); } int KeyCompareWithValue(const char *val) const { return strcmp(name()->c_str(), val); } const flatbuffers::Vector> *calls() const { return GetPointer> *>(VT_CALLS); } const flatbuffers::Vector> *attributes() const { return GetPointer> *>(VT_ATTRIBUTES); } const flatbuffers::Vector> *documentation() const { return GetPointer> *>(VT_DOCUMENTATION); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffsetRequired(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyOffset(verifier, VT_CALLS) && verifier.VerifyVector(calls()) && verifier.VerifyVectorOfTables(calls()) && VerifyOffset(verifier, VT_ATTRIBUTES) && verifier.VerifyVector(attributes()) && verifier.VerifyVectorOfTables(attributes()) && VerifyOffset(verifier, VT_DOCUMENTATION) && verifier.VerifyVector(documentation()) && verifier.VerifyVectorOfStrings(documentation()) && verifier.EndTable(); } }; struct ServiceBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(Service::VT_NAME, name); } void add_calls(flatbuffers::Offset>> calls) { fbb_.AddOffset(Service::VT_CALLS, calls); } void add_attributes(flatbuffers::Offset>> attributes) { fbb_.AddOffset(Service::VT_ATTRIBUTES, attributes); } void add_documentation(flatbuffers::Offset>> documentation) { fbb_.AddOffset(Service::VT_DOCUMENTATION, documentation); } explicit ServiceBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } ServiceBuilder &operator=(const ServiceBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); fbb_.Required(o, Service::VT_NAME); return o; } }; inline flatbuffers::Offset CreateService( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, flatbuffers::Offset>> calls = 0, flatbuffers::Offset>> attributes = 0, flatbuffers::Offset>> documentation = 0) { ServiceBuilder builder_(_fbb); builder_.add_documentation(documentation); builder_.add_attributes(attributes); builder_.add_calls(calls); builder_.add_name(name); return builder_.Finish(); } inline flatbuffers::Offset CreateServiceDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, const std::vector> *calls = nullptr, const std::vector> *attributes = nullptr, const std::vector> *documentation = nullptr) { return reflection::CreateService( _fbb, name ? _fbb.CreateString(name) : 0, calls ? _fbb.CreateVector>(*calls) : 0, attributes ? _fbb.CreateVector>(*attributes) : 0, documentation ? _fbb.CreateVector>(*documentation) : 0); } struct Schema FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_OBJECTS = 4, VT_ENUMS = 6, VT_FILE_IDENT = 8, VT_FILE_EXT = 10, VT_ROOT_TABLE = 12, VT_SERVICES = 14 }; const flatbuffers::Vector> *objects() const { return GetPointer> *>(VT_OBJECTS); } const flatbuffers::Vector> *enums() const { return GetPointer> *>(VT_ENUMS); } const flatbuffers::String *file_ident() const { return GetPointer(VT_FILE_IDENT); } const flatbuffers::String *file_ext() const { return GetPointer(VT_FILE_EXT); } const Object *root_table() const { return GetPointer(VT_ROOT_TABLE); } const flatbuffers::Vector> *services() const { return GetPointer> *>(VT_SERVICES); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffsetRequired(verifier, VT_OBJECTS) && verifier.VerifyVector(objects()) && verifier.VerifyVectorOfTables(objects()) && VerifyOffsetRequired(verifier, VT_ENUMS) && verifier.VerifyVector(enums()) && verifier.VerifyVectorOfTables(enums()) && VerifyOffset(verifier, VT_FILE_IDENT) && verifier.VerifyString(file_ident()) && VerifyOffset(verifier, VT_FILE_EXT) && verifier.VerifyString(file_ext()) && VerifyOffset(verifier, VT_ROOT_TABLE) && verifier.VerifyTable(root_table()) && VerifyOffset(verifier, VT_SERVICES) && verifier.VerifyVector(services()) && verifier.VerifyVectorOfTables(services()) && verifier.EndTable(); } }; struct SchemaBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_objects(flatbuffers::Offset>> objects) { fbb_.AddOffset(Schema::VT_OBJECTS, objects); } void add_enums(flatbuffers::Offset>> enums) { fbb_.AddOffset(Schema::VT_ENUMS, enums); } void add_file_ident(flatbuffers::Offset file_ident) { fbb_.AddOffset(Schema::VT_FILE_IDENT, file_ident); } void add_file_ext(flatbuffers::Offset file_ext) { fbb_.AddOffset(Schema::VT_FILE_EXT, file_ext); } void add_root_table(flatbuffers::Offset root_table) { fbb_.AddOffset(Schema::VT_ROOT_TABLE, root_table); } void add_services(flatbuffers::Offset>> services) { fbb_.AddOffset(Schema::VT_SERVICES, services); } explicit SchemaBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } SchemaBuilder &operator=(const SchemaBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); fbb_.Required(o, Schema::VT_OBJECTS); fbb_.Required(o, Schema::VT_ENUMS); return o; } }; inline flatbuffers::Offset CreateSchema( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset>> objects = 0, flatbuffers::Offset>> enums = 0, flatbuffers::Offset file_ident = 0, flatbuffers::Offset file_ext = 0, flatbuffers::Offset root_table = 0, flatbuffers::Offset>> services = 0) { SchemaBuilder builder_(_fbb); builder_.add_services(services); builder_.add_root_table(root_table); builder_.add_file_ext(file_ext); builder_.add_file_ident(file_ident); builder_.add_enums(enums); builder_.add_objects(objects); return builder_.Finish(); } inline flatbuffers::Offset CreateSchemaDirect( flatbuffers::FlatBufferBuilder &_fbb, const std::vector> *objects = nullptr, const std::vector> *enums = nullptr, const char *file_ident = nullptr, const char *file_ext = nullptr, flatbuffers::Offset root_table = 0, const std::vector> *services = nullptr) { return reflection::CreateSchema( _fbb, objects ? _fbb.CreateVector>(*objects) : 0, enums ? _fbb.CreateVector>(*enums) : 0, file_ident ? _fbb.CreateString(file_ident) : 0, file_ext ? _fbb.CreateString(file_ext) : 0, root_table, services ? _fbb.CreateVector>(*services) : 0); } inline const reflection::Schema *GetSchema(const void *buf) { return flatbuffers::GetRoot(buf); } inline const reflection::Schema *GetSizePrefixedSchema(const void *buf) { return flatbuffers::GetSizePrefixedRoot(buf); } inline const char *SchemaIdentifier() { return "BFBS"; } inline bool SchemaBufferHasIdentifier(const void *buf) { return flatbuffers::BufferHasIdentifier( buf, SchemaIdentifier()); } inline bool VerifySchemaBuffer( flatbuffers::Verifier &verifier) { return verifier.VerifyBuffer(SchemaIdentifier()); } inline bool VerifySizePrefixedSchemaBuffer( flatbuffers::Verifier &verifier) { return verifier.VerifySizePrefixedBuffer(SchemaIdentifier()); } inline const char *SchemaExtension() { return "bfbs"; } inline void FinishSchemaBuffer( flatbuffers::FlatBufferBuilder &fbb, flatbuffers::Offset root) { fbb.Finish(root, SchemaIdentifier()); } inline void FinishSizePrefixedSchemaBuffer( flatbuffers::FlatBufferBuilder &fbb, flatbuffers::Offset root) { fbb.FinishSizePrefixed(root, SchemaIdentifier()); } } // namespace reflection #endif // FLATBUFFERS_GENERATED_REFLECTION_REFLECTION_H_ treesheets-1.0.2/lobster/include/flatbuffers/registry.h000066400000000000000000000106261352107072600233160ustar00rootroot00000000000000/* * Copyright 2017 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_REGISTRY_H_ #define FLATBUFFERS_REGISTRY_H_ #include "flatbuffers/idl.h" namespace flatbuffers { // Convenience class to easily parse or generate text for arbitrary FlatBuffers. // Simply pre-populate it with all schema filenames that may be in use, and // This class will look them up using the file_identifier declared in the // schema. class Registry { public: // Call this for all schemas that may be in use. The identifier has // a function in the generated code, e.g. MonsterIdentifier(). void Register(const char *file_identifier, const char *schema_path) { Schema schema; schema.path_ = schema_path; schemas_[file_identifier] = schema; } // Generate text from an arbitrary FlatBuffer by looking up its // file_identifier in the registry. bool FlatBufferToText(const uint8_t *flatbuf, size_t len, std::string *dest) { // Get the identifier out of the buffer. // If the buffer is truncated, exit. if (len < sizeof(uoffset_t) + FlatBufferBuilder::kFileIdentifierLength) { lasterror_ = "buffer truncated"; return false; } std::string ident( reinterpret_cast(flatbuf) + sizeof(uoffset_t), FlatBufferBuilder::kFileIdentifierLength); // Load and parse the schema. Parser parser; if (!LoadSchema(ident, &parser)) return false; // Now we're ready to generate text. if (!GenerateText(parser, flatbuf, dest)) { lasterror_ = "unable to generate text for FlatBuffer binary"; return false; } return true; } // Converts a binary buffer to text using one of the schemas in the registry, // use the file_identifier to indicate which. // If DetachedBuffer::data() is null then parsing failed. DetachedBuffer TextToFlatBuffer(const char *text, const char *file_identifier) { // Load and parse the schema. Parser parser; if (!LoadSchema(file_identifier, &parser)) return DetachedBuffer(); // Parse the text. if (!parser.Parse(text)) { lasterror_ = parser.error_; return DetachedBuffer(); } // We have a valid FlatBuffer. Detach it from the builder and return. return parser.builder_.ReleaseBufferPointer(); } // Modify any parsing / output options used by the other functions. void SetOptions(const IDLOptions &opts) { opts_ = opts; } // If schemas used contain include statements, call this function for every // directory the parser should search them for. void AddIncludeDirectory(const char *path) { include_paths_.push_back(path); } // Returns a human readable error if any of the above functions fail. const std::string &GetLastError() { return lasterror_; } private: bool LoadSchema(const std::string &ident, Parser *parser) { // Find the schema, if not, exit. auto it = schemas_.find(ident); if (it == schemas_.end()) { // Don't attach the identifier, since it may not be human readable. lasterror_ = "identifier for this buffer not in the registry"; return false; } auto &schema = it->second; // Load the schema from disk. If not, exit. std::string schematext; if (!LoadFile(schema.path_.c_str(), false, &schematext)) { lasterror_ = "could not load schema: " + schema.path_; return false; } // Parse schema. parser->opts = opts_; if (!parser->Parse(schematext.c_str(), vector_data(include_paths_), schema.path_.c_str())) { lasterror_ = parser->error_; return false; } return true; } struct Schema { std::string path_; // TODO(wvo) optionally cache schema file or parsed schema here. }; std::string lasterror_; IDLOptions opts_; std::vector include_paths_; std::map schemas_; }; } // namespace flatbuffers #endif // FLATBUFFERS_REGISTRY_H_ treesheets-1.0.2/lobster/include/flatbuffers/stl_emulation.h000066400000000000000000000174071352107072600243310ustar00rootroot00000000000000/* * Copyright 2017 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_STL_EMULATION_H_ #define FLATBUFFERS_STL_EMULATION_H_ // clang-format off #include #include #include #include #include #if defined(_STLPORT_VERSION) && !defined(FLATBUFFERS_CPP98_STL) #define FLATBUFFERS_CPP98_STL #endif // defined(_STLPORT_VERSION) && !defined(FLATBUFFERS_CPP98_STL) #if defined(FLATBUFFERS_CPP98_STL) #include #endif // defined(FLATBUFFERS_CPP98_STL) // Check if we can use template aliases // Not possible if Microsoft Compiler before 2012 // Possible is the language feature __cpp_alias_templates is defined well // Or possible if the C++ std is C+11 or newer #if !(defined(_MSC_VER) && _MSC_VER <= 1700 /* MSVC2012 */) \ && ((defined(__cpp_alias_templates) && __cpp_alias_templates >= 200704) \ || (defined(__cplusplus) && __cplusplus >= 201103L)) #define FLATBUFFERS_TEMPLATES_ALIASES #endif // This header provides backwards compatibility for C++98 STLs like stlport. namespace flatbuffers { // Retrieve ::back() from a string in a way that is compatible with pre C++11 // STLs (e.g stlport). inline char& string_back(std::string &value) { return value[value.length() - 1]; } inline char string_back(const std::string &value) { return value[value.length() - 1]; } // Helper method that retrieves ::data() from a vector in a way that is // compatible with pre C++11 STLs (e.g stlport). template inline T *vector_data(std::vector &vector) { // In some debug environments, operator[] does bounds checking, so &vector[0] // can't be used. return vector.empty() ? nullptr : &vector[0]; } template inline const T *vector_data( const std::vector &vector) { return vector.empty() ? nullptr : &vector[0]; } template inline void vector_emplace_back(std::vector *vector, V &&data) { #if defined(FLATBUFFERS_CPP98_STL) vector->push_back(data); #else vector->emplace_back(std::forward(data)); #endif // defined(FLATBUFFERS_CPP98_STL) } #ifndef FLATBUFFERS_CPP98_STL #if defined(FLATBUFFERS_TEMPLATES_ALIASES) template using numeric_limits = std::numeric_limits; #else template class numeric_limits : public std::numeric_limits {}; #endif // defined(FLATBUFFERS_TEMPLATES_ALIASES) #else template class numeric_limits : public std::numeric_limits {}; template <> class numeric_limits { public: static unsigned long long min() { return 0ULL; } static unsigned long long max() { return ~0ULL; } }; template <> class numeric_limits { public: static long long min() { return static_cast(1ULL << ((sizeof(long long) << 3) - 1)); } static long long max() { return static_cast( (1ULL << ((sizeof(long long) << 3) - 1)) - 1); } }; #endif // FLATBUFFERS_CPP98_STL #if defined(FLATBUFFERS_TEMPLATES_ALIASES) #ifndef FLATBUFFERS_CPP98_STL template using is_scalar = std::is_scalar; template using is_same = std::is_same; template using is_floating_point = std::is_floating_point; template using is_unsigned = std::is_unsigned; #else // Map C++ TR1 templates defined by stlport. template using is_scalar = std::tr1::is_scalar; template using is_same = std::tr1::is_same; template using is_floating_point = std::tr1::is_floating_point; template using is_unsigned = std::tr1::is_unsigned; #endif // !FLATBUFFERS_CPP98_STL #else // MSVC 2010 doesn't support C++11 aliases. template struct is_scalar : public std::is_scalar {}; template struct is_same : public std::is_same {}; template struct is_floating_point : public std::is_floating_point {}; template struct is_unsigned : public std::is_unsigned {}; #endif // defined(FLATBUFFERS_TEMPLATES_ALIASES) #ifndef FLATBUFFERS_CPP98_STL #if defined(FLATBUFFERS_TEMPLATES_ALIASES) template using unique_ptr = std::unique_ptr; #else // MSVC 2010 doesn't support C++11 aliases. // We're manually "aliasing" the class here as we want to bring unique_ptr // into the flatbuffers namespace. We have unique_ptr in the flatbuffers // namespace we have a completely independent implemenation (see below) // for C++98 STL implementations. template class unique_ptr : public std::unique_ptr { public: unique_ptr() {} explicit unique_ptr(T* p) : std::unique_ptr(p) {} unique_ptr(std::unique_ptr&& u) { *this = std::move(u); } unique_ptr(unique_ptr&& u) { *this = std::move(u); } unique_ptr& operator=(std::unique_ptr&& u) { std::unique_ptr::reset(u.release()); return *this; } unique_ptr& operator=(unique_ptr&& u) { std::unique_ptr::reset(u.release()); return *this; } unique_ptr& operator=(T* p) { return std::unique_ptr::operator=(p); } }; #endif // defined(FLATBUFFERS_TEMPLATES_ALIASES) #else // Very limited implementation of unique_ptr. // This is provided simply to allow the C++ code generated from the default // settings to function in C++98 environments with no modifications. template class unique_ptr { public: typedef T element_type; unique_ptr() : ptr_(nullptr) {} explicit unique_ptr(T* p) : ptr_(p) {} unique_ptr(unique_ptr&& u) : ptr_(nullptr) { reset(u.release()); } unique_ptr(const unique_ptr& u) : ptr_(nullptr) { reset(const_cast(&u)->release()); } ~unique_ptr() { reset(); } unique_ptr& operator=(const unique_ptr& u) { reset(const_cast(&u)->release()); return *this; } unique_ptr& operator=(unique_ptr&& u) { reset(u.release()); return *this; } unique_ptr& operator=(T* p) { reset(p); return *this; } const T& operator*() const { return *ptr_; } T* operator->() const { return ptr_; } T* get() const noexcept { return ptr_; } explicit operator bool() const { return ptr_ != nullptr; } // modifiers T* release() { T* value = ptr_; ptr_ = nullptr; return value; } void reset(T* p = nullptr) { T* value = ptr_; ptr_ = p; if (value) delete value; } void swap(unique_ptr& u) { T* temp_ptr = ptr_; ptr_ = u.ptr_; u.ptr_ = temp_ptr; } private: T* ptr_; }; template bool operator==(const unique_ptr& x, const unique_ptr& y) { return x.get() == y.get(); } template bool operator==(const unique_ptr& x, const D* y) { return static_cast(x.get()) == y; } template bool operator==(const unique_ptr& x, intptr_t y) { return reinterpret_cast(x.get()) == y; } #endif // !FLATBUFFERS_CPP98_STL } // namespace flatbuffers #endif // FLATBUFFERS_STL_EMULATION_H_ treesheets-1.0.2/lobster/include/flatbuffers/util.h000066400000000000000000000427551352107072600224330ustar00rootroot00000000000000/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FLATBUFFERS_UTIL_H_ #define FLATBUFFERS_UTIL_H_ #include #include #include #include #include #ifndef FLATBUFFERS_PREFER_PRINTF # include #else // FLATBUFFERS_PREFER_PRINTF # include # include #endif // FLATBUFFERS_PREFER_PRINTF #include #ifdef _WIN32 # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif # ifndef NOMINMAX # define NOMINMAX # endif # include // Must be included before # include # include # undef interface // This is also important because of reasons #else # include #endif #include #include #include "flatbuffers/base.h" namespace flatbuffers { #ifdef FLATBUFFERS_PREFER_PRINTF template size_t IntToDigitCount(T t) { size_t digit_count = 0; // Count the sign for negative numbers if (t < 0) digit_count++; // Count a single 0 left of the dot for fractional numbers if (-1 < t && t < 1) digit_count++; // Count digits until fractional part T eps = std::numeric_limits::epsilon(); while (t <= (-1 + eps) || (1 - eps) <= t) { t /= 10; digit_count++; } return digit_count; } template size_t NumToStringWidth(T t, int precision = 0) { size_t string_width = IntToDigitCount(t); // Count the dot for floating point numbers if (precision) string_width += (precision + 1); return string_width; } template std::string NumToStringImplWrapper(T t, const char* fmt, int precision = 0) { size_t string_width = NumToStringWidth(t, precision); std::string s(string_width, 0x00); // Allow snprintf to use std::string trailing null to detect buffer overflow snprintf(const_cast(s.data()), (s.size()+1), fmt, precision, t); return s; } #endif // FLATBUFFERS_PREFER_PRINTF // Convert an integer or floating point value to a string. // In contrast to std::stringstream, "char" values are // converted to a string of digits, and we don't use scientific notation. template std::string NumToString(T t) { // clang-format off #ifndef FLATBUFFERS_PREFER_PRINTF std::stringstream ss; ss << t; return ss.str(); #else // FLATBUFFERS_PREFER_PRINTF auto v = static_cast(t); return NumToStringImplWrapper(v, "%.*lld"); #endif // FLATBUFFERS_PREFER_PRINTF // clang-format on } // Avoid char types used as character data. template<> inline std::string NumToString(signed char t) { return NumToString(static_cast(t)); } template<> inline std::string NumToString(unsigned char t) { return NumToString(static_cast(t)); } #if defined(FLATBUFFERS_CPP98_STL) template<> inline std::string NumToString(long long t) { char buf[21]; // (log((1 << 63) - 1) / log(10)) + 2 snprintf(buf, sizeof(buf), "%lld", t); return std::string(buf); } template<> inline std::string NumToString(unsigned long long t) { char buf[22]; // (log((1 << 63) - 1) / log(10)) + 1 snprintf(buf, sizeof(buf), "%llu", t); return std::string(buf); } #endif // defined(FLATBUFFERS_CPP98_STL) // Special versions for floats/doubles. template std::string FloatToString(T t, int precision) { // clang-format off #ifndef FLATBUFFERS_PREFER_PRINTF // to_string() prints different numbers of digits for floats depending on // platform and isn't available on Android, so we use stringstream std::stringstream ss; // Use std::fixed to suppress scientific notation. ss << std::fixed; // Default precision is 6, we want that to be higher for doubles. ss << std::setprecision(precision); ss << t; auto s = ss.str(); #else // FLATBUFFERS_PREFER_PRINTF auto v = static_cast(t); auto s = NumToStringImplWrapper(v, "%0.*f", precision); #endif // FLATBUFFERS_PREFER_PRINTF // clang-format on // Sadly, std::fixed turns "1" into "1.00000", so here we undo that. auto p = s.find_last_not_of('0'); if (p != std::string::npos) { // Strip trailing zeroes. If it is a whole number, keep one zero. s.resize(p + (s[p] == '.' ? 2 : 1)); } return s; } template<> inline std::string NumToString(double t) { return FloatToString(t, 12); } template<> inline std::string NumToString(float t) { return FloatToString(t, 6); } // Convert an integer value to a hexadecimal string. // The returned string length is always xdigits long, prefixed by 0 digits. // For example, IntToStringHex(0x23, 8) returns the string "00000023". inline std::string IntToStringHex(int i, int xdigits) { // clang-format off #ifndef FLATBUFFERS_PREFER_PRINTF std::stringstream ss; ss << std::setw(xdigits) << std::setfill('0') << std::hex << std::uppercase << i; return ss.str(); #else // FLATBUFFERS_PREFER_PRINTF return NumToStringImplWrapper(i, "%.*X", xdigits); #endif // FLATBUFFERS_PREFER_PRINTF // clang-format on } // Portable implementation of strtoll(). inline int64_t StringToInt(const char *str, char **endptr = nullptr, int base = 10) { // clang-format off #ifdef _MSC_VER return _strtoi64(str, endptr, base); #else return strtoll(str, endptr, base); #endif // clang-format on } // Portable implementation of strtoull(). inline uint64_t StringToUInt(const char *str, char **endptr = nullptr, int base = 10) { // clang-format off #ifdef _MSC_VER return _strtoui64(str, endptr, base); #else return strtoull(str, endptr, base); #endif // clang-format on } typedef bool (*LoadFileFunction)(const char *filename, bool binary, std::string *dest); typedef bool (*FileExistsFunction)(const char *filename); LoadFileFunction SetLoadFileFunction(LoadFileFunction load_file_function); FileExistsFunction SetFileExistsFunction( FileExistsFunction file_exists_function); // Check if file "name" exists. bool FileExists(const char *name); // Check if "name" exists and it is also a directory. bool DirExists(const char *name); // Load file "name" into "buf" returning true if successful // false otherwise. If "binary" is false data is read // using ifstream's text mode, otherwise data is read with // no transcoding. bool LoadFile(const char *name, bool binary, std::string *buf); // Save data "buf" of length "len" bytes into a file // "name" returning true if successful, false otherwise. // If "binary" is false data is written using ifstream's // text mode, otherwise data is written with no // transcoding. inline bool SaveFile(const char *name, const char *buf, size_t len, bool binary) { std::ofstream ofs(name, binary ? std::ofstream::binary : std::ofstream::out); if (!ofs.is_open()) return false; ofs.write(buf, len); return !ofs.bad(); } // Save data "buf" into file "name" returning true if // successful, false otherwise. If "binary" is false // data is written using ifstream's text mode, otherwise // data is written with no transcoding. inline bool SaveFile(const char *name, const std::string &buf, bool binary) { return SaveFile(name, buf.c_str(), buf.size(), binary); } // Functionality for minimalistic portable path handling. // The functions below behave correctly regardless of whether posix ('/') or // Windows ('/' or '\\') separators are used. // Any new separators inserted are always posix. // We internally store paths in posix format ('/'). Paths supplied // by the user should go through PosixPath to ensure correct behavior // on Windows when paths are string-compared. static const char kPathSeparator = '/'; static const char kPathSeparatorWindows = '\\'; static const char *PathSeparatorSet = "\\/"; // Intentionally no ':' // Returns the path with the extension, if any, removed. inline std::string StripExtension(const std::string &filepath) { size_t i = filepath.find_last_of("."); return i != std::string::npos ? filepath.substr(0, i) : filepath; } // Returns the extension, if any. inline std::string GetExtension(const std::string &filepath) { size_t i = filepath.find_last_of("."); return i != std::string::npos ? filepath.substr(i + 1) : ""; } // Return the last component of the path, after the last separator. inline std::string StripPath(const std::string &filepath) { size_t i = filepath.find_last_of(PathSeparatorSet); return i != std::string::npos ? filepath.substr(i + 1) : filepath; } // Strip the last component of the path + separator. inline std::string StripFileName(const std::string &filepath) { size_t i = filepath.find_last_of(PathSeparatorSet); return i != std::string::npos ? filepath.substr(0, i) : ""; } // Concatenates a path with a filename, regardless of wether the path // ends in a separator or not. inline std::string ConCatPathFileName(const std::string &path, const std::string &filename) { std::string filepath = path; if (filepath.length()) { char &filepath_last_character = string_back(filepath); if (filepath_last_character == kPathSeparatorWindows) { filepath_last_character = kPathSeparator; } else if (filepath_last_character != kPathSeparator) { filepath += kPathSeparator; } } filepath += filename; // Ignore './' at the start of filepath. if (filepath[0] == '.' && filepath[1] == kPathSeparator) { filepath.erase(0, 2); } return filepath; } // Replaces any '\\' separators with '/' inline std::string PosixPath(const char *path) { std::string p = path; std::replace(p.begin(), p.end(), '\\', '/'); return p; } // This function ensure a directory exists, by recursively // creating dirs for any parts of the path that don't exist yet. inline void EnsureDirExists(const std::string &filepath) { auto parent = StripFileName(filepath); if (parent.length()) EnsureDirExists(parent); // clang-format off #ifdef _WIN32 (void)_mkdir(filepath.c_str()); #else mkdir(filepath.c_str(), S_IRWXU|S_IRGRP|S_IXGRP); #endif // clang-format on } // Obtains the absolute path from any other path. // Returns the input path if the absolute path couldn't be resolved. inline std::string AbsolutePath(const std::string &filepath) { // clang-format off #ifdef FLATBUFFERS_NO_ABSOLUTE_PATH_RESOLUTION return filepath; #else #ifdef _WIN32 char abs_path[MAX_PATH]; return GetFullPathNameA(filepath.c_str(), MAX_PATH, abs_path, nullptr) #else char abs_path[PATH_MAX]; return realpath(filepath.c_str(), abs_path) #endif ? abs_path : filepath; #endif // FLATBUFFERS_NO_ABSOLUTE_PATH_RESOLUTION // clang-format on } // To and from UTF-8 unicode conversion functions // Convert a unicode code point into a UTF-8 representation by appending it // to a string. Returns the number of bytes generated. inline int ToUTF8(uint32_t ucc, std::string *out) { FLATBUFFERS_ASSERT(!(ucc & 0x80000000)); // Top bit can't be set. // 6 possible encodings: http://en.wikipedia.org/wiki/UTF-8 for (int i = 0; i < 6; i++) { // Max bits this encoding can represent. uint32_t max_bits = 6 + i * 5 + static_cast(!i); if (ucc < (1u << max_bits)) { // does it fit? // Remaining bits not encoded in the first byte, store 6 bits each uint32_t remain_bits = i * 6; // Store first byte: (*out) += static_cast((0xFE << (max_bits - remain_bits)) | (ucc >> remain_bits)); // Store remaining bytes: for (int j = i - 1; j >= 0; j--) { (*out) += static_cast(((ucc >> (j * 6)) & 0x3F) | 0x80); } return i + 1; // Return the number of bytes added. } } FLATBUFFERS_ASSERT(0); // Impossible to arrive here. return -1; } // Converts whatever prefix of the incoming string corresponds to a valid // UTF-8 sequence into a unicode code. The incoming pointer will have been // advanced past all bytes parsed. // returns -1 upon corrupt UTF-8 encoding (ignore the incoming pointer in // this case). inline int FromUTF8(const char **in) { int len = 0; // Count leading 1 bits. for (int mask = 0x80; mask >= 0x04; mask >>= 1) { if (**in & mask) { len++; } else { break; } } if ((static_cast(**in) << len) & 0x80) return -1; // Bit after leading 1's must be 0. if (!len) return *(*in)++; // UTF-8 encoded values with a length are between 2 and 4 bytes. if (len < 2 || len > 4) { return -1; } // Grab initial bits of the code. int ucc = *(*in)++ & ((1 << (7 - len)) - 1); for (int i = 0; i < len - 1; i++) { if ((**in & 0xC0) != 0x80) return -1; // Upper bits must 1 0. ucc <<= 6; ucc |= *(*in)++ & 0x3F; // Grab 6 more bits of the code. } // UTF-8 cannot encode values between 0xD800 and 0xDFFF (reserved for // UTF-16 surrogate pairs). if (ucc >= 0xD800 && ucc <= 0xDFFF) { return -1; } // UTF-8 must represent code points in their shortest possible encoding. switch (len) { case 2: // Two bytes of UTF-8 can represent code points from U+0080 to U+07FF. if (ucc < 0x0080 || ucc > 0x07FF) { return -1; } break; case 3: // Three bytes of UTF-8 can represent code points from U+0800 to U+FFFF. if (ucc < 0x0800 || ucc > 0xFFFF) { return -1; } break; case 4: // Four bytes of UTF-8 can represent code points from U+10000 to U+10FFFF. if (ucc < 0x10000 || ucc > 0x10FFFF) { return -1; } break; } return ucc; } #ifndef FLATBUFFERS_PREFER_PRINTF // Wraps a string to a maximum length, inserting new lines where necessary. Any // existing whitespace will be collapsed down to a single space. A prefix or // suffix can be provided, which will be inserted before or after a wrapped // line, respectively. inline std::string WordWrap(const std::string in, size_t max_length, const std::string wrapped_line_prefix, const std::string wrapped_line_suffix) { std::istringstream in_stream(in); std::string wrapped, line, word; in_stream >> word; line = word; while (in_stream >> word) { if ((line.length() + 1 + word.length() + wrapped_line_suffix.length()) < max_length) { line += " " + word; } else { wrapped += line + wrapped_line_suffix + "\n"; line = wrapped_line_prefix + word; } } wrapped += line; return wrapped; } #endif // !FLATBUFFERS_PREFER_PRINTF inline bool EscapeString(const char *s, size_t length, std::string *_text, bool allow_non_utf8, bool natural_utf8) { std::string &text = *_text; text += "\""; for (uoffset_t i = 0; i < length; i++) { char c = s[i]; switch (c) { case '\n': text += "\\n"; break; case '\t': text += "\\t"; break; case '\r': text += "\\r"; break; case '\b': text += "\\b"; break; case '\f': text += "\\f"; break; case '\"': text += "\\\""; break; case '\\': text += "\\\\"; break; default: if (c >= ' ' && c <= '~') { text += c; } else { // Not printable ASCII data. Let's see if it's valid UTF-8 first: const char *utf8 = s + i; int ucc = FromUTF8(&utf8); if (ucc < 0) { if (allow_non_utf8) { text += "\\x"; text += IntToStringHex(static_cast(c), 2); } else { // There are two cases here: // // 1) We reached here by parsing an IDL file. In that case, // we previously checked for non-UTF-8, so we shouldn't reach // here. // // 2) We reached here by someone calling GenerateText() // on a previously-serialized flatbuffer. The data might have // non-UTF-8 Strings, or might be corrupt. // // In both cases, we have to give up and inform the caller // they have no JSON. return false; } } else { if (natural_utf8) { // utf8 points to past all utf-8 bytes parsed text.append(s + i, static_cast(utf8 - s - i)); } else if (ucc <= 0xFFFF) { // Parses as Unicode within JSON's \uXXXX range, so use that. text += "\\u"; text += IntToStringHex(ucc, 4); } else if (ucc <= 0x10FFFF) { // Encode Unicode SMP values to a surrogate pair using two \u // escapes. uint32_t base = ucc - 0x10000; auto high_surrogate = (base >> 10) + 0xD800; auto low_surrogate = (base & 0x03FF) + 0xDC00; text += "\\u"; text += IntToStringHex(high_surrogate, 4); text += "\\u"; text += IntToStringHex(low_surrogate, 4); } // Skip past characters recognized. i = static_cast(utf8 - s - 1); } } break; } } text += "\""; return true; } } // namespace flatbuffers #endif // FLATBUFFERS_UTIL_H_ treesheets-1.0.2/lobster/include/gsl/000077500000000000000000000000001352107072600175525ustar00rootroot00000000000000treesheets-1.0.2/lobster/include/gsl/gsl-lite.hpp000066400000000000000000002413051352107072600220100ustar00rootroot00000000000000// // gsl-lite is based on GSL: Guidelines Support Library. // For more information see https://github.com/martinmoene/gsl-lite // // Copyright (c) 2015-2018 Martin Moene // Copyright (c) 2015-2018 Microsoft Corporation. All rights reserved. // // This code is licensed under the MIT License (MIT). // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #pragma once #ifndef GSL_GSL_LITE_HPP_INCLUDED #define GSL_GSL_LITE_HPP_INCLUDED #include #include #include #include #include #include #include #include #include #include #define gsl_lite_MAJOR 0 #define gsl_lite_MINOR 34 #define gsl_lite_PATCH 0 #define gsl_lite_VERSION gsl_STRINGIFY(gsl_lite_MAJOR) "." gsl_STRINGIFY(gsl_lite_MINOR) "." gsl_STRINGIFY(gsl_lite_PATCH) // gsl-lite backward compatibility: #ifdef gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR # define gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR # pragma message ("gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR is deprecated since gsl-lite 0.7.0; replace with gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR, or consider span(with_container, cont).") #endif // M-GSL compatibility: #if defined( GSL_THROW_ON_CONTRACT_VIOLATION ) # define gsl_CONFIG_CONTRACT_VIOLATION_THROWS 1 #endif #if defined( GSL_TERMINATE_ON_CONTRACT_VIOLATION ) # define gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES 1 #endif #if defined( GSL_UNENFORCED_ON_CONTRACT_VIOLATION ) # define gsl_CONFIG_CONTRACT_LEVEL_OFF 1 #endif // Configuration: Features #ifndef gsl_FEATURE_WITH_CONTAINER_TO_STD # define gsl_FEATURE_WITH_CONTAINER_TO_STD 99 #endif #ifndef gsl_FEATURE_MAKE_SPAN_TO_STD # define gsl_FEATURE_MAKE_SPAN_TO_STD 99 #endif #ifndef gsl_FEATURE_BYTE_SPAN_TO_STD # define gsl_FEATURE_BYTE_SPAN_TO_STD 99 #endif #ifndef gsl_FEATURE_IMPLICIT_MACRO # define gsl_FEATURE_IMPLICIT_MACRO 1 #endif #ifndef gsl_FEATURE_OWNER_MACRO # define gsl_FEATURE_OWNER_MACRO 1 #endif #ifndef gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD # define gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD 0 #endif // Configuration: Other #ifndef gsl_CONFIG_DEPRECATE_TO_LEVEL # define gsl_CONFIG_DEPRECATE_TO_LEVEL 0 #endif #ifndef gsl_CONFIG_SPAN_INDEX_TYPE # define gsl_CONFIG_SPAN_INDEX_TYPE size_t #endif #ifndef gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR # define gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR 0 #endif #ifndef gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF # define gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF 0 #endif #ifndef gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS # define gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS 0 #endif #ifndef gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON # define gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON 1 #endif #ifndef gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR # define gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR 0 #endif #if defined( gsl_CONFIG_CONTRACT_LEVEL_ON ) # define gsl_CONFIG_CONTRACT_LEVEL_MASK 0x11 #elif defined( gsl_CONFIG_CONTRACT_LEVEL_OFF ) # define gsl_CONFIG_CONTRACT_LEVEL_MASK 0x00 #elif defined( gsl_CONFIG_CONTRACT_LEVEL_EXPECTS_ONLY ) # define gsl_CONFIG_CONTRACT_LEVEL_MASK 0x01 #elif defined( gsl_CONFIG_CONTRACT_LEVEL_ENSURES_ONLY ) # define gsl_CONFIG_CONTRACT_LEVEL_MASK 0x10 #else # define gsl_CONFIG_CONTRACT_LEVEL_MASK 0x11 #endif #if 2 <= defined( gsl_CONFIG_CONTRACT_VIOLATION_THROWS ) + defined( gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES ) + defined ( gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER ) # error only one of gsl_CONFIG_CONTRACT_VIOLATION_THROWS, gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES and gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER may be defined. #elif defined( gsl_CONFIG_CONTRACT_VIOLATION_THROWS ) # define gsl_CONFIG_CONTRACT_VIOLATION_THROWS_V 1 # define gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER_V 0 #elif defined( gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES ) # define gsl_CONFIG_CONTRACT_VIOLATION_THROWS_V 0 # define gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER_V 0 #elif defined( gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER ) # define gsl_CONFIG_CONTRACT_VIOLATION_THROWS_V 0 # define gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER_V 1 #else # define gsl_CONFIG_CONTRACT_VIOLATION_THROWS_V 0 # define gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER_V 0 #endif // C++ language version detection (C++20 is speculative): // Note: VC14.0/1900 (VS2015) lacks too much from C++14. #ifndef gsl_CPLUSPLUS # if defined(_MSVC_LANG ) && !defined(__clang__) # define gsl_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) # else # define gsl_CPLUSPLUS __cplusplus # endif #endif #define gsl_CPP98_OR_GREATER ( gsl_CPLUSPLUS >= 199711L ) #define gsl_CPP11_OR_GREATER ( gsl_CPLUSPLUS >= 201103L ) #define gsl_CPP14_OR_GREATER ( gsl_CPLUSPLUS >= 201402L ) #define gsl_CPP17_OR_GREATER ( gsl_CPLUSPLUS >= 201703L ) #define gsl_CPP20_OR_GREATER ( gsl_CPLUSPLUS >= 202000L ) // C++ language version (represent 98 as 3): #define gsl_CPLUSPLUS_V ( gsl_CPLUSPLUS / 100 - (gsl_CPLUSPLUS > 200000 ? 2000 : 1994) ) // half-open range [lo..hi): #define gsl_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) // Compiler versions: // // MSVC++ 6.0 _MSC_VER == 1200 (Visual Studio 6.0) // MSVC++ 7.0 _MSC_VER == 1300 (Visual Studio .NET 2002) // MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio .NET 2003) // MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) // MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) // MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) // MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) // MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) // MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) // MSVC++ 14.1 _MSC_VER >= 1910 (Visual Studio 2017) #if defined(_MSC_VER ) && !defined(__clang__) # define gsl_COMPILER_MSVC_VER (_MSC_VER ) # define gsl_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) #else # define gsl_COMPILER_MSVC_VER 0 # define gsl_COMPILER_MSVC_VERSION 0 #endif #define gsl_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) #if defined(__clang__) # define gsl_COMPILER_CLANG_VERSION gsl_COMPILER_VERSION( __clang_major__, __clang_minor__, __clang_patchlevel__ ) #else # define gsl_COMPILER_CLANG_VERSION 0 #endif #if defined(__GNUC__) && !defined(__clang__) # define gsl_COMPILER_GNUC_VERSION gsl_COMPILER_VERSION( __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__ ) #else # define gsl_COMPILER_GNUC_VERSION 0 #endif // Method enabling (C++98, VC120 (VS2013) cannot use __VA_ARGS__) #define gsl_REQUIRES_0(VA) \ template< bool B = (VA), typename std::enable_if::type = 0 > #define gsl_REQUIRES_T(VA) \ , typename = typename std::enable_if< (VA), gsl::detail::enabler >::type #define gsl_REQUIRES_R(R, VA) \ typename std::enable_if::type #define gsl_REQUIRES_A(VA) \ , typename std::enable_if::type = nullptr // Compiler non-strict aliasing: #if defined(__clang__) || defined(__GNUC__) # define gsl_may_alias __attribute__((__may_alias__)) #else # define gsl_may_alias #endif // Presence of gsl, language and library features: #define gsl_IN_STD( v ) ( ((v) == 98 ? 3 : (v)) >= gsl_CPLUSPLUS_V ) #define gsl_DEPRECATE_TO_LEVEL( level ) ( level <= gsl_CONFIG_DEPRECATE_TO_LEVEL ) #define gsl_FEATURE_TO_STD( feature ) ( gsl_IN_STD( gsl_FEATURE( feature##_TO_STD ) ) ) #define gsl_FEATURE( feature ) ( gsl_FEATURE_##feature ) #define gsl_CONFIG( feature ) ( gsl_CONFIG_##feature ) #define gsl_HAVE( feature ) ( gsl_HAVE_##feature ) // Presence of wide character support: #ifdef __DJGPP__ # define gsl_HAVE_WCHAR 0 #else # define gsl_HAVE_WCHAR 1 #endif // Presence of language & library features: #ifdef _HAS_CPP0X # define gsl_HAS_CPP0X _HAS_CPP0X #else # define gsl_HAS_CPP0X 0 #endif #define gsl_CPP11_100 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1600) #define gsl_CPP11_110 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1700) #define gsl_CPP11_120 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1800) #define gsl_CPP11_140 (gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900) #define gsl_CPP14_000 (gsl_CPP14_OR_GREATER) #define gsl_CPP14_120 (gsl_CPP14_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1800) #define gsl_CPP14_140 (gsl_CPP14_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900) #define gsl_CPP17_000 (gsl_CPP17_OR_GREATER) #define gsl_CPP17_140 (gsl_CPP17_OR_GREATER || gsl_COMPILER_MSVC_VER >= 1900) #define gsl_CPP11_140_CPP0X_90 (gsl_CPP11_140 || (gsl_COMPILER_MSVC_VER >= 1500 && gsl_HAS_CPP0X)) #define gsl_CPP11_140_CPP0X_100 (gsl_CPP11_140 || (gsl_COMPILER_MSVC_VER >= 1600 && gsl_HAS_CPP0X)) // Presence of C++11 language features: #define gsl_HAVE_AUTO gsl_CPP11_100 #define gsl_HAVE_NULLPTR gsl_CPP11_100 #define gsl_HAVE_RVALUE_REFERENCE gsl_CPP11_100 #define gsl_HAVE_ENUM_CLASS gsl_CPP11_110 #define gsl_HAVE_ALIAS_TEMPLATE gsl_CPP11_120 #define gsl_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG gsl_CPP11_120 #define gsl_HAVE_EXPLICIT gsl_CPP11_120 #define gsl_HAVE_INITIALIZER_LIST gsl_CPP11_120 #define gsl_HAVE_CONSTEXPR_11 gsl_CPP11_140 #define gsl_HAVE_IS_DEFAULT gsl_CPP11_140 #define gsl_HAVE_IS_DELETE gsl_CPP11_140 #define gsl_HAVE_NOEXCEPT gsl_CPP11_140 #if gsl_CPP11_OR_GREATER // see above #endif // Presence of C++14 language features: #define gsl_HAVE_CONSTEXPR_14 gsl_CPP14_000 #define gsl_HAVE_DECLTYPE_AUTO gsl_CPP14_140 // Presence of C++17 language features: // MSVC: template parameter deduction guides since Visual Studio 2017 v15.7 #define gsl_HAVE_ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE gsl_CPP17_000 #define gsl_HAVE_DEDUCTION_GUIDES (gsl_CPP17_000 && ! gsl_BETWEEN( gsl_COMPILER_MSVC_VERSION, 1, 999 ) ) // Presence of C++ library features: #define gsl_HAVE_ADDRESSOF gsl_CPP17_000 #define gsl_HAVE_ARRAY gsl_CPP11_110 #define gsl_HAVE_TYPE_TRAITS gsl_CPP11_110 #define gsl_HAVE_TR1_TYPE_TRAITS gsl_CPP11_110 #define gsl_HAVE_CONTAINER_DATA_METHOD gsl_CPP11_140_CPP0X_90 #define gsl_HAVE_STD_DATA gsl_CPP17_000 #define gsl_HAVE_SIZED_TYPES gsl_CPP11_140 #define gsl_HAVE_MAKE_SHARED gsl_CPP11_140_CPP0X_100 #define gsl_HAVE_SHARED_PTR gsl_CPP11_140_CPP0X_100 #define gsl_HAVE_UNIQUE_PTR gsl_CPP11_140_CPP0X_100 #define gsl_HAVE_MAKE_UNIQUE gsl_CPP14_120 #define gsl_HAVE_UNCAUGHT_EXCEPTIONS gsl_CPP17_140 #define gsl_HAVE_ADD_CONST gsl_HAVE_TYPE_TRAITS #define gsl_HAVE_INTEGRAL_CONSTANT gsl_HAVE_TYPE_TRAITS #define gsl_HAVE_REMOVE_CONST gsl_HAVE_TYPE_TRAITS #define gsl_HAVE_REMOVE_REFERENCE gsl_HAVE_TYPE_TRAITS #define gsl_HAVE_TR1_ADD_CONST gsl_HAVE_TR1_TYPE_TRAITS #define gsl_HAVE_TR1_INTEGRAL_CONSTANT gsl_HAVE_TR1_TYPE_TRAITS #define gsl_HAVE_TR1_REMOVE_CONST gsl_HAVE_TR1_TYPE_TRAITS #define gsl_HAVE_TR1_REMOVE_REFERENCE gsl_HAVE_TR1_TYPE_TRAITS // C++ feature usage: #if gsl_HAVE( ADDRESSOF ) # define gsl_ADDRESSOF(x) std::addressof(x) #else # define gsl_ADDRESSOF(x) (&x) #endif #if gsl_HAVE( CONSTEXPR_11 ) # define gsl_constexpr constexpr #else # define gsl_constexpr /*constexpr*/ #endif #if gsl_HAVE( CONSTEXPR_14 ) # define gsl_constexpr14 constexpr #else # define gsl_constexpr14 /*constexpr*/ #endif #if gsl_HAVE( EXPLICIT ) # define gsl_explicit explicit #else # define gsl_explicit /*explicit*/ #endif #if gsl_FEATURE( IMPLICIT_MACRO ) # define implicit /*implicit*/ #endif #if gsl_HAVE( IS_DELETE ) # define gsl_is_delete = delete #else # define gsl_is_delete #endif #if gsl_HAVE( IS_DELETE ) # define gsl_is_delete_access public #else # define gsl_is_delete_access private #endif #if !gsl_HAVE( NOEXCEPT ) || gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) # define gsl_noexcept /*noexcept*/ #else # define gsl_noexcept noexcept #endif #if gsl_HAVE( NULLPTR ) # define gsl_nullptr nullptr #else # define gsl_nullptr NULL #endif #define gsl_DIMENSION_OF( a ) ( sizeof(a) / sizeof(0[a]) ) // Other features: #define gsl_HAVE_CONSTRAINED_SPAN_CONTAINER_CTOR \ ( gsl_HAVE_DEFAULT_FUNCTION_TEMPLATE_ARG && gsl_HAVE_CONTAINER_DATA_METHOD ) // Note: !defined(__NVCC__) doesn't work with nvcc here: #define gsl_HAVE_UNCONSTRAINED_SPAN_CONTAINER_CTOR \ ( gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR && (__NVCC__== 0) ) // GSL API (e.g. for CUDA platform): #ifndef gsl_api # ifdef __CUDACC__ # define gsl_api __host__ __device__ # else # define gsl_api /*gsl_api*/ # endif #endif // Additional includes: #if gsl_HAVE( ARRAY ) # include #endif #if gsl_HAVE( TYPE_TRAITS ) # include #elif gsl_HAVE( TR1_TYPE_TRAITS ) # include #endif #if gsl_HAVE( SIZED_TYPES ) # include #endif // MSVC warning suppression macros: #if gsl_COMPILER_MSVC_VERSION >= 140 # define gsl_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]] # define gsl_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress: code) ) # define gsl_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable: codes)) # define gsl_RESTORE_MSVC_WARNINGS() __pragma(warning(pop )) #else # define gsl_SUPPRESS_MSGSL_WARNING(expr) # define gsl_SUPPRESS_MSVC_WARNING(code, descr) # define gsl_DISABLE_MSVC_WARNINGS(codes) # define gsl_RESTORE_MSVC_WARNINGS() #endif // Suppress the following MSVC GSL warnings: // - C26410: gsl::r.32: the parameter 'ptr' is a reference to const unique pointer, use const T* or const T& instead // - C26415: gsl::r.30: smart pointer parameter 'ptr' is used only to access contained pointer. Use T* or T& instead // - C26418: gsl::r.36: shared pointer parameter 'ptr' is not copied or moved. Use T* or T& instead // - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions; // use brace initialization, gsl::narrow_cast or gsl::narow // - C26439, gsl::f.6 : special function 'function' can be declared 'noexcept' // - C26440, gsl::f.6 : function 'function' can be declared 'noexcept' // - C26473: gsl::t.1 : don't cast between pointer types where the source type and the target type are the same // - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead // - C26482, gsl::b.2 : only index into arrays using constant expressions // - C26490: gsl::t.1 : don't use reinterpret_cast gsl_DISABLE_MSVC_WARNINGS( 26410 26415 26418 26472 26439 26440 26473 26481 26482 26490 ) namespace gsl { // forward declare span<>: template< class T > class span; // C++11 emulation: namespace std11 { #if gsl_HAVE( ADD_CONST ) using std::add_const; #elif gsl_HAVE( TR1_ADD_CONST ) using std::tr1::add_const; #else template< class T > struct add_const { typedef const T type; }; #endif // gsl_HAVE( ADD_CONST ) #if gsl_HAVE( REMOVE_CONST ) using std::remove_cv; using std::remove_const; using std::remove_volatile; #elif gsl_HAVE( TR1_REMOVE_CONST ) using std::tr1::remove_cv; using std::tr1::remove_const; using std::tr1::remove_volatile; #else template< class T > struct remove_const { typedef T type; }; template< class T > struct remove_const { typedef T type; }; template< class T > struct remove_volatile { typedef T type; }; template< class T > struct remove_volatile { typedef T type; }; template< class T > struct remove_cv { typedef typename remove_volatile::type>::type type; }; #endif // gsl_HAVE( REMOVE_CONST ) #if gsl_HAVE( INTEGRAL_CONSTANT ) using std::integral_constant; using std::true_type; using std::false_type; #elif gsl_HAVE( TR1_INTEGRAL_CONSTANT ) using std::tr1::integral_constant; using std::tr1::true_type; using std::tr1::false_type; #else template< int v > struct integral_constant { enum { value = v }; }; typedef integral_constant< true > true_type; typedef integral_constant< false > false_type; #endif } // namespace std11 namespace detail { /// for nsel_REQUIRES_T /*enum*/ class enabler{}; #if gsl_HAVE( TYPE_TRAITS ) template< class Q > struct is_span_oracle : std11::false_type{}; template< class T> struct is_span_oracle< span > : std11::true_type{}; template< class Q > struct is_span : is_span_oracle< typename std11::remove_cv::type >{}; template< class Q > struct is_std_array_oracle : std11::false_type{}; #if gsl_HAVE( ARRAY ) template< class T, std::size_t Extent > struct is_std_array_oracle< std::array > : std11::true_type{}; #endif template< class Q > struct is_std_array : is_std_array_oracle< typename std11::remove_cv::type >{}; template< class Q > struct is_array : std11::false_type {}; template< class T > struct is_array : std11::true_type {}; template< class T, std::size_t N > struct is_array : std11::true_type {}; #endif // gsl_HAVE( TYPE_TRAITS ) } // namespace detail // // GSL.util: utilities // // index type for all container indexes/subscripts/sizes typedef gsl_CONFIG_SPAN_INDEX_TYPE index; // p0122r3 uses std::ptrdiff_t // // GSL.owner: ownership pointers // #if gsl_HAVE( SHARED_PTR ) using std::unique_ptr; using std::shared_ptr; using std::make_shared; # if gsl_HAVE( MAKE_UNIQUE ) using std::make_unique; # endif #endif #if gsl_HAVE( ALIAS_TEMPLATE ) # if gsl_HAVE( TYPE_TRAITS ) template< class T gsl_REQUIRES_T( std::is_pointer::value ) > using owner = T; # else template< class T > using owner = T; # endif #else template< class T > struct owner { typedef T type; }; #endif #define gsl_HAVE_OWNER_TEMPLATE gsl_HAVE_ALIAS_TEMPLATE #if gsl_FEATURE( OWNER_MACRO ) # if gsl_HAVE( OWNER_TEMPLATE ) # define Owner(t) ::gsl::owner # else # define Owner(t) ::gsl::owner::type # endif #endif // // GSL.assert: assertions // #define gsl_ELIDE_CONTRACT_EXPECTS ( 0 == ( gsl_CONFIG_CONTRACT_LEVEL_MASK & 0x01 ) ) #define gsl_ELIDE_CONTRACT_ENSURES ( 0 == ( gsl_CONFIG_CONTRACT_LEVEL_MASK & 0x10 ) ) #if gsl_ELIDE_CONTRACT_EXPECTS # define Expects( x ) /* Expects elided */ #elif gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) # define Expects( x ) ::gsl::fail_fast_assert( (x), "GSL: Precondition failure at " __FILE__ ":" gsl_STRINGIFY(__LINE__) ); #elif gsl_CONFIG( CONTRACT_VIOLATION_CALLS_HANDLER_V ) # define Expects( x ) ::gsl::fail_fast_assert( (x), #x, "GSL: Precondition failure", __FILE__, __LINE__ ); #else # define Expects( x ) ::gsl::fail_fast_assert( (x) ) #endif #if gsl_ELIDE_CONTRACT_EXPECTS # define gsl_EXPECTS_UNUSED_PARAM( x ) /* Make param unnamed if Expects elided */ #else # define gsl_EXPECTS_UNUSED_PARAM( x ) x #endif #if gsl_ELIDE_CONTRACT_ENSURES # define Ensures( x ) /* Ensures elided */ #elif gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) # define Ensures( x ) ::gsl::fail_fast_assert( (x), "GSL: Postcondition failure at " __FILE__ ":" gsl_STRINGIFY(__LINE__) ); #elif gsl_CONFIG( CONTRACT_VIOLATION_CALLS_HANDLER_V ) # define Ensures( x ) ::gsl::fail_fast_assert( (x), #x, "GSL: Postcondition failure", __FILE__, __LINE__ ); #else # define Ensures( x ) ::gsl::fail_fast_assert( (x) ) #endif #define gsl_STRINGIFY( x ) gsl_STRINGIFY_( x ) #define gsl_STRINGIFY_( x ) #x struct fail_fast : public std::logic_error { gsl_api explicit fail_fast( char const * const message ) : std::logic_error( message ) {} }; // workaround for gcc 5 throw/terminate constexpr bug: #if gsl_BETWEEN( gsl_COMPILER_GNUC_VERSION, 430, 600 ) && gsl_HAVE( CONSTEXPR_14 ) # if gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) gsl_api inline gsl_constexpr14 auto fail_fast_assert( bool cond, char const * const message ) -> void { !cond ? throw fail_fast( message ) : 0; } # elif gsl_CONFIG( CONTRACT_VIOLATION_CALLS_HANDLER_V ) // Should be defined by user gsl_api gsl_constexpr14 auto fail_fast_assert_handler(char const * const expression, char const * const message, char const * const file, int line) -> void; gsl_api inline gsl_constexpr14 auto fail_fast_assert( bool cond, char const * const expression, char const * const message, char const * const file, int line ) -> void { struct F { static gsl_constexpr14 void f() {}; }; !cond ? fail_fast_assert_handler( expression, message, file, line ) : F::f(); } # else gsl_api inline gsl_constexpr14 auto fail_fast_assert( bool cond ) -> void { struct F { static gsl_constexpr14 void f(){}; }; !cond ? std::terminate() : F::f(); } # endif #else // workaround # if gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) gsl_api inline gsl_constexpr14 void fail_fast_assert( bool cond, char const * const message ) { if ( !cond ) throw fail_fast( message ); } # elif gsl_CONFIG( CONTRACT_VIOLATION_CALLS_HANDLER_V ) // Should be defined by user gsl_api gsl_constexpr14 void fail_fast_assert_handler( char const * const expression, char const * const message, char const * const file, int line ); gsl_api inline gsl_constexpr14 void fail_fast_assert( bool cond, char const * const expression, char const * const message, char const * const file, int line ) { if ( !cond ) fail_fast_assert_handler( expression, message, file, line ); } # else gsl_api inline gsl_constexpr14 void fail_fast_assert( bool cond ) gsl_noexcept { if ( !cond ) std::terminate(); } # endif #endif // workaround // // GSL.util: utilities // #if gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD ) // Add uncaught_exceptions for pre-2017 MSVC, GCC and Clang // Return unsigned char to save stack space, uncaught_exceptions can only increase by 1 in a scope namespace detail { inline unsigned char to_uchar( unsigned x ) gsl_noexcept { return static_cast( x ); } } // namespace detail namespace std11 { #if gsl_HAVE( UNCAUGHT_EXCEPTIONS ) inline unsigned char uncaught_exceptions() gsl_noexcept { return detail::to_uchar( std::uncaught_exceptions() ); } #elif gsl_COMPILER_MSVC_VERSION extern "C" char * __cdecl _getptd(); inline unsigned char uncaught_exceptions() gsl_noexcept { return detail::to_uchar( *reinterpret_cast(_getptd() + (sizeof(void*) == 8 ? 0x100 : 0x90) ) ); } #elif gsl_COMPILER_CLANG_VERSION || gsl_COMPILER_GNUC_VERSION extern "C" char * __cxa_get_globals(); inline unsigned char uncaught_exceptions() gsl_noexcept { return detail::to_uchar( *reinterpret_cast(__cxa_get_globals() + sizeof(void*) ) ); } #endif } // namespace std11 #endif #if gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 110 template< class F > class final_action { public: gsl_api explicit final_action( F action ) gsl_noexcept : action_( std::move( action ) ) , invoke_( true ) {} gsl_api final_action( final_action && other ) gsl_noexcept : action_( std::move( other.action_ ) ) , invoke_( other.invoke_ ) { other.invoke_ = false; } gsl_api virtual ~final_action() gsl_noexcept { if ( invoke_ ) action_(); } gsl_is_delete_access: gsl_api final_action( final_action const & ) gsl_is_delete; gsl_api final_action & operator=( final_action const & ) gsl_is_delete; gsl_api final_action & operator=( final_action && ) gsl_is_delete; protected: gsl_api void dismiss() gsl_noexcept { invoke_ = false; } private: F action_; bool invoke_; }; template< class F > gsl_api inline final_action finally( F const & action ) gsl_noexcept { return final_action( action ); } template< class F > gsl_api inline final_action finally( F && action ) gsl_noexcept { return final_action( std::forward( action ) ); } #if gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD ) template< class F > class final_action_return : public final_action { public: gsl_api explicit final_action_return( F && action ) gsl_noexcept : final_action( std::move( action ) ) , exception_count( std11::uncaught_exceptions() ) {} gsl_api final_action_return( final_action_return && other ) gsl_noexcept : final_action( std::move( other ) ) , exception_count( std11::uncaught_exceptions() ) {} gsl_api ~final_action_return() override { if ( std11::uncaught_exceptions() != exception_count ) this->dismiss(); } gsl_is_delete_access: gsl_api final_action_return( final_action_return const & ) gsl_is_delete; gsl_api final_action_return & operator=( final_action_return const & ) gsl_is_delete; private: unsigned char exception_count; }; template< class F > gsl_api inline final_action_return on_return( F const & action ) gsl_noexcept { return final_action_return( action ); } template< class F > gsl_api inline final_action_return on_return( F && action ) gsl_noexcept { return final_action_return( std::forward( action ) ); } template< class F > class final_action_error : public final_action { public: gsl_api explicit final_action_error( F && action ) gsl_noexcept : final_action( std::move( action ) ) , exception_count( std11::uncaught_exceptions() ) {} gsl_api final_action_error( final_action_error && other ) gsl_noexcept : final_action( std::move( other ) ) , exception_count( std11::uncaught_exceptions() ) {} gsl_api ~final_action_error() override { if ( std11::uncaught_exceptions() == exception_count ) this->dismiss(); } gsl_is_delete_access: gsl_api final_action_error( final_action_error const & ) gsl_is_delete; gsl_api final_action_error & operator=( final_action_error const & ) gsl_is_delete; private: unsigned char exception_count; }; template< class F > gsl_api inline final_action_error on_error( F const & action ) gsl_noexcept { return final_action_error( action ); } template< class F > gsl_api inline final_action_error on_error( F && action ) gsl_noexcept { return final_action_error( std::forward( action ) ); } #endif // gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD ) #else // gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 110 class final_action { public: typedef void (*Action)(); gsl_api final_action( Action action ) : action_( action ) , invoke_( true ) {} gsl_api final_action( final_action const & other ) : action_( other.action_ ) , invoke_( other.invoke_ ) { other.invoke_ = false; } gsl_api virtual ~final_action() { if ( invoke_ ) action_(); } protected: gsl_api void dismiss() { invoke_ = false; } private: gsl_api final_action & operator=( final_action const & ); private: Action action_; mutable bool invoke_; }; template< class F > gsl_api inline final_action finally( F const & f ) { return final_action(( f )); } #if gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD ) class final_action_return : public final_action { public: gsl_api explicit final_action_return( Action action ) : final_action( action ) , exception_count( std11::uncaught_exceptions() ) {} gsl_api ~final_action_return() { if ( std11::uncaught_exceptions() != exception_count ) this->dismiss(); } private: gsl_api final_action_return & operator=( final_action_return const & ); private: unsigned char exception_count; }; template< class F > gsl_api inline final_action_return on_return( F const & action ) { return final_action_return( action ); } class final_action_error : public final_action { public: gsl_api explicit final_action_error( Action action ) : final_action( action ) , exception_count( std11::uncaught_exceptions() ) {} gsl_api ~final_action_error() { if ( std11::uncaught_exceptions() == exception_count ) this->dismiss(); } private: gsl_api final_action_error & operator=( final_action_error const & ); private: unsigned char exception_count; }; template< class F > gsl_api inline final_action_error on_error( F const & action ) { return final_action_error( action ); } #endif // gsl_FEATURE( EXPERIMENTAL_RETURN_GUARD ) #endif // gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION == 110 #if gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 120 template< class T, class U > gsl_api inline gsl_constexpr T narrow_cast( U && u ) gsl_noexcept { return static_cast( std::forward( u ) ); } #else template< class T, class U > gsl_api inline T narrow_cast( U u ) gsl_noexcept { return static_cast( u ); } #endif // gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 120 struct narrowing_error : public std::exception {}; #if gsl_HAVE( TYPE_TRAITS ) namespace detail { template< class T, class U > struct is_same_signedness : public std::integral_constant::value == std::is_signed::value> {}; } #endif template< class T, class U > gsl_api inline T narrow( U u ) { T t = narrow_cast( u ); if ( static_cast( t ) != u ) { #if gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) throw narrowing_error(); #else std::terminate(); #endif } #if gsl_HAVE( TYPE_TRAITS ) # if gsl_COMPILER_MSVC_VERSION // Suppress MSVC level 4 warning C4127 (conditional expression is constant) if ( 0, ! detail::is_same_signedness::value && ( ( t < T() ) != ( u < U() ) ) ) # else if ( ! detail::is_same_signedness::value && ( ( t < T() ) != ( u < U() ) ) ) # endif #else // Don't assume T() works: if ( ( t < 0 ) != ( u < 0 ) ) #endif { #if gsl_CONFIG( CONTRACT_VIOLATION_THROWS_V ) throw narrowing_error(); #else std::terminate(); #endif } return t; } // // at() - Bounds-checked way of accessing static arrays, std::array, std::vector. // template< class T, size_t N > gsl_api inline gsl_constexpr14 T & at( T(&arr)[N], size_t pos ) { Expects( pos < N ); return arr[pos]; } #if gsl_HAVE( ARRAY ) template< class T, size_t N > gsl_api inline gsl_constexpr14 T & at( std::array & arr, size_t pos ) { Expects( pos < N ); return arr[pos]; } #endif template< class Container > gsl_api inline gsl_constexpr14 typename Container::value_type & at( Container & cont, size_t pos ) { Expects( pos < cont.size() ); return cont[pos]; } #if gsl_HAVE( INITIALIZER_LIST ) template< class T > gsl_api inline const gsl_constexpr14 T & at( std::initializer_list cont, size_t pos ) { Expects( pos < cont.size() ); return *( cont.begin() + pos ); } #endif template< class T > gsl_api inline gsl_constexpr T & at( span s, size_t pos ) { return s.at( pos ); } // // GSL.views: views // // // not_null<> - Wrap any indirection and enforce non-null. // template< class T > class not_null { #if gsl_CONFIG( NOT_NULL_EXPLICIT_CTOR ) # define gsl_not_null_explicit explicit #else # define gsl_not_null_explicit /*explicit*/ #endif #if gsl_CONFIG( NOT_NULL_GET_BY_CONST_REF ) typedef T const & get_result_t; #else typedef T get_result_t; #endif public: #if gsl_HAVE( TYPE_TRAITS ) static_assert( std::is_assignable::value, "T cannot be assigned nullptr." ); #endif template< class U #if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) gsl_REQUIRES_T(( std::is_constructible::value )) #endif > gsl_api gsl_constexpr14 gsl_not_null_explicit #if gsl_HAVE( RVALUE_REFERENCE ) not_null( U && u ) : ptr_( std::forward( u ) ) #else not_null( U const & u ) : ptr_( u ) #endif { Expects( ptr_ != gsl_nullptr ); } #undef gsl_not_null_explicit #if gsl_HAVE( IS_DEFAULT ) gsl_api ~not_null() = default; gsl_api gsl_constexpr not_null( not_null && other ) = default; gsl_api gsl_constexpr not_null( not_null const & other ) = default; gsl_api not_null & operator=( not_null && other ) = default; gsl_api not_null & operator=( not_null const & other ) = default; #else gsl_api ~not_null() {}; gsl_api gsl_constexpr not_null( not_null const & other ) : ptr_ ( other.ptr_ ) {} gsl_api not_null & operator=( not_null const & other ) { ptr_ = other.ptr_; return *this; } # if gsl_HAVE( RVALUE_REFERENCE ) gsl_api gsl_constexpr not_null( not_null && other ) : ptr_( std::move( other.get() ) ) {} gsl_api not_null & operator=( not_null && other ) { ptr_ = std::move( other.get() ); return *this; } # endif #endif template< class U #if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) gsl_REQUIRES_T(( std::is_convertible::value )) #endif > gsl_api gsl_constexpr not_null( not_null const & other ) : ptr_( other.get() ) {} gsl_api gsl_constexpr14 get_result_t get() const { // Without cheating and changing ptr_ from the outside, this check is superfluous: Ensures( ptr_ != gsl_nullptr ); return ptr_; } gsl_api gsl_constexpr operator get_result_t () const { return get(); } gsl_api gsl_constexpr get_result_t operator->() const { return get(); } #if gsl_HAVE( DECLTYPE_AUTO ) gsl_api gsl_constexpr decltype(auto) operator*() const { return *get(); } #endif gsl_is_delete_access: // prevent compilation when initialized with a nullptr or literal 0: #if gsl_HAVE( NULLPTR ) gsl_api not_null( std::nullptr_t ) gsl_is_delete; gsl_api not_null & operator=( std::nullptr_t ) gsl_is_delete; #else gsl_api not_null( int ) gsl_is_delete; gsl_api not_null & operator=( int ) gsl_is_delete; #endif // unwanted operators...pointers only point to single objects! gsl_api not_null & operator++() gsl_is_delete; gsl_api not_null & operator--() gsl_is_delete; gsl_api not_null operator++( int ) gsl_is_delete; gsl_api not_null operator--( int ) gsl_is_delete; gsl_api not_null & operator+ ( size_t ) gsl_is_delete; gsl_api not_null & operator+=( size_t ) gsl_is_delete; gsl_api not_null & operator- ( size_t ) gsl_is_delete; gsl_api not_null & operator-=( size_t ) gsl_is_delete; gsl_api not_null & operator+=( std::ptrdiff_t ) gsl_is_delete; gsl_api not_null & operator-=( std::ptrdiff_t ) gsl_is_delete; gsl_api void operator[]( std::ptrdiff_t ) const gsl_is_delete; private: T ptr_; }; // not_null with implicit constructor, allowing copy-initialization: template< class T > class not_null_ic : public not_null { public: template< class U #if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) gsl_REQUIRES_T(( std::is_constructible::value )) #endif > gsl_api gsl_constexpr14 #if gsl_HAVE( RVALUE_REFERENCE ) not_null_ic( U && u ) : not_null( std::forward( u ) ) #else not_null_ic( U const & u ) : not_null( u ) #endif {} }; // more not_null unwanted operators template< class T, class U > std::ptrdiff_t operator-( not_null const &, not_null const & ) gsl_is_delete; template< class T > not_null operator-( not_null const &, std::ptrdiff_t ) gsl_is_delete; template< class T > not_null operator+( not_null const &, std::ptrdiff_t ) gsl_is_delete; template< class T > not_null operator+( std::ptrdiff_t, not_null const & ) gsl_is_delete; // not_null comparisons template< class T, class U > gsl_api inline gsl_constexpr bool operator==( not_null const & l, not_null const & r ) { return l.get() == r.get(); } template< class T, class U > gsl_api inline gsl_constexpr bool operator< ( not_null const & l, not_null const & r ) { return l.get() < r.get(); } template< class T, class U > gsl_api inline gsl_constexpr bool operator!=( not_null const & l, not_null const & r ) { return !( l == r ); } template< class T, class U > gsl_api inline gsl_constexpr bool operator<=( not_null const & l, not_null const & r ) { return !( r < l ); } template< class T, class U > gsl_api inline gsl_constexpr bool operator> ( not_null const & l, not_null const & r ) { return ( r < l ); } template< class T, class U > gsl_api inline gsl_constexpr bool operator>=( not_null const & l, not_null const & r ) { return !( l < r ); } // // Byte-specific type. // #if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) enum class gsl_may_alias byte : unsigned char {}; #else struct gsl_may_alias byte { typedef unsigned char type; type v; }; #endif #if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) # define gsl_ENABLE_IF_INTEGRAL_T(T) \ gsl_REQUIRES_T(( std::is_integral::value )) #else # define gsl_ENABLE_IF_INTEGRAL_T(T) #endif template< class T > gsl_api inline gsl_constexpr byte to_byte( T v ) gsl_noexcept { #if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) return static_cast( v ); #elif gsl_HAVE( CONSTEXPR_11 ) return { static_cast( v ) }; #else byte b = { static_cast( v ) }; return b; #endif } template< class IntegerType gsl_ENABLE_IF_INTEGRAL_T( IntegerType ) > gsl_api inline gsl_constexpr IntegerType to_integer( byte b ) gsl_noexcept { #if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) return static_cast::type>( b ); #else return b.v; #endif } gsl_api inline gsl_constexpr unsigned char to_uchar( byte b ) gsl_noexcept { return to_integer( b ); } gsl_api inline gsl_constexpr unsigned char to_uchar( int i ) gsl_noexcept { return static_cast( i ); } #if ! gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) gsl_api inline gsl_constexpr bool operator==( byte l, byte r ) gsl_noexcept { return l.v == r.v; } gsl_api inline gsl_constexpr bool operator!=( byte l, byte r ) gsl_noexcept { return !( l == r ); } gsl_api inline gsl_constexpr bool operator< ( byte l, byte r ) gsl_noexcept { return l.v < r.v; } gsl_api inline gsl_constexpr bool operator<=( byte l, byte r ) gsl_noexcept { return !( r < l ); } gsl_api inline gsl_constexpr bool operator> ( byte l, byte r ) gsl_noexcept { return ( r < l ); } gsl_api inline gsl_constexpr bool operator>=( byte l, byte r ) gsl_noexcept { return !( l < r ); } #endif template< class IntegerType gsl_ENABLE_IF_INTEGRAL_T( IntegerType ) > gsl_api inline gsl_constexpr14 byte & operator<<=( byte & b, IntegerType shift ) gsl_noexcept { #if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) return b = to_byte( to_uchar( b ) << shift ); #else b.v = to_uchar( b.v << shift ); return b; #endif } template< class IntegerType gsl_ENABLE_IF_INTEGRAL_T( IntegerType ) > gsl_api inline gsl_constexpr byte operator<<( byte b, IntegerType shift ) gsl_noexcept { return to_byte( to_uchar( b ) << shift ); } template< class IntegerType gsl_ENABLE_IF_INTEGRAL_T( IntegerType ) > gsl_api inline gsl_constexpr14 byte & operator>>=( byte & b, IntegerType shift ) gsl_noexcept { #if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) return b = to_byte( to_uchar( b ) >> shift ); #else b.v = to_uchar( b.v >> shift ); return b; #endif } template< class IntegerType gsl_ENABLE_IF_INTEGRAL_T( IntegerType ) > gsl_api inline gsl_constexpr byte operator>>( byte b, IntegerType shift ) gsl_noexcept { return to_byte( to_uchar( b ) >> shift ); } gsl_api inline gsl_constexpr14 byte & operator|=( byte & l, byte r ) gsl_noexcept { #if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) return l = to_byte( to_uchar( l ) | to_uchar( r ) ); #else l.v = to_uchar( l ) | to_uchar( r ); return l; #endif } gsl_api inline gsl_constexpr byte operator|( byte l, byte r ) gsl_noexcept { return to_byte( to_uchar( l ) | to_uchar( r ) ); } gsl_api inline gsl_constexpr14 byte & operator&=( byte & l, byte r ) gsl_noexcept { #if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) return l = to_byte( to_uchar( l ) & to_uchar( r ) ); #else l.v = to_uchar( l ) & to_uchar( r ); return l; #endif } gsl_api inline gsl_constexpr byte operator&( byte l, byte r ) gsl_noexcept { return to_byte( to_uchar( l ) & to_uchar( r ) ); } gsl_api inline gsl_constexpr14 byte & operator^=( byte & l, byte r ) gsl_noexcept { #if gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) return l = to_byte( to_uchar( l ) ^ to_uchar (r ) ); #else l.v = to_uchar( l ) ^ to_uchar (r ); return l; #endif } gsl_api inline gsl_constexpr byte operator^( byte l, byte r ) gsl_noexcept { return to_byte( to_uchar( l ) ^ to_uchar( r ) ); } gsl_api inline gsl_constexpr byte operator~( byte b ) gsl_noexcept { return to_byte( ~to_uchar( b ) ); } #if gsl_FEATURE_TO_STD( WITH_CONTAINER ) // Tag to select span constructor taking a container (prevent ms-gsl warning C26426): struct with_container_t { gsl_constexpr with_container_t() gsl_noexcept {} }; const gsl_constexpr with_container_t with_container; #endif #if gsl_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) namespace detail { // Can construct from containers that: template< class Container, class ElementType gsl_REQUIRES_T(( ! detail::is_span< Container >::value && ! detail::is_array< Container >::value && ! detail::is_std_array< Container >::value && std::is_convertible().data())>::type(*)[], ElementType(*)[] >::value )) #if gsl_HAVE( STD_DATA ) // data(cont) and size(cont) well-formed: , class = decltype( std::data( std::declval() ) ) , class = decltype( std::size( std::declval() ) ) #endif > struct can_construct_span_from : std11::true_type{}; } // namespace detail #endif // // span<> - A 1D view of contiguous T's, replace (*,len). // template< class T > class span { template< class U > friend class span; public: typedef index index_type; typedef T element_type; typedef typename std11::remove_cv< T >::type value_type; typedef T & reference; typedef T * pointer; typedef T const * const_pointer; typedef T const & const_reference; typedef pointer iterator; typedef const_pointer const_iterator; typedef std::reverse_iterator< iterator > reverse_iterator; typedef std::reverse_iterator< const_iterator > const_reverse_iterator; typedef typename std::iterator_traits< iterator >::difference_type difference_type; // 26.7.3.2 Constructors, copy, and assignment [span.cons] gsl_api gsl_constexpr14 span() gsl_noexcept : first_( gsl_nullptr ) , last_ ( gsl_nullptr ) { Expects( size() == 0 ); } #if ! gsl_DEPRECATE_TO_LEVEL( 5 ) #if gsl_HAVE( NULLPTR ) gsl_api gsl_constexpr14 span( std::nullptr_t, index_type gsl_EXPECTS_UNUSED_PARAM( size_in ) ) : first_( nullptr ) , last_ ( nullptr ) { Expects( size_in == 0 ); } #endif #if gsl_HAVE( IS_DELETE ) gsl_api gsl_constexpr span( reference data_in ) : span( &data_in, 1 ) {} gsl_api gsl_constexpr span( element_type && ) = delete; #endif #endif // deprecate gsl_api gsl_constexpr14 span( pointer data_in, index_type size_in ) : first_( data_in ) , last_ ( data_in + size_in ) { Expects( size_in == 0 || ( size_in > 0 && data_in != gsl_nullptr ) ); } gsl_api gsl_constexpr14 span( pointer first_in, pointer last_in ) : first_( first_in ) , last_ ( last_in ) { Expects( first_in <= last_in ); } #if ! gsl_DEPRECATE_TO_LEVEL( 5 ) template< class U > gsl_api gsl_constexpr14 span( U * & data_in, index_type size_in ) : first_( data_in ) , last_ ( data_in + size_in ) { Expects( size_in == 0 || ( size_in > 0 && data_in != gsl_nullptr ) ); } template< class U > gsl_api gsl_constexpr14 span( U * const & data_in, index_type size_in ) : first_( data_in ) , last_ ( data_in + size_in ) { Expects( size_in == 0 || ( size_in > 0 && data_in != gsl_nullptr ) ); } #endif // deprecate #if ! gsl_DEPRECATE_TO_LEVEL( 5 ) template< class U, size_t N > gsl_api gsl_constexpr span( U (&arr)[N] ) gsl_noexcept : first_( gsl_ADDRESSOF( arr[0] ) ) , last_ ( gsl_ADDRESSOF( arr[0] ) + N ) {} #else template< size_t N # if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) gsl_REQUIRES_T(( std::is_convertible::value )) # endif > gsl_api gsl_constexpr span( element_type (&arr)[N] ) gsl_noexcept : first_( gsl_ADDRESSOF( arr[0] ) ) , last_ ( gsl_ADDRESSOF( arr[0] ) + N ) {} #endif // deprecate #if gsl_HAVE( ARRAY ) #if ! gsl_DEPRECATE_TO_LEVEL( 5 ) template< class U, size_t N > gsl_api gsl_constexpr span( std::array< U, N > & arr ) : first_( arr.data() ) , last_ ( arr.data() + N ) {} template< class U, size_t N > gsl_api gsl_constexpr span( std::array< U, N > const & arr ) : first_( arr.data() ) , last_ ( arr.data() + N ) {} #else template< size_t N # if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) gsl_REQUIRES_T(( std::is_convertible::value )) # endif > gsl_api gsl_constexpr span( std::array< value_type, N > & arr ) : first_( arr.data() ) , last_ ( arr.data() + N ) {} template< size_t N # if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) gsl_REQUIRES_T(( std::is_convertible::value )) # endif > gsl_api gsl_constexpr span( std::array< value_type, N > const & arr ) : first_( arr.data() ) , last_ ( arr.data() + N ) {} #endif // deprecate #endif // gsl_HAVE( ARRAY ) #if gsl_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) template< class Container gsl_REQUIRES_T(( detail::can_construct_span_from< Container, element_type >::value )) > gsl_api gsl_constexpr span( Container & cont ) : first_( cont.data() ) , last_ ( cont.data() + cont.size() ) {} template< class Container gsl_REQUIRES_T(( std::is_const< element_type >::value && detail::can_construct_span_from< Container, element_type >::value )) > gsl_api gsl_constexpr span( Container const & cont ) : first_( cont.data() ) , last_ ( cont.data() + cont.size() ) {} #elif gsl_HAVE( UNCONSTRAINED_SPAN_CONTAINER_CTOR ) template< class Container > gsl_api gsl_constexpr span( Container & cont ) : first_( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) ) , last_ ( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) + cont.size() ) {} template< class Container > gsl_api gsl_constexpr span( Container const & cont ) : first_( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) ) , last_ ( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) + cont.size() ) {} #endif #if gsl_FEATURE_TO_STD( WITH_CONTAINER ) template< class Container > gsl_api gsl_constexpr span( with_container_t, Container & cont ) : first_( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) ) , last_ ( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) + cont.size() ) {} template< class Container > gsl_api gsl_constexpr span( with_container_t, Container const & cont ) : first_( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) ) , last_ ( cont.size() == 0 ? gsl_nullptr : gsl_ADDRESSOF( cont[0] ) + cont.size() ) {} #endif #if ! gsl_DEPRECATE_TO_LEVEL( 4 ) // constructor taking shared_ptr deprecated since 0.29.0 #if gsl_HAVE( SHARED_PTR ) gsl_api gsl_constexpr span( shared_ptr const & ptr ) : first_( ptr.get() ) , last_ ( ptr.get() ? ptr.get() + 1 : gsl_nullptr ) {} #endif // constructors taking unique_ptr deprecated since 0.29.0 #if gsl_HAVE( UNIQUE_PTR ) # if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) template< class ArrayElementType = typename std::add_pointer::type > # else template< class ArrayElementType > # endif gsl_api gsl_constexpr span( unique_ptr const & ptr, index_type count ) : first_( ptr.get() ) , last_ ( ptr.get() + count ) {} gsl_api gsl_constexpr span( unique_ptr const & ptr ) : first_( ptr.get() ) , last_ ( ptr.get() ? ptr.get() + 1 : gsl_nullptr ) {} #endif #endif // deprecate shared_ptr, unique_ptr #if gsl_HAVE( IS_DEFAULT ) && ! gsl_BETWEEN( gsl_COMPILER_GNUC_VERSION, 430, 600) gsl_api gsl_constexpr span( span && ) gsl_noexcept = default; gsl_api gsl_constexpr span( span const & ) = default; #else gsl_api gsl_constexpr span( span const & other ) : first_( other.begin() ) , last_ ( other.end() ) {} #endif #if gsl_HAVE( IS_DEFAULT ) ~span() = default; #else ~span() {} #endif #if gsl_HAVE( IS_DEFAULT ) gsl_api gsl_constexpr14 span & operator=( span && ) gsl_noexcept = default; gsl_api gsl_constexpr14 span & operator=( span const & ) gsl_noexcept = default; #else gsl_api span & operator=( span other ) gsl_noexcept { other.swap( *this ); return *this; } #endif template< class U #if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) gsl_REQUIRES_T(( std::is_convertible::value )) #endif > gsl_api gsl_constexpr span( span const & other ) : first_( other.begin() ) , last_ ( other.end() ) {} #if 0 // Converting from other span ? template< class U > operator=(); #endif // 26.7.3.3 Subviews [span.sub] gsl_api gsl_constexpr14 span first( index_type count ) const gsl_noexcept { Expects( 0 <= count && count <= this->size() ); return span( this->data(), count ); } gsl_api gsl_constexpr14 span last( index_type count ) const gsl_noexcept { Expects( 0 <= count && count <= this->size() ); return span( this->data() + this->size() - count, count ); } gsl_api gsl_constexpr14 span subspan( index_type offset ) const gsl_noexcept { Expects( 0 <= offset && offset <= this->size() ); return span( this->data() + offset, this->size() - offset ); } gsl_api gsl_constexpr14 span subspan( index_type offset, index_type count ) const gsl_noexcept { Expects( 0 <= offset && offset <= this->size() && 0 <= count && count <= this->size() - offset ); return span( this->data() + offset, count ); } // 26.7.3.4 Observers [span.obs] gsl_api gsl_constexpr index_type size() const gsl_noexcept { return narrow_cast( last_ - first_ ); } gsl_api gsl_constexpr std::ptrdiff_t ssize() const gsl_noexcept { return narrow_cast( last_ - first_ ); } gsl_api gsl_constexpr index_type size_bytes() const gsl_noexcept { return size() * narrow_cast( sizeof( element_type ) ); } gsl_api gsl_constexpr bool empty() const gsl_noexcept { return size() == 0; } // 26.7.3.5 Element access [span.elem] gsl_api gsl_constexpr reference operator[]( index_type pos ) const { return at( pos ); } gsl_api gsl_constexpr reference operator()( index_type pos ) const { return at( pos ); } gsl_api gsl_constexpr14 reference at( index_type pos ) const { Expects( pos < size() ); return first_[ pos ]; } gsl_api gsl_constexpr pointer data() const gsl_noexcept { return first_; } // 26.7.3.6 Iterator support [span.iterators] gsl_api gsl_constexpr iterator begin() const gsl_noexcept { return iterator( first_ ); } gsl_api gsl_constexpr iterator end() const gsl_noexcept { return iterator( last_ ); } gsl_api gsl_constexpr const_iterator cbegin() const gsl_noexcept { #if gsl_CPP11_OR_GREATER return { begin() }; #else return const_iterator( begin() ); #endif } gsl_api gsl_constexpr const_iterator cend() const gsl_noexcept { #if gsl_CPP11_OR_GREATER return { end() }; #else return const_iterator( end() ); #endif } gsl_api gsl_constexpr reverse_iterator rbegin() const gsl_noexcept { return reverse_iterator( end() ); } gsl_api gsl_constexpr reverse_iterator rend() const gsl_noexcept { return reverse_iterator( begin() ); } gsl_api gsl_constexpr const_reverse_iterator crbegin() const gsl_noexcept { return const_reverse_iterator( cend() ); } gsl_api gsl_constexpr const_reverse_iterator crend() const gsl_noexcept { return const_reverse_iterator( cbegin() ); } gsl_api void swap( span & other ) gsl_noexcept { using std::swap; swap( first_, other.first_ ); swap( last_ , other.last_ ); } #if ! gsl_DEPRECATE_TO_LEVEL( 3 ) // member length() deprecated since 0.29.0 gsl_api gsl_constexpr index_type length() const gsl_noexcept { return size(); } // member length_bytes() deprecated since 0.29.0 gsl_api gsl_constexpr index_type length_bytes() const gsl_noexcept { return size_bytes(); } #endif #if ! gsl_DEPRECATE_TO_LEVEL( 2 ) // member as_bytes(), as_writeable_bytes deprecated since 0.17.0 gsl_api span< const byte > as_bytes() const gsl_noexcept { return span< const byte >( reinterpret_cast( data() ), size_bytes() ); // NOLINT } gsl_api span< byte > as_writeable_bytes() const gsl_noexcept { return span< byte >( reinterpret_cast( data() ), size_bytes() ); // NOLINT } #endif template< class U > gsl_api span< U > as_span() const gsl_noexcept { Expects( ( this->size_bytes() % sizeof(U) ) == 0 ); return span< U >( reinterpret_cast( this->data() ), this->size_bytes() / sizeof( U ) ); // NOLINT } private: pointer first_; pointer last_; }; // class template argument deduction guides: #if gsl_HAVE( DEDUCTION_GUIDES ) // gsl_CPP17_OR_GREATER template< class T, size_t N > span( T (&)[N] ) -> span; template< class T, size_t N > span( std::array & ) -> span; template< class T, size_t N > span( std::array const & ) -> span; template< class Container > span( Container& ) -> span; template< class Container > span( Container const & ) -> span; #endif // gsl_HAVE( DEDUCTION_GUIDES ) // 26.7.3.7 Comparison operators [span.comparison] #if gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) template< class T, class U > gsl_api inline gsl_constexpr bool operator==( span const & l, span const & r ) { return l.size() == r.size() && (l.begin() == r.begin() || std::equal( l.begin(), l.end(), r.begin() ) ); } template< class T, class U > gsl_api inline gsl_constexpr bool operator< ( span const & l, span const & r ) { return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); } #else template< class T > gsl_api inline gsl_constexpr bool operator==( span const & l, span const & r ) { return l.size() == r.size() && (l.begin() == r.begin() || std::equal( l.begin(), l.end(), r.begin() ) ); } template< class T > gsl_api inline gsl_constexpr bool operator< ( span const & l, span const & r ) { return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); } #endif template< class T, class U > gsl_api inline gsl_constexpr bool operator!=( span const & l, span const & r ) { return !( l == r ); } template< class T, class U > gsl_api inline gsl_constexpr bool operator<=( span const & l, span const & r ) { return !( r < l ); } template< class T, class U > gsl_api inline gsl_constexpr bool operator> ( span const & l, span const & r ) { return ( r < l ); } template< class T, class U > gsl_api inline gsl_constexpr bool operator>=( span const & l, span const & r ) { return !( l < r ); } // span algorithms template< class T > gsl_api inline gsl_constexpr std::size_t size( span const & spn ) { return static_cast( spn.size() ); } template< class T > gsl_api inline gsl_constexpr std::ptrdiff_t ssize( span const & spn ) { return spn.ssize(); } namespace detail { template< class II, class N, class OI > gsl_api inline OI copy_n( II first, N count, OI result ) { if ( count > 0 ) { *result++ = *first; for ( N i = 1; i < count; ++i ) { *result++ = *++first; } } return result; } } template< class T, class U > gsl_api inline void copy( span src, span dest ) { #if gsl_CPP14_OR_GREATER // gsl_HAVE( TYPE_TRAITS ) (circumvent Travis clang 3.4) static_assert( std::is_assignable::value, "Cannot assign elements of source span to elements of destination span" ); #endif Expects( dest.size() >= src.size() ); detail::copy_n( src.data(), src.size(), dest.data() ); } // span creator functions (see ctors) template< class T > gsl_api inline span< const byte > as_bytes( span spn ) gsl_noexcept { return span< const byte >( reinterpret_cast( spn.data() ), spn.size_bytes() ); // NOLINT } template< class T> gsl_api inline span< byte > as_writeable_bytes( span spn ) gsl_noexcept { return span< byte >( reinterpret_cast( spn.data() ), spn.size_bytes() ); // NOLINT } #if gsl_FEATURE_TO_STD( MAKE_SPAN ) template< class T > gsl_api inline gsl_constexpr span make_span( T * ptr, typename span::index_type count ) { return span( ptr, count ); } template< class T > gsl_api inline gsl_constexpr span make_span( T * first, T * last ) { return span( first, last ); } template< class T, size_t N > gsl_api inline gsl_constexpr span make_span( T (&arr)[N] ) { return span( gsl_ADDRESSOF( arr[0] ), N ); } #if gsl_HAVE( ARRAY ) template< class T, size_t N > gsl_api inline gsl_constexpr span make_span( std::array & arr ) { return span( arr ); } template< class T, size_t N > gsl_api inline gsl_constexpr span make_span( std::array const & arr ) { return span( arr ); } #endif #if gsl_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) && gsl_HAVE( AUTO ) template< class Container, class = decltype(std::declval().data()) > gsl_api inline gsl_constexpr auto make_span( Container & cont ) -> span< typename Container::value_type > { return span< typename Container::value_type >( cont ); } template< class Container, class = decltype(std::declval().data()) > gsl_api inline gsl_constexpr auto make_span( Container const & cont ) -> span< const typename Container::value_type > { return span< const typename Container::value_type >( cont ); } #else template< class T > gsl_api inline span make_span( std::vector & cont ) { return span( with_container, cont ); } template< class T > gsl_api inline span make_span( std::vector const & cont ) { return span( with_container, cont ); } #endif #if gsl_FEATURE_TO_STD( WITH_CONTAINER ) template< class Container > gsl_api inline gsl_constexpr span make_span( with_container_t, Container & cont ) gsl_noexcept { return span< typename Container::value_type >( with_container, cont ); } template< class Container > gsl_api inline gsl_constexpr span make_span( with_container_t, Container const & cont ) gsl_noexcept { return span< const typename Container::value_type >( with_container, cont ); } #endif // gsl_FEATURE_TO_STD( WITH_CONTAINER ) template< class Ptr > gsl_api inline span make_span( Ptr & ptr ) { return span( ptr ); } template< class Ptr > gsl_api inline span make_span( Ptr & ptr, typename span::index_type count ) { return span( ptr, count); } #endif // gsl_FEATURE_TO_STD( MAKE_SPAN ) #if gsl_FEATURE_TO_STD( BYTE_SPAN ) template< class T > gsl_api inline gsl_constexpr span byte_span( T & t ) gsl_noexcept { return span( reinterpret_cast( &t ), sizeof(T) ); } template< class T > gsl_api inline gsl_constexpr span byte_span( T const & t ) gsl_noexcept { return span( reinterpret_cast( &t ), sizeof(T) ); } #endif // gsl_FEATURE_TO_STD( BYTE_SPAN ) // // basic_string_span: // template< class T > class basic_string_span; namespace detail { template< class T > struct is_basic_string_span_oracle : std11::false_type {}; template< class T > struct is_basic_string_span_oracle< basic_string_span > : std11::true_type {}; template< class T > struct is_basic_string_span : is_basic_string_span_oracle< typename std11::remove_cv::type > {}; template< class T > gsl_api inline gsl_constexpr14 std::size_t string_length( T * ptr, std::size_t max ) { if ( ptr == gsl_nullptr || max <= 0 ) return 0; std::size_t len = 0; while ( len < max && ptr[len] ) // NOLINT ++len; return len; } } // namespace detail // // basic_string_span<> - A view of contiguous characters, replace (*,len). // template< class T > class basic_string_span { public: typedef T element_type; typedef span span_type; typedef typename span_type::index_type index_type; typedef typename span_type::difference_type difference_type; typedef typename span_type::pointer pointer ; typedef typename span_type::reference reference ; typedef typename span_type::iterator iterator ; typedef typename span_type::const_iterator const_iterator ; typedef typename span_type::reverse_iterator reverse_iterator; typedef typename span_type::const_reverse_iterator const_reverse_iterator; // construction: #if gsl_HAVE( IS_DEFAULT ) gsl_api gsl_constexpr basic_string_span() gsl_noexcept = default; #else gsl_api gsl_constexpr basic_string_span() gsl_noexcept {} #endif #if gsl_HAVE( NULLPTR ) gsl_api gsl_constexpr basic_string_span( std::nullptr_t ptr ) gsl_noexcept : span_( ptr, index_type( 0 ) ) {} #endif gsl_api gsl_constexpr basic_string_span( pointer ptr ) : span_( remove_z( ptr, (std::numeric_limits::max)() ) ) {} gsl_api gsl_constexpr basic_string_span( pointer ptr, index_type count ) : span_( ptr, count ) {} gsl_api gsl_constexpr basic_string_span( pointer firstElem, pointer lastElem ) : span_( firstElem, lastElem ) {} template< std::size_t N > gsl_api gsl_constexpr basic_string_span( element_type (&arr)[N] ) : span_( remove_z( gsl_ADDRESSOF( arr[0] ), N ) ) {} #if gsl_HAVE( ARRAY ) template< std::size_t N > gsl_api gsl_constexpr basic_string_span( std::array< typename std11::remove_const::type, N> & arr ) : span_( remove_z( arr ) ) {} template< std::size_t N > gsl_api gsl_constexpr basic_string_span( std::array< typename std11::remove_const::type, N> const & arr ) : span_( remove_z( arr ) ) {} #endif #if gsl_HAVE( CONSTRAINED_SPAN_CONTAINER_CTOR ) // Exclude: array, [basic_string,] basic_string_span template< class Container gsl_REQUIRES_T(( ! detail::is_std_array< Container >::value && ! detail::is_basic_string_span< Container >::value && std::is_convertible< typename Container::pointer, pointer >::value && std::is_convertible< typename Container::pointer, decltype(std::declval().data()) >::value )) > gsl_api gsl_constexpr basic_string_span( Container & cont ) : span_( ( cont ) ) {} // Exclude: array, [basic_string,] basic_string_span template< class Container gsl_REQUIRES_T(( ! detail::is_std_array< Container >::value && ! detail::is_basic_string_span< Container >::value && std::is_convertible< typename Container::pointer, pointer >::value && std::is_convertible< typename Container::pointer, decltype(std::declval().data()) >::value )) > gsl_api gsl_constexpr basic_string_span( Container const & cont ) : span_( ( cont ) ) {} #elif gsl_HAVE( UNCONSTRAINED_SPAN_CONTAINER_CTOR ) template< class Container > gsl_api gsl_constexpr basic_string_span( Container & cont ) : span_( cont ) {} template< class Container > gsl_api gsl_constexpr basic_string_span( Container const & cont ) : span_( cont ) {} #else template< class U > gsl_api gsl_constexpr basic_string_span( span const & rhs ) : span_( rhs ) {} #endif #if gsl_FEATURE_TO_STD( WITH_CONTAINER ) template< class Container > gsl_api gsl_constexpr basic_string_span( with_container_t, Container & cont ) : span_( with_container, cont ) {} #endif #if gsl_HAVE( IS_DEFAULT ) # if gsl_BETWEEN( gsl_COMPILER_GNUC_VERSION, 440, 600 ) gsl_api gsl_constexpr basic_string_span( basic_string_span const & rhs ) = default; gsl_api gsl_constexpr basic_string_span( basic_string_span && rhs ) = default; # else gsl_api gsl_constexpr basic_string_span( basic_string_span const & rhs ) gsl_noexcept = default; gsl_api gsl_constexpr basic_string_span( basic_string_span && rhs ) gsl_noexcept = default; # endif #endif template< class U #if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) gsl_REQUIRES_T(( std::is_convertible::pointer, pointer>::value )) #endif > gsl_api gsl_constexpr basic_string_span( basic_string_span const & rhs ) : span_( reinterpret_cast( rhs.data() ), rhs.length() ) // NOLINT {} #if gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 120 template< class U gsl_REQUIRES_T(( std::is_convertible::pointer, pointer>::value )) > gsl_api gsl_constexpr basic_string_span( basic_string_span && rhs ) : span_( reinterpret_cast( rhs.data() ), rhs.length() ) // NOLINT {} #endif template< class CharTraits, class Allocator > gsl_api gsl_constexpr basic_string_span( std::basic_string< typename std11::remove_const::type, CharTraits, Allocator > & str ) : span_( gsl_ADDRESSOF( str[0] ), str.length() ) {} template< class CharTraits, class Allocator > gsl_api gsl_constexpr basic_string_span( std::basic_string< typename std11::remove_const::type, CharTraits, Allocator > const & str ) : span_( gsl_ADDRESSOF( str[0] ), str.length() ) {} // destruction, assignment: #if gsl_HAVE( IS_DEFAULT ) gsl_api ~basic_string_span() gsl_noexcept = default; gsl_api basic_string_span & operator=( basic_string_span const & rhs ) gsl_noexcept = default; gsl_api basic_string_span & operator=( basic_string_span && rhs ) gsl_noexcept = default; #endif // sub span: gsl_api gsl_constexpr basic_string_span first( index_type count ) const { return span_.first( count ); } gsl_api gsl_constexpr basic_string_span last( index_type count ) const { return span_.last( count ); } gsl_api gsl_constexpr basic_string_span subspan( index_type offset ) const { return span_.subspan( offset ); } gsl_api gsl_constexpr basic_string_span subspan( index_type offset, index_type count ) const { return span_.subspan( offset, count ); } // observers: gsl_api gsl_constexpr index_type length() const gsl_noexcept { return span_.size(); } gsl_api gsl_constexpr index_type size() const gsl_noexcept { return span_.size(); } gsl_api gsl_constexpr index_type length_bytes() const gsl_noexcept { return span_.size_bytes(); } gsl_api gsl_constexpr index_type size_bytes() const gsl_noexcept { return span_.size_bytes(); } gsl_api gsl_constexpr bool empty() const gsl_noexcept { return size() == 0; } gsl_api gsl_constexpr reference operator[]( index_type idx ) const { return span_[idx]; } gsl_api gsl_constexpr reference operator()( index_type idx ) const { return span_[idx]; } gsl_api gsl_constexpr pointer data() const gsl_noexcept { return span_.data(); } gsl_api iterator begin() const gsl_noexcept { return span_.begin(); } gsl_api iterator end() const gsl_noexcept { return span_.end(); } gsl_api reverse_iterator rbegin() const gsl_noexcept { return span_.rbegin(); } gsl_api reverse_iterator rend() const gsl_noexcept { return span_.rend(); } // const version not in p0123r2: gsl_api const_iterator cbegin() const gsl_noexcept { return span_.cbegin(); } gsl_api const_iterator cend() const gsl_noexcept { return span_.cend(); } gsl_api const_reverse_iterator crbegin() const gsl_noexcept { return span_.crbegin(); } gsl_api const_reverse_iterator crend() const gsl_noexcept { return span_.crend(); } private: gsl_api static gsl_constexpr14 span_type remove_z( pointer const & sz, std::size_t max ) { return span_type( sz, detail::string_length( sz, max ) ); } #if gsl_HAVE( ARRAY ) template< size_t N > gsl_api static gsl_constexpr14 span_type remove_z( std::array::type, N> & arr ) { return remove_z( gsl_ADDRESSOF( arr[0] ), narrow_cast< std::size_t >( N ) ); } template< size_t N > gsl_api static gsl_constexpr14 span_type remove_z( std::array::type, N> const & arr ) { return remove_z( gsl_ADDRESSOF( arr[0] ), narrow_cast< std::size_t >( N ) ); } #endif private: span_type span_; }; // basic_string_span comparison functions: #if gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) template< class T, class U > gsl_api inline gsl_constexpr14 bool operator==( basic_string_span const & l, U const & u ) gsl_noexcept { const basic_string_span< typename std11::add_const::type > r( u ); return l.size() == r.size() && std::equal( l.begin(), l.end(), r.begin() ); } template< class T, class U > gsl_api inline gsl_constexpr14 bool operator<( basic_string_span const & l, U const & u ) gsl_noexcept { const basic_string_span< typename std11::add_const::type > r( u ); return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); } #if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) template< class T, class U gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) > gsl_api inline gsl_constexpr14 bool operator==( U const & u, basic_string_span const & r ) gsl_noexcept { const basic_string_span< typename std11::add_const::type > l( u ); return l.size() == r.size() && std::equal( l.begin(), l.end(), r.begin() ); } template< class T, class U gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) > gsl_api inline gsl_constexpr14 bool operator<( U const & u, basic_string_span const & r ) gsl_noexcept { const basic_string_span< typename std11::add_const::type > l( u ); return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); } #endif #else //gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) template< class T > gsl_api inline gsl_constexpr14 bool operator==( basic_string_span const & l, basic_string_span const & r ) gsl_noexcept { return l.size() == r.size() && std::equal( l.begin(), l.end(), r.begin() ); } template< class T > gsl_api inline gsl_constexpr14 bool operator<( basic_string_span const & l, basic_string_span const & r ) gsl_noexcept { return std::lexicographical_compare( l.begin(), l.end(), r.begin(), r.end() ); } #endif // gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) template< class T, class U > gsl_api inline gsl_constexpr14 bool operator!=( basic_string_span const & l, U const & r ) gsl_noexcept { return !( l == r ); } template< class T, class U > gsl_api inline gsl_constexpr14 bool operator<=( basic_string_span const & l, U const & r ) gsl_noexcept { #if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) || ! gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) return !( r < l ); #else basic_string_span< typename std11::add_const::type > rr( r ); return !( rr < l ); #endif } template< class T, class U > gsl_api inline gsl_constexpr14 bool operator>( basic_string_span const & l, U const & r ) gsl_noexcept { #if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) || ! gsl_CONFIG( ALLOWS_NONSTRICT_SPAN_COMPARISON ) return ( r < l ); #else basic_string_span< typename std11::add_const::type > rr( r ); return ( rr < l ); #endif } template< class T, class U > gsl_api inline gsl_constexpr14 bool operator>=( basic_string_span const & l, U const & r ) gsl_noexcept { return !( l < r ); } #if gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) template< class T, class U gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) > gsl_api inline gsl_constexpr14 bool operator!=( U const & l, basic_string_span const & r ) gsl_noexcept { return !( l == r ); } template< class T, class U gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) > gsl_api inline gsl_constexpr14 bool operator<=( U const & l, basic_string_span const & r ) gsl_noexcept { return !( r < l ); } template< class T, class U gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) > gsl_api inline gsl_constexpr14 bool operator>( U const & l, basic_string_span const & r ) gsl_noexcept { return ( r < l ); } template< class T, class U gsl_REQUIRES_T(( !detail::is_basic_string_span::value )) > gsl_api inline gsl_constexpr14 bool operator>=( U const & l, basic_string_span const & r ) gsl_noexcept { return !( l < r ); } #endif // gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) // convert basic_string_span to byte span: template< class T > gsl_api inline span< const byte > as_bytes( basic_string_span spn ) gsl_noexcept { return span< const byte >( reinterpret_cast( spn.data() ), spn.size_bytes() ); // NOLINT } // // String types: // typedef char * zstring; typedef const char * czstring; #if gsl_HAVE( WCHAR ) typedef wchar_t * zwstring; typedef const wchar_t * cwzstring; #endif typedef basic_string_span< char > string_span; typedef basic_string_span< char const > cstring_span; #if gsl_HAVE( WCHAR ) typedef basic_string_span< wchar_t > wstring_span; typedef basic_string_span< wchar_t const > cwstring_span; #endif // to_string() allow (explicit) conversions from string_span to string #if 0 template< class T > gsl_api inline std::basic_string< typename std::remove_const::type > to_string( basic_string_span spn ) { std::string( spn.data(), spn.length() ); } #else gsl_api inline std::string to_string( string_span const & spn ) { return std::string( spn.data(), spn.length() ); } gsl_api inline std::string to_string( cstring_span const & spn ) { return std::string( spn.data(), spn.length() ); } #if gsl_HAVE( WCHAR ) gsl_api inline std::wstring to_string( wstring_span const & spn ) { return std::wstring( spn.data(), spn.length() ); } gsl_api inline std::wstring to_string( cwstring_span const & spn ) { return std::wstring( spn.data(), spn.length() ); } #endif // gsl_HAVE( WCHAR ) #endif // to_string() // // Stream output for string_span types // namespace detail { template< class Stream > gsl_api void write_padding( Stream & os, std::streamsize n ) { for ( std::streamsize i = 0; i < n; ++i ) os.rdbuf()->sputc( os.fill() ); } template< class Stream, class Span > gsl_api Stream & write_to_stream( Stream & os, Span const & spn ) { typename Stream::sentry sentry( os ); if ( !os ) return os; const std::streamsize length = narrow( spn.length() ); // Whether, and how, to pad const bool pad = ( length < os.width() ); const bool left_pad = pad && ( os.flags() & std::ios_base::adjustfield ) == std::ios_base::right; if ( left_pad ) write_padding( os, os.width() - length ); // Write span characters os.rdbuf()->sputn( spn.begin(), length ); if ( pad && !left_pad ) write_padding( os, os.width() - length ); // Reset output stream width os.width(0); return os; } } // namespace detail template< typename Traits > gsl_api std::basic_ostream< char, Traits > & operator<<( std::basic_ostream< char, Traits > & os, string_span const & spn ) { return detail::write_to_stream( os, spn ); } template< typename Traits > gsl_api std::basic_ostream< char, Traits > & operator<<( std::basic_ostream< char, Traits > & os, cstring_span const & spn ) { return detail::write_to_stream( os, spn ); } #if gsl_HAVE( WCHAR ) template< typename Traits > gsl_api std::basic_ostream< wchar_t, Traits > & operator<<( std::basic_ostream< wchar_t, Traits > & os, wstring_span const & spn ) { return detail::write_to_stream( os, spn ); } template< typename Traits > gsl_api std::basic_ostream< wchar_t, Traits > & operator<<( std::basic_ostream< wchar_t, Traits > & os, cwstring_span const & spn ) { return detail::write_to_stream( os, spn ); } #endif // gsl_HAVE( WCHAR ) // // ensure_sentinel() // // Provides a way to obtain a span from a contiguous sequence // that ends with a (non-inclusive) sentinel value. // // Will fail-fast if sentinel cannot be found before max elements are examined. // namespace detail { template< class T, class SizeType, const T Sentinel > gsl_api static span ensure_sentinel( T * seq, SizeType max = (std::numeric_limits::max)() ) { typedef T * pointer; gsl_SUPPRESS_MSVC_WARNING( 26429, "f.23: symbol 'cur' is never tested for nullness, it can be marked as not_null" ) pointer cur = seq; while ( static_cast( cur - seq ) < max && *cur != Sentinel ) ++cur; Expects( *cur == Sentinel ); return span( seq, narrow_cast< typename span::index_type >( cur - seq ) ); } } // namespace detail // // ensure_z - creates a string_span for a czstring or cwzstring. // Will fail fast if a null-terminator cannot be found before // the limit of size_type. // template< class T > gsl_api inline span ensure_z( T * const & sz, size_t max = (std::numeric_limits::max)() ) { return detail::ensure_sentinel( sz, max ); } template< class T, size_t N > gsl_api inline span ensure_z( T (&sz)[N] ) { return ensure_z( gsl_ADDRESSOF( sz[0] ), N ); } # if gsl_HAVE( TYPE_TRAITS ) template< class Container > gsl_api inline span< typename std::remove_pointer::type > ensure_z( Container & cont ) { return ensure_z( cont.data(), cont.length() ); } # endif // // basic_zstring_span<> - A view of contiguous null-terminated characters, replace (*,len). // template class basic_zstring_span { public: typedef T element_type; typedef span span_type; typedef typename span_type::index_type index_type; typedef typename span_type::difference_type difference_type; typedef element_type * czstring_type; typedef basic_string_span string_span_type; gsl_api gsl_constexpr14 basic_zstring_span( span_type s ) : span_( s ) { // expects a zero-terminated span Expects( s[s.size() - 1] == '\0'); } #if gsl_HAVE( IS_DEFAULT ) gsl_api gsl_constexpr basic_zstring_span( basic_zstring_span const & other ) = default; gsl_api gsl_constexpr basic_zstring_span( basic_zstring_span && other ) = default; gsl_api gsl_constexpr14 basic_zstring_span & operator=( basic_zstring_span const & other ) = default; gsl_api gsl_constexpr14 basic_zstring_span & operator=( basic_zstring_span && other ) = default; #else gsl_api gsl_constexpr basic_zstring_span( basic_zstring_span const & other) : span_ ( other.span_ ) {} gsl_api gsl_constexpr basic_zstring_span & operator=( basic_zstring_span const & other ) { span_ = other.span_; return *this; } #endif gsl_api gsl_constexpr bool empty() const gsl_noexcept { return span_.size() == 0; } gsl_api gsl_constexpr string_span_type as_string_span() const gsl_noexcept { return string_span_type( span_.data(), span_.size() > 1 ? span_.size() - 1 : 0 ); } gsl_api gsl_constexpr string_span_type ensure_z() const { return gsl::ensure_z(span_.data(), span_.size()); } gsl_api gsl_constexpr czstring_type assume_z() const gsl_noexcept { return span_.data(); } private: span_type span_; }; // // zString types: // typedef basic_zstring_span< char > zstring_span; typedef basic_zstring_span< char const > czstring_span; #if gsl_HAVE( WCHAR ) typedef basic_zstring_span< wchar_t > wzstring_span; typedef basic_zstring_span< wchar_t const > cwzstring_span; #endif } // namespace gsl #if gsl_CPP11_OR_GREATER || gsl_COMPILER_MSVC_VERSION >= 120 namespace std { template<> struct hash< gsl::byte > { public: std::size_t operator()( gsl::byte v ) const gsl_noexcept { return gsl::to_integer( v ); } }; } // namespace std #endif gsl_RESTORE_MSVC_WARNINGS() #endif // GSL_GSL_LITE_HPP_INCLUDED // end of file treesheets-1.0.2/lobster/lobster/000077500000000000000000000000001352107072600170145ustar00rootroot00000000000000treesheets-1.0.2/lobster/lobster/language.vcxproj000066400000000000000000000522421352107072600222210ustar00rootroot00000000000000 Debug Win32 Debug x64 Release Win32 Release x64 {2C576764-3A5A-4CCD-B50D-C7C9C0349282} Win32Proj lobster language 10.0 StaticLibrary true MultiByte v142 StaticLibrary true MultiByte v142 false StaticLibrary false true MultiByte v142 StaticLibrary false true MultiByte v142 true ..\..\build\$(SolutionName)\$(ProjectName)\$(PlatformName)\$(Configuration)\ ..\..\build\$(SolutionName)\$(ProjectName)\ $(ProjectName)DBG false NativeMinimumRules.ruleset true ..\..\build\$(SolutionName)\$(ProjectName)\ ..\..\build\$(SolutionName)\$(ProjectName)\$(PlatformName)\$(Configuration)\ $(ProjectName)DBG64 NativeMinimumRules.ruleset false $(ProjectName) ..\..\build\$(SolutionName)\$(ProjectName)\ NativeMinimumRules.ruleset ..\..\build\$(SolutionName)\$(ProjectName)\$(PlatformName)\$(Configuration)\ NativeMinimumRules.ruleset false $(ProjectName)64 ..\..\build\$(SolutionName)\$(ProjectName)\ ..\..\build\$(SolutionName)\$(ProjectName)\$(PlatformName)\$(Configuration)\ true true Use Level4 Disabled WIN32;_DEBUG;_CONSOLE;SDL_XAUDIO2_HAS_SDK=1;BUILD_CONTEXT_$(SolutionName);%(PreprocessorDefinitions) Disabled ..\src;..\include; StreamingSIMDExtensions2 false 4127;4512;4201;4146;4456;4702 ProgramDatabase true false MultiThreadedDebug lobster/stdafx.h stdcpplatest true false Console true opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) ..\lib;$(DXSDK_DIR)\Lib\x86 ..\..\build\$(SolutionName)\$(ProjectName)\$(TargetName)$(TargetExt) Use Level4 Disabled WIN32;_DEBUG;_CONSOLE;SDL_XAUDIO2_HAS_SDK=1;BUILD_CONTEXT_$(SolutionName);%(PreprocessorDefinitions) Disabled ..\src;..\include;..\external\freetype\include;..\external\SDL\include;$(DXSDK_DIR)\Include NotSet true false MultiThreadedDebug 4127;4512;4201;4146;4456;4702 lobster/stdafx.h stdcpplatest true false Console true opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) ..\lib;$(DXSDK_DIR)\Lib\x86 ..\..\build\$(SolutionName)\$(ProjectName)\$(TargetName)$(TargetExt) Level4 Use MaxSpeed true true WIN32;NDEBUG;_CONSOLE;SDL_XAUDIO2_HAS_SDK=1;BUILD_CONTEXT_$(SolutionName);%(PreprocessorDefinitions) ..\src;..\include; ProgramDatabase MultiThreaded StreamingSIMDExtensions2 4127;4512;4201;4146;4456;4702 true lobster/stdafx.h stdcpplatest true false Console true true true ..\lib;$(DXSDK_DIR)\Lib\x86 opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) $(TargetPath) $(IntDir)$(TargetName).pdb false false ..\..\build\$(SolutionName)\$(ProjectName)\$(TargetName)$(TargetExt) Level4 Use MinSpace true true WIN32;NDEBUG;_CONSOLE;SDL_XAUDIO2_HAS_SDK=1;BUILD_CONTEXT_$(SolutionName);%(PreprocessorDefinitions) ..\src;..\include;..\external\freetype\include;..\external\SDL\include;$(DXSDK_DIR)\Include ProgramDatabase MultiThreaded NotSet true 4127;4512;4201;4146;4456;4702 lobster/stdafx.h stdcpplatest true false Console true true true ..\lib;$(DXSDK_DIR)\Lib\x86 opengl32.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) $(TargetPath) $(IntDir)$(TargetName).pdb false false ..\..\build\$(SolutionName)\$(ProjectName)\$(TargetName)$(TargetExt) NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing NotUsing Level4 Level4 Level4 Level4 Level4 Create Create Create Create Create Create Level4 StreamingSIMDExtensions2 StreamingSIMDExtensions2 StreamingSIMDExtensions2 StreamingSIMDExtensions2 NotUsing NotUsing NotUsing NotUsing Designer treesheets-1.0.2/lobster/lobster/language.vcxproj.filters000066400000000000000000000162141352107072600236670ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {564c5719-24a0-4d9b-8808-665854c6bb93} {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd {da60f2b4-0659-4c6d-957e-6436692322c3} {5b6e2421-9663-4999-a2ce-f1589fbbd7ec} {28705062-822f-4b9e-a663-02b43d056eb2} {849d593b-fb90-4a4d-89c5-66130f92b9a4} base compiler\builtins compiler\builtins compiler\builtins base dvm dvm compiler dvm compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers dvm tonative tonative tonative common base base base base base base base dvm dvm compiler compiler compiler compiler compiler compiler compiler compiler compiler common common common common compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers compiler\builtins\flatbuffers tonative tonative treesheets-1.0.2/lobster/readme.md000066400000000000000000000006341352107072600171240ustar00rootroot00000000000000This directory contains a partial clone of The Lobster Programming Language (https://github.com/aardappel/lobster), the scripting language being used in TreeSheets. See that repo for any documentation on building/using it. Because that repo dwarfs TreeSheets in size, and we only need a small part of it, the clone is kept up to date with a simple script (reclone.bat/.sh) instead of git submodule/subtree etc. treesheets-1.0.2/lobster/reclone.bat000066400000000000000000000023771352107072600174720ustar00rootroot00000000000000rem First delete the existing clone to leave no unused files. rmdir /s /q src rmdir /s /q lobster rmdir /s /q include rmdir /s /q external rmdir /s /q ..\TS\scripts\include rem Copy selected dirs we need to build just the language core. rem This currently assumes the lobster repo is parallel to the treesheets one. rem TODO: make this configurable and/or allow it to do a git clone. set source=..\..\lobster md src xcopy %source%\dev\src\*.cpp src md src\lobster xcopy %source%\dev\src\lobster\*.h src\lobster md lobster xcopy %source%\dev\lobster\language.vcxproj lobster xcopy %source%\dev\lobster\language.vcxproj.filters lobster md include md include\flatbuffers xcopy %source%\dev\include\flatbuffers\*.* include\flatbuffers md external md external\flatbuffers md external\flatbuffers\src xcopy %source%\dev\external\flatbuffers\src\*.* external\flatbuffers\src md include\StackWalker xcopy %source%\dev\include\StackWalker\*.* include\StackWalker md include\gsl xcopy %source%\dev\include\gsl\*.* include\gsl md ..\TS\scripts\modules xcopy %source%\modules\stdtype.lobster ..\TS\scripts\modules xcopy %source%\modules\std.lobster ..\TS\scripts\modules xcopy %source%\modules\vec.lobster ..\TS\scripts\modules xcopy %source%\modules\color.lobster ..\TS\scripts\modules treesheets-1.0.2/lobster/src/000077500000000000000000000000001352107072600161315ustar00rootroot00000000000000treesheets-1.0.2/lobster/src/audio.cpp000066400000000000000000000031221352107072600177340ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/sdlinterface.h" using namespace lobster; void AddSound(NativeRegistry &nfr) { nfr("play_wav", "filename,volume", "SI?", "B", "plays a sound defined by a wav file (RAW or MS-ADPCM, any bitrate other than 22050hz 16bit" " will automatically be converted on first load). volume in range 1..128, or omit for max." " returns false on error", [](VM &, Value &ins, Value &vol) { bool ok = SDLPlaySound(ins.sval()->strv(), false, vol.True() ? vol.intval() : 128); return Value(ok); }); nfr("play_sfxr", "filename,volume", "SI?", "B", "plays a synth sound defined by a .sfs file (use http://www.drpetter.se/project_sfxr.html" " to generate these). volume in range 1..128, or omit for max. returns false on error", [](VM &, Value &ins, Value &vol) { bool ok = SDLPlaySound(ins.sval()->strv(), true, vol.True() ? vol.intval() : 128); return Value(ok); }); } treesheets-1.0.2/lobster/src/builtins.cpp000066400000000000000000001304051352107072600204710ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/unicode.h" #include "lobster/wfc.h" namespace lobster { static RandomNumberGenerator rnd; static int IntCompare(const Value &a, const Value &b) { return a.ival() < b.ival() ? -1 : a.ival() > b.ival(); } static int FloatCompare(const Value &a, const Value &b) { return a.fval() < b.fval() ? -1 : a.fval() > b.fval(); } static int StringCompare(const Value &a, const Value &b) { auto _a = a.sval()->strv(); auto _b = b.sval()->strv(); return (_a > _b) - (_b > _a); } template Value BinarySearch(VM &vm, Value &l, Value &key, T comparefun) { intp size = l.vval()->len; intp i = 0; for (;;) { if (!size) break; intp mid = size / 2; intp comp = comparefun(key, l.vval()->At(i + mid)); if (comp) { if (comp < 0) size = mid; else { mid++; i += mid; size -= mid; } } else { i += mid; size = 1; while (i && !comparefun(key, l.vval()->At(i - 1))) { i--; size++; } while (i + size < l.vval()->len && !comparefun(key, l.vval()->At(i + size))) { size++; } break; } } vm.Push(Value(size)); return Value(i); } void AddBuiltins(NativeRegistry &nfr) { nfr("print", "x", "Ss", "", "output any value to the console (with linefeed).", [](VM &vm, Value &a) { vm.ss_reuse.str(string()); vm.ss_reuse.clear(); RefToString(vm, vm.ss_reuse, a.refnil(), vm.programprintprefs); LOG_PROGRAM(vm.ss_reuse.str()); return Value(); }); // This is now the identity function, but still useful to force a coercion. nfr("string", "x", "Ssk", "S", "convert any value to string", [](VM &, Value &a) { return a; }); nfr("set_print_depth", "depth", "I", "", "for printing / string conversion: sets max vectors/objects recursion depth (default 10)", [](VM &vm, Value &a) { vm.programprintprefs.depth = a.ival(); return Value(); }); nfr("set_print_length", "len", "I", "", "for printing / string conversion: sets max string length (default 100000)", [](VM &vm, Value &a) { vm.programprintprefs.budget = a.ival(); return Value(); }); nfr("set_print_quoted", "quoted", "B", "", "for printing / string conversion: if the top level value is a string, whether to convert" " it with escape codes and quotes (default false)", [](VM &vm, Value &a) { vm.programprintprefs.quoted = a.ival() != 0; return Value(); }); nfr("set_print_decimals", "decimals", "I", "", "for printing / string conversion: number of decimals for any floating point output" " (default -1, meaning all)", [](VM &vm, Value &a) { vm.programprintprefs.decimals = a.ival(); return Value(); }); nfr("set_print_indent", "spaces", "I", "", "for printing / string conversion: number of spaces to indent with. default is 0:" " no indent / no multi-line", [](VM &vm, Value &a) { vm.programprintprefs.indent = a.intval(); return Value(); }); nfr("get_line", "", "", "S", "reads a string from the console if possible (followed by enter)", [](VM &vm) { const int MAXSIZE = 1000; char buf[MAXSIZE]; if (!fgets(buf, MAXSIZE, stdin)) buf[0] = 0; buf[MAXSIZE - 1] = 0; for (int i = 0; i < MAXSIZE; i++) if (buf[i] == '\n') { buf[i] = 0; break; } return Value(vm.NewString(buf)); }); nfr("if", "cond,then,else", "ALL?", "A", "evaluates then or else depending on cond, else is optional", [](VM &, Value &c, Value &t, Value &e) { assert(0); // Special case implementation in the VM (void)c; (void)t; (void)e; return Value(); }); nfr("while", "cond,do", "L@L", "A", "evaluates body while cond (converted to a function) holds true, returns last body value", [](VM &, Value &c, Value &b) { assert(0); // Special case implementation in the VM (void)c; (void)b; return Value(); }); nfr("for", "iter,do", "AL", "", "iterates over int/vector/string, body may take [ element [ , index ] ] arguments", [](VM &, Value &iter, Value &body) { assert(0); // Special case implementation in the VM (void)iter; (void)body; return Value(); }); nfr("append", "xs,ys", "A]*A]*1", "A]1", "creates a new vector by appending all elements of 2 input vectors", [](VM &vm, Value &v1, Value &v2) { auto type = v1.vval()->tti; auto nv = (LVector *)vm.NewVec(0, v1.vval()->len + v2.vval()->len, type); nv->Append(vm, v1.vval(), 0, v1.vval()->len); nv->Append(vm, v2.vval(), 0, v2.vval()->len); return Value(nv); }); nfr("vector_reserve", "typeid,len", "TI", "A]*", "creates a new empty vector much like [] would, except now ensures" " it will have space for len push() operations without having to reallocate." " pass \"typeof return\" as typeid.", [](VM &vm, Value &type, Value &len) { return Value(vm.NewVec(0, len.ival(), (type_elem_t)type.ival())); }); nfr("length", "x", "I", "I", "length of int (identity function, useful in combination with string/vector version)", [](VM &, Value &a) { return a; }); nfr("length", "s", "S", "I", "length of string", [](VM &, Value &a) { auto len = a.sval()->len; return Value(len); }); nfr("length", "xs", "A]*", "I", "length of vector", [](VM &, Value &a) { auto len = a.vval()->len; return Value(len); }); nfr("equal", "a,b", "AA", "B", "structural equality between any two values (recurses into vectors/objects," " unlike == which is only true for vectors/objects if they are the same object)", [](VM &vm, Value &a, Value &b) { bool eq = RefEqual(vm, a.refnil(), b.refnil(), true); return Value(eq); }); nfr("push", "xs,x", "A]*Akw1", "Ab]1", "appends one element to a vector, returns existing vector", [](VM &vm) { auto val = vm.PopVecPtr(); auto l = vm.Pop().vval(); assert(val.second == l->width); l->PushVW(vm, val.first); vm.Push(l); }); nfr("pop", "xs", "A]*", "A1", "removes last element from vector and returns it", [](VM &vm) { auto l = vm.Pop().vval(); if (!l->len) vm.BuiltinError("pop: empty vector"); l->PopVW(vm.TopPtr()); vm.PushN((int)l->width); }); nfr("top", "xs", "A]*", "Ab1", "returns last element from vector", [](VM &vm) { auto l = vm.Pop().vval(); if (!l->len) vm.BuiltinError("top: empty vector"); l->TopVW(vm.TopPtr()); vm.PushN((int)l->width); }); nfr("insert", "xs,i,x", "A]*IAkw1", "Ab]1", "inserts a value into a vector at index i, existing elements shift upward," " returns original vector", [](VM &vm) { auto val = vm.PopVecPtr(); auto i = vm.Pop().ival(); auto l = vm.Pop().vval(); if (i < 0 || i > l->len) vm.BuiltinError("insert: index or n out of range"); // note: i==len is legal assert(val.second == l->width); l->Insert(vm, val.first, i); vm.Push(l); }); nfr("remove", "xs,i,n", "A]*II?", "A1", "remove element(s) at index i, following elements shift down. pass the number of elements" " to remove as an optional argument, default 1. returns the first element removed.", [](VM &vm) { auto n = vm.Pop().ival(); auto i = vm.Pop().ival(); auto l = vm.Pop().vval(); auto amount = max(n, (intp)1); if (n < 0 || amount > l->len || i < 0 || i > l->len - amount) vm.BuiltinError(cat("remove: index (", i, ") or n (", amount, ") out of range (", l->len, ")")); l->Remove(vm, i, amount, 1, true); }); nfr("remove_obj", "xs,obj", "A]*A1", "Ab2", "remove all elements equal to obj (==), returns obj.", [](VM &vm, Value &l, Value &o) { intp removed = 0; auto vt = vm.GetTypeInfo(l.vval()->ti(vm).subt).t; for (intp i = 0; i < l.vval()->len; i++) { auto e = l.vval()->At(i); if (e.Equal(vm, vt, o, vt, false)) { l.vval()->Remove(vm, i--, 1, 0, false); removed++; } } return o; }); nfr("binary_search", "xs,key", "I]I", "II", "does a binary search for key in a sorted vector, returns as first return value how many" " matches were found, and as second the index in the array where the matches start (so you" " can read them, overwrite them, or remove them), or if none found, where the key could be" " inserted such that the vector stays sorted. This overload is for int vectors and keys.", [](VM &vm, Value &l, Value &key) { auto r = BinarySearch(vm, l, key, IntCompare); return r; }); nfr("binary_search", "xs,key", "F]F", "II", "float version.", [](VM &vm, Value &l, Value &key) { auto r = BinarySearch(vm, l, key, FloatCompare); return r; }); nfr("binary_search", "xs,key", "S]S", "II", "string version.", [](VM &vm, Value &l, Value &key) { auto r = BinarySearch(vm, l, key, StringCompare); return r; }); nfr("copy", "xs", "A", "A1", "makes a shallow copy of any object.", [](VM &vm, Value &v) { return v.Copy(vm); }); nfr("slice", "xs,start,size", "A]*II", "A]1", "returns a sub-vector of size elements from index start." " size can be negative to indicate the rest of the vector.", [](VM &vm, Value &l, Value &s, Value &e) { auto size = e.ival(); auto start = s.ival(); if (size < 0) size = l.vval()->len - start; if (start < 0 || start + size > l.vval()->len) vm.BuiltinError("slice: values out of range"); auto nv = (LVector *)vm.NewVec(0, size, l.vval()->tti); nv->Append(vm, l.vval(), start, size); return Value(nv); }); nfr("any", "xs", "I}", "B", "returns wether any elements of the numeric struct are true values", [](VM &vm) { auto r = false; auto l = vm.Pop().ival(); for (intp i = 0; i < l; i++) { if (vm.Pop().True()) r = true; } vm.Push(r); }); nfr("any", "xs", "A]*", "B", "returns wether any elements of the vector are true values", [](VM &, Value &v) { Value r(false); intp l = v.vval()->len; for (auto i = 0; i < l; i++) { if (v.vval()->At(i).True()) { r = Value(true); break; } } return r; }); nfr("all", "xs", "I}", "B", "returns wether all elements of the numeric struct are true values", [](VM &vm) { auto r = true; auto l = vm.Pop().ival(); for (intp i = 0; i < l; i++) { if (!vm.Pop().True()) r = false; } vm.Push(r); }); nfr("all", "xs", "A]*", "B", "returns wether all elements of the vector are true values", [](VM &, Value &v) { Value r(true); for (intp i = 0; i < v.vval()->len; i++) { if (!v.vval()->At(i).True()) { r = Value(false); break; } } return r; }); nfr("substring", "s,start,size", "SII", "S", "returns a substring of size characters from index start." " size can be negative to indicate the rest of the string.", [](VM &vm, Value &l, Value &s, Value &e) { intp size = e.ival(); intp start = s.ival(); if (size < 0) size = l.sval()->len - start; if (start < 0 || start + size > l.sval()->len) vm.BuiltinError("substring: values out of range"); auto ns = vm.NewString(string_view(l.sval()->data() + start, size)); return Value(ns); }); nfr("string_to_int", "s", "S", "IB", "converts a string to an int. returns 0 if no numeric data could be parsed." "second return value is true if all characters of the string were parsed", [](VM &vm, Value &s) { char *end; auto sv = s.sval()->strv(); auto i = parse_int(sv, 10, &end); vm.Push(i); return Value(end == sv.data() + sv.size()); }); nfr("string_to_float", "s", "S", "F", "converts a string to a float. returns 0.0 if no numeric data could be parsed", [](VM &, Value &s) { auto f = strtod(s.sval()->data(), nullptr); return Value(f); }); nfr("tokenize", "s,delimiters,whitespace", "SSS", "S]", "splits a string into a vector of strings, by splitting into segments upon each dividing or" " terminating delimiter. Segments are stripped of leading and trailing whitespace." " Example: \"; A ; B C; \" becomes [ \"\", \"A\", \"B C\" ] with \";\" as delimiter and" " \" \" as whitespace.", [](VM &vm, Value &s, Value &delims, Value &whitespace) { auto v = (LVector *)vm.NewVec(0, 0, TYPE_ELEM_VECTOR_OF_STRING); auto ws = whitespace.sval()->strv(); auto dl = delims.sval()->strv(); auto p = s.sval()->strv(); p.remove_prefix(min(p.find_first_not_of(ws), p.size())); while (!p.empty()) { auto delim = min(p.find_first_of(dl), p.size()); auto end = min(p.find_last_not_of(ws) + 1, delim); v->Push(vm, vm.NewString(string_view(p.data(), end))); p.remove_prefix(delim); p.remove_prefix(min(p.find_first_not_of(dl), p.size())); p.remove_prefix(min(p.find_first_not_of(ws), p.size())); } return Value(v); }); nfr("unicode_to_string", "us", "I]", "S", "converts a vector of ints representing unicode values to a UTF-8 string.", [](VM &vm, Value &v) { char buf[7]; string s; for (intp i = 0; i < v.vval()->len; i++) { auto &c = v.vval()->At(i); ToUTF8((int)c.ival(), buf); s += buf; } return Value(vm.NewString(s)); }); nfr("string_to_unicode", "s", "S", "I]?", "converts a UTF-8 string into a vector of unicode values, or nil upon a decoding error", [](VM &vm, Value &s) { auto v = (LVector *)vm.NewVec(0, s.sval()->len, TYPE_ELEM_VECTOR_OF_INT); auto p = s.sval()->strv(); while (!p.empty()) { int u = FromUTF8(p); if (u < 0) return Value(); v->Push(vm, u); } return Value(v); }); nfr("number_to_string", "number,base,minchars", "III", "S", "converts the (unsigned version) of the input integer number to a string given the base" " (2..36, e.g. 16 for hex) and outputting a minimum of characters (padding with 0).", [](VM &vm, Value &n, Value &b, Value &mc) { if (b.ival() < 2 || b.ival() > 36 || mc.ival() > 32) vm.BuiltinError("number_to_string: values out of range"); auto i = (uintp)n.ival(); string s; auto from = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; while (i || (intp)s.length() < mc.ival()) { s.insert(0, 1, from[i % b.ival()]); i /= b.ival(); } return Value(vm.NewString(s)); }); nfr("lowercase", "s", "S", "S", "converts a UTF-8 string from any case to lower case, affecting only A-Z", [](VM &vm, Value &s) { auto ns = vm.NewString(s.sval()->strv()); for (auto &c : ns->strv()) { // This is unicode-safe, since all unicode chars are in bytes >= 128 if (c >= 'A' && c <= 'Z') (char &)c += 'a' - 'A'; } return Value(ns); }); nfr("uppercase", "s", "S", "S", "converts a UTF-8 string from any case to upper case, affecting only a-z", [](VM &vm, Value &s) { auto ns = vm.NewString(s.sval()->strv()); for (auto &c : ns->strv()) { // This is unicode-safe, since all unicode chars are in bytes >= 128 if (c >= 'a' && c <= 'z') (char &)c -= 'a' - 'A'; } return Value(ns); }); nfr("escape_string", "s,set,prefix,postfix", "SSSS", "S", "prefixes & postfixes any occurrences or characters in set in string s", [](VM &vm, Value &s, Value &set, Value &prefix, Value &postfix) { string out; for (auto p = s.sval()->strv();;) { auto loc = p.find_first_of(set.sval()->strv()); if (loc != string_view::npos) { out.append(p.data(), loc); auto presv = prefix.sval()->strv(); out.append(presv.data(), presv.size()); out += p[loc++]; auto postsv = postfix.sval()->strv(); out.append(postsv.data(), postsv.size()); p.remove_prefix(loc); } else { out += p; break; } } return Value(vm.NewString(out)); }); nfr("concat_string", "v,sep", "S]S", "S", "concatenates all elements of the string vector, separated with sep.", [](VM &vm, Value &v, Value &sep) { string s; auto sepsv = sep.sval()->strv(); for (intp i = 0; i < v.vval()->len; i++) { if (i) s.append(sepsv); auto esv = v.vval()->At(i).sval()->strv(); s.append(esv); } return Value(vm.NewString(s)); }); nfr("repeat_string", "s,n", "SI", "S", "returns a string consisting of n copies of the input string.", [](VM &vm, Value &s, Value &_n) { auto n = max(intp(0), _n.ival()); auto len = s.sval()->len; auto ns = vm.NewString(len * n); for (intp i = 0; i < n; i++) memcpy((char *)ns->data() + i * len, s.sval()->data(), len); return Value(ns); }); #define VECTOROPT(op, typeinfo) \ auto len = vm.Pop().ival(); \ auto elems = vm.TopPtr() - len; \ for (intp i = 0; i < len; i++) { \ auto f = elems[i]; \ elems[i] = Value(op); \ } #define VECTOROP(op) VECTOROPT(op, a.oval()->tti) #define VECTOROPI(op) VECTOROPT(op, vm.GetIntVectorType((int)len)) #define VECTOROPF(op) VECTOROPT(op, vm.GetFloatVectorType((int)len)) nfr("pow", "a,b", "FF", "F", "a raised to the power of b", [](VM &, Value &a, Value &b) { return Value(pow(a.fval(), b.fval())); }); nfr("pow", "a,b", "II", "I", "a raised to the power of b, for integers, using exponentiation by squaring", [](VM &, Value &a, Value &b) { return Value(b.ival() >= 0 ? ipow(a.ival(), b.ival()) : 0); }); nfr("pow", "a,b", "F}F", "F}", "vector elements raised to the power of b", [](VM &vm) { auto exp = vm.Pop().fval(); VECTOROPF(pow(f.fval(), exp)); }); nfr("log", "a", "F", "F", "natural logaritm of a", [](VM &, Value &a) { return Value(log(a.fval())); }); nfr("sqrt", "f", "F", "F", "square root", [](VM &, Value &a) { return Value(sqrt(a.fval())); }); nfr("ceiling", "f", "F", "I", "the nearest int >= f", [](VM &, Value &a) { return Value(fceil(a.fval())); }); nfr("ceiling", "v", "F}", "I}", "the nearest ints >= each component of v", [](VM &vm) { VECTOROPI(intp(fceil(f.fval()))); }); nfr("floor", "f", "F", "I", "the nearest int <= f", [](VM &, Value &a) { return Value(ffloor(a.fval())); }); nfr("floor", "v", "F}", "I}", "the nearest ints <= each component of v", [](VM &vm) { VECTOROPI(ffloor(f.fval())); }); nfr("int", "f", "F", "I", "converts a float to an int by dropping the fraction", [](VM &, Value &a) { return Value(intp(a.fval())); }); nfr("int", "v", "F}", "I}", "converts a vector of floats to ints by dropping the fraction", [](VM &vm) { VECTOROPI(intp(f.fval())); }); nfr("round", "f", "F", "I", "converts a float to the closest int. same as int(f + 0.5), so does not work well on" " negative numbers", [](VM &, Value &a) { return Value(intp(a.fval() + 0.5f)); }); nfr("round", "v", "F}", "I}", "converts a vector of floats to the closest ints", [](VM &vm) { VECTOROPI(intp(f.fval() + 0.5f)); }); nfr("fraction", "f", "F", "F", "returns the fractional part of a float: short for f - int(f)", [](VM &, Value &a) { return Value(a.fval() - int(a.fval())); }); nfr("fraction", "v", "F}", "F}", "returns the fractional part of a vector of floats", [](VM &vm) { VECTOROP(f.fval() - int(f.fval())); }); nfr("float", "i", "I", "F", "converts an int to float", [](VM &, Value &a) { return Value(floatp(a.ival())); }); nfr("float", "v", "I}", "F}", "converts a vector of ints to floats", [](VM &vm) { VECTOROPF(floatp(f.ival())); }); nfr("sin", "angle", "F", "F", "the y coordinate of the normalized vector indicated by angle (in degrees)", [](VM &, Value &a) { return Value(sin(a.fval() * RAD)); }); nfr("cos", "angle", "F", "F", "the x coordinate of the normalized vector indicated by angle (in degrees)", [](VM &, Value &a) { return Value(cos(a.fval() * RAD)); }); nfr("tan", "angle", "F", "F", "the tangent of an angle (in degrees)", [](VM &, Value &a) { return Value(tan(a.fval() * RAD)); }); nfr("sincos", "angle", "F", "F}:2", "the normalized vector indicated by angle (in degrees), same as xy { cos(angle), sin(angle) }", [](VM &vm) { auto a = vm.Pop().fval(); vm.PushVec(floatp2(cos(a * RAD), sin(a * RAD))); }); nfr("asin", "y", "F", "F", "the angle (in degrees) indicated by the y coordinate projected to the unit circle", [](VM &, Value &y) { return Value(asin(y.fval()) / RAD); }); nfr("acos", "x", "F", "F", "the angle (in degrees) indicated by the x coordinate projected to the unit circle", [](VM &, Value &x) { return Value(acos(x.fval()) / RAD); }); nfr("atan", "x", "F", "F", "the angle (in degrees) indicated by the y coordinate of the tangent projected to the unit circle", [](VM &, Value &x) { return Value(atan(x.fval()) / RAD); }); nfr("radians", "angle", "F", "F", "converts an angle in degrees to radians", [](VM &, Value &a) { return Value(a.fval() * RAD); }); nfr("degrees", "angle", "F", "F", "converts an angle in radians to degrees", [](VM &, Value &a) { return Value(a.fval() / RAD); }); nfr("atan2", "vec", "F}" , "F", "the angle (in degrees) corresponding to a normalized 2D vector", [](VM &vm) { auto v = vm.PopVec(); vm.Push(atan2(v.y, v.x) / RAD); }); nfr("radians", "angle", "F", "F", "converts an angle in degrees to radians", [](VM &, Value &a) { return Value(a.fval() * RAD); }); nfr("degrees", "angle", "F", "F", "converts an angle in radians to degrees", [](VM &, Value &a) { return Value(a.fval() / RAD); }); nfr("normalize", "vec", "F}" , "F}", "returns a vector of unit length", [](VM &vm) { switch (vm.Top().ival()) { case 2: { auto v = vm.PopVec(); vm.PushVec(v == floatp2_0 ? v : normalize(v)); break; } case 3: { auto v = vm.PopVec(); vm.PushVec(v == floatp3_0 ? v : normalize(v)); break; } case 4: { auto v = vm.PopVec(); vm.PushVec(v == floatp4_0 ? v : normalize(v)); break; } default: assert(false); } }); nfr("dot", "a,b", "F}F}", "F", "the length of vector a when projected onto b (or vice versa)", [](VM &vm) { auto b = vm.PopVec(); auto a = vm.PopVec(); vm.Push(dot(a, b)); }); nfr("magnitude", "v", "F}", "F", "the geometric length of a vector", [](VM &vm) { auto a = vm.PopVec(); vm.Push(length(a)); }); nfr("manhattan", "v", "I}", "I", "the manhattan distance of a vector", [](VM &vm) { auto a = vm.PopVec(); vm.Push(manhattan(a)); }); nfr("cross", "a,b", "F}:3F}:3", "F}:3", "a perpendicular vector to the 2D plane defined by a and b (swap a and b for its inverse)", [](VM &vm) { auto b = vm.PopVec(); auto a = vm.PopVec(); vm.PushVec(cross(a, b)); }); nfr("rnd", "max", "I", "I", "a random value [0..max).", [](VM &, Value &a) { return Value(rnd(max(1, (int)a.ival()))); }); nfr("rnd", "max", "I}", "I}", "a random vector within the range of an input vector.", [](VM &vm) { VECTOROP(rnd(max(1, (int)f.ival()))); }); nfr("rnd_float", "", "", "F", "a random float [0..1)", [](VM &) { return Value(rnd.rnddouble()); }); nfr("rnd_gaussian", "", "", "F", "a random float in a gaussian distribution with mean 0 and stddev 1", [](VM &) { return Value(rnd.rnd_gaussian()); }); nfr("rnd_seed", "seed", "I", "", "explicitly set a random seed for reproducable randomness", [](VM &, Value &seed) { rnd.seed((int)seed.ival()); return Value(); }); nfr("div", "a,b", "II", "F", "forces two ints to be divided as floats", [](VM &, Value &a, Value &b) { return Value(floatp(a.ival()) / floatp(b.ival())); }); nfr("clamp", "x,min,max", "III", "I", "forces an integer to be in the range between min and max (inclusive)", [](VM &, Value &a, Value &b, Value &c) { return Value(geom::clamp(a.ival(), b.ival(), c.ival())); }); nfr("clamp", "x,min,max", "FFF", "F", "forces a float to be in the range between min and max (inclusive)", [](VM &, Value &a, Value &b, Value &c) { return Value(geom::clamp(a.fval(), b.fval(), c.fval())); }); nfr("clamp", "x,min,max", "I}I}I}", "I}", "forces an integer vector to be in the range between min and max (inclusive)", [](VM &vm) { auto l = vm.Top().intval(); auto c = vm.PopVec(); auto b = vm.PopVec(); auto a = vm.PopVec(); vm.PushVec(geom::clamp(a, b, c), l); }); nfr("clamp", "x,min,max", "F}F}F}", "F}", "forces a float vector to be in the range between min and max (inclusive)", [](VM &vm) { auto l = vm.Top().intval(); auto c = vm.PopVec(); auto b = vm.PopVec(); auto a = vm.PopVec(); vm.PushVec(geom::clamp(a, b, c), l); }); nfr("in_range", "x,range,bias", "III?", "B", "checks if an integer is >= bias and < bias + range. Bias defaults to 0.", [](VM &, Value &x, Value &range, Value &bias) { return Value(x.ival() >= bias.ival() && x.ival() < bias.ival() + range.ival()); }); nfr("in_range", "x,range,bias", "FFF?", "B", "checks if a float is >= bias and < bias + range. Bias defaults to 0.", [](VM &, Value &x, Value &range, Value &bias) { return Value(x.fval() >= bias.fval() && x.fval() < bias.fval() + range.fval()); }); nfr("in_range", "x,range,bias", "I}I}I}?", "B", "checks if a 2d/3d integer vector is >= bias and < bias + range. Bias defaults to 0.", [](VM &vm) { auto bias = vm.Top().True() ? vm.PopVec() : (vm.Pop(), intp3_0); auto range = vm.PopVec(1); auto x = vm.PopVec(); vm.Push(x >= bias && x < bias + range); }); nfr("in_range", "x,range,bias", "F}F}F}?", "B", "checks if a 2d/3d float vector is >= bias and < bias + range. Bias defaults to 0.", [](VM &vm) { auto bias = vm.Top().True() ? vm.PopVec() : (vm.Pop(), floatp3_0); auto range = vm.PopVec(1); auto x = vm.PopVec(); vm.Push(x >= bias && x < bias + range); }); nfr("abs", "x", "I", "I", "absolute value of an integer", [](VM &, Value &a) { return Value(abs(a.ival())); }); nfr("abs", "x", "F", "F", "absolute value of a float", [](VM &, Value &a) { return Value(fabs(a.fval())); }); nfr("abs", "x", "I}", "I}", "absolute value of an int vector", [](VM &vm) { VECTOROP(abs(f.ival())); }); nfr("abs", "x", "F}", "F}", "absolute value of a float vector", [](VM &vm) { VECTOROP(fabs(f.fval())); }); nfr("sign", "x", "I", "I", "sign (-1, 0, 1) of an integer", [](VM &, Value &a) { return Value(signum(a.ival())); }); nfr("sign", "x", "F", "I", "sign (-1, 0, 1) of a float", [](VM &, Value &a) { return Value(signum(a.fval())); }); nfr("sign", "x", "I}", "I}", "signs of an int vector", [](VM &vm) { VECTOROP(signum(f.ival())); }); nfr("sign", "x", "F}", "I}", "signs of a float vector", [](VM &vm) { VECTOROPI(signum(f.fval())); }); // FIXME: need to guarantee in typechecking that both vectors are the same len. #define VECBINOP(name, T) \ auto len = vm.Top().intval(); \ auto y = vm.PopVec(); \ auto x = vm.PopVec(); \ vm.PushVec(name(x, y), len); #define VECSCALAROP(type, init, fun, acc, len, at) \ type v = init; \ auto l = x.acc()->len; \ for (intp i = 0; i < l; i++) { \ auto f = x.acc()->at; \ fun; \ } \ return Value(v); #define STSCALAROP(type, init, fun) \ type v = init; \ auto l = vm.Pop().ival(); \ for (intp i = 0; i < l; i++) { \ auto f = vm.Pop(); \ fun; \ } \ vm.Push(v); nfr("min", "x,y", "II", "I", "smallest of 2 integers.", [](VM &, Value &x, Value &y) { return Value(min(x.ival(), y.ival())); }); nfr("min", "x,y", "FF", "F", "smallest of 2 floats.", [](VM &, Value &x, Value &y) { return Value(min(x.fval(), y.fval())); }); nfr("min", "x,y", "I}I}", "I}", "smallest components of 2 int vectors", [](VM &vm) { VECBINOP(min, intp4) }); nfr("min", "x,y", "F}F}", "F}", "smallest components of 2 float vectors", [](VM &vm) { VECBINOP(min, floatp4) }); nfr("min", "v", "I}", "I", "smallest component of a int vector.", [](VM &vm) { STSCALAROP(intp, INT_MAX, v = min(v, f.ival())) }); nfr("min", "v", "F}", "F", "smallest component of a float vector.", [](VM &vm) { STSCALAROP(floatp, FLT_MAX, v = min(v, f.fval())) }); nfr("min", "v", "I]", "I", "smallest component of a int vector, or INT_MAX if length 0.", [](VM &, Value &x) { VECSCALAROP(intp, INT_MAX, v = min(v, f.ival()), vval, len, At(i)) }); nfr("min", "v", "F]", "F", "smallest component of a float vector, or FLT_MAX if length 0.", [](VM &, Value &x) { VECSCALAROP(floatp, FLT_MAX, v = min(v, f.fval()), vval, len, At(i)) }); nfr("max", "x,y", "II", "I", "largest of 2 integers.", [](VM &, Value &x, Value &y) { return Value(max(x.ival(), y.ival())); }); nfr("max", "x,y", "FF", "F", "largest of 2 floats.", [](VM &, Value &x, Value &y) { return Value(max(x.fval(), y.fval())); }); nfr("max", "x,y", "I}I}", "I}", "largest components of 2 int vectors", [](VM &vm) { VECBINOP(max, intp4) }); nfr("max", "x,y", "F}F}", "F}", "largest components of 2 float vectors", [](VM &vm) { VECBINOP(max, floatp4) }); nfr("max", "v", "I}", "I", "largest component of a int vector.", [](VM &vm) { STSCALAROP(intp, INT_MIN, v = max(v, f.ival())) }); nfr("max", "v", "F}", "F", "largest component of a float vector.", [](VM &vm) { STSCALAROP(floatp, FLT_MIN, v = max(v, f.fval())) }); nfr("max", "v", "I]", "I", "largest component of a int vector, or INT_MIN if length 0.", [](VM &, Value &x) { VECSCALAROP(intp, INT_MIN, v = max(v, f.ival()), vval, len, At(i)) }); nfr("max", "v", "F]", "F", "largest component of a float vector, or FLT_MIN if length 0.", [](VM &, Value &x) { VECSCALAROP(floatp, FLT_MIN, v = max(v, f.fval()), vval, len, At(i)) }); nfr("lerp", "x,y,f", "FFF", "F", "linearly interpolates between x and y with factor f [0..1]", [](VM &, Value &x, Value &y, Value &f) { return Value(mix(x.fval(), y.fval(), (float)f.fval())); }); nfr("lerp", "a,b,f", "F}F}F", "F}", "linearly interpolates between a and b vectors with factor f [0..1]", [](VM &vm) { auto f = vm.Pop().fltval(); auto numelems = vm.Top().intval(); auto y = vm.PopVec(); auto x = vm.PopVec(); vm.PushVec(mix(x, y, f), numelems); }); nfr("cardinal_spline", "z,a,b,c,f,tension", "F}F}F}F}FF", "F}:3", "computes the position between a and b with factor f [0..1], using z (before a) and c" " (after b) to form a cardinal spline (tension at 0.5 is a good default)", [](VM &vm) { auto t = vm.Pop().fval(); auto f = vm.Pop().fval(); auto c = vm.PopVec(); auto b = vm.PopVec(); auto a = vm.PopVec(); auto z = vm.PopVec(); vm.PushVec(cardinal_spline(z, a, b, c, f, t)); }); nfr("line_intersect", "line1a,line1b,line2a,line2b", "F}:2F}:2F}:2F}:2", "IF}:2", "computes if there is an intersection point between 2 line segments, with the point as" " second return value", [](VM &vm) { auto l2b = vm.PopVec(); auto l2a = vm.PopVec(); auto l1b = vm.PopVec(); auto l1a = vm.PopVec(); floatp2 ipoint(0, 0); auto r = line_intersect(l1a, l1b, l2a, l2b, &ipoint); vm.Push(r); vm.PushVec(ipoint); }); nfr("circles_within_range", "dist,positions,radiuses,positions2,radiuses2,gridsize", "FF}:2]F]F}:2]F]I}:2", "I]]", "Given a vector of 2D positions (and same size vectors of radiuses), returns a vector of" " vectors of indices (to the second set of positions and radiuses) of the circles that are" " within dist of eachothers radius. If the second set are [], the first set is used for" " both (and the self element is excluded)." " gridsize optionally specifies the size of the grid to use for accellerated lookup of nearby" " points. This is essential for the algorithm to be fast, too big or too small can cause slowdown." " Omit it, and a heuristic will be chosen for you, which is currently sqrt(num_circles) * 2 along" " each dimension, e.g. 100 elements would use a 20x20 grid." " Efficiency wise this algorithm is fastest if there is not too much variance in the radiuses of" " the second set and/or the second set has smaller radiuses than the first.", [](VM &vm) { auto ncelld = vm.PopVec(); auto radiuses2 = vm.Pop().vval(); auto positions2 = vm.Pop().vval(); auto radiuses1 = vm.Pop().vval(); auto positions1 = vm.Pop().vval(); if (!radiuses2->len) radiuses2 = radiuses1; if (!positions2->len) positions2 = positions1; auto qdist = vm.Pop().fval(); if (ncelld.x <= 0 || ncelld.y <= 0) ncelld = intp2((intp)sqrtf(float(positions2->len + 1) * 4)); if (radiuses1->len != positions1->len || radiuses2->len != positions2->len) vm.BuiltinError( "circles_within_range: input vectors size mismatch"); struct Node { floatp2 pos; floatp rad; intp idx; Node *next; }; vector nodes(positions2->len, Node()); floatp maxrad = 0; floatp2 minpos = floatp2(FLT_MAX), maxpos(FLT_MIN); for (intp i = 0; i < positions2->len; i++) { auto &n = nodes[i]; auto p = ValueToF<2>(positions2->AtSt(i), positions2->width); minpos = min(minpos, p); maxpos = max(maxpos, p); n.pos = p; auto r = radiuses2->At(i).fval(); maxrad = max(maxrad, r); n.rad = r; n.idx = i; n.next = nullptr; } vector cells(ncelld.x * ncelld.y, nullptr); auto wsize = maxpos - minpos; wsize *= 1.00001f; // No objects may fall exactly on the far border. auto tocellspace = [&](const floatp2 &pos) { return intp2((pos - minpos) / wsize * floatp2(ncelld)); }; for (intp i = 0; i < positions2->len; i++) { auto &n = nodes[i]; auto cp = tocellspace(n.pos); auto &c = cells[cp.x + cp.y * ncelld.x]; n.next = c; c = &n; } vector within_range; vector results(positions1->len, nullptr); for (intp i = 0; i < positions1->len; i++) { auto pos = ValueToF<2>(positions1->AtSt(i), positions1->width); auto rad = radiuses1->At(i).fval(); auto scanrad = rad + maxrad + qdist; auto minc = max(intp2_0, min(ncelld - 1, tocellspace(pos - scanrad))); auto maxc = max(intp2_0, min(ncelld - 1, tocellspace(pos + scanrad))); for (intp y = minc.y; y <= maxc.y; y++) { for (intp x = minc.x; x <= maxc.x; x++) { for (auto c = cells[x + y * ncelld.x]; c; c = c->next) { if (c->idx != i || positions1 != positions2) { auto d = length(c->pos - pos) - rad - c->rad; if (d < qdist) { within_range.push_back(c->idx); } } } } } auto vec = (LVector *)vm.NewVec(0, (int)within_range.size(), TYPE_ELEM_VECTOR_OF_INT); for (auto i : within_range) vec->Push(vm, Value(i)); within_range.clear(); results[i] = vec; } auto rvec = (LVector *)vm.NewVec(0, positions1->len, TYPE_ELEM_VECTOR_OF_VECTOR_OF_INT); for (auto vec : results) rvec->Push(vm, Value(vec)); vm.Push(rvec); }); nfr("wave_function_collapse", "tilemap,size", "S]I}:2", "S]I", "returns a tilemap of given size modelled after the possible shapes in the input" " tilemap. Tilemap should consist of chars in the 0..127 range. Second return value" " the number of failed neighbor matches, this should" " ideally be 0, but can be non-0 for larger maps. Simply call this function" " repeatedly until it is 0", [](VM &vm) { auto sz = vm.PopVec(); auto tilemap = vm.Pop(); auto rows = tilemap.vval()->len; vector inmap(rows); intp cols = 0; for (intp i = 0; i < rows; i++) { auto sv = tilemap.vval()->At(i).sval()->strv(); if (i) { if ((intp)sv.size() != cols) vm.Error("all columns must be equal length"); } else cols = sv.size(); inmap[i] = sv.data(); } auto outstrings = ToValueOfVectorOfStringsEmpty(vm, sz, 0); vector outmap(sz.y, nullptr); for (int i = 0; i < sz.y; i++) outmap[i] = (char *)outstrings.vval()->At(i).sval()->data(); int num_contradictions = 0; auto ok = WaveFunctionCollapse(int2(intp2(cols, (intp)inmap.size())), inmap.data(), sz, outmap.data(), rnd, num_contradictions); if (!ok) vm.Error("tilemap contained too many tile ids"); vm.Push(outstrings); vm.Push(num_contradictions); }); nfr("resume", "coroutine,return_value", "CkAk%?", "C?", "resumes execution of a coroutine, passing a value back or nil", [](VM &vm, Value &co, Value &ret) { vm.CoResume(co.cval()); // By the time CoResume returns, we're now back in the context of co, meaning that the // return value below is what is returned from yield inside co. return ret; // The actual return value from this call to resume (in the caller) will be the coroutine // itself (which is holding the refcount while active, hence the "k"). // The argument to the next call to yield (or the coroutine return value) will instead // be captured in the dormant coroutine stack and available over return_value() below. }); nfr("return_value", "coroutine", "C", "A1", "gets the last return value of a coroutine", [](VM &vm, Value &co) { Value &rv = co.cval()->Current(vm); return rv; }); nfr("active", "coroutine", "C", "B", "wether the given coroutine is still active", [](VM &, Value &co) { bool active = co.cval()->active; return Value(active); }); nfr("hash", "x", "L", "I", "hashes a function value into an int", [](VM &vm, Value &a) { auto h = a.Hash(vm, V_FUNCTION); return Value(h); }); nfr("hash", "x", "A", "I", "hashes any value into an int", [](VM &vm, Value &a) { auto h = a.ref()->Hash(vm); return Value(h); }); nfr("program_name", "", "", "S", "returns the name of the main program (e.g. \"foo.lobster\".", [](VM &vm) { return Value(vm.NewString(vm.GetProgramName())); }); nfr("vm_compiled_mode", "", "", "B", "returns if the VM is running in compiled mode (Lobster -> C++).", [](VM &) { return Value( #ifdef VM_COMPILED_CODE_MODE true #else false #endif ); }); nfr("seconds_elapsed", "", "", "F", "seconds since program start as a float, unlike gl_time() it is calculated every time it is" " called", [](VM &vm) { return Value(vm.Time()); }); nfr("assert", "condition", "A*", "Ab1", "halts the program with an assertion failure if passed false. returns its input", [](VM &vm, Value &c) { if (!c.True()) vm.BuiltinError("assertion failed"); return c; }); nfr("trace_bytecode", "mode", "I", "", "tracing shows each bytecode instruction as it is being executed, not very useful unless" " you are trying to isolate a compiler bug. Mode is off(0), on(1) or tail only (2)", [](VM &vm, Value &i) { vm.Trace((TraceMode)i.ival()); return Value(); }); nfr("set_max_stack_size", "max", "I", "", "size in megabytes the stack can grow to before an overflow error occurs. defaults to 1", [](VM &vm, Value &max) { vm.SetMaxStack((int)max.ival() * 1024 * 1024 / sizeof(Value)); return Value(); }); nfr("reference_count", "val", "A", "I", "get the reference count of any value. for compiler debugging, mostly", [](VM &, Value &x) { auto refc = x.refnil() ? x.refnil()->refc - 1 : -1; return Value(refc); }); nfr("set_console", "on", "B", "", "lets you turn on/off the console window (on Windows)", [](VM &, Value &x) { SetConsole(x.True()); return Value(); }); nfr("command_line_arguments", "", "", "S]", "", [](VM &vm) { return ToValueOfVectorOfStrings(vm, vm.program_args); }); nfr("thread_information", "", "", "II", "returns the number of hardware threads, and the number of cores", [](VM &vm) { vm.Push(NumHWThreads()); return Value(NumHWCores()); }); nfr("is_worker_thread", "", "", "B", "wether the current thread is a worker thread", [](VM &vm) { return Value(vm.is_worker); }); nfr("start_worker_threads", "numthreads", "I", "", "launch worker threads", [](VM &vm, Value &n) { vm.StartWorkers(n.ival()); return Value(); }); nfr("stop_worker_threads", "", "", "", "only needs to be called if you want to stop the worker threads before the end of" " the program, or if you want to call start_worker_threads again. workers_alive" " will become false inside the workers, which should then exit.", [](VM &vm) { vm.TerminateWorkers(); return Value(); }); nfr("workers_alive", "", "", "B", "wether workers should continue doing work. returns false after" " stop_worker_threads() has been called.", [](VM &vm) { return Value(vm.tuple_space && vm.tuple_space->alive); }); nfr("thread_write", "struct", "A", "", "put this struct in the thread queue", [](VM &vm, Value &s) { vm.WorkerWrite(s.refnil()); return Value(); }); nfr("thread_read", "type", "T", "A1?", "get a struct from the thread queue. pass the typeof struct. blocks if no such" "structs available. returns struct, or nil if stop_worker_threads() was called", [](VM &vm, Value &t) { return Value(vm.WorkerRead((type_elem_t)t.ival())); }); nfr("log_frame", "", "", "", "call this function instead of gl_frame() or gl_log_frame() to simulate a frame based program" " from non-graphical code.", [](VM &vm) { vm.vml.LogFrame(); return Value(); }); } // AddBuiltins } treesheets-1.0.2/lobster/src/compiler.cpp000066400000000000000000000432041352107072600204520ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // lobster.cpp : Defines the entry point for the console application. // #include "lobster/stdafx.h" #include "lobster/lex.h" #include "lobster/idents.h" #include "lobster/node.h" #include "lobster/compiler.h" #include "lobster/parser.h" #include "lobster/typecheck.h" #include "lobster/optimizer.h" #include "lobster/codegen.h" namespace lobster { const Type g_type_int(V_INT); const Type g_type_float(V_FLOAT); const Type g_type_string(V_STRING); const Type g_type_any(V_ANY); const Type g_type_vector_int(V_VECTOR, &g_type_int); const Type g_type_vector_float(V_VECTOR, &g_type_float); const Type g_type_function_null(V_FUNCTION); const Type g_type_function_cocl(V_YIELD); const Type g_type_coroutine(V_COROUTINE); const Type g_type_resource(V_RESOURCE); const Type g_type_typeid(V_TYPEID); const Type g_type_void(V_VOID); const Type g_type_function_void(V_VOID, &g_type_function_null); const Type g_type_undefined(V_UNDEFINED); TypeRef type_int = &g_type_int; TypeRef type_float = &g_type_float; TypeRef type_string = &g_type_string; TypeRef type_any = &g_type_any; TypeRef type_vector_int = &g_type_vector_int; TypeRef type_vector_float = &g_type_vector_float; TypeRef type_function_null = &g_type_function_null; TypeRef type_function_cocl = &g_type_function_cocl; TypeRef type_coroutine = &g_type_coroutine; TypeRef type_resource = &g_type_resource; TypeRef type_typeid = &g_type_typeid; TypeRef type_void = &g_type_void; TypeRef type_function_void = &g_type_function_void; TypeRef type_undefined = &g_type_undefined; const Type g_type_vector_any(V_VECTOR, &g_type_any); const Type g_type_vector_string(V_VECTOR, &g_type_string); const Type g_type_vector_vector_int(V_VECTOR, &g_type_vector_int); const Type g_type_vector_vector_float(V_VECTOR, &g_type_vector_float); const Type g_type_vector_vector_vector_float(V_VECTOR, &g_type_vector_vector_float); TypeRef WrapKnown(TypeRef elem, ValueType with) { if (with == V_VECTOR) { switch (elem->t) { case V_ANY: return &g_type_vector_any; case V_INT: return elem->e ? nullptr : type_vector_int; case V_FLOAT: return type_vector_float; case V_STRING: return &g_type_vector_string; case V_VECTOR: switch (elem->sub->t) { case V_INT: return &g_type_vector_vector_int; case V_FLOAT: return &g_type_vector_vector_float; case V_VECTOR: switch (elem->sub->sub->t) { case V_FLOAT: return &g_type_vector_vector_vector_float; default: return nullptr; } default: return nullptr; } default: return nullptr; } } else if (with == V_NIL) { switch (elem->t) { case V_ANY: { static const Type t(V_NIL, &g_type_any); return &t; } case V_INT: { static const Type t(V_NIL, &g_type_int); return elem->e ? nullptr : &t; } case V_FLOAT: { static const Type t(V_NIL, &g_type_float); return &t; } case V_STRING: { static const Type t(V_NIL, &g_type_string); return &t; } case V_FUNCTION: { static const Type t(V_NIL, &g_type_function_null); return &t; } case V_RESOURCE: { static const Type t(V_NIL, &g_type_resource); return &t; } case V_COROUTINE: { static const Type t(V_NIL, &g_type_coroutine); return &t; } case V_VECTOR: switch (elem->sub->t) { case V_INT: { static const Type t(V_NIL, &g_type_vector_int); return &t; } case V_FLOAT: { static const Type t(V_NIL, &g_type_vector_float); return &t; } case V_STRING: { static const Type t(V_NIL, &g_type_vector_string); return &t; } default: return nullptr; } default: return nullptr; } } else { return nullptr; } } bool IsCompressed(string_view filename) { auto dot = filename.find_last_of('.'); if (dot == string_view::npos) return false; auto ext = filename.substr(dot); return ext == ".lbc" || ext == ".lobster" || ext == ".materials"; } static const uchar *magic = (uchar *)"LPAK"; static const size_t magic_size = 4; static const size_t header_size = magic_size + sizeof(int64_t) * 3; static const char *bcname = "bytecode.lbc"; template int64_t LE(T x) { return flatbuffers::EndianScalar((int64_t)x); }; void BuildPakFile(string &pakfile, string &bytecode, set &files) { // All offsets in 64bit, just in-case we ever want pakfiles > 4GB :) // Since we're building this in memory, they can only be created by a 64bit build. vector filestarts; vector namestarts; vector uncompressed; vector filenames; auto add_file = [&](string_view buf, string_view filename) { filestarts.push_back(LE(pakfile.size())); filenames.push_back(string(filename)); LOG_INFO("adding to pakfile: ", filename); if (IsCompressed(filename)) { string out; WEntropyCoder((uchar *)buf.data(), buf.length(), buf.length(), out); pakfile += out; uncompressed.push_back(buf.length()); } else { pakfile += buf; uncompressed.push_back(-1); } }; // Start with a magic id, just for the hell of it. pakfile.insert(pakfile.end(), magic, magic + magic_size); // Bytecode always first entry. add_file(bytecode, bcname); // Followed by all files. files.insert("data/shaders/default.materials"); // If it hadn't already been added. string buf; for (auto &filename : files) { auto l = LoadFile(filename, &buf); if (l >= 0) { add_file(buf, filename); } else { vector> dir; if (!ScanDir(filename, dir)) THROW_OR_ABORT("cannot load file/dir for pakfile: " + filename); for (auto &[name, size] : dir) { auto fn = filename + name; if (size >= 0 && LoadFile(fn, &buf) >= 0) add_file(buf, fn); } } } // Now we can write the directory, first the names: auto dirstart = LE(pakfile.size()); for (auto &filename : filenames) { namestarts.push_back(LE(pakfile.size())); pakfile.insert(pakfile.end(), filename.c_str(), filename.c_str() + filename.length() + 1); } // Then the starting offsets and other data: pakfile.insert(pakfile.end(), (uchar *)uncompressed.data(), (uchar *)(uncompressed.data() + uncompressed.size())); pakfile.insert(pakfile.end(), (uchar *)filestarts.data(), (uchar *)(filestarts.data() + filestarts.size())); pakfile.insert(pakfile.end(), (uchar *)namestarts.data(), (uchar *)(namestarts.data() + namestarts.size())); auto num = LE(filestarts.size()); // Finally the "header" (or do we call this a "tailer" ? ;) auto header_start = pakfile.size(); auto version = LE(1); pakfile.insert(pakfile.end(), (uchar *)&num, (uchar *)(&num + 1)); pakfile.insert(pakfile.end(), (uchar *)&dirstart, (uchar *)(&dirstart + 1)); pakfile.insert(pakfile.end(), (uchar *)&version, (uchar *)(&version + 1)); pakfile.insert(pakfile.end(), magic, magic + magic_size); assert(pakfile.size() - header_start == header_size); (void)header_start; } // This just loads the directory part of a pakfile such that subsequent LoadFile calls know how // to load from it. bool LoadPakDir(const char *lpak) { // This supports reading from a pakfile > 4GB even on a 32bit system! (as long as individual // files in it are <= 4GB). auto plen = LoadFile(lpak, nullptr, 0, 0); if (plen < 0) return false; string header; if (LoadFile(lpak, &header, plen - (int64_t)header_size, header_size) < 0 || memcmp(header.c_str() + header_size - magic_size, magic, magic_size)) return false; auto read_unaligned64 = [](const void *p) { int64_t r; memcpy(&r, p, sizeof(int64_t)); return LE(r); }; auto num = (size_t)read_unaligned64(header.c_str()); auto dirstart = read_unaligned64((int64_t *)header.c_str() + 1); auto version = read_unaligned64((int64_t *)header.c_str() + 2); if (version > 1) return false; if (dirstart > plen) return false; string dir; if (LoadFile(lpak, &dir, dirstart, plen - dirstart - (int64_t)header_size) < 0) return false; auto namestarts = (int64_t *)(dir.c_str() + dir.length()) - num; auto filestarts = namestarts - num; auto uncompressed = filestarts - num; for (size_t i = 0; i < num; i++) { auto name = string_view(dir.c_str() + (read_unaligned64(namestarts + i) - dirstart)); auto off = read_unaligned64(filestarts + i); auto end = i < num + 1 ? read_unaligned64(filestarts + i + 1) : dirstart; auto len = end - off; LOG_INFO("pakfile dir: ", name, " : ", len); AddPakFileEntry(lpak, name, off, len, read_unaligned64(uncompressed + i)); } return true; } bool LoadByteCode(string &bytecode) { if (LoadFile(bcname, &bytecode) < 0) return false; flatbuffers::Verifier verifier((const uchar *)bytecode.c_str(), bytecode.length()); auto ok = bytecode::VerifyBytecodeFileBuffer(verifier); assert(ok); return ok; } void RegisterBuiltin(NativeRegistry &nfr, const char *name, void (* regfun)(NativeRegistry &)) { LOG_DEBUG("subsystem: ", name); nfr.NativeSubSystemStart(name); regfun(nfr); } void DumpBuiltins(NativeRegistry &nfr, bool justnames, const SymbolTable &st) { string s; if (justnames) { for (auto nf : nfr.nfuns) { s += nf->name; s += "|"; } WriteFile("builtin_functions_names.txt", false, s); return; } s = "\n" "\n\nlobster builtin function reference\n" "\n" "\n" "\n
\n

" "lobster builtin functions:" "(file auto generated by compiler, do not modify)

\n\n"; int cursubsystem = -1; bool tablestarted = false; for (auto nf : nfr.nfuns) { if (nf->subsystemid != cursubsystem) { if (tablestarted) s += "
\n"; tablestarted = false; s += cat("

", nfr.subsystems[nf->subsystemid], "

\n"); cursubsystem = nf->subsystemid; } if (!tablestarted) { s += "\n"; tablestarted = true; } s += cat("\n"); } s += "
", nf->name, "("); int last_non_nil = -1; for (auto [i, a] : enumerate(nf->args.v)) { if (a.type->t != V_NIL) last_non_nil = (int)i; } for (auto [i, a] : enumerate(nf->args.v)) { auto argname = nf->args.GetName(i); if (i) s += ", "; s += argname; s += ""; if (a.type->t != V_ANY) { s += ":"; s += a.flags & NF_BOOL ? "bool" : TypeName(a.type->ElementIfNil(), a.fixed_len, &st); } s += ""; if (a.type->t == V_NIL && (int)i > last_non_nil) s += a.type->sub->Numeric() ? " = 0" : " = nil"; } s += ")"; if (nf->retvals.v.size()) { s += " -> "; for (auto [i, a] : enumerate(nf->retvals.v)) { s += ""; s += TypeName(a.type, a.fixed_len, &st); s += ""; if (i < nf->retvals.v.size() - 1) s += ", "; } } s += cat("", nf->help, "
\n\n\n"; WriteFile("builtin_functions_reference.html", false, s); } void Compile(NativeRegistry &nfr, string_view fn, string_view stringsource, string &bytecode, string *parsedump, string *pakfile, bool dump_builtins, bool dump_names, bool return_value, int runtime_checks) { SymbolTable st; Parser parser(nfr, fn, st, stringsource); parser.Parse(); TypeChecker tc(parser, st, return_value); // Optimizer is not optional, must always run at least one pass, since TypeChecker and CodeGen // rely on it culling const if-thens and other things. Optimizer opt(parser, st, tc); if (parsedump) *parsedump = parser.DumpAll(true); CodeGen cg(parser, st, return_value, runtime_checks); st.Serialize(cg.code, cg.code_attr, cg.type_table, cg.vint_typeoffsets, cg.vfloat_typeoffsets, cg.lineinfo, cg.sids, cg.stringtable, cg.speclogvars, bytecode, cg.vtables); if (pakfile) BuildPakFile(*pakfile, bytecode, parser.pakfiles); if (dump_builtins) DumpBuiltins(nfr, false, st); if (dump_names) DumpBuiltins(nfr, true, st); } Value CompileRun(VM &parent_vm, Value &source, bool stringiscode, const vector &args) { string_view fn = stringiscode ? "string" : source.sval()->strv(); // fixme: datadir + sanitize? #ifdef USE_EXCEPTION_HANDLING try #endif { auto vmargs = VMArgs { parent_vm.nfr, fn, {}, nullptr, nullptr, 0, args }; Compile(parent_vm.nfr, fn, stringiscode ? source.sval()->strv() : string_view(), vmargs.bytecode_buffer, nullptr, nullptr, false, false, true, RUNTIME_ASSERT); #ifdef VM_COMPILED_CODE_MODE // FIXME: Sadly since we modify how the VM operates under compiled code, we can't run in // interpreted mode anymore. THROW_OR_ABORT(string("cannot execute bytecode in compiled mode")); #endif VM vm(std::move(vmargs)); vm.EvalProgram(); auto ret = vm.evalret; parent_vm.Push(Value(parent_vm.NewString(ret))); return Value(); } #ifdef USE_EXCEPTION_HANDLING catch (string &s) { parent_vm.Push(Value(parent_vm.NewString("nil"))); return Value(parent_vm.NewString(s)); } #endif } void AddCompiler(NativeRegistry &nfr) { // it knows how to call itself! nfr("compile_run_code", "code,args", "SS]", "SS?", "compiles and runs lobster source, sandboxed from the current program (in its own VM)." " the argument is a string of code. returns the return value of the program as a string," " with an error string as second return value, or nil if none. using parse_data()," " two program can communicate more complex data structures even if they don't have the same" " version of struct definitions.", [](VM &vm, Value &filename, Value &args) { return CompileRun(vm, filename, true, ValueToVectorOfStrings(args)); }); nfr("compile_run_file", "filename,args", "SS]", "SS?", "same as compile_run_code(), only now you pass a filename.", [](VM &vm, Value &filename, Value &args) { return CompileRun(vm, filename, false, ValueToVectorOfStrings( args)); }); } void RegisterCoreLanguageBuiltins(NativeRegistry &nfr) { extern void AddBuiltins(NativeRegistry &nfr); RegisterBuiltin(nfr, "builtin", AddBuiltins); extern void AddCompiler(NativeRegistry &nfr); RegisterBuiltin(nfr, "compiler", AddCompiler); extern void AddFile(NativeRegistry &nfr); RegisterBuiltin(nfr, "file", AddFile); extern void AddReader(NativeRegistry &nfr); RegisterBuiltin(nfr, "parsedata", AddReader); } VMArgs CompiledInit(int argc, char *argv[], const void *entry_point, const void *bytecodefb, size_t static_size, const lobster::block_t *vtables, FileLoader loader, NativeRegistry &nfr) { min_output_level = OUTPUT_INFO; InitPlatform("../../", "", false, loader); // FIXME: path. auto vmargs = VMArgs { nfr, StripDirPart(argv[0]), {}, entry_point, bytecodefb, static_size, {}, vtables, TraceMode::OFF }; for (int arg = 1; arg < argc; arg++) { vmargs.program_args.push_back(argv[arg]); } return vmargs; } extern "C" int ConsoleRunCompiledCodeMain(int argc, char *argv[], const void *entry_point, const void *bytecodefb, size_t static_size, const lobster::block_t *vtables) { #ifdef USE_EXCEPTION_HANDLING try #endif { NativeRegistry nfr; RegisterCoreLanguageBuiltins(nfr); lobster::VM vm(CompiledInit(argc, argv, entry_point, bytecodefb, static_size, vtables, DefaultLoadFile, nfr)); vm.EvalProgram(); } #ifdef USE_EXCEPTION_HANDLING catch (string &s) { LOG_ERROR(s); return 1; } #endif return 0; } SubFunction::~SubFunction() { delete body; } Field::~Field() { delete defaultval; } Field::Field(const Field &o) : type(o.type), id(o.id), genericref(o.genericref), defaultval(o.defaultval ? o.defaultval->Clone() : nullptr) {} } treesheets-1.0.2/lobster/src/cubegen.cpp000066400000000000000000000571461352107072600202620ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/glinterface.h" #include "lobster/3dgrid.h" #include "lobster/meshgen.h" #include "lobster/cubegen.h" #include "lobster/simplex.h" namespace lobster { const unsigned int default_palette[256] = { 0x00000000, 0xffffffff, 0xffccffff, 0xff99ffff, 0xff66ffff, 0xff33ffff, 0xff00ffff, 0xffffccff, 0xffccccff, 0xff99ccff, 0xff66ccff, 0xff33ccff, 0xff00ccff, 0xffff99ff, 0xffcc99ff, 0xff9999ff, 0xff6699ff, 0xff3399ff, 0xff0099ff, 0xffff66ff, 0xffcc66ff, 0xff9966ff, 0xff6666ff, 0xff3366ff, 0xff0066ff, 0xffff33ff, 0xffcc33ff, 0xff9933ff, 0xff6633ff, 0xff3333ff, 0xff0033ff, 0xffff00ff, 0xffcc00ff, 0xff9900ff, 0xff6600ff, 0xff3300ff, 0xff0000ff, 0xffffffcc, 0xffccffcc, 0xff99ffcc, 0xff66ffcc, 0xff33ffcc, 0xff00ffcc, 0xffffcccc, 0xffcccccc, 0xff99cccc, 0xff66cccc, 0xff33cccc, 0xff00cccc, 0xffff99cc, 0xffcc99cc, 0xff9999cc, 0xff6699cc, 0xff3399cc, 0xff0099cc, 0xffff66cc, 0xffcc66cc, 0xff9966cc, 0xff6666cc, 0xff3366cc, 0xff0066cc, 0xffff33cc, 0xffcc33cc, 0xff9933cc, 0xff6633cc, 0xff3333cc, 0xff0033cc, 0xffff00cc, 0xffcc00cc, 0xff9900cc, 0xff6600cc, 0xff3300cc, 0xff0000cc, 0xffffff99, 0xffccff99, 0xff99ff99, 0xff66ff99, 0xff33ff99, 0xff00ff99, 0xffffcc99, 0xffcccc99, 0xff99cc99, 0xff66cc99, 0xff33cc99, 0xff00cc99, 0xffff9999, 0xffcc9999, 0xff999999, 0xff669999, 0xff339999, 0xff009999, 0xffff6699, 0xffcc6699, 0xff996699, 0xff666699, 0xff336699, 0xff006699, 0xffff3399, 0xffcc3399, 0xff993399, 0xff663399, 0xff333399, 0xff003399, 0xffff0099, 0xffcc0099, 0xff990099, 0xff660099, 0xff330099, 0xff000099, 0xffffff66, 0xffccff66, 0xff99ff66, 0xff66ff66, 0xff33ff66, 0xff00ff66, 0xffffcc66, 0xffcccc66, 0xff99cc66, 0xff66cc66, 0xff33cc66, 0xff00cc66, 0xffff9966, 0xffcc9966, 0xff999966, 0xff669966, 0xff339966, 0xff009966, 0xffff6666, 0xffcc6666, 0xff996666, 0xff666666, 0xff336666, 0xff006666, 0xffff3366, 0xffcc3366, 0xff993366, 0xff663366, 0xff333366, 0xff003366, 0xffff0066, 0xffcc0066, 0xff990066, 0xff660066, 0xff330066, 0xff000066, 0xffffff33, 0xffccff33, 0xff99ff33, 0xff66ff33, 0xff33ff33, 0xff00ff33, 0xffffcc33, 0xffcccc33, 0xff99cc33, 0xff66cc33, 0xff33cc33, 0xff00cc33, 0xffff9933, 0xffcc9933, 0xff999933, 0xff669933, 0xff339933, 0xff009933, 0xffff6633, 0xffcc6633, 0xff996633, 0xff666633, 0xff336633, 0xff006633, 0xffff3333, 0xffcc3333, 0xff993333, 0xff663333, 0xff333333, 0xff003333, 0xffff0033, 0xffcc0033, 0xff990033, 0xff660033, 0xff330033, 0xff000033, 0xffffff00, 0xffccff00, 0xff99ff00, 0xff66ff00, 0xff33ff00, 0xff00ff00, 0xffffcc00, 0xffcccc00, 0xff99cc00, 0xff66cc00, 0xff33cc00, 0xff00cc00, 0xffff9900, 0xffcc9900, 0xff999900, 0xff669900, 0xff339900, 0xff009900, 0xffff6600, 0xffcc6600, 0xff996600, 0xff666600, 0xff336600, 0xff006600, 0xffff3300, 0xffcc3300, 0xff993300, 0xff663300, 0xff333300, 0xff003300, 0xffff0000, 0xffcc0000, 0xff990000, 0xff660000, 0xff330000, 0xff0000ee, 0xff0000dd, 0xff0000bb, 0xff0000aa, 0xff000088, 0xff000077, 0xff000055, 0xff000044, 0xff000022, 0xff000011, 0xff00ee00, 0xff00dd00, 0xff00bb00, 0xff00aa00, 0xff008800, 0xff007700, 0xff005500, 0xff004400, 0xff002200, 0xff001100, 0xffee0000, 0xffdd0000, 0xffbb0000, 0xffaa0000, 0xff880000, 0xff770000, 0xff550000, 0xff440000, 0xff220000, 0xff110000, 0xffeeeeee, 0xffdddddd, 0xffbbbbbb, 0xffaaaaaa, 0xff888888, 0xff777777, 0xff555555, 0xff444444, 0xff222222, 0xff111111 }; Voxels *NewWorld(const int3 &size, const byte4 *from_palette = nullptr) { auto v = new Voxels(size); if (!from_palette) from_palette = (byte4 *)default_palette; v->palette.insert(v->palette.end(), from_palette, from_palette + 256); return v; } Value CubesFromMeshGen(VM &vm, const DistGrid &grid, int targetgridsize, int zoffset) { auto &v = *NewWorld(int3_1 * targetgridsize); auto off = (grid.dim - v.grid.dim) / 2; off.z += zoffset; for (int x = 0; x < v.grid.dim.x; x++) { for (int y = 0; y < v.grid.dim.y; y++) { for (int z = 0; z < v.grid.dim.z; z++) { auto pos = int3(x, y, z); auto spos = pos + off; uchar np = transparant; if (spos >= 0 && spos < grid.dim) { auto &dgc = grid.Get(spos); np = v.Color2Palette(float4(color2vec(dgc.color).xyz(), dgc.dist <= 0)); } v.grid.Get(pos) = np; } } } return vm.NewResource(&v, GetVoxelType()); } } using namespace lobster; void CubeGenClear() { } void AddCubeGen(NativeRegistry &nfr) { nfr("cg_init", "size", "I}:3", "R", "initializes a new, empty 3D cube block. 1 byte per cell, careful with big sizes :)" " returns the block", [](VM &vm) { auto v = NewWorld(vm.PopVec()); vm.Push(vm.NewResource(v, GetVoxelType())); }); nfr("cg_size", "block", "R", "I}:3", "returns the current block size", [](VM &vm) { vm.PushVec(GetVoxels(vm, vm.Pop()).grid.dim); }); nfr("cg_set", "block,pos,size,paletteindex", "RI}:3I}:3I", "", "sets a range of cubes to palette index. index 0 is considered empty space." "Coordinates automatically clipped to the size of the grid", [](VM &vm) { auto color = vm.Pop().ival(); auto size = vm.PopVec(); auto pos = vm.PopVec(); GetVoxels(vm, vm.Pop()).Set(pos, size, (uchar)color); }); nfr("cg_copy", "block,pos,size,dest,flip", "RI}:3I}:3I}:3I}:3", "", "copy a range of cubes from pos to dest. flip can be 1 (regular copy), or -1 (mirror)for" " each component, indicating the step from dest." " Coordinates automatically clipped to the size of the grid", [](VM &vm) { auto fl = vm.PopVec(); auto d = vm.PopVec(); auto sz = vm.PopVec(); auto p = vm.PopVec(); GetVoxels(vm, vm.Pop()).Copy(p, sz, d, fl); }); nfr("cg_clone", "block,pos,size", "RI}:3I}:3", "R", "clone a range of cubes from pos to a new block." " Coordinates automatically clipped to the size of the grid", [](VM &vm) { auto sz = vm.PopVec(); auto p = vm.PopVec(); auto &v = GetVoxels(vm, vm.Pop()); auto nw = NewWorld(sz, v.palette.data()); v.Clone(p, sz, nw); vm.Push(vm.NewResource(nw, GetVoxelType())); }); nfr("cg_color_to_palette", "block,color", "RF}:4", "I", "converts a color to a palette index. alpha < 0.5 is considered empty space." " note: this is fast for the default palette, slow otherwise.", [](VM &vm) { auto color = vm.PopVec(); vm.Push(GetVoxels(vm, vm.Pop()).Color2Palette(color)); }); nfr("cg_palette_to_color", "block,paletteindex", "RI", "F}:4", "converts a palette index to a color. empty space (index 0) will have 0 alpha", [](VM &vm) { auto p = uchar(vm.Pop().ival()); vm.PushVec(color2vec(GetVoxels(vm, vm.Pop()).palette[p])); }); nfr("cg_copy_palette", "fromworld,toworld", "RR", "", "", [](VM &vm, Value &fromworld, Value &toworld) { auto &w1 = GetVoxels(vm, fromworld); auto &w2 = GetVoxels(vm, toworld); w2.palette.clear(); w2.palette.insert(w2.palette.end(), w1.palette.begin(), w1.palette.end()); return Value(); }); nfr("cg_resample_half", "fromworld", "R", "", "", [](VM &vm, Value &world) { auto &v = GetVoxels(vm, world); for (int x = 0; x < v.grid.dim.x / 2; x++) { for (int y = 0; y < v.grid.dim.y / 2; y++) { for (int z = 0; z < v.grid.dim.z / 2; z++) { auto pos = int3(x, y, z); int4 acc(0); for (int xd = 0; xd < 2; xd++) { for (int yd = 0; yd < 2; yd++) { for (int zd = 0; zd < 2; zd++) { auto d = int3(xd, yd, zd); auto c = v.grid.Get(pos * 2 + d); acc += int4(v.palette[c]); } } } auto np = v.Color2Palette(float4(acc) / (8 * 255)); v.grid.Get(pos) = np; } } } v.grid.Shrink(v.grid.dim / 2); return Value(); }); nfr("cg_create_mesh", "block", "R", "R", "converts block to a mesh", [](VM &vm, Value &wid) { auto &v = GetVoxels(vm, wid); static int3 neighbors[] = { int3(1, 0, 0), int3(-1, 0, 0), int3(0, 1, 0), int3( 0, -1, 0), int3(0, 0, 1), int3( 0, 0, -1), }; // FIXME: normal can be byte4, pos can short4 struct cvert { float3 pos; float3 normal; byte4 color; }; vector verts; vector triangles; static const char *faces[6] = { "4576", "0231", "2673", "0154", "1375", "0462" }; static int indices[6] = { 0, 1, 3, 1, 2, 3 }; // off by default because slow, maybe try: https://github.com/greg7mdp/sparsepp bool optimize_verts = false; struct VKey { vec pos; uchar pal, dir; bool operator==(const VKey &o) const { return pos == o.pos && pal == o.pal && dir == o.dir; }; }; auto hasher = [](const VKey &k) { return k.pos.x ^ (k.pos.y << 3) ^ (k.pos.z << 6) ^ (k.pal << 3) ^ k.dir; }; unordered_map vertlookup(optimize_verts ? 100000 : 10, hasher); RandomNumberGenerator rnd; vector rnd_offset(1024); for (auto &f : rnd_offset) { f = (rnd.rnd_float() - 0.5f) * 0.15f; } // Woah nested loops! for (int x = 0; x < v.grid.dim.x; x++) { for (int y = 0; y < v.grid.dim.y; y++) { for (int z = 0; z < v.grid.dim.z; z++) { auto pos = int3(x, y, z); auto c = v.grid.Get(pos); if (c != transparant) { for (int n = 0; n < 6; n++) { auto npos = pos + neighbors[n]; auto nc = npos >= 0 && npos < v.grid.dim ? v.grid.Get(npos) : transparant; if (nc == transparant) { auto face = faces[n]; int vindices[4]; for (int vn = 0; vn < 4; vn++) { int3 vpos; for (int d = 0; d < 3; d++) { vpos[d] = (face[vn] & (1 << (2 - d))) != 0; } vpos += pos; VKey vkey { vec(vpos), c, (uchar)n }; if (optimize_verts) { auto it = vertlookup.find(vkey); if (it != vertlookup.end()) { vindices[vn] = it->second; continue; } } cvert vert; auto oi = ((vpos.z << 8) ^ (vpos.y << 4) ^ vpos.x) % (rnd_offset.size() - 2); auto offset = float3(&rnd_offset[oi]); vert.pos = float3(vpos) + offset; vert.normal = float3(neighbors[n]); vert.color = v.palette[c]; vindices[vn] = (int)verts.size(); verts.push_back(vert); if (optimize_verts) vertlookup[vkey] = vindices[vn]; } for (int i = 0; i < 6; i++) triangles.push_back(vindices[indices[i]]); } } } } } } normalize_mesh(make_span(triangles), verts.data(), verts.size(), sizeof(cvert), (uchar *)&verts.data()->normal - (uchar *)&verts.data()->pos, false); LOG_INFO("cubegen verts = ", verts.size(), ", tris = ", triangles.size() / 3); auto m = new Mesh(new Geometry(make_span(verts), "PNC"), PRIM_TRIS); m->surfs.push_back(new Surface(make_span(triangles), PRIM_TRIS)); extern ResourceType mesh_type; return Value(vm.NewResource(m, &mesh_type)); }); nfr("cg_create_3d_texture", "block,textureformat,monochrome", "RII?", "R", "returns the new texture, for format, pass flags you want in addition to" " 3d|single_channel|has_mips", [](VM &vm, Value &wid, Value &textureflags, Value &monochrome) { auto &v = GetVoxels(vm, wid); auto mipsizes = 0; for (auto d = v.grid.dim; d.x; d /= 2) mipsizes += d.volume(); auto buf = new uchar[mipsizes]; v.grid.ToContinousGrid(buf); auto mipb = buf; for (auto db = v.grid.dim; db.x > 1; db /= 2) { auto ds = db / 2; auto mips = mipb + db.volume(); for (int z = 0; z < ds.z; z++) { auto zb = z * 2; for (int y = 0; y < ds.y; y++) { auto yb = y * 2; for (int x = 0; x < ds.x; x++) { auto xb = x * 2; auto sum = float4_0; int filled = 0; for (int sz = 0; sz < 2; sz++) { for (int sy = 0; sy < 2; sy++) { for (int sx = 0; sx < 2; sx++) { auto i = mipb[(zb + sz) * db.x * db.y + (yb + sy) * db.x + xb + sx]; if (i != transparant) { sum += float4(v.palette[i]); filled++; } } } } auto pi = filled >= 4 ? v.Color2Palette(sum / (filled * 255.0f)) : transparant; mips[z * ds.x * ds.y + y * ds.x + x] = pi; } } } mipb = mips; } if (monochrome.True()) { for (int i = 0; i < mipsizes; i++) buf[i] = buf[i] ? 255 : 0; } auto tex = CreateTexture(buf, v.grid.dim.data(), TF_3D | /*TF_NEAREST_MAG | TF_NEAREST_MIN | TF_CLAMP |*/ TF_SINGLE_CHANNEL | TF_BUFFER_HAS_MIPS | textureflags.intval()); delete[] buf; extern ResourceType texture_type; return Value(vm.NewResource(new Texture(tex), &texture_type)); }); nfr("cg_load_vox", "name", "S", "R?", "loads a file in the .vox format (MagicaVoxel). returns block or nil if file failed to" " load", [](VM &vm, Value &name) { auto namep = name.sval()->strv(); string buf; auto l = LoadFile(namep, &buf); if (l < 0) return Value(0); if (strncmp(buf.c_str(), "VOX ", 4)) return Value(); int3 size = int3_0; Voxels *voxels = nullptr; auto p = buf.c_str() + 8; while (p < buf.c_str() + buf.length()) { auto id = p; p += 4; auto contentlen = *((int *)p); p += 8; if (!strncmp(id, "SIZE", 4)) { size = int3((int *)p); voxels = NewWorld(size); } else if (!strncmp(id, "RGBA", 4)) { if (!voxels) return Value(); voxels->palette.clear(); voxels->palette.push_back(byte4_0); voxels->palette.insert(voxels->palette.end(), (byte4 *)p, ((byte4 *)p) + 255); for (size_t i = 0; i < voxels->palette.size(); i++) { if (voxels->palette[i] != *(byte4 *)&default_palette[i]) { voxels->is_default_palette = false; break; } } } else if (!strncmp(id, "XYZI", 4)) { if (!voxels) return Value(); auto numvoxels = *((int *)p); for (int i = 0; i < numvoxels; i++) { auto vox = byte4((uchar *)(p + i * 4 + 4)); auto pos = int3(vox.xyz()); if (pos < voxels->grid.dim) voxels->grid.Get(pos) = vox.w; } } p += contentlen; } return Value(vm.NewResource(voxels, GetVoxelType())); }); nfr("cg_save_vox", "block,name", "RS", "B", "saves a file in the .vox format (MagicaVoxel). returns false if file failed to save." " this format can only save blocks < 256^3, will fail if bigger", [](VM &vm, Value &wid, Value &name) { auto &v = GetVoxels(vm, wid); if (!(v.grid.dim < 256)) { return Value(false); } vector voxels; for (int x = 0; x < v.grid.dim.x; x++) { for (int y = 0; y < v.grid.dim.y; y++) { for (int z = 0; z < v.grid.dim.z; z++) { auto pos = int3(x, y, z); auto i = v.grid.Get(pos); if (i) voxels.push_back(byte4(int4(pos, i))); } } } FILE *f = OpenForWriting(name.sval()->strv(), true); if (!f) return Value(false); auto wint = [&](int i) { fwrite(&i, 4, 1, f); }; auto wstr = [&](const char *s) { fwrite(s, 4, 1, f); }; wstr("VOX "); wint(150); wstr("MAIN"); wint(0); wint(24 /* SIZE */ + 12 + 1024 /* RGBA */ + 16 + (int)voxels.size() * 4 /* XYZI */); wstr("SIZE"); wint(12); wint(0); wint(v.grid.dim.x); wint(v.grid.dim.y); wint(v.grid.dim.z); wstr("RGBA"); wint(256 * 4); wint(0); fwrite(v.palette.data() + 1, 4, 255, f); wint(0); wstr("XYZI"); wint((int)voxels.size() * 4 + 4); wint(0); wint((int)voxels.size()); fwrite(voxels.data(), 4, voxels.size(), f); fclose(f); return Value(true); }); nfr("cg_get_buf", "block", "R", "S", "returns the data as a string of all palette indices, in z-major order", [](VM &vm, Value &wid) { auto &v = GetVoxels(vm, wid); auto buf = vm.NewString(v.grid.dim.volume()); v.grid.ToContinousGrid((uchar *)buf->strv().data()); return Value(buf); }); nfr("cg_average_surface_color", "world", "R", "F}:4", "", [](VM &vm) { auto &v = GetVoxels(vm, vm.Pop()); int3 col(0); int nsurf = 0; int nvol = 0; int3 neighbors[] = { int3(0, 0, 1), int3(0, 1, 0), int3(1, 0, 0), int3(0, 0, -1), int3(0, -1, 0), int3(-1, 0, 0), }; for (int x = 0; x < v.grid.dim.x; x++) { for (int y = 0; y < v.grid.dim.y; y++) { for (int z = 0; z < v.grid.dim.z; z++) { auto pos = int3(x, y, z); uchar c = v.grid.Get(pos); if (c) { nvol++; // Only count voxels that lay on the surface for color average. for (int i = 0; i < 6; i++) { auto p = pos + neighbors[i]; if (!(p >= 0) || !(p < v.grid.dim) || !v.grid.Get(p)) { col += int3(v.palette[c].xyz()); nsurf++; break; } } } } } } if (nsurf) col /= nsurf; vm.PushVec(nvol < v.grid.dim.volume() / 2 ? float4(0.0f) : float4(float3(col) / 255.0f, 1.0f)); }); nfr("cg_rotate", "block,n", "RI", "R", "returns a new block rotated by n 90 degree steps from the input", [](VM &vm, Value &wid, Value &rots) { auto &v = GetVoxels(vm, wid); auto n = rots.ival(); auto &d = n == 1 || n == 3 ? *NewWorld(int3(v.grid.dim.y, v.grid.dim.x, v.grid.dim.z)) : *NewWorld(v.grid.dim); d.palette.assign(v.palette.begin(), v.palette.end()); for (int x = 0; x < v.grid.dim.x; x++) { for (int y = 0; y < v.grid.dim.y; y++) { for (int z = 0; z < v.grid.dim.z; z++) { uchar c = v.grid.Get(int3(x, y, z)); switch (n) { case 1: d.grid.Get(int3(v.grid.dim.y - y - 1, x, z)) = c; break; case 2: d.grid.Get(int3(v.grid.dim.x - x - 1, v.grid.dim.y - y - 1, z)) = c; break; case 3: d.grid.Get(int3(y, v.grid.dim.x - x - 1, z)) = c; break; default: d.grid.Get(int3(x, y, z)) = c; break; } } } } return Value(vm.NewResource(&d, GetVoxelType())); }); nfr("cg_simplex", "block,pos,size,spos,ssize,octaves,scale,persistence,solidcol,zscale,zbias", "RI}:3I}:3F}:3F}:3IFFIFF", "", "", [](VM &vm) { auto zbias = vm.Pop().fltval(); auto zscale = vm.Pop().fltval(); auto solidcol = vm.Pop().intval(); auto persistence = vm.Pop().fltval(); auto scale = vm.Pop().fltval(); auto octaves = vm.Pop().intval(); auto ssize = vm.PopVec(); auto spos = vm.PopVec(); auto sz = vm.PopVec(); auto p = vm.PopVec(); auto &v = GetVoxels(vm, vm.Pop()); v.Do(p, sz, [&](const int3 &pos, uchar &vox) { auto sp = (float3(pos - p) + 0.5) / float3(sz) * ssize + spos; auto fun = SimplexNoise(octaves, persistence, scale, sp) + sp.z * zscale - zbias; vox = uchar((fun < 0) * solidcol); }); }); nfr("cg_bounding_box", "world,minsolids", "RF", "I}:3I}:3", "", [](VM &vm) { auto minsolids = vm.Pop().fltval(); auto &v = GetVoxels(vm, vm.Pop()); auto bmin = int3_0; auto bmax = v.grid.dim; auto bestsolids = 0.0f; while ((bmax - bmin).volume()) { bestsolids = 1.0f; auto smin = bmin; auto smax = bmax; for (int c = 0; c < 3; c++) { for (int mm = 0; mm < 2; mm++) { auto tmin = bmin; auto tmax = bmax; if (mm) tmin[c] = tmax[c] - 1; else tmax[c] = tmin[c] + 1; int solid = 0; v.Do(tmin, tmax - tmin, [&](const int3 &, uchar &vox) { if (vox) solid++; }); auto total = (tmax - tmin).volume(); auto ratio = solid / float(total); if (ratio < bestsolids) { bestsolids = ratio; smin = bmin; smax = bmax; if (mm) smax[c]--; else smin[c]++; } } } if (bestsolids <= minsolids) { bmin = smin; bmax = smax; } else { break; } } vm.PushVec(bmin); vm.PushVec(bmax); }); } // AddCubeGen treesheets-1.0.2/lobster/src/disasm.cpp000066400000000000000000000143461352107072600201250ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/disasm.h" namespace lobster { const bytecode::LineInfo *LookupLine(const int *ip, const int *code, const bytecode::BytecodeFile *bcf) { auto lineinfo = bcf->lineinfo(); int pos = int(ip - code); int start = 0; auto size = lineinfo->size(); assert(size); for (;;) { // quick hardcoded binary search if (size == 1) return lineinfo->Get(start); auto nsize = size / 2; if (lineinfo->Get(start + nsize)->bytecodestart() <= pos) { start += nsize; size -= nsize; } else { size = nsize; } } } const int *DisAsmIns(NativeRegistry &nfr, ostringstream &ss, const int *ip, const int *code, const type_elem_t *typetable, const bytecode::BytecodeFile *bcf) { auto ilnames = ILNames(); auto ilarity = ILArity(); auto li = LookupLine(ip, code, bcf); // FIXME: some indication of the filename, maybe with a table index? ss << "I " << int(ip - code) << " \tL " << li->line() << " \t"; if (*ip < 0 || *ip >= IL_MAX_OPS) { ss << "ILLEGAL INSTRUCTION: " << *ip; return nullptr; } ss << ilnames[*ip] << ' '; auto arity = ilarity[*ip]; auto ins_start = ip; int opc = *ip++; if (opc < 0 || opc >= IL_MAX_OPS) { ss << opc << " ?"; return ip; } int is_struct = 0; switch(opc) { case IL_PUSHINT64: case IL_PUSHFLT64: { auto a = *ip++; auto v = Int64FromInts(a, *ip++); if (opc == IL_PUSHINT64) ss << v; else { int2float64 i2f; i2f.i = v; ss << i2f.f; } break; } case IL_LOGWRITE: case IL_KEEPREF: ss << *ip++ << ' '; ss << *ip++; break; case IL_RETURN: { auto id = *ip++; ip++; // retvals ss << bcf->functions()->Get(id)->name()->string_view(); break; } case IL_CALL: { auto bc = *ip++; assert(code[bc] == IL_FUNSTART); auto id = code[bc + 1]; auto nargs = code[bc]; ss << nargs << ' ' << bcf->functions()->Get(id)->name()->string_view(); ss << ' ' << bc; break; } case IL_NEWVEC: { ip++; // ti auto nargs = *ip++; ss << "vector " << nargs; break; } case IL_ST2S: case IL_NEWOBJECT: { auto ti = (TypeInfo *)(typetable + *ip++); ss << bcf->udts()->Get(ti->structidx)->name()->string_view(); break; } case IL_BCALLRETV: case IL_BCALLRET0: case IL_BCALLRET1: case IL_BCALLRET2: case IL_BCALLRET3: case IL_BCALLRET4: case IL_BCALLRET5: case IL_BCALLRET6: case IL_BCALLREFV: case IL_BCALLREF0: case IL_BCALLREF1: case IL_BCALLREF2: case IL_BCALLREF3: case IL_BCALLREF4: case IL_BCALLREF5: case IL_BCALLREF6: case IL_BCALLUNBV: case IL_BCALLUNB0: case IL_BCALLUNB1: case IL_BCALLUNB2: case IL_BCALLUNB3: case IL_BCALLUNB4: case IL_BCALLUNB5: case IL_BCALLUNB6: { int a = *ip++; ss << nfr.nfuns[a]->name; break; } #undef LVAL #define LVAL(N, V) case IL_VAR_##N: is_struct = V; goto var; LVALOPNAMES #undef LVAL case IL_PUSHVARV: is_struct = 1; case IL_PUSHVAR: var: ss << IdName(bcf, *ip++); if (is_struct) ss << ' ' << *ip++; break; case IL_PUSHFLT: ss << *(float *)ip; ip++; break; case IL_PUSHSTR: EscapeAndQuote(bcf->stringtable()->Get(*ip++)->string_view(), ss); break; case IL_FUNSTART: { auto fidx = *ip++; ss << (fidx >= 0 ? bcf->functions()->Get(fidx)->name()->string_view() : "__dummy"); ss << "("; int n = *ip++; while (n--) ss << IdName(bcf, *ip++) << ' '; n = *ip++; ss << "=> "; while (n--) ss << IdName(bcf, *ip++) << ' '; auto keepvars = *ip++; if (keepvars) ss << "K:" << keepvars << ' '; n = *ip++; // owned while (n--) ss << "O:" << IdName(bcf, *ip++) << ' '; ss << ")"; break; } case IL_CORO: { ss << *ip++; ip++; // typeinfo int n = *ip++; for (int i = 0; i < n; i++) ss <<" v" << *ip++; break; } default: for (int i = 0; i < arity; i++) { if (i) ss << ' '; ss << *ip++; } break; } assert(arity == ILUNKNOWNARITY || ip - ins_start == arity + 1); (void)ins_start; return ip; } void DisAsm(NativeRegistry &nfr, ostringstream &ss, string_view bytecode_buffer) { auto bcf = bytecode::GetBytecodeFile(bytecode_buffer.data()); assert(FLATBUFFERS_LITTLEENDIAN); auto code = (const int *)bcf->bytecode()->Data(); // Assumes we're on a little-endian machine. auto typetable = (const type_elem_t *)bcf->typetable()->Data(); // Same. auto len = bcf->bytecode()->Length(); const int *ip = code; while (ip < code + len) { ip = DisAsmIns(nfr, ss, ip, code, typetable, bcf); ss << "\n"; if (!ip) break; } } } // namespace lobster treesheets-1.0.2/lobster/src/engine.cpp000066400000000000000000000126451352107072600201120ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Engine integration with Lobster VM and frame management. #include "lobster/stdafx.h" #include "lobster/compiler.h" // For RegisterBuiltin(). #ifdef __EMSCRIPTEN__ #include "emscripten.h" #endif #include "lobster/sdlinterface.h" #include "lobster/engine.h" using namespace lobster; void RegisterCoreEngineBuiltins(NativeRegistry &nfr) { lobster::RegisterCoreLanguageBuiltins(nfr); extern void AddGraphics(NativeRegistry &nfr); lobster::RegisterBuiltin(nfr, "graphics", AddGraphics); extern void AddFont(NativeRegistry &nfr); lobster::RegisterBuiltin(nfr, "font", AddFont); extern void AddSound(NativeRegistry &nfr); lobster::RegisterBuiltin(nfr, "sound", AddSound); extern void AddPhysics(NativeRegistry &nfr); lobster::RegisterBuiltin(nfr, "physics", AddPhysics); extern void AddNoise(NativeRegistry &nfr); lobster::RegisterBuiltin(nfr, "noise", AddNoise); extern void AddMeshGen(NativeRegistry &nfr); lobster::RegisterBuiltin(nfr, "meshgen", AddMeshGen); extern void AddCubeGen(NativeRegistry &nfr); lobster::RegisterBuiltin(nfr, "cubegen", AddCubeGen); extern void AddOcTree(NativeRegistry &nfr); lobster::RegisterBuiltin(nfr, "octree", AddOcTree); extern void AddVR(NativeRegistry &nfr); lobster::RegisterBuiltin(nfr, "vr", AddVR); extern void AddSteam(NativeRegistry &nfr); lobster::RegisterBuiltin(nfr, "steam", AddSteam); extern void AddIMGUI(NativeRegistry &nfr); lobster::RegisterBuiltin(nfr, "imgui", AddIMGUI); } void EngineSuspendIfNeeded() { #ifdef USE_MAIN_LOOP_CALLBACK // Here we have to something hacky: emscripten requires us to not take over the main // loop. So we use this exception to suspend the VM right inside the gl_frame() call. // FIXME: do this at the start of the frame instead? THROW_OR_ABORT(string("SUSPEND-VM-MAINLOOP")); #endif } void EngineExit(int code) { GraphicsShutDown(); #ifdef __EMSCRIPTEN__ emscripten_force_exit(code); #endif exit(code); // Needed at least on iOS to forcibly shut down the wrapper main() } void one_frame_callback(void *arg) { auto &vm = *(lobster::VM *)arg; #ifdef USE_EXCEPTION_HANDLING try #endif { GraphicsFrameStart(); vm.vml.LogFrame(); vm.OneMoreFrame(); // If this returns, we didn't hit a gl_frame() again and exited normally. EngineExit(0); } #ifdef USE_EXCEPTION_HANDLING catch (string &s) { if (s != "SUSPEND-VM-MAINLOOP") { // An actual error. LOG_ERROR(s); EngineExit(1); } } #endif } void EngineRunByteCode(VMArgs &&vmargs) { lobster::VM vm(std::move(vmargs)); #ifdef USE_EXCEPTION_HANDLING try #endif { vm.EvalProgram(); } #ifdef USE_EXCEPTION_HANDLING catch (string &s) { #ifdef USE_MAIN_LOOP_CALLBACK if (s == "SUSPEND-VM-MAINLOOP") { // emscripten requires that we don't control the main loop. // We just got to the start of the first frame inside gl_frame(), and the VM is suspended. // Install the one-frame callback: #ifdef __EMSCRIPTEN__ // This has a better explanation of the last argument than the emscripten docs: // http://flohofwoe.blogspot.com/2013/09/emscripten-and-pnacl-app-entry-in.html // We're passing true as last argument, which means emscripten is not going to // return from this function until completely done, emulating behavior as if we // control the main loop. What it really does is throw a JS exception to escape from // C++ execution, leaving this main loop in a frozen state to later return to. emscripten_set_main_loop_arg(one_frame_callback, &vm, 0, true); // When we return here, we're done and exit normally. #else // Emulate this behavior so we can debug it. while (vm.evalret == "") one_frame_callback(&vm); #endif } else #endif { // An actual error. THROW_OR_ABORT(s); } } #endif } extern "C" int EngineRunCompiledCodeMain(int argc, char *argv[], const void *entry_point, const void *bytecodefb, size_t static_size, const lobster::block_t *vtables) { #ifdef USE_EXCEPTION_HANDLING try #endif { NativeRegistry nfr; RegisterCoreEngineBuiltins(nfr); EngineRunByteCode(CompiledInit(argc, argv, entry_point, bytecodefb, static_size, vtables, SDLLoadFile, nfr)); } #ifdef USE_EXCEPTION_HANDLING catch (string &s) { LOG_ERROR(s); EngineExit(1); } #endif EngineExit(0); return 0; } treesheets-1.0.2/lobster/src/file.cpp000066400000000000000000000372131352107072600175620ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "stdint.h" #include "flatbuffers/idl.h" #ifdef _WIN32 #define VC_EXTRALEAN #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #else #ifndef __ANDROID__ #include #endif #include #include #include #endif namespace lobster { template T Read(VM &vm, intp i, const LString *s) { if ((uintp)i > (uintp)s->len - sizeof(T)) vm.IDXErr(i, s->len - sizeof(T), s); return ReadValLE(s, i); } template Value WriteVal(VM &vm, const Value &str, const Value &idx, const Value &val) { auto i = idx.ival(); if (i < 0) vm.IDXErr(i, 0, str.sval()); vm.Push(WriteValLE(vm, str.sval(), i, val.ifval())); return Value(i + (intp)sizeof(T)); } template Value WriteStr(VM &vm, const Value &str, const Value &idx, LString *s, intp extra) { auto i = idx.ival(); if (i < 0) vm.IDXErr(i, 0, str.sval()); vm.Push(WriteMem(vm, str.sval(), i, s->data(), s->len + extra)); return Value(i + s->len + extra); } template Value ReadVal(VM &vm, const Value &str, const Value &idx) { auto i = idx.ival(); auto val = Read(vm, i, str.sval()); vm.Push(val); return Value(i + (intp)sizeof(T)); } template Value ReadField(VM &vm, const Value &str, const Value &idx, const Value &vidx, const Value &def) { auto i = idx.ival(); auto vtable = Read(vm, i, str.sval()); auto vi = i - vtable; auto vtable_size = Read(vm, vi, str.sval()); auto vo = vidx.ival(); if ((uintp)vo < (uintp)vtable_size) { auto field_offset = Read(vm, vi + vo, str.sval()); if (field_offset) { auto start = i + field_offset; if constexpr (ST) return Value(start); auto val = Read(vm, start, str.sval()); if constexpr (OF) return Value (val + start); return Value(val); } } return def; } LString *GetString(VM &vm, intp fi, LString *buf) { if (fi) { auto len = Read(vm, fi, buf); auto fdata = fi + (intp)sizeof(flatbuffers::uoffset_t); // Read zero terminator just to make sure all string data is in bounds. Read(vm, fdata + len, buf); return vm.NewString(buf->strv().substr(fdata, len)); } else { return vm.NewString(0); } } Value ParseSchemas(VM &vm, flatbuffers::Parser &parser, const Value &schema, const Value &includes) { vector dirs_storage; for (intp i = 0; i < includes.vval()->len; i++) { auto dir = flatbuffers::ConCatPathFileName(string(ProjectDir()), string(includes.vval()->At(i).sval()->strv())); dirs_storage.push_back(dir); } vector dirs; for (auto &dir : dirs_storage) dirs.push_back(dir.c_str()); dirs.push_back(nullptr); Value err; if (!parser.Parse(schema.sval()->data(), dirs.data())) { err = Value(vm.NewString(parser.error_)); } return err; } void AddFile(NativeRegistry &nfr) { nfr("scan_folder", "folder,divisor", "SI", "S]?I]?", "returns two vectors representing all elements in a folder, the first vector containing all" " names, the second vector containing sizes (or -1 if a directory)." " Specify 1 as divisor to get sizes in bytes, 1024 for kb etc. Values > 0x7FFFFFFF will be" " clamped in 32-bit builds. Returns nil if folder couldn't be scanned.", [](VM &vm, Value &fld, Value &divisor) { vector> dir; auto ok = ScanDirAbs(fld.sval()->strv(), dir); if (!ok) { vm.Push(Value()); return Value(); } if (divisor.ival() <= 0) divisor.setival(1); auto nlist = (LVector *)vm.NewVec(0, 0, TYPE_ELEM_VECTOR_OF_STRING); auto slist = (LVector *)vm.NewVec(0, 0, TYPE_ELEM_VECTOR_OF_INT); for (auto &[name, size] : dir) { nlist->Push(vm, Value(vm.NewString(name))); if (size >= 0) { size /= divisor.ival(); if (sizeof(intp) == sizeof(int) && size > 0x7FFFFFFF) size = 0x7FFFFFFF; } slist->Push(vm, Value(size)); } vm.Push(Value(nlist)); return Value(slist); }); nfr("read_file", "file,textmode", "SI?", "S?", "returns the contents of a file as a string, or nil if the file can't be found." " you may use either \\ or / as path separators", [](VM &vm, Value &file, Value &textmode) { string buf; auto l = LoadFile(file.sval()->strv(), &buf, 0, -1, !textmode.True()); if (l < 0) return Value(); auto s = vm.NewString(buf); return Value(s); }); nfr("write_file", "file,contents,textmode", "SSI?", "B", "creates a file with the contents of a string, returns false if writing wasn't possible", [](VM &, Value &file, Value &contents, Value &textmode) { auto ok = WriteFile(file.sval()->strv(), !textmode.True(), contents.sval()->strv()); return Value(ok); }); nfr("ensure_size", "string,size,char,extra", "SkIII?", "S", "ensures a string is at least size characters. if it is, just returns the existing" " string, otherwise returns a new string of that size (with optionally extra bytes" " added), with any new characters set to" " char. You can specify a negative size to mean relative to the end, i.e. new" " characters will be added at the start. ", [](VM &vm, Value &str, Value &size, Value &c, Value &extra) { auto asize = abs(size.ival()); return str.sval()->len >= asize ? str : Value(vm.ResizeString(str.sval(), asize + extra.ival(), c.intval(), size.ival() < 0)); }); auto write_val_desc1 = "writes a value as little endian to a string at location i. Uses ensure_size to" " make the string twice as long (with extra 0 bytes) if no space. Returns" " new string if resized," " and the index of the location right after where the value was written. The" " _back version writes relative to the end (and writes before the index)"; auto write_val_desc2 = "(see write_int64_le)"; #define WRITEOP(N, T, B, D, S) \ nfr(#N, "string,i,val", "SkI" S, "SI", D, \ [](VM &vm, Value &str, Value &idx, Value &val) { \ return WriteVal(vm, str, idx, val); \ }); WRITEOP(write_int64_le, int64_t, false, write_val_desc1, "I") WRITEOP(write_int32_le, int32_t, false, write_val_desc2, "I") WRITEOP(write_int16_le, int16_t, false, write_val_desc2, "I") WRITEOP(write_int8_le, int8_t, false, write_val_desc2, "I") WRITEOP(write_float64_le, double, false, write_val_desc2, "F") WRITEOP(write_float32_le, float, false, write_val_desc2, "F") WRITEOP(write_int64_le_back, int64_t, true, write_val_desc2, "I") WRITEOP(write_int32_le_back, int32_t, true, write_val_desc2, "I") WRITEOP(write_int16_le_back, int16_t, true, write_val_desc2, "I") WRITEOP(write_int8_le_back, int8_t, true, write_val_desc2, "I") WRITEOP(write_float64_le_back, double, true, write_val_desc2, "F") WRITEOP(write_float32_le_back, float, true, write_val_desc2, "F") nfr("write_substring", "string,i,substr,nullterm", "SkISI", "SI", "writes a substring into another string at i (see also write_int64_le)", [](VM &vm, Value &str, Value &idx, Value &val, Value &term) { return WriteStr(vm, str, idx, val.sval(), term.True()); }); nfr("write_substring_back", "string,i,substr,nullterm", "SkISI", "SI", "", [](VM &vm, Value &str, Value &idx, Value &val, Value &term) { return WriteStr(vm, str, idx, val.sval(), term.True()); }); nfr("compare_substring", "string_a,i_a,string_b,i_b,len", "SISII", "I", "returns if the two substrings are equal (0), or a < b (-1) or a > b (1).", [](VM &vm, Value &str1, Value &idx1, Value &str2, Value &idx2, Value &len) { auto s1 = str1.sval(); auto s2 = str2.sval(); auto i1 = idx1.ival(); auto i2 = idx2.ival(); auto l = len.ival(); if (l < 0 || i1 < 0 || i2 < 0 || i1 + l > s1->len || i2 + l > s2->len) vm.Error("compare_substring: index out of bounds"); auto eq = memcmp(s1->data() + i1, s2->data() + i2, l); return Value(eq); }); auto read_val_desc1 = "reads a value as little endian from a string at location i. The value must be within" " bounds of the string. Returns the value, and the index of the location right after where" " the value was read. The" " _back version reads relative to the end (and reads before the index)"; auto read_val_desc2 = "(see read_int64_le)"; #define READOP(N, T, B, D, S) \ nfr(#N, "string,i", "SI", S "I", D, \ [](VM &vm, Value &str, Value &idx) { return ReadVal(vm, str, idx); }); READOP(read_int64_le, int64_t, false, read_val_desc1, "I") READOP(read_int32_le, int32_t, false, read_val_desc2, "I") READOP(read_int16_le, int16_t, false, read_val_desc2, "I") READOP(read_int8_le, int8_t, false, read_val_desc2, "I") READOP(read_float64_le, double, false, read_val_desc2, "F") READOP(read_float32_le, float, false, read_val_desc2, "F") READOP(read_int64_le_back, int64_t, true, read_val_desc2, "I") READOP(read_int32_le_back, int32_t, true, read_val_desc2, "I") READOP(read_int16_le_back, int16_t, true, read_val_desc2, "I") READOP(read_int8_le_back, int8_t, true, read_val_desc2, "I") READOP(read_float64_le_back, double, true, read_val_desc2, "F") READOP(read_float32_le_back, float, true, read_val_desc2, "F") auto read_field_desc1 = "reads a flatbuffers field from a string at table location tablei, field vtable offset vo," " and default value def. The value must be within" " bounds of the string. Returns the value (or default if the field was not present)"; auto read_field_desc2 = "(see flatbuffers_field_int64)"; #define READFOP(N, T, D, S) \ nfr(#N, "string,tablei,vo,def", "SII" S, S, D, \ [](VM &vm, Value &str, Value &idx, Value &vidx, Value &def) { \ auto val = ReadField(vm, str, idx, vidx, def); \ return Value(val); \ }); READFOP(flatbuffers_field_int64, int64_t, read_field_desc1, "I") READFOP(flatbuffers_field_int32, int32_t, read_field_desc2, "I") READFOP(flatbuffers_field_int16, int16_t, read_field_desc2, "I") READFOP(flatbuffers_field_int8, int8_t, read_field_desc2, "I") READFOP(flatbuffers_field_float64, double, read_field_desc2, "F") READFOP(flatbuffers_field_float32, float, read_field_desc2, "F") nfr("flatbuffers_field_string", "string,tablei,vo", "SII", "S", "reads a flatbuffer string field, returns \"\" if not present", [](VM &vm, Value &str, Value &idx, Value &vidx) { auto fi = ReadField(vm, str, idx, vidx, Value(0)).ival(); auto ret = Value(GetString(vm, fi, str.sval())); return ret; }); nfr("flatbuffers_field_vector_len", "string,tablei,vo", "SII", "I", "reads a flatbuffer vector field length, or 0 if not present", [](VM &vm, Value &str, Value &idx, Value &vidx) { auto fi = ReadField(vm, str, idx, vidx, Value(0)).ival(); Value ret(fi ? Read(vm, fi, str.sval()) : 0); return ret; }); nfr("flatbuffers_field_vector", "string,tablei,vo", "SII", "I", "returns a flatbuffer vector field element start, or 0 if not present", [](VM &vm, Value &str, Value &idx, Value &vidx) { auto fi = ReadField(vm, str, idx, vidx, Value(0)).ival(); Value ret(fi ? fi + (intp)sizeof(flatbuffers::uoffset_t) : 0); return ret; }); nfr("flatbuffers_field_table", "string,tablei,vo", "SII", "I", "returns a flatbuffer table field start, or 0 if not present", [](VM &vm, Value &str, Value &idx, Value &vidx) { auto ret = ReadField(vm, str, idx, vidx, Value(0)); return ret; }); nfr("flatbuffers_field_struct", "string,tablei,vo", "SII", "I", "returns a flatbuffer struct field start, or 0 if not present", [](VM &vm, Value &str, Value &idx, Value &vidx) { auto ret = ReadField(vm, str, idx, vidx, Value(0)); return ret; }); nfr("flatbuffers_indirect", "string,index", "SI", "I", "returns a flatbuffer offset at index relative to itself", [](VM &vm, Value &str, Value &idx) { auto off = Read(vm, idx.ival(), str.sval()); return Value(off + idx.ival()); }); nfr("flatbuffers_string", "string,index", "SI", "S", "returns a flatbuffer string whose offset is at given index", [](VM &vm, Value &str, Value &idx) { auto off = Read(vm, idx.ival(), str.sval()); auto ret = GetString(vm, off + idx.ival(), str.sval()); return Value(ret); }); nfr("flatbuffers_binary_to_json", "schemas,binary,includedirs", "SSS]", "SS?", "returns a JSON string generated from the given binary and corresponding schema." "if there was an error parsing the schema, the error will be in the second return" "value, or nil for no error", [](VM &vm, Value &schema, Value &binary, Value &includes) { flatbuffers::Parser parser; auto err = ParseSchemas(vm, parser, schema, includes); string json; if (!err.True() && !GenerateText(parser, binary.sval()->data(), &json)) { err = vm.NewString("unable to generate text for FlatBuffer binary"); } vm.Push(vm.NewString(json)); return err; }); nfr("flatbuffers_json_to_binary", "schema,json,includedirs", "SSS]", "SS?", "returns a binary flatbuffer generated from the given json and corresponding schema." "if there was an error parsing the schema, the error will be in the second return" "value, or nil for no error", [](VM &vm, Value &schema, Value &json, Value &includes) { flatbuffers::Parser parser; auto err = ParseSchemas(vm, parser, schema, includes); string binary; if (!err.True()) { if (!parser.Parse(json.sval()->data())) { err = vm.NewString(parser.error_); } else { binary.assign((const char *)parser.builder_.GetBufferPointer(), parser.builder_.GetSize()); } } vm.Push(vm.NewString(binary)); return err; }); } // AddFile } treesheets-1.0.2/lobster/src/font.cpp000066400000000000000000000136261352107072600176130ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/glinterface.h" #include "lobster/fontrenderer.h" using namespace lobster; map fontcache; BitmapFont *curfont = nullptr; int curfontsize = -1; float curoutlinesize = 0; int maxfontsize = 128; map> loadedfaces; OutlineFont *curface = nullptr; string curfacename; Shader *texturedshader = nullptr; void CullFonts() { for(auto it = fontcache.begin(); it != fontcache.end(); ) { if (it->second->usedcount) { it->second->usedcount = 0; it++; } else { if (curfont == it->second) curfont = nullptr; delete it->second; fontcache.erase(it++); } } } void FontCleanup() { for (auto e : fontcache) delete e.second; fontcache.clear(); curfont = nullptr; for (auto e : loadedfaces) delete e.second; loadedfaces.clear(); curface = nullptr; FTClosedown(); } void AddFont(NativeRegistry &nfr) { nfr("gl_set_font_name", "filename", "S", "B", "sets a freetype/OTF/TTF font as current (and loads it from disk the first time). returns" " true if success.", [](VM &vm, Value &fname) { extern void TestGL(VM &vm); TestGL(vm); auto piname = string(fname.sval()->strv()); auto faceit = loadedfaces.find(piname); if (faceit != loadedfaces.end()) { curface = faceit->second; curfacename = piname; return Value(true); } texturedshader = LookupShader("textured"); assert(texturedshader); curface = LoadFont(piname); if (curface) { curfacename = piname; loadedfaces[piname] = curface; return Value(true); } else { return Value(false); } }); nfr("gl_set_font_size", "size,outlinesize", "IF?", "B", "sets the font for rendering into this fontsize (in pixels). caches into a texture first" " time this size is used, flushes from cache if this size is not used an entire frame. font" " rendering will look best if using 1:1 pixels (careful with gl_scale/gl_translate)." " an optional outlinesize will give the font a black outline." " returns true if success", [](VM &vm, Value &fontsize, Value &outlinesize) { if (!curface) vm.BuiltinError("gl_set_font_size: no current font set with gl_set_font_name"); float osize = min(16.0f, max(0.0f, outlinesize.fltval())); int size = max(1, fontsize.intval()); int csize = min(size, maxfontsize); if (osize > 0 && csize != size) osize = osize * csize / size; string fontname = curfacename; fontname += to_string(csize); fontname += "_"; fontname += to_string_float(osize); curfontsize = size; curoutlinesize = osize; auto fontelem = fontcache.find(fontname); if (fontelem != fontcache.end()) { curfont = fontelem->second; return Value(true); } curfont = new BitmapFont(curface, csize, osize); fontcache.insert({ fontname, curfont }); return Value(true); }); nfr("gl_set_max_font_size", "size", "I", "", "sets the max font size to render to bitmaps. any sizes specified over that by setfontsize" " will still work but cause scaled rendering. default 128", [](VM &, Value &fontsize) { maxfontsize = fontsize.intval(); return Value(); }); nfr("gl_get_font_size", "", "", "I", "the current font size", [](VM &) { return Value(curfontsize); }); nfr("gl_get_outline_size", "", "", "F", "the current font size", [](VM &) { return Value(curoutlinesize); }); nfr("gl_text", "text", "S", "Sb", "renders a text with the current font (at the current coordinate origin)", [](VM &vm, Value &s) { auto f = curfont; if (!f) return vm.BuiltinError("gl_text: no font size set"); if (!s.sval()->len) return s; float4x4 oldobject2view; if (curfontsize > maxfontsize) { oldobject2view = otransforms.object2view; otransforms.object2view *= scaling(curfontsize / float(maxfontsize)); } SetTexture(0, f->tex); texturedshader->Set(); f->RenderText(s.sval()->strv()); if (curfontsize > maxfontsize) otransforms.object2view = oldobject2view; return s; }); nfr("gl_text_size", "text", "S", "I}:2", "the x/y size in pixels the given text would need", [](VM &vm) { auto f = curfont; if (!f) vm.BuiltinError("gl_text_size: no font size set"); auto size = f->TextSize(vm.Pop().sval()->strv()); if (curfontsize > maxfontsize) { size = fceil(float2(size) * float(curfontsize) / float(maxfontsize)); } vm.PushVec(size); }); nfr("gl_get_glyph_name", "i", "I", "S", "the name of a glyph index, or empty string if the font doesn\'t have names", [](VM &vm, Value &i) { return Value(vm.NewString(curface ? curface->GetName((uint)i.ival()) : "")); }); nfr("gl_get_char_code", "name", "S", "I", "the char code of a glyph by specifying its name, or 0 if it can not be found" " (or if the font doesn\'t have names)", [](VM &, Value &n) { return Value(curface ? curface->GetCharCode(n.sval()->strv()) : 0); }); } // AddFont treesheets-1.0.2/lobster/src/fontrenderer.cpp000066400000000000000000000214261352107072600213370ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/glinterface.h" #include "lobster/fontrenderer.h" #define USE_FREETYPE #ifdef USE_FREETYPE #include #include FT_FREETYPE_H #include FT_STROKER_H #else #define STB_TRUETYPE_IMPLEMENTATION #define STBTT_STATIC #include "stb/stb_truetype.h" #endif #include "lobster/unicode.h" FT_Library library = nullptr; BitmapFont::~BitmapFont() { DeleteTexture(tex); } BitmapFont::BitmapFont(OutlineFont *_font, int _size, float _osize) : size(_size), outlinesize(_osize), font(_font) {} bool BitmapFont::CacheChars(string_view text) { usedcount++; font->EnsureCharsPresent(text); if (positions.size() == font->unicodetable.size()) return true; DeleteTexture(tex); positions.clear(); if (FT_Set_Pixel_Sizes((FT_Face)font->fthandle, 0, size)) return false; auto face = (FT_Face)font->fthandle; const int outline_passes = outlinesize > 0 ? 2 : 1; FT_Stroker stroker = nullptr; if (outline_passes > 1) { FT_Stroker_New(library, &stroker); FT_Stroker_Set(stroker, FT_Fixed(outlinesize * 64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); } const int margin = 3; int texh = 0; int texw = MaxTextureSize(); int max_descent = 0; int max_ascent = 0; int space_on_line = texw - margin, lines = 1; for (int i : font->unicodetable) { auto char_index = FT_Get_Char_Index(face, i); FT_Load_Glyph(face, char_index, FT_LOAD_DEFAULT); // FIXME: Can we avoid doing this twice? FT_Glyph glyph; FT_Get_Glyph(face->glyph, &glyph); if (stroker) FT_Glyph_StrokeBorder(&glyph, stroker, false, true); FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, nullptr, true); FT_BitmapGlyph bglyph = (FT_BitmapGlyph)glyph; auto advance = (face->glyph->metrics.horiAdvance >> 6) + margin; if (advance > space_on_line) { space_on_line = texw - margin; ++lines; } space_on_line -= advance; max_ascent = max(bglyph->top, max_ascent); max_descent = max((int)bglyph->bitmap.rows - bglyph->top, max_descent); FT_Done_Glyph(glyph); } height = max_ascent + max_descent; auto needed_image_height = (max_ascent + max_descent + margin) * lines + margin; texh = 1; while (texh < needed_image_height) texh *= 2; uchar *image = new uchar[texh * texw * 4]; memset(image, 0, texh * texw * 4); for (int pass = 0; pass < outline_passes; pass++) { auto x = margin, y = margin + max_ascent; for (int i : font->unicodetable) { auto char_index = FT_Get_Char_Index(face, i); FT_Load_Glyph(face, char_index, FT_LOAD_DEFAULT); FT_Glyph glyph; FT_Get_Glyph(face->glyph, &glyph); if (!pass && stroker) FT_Glyph_StrokeBorder(&glyph, stroker, false, true); FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, nullptr, true); FT_BitmapGlyph bglyph = (FT_BitmapGlyph)glyph; auto advance = (face->glyph->metrics.horiAdvance >> 6) + margin; if (advance > texw - x) { x = margin; y += (max_ascent + max_descent + margin); } if (!pass) positions.push_back(int3(x, y - max_ascent, advance - margin)); for (int row = 0; row < (int)bglyph->bitmap.rows; ++row) { for (int pixel = 0; pixel < (int)bglyph->bitmap.width; ++pixel) { auto p = image + ((x + bglyph->left + pixel) + (y - bglyph->top + row) * texw) * 4; auto alpha = bglyph->bitmap.buffer[pixel + row * bglyph->bitmap.pitch]; if (outline_passes > 1) { if (!pass) { *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = alpha; } else { auto cur_alpha = p[3]; auto max_alpha = max(cur_alpha, alpha); auto col = (uchar)mix(0x00, 0xFF, alpha / 255.0f); *p++ = col; *p++ = col; *p++ = col; *p++ = max_alpha; } } else { *p++ = 0xFF; // FIXME: wastefull *p++ = 0xFF; *p++ = 0xFF; *p++ = alpha; } } } x += advance; FT_Done_Glyph(glyph); } } if (stroker) FT_Stroker_Done(stroker); tex = CreateTexture(image, int2(texw, texh).data(), TF_CLAMP | TF_NOMIPMAP); delete[] image; return true; } void BitmapFont::RenderText(string_view text) { if (!CacheChars(text)) return; struct PT { float3 p; float2 t; }; int len = StrLenUTF8(text); if (len <= 0) return; vector vbuf(len * 4); vector ibuf(len * 6); auto x = 0.0f; auto y = 0.0f; float fontheighttex = height / float(tex.size.y); int idx = 0; for (int i = 0; i < len; i++) { int c = FromUTF8(text); int3 &pos = positions[font->unicodemap[c]]; float x1 = pos.x / float(tex.size.x); float x2 = (pos.x + pos.z) / float(tex.size.x); float y1 = pos.y / float(tex.size.y); float advance = float(pos.z); int j = i * 4; auto &v0 = vbuf[j + 0]; v0.t = float2(x1, y1); v0.p = float3(x, y, 0); auto &v1 = vbuf[j + 1]; v1.t = float2(x1, y1 + fontheighttex); v1.p = float3(x, y + height, 0); auto &v2 = vbuf[j + 2]; v2.t = float2(x2, y1 + fontheighttex); v2.p = float3(x + advance, y + height, 0); auto &v3 = vbuf[j + 3]; v3.t = float2(x2, y1); v3.p = float3(x + advance, y, 0); ibuf[idx++] = j + 0; ibuf[idx++] = j + 1; ibuf[idx++] = j + 2; ibuf[idx++] = j + 2; ibuf[idx++] = j + 3; ibuf[idx++] = j + 0; x += advance; } SetTexture(0, tex); RenderArraySlow(PRIM_TRIS, make_span(vbuf), "PT", make_span(ibuf)); } const int2 BitmapFont::TextSize(string_view text) { if (!CacheChars(text)) return int2_0; auto x = 0; for (;;) { int c = FromUTF8(text); if (c <= 0) return int2(x, height); x += positions[font->unicodemap[c]].z; } } OutlineFont *LoadFont(string_view name) { FT_Error err = 0; if (!library) err = FT_Init_FreeType(&library); if (!err) { string fbuf; if (LoadFile(name, &fbuf) >= 0) { FT_Face face; err = FT_New_Memory_Face(library, (const FT_Byte *)fbuf.c_str(), (FT_Long)fbuf.length(), 0, &face); if (!err) return new OutlineFont(face, fbuf); } } return nullptr; } OutlineFont::~OutlineFont() { FT_Done_Face((FT_Face)fthandle); } string OutlineFont::GetName(uint i) { char buf[256]; FT_Get_Glyph_Name((FT_Face)fthandle, i, buf, sizeof(buf)); return buf; } uint OutlineFont::GetCharCode(string_view name) { auto glyphi = FT_Get_Name_Index((FT_Face)fthandle, (char *)null_terminated(name)); if (!glyphi) return 0; if (glyph_to_char.empty()) { uint cgi = 0; auto c = FT_Get_First_Char((FT_Face)fthandle, &cgi); while (cgi) { glyph_to_char[cgi] = c; c = FT_Get_Next_Char((FT_Face)fthandle, c, &cgi); } } return glyph_to_char[glyphi]; } bool OutlineFont::EnsureCharsPresent(string_view utf8str) { bool anynew = false; for (;;) { int uc = FromUTF8(utf8str); if (uc <= 0) break; auto it = unicodemap.find(uc); if (it == unicodemap.end()) { anynew = true; unicodemap[uc] = (int)unicodetable.size(); unicodetable.push_back(uc); } } return anynew; } void FTClosedown() { if (library) FT_Done_FreeType(library); library = nullptr; } treesheets-1.0.2/lobster/src/glgeom.cpp000066400000000000000000000335561352107072600201230ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/glinterface.h" #include "lobster/glincludes.h" uint GenBO_(GLenum type, size_t bytesize, const void *data) { uint bo; GL_CALL(glGenBuffers(1, &bo)); GL_CALL(glBindBuffer(type, bo)); GL_CALL(glBufferData(type, bytesize, data, GL_STATIC_DRAW)); return bo; } void DeleteBO(uint id) { GL_CALL(glDeleteBuffers(1, &id)); } size_t AttribsSize(string_view fmt) { size_t size = 0; for (auto c : fmt) { switch (c) { case 'P': case 'N': size += 12; break; case 'p': case 'n': case 'T': size += 8; break; case 'C': case 'W': case 'I': size += 4; break; default: assert(0); } } return size; } GLenum GetPrimitive(Primitive prim) { switch (prim) { default: assert(0); case PRIM_TRIS: return GL_TRIANGLES; case PRIM_FAN: return GL_TRIANGLE_FAN; case PRIM_LOOP: return GL_LINE_LOOP; case PRIM_POINT: return GL_POINTS; } } Surface::Surface(span indices, Primitive _prim) : numidx(indices.size()), prim(_prim) { ibo = GenBO(GL_ELEMENT_ARRAY_BUFFER, indices); } void Surface::Render(Shader *sh) { sh->SetTextures(textures); GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)); GL_CALL(glDrawElements(GetPrimitive(prim), (GLsizei)numidx, GL_UNSIGNED_INT, 0)); } Surface::~Surface() { GL_CALL(glDeleteBuffers(1, &ibo)); } void Geometry::Init(const void *verts1, const void *verts2) { vbo1 = GenBO_(GL_ARRAY_BUFFER, vertsize1 * nverts, verts1); if (verts2) vbo2 = GenBO_(GL_ARRAY_BUFFER, vertsize2 * nverts, verts2); GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vbo1)); GL_CALL(glGenVertexArrays(1, &vao)); GL_CALL(glBindVertexArray(vao)); size_t offset = 0; size_t vs = vertsize1; for (auto attr : fmt) { switch (attr) { #define SETATTRIB(idx, comps, type, norm, size) \ GL_CALL(glEnableVertexAttribArray(idx)); \ GL_CALL(glVertexAttribPointer(idx, comps, type, norm, (GLsizei)vs, (void *)offset)); \ offset += size; \ break; case 'P': SETATTRIB(0, 3, GL_FLOAT, false, 12) case 'p': SETATTRIB(0, 2, GL_FLOAT, false, 8) case 'N': SETATTRIB(1, 3, GL_FLOAT, false, 12) case 'n': SETATTRIB(1, 2, GL_FLOAT, false, 8) case 'T': SETATTRIB(2, 2, GL_FLOAT, false, 8) case 'C': SETATTRIB(3, 4, GL_UNSIGNED_BYTE, true, 4) case 'W': SETATTRIB(4, 4, GL_UNSIGNED_BYTE, true, 4) case 'I': SETATTRIB(5, 4, GL_UNSIGNED_BYTE, false, 4) default: LOG_ERROR("unknown attribute type: ", string() + attr); assert(false); } if (vbo2) { GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vbo2)); vs = vertsize2; offset = 0; } } GL_CALL(glBindVertexArray(0)); } void Geometry::RenderSetup() { GL_CALL(glBindVertexArray(vao)); } Geometry::~Geometry() { GL_CALL(glDeleteBuffers(1, &vbo1)); if (vbo2) GL_CALL(glDeleteBuffers(1, &vbo2)); GL_CALL(glDeleteVertexArrays(1, &vao)); } void Geometry::BindAsSSBO(Shader *sh, string_view name) { UniformBufferObject(sh, nullptr, 0, name, true, vbo1); assert(!vbo2); } void Mesh::Render(Shader *sh) { if (prim == PRIM_POINT) SetPointSprite(pointsize); sh->Set(); if (numbones && numframes) { int frame1 = ffloor(curanim); int frame2 = frame1 + 1; float frameoffset = curanim - frame1; float3x4 *mat1 = &mats[(frame1 % numframes) * numbones], *mat2 = &mats[(frame2 % numframes) * numbones]; auto outframe = new float3x4[numbones]; for(int i = 0; i < numbones; i++) outframe[i] = mix(mat1[i], mat2[i], frameoffset); sh->SetAnim(outframe, numbones); delete[] outframe; } geom->RenderSetup(); if (surfs.size()) { for (auto s : surfs) s->Render(sh); } else { GL_CALL(glDrawArrays(GetPrimitive(prim), 0, (GLsizei)geom->nverts)); } } Mesh::~Mesh() { delete geom; for (auto s : surfs) delete s; if (mats) delete[] mats; } bool Geometry::WritePLY(string &s, size_t nindices) { #ifndef PLATFORM_ES3 s += cat("ply\n" "format binary_little_endian 1.0\n" "element vertex ", nverts, "\n"); for (auto fc : fmt) { switch (fc) { case 'P': s += "property float x\nproperty float y\nproperty float z\n"; break; case 'p': s += "property float x\nproperty float y\n"; break; case 'N': s += "property float nx\nproperty float ny\nproperty float nz\n"; break; case 'n': s += "property float nx\nproperty float ny\n"; break; case 'T': s += "property float u\nproperty float v\n"; break; case 'C': s += "property uchar red\nproperty uchar green\n" "property uchar blue\nproperty uchar alpha\n"; break; case 'W': s += "property uchar wa\nproperty uchar wb\n" "property uchar wc\nproperty uchar wd\n"; break; case 'I': s += "property uchar ia\nproperty uchar ib\n" "property uchar ic\nproperty uchar id\n"; break; default: assert(0); } } s += cat("element face ", nindices / 3, "\n" "property list int int vertex_index\n" "end_header\n"); vector vdata(nverts * vertsize1); GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vbo1)); GL_CALL(glGetBufferSubData(GL_ARRAY_BUFFER, 0, vdata.size(), vdata.data())); s.insert(s.end(), vdata.begin(), vdata.end()); return true; #else (void)s; (void)nindices; (void)vertsize1; return false; #endif } void Surface::WritePLY(string &s) { #ifndef PLATFORM_ES3 vector idata(numidx / 3 * 4); GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)); GL_CALL(glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, numidx * sizeof(int), idata.data())); for (int i = (int)numidx - 3; i >= 0; i -= 3) { auto di = i / 3 * 4; idata[di + 3] = idata[i + 1]; idata[di + 2] = idata[i + 2]; // we cull GL_FRONT idata[di + 1] = idata[i + 0]; idata[di] = 3; } s.insert(s.end(), (char *)idata.data(), ((char *)idata.data()) + idata.size() * sizeof(int)); #else (void)s; #endif } bool Mesh::SaveAsPLY(string_view filename) { size_t nindices = 0; for (auto &surf : surfs) nindices += surf->numidx; string s; if (!geom->WritePLY(s, nindices)) return false; for (auto &surf : surfs) surf->WritePLY(s); return WriteFile(filename, true, s); } void SetPointSprite(float scale) { pointscale = scale * custompointscale; #ifdef PLATFORM_ES3 // glEnable(GL_POINT_SPRITE_OES); // glTexEnvi(GL_POINT_SPRITE_OES, GL_COORD_REPLACE_OES, GL_TRUE); #else #ifndef __APPLE__ // GL_CALL(glEnable(GL_POINT_SPRITE)); // GL_CALL(glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE)); #endif GL_CALL(glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); #endif } void RenderArray(Primitive prim, Geometry *geom, uint ibo, size_t tcount) { GLenum glprim = GetPrimitive(prim); geom->RenderSetup(); if (ibo) { GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)); GL_CALL(glDrawElements(glprim, (GLsizei)tcount, GL_UNSIGNED_INT, 0)); } else { GL_CALL(glDrawArrays(glprim, 0, (GLsizei)geom->nverts)); } } void GeometryCache::RenderUnitSquare(Shader *sh, Primitive prim, bool centered) { if (!quadgeom[centered]) { static SpriteVert vb_square[4] = { SpriteVert{ float2(0, 0), float2(0, 0) }, SpriteVert{ float2(0, 1), float2(0, 1) }, SpriteVert{ float2(1, 1), float2(1, 1) }, SpriteVert{ float2(1, 0), float2(1, 0) }, }; static SpriteVert vb_square_centered[4] = { SpriteVert{ float2(-1, -1), float2(0, 0) }, SpriteVert{ float2(-1, 1), float2(0, 1) }, SpriteVert{ float2( 1, 1), float2(1, 1) }, SpriteVert{ float2( 1, -1), float2(1, 0) }, }; quadgeom[centered] = new Geometry(make_span(centered ? vb_square_centered : vb_square, 4), "pT"); } sh->Set(); RenderArray(prim, quadgeom[centered]); } void GeometryCache::RenderQuad(Shader *sh, Primitive prim, bool centered, const float4x4 &trans) { Transform2D(trans, [&]() { RenderUnitSquare(sh, prim, centered); }); } void GeometryCache::RenderLine2D(Shader *sh, Primitive prim, const float3 &v1, const float3 &v2, float thickness) { auto v = (v2 - v1) / 2; auto len = length(v); auto vnorm = v / len; auto trans = translation(v1 + v) * rotationZ(vnorm.xy()) * float4x4(float4(len, thickness / 2, 1, 1)); RenderQuad(sh, prim, true, trans); } void GeometryCache::RenderLine3D(Shader *sh, const float3 &v1, const float3 &v2, const float3 &/*campos*/, float thickness) { GL_CALL(glDisable(GL_CULL_FACE)); // An exception in 3d mode. // FIXME: need to rotate the line also to make it face the camera. //auto camvec = normalize(campos - (v1 + v2) / 2); auto v = v2 - v1; auto vq = quatfromtwovectors(normalize(v), float3_x); //auto sq = quatfromtwovectors(camvec, float3_z); auto trans = translation((v1 + v2) / 2) * float3x3to4x4(rotation(vq)) * // FIXME: cheaper? float4x4(float4(length(v) / 2, thickness, 1, 1)); RenderQuad(sh, PRIM_FAN, true, trans); GL_CALL(glEnable(GL_CULL_FACE)); } void GeometryCache::RenderUnitCube(Shader *sh, int inside) { struct cvert { float3 pos; float3 normal; float2 tc; }; if (!cube_geom[inside]) { static float3 normals[] = { float3(1, 0, 0), float3(-1, 0, 0), float3(0, 1, 0), float3( 0, -1, 0), float3(0, 0, 1), float3( 0, 0, -1), }; static float2 tcs[] = { float2(0, 0), float2(1, 0), float2(1, 1), float2(0, 1) }; static const char *faces[6] = { "4576", "0231", "2673", "0154", "1375", "0462" }; static int indices[2][6] = { { 0, 1, 3, 1, 2, 3 }, { 0, 3, 1, 1, 3, 2 } }; vector verts; vector triangles; for (int n = 0; n < 6; n++) { auto face = faces[n]; for (int i = 0; i < 6; i++) triangles.push_back(indices[inside][i] + (int)verts.size()); for (int vn = 0; vn < 4; vn++) { cvert vert; for (int d = 0; d < 3; d++) { vert.pos[d] = float((face[vn] & (1 << (2 - d))) != 0); } vert.normal = normals[n]; vert.tc = tcs[vn]; verts.push_back(vert); } } cube_geom[inside] = new Geometry(make_span(verts), "PNT"); cube_ibo[inside] = GenBO(GL_ELEMENT_ARRAY_BUFFER, make_span(triangles)); } sh->Set(); RenderArray(PRIM_TRIS, cube_geom[inside], cube_ibo[inside], 36); } void GeometryCache::RenderCircle(Shader *sh, Primitive prim, int segments, float radius) { assert(segments >= 3); auto &geom = circlevbos[segments]; if (!geom) { vector vbuf(segments); float step = PI * 2 / segments; for (int i = 0; i < segments; i++) { // + 1 to reduce "aliasing" from exact 0 / 90 degrees points vbuf[i] = float3(sinf(i * step + 1), cosf(i * step + 1), 0); } geom = new Geometry(make_span(vbuf), "P"); } Transform2D(float4x4(float4(float2_1 * radius, 1)), [&]() { sh->Set(); RenderArray(prim, geom); }); } void GeometryCache::RenderOpenCircle(Shader *sh, int segments, float radius, float thickness) { assert(segments >= 3); auto &vibo = opencirclevbos[{ segments, thickness }]; auto nverts = segments * 2; auto nindices = segments * 6; if (!vibo.first) { vector vbuf(nverts); vector ibuf(nindices); float step = PI * 2 / segments; float inner = 1 - thickness; for (int i = 0; i < segments; i++) { // + 1 to reduce "aliasing" from exact 0 / 90 degrees points float x = sinf(i * step + 1); float y = cosf(i * step + 1); vbuf[i * 2 + 0] = float3(x, y, 0); vbuf[i * 2 + 1] = float3(x * inner, y * inner, 0); ibuf[i * 6 + 0] = i * 2 + 0; ibuf[i * 6 + 1] = ((i + 1) * 2 + 0) % nverts; ibuf[i * 6 + 2] = i * 2 + 1; ibuf[i * 6 + 3] = i * 2 + 1; ibuf[i * 6 + 4] = ((i + 1) * 2 + 1) % nverts; ibuf[i * 6 + 5] = ((i + 1) * 2 + 0) % nverts; } vibo.first = new Geometry(make_span(vbuf), "P"); vibo.second = GenBO(GL_ELEMENT_ARRAY_BUFFER, make_span(ibuf)); } Transform2D(float4x4(float4(float2_1 * radius, 1)), [&]() { sh->Set(); RenderArray(PRIM_TRIS, vibo.first, vibo.second, nindices); }); } GeometryCache::~GeometryCache() { for (int i = 0; i < 2; i++) { delete quadgeom[i]; delete cube_geom[i]; if (cube_ibo[i]) DeleteBO(cube_ibo[i]); } for (auto &p : circlevbos) delete p.second; for (auto &p : opencirclevbos) delete p.second.first; } treesheets-1.0.2/lobster/src/glloadiqm.cpp000066400000000000000000000322111352107072600206050ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // IQM model loader, see: http://sauerbraten.org/iqm/ https://github.com/lsalzman/iqm #include "lobster/stdafx.h" #include "lobster/glinterface.h" #define IQM_MAGIC "INTERQUAKEMODEL" #define IQM_VERSION 2 struct iqmheader { char magic[16]; uint version; uint filesize; uint flags; uint num_text, ofs_text; uint num_meshes, ofs_meshes; uint num_vertexarrays, num_vertexes, ofs_vertexarrays; uint num_triangles, ofs_triangles, ofs_adjacency; uint num_joints, ofs_joints; uint num_poses, ofs_poses; uint num_anims, ofs_anims; uint num_frames, num_framechannels, ofs_frames, ofs_bounds; uint num_comment, ofs_comment; uint num_extensions, ofs_extensions; }; struct iqmmesh { uint name; uint material; uint first_vertex, num_vertexes; uint first_triangle, num_triangles; }; enum { IQM_POSITION = 0, IQM_TEXCOORD = 1, IQM_NORMAL = 2, IQM_TANGENT = 3, IQM_BLENDINDEXES = 4, IQM_BLENDWEIGHTS = 5, IQM_COLOR = 6, IQM_CUSTOM = 0x10 }; enum { IQM_BYTE = 0, IQM_UBYTE = 1, IQM_SHORT = 2, IQM_USHORT = 3, IQM_INT = 4, IQM_UINT = 5, IQM_HALF = 6, IQM_FLOAT = 7, IQM_DOUBLE = 8, }; struct iqmtriangle { uint vertex[3]; }; struct iqmjoint { uint name; int parent; float translate[3], rotate[4], scale[3]; }; struct iqmpose { int parent; uint mask; float channeloffset[10]; float channelscale[10]; }; struct iqmanim { uint name; uint first_frame, num_frames; float framerate; uint flags; }; enum { IQM_LOOP = 1<<0 }; struct iqmvertexarray { uint type; uint flags; uint format; uint size; uint offset; }; struct iqmbounds { float bbmin[3], bbmax[3]; float xyradius, radius; }; inline bool islittleendian() { static const int val = 1; return *(const uchar *)&val != 0; } inline ushort endianswap16(ushort n) { return (n<<8) | (n>>8); } inline uint endianswap32(uint n) { return (n<<24) | (n>>24) | ((n>>8)&0xFF00) | ((n<<8)&0xFF0000); } template inline T endianswap(T n) { union { T t; uint i; } conv; conv.t = n; conv.i = endianswap32(conv.i); return conv.t; } template<> inline ushort endianswap(ushort n) { return endianswap16(n); } template<> inline short endianswap(short n) { return endianswap16(n); } template<> inline uint endianswap(uint n) { return endianswap32(n); } template<> inline int endianswap(int n) { return endianswap32(n); } template inline void endianswap(T *buf, int len) { for(T *end = &buf[len]; buf < end; buf++) *buf = endianswap(*buf); } template inline T lilswap(T n) { return islittleendian() ? n : endianswap(n); } template inline void lilswap(T *buf, int len) { if(!islittleendian()) endianswap(buf, len); } template inline T bigswap(T n) { return islittleendian() ? endianswap(n) : n; } template inline void bigswap(T *buf, int len) { if(islittleendian()) endianswap(buf, len); } template T getval(FILE *f) { T n; return fread(&n, 1, sizeof(n), f) == sizeof(n) ? n : 0; } template T getlil(FILE *f) { return lilswap(getval(f)); } template T getbig(FILE *f) { return bigswap(getval(f)); } static string filebuffer; static float *inposition = nullptr, *innormal = nullptr, *intangent = nullptr, *intexcoord = nullptr; static uchar *inblendindex = nullptr, *inblendweight = nullptr, *incolor = nullptr; static int nummeshes = 0, numtris = 0, numverts = 0, numjoints = 0, numframes = 0, numanims = 0; static iqmtriangle *tris = nullptr, *adjacency = nullptr; static iqmmesh *meshes = nullptr; static const char **textures = nullptr; static iqmjoint *joints = nullptr; static iqmpose *poses = nullptr; static iqmanim *anims = nullptr; static iqmbounds *bounds = nullptr; static float3x4 *baseframe = nullptr, *inversebaseframe = nullptr, *frames = nullptr; void cleanupiqm() { delete[] textures; textures = nullptr; delete[] baseframe; baseframe = nullptr; delete[] inversebaseframe; inversebaseframe = nullptr; delete[] frames; frames = nullptr; string().swap(filebuffer); inposition = nullptr; innormal = nullptr; intangent = nullptr; intexcoord = nullptr; inblendindex = nullptr; inblendweight = nullptr; incolor = nullptr; nummeshes = 0; numtris = 0; numverts = 0; numjoints = 0; numframes = 0; numanims = 0; tris = nullptr; adjacency = nullptr; meshes = nullptr; joints = nullptr; poses = nullptr; anims = nullptr; bounds = nullptr; } bool loadiqmmeshes(const iqmheader &hdr, const char *buf) { lilswap((uint *)&buf[hdr.ofs_vertexarrays], hdr.num_vertexarrays*sizeof(iqmvertexarray)/sizeof(uint)); lilswap((uint *)&buf[hdr.ofs_triangles], hdr.num_triangles*sizeof(iqmtriangle)/sizeof(uint)); lilswap((uint *)&buf[hdr.ofs_meshes], hdr.num_meshes*sizeof(iqmmesh)/sizeof(uint)); lilswap((uint *)&buf[hdr.ofs_joints], hdr.num_joints*sizeof(iqmjoint)/sizeof(uint)); if(hdr.ofs_adjacency) lilswap((uint *)&buf[hdr.ofs_adjacency], hdr.num_triangles*sizeof(iqmtriangle)/sizeof(uint)); nummeshes = hdr.num_meshes; numtris = hdr.num_triangles; numverts = hdr.num_vertexes; numjoints = hdr.num_joints; textures = new const char *[nummeshes]; memset(textures, 0, nummeshes*sizeof(const char *)); const char *str = hdr.ofs_text ? &buf[hdr.ofs_text] : ""; iqmvertexarray *vas = (iqmvertexarray *)&buf[hdr.ofs_vertexarrays]; for(int i = 0; i < (int)hdr.num_vertexarrays; i++) { iqmvertexarray &va = vas[i]; switch(va.type) { case IQM_POSITION: if(va.format != IQM_FLOAT || va.size != 3) return false; inposition = (float *)&buf[va.offset]; lilswap(inposition, 3*hdr.num_vertexes); break; case IQM_NORMAL: if(va.format != IQM_FLOAT || va.size != 3) return false; innormal = (float *)&buf[va.offset]; lilswap(innormal,3*hdr.num_vertexes); break; case IQM_TANGENT: if(va.format != IQM_FLOAT || va.size != 4) return false; intangent = (float *)&buf[va.offset]; lilswap(intangent, 4*hdr.num_vertexes); break; case IQM_TEXCOORD: if(va.format != IQM_FLOAT || va.size != 2) return false; intexcoord = (float *)&buf[va.offset]; lilswap(intexcoord, 2*hdr.num_vertexes); break; case IQM_BLENDINDEXES: if(va.format != IQM_UBYTE || va.size != 4) return false; inblendindex = (uchar *)&buf[va.offset]; break; case IQM_BLENDWEIGHTS: if(va.format != IQM_UBYTE || va.size != 4) return false; inblendweight = (uchar *)&buf[va.offset]; break; case IQM_COLOR: if(va.format != IQM_UBYTE || va.size != 4) return false; incolor = (uchar *)&buf[va.offset]; break; } } tris = (iqmtriangle *)&buf[hdr.ofs_triangles]; meshes = (iqmmesh *)&buf[hdr.ofs_meshes]; joints = (iqmjoint *)&buf[hdr.ofs_joints]; if(hdr.ofs_adjacency) adjacency = (iqmtriangle *)&buf[hdr.ofs_adjacency]; baseframe = new float3x4[hdr.num_joints]; inversebaseframe = new float3x4[hdr.num_joints]; for(int i = 0; i < (int)hdr.num_joints; i++) { iqmjoint &j = joints[i]; baseframe[i] = rotationscaletrans(normalize(quat(j.rotate)), float3(j.scale), float3(j.translate)); inversebaseframe[i] = invertortho(baseframe[i]); if(j.parent >= 0) { baseframe[i] = baseframe[j.parent] * baseframe[i]; inversebaseframe[i] *= inversebaseframe[j.parent]; } } for(int i = 0; i < (int)hdr.num_meshes; i++) { iqmmesh &m = meshes[i]; textures[i] = &str[m.name]; } return true; } bool loadiqmanims(const iqmheader &hdr, const char *buf) { if((int)hdr.num_poses != numjoints) return false; lilswap((uint *)&buf[hdr.ofs_poses], hdr.num_poses*sizeof(iqmpose)/sizeof(uint)); lilswap((uint *)&buf[hdr.ofs_anims], hdr.num_anims*sizeof(iqmanim)/sizeof(uint)); lilswap((ushort *)&buf[hdr.ofs_frames], hdr.num_frames*hdr.num_framechannels); if(hdr.ofs_bounds) lilswap((uint *)&buf[hdr.ofs_bounds], hdr.num_frames*sizeof(iqmbounds)/sizeof(uint)); numanims = hdr.num_anims; numframes = hdr.num_frames; anims = (iqmanim *)&buf[hdr.ofs_anims]; poses = (iqmpose *)&buf[hdr.ofs_poses]; frames = new float3x4[hdr.num_frames * hdr.num_poses]; ushort *framedata = (ushort *)&buf[hdr.ofs_frames]; if(hdr.ofs_bounds) bounds = (iqmbounds *)&buf[hdr.ofs_bounds]; for(int i = 0; i < (int)hdr.num_frames; i++) { for(int j = 0; j < (int)hdr.num_poses; j++) { float trs[10]; iqmpose &p = poses[j]; for (int k = 0; k < 10; k++) { trs[k] = p.channeloffset[k]; if(p.mask&(1< // parentPose * (parentInverseBasePose * parentBasePose) * // childPose * childInverseBasePose => // parentPose * childPose * childInverseBasePose auto m = rotationscaletrans(normalize(quat(&trs[3])), *(float3 *)&trs[7], *(float3 *)&trs[0]); if(p.parent >= 0) m = baseframe[p.parent] * m * inversebaseframe[j]; else m = m * inversebaseframe[j]; // This parent multiplication may have to be moved to blend time for more complicated // anim features. if(joints[j].parent >= 0) m = frames[i*hdr.num_poses + joints[j].parent] * (float3x4 &)m; frames[i*hdr.num_poses + j] = m; } } return true; } bool loadiqm(string_view filename) { if(LoadFile(filename, &filebuffer) < 0) return false; iqmheader hdr = *(iqmheader *)filebuffer.c_str(); if(memcmp(hdr.magic, IQM_MAGIC, sizeof(hdr.magic))) return false; lilswap(&hdr.version, (sizeof(hdr) - sizeof(hdr.magic))/sizeof(uint)); if(hdr.version != IQM_VERSION) return false; if(filebuffer.length() != hdr.filesize || hdr.filesize > (16<<20)) return false; // sanity check... don't load files bigger than 16 MB if(hdr.num_meshes > 0 && !loadiqmmeshes(hdr, filebuffer.c_str())) return false; if(hdr.num_anims > 0 && !loadiqmanims (hdr, filebuffer.c_str())) return false; return true; } Mesh *LoadIQM(string_view filename) { if (!loadiqm(filename)) { cleanupiqm(); return nullptr; } // FIXME: Can save 8 bytes on non-animated verts by using BasicVert. vector verts(numverts); for (int i = 0; i < numverts; i++) { auto &v = verts[i]; v.pos = inposition ? *(float3 *)&inposition [i * 3] : float3_0; v.norm = innormal ? *(float3 *)&innormal [i * 3] : float3_0; v.tc = intexcoord ? *(float2 *)&intexcoord [i * 2] : float2_0; v.col = incolor ? *(byte4 *)&incolor [i * 4] : byte4_255; v.weights = inblendweight ? *(byte4 *)&inblendweight[i * 4] : byte4_0; v.indices = inblendindex ? *(byte4 *)&inblendindex [i * 4] : byte4_0; } if (!innormal) normalize_mesh(make_span((int *)tris, numtris * 3), verts.data(), numverts, sizeof(AnimVert), (uchar *)&verts[0].norm - (uchar *)&verts[0].pos); auto geom = new Geometry(make_span(verts), "PNTCWI"); auto mesh = new Mesh(geom); for (int i = 0; i < nummeshes; i++) { auto surf = new Surface(make_span((int *)(tris + meshes[i].first_triangle), meshes[i].num_triangles * 3)); surf->name = textures[i]; mesh->surfs.push_back(surf); } if (numjoints) { mesh->numbones = numjoints; mesh->numframes = numframes; auto mats = new float3x4[numjoints * numframes]; t_memcpy(mats, frames, numjoints * numframes); mesh->mats = mats; } cleanupiqm(); return mesh; } treesheets-1.0.2/lobster/src/glshader.cpp000066400000000000000000000501741352107072600204350ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/glinterface.h" #include "lobster/glincludes.h" #include "lobster/sdlinterface.h" map> shadermap; Shader *LookupShader(string_view name) { auto shi = shadermap.find(name); if (shi != shadermap.end()) return shi->second; return nullptr; } void ShaderShutDown() { for (auto &it : shadermap) delete it.second; } string GLSLError(uint obj, bool isprogram, const char *source) { GLint length = 0; if (isprogram) GL_CALL(glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &length)); else GL_CALL(glGetShaderiv (obj, GL_INFO_LOG_LENGTH, &length)); if (length > 1) { GLchar *log = new GLchar[length]; if (isprogram) GL_CALL(glGetProgramInfoLog(obj, length, &length, log)); else GL_CALL(glGetShaderInfoLog (obj, length, &length, log)); string err = "GLSL ERROR: "; err += log; int i = 0; if (source) for (;;) { err += cat(++i, ": "); const char *next = strchr(source, '\n'); if (next) { err += string_view(source, next - source + 1); source = next + 1; } else { err += string_view(source) + "\n"; break; } } delete[] log; return err; } return ""; } uint CompileGLSLShader(GLenum type, uint program, const GLchar *source, string &err) { uint obj = glCreateShader(type); GL_CALL(glShaderSource(obj, 1, &source, nullptr)); GL_CALL(glCompileShader(obj)); GLint success; GL_CALL(glGetShaderiv(obj, GL_COMPILE_STATUS, &success)); if (success) { GL_CALL(glAttachShader(program, obj)); return obj; } err = GLSLError(obj, false, source); GL_CALL(glDeleteShader(obj)); return 0; } string ParseMaterialFile(string_view mbuf) { auto p = mbuf; string err; string_view last; string defines; string vfunctions, pfunctions, cfunctions, vertex, pixel, compute, vdecl, pdecl, csdecl, shader; string *accum = nullptr; auto word = [&]() { p.remove_prefix(min(p.find_first_not_of(" \t\r"), p.size())); size_t len = min(p.find_first_of(" \t\r"), p.size()); last = p.substr(0, len); p.remove_prefix(len); }; auto finish = [&]() -> bool { if (!shader.empty()) { auto sh = new Shader(); if (compute.length()) { #ifdef PLATFORM_WINNIX extern string glslversion; auto header = "#version " + glslversion + "\n" + defines; #else auto header = "#version 430\n" + defines; #endif err = sh->Compile(shader.c_str(), (header + csdecl + cfunctions + "void main()\n{\n" + compute + "}\n").c_str()); } else { string header; #ifdef PLATFORM_ES3 #ifdef __EMSCRIPTEN__ header += "#version 300 es\n"; #endif header += "#ifdef GL_ES\nprecision highp float;\n#endif\n"; #else #ifdef __APPLE__ auto supported = glGetString(GL_SHADING_LANGUAGE_VERSION); // Apple randomly changes what it supports, so just ask for that. header += string_view("#version ") + string_view((const char *)supported, 1) + string_view((const char *)supported + 2, 2) + "\n"; #else extern string glslversion; header += "#version " + glslversion + "\n"; header += "#extension GL_EXT_gpu_shader4 : enable\n"; #endif #endif header += defines; pdecl = "out vec4 frag_color;\n" + pdecl; err = sh->Compile(shader.c_str(), (header + vdecl + vfunctions + "void main()\n{\n" + vertex + "}\n").c_str(), (header + pdecl + pfunctions + "void main()\n{\n" + pixel + "}\n").c_str()); } if (!err.empty()) return true; shadermap[shader] = sh; shader.clear(); } return false; }; for (;;) { auto start = p; p.remove_suffix(p.size() - min(p.find_first_of("\n"), p.size())); auto line = p; word(); if (!last.empty()) { if (last == "VERTEXFUNCTIONS") { if (finish()) return err; vfunctions.clear(); accum = &vfunctions; } else if (last == "PIXELFUNCTIONS") { if (finish()) return err; pfunctions.clear(); accum = &pfunctions; } else if (last == "COMPUTEFUNCTIONS") { if (finish()) return err; cfunctions.clear(); accum = &cfunctions; } else if (last == "VERTEX") { vertex.clear(); accum = &vertex; } else if (last == "PIXEL") { pixel.clear(); accum = &pixel; } else if (last == "COMPUTE") { compute.clear(); accum = &compute; } else if (last == "SHADER") { if (finish()) return err; word(); shader = last; vdecl.clear(); pdecl.clear(); csdecl.clear(); vertex.clear(); pixel.clear(); compute.clear(); accum = nullptr; } else if (last == "UNIFORMS") { string &decl = accum == &compute ? csdecl : (accum == &vertex ? vdecl : pdecl); for (;;) { word(); if (last.empty()) break; else if (last == "mvp") decl += "uniform mat4 mvp;\n"; else if (last == "col") decl += "uniform vec4 col;\n"; else if (last == "camera") decl += "uniform vec3 camera;\n"; else if (last == "light1") decl += "uniform vec3 light1;\n"; else if (last == "lightparams1") decl += "uniform vec2 lightparams1;\n"; else if (last == "texturesize") decl += "uniform vec2 texturesize;\n"; // FIXME: Make configurable. else if (last == "bones") decl += "uniform vec4 bones[230];\n"; else if (last == "pointscale") decl += "uniform float pointscale;\n"; else if (last.substr(0, 3) == "tex") { auto tp = last; tp.remove_prefix(3); bool cubemap = false; bool floatingp = false; bool d3 = false; if (starts_with(tp, "cube")) { tp.remove_prefix(4); cubemap = true; } if (starts_with(tp, "3d")) { tp.remove_prefix(2); d3 = true; } if (starts_with(tp, "f")) { tp.remove_prefix(1); floatingp = true; } auto unit = parse_int(tp); if (accum == &compute) { decl += cat("layout(binding = ", unit, ", ", (floatingp ? "rgba32f" : "rgba8"), ") "); } decl += "uniform "; decl += accum == &compute ? (cubemap ? "imageCube" : "image2D") : (cubemap ? "samplerCube" : (d3 ? "sampler3D" : "sampler2D")); decl += " " + last + ";\n"; } else return "unknown uniform: " + last; } } else if (last == "UNIFORM") { string &decl = accum == &compute ? csdecl : (accum == &vertex ? vdecl : pdecl); word(); auto type = last; word(); auto name = last; if (type.empty() || name.empty()) return "uniform decl must specify type and name"; decl += "uniform " + type + " " + name + ";\n"; } else if (last == "INPUTS") { string decl; for (;;) { word(); if (last.empty()) break; auto pos = last.find_first_of(":"); if (pos == string_view::npos) { return "input " + last + " doesn't specify number of components, e.g. anormal:3"; } int comp = parse_int(last.substr(pos + 1)); if (comp <= 0 || comp > 4) { return "input " + last + " can only use 1..4 components"; } last = last.substr(0, pos); string d = cat(" vec", comp, " ", last, ";\n"); if (accum == &vertex) vdecl += "in" + d; else { vdecl += "out" + d; pdecl += "in" + d; } } } else if (last == "LAYOUT") { word(); auto xs = last; word(); auto ys = last; csdecl += "layout(local_size_x = " + xs + ", local_size_y = " + ys + ") in;\n"; } else if (last == "DEFINE") { word(); auto def = last; word(); auto val = last; defines += "#define " + (val.empty() ? def : def + " " + val) + "\n"; } else { if (!accum) return "GLSL code outside of FUNCTIONS/VERTEX/PIXEL block: " + line; *accum += line; *accum += "\n"; } } if (line.size() == start.size()) break; start.remove_prefix(line.size() + 1); p = start; } finish(); return err; } string LoadMaterialFile(string_view mfile) { string mbuf; if (LoadFile(mfile, &mbuf) < 0) return string_view("cannot load material file: ") + mfile; auto err = ParseMaterialFile(mbuf); return err; } string Shader::Compile(const char *name, const char *vscode, const char *pscode) { program = glCreateProgram(); string err; vs = CompileGLSLShader(GL_VERTEX_SHADER, program, vscode, err); if (!vs) return string_view("couldn't compile vertex shader: ") + name + "\n" + err; ps = CompileGLSLShader(GL_FRAGMENT_SHADER, program, pscode, err); if (!ps) return string_view("couldn't compile pixel shader: ") + name + "\n" + err; GL_CALL(glBindAttribLocation(program, 0, "apos")); GL_CALL(glBindAttribLocation(program, 1, "anormal")); GL_CALL(glBindAttribLocation(program, 2, "atc")); GL_CALL(glBindAttribLocation(program, 3, "acolor")); GL_CALL(glBindAttribLocation(program, 4, "aweights")); GL_CALL(glBindAttribLocation(program, 5, "aindices")); Link(name); return ""; } string Shader::Compile(const char *name, const char *cscode) { #ifdef PLATFORM_WINNIX program = glCreateProgram(); string err; cs = CompileGLSLShader(GL_COMPUTE_SHADER, program, cscode, err); if (!cs) return string_view("couldn't compile compute shader: ") + name + "\n" + err; Link(name); return ""; #else return "compute shaders not supported"; #endif } void Shader::Link(const char *name) { GL_CALL(glLinkProgram(program)); GLint status; GL_CALL(glGetProgramiv(program, GL_LINK_STATUS, &status)); if (status != GL_TRUE) { GLSLError(program, true, nullptr); THROW_OR_ABORT(string_view("linking failed for shader: ") + name); } mvp_i = glGetUniformLocation(program, "mvp"); col_i = glGetUniformLocation(program, "col"); camera_i = glGetUniformLocation(program, "camera"); light1_i = glGetUniformLocation(program, "light1"); lightparams1_i = glGetUniformLocation(program, "lightparams1"); texturesize_i = glGetUniformLocation(program, "texturesize"); bones_i = glGetUniformLocation(program, "bones"); pointscale_i = glGetUniformLocation(program, "pointscale"); Activate(); for (int i = 0; i < MAX_SAMPLERS; i++) { auto loc = glGetUniformLocation(program, cat("tex", i).c_str()); if (loc < 0) { loc = glGetUniformLocation(program, cat("texcube", i).c_str()); if (loc < 0) loc = glGetUniformLocation(program, cat("tex3d", i).c_str()); } if (loc >= 0) { glUniform1i(loc, i); max_tex_defined = i + 1; } } } Shader::~Shader() { if (program) GL_CALL(glDeleteProgram(program)); if (ps) GL_CALL(glDeleteShader(ps)); if (vs) GL_CALL(glDeleteShader(vs)); if (cs) GL_CALL(glDeleteShader(cs)); } // FIXME: unlikely to cause ABA problem, but still better to reset once per frame just in case. static uint last_program = 0; void Shader::Activate() { if (program != last_program) { GL_CALL(glUseProgram(program)); last_program = program; } } void Shader::Set() { Activate(); if (mvp_i >= 0) GL_CALL(glUniformMatrix4fv(mvp_i, 1, false, (view2clip * otransforms.object2view).data())); if (col_i >= 0) GL_CALL(glUniform4fv(col_i, 1, curcolor.begin())); if (camera_i >= 0) GL_CALL(glUniform3fv(camera_i, 1, otransforms.view2object[3].begin())); if (pointscale_i >= 0) GL_CALL(glUniform1f(pointscale_i, pointscale)); if (lights.size() > 0) { if (light1_i >= 0) GL_CALL(glUniform3fv(light1_i, 1, (otransforms.view2object * lights[0].pos).begin())); if (lightparams1_i >= 0) GL_CALL(glUniform2fv(lightparams1_i, 1, lights[0].params.begin())); } if (texturesize_i >= 0) GL_CALL(glUniform2fv(texturesize_i, 1, float2(GetScreenSize()).begin())); } void Shader::SetAnim(float3x4 *bones, int num) { // FIXME: Check if num fits with shader def. if (bones_i >= 0) GL_CALL(glUniform4fv(bones_i, num * 3, (float *)bones)); } void Shader::SetTextures(const vector &textures) { for (int i = 0; i < min(max_tex_defined, (int)textures.size()); i++) { SetTexture(i, textures[i]); } } bool Shader::SetUniform(string_view name, const float *val, int components, int elements) { auto loc = glGetUniformLocation(program, null_terminated(name)); if (loc < 0) return false; switch (components) { case 1: GL_CALL(glUniform1fv(loc, elements, val)); return true; case 2: GL_CALL(glUniform2fv(loc, elements, val)); return true; case 3: GL_CALL(glUniform3fv(loc, elements, val)); return true; case 4: GL_CALL(glUniform4fv(loc, elements, val)); return true; default: return false; } } bool Shader::SetUniformMatrix(string_view name, const float *val, int components, int elements) { auto loc = glGetUniformLocation(program, null_terminated(name)); if (loc < 0) return false; switch (components) { case 4: GL_CALL(glUniformMatrix2fv(loc, elements, false, val)); return true; case 9: GL_CALL(glUniformMatrix3fv(loc, elements, false, val)); return true; case 12: GL_CALL(glUniformMatrix3x4fv(loc, elements, false, val)); return true; case 16: GL_CALL(glUniformMatrix4fv(loc, elements, false, val)); return true; default: return false; } } void DispatchCompute(const int3 &groups) { #ifdef PLATFORM_WINNIX if (glDispatchCompute) GL_CALL(glDispatchCompute(groups.x, groups.y, groups.z)); // Make sure any imageStore/VBOasSSBO operations have completed. // Would be better to decouple this from DispatchCompute. if (glMemoryBarrier) GL_CALL(glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT)); #else assert(false); #endif } // Simple function for getting some uniform / shader storage attached to a shader. Should ideally // be split up for more flexibility. // Use this for reusing BO's for now: map, less<>> ubomap; // Note that bo_binding_point_index is assigned automatically based on unique block names. // You can also specify these in the shader using `binding=`, but GL doesn't seem to have a way // to retrieve these programmatically. // If data is nullptr, bo is used instead. uint UniformBufferObject(Shader *sh, const void *data, size_t len, string_view uniformblockname, bool ssbo, uint bo) { #ifdef PLATFORM_WINNIX if (sh && glGetProgramResourceIndex && glShaderStorageBlockBinding && glBindBufferBase && glUniformBlockBinding && glGetUniformBlockIndex) { sh->Activate(); auto idx = ssbo ? glGetProgramResourceIndex(sh->program, GL_SHADER_STORAGE_BLOCK, null_terminated(uniformblockname)) : glGetUniformBlockIndex(sh->program, null_terminated(uniformblockname)); GLint maxsize = 0; // FIXME: call glGetInteger64v if we ever want buffers >2GB. if (ssbo) glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &maxsize); else glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxsize); if (idx != GL_INVALID_INDEX && len <= size_t(maxsize)) { auto type = ssbo ? GL_SHADER_STORAGE_BUFFER : GL_UNIFORM_BUFFER; static uint binding_point_index_alloc = 0; auto it = ubomap.find(uniformblockname); uint bo_binding_point_index = 0; if (it == ubomap.end()) { if (data) bo = GenBO_(type, len, data); bo_binding_point_index = binding_point_index_alloc++; ubomap[string(uniformblockname)] = { bo, bo_binding_point_index }; } else { if (data) bo = it->second.first; bo_binding_point_index = it->second.second; glBindBuffer(type, bo); if (data) glBufferData(type, len, data, GL_STATIC_DRAW); } GL_CALL(glBindBuffer(type, 0)); GL_CALL(glBindBufferBase(type, bo_binding_point_index, bo)); if (ssbo) GL_CALL(glShaderStorageBlockBinding(sh->program, idx, bo_binding_point_index)); else GL_CALL(glUniformBlockBinding(sh->program, idx, bo_binding_point_index)); } } #else // UBO's are in ES 3.0, not sure why OS X doesn't have them #endif return bo; } bool Shader::Dump(string_view filename, bool stripnonascii) { #ifdef PLATFORM_WINNIX if (!glGetProgramBinary) return false; #endif #ifndef __EMSCRIPTEN__ int len = 0; GL_CALL(glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &len)); string buf; buf.resize(len); GLenum format = 0; GL_CALL(glGetProgramBinary(program, len, nullptr, &format, buf.data())); if (stripnonascii) { buf.erase(remove_if(buf.begin(), buf.end(), [](char c) { return (c < ' ' || c > '~') && c != '\n' && c != '\t'; }), buf.end()); } return WriteFile(filename, true, buf); #else return false; #endif } treesheets-1.0.2/lobster/src/glsystem.cpp000066400000000000000000000132671352107072600205150ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/glincludes.h" #include "lobster/glinterface.h" #include "lobster/sdlincludes.h" #ifdef PLATFORM_WINNIX #define GLEXT(type, name, needed) type name = nullptr; GLBASEEXTS GLEXTS #undef GLEXT #endif float4 curcolor = float4_0; float4x4 view2clip(1); objecttransforms otransforms; vector lights; float pointscale = 1.0f; float custompointscale = 1.0f; bool mode2d = true; GeometryCache *geomcache = nullptr; void AppendTransform(const float4x4 &forward, const float4x4 &backward) { otransforms.object2view *= forward; otransforms.view2object = backward * otransforms.view2object; } int SetBlendMode(BlendMode mode) { static BlendMode curblendmode = BLEND_NONE; if (mode == curblendmode) return curblendmode; switch (mode) { case BLEND_NONE: GL_CALL(glDisable(GL_BLEND)); break; case BLEND_ALPHA: GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); break; case BLEND_ADD: GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE)); break; case BLEND_ADDALPHA: GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_SRC_ALPHA, GL_ONE)); break; case BLEND_MUL: GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_DST_COLOR, GL_ZERO)); break; case BLEND_PREMULALPHA: GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); break; } int old = curblendmode; curblendmode = mode; return old; } void ClearFrameBuffer(const float3 &c) { GL_CALL(glClearColor(c.x, c.y, c.z, 1.0)); GL_CALL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); } void Set2DMode(const int2 &ssize, bool lh, bool depthtest) { GL_CALL(glDisable(GL_CULL_FACE)); if (depthtest) GL_CALL(glEnable(GL_DEPTH_TEST)); else GL_CALL(glDisable(GL_DEPTH_TEST)); otransforms = objecttransforms(); auto y = (float)ssize.y; view2clip = ortho(0, (float)ssize.x, lh ? y : 0, lh ? 0 : y, 1, -1); mode2d = true; } void Set3DOrtho(const float3 ¢er, const float3 &extends) { GL_CALL(glEnable(GL_DEPTH_TEST)); GL_CALL(glEnable(GL_CULL_FACE)); otransforms = objecttransforms(); auto p = center + extends; auto m = center - extends; view2clip = ortho(m.x, p.x, p.y, m.y, m.z, p.z); // left handed coordinate system mode2d = false; } void Set3DMode(float fovy, float ratio, float znear, float zfar) { GL_CALL(glEnable(GL_DEPTH_TEST)); GL_CALL(glEnable(GL_CULL_FACE)); otransforms = objecttransforms(); view2clip = perspective(fovy, ratio, znear, zfar, 1); mode2d = false; } bool Is2DMode() { return mode2d; } uchar *ReadPixels(const int2 &pos, const int2 &size) { uchar *pixels = new uchar[size.x * size.y * 3]; for (int y = 0; y < size.y; y++) GL_CALL(glReadPixels(pos.x, pos.y + size.y - y - 1, size.x, 1, GL_RGB, GL_UNSIGNED_BYTE, pixels + y * (size.x * 3))); return pixels; } void OpenGLFrameStart(const int2 &ssize) { GL_CALL(glViewport(0, 0, ssize.x, ssize.y)); SetBlendMode(BLEND_ALPHA); curcolor = float4(1); lights.clear(); } void OpenGLInit(int samples) { GL_CHECK("before_init"); // If not called, flashes red framebuffer on OS X before first gl_clear() is called. ClearFrameBuffer(float3_0); #ifdef PLATFORM_WINNIX #define GLEXT(type, name, needed) { \ union { void *proc; type fun; } funcast; /* regular cast causes gcc warning */ \ funcast.proc = SDL_GL_GetProcAddress(#name); \ name = funcast.fun; \ if (!name && needed) THROW_OR_ABORT(string("no " #name)); \ } GLBASEEXTS GLEXTS #undef GLEXT #endif #ifndef PLATFORM_ES3 GL_CALL(glEnable(GL_LINE_SMOOTH)); GL_CALL(glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)); GL_CALL(glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST)); if (samples > 1) GL_CALL(glEnable(GL_MULTISAMPLE)); #endif GL_CALL(glCullFace(GL_FRONT)); assert(!geomcache); geomcache = new GeometryCache(); } void OpenGLCleanup() { if (geomcache) delete geomcache; geomcache = nullptr; } void LogGLError(const char *file, int line, const char *call) { auto err = glGetError(); if (err == GL_NO_ERROR) return; const char *err_str = ""; switch (err) { case GL_INVALID_ENUM: err_str = "GL_INVALID_ENUM"; break; case GL_INVALID_VALUE: err_str = "GL_INVALID_VALUE"; break; case GL_INVALID_OPERATION: err_str = "GL_INVALID_OPERATION"; break; case GL_INVALID_FRAMEBUFFER_OPERATION: err_str = "GL_INVALID_FRAMEBUFFER_OPERATION"; break; case GL_OUT_OF_MEMORY: err_str = "GL_OUT_OF_MEMORY"; break; } LOG_ERROR(file, "(", line, "): OpenGL Error: ", err_str, " from ", call); assert(false); } treesheets-1.0.2/lobster/src/gltexture.cpp000066400000000000000000000252041352107072600206630ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/glinterface.h" #include "lobster/glincludes.h" #define STB_IMAGE_IMPLEMENTATION #ifdef _WIN32 #pragma warning(push) #pragma warning(disable: 4244) #endif #include "stb/stb_image.h" #ifdef _WIN32 #pragma warning(pop) #endif #ifndef __EMSCRIPTEN__ const int nummultisamples = 4; #endif Texture CreateTexture(const uchar *buf, const int *dim, int tf) { uint id; GL_CALL(glGenTextures(1, &id)); assert(id); GLenum textype = #ifdef PLATFORM_WINNIX tf & TF_MULTISAMPLE ? GL_TEXTURE_2D_MULTISAMPLE : #endif (tf & TF_3D ? GL_TEXTURE_3D : GL_TEXTURE_2D); GLenum teximagetype = textype; textype = tf & TF_CUBEMAP ? GL_TEXTURE_CUBE_MAP : textype; teximagetype = tf & TF_CUBEMAP ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : teximagetype; int texnumfaces = tf & TF_CUBEMAP ? 6 : 1; GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(textype, id)); auto wrap = tf & TF_CLAMP ? GL_CLAMP_TO_EDGE : GL_REPEAT; GL_CALL(glTexParameteri(textype, GL_TEXTURE_WRAP_S, wrap)); GL_CALL(glTexParameteri(textype, GL_TEXTURE_WRAP_T, wrap)); if(tf & TF_3D) GL_CALL(glTexParameteri(textype, GL_TEXTURE_WRAP_R, wrap)); GL_CALL(glTexParameteri(textype, GL_TEXTURE_MAG_FILTER, tf & TF_NEAREST_MAG ? GL_NEAREST : GL_LINEAR)); GL_CALL(glTexParameteri(textype, GL_TEXTURE_MIN_FILTER, tf & TF_NOMIPMAP ? (tf & TF_NEAREST_MIN ? GL_NEAREST : GL_LINEAR) : (tf & TF_NEAREST_MIN ? GL_NEAREST_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR))); //if (mipmap) glTexParameteri(textype, GL_GENERATE_MIPMAP, GL_TRUE); auto internalformat = tf & TF_SINGLE_CHANNEL ? GL_R8 : GL_RGBA8; auto bufferformat = tf & TF_SINGLE_CHANNEL ? GL_RED : GL_RGBA; auto buffersize = tf & TF_SINGLE_CHANNEL ? sizeof(uchar) : sizeof(byte4); auto buffercomponent = GL_UNSIGNED_BYTE; if ((tf & TF_SINGLE_CHANNEL) && (dim[0] & 0x3)) { GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); // Defaults to 4. } if (tf & TF_FLOAT) { #ifdef PLATFORM_WINNIX internalformat = tf & TF_SINGLE_CHANNEL ? GL_R32F : GL_RGBA32F; bufferformat = tf & TF_SINGLE_CHANNEL ? GL_RED : GL_RGBA; buffersize = tf & TF_SINGLE_CHANNEL ? sizeof(float) : sizeof(float4); buffercomponent = GL_FLOAT; #else assert(false); // buf points to float data, which we don't support. #endif } if (tf & TF_DEPTH) { internalformat = GL_DEPTH_COMPONENT32F; bufferformat = GL_DEPTH_COMPONENT; buffersize = sizeof(float); buffercomponent = GL_FLOAT; } if (tf & TF_MULTISAMPLE) { #ifdef PLATFORM_WINNIX GL_CALL(glTexImage2DMultisample(teximagetype, nummultisamples, internalformat, dim[0], dim[1], true)); #else assert(false); #endif } else if(tf & TF_3D) { #ifndef __EMSCRIPTEN__ int mipl = 0; for (auto d = int3(dim); tf & TF_BUFFER_HAS_MIPS ? d.volume() : !mipl; d /= 2) { GL_CALL(glTexImage3D(textype, mipl, internalformat, d.x, d.y, d.z, 0, bufferformat, buffercomponent, buf)); mipl++; buf += d.volume() * buffersize; } #else assert(false); #endif } else { int mipl = 0; for (auto d = int2(dim); tf & TF_BUFFER_HAS_MIPS ? d.volume() : !mipl; d /= 2) { for (int i = 0; i < texnumfaces; i++) GL_CALL(glTexImage2D(teximagetype + i, mipl, internalformat, d.x, d.y, 0, bufferformat, buffercomponent, buf)); mipl++; buf += d.volume() * buffersize; } } if (!(tf & TF_NOMIPMAP) && !(tf & TF_BUFFER_HAS_MIPS)) { #ifdef PLATFORM_WINNIX if (glGenerateMipmap) // only exists in 3.0 contexts and up. #endif GL_CALL(glGenerateMipmap(textype)); } GL_CALL(glBindTexture(textype, 0)); return Texture(id, int3(dim)); } Texture CreateTextureFromFile(string_view name, int tf) { tf &= ~TF_FLOAT; // Not supported yet. string fbuf; if (LoadFile(name, &fbuf) < 0) return Texture(); int2 dim; int comp; auto buf = stbi_load_from_memory((uchar *)fbuf.c_str(), (int)fbuf.length(), &dim.x, &dim.y, &comp, 4); if (!buf) return Texture(); auto tex = CreateTexture(buf, dim.data(), tf); stbi_image_free(buf); return tex; } Texture CreateBlankTexture(const int2 &size, const float4 &color, int tf) { if (tf & TF_MULTISAMPLE) { return CreateTexture(nullptr, size.data(), tf); // No buffer required. } else { auto sz = tf & TF_FLOAT ? sizeof(float4) : sizeof(byte4); auto buf = new uchar[size.x * size.y * sz]; for (int y = 0; y < size.y; y++) for (int x = 0; x < size.x; x++) { auto idx = y * size.x + x; if (tf & TF_FLOAT) ((float4 *)buf)[idx] = color; else ((byte4 *)buf)[idx] = quantizec(color); } auto tex = CreateTexture(buf, size.data(), tf); delete[] buf; return tex; } } void DeleteTexture(Texture &tex) { if (tex.id) GL_CALL(glDeleteTextures(1, &tex.id)); tex.id = 0; } void SetTexture(int textureunit, const Texture &tex, int tf) { GL_CALL(glActiveTexture(GL_TEXTURE0 + textureunit)); GL_CALL(glBindTexture(tf & TF_CUBEMAP ? GL_TEXTURE_CUBE_MAP : (tf & TF_3D ? GL_TEXTURE_3D : GL_TEXTURE_2D), tex.id)); } uchar *ReadTexture(const Texture &tex) { #ifndef PLATFORM_ES3 GL_CALL(glBindTexture(GL_TEXTURE_2D, tex.id)); auto pixels = new uchar[tex.size.x * tex.size.y * 4]; GL_CALL(glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); return pixels; #else return nullptr; #endif } void SetImageTexture(uint textureunit, const Texture &tex, int tf) { #ifdef PLATFORM_WINNIX if (glBindImageTexture) GL_CALL(glBindImageTexture(textureunit, tex.id, 0, GL_TRUE, 0, tf & TF_WRITEONLY ? GL_WRITE_ONLY : (tf & TF_READWRITE ? GL_READ_WRITE : GL_READ_ONLY), tf & TF_FLOAT ? GL_RGBA32F : GL_RGBA8)); #else assert(false); #endif } // from 2048 on older GLES2 devices and very old PCs to 16384 on the latest PC cards int MaxTextureSize() { int mts = 0; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mts); return mts; } uint CreateFrameBuffer(const Texture &tex, int tf) { #ifdef PLATFORM_WINNIX if (!glGenFramebuffers) return 0; #endif uint fb = 0; GL_CALL(glGenFramebuffers(1, &fb)); GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, fb)); auto target = #ifdef PLATFORM_WINNIX tf & TF_MULTISAMPLE ? GL_TEXTURE_2D_MULTISAMPLE : #endif GL_TEXTURE_2D; GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, tex.id, 0)); return fb; } #ifndef __EMSCRIPTEN__ static uint fb = 0; static uint rb = 0; static Texture retex; // Texture to resolve to at the end when fb refers to a multisample texture. static int retf = 0; static bool hasdepthtex = false; #endif bool SwitchToFrameBuffer(const Texture &tex, bool depth, int tf, const Texture &resolvetex, const Texture &depthtex) { #ifdef PLATFORM_WINNIX if (!glGenRenderbuffers) return false; #endif #ifndef __EMSCRIPTEN__ if (hasdepthtex) { GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0)); hasdepthtex = false; } if (rb) { GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); GL_CALL(glDeleteRenderbuffers(1, &rb)); rb = 0; } if (fb) { if (retex.id) { uint refb = CreateFrameBuffer(retex, retf); GL_CALL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, refb)); GL_CALL(glBindFramebuffer(GL_READ_FRAMEBUFFER, fb)); GL_CALL(glBlitFramebuffer(0, 0, retex.size.x, retex.size.y, 0, 0, retex.size.x, retex.size.y, GL_COLOR_BUFFER_BIT, GL_NEAREST)); GL_CALL(glDeleteFramebuffers(1, &refb)); retex = Texture(); retf = 0; } GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); GL_CALL(glDeleteFramebuffers(1, &fb)); fb = 0; } if (!tex.id) { OpenGLFrameStart(tex.size.xy()); Set2DMode(tex.size.xy(), true, depth); return true; } fb = CreateFrameBuffer(tex, tf); if (depth) { if (depthtex.id) { GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthtex.id, 0)); hasdepthtex = true; } else { GL_CALL(glGenRenderbuffers(1, &rb)); GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER, rb)); if (tf & TF_MULTISAMPLE) { GL_CALL(glRenderbufferStorageMultisample(GL_RENDERBUFFER, nummultisamples, GL_DEPTH_COMPONENT24, tex.size.x, tex.size.y)); } else { GL_CALL(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, tex.size.x, tex.size.y)); } GL_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rb)); } } retex = resolvetex; retf = tf & ~TF_MULTISAMPLE; OpenGLFrameStart(tex.size.xy()); Set2DMode(tex.size.xy(), false, depth); // Have to use rh mode here, otherwise texture is filled flipped. auto stat = glCheckFramebufferStatus(GL_FRAMEBUFFER); auto ok = stat == GL_FRAMEBUFFER_COMPLETE; assert(ok); return ok; #else return false; #endif } treesheets-1.0.2/lobster/src/glvr.cpp000066400000000000000000000342441352107072600176160ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/glinterface.h" #include "lobster/glincludes.h" #include "lobster/sdlinterface.h" #include "lobster/sdlincludes.h" #ifdef PLATFORM_VR #include "openvr.h" static vr::IVRSystem *vrsys = nullptr; static vr::IVRRenderModels *vrmodels = nullptr; static vr::TrackedDevicePose_t trackeddeviceposes[vr::k_unMaxTrackedDeviceCount]; static map> button_ids; string GetTrackedDeviceString(vr::TrackedDeviceIndex_t device, vr::TrackedDeviceProperty prop) { assert(vrsys); uint32_t buflen = vrsys->GetStringTrackedDeviceProperty(device, prop, nullptr, 0, nullptr); if(buflen == 0) return ""; char *buf = new char[buflen]; buflen = vrsys->GetStringTrackedDeviceProperty(device, prop, buf, buflen, nullptr); std::string s = buf; delete [] buf; return s; } float4x4 FromOpenVR(const vr::HmdMatrix44_t &mat) { return float4x4(&mat.m[0][0]).transpose(); } float4x4 FromOpenVR(const vr::HmdMatrix34_t &mat) { return float4x4(float4(&mat.m[0][0]), float4(&mat.m[1][0]), float4(&mat.m[2][0]), float4(0, 0, 0, 1)).transpose(); // FIXME: simplify } #endif // PLATFORM_VR static int2 rtsize = int2_0; static Texture mstex[2]; static Texture retex[2]; static float4x4 hmdpose = float4x4_1; struct MotionController { float4x4 mat; uint device; #ifdef PLATFORM_VR vr::VRControllerState_t state, laststate; #endif bool tracking; }; static vector motioncontrollers; void VRShutDown() { #ifdef PLATFORM_VR button_ids.clear(); if (vrsys) vr::VR_Shutdown(); vrsys = NULL; for (int i = 0; i < 2; i++) { DeleteTexture(mstex[i]); DeleteTexture(retex[i]); } #else (void)mstex; #endif // PLATFORM_VR } bool VRInit() { #ifdef PLATFORM_VR if (vrsys) return true; extern bool noninteractivetestmode; if (noninteractivetestmode) return false; button_ids["system"] = vr::k_EButton_System; button_ids["menu"] = vr::k_EButton_ApplicationMenu; button_ids["grip"] = vr::k_EButton_Grip; button_ids["touchpad"] = vr::k_EButton_SteamVR_Touchpad; button_ids["trigger"] = vr::k_EButton_SteamVR_Trigger; if (!vr::VR_IsHmdPresent()) return false; vr::EVRInitError err = vr::VRInitError_None; vrsys = vr::VR_Init(&err, vr::VRApplication_Scene); if (err != vr::VRInitError_None) { vrsys = nullptr; LOG_ERROR("VR system init failed: ", vr::VR_GetVRInitErrorAsEnglishDescription(err)); return false; } vrsys->GetRecommendedRenderTargetSize((uint *)&rtsize.x, (uint *)&rtsize.y); auto devicename = GetTrackedDeviceString(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_TrackingSystemName_String); auto displayname = GetTrackedDeviceString(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SerialNumber_String); LOG_INFO("VR running on device: \"", devicename, "\", display: \"", displayname, "\", rt size: (", rtsize.x, ", ", rtsize.y, ")"); vrmodels = (vr::IVRRenderModels *)vr::VR_GetGenericInterface(vr::IVRRenderModels_Version, &err); if(!vrmodels) { VRShutDown(); LOG_ERROR("VR get render models failed: ", vr::VR_GetVRInitErrorAsEnglishDescription(err)); return false; } if (!vr::VRCompositor()) { VRShutDown(); LOG_ERROR("VR compositor failed to initialize"); return false; } // Get focus? vr::VRCompositor()->WaitGetPoses(trackeddeviceposes, vr::k_unMaxTrackedDeviceCount, NULL, 0); return true; #else (void)rtsize; return false; #endif // PLATFORM_VR } void VRStart() { #ifdef PLATFORM_VR if (!vrsys) return; auto err = vr::VRCompositor()->WaitGetPoses(trackeddeviceposes, vr::k_unMaxTrackedDeviceCount, NULL, 0); (void)err; assert(!err); // Feels worse with this prediction? /* float fSecondsSinceLastVsync = 0; vrsys->GetTimeSinceLastVsync(&fSecondsSinceLastVsync, NULL); float fDisplayFrequency = vrsys->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float); float fFrameDuration = 1.f / fDisplayFrequency; float fVsyncToPhotons = vrsys->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float); float fPredictedSecondsFromNow = fFrameDuration - fSecondsSinceLastVsync + fVsyncToPhotons; vrsys->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, fPredictedSecondsFromNow, trackeddeviceposes, vr::k_unMaxTrackedDeviceCount); */ size_t mcn = 0; for (uint device = 0; device < vr::k_unMaxTrackedDeviceCount; device++) { if (vrsys->GetTrackedDeviceClass(device) != vr::TrackedDeviceClass_Controller) continue; if (mcn == motioncontrollers.size()) { MotionController mc; memset(&mc, 0, sizeof(mc)); motioncontrollers.push_back(mc); } auto &mc = motioncontrollers[mcn]; mc.tracking = trackeddeviceposes[device].bPoseIsValid; mc.mat = mc.tracking ? FromOpenVR(trackeddeviceposes[device].mDeviceToAbsoluteTracking) : float4x4_1; mc.device = device; mc.laststate = mc.state; auto ok = vrsys->GetControllerState(device, &mc.state); if (!ok) memset(&mc.state, 0, sizeof(vr::VRControllerState_t)); mcn++; } #endif // PLATFORM_VR } void VREye(int eye, float znear, float zfar) { #ifdef PLATFORM_VR if (!vrsys) return; auto retf = TF_CLAMP | TF_NOMIPMAP; auto mstf = retf | TF_MULTISAMPLE; if (!mstex[eye].id) mstex[eye] = CreateBlankTexture(rtsize, float4_0, mstf); if (!retex[eye].id) retex[eye] = CreateBlankTexture(rtsize, float4_0, retf); SwitchToFrameBuffer(mstex[eye], true, mstf, retex[eye]); auto proj = FromOpenVR(vrsys->GetProjectionMatrix((vr::EVREye)eye, znear, zfar, vr::API_OpenGL)); Set3DMode(80, 1, znear, zfar); view2clip = proj; // Override the projection set by Set3DMode auto eye2head = FromOpenVR(vrsys->GetEyeToHeadTransform((vr::EVREye)eye)); auto vrview = eye2head; if (trackeddeviceposes[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid) { hmdpose = FromOpenVR( trackeddeviceposes[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking); vrview = hmdpose * vrview; } AppendTransform(invert(vrview), vrview); #endif // PLATFORM_VR } void VRFinish() { #ifdef PLATFORM_VR if (!vrsys) return; SwitchToFrameBuffer(Texture(0, GetScreenSize())); for (int i = 0; i < 2; i++) { vr::Texture_t vrtex = { (void *)(size_t)retex[i].id, vr::API_OpenGL, vr::ColorSpace_Gamma }; auto err = vr::VRCompositor()->Submit((vr::EVREye)i, &vrtex); (void)err; assert(!err); } // This should be after swap, but isn't needed if it is followed by WaitGetPoses anyway. //vr::VRCompositor()->PostPresentHandoff(); #endif // PLATFORM_VR } Mesh *VRCreateMesh(uint device) { #ifdef PLATFORM_VR if (!vrsys) return 0; auto name = GetTrackedDeviceString(device, vr::Prop_RenderModelName_String); vr::RenderModel_t *model = nullptr; for (;;) { auto err = vr::VRRenderModels()->LoadRenderModel_Async(name.c_str(), &model); if (err == vr::VRRenderModelError_None) { break; } else if (err != vr::VRRenderModelError_Loading) { return 0; } SDL_Delay(1); } vr::RenderModel_TextureMap_t *modeltex = nullptr; for (;;) { auto err = vr::VRRenderModels()->LoadTexture_Async(model->diffuseTextureId, &modeltex); if (err == vr::VRRenderModelError_None) { break; } else if (err != vr::VRRenderModelError_Loading) { vr::VRRenderModels()->FreeRenderModel(model); return 0; } SDL_Delay(1); } auto tex = CreateTexture(modeltex->rubTextureMapData, int2(modeltex->unWidth, modeltex->unHeight).data(), TF_CLAMP); auto m = new Mesh(new Geometry(make_span(model->rVertexData, model->unVertexCount), "PNT"), PRIM_TRIS); auto nindices = model->unTriangleCount * 3; vector indices(nindices); for (uint i = 0; i < nindices; i += 3) { indices[i + 0] = model->rIndexData[i + 0]; indices[i + 1] = model->rIndexData[i + 2]; indices[i + 2] = model->rIndexData[i + 1]; } auto surf = new Surface(make_span(indices), PRIM_TRIS); surf->Get(0) = tex; m->surfs.push_back(surf); vr::VRRenderModels()->FreeRenderModel(model); vr::VRRenderModels()->FreeTexture(modeltex); return m; #else return nullptr; #endif // PLATFORM_VR } using namespace lobster; MotionController *GetMC(const Value &mc) { auto n = mc.ival(); return n >= 0 && n < (int)motioncontrollers.size() ? &motioncontrollers[n] : nullptr; }; #ifdef PLATFORM_VR vr::EVRButtonId GetButtonId(VM &vm, Value &button) { auto it = button_ids.find(button.sval()->strv()); if (it == button_ids.end()) vm.BuiltinError("unknown button name: " + button.sval()->strv()); return it->second; } #endif // PLATFORM_VR void AddVR(NativeRegistry &nfr) { nfr("vr_init", "", "", "B", "initializes VR mode. returns true if a hmd was found and initialized", [](VM &) { return Value(VRInit()); }); nfr("vr_start_eye", "isright,znear,zfar", "IFF", "", "starts rendering for an eye. call for each eye, followed by drawing the world as normal." " replaces gl_perspective", [](VM &, Value &isright, Value &znear, Value &zfar) { VREye(isright.True(), znear.fltval(), zfar.fltval()); return Value(); }); nfr("vr_start", "", "", "", "starts VR by updating hmd & controller poses", [](VM &) { VRStart(); return Value(); }); nfr("vr_finish", "", "", "", "finishes vr rendering by compositing (and distorting) both eye renders to the screen", [](VM &) { VRFinish(); return Value(); }); nfr("vr_set_eye_texture", "unit,isright", "II", "", "sets the texture for an eye (like gl_set_primitive_texture). call after vr_finish. can be" " used to render the non-VR display", [](VM &vm, Value &unit, Value &isright) { extern int GetSampler(VM &vm, Value &i); SetTexture(GetSampler(vm, unit), retex[isright.True()]); return Value(); }); nfr("vr_num_motion_controllers", "", "", "I", "returns the number of motion controllers in the system", [](VM &) { return Value((int)motioncontrollers.size()); }); nfr("vr_motioncontrollerstracking", "n", "I", "B", "returns if motion controller n is tracking", [](VM &, Value &mc) { auto mcd = GetMC(mc); return Value(mcd && mcd->tracking); }); extern Value PushTransform(VM &vm, const float4x4 &forward, const float4x4 &backward, const Value &body); extern void PopTransform(VM &vm); nfr("vr_motion_controller", "n,body", "IL?", "", "sets up the transform ready to render controller n." " when a body is given, restores the previous transform afterwards." " if there is no controller n (or it is currently not" " tracking) the identity transform is used", [](VM &vm, Value &mc, Value &body) { auto mcd = GetMC(mc); return mcd ? PushTransform(vm, mcd->mat, invert(mcd->mat), body) : PushTransform(vm, float4x4_1, float4x4_1, body); }, [](VM &vm) { PopTransform(vm); }); nfr("vr_create_motion_controller_mesh", "n", "I", "R?", "returns the mesh for motion controller n, or nil if not available", [](VM &vm, Value &mc) { auto mcd = GetMC(mc); extern ResourceType mesh_type; return mcd ? Value(vm.NewResource(VRCreateMesh(mcd->device), &mesh_type)) : Value(); }); nfr("vr_motion_controller_button", "n,button", "IS", "I", "returns the button state for motion controller n." " isdown: >= 1, wentdown: == 1, wentup: == 0, isup: <= 0." " buttons are: system, menu, grip, trigger, touchpad", [](VM &vm, Value &mc, Value &button) { #ifdef PLATFORM_VR auto mcd = GetMC(mc); auto mask = ButtonMaskFromId(GetButtonId(vm, button)); if (!mcd) return Value(TimeBool8().Step()); auto masknow = mcd->state.ulButtonPressed & mask; auto maskbef = mcd->laststate.ulButtonPressed & mask; return Value(TimeBool8(masknow != 0, maskbef != 0).Step()); #else return Value(0); #endif }); nfr("vr_motion_controller_vec", "n,i", "II", "F}:3", "returns one of the vectors for motion controller n. 0 = left, 1 = up, 2 = fwd, 4 = pos." " These are in Y up space.", [](VM &vm) { auto idx = vm.Pop(); auto mcd = GetMC(vm.Pop()); if (!mcd) { vm.PushVec(float3_0); return; } auto i = RangeCheck(vm, idx, 4); vm.PushVec(mcd->mat[i].xyz()); }); nfr("vr_hmd_vec", "i", "I", "F]:3", "returns one of the vectors for hmd pose. 0 = left, 1 = up, 2 = fwd, 4 = pos." " These are in Y up space.", [](VM &vm) { auto i = RangeCheck(vm, vm.Pop(), 4); vm.PushVec(hmdpose[i].xyz()); }); } // AddVR treesheets-1.0.2/lobster/src/graphics.cpp000066400000000000000000001270271352107072600204460ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/glinterface.h" #include "lobster/sdlinterface.h" #include "lobster/engine.h" using namespace lobster; Primitive polymode = PRIM_FAN; Shader *currentshader = NULL; Shader *colorshader = NULL; float3 lasthitsize = float3_0; float3 lastframehitsize = float3_0; bool graphics_initialized = false; ResourceType mesh_type = { "mesh", [](void *m) { delete (Mesh *)m; } }; ResourceType texture_type = { "texture", [](void *t) { auto tex = (Texture *)t; DeleteTexture(*tex); delete tex; } }; Mesh &GetMesh(VM &vm, Value &res) { return *GetResourceDec(vm, res, &mesh_type); } Texture GetTexture(VM &vm, const Value &res) { auto tex = GetResourceDec(vm, res, &texture_type); return tex ? *tex : Texture(); } // Should be safe to call even if it wasn't initialized partially or at all. // FIXME: move this elsewhere. void GraphicsShutDown() { extern void SteamShutDown(); SteamShutDown(); VRShutDown(); extern void CleanPhysics(); CleanPhysics(); extern void MeshGenClear(); MeshGenClear(); extern void CubeGenClear(); CubeGenClear(); extern void FontCleanup(); FontCleanup(); extern void IMGUICleanup(); IMGUICleanup(); ShaderShutDown(); currentshader = NULL; colorshader = NULL; OpenGLCleanup(); SDLSoundClose(); SDLShutdown(); // We don't set this to false on most platforms, as currently SDL doesn't like being // reinitialized #ifdef __ANDROID__ // FIXME: really only allow this if the app has been killed graphics_initialized = false; #endif } bool GraphicsFrameStart() { extern void CullFonts(); CullFonts(); extern void SteamUpdate(); SteamUpdate(); bool cb = SDLFrame(); lastframehitsize = lasthitsize; lasthitsize = float3_0; OpenGLFrameStart(GetScreenSize()); Set2DMode(GetScreenSize(), true); currentshader = colorshader; return cb; } void TestGL(VM &vm) { if (!graphics_initialized) vm.BuiltinError("graphics system not initialized yet, call gl_window() first"); } float2 localpos(const int2 &pos) { return (otransforms.view2object * float4(float3(float2(pos), 0), 1)).xyz().xy(); } float2 localfingerpos(int i) { return localpos(GetFinger(i, false)); } Value PushTransform(VM &vm, const float4x4 &forward, const float4x4 &backward, const Value &body) { if (body.True()) vm.PushAnyAsString(otransforms); AppendTransform(forward, backward); return body; } void PopTransform(VM &vm) { vm.PopAnyFromString(otransforms); } int GetSampler(VM &vm, Value &i) { if (i.ival() < 0 || i.ival() >= Shader::MAX_SAMPLERS) vm.BuiltinError("graphics: illegal texture unit"); return i.intval(); } Mesh *CreatePolygon(VM &vm, Value &vl) { TestGL(vm); auto len = vl.vval()->len; if (len < 3) vm.BuiltinError("polygon: must have at least 3 verts"); vector vbuf(len); for (int i = 0; i < len; i++) vbuf[i].pos = ValueToFLT<3>(vl.vval()->AtSt(i), vl.vval()->width); auto v1 = vbuf[1].pos - vbuf[0].pos; auto v2 = vbuf[2].pos - vbuf[0].pos; auto norm = normalize(cross(v2, v1)); for (int i = 0; i < len; i++) { vbuf[i].norm = norm; vbuf[i].tc = vbuf[i].pos.xy(); vbuf[i].col = byte4_255; } auto m = new Mesh(new Geometry(make_span(vbuf), "PNTC"), polymode); return m; } Value SetUniform(VM &vm, const Value &name, const float *data, int len, bool ignore_errors) { TestGL(vm); currentshader->Activate(); auto ok = currentshader->SetUniform(name.sval()->strv(), data, len); if (!ok && !ignore_errors) vm.Error("failed to set uniform: " + name.sval()->strv()); return Value(ok); } void AddGraphics(NativeRegistry &nfr) { nfr("gl_window", "title,xs,ys,fullscreen,novsync,samples", "SIII?I?I?", "S?", "opens a window for OpenGL rendering. returns error string if any problems, nil" " otherwise.", [](VM &vm, Value &title, Value &xs, Value &ys, Value &fullscreen, Value &novsync, Value &samples) { if (graphics_initialized) vm.BuiltinError("cannot call gl_window() twice"); string err = SDLInit(title.sval()->strv(), int2(intp2(xs.ival(), ys.ival())), fullscreen.ival() != 0, novsync.ival() == 0, max(1, samples.intval())); if (err.empty()) { err = LoadMaterialFile("data/shaders/default.materials"); } if (!err.empty()) { LOG_INFO(err); return Value(vm.NewString(err)); } colorshader = LookupShader("color"); assert(colorshader); LOG_INFO("graphics fully initialized..."); graphics_initialized = true; return Value(); }); nfr("gl_require_version", "major,minor", "II", "", "Call this before gl_window to request a certain version of OpenGL context." " Currently only works on win/nix, minimum is 3.2.", [](VM &, Value &major, Value &minor) { SDLRequireGLVersion(major.intval(), minor.intval()); return Value(); }); nfr("gl_load_materials", "materialdefs,inline", "SI?", "S?", "loads an additional materials file (data/shaders/default.materials is already loaded by default" " by gl_window()). if inline is true, materialdefs is not a filename, but the actual" " materials. returns error string if any problems, nil otherwise.", [](VM &vm, Value &fn, Value &isinline) { TestGL(vm); auto err = isinline.True() ? ParseMaterialFile(fn.sval()->strv()) : LoadMaterialFile(fn.sval()->strv()); return err[0] ? Value(vm.NewString(err)) : Value(); }); nfr("gl_frame", "", "", "B", "advances rendering by one frame, swaps buffers, and collects new input events." " returns true if the closebutton on the window was pressed", [](VM &vm) { TestGL(vm); EngineSuspendIfNeeded(); auto cb = GraphicsFrameStart(); vm.vml.LogFrame(); return Value(!cb); }); nfr("gl_log_frame", "delta", "F", "", "call this function instead of gl_frame() to simulate a frame based program from" " non-graphical code. does not require gl_window(). manages frame log state much like" " gl_frame(). allows gl_time and gl_delta_time to work. pass a desired delta time," " e.g. 1.0/60.0", [](VM &vm, Value &delta) { SDLUpdateTime(delta.fval()); vm.vml.LogFrame(); return Value(); }); nfr("gl_shutdown", "", "", "", "shuts down the OpenGL window. you only need to call this function if you wish to close it" " before the end of the program", [](VM &) { GraphicsShutDown(); return Value(); }); nfr("gl_window_title", "title", "S", "Sb", "changes the window title.", [](VM &vm, Value &s) { TestGL(vm); SDLTitle(s.sval()->strv()); return s; }); nfr("gl_window_min_max", "dir", "I", "", ">0 to maximize, <0 to minimize or 0 to restore.", [](VM &vm, Value &dir) { TestGL(vm); SDLWindowMinMax(dir.intval()); return Value(); }); nfr("gl_visible", "", "", "B", "checks if the window is currently visible (not minimized, or on mobile devices, in the" " foreground). If false, you should not render anything, nor run the frame's code.", [](VM &) { return Value(!SDLIsMinimized()); }); nfr("gl_cursor", "on", "B", "B", "default the cursor is visible, turn off for implementing FPS like control schemes. return" " wether it's on.", [](VM &vm, Value &on) { TestGL(vm); return Value(SDLCursor(on.ival() != 0)); }); nfr("gl_grab", "on", "B", "B", "grabs the mouse when the window is active. return wether it's on.", [](VM &vm, Value &on) { TestGL(vm); return Value(SDLGrab(on.ival() != 0)); }); nfr("gl_button", "name", "S", "B", "returns the state of a key/mousebutton/finger." " isdown: >= 1, wentdown: == 1, wentup: == 0, isup: <= 0." " (pass a string like mouse1/mouse2/mouse3/escape/space/up/down/a/b/f1 etc." " mouse11 and on are additional fingers)", [](VM &, Value &name) { auto ks = GetKS(name.sval()->strv()); return Value(ks.Step()); }); nfr("gl_touchscreen", "", "", "B", "wether a you\'re getting input from a touch screen (as opposed to mouse & keyboard)", [](VM &) { #ifdef PLATFORM_TOUCH return Value(true); #else return Value(false); #endif }); nfr("gl_dpi", "screen", "I", "I", "the DPI of the screen. always returns a value for screen 0, any other screens may return" " 0 to indicate the screen doesn\'t exist", [](VM &, Value &screen) { return Value(SDLScreenDPI(screen.intval())); }); nfr("gl_window_size", "", "", "I}:2", "a vector representing the size (in pixels) of the window, changes when the user resizes", [](VM &vm) { vm.PushVec(GetScreenSize()); }); nfr("gl_mouse_pos", "i", "I", "I}:2", "the current mouse/finger position in pixels, pass a value other than 0 to read additional" " fingers (for touch screens only if the corresponding gl_isdown is true)", [](VM &vm) { vm.PushVec(GetFinger(vm.Pop().intval(), false)); }); nfr("gl_mouse_delta", "i", "I", "I}:2", "number of pixels the mouse/finger has moved since the last frame. use this instead of" " substracting positions to correctly deal with lifted fingers and FPS mode" " (gl_cursor(0))", [](VM &vm) { vm.PushVec(GetFinger(vm.Pop().intval(), true)); }); nfr("gl_local_mouse_pos", "i", "I", "F}:2", "the current mouse/finger position local to the current transform (gl_translate etc)" " (for touch screens only if the corresponding gl_isdown is true)", [](VM &vm) { vm.PushVec(localfingerpos(vm.Pop().intval())); }); nfr("gl_last_pos", "name,down", "SI", "I}:2", "position (in pixels) key/mousebutton/finger last went down (true) or up (false)", [](VM &vm) { auto on = vm.Pop().intval(); auto name = vm.Pop().sval(); auto p = GetKeyPos(name->strv(), on); vm.PushVec(p); }); nfr("gl_local_last_pos", "name,down", "SI", "F}:2", "position (local to the current transform) key/mousebutton/finger last went down (true) or" " up (false)", [](VM &vm) { auto on = vm.Pop().intval(); auto name = vm.Pop().sval(); auto p = localpos(GetKeyPos(name->strv(), on)); vm.PushVec(p); }); nfr("gl_mousewheel_delta", "", "", "I", "amount the mousewheel scrolled this frame, in number of notches", [](VM &) { return Value(SDLWheelDelta()); }); nfr("gl_joy_axis", "i", "I", "F", "the current joystick orientation for axis i, as -1 to 1 value", [](VM &, Value &i) { return Value(GetJoyAxis(i.intval())); }); nfr("gl_delta_time", "", "", "F", "seconds since the last frame, updated only once per frame", [](VM &) { return Value(SDLDeltaTime()); }); nfr("gl_time", "", "", "F", "seconds since the start of the OpenGL subsystem, updated only once per frame (use" " seconds_elapsed() for continuous timing)", [](VM &) { return Value(SDLTime()); }); nfr("gl_last_time", "name,down", "SI", "F", "time key/mousebutton/finger last went down (true) or up (false)", [](VM &, Value &name, Value &on) { auto t = GetKeyTime(name.sval()->strv(), on.intval()); return Value(t); }); nfr("gl_clear", "col", "F}:4", "", "clears the framebuffer (and depth buffer) to the given color", [](VM &vm) { TestGL(vm); ClearFrameBuffer(vm.PopVec()); }); nfr("gl_color", "col,body", "F}:4L?", "", "sets the current color. when a body is given, restores the previous color afterwards", [](VM &vm) { auto body = vm.Pop(); auto col = vm.PopVec(); auto cc = quantizec(curcolor); if (body.True()) vm.Push(*(int *)&cc); curcolor = col; vm.Push(body); }, [](VM &vm) { auto tmpcol = vm.Pop().intval(); curcolor = color2vec(*(byte4 *)&tmpcol); }); nfr("gl_polygon", "vertlist", "F}]", "", "renders a polygon using the list of points given." " warning: gl_polygon creates a new mesh every time, gl_new_poly/gl_render_mesh is faster.", [](VM &vm, Value &vl) { auto m = CreatePolygon(vm, vl); m->Render(currentshader); delete m; return Value(); }); nfr("gl_circle", "radius,segments", "FI", "", "renders a circle", [](VM &vm, Value &radius, Value &segments) { TestGL(vm); geomcache->RenderCircle(currentshader, polymode, max(segments.intval(), 3), radius.fltval()); return Value(); }); nfr("gl_open_circle", "radius,segments,thickness", "FIF", "", "renders a circle that is open on the inside. thickness is the fraction of the radius that" " is filled, try e.g. 0.2", [](VM &vm, Value &radius, Value &segments, Value &thickness) { TestGL(vm); geomcache->RenderOpenCircle(currentshader, max(segments.intval(), 3), radius.fltval(), thickness.fltval()); return Value(); }); nfr("gl_unit_cube", "insideout", "I?", "", "renders a unit cube (0,0,0) - (1,1,1). optionally pass true to have it rendered inside" " out", [](VM &, Value &inside) { geomcache->RenderUnitCube(currentshader, inside.True()); return Value(); }); nfr("gl_rotate_x", "vector,body", "F}:2L?", "", "rotates the yz plane around the x axis, using a 2D vector normalized vector as angle." " when a body is given, restores the previous transform afterwards", [](VM &vm) { auto body = vm.Pop(); auto a = vm.PopVec(); vm.Push(PushTransform(vm, rotationX(a), rotationX(a * float2(1, -1)), body)); }, [](VM &vm) { PopTransform(vm); }); nfr("gl_rotate_y", "angle,body", "F}:2L?", "", "rotates the xz plane around the y axis, using a 2D vector normalized vector as angle." " when a body is given, restores the previous transform afterwards", [](VM &vm) { auto body = vm.Pop(); auto a = vm.PopVec(); vm.Push(PushTransform(vm, rotationY(a), rotationY(a * float2(1, -1)), body)); }, [](VM &vm) { PopTransform(vm); }); nfr("gl_rotate_z", "angle,body", "F}:2L?", "", "rotates the xy plane around the z axis (used in 2D), using a 2D vector normalized vector" " as angle. when a body is given, restores the previous transform afterwards", [](VM &vm) { auto body = vm.Pop(); auto a = vm.PopVec(); vm.Push(PushTransform(vm, rotationZ(a), rotationZ(a * float2(1, -1)), body)); }, [](VM &vm) { PopTransform(vm); }); nfr("gl_translate", "vec,body", "F}L?", "", "translates the current coordinate system along a vector. when a body is given," " restores the previous transform afterwards", [](VM &vm) { auto body = vm.Pop(); auto v = vm.PopVec(); vm.Push(PushTransform(vm, translation(v), translation(-v), body)); }, [](VM &vm) { PopTransform(vm); }); nfr("gl_scale", "factor,body", "FL?", "", "scales the current coordinate system using a numerical factor." " when a body is given, restores the previous transform afterwards", [](VM &vm, Value &f, Value &body) { auto v = f.fltval() * float3_1; return PushTransform(vm, float4x4(float4(v, 1)), float4x4(float4(float3_1 / v, 1)), body); }, [](VM &vm) { PopTransform(vm); }); nfr("gl_scale", "factor,body", "F}L?", "", "scales the current coordinate system using a vector." " when a body is given, restores the previous transform afterwards", [](VM &vm) { auto body = vm.Pop(); auto v = vm.PopVec(); vm.Push(PushTransform(vm, float4x4(float4(v, 1)), float4x4(float4(float3_1 / v, 1)), body)); }, [](VM &vm) { PopTransform(vm); }); nfr("gl_origin", "", "", "F}:2", "returns a vector representing the current transform origin in pixels." " only makes sense in 2D mode (no gl_perspective called).", [](VM &vm) { auto pos = floatp2(otransforms.object2view[3].x, otransforms.object2view[3].y); vm.PushVec(pos); }); nfr("gl_scaling", "", "", "F}:2", "returns a vector representing the current transform scale in pixels." " only makes sense in 2D mode (no gl_perspective called).", [](VM &vm) { auto sc = floatp2(otransforms.object2view[0].x, otransforms.object2view[1].y); vm.PushVec(sc); }); nfr("gl_model_view_projection", "", "", "F]", "returns a vector representing the current model view projection matrix" " (16 elements)", [](VM &vm) { auto v = vm.NewVec(16, 16, TYPE_ELEM_VECTOR_OF_FLOAT); auto mvp = view2clip * otransforms.object2view; for (int i = 0; i < 16; i++) v->At(i) = mvp.data()[i]; return Value(v); }); nfr("gl_point_scale", "factor", "F", "", "sets the current scaling factor for point sprites." " this can be what the current gl_scale is, or different, depending on the desired visuals." " the ideal size may also be FOV dependent.", [](VM &, Value &f) { custompointscale = f.fltval(); return Value(); }); nfr("gl_line_mode", "on,body", "IL", "", "set line mode (true == on). when a body is given," " restores the previous mode afterwards", [](VM &vm, Value &on, Value &body) { if (body.True()) vm.Push(Value(polymode)); polymode = on.ival() ? PRIM_LOOP : PRIM_FAN; return body; }, [](VM &vm) { polymode = (Primitive)vm.Pop().ival(); }); nfr("gl_hit", "vec,i", "F}I", "B", "wether the mouse/finger is inside of the rectangle specified in terms of the current" " transform (for touch screens only if the corresponding gl_isdown is true). Only true if" " the last rectangle for which gl_hit was true last frame is of the same size as this one" " (allows you to safely test in most cases of overlapping rendering)", [](VM &vm) { auto i = vm.Pop().intval(); auto size = vm.PopVec(); auto localmousepos = localfingerpos(i); auto hit = localmousepos.x >= 0 && localmousepos.y >= 0 && localmousepos.x < size.x && localmousepos.y < size.y; if (hit) lasthitsize = size; /* #ifdef PLATFORM_TOUCH // Inefficient for fingers other than 0, which is going to be rare. auto ks = i ? GetKS((string_view("mouse1") + (char)('0' + i)).c_str()) : GetKS("mouse1"); // On mobile, if the finger just went down, we wont have meaningfull lastframehitsize, so if // the programmer checks for the combination of gl_hit and gl_wentdown, that would fail. // Instead, we bypass that check. // PROBLEM: now we'll be returning true for overlapping elements. // if we can solve this, we can remove the frame delay from the input system. if (ks.wentdown && hit) return true; #endif */ vm.Push(size == lastframehitsize && hit); }); nfr("gl_rect", "size,centered", "F}:2I?", "", "renders a rectangle (0,0)..(1,1) (or (-1,-1)..(1,1) when centered), scaled by the given" " size.", [](VM &vm) { auto centered = vm.Pop().True(); auto vec = vm.PopVec(); TestGL(vm); geomcache->RenderQuad(currentshader, polymode, centered, float4x4(float4(vec, 1))); }); nfr("gl_rect_tc_col", "size,tc,tcsize,cols", "F}:2F}:2F}:2F}:4]", "", "Like gl_rect renders a sized quad, but allows you to specify texture coordinates and" " optionally colors (empty list for all white). Slow.", [](VM &vm) { TestGL(vm); auto cols = vm.Pop().vval(); auto td = vm.PopVec(); auto t = vm.PopVec(); auto sz = vm.PopVec(); auto te = t + td; struct Vert { float x, y, z, u, v; byte4 c; }; Vert vb_square[4] = { #define _GETCOL(N) \ cols->len > N ? quantizec(ValueToFLT<4>(cols->AtSt(N), cols->width)) : byte4_255 { 0, 0, 0, t.x, t.y, _GETCOL(0) }, { 0, sz.y, 0, t.x, te.y, _GETCOL(1) }, { sz.x, sz.y, 0, te.x, te.y, _GETCOL(2) }, { sz.x, 0, 0, te.x, t.y, _GETCOL(3) } }; currentshader->Set(); RenderArraySlow(PRIM_FAN, make_span(vb_square, 4), "PTC"); }); nfr("gl_unit_square", "centered", "I?", "", "renders a square (0,0)..(1,1) (or (-1,-1)..(1,1) when centered)", [](VM &vm, Value ¢ered) { TestGL(vm); geomcache->RenderUnitSquare(currentshader, polymode, centered.True()); return Value(); }); nfr("gl_line", "start,end,thickness", "F}F}F", "", "renders a line with the given thickness", [](VM &vm) { TestGL(vm); auto thickness = vm.Pop().fltval(); auto v2 = vm.PopVec(); auto v1 = vm.PopVec(); if (Is2DMode()) geomcache->RenderLine2D(currentshader, polymode, v1, v2, thickness); else geomcache->RenderLine3D(currentshader, v1, v2, float3_0, thickness); }); nfr("gl_perspective", "fovy,znear,zfar", "FFF", "", "changes from 2D mode (default) to 3D right handed perspective mode with vertical fov (try" " 60), far plane (furthest you want to be able to render, try 1000) and near plane (try" " 1)", [](VM &, Value &fovy, Value &znear, Value &zfar) { Set3DMode(fovy.fltval() * RAD, GetScreenSize().x / (float)GetScreenSize().y, znear.fltval(), zfar.fltval()); return Value(); }); nfr("gl_ortho", "rh,depth", "I?I?", "", "changes back to 2D mode rendering with a coordinate system from (0,0) top-left to the" " screen size in pixels bottom right. this is the default at the start of a frame, use this" " call to get back to that after gl_perspective." " Pass true to rh have (0,0) bottom-left instead." " Pass true to depth to have depth testing/writing on.", [](VM &, Value &rh, Value &depth) { Set2DMode(GetScreenSize(), !rh.True(), depth.True()); return Value(); }); nfr("gl_ortho3d", "center,extends", "F}F}", "", "sets a custom ortho projection as 3D projection.", [](VM &vm) { auto extends = vm.PopVec(); auto center = vm.PopVec(); Set3DOrtho(center, extends); }); nfr("gl_new_poly", "positions", "F}]", "R", "creates a mesh out of a loop of points, much like gl_polygon." " gl_line_mode determines how this gets drawn (fan or loop)." " returns mesh id", [](VM &vm, Value &positions) { auto m = CreatePolygon(vm, positions); return Value(vm.NewResource(m, &mesh_type)); }); nfr("gl_new_mesh", "format,positions,colors,normals,texcoords1,texcoords2,indices", "SF}:3]F}:4]F}:3]F}:2]F}:2]I]?", "R", "creates a new vertex buffer and returns an integer id (1..) for it." " format must be made up of characters P (position), C (color), T (texcoord), N (normal)." " indices may be []. positions is obligatory." " you may specify [] for any of the other attributes if not required by format," " or to get defaults for colors (white) / texcoords (position x & y) /" " normals (generated from adjacent triangles).", [](VM &vm, Value &format, Value &positions, Value &colors, Value &normals, Value &texcoords1, Value &texcoords2, Value &indices) { TestGL(vm); auto nattr = format.sval()->len; if (nattr < 1 || nattr > 10) vm.BuiltinError("newmesh: illegal format/attributes size"); auto fmt = format.sval()->strv(); if (nattr > (int)min(fmt.find_first_not_of("PCTN"), fmt.size()) || fmt[0] != 'P') vm.BuiltinError("newmesh: illegal format characters (only PCTN allowed), P must be" " first"); intp nverts = positions.vval()->len; vector idxs; if (indices.True()) { for (int i = 0; i < indices.vval()->len; i++) { auto &e = indices.vval()->At(i); if (e.ival() < 0 || e.ival() >= nverts) vm.BuiltinError("newmesh: index out of range of vertex list"); idxs.push_back(e.intval()); } } size_t vsize = AttribsSize(fmt); size_t normal_offset = 0; auto verts = new uchar[nverts * vsize]; for (intp i = 0; i < nverts; i++) { auto start = &verts[i * vsize]; auto p = start; float3 pos; int texcoordn = 0; for (auto c : fmt) { switch (c) { case 'P': { pos = ValueToFLT<3>(positions.vval()->AtSt(i), positions.vval()->width); WriteMemInc(p, pos); break; } case 'C': WriteMemInc(p, i < colors.vval()->len ? quantizec(ValueToFLT<4>(colors.vval()->AtSt(i), colors.vval()->width, 1)) : byte4_255); break; case 'T': { auto &texcoords = texcoordn ? texcoords2 : texcoords1; WriteMemInc(p, i < texcoords.vval()->len ? ValueToFLT<2>(texcoords.vval()->AtSt(i), texcoords.vval()->width, 0) : pos.xy()); texcoordn++; break; } case 'N': if (!normals.vval()->len) normal_offset = p - start; WriteMemInc(p, i < normals.vval()->len ? ValueToFLT<3>(normals.vval()->AtSt(i), normals.vval()->width, 0) : float3_0); break; default: assert(0); } } } if (normal_offset) { // if no normals were specified, generate them. normalize_mesh(make_span(idxs), verts, nverts, vsize, normal_offset); } // FIXME: make meshes into points in a more general way. auto m = new Mesh(new Geometry(make_span(verts, nverts * vsize), fmt, span(), vsize), indices.True() ? PRIM_TRIS : PRIM_POINT); if (idxs.size()) m->surfs.push_back(new Surface(make_span(idxs))); delete[] verts; return Value(vm.NewResource(m, &mesh_type)); }); nfr("gl_new_mesh_iqm", "filename", "S", "R?", "load a .iqm file into a mesh, returns mesh or nil on failure to load.", [](VM &vm, Value &fn) { TestGL(vm); auto m = LoadIQM(fn.sval()->strv()); return m ? Value(vm.NewResource(m, &mesh_type)) : Value(); }); nfr("gl_mesh_parts", "m", "R", "S]", "returns an array of names of all parts of mesh m (names may be empty)", [](VM &vm, Value &i) { auto &m = GetMesh(vm, i); auto v = (LVector *)vm.NewVec(0, (int)m.surfs.size(), TYPE_ELEM_VECTOR_OF_STRING); for (auto s : m.surfs) v->Push(vm, Value(vm.NewString(s->name))); return Value(v); }); nfr("gl_mesh_size", "m", "R", "I", "returns the number of verts in this mesh", [](VM &vm, Value &i) { auto &m = GetMesh(vm, i); return Value((int)m.geom->nverts); }); nfr("gl_animate_mesh", "m,frame", "RF", "", "set the frame for animated mesh m", [](VM &vm, Value &i, Value &f) { GetMesh(vm, i).curanim = f.fltval(); return Value(); }); nfr("gl_render_mesh", "m", "R", "", "renders the specified mesh", [](VM &vm, Value &i) { TestGL(vm); GetMesh(vm, i).Render(currentshader); return Value(); }); nfr("gl_save_mesh", "m,name", "RS", "B", "saves the specified mesh to a file in the PLY format. useful if the mesh was generated" " procedurally. returns false if the file could not be written", [](VM &vm, Value &i, Value &name) { TestGL(vm); bool ok = GetMesh(vm, i).SaveAsPLY(name.sval()->strv()); return Value(ok); }); nfr("gl_set_shader", "shader", "S", "", "changes the current shader. shaders must reside in the shaders folder, builtin ones are:" " color / textured / phong", [](VM &vm, Value &shader) { TestGL(vm); auto sh = LookupShader(shader.sval()->strv()); if (!sh) vm.BuiltinError("no such shader: " + shader.sval()->strv()); currentshader = sh; return Value(); }); nfr("gl_set_uniform", "name,value,ignore_errors", "SF}I?", "B", "set a uniform on the current shader. size of float vector must match size of uniform" " in the shader.", [](VM &vm) { auto ignore_errors = vm.Pop().True(); auto len = vm.Top().intval(); auto v = vm.PopVec(); auto r = SetUniform(vm, vm.Pop(), v.begin(), len, ignore_errors); vm.Push(r); }); nfr("gl_set_uniform", "name,value,ignore_errors", "SFI?", "B", "set a uniform on the current shader. uniform" " in the shader must be a single float.", [](VM &vm, Value &name, Value &vec, Value &ignore_errors) { auto f = vec.fltval(); return SetUniform(vm, name, &f, 1, ignore_errors.True()); }); nfr("gl_set_uniform_array", "name,value", "SF}:4]", "B", "set a uniform on the current shader. uniform in the shader must be an array of vec4." " returns false on error.", [](VM &vm, Value &name, Value &vec) { TestGL(vm); vector vals(vec.vval()->len); for (int i = 0; i < vec.vval()->len; i++) vals[i] = ValueToFLT<4>(vec.vval()->AtSt(i), vec.vval()->width); currentshader->Activate(); auto ok = currentshader->SetUniform(name.sval()->strv(), vals.data()->data(), 4, (int)vals.size()); return Value(ok); }); nfr("gl_set_uniform_matrix", "name,value", "SF]", "B", "set a uniform on the current shader. pass a vector of 4/9/12/16 floats to set a" " mat2/mat3/mat3x4/mat4 respectively. returns false on error.", [](VM &vm, Value &name, Value &vec) { TestGL(vm); vector vals(vec.vval()->len); for (int i = 0; i < vec.vval()->len; i++) vals[i] = vec.vval()->At(i).fltval(); currentshader->Activate(); auto ok = currentshader->SetUniformMatrix(name.sval()->strv(), vals.data(), (int)vals.size(), 1); return Value(ok); }); nfr("gl_uniform_buffer_object", "name,value,ssbo", "SF}:4]I", "I", "creates a uniform buffer object, and attaches it to the current shader at the given" " uniform block name. uniforms in the shader must be all vec4s, or an array of them." " ssbo indicates if you want a shader storage block instead." " returns buffer id or 0 on error.", [](VM &vm, Value &name, Value &vec, Value &ssbo) { TestGL(vm); vector vals(vec.vval()->len); for (int i = 0; i < vec.vval()->len; i++) vals[i] = ValueToFLT<4>(vec.vval()->AtSt(i), vec.vval()->width); auto id = UniformBufferObject(currentshader, vals.data()->data(), 4 * sizeof(float) * vals.size(), name.sval()->strv(), ssbo.True(), 0); return Value((int)id); }); nfr("gl_uniform_buffer_object", "name,value,ssbo", "SSI", "I", "creates a uniform buffer object, and attaches it to the current shader at the given" " uniform block name. uniforms in the shader can be any type, as long as it matches the" " data layout in the string buffer." " ssbo indicates if you want a shader storage block instead." " returns buffer id or 0 on error.", [](VM &vm, Value &name, Value &vec, Value &ssbo) { TestGL(vm); auto id = UniformBufferObject(currentshader, vec.sval()->strv().data(), vec.sval()->strv().size(), name.sval()->strv(), ssbo.True(), 0); return Value((int)id); }); nfr("gl_delete_buffer_object", "id", "I", "", "deletes a buffer objects, e.g. one allocated by gl_uniform_buffer_object().", [](VM &vm, Value &id) { TestGL(vm); // FIXME: should route this thru a IntResourceManagerCompact to be safe? // I guess GL doesn't care about illegal id's? DeleteBO(id.intval()); return Value(); }); nfr("gl_bind_mesh_to_compute", "mesh,name", "R?S", "", "Bind the vertex data of a mesh to a SSBO binding of a compute shader. Pass a nil mesh to" " unbind.", [](VM &vm, Value &mesh, Value &name) { TestGL(vm); if (mesh.True()) GetMesh(vm, mesh).geom->BindAsSSBO(currentshader, name.sval()->strv()); else UniformBufferObject(currentshader, nullptr, 0, name.sval()->strv(), true, 0); return Value(); }); nfr("gl_dispatch_compute", "groups", "I}:3", "", "dispatches the currently set compute shader in groups of sizes of the specified x/y/z" " values.", [](VM &vm) { auto groups = vm.PopVec(); TestGL(vm); DispatchCompute(groups); }); nfr("gl_dump_shader", "filename,stripnonascii", "SB", "B", "Dumps the compiled (binary) version of the current shader to a file. Contents are driver" " dependent. On Nvidia hardware it contains the assembly version of the shader as text," " pass true for stripnonascii if you're only interested in that part.", [](VM &vm, Value &filename, Value &stripnonascii) { TestGL(vm); currentshader->Activate(); auto ok = currentshader->Dump(filename.sval()->strv(), stripnonascii.True()); return Value(ok); }); nfr("gl_blend", "on,body", "IL?", "", "changes the blending mode (use blending constants from color.lobster). when a body is" " given, restores the previous mode afterwards", [](VM &vm, Value &mode, Value &body) { TestGL(vm); int old = SetBlendMode((BlendMode)mode.ival()); if (body.True()) vm.Push(Value(old)); return body; }, [](VM &vm) { auto m = vm.Pop(); assert(m.type == V_INT); SetBlendMode((BlendMode)m.ival()); }); nfr("gl_load_texture", "name,textureformat", "SI?", "R?", "returns texture if succesfully loaded from file name, otherwise nil." " see color.lobster for texture format. Uses stb_image internally" " (see http://nothings.org/), loads JPEG Baseline, subsets of PNG, TGA, BMP, PSD, GIF, HDR," " PIC.", [](VM &vm, Value &name, Value &tf) { TestGL(vm); auto tex = CreateTextureFromFile(name.sval()->strv(), tf.intval()); return tex.id ? vm.NewResource(new Texture(tex), &texture_type) : Value(); }); nfr("gl_set_primitive_texture", "i,tex,textureformat", "IRI?", "", "sets texture unit i to texture (for use with rect/circle/polygon/line)", [](VM &vm, Value &i, Value &id, Value &tf) { TestGL(vm); SetTexture(GetSampler(vm, i), GetTexture(vm, id), tf.intval()); return Value(); }); nfr("gl_set_mesh_texture", "mesh,part,i,texture", "RIIR", "", "sets texture unit i to texture for a mesh and part (0 if not a multi-part mesh)", [](VM &vm, Value &mid, Value &part, Value &i, Value &id) { auto &m = GetMesh(vm, mid); if (part.ival() < 0 || part.ival() >= (int)m.surfs.size()) vm.BuiltinError("setmeshtexture: illegal part index"); m.surfs[part.ival()]->Get(GetSampler(vm, i)) = GetTexture(vm, id); return Value(); }); nfr("gl_set_image_texture", "i,tex,textureformat", "IRI", "", "sets image unit i to texture (for use with compute). texture format must be the same" " as what you specified in gl_load_texture / gl_create_texture," " with optionally writeonly/readwrite flags.", [](VM &vm, Value &i, Value &id, Value &tf) { TestGL(vm); SetImageTexture(GetSampler(vm, i), GetTexture(vm, id), tf.intval()); return Value(); }); nfr("gl_create_texture", "matrix,textureformat", "F}:4]]I?", "R", "creates a texture from a 2d array of color vectors." " see texture.lobster for texture format", [](VM &vm, Value &matv, Value &tf) { TestGL(vm); auto mat = matv.vval(); auto ys = mat->len; auto xs = mat->At(0).vval()->len; auto sz = tf.ival() & TF_FLOAT ? sizeof(float4) : sizeof(byte4); auto buf = new uchar[xs * ys * sz]; memset(buf, 0, xs * ys * sz); for (int i = 0; i < ys; i++) { auto row = mat->At(i).vval(); for (int j = 0; j < min(xs, row->len); j++) { float4 col = ValueToFLT<4>(row->AtSt(j), row->width); auto idx = i * xs + j; if (tf.ival() & TF_FLOAT) ((float4 *)buf)[idx] = col; else ((byte4 *)buf)[idx] = quantizec(col); } } auto tex = CreateTexture(buf, int2(intp2(xs, ys)).data(), tf.intval()); delete[] buf; return Value(vm.NewResource(new Texture(tex), &texture_type)); }); nfr("gl_create_blank_texture", "size,color,textureformat", "I}:2F}:4I?", "R", "creates a blank texture (for use as frame buffer or with compute shaders)." " see texture.lobster for texture format", [](VM &vm) { TestGL(vm); auto tf = vm.Pop().intval(); auto col = vm.PopVec(); auto size = vm.PopVec(); auto tex = CreateBlankTexture(size, col, tf); vm.Push(vm.NewResource(new Texture(tex), &texture_type)); }); nfr("gl_texture_size", "tex", "R", "I}:2", "returns the size of a texture", [](VM &vm) { TestGL(vm); vm.PushVec(GetTexture(vm, vm.Pop()).size.xy()); }); nfr("gl_read_texture", "tex", "R", "S?", "read back RGBA texture data into a string or nil on failure", [](VM &vm, Value &t) { TestGL(vm); auto tex = GetTexture(vm, t); auto numpixels = tex.size.x * tex.size.y; if (!numpixels) return Value(); auto buf = ReadTexture(tex); if (!buf) return Value(); auto s = vm.NewString(string_view((char *)buf, numpixels * 4)); delete[] buf; return Value(s); }); nfr("gl_switch_to_framebuffer", "tex,hasdepth,textureformat,resolvetex,depthtex", "R?I?I?R?R?", "B", "switches to a new framebuffer, that renders into the given texture." " also allocates a depth buffer for it if depth is true." " pass the textureformat that was used for this texture." " pass a resolve texture if the base texture is multisample." " pass your own depth texture if desired." " pass a nil texture to switch back to the original framebuffer." " performance note: do not recreate texture passed in unless necessary.", [](VM &vm, Value &t, Value &depth, Value &tf, Value &retex, Value &depthtex) { TestGL(vm); auto tex = GetTexture(vm, t); return Value(SwitchToFrameBuffer(tex.id ? tex : Texture(0, GetScreenSize()), depth.True(), tf.intval(), GetTexture(vm, retex), GetTexture(vm, depthtex))); }); nfr("gl_light", "pos,params", "F}:3F}:2", "", "sets up a light at the given position for this frame. make sure to call this after your" " camera transforms but before any object transforms (i.e. defined in \"worldspace\")." " params contains specular exponent in x (try 32/64/128 for different material looks) and" " the specular scale in y (try 1 for full intensity)", [](VM &vm) { Light l; l.params = vm.PopVec(); l.pos = otransforms.object2view * float4(vm.PopVec(), 1); lights.push_back(l); }); nfr("gl_render_tiles", "positions,tilecoords,mapsize", "F}:2]I}:2]I}:2", "", "Renders a list of tiles from a tilemap. Each tile rendered is 1x1 in size." " Positions may be anywhere. Tile coordinates are inside the texture map, map size is" " the amount of tiles in the texture. Tiles may overlap, they are drawn in order." " Before calling this, make sure to have the texture set and a textured shader", [](VM &vm) { TestGL(vm); auto msize = float2(vm.PopVec()); auto tile = vm.Pop().vval(); auto pos = vm.Pop().vval(); auto len = pos->len; if (len != tile->len) vm.BuiltinError("rendertiles: vectors of different size"); vector vbuf(len * 6); for (intp i = 0; i < len; i++) { auto p = ValueToFLT<2>(pos->AtSt(i), pos->width); auto t = float2(ValueToI<2>(tile->AtSt(i), tile->width)) / msize; vbuf[i * 6 + 0].pos = p; vbuf[i * 6 + 1].pos = p + float2_y; vbuf[i * 6 + 2].pos = p + float2_1; vbuf[i * 6 + 3].pos = p; vbuf[i * 6 + 4].pos = p + float2_1; vbuf[i * 6 + 5].pos = p + float2_x; vbuf[i * 6 + 0].tc = t; vbuf[i * 6 + 1].tc = t + float2_y / msize; vbuf[i * 6 + 2].tc = t + float2_1 / msize; vbuf[i * 6 + 3].tc = t; vbuf[i * 6 + 4].tc = t + float2_1 / msize; vbuf[i * 6 + 5].tc = t + float2_x / msize; } currentshader->Set(); RenderArraySlow(PRIM_TRIS, make_span(vbuf), "pT"); }); nfr("gl_debug_grid", "num,dist,thickness", "I}:3F}:3F", "", "renders a grid in space for debugging purposes. num is the number of lines in all 3" " directions, and dist their spacing. thickness of the lines in the same units", [](VM &vm) { TestGL(vm); auto thickness = vm.Pop().fltval(); auto dist = vm.PopVec(); auto num = vm.PopVec(); float3 cp = otransforms.view2object[3].xyz(); auto m = float3(num); auto step = dist; auto oldcolor = curcolor; curcolor = float4(0, 1, 0, 1); for (float z = 0; z <= m.z; z += step.x) { for (float x = 0; x <= m.x; x += step.x) { geomcache->RenderLine3D(currentshader, float3(x, 0, z), float3(x, m.y, z), cp, thickness); } } curcolor = float4(1, 0, 0, 1); for (float z = 0; z <= m.z; z += step.y) { for (float y = 0; y <= m.y; y += step.y) { geomcache->RenderLine3D(currentshader, float3(0, y, z), float3(m.x, y, z), cp, thickness); } } curcolor = float4(0, 0, 1, 1); for (float y = 0; y <= m.y; y += step.z) { for (float x = 0; x <= m.x; x += step.z) { geomcache->RenderLine3D(currentshader, float3(x, y, 0), float3(x, y, m.z), cp, thickness); } } curcolor = oldcolor; }); nfr("gl_screenshot", "filename", "S", "B", "saves a screenshot in .png format, returns true if succesful", [](VM &, Value &fn) { bool ok = ScreenShot(fn.sval()->strv()); return Value(ok); }); } // AddFont treesheets-1.0.2/lobster/src/imbind.cpp000066400000000000000000000370521352107072600201060ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "imgui.h" #include "imgui_impl_sdl.h" #include "imgui_impl_opengl3.h" #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/vmdata.h" #define FLATBUFFERS_DEBUG_VERIFICATION_FAILURE #include "lobster/bytecode_generated.h" #include "lobster/sdlincludes.h" #include "lobster/sdlinterface.h" using namespace lobster; extern SDL_Window *_sdl_window; extern SDL_GLContext _sdl_context; bool imgui_init = false; int imgui_windows = 0; void IMGUICleanup() { if (!imgui_init) return; ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); imgui_init = false; imgui_windows = 0; } void IsInit(VM &vm, bool require_window = true) { if (!imgui_init) vm.BuiltinError("IMGUI not running: call im_init first"); if (!imgui_windows && require_window) vm.BuiltinError("no window: call im_window first"); } pair IMGUIEvent(SDL_Event *event) { if (!imgui_init) return { false, false }; ImGui_ImplSDL2_ProcessEvent(event); return { ImGui::GetIO().WantCaptureMouse, ImGui::GetIO().WantCaptureKeyboard }; } LString *LStringInputText(VM &vm, const char *label, LString *str, ImGuiInputTextFlags flags = 0) { struct InputTextCallbackData { LString *str; VM &vm; static int InputTextCallback(ImGuiInputTextCallbackData *data) { if (data->EventFlag != ImGuiInputTextFlags_CallbackResize) return 0; auto cbd = (InputTextCallbackData *)data->UserData; IM_ASSERT(data->Buf == cbd->str->data()); auto str = cbd->vm.NewString(string_view { data->Buf, (size_t)data->BufTextLen }); cbd->str->Dec(cbd->vm); cbd->str = str; data->Buf = (char *)str->data(); return 0; } }; flags |= ImGuiInputTextFlags_CallbackResize; InputTextCallbackData cbd { str, vm }; ImGui::InputText(label, (char *)str->data(), str->len + 1, flags, InputTextCallbackData::InputTextCallback, &cbd); return cbd.str; } void ValToGUI(VM &vm, Value *v, const TypeInfo &ti, string_view label, bool expanded) { auto l = null_terminated(label); auto flags = expanded ? ImGuiTreeNodeFlags_DefaultOpen : 0; switch (ti.t) { case V_INT: { if (ti.enumidx == 0) { assert(vm.EnumName(ti.enumidx) == "bool"); bool b = v->True(); if (ImGui::Checkbox(l, &b)) *v = b; } else if (ti.enumidx >= 0) { int val = v->intval(); int sel = 0; auto &vals = *vm.bcf->enums()->Get(ti.enumidx)->vals(); vector items(vals.size()); int i = 0; for (auto vi : vals) { items[i] = vi->name()->c_str(); if (val == vi->val()) sel = i; i++; } ImGui::Combo(l, &sel, items.data(), (int)items.size()); *v = vals[sel]->val(); } else { int i = v->intval(); // FIXME: what if int64_t? if (ImGui::InputInt(l, &i)) *v = i; } return; } case V_FLOAT: { float f = v->fltval(); // FIXME: what if double? if (ImGui::InputFloat(l, &f)) *v = f; return; } case V_VECTOR: if (!v->True()) break; if (ImGui::TreeNodeEx(*l ? l : "[]", flags)) { auto &sti = vm.GetTypeInfo(ti.subt); auto vec = v->vval(); for (intp i = 0; i < vec->len; i++) { ValToGUI(vm, vec->AtSt(i), sti, to_string(i), false); } ImGui::TreePop(); } return; case V_CLASS: if (!v->True()) break; v = v->oval()->Elems(); // To iterate it like a struct. case V_STRUCT_R: case V_STRUCT_S: { auto st = vm.bcf->udts()->Get(ti.structidx); // Special case for numeric structs & colors. if (ti.len >= 2 && ti.len <= 4) { for (int i = 1; i < ti.len; i++) if (ti.elemtypes[i] != ti.elemtypes[0]) goto generic; if (ti.elemtypes[0] == TYPE_ELEM_INT) { auto nums = ValueToI<4>(v, ti.len); if (ImGui::InputScalarN( l, sizeof(intp) == sizeof(int) ? ImGuiDataType_S32 : ImGuiDataType_S64, (void *)nums.data(), ti.len, NULL, NULL, "%d", flags)) { ToValue(v, ti.len, nums); } return; } else if (ti.elemtypes[0] == TYPE_ELEM_FLOAT) { if (strcmp(st->name()->c_str(), "color") == 0) { auto c = ValueToFLT<4>(v, ti.len); if (ImGui::ColorEdit4(l, (float *)c.data())) { ToValue(v, ti.len, c); } } else { auto nums = ValueToF<4>(v, ti.len); // FIXME: format configurable. if (ImGui::InputScalarN( l, sizeof(floatp) == sizeof(float) ? ImGuiDataType_Float : ImGuiDataType_Double, (void *)nums.data(), ti.len, NULL, NULL, "%.3f", flags)) { ToValue(v, ti.len, nums); } } return; } } generic: if (ImGui::TreeNodeEx(*l ? l : st->name()->c_str(), flags)) { auto fields = st->fields(); int fi = 0; for (int i = 0; i < ti.len; i++) { auto &sti = vm.GetTypeInfo(ti.GetElemOrParent(i)); ValToGUI(vm, v + i, sti, fields->Get(fi++)->name()->string_view(), false); if (IsStruct(sti.t)) i += sti.len - 1; } ImGui::TreePop(); } return; } case V_STRING: { if (!v->True()) break; *v = LStringInputText(vm, l, v->sval()); return; } case V_NIL: ValToGUI(vm, v, vm.GetTypeInfo(ti.subt), label, expanded); return; } ostringstream ss; v->ToString(vm, ss, ti, vm.debugpp); ImGui::LabelText(l, "%s", ss.str().c_str()); // FIXME: no formatting? } void VarsToGUI(VM &vm) { auto DumpVars = [&](bool constants) { for (uint i = 0; i < vm.bcf->specidents()->size(); i++) { auto &val = vm.vars[i]; auto sid = vm.bcf->specidents()->Get(i); auto id = vm.bcf->idents()->Get(sid->ididx()); if (!id->global() || id->readonly() != constants) continue; auto name = id->name()->string_view(); auto &ti = vm.GetVarTypeInfo(i); #if RTT_ENABLED if (ti.t != val.type) continue; // Likely uninitialized. #endif ValToGUI(vm, &val, ti, name, false); if (IsStruct(ti.t)) i += ti.len - 1; } }; DumpVars(false); if (ImGui::TreeNodeEx("Constants", 0)) { DumpVars(true); ImGui::TreePop(); } } void EngineStatsGUI() { auto &ft = SDLGetFrameTimeLog(); ImGui::PlotLines("gl_deltatime", ft.data(), (int)ft.size()); } void AddIMGUI(NativeRegistry &nfr) { nfr("im_init", "dark_style", "B?", "", "", [](VM &, Value &darkstyle) { if (imgui_init) return Value(); IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; if (darkstyle.True()) ImGui::StyleColorsDark(); else ImGui::StyleColorsClassic(); ImGui_ImplSDL2_InitForOpenGL(_sdl_window, _sdl_context); ImGui_ImplOpenGL3_Init("#version 150"); imgui_init = true; return Value(); }); nfr("im_add_font", "font_path,size", "SF", "B", "", [](VM &vm, Value &fontname, Value &size) { IsInit(vm, false); string buf; auto l = LoadFile(fontname.sval()->strv(), &buf); if (l < 0) return Value(); auto mb = malloc(buf.size()); // FIXME. memcpy(mb, buf.data(), buf.size()); ImFontConfig imfc; imfc.FontDataOwnedByAtlas = true; auto font = ImGui::GetIO().Fonts->AddFontFromMemoryTTF(mb, (int)buf.size(), size.fltval(), &imfc); return Value(font != nullptr); }); nfr("im_frame", "body", "L", "", "", [](VM &vm, Value &body) { IsInit(vm, false); ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(_sdl_window); ImGui::NewFrame(); return body; }, [](VM &) { ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); }); nfr("im_window_demo", "", "", "B", "", [](VM &vm) { IsInit(vm, false); bool show = true; ImGui::ShowDemoWindow(&show); return Value(show); }); nfr("im_window", "title,flags,body", "SIL", "", "", [](VM &vm, Value &title, Value &flags, Value &body) { IsInit(vm, false); ImGui::Begin(title.sval()->data(), nullptr, (ImGuiWindowFlags)flags.ival()); imgui_windows++; return body; }, [](VM &) { ImGui::End(); imgui_windows--; }); nfr("im_button", "label,body", "SL", "", "", [](VM &vm, Value &title, Value &body) { IsInit(vm); auto press = ImGui::Button(title.sval()->data()); return press ? body : Value(); }, [](VM &) { }); nfr("im_same_line", "", "", "", "", [](VM &vm) { IsInit(vm); ImGui::SameLine(); return Value(); }); nfr("im_separator", "", "", "", "", [](VM &vm) { IsInit(vm); ImGui::Separator(); return Value(); }); nfr("im_text", "label", "S", "", "", [](VM &vm, Value &text) { IsInit(vm); ImGui::Text("%s", text.sval()->data()); return Value(); }); nfr("im_tooltip", "label", "S", "", "", [](VM &vm, Value &text) { IsInit(vm); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", text.sval()->data()); return Value(); }); nfr("im_checkbox", "label,bool", "SI", "I2", "", [](VM &vm, Value &text, Value &boolean) { IsInit(vm); bool b = boolean.True(); ImGui::Checkbox(text.sval()->data(), &b); return Value(b); }); nfr("im_input_text", "label,str", "SSk", "S", "", [](VM &vm, Value &text, Value &str) { IsInit(vm); return Value(LStringInputText(vm, text.sval()->data(), str.sval())); }); nfr("im_radio", "labels,active,horiz", "S]II", "I", "", [](VM &vm, Value &strs, Value &active, Value &horiz) { IsInit(vm); int sel = active.intval(); for (intp i = 0; i < strs.vval()->len; i++) { if (i && horiz.True()) ImGui::SameLine(); ImGui::RadioButton(strs.vval()->At(i).sval()->data(), &sel, (int)i); } return Value(sel); }); nfr("im_combo", "label,labels,active", "SS]I", "I", "", [](VM &vm, Value &text, Value &strs, Value &active) { IsInit(vm); int sel = active.intval(); vector items(strs.vval()->len); for (intp i = 0; i < strs.vval()->len; i++) { items[i] = strs.vval()->At(i).sval()->data(); } ImGui::Combo(text.sval()->data(), &sel, items.data(), (int)items.size()); return Value(sel); }); nfr("im_listbox", "label,labels,active,height", "SS]II", "I", "", [](VM &vm, Value &text, Value &strs, Value &active, Value &height) { IsInit(vm); int sel = active.intval(); vector items(strs.vval()->len); for (intp i = 0; i < strs.vval()->len; i++) { items[i] = strs.vval()->At(i).sval()->data(); } ImGui::ListBox(text.sval()->data(), &sel, items.data(), (int)items.size(), height.intval()); return Value(sel); }); nfr("im_sliderint", "label,i,min,max", "SIII", "I", "", [](VM &vm, Value &text, Value &integer, Value &min, Value &max) { IsInit(vm); int i = integer.intval(); ImGui::SliderInt(text.sval()->data(), &i, min.intval(), max.intval()); return Value(i); }); nfr("im_sliderfloat", "label,f,min,max", "SFFF", "F", "", [](VM &vm, Value &text, Value &flt, Value &min, Value &max) { IsInit(vm); float f = flt.fltval(); ImGui::SliderFloat(text.sval()->data(), &f, min.fltval(), max.fltval()); return Value(f); }); nfr("im_coloredit", "label,color", "SF}", "A2", "", [](VM &vm) { IsInit(vm); auto c = vm.PopVec(); ImGui::ColorEdit4(vm.Pop().sval()->data(), (float *)c.data()); vm.PushVec(c); }); nfr("im_treenode", "label,body", "SL", "", "", [](VM &vm, Value &title, Value &body) { IsInit(vm); auto open = ImGui::TreeNode(title.sval()->data()); vm.Push(open); return open ? body : Value(); }, [](VM &vm) { if (vm.Pop().True()) ImGui::TreePop(); }); nfr("im_group", "label,body", "SsL", "", "an invisble group around some widgets, useful to ensure these widgets are unique" " (if they have the same label as widgets in another group that has a different group" " label)", [](VM &vm, Value &title, Value &body) { IsInit(vm); ImGui::PushID(title.sval()->data()); return body; }, [](VM &) { ImGui::PopID(); }); nfr("im_edit_anything", "value,label", "AkS?", "A1", "creates a UI for any lobster reference value, and returns the edited version", [](VM &vm, Value &v, Value &label) { IsInit(vm); // FIXME: would be good to support structs, but that requires typeinfo, not just len. auto &ti = vm.GetTypeInfo(v.True() ? v.ref()->tti : TYPE_ELEM_ANY); ValToGUI(vm, &v, ti, label.True() ? label.sval()->strv() : "", true); return v; }); nfr("im_graph", "label,values,ishistogram", "SF]I", "", "", [](VM &vm, Value &label, Value &vals, Value &histogram) { IsInit(vm); auto getter = [](void *data, int i) -> float { return ((Value *)data)[i].fltval(); }; if (histogram.True()) { ImGui::PlotHistogram(label.sval()->data(), getter, vals.vval()->Elems(), (int)vals.vval()->len); } else { ImGui::PlotLines(label.sval()->data(), getter, vals.vval()->Elems(), (int)vals.vval()->len); } return Value(); }); nfr("im_show_vars", "", "", "", "shows an automatic editing UI for each global variable in your program", [](VM &vm) { IsInit(vm); VarsToGUI(vm); return Value(); }); nfr("im_show_engine_stats", "", "", "", "", [](VM &vm) { IsInit(vm); EngineStatsGUI(); return Value(); }); } // AddIMGUI treesheets-1.0.2/lobster/src/lobster/000077500000000000000000000000001352107072600176035ustar00rootroot00000000000000treesheets-1.0.2/lobster/src/lobster/3dgrid.h000066400000000000000000000157001352107072600211330ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_3DGRID_H #define LOBSTER_3DGRID_H // A basic 3D grid, with individually allocated YZ arrays. // Make Z your inner loop when accessing this. template class Chunk3DGrid : NonCopyable { public: int3 dim; private: vector grid; T &Access(const int3 &pos) const { return grid[pos.x][pos.y * dim.z + pos.z]; } public: Chunk3DGrid(const int3 &_dim, T default_val) : dim(_dim) { grid.resize(dim.x, nullptr); for (int i = 0; i < dim.x; i++) { auto len = dim.y * dim.z; grid[i] = new T[len]; std::fill_n(grid[i], len, default_val); } } ~Chunk3DGrid() { for (auto p : grid) delete[] p; } T &Get(const int3 &pos) const { return Access(pos); } // This creates a Z-major continuous buffer, whereas the original data is X-major. void ToContinousGrid(T *buf) { for (int z = 0; z < dim.z; z++) { for (int y = 0; y < dim.y; y++) { for (int x = 0; x < dim.x; x++) { buf[z * dim.x * dim.y + y * dim.x + x] = Get(int3(x, y, z)); } } } } void Shrink(const int3 &ndim) { assert(ndim <= dim); for (auto [i, p] : enumerate(grid)) if ((int)i >= ndim.x) delete[] p; grid.resize(ndim.x); for (auto &p : grid) { auto n = new T[ndim.x * ndim.y]; for (int y = 0; y < ndim.y; y++) { for (int z = 0; z < ndim.z; z++) { n[y * ndim.z + z] = p[y * dim.z + z]; } } delete p; p = n; } dim = ndim; } }; // Stores an XY grid of RLE Z lists, based on the value of T. // Caches the current location in the list, so as long as you iterate through this with +Z as your // inner loop, will be close to the efficiency of accessing a 3D grid, while using less memory and // smaller blocks of it. Individual lists are reallocated as splitting of ranges makes this // necessary. template class RLE3DGrid : NonCopyable { public: int3 dim; private: union RLEItem { T val; int count; }; // FIXME: bad if not the same size. vector grid; SlabAlloc alloc; T default_val; int2 cur_pos; RLEItem *cur, *it; int it_z; int CurSize() { return cur[0].count; } RLEItem *&GridLoc(const int2 &p) { return grid[p.x + p.y * dim.x]; } void Iterate(const int3 &pos) { auto p = pos.xy(); if (p != cur_pos) { // We're switching to a new xy. Look up a new RLE list, and cache it. cur_pos = p; auto &rle = GridLoc(p); if (!rle) { // Lazyly allocate lists. // A RLE list is a count of RLE pairs, first of which is a count, second the value. rle = alloc.alloc_array(3); rle[0].count = 3; rle[1].count = dim.z; rle[2].val = default_val; } cur = rle; it = cur + 1; it_z = 0; } else if (pos.z < it_z) { // Iterating to an earlier part of the list (very uncommon). it_z = 0; it = cur + 1; } // Iterate towards correct part of the list (common case, usually skips while-body). while (pos.z >= it_z + it[0].count) { it_z += it[0].count; it += 2; assert(it < cur + CurSize()); // Should not run off end of list. } // Here, it[1] now has the correct value for "pos". } template void CopyCurrent(int change, const int3 &pos, F f) { auto rle = alloc.alloc_array(CurSize() + change); t_memcpy(rle, cur, it - cur); rle[0].count += change; auto nit = rle + (it - cur); f(nit); t_memcpy(nit + 2 + change, it + 2, CurSize() - (it - cur + 2)); alloc.dealloc_array(cur, CurSize()); GridLoc(pos.xy()) = rle; cur = rle; it = nit; } public: RLE3DGrid(const int3 &_dim, T _default_val) : dim(_dim), default_val(_default_val), cur_pos(-1, -1), cur(nullptr), it(nullptr), it_z(0) { grid.resize(dim.x * dim.y, nullptr); } ~RLE3DGrid() { } T Get(const int3 &pos) { Iterate(pos); return it[1].val; } void Set(const int3 &pos, T newval) { Iterate(pos); // No change, we're done, yay! if (it[1].val == newval) return; if (it[0].count == 1) { // We can just overwrite. it[1].val = newval; return; } // Part of a larger range. if (it_z == pos.z) { // If this was the first value of a range.. if (it > cur + 1 && it[-1].val == newval) { // ..and the preceding range has that value, we can simply shift the two ranges. it[-2].count++; it[0].count--; it_z++; } else { // Otherwise split in two. CopyCurrent(2, pos, [&](RLEItem *nit) { nit[0].count = 1; nit[1].val = newval; nit[2].count = it[0].count - 1; nit[3].val = it[1].val; }); } } else if (it_z + it[0].count - 1 == pos.z) { // Similarly, if this is the last value of the range.. if (dim.z > it_z + it[0].count && it[3].val == newval) { // ..and the next range has the new value, we can also shift these ranges. it[2].count++; it[0].count--; } else { // Otherwise split in two. CopyCurrent(2, pos, [&](RLEItem *nit) { nit[0].count = it[0].count - 1; nit[1].val = it[1].val; nit[2].count = 1; nit[3].val = newval; }); } } else { // Split in 3. CopyCurrent(4, pos, [&](RLEItem *nit) { nit[0].count = pos.z - it_z; nit[1].val = it[1].val; nit[2].count = 1; nit[3].val = newval; nit[4].count = it[0].count - nit[0].count - 1; nit[5].val = it[1].val; }); } } }; #endif // LOBSTER_3DGRID_H treesheets-1.0.2/lobster/src/lobster/bytecode_generated.h000066400000000000000000000675221352107072600236040ustar00rootroot00000000000000// automatically generated by the FlatBuffers compiler, do not modify #ifndef FLATBUFFERS_GENERATED_BYTECODE_BYTECODE_H_ #define FLATBUFFERS_GENERATED_BYTECODE_BYTECODE_H_ #include "flatbuffers/flatbuffers.h" namespace bytecode { struct LineInfo; struct Function; struct Field; struct UDT; struct EnumVal; struct Enum; struct Ident; struct SpecIdent; struct BytecodeFile; enum Attr { Attr_SPLIT = 1, Attr_NONE = 0, Attr_ANY = 1 }; inline const Attr (&EnumValuesAttr())[1] { static const Attr values[] = { Attr_SPLIT }; return values; } inline const char * const *EnumNamesAttr() { static const char * const names[] = { "SPLIT", nullptr }; return names; } inline const char *EnumNameAttr(Attr e) { const size_t index = static_cast(e) - static_cast(Attr_SPLIT); return EnumNamesAttr()[index]; } FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) LineInfo FLATBUFFERS_FINAL_CLASS { private: int32_t line_; int32_t fileidx_; int32_t bytecodestart_; public: LineInfo() { memset(this, 0, sizeof(LineInfo)); } LineInfo(int32_t _line, int32_t _fileidx, int32_t _bytecodestart) : line_(flatbuffers::EndianScalar(_line)), fileidx_(flatbuffers::EndianScalar(_fileidx)), bytecodestart_(flatbuffers::EndianScalar(_bytecodestart)) { } int32_t line() const { return flatbuffers::EndianScalar(line_); } int32_t fileidx() const { return flatbuffers::EndianScalar(fileidx_); } int32_t bytecodestart() const { return flatbuffers::EndianScalar(bytecodestart_); } }; FLATBUFFERS_STRUCT_END(LineInfo, 12); FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) SpecIdent FLATBUFFERS_FINAL_CLASS { private: int32_t ididx_; int32_t typeidx_; public: SpecIdent() { memset(this, 0, sizeof(SpecIdent)); } SpecIdent(int32_t _ididx, int32_t _typeidx) : ididx_(flatbuffers::EndianScalar(_ididx)), typeidx_(flatbuffers::EndianScalar(_typeidx)) { } int32_t ididx() const { return flatbuffers::EndianScalar(ididx_); } int32_t typeidx() const { return flatbuffers::EndianScalar(typeidx_); } }; FLATBUFFERS_STRUCT_END(SpecIdent, 8); struct Function FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_BYTECODESTART = 6 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } int32_t bytecodestart() const { return GetField(VT_BYTECODESTART, 0); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyField(verifier, VT_BYTECODESTART) && verifier.EndTable(); } }; struct FunctionBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(Function::VT_NAME, name); } void add_bytecodestart(int32_t bytecodestart) { fbb_.AddElement(Function::VT_BYTECODESTART, bytecodestart, 0); } explicit FunctionBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } FunctionBuilder &operator=(const FunctionBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); return o; } }; inline flatbuffers::Offset CreateFunction( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, int32_t bytecodestart = 0) { FunctionBuilder builder_(_fbb); builder_.add_bytecodestart(bytecodestart); builder_.add_name(name); return builder_.Finish(); } inline flatbuffers::Offset CreateFunctionDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, int32_t bytecodestart = 0) { return bytecode::CreateFunction( _fbb, name ? _fbb.CreateString(name) : 0, bytecodestart); } struct Field FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_OFFSET = 6 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } int32_t offset() const { return GetField(VT_OFFSET, 0); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyField(verifier, VT_OFFSET) && verifier.EndTable(); } }; struct FieldBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(Field::VT_NAME, name); } void add_offset(int32_t offset) { fbb_.AddElement(Field::VT_OFFSET, offset, 0); } explicit FieldBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } FieldBuilder &operator=(const FieldBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); return o; } }; inline flatbuffers::Offset CreateField( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, int32_t offset = 0) { FieldBuilder builder_(_fbb); builder_.add_offset(offset); builder_.add_name(name); return builder_.Finish(); } inline flatbuffers::Offset CreateFieldDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, int32_t offset = 0) { return bytecode::CreateField( _fbb, name ? _fbb.CreateString(name) : 0, offset); } struct UDT FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_IDX = 6, VT_FIELDS = 8, VT_SIZE = 10 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } int32_t idx() const { return GetField(VT_IDX, 0); } const flatbuffers::Vector> *fields() const { return GetPointer> *>(VT_FIELDS); } int32_t size() const { return GetField(VT_SIZE, 0); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyField(verifier, VT_IDX) && VerifyOffset(verifier, VT_FIELDS) && verifier.VerifyVector(fields()) && verifier.VerifyVectorOfTables(fields()) && VerifyField(verifier, VT_SIZE) && verifier.EndTable(); } }; struct UDTBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(UDT::VT_NAME, name); } void add_idx(int32_t idx) { fbb_.AddElement(UDT::VT_IDX, idx, 0); } void add_fields(flatbuffers::Offset>> fields) { fbb_.AddOffset(UDT::VT_FIELDS, fields); } void add_size(int32_t size) { fbb_.AddElement(UDT::VT_SIZE, size, 0); } explicit UDTBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } UDTBuilder &operator=(const UDTBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); return o; } }; inline flatbuffers::Offset CreateUDT( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, int32_t idx = 0, flatbuffers::Offset>> fields = 0, int32_t size = 0) { UDTBuilder builder_(_fbb); builder_.add_size(size); builder_.add_fields(fields); builder_.add_idx(idx); builder_.add_name(name); return builder_.Finish(); } inline flatbuffers::Offset CreateUDTDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, int32_t idx = 0, const std::vector> *fields = nullptr, int32_t size = 0) { return bytecode::CreateUDT( _fbb, name ? _fbb.CreateString(name) : 0, idx, fields ? _fbb.CreateVector>(*fields) : 0, size); } struct EnumVal FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_VAL = 6 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } int64_t val() const { return GetField(VT_VAL, 0); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyField(verifier, VT_VAL) && verifier.EndTable(); } }; struct EnumValBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(EnumVal::VT_NAME, name); } void add_val(int64_t val) { fbb_.AddElement(EnumVal::VT_VAL, val, 0); } explicit EnumValBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } EnumValBuilder &operator=(const EnumValBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); return o; } }; inline flatbuffers::Offset CreateEnumVal( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, int64_t val = 0) { EnumValBuilder builder_(_fbb); builder_.add_val(val); builder_.add_name(name); return builder_.Finish(); } inline flatbuffers::Offset CreateEnumValDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, int64_t val = 0) { return bytecode::CreateEnumVal( _fbb, name ? _fbb.CreateString(name) : 0, val); } struct Enum FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_VALS = 6 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } const flatbuffers::Vector> *vals() const { return GetPointer> *>(VT_VALS); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyOffset(verifier, VT_VALS) && verifier.VerifyVector(vals()) && verifier.VerifyVectorOfTables(vals()) && verifier.EndTable(); } }; struct EnumBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(Enum::VT_NAME, name); } void add_vals(flatbuffers::Offset>> vals) { fbb_.AddOffset(Enum::VT_VALS, vals); } explicit EnumBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } EnumBuilder &operator=(const EnumBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); return o; } }; inline flatbuffers::Offset CreateEnum( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, flatbuffers::Offset>> vals = 0) { EnumBuilder builder_(_fbb); builder_.add_vals(vals); builder_.add_name(name); return builder_.Finish(); } inline flatbuffers::Offset CreateEnumDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, const std::vector> *vals = nullptr) { return bytecode::CreateEnum( _fbb, name ? _fbb.CreateString(name) : 0, vals ? _fbb.CreateVector>(*vals) : 0); } struct Ident FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_NAME = 4, VT_READONLY = 6, VT_GLOBAL = 8 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); } bool readonly() const { return GetField(VT_READONLY, 0) != 0; } bool global() const { return GetField(VT_GLOBAL, 0) != 0; } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_NAME) && verifier.VerifyString(name()) && VerifyField(verifier, VT_READONLY) && VerifyField(verifier, VT_GLOBAL) && verifier.EndTable(); } }; struct IdentBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_name(flatbuffers::Offset name) { fbb_.AddOffset(Ident::VT_NAME, name); } void add_readonly(bool readonly) { fbb_.AddElement(Ident::VT_READONLY, static_cast(readonly), 0); } void add_global(bool global) { fbb_.AddElement(Ident::VT_GLOBAL, static_cast(global), 0); } explicit IdentBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } IdentBuilder &operator=(const IdentBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); return o; } }; inline flatbuffers::Offset CreateIdent( flatbuffers::FlatBufferBuilder &_fbb, flatbuffers::Offset name = 0, bool readonly = false, bool global = false) { IdentBuilder builder_(_fbb); builder_.add_name(name); builder_.add_global(global); builder_.add_readonly(readonly); return builder_.Finish(); } inline flatbuffers::Offset CreateIdentDirect( flatbuffers::FlatBufferBuilder &_fbb, const char *name = nullptr, bool readonly = false, bool global = false) { return bytecode::CreateIdent( _fbb, name ? _fbb.CreateString(name) : 0, readonly, global); } struct BytecodeFile FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { enum { VT_BYTECODE_VERSION = 4, VT_BYTECODE = 6, VT_BYTECODE_ATTR = 8, VT_TYPETABLE = 10, VT_STRINGTABLE = 12, VT_LINEINFO = 14, VT_FILENAMES = 16, VT_FUNCTIONS = 18, VT_UDTS = 20, VT_IDENTS = 22, VT_SPECIDENTS = 24, VT_DEFAULT_INT_VECTOR_TYPES = 26, VT_DEFAULT_FLOAT_VECTOR_TYPES = 28, VT_LOGVARS = 30, VT_ENUMS = 32, VT_VTABLES = 34 }; int32_t bytecode_version() const { return GetField(VT_BYTECODE_VERSION, 0); } const flatbuffers::Vector *bytecode() const { return GetPointer *>(VT_BYTECODE); } const flatbuffers::Vector *bytecode_attr() const { return GetPointer *>(VT_BYTECODE_ATTR); } const flatbuffers::Vector *typetable() const { return GetPointer *>(VT_TYPETABLE); } const flatbuffers::Vector> *stringtable() const { return GetPointer> *>(VT_STRINGTABLE); } const flatbuffers::Vector *lineinfo() const { return GetPointer *>(VT_LINEINFO); } const flatbuffers::Vector> *filenames() const { return GetPointer> *>(VT_FILENAMES); } const flatbuffers::Vector> *functions() const { return GetPointer> *>(VT_FUNCTIONS); } const flatbuffers::Vector> *udts() const { return GetPointer> *>(VT_UDTS); } const flatbuffers::Vector> *idents() const { return GetPointer> *>(VT_IDENTS); } const flatbuffers::Vector *specidents() const { return GetPointer *>(VT_SPECIDENTS); } const flatbuffers::Vector *default_int_vector_types() const { return GetPointer *>(VT_DEFAULT_INT_VECTOR_TYPES); } const flatbuffers::Vector *default_float_vector_types() const { return GetPointer *>(VT_DEFAULT_FLOAT_VECTOR_TYPES); } const flatbuffers::Vector *logvars() const { return GetPointer *>(VT_LOGVARS); } const flatbuffers::Vector> *enums() const { return GetPointer> *>(VT_ENUMS); } const flatbuffers::Vector *vtables() const { return GetPointer *>(VT_VTABLES); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_BYTECODE_VERSION) && VerifyOffset(verifier, VT_BYTECODE) && verifier.VerifyVector(bytecode()) && VerifyOffset(verifier, VT_BYTECODE_ATTR) && verifier.VerifyVector(bytecode_attr()) && VerifyOffset(verifier, VT_TYPETABLE) && verifier.VerifyVector(typetable()) && VerifyOffset(verifier, VT_STRINGTABLE) && verifier.VerifyVector(stringtable()) && verifier.VerifyVectorOfStrings(stringtable()) && VerifyOffset(verifier, VT_LINEINFO) && verifier.VerifyVector(lineinfo()) && VerifyOffset(verifier, VT_FILENAMES) && verifier.VerifyVector(filenames()) && verifier.VerifyVectorOfStrings(filenames()) && VerifyOffset(verifier, VT_FUNCTIONS) && verifier.VerifyVector(functions()) && verifier.VerifyVectorOfTables(functions()) && VerifyOffset(verifier, VT_UDTS) && verifier.VerifyVector(udts()) && verifier.VerifyVectorOfTables(udts()) && VerifyOffset(verifier, VT_IDENTS) && verifier.VerifyVector(idents()) && verifier.VerifyVectorOfTables(idents()) && VerifyOffset(verifier, VT_SPECIDENTS) && verifier.VerifyVector(specidents()) && VerifyOffset(verifier, VT_DEFAULT_INT_VECTOR_TYPES) && verifier.VerifyVector(default_int_vector_types()) && VerifyOffset(verifier, VT_DEFAULT_FLOAT_VECTOR_TYPES) && verifier.VerifyVector(default_float_vector_types()) && VerifyOffset(verifier, VT_LOGVARS) && verifier.VerifyVector(logvars()) && VerifyOffset(verifier, VT_ENUMS) && verifier.VerifyVector(enums()) && verifier.VerifyVectorOfTables(enums()) && VerifyOffset(verifier, VT_VTABLES) && verifier.VerifyVector(vtables()) && verifier.EndTable(); } }; struct BytecodeFileBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; void add_bytecode_version(int32_t bytecode_version) { fbb_.AddElement(BytecodeFile::VT_BYTECODE_VERSION, bytecode_version, 0); } void add_bytecode(flatbuffers::Offset> bytecode) { fbb_.AddOffset(BytecodeFile::VT_BYTECODE, bytecode); } void add_bytecode_attr(flatbuffers::Offset> bytecode_attr) { fbb_.AddOffset(BytecodeFile::VT_BYTECODE_ATTR, bytecode_attr); } void add_typetable(flatbuffers::Offset> typetable) { fbb_.AddOffset(BytecodeFile::VT_TYPETABLE, typetable); } void add_stringtable(flatbuffers::Offset>> stringtable) { fbb_.AddOffset(BytecodeFile::VT_STRINGTABLE, stringtable); } void add_lineinfo(flatbuffers::Offset> lineinfo) { fbb_.AddOffset(BytecodeFile::VT_LINEINFO, lineinfo); } void add_filenames(flatbuffers::Offset>> filenames) { fbb_.AddOffset(BytecodeFile::VT_FILENAMES, filenames); } void add_functions(flatbuffers::Offset>> functions) { fbb_.AddOffset(BytecodeFile::VT_FUNCTIONS, functions); } void add_udts(flatbuffers::Offset>> udts) { fbb_.AddOffset(BytecodeFile::VT_UDTS, udts); } void add_idents(flatbuffers::Offset>> idents) { fbb_.AddOffset(BytecodeFile::VT_IDENTS, idents); } void add_specidents(flatbuffers::Offset> specidents) { fbb_.AddOffset(BytecodeFile::VT_SPECIDENTS, specidents); } void add_default_int_vector_types(flatbuffers::Offset> default_int_vector_types) { fbb_.AddOffset(BytecodeFile::VT_DEFAULT_INT_VECTOR_TYPES, default_int_vector_types); } void add_default_float_vector_types(flatbuffers::Offset> default_float_vector_types) { fbb_.AddOffset(BytecodeFile::VT_DEFAULT_FLOAT_VECTOR_TYPES, default_float_vector_types); } void add_logvars(flatbuffers::Offset> logvars) { fbb_.AddOffset(BytecodeFile::VT_LOGVARS, logvars); } void add_enums(flatbuffers::Offset>> enums) { fbb_.AddOffset(BytecodeFile::VT_ENUMS, enums); } void add_vtables(flatbuffers::Offset> vtables) { fbb_.AddOffset(BytecodeFile::VT_VTABLES, vtables); } explicit BytecodeFileBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } BytecodeFileBuilder &operator=(const BytecodeFileBuilder &); flatbuffers::Offset Finish() { const auto end = fbb_.EndTable(start_); auto o = flatbuffers::Offset(end); return o; } }; inline flatbuffers::Offset CreateBytecodeFile( flatbuffers::FlatBufferBuilder &_fbb, int32_t bytecode_version = 0, flatbuffers::Offset> bytecode = 0, flatbuffers::Offset> bytecode_attr = 0, flatbuffers::Offset> typetable = 0, flatbuffers::Offset>> stringtable = 0, flatbuffers::Offset> lineinfo = 0, flatbuffers::Offset>> filenames = 0, flatbuffers::Offset>> functions = 0, flatbuffers::Offset>> udts = 0, flatbuffers::Offset>> idents = 0, flatbuffers::Offset> specidents = 0, flatbuffers::Offset> default_int_vector_types = 0, flatbuffers::Offset> default_float_vector_types = 0, flatbuffers::Offset> logvars = 0, flatbuffers::Offset>> enums = 0, flatbuffers::Offset> vtables = 0) { BytecodeFileBuilder builder_(_fbb); builder_.add_vtables(vtables); builder_.add_enums(enums); builder_.add_logvars(logvars); builder_.add_default_float_vector_types(default_float_vector_types); builder_.add_default_int_vector_types(default_int_vector_types); builder_.add_specidents(specidents); builder_.add_idents(idents); builder_.add_udts(udts); builder_.add_functions(functions); builder_.add_filenames(filenames); builder_.add_lineinfo(lineinfo); builder_.add_stringtable(stringtable); builder_.add_typetable(typetable); builder_.add_bytecode_attr(bytecode_attr); builder_.add_bytecode(bytecode); builder_.add_bytecode_version(bytecode_version); return builder_.Finish(); } inline flatbuffers::Offset CreateBytecodeFileDirect( flatbuffers::FlatBufferBuilder &_fbb, int32_t bytecode_version = 0, const std::vector *bytecode = nullptr, const std::vector *bytecode_attr = nullptr, const std::vector *typetable = nullptr, const std::vector> *stringtable = nullptr, const std::vector *lineinfo = nullptr, const std::vector> *filenames = nullptr, const std::vector> *functions = nullptr, const std::vector> *udts = nullptr, const std::vector> *idents = nullptr, const std::vector *specidents = nullptr, const std::vector *default_int_vector_types = nullptr, const std::vector *default_float_vector_types = nullptr, const std::vector *logvars = nullptr, const std::vector> *enums = nullptr, const std::vector *vtables = nullptr) { return bytecode::CreateBytecodeFile( _fbb, bytecode_version, bytecode ? _fbb.CreateVector(*bytecode) : 0, bytecode_attr ? _fbb.CreateVector(*bytecode_attr) : 0, typetable ? _fbb.CreateVector(*typetable) : 0, stringtable ? _fbb.CreateVector>(*stringtable) : 0, lineinfo ? _fbb.CreateVectorOfStructs(*lineinfo) : 0, filenames ? _fbb.CreateVector>(*filenames) : 0, functions ? _fbb.CreateVector>(*functions) : 0, udts ? _fbb.CreateVector>(*udts) : 0, idents ? _fbb.CreateVector>(*idents) : 0, specidents ? _fbb.CreateVectorOfStructs(*specidents) : 0, default_int_vector_types ? _fbb.CreateVector(*default_int_vector_types) : 0, default_float_vector_types ? _fbb.CreateVector(*default_float_vector_types) : 0, logvars ? _fbb.CreateVector(*logvars) : 0, enums ? _fbb.CreateVector>(*enums) : 0, vtables ? _fbb.CreateVector(*vtables) : 0); } inline const bytecode::BytecodeFile *GetBytecodeFile(const void *buf) { return flatbuffers::GetRoot(buf); } inline const bytecode::BytecodeFile *GetSizePrefixedBytecodeFile(const void *buf) { return flatbuffers::GetSizePrefixedRoot(buf); } inline const char *BytecodeFileIdentifier() { return "LBCF"; } inline bool BytecodeFileBufferHasIdentifier(const void *buf) { return flatbuffers::BufferHasIdentifier( buf, BytecodeFileIdentifier()); } inline bool VerifyBytecodeFileBuffer( flatbuffers::Verifier &verifier) { return verifier.VerifyBuffer(BytecodeFileIdentifier()); } inline bool VerifySizePrefixedBytecodeFileBuffer( flatbuffers::Verifier &verifier) { return verifier.VerifySizePrefixedBuffer(BytecodeFileIdentifier()); } inline const char *BytecodeFileExtension() { return "lbc"; } inline void FinishBytecodeFileBuffer( flatbuffers::FlatBufferBuilder &fbb, flatbuffers::Offset root) { fbb.Finish(root, BytecodeFileIdentifier()); } inline void FinishSizePrefixedBytecodeFileBuffer( flatbuffers::FlatBufferBuilder &fbb, flatbuffers::Offset root) { fbb.FinishSizePrefixed(root, BytecodeFileIdentifier()); } } // namespace bytecode #endif // FLATBUFFERS_GENERATED_BYTECODE_BYTECODE_H_ treesheets-1.0.2/lobster/src/lobster/codegen.h000066400000000000000000001504401352107072600213640ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. namespace lobster { #define FLATBUFFERS_DEBUG_VERIFICATION_FAILURE #include "lobster/bytecode_generated.h" struct CodeGen { vector code; vector code_attr; vector lineinfo; vector sids; Parser &parser; vector linenumbernodes; vector> call_fixups; SymbolTable &st; vector type_table, vint_typeoffsets, vfloat_typeoffsets; map, type_elem_t> type_lookup; // Wasteful, but simple. vector rettypes, temptypestack; size_t nested_fors = 0; vector stringtable; // sized strings. vector node_context; vector speclogvars; // Index into specidents. int keepvars = 0; int runtime_checks; vector vtables; int Pos() { return (int)code.size(); } void GrowCodeAttr(size_t mins) { while (mins > code_attr.size()) code_attr.push_back(bytecode::Attr_NONE); } void Emit(int i) { auto &ln = linenumbernodes.back()->line; if (lineinfo.empty() || ln.line != lineinfo.back().line() || ln.fileidx != lineinfo.back().fileidx()) lineinfo.push_back(bytecode::LineInfo(ln.line, ln.fileidx, Pos())); code.push_back(i); GrowCodeAttr(code.size()); } void SplitAttr(int at) { GrowCodeAttr(at + 1); code_attr[at] |= bytecode::Attr_SPLIT; } void Emit(int i, int j) { Emit(i); Emit(j); } void Emit(int i, int j, int k) { Emit(i); Emit(j); Emit(k); } void Emit(int i, int j, int k, int l) { Emit(i); Emit(j); Emit(k); Emit(l); } void SetLabel(int jumploc) { code[jumploc - 1] = Pos(); SplitAttr(Pos()); } const size_t ti_num_udt_fields = 4; const size_t ti_num_udt_per_field = 2; void PushFields(UDT *udt, vector &tt, type_elem_t parent = (type_elem_t)-1) { for (auto &field : udt->fields.v) { auto ti = GetTypeTableOffset(field.type); if (IsStruct(field.type->t)) { PushFields(field.type->udt, tt, ti); } else { tt.insert(tt.begin() + (tt.size() - ti_num_udt_fields) / ti_num_udt_per_field + ti_num_udt_fields, ti); tt.push_back(parent); } } } // Make a table for use as VM runtime type. type_elem_t GetTypeTableOffset(TypeRef type) { vector tt; tt.push_back((type_elem_t)type->t); switch (type->t) { case V_INT: tt.push_back((type_elem_t)(type->e ? type->e->idx : -1)); break; case V_NIL: case V_VECTOR: tt.push_back(GetTypeTableOffset(type->sub)); break; case V_FUNCTION: tt.push_back((type_elem_t)type->sf->idx); break; case V_COROUTINE: if (type->sf) { if (type->sf->cotypeinfo >= 0) return type->sf->cotypeinfo; type->sf->cotypeinfo = (type_elem_t)type_table.size(); // Reserve space, so other types can be added afterwards safely. type_table.insert(type_table.end(), 3, (type_elem_t)0); tt.push_back((type_elem_t)type->sf->idx); tt.push_back(GetTypeTableOffset(type->sf->returntype)); std::copy(tt.begin(), tt.end(), type_table.begin() + type->sf->cotypeinfo); return type->sf->cotypeinfo; } else { tt.push_back((type_elem_t)-1); tt.push_back(TYPE_ELEM_ANY); } break; case V_CLASS: case V_STRUCT_R: case V_STRUCT_S: { if (type->udt->typeinfo >= 0) return type->udt->typeinfo; type->udt->typeinfo = (type_elem_t)type_table.size(); // Reserve space, so other types can be added afterwards safely. assert(type->udt->numslots >= 0); auto ttsize = (size_t)(type->udt->numslots * ti_num_udt_per_field) + ti_num_udt_fields; type_table.insert(type_table.end(), ttsize, (type_elem_t)0); tt.push_back((type_elem_t)type->udt->idx); tt.push_back((type_elem_t)type->udt->numslots); tt.push_back((type_elem_t)type->udt->vtable_start); PushFields(type->udt, tt); assert(tt.size() == ttsize); std::copy(tt.begin(), tt.end(), type_table.begin() + type->udt->typeinfo); return type->udt->typeinfo; } case V_VAR: // This can happen with an empty [] vector that was never bound to anything. // Should be benign to use any, since it is never accessed anywhere. // FIXME: would be even better to check this case before codegen, since this may // mask bugs. return GetTypeTableOffset(type_any); default: assert(IsRuntime(type->t)); break; } // For everything that's not a struct / known coroutine: auto it = type_lookup.find(tt); if (it != type_lookup.end()) return it->second; auto offset = (type_elem_t)type_table.size(); type_lookup[tt] = offset; type_table.insert(type_table.end(), tt.begin(), tt.end()); return offset; } CodeGen(Parser &_p, SymbolTable &_st, bool return_value, int runtime_checks) : parser(_p), st(_st), runtime_checks(runtime_checks) { // Reserve space and index for all vtables. for (auto udt : st.udttable) { udt->vtable_start = (int)vtables.size(); vtables.insert(vtables.end(), udt->dispatch.size(), -1); } // Pre-load some types into the table, must correspond to order of type_elem_t enums. GetTypeTableOffset(type_int); GetTypeTableOffset(type_float); GetTypeTableOffset(type_string); GetTypeTableOffset(type_resource); GetTypeTableOffset(type_any); Type type_valuebuf(V_VALUEBUF); GetTypeTableOffset(&type_valuebuf); Type type_stackframebuf(V_STACKFRAMEBUF); GetTypeTableOffset(&type_stackframebuf); GetTypeTableOffset(type_vector_int); GetTypeTableOffset(type_vector_float); Type type_vec_str(V_VECTOR, &*type_string); GetTypeTableOffset(&type_vec_str); Type type_v_v_int(V_VECTOR, &*type_vector_int); GetTypeTableOffset(&type_v_v_int); Type type_v_v_float(V_VECTOR, &*type_vector_float); GetTypeTableOffset(&type_v_v_float); assert(type_table.size() == TYPE_ELEM_FIXED_OFFSET_END); for (auto type : st.default_int_vector_types[0]) vint_typeoffsets.push_back(!type.Null() ? GetTypeTableOffset(type) : (type_elem_t)-1); for (auto type : st.default_float_vector_types[0]) vfloat_typeoffsets.push_back(!type.Null() ? GetTypeTableOffset(type) : (type_elem_t)-1); int sidx = 0; for (auto sid : st.specidents) { if (!sid->type.Null()) { // Null ones are in unused functions. auto tti = GetTypeTableOffset(sid->type); assert(!IsStruct(sid->type->t) || sid->type->udt->numslots >= 0); sid->sidx = sidx; auto ns = ValWidth(sid->type); sidx += ns; if (sid->id->logvar) { sid->logvaridx = (int)speclogvars.size(); speclogvars.push_back(sid->Idx()); } for (int i = 0; i < ns; i++) sids.push_back(bytecode::SpecIdent(sid->id->idx, tti)); } } linenumbernodes.push_back(parser.root); SplitAttr(0); Emit(IL_JUMP, 0); auto fundefjump = Pos(); SplitAttr(Pos()); for (auto f : parser.st.functiontable) { if (f->bytecodestart <= 0 && !f->istype) { f->bytecodestart = Pos(); for (auto sf : f->overloads) for (; sf; sf = sf->next) { if (sf && sf->typechecked) GenScope(*sf); } } } // Generate a dummmy function for function values that are never called. // Would be good if the optimizer guarantees these don't exist, but for now this is // more debuggable if it does happen to get called. auto dummyfun = Pos(); SplitAttr(dummyfun); Emit(IL_FUNSTART, -1, 0, 0); Emit(0, 0); // keepvars, ownedvars Emit(IL_ABORT); // Emit the root function. SetLabel(fundefjump); SplitAttr(Pos()); Gen(parser.root, return_value); auto type = parser.root->exptype; assert(type->NumValues() == (size_t)return_value); Emit(IL_EXIT, return_value ? GetTypeTableOffset(type): -1); SplitAttr(Pos()); // Allow off by one indexing. linenumbernodes.pop_back(); for (auto &[loc, sf] : call_fixups) { auto bytecodestart = sf->subbytecodestart; if (!bytecodestart) bytecodestart = dummyfun; assert(!code[loc]); code[loc] = bytecodestart; } // Now fill in the vtables. for (auto udt : st.udttable) { for (auto [i, de] : enumerate(udt->dispatch)) { if (de.sf) { vtables[udt->vtable_start + i] = de.sf->subbytecodestart; } } } } ~CodeGen() { } // FIXME: remove. void Dummy(size_t retval) { assert(!retval); while (retval--) Emit(IL_PUSHNIL); } void GenScope(SubFunction &sf) { if (sf.subbytecodestart > 0) return; keepvars = 0; sf.subbytecodestart = Pos(); if (!sf.typechecked) { auto s = DumpNode(*sf.body, 0, false); LOG_DEBUG("untypechecked: ", sf.parent->name, " : ", s); assert(0); } vector ownedvars; linenumbernodes.push_back(sf.body); SplitAttr(Pos()); Emit(IL_FUNSTART); Emit(sf.parent->idx); auto emitvars = [&](const vector &v) { auto nvarspos = Pos(); Emit(0); auto nvars = 0; for (auto &arg : v) { auto n = ValWidth(arg.sid->type); for (int i = 0; i < n; i++) { Emit(arg.sid->Idx() + i); nvars++; if (ShouldDec(IsStruct(arg.sid->type->t) ? TypeLT { FindSlot(*arg.sid->type->udt, i)->type, arg.sid->lt } : TypeLT { *arg.sid }) && !arg.sid->consume_on_last_use) { ownedvars.push_back(arg.sid->Idx() + i); } } } code[nvarspos] = nvars; }; emitvars(sf.args.v); emitvars(sf.locals.v); auto keepvarspos = Pos(); Emit(0); // FIXME: don't really want to emit these.. instead should ensure someone takes // ownership of them. Emit((int)ownedvars.size()); for (auto si : ownedvars) Emit(si); if (sf.body) for (auto c : sf.body->children) { Gen(c, 0); if (runtime_checks >= RUNTIME_ASSERT_PLUS) Emit(IL_ENDSTATEMENT, c->line.line, c->line.fileidx); } else Dummy(sf.reqret); assert(temptypestack.empty()); code[keepvarspos] = keepvars; linenumbernodes.pop_back(); } // This must be called explicitly when any values are consumed. void TakeTemp(size_t n, bool can_handle_structs) { assert(node_context.size()); for (; n; n--) { auto tlt = temptypestack.back(); temptypestack.pop_back(); assert(can_handle_structs || ValWidth(tlt.type) == 1); (void)tlt; (void)can_handle_structs; } } void GenFixup(const SubFunction *sf) { assert(sf->body); auto pos = Pos() - 1; if (!code[pos]) call_fixups.push_back({ pos, sf }); } void GenCall(const SubFunction &sf, int vtable_idx, const List *args, size_t retval) { auto &f = *sf.parent; for (auto c : args->children) { Gen(c, 1); } size_t nargs = args->children.size(); if (f.nargs() != nargs) parser.Error(cat("call to function ", f.name, " needs ", f.nargs(), " arguments, ", nargs, " given"), node_context.back()); TakeTemp(nargs, true); if (vtable_idx < 0) { Emit(IL_CALL, sf.subbytecodestart); GenFixup(&sf); } else { int stack_depth = -1; for (auto c : args->children) stack_depth += ValWidth(c->exptype); Emit(IL_DDCALL, vtable_idx, stack_depth); } SplitAttr(Pos()); auto nretvals = sf.returntype->NumValues(); for (size_t i = 0; i < nretvals; i++) { if (retval) { rettypes.push_back({ sf, i }); } else { // FIXME: better if this is impossible by making sure typechecker makes it !reqret. GenPop({ sf, i }); } } }; void GenFloat(double f) { if ((float)f == f) { Emit(IL_PUSHFLT); int2float i2f; i2f.f = (float)f; Emit(i2f.i); } else { Emit(IL_PUSHFLT64); int2float64 i2f; i2f.f = f; Emit((int)i2f.i); Emit((int)(i2f.i >> 32)); } } bool ShouldDec(TypeLT typelt) { return IsRefNil(typelt.type->t) && typelt.lt == LT_KEEP; } void GenPop(TypeLT typelt) { if (IsStruct(typelt.type->t)) { Emit(typelt.type->t == V_STRUCT_R ? IL_POPVREF : IL_POPV, typelt.type->udt->numslots); } else { Emit(ShouldDec(typelt) ? IL_POPREF : IL_POP); } } void GenDup(TypeLT tlt) { Emit(IL_DUP); temptypestack.push_back(tlt); } void Gen(const Node *n, size_t retval) { // Generate() below generate no retvals if retval==0, otherwise they generate however many // they can irrespective of retval, optionally record that in rettypes for the more complex // cases. Then at the end of this function the two get matched up. auto tempstartsize = temptypestack.size(); linenumbernodes.push_back(n); node_context.push_back(n); n->Generate(*this, retval); node_context.pop_back(); assert(n->exptype->t != V_UNDEFINED); assert(tempstartsize == temptypestack.size()); (void)tempstartsize; // If 0, the above code already made sure to not generate value(s). if (retval) { // default case, no rettypes specified. if (rettypes.empty()) { for (size_t i = 0; i < n->exptype->NumValues(); i++) rettypes.push_back(TypeLT { *n, i }); } // if the caller doesn't want all return values, just pop em if (rettypes.size() > retval) { while (rettypes.size() > retval) { GenPop(rettypes.back()); rettypes.pop_back(); } } assert(rettypes.size() == retval); // Copy return types on temp stack. while (rettypes.size()) { temptypestack.push_back(rettypes.front()); rettypes.erase(rettypes.begin()); } } assert(rettypes.empty()); linenumbernodes.pop_back(); } void VarModified(SpecIdent *sid) { if (sid->id->logvar) Emit(IL_LOGWRITE, sid->Idx(), sid->logvaridx); } void GenStructIns(TypeRef type) { if (IsStruct(type->t)) Emit(ValWidth(type)); } void GenValueSize(TypeRef type) { // FIXME: struct variable size. Emit(IL_PUSHINT, ValWidth(type)); } void GenAssign(const Node *lval, int lvalop, size_t retval, const Node *rhs, int take_temp) { assert(node_context.back()->exptype->NumValues() == retval); auto type = lval->exptype; if (lvalop >= LVO_IADD && lvalop <= LVO_IMOD) { if (type->t == V_INT) { } else if (type->t == V_FLOAT) { assert(lvalop != LVO_IMOD); lvalop += LVO_FADD - LVO_IADD; } else if (type->t == V_STRING) { assert(lvalop == LVO_IADD); lvalop = LVO_SADD; } else if (type->t == V_STRUCT_S) { auto sub = type->udt->sametype; bool withscalar = IsScalar(rhs->exptype->t); if (sub->t == V_INT) { lvalop += (withscalar ? LVO_IVSADD : LVO_IVVADD) - LVO_IADD; } else if (sub->t == V_FLOAT) { assert(lvalop != LVO_IMOD); lvalop += (withscalar ? LVO_FVSADD : LVO_FVVADD) - LVO_IADD; } else assert(false); } else { assert(false); } } else if (lvalop >= LVO_IPP && lvalop <= LVO_IMMP) { if (type->t == V_FLOAT) lvalop += LVO_FPP - LVO_IPP; else assert(type->t == V_INT); } if (retval) lvalop++; if (rhs) Gen(rhs, 1); if (auto idr = Is(lval)) { TakeTemp(take_temp, true); Emit(GENLVALOP(VAR, lvalop), idr->sid->Idx()); GenStructIns(idr->sid->type); VarModified(idr->sid); } else if (auto dot = Is(lval)) { auto stype = dot->children[0]->exptype; assert(IsUDT(stype->t)); // Ensured by typechecker. auto idx = stype->udt->Has(dot->fld); assert(idx >= 0); auto &field = stype->udt->fields.v[idx]; Gen(dot->children[0], 1); TakeTemp(take_temp + 1, true); Emit(GENLVALOP(FLD, lvalop), field.slot); GenStructIns(field.type); } else if (auto cod = Is(lval)) { auto ir = AssertIs(cod->variable); Gen(cod->coroutine, 1); TakeTemp(take_temp + 1, true); Emit(GENLVALOP(LOC, lvalop), ir->sid->Idx()); GenStructIns(ir->sid->type); } else if (auto indexing = Is(lval)) { Gen(indexing->object, 1); Gen(indexing->index, 1); TakeTemp(take_temp + 2, true); switch (indexing->object->exptype->t) { case V_VECTOR: Emit(indexing->index->exptype->t == V_INT ? GENLVALOP(IDXVI, lvalop) : GENLVALOP(IDXVV, lvalop)); GenStructIns(indexing->index->exptype); // When index is struct. GenStructIns(type); // When vector elem is struct. break; case V_CLASS: assert(indexing->index->exptype->t == V_INT && indexing->object->exptype->udt->sametype->Numeric()); Emit(GENLVALOP(IDXNI, lvalop)); break; case V_STRUCT_R: case V_STRUCT_S: case V_STRING: // FIXME: Would be better to catch this in typechecking, but typechecker does // not currently distinquish lvalues. parser.Error("cannot use this type as lvalue", lval); default: assert(false); } } else { parser.Error("lvalue required", lval); } } void GenMathOp(const BinOp *n, size_t retval, int opc) { Gen(n->left, retval); Gen(n->right, retval); if (retval) GenMathOp(n->left->exptype, n->right->exptype, n->exptype, opc); } void GenMathOp(TypeRef ltype, TypeRef rtype, TypeRef ptype, int opc) { TakeTemp(2, true); // Have to check right and left because comparison ops generate ints for node // overall. if (rtype->t == V_INT && ltype->t == V_INT) { Emit(IL_IADD + opc); } else if (rtype->t == V_FLOAT && ltype->t == V_FLOAT) { Emit(IL_FADD + opc); } else if (rtype->t == V_STRING && ltype->t == V_STRING) { Emit(IL_SADD + opc); } else if (rtype->t == V_FUNCTION && ltype->t == V_FUNCTION) { assert(opc == MOP_EQ || opc == MOP_NE); Emit(IL_LEQ + (opc - MOP_EQ)); } else { if (opc >= MOP_EQ) { // EQ/NEQ if (IsStruct(ltype->t)) { Emit(IL_STEQ + opc - MOP_EQ); GenStructIns(ltype); } else { assert(IsRefNil(ltype->t) && IsRefNil(rtype->t)); Emit(IL_AEQ + opc - MOP_EQ); } } else { // If this is a comparison op, be sure to use the child type. TypeRef vectype = opc >= MOP_LT ? ltype : ptype; assert(vectype->t == V_STRUCT_S); auto sub = vectype->udt->sametype; bool withscalar = IsScalar(rtype->t); if (sub->t == V_INT) Emit((withscalar ? IL_IVSADD : IL_IVVADD) + opc); else if (sub->t == V_FLOAT) Emit((withscalar ? IL_FVSADD : IL_FVVADD) + opc); else assert(false); GenStructIns(vectype); } } } void GenBitOp(const BinOp *n, size_t retval, ILOP opc) { Gen(n->left, retval); Gen(n->right, retval); if (retval) { TakeTemp(2, false); Emit(opc); } } int AssignBaseOp(TypeLT typelt) { return IsStruct(typelt.type->t) ? (typelt.type->t == V_STRUCT_R ? LVO_WRITEREFV : LVO_WRITEV) : (ShouldDec(typelt) ? LVO_WRITEREF : LVO_WRITE); } void GenPushVar(size_t retval, TypeRef type, int offset) { if (!retval) return; if (IsStruct(type->t)) { Emit(IL_PUSHVARV, offset); GenStructIns(type); } else { Emit(IL_PUSHVAR, offset); } } void GenPushField(size_t retval, Node *object, TypeRef stype, TypeRef ftype, int offset) { if (IsStruct(stype->t)) { // Attempt to not generate object at all, by reading the field inline. if (auto idr = Is(object)) { GenPushVar(retval, ftype, idr->sid->Idx() + offset); return; } else if (auto dot = Is(object)) { auto sstype = dot->children[0]->exptype; assert(IsUDT(sstype->t)); auto idx = sstype->udt->Has(dot->fld); assert(idx >= 0); auto &field = sstype->udt->fields.v[idx]; assert(field.slot >= 0); GenPushField(retval, dot->children[0], sstype, ftype, field.slot + offset); return; } else if (auto cod = Is(object)) { // This is already a very slow op, so not worth further optimizing for now. (void)cod; } else if (auto indexing = Is(object)) { // For now only do this for vectors. if (indexing->object->exptype->t == V_VECTOR) { GenPushIndex(retval, indexing->object, indexing->index, ValWidth(ftype), offset); return; } } } Gen(object, retval); if (!retval) return; TakeTemp(1, true); if (IsStruct(stype->t)) { if (IsStruct(ftype->t)) { Emit(IL_PUSHFLDV2V, offset); GenStructIns(ftype); } else { Emit(IL_PUSHFLDV, offset); } GenStructIns(stype); } else { if (IsStruct(ftype->t)) { Emit(IL_PUSHFLD2V, offset); GenStructIns(ftype); } else { Emit(IL_PUSHFLD, offset); } } } void GenPushIndex(size_t retval, Node *object, Node *index, int width = -1, int offset = -1) { Gen(object, retval); Gen(index, retval); if (!retval) return; TakeTemp(2, true); switch (object->exptype->t) { case V_VECTOR: { auto etype = object->exptype; if (index->exptype->t == V_INT) { etype = etype->Element(); } else { auto &udt = *index->exptype->udt; for (auto &field : udt.fields.v) { (void)field; etype = etype->Element(); } } if (width < 0) { Emit(index->exptype->t == V_INT ? IL_VPUSHIDXI : IL_VPUSHIDXV); GenStructIns(index->exptype); } else { // We're indexing a sub-part of the element. Emit(index->exptype->t == V_INT ? IL_VPUSHIDXIS : IL_VPUSHIDXVS); GenStructIns(index->exptype); Emit(width, offset); } break; } case V_STRUCT_S: assert(index->exptype->t == V_INT && object->exptype->udt->sametype->Numeric()); Emit(IL_NPUSHIDXI); GenStructIns(object->exptype); break; case V_STRING: assert(index->exptype->t == V_INT); Emit(IL_SPUSHIDXI); break; default: assert(false); } } }; void Nil::Generate(CodeGen &cg, size_t retval) const { if (retval) { cg.Emit(IL_PUSHNIL); } } void IntConstant::Generate(CodeGen &cg, size_t retval) const { if (!retval) return; if (integer == (int)integer) cg.Emit(IL_PUSHINT, (int)integer); else cg.Emit(IL_PUSHINT64, (int)integer, (int)(integer >> 32)); } void FloatConstant::Generate(CodeGen &cg, size_t retval) const { if (retval) { cg.GenFloat(flt); }; } void StringConstant::Generate(CodeGen &cg, size_t retval) const { if (!retval) return; cg.Emit(IL_PUSHSTR, (int)cg.stringtable.size()); cg.stringtable.push_back(str); } void DefaultVal::Generate(CodeGen &cg, size_t retval) const { if (!retval) return; // Optional args are indicated by being nillable, but for structs passed to builtins the type // has already been made non-nil. switch (exptype->ElementIfNil()->t) { case V_INT: cg.Emit(IL_PUSHINT, 0); break; case V_FLOAT: cg.GenFloat(0); break; default: cg.Emit(IL_PUSHNIL); break; } } void IdentRef::Generate(CodeGen &cg, size_t retval) const { cg.GenPushVar(retval, sid->type, sid->Idx()); } void Dot::Generate(CodeGen &cg, size_t retval) const { auto stype = children[0]->exptype; assert(IsUDT(stype->t)); auto idx = stype->udt->Has(fld); assert(idx >= 0); auto &field = stype->udt->fields.v[idx]; assert(field.slot >= 0); cg.GenPushField(retval, children[0], stype, field.type, field.slot); } void Indexing::Generate(CodeGen &cg, size_t retval) const { cg.GenPushIndex(retval, object, index); } void GenericCall::Generate(CodeGen &, size_t /*retval*/) const { assert(false); } void CoDot::Generate(CodeGen &cg, size_t retval) const { cg.Gen(coroutine, retval); if (retval) { cg.TakeTemp(1, false); auto sid = AssertIs(variable)->sid; if (IsStruct(sid->type->t)) { cg.Emit(IL_PUSHLOCV, sid->Idx()); cg.GenStructIns(sid->type); } else { cg.Emit(IL_PUSHLOC, sid->Idx()); } } } void AssignList::Generate(CodeGen &cg, size_t retval) const { cg.Gen(children.back(), children.size() - 1); for (size_t i = children.size() - 1; i-- > 0; ) { auto left = children[i]; auto id = Is(left); auto llt = id ? id->sid->lt : LT_KEEP /* Dot */; cg.GenAssign(left, cg.AssignBaseOp({ left->exptype, llt }), 0, nullptr, 1); } assert(!retval); // Type checker guarantees this. (void)retval; } void Define::Generate(CodeGen &cg, size_t retval) const { cg.Gen(child, sids.size()); for (size_t i = sids.size(); i-- > 0; ) { auto sid = sids[i].first; if (sid->id->logvar) cg.Emit(IL_LOGREAD, sid->logvaridx); cg.TakeTemp(1, true); // FIXME: Sadly, even though FunIntro now guarantees that variables start as V_NIL, // we still can't replace this with a WRITEDEF that doesn't have to decrement, since // loops with inlined bodies cause this def to be execute multiple times. // (also: multiple copies of the same inlined function in one parent). // We should emit a specialized opcode for these cases only. cg.Emit(GENLVALOP(VAR, cg.AssignBaseOp({ *sid })), sid->Idx()); cg.GenStructIns(sid->type); cg.VarModified(sid); } assert(!retval); // Parser guarantees this. (void)retval; } void Assign::Generate(CodeGen &cg, size_t retval) const { cg.GenAssign(left, cg.AssignBaseOp({ *right, 0 }), retval, right, 1); } void PlusEq::Generate(CodeGen &cg, size_t retval) const { cg.GenAssign(left, LVO_IADD, retval, right, 1); } void MinusEq::Generate(CodeGen &cg, size_t retval) const { cg.GenAssign(left, LVO_ISUB, retval, right, 1); } void MultiplyEq::Generate(CodeGen &cg, size_t retval) const { cg.GenAssign(left, LVO_IMUL, retval, right, 1); } void DivideEq::Generate(CodeGen &cg, size_t retval) const { cg.GenAssign(left, LVO_IDIV, retval, right, 1); } void ModEq::Generate(CodeGen &cg, size_t retval) const { cg.GenAssign(left, LVO_IMOD, retval, right, 1); } void PostDecr::Generate(CodeGen &cg, size_t retval) const { cg.GenAssign(child, LVO_IMMP, retval, nullptr, 0); } void PostIncr::Generate(CodeGen &cg, size_t retval) const { cg.GenAssign(child, LVO_IPPP, retval, nullptr, 0); } void PreDecr ::Generate(CodeGen &cg, size_t retval) const { cg.GenAssign(child, LVO_IMM, retval, nullptr, 0); } void PreIncr ::Generate(CodeGen &cg, size_t retval) const { cg.GenAssign(child, LVO_IPP, retval, nullptr, 0); } void NotEqual ::Generate(CodeGen &cg, size_t retval) const { cg.GenMathOp(this, retval, MOP_NE); } void Equal ::Generate(CodeGen &cg, size_t retval) const { cg.GenMathOp(this, retval, MOP_EQ); } void GreaterThanEq::Generate(CodeGen &cg, size_t retval) const { cg.GenMathOp(this, retval, MOP_GE); } void LessThanEq ::Generate(CodeGen &cg, size_t retval) const { cg.GenMathOp(this, retval, MOP_LE); } void GreaterThan ::Generate(CodeGen &cg, size_t retval) const { cg.GenMathOp(this, retval, MOP_GT); } void LessThan ::Generate(CodeGen &cg, size_t retval) const { cg.GenMathOp(this, retval, MOP_LT); } void Mod ::Generate(CodeGen &cg, size_t retval) const { cg.GenMathOp(this, retval, MOP_MOD); } void Divide ::Generate(CodeGen &cg, size_t retval) const { cg.GenMathOp(this, retval, MOP_DIV); } void Multiply ::Generate(CodeGen &cg, size_t retval) const { cg.GenMathOp(this, retval, MOP_MUL); } void Minus ::Generate(CodeGen &cg, size_t retval) const { cg.GenMathOp(this, retval, MOP_SUB); } void Plus ::Generate(CodeGen &cg, size_t retval) const { cg.GenMathOp(this, retval, MOP_ADD); } void UnaryMinus::Generate(CodeGen &cg, size_t retval) const { cg.Gen(child, retval); if (!retval) return; cg.TakeTemp(1, true); auto ctype = child->exptype; switch (ctype->t) { case V_INT: cg.Emit(IL_IUMINUS); break; case V_FLOAT: cg.Emit(IL_FUMINUS); break; case V_STRUCT_S: { auto elem = ctype->udt->sametype->t; cg.Emit(elem == V_INT ? IL_IVUMINUS : IL_FVUMINUS); cg.GenStructIns(ctype); break; } default: assert(false); } } void BitAnd ::Generate(CodeGen &cg, size_t retval) const { cg.GenBitOp(this, retval, IL_BINAND); } void BitOr ::Generate(CodeGen &cg, size_t retval) const { cg.GenBitOp(this, retval, IL_BINOR); } void Xor ::Generate(CodeGen &cg, size_t retval) const { cg.GenBitOp(this, retval, IL_XOR); } void ShiftLeft ::Generate(CodeGen &cg, size_t retval) const { cg.GenBitOp(this, retval, IL_ASL); } void ShiftRight::Generate(CodeGen &cg, size_t retval) const { cg.GenBitOp(this, retval, IL_ASR); } void Negate::Generate(CodeGen &cg, size_t retval) const { cg.Gen(child, retval); if (!retval) return; cg.TakeTemp(1, false); cg.Emit(IL_NEG); } void ToFloat::Generate(CodeGen &cg, size_t retval) const { cg.Gen(child, retval); if (!retval) return; cg.TakeTemp(1, false); cg.Emit(IL_I2F); } void ToString::Generate(CodeGen &cg, size_t retval) const { cg.Gen(child, retval); if (!retval) return; cg.TakeTemp(1, true); switch (child->exptype->t) { case V_STRUCT_R: case V_STRUCT_S: { // TODO: can also roll these into A2S? cg.Emit(IL_ST2S, cg.GetTypeTableOffset(child->exptype)); break; } default: { cg.Emit(IL_A2S, cg.GetTypeTableOffset(child->exptype->ElementIfNil())); break; } } } void ToBool::Generate(CodeGen &cg, size_t retval) const { cg.Gen(child, retval); if (!retval) return; cg.TakeTemp(1, false); cg.Emit(IsRefNil(child->exptype->t) ? IL_E2BREF : IL_E2B); } void ToInt::Generate(CodeGen &cg, size_t retval) const { cg.Gen(child, retval); // No actual opcode needed, this node is purely to store correct types. if (retval) cg.TakeTemp(1, false); } void ToLifetime::Generate(CodeGen &cg, size_t retval) const { cg.Gen(child, retval); for (size_t i = 0; i < retval; i++) { // We have to check for reftype again, since typechecker allowed V_VAR values that may // have become scalars by now. if (IsRefNil(child->exptype->Get(i)->t)) { assert(i < cg.temptypestack.size()); auto fi = (int)(retval - i - 1); auto type = cg.temptypestack[cg.temptypestack.size() - 1 - fi].type; if (incref & (1LL << i)) { assert(IsRefNil(type->t)); cg.Emit(IL_INCREF, fi); } if (decref & (1LL << i)) { assert(IsRefNil(type->t)); int stack_depth = 0; for (auto &tlt : cg.temptypestack) stack_depth += ValWidth(tlt.type); if (type->t == V_STRUCT_R) { // TODO: alternatively emit a single op with a list or bitmask? for (int j = 0; j < type->udt->numslots; j++) { if (IsRefNil(FindSlot(*type->udt, j)->type->t)) cg.Emit(IL_KEEPREF, fi + j, cg.keepvars++ + stack_depth); } } else { cg.Emit(IL_KEEPREF, fi, cg.keepvars++ + stack_depth); } } } } // We did not consume these, so we have to pass them on. for (size_t i = 0; i < retval; i++) { cg.rettypes.push_back(cg.temptypestack.back()); cg.temptypestack.pop_back(); } } void FunRef::Generate(CodeGen &cg, size_t retval) const { if (!retval) return; // If no body, then the function has been optimized away, meaning this // function value will never be used. // FIXME: instead, ensure such values are removed by the optimizer. if (sf->parent->anonymous && sf->body) { cg.Emit(IL_PUSHFUN, sf->subbytecodestart); cg.GenFixup(sf); } else { cg.Dummy(retval); } } void EnumRef::Generate(CodeGen &cg, size_t retval) const { cg.Dummy(retval); } void UDTRef::Generate(CodeGen &cg, size_t retval) const { cg.Dummy(retval); } void NativeCall::Generate(CodeGen &cg, size_t retval) const { if (nf->IsAssert()) { // FIXME: lift this into a language feature. auto c = children[0]; if (retval || cg.runtime_checks >= RUNTIME_ASSERT) { cg.Gen(c, 1); cg.TakeTemp(1, false); if (cg.runtime_checks >= RUNTIME_ASSERT) { cg.Emit(IL_ASSERT + (!!retval), c->line.line, c->line.fileidx); cg.Emit((int)cg.stringtable.size()); // FIXME: would be better to use the original source code here. cg.stringtable.push_back(cg.st.StoreName(DumpNode(*c, 0, true))); } } else { cg.Gen(c, 0); } return; } // TODO: could pass arg types in here if most exps have types, cheaper than // doing it all in call instruction? size_t numstructs = 0; for (auto [i, c] : enumerate(children)) { cg.Gen(c, 1); if ((IsStruct(c->exptype->t) || nf->args.v[i].flags & NF_PUSHVALUEWIDTH) && !Is(c)) { cg.GenValueSize(c->exptype); cg.temptypestack.push_back({ type_int, LT_ANY }); numstructs++; } } size_t nargs = children.size(); cg.TakeTemp(nargs + numstructs, true); assert(nargs == nf->args.size() && (nf->fun.fnargs < 0 || nargs <= 7)); int vmop = nf->fun.fnargs >= 0 ? IL_BCALLRET0 + (int)nargs * 3 : IL_BCALLRETV; if (nf->cont1) { // graphics.h auto lastarg = children.empty() ? nullptr : children.back(); if (!Is(lastarg)) { cg.Emit(vmop, nf->idx); cg.Emit(IL_CALLVCOND); // FIXME: doesn't need to be COND anymore? cg.SplitAttr(cg.Pos()); assert(lastarg->exptype->t == V_FUNCTION); assert(!lastarg->exptype->sf->reqret); // We never use the retval. cg.Emit(IL_CONT1, nf->idx); // Never returns a value. cg.Dummy(retval); } else { if (!retval) vmop += 2; // These always return nil. cg.Emit(vmop, nf->idx); } } else if (nf->CanChangeControlFlow()) { cg.Emit(vmop, nf->idx); cg.SplitAttr(cg.Pos()); if (!retval) cg.GenPop({ nattype, natlt }); } else { auto last = nattype->NumValues() - 1; auto tlt = TypeLT { nattype->Get(last), nattype->GetLifetime(last, natlt) }; // FIXME: simplify. auto val_width_1 = !IsStruct(tlt.type->t) || tlt.type->udt->numslots == 1; auto var_width_void = nf->fun.fnargs < 0 && nf->retvals.v.empty(); if (!retval && val_width_1 && !var_width_void) { // Generate version that never produces top of stack (but still may have // additional return values) vmop++; if (!cg.ShouldDec(tlt)) vmop++; } cg.Emit(vmop, nf->idx); if (!retval && !val_width_1) { cg.GenPop(tlt); } } if (nf->retvals.v.size() > 1) { assert(nf->retvals.v.size() == nattype->NumValues()); for (size_t i = 0; i < nattype->NumValues(); i++) { cg.rettypes.push_back({ nattype->Get(i), nattype->GetLifetime(i, natlt) }); } } else { assert(nf->retvals.v.size() >= retval); } if (!retval) { // Top of stack has already been removed by op, but still need to pop any // additional values. if (cg.rettypes.size()) cg.rettypes.pop_back(); while (cg.rettypes.size()) { cg.GenPop(cg.rettypes.back()); cg.rettypes.pop_back(); } } } void Call::Generate(CodeGen &cg, size_t retval) const { cg.GenCall(*sf, vtable_idx, this, retval); } void DynCall::Generate(CodeGen &cg, size_t retval) const { if (sid->type->t == V_YIELD) { if (Arity()) { for (auto c : children) { cg.Gen(c, 1); } size_t nargs = children.size(); cg.TakeTemp(nargs, false); assert(nargs == 1); } else { cg.Emit(IL_PUSHNIL); } cg.Emit(IL_YIELD); // We may have temps on the stack from an enclosing for. // Check that these temps are actually from for loops, to not mask bugs. assert(cg.temptypestack.size() == cg.nested_fors * 2); cg.SplitAttr(cg.Pos()); if (!retval) cg.GenPop({ exptype, lt }); } else { assert(sf && sf == sid->type->sf); // FIXME: in the future, we can make a special case for istype calls. if (!sf->parent->istype) { // We statically know which function this is calling. // We can now turn this into a normal call. cg.GenCall(*sf, -1, this, retval); } else { for (auto c : children) { cg.Gen(c, 1); } size_t nargs = children.size(); assert(nargs == sf->args.size()); cg.Emit(IL_PUSHVAR, sid->Idx()); cg.TakeTemp(nargs, true); cg.Emit(IL_CALLV); cg.SplitAttr(cg.Pos()); if (sf->reqret) { if (!retval) cg.GenPop({ exptype, lt }); } else { cg.Dummy(retval); } } } } void List::Generate(CodeGen & /*cg*/, size_t /*retval*/) const { assert(false); // Handled by individual parents. } void TypeAnnotation::Generate(CodeGen & /*cg*/, size_t /*retval*/) const { assert(false); // Handled by individual parents. } void Unary::Generate(CodeGen & /*cg*/, size_t /*retval*/) const { assert(false); // Handled by individual parents. } void Coercion::Generate(CodeGen & /*cg*/, size_t /*retval*/) const { assert(false); // Handled by individual parents. } void BinOp::Generate(CodeGen & /*cg*/, size_t /*retval*/) const { assert(false); // Handled by individual parents. } void Inlined::Generate(CodeGen &cg, size_t retval) const { for (auto c : children) { auto rv = c != children.back() ? 0 : retval; cg.Gen(c, rv); if (rv) cg.TakeTemp(retval != 0, true); } } void Seq::Generate(CodeGen &cg, size_t retval) const { cg.Gen(head, 0); cg.Gen(tail, retval); if (retval) cg.TakeTemp(1, true); } void MultipleReturn::Generate(CodeGen &cg, size_t retval) const { for (auto [i, c] : enumerate(children)) cg.Gen(c, i < retval); cg.TakeTemp(retval, true); for (auto[i, c] : enumerate(children)) if (i < retval) cg.rettypes.push_back({ c->exptype, c->lt }); } void And::Generate(CodeGen &cg, size_t retval) const { cg.Gen(left, 1); cg.TakeTemp(1, false); cg.Emit(retval ? IL_JUMPFAILR : IL_JUMPFAIL, 0); auto loc = cg.Pos(); cg.Gen(right, retval); if (retval) cg.TakeTemp(1, false); cg.SetLabel(loc); } void Or::Generate(CodeGen &cg, size_t retval) const { cg.Gen(left, 1); cg.TakeTemp(1, false); cg.Emit(retval ? IL_JUMPNOFAILR : IL_JUMPNOFAIL, 0); auto loc = cg.Pos(); cg.Gen(right, retval); if (retval) cg.TakeTemp(1, false); cg.SetLabel(loc); } void Not::Generate(CodeGen &cg, size_t retval) const { cg.Gen(child, retval); if (retval) { cg.TakeTemp(1, false); cg.Emit(IsRefNil(child->exptype->t) ? IL_LOGNOTREF : IL_LOGNOT); } } void If::Generate(CodeGen &cg, size_t retval) const { cg.Gen(condition, 1); cg.TakeTemp(1, false); bool has_else = !Is(falsepart); cg.Emit(!has_else && retval ? IL_JUMPFAILN : IL_JUMPFAIL, 0); auto loc = cg.Pos(); if (has_else) { cg.Gen(truepart, retval); if (retval) cg.TakeTemp(1, true); cg.Emit(IL_JUMP, 0); auto loc2 = cg.Pos(); cg.SetLabel(loc); cg.Gen(falsepart, retval); if (retval) cg.TakeTemp(1, true); cg.SetLabel(loc2); } else { assert(!retval); cg.Gen(truepart, 0); cg.SetLabel(loc); } } void While::Generate(CodeGen &cg, size_t retval) const { cg.SplitAttr(cg.Pos()); auto loopback = cg.Pos(); cg.Gen(condition, 1); cg.TakeTemp(1, false); cg.Emit(IL_JUMPFAIL, 0); auto jumpout = cg.Pos(); cg.Gen(body, 0); cg.Emit(IL_JUMP, loopback); cg.SetLabel(jumpout); cg.Dummy(retval); } void For::Generate(CodeGen &cg, size_t retval) const { cg.Emit(IL_PUSHINT, -1); // i cg.temptypestack.push_back({ type_int, LT_ANY }); cg.Gen(iter, 1); cg.nested_fors++; cg.Emit(IL_JUMP, 0); auto startloop = cg.Pos(); cg.SplitAttr(cg.Pos()); cg.Gen(body, 0); cg.SetLabel(startloop); switch (iter->exptype->t) { case V_INT: cg.Emit(IL_IFOR); break; case V_STRING: cg.Emit(IL_SFOR); break; case V_VECTOR: cg.Emit(IL_VFOR); break; default: assert(false); } cg.Emit(startloop); cg.nested_fors--; cg.TakeTemp(2, false); cg.Dummy(retval); } void ForLoopElem::Generate(CodeGen &cg, size_t /*retval*/) const { auto typelt = cg.temptypestack.back(); switch (typelt.type->t) { case V_INT: cg.Emit(IL_IFORELEM); break; case V_STRING: cg.Emit(IL_SFORELEM); break; case V_VECTOR: cg.Emit(IsRefNil(typelt.type->sub->t) ? IL_VFORELEMREF : IL_VFORELEM); break; default: assert(false); } } void ForLoopCounter::Generate(CodeGen &cg, size_t /*retval*/) const { cg.Emit(IL_FORLOOPI); } void Switch::Generate(CodeGen &cg, size_t retval) const { // TODO: create specialized version for dense range of ints with jump table. cg.Gen(value, 1); cg.TakeTemp(1, false); auto valtlt = TypeLT { *value, 0 }; vector nextcase, thiscase, exitswitch; for (auto n : cases->children) { for (auto loc : nextcase) cg.SetLabel(loc); nextcase.clear(); cg.temptypestack.push_back(valtlt); auto cas = AssertIs(n); for (auto c : cas->pattern->children) { auto is_last = c == cas->pattern->children.back(); cg.GenDup(valtlt); auto compare_one = [&](MathOp op, Node *cn) { cg.Gen(cn, 1); cg.GenMathOp(value->exptype, c->exptype, value->exptype, op); }; auto compare_one_jump = [&](MathOp op, Node *cn) { compare_one(op, cn); cg.Emit(is_last ? IL_JUMPFAIL : IL_JUMPNOFAIL, 0); (is_last ? nextcase : thiscase).push_back(cg.Pos()); }; if (auto r = Is(c)) { compare_one(MOP_GE, r->start); cg.Emit(IL_JUMPFAIL, 0); auto loc = cg.Pos(); if (is_last) nextcase.push_back(loc); cg.GenDup(valtlt); compare_one_jump(MOP_LE, r->end); if (!is_last) cg.SetLabel(loc); } else { // FIXME: if this is a string, will alloc a temp string object just for the sake of // comparison. Better to create special purpose opcode to compare with const string. compare_one_jump(MOP_EQ, c); } } for (auto loc : thiscase) cg.SetLabel(loc); thiscase.clear(); cg.GenPop(valtlt); cg.TakeTemp(1, false); cg.Gen(cas->body, retval); if (retval) cg.TakeTemp(1, true); if (n != cases->children.back()) { cg.Emit(IL_JUMP, 0); exitswitch.push_back(cg.Pos()); } } for (auto loc : nextcase) cg.SetLabel(loc); for (auto loc : exitswitch) cg.SetLabel(loc); } void Case::Generate(CodeGen &/*cg*/, size_t /*retval*/) const { assert(false); } void Range::Generate(CodeGen &/*cg*/, size_t /*retval*/) const { assert(false); } void Constructor::Generate(CodeGen &cg, size_t retval) const { // FIXME: a malicious script can exploit this for a stack overflow. for (auto c : children) { cg.Gen(c, retval); } if (!retval) return; cg.TakeTemp(Arity(), true); auto offset = cg.GetTypeTableOffset(exptype); if (IsUDT(exptype->t)) { assert(exptype->udt->fields.size() == Arity()); if (IsStruct(exptype->t)) { // This is now a no-op! Struct elements sit inline on the stack. } else { cg.Emit(IL_NEWOBJECT, offset); } } else { assert(exptype->t == V_VECTOR); cg.Emit(IL_NEWVEC, offset, (int)Arity()); } } void IsType::Generate(CodeGen &cg, size_t retval) const { cg.Gen(child, retval); // If the value was a scalar, then it always results in a compile time type check, // which means this T_IS would have been optimized out. Which means from here on we // can assume its a ref. assert(!IsUnBoxed(child->exptype->t)); if (retval) { cg.TakeTemp(1, false); cg.Emit(IL_ISTYPE, cg.GetTypeTableOffset(giventype)); } } void EnumCoercion::Generate(CodeGen &cg, size_t retval) const { cg.Gen(child, retval); if (retval) cg.TakeTemp(1, false); } void Return::Generate(CodeGen &cg, size_t retval) const { assert(!retval); (void)retval; assert(!cg.rettypes.size()); if (cg.temptypestack.size()) { // We have temps on the stack from an enclosing for. // We can't actually remove these from the stack as the parent nodes still // expect them to be there. // Check that these temps are actually from for loops, to not mask bugs. assert(cg.temptypestack.size() == cg.nested_fors * 2); for (int i = (int)cg.temptypestack.size() - 1; i >= 0; i--) { cg.GenPop(cg.temptypestack[i]); } } auto nretvals = make_void ? 0 : sf->returntype->NumValues(); int nretslots = 0; if (!make_void) { for (size_t i = 0; i < nretvals; i++) { nretslots += ValWidth(sf->returntype->Get(i)); } } if (nretslots > MAX_RETURN_VALUES) cg.parser.Error("too many return values"); if (sf->reqret) { if (!Is(child)) { cg.Gen(child, nretvals); cg.TakeTemp(nretvals, true); } else { cg.Emit(IL_PUSHNIL); assert(nretvals == 1); } } else { if (!Is(child)) cg.Gen(child, 0); nretvals = 0; nretslots = 0; } // FIXME: we could change the VM to instead work with SubFunction ids. // Note: this can only work as long as the type checker forces specialization // of the functions in between here and the function returned to. // FIXME: shouldn't need any type here if V_VOID, but nretvals is at least 1 ? cg.Emit(IL_RETURN, sf->parent->idx, nretslots); } void CoClosure::Generate(CodeGen &cg, size_t retval) const { if (retval) cg.Emit(IL_COCL); } void CoRoutine::Generate(CodeGen &cg, size_t retval) const { cg.Emit(IL_CORO, 0); auto loc = cg.Pos(); auto sf = exptype->sf; assert(exptype->t == V_COROUTINE && sf); cg.Emit(cg.GetTypeTableOffset(exptype)); // TODO: We shouldn't need to store this table for each call, instead do it once for // each function. auto num = cg.Pos(); cg.Emit(0); for (auto &arg : sf->coyieldsave.v) { auto n = ValWidth(arg.sid->type); for (int i = 0; i < n; i++) { cg.Emit(arg.sid->Idx() + i); cg.code[num]++; } } cg.temptypestack.push_back(TypeLT { *this, 0 }); cg.Gen(call, 1); cg.TakeTemp(2, false); cg.Emit(IL_COEND); cg.SetLabel(loc); if (!retval) cg.Emit(IL_POPREF); } void TypeOf::Generate(CodeGen &cg, size_t /*retval*/) const { if (auto dv = Is(child)) { if (cg.node_context.size() >= 2) { auto parent = cg.node_context[cg.node_context.size() - 2]; if (Is(parent)) { cg.Emit(IL_PUSHINT, cg.GetTypeTableOffset(parent->exptype)); return; } } cg.parser.Error("typeof return out of call context", dv); } else if (auto idr = Is(child)) { cg.Emit(IL_PUSHINT, cg.GetTypeTableOffset(idr->exptype)); } else { auto ta = AssertIs(child); cg.Emit(IL_PUSHINT, cg.GetTypeTableOffset(ta->giventype)); } } } // namespace lobster treesheets-1.0.2/lobster/src/lobster/compiler.h000066400000000000000000000035411352107072600215710ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_COMPILER #define LOBSTER_COMPILER #include "lobster/natreg.h" namespace lobster { enum { RUNTIME_NO_ASSERT, RUNTIME_ASSERT, RUNTIME_ASSERT_PLUS }; extern void Compile(NativeRegistry &natreg, string_view fn, string_view stringsource, string &bytecode, string *parsedump, string *pakfile, bool dump_builtins, bool dump_names, bool return_value, int runtime_checks); extern bool LoadPakDir(const char *lpak); extern bool LoadByteCode(string &bytecode); extern void RegisterBuiltin(NativeRegistry &natreg, const char *name, void (* regfun)(NativeRegistry &)); extern void RegisterCoreLanguageBuiltins(NativeRegistry &natreg); extern VMArgs CompiledInit(int argc, char *argv[], const void *entry_point, const void *bytecodefb, size_t static_size, const lobster::block_t *vtables, FileLoader loader, NativeRegistry &nfr); extern "C" int ConsoleRunCompiledCodeMain(int argc, char *argv[], const void *entry_point, const void *bytecodefb, size_t static_size, const lobster::block_t *vtables); } // namespace lobster #endif // LOBSTER_COMPILER treesheets-1.0.2/lobster/src/lobster/cubegen.h000066400000000000000000000060571352107072600213740ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_CUBEGEN_H #define LOBSTER_CUBEGEN_H const uchar transparant = 0; struct Voxels { vector palette; bool is_default_palette; Chunk3DGrid grid; int idx = 0; Voxels(const int3 &dim) : is_default_palette(true), grid(dim, transparant) {} template void Do(const int3 &p, const int3 &sz, F f) { for (int x = max(0, p.x); x < min(p.x + sz.x, grid.dim.x); x++) { for (int y = max(0, p.y); y < min(p.y + sz.y, grid.dim.y); y++) { for (int z = max(0, p.z); z < min(p.z + sz.z, grid.dim.z); z++) { auto pos = int3(x, y, z); f(pos, grid.Get(pos)); } } } } void Set(const int3 &p, const int3 &sz, uchar pi) { Do(p, sz, [&](const int3 &, uchar &vox) { vox = pi; }); } void Copy(const int3 &p, const int3 &sz, const int3 &dest, const int3 &flip) { Do(p, sz, [&](const int3 &pos, uchar &vox) { auto d = (pos - p) * flip + dest; if (d >= int3_0 && d < grid.dim) grid.Get(d) = vox; }); } void Clone(const int3 &p, const int3 &sz, Voxels *dest) { assert(dest->grid.dim == sz); Do(p, sz, [&](const int3 &pos, uchar &vox) { dest->grid.Get(pos - p) = vox; }); } uchar Color2Palette(const float4 &color) const { uchar pi = transparant; if (color.w >= 0.5f) { if (is_default_palette) { // Fast path. auto ic = byte4((int4(quantizec(color)) + (0x33 / 2)) / 0x33); pi = (5 - ic.x) * 36 + (5 - ic.y) * 6 + (5 - ic.z) + 1; } else { float error = 999999; for (size_t i = 1; i < palette.size(); i++) { auto err = squaredlength(color2vec(palette[i]) - color); if (err < error) { error = err; pi = (uchar)i; } } } } return pi; } }; namespace lobster { inline ResourceType *GetVoxelType() { static ResourceType voxel_type = { "voxels", [](void *v) { delete (Voxels *)v; } }; return &voxel_type; } inline Voxels &GetVoxels(VM &vm, const Value &res) { return *GetResourceDec(vm, res, GetVoxelType()); } Value CubesFromMeshGen(VM &vm, const DistGrid &grid, int targetgridsize, int zoffset); extern const unsigned int default_palette[256]; } #endif // LOBSTER_CUBEGEN_H treesheets-1.0.2/lobster/src/lobster/disasm.h000066400000000000000000000032241352107072600212350ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_DISASM #define LOBSTER_DISASM #include "lobster/natreg.h" #define FLATBUFFERS_DEBUG_VERIFICATION_FAILURE #include "lobster/bytecode_generated.h" namespace lobster { inline string IdName(const bytecode::BytecodeFile *bcf, int i) { auto idx = bcf->specidents()->Get(i)->ididx(); int j = i; // FIXME: this theoretically can span 2 specializations of the same var. while (j && bcf->specidents()->Get(j - 1)->ididx() == idx) j--; auto basename = bcf->idents()->Get(idx)->name()->string_view(); return j == i ? string(basename) : cat(basename, '+', i - j); } const bytecode::LineInfo *LookupLine(const int *ip, const int *code, const bytecode::BytecodeFile *bcf); const int *DisAsmIns(NativeRegistry &natreg, ostringstream &ss, const int *ip, const int *code, const type_elem_t *typetable, const bytecode::BytecodeFile *bcf); void DisAsm(NativeRegistry &natreg, ostringstream &ss, string_view bytecode_buffer); } // namespace lobster #endif // LOBSTER_DISASM treesheets-1.0.2/lobster/src/lobster/engine.h000066400000000000000000000023331352107072600212220ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_ENGINE_H #define LOBSTER_ENGINE_H #include "lobster/vmdata.h" extern void RegisterCoreEngineBuiltins(lobster::NativeRegistry &natreg); extern void EngineRunByteCode(lobster::VMArgs &&vmargs); extern "C" int EngineRunCompiledCodeMain(int argc, char *argv[], const void *entry_point, const void *bytecodefb, size_t static_size, const lobster::block_t *vtables); extern void EngineSuspendIfNeeded(); extern void EngineExit(int code); #ifdef __EMSCRIPTEN__ #define USE_MAIN_LOOP_CALLBACK #endif #endif // LOBSTER_ENGINE_H treesheets-1.0.2/lobster/src/lobster/fontrenderer.h000066400000000000000000000030121352107072600224450ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // simple interface for FreeType (that doesn't depend on its headers) struct BitmapFont; struct OutlineFont { void *fthandle; string fbuf; unordered_map unicodemap; vector unicodetable; map glyph_to_char; OutlineFont(void *fth, string &fb) : fthandle(fth) { fbuf.swap(fb); } ~OutlineFont(); bool EnsureCharsPresent(string_view utf8str); string GetName(uint i); uint GetCharCode(string_view name); }; struct BitmapFont { Texture tex; vector positions; int height = 0; int usedcount = 1; int size; float outlinesize; OutlineFont *font; ~BitmapFont(); BitmapFont(OutlineFont *_font, int _size, float _osize); void RenderText(string_view text); const int2 TextSize(string_view text); bool CacheChars(string_view text); }; extern OutlineFont *LoadFont(string_view name); extern void FTClosedown(); treesheets-1.0.2/lobster/src/lobster/geom.h000066400000000000000000000773321352107072600207170ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. namespace geom { #define PI 3.14159265f #define RAD (PI/180.0f) // vec: supports 2..4 components of any numerical type. // Compile time unrolled loops for 2..4 components. #define DOVEC(F) { const int i = 0; F; \ { const int i = 1; F; if constexpr (N > 2) \ { const int i = 2; F; if constexpr (N > 3) \ { const int i = 3; F; } } } } #define DOVECR(F) { vec _t; DOVEC(_t[i] = F); return _t; } #define DOVECRI(F) { vec _t; DOVEC(_t[i] = F); return _t; } #define DOVECF(I,F) { T _ = I; DOVEC(_ = F); return _; } #define DOVECB(I,F) { bool _ = I; DOVEC(_ = F); return _; } union int2float { int i; float f; }; union int2float64 { int64_t i; double f; }; inline void default_debug_value(float &a) { int2float nan; nan.i = 0x7F800001; a = nan.f; } inline void default_debug_value(double &a) { int2float nan; nan.i = 0x7F800001; a = nan.f; } inline void default_debug_value(int &a) { a = 0x1BADCAFE; } inline void default_debug_value(int64_t &a) { a = 0x1BADCAFEABADD00D; } inline void default_debug_value(short &a) { a = 0x1BAD; } inline void default_debug_value(uchar &a) { a = 0x1B; } template class matrix; template struct basevec { }; template struct basevec { union { T c[2]; struct { T x; T y; }; }; }; template struct basevec { union { T c[3]; struct { T x; T y; T z; }; }; }; template struct basevec { union { T c[4]; struct { T x; T y; T z; T w; }; }; }; template struct vec : basevec { enum { NUM_ELEMENTS = N }; typedef T CTYPE; // Clang needs these, but VS is cool without them? using basevec::c; using basevec::x; using basevec::y; vec() { #ifndef NDEBUG DOVEC(default_debug_value(c[i])); #endif } explicit vec(T e) { DOVEC(c[i] = e); } explicit vec(const T *v) { DOVEC(c[i] = v[i]); } template explicit vec(const vec &v) { DOVEC(c[i] = (T)v[i]); } vec(T _x, T _y, T _z, T _w) { x = _x; y = _y; assert(N == 4); if constexpr (N > 2) c[2] = _z; else (void)_z; if constexpr (N > 3) c[3] = _w; else (void)_w; } vec(T _x, T _y, T _z) { x = _x; y = _y; assert(N == 3); if constexpr (N > 2) c[2] = _z; else (void)_z; } vec(T _x, T _y) { x = _x; y = _y; assert(N == 2); } vec(const pair &p) { x = p.first; y = p.second; assert(N == 2); } const T *data() const { return c; } const T *begin() const { return c; } const T *end() const { return c + N; } T operator[](size_t i) const { return c[i]; } T &operator[](size_t i) { return c[i]; } vec(const vec &v, T e) { DOVEC(c[i] = i < 3 ? v[i] : e); } vec(const vec &v, T e) { DOVEC(c[i] = i < 2 ? v[i] : e); } vec xyz() const { assert(N == 4); return vec(c); } vec xy() const { assert(N >= 3); return vec(c); } pair to_pair() const { assert(N == 2); return { x, y }; } vec operator+(const vec &v) const { DOVECR(c[i] + v[i]); } vec operator-(const vec &v) const { DOVECR(c[i] - v[i]); } vec operator*(const vec &v) const { DOVECR(c[i] * v[i]); } vec operator/(const vec &v) const { DOVECR(c[i] / v[i]); } vec operator%(const vec &v) const { DOVECR(c[i] % v[i]); } vec operator+(T e) const { DOVECR(c[i] + e); } vec operator-(T e) const { DOVECR(c[i] - e); } vec operator*(T e) const { DOVECR(c[i] * e); } vec operator/(T e) const { DOVECR(c[i] / e); } vec operator%(T e) const { DOVECR(c[i] % e); } vec operator&(T e) const { DOVECR(c[i] & e); } vec operator|(T e) const { DOVECR(c[i] | e); } vec operator<<(T e) const { DOVECR(c[i] << e); } vec operator>>(T e) const { DOVECR(c[i] >> e); } vec operator-() const { DOVECR(-c[i]); } vec &operator+=(const vec &v) { DOVEC(c[i] += v[i]); return *this; } vec &operator-=(const vec &v) { DOVEC(c[i] -= v[i]); return *this; } vec &operator*=(const vec &v) { DOVEC(c[i] *= v[i]); return *this; } vec &operator/=(const vec &v) { DOVEC(c[i] /= v[i]); return *this; } vec &operator+=(T e) { DOVEC(c[i] += e); return *this; } vec &operator-=(T e) { DOVEC(c[i] -= e); return *this; } vec &operator*=(T e) { DOVEC(c[i] *= e); return *this; } vec &operator/=(T e) { DOVEC(c[i] /= e); return *this; } vec &operator&=(T e) { DOVEC(c[i] &= e); return *this; } bool operator<=(const vec &v) const { DOVECB(true, _ && c[i] <= v[i]); } bool operator< (const vec &v) const { DOVECB(true, _ && c[i] < v[i]); } bool operator>=(const vec &v) const { DOVECB(true, _ && c[i] >= v[i]); } bool operator> (const vec &v) const { DOVECB(true, _ && c[i] > v[i]); } bool operator==(const vec &v) const { DOVECB(true, _ && c[i] == v[i]); } bool operator!=(const vec &v) const { DOVECB(false, _ || c[i] != v[i]); } bool operator<=(T e) const { DOVECB(true, _ && c[i] <= e); } bool operator< (T e) const { DOVECB(true, _ && c[i] < e); } bool operator>=(T e) const { DOVECB(true, _ && c[i] >= e); } bool operator> (T e) const { DOVECB(true, _ && c[i] > e); } bool operator==(T e) const { DOVECB(true, _ && c[i] == e); } bool operator!=(T e) const { DOVECB(false, _ || c[i] != e); } vec lte(T e) const { DOVECRI(c[i] <= e); } vec lt (T e) const { DOVECRI(c[i] < e); } vec gte(T e) const { DOVECRI(c[i] >= e); } vec gt (T e) const { DOVECRI(c[i] > e); } vec eq (T e) const { DOVECRI(c[i] == e); } vec ne (T e) const { DOVECRI(c[i] != e); } vec iflt(T e, const vec &a, const vec &b) const { DOVECR(c[i] < e ? a[i] : b[i]); } string to_string() const { string s = "("; DOVEC(if (i) s += ", "; s += std::to_string(c[i])); return s + ")"; } T volume() const { DOVECF(1, _ * c[i]); } template friend class matrix; }; template inline T mix(T a, T b, float f) { return (T)(a * (1 - f) + b * f); } // Rational replacement for powf (when t = 0..1), due to // "Ratioquadrics: An Alternative Model for Superquadrics" //inline float rpowf(float t, float e) //{ // assert(t >= 0 && t <= 1); e = 1 / e + 1.85f/* wtf */; return t / (e + (1 - e) * t); //} inline float rpowf(float t, float e) { return expf(e * logf(t)); } // Exponentiation by squaring for integer types. template T ipow(T base, T exp) { assert(exp >= 0); T result = 1; for (;;) { if (exp & 1) result *= base; exp >>= 1; if (!exp) return result; base *= base; } } template int ffloor(T f) { int i = (int)f; return i - (f < i); } template int fceil(T f) { int i = (int)f; return i + (f > i); } template int signum(T val) { return (T(0) < val) - (val < T(0)); } template inline vec operator+(T f, const vec &v) { DOVECR(f + v[i]); } template inline vec operator-(T f, const vec &v) { DOVECR(f - v[i]); } template inline vec operator*(T f, const vec &v) { DOVECR(f * v[i]); } template inline vec operator/(T f, const vec &v) { DOVECR(f / v[i]); } template inline T dot(const vec &a, const vec &b) { DOVECF(0, _ + a[i] * b[i]); } template inline T squaredlength(const vec &v) { return dot(v, v); } template inline T length(const vec &v) { return sqrt(squaredlength(v)); } template inline vec normalize(const vec &v) { return v / length(v); } template inline vec abs(const vec &v) { DOVECR(fabsf(v[i])); } template inline vec sign(const vec &v) { DOVECR((T)(v[i] >= 0 ? 1 : -1)); } template inline vec signum(const vec &v) { DOVECR(signum(v[i])); } template inline vec min(const vec &a, const vec &b) { DOVECR(std::min(a[i], b[i])); } template inline vec max(const vec &a, const vec &b) { DOVECR(std::max(a[i], b[i])); } template inline vec pow(const vec &a, const vec &b) { DOVECR(powf(a[i], b[i])); } template inline vec rpow(const vec &a, const vec &b) { DOVECR(rpowf(a[i], b[i])); } template inline T min(const vec &a) { DOVECF(FLT_MAX, std::min(a[i], _)); } template inline T max(const vec &a) { DOVECF(-FLT_MAX, std::max(a[i], _)); } template inline T sum(const vec &a) { DOVECF(0, _ + a[i]); } template inline T average(const vec &a) { return sum(a) / N; } template inline T manhattan(const vec &a) { DOVECF(0, _ + std::abs(a[i])); } template inline vec fceil(const vec &v) { DOVECRI(fceil(v[i])); } template inline vec ffloor(const vec &v) { DOVECRI(ffloor(v[i])); } template inline vec round(const vec &v) { DOVECR(roundf(v[i])); } template inline T clamp(T v, T lo, T hi) { static_assert(is_scalar(), ""); return std::min(hi, std::max(lo, v)); } template inline vec clamp(const vec &v, const vec &lo, const vec &hi) { DOVECR(clamp(v[i], lo[i], hi[i])); } template inline vec clamp(const vec &v, T lo, T hi) { DOVECR(clamp(v[i], lo, hi)); } template inline vec rndunitvec(RandomNumberGenerator &r) { DOVECR(r.rnd_float()); } template inline vec rndsignedvec(RandomNumberGenerator &r) { DOVECR(r.rndfloatsigned()); } template inline vec rndivec(RandomNumberGenerator &r, const vec &max) { DOVECR(r(max[i])); } #undef DOVEC #undef DOVECR typedef vec float2; typedef vec float3; typedef vec float4; typedef vec int2; typedef vec int3; typedef vec int4; typedef vec byte4; const float4 float4_0 = float4(0.0f); const float4 float4_1 = float4(1.0f); const float3 float3_0 = float3(0.0f); const float3 float3_1 = float3(1.0f); const float3 float3_x = float3(1, 0, 0); const float3 float3_y = float3(0, 1, 0); const float3 float3_z = float3(0, 0, 1); const float2 float2_0 = float2(0.0f); const float2 float2_1 = float2(1.0f); const float2 float2_x = float2(1, 0); const float2 float2_y = float2(0, 1); const int2 int2_0 = int2(0); const int2 int2_1 = int2(1); const int3 int3_0 = int3(0); const int3 int3_1 = int3(1); const byte4 byte4_0 = byte4((uchar)0); const byte4 byte4_255 = byte4((uchar)255); template vec cross(const vec &a, const vec &b) { return vec(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); } inline float smoothminh(float a, float b, float k) { return std::min(std::max(0.5f + 0.5f * (b - a) / k, 0.0f), 1.0f); } template float smoothmix(T a, T b, T k, T h) { return mix(b, a, h) - k * h * (1.0f - h); } inline float smoothmin(float a, float b, float k) { return smoothmix(a, b, k, smoothminh(a, b, k)); } inline float smoothmax(float a, float b, float r) { auto u = max(float2(r + a, r + b), float2_0); return std::min(-r, std::max(a, b)) + length(u); } template inline float3 random_point_in_sphere(RandomNumberGenerator &r) { for (;;) { const float3 p(r.rndfloatsigned(), r.rndfloatsigned(), r.rndfloatsigned()); if (dot(p, p) < 1.f) return p; } } inline float3 rotateX(const float3 &v, const float2 &a) { return float3(v.x, v.y * a.x - v.z * a.y, v.y * a.y + v.z * a.x); } inline float3 rotateY(const float3 &v, const float2 &a) { return float3(v.x * a.x + v.z * a.y, v.y, v.z * a.x - v.x * a.y); } inline float3 rotateZ(const float3 &v, const float2 &a) { return float3(v.x * a.x - v.y * a.y, v.x * a.y + v.y * a.x, v.z); } inline float3 rotateX(const float3 &v, float a) { return rotateX(v, float2(cosf(a), sinf(a))); } inline float3 rotateY(const float3 &v, float a) { return rotateY(v, float2(cosf(a), sinf(a))); } inline float3 rotateZ(const float3 &v, float a) { return rotateZ(v, float2(cosf(a), sinf(a))); } struct quat : float4 { quat() {} quat(float x, float y, float z, float w) : float4(x, y, z, w) {} quat(const float3 &v, float w) : float4(v, w) {} quat(const float4 &v) : float4(v) {} quat(float angle, const float3 &axis) { float s = sinf(0.5f*angle); *this = quat(s * axis, cosf(0.5f * angle)); } explicit quat(const float3 &v) : float4(v, -sqrtf(std::max(1.0f - squaredlength(v), 0.0f))) {} explicit quat(const float *v) : float4(v[0], v[1], v[2], v[3]) {} quat operator*(const quat &o) const { return quat(w * o.x + x * o.w + y * o.z - z * o.y, w * o.y - x * o.z + y * o.w + z * o.x, w * o.z + x * o.y - y * o.x + z * o.w, w * o.w - x * o.x - y * o.y - z * o.z); } quat operator-() const { return quat(-xyz(), w); } void flip() { *this = quat(-(float4)*this); } float3 transform(const float3 &p) const { return p + cross(xyz(), cross(xyz(), p) + p * w) * 2.0f; } }; template class matrix { typedef vec V; V m[C]; public: matrix() {} explicit matrix(T e) { /* this used to be the following code that triggered a codegen bug in VS2017 (version 15.3) release mode. Fixed in 15.4. Can reinstate in the future once 15.3 is unlikely in use anymore? for (int x = 0; x < C; x++) for (int y = 0; y < R; y++) m[x].c[y] = e * (T)(x == y); */ memset(this, 0, sizeof(*this)); for (int x = 0; x < std::min(C, R); x++) m[x].c[x] = e; } explicit matrix(const V &v) { memset(this, 0, sizeof(*this)); for (int x = 0; x < std::min(C, R); x++) m[x].c[x] = v[x]; } explicit matrix(const T *mat_data) { memcpy(this, mat_data, sizeof(T) * R * C); } matrix(V x, V y, V z, V w) { assert(C == 4); m[0] = x; m[1] = y; m[2] = z; m[3] = w; } matrix(V x, V y, V z) { assert(C == 3); m[0] = x; m[1] = y; m[2] = z; } matrix(V x, V y) { assert(C == 2); m[0] = x; m[1] = y; } matrix(float a, const float3 &v) { assert(C >= 3); assert(R >= 3); *this = matrix(1); float s = sinf(a); float c = cosf(a); const float t = 1.f - c; const float3 n = normalize(v); const float x = n.x; const float y = n.y; const float z = n.z; m[0].x = t*x*x + c; m[0].y = t*x*y + z*s; m[0].z = t*x*z - y*s; m[1].x = t*x*y - z*s; m[1].y = t*y*y + c; m[1].z = t*y*z + x*s; m[2].x = t*x*z + y*s; m[2].y = t*y*z - x*s; m[2].z = t*z*z + c; } const T *data() const { return m[0].c; } T *data_mut() { return m[0].c; } const T *begin() const { return m[0].c; } const T *end() const { return m[C].c; } const V &operator[](size_t i) const { return m[i]; } // not an operator on purpose, don't use outside this header void set(int i, const V &v) { m[i] = v; } vec row(int i) const { if constexpr (C == 2) return vec(m[0][i], m[1][i]); if constexpr (C == 3) return vec(m[0][i], m[1][i], m[2][i]); if constexpr (C == 4) return vec(m[0][i], m[1][i], m[2][i], m[3][i]); } matrix transpose() const { matrix res; for (int y = 0; y < R; y++) res.set(y, row(y)); return res; } V operator*(const vec &v) const { V res(0.0f); for (int i = 0; i < C; i++) res += m[i] * v[i]; return res; } matrix &operator*=(const matrix &o) { return *this = *this * o; } matrix operator*(const matrix &o) const { matrix t = transpose(); matrix res; for (int x = 0; x < R; x++) for (int y = 0; y < R; y++) res.m[x].c[y] = dot(t[y], o[x]); return res; } matrix operator*(T f) const { matrix res; for (int x = 0; x < C; x++) res.m[x] = m[x] * f; return res; } matrix operator+(const matrix &o) const { matrix res; for (int x = 0; x < C; x++) res.m[x] = m[x] + o.m[x]; return res; } string to_string() const { string s = "("; for (int x = 0; x < C; x++) { if (x) s += ", "; s += m[x].to_string(); } return s + ")"; } }; template inline vec operator*(const vec &v, const matrix &m) { vec t; for (int i = 0; i < R; i++) t[i] = dot(v, m[i]); return t; } typedef matrix float4x4; typedef matrix float3x3; typedef matrix float3x4; typedef matrix float4x3; const float4x4 float4x4_1 = float4x4(1); const float3x3 float3x3_1 = float3x3(1); inline float3x4 operator*(const float3x4 &m, const float3x4 &o) { // FIXME: clean this up return float3x4( (o[0]*m[0].x + o[1]*m[0].y + o[2]*m[0].z + float4(0, 0, 0, m[0].w)), (o[0]*m[1].x + o[1]*m[1].y + o[2]*m[1].z + float4(0, 0, 0, m[1].w)), (o[0]*m[2].x + o[1]*m[2].y + o[2]*m[2].z + float4(0, 0, 0, m[2].w))); } inline float4x4 translation(const float3 &t) { return float4x4( float4(1, 0, 0, 0), float4(0, 1, 0, 0), float4(0, 0, 1, 0), float4(t, 1)); } inline float4x4 scaling(float s) { return float4x4( float4(s, 0, 0, 0), float4(0, s, 0, 0), float4(0, 0, s, 0), float4(0, 0, 0, 1)); } inline float4x4 scaling(const float3 &s) { return float4x4( float4(s.x, 0, 0, 0), float4(0, s.y, 0, 0), float4(0, 0, s.z, 0), float4(0, 0, 0, 1)); } inline float4x4 rotationX(const float2 &v) { return float4x4( float4(1, 0, 0, 0), float4(0, v.x, v.y, 0), float4(0,-v.y, v.x, 0), float4(0, 0, 0, 1)); } inline float4x4 rotationY(const float2 &v) { return float4x4( float4(v.x, 0,-v.y, 0), float4(0, 1, 0, 0), float4(v.y, 0, v.x, 0), float4(0, 0, 0, 1)); } inline float4x4 rotationZ(const float2 &v) { return float4x4( float4( v.x, v.y, 0, 0), float4(-v.y, v.x, 0, 0), float4( 0, 0, 1, 0), float4( 0, 0, 0, 1)); } inline float4x4 rotation3D(const float3 &v) { return float4x4( float4( 0, -v.z, v.y, 0), float4( v.z, 0, -v.x, 0), float4(-v.y, v.x, 0, 0), float4( 0, 0, 0, 1)); } inline float4x4 rotationX(float a) { return rotationX(float2(cosf(a), sinf(a))); } inline float4x4 rotationY(float a) { return rotationY(float2(cosf(a), sinf(a))); } inline float4x4 rotationZ(float a) { return rotationZ(float2(cosf(a), sinf(a))); } inline quat quatfromtwovectors(const float3 &u, const float3 &v) { float norm_u_norm_v = sqrt(dot(u, u) * dot(v, v)); float real_part = norm_u_norm_v + dot(u, v); float3 w; if (real_part < 1.e-6f * norm_u_norm_v) { // If u and v are exactly opposite, rotate 180 degrees // around an arbitrary orthogonal axis. Axis normalisation // can happen later, when we normalise the quaternion. real_part = 0.0f; w = fabsf(u.x) > fabsf(u.z) ? float3(-u.y, u.x, 0.f) : float3(0.f, -u.z, u.y); } else { // Otherwise, build quaternion the standard way. w = cross(u, v); } return normalize(quat(w, real_part)); } inline float3x3 rotation(const quat &q) { float x = q.x, y = q.y, z = q.z, w = q.w, tx = 2*x, ty = 2*y, tz = 2*z, txx = tx*x, tyy = ty*y, tzz = tz*z, txy = tx*y, txz = tx*z, tyz = ty*z, twx = w*tx, twy = w*ty, twz = w*tz; return float3x3(float3(1 - (tyy + tzz), txy - twz, txz + twy), float3(txy + twz, 1 - (txx + tzz), tyz - twx), float3(txz - twy, tyz + twx, 1 - (txx + tyy))); } // FIXME: This is not generic, here because of IQM. inline float3x4 rotationscaletrans(const quat &q, const float3 &s, const float3 &t) { float3x3 m = rotation(q); for (int i = 0; i < 3; i++) m.set(i, m[i] * s); return float3x4(float4(m[0], t.x), float4(m[1], t.y), float4(m[2], t.z)); } inline float4x4 float3x3to4x4(const float3x3 &m) { return float4x4(float4(m[0], 0), float4(m[1], 0), float4(m[2], 0), float4(0, 0, 0, 1)); } // FIXME: This is not generic, here because of IQM. inline float3x4 invertortho(const float3x4 &o) { float4x3 inv = o.transpose(); for (int i = 0; i < 3; i++) inv.set(i, inv[i] / squaredlength(inv[i])); return float3x4(float4(inv[0], -dot(inv[0], inv[3])), float4(inv[1], -dot(inv[1], inv[3])), float4(inv[2], -dot(inv[2], inv[3]))); } inline float4x4 invert(const float4x4 &mat) { auto m = mat.data(); float4x4 dest; auto inv = dest.data_mut(); inv[0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10]; inv[4] = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10]; inv[8] = m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9]; inv[12] = -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9]; inv[1] = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10]; inv[5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10]; inv[9] = -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9]; inv[13] = m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9]; inv[2] = m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7] - m[13] * m[3] * m[6]; inv[6] = -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7] + m[12] * m[3] * m[6]; inv[10] = m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7] - m[12] * m[3] * m[5]; inv[14] = -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6] + m[12] * m[2] * m[5]; inv[3] = -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9] * m[2] * m[7] + m[9] * m[3] * m[6]; inv[7] = m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8] * m[2] * m[7] - m[8] * m[3] * m[6]; inv[11] = -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - m[8] * m[1] * m[7] + m[8] * m[3] * m[5]; inv[15] = m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - m[8] * m[2] * m[5]; float det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]; if (det == 0) { assert(false); return float4x4_1; } det = 1.0f / det; for (int i = 0; i < 16; i++) inv[i] = inv[i] * det; return dest; } // Handedness: 1.f for RH, -1.f for LH. inline float4x4 perspective( float fovy, float aspect, float znear, float zfar, float handedness) { const float y = 1 / tanf(fovy * .5f); const float x = y / aspect; const float zdist = (znear - zfar) * handedness; const float zfar_per_zdist = zfar / zdist; return float4x4( float4(x, 0, 0, 0), float4(0, y, 0, 0), float4(0, 0, zfar_per_zdist, -1.f * handedness), float4(0, 0, 2 * znear * zfar_per_zdist * handedness, 0)); } inline float4x4 ortho(float left, float right, float bottom, float top, float znear, float zfar) { return float4x4( float4(2.0f / (right - left), 0, 0, 0), float4(0, 2.0f / (top - bottom), 0, 0), float4(0, 0, -2.0f / (zfar - znear), 0), float4(-(right + left) / (right - left), -(top + bottom) / (top - bottom), -(zfar + znear) / (zfar - znear), 1.0f) ); } inline byte4 quantizec(const float3 &v) { return byte4(float4(v, 1) * 255); } inline byte4 quantizec(const float4 &v) { return byte4(v * 255); } inline float4 color2vec(const byte4 &col) { return float4(col) / 255; } // Spline interpolation. template inline vec cardinal_spline(const vec &z, const vec &a, const vec &b, const vec &c, T s, T tension = 0.5) { T s2 = s*s; T s3 = s*s2; return a * ( 2*s3 - 3*s2 + 1) + b * (-2*s3 + 3*s2 ) + (b - z) * tension * ( s3 - 2*s2 + s) + (c - a) * tension * ( s3 - s2 ); } inline float triangle_area(const float3 &a, const float3 &b, const float3 &c) { return length(cross(b - a, c - a)) / 2; } template bool line_intersect(const vec &l1a, const vec &l1b, const vec &l2a, const vec &l2b, vec *out = nullptr) { vec a(l1b - l1a); vec b(l2b - l2a); vec aperp(-a.y, a.x); auto f = dot(aperp, b); if (!f) return false; // Parallel. vec c(l2b - l1b); vec bperp(-b.y, b.x); auto aa = dot(aperp, c); auto bb = dot(bperp, c); if(f < 0) { if(aa > 0 || bb > 0 || aa < f || bb < f) return false; } else { if(aa < 0 || bb < 0 || aa > f || bb > f) return false; } if(out) { auto lerp = 1.0f - (aa / f); *out = ((l2b - l2a) * lerp) + l2a; } return true; } // Return the enter and exit t value. inline float2 ray_bb_intersect(const float3 &bbmin, const float3 &bbmax, const float3 &rayo, const float3 &reciprocal_raydir) { auto v1 = (bbmin - rayo) * reciprocal_raydir; auto v2 = (bbmax - rayo) * reciprocal_raydir; auto tmin = std::min(v1.x, v2.x); auto tmax = std::max(v1.x, v2.x); tmin = std::max(tmin, std::min(std::min(v1.y, v2.y), tmax)); tmax = std::min(tmax, std::max(std::max(v1.y, v2.y), tmin)); tmin = std::max(tmin, std::min(std::min(v1.z, v2.z), tmax)); tmax = std::min(tmax, std::max(std::max(v1.z, v2.z), tmin)); return float2(tmin, tmax); } // Call this on the result of ray_bb_intersect() if it isn't known wether there is an intersection. inline bool does_intersect(const float2 &minmax) { return minmax.y >= std::max(minmax.x, 0.0f); } // Moves start of ray to first intersection point if one exists, otherwise leaves it in place. // returns false for no intersection. inline bool clamp_bb(const float3 &bbmin, const float3 &bbmax, const float3 &rayo, const float3 &raydir, const float3 &reciprocal_raydir, float3 &dest) { auto minmax = ray_bb_intersect(bbmin, bbmax, rayo, reciprocal_raydir); auto ok = does_intersect(minmax); dest = rayo; if (ok && minmax.x >= 0) dest += raydir * minmax.x; return ok; } inline void normalize_mesh(span idxs, void *verts, size_t vertlen, size_t vsize, size_t normaloffset, bool ignore_bad_tris = true) { for (size_t i = 0; i < vertlen; i++) { *(float3 *)((uchar *)verts + i * vsize + normaloffset) = float3_0; } for (size_t t = 0; t < idxs.size(); t += 3) { int v1i = idxs[t + 0]; int v2i = idxs[t + 1]; int v3i = idxs[t + 2]; float3 &v1p = *(float3 *)((uchar *)verts + v1i * vsize); float3 &v2p = *(float3 *)((uchar *)verts + v2i * vsize); float3 &v3p = *(float3 *)((uchar *)verts + v3i * vsize); float3 &v1n = *(float3 *)((uchar *)verts + v1i * vsize + normaloffset); float3 &v2n = *(float3 *)((uchar *)verts + v2i * vsize + normaloffset); float3 &v3n = *(float3 *)((uchar *)verts + v3i * vsize + normaloffset); if (v1p != v2p && v1p != v3p && v2p != v3p) { float3 v12 = normalize(v2p - v1p); float3 v13 = normalize(v3p - v1p); float3 v23 = normalize(v3p - v2p); float3 d3 = normalize(cross(v13, v12)); v1n += d3 * (1 - dot( v12, v13)); v2n += d3 * (1 - dot(-v12, v23)); v3n += d3 * (1 - dot(-v23,-v13)); } else { assert(ignore_bad_tris); (void)ignore_bad_tris; } } for (size_t i = 0; i < vertlen; i++) { float3 &norm = *(float3 *)((uchar *)verts + i * vsize + normaloffset); if (norm != float3_0) norm = normalize(norm); } } } // namespace geom treesheets-1.0.2/lobster/src/lobster/glincludes.h000066400000000000000000000214571352107072600221160ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // OpenGL platform definitions #ifdef __APPLE__ #define GL_SILENCE_DEPRECATION #include "TargetConditionals.h" #ifdef __IOS__ //#include #include #include #else #include #endif #elif defined(__ANDROID__) #include #include #include #include #elif defined(__EMSCRIPTEN__) #define GL_GLEXT_PROTOTYPES #include #include #include //#include "SDL/SDL_opengl.h" #else // _WIN32 & Linux #ifdef _WIN32 #define VC_EXTRALEAN #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #endif #include #include #ifdef _WIN32 #define GLBASEEXTS \ GLEXT(PFNGLACTIVETEXTUREARBPROC , glActiveTexture , 1) \ GLEXT(PFNGLTEXIMAGE3DPROC , glTexImage3D , 1) \ GLEXT(PFNGLBLENDEQUATIONPROC , glBlendEquation , 1) #else #define GLBASEEXTS #endif #define GLEXTS \ GLEXT(PFNGLGENBUFFERSARBPROC , glGenBuffers , 1) \ GLEXT(PFNGLBINDBUFFERARBPROC , glBindBuffer , 1) \ GLEXT(PFNGLMAPBUFFERARBPROC , glMapBuffer , 1) \ GLEXT(PFNGLUNMAPBUFFERARBPROC , glUnmapBuffer , 1) \ GLEXT(PFNGLBUFFERDATAARBPROC , glBufferData , 1) \ GLEXT(PFNGLBUFFERSUBDATAARBPROC , glBufferSubData , 1) \ GLEXT(PFNGLDELETEBUFFERSARBPROC , glDeleteBuffers , 1) \ GLEXT(PFNGLGETBUFFERSUBDATAARBPROC , glGetBufferSubData , 1) \ GLEXT(PFNGLVERTEXATTRIBPOINTERARBPROC , glVertexAttribPointer , 1) \ GLEXT(PFNGLENABLEVERTEXATTRIBARRAYARBPROC , glEnableVertexAttribArray , 1) \ GLEXT(PFNGLDISABLEVERTEXATTRIBARRAYARBPROC , glDisableVertexAttribArray , 1) \ GLEXT(PFNGLGENVERTEXARRAYSPROC , glGenVertexArrays , 1) \ GLEXT(PFNGLBINDVERTEXARRAYPROC , glBindVertexArray , 1) \ GLEXT(PFNGLDELETEVERTEXARRAYSPROC , glDeleteVertexArrays , 1) \ GLEXT(PFNGLCREATEPROGRAMPROC , glCreateProgram , 1) \ GLEXT(PFNGLDELETEPROGRAMPROC , glDeleteProgram , 1) \ GLEXT(PFNGLDELETESHADERPROC , glDeleteShader , 1) \ GLEXT(PFNGLUSEPROGRAMPROC , glUseProgram , 1) \ GLEXT(PFNGLCREATESHADERPROC , glCreateShader , 1) \ GLEXT(PFNGLSHADERSOURCEPROC , glShaderSource , 1) \ GLEXT(PFNGLCOMPILESHADERPROC , glCompileShader , 1) \ GLEXT(PFNGLGETPROGRAMIVARBPROC , glGetProgramiv , 1) \ GLEXT(PFNGLGETSHADERIVPROC , glGetShaderiv , 1) \ GLEXT(PFNGLGETPROGRAMINFOLOGPROC , glGetProgramInfoLog , 1) \ GLEXT(PFNGLGETSHADERINFOLOGPROC , glGetShaderInfoLog , 1) \ GLEXT(PFNGLATTACHSHADERPROC , glAttachShader , 1) \ GLEXT(PFNGLDETACHSHADERPROC , glDetachShader , 1) \ GLEXT(PFNGLLINKPROGRAMARBPROC , glLinkProgram , 1) \ GLEXT(PFNGLGETUNIFORMLOCATIONARBPROC , glGetUniformLocation , 1) \ GLEXT(PFNGLUNIFORM1FARBPROC , glUniform1f , 1) \ GLEXT(PFNGLUNIFORM2FARBPROC , glUniform2f , 1) \ GLEXT(PFNGLUNIFORM3FARBPROC , glUniform3f , 1) \ GLEXT(PFNGLUNIFORM4FARBPROC , glUniform4f , 1) \ GLEXT(PFNGLUNIFORM1FVARBPROC , glUniform1fv , 1) \ GLEXT(PFNGLUNIFORM2FVARBPROC , glUniform2fv , 1) \ GLEXT(PFNGLUNIFORM3FVARBPROC , glUniform3fv , 1) \ GLEXT(PFNGLUNIFORM4FVARBPROC , glUniform4fv , 1) \ GLEXT(PFNGLUNIFORM1IARBPROC , glUniform1i , 1) \ GLEXT(PFNGLUNIFORMMATRIX2FVARBPROC , glUniformMatrix2fv , 1) \ GLEXT(PFNGLUNIFORMMATRIX3FVARBPROC , glUniformMatrix3fv , 1) \ GLEXT(PFNGLUNIFORMMATRIX4FVARBPROC , glUniformMatrix4fv , 1) \ GLEXT(PFNGLUNIFORMMATRIX4FVARBPROC/*type*/ , glUniformMatrix3x4fv , 1) \ GLEXT(PFNGLBINDATTRIBLOCATIONARBPROC , glBindAttribLocation , 1) \ GLEXT(PFNGLGETATTRIBLOCATIONARBPROC , glGetAttribLocation , 1) \ GLEXT(PFNGLGETACTIVEUNIFORMARBPROC , glGetActiveUniform , 1) \ GLEXT(PFNGLBLENDEQUATIONSEPARATEPROC , glBlendEquationSeparate , 1) \ GLEXT(PFNGLBLENDFUNCSEPARATEPROC , glBlendFuncSeparate , 1) \ GLEXT(PFNGLBINDSAMPLERPROC , glBindSampler , 1) \ GLEXT(PFNGLBINDRENDERBUFFERPROC , glBindRenderbuffer , 0) \ GLEXT(PFNGLDELETERENDERBUFFERSPROC , glDeleteRenderbuffers , 0) \ GLEXT(PFNGLBINDFRAMEBUFFERPROC , glBindFramebuffer , 0) \ GLEXT(PFNGLDELETEFRAMEBUFFERSPROC , glDeleteFramebuffers , 0) \ GLEXT(PFNGLGENFRAMEBUFFERSPROC , glGenFramebuffers , 0) \ GLEXT(PFNGLFRAMEBUFFERTEXTURE2DPROC , glFramebufferTexture2D , 0) \ GLEXT(PFNGLGENRENDERBUFFERSPROC , glGenRenderbuffers , 0) \ GLEXT(PFNGLRENDERBUFFERSTORAGEPROC , glRenderbufferStorage , 0) \ GLEXT(PFNGLFRAMEBUFFERRENDERBUFFERPROC , glFramebufferRenderbuffer , 0) \ GLEXT(PFNGLCHECKFRAMEBUFFERSTATUSPROC , glCheckFramebufferStatus , 0) \ GLEXT(PFNGLTEXIMAGE2DMULTISAMPLEPROC , glTexImage2DMultisample , 0) \ GLEXT(PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC, glRenderbufferStorageMultisample, 0) \ GLEXT(PFNGLBLITFRAMEBUFFERPROC , glBlitFramebuffer , 0) \ GLEXT(PFNGLGENERATEMIPMAPEXTPROC , glGenerateMipmap , 0) \ GLEXT(PFNGLDISPATCHCOMPUTEPROC , glDispatchCompute , 0) \ GLEXT(PFNGLBINDIMAGETEXTUREPROC , glBindImageTexture , 0) \ GLEXT(PFNGLGETPROGRAMRESOURCEINDEXPROC , glGetProgramResourceIndex , 0) \ GLEXT(PFNGLSHADERSTORAGEBLOCKBINDINGPROC , glShaderStorageBlockBinding , 0) \ GLEXT(PFNGLGETUNIFORMBLOCKINDEXPROC , glGetUniformBlockIndex , 0) \ GLEXT(PFNGLUNIFORMBLOCKBINDINGPROC , glUniformBlockBinding , 0) \ GLEXT(PFNGLGETPROGRAMBINARYPROC , glGetProgramBinary , 0) \ GLEXT(PFNGLBINDBUFFERBASEPROC , glBindBufferBase , 0) \ GLEXT(PFNGLMEMORYBARRIERPROC , glMemoryBarrier , 0) \ GLEXT(PFNGLMAPBUFFERRANGEPROC , glMapBufferRange , 0) #define GLEXT(type, name, needed) extern type name; GLBASEEXTS GLEXTS #undef GLEXT #endif #if !defined(NDEBUG) #define LOG_GL_ERRORS #endif #ifdef LOG_GL_ERRORS #define GL_CHECK(what) LogGLError(__FILE__, __LINE__, what) #define GL_CALL(call) do { call; GL_CHECK(#call); } while (0) #else #define GL_CHECK(what) (void)what #define GL_CALL(call) do { call; } while (0) #endif treesheets-1.0.2/lobster/src/lobster/glinterface.h000066400000000000000000000213301352107072600222360ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // simple rendering interface for OpenGL (ES) (that doesn't depend on its headers) enum BlendMode { BLEND_NONE = 0, BLEND_ALPHA, BLEND_ADD, BLEND_ADDALPHA, BLEND_MUL, BLEND_PREMULALPHA }; enum Primitive { PRIM_TRIS, PRIM_FAN, PRIM_LOOP, PRIM_POINT }; // Meant to be passed by value. struct Texture { uint id = 0; int3 size { 0 }; Texture() = default; Texture(int _id, const int2 &_size) : id(_id), size(int3(_size, 0)) {} Texture(int _id, const int3 &_size) : id(_id), size(_size) {} }; struct Shader { uint vs = 0, ps = 0, cs = 0, program = 0; int mvp_i, col_i, camera_i, light1_i, lightparams1_i, texturesize_i, bones_i, pointscale_i; int max_tex_defined = 0; enum { MAX_SAMPLERS = 32 }; ~Shader(); string Compile(const char *name, const char *vscode, const char *pscode); string Compile(const char *name, const char *comcode); void Link(const char *name); void Activate(); // Makes shader current; void Set(); // Activate + sets common uniforms. void SetAnim(float3x4 *bones, int num); // Optionally, after Activate(). void SetTextures(const vector &textures); // Optionally, after Activate(). bool SetUniform(string_view name, // Optionally, after Activate(). const float *val, int components, int elements = 1); bool SetUniformMatrix(string_view name, const float *val, int components, int elements = 1); bool Dump(string_view filename, bool stripnonascii); }; struct Textured { vector textures; Texture &Get(size_t i) { textures.resize(max(i + 1, textures.size())); return textures[i]; } }; struct Surface : Textured { size_t numidx; uint ibo; string name; Primitive prim; Surface(span indices, Primitive _prim = PRIM_TRIS); ~Surface(); void Render(Shader *sh); void WritePLY(string &s); }; struct BasicVert { // common generic format: "PNTC" float3 pos; float3 norm; float2 tc; byte4 col; }; struct AnimVert : BasicVert { // "PNTCWI" byte4 weights; byte4 indices; }; struct SpriteVert { // "pT" float2 pos; float2 tc; }; class Geometry { const size_t vertsize1, vertsize2; string fmt; uint vbo1 = 0, vbo2 = 0, vao = 0; public: const size_t nverts; template Geometry(span verts1, string_view _fmt, span verts2 = span(), size_t elem_multiple = 1) : vertsize1(sizeof(T) * elem_multiple), vertsize2(sizeof(U) * elem_multiple), fmt(_fmt), nverts(verts1.size() / elem_multiple) { assert(verts2.empty() || verts2.size() == verts1.size()); Init(verts1.data(), verts2.data()); } ~Geometry(); void Init(const void *verts1, const void *verts2); void RenderSetup(); void BindAsSSBO(Shader *sh, string_view name); bool WritePLY(string &s, size_t nindices); }; struct Mesh { Geometry *geom; vector surfs; Primitive prim; // If surfs is empty, this determines how to draw the verts. float pointsize = 1; // if prim == PRIM_POINT int numframes = 0, numbones = 0; float3x4 *mats = nullptr; float curanim = 0; Mesh(Geometry *_g, Primitive _prim = PRIM_FAN) : geom(_g), prim(_prim) {} ~Mesh(); void Render(Shader *sh); bool SaveAsPLY(string_view filename); }; struct Light { float4 pos; float2 params; }; extern void OpenGLInit(int samples); extern void OpenGLCleanup(); extern void OpenGLFrameStart(const int2 &ssize); extern void LogGLError(const char *file, int line, const char *call); extern void Set2DMode(const int2 &ssize, bool lh, bool depthtest = false); extern void Set3DMode(float fovy, float ratio, float znear, float zfar); extern void Set3DOrtho(const float3 ¢er, const float3 &extends); extern bool Is2DMode(); extern void ClearFrameBuffer(const float3 &c); extern int SetBlendMode(BlendMode mode); extern void SetPointSprite(float size); extern void AppendTransform(const float4x4 &forward, const float4x4 &backward); extern string LoadMaterialFile(string_view mfile); extern string ParseMaterialFile(string_view mfile); extern Shader *LookupShader(string_view name); extern void ShaderShutDown(); extern void DispatchCompute(const int3 &groups); extern void SetImageTexture(uint textureunit, const Texture &tex, int tf); extern uint UniformBufferObject(Shader *sh, const void *data, size_t len, string_view uniformblockname, bool ssbo, uint bo); // These must correspond to the constants in color.lobster enum TextureFlag { TF_NONE = 0, TF_CLAMP = 1, TF_NOMIPMAP = 2, TF_NEAREST_MAG = 4, TF_NEAREST_MIN = 8, TF_FLOAT = 16, // rgba32f instead of rgba8 TF_WRITEONLY = 32, TF_READWRITE = 64, // Default is readonly. TF_CUBEMAP = 128, TF_MULTISAMPLE = 256, TF_SINGLE_CHANNEL = 512, // Default is RGBA. TF_3D = 1024, TF_BUFFER_HAS_MIPS = 2048, TF_DEPTH = 4096 }; extern Texture CreateTexture(const uchar *buf, const int *dim, int tf = TF_NONE); extern Texture CreateTextureFromFile(string_view name, int tf = TF_NONE); extern Texture CreateBlankTexture(const int2 &size, const float4 &color, int tf = TF_NONE); extern void DeleteTexture(Texture &id); extern void SetTexture(int textureunit, const Texture &tex, int tf = TF_NONE); extern uchar *ReadTexture(const Texture &tex); extern int MaxTextureSize(); extern bool SwitchToFrameBuffer(const Texture &tex, bool depth = false, int tf = 0, const Texture &resolvetex = Texture(), const Texture &depthtex = Texture()); extern uchar *ReadPixels(const int2 &pos, const int2 &size); extern uint GenBO_(uint type, size_t bytesize, const void *data); template uint GenBO(uint type, span d) { return GenBO_(type, sizeof(T) * d.size(), d.data()); } extern void DeleteBO(uint id); extern void RenderArray(Primitive prim, Geometry *geom, uint ibo = 0, size_t tcount = 0); template void RenderArraySlow(Primitive prim, span vbuf1, string_view fmt, span ibuf = span(), span vbuf2 = span()) { Geometry geom(vbuf1, fmt, vbuf2); if (ibuf.empty()) { RenderArray(prim, &geom); } else { Surface surf(ibuf, prim); RenderArray(prim, &geom, surf.ibo, ibuf.size()); } } struct GeometryCache { Geometry *quadgeom[2] = { nullptr, nullptr }; Geometry *cube_geom[2] = { nullptr, nullptr }; uint cube_ibo[2] = { 0, 0 }; map circlevbos; map, pair> opencirclevbos; ~GeometryCache(); void RenderUnitSquare(Shader *sh, Primitive prim, bool centered); void RenderQuad(Shader *sh, Primitive prim, bool centered, const float4x4 &trans); void RenderLine2D(Shader *sh, Primitive prim, const float3 &v1, const float3 &v2, float thickness); void RenderLine3D(Shader *sh, const float3 &v1, const float3 &v2, const float3 &campos, float thickness); void RenderUnitCube(Shader *sh, int inside); void RenderCircle(Shader *sh, Primitive prim, int segments, float radius); void RenderOpenCircle(Shader *sh, int segments, float radius, float thickness); }; extern size_t AttribsSize(string_view fmt); extern Mesh *LoadIQM(string_view filename); extern float4x4 view2clip; struct objecttransforms { float4x4 view2object; float4x4 object2view; objecttransforms() : view2object(1), object2view(1) {} }; extern objecttransforms otransforms; extern vector lights; extern float4 curcolor; extern float pointscale, custompointscale; extern GeometryCache *geomcache; // 2D, since this skips view2object needed for lighting. template void Transform2D(const float4x4 &mat, F body) { auto oldobject2view = otransforms.object2view; otransforms.object2view *= mat; body(); otransforms.object2view = oldobject2view; } extern bool VRInit(); extern void VRShutDown(); treesheets-1.0.2/lobster/src/lobster/idents.h000066400000000000000000000756621352107072600212620ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_IDENTS #define LOBSTER_IDENTS #include "lobster/natreg.h" #define FLATBUFFERS_DEBUG_VERIFICATION_FAILURE #include "lobster/bytecode_generated.h" namespace lobster { struct NativeFun; struct SymbolTable; struct Node; struct List; struct Function; struct SubFunction; struct SpecIdent; struct Ident : Named { size_t scopelevel; bool single_assignment = true; // not declared const but def only, exp may or may not be const bool constant = false; // declared const bool static_constant = false; // not declared const but def only, exp is const. bool anonymous_arg = false; bool logvar = false; SpecIdent *cursid = nullptr; Ident(string_view _name, int _idx, size_t _sl) : Named(_name, _idx), scopelevel(_sl) {} void Assign(Lex &lex) { single_assignment = false; if (constant) lex.Error("variable " + name + " is constant"); } flatbuffers::Offset Serialize(flatbuffers::FlatBufferBuilder &fbb, bool is_top_level) { return bytecode::CreateIdent(fbb, fbb.CreateString(name), constant, is_top_level); } }; struct SpecIdent { Ident *id; TypeRef type; Lifetime lt = LT_UNDEF; bool consume_on_last_use = false; int logvaridx = -1; int idx, sidx = -1; // Into specidents, and into vm ordering. SubFunction *sf_def = nullptr; // Where it is defined, including anonymous functions. SpecIdent(Ident *_id, TypeRef _type, int idx) : id(_id), type(_type), idx(idx){} int Idx() { assert(sidx >= 0); return sidx; } SpecIdent *&Current() { return id->cursid; } }; struct Enum; struct EnumVal : Named { int64_t val = 0; Enum *e = nullptr; EnumVal(string_view _name, int _idx) : Named(_name, _idx) {} }; struct Enum : Named { vector> vals; Type thistype; Enum(string_view _name, int _idx) : Named(_name, _idx) { thistype = Type { this }; } flatbuffers::Offset Serialize(flatbuffers::FlatBufferBuilder &fbb) { vector> valoffsets; for (auto &v : vals) valoffsets.push_back( bytecode::CreateEnumVal(fbb, fbb.CreateString(v->name), v->val)); return bytecode::CreateEnum(fbb, fbb.CreateString(name), fbb.CreateVector(valoffsets)); } }; // Only still needed because we have no idea which struct it refers to at parsing time. struct SharedField : Named { SharedField(string_view _name, int _idx) : Named(_name, _idx) {} SharedField() : SharedField("", 0) {} }; struct Field { TypeRef type; SharedField *id = nullptr; int genericref = -1; Node *defaultval = nullptr; int slot = -1; Field() = default; Field(SharedField *_id, TypeRef _type, int _genericref, Node *_defaultval) : type(_type), id(_id), genericref(_genericref), defaultval(_defaultval) {} Field(const Field &o); ~Field(); }; struct FieldVector : GenericArgs { vector v; FieldVector(size_t nargs) : v(nargs) {} size_t size() const { return v.size(); } TypeRef GetType(size_t i) const { return v[i].type; } ArgFlags GetFlags(size_t) const { return AF_NONE; } string_view GetName(size_t i) const { return v[i].id->name; } }; struct GenericParameter { string_view name; }; struct DispatchEntry { SubFunction *sf = nullptr; bool is_dispatch_root = false; // Shared return type if root of dispatch. TypeRef returntype = nullptr; }; struct UDT : Named { FieldVector fields { 0 }; vector generics; UDT *next = nullptr, *first = this; // Specializations UDT *superclass = nullptr; bool is_struct = false, hasref = false; bool predeclaration = false; bool constructed = false; // Is this instantiated anywhere in the code? Type thistype; // convenient place to store the type corresponding to this. TypeRef sametype = type_undefined; // If all fields are int/float, this allows vector ops. type_elem_t typeinfo = (type_elem_t)-1; // Runtime type. int numslots = -1; int vtable_start = -1; vector subudts; // Including self. // Subset of methods that participate in dynamic dispatch. Order in this table determines // vtable layout and is compatible with sub/super classes. // Multiple specializations of a method may be in here. // Methods whose dispatch can be determined statically for the current program do not end up // in here. vector dispatch; UDT(string_view _name, int _idx, bool is_struct) : Named(_name, _idx), is_struct(is_struct) { thistype = is_struct ? Type { V_STRUCT_R, this } : Type { V_CLASS, this }; } int Has(SharedField *fld) { for (auto &uf : fields.v) if (uf.id == fld) return int(&uf - &fields.v[0]); return -1; } UDT *CloneInto(UDT *st) { *st = *this; st->thistype.udt = st; st->next = next; st->first = first; next = st; return st; } bool IsSpecialization(UDT *other) { if (!generics.empty()) { for (auto udt = first->next; udt; udt = udt->next) if (udt == other) return true; return false; } else { return this == other; } } int NumSuperTypes() { int n = 0; for (auto t = superclass; t; t = t->superclass) n++; return n; } bool ComputeSizes(int depth = 0) { if (numslots >= 0) return true; if (depth > 16) return false; // Simple protection against recursive references. int size = 0; for (auto &uf : fields.v) { uf.slot = size; if (IsStruct(uf.type->t)) { if (!uf.type->udt->ComputeSizes(depth + 1)) return false; size += uf.type->udt->numslots; } else { size++; } } numslots = size; return true; } flatbuffers::Offset Serialize(flatbuffers::FlatBufferBuilder &fbb) { vector> fieldoffsets; for (auto f : fields.v) fieldoffsets.push_back( bytecode::CreateField(fbb, fbb.CreateString(f.id->name), f.slot)); return bytecode::CreateUDT(fbb, fbb.CreateString(name), idx, fbb.CreateVector(fieldoffsets), numslots); } }; inline int ValWidth(TypeRef type) { return IsStruct(type->t) ? type->udt->numslots : 1; } inline const Field *FindSlot(const UDT &udt, int i) { for (auto &f : udt.fields.v) if (i >= f.slot && i < f.slot + ValWidth(f.type)) { return IsStruct(f.type->t) ? FindSlot(*f.type->udt, i - f.slot) : &f; } assert(false); return nullptr; } struct Arg : Typed { SpecIdent *sid = nullptr; Arg() = default; Arg(const Arg &o) : Typed(o), sid(o.sid) {} Arg(SpecIdent *_sid, TypeRef _type, ArgFlags _flags) : Typed(_type, _flags), sid(_sid) {} }; struct ArgVector : GenericArgs { vector v; ArgVector(size_t nargs) : v(nargs) {} size_t size() const { return v.size(); } TypeRef GetType(size_t i) const { return v[i].type; } ArgFlags GetFlags(size_t i) const { return v[i].flags; } string_view GetName(size_t i) const { return v[i].sid->id->name; } bool Add(const Arg &in) { for (auto &arg : v) if (arg.sid->id == in.sid->id) return false; v.push_back(in); return true; } }; struct Function; struct SubFunction { int idx; ArgVector args { 0 }; ArgVector locals { 0 }; ArgVector freevars { 0 }; // any used from outside this scope TypeRef fixedreturntype = nullptr; TypeRef returntype = type_undefined; size_t num_returns = 0; size_t reqret = 0; // Do the caller(s) want values to be returned? const Lifetime ltret = LT_KEEP; vector> reuse_return_events; bool isrecursivelycalled = false; bool iscoroutine = false; ArgVector coyieldsave { 0 }; TypeRef coresumetype; type_elem_t cotypeinfo = (type_elem_t)-1; List *body = nullptr; SubFunction *next = nullptr; Function *parent = nullptr; int subbytecodestart = 0; bool typechecked = false; bool freevarchecked = false; bool mustspecialize = false; bool logvarcallgraph = false; bool isdynamicfunctionvalue = false; UDT *method_of = nullptr; int numcallers = 0; Type thistype { V_FUNCTION, this }; // convenient place to store the type corresponding to this SubFunction(int _idx) : idx(_idx) {} void SetParent(Function &f, SubFunction *&link) { parent = &f; next = link; link = this; } ~SubFunction(); }; struct Function : Named { // Start of all SubFunctions sequentially. int bytecodestart = 0; // functions with the same name and args, but different types (dynamic dispatch | // specialization) vector overloads; // functions with the same name but different number of args (overloaded) Function *sibf = nullptr; // does not have a programmer specified name bool anonymous = false; // its merely a function type, has no body, but does have a set return type. bool istype = false; // Store the original types the function was declared with, before specialization. ArgVector orig_args { 0 }; size_t scopelevel; Function(string_view _name, int _idx, size_t _sl) : Named(_name, _idx), scopelevel(_sl) {} ~Function() {} size_t nargs() const { return overloads[0]->args.v.size(); } int NumSubf() { int sum = 0; for (auto sf : overloads) for (; sf; sf = sf->next) sum++; return sum; } bool RemoveSubFunction(SubFunction *sf) { for (auto &sfh : overloads) { for (auto sfp = &sfh; *sfp; sfp = &(*sfp)->next) { if (*sfp == sf) { *sfp = sf->next; sf->next = nullptr; return true; } } } return false; } flatbuffers::Offset Serialize(flatbuffers::FlatBufferBuilder &fbb) { return bytecode::CreateFunction(fbb, fbb.CreateString(name), bytecodestart); } }; struct SymbolTable { unordered_map idents; // Key points to value! vector identtable; vector identstack; vector specidents; unordered_map udts; // Key points to value! vector udttable; unordered_map fields; // Key points to value! vector fieldtable; unordered_map functions; // Key points to value! vector functiontable; vector subfunctiontable; SubFunction *toplevel = nullptr; unordered_map enums; // Key points to value! unordered_map enumvals; // Key points to value! vector enumtable; vector filenames; vector scopelevels; struct WithStackElem { TypeRef type; Ident *id = nullptr; SubFunction *sf = nullptr; }; vector withstack; vector withstacklevels; enum { NUM_VECTOR_TYPE_WRAPPINGS = 3 }; vector default_int_vector_types[NUM_VECTOR_TYPE_WRAPPINGS], default_float_vector_types[NUM_VECTOR_TYPE_WRAPPINGS]; Enum *default_bool_type = nullptr; // Used during parsing. vector defsubfunctionstack; vector typelist; // Used for constructing new vector types, variables, etc. vector *> tuplelist; string current_namespace; // FIXME: because we cleverly use string_view's into source code everywhere, we now have // no way to refer to constructed strings, and need to store them seperately :( // TODO: instead use larger buffers and constuct directly into those, so no temp string? vector stored_names; ~SymbolTable() { for (auto id : identtable) delete id; for (auto sid : specidents) delete sid; for (auto u : udttable) delete u; for (auto f : functiontable) delete f; for (auto e : enumtable) delete e; for (auto sf : subfunctiontable) delete sf; for (auto f : fieldtable) delete f; for (auto t : typelist) delete t; for (auto t : tuplelist) delete t; for (auto n : stored_names) delete[] n; } string NameSpaced(string_view name) { assert(!current_namespace.empty()); return cat(current_namespace, "_", name); } string_view StoreName(const string &s) { auto buf = new char[s.size()]; memcpy(buf, s.data(), s.size()); // Look ma, no terminator :) stored_names.push_back(buf); return string_view(buf, s.size()); } string_view MaybeNameSpace(string_view name, bool other_conditions) { return other_conditions && !current_namespace.empty() && scopelevels.size() == 1 ? StoreName(NameSpaced(name)) : name; } Ident *Lookup(string_view name) { auto it = idents.find(name); if (it != idents.end()) return it->second; if (!current_namespace.empty()) { auto it = idents.find(NameSpaced(name)); if (it != idents.end()) return it->second; } return nullptr; } Ident *LookupAny(string_view name) { for (auto id : identtable) if (id->name == name) return id; return nullptr; } Ident *NewId(string_view name, SubFunction *sf) { auto ident = new Ident(name, (int)identtable.size(), scopelevels.size()); ident->cursid = NewSid(ident, sf); identtable.push_back(ident); return ident; } Ident *LookupDef(string_view name, Lex &lex, bool anonymous_arg, bool islocal, bool withtype) { auto sf = defsubfunctionstack.back(); auto existing_ident = Lookup(name); if (anonymous_arg && existing_ident && existing_ident->cursid->sf_def == sf) return existing_ident; Ident *ident = nullptr; if (LookupWithStruct(name, lex, ident)) lex.Error("cannot define variable with same name as field in this scope: " + name); ident = NewId(name, sf); ident->anonymous_arg = anonymous_arg; (islocal ? sf->locals : sf->args).v.push_back( Arg(ident->cursid, type_any, AF_GENERIC | (withtype ? AF_WITHTYPE : AF_NONE))); if (existing_ident) { lex.Error("identifier redefinition / shadowing: " + ident->name); } idents[ident->name /* must be in value */] = ident; identstack.push_back(ident); return ident; } Ident *LookupUse(string_view name, Lex &lex) { auto id = Lookup(name); if (!id) lex.Error("unknown identifier: " + name); return id; } void AddWithStruct(TypeRef t, Ident *id, Lex &lex, SubFunction *sf) { if (!IsUDT(t->t)) lex.Error(":: can only be used with struct/value types"); for (auto &wp : withstack) if (wp.type->udt == t->udt) lex.Error("type used twice in the same scope with ::"); // FIXME: should also check if variables have already been defined in this scope that clash // with the struct, or do so in LookupUse assert(t->udt); withstack.push_back({ t, id, sf }); } SharedField *LookupWithStruct(string_view name, Lex &lex, Ident *&id) { auto fld = FieldUse(name); if (!fld) return nullptr; assert(!id); for (auto &wse : withstack) { if (wse.type->udt->Has(fld) >= 0) { if (id) lex.Error("access to ambiguous field: " + fld->name); id = wse.id; } } return id ? fld : nullptr; } WithStackElem GetWithStackBack() { return withstack.size() ? withstack.back() : WithStackElem(); } void MakeLogVar(Ident *id) { id->logvar = true; defsubfunctionstack.back()->logvarcallgraph = true; } SubFunction *ScopeStart() { scopelevels.push_back(identstack.size()); withstacklevels.push_back(withstack.size()); auto sf = CreateSubFunction(); defsubfunctionstack.push_back(sf); return sf; } void ScopeCleanup() { defsubfunctionstack.pop_back(); while (identstack.size() > scopelevels.back()) { auto ident = identstack.back(); auto it = idents.find(ident->name); if (it != idents.end()) { // can already have been removed by private var cleanup idents.erase(it); } identstack.pop_back(); } scopelevels.pop_back(); while (withstack.size() > withstacklevels.back()) withstack.pop_back(); withstacklevels.pop_back(); } void UnregisterStruct(const UDT *st, Lex &lex) { if (st->predeclaration) lex.Error("pre-declared struct never defined: " + st->name); auto it = udts.find(st->name); if (it != udts.end()) udts.erase(it); } void UnregisterFun(Function *f) { auto it = functions.find(f->name); if (it != functions.end()) // it can already have been removed by another variation functions.erase(it); } void UnregisterEnum(Enum *e) { for (auto &ev : e->vals) { auto it = enumvals.find(ev->name); assert(it != enumvals.end()); enumvals.erase(it); } auto it = enums.find(e->name); assert(it != enums.end()); enums.erase(it); } void EndOfInclude() { current_namespace.clear(); auto it = idents.begin(); while (it != idents.end()) { if (it->second->isprivate) { idents.erase(it++); } else it++; } } Enum *EnumLookup(string_view name, Lex &lex, bool decl) { auto eit = enums.find(name); if (eit != enums.end()) { if (decl) lex.Error("double declaration of enum: " + name); return eit->second; } if (!decl) { if (!current_namespace.empty()) { eit = enums.find(NameSpaced(name)); if (eit != enums.end()) return eit->second; } return nullptr; } auto e = new Enum(name, (int)enumtable.size()); enumtable.push_back(e); enums[e->name /* must be in value */] = e; return e; } EnumVal *EnumValLookup(string_view name, Lex &lex, bool decl) { auto evit = enumvals.find(name); if (evit != enumvals.end()) { if (decl) lex.Error("double declaration of enum value: " + name); return evit->second; } if (!decl) { if (!current_namespace.empty()) { evit = enumvals.find(NameSpaced(name)); if (evit != enumvals.end()) return evit->second; } return nullptr; } auto ev = new EnumVal(name, 0); enumvals[ev->name /* must be in value */] = ev; return ev; } UDT &StructDecl(string_view name, Lex &lex, bool is_struct) { auto uit = udts.find(name); if (uit != udts.end()) { if (!uit->second->predeclaration) lex.Error("double declaration of type: " + name); if (uit->second->is_struct != is_struct) lex.Error("class/struct previously declared as different kind"); uit->second->predeclaration = false; return *uit->second; } auto st = new UDT(name, (int)udttable.size(), is_struct); udts[st->name /* must be in value */] = st; udttable.push_back(st); return *st; } UDT &StructUse(string_view name, Lex &lex) { auto uit = udts.find(name); if (uit != udts.end()) return *uit->second; if (!current_namespace.empty()) { uit = udts.find(NameSpaced(name)); if (uit != udts.end()) return *uit->second; } lex.Error("unknown type: " + name); return *uit->second; } int SuperDistance(const UDT *sup, const UDT *sub) { int dist = 0; for (auto t = sub; t; t = t->superclass) { if (t == sup) return dist; dist++; } return -1; } const UDT *CommonSuperType(const UDT *a, const UDT *b) { if (a != b) for (;;) { a = a->superclass; if (!a) return nullptr; if (SuperDistance(a, b) >= 0) break; } return a; } SharedField &FieldDecl(string_view name) { auto fld = FieldUse(name); if (fld) return *fld; fld = new SharedField(name, (int)fieldtable.size()); fields[fld->name /* must be in value */] = fld; fieldtable.push_back(fld); return *fld; } SharedField *FieldUse(string_view name) { auto it = fields.find(name); return it != fields.end() ? it->second : nullptr; } SubFunction *CreateSubFunction() { auto sf = new SubFunction((int)subfunctiontable.size()); subfunctiontable.push_back(sf); return sf; } Function &CreateFunction(string_view name, string_view context) { auto fname = name.length() ? string(name) : cat("function", functiontable.size(), context); auto f = new Function(fname, (int)functiontable.size(), scopelevels.size()); functiontable.push_back(f); return *f; } Function &FunctionDecl(string_view name, size_t nargs, Lex &lex) { auto fit = functions.find(name); if (fit != functions.end()) { if (fit->second->scopelevel != scopelevels.size()) lex.Error("cannot define a variation of function " + name + " at a different scope level"); for (auto f = fit->second; f; f = f->sibf) if (f->nargs() == nargs) return *f; } auto &f = CreateFunction(name, ""); if (fit != functions.end()) { f.sibf = fit->second->sibf; fit->second->sibf = &f; } else { functions[f.name /* must be in value */] = &f; } return f; } Function *FindFunction(string_view name) { auto it = functions.find(name); if (it != functions.end()) return it->second; if (!current_namespace.empty()) { auto it = functions.find(NameSpaced(name)); if (it != functions.end()) return it->second; } return nullptr; } SpecIdent *NewSid(Ident *id, SubFunction *sf, TypeRef type = nullptr) { auto sid = new SpecIdent(id, type, (int)specidents.size()); sid->sf_def = sf; specidents.push_back(sid); return sid; } void CloneSids(ArgVector &av, SubFunction *sf) { for (auto &a : av.v) { a.sid = NewSid(a.sid->id, sf); } } void CloneIds(SubFunction &sf, const SubFunction &o) { sf.args = o.args; CloneSids(sf.args, &sf); sf.locals = o.locals; CloneSids(sf.locals, &sf); // Don't clone freevars, these will be accumulated in the new copy anew. } Type *NewType() { // These get allocated for very few nodes, given that most types are shared or stored in // their own struct. auto t = new Type(); typelist.push_back(t); return t; } TypeRef Wrap(TypeRef elem, ValueType with) { auto wt = WrapKnown(elem, with); return !wt.Null() ? wt : elem->Wrap(NewType(), with); } bool RegisterTypeVector(vector *sv, const char **names) { if (sv[0].size()) return true; // Already initialized. for (size_t i = 0; i < NUM_VECTOR_TYPE_WRAPPINGS; i++) { sv[i].push_back(nullptr); sv[i].push_back(nullptr); } for (auto name = names; *name; name++) { // Can't use stucts.find, since all are out of scope. for (auto udt : udttable) if (udt->name == *name) { for (size_t i = 0; i < NUM_VECTOR_TYPE_WRAPPINGS; i++) { auto vt = TypeRef(&udt->thistype); for (size_t j = 0; j < i; j++) vt = Wrap(vt, V_VECTOR); sv[i].push_back(vt); } goto found; } return false; found:; } return true; } bool RegisterDefaultTypes() { // TODO: This isn't great hardcoded in the compiler, would be better if it was declared in // lobster code. for (auto e : enumtable) { if (e->name == "bool") { default_bool_type = e; break; } } static const char *default_int_vector_type_names[] = { "xy_i", "xyz_i", "xyzw_i", nullptr }; static const char *default_float_vector_type_names[] = { "xy_f", "xyz_f", "xyzw_f", nullptr }; return RegisterTypeVector(default_int_vector_types, default_int_vector_type_names) && RegisterTypeVector(default_float_vector_types, default_float_vector_type_names) && default_bool_type; } TypeRef VectorType(TypeRef vt, size_t level, int arity) const { return vt->sub->t == V_INT ? default_int_vector_types[level][arity] : default_float_vector_types[level][arity]; } bool IsGeneric(TypeRef type) { if (type->t == V_ANY) return true; auto u = type->UnWrapped(); return IsUDT(u->t) && !u->udt->generics.empty(); } // This one is used to sort types for multi-dispatch. bool IsLessGeneralThan(const Type &a, const Type &b) const { if (a.t != b.t) return a.t > b.t; switch (a.t) { case V_VECTOR: case V_NIL: return IsLessGeneralThan(*a.sub, *b.sub); case V_FUNCTION: return a.sf->idx < b.sf->idx; case V_STRUCT_R: case V_STRUCT_S: case V_CLASS: { if (a.udt == b.udt) return false; auto ans = a.udt->NumSuperTypes(); auto bns = b.udt->NumSuperTypes(); return ans != bns ? ans > bns : a.udt->idx < b.udt->idx; } default: return false; } } void Serialize(vector &code, vector &code_attr, vector &typetable, vector &vint_typeoffsets, vector &vfloat_typeoffsets, vector &linenumbers, vector &sids, vector &stringtable, vector &speclogvars, string &bytecode, vector &vtables) { flatbuffers::FlatBufferBuilder fbb; vector> fns; for (auto &f : filenames) fns.push_back(fbb.CreateString(f)); vector> functionoffsets; for (auto f : functiontable) functionoffsets.push_back(f->Serialize(fbb)); vector> udtoffsets; for (auto u : udttable) udtoffsets.push_back(u->Serialize(fbb)); vector> identoffsets; for (auto i : identtable) identoffsets.push_back(i->Serialize(fbb, i->cursid->sf_def == toplevel)); vector> enumoffsets; for (auto e : enumtable) enumoffsets.push_back(e->Serialize(fbb)); auto bcf = bytecode::CreateBytecodeFile(fbb, LOBSTER_BYTECODE_FORMAT_VERSION, fbb.CreateVector(code), fbb.CreateVector(code_attr), fbb.CreateVector((vector &)typetable), fbb.CreateVector>(stringtable.size(), [&](size_t i) { return fbb.CreateString(stringtable[i].data(), stringtable[i].size()); } ), fbb.CreateVectorOfStructs(linenumbers), fbb.CreateVector(fns), fbb.CreateVector(functionoffsets), fbb.CreateVector(udtoffsets), fbb.CreateVector(identoffsets), fbb.CreateVectorOfStructs(sids), fbb.CreateVector((vector &)vint_typeoffsets), fbb.CreateVector((vector &)vfloat_typeoffsets), fbb.CreateVector(speclogvars), fbb.CreateVector(enumoffsets), fbb.CreateVector(vtables)); bytecode::FinishBytecodeFileBuffer(fbb, bcf); bytecode.assign(fbb.GetBufferPointer(), fbb.GetBufferPointer() + fbb.GetSize()); } }; inline string TypeName(TypeRef type, int flen = 0, const SymbolTable *st = nullptr) { switch (type->t) { case V_STRUCT_R: case V_STRUCT_S: case V_CLASS: return type->udt->name; case V_VECTOR: return flen && type->Element()->Numeric() ? (flen < 0 ? (type->Element()->t == V_INT ? "vec_i" : "vec_f") // FIXME: better names? : TypeName(st->VectorType(type, 0, flen))) : (type->Element()->t == V_VAR ? "[]" : "[" + TypeName(type->Element(), flen, st) + "]"); case V_FUNCTION: return type->sf // || type->sf->anonymous ? type->sf->parent->name : "function"; case V_NIL: return type->Element()->t == V_VAR ? "nil" : TypeName(type->Element(), flen, st) + "?"; case V_COROUTINE: return type->sf ? "coroutine(" + type->sf->parent->name + ")" : "coroutine"; case V_TUPLE: { string s = "("; for (auto [i, te] : enumerate(*type->tup)) { if (i) s += ", "; s += TypeName(te.type); } s += ")"; return s; } case V_INT: return type->e ? type->e->name : "int"; default: return string(BaseTypeName(type->t)); } } } // namespace lobster #endif // LOBSTER_IDENTS treesheets-1.0.2/lobster/src/lobster/il.h000066400000000000000000000146641352107072600203730ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_IL #define LOBSTER_IL // FlatBuffers takes care of backwards compatibility of all metadata, but not the actual bytecode. // This needs to be bumped each time we make changes to the format. namespace lobster { const int LOBSTER_BYTECODE_FORMAT_VERSION = 15; const int MAX_RETURN_VALUES = 64; // Any type specialized ops below must always have this ordering. enum MathOp { MOP_ADD, MOP_SUB, MOP_MUL, MOP_DIV, MOP_MOD, MOP_LT, MOP_GT, MOP_LE, MOP_GE, MOP_EQ, MOP_NE }; #define ILUNKNOWNARITY 9 #define ILBASENAMES \ F(PUSHINT, 1) \ F(PUSHINT64, 2) \ F(PUSHFLT, 1) \ F(PUSHFLT64, 2) \ F(PUSHSTR, 1) \ F(PUSHNIL, 0) \ F(PUSHVAR, 1) \ F(PUSHVARV, 2) \ F(VPUSHIDXI, 0) F(VPUSHIDXV, 1) F(VPUSHIDXIS, 2) F(VPUSHIDXVS, 3) F(NPUSHIDXI, 1) F(SPUSHIDXI, 0) \ F(PUSHFLD, 1) F(PUSHFLDMREF, 1) F(PUSHFLDV, 2) F(PUSHFLD2V, 2) F(PUSHFLDV2V, 3) \ F(PUSHLOC, 1) F(PUSHLOCV, 2) \ F(BCALLRETV, 1) F(BCALLREFV, 1) F(BCALLUNBV, 1) \ F(BCALLRET0, 1) F(BCALLREF0, 1) F(BCALLUNB0, 1) \ F(BCALLRET1, 1) F(BCALLREF1, 1) F(BCALLUNB1, 1) \ F(BCALLRET2, 1) F(BCALLREF2, 1) F(BCALLUNB2, 1) \ F(BCALLRET3, 1) F(BCALLREF3, 1) F(BCALLUNB3, 1) \ F(BCALLRET4, 1) F(BCALLREF4, 1) F(BCALLUNB4, 1) \ F(BCALLRET5, 1) F(BCALLREF5, 1) F(BCALLUNB5, 1) \ F(BCALLRET6, 1) F(BCALLREF6, 1) F(BCALLUNB6, 1) \ F(BCALLRET7, 1) F(BCALLREF7, 1) F(BCALLUNB7, 1) \ F(ASSERT, 3) F(ASSERTR, 3) \ F(CONT1, 1) \ F(FUNSTART, ILUNKNOWNARITY) \ F(ENDSTATEMENT, 2) \ F(NEWVEC, 2) F(NEWOBJECT, 1) \ F(POP, 0) F(POPREF, 0) F(POPV, 1) F(POPVREF, 1) \ F(DUP, 0) \ F(EXIT, 1) F(ABORT, 0) \ F(IADD, 0) F(ISUB, 0) F(IMUL, 0) F(IDIV, 0) F(IMOD, 0) \ F(ILT, 0) F(IGT, 0) F(ILE, 0) F(IGE, 0) F(IEQ, 0) F(INE, 0) \ F(FADD, 0) F(FSUB, 0) F(FMUL, 0) F(FDIV, 0) F(FMOD, 0) \ F(FLT, 0) F(FGT, 0) F(FLE, 0) F(FGE, 0) F(FEQ, 0) F(FNE, 0) \ F(SADD, 0) F(SSUB, 0) F(SMUL, 0) F(SDIV, 0) F(SMOD, 0) \ F(SLT, 0) F(SGT, 0) F(SLE, 0) F(SGE, 0) F(SEQ, 0) F(SNE, 0) \ F(IVVADD, 1) F(IVVSUB, 1) F(IVVMUL, 1) F(IVVDIV, 1) F(IVVMOD, 1) \ F(IVVLT, 1) F(IVVGT, 1) F(IVVLE, 1) F(IVVGE, 1) \ F(FVVADD, 1) F(FVVSUB, 1) F(FVVMUL, 1) F(FVVDIV, 1) F(FVVMOD, 1) \ F(FVVLT, 1) F(FVVGT, 1) F(FVVLE, 1) F(FVVGE, 1) \ F(IVSADD, 1) F(IVSSUB, 1) F(IVSMUL, 1) F(IVSDIV, 1) F(IVSMOD, 1) \ F(IVSLT, 1) F(IVSGT, 1) F(IVSLE, 1) F(IVSGE, 1) \ F(FVSADD, 1) F(FVSSUB, 1) F(FVSMUL, 1) F(FVSDIV, 1) F(FVSMOD, 1) \ F(FVSLT, 1) F(FVSGT, 1) F(FVSLE, 1) F(FVSGE, 1) \ F(AEQ, 0) F(ANE, 0) \ F(STEQ, 1) F(STNE, 1) \ F(LEQ, 0) F(LNE, 0) \ F(IUMINUS, 0) F(FUMINUS, 0) F(IVUMINUS, 1) F(FVUMINUS, 1) \ F(LOGNOT, 0) F(LOGNOTREF, 0) \ F(BINAND, 0) F(BINOR, 0) F(XOR, 0) F(ASL, 0) F(ASR, 0) F(NEG, 0) \ F(I2F, 0) F(A2S, 1) F(E2B, 0) F(E2BREF, 0) F(ST2S, 1) \ F(RETURN, 2) \ F(ISTYPE, 1) F(COCL, 0) F(COEND, 0) \ F(LOGREAD, 1) F(LOGWRITE, 2) \ F(FORLOOPI, 0) F(IFORELEM, 0) F(SFORELEM, 0) F(VFORELEM, 0) F(VFORELEMREF, 0) \ F(INCREF, 1) F(KEEPREF, 2) #define ILCALLNAMES \ F(CALL, 1) F(CALLV, 0) F(CALLVCOND, 0) F(DDCALL, 2) \ F(PUSHFUN, 1) F(CORO, ILUNKNOWNARITY) F(YIELD, 0) #define ILJUMPNAMES \ F(JUMP, 1) \ F(JUMPFAIL, 1) F(JUMPFAILR, 1) F(JUMPFAILN, 1) \ F(JUMPNOFAIL, 1) F(JUMPNOFAILR, 1) \ F(IFOR, 1) F(SFOR, 1) F(VFOR, 1) #define LVALOPNAMES \ LVAL(WRITE, 0) LVAL(WRITER, 0) LVAL(WRITEREF, 0) LVAL(WRITERREF, 0) \ LVAL(WRITEV, 1) LVAL(WRITERV, 1) LVAL(WRITEREFV, 1) LVAL(WRITERREFV, 1) \ LVAL(IADD, 0) LVAL(IADDR, 0) LVAL(ISUB, 0) LVAL(ISUBR, 0) LVAL(IMUL, 0) LVAL(IMULR, 0) LVAL(IDIV, 0) LVAL(IDIVR, 0) \ LVAL(IMOD, 0) LVAL(IMODR, 0) \ LVAL(FADD, 0) LVAL(FADDR, 0) LVAL(FSUB, 0) LVAL(FSUBR, 0) LVAL(FMUL, 0) LVAL(FMULR, 0) LVAL(FDIV, 0) LVAL(FDIVR, 0) \ LVAL(IVVADD, 1) LVAL(IVVADDR, 1) LVAL(IVVSUB, 1) LVAL(IVVSUBR, 1) LVAL(IVVMUL, 1) LVAL(IVVMULR, 1) LVAL(IVVDIV, 1) LVAL(IVVDIVR, 1) \ LVAL(IVVMOD, 1) LVAL(IVVMODR, 1) \ LVAL(FVVADD, 1) LVAL(FVVADDR, 1) LVAL(FVVSUB, 1) LVAL(FVVSUBR, 1) LVAL(FVVMUL, 1) LVAL(FVVMULR, 1) LVAL(FVVDIV, 1) LVAL(FVVDIVR, 1) \ LVAL(IVSADD, 1) LVAL(IVSADDR, 1) LVAL(IVSSUB, 1) LVAL(IVSSUBR, 1) LVAL(IVSMUL, 1) LVAL(IVSMULR, 1) LVAL(IVSDIV, 1) LVAL(IVSDIVR, 1) \ LVAL(IVSMOD, 1) LVAL(IVSMODR, 1) \ LVAL(FVSADD, 1) LVAL(FVSADDR, 1) LVAL(FVSSUB, 1) LVAL(FVSSUBR, 1) LVAL(FVSMUL, 1) LVAL(FVSMULR, 1) LVAL(FVSDIV, 1) LVAL(FVSDIVR, 1) \ LVAL(SADD, 0) LVAL(SADDR, 0) \ LVAL(IPP, 0) LVAL(IPPR, 0) LVAL(IMM, 0) LVAL(IMMR, 0) LVAL(IPPP, 0) LVAL(IPPPR, 0) LVAL(IMMP, 0) LVAL(IMMPR, 0) \ LVAL(FPP, 0) LVAL(FPPR, 0) LVAL(FMM, 0) LVAL(FMMR, 0) LVAL(FPPP, 0) LVAL(FPPPR, 0) LVAL(FMMP, 0) LVAL(FMMPR, 0) enum LVALOP { #define LVAL(N, V) LVO_##N, LVALOPNAMES #undef LVAL }; #define NUMBASELVALOPS 6 // HAS to match LVAL below! #define GENLVALOP(LV, OP) (IL_##LV##_WRITE + (OP) * NUMBASELVALOPS) // WRITE assumed to be first! #define ILADD00 0 #define ILADD01 1 #define ILADD10 1 #define ILADD11 2 #define ILADD(X, Y) ILADD##X##Y #define LVAL(N, V) F(VAR_##N, ILADD(1, V)) F(FLD_##N, ILADD(1, V)) F(LOC_##N, ILADD(1, V)) \ F(IDXVI_##N, ILADD(0, V)) F(IDXVV_##N, ILADD(1, V)) F(IDXNI_##N, ILADD(0, V)) // This assumes VAR is first! #define ISLVALVARINS(O) O >= IL_VAR_WRITE && O <= IL_VAR_FMMPR && (O % NUMBASELVALOPS) == 0 #define ISBCALL(O) (O >= IL_BCALLRETV && O <= IL_BCALLUNB7) #define ILNAMES LVALOPNAMES ILBASENAMES ILCALLNAMES ILJUMPNAMES enum ILOP { #define F(N, A) IL_##N, ILNAMES #undef F IL_MAX_OPS }; inline const char **ILNames() { #define F(N, A) #N, static const char *ilnames[] = { ILNAMES }; #undef F return ilnames; } inline const int *ILArity() { #define F(N, A) A, static const int ilarity[] = { ILNAMES }; #undef F return ilarity; } } #endif // LOBSTER_IL treesheets-1.0.2/lobster/src/lobster/lex.h000066400000000000000000000445541352107072600205600ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_LEX #define LOBSTER_LEX #include "lobster/ttypes.h" namespace lobster { struct Line { int line; int fileidx; Line(int _line, int _fileidx) : line(_line), fileidx(_fileidx) {} bool operator==(const Line &o) const { return line == o.line && fileidx == o.fileidx; } }; struct LoadedFile : Line { const char *p, *linestart, *tokenstart = nullptr; shared_ptr source { new string() }; TType token = T_NONE; int tokline = 1; // line before, if current token crossed a line bool islf = false; bool cont = false; string_view sattr; size_t whitespacebefore = 0; vector> bracketstack; vector> indentstack; const char *prevline = nullptr, *prevlinetok = nullptr; struct Tok { TType t; string_view a; }; vector gentokens; LoadedFile(string_view fn, vector &fns, string_view stringsource) : Line(1, (int)fns.size()) { if (!stringsource.empty()) { *source.get() = stringsource; } else { if (LoadFile("modules/" + fn, source.get()) < 0 && LoadFile(fn, source.get()) < 0) { THROW_OR_ABORT("can't open file: " + fn); } } linestart = p = source.get()->c_str(); indentstack.push_back({ 0, false }); fns.push_back(string(fn)); } }; struct Lex : LoadedFile { vector parentfiles; set> allfiles; vector> allsources; vector &filenames; Lex(string_view fn, vector &fns, string_view _ss = {}) : LoadedFile(fn, fns, _ss), filenames(fns) { allsources.push_back(source); FirstToken(); } void FirstToken() { Next(); if (token == T_LINEFEED) Next(); } void Include(string_view _fn) { if (allfiles.find(_fn) != allfiles.end()) { return; } allfiles.insert(string(_fn)); parentfiles.push_back(*this); *((LoadedFile *)this) = LoadedFile(_fn, filenames, {}); allsources.push_back(source); FirstToken(); } void PopIncludeContinue() { *((LoadedFile *)this) = parentfiles.back(); parentfiles.pop_back(); } void Push(TType t, string_view a = {}) { Tok tok; tok.t = t; if (a.data()) tok.a = a; gentokens.push_back(tok); } void PushCur() { Push(token, sattr); } void Undo(TType t, string_view a = {}) { PushCur(); Push(t, a); Next(); } void Next() { if (gentokens.size()) { token = gentokens.back().t; sattr = gentokens.back().a; gentokens.pop_back(); return; } bool lastcont = cont; cont = false; token = NextToken(); if (islf && token != T_ENDOFFILE && token != T_ENDOFINCLUDE) { int indent = (int)(tokenstart - linestart); if (indent > 0) { if (prevline) for (const char *indentp = linestart; indentp < tokenstart && prevline < prevlinetok; indentp++, prevline++) if (*indentp != *prevline) Error("adjacent lines do not start with the same sequence of spaces" " and/or tabs"); prevline = linestart; prevlinetok = tokenstart; } else { //prevlineindenttype = 0; prevlinetok = prevline = nullptr; } if (lastcont) { if (indent < indentstack.back().first) Error("line continuation can't indent less than the previous line"); if (indent > indentstack.back().first) indentstack.push_back({ indent, true }); return; } PushCur(); tryagain: if (indent != indentstack.back().first) { if (indent > indentstack.back().first) { indentstack.push_back({ indent, false }); Push(T_INDENT); } else { bool iscont = false; while (indentstack.back().first > indent) { iscont = indentstack.back().second; indentstack.pop_back(); if (!iscont) { Push(T_LINEFEED); Push(T_DEDENT); } } if (iscont) goto tryagain; if (indent != indentstack.back().first) Error("inconsistent dedent"); } } else { Push(T_LINEFEED); } Next(); } } void OverrideCont(bool c) { cont = c; } void PopBracket(char c) { if (bracketstack.empty()) Error(string("unmatched \'") + c + "\'"); if (bracketstack.back().second != c) Error(string("mismatched \'") + c + "\', expected \'" + bracketstack.back().second + "\'"); bracketstack.pop_back(); } TType NextToken() { line = tokline; islf = false; whitespacebefore = 0; char c; for (;;) switch (tokenstart = p, c = *p++) { case '\0': p--; if (indentstack.size() > 1) { bool iscont = indentstack.back().second; indentstack.pop_back(); if (iscont) return NextToken(); islf = false; // avoid indents being generated because of this dedent return T_DEDENT; } else { if (bracketstack.size()) Error(string("unmatched \'") + bracketstack.back().first + "\' at end of file"); return parentfiles.empty() ? T_ENDOFFILE : T_ENDOFINCLUDE; } case '\n': tokline++; islf = bracketstack.empty(); linestart = p; break; case ' ': case '\t': case '\r': case '\f': whitespacebefore++; break; case '(': bracketstack.push_back({ c, ')' }); return T_LEFTPAREN; case '[': bracketstack.push_back({ c, ']' }); return T_LEFTBRACKET; case '{': bracketstack.push_back({ c, '}' }); return T_LEFTCURLY; case ')': PopBracket(c); return T_RIGHTPAREN; case ']': PopBracket(c); return T_RIGHTBRACKET; case '}': PopBracket(c); return T_RIGHTCURLY; case ';': return T_SEMICOLON; case ',': cont = true; return T_COMMA; #define secondb(s, t, b) if (*p == s) { p++; b; return t; } #define second(s, t) secondb(s, t, {}) case '+': second('+', T_INCR); cont = true; second('=', T_PLUSEQ); return T_PLUS; case '-': second('-', T_DECR); cont = true; second('=', T_MINUSEQ); second('>', T_CODOT); return T_MINUS; case '*': cont = true; second('=', T_MULTEQ); return T_MULT; case '%': cont = true; second('=', T_MODEQ); return T_MOD; case '<': cont = true; second('=', T_LTEQ); second('<', T_ASL); return T_LT; case '=': cont = true; second('=', T_EQ); return T_ASSIGN; case '!': cont = true; second('=', T_NEQ); cont = false; return T_NOT; case '>': cont = true; second('=', T_GTEQ); second('>', T_ASR); return T_GT; case '&': cont = true; second('&', T_AND); return T_BITAND; case '|': cont = true; second('|', T_OR); return T_BITOR; case '^': cont = true; return T_XOR; case '~': cont = true; return T_NEG; case '?': cont = true; second('=', T_LOGASSIGN); cont = false; return T_QUESTIONMARK; case ':': cont = true; if (*p == ':') { p++; return T_TYPEIN; }; cont = false; return T_COLON; case '/': cont = true; second('=', T_DIVEQ); cont = false; if (*p == '/') { while (*p != '\n' && *p != '\0') p++; break; } else if (*p == '*') { for (;;) { p++; if (*p == '\0') Error("end of file in multi-line comment"); if (*p == '\n') tokline++; if (*p == '*' && *(p + 1) == '/') { p += 2; break; } } linestart = p; // not entirely correct, but best we can do break; } else { cont = true; return T_DIV; } case '\"': case '\'': return SkipString(c); default: { if (isalpha(c) || c == '_' || c < 0) { while (isalnum(*p) || *p == '_' || *p < 0) p++; sattr = string_view(tokenstart, p - tokenstart); if (sattr == "nil") return T_NIL; else if (sattr == "return") return T_RETURN; else if (sattr == "class") return T_CLASS; else if (sattr == "struct") return T_STRUCT; else if (sattr == "import") return T_INCLUDE; else if (sattr == "int") return T_INTTYPE; else if (sattr == "float") return T_FLOATTYPE; else if (sattr == "string") return T_STRTYPE; else if (sattr == "any") return T_ANYTYPE; else if (sattr == "void") return T_VOIDTYPE; else if (sattr == "lazy_expression") return T_LAZYEXP; else if (sattr == "def") return T_FUN; else if (sattr == "is") return T_IS; else if (sattr == "from") return T_FROM; else if (sattr == "program") return T_PROGRAM; else if (sattr == "private") return T_PRIVATE; else if (sattr == "coroutine") return T_COROUTINE; else if (sattr == "resource") return T_RESOURCE; else if (sattr == "enum") return T_ENUM; else if (sattr == "enum_flags") return T_ENUM_FLAGS; else if (sattr == "typeof") return T_TYPEOF; else if (sattr == "var") return T_VAR; else if (sattr == "let") return T_CONST; else if (sattr == "pakfile") return T_PAKFILE; else if (sattr == "switch") return T_SWITCH; else if (sattr == "case") return T_CASE; else if (sattr == "default") return T_DEFAULT; else if (sattr == "namespace") return T_NAMESPACE; else if (sattr == "not") return T_NOT; else if (sattr == "and") { cont = true; return T_AND; } else if (sattr == "or") { cont = true; return T_OR; } else return T_IDENT; } bool isfloat = c == '.' && *p != '.'; if (isdigit(c) || (isfloat && isdigit(*p))) { if (c == '0' && *p == 'x') { p++; while (isxdigit(*p)) p++; sattr = string_view(tokenstart, p - tokenstart); return T_INT; } else { while (isdigit(*p)) p++; if (!isfloat && *p == '.' && *(p + 1) != '.' && !isalpha(*(p + 1))) { p++; isfloat = true; while (isdigit(*p)) p++; } if (isfloat && (*p == 'e' || *p == 'E')) { p++; if (*p == '+' || *p == '-') p++; while (isdigit(*p)) p++; } sattr = string_view(tokenstart, p - tokenstart); return isfloat ? T_FLOAT : T_INT; } } if (c == '.') { if (*p == '.') { p++; return T_DOTDOT; } islf = false; // Support "builder" APIs. return T_DOT; } auto tok = c < ' ' || c >= 127 ? cat("[ascii ", int(c), "]") : cat('\'', char(c), '\''); Error("illegal token: " + tok); return T_NONE; } } } char HexDigit(char c) { if (isdigit(c)) return c - '0'; if (isxdigit(c)) return c - (c < 'a' ? 'A' : 'a') + 10; return -1; } TType SkipString(char initial) { auto start = p - 1; char c = 0; // Check if its a multi-line constant. if (initial == '\"' && p[0] == '\"' && p[1] == '\"') { p += 2; for (;;) { switch (c = *p++) { case '\0': Error("end of file found in multi-line string constant"); break; case '\n': tokline++; break; case '\"': if (p[0] == '\"' && p[1] == '\"') { p += 2; sattr = string_view(start, p - start); return T_STR; } } } } // Regular string or character constant. while ((c = *p++) != initial) switch (c) { case 0: case '\r': case '\n': p--; Error("end of line found in string constant"); case '\'': case '\"': Error("\' and \" should be prefixed with a \\ in a string constant"); case '\\': switch(*p) { case '\\': case '\"': case '\'': p++; }; break; default: if (c < ' ') Error("unprintable character in string constant"); }; sattr = string_view(start, p - start); return initial == '\"' ? T_STR : T_INT; } int64_t IntVal() { if (sattr[0] == '\'') { auto s = StringVal(); if (s.size() > 4) Error("character constant too long"); int64_t ival = 0; for (auto c : s) ival = (ival << 8) + c; return ival; } else if (sattr[0] == '0' && sattr.size() > 1 && sattr[1] == 'x') { // Test for hex explicitly since we don't want to allow octal parsing. return parse_int(sattr, 16); } else { return parse_int(sattr); } } string StringVal() { auto s = sattr.data(); // This is ok, because has been parsed before and is inside buf. auto initial = *s++; // Check if its a multi-line constant. if (initial == '\"' && s[0] == '\"' && s[1] == '\"') { auto start = s + 2; if (*start == '\r') start++; if (*start == '\n') start++; auto end = sattr.data() + sattr.size() - 3; string r; r.reserve(end - start); for (auto c : string_view(start, end - start)) { if (c != '\r') r += c; } return r; } // Regular string or character constant. string r; char c = 0; while ((c = *s++) != initial) switch (c) { case '\\': switch(c = *s++) { case 'n': c = '\n'; break; case 't': c = '\t'; break; case 'r': c = '\r'; break; case '\\': case '\"': case '\'': break; case 'x': if (!isxdigit(*s) || !isxdigit(s[1])) Error("illegal hexadecimal escape code in string constant"); c = HexDigit(*s++) << 4; c |= HexDigit(*s++); break; default: s--; Error("unknown control code in string constant"); }; r += c; break; default: r += c; }; return r; }; string_view TokStr(TType t) { return TName(t); } string_view TokStr() { switch (token) { case T_IDENT: case T_FLOAT: case T_INT: case T_STR: return sattr; default: return TName(token); } } string Location(const Line &ln) { return cat(filenames[ln.fileidx], "(", ln.line, ")"); } void Error(string_view msg, const Line *ln = nullptr) { auto err = Location(ln ? *ln : *this) + ": error: " + msg; //LOG_DEBUG(err); THROW_OR_ABORT(err); } void Warn(string_view msg, const Line *ln = nullptr) { LOG_WARN(Location(ln ? *ln : *this) + ": warning: " + msg); } }; } // namespace lobster #endif // LOBSTER_LEX treesheets-1.0.2/lobster/src/lobster/mctables.h000066400000000000000000000432161352107072600215540ustar00rootroot00000000000000static int mc_edge_to_vert[12][2] = { { 0, 1 }, { 1, 2 }, { 3, 2 }, { 0, 3 }, { 4, 5 }, { 5, 6 }, { 7, 6 }, { 4, 7 }, { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 }, }; static int mc_edge_table[256] = { 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 }; static int mc_tri_table[256][16] = { {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1}, {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1}, {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1}, {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1}, {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1}, {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1}, {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1}, {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1}, {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}, {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1}, {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1}, {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1}, {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1}, {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}, {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1}, {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1}, {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1}, {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1}, {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1}, {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1}, {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1}, {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1}, {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1}, {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1}, {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1}, {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1}, {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1}, {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1}, {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1}, {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1}, {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1}, {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1}, {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1}, {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}, {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1}, {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1}, {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1}, {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1}, {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1}, {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1}, {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1}, {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}, {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1}, {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1}, {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1}, {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1}, {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1}, {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1}, {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1}, {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1}, {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1}, {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1}, {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1}, {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1}, {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1}, {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1}, {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1}, {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1}, {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1}, {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1}, {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1}, {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1}, {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1}, {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1}, {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1}, {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1}, {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1}, {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1}, {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1}, {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1}, {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1}, {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1}, {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}, {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1}, {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1}, {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1}, {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1}, {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1}, {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1}, {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}, {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1}, {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1}, {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1}, {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1}, {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1}, {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1}, {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1}, {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1}, {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1}, {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1}, {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1}, {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1}, {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1}, {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1}, {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}, {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1}, {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1}, {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1}, {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1}, {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1}, {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1}, {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1}, {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1}, {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1}, {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1}, {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1}, {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1}, {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1}, {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1}, {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1}, {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1}, {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1}, {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1}, {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1}, {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1}, {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}, {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1}, {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1}, {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1}, {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1}, {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1}, {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1}, {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1}, {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1}, {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1}, {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1}, {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1}, {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1}, {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1}, {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1}, {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1}, {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1}, {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1}, {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1}, {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1}, {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1}, {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1}, {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1}, {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1}, {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1}, {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1}, {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1}, {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1}, {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1}, {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1}, {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1}, {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1}, {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1}, {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1}, {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1}, {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1}, {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1}, {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1}, {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1}, {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1}, {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1}, {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1}, {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} }; treesheets-1.0.2/lobster/src/lobster/meshgen.h000066400000000000000000000033601352107072600214040ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_MESHGEN_H #define LOBSTER_MESHGEN_H #include "3dgrid.h" struct DistVert { // FIXME: can optimize this for memory usage making both 16bit float dist; byte4 color; DistVert() : dist(FLT_MAX), color(byte4_0) {} }; // Turns out the way shapes overlap and are rasterized invidually below makes this not as efficient // a data structure for this purpose as it at first seemed. //typedef RLE3DGrid DistGrid; //typedef RLE3DGrid IntGrid; // So use this instead: typedef Chunk3DGrid DistGrid; typedef Chunk3DGrid IntGrid; typedef Chunk3DGrid EdgeGrid; struct mgvert { float3 pos; float3 norm; byte4 col; }; inline void RecomputeNormals(vector &triangles, vector &verts) { normalize_mesh(make_span(triangles), verts.data(), verts.size(), sizeof(mgvert), (uchar *)&verts.data()->norm - (uchar *)&verts.data()->pos, false); }; extern Mesh *polygonize_mc(const int3 &gridsize, float gridscale, const float3 &gridtrans, const DistGrid *distgrid, float3 (* grid_to_world)(const int3 &pos)); #endif // LOBSTER_MESHGEN_H treesheets-1.0.2/lobster/src/lobster/natreg.h000066400000000000000000000345501352107072600212430ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_NATREG #define LOBSTER_NATREG #include "lobster/vmdata.h" namespace lobster { // Compile time lifetime tracking between values and their recipients. // If lifetimes correspond, no action is required. // If recipient wants to keep, but value is borrowed, inc ref or copy or error. // If recipient wants to borrow, but value is keep, dec ref or delete after recipient is done. // NOTE: all positive values are an index of the SpecIdent being borrowed. // If you're borrowing, you are "locking" the modification of the variable you borrow from. enum Lifetime { // Value: you are receiving a value stored elsewhere, do not hold on. // Recipient: I do not want to be responsible for managing this value. LT_BORROW = -1, // Value: you are responsible for this value, you must delete or store. // Recipient: I want to hold on to this value (inc ref, or be sole owner). LT_KEEP = -2, // Value: lifetime shouldn't matter, because type is non-reference. // Recipient: I'm cool with any lifetime. LT_ANY = -3, // Value: there are multiple lifetimes, stored elsewhere. LT_MULTIPLE = -4, // Lifetime is not valid. LT_UNDEF = -5, }; inline bool IsBorrow(Lifetime lt) { return lt >= LT_BORROW; } inline Lifetime LifetimeType(Lifetime lt) { return IsBorrow(lt) ? LT_BORROW : lt; } struct Named { string name; int idx = -1; bool isprivate = false; Named() = default; Named(string_view _name, int _idx = 0) : name(_name), idx(_idx) {} }; struct SubFunction; struct Enum; struct UDT; struct Type { const ValueType t = V_UNDEFINED; struct TupleElem { const Type *type; Lifetime lt; }; union { const Type *sub; // V_VECTOR | V_NIL | V_VAR SubFunction *sf; // V_FUNCTION | V_COROUTINE UDT *udt; // V_CLASS | V_STRUCT_* Enum *e; // V_INT vector *tup; // V_TUPLE }; Type() : sub(nullptr) {} explicit Type(ValueType _t) : t(_t), sub(nullptr) {} Type(ValueType _t, const Type *_s) : t(_t), sub(_s) {} Type(ValueType _t, SubFunction *_sf) : t(_t), sf(_sf) {} Type(ValueType _t, UDT *_udt) : t(_t), udt(_udt) {} Type(Enum *_e) : t(V_INT), e(_e) {} bool operator==(const Type &o) const { return t == o.t && (sub == o.sub || // Also compares sf/udt (Wrapped() && *sub == *o.sub)); } bool operator!=(const Type &o) const { return !(*this == o); } bool EqNoIndex(const Type &o) const { return t == o.t && (!Wrapped() || sub->EqNoIndex(*o.sub)); } Type &operator=(const Type &o) { // Hack: we want t to be const, but still have a working assignment operator. (ValueType &)t = o.t; sub = o.sub; return *this; } const Type *Element() const { assert(Wrapped()); return sub; } const Type *ElementIfNil() const { return t == V_NIL ? sub : this; } Type *Wrap(Type *dest, ValueType with) const { *dest = Type(with, this); return dest; } bool Wrapped() const { return t == V_VECTOR || t == V_NIL; } const Type *UnWrapped() const { return Wrapped() ? sub : this; } bool Numeric() const { return t == V_INT || t == V_FLOAT; } bool IsFunction() const { return t == V_FUNCTION && sf; } bool IsEnum() const { return t == V_INT && e; } bool IsBoundVar() const { return t == V_VAR && sub; } bool HasValueType(ValueType vt) const { return t == vt || (Wrapped() && Element()->HasValueType(vt)); } size_t NumValues() const { if (t == V_VOID) return 0; if (t == V_TUPLE) return tup->size(); return 1; } const Type *Get(size_t i) const { return t == V_TUPLE ? (*tup)[i].type : this; } void Set(size_t i, const Type *type, Lifetime lt) const { assert(t == V_TUPLE); (*tup)[i] = { type, lt }; } Lifetime GetLifetime(size_t i, Lifetime lt) const { return lt == LT_MULTIPLE && t == V_TUPLE ? (*tup)[i].lt : lt; } }; extern const Type g_type_undefined; // This is essentially a smart-pointer, but behaves a little bit differently: // - initialized to type_undefined instead of nullptr // - pointer is const // - comparisons are by value. class TypeRef { const Type *type; public: TypeRef() : type(&g_type_undefined) {} TypeRef(const Type *_type) : type(_type) {} TypeRef &operator=(const TypeRef &o) { type = o.type; return *this; } const Type &operator*() const { return *type; } const Type *operator->() const { return type; } const Type *get() const { return type; } // Must compare Type instances by value. bool operator==(const TypeRef &o) const { return *type == *o.type; }; bool operator!=(const TypeRef &o) const { return *type != *o.type; }; bool Null() const { return type == nullptr; } }; extern TypeRef type_int; extern TypeRef type_float; extern TypeRef type_string; extern TypeRef type_any; extern TypeRef type_vector_int; extern TypeRef type_vector_float; extern TypeRef type_function_null; extern TypeRef type_function_cocl; extern TypeRef type_function_void; extern TypeRef type_coroutine; extern TypeRef type_resource; extern TypeRef type_typeid; extern TypeRef type_void; extern TypeRef type_undefined; TypeRef WrapKnown(TypeRef elem, ValueType with); enum ArgFlags { AF_NONE = 0, AF_EXPFUNVAL = 1, AF_GENERIC = 2, NF_SUBARG1 = 4, NF_SUBARG2 = 8, NF_SUBARG3 = 16, NF_ANYVAR = 32, NF_CORESUME = 64, AF_WITHTYPE = 128, NF_CONVERTANYTOSTRING = 256, NF_PUSHVALUEWIDTH = 512, NF_BOOL = 1024, }; DEFINE_BITWISE_OPERATORS_FOR_ENUM(ArgFlags) struct Ident; struct SpecIdent; struct Typed { TypeRef type = type_undefined; ArgFlags flags = AF_NONE; Typed() = default; Typed(const Typed &o) : type(o.type), flags(o.flags) {} Typed(TypeRef _type, ArgFlags _flags) : type(_type), flags(_flags) {} }; struct Narg : Typed { char fixed_len = 0; Lifetime lt = LT_UNDEF; Narg() = default; Narg(const Narg &o) : Typed(o), fixed_len(o.fixed_len), lt(o.lt) {} void Set(const char *&tid, Lifetime def) { char t = *tid++; flags = AF_NONE; lt = def; switch (t) { case 'A': type = type_any; break; case 'I': type = type_int; break; case 'B': type = type_int; flags = flags | NF_BOOL; break; case 'F': type = type_float; break; case 'S': type = type_string; break; case 'L': type = type_function_null; break; case 'C': type = type_coroutine; break; case 'R': type = type_resource; break; case 'T': type = type_typeid; break; default: assert(0); } while (*tid && !isupper(*tid)) { switch (auto c = *tid++) { case 0: break; case '1': flags = flags | NF_SUBARG1; break; case '2': flags = flags | NF_SUBARG2; break; case '3': flags = flags | NF_SUBARG3; break; case '*': flags = flags | NF_ANYVAR; break; case '@': flags = flags | AF_EXPFUNVAL; break; case '%': flags = flags | NF_CORESUME; break; // FIXME: make a vm op. case 's': flags = flags | NF_CONVERTANYTOSTRING; break; case 'w': flags = flags | NF_PUSHVALUEWIDTH; break; case 'k': lt = LT_KEEP; break; case 'b': lt = LT_BORROW; break; case ']': case '}': type = WrapKnown(type, V_VECTOR); assert(!type.Null()); if (c == '}') fixed_len = -1; break; case '?': type = WrapKnown(type, V_NIL); assert(!type.Null()); break; case ':': assert(*tid >= '/' && *tid <= '9'); fixed_len = *tid++ - '0'; break; default: assert(false); } } } }; struct GenericArgs { virtual string_view GetName(size_t i) const = 0; virtual TypeRef GetType(size_t i) const = 0; virtual ArgFlags GetFlags(size_t i) const = 0; virtual size_t size() const = 0; }; struct NargVector : GenericArgs { vector v; const char *idlist; NargVector(size_t nargs, const char *_idlist) : v(nargs), idlist(_idlist) {} size_t size() const { return v.size(); } TypeRef GetType(size_t i) const { return v[i].type; } ArgFlags GetFlags(size_t i) const { return v[i].flags; } string_view GetName(size_t i) const { auto ids = idlist; for (;;) { const char *idend = strchr(ids, ','); if (!idend) { // if this fails, you're not specifying enough arg names in the comma separated list assert(!i); idend = ids + strlen(ids); } if (!i--) return string_view(ids, idend - ids); ids = idend + 1; } } }; typedef void (*builtinfV)(VM &vm); typedef Value (*builtinf0)(VM &vm); typedef Value (*builtinf1)(VM &vm, Value &); typedef Value (*builtinf2)(VM &vm, Value &, Value &); typedef Value (*builtinf3)(VM &vm, Value &, Value &, Value &); typedef Value (*builtinf4)(VM &vm, Value &, Value &, Value &, Value &); typedef Value (*builtinf5)(VM &vm, Value &, Value &, Value &, Value &, Value &); typedef Value (*builtinf6)(VM &vm, Value &, Value &, Value &, Value &, Value &, Value &); typedef Value (*builtinf7)(VM &vm, Value &, Value &, Value &, Value &, Value &, Value &, Value &); struct BuiltinPtr { union { builtinfV fV; builtinf0 f0; builtinf1 f1; builtinf2 f2; builtinf3 f3; builtinf4 f4; builtinf5 f5; builtinf6 f6; builtinf7 f7; }; int fnargs; BuiltinPtr() : f0(nullptr), fnargs(0) {} BuiltinPtr(builtinfV f) : fV(f), fnargs(-1) {} BuiltinPtr(builtinf0 f) : f0(f), fnargs(0) {} BuiltinPtr(builtinf1 f) : f1(f), fnargs(1) {} BuiltinPtr(builtinf2 f) : f2(f), fnargs(2) {} BuiltinPtr(builtinf3 f) : f3(f), fnargs(3) {} BuiltinPtr(builtinf4 f) : f4(f), fnargs(4) {} BuiltinPtr(builtinf5 f) : f5(f), fnargs(5) {} BuiltinPtr(builtinf6 f) : f6(f), fnargs(6) {} BuiltinPtr(builtinf7 f) : f7(f), fnargs(7) {} }; struct NativeFun : Named { BuiltinPtr fun; NargVector args, retvals; builtinfV cont1; const char *idlist; const char *help; int subsystemid = -1; NativeFun *overloads = nullptr, *first = this; int TypeLen(const char *s) { int i = 0; while (*s) if(isupper(*s++)) i++; return i; }; NativeFun(const char *name, BuiltinPtr f, const char *ids, const char *typeids, const char *rets, const char *help, builtinfV cont1) : Named(name, 0), fun(f), args(TypeLen(typeids), ids), retvals(0, nullptr), cont1(cont1), help(help) { auto nretvalues = TypeLen(rets); assert((int)args.v.size() == f.fnargs || f.fnargs < 0); auto StructArgsVararg = [&](const Narg &arg) { assert(!arg.fixed_len || IsRef(arg.type->sub->t) || f.fnargs < 0); (void)arg; }; for (size_t i = 0; i < args.v.size(); i++) { args.GetName(i); // Call this just to trigger the assert. args.v[i].Set(typeids, LT_BORROW); StructArgsVararg(args.v[i]); } for (int i = 0; i < nretvalues; i++) { retvals.v.push_back(Narg()); retvals.v[i].Set(rets, LT_KEEP); StructArgsVararg(retvals.v[i]); } } bool CanChangeControlFlow() { // FIXME: make resume a VM op. return name == "resume" || name == "gl_frame"; } bool IsAssert() { // FIXME: make into a language feature. return name == "assert"; } }; struct NativeRegistry { vector nfuns; unordered_map nfunlookup; // Key points to value! vector subsystems; ~NativeRegistry() { for (auto f : nfuns) delete f; } void NativeSubSystemStart(const char *name) { subsystems.push_back(name); } #define REGISTER(N) \ void operator()(const char *name, const char *ids, const char *typeids, \ const char *rets, const char *help, builtinf##N f, \ builtinfV cont1 = nullptr) { \ Reg(new NativeFun(name, BuiltinPtr(f), ids, typeids, rets, help, cont1)); \ } REGISTER(V) REGISTER(0) REGISTER(1) REGISTER(2) REGISTER(3) REGISTER(4) REGISTER(5) REGISTER(6) REGISTER(7) #undef REGISTER void Reg(NativeFun *nf) { nf->idx = (int)nfuns.size(); nf->subsystemid = (int)subsystems.size() - 1; auto existing = FindNative(nf->name); if (existing) { if (/*nf->args.v.size() != existing->args.v.size() || nf->retvals.v.size() != existing->retvals.v.size() || */ nf->subsystemid != existing->subsystemid ) { // Must have similar signatures. assert(0); THROW_OR_ABORT("native library name clash: " + nf->name); } nf->overloads = existing->overloads; existing->overloads = nf; nf->first = existing->first; } else { nfunlookup[nf->name /* must be in value */] = nf; } nfuns.push_back(nf); } NativeFun *FindNative(string_view name) { auto it = nfunlookup.find(name); return it != nfunlookup.end() ? it->second : nullptr; } }; } // namespace lobster #endif // LOBSTER_NATREG treesheets-1.0.2/lobster/src/lobster/node.h000066400000000000000000000404031352107072600207020ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_NODE #define LOBSTER_NODE namespace lobster { typedef const function &IterateFun; struct TypeChecker; struct Optimizer; struct CodeGen; struct Node { Line line { 0, 0 }; TypeRef exptype; Lifetime lt = LT_UNDEF; virtual ~Node() {}; virtual size_t Arity() const { return 0; } virtual Node **Children() { return nullptr; } virtual Node *Clone() = 0; virtual bool IsConstInit() const { return false; } virtual string_view Name() const = 0; virtual void Dump(ostringstream &ss) const { ss << Name(); } void Iterate(IterateFun f) { f(this); auto ch = Children(); if (ch) for (size_t i = 0; i < Arity(); i++) ch[i]->Iterate(f); } // Used in the optimizer to see if this node can be discarded without consequences. virtual bool SideEffect() const = 0; // Just this node. bool HasSideEffects() { // Transitively. bool se = false; Iterate([&](Node *n) { se = se || n->SideEffect(); }); return se; } size_t Count() { size_t count = 0; Iterate([&](Node *) { count++; }); return count; } // Used by type-checker to and optimizer. // If it returns true, sets val to a value that gives the correct True(). // Also sets correct scalar values. virtual bool ConstVal(TypeChecker &, Value &) const { return false; } virtual Node *TypeCheck(TypeChecker &tc, size_t reqret) = 0; virtual Node *Optimize(Optimizer &opt, Node *parent_maybe); virtual void Generate(CodeGen &cg, size_t retval) const = 0; protected: Node(const Line &ln) : line(ln) {} Node() = default; }; struct TypeLT { TypeRef type; Lifetime lt; TypeLT(TypeRef type, Lifetime lt) : type(type), lt(lt) {} TypeLT(const SpecIdent &sid) : type(sid.type), lt(sid.lt) {} TypeLT(const Node &n, size_t i) : type(n.exptype->Get(i)), lt(n.exptype->GetLifetime(i, n.lt)) {} TypeLT(const SubFunction &sf, size_t i) : type(sf.returntype->Get(i)), lt(sf.ltret) {} }; template T *DoClone(T *dest, T *src) { *dest = *src; // Copy contructor copies all values & non-owned. for (size_t i = 0; i < src->Arity(); i++) dest->Children()[i] = src->Children()[i]->Clone(); return dest; } #define SHARED_SIGNATURE_NO_TT(NAME, STR, SE) \ string_view Name() const { return STR; } \ bool SideEffect() const { return SE; } \ void Generate(CodeGen &cg, size_t retval) const; \ Node *Clone() { return DoClone(new NAME(), this); } \ protected: \ NAME() {}; // Only used by clone. #define SHARED_SIGNATURE(NAME, STR, SE) \ Node *TypeCheck(TypeChecker &tc, size_t reqret); \ SHARED_SIGNATURE_NO_TT(NAME, STR, SE) #define ZERO_NODE(NAME, STR, SE, METHODS) \ struct NAME : Node { \ NAME(const Line &ln) : Node(ln) {} \ SHARED_SIGNATURE(NAME, STR, SE) \ METHODS \ }; #define UNARY_NODE(NAME, STR, SE, A, METHODS) \ struct NAME : Node { \ Node *A; \ NAME(const Line &ln, Node *_a) : Node(ln), A(_a) {} \ ~NAME() { delete A; } \ size_t Arity() const { return 1; } \ Node **Children() { return &A; } \ SHARED_SIGNATURE(NAME, STR, SE) \ METHODS \ }; #define UNOP_NODE(NAME, STR, SE, METHODS) \ struct NAME : Unary { \ NAME(const Line &ln, Node *_a) : Unary(ln, _a) {} \ SHARED_SIGNATURE(NAME, STR, SE) \ METHODS \ }; #define COER_NODE(NAME, STR) \ struct NAME : Coercion { \ NAME(const Line &ln, Node *_a) : Coercion(ln, _a) {} \ SHARED_SIGNATURE_NO_TT(NAME, STR, false) \ }; #define BINARY_NODE_T(NAME, STR, SE, AT, A, BT, B, METHODS) \ struct NAME : Node { \ AT *A; BT *B; \ NAME(const Line &ln, AT *_a, BT *_b) : Node(ln), A(_a), B(_b) {}; \ ~NAME() { delete A; delete B; } \ size_t Arity() const { return 2; } \ Node **Children() { return (Node **)&A; } \ SHARED_SIGNATURE(NAME, STR, SE) \ METHODS \ }; #define BINARY_NODE(NAME, STR, SE, A, B, METHODS) \ BINARY_NODE_T(NAME, STR, SE, Node, A, Node, B, METHODS) #define BINOP_NODE(NAME, STR, SE, METHODS) \ struct NAME : BinOp { \ NAME(const Line &ln, Node *_a, Node *_b) : BinOp(ln, _a, _b) {}; \ SHARED_SIGNATURE(NAME, STR, SE) \ METHODS \ }; #define TERNARY_NODE(NAME, STR, SE, A, B, C, METHODS) \ struct NAME : Node { \ Node *A, *B, *C; \ NAME(const Line &ln, Node *_a, Node *_b, Node *_c) : Node(ln), A(_a), B(_b), C(_c) {} \ ~NAME() { delete A; delete B; delete C; } \ size_t Arity() const { return 3; } \ Node **Children() { return &A; } \ SHARED_SIGNATURE(NAME, STR, SE) \ METHODS \ }; #define NARY_NODE(NAME, STR, SE, METHODS) \ struct NAME : Node { \ vector children; \ NAME(const Line &ln) : Node(ln) {}; \ ~NAME() { for (auto n : children) delete n; } \ size_t Arity() const { return children.size(); } \ Node **Children() { return children.data(); } \ NAME *Add(Node *a) { children.push_back(a); return this; }; \ SHARED_SIGNATURE(NAME, STR, SE) \ METHODS \ }; struct TypeAnnotation : Node { TypeRef giventype; TypeAnnotation(const Line &ln, TypeRef tr) : Node(ln), giventype(tr) {} void Dump(ostringstream &ss) const { ss << TypeName(giventype); } SHARED_SIGNATURE(TypeAnnotation, "type", false) }; #define CONSTVALMETHOD bool ConstVal(TypeChecker &tc, Value &val) const; #define OPTMETHOD Node *Optimize(Optimizer &opt, Node *parent_maybe); // generic node types NARY_NODE(List, "list", false, ) UNARY_NODE(Unary, "unary", false, child, ) BINARY_NODE(BinOp, "binop", false, left, right, ) UNOP_NODE(Coercion, "coercion", false, ) BINOP_NODE(Plus, TName(T_PLUS), false, ) BINOP_NODE(Minus, TName(T_MINUS), false, ) BINOP_NODE(Multiply, TName(T_MULT), false, ) BINOP_NODE(Divide, TName(T_DIV), false, ) BINOP_NODE(Mod, TName(T_MOD), false, ) BINOP_NODE(PlusEq, TName(T_PLUSEQ), true, ) BINOP_NODE(MinusEq, TName(T_MINUSEQ), true, ) BINOP_NODE(MultiplyEq, TName(T_MULTEQ), true, ) BINOP_NODE(DivideEq, TName(T_DIVEQ), true, ) BINOP_NODE(ModEq, TName(T_MODEQ), true, ) BINOP_NODE(And, TName(T_AND), false, CONSTVALMETHOD) BINOP_NODE(Or, TName(T_OR), false, CONSTVALMETHOD) UNARY_NODE(Not, TName(T_NOT), false, child, CONSTVALMETHOD) UNOP_NODE(PreIncr, TName(T_INCR), true, ) UNOP_NODE(PreDecr, TName(T_DECR), true, ) BINOP_NODE(Equal, TName(T_EQ), false, ) BINOP_NODE(NotEqual, TName(T_NEQ), false, ) BINOP_NODE(LessThan, TName(T_LT), false, ) BINOP_NODE(GreaterThan, TName(T_GT), false, ) BINOP_NODE(LessThanEq, TName(T_LTEQ), false, ) BINOP_NODE(GreaterThanEq, TName(T_GTEQ), false, ) BINOP_NODE(BitAnd, TName(T_BITAND), false, ) BINOP_NODE(BitOr, TName(T_BITOR), false, ) BINOP_NODE(Xor, TName(T_XOR), false, ) UNARY_NODE(Negate, TName(T_NEG), false, child, ) BINOP_NODE(ShiftLeft, TName(T_ASL), false, ) BINOP_NODE(ShiftRight, TName(T_ASR), false, ) BINOP_NODE(Assign, TName(T_ASSIGN), true, ) BINOP_NODE(LogAssign, TName(T_LOGASSIGN), true, ) BINARY_NODE(CoDot, TName(T_CODOT), false, coroutine, variable, ) ZERO_NODE(DefaultVal, "default value", false, ) UNARY_NODE(TypeOf, TName(T_TYPEOF), false, child, ) UNARY_NODE(CoRoutine, TName(T_COROUTINE), true, call, ) ZERO_NODE(CoClosure, "coroutine yield", false, ) BINARY_NODE(Seq, "statements", false, head, tail, ) BINARY_NODE(Indexing, "indexing operation", false, object, index, ) UNOP_NODE(PostIncr, TName(T_INCR), true, ) UNOP_NODE(PostDecr, TName(T_DECR), true, ) UNARY_NODE(UnaryMinus, TName(T_MINUS), false, child, ) COER_NODE(ToFloat, "tofloat") COER_NODE(ToString, "tostring") COER_NODE(ToBool, "tobool") COER_NODE(ToInt, "toint") TERNARY_NODE(If, "if", false, condition, truepart, falsepart, OPTMETHOD) BINARY_NODE(While, "while", false, condition, body, ) BINARY_NODE(For, "for", false, iter, body, ) ZERO_NODE(ForLoopElem, "for loop element", false, ) ZERO_NODE(ForLoopCounter, "for loop counter", false, ) NARY_NODE(Inlined, "inlined", false, ) BINARY_NODE_T(Switch, "switch", false, Node, value, List, cases, ) BINARY_NODE_T(Case, "case", false, List, pattern, Node, body, ) BINARY_NODE(Range, "range", false, start, end, ) struct Nil : Node { TypeRef giventype; Nil(const Line &ln, TypeRef tr) : Node(ln), giventype(tr) {} bool ConstVal(TypeChecker &, Value &val) const { val = Value(); return true; } SHARED_SIGNATURE(Nil, TName(T_NIL), false) }; struct IdentRef : Node { SpecIdent *sid; IdentRef(const Line &ln, SpecIdent *_sid) : Node(ln), sid(_sid) {} bool IsConstInit() const { return sid->id->static_constant; } void Dump(ostringstream &ss) const { ss << sid->id->name; } SHARED_SIGNATURE(IdentRef, TName(T_IDENT), false) }; struct IntConstant : Node { int64_t integer; EnumVal *from; IntConstant(const Line &ln, int64_t i) : Node(ln), integer(i), from(nullptr) {} bool IsConstInit() const { return true; } void Dump(ostringstream& ss) const { if (from) ss << from->name; else ss << integer; } bool ConstVal(TypeChecker &, Value &val) const { val = Value(integer); // FIXME: this clips. return true; } SHARED_SIGNATURE(IntConstant, TName(T_INT), false) }; struct FloatConstant : Node { double flt; FloatConstant(const Line &ln, double f) : Node(ln), flt(f) {} bool IsConstInit() const { return true; } void Dump(ostringstream &ss) const { ss << flt; } bool ConstVal(TypeChecker &, Value &val) const { val = Value(flt); return true; } SHARED_SIGNATURE(FloatConstant, TName(T_FLOAT), false) }; struct StringConstant : Node { string str; StringConstant(const Line &ln, string_view s) : Node(ln), str(s) {} bool IsConstInit() const { return true; } void Dump(ostringstream &ss) const { EscapeAndQuote(str, ss); } SHARED_SIGNATURE(StringConstant, TName(T_STR), false) }; struct EnumRef : Node { Enum *e; EnumRef(const Line &ln, Enum *_e) : Node(ln), e(_e) {} void Dump(ostringstream &ss) const { ss << "enum" << e->name; } SHARED_SIGNATURE(EnumRef, TName(T_ENUM), false) }; struct UDTRef : Node { UDT *udt; UDTRef(const Line &ln, UDT *_udt) : Node(ln), udt(_udt) {} void Dump(ostringstream &ss) const { ss << (udt->is_struct ? "struct " : "class ") << udt->name; } SHARED_SIGNATURE(UDTRef, TName(T_CLASS), false) }; struct FunRef : Node { SubFunction *sf; FunRef(const Line &ln, SubFunction *_sf) : Node(ln), sf(_sf) {} bool IsConstInit() const { return true; } void Dump(ostringstream &ss) const { ss << "(def " << sf->parent->name << ")"; } SHARED_SIGNATURE(FunRef, TName(T_FUN), false) }; // This is either a Dot, Call, or NativeCall, to be specialized by the typechecker struct GenericCall : List { string_view name; SubFunction *sf; // Need to store this, since only parser tracks scopes. bool dotnoparens; GenericCall(const Line &ln, string_view name, SubFunction *sf, bool dotnoparens) : List(ln), name(name), sf(sf), dotnoparens(dotnoparens) {}; SHARED_SIGNATURE(GenericCall, "generic call", true) }; struct Constructor : List { TypeRef giventype; Constructor(const Line &ln, TypeRef _type) : List(ln), giventype(_type) {}; bool IsConstInit() const { for (auto n : children) { if (!n->IsConstInit()) return false; } return true; } SHARED_SIGNATURE(Constructor, "constructor", false) }; struct Call : GenericCall { int vtable_idx = -1; explicit Call(GenericCall &gc) : GenericCall(gc.line, gc.name, gc.sf, gc.dotnoparens) {}; Call(Line &ln, SubFunction *sf) : GenericCall(ln, sf->parent->name, sf, false) {}; void Dump(ostringstream &ss) const { ss << sf->parent->name; } void TypeCheckSpecialized(TypeChecker &tc, size_t reqret); SHARED_SIGNATURE_NO_TT(Call, "call", true) OPTMETHOD }; struct DynCall : List { SubFunction *sf; SpecIdent *sid; DynCall(const Line &ln, SubFunction *_sf, SpecIdent *_sid) : List(ln), sf(_sf), sid(_sid) {}; void Dump(ostringstream &ss) const { ss << sid->id->name; } SHARED_SIGNATURE(DynCall, "dynamic call", true) OPTMETHOD }; struct NativeCall : GenericCall { NativeFun *nf; TypeRef nattype = nullptr; Lifetime natlt = LT_UNDEF; NativeCall(NativeFun *_nf, GenericCall &gc) : GenericCall(gc.line, gc.name, gc.sf, gc.dotnoparens), nf(_nf) {}; void Dump(ostringstream &ss) const { ss << nf->name; } void TypeCheckSpecialized(TypeChecker &tc, size_t reqret); SHARED_SIGNATURE_NO_TT(NativeCall, "native call", true) }; struct Return : Unary { SubFunction *sf; bool make_void; Return(const Line &ln, Node *_a, SubFunction *sf, bool make_void) : Unary(ln, _a), sf(sf), make_void(make_void) {} SHARED_SIGNATURE(Return, TName(T_RETURN), true) }; struct MultipleReturn : List { MultipleReturn(const Line &ln) : List(ln) {}; SHARED_SIGNATURE(MultipleReturn, "multiple return", false) }; struct AssignList : List { AssignList(const Line &ln, Node *a) : List(ln) { children.push_back(a); } void Dump(ostringstream &ss) const { for (auto e : children) ss << e << " "; } SHARED_SIGNATURE(AssignList, "assign list", true) }; struct Define : Unary { vector> sids; Define(const Line &ln, SpecIdent *sid, Node *_a) : Unary(ln, _a) { if (sid) sids.push_back({ sid, nullptr }); } void Dump(ostringstream &ss) const { for (auto p : sids) ss << p.first->id->name << " "; ss << Name(); } SHARED_SIGNATURE(Define, "var", true) }; struct Dot : GenericCall { SharedField *fld; // FIXME Dot(SharedField *_fld, GenericCall &gc) : GenericCall(gc.line, gc.name, gc.sf, gc.dotnoparens), fld(_fld) {} void Dump(ostringstream &ss) const { ss << Name() << fld->name; } void TypeCheckSpecialized(TypeChecker &tc, size_t reqret); SHARED_SIGNATURE_NO_TT(Dot, TName(T_DOT), false) }; struct IsType : Unary { TypeRef giventype; IsType(const Line &ln, Node *_a, TypeRef t) : Unary(ln, _a), giventype(t) {} void Dump(ostringstream &ss) const { ss << Name() << ":" << TypeName(giventype); } CONSTVALMETHOD SHARED_SIGNATURE(IsType, TName(T_IS), false) OPTMETHOD }; struct EnumCoercion : Unary { Enum *e; EnumCoercion(const Line &ln, Node *_a, Enum *e) : Unary(ln, _a), e(e) {} void Dump(ostringstream &ss) const { ss << e->name; } CONSTVALMETHOD SHARED_SIGNATURE(EnumCoercion, e->name, false) }; struct ToLifetime : Coercion { uint64_t incref, decref; ToLifetime(const Line &ln, Node *_a, uint64_t incref, uint64_t decref) : Coercion(ln, _a), incref(incref), decref(decref) {} void Dump(ostringstream &ss) const { ss << Name() << "<" << incref << "|" << decref << ">"; } SHARED_SIGNATURE_NO_TT(ToLifetime, "lifetime change", true) }; template Node *Forward(Node *n) { if (auto t = Is(n)) return t->child; return n; } inline string DumpNode(Node &n, int indent, bool single_line) { ostringstream ss; n.Dump(ss); string s = ss.str(); auto arity = n.Arity(); if (!arity) return s; bool ml = false; auto ch = n.Children(); vector sv; size_t total = 0; for (size_t i = 0; i < arity; i++) { auto a = DumpNode(*ch[i], indent + 2, single_line); a += ":"; a += TypeName(ch[i]->exptype); if (a[0] == ' ') ml = true; total += a.length(); sv.push_back(a); } if (total > 60) ml = true; if (ml && !single_line) { s = string(indent, ' ') + "(" + s; s += "\n"; for (size_t i = 0; i < arity; i++) { if (i) s += "\n"; if (sv[i][0] != ' ') s += string(indent + 2, ' '); s += sv[i]; } return s + ")"; } else { for (size_t i = 0; i < arity; i++) s += " " + sv[i]; return "(" + s + ")"; } } } // namespace lobster #endif // LOBSTER_NODE treesheets-1.0.2/lobster/src/lobster/octree.h000066400000000000000000000121351352107072600212370ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. template struct OcTree { vector nodes; vector freelist; int world_bits; enum { OCTREE_SUBDIV = 8, // This one is kind of a given.. PARENT_INDEX = OCTREE_SUBDIV, ELEMENTS_PER_NODE = OCTREE_SUBDIV + 1, // Last is parent pointer. ROOT_INDEX = 1 // Such that index 0 means "no parent". }; OcTree(int bits) : nodes(ROOT_INDEX + ELEMENTS_PER_NODE), world_bits(bits) { for (auto &n : nodes) n.SetLeafData(0); nodes[ROOT_INDEX + OCTREE_SUBDIV].SetNodeIdx(0); // Root doesn't have a parent. } int ToParent(int i) { return i - ((i - ROOT_INDEX) % ELEMENTS_PER_NODE); } int Deref(int children) { return nodes[children + PARENT_INDEX].NodeIdx(); } void Set(const int3 &pos, T val) { int cur = ROOT_INDEX; for (auto bit = world_bits - 1; ; bit--) { auto size = 1 << bit; auto off = pos & size; auto bv = off >> bit; auto ccur = cur + dot(bv, int3(1, 2, 4)); auto oval = nodes[ccur]; if (oval == val) return; if (bit) { // Not at bottom yet. if (oval.IsLeaf()) { // Values are not equal, so we must subdivide. int ncur; T parent; parent.SetNodeIdx(ccur); if (freelist.empty()) { ncur = (int)nodes.size(); for (int i = 0; i < OCTREE_SUBDIV; i++) nodes.push_back(oval); nodes.push_back(parent); } else { ncur = freelist.back(); freelist.pop_back(); for (int i = 0; i < OCTREE_SUBDIV; i++) nodes[ncur + i] = oval; nodes[ncur + OCTREE_SUBDIV] = parent; } nodes[ccur].SetNodeIdx(ncur); cur = ncur; } else { cur = oval.NodeIdx(); } } else { // Bottom level. assert(val.IsLeaf()); nodes[ccur] = val; // Try to merge this level all the way to the top. for (int pbit = 1; pbit < world_bits; pbit++) { auto parent = Deref(cur); auto children = nodes[parent].NodeIdx(); for (int i = 1; i < OCTREE_SUBDIV; i++) { // If all 8 are the same.. if (nodes[children] != nodes[children + i]) return; } // Merge. nodes[parent] = nodes[children]; freelist.push_back(children); cur = ToParent(parent); } return; } } } pair Get(const int3 &pos) { int bit = world_bits; int i = Get(pos, bit, ROOT_INDEX); return make_pair(i, bit); } int Get(const int3 &pos, int &bit, int cur) { for (;;) { // FIXME: dedup from Set. bit--; auto off = pos & (1 << bit); auto bv = off >> bit; auto ccur = cur + dot(bv, int3(1, 2, 4)); auto oval = nodes[ccur]; if (oval.IsLeaf()) { return ccur; } else { cur = oval.NodeIdx(); } } } // This function is only needed when creating nodes without Set, as Set already merges // on the fly. T Merge(int cur = ROOT_INDEX) { for (int i = 0; i < OCTREE_SUBDIV; i++) { auto &n = nodes[cur + i]; if (!n.IsLeaf()) n = Merge(n.NodeIdx()); } T ov; ov.SetNodeIdx(cur); if (!nodes[cur].IsLeaf()) return ov; for (int i = 1; i < OCTREE_SUBDIV; i++) { if (nodes[cur] != nodes[cur + i]) return ov; } if (cur != ROOT_INDEX) { freelist.push_back(cur); ov = nodes[cur]; } return ov; } }; class OcVal { int32_t node; public: bool IsLeaf() const { return node < 0; } int32_t NodeIdx() { assert(node >= 0); return node; } void SetNodeIdx(int32_t n) { assert(n >= 0); node = n; } // leaf data is a 31-bit usigned integer. int32_t LeafData() { assert(node < 0); return node & 0x7FFFFFFF; } void SetLeafData(int32_t v) { assert(v >= 0); node = v | 0x80000000; } bool operator==(const OcVal &o) const { return node == o.node; } bool operator!=(const OcVal &o) const { return node != o.node; } }; treesheets-1.0.2/lobster/src/lobster/optimizer.h000066400000000000000000000170731352107072600220060ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. namespace lobster { struct Optimizer { Parser &parser; SymbolTable &st; TypeChecker &tc; size_t total_changes = 0; SubFunction *cursf = nullptr; Optimizer(Parser &_p, SymbolTable &_st, TypeChecker &_tc) : parser(_p), st(_st), tc(_tc) { // We don't optimize parser.root, it only contains a single call. for (auto f : parser.st.functiontable) { for (auto sf : f->overloads) { if (sf && sf->typechecked) { for (; sf; sf = sf->next) if (sf->body) { cursf = sf; auto nb = sf->body->Optimize(*this, nullptr); assert(nb == sf->body); (void)nb; } } } } LOG_INFO("optimizer: ", total_changes, " optimizations"); } void Changed() { total_changes++; } Node *Typed(TypeRef type, Lifetime lt, Node *n) { n->exptype = type; n->lt = lt; return n; } }; Node *Node::Optimize(Optimizer &opt, Node * /*parent_maybe*/) { for (size_t i = 0; i < Arity(); i++) Children()[i] = Children()[i]->Optimize(opt, this); return this; } Node *If::Optimize(Optimizer &opt, Node * /*parent_maybe*/) { // This optimzation MUST run, since it deletes untypechecked code. condition = condition->Optimize(opt, this); Value cval; if (condition->ConstVal(opt.tc, cval)) { auto &branch = cval.True() ? truepart : falsepart; auto other = cval.True() ? falsepart : truepart; auto r = branch->Optimize(opt, this); branch = nullptr; if (auto call = Is(other)) { if (!call->sf->typechecked) { // Typechecker did not typecheck this function for use in this if-then, // but neither did any other instances, so it can be removed. // Since this function is not specialized, it may be referenced by // multiple specialized parents, so we don't care if it was already // removed. call->sf->parent->RemoveSubFunction(call->sf); call->sf = nullptr; } } else if (Is(other)) { } else { assert(false); // deal with coercions. } delete this; opt.Changed(); return r; } else { truepart = truepart->Optimize(opt, this); falsepart = falsepart->Optimize(opt, this); return this; } } Node *IsType::Optimize(Optimizer &opt, Node *parent_maybe) { Value cval; child = child->Optimize(opt, this); if (ConstVal(opt.tc, cval)) { auto r = opt.Typed(exptype, LT_ANY, new IntConstant(line, cval.ival())); if (child->HasSideEffects()) { child->exptype = type_void; r = opt.Typed(exptype, LT_ANY, new Seq(line, child, r)); child = nullptr; } delete this; opt.Changed(); return r->Optimize(opt, parent_maybe); } return this; } Node *DynCall::Optimize(Optimizer &opt, Node *parent_maybe) { Node::Optimize(opt, parent_maybe); if (!sf) return this; // This optimization MUST run, to remove redundant arguments. // Note that sf is not necessarily the same as sid->type->sf, since a // single function variable may have 1 specialization per call. for (auto[i, c] : enumerate(children)) { if (i >= sf->parent->nargs()) { opt.Changed(); delete c; } } children.resize(sf->parent->nargs()); // Now convert it to a Call if possible. This also allows it to be inlined. // We rely on all these DynCalls being converted in the first pass, and only // potentially inlined in the second for this increase to not cause problems. sf->numcallers++; if (sf->parent->istype) return this; auto c = new Call(line, sf); c->children.insert(c->children.end(), children.begin(), children.end()); children.clear(); auto r = opt.Typed(exptype, lt, c); delete this; opt.Changed(); return r->Optimize(opt, parent_maybe); } Node *Call::Optimize(Optimizer &opt, Node *parent_maybe) { Node::Optimize(opt, parent_maybe); // Check if we should inline this call. // FIXME: Reduce these requirements where possible. // FIXME: currently a function called 10x whose body is only a gigantic for loop will be inlined, // because the for body does not count towards its nodes. maybe inline all fors first? // Always inline for bodies. if (!parent_maybe || typeid(*parent_maybe) != typeid(For)) { if (!sf->parent->anonymous || sf->num_returns > 1 || // Implied by anonymous, but here for clarity. vtable_idx >= 0 || sf->iscoroutine || sf->returntype->NumValues() > 1 || (sf->numcallers > 1 && sf->body->Count() >= 8)) // FIXME: configurable. return this; } auto AddToLocals = [&](const ArgVector &av) { for (auto &arg : av.v) { // We have to check if the sid already exists, since inlining the same function // multiple times in the same parent can cause this. This variable is shared // between the copies in the parent, second use overwrites the first etc. for (auto &loc : opt.cursf->locals.v) if (loc.sid == arg.sid) goto already; opt.cursf->locals.v.push_back(arg); arg.sid->sf_def = opt.cursf; already:; } }; AddToLocals(sf->args); AddToLocals(sf->locals); int ai = 0; auto list = new Inlined(line); for (auto c : children) { auto &arg = sf->args.v[ai]; list->Add(opt.Typed(type_void, LT_ANY, new Define(line, arg.sid, c))); ai++; } // TODO: triple-check this similar in semantics to what happens in CloneFunction() in the // typechecker. if (sf->numcallers <= 1) { list->children.insert(list->children.end(), sf->body->children.begin(), sf->body->children.end()); sf->body->children.clear(); bool wasremoved = sf->parent->RemoveSubFunction(sf); assert(wasremoved); (void)wasremoved; } else { for (auto c : sf->body->children) { auto nc = c->Clone(); list->children.push_back(nc); nc->Iterate([](Node *i) { if (auto call = Is(i)) call->sf->numcallers++; }); } sf->numcallers--; } // Remove single return statement pointing to function that is now gone. auto ret = Is(list->children.back()); assert(ret); if (ret->sf == sf) { assert(ret->child->exptype->NumValues() <= 1); assert(sf->num_returns <= 1); list->children.back() = ret->child; ret->child = nullptr; delete ret; } auto r = opt.Typed(exptype, sf->ltret, list); children.clear(); delete this; opt.Changed(); return r->Optimize(opt, parent_maybe); } } // namespace lobster treesheets-1.0.2/lobster/src/lobster/parser.h000066400000000000000000001542551352107072600212640ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. namespace lobster { struct Parser { NativeRegistry &natreg; Lex lex; Node *root = nullptr; SymbolTable &st; vector functionstack; vector trailingkeywordedfunctionvaluestack; struct ForwardFunctionCall { size_t maxscopelevel; GenericCall *n; bool has_firstarg; SymbolTable::WithStackElem wse; }; vector forwardfunctioncalls; bool call_noparens = false; set pakfiles; Parser(NativeRegistry &natreg, string_view _src, SymbolTable &_st, string_view _stringsource) : natreg(natreg), lex(_src, _st.filenames, _stringsource), st(_st) {} ~Parser() { delete root; } void Error(string_view err, const Node *what = nullptr) { lex.Error(err, what ? &what->line : nullptr); } void Warn(string_view warn, const Node *what = nullptr) { lex.Warn(warn, what ? &what->line : nullptr); } void Parse() { auto sf = st.ScopeStart(); st.toplevel = sf; auto &f = st.CreateFunction("__top_level_expression", ""); f.overloads.push_back(nullptr); sf->SetParent(f, f.overloads[0]); f.anonymous = true; lex.Include("stdtype.lobster"); sf->body = ParseStatements(T_ENDOFFILE); ImplicitReturn(sf); st.ScopeCleanup(); root = new Call(lex, sf); assert(forwardfunctioncalls.empty()); } List *ParseStatements(TType terminator) { auto list = new List(lex); for (;;) { ParseTopExp(list); if (lex.token == T_ENDOFINCLUDE) { st.EndOfInclude(); lex.PopIncludeContinue(); } else if (!IsNext(T_LINEFEED)) { break; } if (Either(T_ENDOFFILE, T_DEDENT)) break; } auto b = list->children.back(); if (Is(b) || Is(b) || Is(b) || Is(b)) { if (terminator == T_ENDOFFILE) list->Add(new IntConstant(lex, 0)); else Error("last expression in list can\'t be a definition"); } Expect(terminator); CleanupStatements(list); return list; } void CleanupStatements(List *list) { ResolveForwardFunctionCalls(); list->children.erase(remove_if(list->children.begin(), list->children.end(), [&](auto def) { if (auto er = Is(def)) { st.UnregisterEnum(er->e); } else if (auto sr = Is(def)) { st.UnregisterStruct(sr->udt, lex); } else if (auto fr = Is(def)) { auto f = fr->sf->parent; if (!f->anonymous) st.UnregisterFun(f); delete fr; return true; } else if (auto d = Is(def)) { for (auto p : d->sids) { auto id = p.first->id; id->static_constant = id->single_assignment && d->child->IsConstInit(); if (id->single_assignment && !id->constant && d->line.fileidx == 0) Warn("use \'let\' to declare: " + id->name, d); } } else if (auto r = Is(def)) { if (r != list->children.back()) Error("return must be last in block"); } return false; }), list->children.end()); } void ParseTopExp(List *list, bool isprivate = false) { switch(lex.token) { case T_NAMESPACE: if (st.scopelevels.size() != 1 || isprivate) Error("namespace must be used at file scope"); lex.Next(); st.current_namespace = lex.sattr; Expect(T_IDENT); break; case T_PRIVATE: if (st.scopelevels.size() != 1 || isprivate) Error("private must be used at file scope"); lex.Next(); ParseTopExp(list, true); break; case T_INCLUDE: { if (isprivate) Error("include cannot be private"); lex.Next(); if (IsNext(T_FROM)) { auto fn = lex.StringVal(); Expect(T_STR); AddDataDir(move(fn)); } else { string fn; if (lex.token == T_STR) { fn = lex.StringVal(); lex.Next(); } else { fn = lex.sattr; Expect(T_IDENT); while (IsNext(T_DOT)) { fn += "/"; fn += lex.sattr; Expect(T_IDENT); } fn += ".lobster"; } Expect(T_LINEFEED); lex.Include(fn); ParseTopExp(list); } break; } case T_STRUCT: ParseTypeDecl(true, isprivate, list); break; case T_CLASS: ParseTypeDecl(false, isprivate, list); break; case T_FUN: { lex.Next(); list->Add(ParseNamedFunctionDefinition(isprivate, nullptr)); break; } case T_ENUM: case T_ENUM_FLAGS: { bool incremental = lex.token == T_ENUM; lex.Next(); int64_t cur = incremental ? 0 : 1; auto enumname = st.MaybeNameSpace(ExpectId(), !isprivate); auto def = st.EnumLookup(enumname, lex, true); def->isprivate = isprivate; // FIXME: not used? Expect(T_COLON); Expect(T_INDENT); for (;;) { auto evname = st.MaybeNameSpace(ExpectId(), !isprivate); if (IsNext(T_ASSIGN)) { cur = lex.IntVal(); Expect(T_INT); } auto ev = st.EnumValLookup(evname, lex, true); ev->isprivate = isprivate; // FIXME: not used? ev->val = cur; ev->e = def; def->vals.emplace_back(ev); if (incremental) cur++; else cur *= 2; if (!IsNext(T_LINEFEED) || Either(T_ENDOFFILE, T_DEDENT)) break; } Expect(T_DEDENT); list->Add(new EnumRef(lex, def)); break; } case T_VAR: case T_CONST: { auto isconst = lex.token == T_CONST; lex.Next(); auto def = new Define(lex, nullptr, nullptr); for (;;) { auto idname = ExpectId(); bool withtype = lex.token == T_TYPEIN; TypeRef type = nullptr; if (lex.token == T_COLON || withtype) { lex.Next(); ParseType(type, withtype); } auto id = st.LookupDef(idname, lex, false, true, withtype); if (isconst) id->constant = true; if (isprivate) id->isprivate = true; def->sids.push_back({ id->cursid, type }); if (!IsNext(T_COMMA)) break; } if (IsNext(T_LOGASSIGN)) { for (auto p : def->sids) st.MakeLogVar(p.first->id); } else { Expect(T_ASSIGN); } def->child = ParseMultiRet(ParseOpExp()); list->Add(def); break; } default: { if (isprivate) Error("private only applies to declarations"); if (IsNextId()) { // Regular assign is handled in normal expression parsing below. if (lex.token == T_COMMA) { auto al = new AssignList(lex, Modify(IdentUseOrWithStruct(lastid))); while (IsNext(T_COMMA)) al->children.push_back(Modify(IdentUseOrWithStruct(ExpectId()))); Expect(T_ASSIGN); al->children.push_back(ParseMultiRet(ParseOpExp())); list->Add(al); break; } else { lex.Undo(T_IDENT, lastid); } } list->Add(ParseExpStat()); break; } } } void ParseTypeDecl(bool is_struct, bool isprivate, List *parent_list) { lex.Next(); auto sname = st.MaybeNameSpace(ExpectId(), !isprivate); UDT *udt = &st.StructDecl(sname, lex, is_struct); auto parse_sup = [&] () { ExpectId(); auto sup = &st.StructUse(lastid, lex); if (sup == udt) Error("can\'t inherit from: " + lastid); if (is_struct != sup->is_struct) Error("class/struct must match parent"); return sup; }; auto parse_specializers = [&] () { int i = 0; if (IsNext(T_LT)) { for (;;) { if (udt->generics.empty()) Error("too many type specializers"); udt->generics.erase(udt->generics.begin()); TypeRef type; ParseType(type, false); auto def = IsNext(T_ASSIGN) ? ParseFactor() : nullptr; for (auto &field : udt->fields.v) { if (field.genericref == i) { field.type = type; // We don't reset genericref here, because its used to know which // fields to select a specialization on. if (def) { if (field.defaultval) Error("field already has a default value"); field.defaultval = def; } } } i++; if (lex.token == T_GT) { lex.OverrideCont(false); // T_GT here should not continue the line. lex.Next(); break; } Expect(T_COMMA); } } return i; }; if (IsNext(T_ASSIGN)) { // A specialization of an existing struct auto sup = parse_sup(); udt = sup->CloneInto(udt); udt->idx = (int)st.udttable.size() - 1; udt->name = sname; if (!parse_specializers()) Error("no specialization types specified"); if (isprivate != sup->isprivate) Error("specialization must have same privacy level"); if (sup->predeclaration) Error("must specialization fully defined type"); if (udt->superclass) { // This points to a generic version of the superclass of this class. // See if we can find a matching specialization instead. // FIXME: better to move this to start of typecheck just in case more to be // declared. auto sti = udt->superclass->next; for (; sti; sti = sti->next) { for (size_t i = 0; i < sti->fields.size(); i++) if (sti->fields.v[i].type != udt->fields.v[i].type) goto fail; goto done; fail:; } done: udt->superclass = sti; // Either a match or nullptr. } } else if (Either(T_COLON, T_LT)) { // A regular struct declaration udt->isprivate = isprivate; if (IsNext(T_LT)) { for (;;) { auto id = ExpectId(); for (auto &g : udt->generics) if (g.name == id) Error("re-declaration of generic type"); udt->generics.push_back({ id }); if (IsNext(T_GT)) break; Expect(T_COMMA); } } Expect(T_COLON); if (lex.token == T_IDENT) { auto sup = parse_sup(); if (sup) { if (sup->predeclaration) sup->predeclaration = false; // Empty base class. udt->superclass = sup; udt->generics = sup->generics; for (auto &fld : sup->fields.v) { udt->fields.v.push_back(fld); } } parse_specializers(); } if (IsNext(T_INDENT)) { bool fieldsdone = false; for (;;) { if (IsNext(T_FUN)) { fieldsdone = true; parent_list->Add(ParseNamedFunctionDefinition(false, udt)); } else { if (fieldsdone) Error("fields must be declared before methods"); ExpectId(); auto &sfield = st.FieldDecl(lastid); TypeRef type = type_any; int genericref = -1; if (IsNext(T_COLON)) { genericref = ParseType(type, false, udt); } Node *defaultval = IsNext(T_ASSIGN) ? ParseExp() : nullptr; udt->fields.v.push_back(Field(&sfield, type, genericref, defaultval)); } if (!IsNext(T_LINEFEED) || Either(T_ENDOFFILE, T_DEDENT)) break; } Expect(T_DEDENT); } if (udt->fields.v.empty() && udt->is_struct) Error("structs cannot be empty"); } else { // A pre-declaration. udt->predeclaration = true; } parent_list->Add(new UDTRef(lex, udt)); } Node *ParseNamedFunctionDefinition(bool isprivate, UDT *self) { // TODO: also exclude functions from namespacing whose first arg is a type namespaced to // current namespace (which is same as !self). auto idname = st.MaybeNameSpace(ExpectId(), !isprivate && !self); if (natreg.FindNative(idname)) Error("cannot override built-in function: " + idname); return ParseFunction(&idname, isprivate, true, true, "", false, false, self); } void ImplicitReturn(SubFunction *sf) { // Anonymous functions and one-liners have an implicit return value. auto &stats = sf->body->children; if (!Is(stats.back())) { // Conversely, if named functions have no return at the end, we should // ensure any value accidentally available gets ignored and does not become a return // value. auto make_void = !sf->parent->anonymous; // All function bodies end in return, simplifying code downstream. stats.back() = new Return(stats.back()->line, stats.back(), sf, make_void); } } Node *ParseFunction(string_view *name, bool isprivate, bool parens, bool parseargs, string_view context, bool expfunval = false, bool parent_noparens = false, UDT *self = nullptr) { auto sf = st.ScopeStart(); if (parens) Expect(T_LEFTPAREN); size_t nargs = 0; auto SetArgFlags = [&](Arg &arg, bool withtype) { if (!st.IsGeneric(arg.type)) arg.flags &= ~AF_GENERIC; if (withtype) arg.flags |= AF_WITHTYPE; }; if (self) { nargs++; auto id = st.LookupDef("this", lex, false, false, true); auto &arg = sf->args.v.back(); arg.type = &self->thistype; st.AddWithStruct(arg.type, id, lex, sf); SetArgFlags(arg, true); } if (lex.token != T_RIGHTPAREN && parseargs) { for (;;) { ExpectId(); nargs++; auto id = st.LookupDef(lastid, lex, false, false, false); auto &arg = sf->args.v.back(); bool withtype = lex.token == T_TYPEIN; if (parens && (lex.token == T_COLON || withtype)) { lex.Next(); ParseType(arg.type, withtype, nullptr, nullptr, &sf->args.v.back()); if (withtype) st.AddWithStruct(arg.type, id, lex, sf); if (nargs == 1 && IsUDT(arg.type->t)) self = arg.type->udt; } SetArgFlags(arg, withtype); if (!IsNext(T_COMMA)) break; } } if (parens) Expect(T_RIGHTPAREN); sf->method_of = self; auto &f = name ? st.FunctionDecl(*name, nargs, lex) : st.CreateFunction("", context); if (name && self) { for (auto isf : f.overloads) { if (isf->method_of == self) { // FIXME: this currently disallows static overloads on the other args, that // would be nice to add. Error("method " + *name + " already declared for type: " + self->name); } } } f.overloads.push_back(nullptr); sf->SetParent(f, f.overloads.back()); if (!expfunval) { if (IsNext(T_CODOT)) { // Return type decl. ParseType(sf->fixedreturntype, false, nullptr, sf); } if (!IsNext(T_COLON)) { if (lex.token == T_IDENT || !name) Expect(T_COLON); if (f.istype || f.overloads.size() > 1) Error("redefinition of function type: " + *name); f.istype = true; sf->typechecked = true; // Any untyped args truely mean "any", they should not be specialized (we wouldn't know // which specialization that refers to). for (auto &arg : sf->args.v) { if (arg.flags & AF_GENERIC) arg.flags = AF_NONE; // No idea what the function is going to be, so have to default to borrow. arg.sid->lt = LT_BORROW; } if (sf->fixedreturntype.Null()) Error("missing return type"); sf->returntype = sf->fixedreturntype; sf->reqret = sf->returntype->NumValues(); } } if (name) { if (f.overloads.size() > 1) { // We could check here for "double declaration", but since that entails // detecting what is a legit overload or not, this is in general better left to the // type checker. if (!f.nargs()) Error("double declaration: " + f.name); for (auto [i, arg] : enumerate(sf->args.v)) { if (arg.flags & AF_GENERIC && !i) Error("first argument of overloaded function must be typed: " + f.name); } if (isprivate != f.isprivate) Error("inconsistent private annotation of multiple function implementations" " for: " + *name); } f.isprivate = isprivate; functionstack.push_back(&f); } else { f.anonymous = true; } // Parse the body. if (!f.istype) { if (expfunval) { sf->body = (new List(lex))->Add(ParseExp(parent_noparens)); } else if (IsNext(T_INDENT)) { sf->body = ParseStatements(T_DEDENT); } else { sf->body = new List(lex); sf->body->children.push_back(ParseExpStat()); CleanupStatements(sf->body); } ImplicitReturn(sf); } for (auto &arg : sf->args.v) { if (arg.sid->id->anonymous_arg) { if (name) Error("cannot use anonymous argument: " + arg.sid->id->name + " in named function: " + f.name, sf->body); if (nargs) Error("cannot mix anonymous argument: " + arg.sid->id->name + " with declared arguments in function", sf->body); } } st.ScopeCleanup(); if (name) functionstack.pop_back(); // Keep copy or arg types from before specialization. f.orig_args = sf->args; // not used for multimethods return new FunRef(lex, sf); } int ParseType(TypeRef &dest, bool withtype, UDT *udt = nullptr, SubFunction *sfreturntype = nullptr, Arg *funarg = nullptr) { switch(lex.token) { case T_INTTYPE: dest = type_int; lex.Next(); break; case T_FLOATTYPE: dest = type_float; lex.Next(); break; case T_STRTYPE: dest = type_string; lex.Next(); break; case T_COROUTINE: dest = type_coroutine; lex.Next(); break; case T_RESOURCE: dest = type_resource; lex.Next(); break; case T_LAZYEXP: { lex.Next(); if (!funarg) { Error("lazy_expression cannot be used outside function signature"); } funarg->flags = ArgFlags(funarg->flags | AF_EXPFUNVAL); return -1; } case T_IDENT: { if (udt) { for (auto [i, gen] : enumerate(udt->generics)) { if (gen.name == lex.sattr) { lex.Next(); dest = type_undefined; return (int)i; } } } auto f = st.FindFunction(lex.sattr); if (f && f->istype) { dest = &f->overloads[0]->thistype; lex.Next(); break; } auto e = st.EnumLookup(lex.sattr, lex, false); if (e) { dest = &e->thistype; lex.Next(); break; } dest = &st.StructUse(lex.sattr, lex).thistype; lex.Next(); break; } case T_LEFTBRACKET: { lex.Next(); TypeRef elem; ParseType(elem, false); Expect(T_RIGHTBRACKET); dest = st.Wrap(elem, V_VECTOR); break; } case T_VOIDTYPE: if (sfreturntype) { lex.Next(); dest = type_void; sfreturntype->reqret = 0; break; } // FALL-THRU: default: Error("illegal type syntax: " + lex.TokStr()); } if (IsNext(T_QUESTIONMARK)) { if (!IsNillable(dest->t)) Error("value types can\'t be made nilable"); dest = st.Wrap(dest, V_NIL); } if (withtype && !IsUDT(dest->t)) Error(":: must be used with a class type"); return -1; } void ParseFunArgs(List *list, bool coroutine, Node *derefarg, string_view fname = "", GenericArgs *args = nullptr, bool noparens = false) { if (derefarg) { CheckArg(args, 0, fname); list->Add(derefarg); if (IsNext(T_LEFTPAREN)) { ParseFunArgsRec(list, false, false, args, 1, fname, noparens); } } else { if (!noparens) Expect(T_LEFTPAREN); ParseFunArgsRec(list, coroutine, false, args, 0, fname, noparens); } } void ParseFunArgsRec(List *list, bool coroutine, bool needscomma, GenericArgs *args, size_t thisarg, string_view fname, bool noparens /* this call */) { if (!noparens && IsNext(T_RIGHTPAREN)) { if (call_noparens) { // This call is an arg to a call that has no parens. // Don't unnecessarily parse funvals. Means "if f(x):" parses as expected. return; } ParseTrailingFunctionValues(list, coroutine, args, thisarg, fname); return; } if (needscomma) Expect(T_COMMA); CheckArg(args, thisarg, fname); if (args && (args->GetFlags(thisarg) & AF_EXPFUNVAL)) { list->Add(ParseFunction(nullptr, false, false, false, args->GetName(thisarg), true, noparens)); } else { list->Add(ParseExp(noparens)); } if (noparens) { if (lex.token == T_COLON) ParseTrailingFunctionValues(list, coroutine, args, thisarg + 1, fname); } else { ParseFunArgsRec(list, coroutine, !noparens, args, thisarg + 1, fname, noparens); } } void CheckArg(GenericArgs *args, size_t thisarg, string_view fname) { if (args && thisarg == args->size()) Error("too many arguments passed to function " + fname); } void ParseTrailingFunctionValues(List *list, bool coroutine, GenericArgs *args, size_t thisarg, string_view fname) { if (args && thisarg + 1 < args->size()) trailingkeywordedfunctionvaluestack.push_back(args->GetName(thisarg + 1)); auto name = args && thisarg < args->size() ? args->GetName(thisarg) : ""; Node *e = nullptr; switch (lex.token) { case T_COLON: e = ParseFunction(nullptr, false, false, false, name); break; case T_IDENT: // skip if this function value starts with an ID that's equal to the parents next // keyworded function val ID, e.g. "else" in: if(..): currentcall(..) else: .. // FIXME: if you forget : after else, it is going to try to declare any following // identifier as the first arg of a new function, leading to weird errors. // Should ideally know here how many args to expect. if (trailingkeywordedfunctionvaluestack.empty() || trailingkeywordedfunctionvaluestack.back() != lex.sattr) e = ParseFunction(nullptr, false, false, true, name); break; case T_LEFTPAREN: e = ParseFunction(nullptr, false, true, true, name); break; default: break; } if (args && thisarg + 1 < args->size()) trailingkeywordedfunctionvaluestack.pop_back(); if (!e) { if (coroutine) { e = new CoClosure(lex); coroutine = false; } else { return; } } list->Add(e); CheckArg(args, thisarg, fname); thisarg++; bool islf = lex.token == T_LINEFEED; if (args && thisarg < args->size() && (lex.token == T_IDENT || islf)) { if (islf) lex.Next(); if (lex.token == T_IDENT && args->GetName(thisarg) == lex.sattr) { lex.Next(); ParseTrailingFunctionValues(list, coroutine, args, thisarg, fname); } else { lex.PushCur(); if (islf) lex.Push(T_LINEFEED); lex.Next(); } } } Node *ParseMultiRet(Node *first) { if (lex.token != T_COMMA) return first; auto list = new MultipleReturn(lex); list->Add(first); while (IsNext(T_COMMA)) { list->Add(ParseOpExp()); } return list; } Node *ParseExpStat() { if (IsNext(T_RETURN)) { Node *rv = nullptr; if (!Either(T_LINEFEED, T_DEDENT, T_FROM)) { rv = ParseMultiRet(ParseOpExp()); } else { rv = new DefaultVal(lex); } auto sf = st.toplevel; if (IsNext(T_FROM)) { if(!IsNext(T_PROGRAM)) { if (!IsNextId()) Error("return from: must be followed by function identifier or" " \"program\""); auto f = st.FindFunction(lastid); if (!f) Error("return from: not a known function"); if (f->sibf || f->overloads.size() > 1) Error("return from: function must have single implementation"); sf = f->overloads[0]; } } else { if (functionstack.size()) sf = functionstack.back()->overloads.back(); } return new Return(lex, rv, sf, false); } auto e = ParseExp(); while (IsNext(T_SEMICOLON)) { if (IsNext(T_LINEFEED)) { // specialized error for all the C-style language users Error("\';\' is not a statement terminator"); } e = new Seq(lex, e, ParseExp()); } return e; } Node *Modify(Node *e) { if (auto idr = Is(e)) idr->sid->id->Assign(lex); return e; } void CheckOpEq(Node *e) { if (!Is(e) && !Is(e) && !Is(e) && !Is(e)) Error("illegal left hand side of assignment"); Modify(e); lex.Next(); } Node *ParseExp(bool parent_noparens = false) { DS ds(call_noparens, parent_noparens); auto e = ParseOpExp(); switch (lex.token) { case T_ASSIGN: CheckOpEq(e); return new Assign(lex, e, ParseExp()); case T_PLUSEQ: CheckOpEq(e); return new PlusEq(lex, e, ParseExp()); case T_MINUSEQ: CheckOpEq(e); return new MinusEq(lex, e, ParseExp()); case T_MULTEQ: CheckOpEq(e); return new MultiplyEq(lex, e, ParseExp()); case T_DIVEQ: CheckOpEq(e); return new DivideEq(lex, e, ParseExp()); case T_MODEQ: CheckOpEq(e); return new ModEq(lex, e, ParseExp()); default: return e; } } Node *ParseOpExp(uint level = 6) { static TType ops[][4] = { { T_MULT, T_DIV, T_MOD, T_NONE }, { T_PLUS, T_MINUS, T_NONE, T_NONE }, { T_ASL, T_ASR, T_NONE, T_NONE }, { T_BITAND, T_BITOR, T_XOR, T_NONE }, { T_LT, T_GT, T_LTEQ, T_GTEQ }, { T_EQ, T_NEQ, T_NONE, T_NONE }, { T_AND, T_OR, T_NONE, T_NONE }, }; Node *exp = level ? ParseOpExp(level - 1) : ParseUnary(); TType *o = &ops[level][0]; while (Either(o[0], o[1]) || Either(o[2], o[3])) { TType op = lex.token; lex.Next(); auto rhs = level ? ParseOpExp(level - 1) : ParseUnary(); switch (op) { case T_MULT: exp = new Multiply(lex, exp, rhs); break; case T_DIV: exp = new Divide(lex, exp, rhs); break; case T_MOD: exp = new Mod(lex, exp, rhs); break; case T_PLUS: exp = new Plus(lex, exp, rhs); break; case T_MINUS: exp = new Minus(lex, exp, rhs); break; case T_ASL: exp = new ShiftLeft(lex, exp, rhs); break; case T_ASR: exp = new ShiftRight(lex, exp, rhs); break; case T_BITAND: exp = new BitAnd(lex, exp, rhs); break; case T_BITOR: exp = new BitOr(lex, exp, rhs); break; case T_XOR: exp = new Xor(lex, exp, rhs); break; case T_LT: exp = new LessThan(lex, exp, rhs); break; case T_GT: exp = new GreaterThan(lex, exp, rhs); break; case T_LTEQ: exp = new LessThanEq(lex, exp, rhs); break; case T_GTEQ: exp = new GreaterThanEq(lex, exp, rhs); break; case T_EQ: exp = new Equal(lex, exp, rhs); break; case T_NEQ: exp = new NotEqual(lex, exp, rhs); break; case T_AND: exp = new And(lex, exp, rhs); break; case T_OR: exp = new Or(lex, exp, rhs); break; default: assert(false); } } return exp; } Node *UnaryArg() { auto t = lex.token; lex.Next(); auto e = ParseUnary(); return t == T_INCR || t == T_DECR ? Modify(e) : e; } Node *ParseUnary() { switch (lex.token) { case T_MINUS: return new UnaryMinus(lex, UnaryArg()); case T_NOT: return new Not(lex, UnaryArg()); case T_NEG: return new Negate(lex, UnaryArg()); case T_INCR: return new PreIncr(lex, UnaryArg()); case T_DECR: return new PreDecr(lex, UnaryArg()); default: return ParseDeref(); } } Node *BuiltinControlClosure(Node *funval, size_t maxargs) { size_t clnargs = 0; auto fr = Is(funval); if (fr) clnargs = fr->sf->parent->nargs(); else if (!Is(funval)) Error("illegal body", funval); if (clnargs > maxargs) Error(cat("body has ", clnargs - maxargs, " parameters too many"), funval); if (Is(funval)) return funval; assert(fr); auto call = new Call(lex, fr->sf); delete fr; if (clnargs > 0) { call->Add(new ForLoopElem(lex)); if (clnargs > 1) call->Add(new ForLoopCounter(lex)); } return call; } Node *ParseFunctionCall(Function *f, NativeFun *nf, string_view idname, Node *firstarg, bool coroutine, bool noparens) { auto wse = st.GetWithStackBack(); // FIXME: move more of the code below into the type checker, and generalize the remaining // code to be as little dependent as possible on wether nf or f are available. // It should only parse args and construct a GenericCall. // We give precedence to builtins, unless we're calling a known function in a :: context. if (nf && (!f || !wse.id)) { auto nc = new GenericCall(lex, idname, nullptr, false); ParseFunArgs(nc, coroutine, firstarg, idname, &nf->args, noparens); for (auto [i, arg] : enumerate(nf->args.v)) { if (i >= nc->Arity()) { auto &type = arg.type; if (type->t == V_NIL) { nc->Add(new DefaultVal(lex)); } else { auto nargs = nc->Arity(); for (auto ol = nf->overloads; ol; ol = ol->overloads) { // Typechecker will deal with it. if (ol->args.v.size() == nargs) goto argsok; } Error("missing arg to builtin function: " + idname); } } } argsok: // Special formats for these functions, for better type checking and performance auto convertnc = [&](Node *e) { nc->children.clear(); delete nc; return e; }; if (nf->name == "if") { return convertnc(new If(lex, nc->children[0], BuiltinControlClosure(nc->children[1], 0), BuiltinControlClosure(nc->children[2], 0))); } else if (nf->name == "while") { return convertnc(new While(lex, BuiltinControlClosure(nc->children[0], 0), BuiltinControlClosure(nc->children[1], 0))); } else if (nf->name == "for") { return convertnc(new For(lex, nc->children[0], BuiltinControlClosure(nc->children[1], 2))); } return nc; } auto id = st.Lookup(idname); // If both a var and a function are in scope, the deepest scope wins. // Note: <, because functions are inside their own scope. if (f && (!id || id->scopelevel < f->scopelevel)) { if (f->istype) Error("can\'t call function type: " + f->name); auto bestf = f; for (auto fi = f->sibf; fi; fi = fi->sibf) if (fi->nargs() > bestf->nargs()) bestf = fi; auto call = new GenericCall(lex, idname, nullptr, false); if (!firstarg) firstarg = SelfArg(f, wse); ParseFunArgs(call, coroutine, firstarg, idname, &bestf->overloads.back()->args, noparens); auto nargs = call->Arity(); f = FindFunctionWithNargs(f, nargs, idname, nullptr); call->sf = f->overloads.back(); return call; } if (id) { auto dc = new DynCall(lex, nullptr, id->cursid); ParseFunArgs(dc, coroutine, firstarg); return dc; } else { auto call = new GenericCall(lex, idname, nullptr, false); ParseFunArgs(call, coroutine, firstarg); ForwardFunctionCall ffc = { st.scopelevels.size(), call, !!firstarg, wse }; forwardfunctioncalls.push_back(ffc); return call; } } IdentRef *SelfArg(const Function *f, const SymbolTable::WithStackElem &wse) { if (f->nargs()) { // If we're in the context of a withtype, calling a function that starts with an // arg of the same type we pass it in automatically. // This is maybe a bit very liberal, should maybe restrict it? for (auto sf : f->overloads) { if (wse.type == sf->args.v[0].type && sf->args.v[0].flags & AF_WITHTYPE) { if (wse.id && wse.sf->parent != f) { // Not in recursive calls. return new IdentRef(lex, wse.id->cursid); } break; } } } return nullptr; } Function *FindFunctionWithNargs(Function *f, size_t nargs, string_view idname, Node *errnode) { for (; f; f = f->sibf) if (f->nargs() == nargs) return f; Error(cat("no version of function ", idname, " takes ", nargs, " arguments"), errnode); return nullptr; } void ResolveForwardFunctionCalls() { for (auto ffc = forwardfunctioncalls.begin(); ffc != forwardfunctioncalls.end(); ) { if (ffc->maxscopelevel >= st.scopelevels.size()) { auto f = st.FindFunction(ffc->n->name); if (f) { if (!ffc->has_firstarg) { auto self = SelfArg(f, ffc->wse); if (self) ffc->n->children.insert(ffc->n->children.begin(), self); } ffc->n->sf = FindFunctionWithNargs(f, ffc->n->Arity(), ffc->n->name, ffc->n)->overloads.back(); ffc = forwardfunctioncalls.erase(ffc); continue; } else { if (st.scopelevels.size() == 1) Error("call to unknown function: " + ffc->n->name, ffc->n); // Prevent it being found in sibling scopes. ffc->maxscopelevel = st.scopelevels.size() - 1; } } ffc++; } } Node *ParseDeref() { auto n = ParseFactor(); // FIXME: it would be good to narrow the kind of factors these derefs can attach to, // since for some of them it makes no sense (e.g. function call with lambda args). for (;;) switch (lex.token) { case T_DOT: case T_CODOT: { auto op = lex.token; lex.Next(); auto idname = ExpectId(); if (op == T_CODOT) { // Here we just look up ANY var with this name, only in the typechecker can we // know if it exists inside the coroutine. Can cause error if used before // coroutine is defined, error hopefully hints at that. auto id = st.LookupAny(idname); if (!id) Error("coroutines have no variable named: " + idname); n = new CoDot(lex, n, new IdentRef(lex, id->cursid)); } else { auto fld = st.FieldUse(idname); auto f = st.FindFunction(idname); auto nf = natreg.FindNative(idname); if (fld || f || nf) { if (fld && lex.token != T_LEFTPAREN) { auto dot = new GenericCall(lex, idname, f ? f->overloads.back() : nullptr, true); dot->Add(n); n = dot; } else { n = ParseFunctionCall(f, nf, idname, n, false, false); } } else { Error("unknown field/function: " + idname); } } break; } case T_LEFTPAREN: { // Special purpose error to make this more understandable for the user. // FIXME: can remove this restriction if we make DynCall work with any node. Error("dynamic function value call must be on variable"); return n; } case T_LEFTBRACKET: { lex.Next(); n = new Indexing(lex, n, ParseExp()); Expect(T_RIGHTBRACKET); break; } case T_INCR: n = new PostIncr(lex, Modify(n)); lex.Next(); return n; case T_DECR: n = new PostDecr(lex, Modify(n)); lex.Next(); return n; case T_IS: { lex.Next(); auto is = new IsType(lex, n, TypeRef()); ParseType(is->giventype, false); return is; } default: return n; } } Node *ParseFactor() { switch (lex.token) { case T_INT: { auto i = lex.IntVal(); lex.Next(); return new IntConstant(lex, i); } case T_FLOAT: { auto f = strtod(lex.sattr.data(), nullptr); lex.Next(); return new FloatConstant(lex, f); } case T_STR: { string s = lex.StringVal(); lex.Next(); return new StringConstant(lex, s); } case T_NIL: { lex.Next(); auto n = new Nil(lex, nullptr); if (IsNext(T_COLON)) { ParseType(n->giventype, false); if (!IsNillable(n->giventype->t)) Error("illegal nil type"); n->giventype = st.Wrap(n->giventype, V_NIL); } return n; } case T_LEFTPAREN: { lex.Next(); auto n = ParseExp(); Expect(T_RIGHTPAREN); return n; } case T_LEFTBRACKET: { lex.Next(); auto constructor = new Constructor(lex, nullptr); ParseVector([this, &constructor] () { constructor->Add(this->ParseExp()); }, T_RIGHTBRACKET); if (IsNext(T_TYPEIN)) { ParseType(constructor->giventype, false); constructor->giventype = st.Wrap(constructor->giventype, V_VECTOR); } return constructor; } case T_FUN: { lex.Next(); return ParseFunction(nullptr, false, true, true, ""); } case T_COROUTINE: { lex.Next(); auto idname = ExpectId(); auto n = ParseFunctionCall(st.FindFunction(idname), nullptr, idname, nullptr, true, false); return new CoRoutine(lex, n); } case T_FLOATTYPE: case T_INTTYPE: case T_STRTYPE: case T_ANYTYPE: { // These are also used as built-in functions, so allow them to function as // identifier for calls. auto idname = lex.sattr; lex.Next(); if (lex.token != T_LEFTPAREN) Error("type used as expression"); return IdentFactor(idname); } case T_TYPEOF: { // "return", ident or type. lex.Next(); if (lex.token == T_RETURN) { lex.Next(); return new TypeOf(lex, new DefaultVal(lex)); } if (lex.token == T_IDENT) { auto id = st.Lookup(lex.sattr); if (id) { lex.Next(); return new TypeOf(lex, new IdentRef(lex, id->cursid)); } } auto tn = new TypeAnnotation(lex, TypeRef()); ParseType(tn->giventype, false); return new TypeOf(lex, tn); } case T_IDENT: { auto idname = lex.sattr; lex.Next(); return IdentFactor(idname); } case T_PAKFILE: { lex.Next(); string s = lex.StringVal(); Expect(T_STR); pakfiles.insert(s); return new StringConstant(lex, s); } case T_SWITCH: { lex.Next(); auto value = ParseExp(true); Expect(T_COLON); Expect(T_INDENT); bool have_default = false; auto cases = new List(lex); for (;;) { List *pattern = new List(lex); if (lex.token == T_DEFAULT) { if (have_default) Error("cannot have more than one default in a switch"); lex.Next(); have_default = true; } else { Expect(T_CASE); for (;;) { auto f = ParseDeref(); if (lex.token == T_DOTDOT) { lex.Next(); f = new Range(lex, f, ParseDeref()); } pattern->Add(f); if (lex.token == T_COLON) break; Expect(T_COMMA); } } auto body = BuiltinControlClosure( ParseFunction(nullptr, false, false, false, "case"), 0); cases->Add(new Case(lex, pattern, body)); if (!IsNext(T_LINEFEED)) break; if (lex.token == T_DEDENT) break; } Expect(T_DEDENT); return new Switch(lex, value, cases); } default: Error("illegal start of expression: " + lex.TokStr()); return nullptr; } } void ParseVector(const function &f, TType closing) { if (IsNext(closing)) return; assert(lex.token != T_INDENT); // Not generated inside brackets/braces. for (;;) { f(); if (!IsNext(T_COMMA) || lex.token == closing) break; } Expect(closing); } Node *IdentFactor(string_view idname) { if (IsNext(T_LEFTCURLY)) { auto &udt = st.StructUse(idname, lex); udt.constructed = true; vector exps(udt.fields.size(), nullptr); ParseVector([&] () { auto id = lex.sattr; if (IsNext(T_IDENT)) { if (IsNext(T_COLON)) { auto fld = st.FieldUse(id); auto field = udt.Has(fld); if (field < 0) Error("unknown field: " + id); if (exps[field]) Error("field initialized twice: " + id); exps[field] = ParseExp(); return; } else { lex.Undo(T_IDENT, id); } } // An initializer without a tag. Find first field without a default thats not // set yet. for (size_t i = 0; i < exps.size(); i++) { if (!exps[i] && !udt.fields.v[i].defaultval) { exps[i] = ParseExp(); return; } } // Since this struct may be pre-declared, we allow to parse more initializers // than there are fields. We will catch this in the type checker. exps.push_back(ParseExp()); }, T_RIGHTCURLY); // Now fill in defaults, check for missing fields, and construct list. auto constructor = new Constructor(lex, &udt.thistype); for (size_t i = 0; i < exps.size(); i++) { if (!exps[i]) { if (udt.fields.v[i].defaultval) exps[i] = udt.fields.v[i].defaultval->Clone(); else Error("field not initialized: " + udt.fields.v[i].id->name); } constructor->Add(exps[i]); } return constructor; } else { // If we see "f(" the "(" is the start of an argument list, but for "f (", "(" is // part of an expression of a single argument with no extra "()". // This avoids things like "f (1 + 2) * 3" ("* 3" part of the single arg) being // interpreted as "f(1 + 2) * 3" (not part of the arg). // This is benign, since single arg calls with "()" work regardless of whitespace, // and multi-arg calls with whitespace will now error on the first "," (since we // don't have C's ","-operator). auto nf = natreg.FindNative(idname); auto f = st.FindFunction(idname); auto e = st.EnumLookup(idname, lex, false); if (lex.token == T_LEFTPAREN && lex.whitespacebefore == 0) { if (e && !f && !nf) { lex.Next(); auto ec = new EnumCoercion(lex, ParseExp(), e); Expect(T_RIGHTPAREN); return ec; } return ParseFunctionCall(f, nf, idname, nullptr, false, false); } // Check for implicit variable. if (idname[0] == '_') { return new IdentRef(lex, st.LookupDef(idname, lex, true, false, false)->cursid); } auto id = st.Lookup(idname); // Check for function call without (). if (!id && (nf || f) && lex.whitespacebefore > 0) { return ParseFunctionCall(f, nf, idname, nullptr, false, true); } // Check for enum value. auto ev = st.EnumValLookup(idname, lex, false); if (ev) { auto ic = new IntConstant(lex, ev->val); ic->from = ev; return ic; } return IdentUseOrWithStruct(idname); } } Node *IdentUseOrWithStruct(string_view idname) { // Check for field reference in function with :: arguments. Ident *id = nullptr; auto fld = st.LookupWithStruct(idname, lex, id); if (fld) { auto dot = new GenericCall(lex, idname, nullptr, true); dot->Add(new IdentRef(lex, id->cursid)); return dot; } // It's a regular variable. return new IdentRef(lex, st.LookupUse(idname, lex)->cursid); } bool IsNext(TType t) { bool isnext = lex.token == t; if (isnext) lex.Next(); return isnext; } string_view lastid; bool IsNextId() { if (lex.token != T_IDENT) return false; lastid = lex.sattr; lex.Next(); return true; } string_view ExpectId() { lastid = lex.sattr; Expect(T_IDENT); return lastid; } bool Either(TType t1, TType t2) { return lex.token == t1 || lex.token == t2; } bool Either(TType t1, TType t2, TType t3) { return lex.token == t1 || lex.token == t2 || lex.token == t3; } void Expect(TType t) { if (!IsNext(t)) Error(lex.TokStr(t) + " expected, found: " + lex.TokStr()); } string DumpAll(bool onlytypechecked = false) { string s; for (auto f : st.functiontable) { for (auto sf : f->overloads) { for (; sf; sf = sf->next) { if (!onlytypechecked || sf->typechecked) { s += "FUNCTION: " + f->name + "("; for (auto &arg : sf->args.v) { s += arg.sid->id->name + ":" + TypeName(arg.type) + " "; } s += ") -> "; s += TypeName(sf->returntype); s += "\n"; if (sf->body) s += DumpNode(*sf->body, 4, false); s += "\n\n"; } } } } return s; } }; } // namespace lobster treesheets-1.0.2/lobster/src/lobster/platform.h000066400000000000000000000114761352107072600216110ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Platform independent file access: typedef int64_t (* FileLoader)(string_view absfilename, string *dest, int64_t start, int64_t len); // Call this at init to determine default folders to load stuff from. string GetMainDirFromExePath(const char *argv_0); // Then pass the result as maindir to InitPlatform. // This also initializes anything else functions in this file need. extern bool InitPlatform(string maindir, const char *auxfilepath, bool from_bundle, FileLoader loader); extern void AddDataDir(string_view path); // Any additional dirs besides the above. extern string_view ProjectDir(); extern string_view MainDir(); extern string_view StripFilePart(string_view filepath); extern const char *StripDirPart(const char *filepath); // Read all or part of a file. // To read the whole file, pass -1 for len. // To just obtain the file length but don't do any reading, pass 0 for len. // Returns file length or read length, or -1 if failed. extern int64_t LoadFile(string_view relfilename, string *dest, int64_t start = 0, int64_t len = -1, bool binary = true); // fopen based implementation of FileLoader above to pass to InitPlatform if needed. extern int64_t DefaultLoadFile(string_view absfilename, string *dest, int64_t start, int64_t len); extern FILE *OpenForWriting(string_view relfilename, bool binary); extern bool WriteFile(string_view relfilename, bool binary, string_view contents); extern bool FileExists(string_view relfilename); extern bool FileDelete(string_view relfilename); extern string SanitizePath(string_view path); extern void AddPakFileEntry(string_view pakfilename, string_view relfilename, int64_t off, int64_t len, int64_t uncompressed); extern bool ScanDir(string_view reldir, vector> &dest); extern bool ScanDirAbs(string_view absdir, vector> &dest); // Logging: enum OutputType { // Temp spam, should eventually be removed, shown only at --debug. OUTPUT_DEBUG, // Output that helps understanding what the code is doing when not under a debugger, // shown with --verbose. OUTPUT_INFO, // Non-critical issues, e.g. SDL errors. This level shown by default. OUTPUT_WARN, // Output by the Lobster code. OUTPUT_PROGRAM, // Compiler & vm errors, program terminates after this. Only thing shown at --silent. OUTPUT_ERROR, }; extern OutputType min_output_level; // Defaults to showing OUTPUT_WARN and up. extern void LogOutput(OutputType ot, const char *buf); inline void LogOutput(OutputType ot, const string &buf) { LogOutput(ot, buf.c_str()); }; template void LogOutput(OutputType ot, const Ts&... args) { if (ot >= min_output_level) LogOutput(ot, cat(args...).c_str()); } // This is to make it lazy: arguments are not constructed at all if level is too low. #define LOG_DEBUG(...) { if (min_output_level <= OUTPUT_DEBUG) \ LogOutput(OUTPUT_DEBUG, __VA_ARGS__); } #define LOG_INFO(...) { if (min_output_level <= OUTPUT_INFO) \ LogOutput(OUTPUT_INFO, __VA_ARGS__); } #define LOG_WARN(...) { if (min_output_level <= OUTPUT_WARN) \ LogOutput(OUTPUT_WARN, __VA_ARGS__); } #define LOG_PROGRAM(...) { if (min_output_level <= OUTPUT_PROGRAM) \ LogOutput(OUTPUT_PROGRAM, __VA_ARGS__); } #define LOG_ERROR(...) { if (min_output_level <= OUTPUT_ERROR) \ LogOutput(OUTPUT_ERROR, __VA_ARGS__); } // Time: extern double SecondsSinceStart(); // CPU: extern uint NumHWThreads(); extern uint NumHWCores(); // Misc: extern void ConditionalBreakpoint(bool shouldbreak); extern void CountingBreakpoint(int i = -1); extern void MakeDPIAware(); extern string GetDateTime(); extern void SetConsole(bool on); #if defined(__IOS__) || defined(__ANDROID__) || defined(__EMSCRIPTEN__) #define PLATFORM_ES3 #endif #if defined(__IOS__) || defined(__ANDROID__) #define PLATFORM_TOUCH #endif #if !defined(PLATFORM_ES3) && !defined(__APPLE__) #define PLATFORM_WINNIX #endif #if defined(_WIN32) && !defined(SKIP_SDKS) // FIXME: Also make work on Linux/OS X. #define PLATFORM_VR #define PLATFORM_STEAMWORKS #endif treesheets-1.0.2/lobster/src/lobster/polyreduce.h000066400000000000000000000161411352107072600221320ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. int polyreductionpasses = 0; float epsilon = 0.98f; float maxtricornerdot = 0.95f; inline void PolyReduce(vector &triangles, vector &verts) { // FIXME: factor out holding triangle data (face normal etc) in arrays int *vertmap = new int[verts.size()]; for (int prp = 0; prp < polyreductionpasses; prp++) { memset(vertmap, -1, verts.size() * sizeof(int)); for (size_t t = 0; t < triangles.size(); t += 3) { auto &v1 = verts[triangles[t + 0]]; auto &v2 = verts[triangles[t + 1]]; auto &v3 = verts[triangles[t + 2]]; float3 v12u = v2.pos - v1.pos; float3 v13u = v3.pos - v1.pos; float3 d3 = normalize(cross(v13u, v12u)); for (int i = 0; i < 3; i++) { if (dot(verts[triangles[t + i]].norm, d3) < epsilon) vertmap[triangles[t + i]] = -2; // not available for reduction } } for (size_t t = 0; t < triangles.size(); t += 3) { auto &v1 = verts[triangles[t + 0]]; auto &v2 = verts[triangles[t + 1]]; auto &v3 = verts[triangles[t + 2]]; float3 v12u = v2.pos - v1.pos; float3 v13u = v3.pos - v1.pos; int i1 = -1, i2 = -1, i3 = -1; for (int i = 0; i < 3; i++) { if (vertmap[triangles[t + i]] == -1) { if (i2 < 0) { if (i1 >= 0) i2 = i; else i1 = i; } } else i3 = i; } if (i3 < 0) { // all 3 flat, pick the shortest edge auto l12 = length(v12u); auto l13 = length(v13u); auto l23 = length(v2.pos - v3.pos); if (l13 < l12 && l13 < l23) { // pick 13 i2 = 2; i3 = 1; } else if (l23 < l12 && l23 < l13) { // pick 23 i1 = 1; i2 = 2; i3 = 0; } else { // pick 12 i3 = 2; } } if (i1 >= 0 && i2 >= 0 && vertmap[triangles[t + i3]] < 0 // why is this so important??? ) { int vi = triangles[t + i1]; int ovi = triangles[t + i2]; vertmap[vi] = ovi; vertmap[ovi] = vi; } } int flipped = 0; for (size_t t = 0; t < triangles.size(); t += 3) { for (int i = 0; i < 3; i++) { int vi1 = triangles[t + i]; int vi2 = triangles[t + (i + 1) % 3]; int vi3 = triangles[t + (i + 2) % 3]; if (vertmap[vi1] >= 0 && vertmap[vi1] != vi2 && vertmap[vi1] != vi3) { auto &v1 = verts[vi1]; auto &v2 = verts[vi2]; auto &v3 = verts[vi3]; float3 d3 = normalize(cross(v3.pos - v1.pos, v2.pos - v1.pos)); float3 vm = (verts[vertmap[vi1]].pos + v1.pos) / 2; float3 v12m = normalize(v2.pos - vm); float3 v13m = normalize(v3.pos - vm); float3 v23m = normalize(v3.pos - v2.pos); float3 d3m = normalize(cross(v13m, v12m)); if (dot(d3, d3m) < epsilon || dot(v12m, v13m) > maxtricornerdot || dot(-v12m, v23m) > maxtricornerdot || dot(-v23m, -v13m) > maxtricornerdot) { vertmap[vertmap[vi1]] = -1; vertmap[vi1] = -1; flipped++; } } } } //LOG_DEBUG("flipped tris: ", flipped); for (size_t t = 0; t < triangles.size(); t += 3) { int keep = -1; for (int i = 0; i < 3; i++) { int vi = triangles[t + i]; if (vertmap[vi] >= 0) { if (keep >= 0) { int kvi = triangles[t + keep]; if (vertmap[vi] != kvi) { if (length(verts[ vi].pos - verts[vertmap[ vi]].pos) < length(verts[kvi].pos - verts[vertmap[kvi]].pos)) { vertmap[vertmap[kvi]] = -1; vertmap[kvi] = -1; keep = i; } else { vertmap[vertmap[vi]] = -1; vertmap[vi] = -1; } } } else keep = i; } } } size_t writep = 0; for (size_t t = 0; t < triangles.size(); t += 3) { for (int i = 0; i < 3; i++) { int target = vertmap[triangles[t + i]]; if (target >= 0) { if (triangles[t + (i + 1) % 3] == target || triangles[t + (i + 2) % 3] == target) { writep -= i; break; } } triangles[writep++] = triangles[t + i]; } } auto polysreduced = (triangles.size() - writep) / 3; //LOG_DEBUG("reduced tris: ", polysreduced); triangles.erase(triangles.begin() + writep, triangles.end()); for (size_t t = 0; t < triangles.size(); t++) { if (vertmap[triangles[t]] >= 0 && vertmap[triangles[t]] < triangles[t]) triangles[t] = vertmap[triangles[t]]; } for (size_t i = 0; i < verts.size(); i++) if (vertmap[i] >= 0 && vertmap[i] < (int)i) { auto &v1 = verts[i]; auto &v2 = verts[vertmap[i]]; v2.pos = (v1.pos + v2.pos) / 2; v2.col = byte4((int4(v1.col) + int4(v2.col)) / 2); } RecomputeNormals(triangles, verts); if (polysreduced < 100) break; } // TODO: this also deletes verts from the bad triangle finder, but only if tri reduction is on memset(vertmap, -1, verts.size() * sizeof(int)); for (size_t t = 0; t < triangles.size(); t++) vertmap[triangles[t]]++; int ni = 0; for (size_t i = 0; i < verts.size(); i++) { if (vertmap[i] >= 0) { verts[ni] = verts[i]; vertmap[i] = ni++; } } verts.erase(verts.begin() + ni, verts.end()); for (size_t t = 0; t < triangles.size(); t++) triangles[t] = vertmap[triangles[t]]; delete[] vertmap; }treesheets-1.0.2/lobster/src/lobster/sdlincludes.h000066400000000000000000000022211352107072600222620ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #define DECLSPEC #define SDL_NO_COMPAT // hack: so we can share one include folder for SDL #ifdef __APPLE__ #ifdef __IOS__ #include "SDL_config_iphoneos.h" #else #include "SDL_config_macosx.h" #endif #else #ifdef __ANDROID__ #include "SDL_config_android.h" #else #ifdef _WIN32 #include "SDL_config_windows.h" #else #include "SDL_config.h" #endif #endif #endif #define _SDL_config_h //#define SDL_MAIN_HANDLED #include "SDL.h" //#include "SDL_main.h" treesheets-1.0.2/lobster/src/lobster/sdlinterface.h000066400000000000000000000036631352107072600224270ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // simple interface for SDL (that doesn't depend on its headers) extern string SDLInit(string_view title, const int2 &screensize, bool fullscreen, int vsync, int samples); extern void SDLRequireGLVersion(int major, int minor); extern bool SDLFrame(); extern void SDLShutdown(); extern void SDLTitle(string_view title); extern bool SDLIsMinimized(); extern void SDLWindowMinMax(int dir); extern const int2 &GetScreenSize(); extern const int2 &GetFinger(int i, bool delta); extern TimeBool8 GetKS(string_view name); extern double GetKeyTime(string_view name, int on); extern int2 GetKeyPos(string_view name, int on); extern float GetJoyAxis(int i); extern double SDLTime(); extern double SDLDeltaTime(); extern void SDLUpdateTime(double delta); extern vector &SDLGetFrameTimeLog(); extern int SDLWheelDelta(); extern bool SDLCursor(bool on); extern bool SDLGrab(bool on); extern void SDLMessageBox(string_view title, string_view msg); extern bool SDLPlaySound(string_view filename, bool sfxr, int vol = 128); extern void SDLSoundClose(); extern int64_t SDLLoadFile(string_view absfilename, string *dest, int64_t start, int64_t len); extern bool ScreenShot(string_view filename); extern void SDLTestMode(); extern int SDLScreenDPI(int screen); extern bool GraphicsFrameStart(); extern void GraphicsShutDown(); treesheets-1.0.2/lobster/src/lobster/simplex.h000066400000000000000000000015701352107072600214400ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. extern float SimplexNoise(const int octaves, const float persistence, const float scale, const float3 &v); extern float SimplexNoise(const int octaves, const float persistence, const float scale, const float4 &v); treesheets-1.0.2/lobster/src/lobster/slaballoc.h000066400000000000000000000405761352107072600217240ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Very fast, low overhead slab allocator. In the common case, each size of allocation has its own bucket (a linked list of blocks of exactly that size), meaning allocation is almost as fast as de-linking an element. It acquires new blocks by allocating a page from the system, and subdividing that page into blocks of the same size, which it then adds to the bucket all at once. Each page has a page header that keeps track of how much of the page is in use. The allocator can access the page header from any memory block because pages are allocated aligned to their sizes (by clearing the lower bits of any pointer therein). To do this alignment, many pages at once are allocated from the system, which wastes 1 page on alignment, currently representing 1% of memory. Because each page tracks the number of blocks in use, the moment any page becomes empty, it will remove all blocks therein from its bucket, and then make the page available to a different size allocation. This avoids that if at some point a lot of blocks of a certain size were allocated that they will always be allocated. There aren't a lot of realistic worst cases for this allocator. To achieve a hypothetical worst case, you'd have to: - allocate lots of objects of size N - deallocate most of them, but not all - the ones that you don't deallocate would have to each exactly keep 1 page "alive" (i.e. if N = 100, there will be 20 blocks to a page, meaning if you didn't deallocate every 20th object allocated, that would create maximum waste.) - never use objects of size N again after this. This allocator can offer major cache advantages, because objects of the same type are often allocated closer to eachother. Alloc/dealloc do NOT store the size of the object, for a savings of size_t per object. This assumes you know the size of the object when deallocating. alloc_sized/dealloc_sized instead do store the size, if a more drop-in replacement for malloc/free is desired. */ #ifndef NDEBUG // Uncomment if debugging with crtdbg functionality is required (for finding memory corruption). //#define PASSTHRUALLOC #define COLLECT_STATS #endif class SlabAlloc { enum { // Must be ^2. lower means more blocks have to go thru the traditional allocator (slower). // Higher means you may get pages with only few allocs of that unique size (memory wasted). // On 32bit, 32 means all allocations <= 256 bytes go into buckets (in increments of 8 bytes // each). MAXBUCKETS = 32, // Depends on how much you want to take from the OS at once: PAGEATONCE*PAGESIZEF // You will waste 1 page to alignment with MAXBUCKETS at 32 on a 32bit system, PAGESIZEF is // 2048, so this is 202k. PAGESATONCE = 101, // "64bit should be enough for everyone". Everything is twice as big on 64bit: alignment, // memory blocks, and pages. PTRBITS = sizeof(char *) == 4 ? 2 : 3, // Must fit 2 pointers in smallest block for doubly linked list. ALIGNBITS = PTRBITS + 1, ALIGN = 1 << ALIGNBITS, ALIGNMASK = ALIGN - 1, MAXREUSESIZE = (MAXBUCKETS - 1) * ALIGN, // The largest block will fit almost 8 times. PAGESIZEF = MAXBUCKETS * ALIGN * 8, PAGEMASK = (~(PAGESIZEF - 1)), PAGEBLOCKSIZE = PAGESIZEF * PAGESATONCE, }; struct PageHeader : DLNodeRaw { int refc; int size; char *isfree; }; inline int bucket(int s) { return (s+ALIGNMASK)>>ALIGNBITS; } inline PageHeader *ppage(const void *p) { return (PageHeader *)(((size_t)p)&PAGEMASK); } inline int numobjs(int size) { return (PAGESIZEF-sizeof(PageHeader))/size; } DLList reuse[MAXBUCKETS]; DLList freepages, usedpages; void **blocks; DLList largeallocs; #ifdef COLLECT_STATS long long stats[MAXBUCKETS]; #endif long long statbig; void putinbuckets(char *start, char *end, int b, int size) { assert((int)sizeof(DLNodeRaw) <= size); for (end -= size; start<=end; start += size) { reuse[b].InsertAfterThis((DLNodeRaw *)start); } } void newpageblocks() { // If we could get page aligned memory here, that would be even better. void **b = (void **)malloc(PAGEBLOCKSIZE+sizeof(void *)); assert(b); *b = (void *)blocks; blocks = b; b++; char *first = ((char *)ppage(b))+PAGESIZEF; for (int i = 0; irefc = 0; page->size = b*ALIGN; putinbuckets((char *)(page+1), ((char *)page)+PAGESIZEF, b, page->size); return alloc_small(page->size); } void freepage(PageHeader *page, int size) { for (char *b = (char *)(page+1); b+size<=((char *)page)+PAGESIZEF; b += size) ((DLNodeRaw *)b)->Remove(); page->Remove(); freepages.InsertAfterThis(page); } void *alloc_large(size_t size) { statbig++; DLNodeRaw *buf = (DLNodeRaw *)malloc(size + sizeof(DLNodeRaw)); largeallocs.InsertAfterThis(buf); return ++buf; } void dealloc_large(void *p) { DLNodeRaw *buf = (DLNodeRaw *)p; --buf; buf->Remove(); free(buf); } public: SlabAlloc() : blocks(nullptr), statbig(0) { for (int i = 0; irefc++; return r; #endif } void dealloc_small(void *p) { #ifdef PASSTHRUALLOC dealloc_large(p); return; #endif PageHeader *page = ppage(p); #ifndef NDEBUG memset(p, 0xBA, page->size); #endif int b = page->size >> ALIGNBITS; reuse[b].InsertAfterThis((DLNodeRaw *)p); if (!--page->refc) freepage(page, page->size); } size_t size_of_small_allocation(const void *p) { return ppage(p)->size; } template size_t size_of_small_allocation_typed(const T *p) { return size_of_small_allocation(p) / sizeof(T); } // Unlike the _small functions, these functions can deal with any size, // but require you to know the size you allocated upon deallocation. void *alloc(size_t size) { return size > MAXREUSESIZE ? alloc_large(size) : alloc_small(size); } void dealloc(void *p, size_t size) { if (size > MAXREUSESIZE) dealloc_large(p); else dealloc_small(p); } void *resize(void *p, size_t oldsize, size_t size) { void *np = alloc(size); memcpy(np, p, size>oldsize ? oldsize : size); dealloc(p, oldsize); return np; } // Versions of the above functions that track size for you, if you need drop-in free/malloc // style functionality. void *alloc_sized(size_t size) { size_t *p = (size_t *)alloc(size + sizeof(size_t)); *p++ = size; // Stores 2 sizes for big objects! return p; } void dealloc_sized(void *p) { size_t *t = (size_t *)p; size_t size = *--t; size += sizeof(size_t); dealloc(t, size); } static size_t size_of_allocation(const void *p) { return ((size_t *)p)[-1]; } template static size_t size_of_allocation_typed(const T *p) { return size_of_allocation(p) / sizeof(T); } void *resize_sized(void *p, size_t size) { void *np = alloc_sized(size); size_t oldsize = size_of_allocation(p); memcpy(np, p, size>oldsize ? oldsize : size); dealloc_sized(p); return np; } void *clone_sized(const void *p) { auto len = size_of_allocation(p); auto buf = alloc_sized(len); memcpy(buf, p, len); return buf; } // Convenient string allocation, dealloc with dealloc_sized. char *alloc_string_sized(string_view from) { auto len = from.size(); char *buf = (char *)alloc_sized(len + 1); memcpy(buf, from.data(), len); buf[len] = 0; return buf; } static size_t size_of_string(const void *p) { return size_of_allocation(p) - 1; } // Typed helpers. template T *alloc_obj_small() { // T must fit inside MAXREUSESIZE. return (T *)alloc_small(sizeof(T)); } template T *clone_obj_small(const T *from) { assert(from); auto to = (T *)alloc_small(sizeof(T)); memcpy(to, from, sizeof(T)); return to; } template T *alloc_array(size_t numelems) { return (T *)alloc(sizeof(T) * numelems); } template void dealloc_array(T *array, size_t numelems) { dealloc(array, sizeof(T) * numelems); } // Clones anything regardless of what it is, finds out size itself. void *clone_obj_small_unknown(const void *from) { assert(from); auto sz = size_of_small_allocation(from); auto to = alloc_small(sz); memcpy(to, from, sz); return to; } // Will even work with a derived class of T, assuming it was also allocated with // alloc_obj_small(). template void destruct_obj_small(T *obj) { obj->~T(); dealloc_small(obj); } // Can get size of copied vector with size_of_allocation, and deallocate with dealloc_sized. template T *vector_copy_sized(vector from) { auto buf = (T *)alloc_sized(sizeof(T) * from.size()); memcpy(buf, &from[0], sizeof(T) * from.size()); return buf; } // For diagnostics and GC. size_t count_small_allocs() { size_t sum = 0; loopdllist(usedpages, h) sum += h->refc; return sum; } bool pointer_is_in_allocator(void *p) { for (auto b = blocks; b; b = (void **)*b) { if (p > b && p <= ((char *)b) + PAGEBLOCKSIZE) return true; } return false; } vector findleaks() { vector leaks; loopdllist(usedpages, h) { h->isfree = (char *)calloc(numobjs(h->size), 1); } for (int i = 0; i < MAXBUCKETS; i++) { loopdllist(reuse[i], n) { PageHeader *page = ppage(n); page->isfree[(((char *)n) - ((char *)(page + 1))) / (i * ALIGN)] = 1; } } loopdllist(usedpages, h) { for (int i = 0; i < numobjs(h->size); i++) { if (!h->isfree[i]) { leaks.push_back(((char *)(h + 1)) + i * h->size); } } free(h->isfree); h->isfree = nullptr; } loopdllist(largeallocs, n) leaks.push_back(n + 1); return leaks; } void printstats(bool full = false) { size_t totalwaste = 0; long long totalallocs = 0; for (int i = 0; i freelist ", num, " (", waste, " k), ", stats[i], " total allocs"); } } #endif } int numfree = 0, numused = 0, numlarge = 0; loopdllist(freepages, h) numfree++; loopdllist(usedpages, h) numused++; loopdllist(largeallocs, n) numlarge++; if (full || numused || numlarge || totalallocs) { LOG_INFO("totalwaste ", totalwaste, " k, pages ", numfree, " empty / ", numused, " used, ", numlarge, " big alloc live, ", totalallocs, " total allocs made, ", statbig, " big allocs made"); } } }; /* TODO / improvements: If we could distinguish a big alloc pointer from a slab allocated one, we could read the size of the block from the pageheader, giving the functionality of the _sized() functions without their overhead to all allocations. Ways this could be done: - Cooperate with the OS to have small pages mapped to a separate virtual memory area so we can do a simple pointer comparison - Have magic numbers in the page header to distinguish it.. still not totally safe, and potentially access unused memory could be a pointer to itself. this guarantees that it can't be any other pointer. what are the chances that a float/int or other random data has the same value as the location its stored in? small, but still.. of course could up the ante by having 2 such pointers, another one at the end of the page or whatever or could up it even further by having all big allocations have a magic number at the start of their buffer (not the page), so you'd first check that, then the page header... still not fool proof and many checks means slower - Write a large block allocator that also always has page headers for the start of any allocation this means multi-page allocations can't reuse the remaining space in the last page. They also generally need to allocate their own pages, so unless you can acquire page aligned mem from the system, this would be anothe page of overhead and allocations that fit in a single page can possibly share a page (sizes roughly between MAXREUSESIZE and PAGESIZE). this category would share the page pool with the small allocs. Generally, large allocators that waste memory should make allocations that are as large as possible, then allow the client to query the size it actually got: this is useful for vectors etc that can actually use the extra space. This idea could even be useful for small allocs to some extend, especially if you start doing non-linear bucket sizes This all could be made specific to a programming language interpreter, since all large blocks are long strings or vectors, esp vectors, which make sense to make em just fill pages. - Have a bit array for all pages of the entire 32bit address space that say if it is a small page block or not. at 2K pages, there's 2^21 pages, needing 2^18 bytes, i.e. 256K Doesn't work in 64bit mode, and 256K fixed overhead is not great but doable. Accessing that 256k might be a cache issue but the bulk of those pages sit really close. Can of course increase the page size to 4K or 8K, TC-Malloc thinks everything below 32k alloc is small, so we're probably being too careful. Or if you can get aligned memory from the OS, you could make this table really small, even if our pages are smaller than the OS ones. */ treesheets-1.0.2/lobster/src/lobster/stdafx.h000066400000000000000000000046341352107072600212540ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #ifdef _WIN32 #define _CRT_SECURE_NO_WARNINGS #define _SCL_SECURE_NO_WARNINGS #define _CRTDBG_MAP_ALLOC #include #include #ifndef NDEBUG #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) #define new DEBUG_NEW #endif #include "StackWalker\StackWalkerHelpers.h" #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__has_include) && __has_include() #include #else #include #endif #include #include #include #include #include using namespace std; #include "gsl/gsl-lite.hpp" using namespace gsl; #include "flatbuffers/flatbuffers.h" typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; #ifdef nullptr #undef nullptr #endif #define nullptr nullptr // Our universally used headers. #include "wentropy.h" #include "tools.h" #include "platform.h" #include "slaballoc.h" #include "geom.h" using namespace geom; #ifdef BUILD_CONTEXT_compiled_lobster // This code is being build as part of lobster code compiled to C++, modify VM behavior // accordingly. #define VM_COMPILED_CODE_MODE #endif #ifndef LOBSTER_ENGINE // By default, build Lobster assuming it comes with the default engine. // Build systems can override this for a console-only build. #define LOBSTER_ENGINE 1 #endif treesheets-1.0.2/lobster/src/lobster/tonative.h000066400000000000000000000045321352107072600216110ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_TONATIVE #define LOBSTER_TONATIVE #include "lobster/natreg.h" namespace lobster { struct NativeGenerator { int current_block_id = -1; virtual ~NativeGenerator() {} virtual void FileStart() = 0; virtual void DeclareBlock(int id) = 0; virtual void BeforeBlocks(int start_id, string_view bytecode_buffer) = 0; virtual void FunStart(const bytecode::Function *f) = 0; virtual void BlockStart(int id) = 0; virtual void InstStart() = 0; virtual void EmitJump(int id) = 0; virtual void EmitConditionalJump(int opc, int id) = 0; virtual void EmitOperands(const char *base, const int *args, int arity, bool is_vararg) = 0; virtual void SetNextCallTarget(int id) = 0; virtual void EmitGenericInst(int opc, const int *args, int arity, bool is_vararg, int target) = 0; virtual void EmitCall(int id) = 0; virtual void EmitCallIndirect() = 0; virtual void EmitCallIndirectNull() = 0; virtual void InstEnd() = 0; virtual void BlockEnd(int id, bool already_returned, bool is_exit) = 0; virtual void CodeEnd() = 0; virtual void VTables(vector &vtables) = 0; virtual void FileEnd(int start_id, string_view bytecode_buffer) = 0; virtual void Annotate(string_view comment) = 0; }; extern string ToNative(NativeRegistry &natreg, NativeGenerator &ng, string_view bytecode_buffer); extern string ToCPP(NativeRegistry &natreg, ostringstream &ss, string_view bytecode_buffer); extern string ToWASM(NativeRegistry &natreg, vector &dest, string_view bytecode_buffer); } // namespace lobster; // Test the wasm binary writer is working as expected. void unit_test_wasm(bool full); #endif // LOBSTER_TONATIVE treesheets-1.0.2/lobster/src/lobster/tools.h000066400000000000000000000644711352107072600211300ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. template void t_memcpy(T *dest, const T *src, S n) { memcpy(dest, src, n * sizeof(T)); } template void t_memmove(T *dest, const T *src, S n) { memmove(dest, src, n * sizeof(T)); } template void ts_memcpy(T *dest, const T *src, S n) { if (n) { *dest++ = *src++; if (n > 1) { *dest++ = *src++; if (n > 2) { *dest++ = *src++; if (n > 3) { *dest++ = *src++; for (S i = 4; i < n; i++) *dest++ = *src++; } } } } } template void tsnz_memcpy(T *dest, const T *src, S n) { assert(n); *dest++ = *src++; if (n > 1) { *dest++ = *src++; if (n > 2) { *dest++ = *src++; if (n > 3) { *dest++ = *src++; for (S i = 4; i < n; i++) *dest++ = *src++; } } } } // Doubly linked list. // DLNodeRaw does not initialize nor assumes initialization, so can be used in // situations where memory is already allocated DLNodeBase is meant to be a base // class for objects that want to be held in a DLList. Unlike DLNodeRaw it is always // initialized and checks that it's being used sensibly. // These are better than std::list which doesn't have a node to inherit from, // so causes 2 allocations rather than 1 for object hierarchies. struct DLNodeRaw { DLNodeRaw *prev, *next; void Remove() { prev->next = next; next->prev = prev; } void InsertAfterThis(DLNodeRaw *o) { o->next = next; o->prev = this; next->prev = o; next = o; } void InsertBeforeThis(DLNodeRaw *o) { o->prev = prev; o->next = this; prev->next = o; prev = o; } }; struct DLNodeBase : DLNodeRaw { DLNodeBase() { prev = next = nullptr; } bool Connected() { return next && prev; } virtual ~DLNodeBase() { if (Connected()) Remove(); } void Remove() { assert(Connected()); DLNodeRaw::Remove(); next = prev = nullptr; } void InsertAfterThis(DLNodeBase *o) { assert(Connected() && !o->Connected()); DLNodeRaw::InsertAfterThis(o); } void InsertBeforeThis(DLNodeBase *o) { assert(Connected() && !o->Connected()); DLNodeRaw::InsertBeforeThis(o); } }; template struct DLList : DLNodeRaw { typedef T nodetype; DLList() { next = prev = this; } bool Empty() { return next==this; } T *Get() { assert(!Empty()); DLNodeRaw *r = next; r->Remove(); return (T *)r; } T *Next() { return (T *)next; } T *Prev() { return (T *)prev; } }; template T *Next(T *n) { return (T *)n->next; } template T *Prev(T *n) { return (T *)n->prev; } // Safe Remove on not self. #define loopdllistother(L, n) \ for (auto n = (L).Next(); n != (void *)&(L); n = Next(n)) // Safe Remove on self. #define loopdllist(L, n) \ for (auto n = (L).Next(), p = Next(n); n != (void *)&(L); (n = p),(p = Next(n))) // Safe Remove on self reverse. #define loopdllistreverse(L, n) \ for (auto n = (L).Prev(), p = Prev(n); n != (void *)&(L); (n = p),(p = Prev(n))) class MersenneTwister { const static uint N = 624; const static uint M = 397; const static uint K = 0x9908B0DFU; uint hiBit(uint u) { return u & 0x80000000U; } uint loBit(uint u) { return u & 0x00000001U; } uint loBits(uint u) { return u & 0x7FFFFFFFU; } uint mixBits(uint u, uint v) { return hiBit(u) | loBits(v); } uint state[N + 1]; uint *next; int left = -1; public: void Seed(uint seed) { uint x = (seed | 1U) & 0xFFFFFFFFU, *s = state; int j; for (left = 0, *s++ = x, j = N; --j; *s++ = (x *= 69069U) & 0xFFFFFFFFU) ; } uint Reload() { uint *p0 = state, *p2 = state + 2, *pM = state + M, s0, s1; int j; if (left < -1) Seed(4357U); left = N - 1; next = state + 1; for (s0 = state[0], s1 = state[1], j = N - M + 1; --j; s0 = s1, s1 = *p2++) *p0++ = *pM++ ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? K : 0U); for (pM = state, j = M; --j; s0 = s1, s1 = *p2++) *p0++ = *pM++ ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? K : 0U); s1 = state[0]; *p0 = *pM ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? K : 0U); s1 ^= (s1 >> 11); s1 ^= (s1 << 7) & 0x9D2C5680U; s1 ^= (s1 << 15) & 0xEFC60000U; return (s1 ^ (s1 >> 18)); } uint Random() { uint y; if (--left < 0) return (Reload()); y = *next++; y ^= (y >> 11); y ^= (y << 7) & 0x9D2C5680U; y ^= (y << 15) & 0xEFC60000U; return (y ^ (y >> 18)); } void ReSeed(uint seed) { Seed(seed); left = 0; Reload(); } }; class PCG32 { // This is apparently better than the Mersenne Twister, and its also smaller/faster! // Adapted from *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org // Licensed under Apache License 2.0 (NO WARRANTY, etc. see website). uint64_t state = 0xABADCAFEDEADBEEF; uint64_t inc = 0xDEADBABEABADD00D; public: uint32_t Random() { uint64_t oldstate = state; // Advance internal state. state = oldstate * 6364136223846793005ULL + (inc | 1); // Calculate output function (XSH RR), uses old state for max ILP. uint32_t xorshifted = uint32_t(((oldstate >> 18u) ^ oldstate) >> 27u); uint32_t rot = oldstate >> 59u; return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); } void ReSeed(uint32_t s) { state = s; inc = 0xDEADBABEABADD00D; } }; template struct RandomNumberGenerator { T rnd; void seed(uint s) { rnd.ReSeed(s); } int operator()(int max) { return rnd.Random() % max; } int operator()() { return rnd.Random(); } double rnddouble() { return rnd.Random() * (1.0 / 4294967296.0); } float rnd_float() { return (float)rnddouble(); } // FIXME: performance? float rndfloatsigned() { return (float)(rnddouble() * 2 - 1); } double n2 = 0.0; bool n2_cached = false; // Returns gaussian with stddev of 1 and mean of 0. // Box Muller method. double rnd_gaussian() { n2_cached = !n2_cached; if (n2_cached) { double x, y, r; do { x = 2.0 * rnddouble() - 1; y = 2.0 * rnddouble() - 1; r = x * x + y * y; } while (r == 0.0 || r > 1.0); double d = sqrt(-2.0 * log(r) / r); double n1 = x * d; n2 = y * d; return n1; } else { return n2; } } }; // Special case for to_string to get exact float formatting we need. template string to_string_float(T x, int decimals = -1) { // ostringstream gives more consistent cross-platform results than to_string() for floats, and // can at least be configured to turn scientific notation off. ostringstream ss; // Suppress scientific notation. ss << std::fixed; // There's no way to tell it to just output however many decimals are actually significant, // sigh. Once you turn on fixed, it will default to 5 or 6, depending on platform, for both // float and double. So we set our own more useful defaults: int default_precision = sizeof(T) == sizeof(float) ? 6 : 12; ss << std::setprecision(decimals <= 0 ? default_precision : decimals); ss << x; auto s = ss.str(); if (decimals <= 0) { // First trim whatever lies beyond the precision to avoid garbage digits. size_t max_significant = default_precision; max_significant += 2; // "0." if (s[0] == '-') max_significant++; while (s.length() > max_significant && s.back() != '.') s.pop_back(); // Now strip unnecessary trailing zeroes. while (s.back() == '0') s.pop_back(); // If there were only zeroes, keep at least 1. if (s.back() == '.') s.push_back('0'); } return s; } inline void to_string_hex(ostringstream &ss, size_t x) { ss << "0x" << std::hex << x << std::dec; } /* Accumulator: a container that is great for accumulating data like std::vector, but without the reallocation/copying and unused memory overhead. Instead stores elements as a 2-way growing list of blocks. Loses the O(1) random access time, but instead has fast block-wise iteration. Optimized to append/prepend many of T at once. Can specify a minimum growth (block size) such that small appends are also efficient */ template class Accumulator { struct Buf { Buf *next; size_t size, unused; T *Elems() { return this + 1; } }; Buf *first = nullptr, *last = nullptr, *iterator = nullptr; size_t mingrowth; size_t totalsize = 0; Buf *NewBuf(size_t _size, size_t numelems, T *elems, Buf *_next) { Buf *buf = (Buf *)malloc(sizeof(Buf) + sizeof(T) * _size); buf->size = _size; buf->unused = _size - numelems; buf->next = _next; t_memcpy(buf->Elems(), elems, numelems); return buf; } // Don't copy, create instances of this class with new preferably. Accumulator(const Accumulator &); Accumulator &operator=(const Accumulator &); public: Accumulator(size_t _mingrowth = 1024) : mingrowth(_mingrowth) {} ~Accumulator() { while (first) { Buf *buf = first->next; free(first); first = buf; } } size_t Size() { return totalsize; } void Append(const T &elem) { Append(&elem, 1); } void Append(const T *newelems, size_t amount) { totalsize += amount; // First fill up any unused space in the last block, this the common path for small appends. if (last && last->unused) { size_t fit = min(amount, last->unused); // Note: copy constructor skipped, if any. t_memcpy(last->Elems() + (last->size - last->unused), newelems, fit); last->unused -= fit; amount -= fit; } // If there are more elements left, create a new block of mingrowth or bigger size. if (amount) { size_t allocsize = max(mingrowth, amount); Buf *buf = NewBuf(allocsize, min(amount, allocsize), newelems, nullptr); if (last) last->next = buf; else last = first = buf; } } void Prepend(const T *newelems, size_t amount) { totalsize += amount; // Since Prepend is a less common operation, we don't respect mingrowth here and just // allocate a single block every time we could support mingrowth if needed, at the cost of // complicating tracking where the unused space lives. first = NewBuf(amount, amount, newelems, first); if (!last) last = first; } // Custom iterator, because it is more efficient to do this a block at a time for clients // wishing to process multiple elements at once. limitation of one iterator at a time seems // reasonable. void ResetIterator() { iterator = first; } size_t Iterate(T *&buf) { if (!iterator) return 0; size_t size = iterator->size - iterator->unused; buf = iterator->Elems(); iterator = iterator->next; return size; } // Example of iterator usage: Copy into a single linear buffer. The size of dest must equal // what Size() returns. void CopyTo(T *dest) { T *buf; size_t size; ResetIterator(); while((size = Iterate(buf))) { t_memcpy(dest, buf, size); dest += size; } } }; typedef Accumulator ByteAccumulator; // Easy "dynamic scope" helper: replace any variable by a new value, and at the end of the scope // put the old value back. template struct DS { T temp; T &dest; DS(T &_dest, const T &val) : dest(_dest) { temp = dest; dest = val; } ~DS() { dest = temp; } }; // Container that turns pointers into integers, with O(1) add/delete/get. // Robust: passing invalid integers will just return a nullptr pointer / ignore the delete // Cannot store nullptr pointers (will assert on Add) // conveniently, index 0 is never used, so can be used by the client to indicate invalid index. // TODO: Can change IntResourceManager to takes T's instead of T* by making the next field have a // special value for in-use (e.g. -2, or 0 if you skip the first field). template class IntResourceManager { struct Elem { T *t; size_t nextfree; Elem() : t(nullptr), nextfree(size_t(-1)) {} }; vector elems; size_t firstfree = size_t(-1); public: IntResourceManager() { // A nullptr item at index 0 that can never be allocated/deleted. elems.push_back(Elem()); } ~IntResourceManager() { for (auto &e : elems) if (e.t) delete e.t; } size_t Add(T *t) { // We can't store nullptr pointers as elements, because we wouldn't be able to distinguish // them from unallocated slots. assert(t); size_t i = elems.size(); if (firstfree < i) { i = firstfree; firstfree = elems[i].nextfree; } else { elems.push_back(Elem()); } elems[i].t = t; return i; } T *Get(size_t i) { return i < elems.size() ? elems[i].t : nullptr; } void Delete(size_t i) { T *e = Get(i); if (e) { delete e; elems[i].t = nullptr; elems[i].nextfree = firstfree; firstfree = i; } } size_t Range() { return elems.size(); } // If you wanted to iterate over all elements. }; // Same as IntResourceManager, but now uses pointer tagging to store the free list in-place. // Uses half the memory. Access is slightly slower, but if memory bound could still be faster // overall. Can store nullptr pointers, but not pointers with the lowest bit set (e.g. char * // pointing inside of another allocation, will assert on Add). template class IntResourceManagerCompact { vector elems; size_t firstfree = SIZE_MAX; const function deletefun; // Free slots have their lowest bit set, and represent an index (shifted by 1). bool IsFree(T *e) { return ((size_t)e) & 1; } size_t GetIndex(T *e) { return ((size_t)e) >> 1; } T *CreateIndex(size_t i) { return (T *)((i << 1) | 1); } bool ValidSlot(size_t i) { return i < elems.size() && !IsFree(elems[i]); } public: IntResourceManagerCompact(const function &_df) : deletefun(_df) { // Slot 0 is permanently blocked, so can be used to denote illegal index. elems.push_back(nullptr); } ~IntResourceManagerCompact() { ForEach(deletefun); } void ForEach(const function &f) { for (auto e : elems) if (!IsFree(e) && e) f(e); } size_t Add(T *e) { assert(!IsFree(e)); // Can't store pointers with their lowest bit set. size_t i = elems.size(); if (firstfree < i) { i = firstfree; firstfree = GetIndex(elems[i]); elems[i] = e; } else { elems.push_back(e); } return i; } T *Get(size_t i) { return ValidSlot(i) ? elems[i] : nullptr; } void Delete(size_t i) { if (ValidSlot(i) && i) { T *&e = elems[i]; if (e) deletefun(e); e = CreateIndex(firstfree); firstfree = i; } } size_t Range() { return elems.size(); } // If you wanted to iterate over all elements. }; /* // From: http://average-coder.blogspot.com/2012/07/python-style-range-loops-in-c.html template class range_iterator : public std::iterator{ public: range_iterator(const T &item) : item(item) {} // Dereference, returns the current item. const T &operator*() { return item; } // Prefix. range_iterator &operator++() { ++item; return *this; } // Postfix. range_iterator &operator++(int) { range_iterator range_copy(*this); ++item; return range_copy; } // Compare internal item bool operator==(const range_iterator &rhs) { return item == rhs.item; } // Same as above bool operator!=(const range_iterator &rhs) { return !(*this == rhs); } private: T item; }; template class range_wrapper { public: range_wrapper(const T &r_start, const T &r_end) : r_start(r_start), r_end(r_end) {} range_iterator begin() { return range_iterator(r_start); } range_iterator end() { return range_iterator(r_end); } private: T r_start, r_end; }; // Returns a range_wrapper containing the range [start, end) template range_wrapper range(const T &start, const T &end) { return range_wrapper(start, end); } // Returns a range_wrapper containing the range [T(), end) template range_wrapper range(const T &end) { return range_wrapper(T(), end); } */ // From: http://reedbeta.com/blog/python-like-enumerate-in-cpp17/ template ())), typename = decltype(std::end(std::declval()))> constexpr auto enumerate(T && iterable) { struct iterator { size_t i; TIter iter; bool operator != (const iterator & other) const { return iter != other.iter; } void operator ++ () { ++i; ++iter; } auto operator * () const { return std::tie(i, *iter); } }; struct iterable_wrapper { T iterable; auto begin() { return iterator{ 0, std::begin(iterable) }; } auto end() { return iterator{ 0, std::end(iterable) }; } }; return iterable_wrapper{ std::forward(iterable) }; } // --- Reversed iterable template struct reversion_wrapper { T& iterable; }; template auto begin(reversion_wrapper w) { return rbegin(w.iterable); } template auto end(reversion_wrapper w) { return rend(w.iterable); } template reversion_wrapper reverse(T &&iterable) { return { iterable }; } // Stops a class from being accidental victim to default copy + destruct twice problem. class NonCopyable { NonCopyable(const NonCopyable&); const NonCopyable& operator=(const NonCopyable&); protected: NonCopyable() {} //virtual ~NonCopyable() {} }; // This turns a sequence of booleans (the current value, and the values it had before) into a // single number: // 0: "became false": false, but it was true before. // 1: "became true": true, but it was false before. // >1: "still true": true, and was already true. Number indicates how many times it has been true // in sequence. // <0: "still false": false, and false before it. Negative number indicates how many times it has // been false. // >=1: "true": currently true, regardless of history. // <=0: "false": currently false, regardless of history. // This is useful for detecting state changes and acting on them, such as for input device buttons. // T must be a signed integer type. Bigger types means more history before it clamps. // This is nicer than a bitfield, because basic checks like "became true" are simpler, it encodes // the history in a more human readable way, and it can encode a way longer history in the same // bits. template class TimeBool { T step; enum { SIGN_BIT = 1 << ((sizeof(T) * 8) - 1), HIGHEST_VALUE_BIT = SIGN_BIT >> 1 }; // This encodes 2 booleans into 1 number. static T Step(bool current, bool before) { return T(current) * 2 - T(!before); } TimeBool(T _step) : step(_step) {} public: TimeBool() : step(-HIGHEST_VALUE_BIT) {} TimeBool(bool current, bool before) : step(Step(current, before)) {} bool True() { return step >= 1; } bool False() { return step <= 0; } bool BecameTrue() { return step == 1; } bool BecameFalse() { return step == 0; } bool StillTrue() { return step > 1; } bool StillFalse() { return step < 0; } T Step() { return step; } T Sign() { return True() * 2 - 1; } // This makes one time step, retaining the current value. // It increases the counter, i.e. 2 -> 3 or -1 -> -2, indicating the amount of steps it has // been true. Only allows this to increase to the largest power of 2 that fits inside T, e.g. // 64 and -64 for a char. void Advance() { // Increase away from 0 if highest 2 bits are equal. if ((step & HIGHEST_VALUE_BIT) == (step & SIGN_BIT)) step += Sign(); } // Sets new value, assumes its different from the one before (wipes history). void Set(bool newcurrent) { step = Step(newcurrent, True()); } void Update(bool newcurrent) { Advance(); Set(newcurrent); } // Previous state. // This gives accurate history for the "still true/false" values, but for "became true/false" // it assumes the history is "still false/true" as opposed to "became false/true" (which is // also possible). This is typically desirable, and usually the difference doesn't matter. TimeBool Back() { return TimeBool(step - Sign() * (step & ~1 ? 1 : HIGHEST_VALUE_BIT)); } }; typedef TimeBool TimeBool8; inline uint FNV1A(string_view s) { uint hash = 0x811C9DC5; for (auto c : s) { hash ^= (uchar)c; hash *= 0x01000193; } return hash; } // dynamic_cast succeeds on both the given type and any derived types, which is frequently // undesirable. "is" only succeeds on the exact type given, and is cheaper. It also defaults // to pointer arguments. template T *Is(U *o) { return typeid(T) == typeid(*o) ? static_cast(o) : nullptr; } template const T *Is(const U *o) { return typeid(T) == typeid(*o) ? static_cast(o) : nullptr; } template T *Is(U &o) { return typeid(T) == typeid(o) ? static_cast(&o) : nullptr; } template const T *Is(const U &o) { return typeid(T) == typeid(o) ? static_cast(&o) : nullptr; } template T *AssertIs(U *o) { assert(typeid(T) == typeid(*o)); return static_cast(o); } template const T *AssertIs(const U *o) { assert(typeid(T) == typeid(*o)); return static_cast(o); } inline int PopCount(uint32_t val) { #ifdef _WIN32 return (int)__popcnt(val); #else return __builtin_popcount(val); #endif } inline int PopCount(uint64_t val) { #ifdef _WIN32 #ifdef _WIN64 return (int)__popcnt64(val); #else return (int)(__popcnt((uint)val) + __popcnt((uint)(val >> 32))); #endif #else return __builtin_popcountll(val); #endif } // string & string_view helpers. inline string operator+(string_view a, string_view b) { string r; r.reserve(a.size() + b.size()); r += a; r += b; return r; } inline void cat_helper(stringstream &) {} template void cat_helper(stringstream &ss, const T &t, const Ts&... args) { ss << t; cat_helper(ss, args...); } template string cat(const Ts&... args) { stringstream ss; cat_helper(ss, args...); return ss.str(); } // This method is in C++20, but quite essential. inline bool starts_with(string_view sv, string_view start) { return start.size() <= sv.size() && sv.substr(0, start.size()) == start; } // Efficient passing of string_view to old APIs wanting a null-terminated // const char *: only go thru a string if not null-terminated already, which is // often the case. // NOTE: uses static string, so to call twice inside the same statement supply // template args <0>, <1> etc. template const char *null_terminated(string_view sv) { if (!sv.data()[sv.size()]) return sv.data(); static string temp; temp = sv; return temp.data(); } template T parse_int(string_view sv, int base = 10, char **end = nullptr) { // This should be using from_chars(), which apparently is not supported by // gcc/clang yet :( return (T)strtoll(null_terminated(sv), end, base); } // Strict aliasing safe memory reading and writing. // memcpy with a constant size is replaced by a single instruction in VS release mode, and for // clang/gcc always. template T ReadMem(const void *p) { T dest; memcpy(&dest, p, sizeof(T)); return dest; } template T ReadMemInc(const uchar *&p) { T dest = ReadMem(p); p += sizeof(T); return dest; } template void WriteMemInc(uchar *&dest, const T &src) { memcpy(dest, &src, sizeof(T)); dest += sizeof(T); } // Enum operators. #define DEFINE_BITWISE_OPERATORS_FOR_ENUM(T) \ inline T operator~ (T a) { return (T)~(int)a; } \ inline T operator| (T a, T b) { return (T)((int)a | (int)b); } \ inline T operator& (T a, T b) { return (T)((int)a & (int)b); } \ inline T &operator|= (T &a, T b) { return (T &)((int &)a |= (int)b); } \ inline T &operator&= (T &a, T b) { return (T &)((int &)a &= (int)b); } #ifndef DISABLE_EXCEPTION_HANDLING #define USE_EXCEPTION_HANDLING #endif #ifdef USE_EXCEPTION_HANDLING #define THROW_OR_ABORT(X) { throw (X); } #else #define THROW_OR_ABORT(X) { printf("%s\n", (X).c_str()); abort(); } #endif inline void unit_test_tools() { assert(strcmp(null_terminated<0>(string_view("aa", 1)), null_terminated<1>(string_view("bb", 1))) != 0); } treesheets-1.0.2/lobster/src/lobster/ttypes.h000066400000000000000000000064541352107072600213150ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_TTYPES #define LOBSTER_TTYPES namespace lobster { #define TTYPES_LIST \ TOK(T_NONE, "invalid_token") \ TOK(T_PLUS, "+") \ TOK(T_MINUS, "-") \ TOK(T_MULT, "*") \ TOK(T_DIV, "/") \ TOK(T_MOD, "%") \ TOK(T_PLUSEQ, "+=") \ TOK(T_MINUSEQ, "-=") \ TOK(T_MULTEQ, "*=") \ TOK(T_DIVEQ, "/=") \ TOK(T_MODEQ, "%=") \ TOK(T_AND, "and") \ TOK(T_OR, "or") \ TOK(T_NOT, "not") \ TOK(T_INCR, "++") \ TOK(T_DECR, "--") \ TOK(T_EQ, "==") \ TOK(T_NEQ, "!=") \ TOK(T_LT, "<") \ TOK(T_GT, ">") \ TOK(T_LTEQ, "<=") \ TOK(T_GTEQ, ">=") \ TOK(T_BITAND, "&") \ TOK(T_BITOR, "|") \ TOK(T_XOR, "^") \ TOK(T_NEG, "~") \ TOK(T_ASL, "<<") \ TOK(T_ASR, ">>") \ TOK(T_ASSIGN, "=") \ TOK(T_LOGASSIGN, "?=") \ TOK(T_DOT, ".") \ TOK(T_DOTDOT, "..") \ TOK(T_CODOT, "->") \ TOK(T_INT, "integer literal") \ TOK(T_FLOAT, "floating point literal") \ TOK(T_STR, "string literal") \ TOK(T_NIL, "nil") \ TOK(T_DEFAULTVAL, "default value") \ TOK(T_IDENT, "identifier") \ TOK(T_CLASS, "class") \ TOK(T_FUN, "def") \ TOK(T_RETURN, "return") \ TOK(T_IS, "is") \ TOK(T_TYPEOF, "typeof") \ TOK(T_COROUTINE, "coroutine") \ TOK(T_LINEFEED, "linefeed") \ TOK(T_ENDOFINCLUDE, "end of include") \ TOK(T_ENDOFFILE, "end of file") \ TOK(T_INDENT, "indentation") \ TOK(T_DEDENT, "de-indentation") \ TOK(T_LEFTPAREN, "(") \ TOK(T_RIGHTPAREN, ")") \ TOK(T_LEFTBRACKET, "[") \ TOK(T_RIGHTBRACKET, "]") \ TOK(T_LEFTCURLY, "{") \ TOK(T_RIGHTCURLY, "}") \ TOK(T_SEMICOLON, ";") \ TOK(T_AT, "@") \ TOK(T_QUESTIONMARK, "?") \ TOK(T_COMMA, ",") \ TOK(T_COLON, ":") \ TOK(T_TYPEIN, "::") \ TOK(T_STRUCT, "struct") \ TOK(T_INCLUDE, "include") \ TOK(T_INTTYPE, "int") \ TOK(T_FLOATTYPE, "float") \ TOK(T_STRTYPE, "string") \ TOK(T_ANYTYPE, "any") \ TOK(T_VOIDTYPE, "void") \ TOK(T_LAZYEXP, "lazy_expression") \ TOK(T_FROM, "from") \ TOK(T_PROGRAM, "program") \ TOK(T_PRIVATE, "private") \ TOK(T_RESOURCE, "resource") \ TOK(T_ENUM, "enum") \ TOK(T_ENUM_FLAGS, "enum_flags") \ TOK(T_VAR, "var") \ TOK(T_CONST, "let") \ TOK(T_PAKFILE, "pakfile") \ TOK(T_SWITCH, "switch") \ TOK(T_CASE, "case") \ TOK(T_DEFAULT, "default") \ TOK(T_NAMESPACE, "namespace") enum TType { #define TOK(ENUM, STR) ENUM, TTYPES_LIST #undef TOK }; inline const char *TName(TType t) { static const char *names[] = { #define TOK(ENUM, STR) STR, TTYPES_LIST #undef TOK }; return names[t]; } } // namespace lobster #endif // LOBSTER_TTYPES treesheets-1.0.2/lobster/src/lobster/typecheck.h000066400000000000000000003600611352107072600217410ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. namespace lobster { struct LValContext { // For now, only: ident ( . field )*. const SpecIdent *sid; vector derefs; LValContext(const Node &n) { auto t = &n; while (auto dot = Is(t)) { derefs.insert(derefs.begin(), dot->fld); t = dot->children[0]; } auto idr = Is(t); sid = idr ? idr->sid : nullptr; } bool IsValid() { return sid; } bool DerefsEqual(const LValContext &o) { if (derefs.size() != o.derefs.size()) return false; for (auto &shf : derefs) if (shf != o.derefs[&shf - &derefs[0]]) return false; return true; } bool IsPrefix(const LValContext &o) { if (sid != o.sid || derefs.size() < o.derefs.size()) return false; for (auto &shf : o.derefs) if (shf != derefs[&shf - &o.derefs[0]]) return false; return true; } string Name() { auto s = sid ? sid->id->name : ""; for (auto &shf : derefs) { s += "."; s += shf->name; } return s; } }; struct FlowItem : LValContext { TypeRef old, now; FlowItem(const Node &n, TypeRef type) : LValContext(n), old(n.exptype), now(type) {} }; struct Borrow : LValContext { int refc = 1; // Number of outstanding borrowed values. While >0 can't assign. Borrow(const Node &n) : LValContext(n) {} }; struct TypeChecker { Parser &parser; SymbolTable &st; struct Scope { SubFunction *sf; const Node *call_context; }; vector scopes, named_scopes; vector flowstack; vector borrowstack; TypeChecker(Parser &_p, SymbolTable &_st, size_t retreq) : parser(_p), st(_st) { // FIXME: this is unfriendly. if (!st.RegisterDefaultTypes()) TypeError("cannot find standard types (from stdtype.lobster)", *parser.root); for (auto &udt : st.udttable) { if (udt->generics.empty()) { // NOTE: all users of sametype will only act on it if it is numeric, since // otherwise it would a scalar field to become any without boxing. // Much of the implementation relies on these being 2-4 component vectors, so // deny this functionality to any other structs. if (udt->fields.size() >= 2 && udt->fields.size() <= 4) { udt->sametype = udt->fields.v[0].type; for (size_t i = 1; i < udt->fields.size(); i++) { // Can't use Union here since it will bind variables, use simplified alternative: if (!ExactType(udt->fields.v[i].type, udt->sametype)) { udt->sametype = type_undefined; break; } } } // Update the type to the correct struct type. if (udt->is_struct) { for (auto &field : udt->fields.v) { if (IsRefNil(field.type->t)) { udt->hasref = true; break; } } const_cast(udt->thistype.t) = udt->hasref ? V_STRUCT_R : V_STRUCT_S; } } if (udt->superclass) { // If this type has fields inherited from the superclass that refer to the // superclass, make it refer to this type instead. There may be corner cases where // this is not what you want, but generally you do. for (auto &field : make_span(udt->fields.v.data(), udt->superclass->fields.v.size())) { PromoteStructIdx(field.type, udt->superclass, udt); } } for (auto u = udt; u; u = u->superclass) u->subudts.push_back(udt); } AssertIs(parser.root)->sf->reqret = retreq; TT(parser.root, retreq, LT_KEEP); AssertIs(parser.root); CleanUpFlow(0); assert(borrowstack.empty()); assert(scopes.empty()); assert(named_scopes.empty()); Stats(); } // Needed for any sids in cloned code. void UpdateCurrentSid(SpecIdent *&sid) { sid = sid->Current(); } void RevertCurrentSid(SpecIdent *&sid) { sid->Current() = sid; } void PromoteStructIdx(TypeRef &type, const UDT *olds, const UDT *news) { auto u = type; while (u->Wrapped()) u = u->Element(); if (IsUDT(u->t) && u->udt == olds) type = PromoteStructIdxRec(type, news); } TypeRef PromoteStructIdxRec(TypeRef type, const UDT *news) { return type->Wrapped() ? st.Wrap(PromoteStructIdxRec(type->sub, news), type->t) : &news->thistype; } string TypedArg(const GenericArgs &args, size_t i, bool withtype = true) { string s; s += args.GetName(i); if (args.GetType(i)->t != V_ANY && withtype) s += ":" + TypeName(args.GetType(i)); return s; } string Signature(const GenericArgs &args, bool withtype = true) { string s = "("; for (size_t i = 0; i < args.size(); i++) { if (i) s += ", "; s += TypedArg(args, i, withtype); } return s + ")"; } string Signature(const UDT &udt) { return udt.name + Signature(udt.fields); } string Signature(const SubFunction &sf, bool withtype = true) { return sf.parent->name + Signature(sf.args, withtype); } string Signature(const NativeFun &nf) { return nf.name + Signature(nf.args); } string SignatureWithFreeVars(const SubFunction &sf, set *already_seen, bool withtype = true) { string s = Signature(sf, withtype) + " { "; for (auto [i, freevar] : enumerate(sf.freevars.v)) { if (freevar.type->t != V_FUNCTION && !freevar.sid->id->static_constant && (!already_seen || already_seen->find(freevar.sid->id) == already_seen->end())) { s += TypedArg(sf.freevars, i) + " "; if (already_seen) already_seen->insert(freevar.sid->id); } } s += "}"; return s; } string ArgName(size_t i) { switch (i) { case 0: return "1st"; case 1: return "2nd"; case 2: return "3rd"; default: return cat(i + 1, "th"); } } string_view NiceName(const Node &n) { if (auto call = Is(n)) if (!call->sf->parent->anonymous) return call->sf->parent->name; if (auto idr = Is(n)) return idr->sid->id->name; return n.Name(); } void TypeError(string_view required, TypeRef got, const Node &n, string_view argname = "", string_view context = "") { TypeError(cat("\"", (context.size() ? context : NiceName(n)), "\" ", (argname.size() ? "(" + argname + " argument) " : ""), "requires type: ", required, ", got: ", TypeName(got)), n); } void TypeError(string err, const Node &n) { set already_seen; if (!scopes.empty()) for (auto scope : reverse(scopes)) { if (scope.sf == st.toplevel) continue; err += "\n in " + parser.lex.Location(scope.call_context->line) + ": "; err += SignatureWithFreeVars(*scope.sf, &already_seen); for (auto dl : scope.sf->body->children) { if (auto def = Is(dl)) { for (auto p : def->sids) { err += ", " + p.first->id->name + ":" + TypeName(p.first->type); } } } } parser.Error(err, &n); } void NoStruct(const Node &n, string_view context) { if (IsStruct(n.exptype->t)) TypeError("struct value cannot be used in: " + context, n); } void NatCallError(string_view errstr, const NativeFun *nf, const NativeCall &callnode) { auto err = errstr + nf->name; err += "\n got:"; for (auto c : callnode.children) { err += " " + TypeName(c->exptype); } for (auto cnf = nf->first; cnf; cnf = cnf->overloads) { err += "\n overload: " + Signature(*cnf); } TypeError(err, callnode); } TypeRef NewTypeVar() { auto var = st.NewType(); *var = Type(V_VAR); // Vars store a cycle of all vars its been unified with, starting with itself. var->sub = var; return var; } TypeRef NewNilTypeVar() { auto nil = st.NewType(); *nil = Type(V_NIL); nil->sub = &*NewTypeVar(); return nil; } TypeRef NewTuple(size_t sz) { auto type = st.NewType(); *type = Type(V_TUPLE); type->tup = new vector(sz); st.tuplelist.push_back(type->tup); return type; } void UnifyVar(TypeRef type, TypeRef hasvar) { // Typically Type is const, but this is the one place we overwrite them. // Type objects that are V_VAR are seperate heap instances, so overwriting them has no // side-effects on non-V_VAR Type instances. assert(hasvar->t == V_VAR); if (type->t == V_VAR) { // Combine two cyclic linked lists.. elegant! swap((Type *&)hasvar->sub, (Type *&)type->sub); } else { auto v = hasvar; do { // Loop thru all vars in unification cycle. auto next = v->sub; *(Type *)&*v = *type; // Overwrite Type struct! v = next; } while (&*v != &*hasvar); // Force TypeRef pointer comparison. } } bool ConvertsTo(TypeRef type, TypeRef sub, bool coercions, bool unifications = true) { if (sub == type) return true; if (type->t == V_VAR) { if (unifications) UnifyVar(sub, type); return true; } switch (sub->t) { case V_VOID: return coercions; case V_VAR: UnifyVar(type, sub); return true; case V_FLOAT: return type->t == V_INT && coercions; case V_INT: return (type->t == V_TYPEID && coercions) || (type->t == V_INT && !sub->e); case V_STRING: return coercions && IsRuntimePrintable(type->t); case V_FUNCTION: return type->t == V_FUNCTION && !sub->sf; case V_NIL: return (type->t == V_NIL && ConvertsTo(type->Element(), sub->Element(), false, unifications)) || (!type->Numeric() && type->t != V_VOID && !IsStruct(type->t) && ConvertsTo(type, sub->Element(), false, unifications)) || (type->Numeric() && // For builtins. ConvertsTo(type, sub->Element(), false, unifications)); case V_VECTOR: return (type->t == V_VECTOR && ConvertsTo(type->Element(), sub->Element(), false, unifications)); case V_CLASS: return type->t == V_CLASS && st.SuperDistance(sub->udt, type->udt) >= 0; case V_STRUCT_R: case V_STRUCT_S: return type->t == sub->t && type->udt == sub->udt; case V_COROUTINE: return type->t == V_COROUTINE && (sub->sf == type->sf || (!sub->sf && type->sf && ConvertsTo(type->sf->coresumetype, NewNilTypeVar(), false))); case V_TUPLE: return type->t == V_TUPLE && ConvertsToTuple(*type->tup, *sub->tup); default: return false; } } bool ConvertsToTuple(const vector &ttup, const vector &stup) { if (ttup.size() != stup.size()) return false; for (auto [i, te] : enumerate(ttup)) if (!ConvertsTo(te.type, stup[i].type, false)) return false; return true; } TypeRef Union(TypeRef at, TypeRef bt, bool coercions, const Node *err) { if (ConvertsTo(at, bt, coercions)) return bt; if (ConvertsTo(bt, at, coercions)) return at; if (at->t == V_VECTOR && bt->t == V_VECTOR) { auto et = Union(at->Element(), bt->Element(), false, err); return st.Wrap(et, V_VECTOR); } if (at->t == V_CLASS && bt->t == V_CLASS) { auto sstruc = st.CommonSuperType(at->udt, bt->udt); if (sstruc) return &sstruc->thistype; } if (err) TypeError(cat(TypeName(at), " and ", TypeName(bt), " have no common supertype"), *err); return type_undefined; } bool ExactType(TypeRef a, TypeRef b) { return a == b; // Not inlined for documentation purposes. } void MakeString(Node *&a, Lifetime orig_recip) { assert(a->exptype->t != V_STRING); DecBorrowers(a->lt, *a); a = new ToString(a->line, a); a->exptype = type_string; a->lt = LT_KEEP; // Make sure whatever lifetime a was typechecked at is preserved. AdjustLifetime(a, orig_recip); } void MakeBool(Node *&a) { DecBorrowers(a->lt, *a); if (a->exptype->t == V_INT) return; a = new ToBool(a->line, a); a->exptype = &st.default_bool_type->thistype; a->lt = LT_ANY; } void MakeInt(Node *&a) { auto ti = new ToInt(a->line, a); ti->exptype = type_int; ti->lt = a->lt; a = ti; } void MakeFloat(Node *&a) { auto tf = new ToFloat(a->line, a); tf->exptype = type_float; tf->lt = a->lt; a = tf; } void MakeLifetime(Node *&n, Lifetime lt, uint64_t incref, uint64_t decref) { auto tlt = new ToLifetime(n->line, n, incref, decref); tlt->exptype = n->exptype; tlt->lt = lt; n = tlt; } void StorageType(TypeRef type, const Node &context) { if (type->HasValueType(V_VOID)) TypeError("cannot store value of type void", context); } void SubTypeLR(TypeRef sub, BinOp &n) { SubType(n.left, sub, "left", n); SubType(n.right, sub, "right", n); } void SubType(Node *&a, TypeRef sub, string_view argname, const Node &context) { SubType(a, sub, argname, NiceName(context)); } void SubType(Node *&a, TypeRef sub, string_view argname, string_view context) { if (ConvertsTo(a->exptype, sub, false)) return; switch (sub->t) { case V_FLOAT: if (a->exptype->t == V_INT) { MakeFloat(a); return; } break; case V_INT: if (a->exptype->t == V_TYPEID) { MakeInt(a); return; } break; case V_FUNCTION: if (a->exptype->IsFunction() && sub->sf) { // See if these functions can be made compatible. Specialize and typecheck if // needed. auto sf = a->exptype->sf; auto ss = sub->sf; if (!ss->parent->istype) TypeError("dynamic function value can only be passed to declared " "function type", *a); if (sf->args.v.size() != ss->args.v.size()) break; for (auto [i, arg] : enumerate(sf->args.v)) { // Specialize to the function type, if requested. if (!sf->typechecked && arg.flags & AF_GENERIC) { arg.type = ss->args.v[i].type; } // Note this has the args in reverse: function args are contravariant. if (!ConvertsTo(ss->args.v[i].type, arg.type, false)) goto error; // This function must be compatible with all other function values that // match this type, so we fix lifetimes to LT_BORROW. // See typechecking of istype calls. arg.sid->lt = LT_BORROW; } if (sf->typechecked) { if (sf->reqret != ss->reqret) goto error; } else { sf->reqret = ss->reqret; } sf->isdynamicfunctionvalue = true; TypeCheckFunctionDef(*sf, *sf->body); // Covariant again. if (sf->returntype->NumValues() != ss->returntype->NumValues() || !ConvertsTo(sf->returntype, ss->returntype, false)) break; // Parser only parses one ret type for function types. assert(ss->returntype->NumValues() <= 1); return; } break; default: ; } error: TypeError(TypeName(sub), a->exptype, *a, argname, context); } void SubTypeT(TypeRef type, TypeRef sub, const Node &n, string_view argname, string_view context = {}) { if (!ConvertsTo(type, sub, false)) TypeError(TypeName(sub), type, n, argname, context); } bool MathCheckVector(TypeRef &type, Node *&left, Node *&right) { TypeRef ltype = left->exptype; TypeRef rtype = right->exptype; // Special purpose check for vector * scalar etc. if (ltype->t == V_STRUCT_S && rtype->Numeric()) { auto etype = ltype->udt->sametype; if (etype->Numeric()) { if (etype->t == V_INT) { // Don't implicitly convert int vectors to float. if (rtype->t == V_FLOAT) return false; } else { if (rtype->t == V_INT) SubType(right, type_float, "right", *right); } type = <ype->udt->thistype; return true; } } return false; } const char *MathCheck(TypeRef &type, BinOp &n, bool &unionchecked, bool typechangeallowed) { if (Is(&n) || Is(&n)) { if (type->t != V_INT) return "int"; } else { if (!type->Numeric() && type->t != V_VECTOR && !IsUDT(type->t)) { if (MathCheckVector(type, n.left, n.right)) { unionchecked = true; return nullptr; } if (Is(&n) || Is(&n)) { auto ltype = n.left->exptype; auto rtype = n.right->exptype; if (ltype->t == V_STRING) { if (rtype->t != V_STRING) { // Anything can be added to a string on the right (because of +=). MakeString(n.right, LT_BORROW); // Make sure the overal type is string. type = type_string; unionchecked = true; } } else if (rtype->t == V_STRING && ltype->t != V_STRING && typechangeallowed) { // Only if not in a += MakeString(n.left, LT_BORROW); type = type_string; unionchecked = true; } else { return "numeric/string/vector/struct"; } } else { return "numeric/vector/struct"; } } } return nullptr; } void MathError(TypeRef &type, BinOp &n, bool &unionchecked, bool typechangeallowed) { auto err = MathCheck(type, n, unionchecked, typechangeallowed); if (err) { if (MathCheck(n.left->exptype, n, unionchecked, typechangeallowed)) TypeError(err, n.left->exptype, n, "left"); if (MathCheck(n.right->exptype, n, unionchecked, typechangeallowed)) TypeError(err, n.right->exptype, n, "right"); TypeError("can\'t use \"" + NiceName(n) + "\" on " + TypeName(n.left->exptype) + " and " + TypeName(n.right->exptype), n); } } void TypeCheckMathOp(BinOp &n) { TT(n.left, 1, LT_BORROW); TT(n.right, 1, LT_BORROW); n.exptype = Union(n.left->exptype, n.right->exptype, true, nullptr); bool unionchecked = false; MathError(n.exptype, n, unionchecked, true); if (!unionchecked) SubTypeLR(n.exptype, n); DecBorrowers(n.left->lt, n); DecBorrowers(n.right->lt, n); n.lt = LT_KEEP; } void TypeCheckMathOpEq(BinOp &n) { TT(n.left, 1, LT_BORROW); DecBorrowers(n.left->lt, n); TT(n.right, 1, LT_BORROW); CheckLval(n.left); n.exptype = n.left->exptype; if (!MathCheckVector(n.exptype, n.left, n.right)) { bool unionchecked = false; MathError(n.exptype, n, unionchecked, false); if (!unionchecked) SubType(n.right, n.exptype, "right", n); } // This really does: "left = left op right" the result of op is LT_KEEP, which is // implicit, so the left var must be LT_KEEP as well. This is ensured elsewhere because // all !single_assignment vars are LT_KEEP. assert(!Is(n.left) || LifetimeType(Is(n.left)->sid->lt) != LT_BORROW); DecBorrowers(n.right->lt, n); n.lt = PushBorrow(n.left); } void TypeCheckComp(BinOp &n) { TT(n.left, 1, LT_BORROW); TT(n.right, 1, LT_BORROW); n.exptype = &st.default_bool_type->thistype; auto u = Union(n.left->exptype, n.right->exptype, true, nullptr); if (!u->Numeric() && u->t != V_STRING) { if (Is(&n) || Is(&n)) { // Comparison with one result, but still by value for structs. if (u->t != V_VECTOR && !IsUDT(u->t) && u->t != V_NIL && u->t != V_FUNCTION) TypeError(TypeName(n.left->exptype), n.right->exptype, n, "right-hand side"); } else { // Comparison vector op: vector inputs, vector out. if (u->t == V_STRUCT_S && u->udt->sametype->Numeric()) { n.exptype = st.default_int_vector_types[0][u->udt->fields.size()]; } else if (MathCheckVector(n.exptype, n.left, n.right)) { n.exptype = st.default_int_vector_types[0][n.exptype->udt->fields.size()]; // Don't do SubTypeLR since type already verified and `u` not // appropriate anyway. goto out; } else { TypeError(n.Name() + " doesn\'t work on " + TypeName(n.left->exptype) + " and " + TypeName(n.right->exptype), n); } } } SubTypeLR(u, n); out: DecBorrowers(n.left->lt, n); DecBorrowers(n.right->lt, n); n.lt = LT_KEEP; } void TypeCheckBitOp(BinOp &n) { TT(n.left, 1, LT_BORROW); TT(n.right, 1, LT_BORROW); auto u = Union(n.left->exptype, n.right->exptype, true, nullptr); if (u->t != V_INT) u = type_int; SubTypeLR(u, n); n.exptype = u; DecBorrowers(n.left->lt, n); DecBorrowers(n.right->lt, n); n.lt = LT_ANY; } void TypeCheckPlusPlus(Unary &n) { TT(n.child, 1, LT_BORROW); CheckLval(n.child); n.exptype = n.child->exptype; if (!n.exptype->Numeric()) TypeError("numeric", n.exptype, n); n.lt = n.child->lt; } SubFunction *TopScope(vector &_scopes) { return _scopes.empty() ? nullptr : _scopes.back().sf; } void RetVal(TypeRef type, SubFunction *sf, const Node &err, bool register_return = true) { if (register_return) { for (auto isc : reverse(scopes)) { if (isc.sf->parent == sf->parent) break; // isc.sf is a function in the call chain between the return statement and the // function it is returning from. Since we're affecting the return type of the // function we're returning from, if it gets specialized but a function along the // call chain (isc.sf) does not, we must ensure that return type affects the second // specialization. // We do this by tracking return types, and replaying them when a function gets // reused. // A simple test case is in return_from unit test, and recursive_exception is also // affected. isc.sf->reuse_return_events.push_back({ sf, type }); } } sf->num_returns++; if (sf->fixedreturntype.Null()) { if (sf->reqret) { // If this is a recursive call we must be conservative because there may already // be callers dependent on the return type so far, so any others must be subtypes. if (!sf->isrecursivelycalled) { // We can safely generalize the type if needed, though not with coercions. sf->returntype = Union(type, sf->returntype, false, &err); } } else { // The caller doesn't want return values. sf->returntype = type_void; } } } void TypeCheckFunctionDef(SubFunction &sf, const Node &call_context) { if (sf.typechecked) return; LOG_DEBUG("function start: ", SignatureWithFreeVars(sf, nullptr)); Scope scope; scope.sf = &sf; scope.call_context = &call_context; scopes.push_back(scope); //for (auto &ns : named_scopes) LOG_DEBUG("named scope: ", ns.sf->parent->name); if (!sf.parent->anonymous) named_scopes.push_back(scope); sf.typechecked = true; for (auto &arg : sf.args.v) StorageType(arg.type, call_context); for (auto &fv : sf.freevars.v) UpdateCurrentSid(fv.sid); auto backup_vars = [&](ArgVector &in, ArgVector &backup) { for (auto [i, arg] : enumerate(in.v)) { // Need to not overwrite nested/recursive calls. e.g. map(): map(): .. backup.v[i].sid = arg.sid->Current(); arg.sid->type = arg.type; RevertCurrentSid(arg.sid); } }; auto backup_args = sf.args; backup_vars(sf.args, backup_args); auto backup_locals = sf.locals; backup_vars(sf.locals, backup_locals); auto enter_scope = [&](const Arg &var) { IncBorrowers(var.sid->lt, call_context); }; for (auto &arg : sf.args.v) enter_scope(arg); for (auto &local : sf.locals.v) enter_scope(local); sf.coresumetype = sf.iscoroutine ? NewNilTypeVar() : type_undefined; sf.returntype = sf.reqret ? (!sf.fixedreturntype.Null() ? sf.fixedreturntype : NewTypeVar()) : type_void; auto start_borrowed_vars = borrowstack.size(); auto start_promoted_vars = flowstack.size(); TypeCheckList(sf.body, true, 0, LT_ANY); CleanUpFlow(start_promoted_vars); if (!sf.num_returns) { if (!sf.fixedreturntype.Null() && sf.fixedreturntype->t != V_VOID) TypeError("missing return statement", *sf.body->children.back()); sf.returntype = type_void; } // Let variables go out of scope in reverse order of declaration. auto exit_scope = [&](const Arg &var) { DecBorrowers(var.sid->lt, call_context); }; for (auto &local : reverse(sf.locals.v)) exit_scope(local); for (auto &arg : sf.args.v) exit_scope(arg); // No order. while (borrowstack.size() > start_borrowed_vars) { auto &b = borrowstack.back(); if (b.refc) { TypeError(cat("variable ", b.Name(), " still has ", b.refc, " borrowers"), *sf.body->children.back()); } borrowstack.pop_back(); } for (auto &back : backup_args.v) RevertCurrentSid(back.sid); for (auto &back : backup_locals.v) RevertCurrentSid(back.sid); if (sf.returntype->HasValueType(V_VAR)) { // If this function return something with a variable in it, then it likely will get // bound by the caller. If the function then gets reused without specialization, it will // get the wrong return type, so we force specialization for subsequent calls of this // function. FIXME: check in which cases this is typically true, since its expensive // if done without reason. sf.mustspecialize = true; } if (!sf.parent->anonymous) named_scopes.pop_back(); scopes.pop_back(); LOG_DEBUG("function end ", Signature(sf), " returns ", TypeName(sf.returntype)); } UDT *FindStructSpecialization(UDT *given, const Constructor *cons) { // This code is somewhat similar to function specialization, but not similar enough to // share. If they're all typed, we bail out early: if (given->generics.empty()) return given; auto head = given->first; assert(cons->Arity() == head->fields.size()); // Now find a match: UDT *best = nullptr; int bestmatch = 0; for (auto udt = head->next; udt; udt = udt->next) { int nmatches = 0; for (auto [i, arg] : enumerate(cons->children)) { auto &field = udt->fields.v[i]; if (field.genericref >= 0) { if (ExactType(arg->exptype, field.type)) nmatches++; else break; } } if (nmatches > bestmatch) { bestmatch = nmatches; best = udt; } } if (best) return best; string s; for (auto &arg : cons->children) s += " " + TypeName(arg->exptype); TypeError("no specialization of " + given->first->name + " matches these types:" + s, *cons); return nullptr; } void CheckIfSpecialization(UDT *spec_struc, TypeRef given, const Node &n, string_view argname, string_view req = {}, bool subtypeok = false, string_view context = {}) { auto givenu = given->UnWrapped(); if (!IsUDT(given->t) || (!spec_struc->IsSpecialization(givenu->udt) && (!subtypeok || st.SuperDistance(spec_struc, givenu->udt) < 0))) { TypeError(req.data() ? req : spec_struc->name, given, n, argname, context); } } void CheckGenericArg(TypeRef otype, TypeRef argtype, string_view argname, const Node &n, string_view context) { // Check if argument is a generic struct type, or wrapped in vector/nilable. if (otype->t != V_ANY) { auto u = otype->UnWrapped(); assert(IsUDT(u->t)); if (otype->EqNoIndex(*argtype)) { CheckIfSpecialization(u->udt, argtype, n, argname, TypeName(otype), true, context); } else { // This likely generates either an error, or contains an unbound var that will get // bound. SubTypeT(argtype, otype, n, argname, context); //TypeError(TypeName(otype), argtype, n, argname, context); } } } bool FreeVarsSameAsCurrent(const SubFunction &sf, bool prespecialize) { for (auto &freevar : sf.freevars.v) { //auto atype = Promote(freevar.id->type); if (freevar.sid != freevar.sid->Current() || !ExactType(freevar.type, freevar.sid->Current()->type)) { (void)prespecialize; assert(prespecialize || freevar.sid == freevar.sid->Current() || (freevar.sid && freevar.sid->Current())); return false; } //if (atype->t == V_FUNCTION) return false; } return true; } SubFunction *CloneFunction(SubFunction *csf, int i) { LOG_DEBUG("cloning: ", csf->parent->name); auto sf = st.CreateSubFunction(); sf->SetParent(*csf->parent, csf->parent->overloads[i]); // Any changes here make sure this corresponds what happens in Inline() in the optimizer. st.CloneIds(*sf, *csf); sf->body = (List *)csf->body->Clone(); sf->freevarchecked = true; sf->fixedreturntype = csf->fixedreturntype; sf->returntype = csf->returntype; sf->logvarcallgraph = csf->logvarcallgraph; sf->method_of = csf->method_of; return sf; } // See if returns produced by an existing specialization are compatible with our current // context of functions. bool CompatibleReturns(const SubFunction &ssf) { for (auto re : ssf.reuse_return_events) { auto sf = re.first; for (auto isc : reverse(scopes)) { if (isc.sf->parent == sf->parent) { if (isc.sf->reqret != sf->reqret) return false; goto found; } } return false; // Function not in context. found:; } return true; } void CheckReturnPast(const SubFunction *sf, const SubFunction *sf_to, const Node &context) { // Special case for returning out of top level, which is always allowed. if (sf_to == st.toplevel) return; if (sf->iscoroutine) { TypeError("cannot return out of coroutine", context); } if (sf->isdynamicfunctionvalue) { // This is because the function has been typechecked against one context, but // can be called again in a different context that does not have the same callers. TypeError("cannot return out of dynamic function value", context); } } TypeRef TypeCheckCall(SubFunction *csf, List *call_args, SubFunction *&chosen, const Node &call_context, size_t reqret, int &vtable_idx) { Function &f = *csf->parent; UDT *dispatch_udt = nullptr; vtable_idx = -1; auto TypeCheckMatchingCall = [&](SubFunction *sf, bool static_dispatch, bool first_dynamic) { // Here we have a SubFunction witch matching specialized types. sf->numcallers++; Function &f = *sf->parent; if (!f.istype) TypeCheckFunctionDef(*sf, call_context); // Finally check all the manually typed args. We do this after checking the function // definition, since SubType below can cause specializations of the current function // to be typechecked with strongly typed function value arguments. for (auto [i, c] : enumerate(call_args->children)) { if (i < f.nargs()) /* see below */ { auto &arg = sf->args.v[i]; if (static_dispatch || first_dynamic) { // Check a dynamic dispatch only for the first case, and then skip // checking the first arg. if (!(arg.flags & AF_GENERIC) && (static_dispatch || i)) SubType(c, arg.type, ArgName(i), f.name); AdjustLifetime(c, arg.sid->lt); } } // This has to happen even to dead args: if (static_dispatch || first_dynamic) DecBorrowers(c->lt, call_context); } chosen = sf; for (auto &freevar : sf->freevars.v) { // New freevars may have been added during the function def typecheck above. // In case their types differ from the flow-sensitive value at the callsite (here), // we want to override them. freevar.type = freevar.sid->Current()->type; } // See if this call is recursive: for (auto &sc : scopes) if (sc.sf == sf) { sf->isrecursivelycalled = true; break; } return sf->returntype; }; auto SpecializationIsCompatible = [&](const SubFunction &sf) { return reqret == sf.reqret && FreeVarsSameAsCurrent(sf, false) && CompatibleReturns(sf); }; auto ReplayReturns = [&](const SubFunction *sf) { // Apply effects of return statements for functions being reused, see // RetVal above. for (auto [isf, type] : sf->reuse_return_events) { for (auto isc : reverse(scopes)) { if (isc.sf->parent == isf->parent) { // NOTE: will have to re-apply lifetimes as well if we change // from default of LT_KEEP. RetVal(type, isc.sf, call_context, false); // This should in theory not cause an error, since the previous // specialization was also ok with this set of return types. // It could happen though if this specialization has an // additional return statement that was optimized // out in the previous one. SubTypeT(type, isc.sf->returntype, call_context, "", "reused return value"); break; } CheckReturnPast(isc.sf, isf, call_context); } } }; auto TypeCheckCallStatic = [&](int overload_idx, bool static_dispatch, bool first_dynamic) { Function &f = *csf->parent; SubFunction *sf = f.overloads[overload_idx]; if (sf->logvarcallgraph) { // Mark call-graph up to here as using logvars, if it hasn't been already. for (auto sc : reverse(scopes)) { if (sc.sf->logvarcallgraph) break; sc.sf->logvarcallgraph = true; } } // Check if we need to specialize: generic args, free vars and need of retval // must match previous calls. auto AllowAnyLifetime = [&](const Arg & arg) { return arg.sid->id->single_assignment && !sf->iscoroutine; }; // Check if any existing specializations match. for (sf = f.overloads[overload_idx]; sf; sf = sf->next) { if (sf->typechecked && !sf->mustspecialize && !sf->logvarcallgraph) { // We check against f.nargs because HOFs are allowed to call a function // value with more arguments than it needs (if we're called from // TypeCheckDynCall). Optimizer always removes these. // Note: we compare only lt, since calling with other borrowed sid // should be ok to reuse. for (auto [i, c] : enumerate(call_args->children)) if (i < f.nargs()) { auto &arg = sf->args.v[i]; if ((arg.flags & AF_GENERIC && !ExactType(c->exptype, arg.type)) || (IsBorrow(c->lt) != IsBorrow(arg.sid->lt) && AllowAnyLifetime(arg))) goto fail; } if (SpecializationIsCompatible(*sf)) { // This function can be reused. // Make sure to add any freevars this call caused to be // added to its parents also to the current parents, just in case // they're different. LOG_DEBUG("re-using: ", Signature(*sf)); for (auto &fv : sf->freevars.v) CheckFreeVariable(*fv.sid); ReplayReturns(sf); return TypeCheckMatchingCall(sf, static_dispatch, first_dynamic); } fail:; } } // No match. sf = f.overloads[overload_idx]; // Specialize existing function, or its clone. if (sf->typechecked) sf = CloneFunction(sf, overload_idx); // Now specialize. sf->reqret = reqret; // See if this is going to be a coroutine. for (auto [i, c] : enumerate(call_args->children)) if (i < f.nargs()) /* see above */ { if (Is(c)) sf->iscoroutine = true; } for (auto [i, c] : enumerate(call_args->children)) if (i < f.nargs()) /* see above */ { auto &arg = sf->args.v[i]; arg.sid->lt = AllowAnyLifetime(arg) ? c->lt : LT_KEEP; if (arg.flags & AF_GENERIC) { arg.type = c->exptype; // Specialized to arg. CheckGenericArg(f.orig_args.v[i].type, arg.type, arg.sid->id->name, *c, f.name); LOG_DEBUG("arg: ", arg.sid->id->name, ":", TypeName(arg.type)); } } // This must be the correct freevar specialization. assert(!f.anonymous || sf->freevarchecked); assert(!sf->freevars.v.size()); LOG_DEBUG("specialization: ", Signature(*sf)); return TypeCheckMatchingCall(sf, static_dispatch, first_dynamic); }; auto TypeCheckCallDispatch = [&]() { // We must assume the instance may dynamically be different, so go thru vtable. // See if we already have a vtable entry for this type of call. for (auto [i, disp] : enumerate(dispatch_udt->dispatch)) { // FIXME: does this guarantee it find it in the recursive case? // TODO: we chould check for a superclass vtable entry also, but chances // two levels will be present are low. if (disp.sf && disp.sf->method_of == dispatch_udt && disp.is_dispatch_root && &f == disp.sf->parent && SpecializationIsCompatible(*disp.sf)) { for (auto [i, c] : enumerate(call_args->children)) if (i < f.nargs()) { auto &arg = disp.sf->args.v[i]; if (i && !ConvertsTo(c->exptype, arg.type, false, false)) goto fail; } for (auto udt : dispatch_udt->subudts) { // Since all functions were specialized with the same args, they should // all be compatible if the root is. auto sf = udt->dispatch[i].sf; LOG_DEBUG("re-using dyndispatch: ", Signature(*sf)); assert(SpecializationIsCompatible(*sf)); for (auto &fv : sf->freevars.v) CheckFreeVariable(*fv.sid); ReplayReturns(sf); } // Type check this as if it is a static dispatch to just the root function. TypeCheckMatchingCall(disp.sf, true, false); vtable_idx = (int)i; return dispatch_udt->dispatch[i].returntype; } fail:; } // Must create a new vtable entry. // TODO: would be good to search superclass if it has this method also. // Probably not super important since dispatching on the "middle" type in a // hierarchy will be rare. // Find subclasses and max vtable size. { vector overload_idxs; for (auto sub : dispatch_udt->subudts) { int best = -1; int bestdist = 0; for (auto [i, sf] : enumerate(csf->parent->overloads)) { if (sf->method_of) { auto sdist = st.SuperDistance(sf->method_of, sub); if (sdist >= 0 && (best < 0 || bestdist > sdist)) { best = (int)i; bestdist = sdist; } } } if (best < 0) { if (sub->constructed) { TypeError("no implementation for " + sub->name + "." + csf->parent->name, call_context); } else { // This UDT is unused, so we're ok there not being an implementation // for it.. like e.g. an abstract base class. } } overload_idxs.push_back(best); vtable_idx = max(vtable_idx, (int)sub->dispatch.size()); } // Add functions to all vtables. for (auto [i, udt] : enumerate(dispatch_udt->subudts)) { auto &dt = udt->dispatch; assert((int)dt.size() <= vtable_idx); // Double entry. // FIXME: this is not great, wasting space, but only way to do this // on the fly without tracking lots of things. while ((int)dt.size() < vtable_idx) dt.push_back({}); dt.push_back({ overload_idxs[i] < 0 ? nullptr : csf->parent->overloads[overload_idxs[i]] }); } // FIXME: if any of the overloads below contain recursive calls, it may run into // issues finding an existing dispatch above? would be good to guarantee.. // The fact that in subudts the superclass comes first will help avoid problems // in many cases. auto de = &dispatch_udt->dispatch[vtable_idx]; de->is_dispatch_root = true; de->returntype = NewTypeVar(); // Typecheck all the individual functions. SubFunction *last_sf = nullptr; for (auto [i, udt] : enumerate(dispatch_udt->subudts)) { auto sf = udt->dispatch[vtable_idx].sf; if (!sf) continue; // Missing implementation for unused UDT. // FIXME: this possible runs the code below multiple times for the same sf, // we rely on it finding the same specialization. if (last_sf) { // FIXME: good to have this check here so it only occurs for functions // participating in the dispatch, but error now appears at the call site! for (auto [j, arg] : enumerate(sf->args.v)) { if (j && arg.type != last_sf->args.v[j].type && !(arg.flags & AF_GENERIC)) TypeError("argument " + to_string(j + 1) + " of " + f.name + " overload type mismatch", call_context); } } call_args->children[0]->exptype = &udt->thistype; // FIXME: return value? /*auto rtype =*/ TypeCheckCallStatic(overload_idxs[i], false, !last_sf); de = &dispatch_udt->dispatch[vtable_idx]; // May have realloced. sf = chosen; sf->method_of->dispatch[vtable_idx].sf = sf; // FIXME: Lift these limits? if (sf->returntype->NumValues() > 1) TypeError("dynamic dispatch can currently return only 1 value.", call_context); auto u = sf->returntype; if (de->returntype->IsBoundVar()) { // Typically in recursive calls, but can happen otherwise also? if (!ConvertsTo(u, de->returntype, false)) // FIXME: not a great error, but should be rare. TypeError("dynamic dispatch for " + f.name + " return value type " + TypeName(sf->returntype) + " doesn\'t match other case returning " + TypeName(de->returntype), *sf->body); } else { if (i) { // We have to be able to take the union of all retvals without // coercion, since we're not fixing up any previously typechecked // functions. u = Union(u, de->returntype, false, &call_context); // Ensure we didn't accidentally widen the type from a scalar. assert(IsRef(de->returntype->t) || !IsRef(u->t)); } de->returntype = u; } last_sf = sf; } call_args->children[0]->exptype = &dispatch_udt->thistype; } return dispatch_udt->dispatch[vtable_idx].returntype; }; if (f.istype) { // Function types are always fully typed. // All calls thru this type must have same lifetimes, so we fix it to LT_BORROW. return TypeCheckMatchingCall(csf, true, false); } // Check if we need to do dynamic dispatch. We only do this for functions that have a // explicit first arg type of a class (not structs, since they can never dynamically be // different from their static type), and only when there is a sub-class that has a // method that can be called also. if (f.nargs()) { auto type = call_args->children[0]->exptype; if (type->t == V_CLASS) dispatch_udt = type->udt; } if (dispatch_udt) { size_t num_methods = 0; for (auto isf : csf->parent->overloads) if (isf->method_of) num_methods++; if (num_methods > 1) { // Go thru all other overloads, and see if any of them have this one as superclass. for (auto isf : csf->parent->overloads) { if (isf->method_of && st.SuperDistance(dispatch_udt, isf->method_of) > 0) { LOG_DEBUG("dynamic dispatch: ", Signature(*isf)); return TypeCheckCallDispatch(); } } // Yay there are no sub-class implementations, we can just statically dispatch. } // Yay only one method, we can statically dispatch. } // Do a static dispatch, if there are overloads, figure out from first arg which to pick, // much like dynamic dispatch. Unlike dynamic dispatch, we also include non-class types. // TODO: also involve the other arguments for more complex static overloads? int overload_idx = 0; if (f.nargs() && f.overloads.size() > 1) { overload_idx = -1; auto type0 = call_args->children[0]->exptype; // First see if there is an exact match. for (auto [i, isf] : enumerate(f.overloads)) { if (ExactType(type0, isf->args.v[0].type)) { if (overload_idx >= 0) TypeError("multiple overloads have the same type: " + f.name + ", first arg: " + TypeName(type0), call_context); overload_idx = (int)i; } } // Then see if there's a match by subtyping. if (overload_idx < 0) { for (auto [i, isf] : enumerate(f.overloads)) { auto arg0 = isf->args.v[0].type; if (ConvertsTo(type0, arg0, false, false)) { if (overload_idx >= 0) { if (type0->t == V_CLASS) { auto oarg0 = f.overloads[overload_idx]->args.v[0].type; // Prefer "closest" supertype. auto dist = st.SuperDistance(arg0->udt, type0->udt); auto odist = st.SuperDistance(oarg0->udt, type0->udt); if (dist < odist) overload_idx = (int)i; else if (odist < dist) { /* keep old one */ } else { TypeError("multiple overloads have the same class: " + f.name + ", first arg: " + TypeName(type0), call_context); } } else { TypeError("multiple overloads apply: " + f.name + ", first arg: " + TypeName(type0), call_context); } } else { overload_idx = (int)i; } } } } // Then finally try with coercion. if (overload_idx < 0) { for (auto [i, isf] : enumerate(f.overloads)) { if (ConvertsTo(type0, isf->args.v[0].type, true, false)) { if (overload_idx >= 0) { TypeError("multiple overloads can coerce: " + f.name + ", first arg: " + TypeName(type0), call_context); } overload_idx = (int)i; } } } if (overload_idx < 0) TypeError("no overloads apply: " + f.name + ", first arg: " + TypeName(type0), call_context); } LOG_DEBUG("static dispatch: ", Signature(*f.overloads[overload_idx])); return TypeCheckCallStatic(overload_idx, true, false); } SubFunction *PreSpecializeFunction(SubFunction *hsf) { // Don't pre-specialize named functions, because this is not their call-site. if (!hsf->parent->anonymous) return hsf; assert(hsf->parent->overloads.size() == 1); hsf = hsf->parent->overloads[0]; auto sf = hsf; if (sf->freevarchecked) { // See if there's an existing match. for (; sf; sf = sf->next) if (sf->freevarchecked) { if (FreeVarsSameAsCurrent(*sf, true)) return sf; } sf = CloneFunction(hsf, 0); } else { // First time this function has ever been touched. sf->freevarchecked = true; } assert(!sf->freevars.v.size()); // Output without arg types, since those are yet to be overwritten. LOG_DEBUG("pre-specialization: ", SignatureWithFreeVars(*sf, nullptr, false)); return sf; } pair TypeCheckDynCall(SpecIdent *fval, List *args, SubFunction *&fspec, size_t reqret) { auto &ftype = fval->type; auto nargs = args->Arity(); // FIXME: split this up in a Call, a Yield and a DynCall(istype = true) node, just like // GenericCall does. if (ftype->IsFunction()) { // We can statically typecheck this dynamic call. Happens for almost all non-escaping // closures. auto sf = ftype->sf; if (nargs < sf->parent->nargs()) TypeError("function value called with too few arguments", *args); // In the case of too many args, TypeCheckCall will ignore them (and optimizer will // remove them). int vtable_idx = -1; auto type = TypeCheckCall(sf, args, fspec, *args, reqret, vtable_idx); assert(vtable_idx < 0); ftype = &fspec->thistype; return { type, fspec->ltret }; } else if (ftype->t == V_YIELD) { // V_YIELD must have perculated up from a coroutine call. if (nargs != 1) TypeError("coroutine yield call must have exactly one argument", *args); NoStruct(*args->children[0], "yield"); // FIXME: implement. AdjustLifetime(args->children[0], LT_KEEP); for (auto scope : reverse(named_scopes)) { auto sf = scope.sf; if (!sf->iscoroutine) continue; // What yield returns to return_value(). If no arg, then it will return nil. auto type = args->children[0]->exptype; RetVal(type, sf, *args); SubTypeT(type, sf->returntype, *args, "", "yield value"); // Now collect all ids between coroutine and yield, so that we can save these in the // VM. bool foundstart = false; for (auto savescope = scopes.begin(); savescope != scopes.end(); ++savescope) { auto ssf = savescope->sf; if (ssf == sf) foundstart = true; if (!foundstart) continue; for (auto &arg : ssf->args.v) sf->coyieldsave.Add(arg); for (auto &loc : ssf->locals.v) sf->coyieldsave.Add(Arg(loc.sid, loc.sid->type, loc.flags & AF_WITHTYPE)); } for (auto &cys : sf->coyieldsave.v) UpdateCurrentSid(cys.sid); return { sf->coresumetype, LT_KEEP }; } TypeError("yield function called outside scope of coroutine", *args); return { type_void, LT_ANY }; } else { TypeError("dynamic function call value doesn\'t have a function type: " + TypeName(ftype), *args); return { type_void, LT_ANY }; } } TypeRef TypeCheckBranch(bool iftrue, const Node *condition, Node *&bodycall, bool reqret, Lifetime recip) { auto flowstart = CheckFlowTypeChanges(iftrue, condition); TT(bodycall, reqret, recip); CleanUpFlow(flowstart); return bodycall->exptype; } void CheckFlowTypeIdOrDot(const Node &n, TypeRef type) { FlowItem fi(n, type); if (fi.IsValid()) flowstack.push_back(fi); } void CheckFlowTypeChangesSub(bool iftrue, const Node *condition) { condition = SkipCoercions(condition); auto type = condition->exptype; if (auto c = Is(condition)) { if (iftrue) CheckFlowTypeIdOrDot(*c->child, c->giventype); } else if (auto c = Is(condition)) { CheckFlowTypeChangesSub(!iftrue, c->child); } else { if (iftrue && type->t == V_NIL) CheckFlowTypeIdOrDot(*condition, type->Element()); } } void CheckFlowTypeChangesAndOr(bool iftrue, const BinOp *condition) { // AND only works for then, and OR only for else. if (iftrue == (Is(condition) != nullptr)) { // This allows for a chain of and's without allowing mixed operators. auto cleft = SkipCoercions(condition->left); if (typeid(*cleft) == typeid(*condition)) { CheckFlowTypeChanges(iftrue, condition->left); } else { CheckFlowTypeChangesSub(iftrue, condition->left); } CheckFlowTypeChangesSub(iftrue, condition->right); } } size_t CheckFlowTypeChanges(bool iftrue, const Node *condition) { auto start = flowstack.size(); condition = SkipCoercions(condition); if (auto c = Is(condition)) { CheckFlowTypeChangesAndOr(iftrue, c); } else if (auto c = Is(condition)) { CheckFlowTypeChangesAndOr(iftrue, c); } else { CheckFlowTypeChangesSub(iftrue, condition); } return start; } void AssignFlowPromote(Node &left, TypeRef right) { if ((left.exptype->t == V_ANY && right->t != V_ANY) || (left.exptype->t == V_NIL && right->t != V_NIL)) { CheckFlowTypeIdOrDot(left, right); } } // FIXME: this can in theory find the wrong node, if the same function nests, and the outer // one was specialized to a nilable and the inner one was not. // This would be very rare though, and benign. TypeRef AssignFlowDemote(FlowItem &left, TypeRef overwritetype, bool coercions) { // Early out, numeric types are not nillable, nor do they make any sense for "is" auto &type = left.now; if (type->Numeric()) return type; for (auto flow : reverse(flowstack)) { if (flow.sid == left.sid) { if (left.derefs.empty()) { if (flow.derefs.empty()) { type = flow.old; goto found; } else { // We're writing to var V and V.f is in the stack: invalidate regardless. goto found; } } else { if (flow.DerefsEqual(left)) { type = flow.old; goto found; } } } continue; found: if (!ConvertsTo(overwritetype, flow.now, coercions)) { // FLow based promotion is invalidated. flow.now = flow.old; // TODO: It be cool to instead overwrite with whatever type is currently being // assigned. That currently doesn't work, since our flow analysis is a // conservative approximation, so if this assignment happens conditionally it // wouldn't work. } // We continue with the loop here, since a single assignment may invalidate multiple // promotions } return type; } TypeRef UseFlow(const FlowItem &left) { if (left.now->Numeric()) return left.now; // Early out, same as above. for (auto flow : reverse(flowstack)) { if (flow.sid == left.sid && flow.DerefsEqual(left)) { return flow.now; } } return left.now; } void CleanUpFlow(size_t start) { while (flowstack.size() > start) flowstack.pop_back(); } void TypeCheckAndOr(BinOp &ao, bool only_true_type, bool reqret, TypeRef &promoted_type) { // only_true_type supports patterns like ((a & b) | c) where the type of a doesn't matter, // and the overal type should be the union of b and c. // Or a? | b, which should also be the union of a and b. TypeRef tleft, tright; TypeCheckAndOrSub(ao.left, Is(ao), true, tleft); auto flowstart = CheckFlowTypeChanges(Is(ao), ao.left); TypeCheckAndOrSub(ao.right, only_true_type, reqret, tright); CleanUpFlow(flowstart); if (only_true_type && Is(ao)) { ao.exptype = tright; ao.lt = ao.right->lt; DecBorrowers(ao.left->lt, ao); } else { ao.exptype = Union(tleft, tright, false, nullptr); if (ao.exptype->t == V_UNDEFINED) { // Special case: unlike elsewhere, we allow merging scalar and reference types, // since they are just tested and thrown away. To make this work, we force all // values to bools. MakeBool(ao.left); MakeBool(ao.right); ao.exptype = &st.default_bool_type->thistype; ao.lt = LT_ANY; } else { ao.lt = LifetimeUnion(ao.left, ao.right, Is(ao)); } } promoted_type = ao.exptype; } void TypeCheckAndOrSub(Node *&n, bool only_true_type, bool reqret, TypeRef &promoted_type) { // only_true_type supports patterns like ((a & b) | c) where the type of a doesn't matter, // and the overal type should be the union of b and c. // Or a? | b, which should also be the union of a and b. n = RemoveCoercions(n); if (!Is(n) && !Is(n)) { TT(n, reqret, LT_ANY); NoStruct(*n, "and / or"); promoted_type = n->exptype; if (promoted_type->t == V_NIL && only_true_type) promoted_type = promoted_type->Element(); } else { auto ao = dynamic_cast(n); assert(ao); TypeCheckAndOr(*ao, only_true_type, reqret, promoted_type); } } void CheckLval(Node *n) { if (auto dot = Is(n)) { auto type = dot->children[0]->exptype; if (IsStruct(type->t)) TypeError("cannot write to field of value: " + type->udt->name, *n); } // This can happen due to late specialization of GenericCall. if (Is(n) || Is(n)) TypeError("function-call cannot be an l-value", *n); Borrow lv(*n); if (!lv.IsValid()) return; // FIXME: force these to LT_KEEP? if (lv.derefs.empty() && LifetimeType(lv.sid->lt) == LT_BORROW) { // This should only happen for multimethods and anonymous functions used with istype // where we can't avoid arguments being LT_BORROW. // All others should have been specialized to LT_KEEP when a var is not // single_assignment. // This is not particularly elegant but should be rare. TypeError(cat("cannot assign to borrowed argument: ", lv.sid->id->name), *n); } // FIXME: make this faster. for (auto &b : reverse(borrowstack)) { if (!b.IsPrefix(lv)) continue; // Not overwriting this one. if (!b.refc) continue; // Lval is not borowed, writing is ok. TypeError(cat("cannot assign to ", lv.Name(), " while borrowed"), *n); } } Lifetime PushBorrow(Node *n) { if (!IsRefNilVar(n->exptype->t)) return LT_ANY; Borrow lv(*n); // FIXME: if this is an exp we don't know how to borrow from (like a[i].b) we // return a generic borrow, but this disables lock checks so is unsafe. if (!lv.IsValid()) return LT_BORROW; for (auto &b : reverse(borrowstack)) { if (b.sid == lv.sid && b.DerefsEqual(lv)) { b.refc++; return (Lifetime)(&b - &borrowstack[0]); } } // FIXME: this path is slow, should not have to scan all of borrowstack. auto lt = (Lifetime)borrowstack.size(); borrowstack.push_back(lv); return lt; } void CheckFreeVariable(SpecIdent &sid) { // If this is a free variable, record it in all parents up to the definition point. // FIXME: this is technically not the same as a "free variable" in the literature, // since HOFs get marked with freevars of their functionvalue this way. // This is benign, since the HOF will be specialized to the function value anyway, // but would be good to clean up. // We currently don't have an easy way to test for lexically enclosing functions. for (int i = (int)scopes.size() - 1; i >= 0; i--) { auto sf = scopes[i].sf; // Check if we arrived at the definition point. if (sid.sf_def == sf) break; // We use the id's type, not the flow sensitive type, just in case there's multiple uses // of the var. This will get corrected after the call this is part of. if (sf->freevars.Add(Arg(&sid, sid.type, AF_GENERIC))) { //LOG_DEBUG("freevar added: ", id.name, " (", TypeName(id.type), // ") in ", sf->parent->name); } } } bool NeverReturns(const Node *n) { if (auto call = Is(n)) { // Have to be conservative for recursive calls since we're not done typechecking it. if (call->sf->isrecursivelycalled || call->sf->method_of || call->sf->iscoroutine || call->sf->parent->istype) return false; if (!call->sf->num_returns) return true; if (call->sf->num_returns == 1) { auto ret = AssertIs(call->sf->body->children.back()); assert(ret->sf == call->sf); return NeverReturns(ret->child); } // TODO: could also check num_returns > 1, but then have to scan all children. } else if (auto ifthen = Is(n)) { auto tp = Is(ifthen->truepart); auto fp = Is(ifthen->falsepart); return tp && fp && NeverReturns(tp) && NeverReturns(fp); } else if (auto sw = Is(n)) { auto have_default = false; for (auto c : sw->cases->children) { auto cas = AssertIs(c); if (cas->pattern->children.empty()) have_default = true; if (!NeverReturns(cas->body)) return false; } return have_default; } else if (auto nc = Is(n)) { // A function may end in "assert false" and have only its previous return statements // taken into account. Value cval; if (nc->nf->IsAssert() && nc->children[0]->ConstVal(*this, cval) && !cval.True()) return true; } // TODO: Other situations? return false; } void TypeCheckList(List *n, bool onlylast, size_t reqret, Lifetime lt) { for (auto &c : n->children) { auto tovoid = onlylast && c != n->children.back(); TT(c, tovoid ? 0 : reqret, tovoid ? LT_ANY : lt); } } TypeRef TypeCheckId(SpecIdent *sid) { auto type = sid->type; CheckFreeVariable(*sid); return type; } const Coercion *IsCoercion(const Node *n) { return dynamic_cast(n); } const Node *SkipCoercions(const Node *n) { auto c = IsCoercion(n); return c ? SkipCoercions(c->child) : n; } Node *RemoveCoercions(Node *n) { auto c = IsCoercion(n); return c ? RemoveCoercions(DeleteCoercion((Coercion *)c)) : n; } Node *DeleteCoercion(Coercion *c) { auto ch = c->child; c->child = nullptr; delete c; return ch; } Lifetime LvalueLifetime(const Node &lval, bool deref) { if (auto idr = Is(lval)) return idr->sid->lt; if (deref) { if (auto dot = Is(lval)) return LvalueLifetime(*dot->children[0], deref); if (auto idx = Is(lval)) return LvalueLifetime(*idx->object, deref); } if (auto cod = Is(lval)) return AssertIs(cod->variable)->sid->lt; return LT_KEEP; } Lifetime LifetimeUnion(Node *&a, Node *&b, bool is_and) { if (a->lt == b->lt) { DecBorrowers(b->lt, *b); return a->lt; } else if (a->lt == LT_ANY && b->lt >= LT_BORROW) { // This case may apply in an if-then between a var and nil, or an and/or between // a var and a scalar. return b->lt; } else if (b->lt == LT_ANY && a->lt >= LT_BORROW) { // Same. return a->lt; } else if (is_and && a->lt >= LT_BORROW && b->lt >= LT_BORROW) { // var_a and var_b never results in var_a. DecBorrowers(a->lt, *a); return b->lt; } else { // If it is an and we want to borrow the lhs since it will never be used. // Otherwise default to LT_KEEP for everything. // FIXME: for cases where both sides are >= LT_BORROW (in an if-then) we'd like to // combine both lifetimes into one, but we currently can't represent that. AdjustLifetime(a, is_and ? LT_BORROW : LT_KEEP); if (is_and) DecBorrowers(a->lt, *a); AdjustLifetime(b, LT_KEEP); return LT_KEEP; } } void Borrowers(Lifetime lt, int change, const Node &context) { if (lt < 0) return; auto &b = borrowstack[lt]; assert(IsRefNilVar(b.sid->type->t)); b.refc += change; LOG_DEBUG("borrow ", change, ": ", b.sid->id->name, " in ", NiceName(context), ", ", b.refc, " remain"); // FIXME: this should really just not be possible, but hard to guarantee. if (b.refc < 0) TypeError(cat(b.sid->id->name, " used in ", NiceName(context), " without being borrowed"), context); assert(b.refc >= 0); (void)context; } void IncBorrowers(Lifetime lt, const Node &context) { Borrowers(lt, 1, context); } void DecBorrowers(Lifetime lt, const Node &context) { Borrowers(lt, -1, context); } void ModifyLifetime(Node *n, size_t i, Lifetime lt) { if (n->lt == LT_MULTIPLE) { n->exptype->Set(i, n->exptype->Get(i), lt); } else { n->lt = lt; } } void AdjustLifetime(Node *&n, Lifetime recip, const vector *idents = nullptr) { assert(n->lt != LT_UNDEF && recip != LT_UNDEF); if (recip == LT_ANY) return; uint64_t incref = 0, decref = 0; auto rt = n->exptype; for (size_t i = 0; i < rt->NumValues(); i++) { assert (n->lt != LT_MULTIPLE || rt->t == V_TUPLE); auto givenlt = rt->GetLifetime(i, n->lt); auto given = LifetimeType(givenlt); if (idents) recip = LvalueLifetime(*(*idents)[i], false); // FIXME: overwrite var? recip = LifetimeType(recip); if (given != recip) { auto rtt = rt->Get(i)->t; // Sadly, if it a V_VAR we have to be conservate and assume it may become a ref. if (IsRefNilVar(rtt)) { // Special action required. if (i >= sizeof(incref) * 8) TypeError("too many return values", *n); if (given == LT_BORROW && recip == LT_KEEP) { incref |= 1LL << i; DecBorrowers(givenlt, *n); } else if (given == LT_KEEP && recip == LT_BORROW) { decref |= 1LL << i; } else if (given == LT_ANY) { // These are compatible with whatever recip wants. } else { assert(false); } } else { if (given == LT_BORROW) { // This is a scalar that depends on a borrowed value, but the recipient // doesn't care. ModifyLifetime(n, i, LT_ANY); // Avoid it travelling any further. DecBorrowers(givenlt, *n); } } if (given == LT_ANY) { // Fill in desired lifetime, for consistency. ModifyLifetime(n, i, recip); } } } if (incref || decref) { LOG_DEBUG("lifetime adjust for ", NiceName(*n), " to ", incref, "/", decref); MakeLifetime(n, idents ? LT_MULTIPLE: recip, incref, decref); } } // This is the central function thru which all typechecking flows, so we can conveniently // match up what the node produces and what the recipient expects. void TT(Node *&n, size_t reqret, Lifetime recip, const vector *idents = nullptr) { // Central point from which each node is typechecked. n = n->TypeCheck(*this, reqret); // Check if we need to do any type adjustmenst. auto &rt = n->exptype; n->exptype = rt; auto nret = rt->NumValues(); if (nret < reqret) { TypeError(cat(NiceName(*n), " returns ", nret, " values, ", reqret, " needed"), *n); } else if (nret > reqret) { for (size_t i = reqret; i < nret; i++) { // This value will be dropped. DecBorrowers(rt->GetLifetime(i, n->lt), *n); // If this is a LT_KEEP value, codegen will make sure to throw it away. } switch (reqret) { case 0: n->lt = LT_ANY; rt = type_void; break; case 1: { auto typelt = TypeLT { *n, 0 }; // Get from tuple. n->lt = typelt.lt; rt = typelt.type; break; } default: { auto nt = NewTuple(reqret); nt->tup->assign(rt->tup->begin(), rt->tup->begin() + reqret); rt = nt; } } } // Check if we need to do any lifetime adjustments. AdjustLifetime(n, recip, idents); } // TODO: Can't do this transform ahead of time, since it often depends upon the input args. TypeRef ActualBuiltinType(int flen, TypeRef type, ArgFlags flags, Node *exp, const NativeFun *nf, bool test_overloads, size_t argn, const Node &errorn) { if (flags & NF_BOOL) { type = type->ElementIfNil(); assert(type->t == V_INT); return &st.default_bool_type->thistype; } // See if we can promote the type to one of the standard vector types // (xy/xyz/xyzw). if (!flen) return type; type = type->ElementIfNil(); auto etype = exp ? exp->exptype : nullptr; auto e = etype; size_t i = 0; for (auto vt = type; vt->t == V_VECTOR && i < SymbolTable::NUM_VECTOR_TYPE_WRAPPINGS; vt = vt->sub) { if (vt->sub->Numeric()) { // Check if we allow any vector length. if (!e.Null() && flen == -1 && e->t == V_STRUCT_S) { flen = (int)e->udt->fields.size(); } if (!etype.Null() && flen == -1 && etype->t == V_VAR) { // Special case for "F}?" style types that can be matched against a // DefaultArg, would be good to solve this more elegantly.. // FIXME: don't know arity, but it doesn't matter, so we pick 2.. return st.VectorType(vt, i, 2); } if (flen >= 2 && flen <= 4) { if (!e.Null() && e->t == V_STRUCT_S && (int)e->udt->fields.size() == flen && e->udt->sametype == vt->sub) { // Allow any similar vector type, like "color". return etype; } else { // Require xy/xyz/xyzw return st.VectorType(vt, i, flen); } } } e = !e.Null() && e->t == V_VECTOR ? e->sub : nullptr; i++; } // We arrive here typically if flen == -1 but we weren't able to derive a length. // Sadly, we can't allow to return a vector type instead of a struct, so we error out, // and rely on the user to specify more precise types. // Not sure if there is a better solution. if (!test_overloads) TypeError("cannot deduce struct type for " + (argn ? cat("argument ", argn) : "return value") + " of " + nf->name + (!etype.Null() ? ", got: " + TypeName(etype) : ""), errorn); return type; }; void Stats() { if (min_output_level > OUTPUT_INFO) return; int origsf = 0, clonesf = 0; size_t orignodes = 0, clonenodes = 0; typedef pair Pair; vector funstats; for (auto f : st.functiontable) funstats.push_back({ 0, f }); for (auto sf : st.subfunctiontable) { auto count = sf->body ? sf->body->Count() : 0; if (!sf->next) { origsf++; orignodes += count; } else { clonesf++; clonenodes += count; funstats[sf->parent->idx].first += count; } } LOG_INFO("SF count: orig: ", origsf, ", cloned: ", clonesf); LOG_INFO("Node count: orig: ", orignodes, ", cloned: ", clonenodes); sort(funstats.begin(), funstats.end(), [](const Pair &a, const Pair &b) { return a.first > b.first; }); for (auto &[fsize, f] : funstats) if (fsize > orignodes / 100) { auto &pos = f->overloads.back()->body->line; LOG_INFO("Most clones: ", f->name, " (", st.filenames[pos.fileidx], ":", pos.line, ") -> ", fsize, " nodes accross ", f->NumSubf() - f->overloads.size(), " clones (+", f->overloads.size(), " orig)"); } } }; Node *List::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { assert(false); // Parent calls TypeCheckList. return this; } Node *Unary::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { assert(false); return this; } Node *BinOp::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { assert(false); return this; } Node *Inlined::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { assert(false); // Generated after type-checker in optimizer. return this; } Node *Or::TypeCheck(TypeChecker &tc, size_t reqret) { TypeRef dummy; tc.TypeCheckAndOr(*this, false, reqret, dummy); return this; } Node *And::TypeCheck(TypeChecker &tc, size_t reqret) { TypeRef dummy; tc.TypeCheckAndOr(*this, false, reqret, dummy); return this; } Node *If::TypeCheck(TypeChecker &tc, size_t reqret) { tc.TT(condition, 1, LT_BORROW); tc.NoStruct(*condition, "if"); tc.DecBorrowers(condition->lt, *this); Value cval; bool isconst = condition->ConstVal(tc, cval); if (!Is(falsepart)) { if (!isconst) { auto tleft = tc.TypeCheckBranch(true, condition, truepart, reqret, LT_ANY); auto tright = tc.TypeCheckBranch(false, condition, falsepart, reqret, LT_ANY); // FIXME: this is a bit of a hack. Much better if we had an actual type // to signify NORETURN, to be taken into account in more places. auto truec = AssertIs(truepart); auto falsec = AssertIs(falsepart); if (tc.NeverReturns(truec)) { exptype = tright; lt = falsepart->lt; } else if (tc.NeverReturns(falsec)) { exptype = tleft; lt = truepart->lt; } else { exptype = tc.Union(tleft, tright, true, this); // These will potentially make either body from T_CALL into some // coercion. tc.SubType(truepart, exptype, "then branch", *this); tc.SubType(falsepart, exptype, "else branch", *this); lt = tc.LifetimeUnion(truepart, falsepart, false); } } else if (cval.True()) { // Ignore the else part, optimizer guaranteed to cull it. exptype = tc.TypeCheckBranch(true, condition, truepart, reqret, LT_ANY); lt = truepart->lt; } else { // Ignore the then part, optimizer guaranteed to cull it. exptype = tc.TypeCheckBranch(false, condition, falsepart, reqret, LT_ANY); lt = falsepart->lt; } } else { // No else: this always returns void. if (!isconst || cval.True()) { tc.TypeCheckBranch(true, condition, truepart, 0, LT_ANY); truepart->exptype = type_void; } else { // constant == false: this if-then will get optimized out entirely, ignore it. } falsepart->exptype = type_void; exptype = type_void; lt = LT_ANY; } return this; } Node *While::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(condition, 1, LT_BORROW); tc.NoStruct(*condition, "while"); tc.DecBorrowers(condition->lt, *this); // FIXME: this is caused by call forced to LT_KEEP. auto condc = AssertIs(Forward(condition)); auto condexp = AssertIs(condc->sf->body->children.back()); tc.TypeCheckBranch(true, condexp->child, body, 0, LT_ANY); exptype = type_void; lt = LT_ANY; return this; } Node *For::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { // FIXME: would be good to detect when iter is not written to, so ForLoopElem can be LT_BORROW. // Alternatively we could IncBorrowers on iter, but that would be very restrictive. tc.TT(iter, 1, LT_BORROW); auto itertype = iter->exptype; if (itertype->t == V_INT) {} else if (itertype->t == V_STRING) itertype = type_int; else if (itertype->t == V_VECTOR) itertype = itertype->Element(); else tc.TypeError("for can only iterate over int / string / vector, not: " + TypeName(itertype), *this); auto bodyc = AssertIs(body); auto &args = bodyc->children; if (args.size()) { args[0]->exptype = itertype; // ForLoopElem } tc.TT(body, 0, LT_ANY); tc.DecBorrowers(iter->lt, *this); // Currently always return V_NIL exptype = type_void; lt = LT_ANY; return this; } Node *ForLoopElem::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { // Already been assigned a type in For. lt = LT_KEEP; return this; } Node *ForLoopCounter::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { exptype = type_int; lt = LT_ANY; return this; } Node *Switch::TypeCheck(TypeChecker &tc, size_t reqret) { // TODO: much like If, should only typecheck one case if the value is constant, and do // the corresponding work in the optimizer. tc.TT(value, 1, LT_BORROW); auto ptype = value->exptype; if (!ptype->Numeric() && ptype->t != V_STRING) tc.TypeError("switch value must be int / float / string", *this); exptype = nullptr; bool have_default = false; vector enum_cases; if (ptype->IsEnum()) enum_cases.resize(ptype->e->vals.size()); for (auto &n : cases->children) { tc.TT(n, reqret, LT_KEEP); auto cas = AssertIs(n); if (cas->pattern->children.empty()) have_default = true; for (auto c : cas->pattern->children) { tc.SubTypeT(c->exptype, ptype, *c, "", "case"); tc.DecBorrowers(c->lt, *cas); if (ptype->IsEnum()) { assert(c->exptype->IsEnum()); Value v; if (c->ConstVal(tc, v)) { for (auto [i, ev] : enumerate(ptype->e->vals)) if (ev->val == v.ival()) { enum_cases[i] = true; break; } } } } auto body = AssertIs(cas->body); if (!tc.NeverReturns(body)) { exptype = exptype.Null() ? body->exptype : tc.Union(exptype, body->exptype, true, cas); } } for (auto n : cases->children) { auto cas = AssertIs(n); auto body = AssertIs(cas->body); if (!tc.NeverReturns(body)) { assert(!exptype.Null()); tc.SubType(cas->body, exptype, "", "case block"); } } if (exptype.Null()) exptype = type_void; // Empty switch or all return statements. if (!have_default) { if (reqret) tc.TypeError("switch that returns a value must have a default case", *this); if (ptype->IsEnum()) { for (auto [i, ev] : enumerate(ptype->e->vals)) if (!enum_cases[i]) tc.TypeError("enum value not tested in switch: " + ev->name, *value); } } tc.DecBorrowers(value->lt, *this); lt = LT_KEEP; return this; } Node *Case::TypeCheck(TypeChecker &tc, size_t reqret) { // FIXME: Since string constants are the real use case, LT_KEEP would be more // natural here, as this will introduce a lot of keeprefs. Alternatively make sure // string consts don't introduce keeprefs. tc.TypeCheckList(pattern, false, 1, LT_BORROW); tc.TT(body, reqret, LT_KEEP); exptype = body->exptype; lt = LT_KEEP; return this; } Node *Range::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(start, 1, LT_KEEP); tc.TT(end, 1, LT_KEEP); exptype = start->exptype; if (exptype->t != end->exptype->t || !exptype->Numeric()) tc.TypeError("range can only be two equal numeric types", *this); lt = LT_ANY; return this; } Node *CoDot::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(coroutine, 1, LT_BORROW); // Leave right ident untypechecked. tc.SubType(coroutine, type_coroutine, "coroutine", *this); auto sf = coroutine->exptype->sf; Arg *uarg = nullptr; // This ident is not necessarily the right one. auto var = AssertIs(variable); auto &name = var->sid->id->name; for (auto &arg : sf->coyieldsave.v) if (arg.sid->id->name == name) { if (uarg) tc.TypeError("multiple coroutine variables named: " + name, *this); uarg = &arg; } if (!uarg) tc.TypeError("no coroutine variables named: " + name, *this); var->sid = uarg->sid; var->exptype = exptype = uarg->type; // FIXME: this really also borrows from the actual variable, in case the coroutine is run // again? lt = coroutine->lt; return this; } Node *Define::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { for (auto &p : sids) { tc.UpdateCurrentSid(p.first); // We have to set these here just in case the init exp is a function/coroutine call that // tries use/assign this variable, type_undefined will force that to be an error. // TODO: could make this a specialized error, but probably not worth it because it is rare. p.first->type = type_undefined; p.first->lt = LT_UNDEF; } // We default to LT_KEEP here. // There are case where we could allow borrow, but in practise this runs into trouble easily: // - Variables that later get assigned (!sid->id->single_assignment) where taking ownership // was really what was intended (since the lval being assigned from may go away). // - old := cur cases, where old is meant to hang on to the previous value as cur gets updated, // which then runs into borrowing errors. tc.TT(child, sids.size(), LT_KEEP); for (auto [i, p] : enumerate(sids)) { auto var = TypeLT(*child, i); if (!p.second.Null()) { var.type = p.second; // Have to subtype the initializer value, as that node may contain // unbound vars (a:[int] = []) or values that that need to be coerced // (a:float = 1) tc.SubType(child, var.type, "initializer", "definition"); } auto sid = p.first; sid->type = var.type; tc.StorageType(var.type, *this); sid->type = var.type; sid->lt = var.lt; LOG_DEBUG("var: ", sid->id->name, ":", TypeName(var.type)); if (sid->id->logvar) { for (auto &sc : tc.scopes) if (sc.sf->iscoroutine) tc.TypeError("can\'t use log variable inside coroutine", *this); } } exptype = type_void; lt = LT_ANY; return this; } Node *AssignList::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { for (auto &c : children) { if (c != children.back()) { tc.TT(c, 1, LT_BORROW); tc.DecBorrowers(c->lt, *this); } else { tc.TT(c, children.size() - 1, LT_MULTIPLE /*unused*/, &children); } } for (size_t i = 0; i < children.size() - 1; i++) { auto left = children[i]; tc.CheckLval(left); TypeRef righttype = children.back()->exptype->Get(i); FlowItem fi(*left, left->exptype); assert(fi.IsValid()); tc.AssignFlowDemote(fi, righttype, false); tc.SubTypeT(righttype, left->exptype, *this, "right"); tc.StorageType(left->exptype, *left); // TODO: should call tc.AssignFlowPromote(*left, vartype) here? } exptype = type_void; lt = LT_ANY; return this; } Node *IntConstant::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { exptype = from ? &from->e->thistype : type_int; lt = LT_ANY; return this; } Node *FloatConstant::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { exptype = type_float; lt = LT_ANY; return this; } Node *StringConstant::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { exptype = type_string; // The VM keeps all the constant strings for the length of the program, // so these can be borrow, avoiding a ton of keepvars when used in + and // builtin functions etc (at the cost of some increfs when stored in vars // and data structures). lt = STRING_CONSTANTS_KEEP ? LT_KEEP : LT_BORROW; return this; } Node *Nil::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { exptype = !giventype.Null() ? giventype : tc.st.Wrap(tc.NewTypeVar(), V_NIL); lt = LT_ANY; return this; } Node *Plus::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckMathOp(*this); return this; } Node *Minus::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckMathOp(*this); return this; } Node *Multiply::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckMathOp(*this); return this; } Node *Divide::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckMathOp(*this); return this; } Node *Mod::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckMathOp(*this); return this; } Node *PlusEq::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckMathOpEq(*this); return this; } Node *MultiplyEq::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckMathOpEq(*this); return this; } Node *MinusEq::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckMathOpEq(*this); return this; } Node *DivideEq::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckMathOpEq(*this); return this; } Node *ModEq::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckMathOpEq(*this); return this; } Node *NotEqual::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckComp(*this); return this; } Node *Equal::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckComp(*this); return this; } Node *GreaterThanEq::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckComp(*this); return this; } Node *LessThanEq::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckComp(*this); return this; } Node *GreaterThan::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckComp(*this); return this; } Node *LessThan::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckComp(*this); return this; } Node *Not::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(child, 1, LT_BORROW); tc.DecBorrowers(child->lt, *this); tc.NoStruct(*child, "not"); exptype = &tc.st.default_bool_type->thistype; lt = LT_ANY; return this; } Node *BitAnd::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckBitOp(*this); return this; } Node *BitOr::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckBitOp(*this); return this; } Node *Xor::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckBitOp(*this); return this; } Node *ShiftLeft::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckBitOp(*this); return this; } Node *ShiftRight::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckBitOp(*this); return this; } Node *Negate::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(child, 1, LT_BORROW); tc.SubType(child, type_int, "negated value", *this); tc.DecBorrowers(child->lt, *this); exptype = child->exptype; lt = LT_ANY; return this; } Node *PostDecr::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckPlusPlus(*this); return this; } Node *PostIncr::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckPlusPlus(*this); return this; } Node *PreDecr::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckPlusPlus(*this); return this; } Node *PreIncr::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckPlusPlus(*this); return this; } Node *UnaryMinus::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(child, 1, LT_BORROW); exptype = child->exptype; if (!exptype->Numeric() && (exptype->t != V_STRUCT_S || !exptype->udt->sametype->Numeric())) tc.TypeError("numeric / numeric struct", exptype, *this); tc.DecBorrowers(child->lt, *this); lt = LT_KEEP; return this; } Node *IdentRef::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.UpdateCurrentSid(sid); for (auto &sc : reverse(tc.scopes)) if (sc.sf == sid->sf_def) goto in_scope; tc.TypeError("free variable not in scope: " + sid->id->name, *this); in_scope: exptype = tc.TypeCheckId(sid); FlowItem fi(*this, exptype); assert(fi.IsValid()); exptype = tc.UseFlow(fi); lt = tc.PushBorrow(this); return this; } Node *Assign::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(left, 1, LT_BORROW); tc.DecBorrowers(left->lt, *this); tc.TT(right, 1, tc.LvalueLifetime(*left, false)); tc.CheckLval(left); FlowItem fi(*left, left->exptype); if (fi.IsValid()) { left->exptype = tc.AssignFlowDemote(fi, right->exptype, true); } tc.SubType(right, left->exptype, "right", *this); if (fi.IsValid()) tc.AssignFlowPromote(*left, right->exptype); exptype = left->exptype; if (fi.IsValid()) exptype = tc.UseFlow(fi); lt = tc.PushBorrow(left); return this; } Node *DefaultVal::TypeCheck(TypeChecker &tc, size_t reqret) { // This is used as a default value for native call arguments. The variable // makes it equal to whatever the function expects, then codegen can use that type // to generate a correct value. // Also used as an empty else branch. exptype = reqret ? tc.NewTypeVar() : type_void; lt = LT_ANY; return this; } Node *GenericCall::TypeCheck(TypeChecker &tc, size_t reqret) { // Here we decide which of Dot / Call / NativeCall this call should be transformed into. tc.TypeCheckList(this, false, 1, LT_ANY); auto nf = tc.parser.natreg.FindNative(name); auto fld = tc.st.FieldUse(name); TypeRef type; UDT *udt = nullptr; if (children.size()) { type = children[0]->exptype; if (IsUDT(type->t)) udt = type->udt; } Node *r = nullptr; if (fld && dotnoparens && udt && udt->Has(fld) >= 0) { auto dot = new Dot(fld, *this); dot->children = children; dot->TypeCheckSpecialized(tc, reqret); r = dot; } else { // See if any of sf's specializations matches type exactly, then it overrides nf. bool prefer_sf = false; if (sf && udt && sf->parent->nargs()) { for (auto sfi : sf->parent->overloads) { if (sfi->args.v[0].type->udt == udt) { prefer_sf = true; break; } } } if (nf && !prefer_sf) { auto nc = new NativeCall(nf, *this); nc->children = children; nc->TypeCheckSpecialized(tc, reqret); r = nc; } else if (sf) { auto fc = new Call(*this); fc->children = children; fc->TypeCheckSpecialized(tc, reqret); r = fc; } else { if (fld && dotnoparens) tc.TypeError("type " + TypeName(type) + " does not have field: " + fld->name, *this); tc.TypeError("unknown field/function reference: " + name, *this); } } children.clear(); delete this; return r; } void NativeCall::TypeCheckSpecialized(TypeChecker &tc, size_t /*reqret*/) { if (nf->first->overloads) { // Multiple overloads available, figure out which we want to call. auto cnf = nf->first; auto nargs = Arity(); for (; cnf; cnf = cnf->overloads) { if (cnf->args.v.size() != nargs) continue; for (auto [i, arg] : enumerate(cnf->args.v)) { // Special purpose treatment of V_ANY to allow generic vectors in overloaded // length() etc. if (arg.type->t != V_ANY && (arg.type->t != V_VECTOR || children[i]->exptype->t != V_VECTOR || arg.type->sub->t != V_ANY) && !tc.ConvertsTo(children[i]->exptype, tc.ActualBuiltinType(arg.fixed_len, arg.type, arg.flags, children[i], nf, true, i + 1, *this), arg.type->t != V_STRING, false)) goto nomatch; } nf = cnf; break; nomatch:; } if (!cnf) tc.NatCallError("arguments match no overloads of ", nf, *this); } vector argtypes(children.size()); for (auto [i, c] : enumerate(children)) { auto &arg = nf->args.v[i]; auto argtype = tc.ActualBuiltinType(arg.fixed_len, arg.type, arg.flags, children[i], nf, false, i + 1, *this); // Filter out functions that are not struct aware. bool typed = false; if (argtype->t == V_NIL && argtype->sub->Numeric() && !Is(c)) { // This is somewhat of a hack, because we conflate V_NIL with being optional // for native functions, but we don't want numeric types to be nilable. // Codegen has a special case for T_DEFAULTVAL however. argtype = argtype->sub; } if (arg.flags & NF_CONVERTANYTOSTRING && c->exptype->t != V_STRING) { tc.AdjustLifetime(c, LT_BORROW); // MakeString wants to borrow. tc.MakeString(c, arg.lt); argtype = type_string; typed = true; } int flag = NF_SUBARG1; for (int sa = 0; sa < 3; sa++) { if (arg.flags & flag) { tc.SubType(c, nf->args.v[sa].type->t == V_VECTOR && argtype->t != V_VECTOR ? argtypes[sa]->sub : argtypes[sa], tc.ArgName(i), nf->name); // Stop these generic params being turned into any by SubType below. typed = true; } flag *= 2; } if (arg.flags & NF_ANYVAR) { if (argtype->t == V_VECTOR) argtype = tc.st.Wrap(tc.NewTypeVar(), V_VECTOR); else if (argtype->t == V_ANY) argtype = tc.NewTypeVar(); else assert(0); } if (arg.flags & NF_CORESUME) { // Specialized typechecking for resume() assert(argtypes[0]->t == V_COROUTINE); auto csf = argtypes[0]->sf; if (csf) { tc.SubType(c, csf->coresumetype, "resume value", *c); } else { if (!Is(c)) tc.TypeError("cannot resume a generic coroutine type with an argument", *this); } if (c->exptype->t == V_VAR) { // No value supplied to resume, and none expected at yield either. // nil will be supplied, so make type reflect that. tc.UnifyVar(tc.NewNilTypeVar(), c->exptype); } typed = true; } if (argtype->t == V_ANY) { if (!arg.flags) { // Special purpose type checking to allow any reference type for functions like // copy/equal/hash etc. Note that this is the only place in the language where // we allow this! if (!IsRefNilNoStruct(c->exptype->t)) tc.TypeError("reference type", c->exptype, *c, nf->args.GetName(i), nf->name); typed = true; } else if (IsStruct(c->exptype->t) && !(arg.flags & NF_PUSHVALUEWIDTH) && c->exptype->udt->numslots > 1) { // Avoid unsuspecting generic functions taking values as args. // TODO: ideally this does not trigger for any functions. tc.TypeError("function does not support this struct type", *this); } } if (nf->fun.fnargs >= 0 && !arg.fixed_len && !(arg.flags & NF_PUSHVALUEWIDTH)) tc.NoStruct(*c, nf->name); if (!typed) tc.SubType(c, argtype, tc.ArgName(i), nf->name); auto actualtype = c->exptype; if (actualtype->IsFunction()) { // We must assume this is going to get called and type-check it auto fsf = actualtype->sf; if (fsf->args.v.size()) { // we have no idea what args. tc.TypeError("function passed to " + nf->name + " cannot take any arguments", *this); } auto chosen = fsf; List args(tc.parser.lex); int vtable_idx = -1; tc.TypeCheckCall(fsf, &args, chosen, *c, false, vtable_idx); assert(vtable_idx < 0); assert(fsf == chosen); } argtypes[i] = actualtype; tc.StorageType(actualtype, *this); tc.AdjustLifetime(c, arg.lt); tc.DecBorrowers(c->lt, *this); } exptype = type_void; // no retvals lt = LT_ANY; if (nf->retvals.v.size() > 1) exptype = tc.NewTuple(nf->retvals.v.size()); for (auto [i, ret] : enumerate(nf->retvals.v)) { int sa = 0; auto type = ret.type; auto rlt = ret.lt; switch (ret.flags) { case NF_SUBARG3: sa++; case NF_SUBARG2: sa++; case NF_SUBARG1: { type = argtypes[sa]; auto nftype = nf->args.v[sa].type; if (nftype->t == V_TYPEID) { assert(!sa); // assumes always first. auto tin = AssertIs(children[0]); if (!Is(tin->child)) type = tin->child->exptype; } if (ret.type->t == V_NIL) { if (!IsNillable(type->t)) tc.TypeError(cat("argument ", sa + 1, " to ", nf->name, " has to be a reference type"), *this); type = tc.st.Wrap(type, V_NIL); } else if (nftype->t == V_VECTOR && ret.type->t != V_VECTOR) { if (type->t == V_VECTOR) type = type->sub; } else if (nftype->t == V_COROUTINE || nftype->t == V_FUNCTION) { auto csf = type->sf; if (csf) { // In theory it is possible this hasn't been generated yet.. type = csf->returntype; } else { // This can happen when typechecking a multimethod with a coroutine arg. tc.TypeError(cat("cannot call ", nf->name, " on generic coroutine type"), *this); } } if (rlt == LT_BORROW) { auto alt = nf->args.v[sa].lt; assert(alt >= LT_BORROW); rlt = alt; } break; } case NF_ANYVAR: type = ret.type->t == V_VECTOR ? tc.st.Wrap(tc.NewTypeVar(), V_VECTOR) : tc.NewTypeVar(); assert(rlt == LT_KEEP); break; } type = tc.ActualBuiltinType(ret.fixed_len, type, ret.flags, !i && Arity() ? children[0] : nullptr, nf, false, 0, *this); if (!IsRefNilVar(type->t)) rlt = LT_ANY; if (nf->retvals.v.size() > 1) { exptype->Set(i, type.get(), rlt); lt = LT_MULTIPLE; } else { exptype = type; lt = rlt; } } if (nf->IsAssert()) { // Special case, add to flow: tc.CheckFlowTypeChanges(true, children[0]); // Also make result non-nil, if it was. if (exptype->t == V_NIL) exptype = exptype->Element(); } nattype = exptype; natlt = lt; } void Call::TypeCheckSpecialized(TypeChecker &tc, size_t reqret) { sf = tc.PreSpecializeFunction(sf); exptype = tc.TypeCheckCall(sf, this, sf, *this, reqret, vtable_idx); lt = sf->ltret; } Node *FunRef::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { sf = tc.PreSpecializeFunction(sf); exptype = &sf->thistype; lt = LT_ANY; return this; } Node *DynCall::TypeCheck(TypeChecker &tc, size_t reqret) { tc.UpdateCurrentSid(sid); tc.TypeCheckId(sid); //if (sid->type->IsFunction()) sid->type = &tc.PreSpecializeFunction(sid->type->sf)->thistype; tc.TypeCheckList(this, false, 1, LT_ANY); tie(exptype, lt) = tc.TypeCheckDynCall(sid, this, sf, reqret); return this; } Node *Return::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { exptype = type_void; lt = LT_ANY; for (auto isc : reverse(tc.scopes)) { if (isc.sf->parent == sf->parent) { sf = isc.sf; // Take specialized version. break; } tc.CheckReturnPast(isc.sf, sf, *this); } // TODO: LT_KEEP here is to keep it simple for now, since ideally we want to also allow // LT_BORROW, but then we have to prove that we don't outlive the owner. // Additionally, we have to do this for reused specializations on new SpecIdents. auto reqlt = LT_KEEP; auto reqret = make_void ? 0 : sf->reqret; // Special (but very common) case: optimize lifetime for "return var" case, where var owns // and this is the only return statement. Without this we'd get an inc on the var that's // immediately undone as the scope ends. auto ir = Is(child); if (ir) { tc.UpdateCurrentSid(ir->sid); // Ahead of time, because ir not typechecked yet. if (ir->sid->lt == LT_KEEP && ir->sid->sf_def == sf && sf->num_returns == 0 && reqret && sf->body->children.back() == this) { reqlt = LT_BORROW; // Fake that we're cool borrowing this. ir->sid->consume_on_last_use = true; // Don't decref this one when going out of scope. } } tc.TT(child, reqret, reqlt); tc.DecBorrowers(child->lt, *this); if (sf == tc.st.toplevel) { // return from program if (child->exptype->NumValues() > 1) tc.TypeError("cannot return multiple values from top level", *this); } auto nsf = tc.TopScope(tc.named_scopes); if (nsf != sf) { // This is a non-local "return from". if (!sf->typechecked) tc.parser.Error("return from " + sf->parent->name + " called out of context", this); } auto never_returns = tc.NeverReturns(child); if (never_returns && make_void && sf->num_returns) { // A return with other returns inside of it that always bypass this return, // so should not contribute to return types. assert(child->exptype->t == V_VOID || child->exptype->t == V_VAR); return this; } if (never_returns && sf->reqret && sf->parent->anonymous) { // A return to the immediately enclosing anonymous function that needs to return a value // but is bypassed. tc.RetVal(child->exptype, sf, *this); // If it's a variable, bind it. return this; } if (!Is(child)) { if (auto mrs = Is(child)) { tc.RetVal(mrs->exptype, sf, *this); for (auto [i, mr] : enumerate(mrs->children)) { if (i < sf->reqret) tc.SubType(mr, sf->returntype->Get(i), tc.ArgName(i), *this); } } else { tc.RetVal(child->exptype, sf, *this); tc.SubType(child, sf->returntype, "", *this); } } else { tc.RetVal(type_void, sf, *this); tc.SubType(child, sf->returntype, "", *this); } return this; } Node *TypeAnnotation::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { exptype = giventype; lt = LT_ANY; return this; } Node *IsType::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(child, 1, LT_BORROW); tc.NoStruct(*child, "is"); // FIXME tc.DecBorrowers(child->lt, *this); exptype = &tc.st.default_bool_type->thistype; lt = LT_ANY; return this; } Node *Constructor::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckList(this, false, 1, LT_KEEP); exptype = giventype; if (exptype.Null()) { if (Arity()) { // No type was specified.. first find union of all elements. TypeRef u(nullptr); for (auto c : children) { u = u.Null() ? c->exptype : tc.Union(u, c->exptype, true, c); } exptype = tc.st.Wrap(u, V_VECTOR); tc.StorageType(exptype, *this); } else { // special case for empty vectors exptype = tc.st.Wrap(tc.NewTypeVar(), V_VECTOR); } } if (IsUDT(exptype->t)) { // We have to check this here, since the parser couldn't check this yet. if (exptype->udt->fields.v.size() < children.size()) tc.TypeError("too many initializers for: " + exptype->udt->name, *this); auto udt = tc.FindStructSpecialization(exptype->udt, this); exptype = &udt->thistype; } for (auto [i, c] : enumerate(children)) { TypeRef elemtype = IsUDT(exptype->t) ? exptype->udt->fields.v[i].type : exptype->Element(); tc.SubType(c, elemtype, tc.ArgName(i), *this); } lt = LT_KEEP; return this; } void Dot::TypeCheckSpecialized(TypeChecker &tc, size_t /*reqret*/) { tc.AdjustLifetime(children[0], LT_BORROW); tc.DecBorrowers(children[0]->lt, *this); // New borrow created below. auto stype = children[0]->exptype; if (!IsUDT(stype->t)) tc.TypeError("class/struct", stype, *this, "object"); auto udt = stype->udt; auto fieldidx = udt->Has(fld); if (fieldidx < 0) tc.TypeError("type " + udt->name + " has no field named " + fld->name, *this); exptype = udt->fields.v[fieldidx].type; FlowItem fi(*this, exptype); if (fi.IsValid()) exptype = tc.UseFlow(fi); lt = tc.PushBorrow(this); //lt = children[0]->lt; // Also LT_BORROW, also depending on the same variable. } Node *Indexing::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(object, 1, LT_BORROW); tc.TT(index, 1, LT_BORROW); tc.DecBorrowers(index->lt, *this); auto vtype = object->exptype; if (vtype->t != V_VECTOR && vtype->t != V_STRING && (!IsStruct(vtype->t) || !vtype->udt->sametype->Numeric())) tc.TypeError("vector/string/numeric struct", vtype, *this, "container"); auto itype = index->exptype; switch (itype->t) { case V_INT: exptype = vtype->t == V_VECTOR ? vtype->Element() : (IsUDT(vtype->t) ? vtype->udt->sametype : type_int); break; case V_STRUCT_S: { if (vtype->t != V_VECTOR) tc.TypeError("multi-dimensional indexing on non-vector", *this); auto &udt = *itype->udt; exptype = vtype; for (auto &field : udt.fields.v) { if (field.type->t != V_INT) tc.TypeError("int field", field.type, *this, "index"); if (exptype->t != V_VECTOR) tc.TypeError("nested vector", exptype, *this, "container"); exptype = exptype->Element(); } break; } default: tc.TypeError("int/struct of int", itype, *this, "index"); } lt = object->lt; // Also LT_BORROW, also depending on the same variable. return this; } Node *Seq::TypeCheck(TypeChecker &tc, size_t reqret) { tc.TT(head, 0, LT_ANY); tc.TT(tail, reqret, LT_ANY); exptype = tail->exptype; lt = tail->lt; return this; } Node *CoClosure::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { exptype = type_function_cocl; lt = LT_ANY; return this; } Node *CoRoutine::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(call, 1, LT_KEEP); tc.NoStruct(*call, "coroutine call return value"); // FIXME if (auto fc = Is(call)) { auto sf = fc->sf; assert(sf->iscoroutine); auto ct = tc.st.NewType(); *ct = Type(V_COROUTINE, sf); exptype = ct; } else { tc.TypeError("coroutine constructor must be regular function call", *call); } lt = LT_KEEP; return this; } Node *TypeOf::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(child, 1, LT_BORROW); tc.DecBorrowers(child->lt, *this); exptype = type_typeid; lt = LT_ANY; return this; } Node *EnumCoercion::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TT(child, 1, LT_BORROW); tc.SubType(child, type_int, "coerced value", *this); tc.DecBorrowers(child->lt, *this); exptype = &e->thistype; lt = LT_ANY; return this; } Node *MultipleReturn::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { tc.TypeCheckList(this, false, 1, LT_ANY); exptype = tc.NewTuple(children.size()); for (auto [i, mrc] : enumerate(children)) exptype->Set(i, mrc->exptype.get(), mrc->lt); lt = LT_MULTIPLE; return this; } Node *EnumRef::TypeCheck(TypeChecker & /*tc*/, size_t /*reqret*/) { return this; } Node *UDTRef::TypeCheck(TypeChecker &tc, size_t /*reqret*/) { for (auto [i, f] : enumerate(udt->fields.v)) { if (f.defaultval && f.type->t == V_ANY) { // FIXME: would be good to not call TT here generically but instead have some // specialized checking, just in case TT has a side effect. tc.TT(f.defaultval, 1, LT_ANY); tc.DecBorrowers(f.defaultval->lt, *this); f.defaultval->lt = LT_UNDEF; f.type = f.defaultval->exptype; } // FIXME: this is a temp limitation, remove. if (udt->thistype.t == V_STRUCT_R && i && IsRefNil(f.type->t) != IsRefNil(udt->fields.v[0].type->t)) tc.TypeError("structs fields must be either all scalar or all references: " + udt->name, *this); } if (!udt->ComputeSizes()) tc.TypeError("structs cannot be self-referential: " + udt->name, *this); exptype = type_void; lt = LT_ANY; return this; } Node *Coercion::TypeCheck(TypeChecker &tc, size_t reqret) { // These have been added by another specialization. We could check if they still apply, but // even more robust is just to remove them, and let them be regenerated if need be. tc.TT(child, reqret, LT_ANY); return tc.DeleteCoercion(this); } bool And::ConstVal(TypeChecker &tc, Value &val) const { return left->ConstVal(tc, val) && (!val.True() || right->ConstVal(tc, val)); } bool Or::ConstVal(TypeChecker &tc, Value &val) const { return left->ConstVal(tc, val) && (val.True() || right->ConstVal(tc, val)); } bool Not::ConstVal(TypeChecker &tc, Value &val) const { auto isconst = child->ConstVal(tc, val); val = Value(!val.True()); return isconst; } bool IsType::ConstVal(TypeChecker &tc, Value &val) const { if (child->exptype == giventype || giventype->t == V_ANY) { val = Value(true); return true; } if (!tc.ConvertsTo(giventype, child->exptype, false)) { val = Value(false); return true; } // This means it is always a reference type, since int/float/function don't convert // into anything without coercion. return false; } bool EnumCoercion::ConstVal(TypeChecker &tc, Value &val) const { return child->ConstVal(tc, val); } } // namespace lobster treesheets-1.0.2/lobster/src/lobster/unicode.h000066400000000000000000000100461352107072600214030ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // To and from UTF-8 unicode conversion functions. inline int ToUTF8(int u, char *out /* must have space for 7 chars */) { assert(u >= 0); // top bit can't be set for (int i = 0; i < 6; i++) { // 6 possible encodings: http://en.wikipedia.org/wiki/UTF-8 int maxbits = 6 + i * 5 + !i; // max bits this encoding can represent if (u < (1 << maxbits)) { // does it fit? int remainbits = i * 6; // remaining bits not encoded in the first byte, store 6 each *out++ = char((0xFE << (maxbits - remainbits)) + (u >> remainbits)); // first byte for (int j = i - 1; j >= 0; j--) *out++ = ((u >> (j * 6)) & 0x3F) | 0x80; // other bytes *out++ = 0; // terminate it return i + 1; // len } } assert(0); // impossible to arrive here return -1; } inline int FromUTF8(string_view &in) { // returns -1 upon corrupt UTF-8 encoding if (in.empty()) return 0; int len = 0; auto c = in.front(); for (int mask = 0x80; mask >= 0x04; mask >>= 1) { // count leading 1 bits if (c & mask) len++; else break; } if ((c << len) & 0x80) return -1; // bit after leading 1's must be 0 in.remove_prefix(1); if (!len) return c; int r = c & ((1 << (7 - len)) - 1); // grab initial bits of the code for (int i = 0; i < len - 1; i++) { if (in.empty()) return -1; c = in.front(); if ((c & 0xC0) != 0x80) return -1; // upper bits must 1 0 r <<= 6; r |= c & 0x3F; // grab 6 more bits of the code in.remove_prefix(1); } return r; } // convenience functions inline string ToUTF8(const wchar_t *in) { string r; char buf[7]; while (*in) { ToUTF8(*in++, buf); r += buf; } return r; } // Appends into dest, returns false if encoding error encountered. inline bool FromUTF8(string_view &in, wstring &dest) { while (!in.empty()) { int u = FromUTF8(in); if (u < 0) return false; dest += (wchar_t)u; // should we check it fits inside a wchar_t ? } return true; } // Returns number of code points, returns -1 if encoding error encountered. inline int StrLenUTF8(string_view in) { int num = 0; while (!in.empty()) { int u = FromUTF8(in); if (u < 0) return -1; num++; } return num; } inline void unit_test_unicode() { char buf[7]; ToUTF8(0x24, buf); assert(!strcmp(buf, "\x24")); ToUTF8(0xA2, buf); assert(!strcmp(buf, "\xC2\xA2")); ToUTF8(0x20AC, buf); assert(!strcmp(buf, "\xE2\x82\xAC")); ToUTF8(0x24B62, buf); assert(!strcmp(buf, "\xF0\xA4\xAD\xA2")); assert(ToUTF8(L"\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8") == "\xe3\x83\xa6\xe3\x83\xbc\xe3\x82\xb6\xe3\x83\xbc\xe5\x88\xa5\xe3\x82\xb5\xe3\x82" "\xa4\xe3\x83\x88"); string_view p; p = "\x24"; assert(FromUTF8(p) == 0x24 && p.empty()); p = "\xC2\xA2"; assert(FromUTF8(p) == 0xA2 && p.empty()); p = "\xE2\x82\xAC"; assert(FromUTF8(p) == 0x20AC && p.empty()); p = "\xF0\xA4\xAD\xA2"; assert(FromUTF8(p) == 0x24B62 && p.empty()); (void)p; wstring dest; p = "\xe3\x83\xa6\xe3\x83\xbc\xe3\x82\xb6\xe3\x83\xbc\xe5\x88" "\xa5\xe3\x82\xb5\xe3\x82\xa4\xe3\x83\x88\x00"; assert(FromUTF8(p, dest) && p.empty()); assert(dest == L"\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8"); } treesheets-1.0.2/lobster/src/lobster/vmdata.h000066400000000000000000001246561352107072600212460ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef LOBSTER_VMDATA #define LOBSTER_VMDATA #include "il.h" namespace bytecode { struct BytecodeFile; } // FIXME namespace lobster { #ifndef NDEBUG #define RTT_ENABLED 1 #define RTT_TYPE_ERRORS 1 #else #define RTT_ENABLED 0 #define RTT_TYPE_ERRORS 0 #endif // These are used with VM_COMPILED_CODE_MODE #define VM_DISPATCH_TRAMPOLINE 1 #define VM_DISPATCH_SWITCH_GOTO 2 #define VM_DISPATCH_METHOD VM_DISPATCH_TRAMPOLINE #define STRING_CONSTANTS_KEEP 0 #define DELETE_DELAY 0 // Typedefs to make pointers and scalars the same size. #if _WIN64 || __amd64__ || __x86_64__ || __ppc64__ || __LP64__ #if !defined(VM_COMPILED_CODE_MODE) || VM_DISPATCH_METHOD != VM_DISPATCH_TRAMPOLINE //#define FORCE_32_BIT_MODEL #endif #ifndef FORCE_32_BIT_MODEL #define VALUE_MODEL_64 1 #else #define VALUE_MODEL_64 0 #endif #else #define VALUE_MODEL_64 0 #endif #if VALUE_MODEL_64 typedef int64_t intp; typedef uint64_t uintp; typedef double floatp; #else typedef int32_t intp; typedef uint32_t uintp; typedef float floatp; #endif typedef vec floatp2; typedef vec floatp3; typedef vec floatp4; typedef vec intp2; typedef vec intp3; typedef vec intp4; const floatp4 floatp4_0 = floatp4(0.0f); const floatp3 floatp3_0 = floatp3(0.0f); const floatp2 floatp2_0 = floatp2(0.0f); const intp2 intp2_0 = intp2((intp)0); const intp2 intp2_1 = intp2(1); const intp3 intp3_0 = intp3((intp)0); enum ValueType : int { // refc types are negative V_MINVMTYPES = -10, V_ANY = -9, // any other reference type. V_STACKFRAMEBUF = -8, V_VALUEBUF = -7, // only used as memory type for vector/coro buffers, not used by Value. V_STRUCT_R = -6, V_RESOURCE = -5, V_COROUTINE = -4, V_STRING = -3, V_CLASS = -2, V_VECTOR = -1, V_NIL = 0, // VM: null reference, Type checker: nillable. V_INT, V_FLOAT, V_FUNCTION, V_YIELD, V_STRUCT_S, V_VAR, // [typechecker only] like V_ANY, except idx refers to a type variable V_TYPEID, // [typechecker only] a typetable offset. V_VOID, // [typechecker/codegen only] this exp does not produce a value. V_TUPLE, // [typechecker/codegen only] this exp produces >1 value. V_UNDEFINED, // [typechecker only] this type should never be accessed. V_MAXVMTYPES }; inline bool IsScalar(ValueType t) { return t == V_INT || t == V_FLOAT; } inline bool IsUnBoxed(ValueType t) { return t == V_INT || t == V_FLOAT || t == V_FUNCTION; } inline bool IsRef(ValueType t) { return t < V_NIL; } inline bool IsRefNil(ValueType t) { return t <= V_NIL; } inline bool IsRefNilVar(ValueType t) { return t <= V_NIL || t == V_VAR; } inline bool IsRefNilStruct(ValueType t) { return t <= V_NIL || t == V_STRUCT_S; } inline bool IsRefNilNoStruct(ValueType t) { return t <= V_NIL && t != V_STRUCT_R; } inline bool IsRuntime(ValueType t) { return t < V_VAR; } inline bool IsRuntimePrintable(ValueType t) { return t <= V_FLOAT; } inline bool IsStruct(ValueType t) { return t == V_STRUCT_R || t == V_STRUCT_S; } inline bool IsUDT(ValueType t) { return t == V_CLASS || IsStruct(t); } inline bool IsNillable(ValueType t) { return IsRef(t) && t != V_STRUCT_R; } inline string_view BaseTypeName(ValueType t) { static const char *typenames[] = { "any", "", "", "struct_ref", "resource", "coroutine", "string", "class", "vector", "nil", "int", "float", "function", "yield_function", "struct_scalar", "variable", "typeid", "void", "tuple", "undefined", }; if (t <= V_MINVMTYPES || t >= V_MAXVMTYPES) { assert(false); return ""; } return typenames[t - V_MINVMTYPES - 1]; } enum type_elem_t : int { // Strongly typed element of typetable. // These must correspond to typetable init in Codegen constructor. TYPE_ELEM_INT = 0, // This has -1 for its enumidx. TYPE_ELEM_FLOAT = 2, TYPE_ELEM_STRING = 3, TYPE_ELEM_RESOURCE = 4, TYPE_ELEM_ANY = 5, TYPE_ELEM_VALUEBUF = 6, TYPE_ELEM_STACKFRAMEBUF = 7, TYPE_ELEM_VECTOR_OF_INT = 8, // 2 each. TYPE_ELEM_VECTOR_OF_FLOAT = 10, TYPE_ELEM_VECTOR_OF_STRING = 12, TYPE_ELEM_VECTOR_OF_VECTOR_OF_INT = 14, TYPE_ELEM_VECTOR_OF_VECTOR_OF_FLOAT = 16, TYPE_ELEM_FIXED_OFFSET_END = 18 }; struct VM; struct TypeInfo { ValueType t; union { type_elem_t subt; // V_VECTOR | V_NIL struct { // V_CLASS, V_STRUCT_* int structidx; int len; int vtable_start; type_elem_t elemtypes[1]; // len elems, followed by len parent types. }; int enumidx; // V_INT, -1 if not an enum. int sfidx; // V_FUNCTION; struct { // V_COROUTINE int cofunidx; type_elem_t yieldtype; }; }; TypeInfo() = delete; TypeInfo(const TypeInfo &) = delete; TypeInfo &operator=(const TypeInfo &) = delete; string Debug(VM &vm, bool rec = true) const; type_elem_t GetElemOrParent(intp i) const { auto pti = elemtypes[len + i]; return pti >= 0 ? pti : elemtypes[i]; } }; struct Value; struct LString; struct LVector; struct LObject; struct LCoRoutine; struct PrintPrefs { intp depth; intp budget; bool quoted; intp decimals; int cycles = -1; int indent = 0; int cur_indent = 0; PrintPrefs(intp _depth, intp _budget, bool _quoted, intp _decimals) : depth(_depth), budget(_budget), quoted(_quoted), decimals(_decimals) {} }; typedef void *(*block_base_t)(VM &); #if VM_DISPATCH_METHOD == VM_DISPATCH_TRAMPOLINE typedef block_base_t block_t; #elif VM_DISPATCH_METHOD == VM_DISPATCH_SWITCH_GOTO typedef intp block_t; #endif // ANY memory allocated by the VM must inherit from this, so we can identify leaked memory struct DynAlloc { type_elem_t tti; // offset into the VM's typetable const TypeInfo &ti(VM &vm) const; DynAlloc(type_elem_t _tti) : tti(_tti) {} }; struct RefObj : DynAlloc { int refc = 1; #if DELETE_DELAY const int *alloc_ip; #endif RefObj(type_elem_t _tti) : DynAlloc(_tti) #if DELETE_DELAY , alloc_ip(nullptr) #endif {} void Inc() { #ifndef NDEBUG if (refc <= 0) { // Should never be "re-vived". #if DELETE_DELAY LOG_DEBUG("revive: ", (size_t)this); #endif assert(false); } #endif #if DELETE_DELAY LOG_DEBUG("inc: ", (size_t)this); #endif refc++; } void Dec(VM &vm) { refc--; #ifndef NDEBUG DECSTAT(vm); #endif #if DELETE_DELAY LOG_DEBUG("dec: ", (size_t)this); #endif if (refc <= 0) { DECDELETE(vm); } } void CycleStr(ostringstream &ss) const { ss << "_" << -refc << "_"; } bool CycleCheck(ostringstream &ss, PrintPrefs &pp) { if (pp.cycles >= 0) { if (refc < 0) { CycleStr(ss); return true; } refc = -(++pp.cycles); } return false; } void DECDELETE(VM &vm); void DECDELETENOW(VM &vm); void DECSTAT(VM &vm); intp Hash(VM &vm); }; extern bool RefEqual(VM &vm, const RefObj *a, const RefObj *b, bool structural); extern void RefToString(VM &vm, ostringstream &ss, const RefObj *ro, PrintPrefs &pp); struct LString : RefObj { intp len; // has to match the Value integer type, since we allow the length to be obtained LString(intp _l); const char *data() const { return (char *)(this + 1); } string_view strv() const { return string_view(data(), len); } void ToString(ostringstream &ss, PrintPrefs &pp); void DeleteSelf(VM &vm); bool operator==(LString &o) { return strv() == o.strv(); } bool operator!=(LString &o) { return strv() != o.strv(); } bool operator< (LString &o) { return strv() < o.strv(); } bool operator<=(LString &o) { return strv() <= o.strv(); } bool operator> (LString &o) { return strv() > o.strv(); } bool operator>=(LString &o) { return strv() >= o.strv(); } intp Hash(); }; // There must be a single of these per type, since they are compared by pointer. struct ResourceType { const char *name; void (* deletefun)(void *); }; struct LResource : RefObj { void *val; const ResourceType *type; LResource(void *v, const ResourceType *t); void DeleteSelf(VM &vm); void ToString(ostringstream &ss) { ss << "(resource:" << type->name << ")"; } }; struct InsPtr { #ifdef VM_COMPILED_CODE_MODE block_t f; explicit InsPtr(block_t _f) : f(_f) {} static_assert(sizeof(block_t) == sizeof(intp), ""); #else intp f; explicit InsPtr(intp _f) : f(_f) {} #ifdef FORCE_32_BIT_MODEL explicit InsPtr(ptrdiff_t _f) : f((intp)_f) {} #endif #endif InsPtr() : f(0) {} bool operator==(const InsPtr o) const { return f == o.f; } bool operator!=(const InsPtr o) const { return f != o.f; } }; #if RTT_ENABLED #define TYPE_INIT(t) type(t), #else #define TYPE_INIT(t) #endif // These pointer types are for use inside Value below. In most other parts of the code we // use naked pointers. #ifndef FORCE_32_BIT_MODEL // We use regular pointers of the current architecture. typedef LString *LStringPtr; typedef LVector *LVectorPtr; typedef LObject *LStructPtr; typedef LCoRoutine *LCoRoutinePtr; typedef LResource *LResourcePtr; typedef RefObj *RefObjPtr; #else // We use a compressed pointer to fit in 32-bit on a 64-bit build. // These are shifted by COMPRESS_BITS, so for 3 we can address the bottom 32GB of the // address space. The memory allocator for these values must guarantee we only allocate // from that region, by using mmap or similar. template class CompressedPtr { uint32_t c; enum { COMPRESS_BITS = 3, COMPRESS_MASK = (1 << COMPRESS_BITS) - 1 }; public: CompressedPtr(const T *p) { auto bits = (size_t)p; assert(!(bits & COMPRESS_MASK)); // Must not have low bits set. bits >>= COMPRESS_BITS; assert(!(bits >> 32)); // Must not have high bits set. c = (uint32_t)bits; } T *get() const { return (T *)(((size_t)c) << COMPRESS_BITS); } operator T *() const { return get(); } T *operator->() const { return get(); } }; typedef CompressedPtr LStringPtr; typedef CompressedPtr LVectorPtr; typedef CompressedPtr LStructPtr; typedef CompressedPtr LCoRoutinePtr; typedef CompressedPtr LResourcePtr; typedef CompressedPtr BoxedIntPtr; typedef CompressedPtr BoxedFloatPtr; typedef CompressedPtr RefObjPtr; #endif static_assert(sizeof(intp) == sizeof(floatp) && sizeof(intp) == sizeof(RefObjPtr), "typedefs need fixing"); struct Value { #if RTT_ENABLED ValueType type; #endif private: union { // All these types can be defined to be either all 32 or 64-bit, depending on the // compilation mode. // Non-reference values. intp ival_; // scalars stored as pointer-sized versions. floatp fval_; InsPtr ip_; // Reference values (includes NULL if nillable version). LStringPtr sval_; LVectorPtr vval_; LStructPtr oval_; LCoRoutinePtr cval_; LResourcePtr xval_; // Generic reference access. RefObjPtr ref_; // Temp: for inline structs. TypeInfo *ti_; }; public: // These asserts help track down any invalid code generation issues. intp ival () const { assert(type == V_INT); return ival_; } floatp fval () const { assert(type == V_FLOAT); return fval_; } int intval() const { assert(type == V_INT); return (int)ival_; } float fltval() const { assert(type == V_FLOAT); return (float)fval_; } LString *sval () const { assert(type == V_STRING); return sval_; } LVector *vval () const { assert(type == V_VECTOR); return vval_; } LObject *oval () const { assert(type == V_CLASS); return oval_; } LCoRoutine *cval () const { assert(type == V_COROUTINE); return cval_; } LResource *xval () const { assert(type == V_RESOURCE); return xval_; } RefObj *ref () const { assert(IsRef(type)); return ref_; } RefObj *refnil() const { assert(IsRefNil(type)); return ref_; } InsPtr ip () const { assert(type >= V_FUNCTION); return ip_; } void *any () const { return ref_; } TypeInfo *tival () const { assert(type == V_STRUCT_S); return ti_; } template T ifval() const { if constexpr (is_floating_point()) { assert(type == V_FLOAT); return (T)fval_; } else { assert(type == V_INT); return (T)ival_; } } void setival(intp i) { assert(type == V_INT); ival_ = i; } void setfval(floatp f) { assert(type == V_FLOAT); fval_ = f; } inline Value() : TYPE_INIT(V_NIL) ref_(nullptr) {} inline Value(int i) : TYPE_INIT(V_INT) ival_(i) {} inline Value(uint i) : TYPE_INIT(V_INT) ival_((intp)i) {} inline Value(int64_t i) : TYPE_INIT(V_INT) ival_((intp)i) {} inline Value(uint64_t i) : TYPE_INIT(V_INT) ival_((intp)i) {} inline Value(int i, ValueType t) : TYPE_INIT(t) ival_(i) { (void)t; } inline Value(bool b) : TYPE_INIT(V_INT) ival_(b) {} inline Value(float f) : TYPE_INIT(V_FLOAT) fval_(f) {} inline Value(double f) : TYPE_INIT(V_FLOAT) fval_((floatp)f) {} inline Value(InsPtr i) : TYPE_INIT(V_FUNCTION) ip_(i) {} inline Value(LString *s) : TYPE_INIT(V_STRING) sval_(s) {} inline Value(LVector *v) : TYPE_INIT(V_VECTOR) vval_(v) {} inline Value(LObject *s) : TYPE_INIT(V_CLASS) oval_(s) {} inline Value(LCoRoutine *c) : TYPE_INIT(V_COROUTINE) cval_(c) {} inline Value(LResource *r) : TYPE_INIT(V_RESOURCE) xval_(r) {} inline Value(RefObj *r) : TYPE_INIT(V_NIL) ref_(r) { assert(false); } inline Value(TypeInfo *ti) : TYPE_INIT(V_STRUCT_S) ti_(ti) {} inline bool True() const { return ival_ != 0; } inline Value <INCRT() { assert(IsRef(type) && ref_); ref_->Inc(); return *this; } inline Value <INCRTNIL() { // Can't assert IsRefNil here, since scalar 0 are valid NIL values due to e.g. and/or. if (ref_) LTINCRT(); return *this; } inline Value <INCTYPE(ValueType t) { return IsRefNil(t) ? LTINCRTNIL() : *this; } inline void LTDECRT(VM &vm) const { // we already know its a ref type assert(IsRef(type) && ref_); ref_->Dec(vm); } inline void LTDECRTNIL(VM &vm) const { // Can't assert IsRefNil here, since scalar 0 are valid NIL values due to e.g. and/or. if (ref_) LTDECRT(vm); } inline void LTDECTYPE(VM &vm, ValueType t) const { if (IsRefNil(t)) LTDECRTNIL(vm); } void ToString(VM &vm, ostringstream &ss, const TypeInfo &ti, PrintPrefs &pp) const; void ToStringBase(VM &vm, ostringstream &ss, ValueType t, PrintPrefs &pp) const; bool Equal(VM &vm, ValueType vtype, const Value &o, ValueType otype, bool structural) const; intp Hash(VM &vm, ValueType vtype); Value Copy(VM &vm); // Shallow. }; template inline T *AllocSubBuf(VM &vm, size_t size, type_elem_t tti); template inline void DeallocSubBuf(VM &vm, T *v, size_t size); struct LObject : RefObj { LObject(type_elem_t _tti) : RefObj(_tti) {} // FIXME: reduce the use of these. intp Len(VM &vm) const { return ti(vm).len; } Value *Elems() const { return (Value *)(this + 1); } // This may only be called from a context where i < len has already been ensured/asserted. Value &AtS(intp i) const { return Elems()[i]; } void DeleteSelf(VM &vm); // This may only be called from a context where i < len has already been ensured/asserted. const TypeInfo &ElemTypeS(VM &vm, intp i) const; const TypeInfo &ElemTypeSP(VM &vm, intp i) const; void ToString(VM &vm, ostringstream &ss, PrintPrefs &pp); bool Equal(VM &vm, const LObject &o) { // RefObj::Equal has already guaranteed the typeoff's are the same. auto len = Len(vm); assert(len == o.Len(vm)); for (intp i = 0; i < len; i++) { auto et = ElemTypeS(vm, i).t; if (!AtS(i).Equal(vm, et, o.AtS(i), et, true)) return false; } return true; } intp Hash(VM &vm) { intp hash = 0; for (int i = 0; i < Len(vm); i++) hash ^= AtS(i).Hash(vm, ElemTypeS(vm, i).t); return hash; } void Init(VM &vm, Value *from, intp len, bool inc) { assert(len && len == Len(vm)); t_memcpy(Elems(), from, len); if (inc) for (intp i = 0; i < len; i++) { AtS(i).LTINCTYPE(ElemTypeS(vm, i).t); } } }; struct LVector : RefObj { intp len; // has to match the Value integer type, since we allow the length to be obtained intp maxl; intp width; // TODO: would be great to not have to store this. private: Value *v; // use At() public: LVector(VM &vm, intp _initial, intp _max, type_elem_t _tti); ~LVector() { assert(0); } // destructed by DECREF void DeallocBuf(VM &vm) { if (v) DeallocSubBuf(vm, v, maxl * width); } void DecSlot(VM &vm, intp i, ValueType et) const { AtSlot(i).LTDECTYPE(vm, et); } void DeleteSelf(VM &vm); const TypeInfo &ElemType(VM &vm) const; void Resize(VM &vm, intp newmax); void Push(VM &vm, const Value &val) { assert(width == 1); if (len == maxl) Resize(vm, maxl ? maxl * 2 : 4); v[len++] = val; } void PushVW(VM &vm, const Value *vals) { if (len == maxl) Resize(vm, maxl ? maxl * 2 : 4); tsnz_memcpy(v + len * width, vals, width); len++; } Value Pop() { assert(width == 1); return v[--len]; } void PopVW(Value *dest) { len--; tsnz_memcpy(dest, v + len * width, width); } Value &Top() const { assert(width == 1); return v[len - 1]; } void TopVW(Value *dest) { tsnz_memcpy(dest, v + (len - 1) * width, width); } void Insert(VM &vm, const Value *vals, intp i) { assert(i >= 0 && i <= len); // note: insertion right at the end is legal, hence <= if (len + 1 > maxl) Resize(vm, max(len + 1, maxl ? maxl * 2 : 4)); t_memmove(v + (i + 1) * width, v + i * width, (len - i) * width); len++; tsnz_memcpy(v + i * width, vals, width); } void Remove(VM &vm, intp i, intp n, intp decfrom, bool stack_ret); Value *Elems() { return v; } Value &At(intp i) const { assert(i < len && width == 1); return v[i]; } Value *AtSt(intp i) const { assert(i < len); return v + i * width; } Value &AtSlot(intp i) const { assert(i < len * width); return v[i]; } void AtVW(VM &vm, intp i) const; void AtVWSub(VM &vm, intp i, int w, int off) const; void Append(VM &vm, LVector *from, intp start, intp amount); void ToString(VM &vm, ostringstream &ss, PrintPrefs &pp); bool Equal(VM &vm, const LVector &o) { // RefObj::Equal has already guaranteed the typeoff's are the same. assert(width == 1); if (len != o.len) return false; auto et = ElemType(vm).t; for (intp i = 0; i < len; i++) { if (!At(i).Equal(vm, et, o.At(i), et, true)) return false; } return true; } intp Hash(VM &vm) { intp hash = 0; assert(width == 1); auto et = ElemType(vm).t; for (int i = 0; i < len; i++) hash ^= At(i).Hash(vm, et); return hash; } void Init(VM &vm, Value *from, bool inc) { assert(len); t_memcpy(v, from, len * width); auto et = ElemType(vm).t; if (inc && IsRefNil(et)) { for (intp i = 0; i < len; i++) { At(i).LTINCRTNIL(); } } } }; struct VMLog { struct LogVar { vector values; size_t read; const TypeInfo *type; }; vector logvars; VM &vm; VMLog(VM &_vm); void LogInit(const uchar *bcf); void LogPurge(); void LogFrame(); Value LogGet(Value def, int idx); void LogWrite(Value newval, int idx); void LogCleanup(); }; struct StackFrame { InsPtr retip; const int *funstart; int spstart; }; struct NativeFun; struct NativeRegistry; // This contains all data shared between threads. struct TupleSpace { struct TupleType { // We have an independent list of tuples and synchronization per type, for minimum // contention. list tuples; mutex mtx; condition_variable condition; }; vector tupletypes; atomic alive = true; TupleSpace(size_t numstructs) : tupletypes(numstructs) {} ~TupleSpace() { for (auto &tt : tupletypes) for (auto p : tt.tuples) delete[] p; } }; enum class TraceMode { OFF, ON, TAIL }; struct VMArgs { NativeRegistry 𝔫 string_view programname; string bytecode_buffer; const void *entry_point = nullptr; const void *static_bytecode = nullptr; size_t static_size = 0; vector program_args; const lobster::block_t *native_vtables = nullptr; TraceMode trace = TraceMode::OFF; }; struct VM : VMArgs { SlabAlloc pool; Value *stack = nullptr; int stacksize = 0; int maxstacksize; int sp = -1; Value retvalstemp[MAX_RETURN_VALUES]; #ifdef VM_COMPILED_CODE_MODE block_t next_call_target = 0; #else const int *ip = nullptr; #endif vector stackframes; LCoRoutine *curcoroutine = nullptr; Value *vars = nullptr; size_t codelen = 0; const int *codestart = nullptr; vector codebigendian; vector typetablebigendian; uint64_t *byteprofilecounts = nullptr; const bytecode::BytecodeFile *bcf = nullptr; PrintPrefs programprintprefs { 10, 100000, false, -1 }; const type_elem_t *typetable = nullptr; string evalret; int currentline = -1; int maxsp = -1; PrintPrefs debugpp { 2, 50, true, -1 }; VMLog vml { *this }; ostringstream ss_reuse; vector trace_output; size_t trace_ring_idx = 0; vector delete_delay; vector constant_strings; vector vtables; int64_t vm_count_ins = 0; int64_t vm_count_fcalls = 0; int64_t vm_count_bcalls = 0; int64_t vm_count_decref = 0; //#define VM_ERROR_RET_EXPERIMENT #if defined(VM_ERROR_RET_EXPERIMENT) && !defined(VM_COMPILED_CODE_MODE) #define VM_INS_RET bool #define VM_RET return false #define VM_TERMINATE return true #else #define VM_INS_RET void #define VM_RET #define VM_TERMINATE #endif typedef VM_INS_RET (VM::* f_ins_pointer)(); f_ins_pointer f_ins_pointers[IL_MAX_OPS]; const void *compiled_code_ip = nullptr; bool is_worker = false; vector workers; TupleSpace *tuple_space = nullptr; VM(VMArgs &&args); ~VM(); void OneMoreFrame(); const TypeInfo &GetTypeInfo(type_elem_t offset) { return *(TypeInfo *)(typetable + offset); } const TypeInfo &GetVarTypeInfo(int varidx); void SetMaxStack(int ms) { maxstacksize = ms; } string_view GetProgramName() { return programname; } type_elem_t GetIntVectorType(int which); type_elem_t GetFloatVectorType(int which); void DumpVal(RefObj *ro, const char *prefix); void DumpFileLine(const int *fip, ostringstream &ss); void DumpLeaks(); ostringstream &TraceStream(); void OnAlloc(RefObj *ro); LVector *NewVec(intp initial, intp max, type_elem_t tti); LObject *NewObject(intp max, type_elem_t tti); LCoRoutine *NewCoRoutine(InsPtr rip, const int *vip, LCoRoutine *p, type_elem_t tti); LResource *NewResource(void *v, const ResourceType *t); LString *NewString(size_t l); LString *NewString(string_view s); LString *NewString(string_view s1, string_view s2); LString *ResizeString(LString *s, intp size, int c, bool back); Value Error(string err, const RefObj *a = nullptr, const RefObj *b = nullptr); Value BuiltinError(string err) { return Error(err); } void VMAssert(const char *what); void VMAssert(const char *what, const RefObj *a, const RefObj *b); int DumpVar(ostringstream &ss, const Value &x, size_t idx, bool dumpglobals); void FinalStackVarsCleanup(); void StartWorkers(size_t numthreads); void TerminateWorkers(); void WorkerWrite(RefObj *ref); LObject *WorkerRead(type_elem_t tti); #ifdef VM_COMPILED_CODE_MODE #define VM_COMMA , #define VM_OP_ARGS const int *ip #define VM_OP_ARGS_CALL block_t fcont #define VM_IP_PASS_THRU ip #define VM_FC_PASS_THRU fcont #define VM_JMP_RET bool #else #define VM_COMMA #define VM_OP_ARGS #define VM_OP_ARGS_CALL #define VM_IP_PASS_THRU #define VM_FC_PASS_THRU #define VM_JMP_RET VM_INS_RET #endif void JumpTo(InsPtr j); InsPtr GetIP(); template int VarCleanup(ostringstream *error, int towhere); void StartStackFrame(InsPtr retip); void FunIntroPre(InsPtr fun); void FunIntro(VM_OP_ARGS); void FunOut(int towhere, int nrv); void CoVarCleanup(LCoRoutine *co); void CoNonRec(const int *varip); void CoNew(VM_OP_ARGS VM_COMMA VM_OP_ARGS_CALL); void CoSuspend(InsPtr retip); void CoClean(); void CoYield(VM_OP_ARGS_CALL); void CoResume(LCoRoutine *co); void EndEval(const Value &ret, const TypeInfo &ti); void InstructionPointerInit() { #ifdef VM_COMPILED_CODE_MODE #define F(N, A) f_ins_pointers[IL_##N] = nullptr; #else #define F(N, A) f_ins_pointers[IL_##N] = &VM::F_##N; #endif ILNAMES #undef F } #define VM_OP_ARGS0 #define VM_OP_ARGS1 int _a #define VM_OP_ARGS2 int _a, int _b #define VM_OP_ARGS3 int _a, int _b, int _c #define VM_OP_ARGS9 VM_OP_ARGS // ILUNKNOWNARITY #define VM_OP_ARGSN(N) VM_OP_ARGS##N #define VM_OP_DEFS0 #define VM_OP_DEFS1 int _a = *ip++; #define VM_OP_DEFS2 int _a = *ip++; int _b = *ip++; #define VM_OP_DEFS3 int _a = *ip++; int _b = *ip++; int _c = *ip++; #define VM_OP_DEFS9 // ILUNKNOWNARITY #define VM_OP_DEFSN(N) VM_OP_DEFS##N (void)ip; #define VM_OP_PASS0 #define VM_OP_PASS1 _a #define VM_OP_PASS2 _a, _b #define VM_OP_PASS3 _a, _b, _c #define VM_OP_PASS9 VM_IP_PASS_THRU // ILUNKNOWNARITY #define VM_OP_PASSN(N) VM_OP_PASS##N #define VM_COMMA_0 #define VM_COMMA_1 , #define VM_COMMA_2 , #define VM_COMMA_3 , #define VM_COMMA_9 , #define VM_COMMA_IF(N) VM_COMMA_##N #define VM_CCOMMA_0 #define VM_CCOMMA_1 VM_COMMA #define VM_CCOMMA_2 VM_COMMA #define VM_CCOMMA_9 VM_COMMA #define VM_CCOMMA_IF(N) VM_CCOMMA_##N #define F(N, A) VM_INS_RET U_##N(VM_OP_ARGSN(A)); \ VM_INS_RET F_##N(VM_OP_ARGS) { \ VM_OP_DEFSN(A); \ return U_##N(VM_OP_PASSN(A)); \ } LVALOPNAMES #undef F #define F(N, A) VM_INS_RET U_##N(VM_OP_ARGSN(A)); \ VM_INS_RET F_##N(VM_OP_ARGS) { \ VM_OP_DEFSN(A); \ return U_##N(VM_OP_PASSN(A)); \ } ILBASENAMES #undef F #define F(N, A) VM_INS_RET U_##N(VM_OP_ARGSN(A) VM_CCOMMA_IF(A) VM_OP_ARGS_CALL); \ VM_INS_RET F_##N(VM_OP_ARGS VM_COMMA VM_OP_ARGS_CALL) { \ VM_OP_DEFSN(A); \ return U_##N(VM_OP_PASSN(A) VM_CCOMMA_IF(A) VM_FC_PASS_THRU); \ } ILCALLNAMES #undef F #define F(N, A) VM_JMP_RET U_##N(); VM_JMP_RET F_##N() { return U_##N(); } ILJUMPNAMES #undef F #pragma push_macro("LVAL") #undef LVAL #define LVAL(N, V) void LV_##N(Value &a VM_COMMA_IF(V) VM_OP_ARGSN(V)); LVALOPNAMES #undef LVAL #pragma pop_macro("LVAL") void EvalProgram(); void EvalProgramInner(); VM_JMP_RET ForLoop(intp len); Value &GetFieldLVal(intp i); Value &GetFieldILVal(intp i); Value &GetLocLVal(int i); Value &GetVecLVal(intp i); void PushDerefIdxVector(intp i); void PushDerefIdxVectorSub(intp i, int width, int offset); void PushDerefIdxStruct(intp i, int l); void PushDerefIdxString(intp i); void LvalueIdxVector(int lvalop, intp i); void LvalueIdxStruct(int lvalop, intp i); void LvalueField(int lvalop, intp i); void LvalueOp(int op, Value &a); string ProperTypeName(const TypeInfo &ti); void Div0() { Error("division by zero"); } void IDXErr(intp i, intp n, const RefObj *v); void BCallProf(); void BCallRetCheck(const NativeFun *nf); intp GrabIndex(int len); #define VM_PUSH(v) (stack[++sp] = (v)) #define VM_TOP() (stack[sp]) #define VM_TOPM(n) (stack[sp - (n)]) #define VM_POP() (stack[sp--]) #define VM_POPN(n) (sp -= (n)) #define VM_PUSHN(n) (sp += (n)) #define VM_TOPPTR() (stack + sp + 1) void Push(const Value &v) { VM_PUSH(v); } Value Pop() { return VM_POP(); } Value Top() { return VM_TOP(); } Value *TopPtr() { return VM_TOPPTR(); } void PushN(int n) { VM_PUSHN(n); } void PopN(int n) { VM_POPN(n); } pair PopVecPtr() { auto width = VM_POP().intval(); VM_POPN(width); return { VM_TOPPTR(), width }; } template void PushVec(const vec &v, int truncate = 4) { auto l = min(N, truncate); for (int i = 0; i < l; i++) VM_PUSH(v[i]); } template T PopVec(typename T::CTYPE def = 0) { T v; auto l = VM_POP().intval(); if (l > T::NUM_ELEMENTS) VM_POPN(l - T::NUM_ELEMENTS); for (int i = T::NUM_ELEMENTS - 1; i >= 0; i--) { v[i] = i < l ? VM_POP().ifval() : def; } return v; } template void PushAnyAsString(const T &t) { Push(NewString(string_view((char *)&t, sizeof(T)))); } template void PopAnyFromString(T &t) { auto s = Pop(); assert(s.type == V_STRING); assert(s.sval()->len == sizeof(T)); t = *(T *)s.sval()->strv().data(); s.LTDECRT(*this); } string_view StructName(const TypeInfo &ti); string_view ReverseLookupType(uint v); void Trace(TraceMode m) { trace = m; } double Time() { return SecondsSinceStart(); } Value ToString(const Value &a, const TypeInfo &ti) { ss_reuse.str(string()); ss_reuse.clear(); a.ToString(*this, ss_reuse, ti, programprintprefs); return NewString(ss_reuse.str()); } Value StructToString(const Value *elems, const TypeInfo &ti) { ss_reuse.str(string()); ss_reuse.clear(); StructToString(ss_reuse, programprintprefs, ti, elems); return NewString(ss_reuse.str()); } void StructToString(ostringstream &ss, PrintPrefs &pp, const TypeInfo &ti, const Value *elems); string_view EnumName(intp val, int enumidx); string_view EnumName(int enumidx); optional LookupEnum(string_view name, int enumidx); }; inline int64_t Int64FromInts(int a, int b) { int64_t v = (uint)a; v |= ((int64_t)b) << 32; return v; } inline const TypeInfo &DynAlloc::ti(VM &vm) const { return vm.GetTypeInfo(tti); } template inline T *AllocSubBuf(VM &vm, size_t size, type_elem_t tti) { auto header_sz = max(alignof(T), sizeof(DynAlloc)); auto mem = (uchar *)vm.pool.alloc(size * sizeof(T) + header_sz); ((DynAlloc *)mem)->tti = tti; mem += header_sz; return (T *)mem; } template inline void DeallocSubBuf(VM &vm, T *v, size_t size) { auto header_sz = max(alignof(T), sizeof(DynAlloc)); auto mem = ((uchar *)v) - header_sz; vm.pool.dealloc(mem, size * sizeof(T) + header_sz); } template LString *WriteMem(VM &vm, LString *s, intp i, const void *data, size_t size) { auto minsize = i + (intp)size; if (s->len < minsize) s = vm.ResizeString(s, minsize * 2, 0, back); memcpy((void *)(s->data() + (back ? s->len - i - size : i)), data, size); return s; } template LString *WriteValLE(VM &vm, LString *s, intp i, T val) { T t = flatbuffers::EndianScalar(val); return WriteMem(vm, s, i, &t, sizeof(T)); } template T ReadValLE(const LString *s, intp i) { T val; memcpy(&val, (void *)(s->data() + (back ? s->len - i - sizeof(T) : i)), sizeof(T)); return flatbuffers::EndianScalar(val); } // FIXME: turn check for len into an assert and make caller guarantee lengths match. template inline vec ValueToF(const Value *v, intp width, floatp def = 0) { vec t; for (int i = 0; i < N; i++) t[i] = width > i ? (v + i)->fval() : def; return t; } template inline vec ValueToI(const Value *v, intp width, intp def = 0) { vec t; for (int i = 0; i < N; i++) t[i] = width > i ? (v + i)->ival() : def; return t; } template inline vec ValueToFLT(const Value *v, intp width, float def = 0) { vec t; for (int i = 0; i < N; i++) t[i] = width > i ? (v + i)->fltval() : def; return t; } template inline vec ValueToINT(const Value *v, intp width, int def = 0) { vec t; for (int i = 0; i < N; i++) t[i] = width > i ? (v + i)->intval() : def; return t; } template inline void ToValue(Value *dest, intp width, const vec &v) { for (intp i = 0; i < width; i++) dest[i] = i < N ? v[i] : 0; } inline intp RangeCheck(VM &vm, const Value &idx, intp range, intp bias = 0) { auto i = idx.ival(); if (i < bias || i >= bias + range) vm.BuiltinError(cat("index out of range [", bias, "..", bias + range, "): ", i)); return i; } template inline T GetResourceDec(VM &vm, const Value &val, const ResourceType *type) { if (!val.True()) return nullptr; auto x = val.xval(); if (x->type != type) vm.BuiltinError(string_view("needed resource type: ") + type->name + ", got: " + x->type->name); return (T)x->val; } inline vector ValueToVectorOfStrings(Value &v) { vector r; for (int i = 0; i < v.vval()->len; i++) r.push_back(string(v.vval()->At(i).sval()->strv())); return r; } inline Value ToValueOfVectorOfStrings(VM &vm, const vector &in) { auto v = vm.NewVec(0, (intp)in.size(), TYPE_ELEM_VECTOR_OF_STRING); for (auto &a : in) v->Push(vm, vm.NewString(a)); return Value(v); } inline Value ToValueOfVectorOfStringsEmpty(VM &vm, const int2 &size, char init) { auto v = vm.NewVec(0, size.y, TYPE_ELEM_VECTOR_OF_STRING); for (int i = 0; i < size.y; i++) { auto s = vm.NewString(size.x); memset((char *)s->data(), init, size.x); v->Push(vm, s); } return Value(v); } void EscapeAndQuote(string_view s, ostringstream &ss); struct LCoRoutine : RefObj { bool active = true; // Goes to false when it has hit the end of the coroutine instead of a yield. int stackstart; // When currently running, otherwise -1 Value *stackcopy = nullptr; int stackcopylen = 0; int stackcopymax = 0; int stackframestart; // When currently running, otherwise -1 StackFrame *stackframescopy = nullptr; int stackframecopylen = 0; int stackframecopymax = 0; int top_at_suspend = -1; InsPtr returnip; const int *varip; LCoRoutine *parent; LCoRoutine(int _ss, int _sfs, InsPtr _rip, const int *_vip, LCoRoutine *_p, type_elem_t cti) : RefObj(cti), stackstart(_ss), stackframestart(_sfs), returnip(_rip), varip(_vip), parent(_p) {} Value &Current(VM &vm) { if (stackstart >= 0) vm.BuiltinError("cannot get value of active coroutine"); return stackcopy[stackcopylen - 1].LTINCTYPE(vm.GetTypeInfo(ti(vm).yieldtype).t); } void Resize(VM &vm, int newlen) { if (newlen > stackcopymax) { if (stackcopy) DeallocSubBuf(vm, stackcopy, stackcopymax); stackcopy = AllocSubBuf(vm, stackcopymax = newlen, TYPE_ELEM_VALUEBUF); } stackcopylen = newlen; } void ResizeFrames(VM &vm, int newlen) { if (newlen > stackframecopymax) { if (stackframescopy) DeallocSubBuf(vm, stackframescopy, stackframecopymax); stackframescopy = AllocSubBuf(vm, stackframecopymax = newlen, TYPE_ELEM_STACKFRAMEBUF); } stackframecopylen = newlen; } int Suspend(VM &vm, int top, Value *stack, vector &stackframes, InsPtr &rip, LCoRoutine *&curco) { assert(stackstart >= 0); swap(rip, returnip); assert(curco == this); curco = parent; parent = nullptr; ResizeFrames(vm, (int)stackframes.size() - stackframestart); t_memcpy(stackframescopy, stackframes.data() + stackframestart, stackframecopylen); stackframes.erase(stackframes.begin() + stackframestart, stackframes.end()); stackframestart = -1; top_at_suspend = top; Resize(vm, top - stackstart); t_memcpy(stackcopy, stack + stackstart, stackcopylen); int ss = stackstart; stackstart = -1; return ss; } void AdjustStackFrames(int top) { int topdelta = (top + stackcopylen) - top_at_suspend; if (topdelta) { for (int i = 0; i < stackframecopylen; i++) { stackframescopy[i].spstart += topdelta; } } } int Resume(int top, Value *stack, vector &stackframes, InsPtr &rip, LCoRoutine *p) { assert(stackstart < 0); swap(rip, returnip); assert(!parent); parent = p; stackframestart = (int)stackframes.size(); AdjustStackFrames(top); stackframes.insert(stackframes.end(), stackframescopy, stackframescopy + stackframecopylen); stackstart = top; // FIXME: assume that it fits, which is not guaranteed with recursive coros t_memcpy(stack + top, stackcopy, stackcopylen); return stackcopylen; } void BackupParentVars(VM &vm, Value *vars) { // stored here while coro is active Resize(vm, *varip); for (int i = 1; i <= *varip; i++) { auto &var = vars[varip[i]]; // we don't INC, since parent var is still on the stack and will hold ref stackcopy[i - 1] = var; } } Value &AccessVar(int savedvaridx) { assert(stackstart < 0); // Variables are always saved on top of the stack before the stackcopy gets made, so they // are last, followed by the retval (thus -1). return stackcopy[stackcopylen - *varip + savedvaridx - 1]; } Value &GetVar(VM &vm, int ididx) { if (stackstart >= 0) vm.BuiltinError("cannot access locals of running coroutine"); // FIXME: we can probably make it work without this search, but for now no big deal for (int i = 1; i <= *varip; i++) { if (varip[i] == ididx) { return AccessVar(i - 1); } } // This one should be really rare, since parser already only allows lexically contained vars // for that function, could happen when accessing var that's not in the callchain of yields. vm.BuiltinError("local variable being accessed is not part of coroutine state"); return *stackcopy; } void DeleteSelf(VM &vm) { assert(stackstart < 0); if (stackcopy) { auto curvaltype = vm.GetTypeInfo(ti(vm).yieldtype).t; auto &ts = stackcopy[--stackcopylen]; ts.LTDECTYPE(vm, curvaltype); if (active) { for (int i = *varip; i > 0; i--) { auto &vti = vm.GetVarTypeInfo(varip[i]); stackcopy[--stackcopylen].LTDECTYPE(vm, vti.t); } top_at_suspend -= *varip + 1; // This calls Resume() to get the rest back onto the stack, then unwinds it. vm.CoVarCleanup(this); } else { assert(!stackcopylen); } DeallocSubBuf(vm, stackcopy, stackcopymax); } if (stackframescopy) DeallocSubBuf(vm, stackframescopy, stackframecopymax); vm.pool.dealloc(this, sizeof(LCoRoutine)); } ValueType ElemType(VM &vm, int i) { assert(i < *varip); auto varidx = varip[i + 1]; auto &vti = vm.GetVarTypeInfo(varidx); auto vt = vti.t; if (vt == V_NIL) vt = vm.GetTypeInfo(vti.subt).t; #if RTT_ENABLED auto &var = AccessVar(i); // FIXME: For testing. if(vt != var.type && var.type != V_NIL && !(vt == V_VECTOR && IsUDT(var.type))) { LOG_INFO("coro elem ", vti.Debug(vm), " != ", BaseTypeName(var.type)); assert(false); } #endif return vt; } }; } // namespace lobster #endif // LOBSTER_VMDATA treesheets-1.0.2/lobster/src/lobster/wasm_binary_writer.h000066400000000000000000000642421352107072600236730ustar00rootroot00000000000000// Copyright 2019 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef WASM_BINARY_WRITER_H #define WASM_BINARY_WRITER_H #include "assert.h" #include "cstring" #include "vector" #include "string" #include "string_view" // Stand-alone single header WASM module writer class. // Takes care of the "heavy lifting" of generating the binary format // correctly, and provide a friendly code generation API, that may // be useful outside of Lobster as well. // Documentation and example of the API in this file: // http://aardappel.github.io/lobster/implementation_wasm.html // (and see also test cases in wasm_binary_writer_test.h) // Main use of this API in towasm.cpp. namespace WASM { enum class Section { None = -1, Custom = 0, Type, Import, Function, Table, Memory, Global, Export, Start, Element, Code, Data, }; enum { I32 = 0x7F, I64 = 0x7E, F32 = 0x7D, F64 = 0x7C, ANYFUNC = 0x70, FUNC = 0x60, VOID = 0x40, }; class BinaryWriter { std::vector &buf; Section cur_section = Section::None; Section last_known_section = Section::None; size_t section_size = 0; size_t section_count = 0; size_t section_data = 0; size_t section_index_in_file = 0; size_t section_index_in_file_code = 0; size_t section_index_in_file_data = 0; size_t segment_payload_start = 0; size_t num_function_imports = 0; size_t num_global_imports = 0; size_t num_function_decls = 0; struct Function { std::string name; bool import; bool local; }; std::vector function_symbols; size_t function_body_start = 0; size_t data_section_size = 0; struct DataSegment { std::string name; size_t align; size_t size; bool local; }; std::vector data_segments; struct Reloc { uint8_t type; size_t src_offset; // Where we are doing the relocation. size_t sym_index; // If is_function. size_t target_index; // Index inside thing we're referring to, e.g. addend. bool is_function; }; std::vector code_relocs; std::vector data_relocs; template void UInt8(T v) { buf.push_back(static_cast(v)); } template void UInt16(T v) { UInt8(v & 0xFF); UInt8(v >> 8); } template void UInt32(T v) { UInt16(v & 0xFFFF); UInt16(v >> 16); } template void UInt64(T v) { UInt32(v & 0xFFFFFFFF); UInt32(v >> 32); } template U Bits(T v) { static_assert(sizeof(T) == sizeof(U), ""); U u; memcpy(&u, &v, sizeof(T)); return u; } template void ULEB(T v) { for (;;) { UInt8(v & 0x7F); v = (T)(v >> 7); if (!v) break; buf.back() |= 0x80; } } template void SLEB(T v) { auto negative = v < 0; for (;;) { UInt8(v & 0x7F); auto sign = v & 0x40; v = (T)(v >> 7); if (negative) v |= T(0x7F) << (sizeof(T) * 8 - 7); if ((!v && !sign) || (v == -1 && sign)) break; buf.back() |= 0x80; } } enum { PATCHABLE_ULEB_SIZE = 5 }; size_t PatchableLEB() { auto pos = buf.size(); for (size_t i = 0; i < PATCHABLE_ULEB_SIZE - 1; i++) UInt8(0x80); UInt8(0x00); return pos; } void PatchULEB(size_t pos, size_t v) { for (size_t i = 0; i < PATCHABLE_ULEB_SIZE; i++) { buf[pos + i] |= v & 0x7F; v >>= 7; } assert(!v); } void Chars(std::string_view chars) { for (auto c : chars) UInt8(c); } size_t LenChars(std::string_view chars) { ULEB(chars.size()); auto pos = buf.size(); Chars(chars); return pos; } bool StartsWithCount() { return cur_section != Section::Custom && cur_section != Section::Start; } enum { R_WASM_FUNCTION_INDEX_LEB = 0, R_WASM_TABLE_INDEX_SLEB = 1, R_WASM_TABLE_INDEX_I32 = 2, R_WASM_MEMORY_ADDR_LEB = 3, R_WASM_MEMORY_ADDR_SLEB = 4, R_WASM_MEMORY_ADDR_I32 = 5, R_WASM_TYPE_INDEX_LEB = 6, R_WASM_GLOBAL_INDEX_LEB = 7, R_WASM_FUNCTION_OFFSET_I32 = 8, R_WASM_SECTION_OFFSET_I32 = 9, R_WASM_EVENT_INDEX_LEB = 10, }; void RelocULEB(uint8_t reloc_type, size_t sym_index, size_t target_index, bool is_function) { code_relocs.push_back({ reloc_type, buf.size() - section_data, sym_index, target_index, is_function }); // A relocatable LEB typically can be 0, since all information about // this value is stored in the relocation itself. But putting // a meaningful value here will help with reading the output of // objdump. PatchULEB(PatchableLEB(), is_function ? sym_index : target_index); } public: explicit BinaryWriter(std::vector &dest) : buf(dest) { Chars(std::string_view("\0asm", 4)); UInt32(1); } // Call Begin/EndSection pairs for each segment type, in order. // In between, call the Add functions below corresponding to the section // type. void BeginSection(Section st, std::string_view name = "") { // Call EndSection before calling another BeginSection. assert(cur_section == Section::None); cur_section = st; if (st == Section::Code) section_index_in_file_code = section_index_in_file; if (st == Section::Data) section_index_in_file_data = section_index_in_file; UInt8(st); section_size = PatchableLEB(); if (st == Section::Custom) { LenChars(name); } else { // Known sections must be created in order and only once. assert(st > last_known_section); last_known_section = st; } section_count = 0; section_data = buf.size(); if (StartsWithCount()) PatchableLEB(); } void EndSection(Section st) { assert(cur_section == st); (void)st; // Most sections start with a "count" field. if (StartsWithCount()) { PatchULEB(section_data, section_count); } // Patch up the size of this section. PatchULEB(section_size, buf.size() - section_size - PATCHABLE_ULEB_SIZE); cur_section = Section::None; section_index_in_file++; } size_t AddType(const std::vector ¶ms, const std::vector &returns) { assert(cur_section == Section::Type); ULEB(FUNC); ULEB(params.size()); for (auto p : params) ULEB(p); ULEB(returns.size()); for (auto r : returns) ULEB(r); return section_count++; } enum { EXTERNAL_FUNCTION, EXTERNAL_TABLE, EXTERNAL_MEMORY, EXTERNAL_GLOBAL, }; size_t AddImportLinkFunction(std::string_view name, size_t tidx) { LenChars(""); // Module, unused. LenChars(name); ULEB(EXTERNAL_FUNCTION); ULEB(tidx); function_symbols.push_back({ std::string(name), true, true }); section_count++; return num_function_imports++; } size_t AddImportGlobal(std::string_view name, uint8_t type, bool is_mutable) { LenChars(""); // Module, unused. LenChars(name); ULEB(EXTERNAL_GLOBAL); UInt8(type); ULEB(is_mutable); section_count++; return num_global_imports++; } size_t GetNumFunctionImports() { return num_function_imports; } size_t GetNumGlobalImports() { return num_global_imports; } void AddFunction(size_t tidx) { assert(cur_section == Section::Function); ULEB(tidx); num_function_decls++; section_count++; } size_t GetNumDefinedFunctions() { return num_function_decls; } void AddTable() { assert(cur_section == Section::Table); UInt8(WASM::ANYFUNC); // Currently only option. ULEB(0); // Flags: no maximum. ULEB(0); // Initial length. section_count++; } void AddMemory(size_t initial_pages) { assert(cur_section == Section::Memory); ULEB(0); // Flags: no maximum. ULEB(initial_pages); section_count++; } // You MUST emit an init exp after calling this, e.g. EmitI32Const(0); EmitEnd(); void AddGlobal(uint8_t type, bool is_mutable) { assert(cur_section == Section::Global); UInt8(type); ULEB(is_mutable); section_count++; } void AddExportFunction(std::string_view name, size_t fidx) { assert(cur_section == Section::Export); LenChars(name); ULEB(EXTERNAL_FUNCTION); ULEB(fidx); } void AddExportGlobal(std::string_view name, size_t gidx) { assert(cur_section == Section::Export); LenChars(name); ULEB(EXTERNAL_GLOBAL); ULEB(gidx); } void AddStart(size_t fidx) { assert(cur_section == Section::Start); ULEB(fidx); } // Simple 1:1 mapping of function ids. // TODO: add more flexible Element functions later. void AddElementAllFunctions() { assert(cur_section == Section::Element); ULEB(0); // Table index, always 0 for now. EmitI32Const(0); // Offset. EmitEnd(); auto total_funs = num_function_imports + num_function_decls; ULEB(total_funs); for (size_t i = 0; i < total_funs; i++) ULEB(i); section_count++; } // After calling this, use the Emit Functions below to add to the function body, // and be sure to end with EmitEndFunction. void AddCode(const std::vector &locals, std::string_view name, bool local) { assert(cur_section == Section::Code); assert(!function_body_start); function_body_start = PatchableLEB(); std::vector> entries; for (auto l : locals) { if (entries.empty() || entries.back().second != l) { entries.emplace_back(std::pair { 1, l }); } else { entries.back().first++; } } ULEB(entries.size()); for (auto &e : entries) { ULEB(e.first); ULEB(e.second); } function_symbols.push_back({ std::string(name), false, local }); section_count++; } // --- CONTROL FLOW --- void EmitUnreachable() { UInt8(0x00); } void EmitNop() { UInt8(0x01); } void EmitBlock(uint8_t block_type) { UInt8(0x02); UInt8(block_type); } void EmitLoop(uint8_t block_type) { UInt8(0x03); UInt8(block_type); } void EmitIf(uint8_t block_type) { UInt8(0x04); UInt8(block_type); } void EmitElse() { UInt8(0x05); } void EmitEnd() { UInt8(0x0B); } void EmitBr(size_t relative_depth) { UInt8(0x0C); ULEB(relative_depth); } void EmitBrIf(size_t relative_depth) { UInt8(0x0D); ULEB(relative_depth); } void EmitBrTable(const std::vector &targets, size_t default_target) { UInt8(0x0E); ULEB(targets.size()); for (auto t : targets) ULEB(t); ULEB(default_target); } void EmitReturn() { UInt8(0x0F); } // --- CALL OPERATORS --- // fun_idx is 0..N-1 imports followed by N..M-1 defined functions. void EmitCall(size_t fun_idx) { UInt8(0x10); RelocULEB(R_WASM_FUNCTION_INDEX_LEB, fun_idx, 0, true); } void EmitCallIndirect(size_t type_index) { UInt8(0x11); RelocULEB(R_WASM_TYPE_INDEX_LEB, 0, type_index, false); ULEB(0); } // --- PARAMETRIC OPERATORS void EmitDrop() { UInt8(0x1A); } void EmitSelect() { UInt8(0x1B); } // --- VARIABLE ACCESS --- void EmitGetLocal(size_t local) { UInt8(0x20); ULEB(local); } void EmitSetLocal(size_t local) { UInt8(0x21); ULEB(local); } void EmitTeeLocal(size_t local) { UInt8(0x22); ULEB(local); } void EmitGetGlobal(size_t global) { UInt8(0x23); ULEB(global); } void EmitSetGlobal(size_t global) { UInt8(0x24); ULEB(global); } // --- MEMORY ACCESS --- void EmitI32Load(size_t off, size_t flags = 2) { UInt8(0x28); ULEB(flags); ULEB(off); } void EmitI64Load(size_t off, size_t flags = 3) { UInt8(0x29); ULEB(flags); ULEB(off); } void EmitF32Load(size_t off, size_t flags = 2) { UInt8(0x2A); ULEB(flags); ULEB(off); } void EmitF64Load(size_t off, size_t flags = 3) { UInt8(0x2B); ULEB(flags); ULEB(off); } void EmitI32Load8S(size_t off, size_t flags = 0) { UInt8(0x2C); ULEB(flags); ULEB(off); } void EmitI32Load8U(size_t off, size_t flags = 0) { UInt8(0x2D); ULEB(flags); ULEB(off); } void EmitI32Load16S(size_t off, size_t flags = 1) { UInt8(0x2E); ULEB(flags); ULEB(off); } void EmitI32Load16U(size_t off, size_t flags = 1) { UInt8(0x2F); ULEB(flags); ULEB(off); } void EmitI64Load8S(size_t off, size_t flags = 0) { UInt8(0x30); ULEB(flags); ULEB(off); } void EmitI64Load8U(size_t off, size_t flags = 0) { UInt8(0x31); ULEB(flags); ULEB(off); } void EmitI64Load16S(size_t off, size_t flags = 1) { UInt8(0x32); ULEB(flags); ULEB(off); } void EmitI64Load16U(size_t off, size_t flags = 1) { UInt8(0x33); ULEB(flags); ULEB(off); } void EmitI64Load32S(size_t off, size_t flags = 2) { UInt8(0x34); ULEB(flags); ULEB(off); } void EmitI64Load32U(size_t off, size_t flags = 2) { UInt8(0x35); ULEB(flags); ULEB(off); } void EmitI32Store(size_t off, size_t flags = 2) { UInt8(0x36); ULEB(flags); ULEB(off); } void EmitI64Store(size_t off, size_t flags = 3) { UInt8(0x37); ULEB(flags); ULEB(off); } void EmitF32Store(size_t off, size_t flags = 2) { UInt8(0x38); ULEB(flags); ULEB(off); } void EmitF64Store(size_t off, size_t flags = 3) { UInt8(0x39); ULEB(flags); ULEB(off); } void EmitI32Store8(size_t off, size_t flags = 0) { UInt8(0x3A); ULEB(flags); ULEB(off); } void EmitI32Store16(size_t off, size_t flags = 1) { UInt8(0x3B); ULEB(flags); ULEB(off); } void EmitI64Store8(size_t off, size_t flags = 0) { UInt8(0x3C); ULEB(flags); ULEB(off); } void EmitI64Store16(size_t off, size_t flags = 1) { UInt8(0x3D); ULEB(flags); ULEB(off); } void EmitI64Store32(size_t off, size_t flags = 2) { UInt8(0x3E); ULEB(flags); ULEB(off); } void EmitCurrentMemory() { UInt8(0x3F); ULEB(0); } void EmitGrowMemory() { UInt8(0x40); ULEB(0); } // --- CONSTANTS --- void EmitI32Const(int32_t v) { UInt8(0x41); SLEB(v); } void EmitI64Const(int64_t v) { UInt8(0x42); SLEB(v); } void EmitF32Const(float v) { UInt8(0x43); UInt32(Bits(v)); } void EmitF64Const(double v) { UInt8(0x44); UInt64(Bits(v)); } // Getting the address of data in a data segment, encoded as a i32.const + reloc. void EmitI32ConstDataRef(size_t segment, size_t addend) { UInt8(0x41); RelocULEB(R_WASM_MEMORY_ADDR_SLEB, segment, addend, false ); } // fun_idx is 0..N-1 imports followed by N..M-1 defined functions. void EmitI32ConstFunctionRef(size_t fun_idx) { UInt8(0x41); RelocULEB(R_WASM_TABLE_INDEX_SLEB, fun_idx, 0, true); } // --- COMPARISON OPERATORS --- void EmitI32Eqz() { UInt8(0x45); } void EmitI32Eq() { UInt8(0x46); } void EmitI32Ne() { UInt8(0x47); } void EmitI32LtS() { UInt8(0x48); } void EmitI32LtU() { UInt8(0x49); } void EmitI32GtS() { UInt8(0x4A); } void EmitI32GtU() { UInt8(0x4B); } void EmitI32LeS() { UInt8(0x4C); } void EmitI32LeU() { UInt8(0x4D); } void EmitI32GeS() { UInt8(0x4E); } void EmitI32GeU() { UInt8(0x4F); } void EmitI64Eqz() { UInt8(0x50); } void EmitI64Eq() { UInt8(0x51); } void EmitI64Ne() { UInt8(0x52); } void EmitI64LtS() { UInt8(0x53); } void EmitI64LtU() { UInt8(0x54); } void EmitI64GtS() { UInt8(0x55); } void EmitI64GtU() { UInt8(0x56); } void EmitI64LeS() { UInt8(0x57); } void EmitI64LeU() { UInt8(0x58); } void EmitI64GeS() { UInt8(0x59); } void EmitI64GeU() { UInt8(0x5A); } void EmitF32Eq() { UInt8(0x5B); } void EmitF32Ne() { UInt8(0x5C); } void EmitF32Lt() { UInt8(0x5D); } void EmitF32Gt() { UInt8(0x5E); } void EmitF32Le() { UInt8(0x5F); } void EmitF32Ge() { UInt8(0x60); } void EmitF64Eq() { UInt8(0x61); } void EmitF64Ne() { UInt8(0x62); } void EmitF64Lt() { UInt8(0x63); } void EmitF64Gt() { UInt8(0x64); } void EmitF64Le() { UInt8(0x65); } void EmitF64Ge() { UInt8(0x66); } // --- NUMERIC OPERATORS void EmitI32Clz() { UInt8(0x67); } void EmitI32Ctz() { UInt8(0x68); } void EmitI32PopCnt() { UInt8(0x69); } void EmitI32Add() { UInt8(0x6A); } void EmitI32Sub() { UInt8(0x6B); } void EmitI32Mul() { UInt8(0x6C); } void EmitI32DivS() { UInt8(0x6D); } void EmitI32DivU() { UInt8(0x6E); } void EmitI32RemS() { UInt8(0x6F); } void EmitI32RemU() { UInt8(0x70); } void EmitI32And() { UInt8(0x71); } void EmitI32Or() { UInt8(0x72); } void EmitI32Xor() { UInt8(0x73); } void EmitI32Shl() { UInt8(0x74); } void EmitI32ShrS() { UInt8(0x75); } void EmitI32ShrU() { UInt8(0x76); } void EmitI32RotL() { UInt8(0x77); } void EmitI32RotR() { UInt8(0x78); } void EmitI64Clz() { UInt8(0x79); } void EmitI64Ctz() { UInt8(0x7A); } void EmitI64PopCnt() { UInt8(0x7B); } void EmitI64Add() { UInt8(0x7C); } void EmitI64Sub() { UInt8(0x7D); } void EmitI64Mul() { UInt8(0x7E); } void EmitI64DivS() { UInt8(0x7F); } void EmitI64DivU() { UInt8(0x80); } void EmitI64RemS() { UInt8(0x81); } void EmitI64RemU() { UInt8(0x82); } void EmitI64And() { UInt8(0x83); } void EmitI64Or() { UInt8(0x84); } void EmitI64Xor() { UInt8(0x85); } void EmitI64Shl() { UInt8(0x86); } void EmitI64ShrS() { UInt8(0x87); } void EmitI64ShrU() { UInt8(0x88); } void EmitI64RotL() { UInt8(0x89); } void EmitI64RotR() { UInt8(0x8A); } void EmitF32Abs() { UInt8(0x8B); } void EmitF32Neg() { UInt8(0x8C); } void EmitF32Ceil() { UInt8(0x8D); } void EmitF32Floor() { UInt8(0x8E); } void EmitF32Trunc() { UInt8(0x8F); } void EmitF32Nearest() { UInt8(0x90); } void EmitF32Sqrt() { UInt8(0x91); } void EmitF32Add() { UInt8(0x92); } void EmitF32Sub() { UInt8(0x93); } void EmitF32Mul() { UInt8(0x94); } void EmitF32Div() { UInt8(0x95); } void EmitF32Min() { UInt8(0x96); } void EmitF32Max() { UInt8(0x97); } void EmitF32CopySign() { UInt8(0x98); } void EmitF64Abs() { UInt8(0x99); } void EmitF64Neg() { UInt8(0x9A); } void EmitF64Ceil() { UInt8(0x9B); } void EmitF64Floor() { UInt8(0x9C); } void EmitF64Trunc() { UInt8(0x9D); } void EmitF64Nearest() { UInt8(0x9E); } void EmitF64Sqrt() { UInt8(0x9F); } void EmitF64Add() { UInt8(0xA0); } void EmitF64Sub() { UInt8(0xA1); } void EmitF64Mul() { UInt8(0xA2); } void EmitF64Div() { UInt8(0xA3); } void EmitF64Min() { UInt8(0xA4); } void EmitF64Max() { UInt8(0xA5); } void EmitF64CopySign() { UInt8(0xA6); } // --- CONVERSION OPERATORS --- void EmitI32WrapI64() { UInt8(0xA7); } void EmitI32TruncSF32() { UInt8(0xA8); } void EmitI32TruncUF32() { UInt8(0xA9); } void EmitI32TruncSF64() { UInt8(0xAA); } void EmitI32TruncUF64() { UInt8(0xAB); } void EmitI64ExtendSI32() { UInt8(0xAC); } void EmitI64ExtendUI32() { UInt8(0xAD); } void EmitI64TruncSF32() { UInt8(0xAE); } void EmitI64TruncUF32() { UInt8(0xAF); } void EmitI64TruncSF64() { UInt8(0xB0); } void EmitI64TruncUF64() { UInt8(0xB1); } void EmitF32ConvertSI32() { UInt8(0xB2); } void EmitF32ConvertUI32() { UInt8(0xB3); } void EmitF32ConvertSI64() { UInt8(0xB4); } void EmitF32ConvertUI64() { UInt8(0xB5); } void EmitF32DemoteF64() { UInt8(0xB6); } void EmitF64ConvertSI32() { UInt8(0xB7); } void EmitF64ConvertUI32() { UInt8(0xB8); } void EmitF64ConvertSI64() { UInt8(0xB9); } void EmitF64ConvertUI64() { UInt8(0xBA); } void EmitF64PromoteF32() { UInt8(0xBB); } // --- REINTERPRETATIONS --- void EmitI32ReinterpretF32() { UInt8(0xBC); } void EmitI64ReinterpretF64() { UInt8(0xBD); } void EmitF32ReinterpretI32() { UInt8(0xBE); } void EmitF64ReinterpretI64() { UInt8(0xBF); } // --- END FUNCTION --- void EmitEndFunction() { assert(cur_section == Section::Code); EmitEnd(); assert(function_body_start); PatchULEB(function_body_start, buf.size() - function_body_start - PATCHABLE_ULEB_SIZE); function_body_start = 0; } void AddData(std::string_view data, std::string_view symbol, size_t align, bool local = true) { assert(cur_section == Section::Data); ULEB(0); // Linear memory index. // Init exp: must use 32-bit for wasm32 target. EmitI32Const(static_cast(data_section_size)); EmitEnd(); segment_payload_start = LenChars(data); data_section_size += data.size(); data_segments.push_back({ std::string(symbol), align, data.size(), local }); section_count++; } // "off" is relative to the data in the last AddData call. void DataFunctionRef(size_t fid, size_t off) { assert(segment_payload_start); data_relocs.push_back({ R_WASM_TABLE_INDEX_I32, off + (segment_payload_start - section_data), fid, 0, true }); } // Call this last, to finalize the buffer into a valid WASM module, // and to add linking/reloc sections based on the previous sections. void Finish() { assert(cur_section == Section::None); // If this assert fails, you likely have not matched the number of // AddFunction calls in a Function section with the number of AddCode // calls in a Code section. assert(num_function_imports + num_function_decls == function_symbols.size()); // Linking section. { BeginSection(Section::Custom, "linking"); ULEB(2); // Version. enum { WASM_SEGMENT_INFO = 5, WASM_INIT_FUNCS = 6, WASM_COMDAT_INFO = 7, WASM_SYMBOL_TABLE = 8, }; // Segment Info. { UInt8(WASM_SEGMENT_INFO); auto sisize = PatchableLEB(); ULEB(data_segments.size()); for (auto &ds : data_segments) { LenChars(ds.name); ULEB(ds.align); ULEB(0); // Flags. FIXME: any valid values? } PatchULEB(sisize, buf.size() - sisize - PATCHABLE_ULEB_SIZE); } // Symbol Table. { UInt8(WASM_SYMBOL_TABLE); auto stsize = PatchableLEB(); enum { SYMTAB_FUNCTION = 0, SYMTAB_DATA = 1, SYMTAB_GLOBAL = 2, SYMTAB_SECTION = 3, SYMTAB_EVENT = 4, }; enum { WASM_SYM_BINDING_WEAK = 1, WASM_SYM_BINDING_LOCAL = 2, WASM_SYM_VISIBILITY_HIDDEN = 4, WASM_SYM_UNDEFINED = 16, WASM_SYM_EXPORTED = 32, }; ULEB(data_segments.size() + function_symbols.size()); size_t segi = 0; for (auto &ds : data_segments) { UInt8(SYMTAB_DATA); ULEB(ds.local ? WASM_SYM_BINDING_LOCAL : WASM_SYM_EXPORTED); LenChars(ds.name); ULEB(segi++); ULEB(0); // Offset in segment, always 0 (1 seg per sym). ULEB(ds.size); } size_t wasm_function = 0; for (auto &fs : function_symbols) { UInt8(SYMTAB_FUNCTION); ULEB(fs.import ? WASM_SYM_UNDEFINED : (fs.local ? WASM_SYM_BINDING_LOCAL : WASM_SYM_EXPORTED)); ULEB(wasm_function++); if (!fs.import) { LenChars(fs.name); } } PatchULEB(stsize, buf.size() - stsize - PATCHABLE_ULEB_SIZE); } EndSection(Section::Custom); // linking } // Reloc sections { auto EncodeReloc = [&](Reloc &r) { UInt8(r.type); ULEB(r.src_offset); ULEB(r.sym_index + (r.is_function ? data_segments.size() : 0)); if (r.type == R_WASM_MEMORY_ADDR_LEB || r.type == R_WASM_MEMORY_ADDR_SLEB || r.type == R_WASM_MEMORY_ADDR_I32 || r.type == R_WASM_FUNCTION_OFFSET_I32 || r.type == R_WASM_SECTION_OFFSET_I32) SLEB((ptrdiff_t)r.target_index); }; BeginSection(Section::Custom, "reloc.CODE"); ULEB(section_index_in_file_code); ULEB(code_relocs.size()); for (auto &r : code_relocs) EncodeReloc(r); EndSection(Section::Custom); // reloc.CODE BeginSection(Section::Custom, "reloc.DATA"); ULEB(section_index_in_file_data); ULEB(data_relocs.size()); for (auto &r : data_relocs) EncodeReloc(r); EndSection(Section::Custom); // reloc.DATA } } }; } // namespace WASM #endif // WASM_BINARY_WRITER_H treesheets-1.0.2/lobster/src/lobster/wasm_binary_writer_test.h000066400000000000000000000376441352107072600247400ustar00rootroot00000000000000// Copyright 2019 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef WASM_BINARY_WRITER_TEST_H #define WASM_BINARY_WRITER_TEST_H #include "wasm_binary_writer.h" using namespace std::string_view_literals; namespace WASM { // This is a simple test of the instruction encoding. The function returns a binary that // when written to a file should pass e.g. wasm-validate. std::vector SimpleBinaryWriterTest() { std::vector vec; BinaryWriter bw(vec); // Write a (function) type section, to be referred to by functions below. // For any of these sections, if you write them out of order, or don't match // begin/end, you'll get an assert. // As with everything, to refer to things in wasm, use a 0 based index. bw.BeginSection(WASM::Section::Type); // A list of arguments followed by a list of return values. // You don't have to use the return value, but it may make referring to this // type easier. auto type_ii_i = bw.AddType({ WASM::I32, WASM::I32 }, { WASM::I32 }); // 0 auto type_i_v = bw.AddType({ WASM::I32 }, {}); // 1 bw.EndSection(WASM::Section::Type); // Import some functions, from the runtime compiled in other modules. // For our example that will just be the printing function. // Note: we assume this function has been declared with: extern "C" // You can link against C++ functions as well if you don't mind dealing // with name mangling. bw.BeginSection(WASM::Section::Import); auto import_print = bw.AddImportLinkFunction("print", type_i_v); // 0 bw.EndSection(WASM::Section::Import); // Declare all the functions we will generate. Note this is just the type, // the body of the code will follow below. bw.BeginSection(WASM::Section::Function); bw.AddFunction(type_ii_i); // main() bw.AddFunction(type_i_v); // TestAllInstructions() bw.EndSection(WASM::Section::Function); // We need this (and Element below) to be able to use call_indirect. bw.BeginSection(WASM::Section::Table); bw.AddTable(); bw.EndSection(WASM::Section::Table); // Declare the linear memory we want to use, with 1 initial page. bw.BeginSection(WASM::Section::Memory); bw.AddMemory(1); bw.EndSection(WASM::Section::Memory); // Declare a global, used in the tests below. bw.BeginSection(WASM::Section::Global); bw.AddGlobal(WASM::I32, true); bw.EmitI32Const(0); bw.EmitEnd(); bw.EndSection(WASM::Section::Global); // Here we'd normally declare a "Start" section, but the linker will // take care for that for us. // This initializes the Table declared above. Needed for call_indirect. // For now we use a utility function that maps all functions ids 1:1 to the table. bw.BeginSection(WASM::Section::Element); bw.AddElementAllFunctions(); bw.EndSection(WASM::Section::Element); // Now the exciting part: emitting function bodies. bw.BeginSection(WASM::Section::Code); // A list of 0 local types, bw.AddCode({}, "main", false); // Refers to data segment 0 at offset 0 below. This emits an i32.const // instruction, whose immediate value will get relocated to refer to the // data correctly. bw.EmitI32ConstDataRef(0, 0); bw.EmitCall(import_print); bw.EmitI32Const(0); // Return value. bw.EmitEndFunction(); // Test each instruction at least once. Needs to have correct stack inputs to pass // wasm-validate etc, but is otherwise not meant to be meaningfull to execute. bw.AddCode({}, "TestAllInstructions", false); // Control flow. bw.EmitNop(); bw.EmitBlock(WASM::VOID); bw.EmitI32Const(true); bw.EmitBrIf(0); bw.EmitBr(0); bw.EmitEnd(); bw.EmitLoop(WASM::VOID); bw.EmitI32Const(false); bw.EmitBrIf(0); bw.EmitEnd(); bw.EmitI32Const(false); bw.EmitIf(WASM::I32); bw.EmitI32Const(1); bw.EmitElse(); bw.EmitI32Const(2); bw.EmitEnd(); bw.EmitDrop(); bw.EmitBlock(WASM::VOID); bw.EmitI32Const(2); bw.EmitBrTable({}, 0); bw.EmitEnd(); bw.EmitI32Const(0); bw.EmitI32ConstFunctionRef(import_print); bw.EmitCallIndirect(type_i_v); bw.EmitI32Const(1); bw.EmitI32Const(2); bw.EmitI32Const(true); bw.EmitSelect(); // Variables. bw.EmitGetLocal(0); bw.EmitTeeLocal(0); bw.EmitSetLocal(0); bw.EmitGetGlobal(0); bw.EmitSetGlobal(0); // Memory. for (int i = 0; i < 14; i++) bw.EmitI32Const(0); // Address. bw.EmitI32Load(0); bw.EmitDrop(); bw.EmitI64Load(0); bw.EmitDrop(); bw.EmitF32Load(0); bw.EmitDrop(); bw.EmitF64Load(0); bw.EmitDrop(); bw.EmitI32Load8S(0); bw.EmitDrop(); bw.EmitI32Load8U(0); bw.EmitDrop(); bw.EmitI32Load16S(0); bw.EmitDrop(); bw.EmitI32Load16U(0); bw.EmitDrop(); bw.EmitI64Load8S(0); bw.EmitDrop(); bw.EmitI64Load8U(0); bw.EmitDrop(); bw.EmitI64Load16S(0); bw.EmitDrop(); bw.EmitI64Load16U(0); bw.EmitDrop(); bw.EmitI64Load32S(0); bw.EmitDrop(); bw.EmitI64Load32U(0); bw.EmitDrop(); for (int i = 0; i < 11; i++) bw.EmitI32Const(0); // Address. bw.EmitI32Const(0); bw.EmitI32Store(0); bw.EmitI64Const(0); bw.EmitI64Store(0); bw.EmitF32Const(0); bw.EmitF32Store(0); bw.EmitF64Const(0); bw.EmitF64Store(0); bw.EmitI32Const(0); bw.EmitI32Store8(0); bw.EmitI32Const(0); bw.EmitI32Store16(0); bw.EmitI64Const(0); bw.EmitI64Store8(0); bw.EmitI64Const(0); bw.EmitI64Store16(0); bw.EmitI64Const(0); bw.EmitI64Store32(0); bw.EmitCurrentMemory(); bw.EmitDrop(); bw.EmitGrowMemory(); // Equality operators. bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32Eqz(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32Eq(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32Ne(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32LtS(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32LtU(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32GtS(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32GtU(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32LeS(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32LeU(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32GeS(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32GeU(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64Eqz(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64Eq(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64Ne(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64LtS(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64LtU(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64GtS(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64GtU(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64LeS(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64LeU(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64GeS(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64GeU(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Eq(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Ne(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Lt(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Gt(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Le(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Ge(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Eq(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Ne(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Lt(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Gt(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Le(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Ge(); bw.EmitDrop(); // Numeric operators. bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32Clz(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32Ctz(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32PopCnt(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32Add(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32Sub(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32Mul(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32DivS(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32DivU(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32RemS(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32RemU(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32And(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32Or(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32Xor(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32Shl(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32ShrS(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32ShrU(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32RotL(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI32Const(0); bw.EmitI32RotR(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64Clz(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64Ctz(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64PopCnt(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64Add(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64Sub(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64Mul(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64DivS(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64DivU(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64RemS(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64RemU(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64And(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64Or(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64Xor(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64Shl(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64ShrS(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64ShrU(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64RotL(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitI64Const(0); bw.EmitI64RotR(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Abs(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Neg(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Ceil(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Floor(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Trunc(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Nearest(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Sqrt(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Add(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Sub(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Mul(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Div(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Min(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32Max(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF32Const(0); bw.EmitF32CopySign(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Abs(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Neg(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Ceil(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Floor(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Trunc(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Nearest(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Sqrt(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Add(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Sub(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Mul(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Div(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Min(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64Max(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF64Const(0); bw.EmitF64CopySign(); bw.EmitDrop(); // Coversion operations. bw.EmitI64Const(0); bw.EmitI32WrapI64(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitI32TruncSF32(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitI32TruncUF32(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitI32TruncSF64(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitI32TruncUF64(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI64ExtendSI32(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitI64ExtendUI32(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitI64TruncSF32(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitI64TruncUF32(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitI64TruncSF64(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitI64TruncUF64(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitF32ConvertSI32(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitF32ConvertUI32(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitF32ConvertSI64(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitF32ConvertUI64(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitF32DemoteF64(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitF64ConvertSI32(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitF64ConvertUI32(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitF64ConvertSI64(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitF64ConvertUI64(); bw.EmitDrop(); bw.EmitF32Const(0); bw.EmitF64PromoteF32(); bw.EmitDrop(); // Reinterpretations. bw.EmitF32Const(0); bw.EmitI32ReinterpretF32(); bw.EmitDrop(); bw.EmitF64Const(0); bw.EmitI64ReinterpretF64(); bw.EmitDrop(); bw.EmitI32Const(0); bw.EmitF32ReinterpretI32(); bw.EmitDrop(); bw.EmitI64Const(0); bw.EmitF64ReinterpretI64(); bw.EmitDrop(); // More control flow. bw.EmitReturn(); bw.EmitUnreachable(); bw.EmitEndFunction(); bw.EndSection(WASM::Section::Code); // Add all our static data. bw.BeginSection(WASM::Section::Data); // This is our first segment, we referred to this above as 0. auto hello = "Hello, World\n\0"sv; // Data, name, and alignment. bw.AddData(hello, "hello", 0); // Create another segment, this time with function references. int function_ref = (int)bw.GetNumFunctionImports() + 0; // Refers to main() bw.AddData(std::string_view((char *)&function_ref, sizeof(int)), "funids", sizeof(int)); bw.DataFunctionRef(function_ref, 0); // Reloc it. bw.EndSection(WASM::Section::Data); // This call does all the remaining work of generating the linking // information, and wrapping up the file. bw.Finish(); return vec; } } // namespace WASM #endif // WASM_BINARY_WRITER_TEST_H treesheets-1.0.2/lobster/src/lobster/wentropy.h000066400000000000000000000067471352107072600216610ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Wouter's Entropy Coder: apparently I re-invented adaptive Shannon-Fano. // // similar compression performance to huffman, and absolutely tiny code, one function for both // compression and decompression. Adaptive, so should work well even for tiny buffers. // One of the vectors passed in is the input, the other the output (and exactly one of the two // should be empty initially). // not the fastest possible implementation (only 40MB/sec for either compression or decompression // on a modern pc), but should be sufficient for many uses // // uses std::string and std::swap as its only external dependencies for simplicity, but could made // to not rely on them relatively easily. template void WEntropyCoder(const unsigned char *in, size_t inlen, // Size of input. size_t origlen, // Uncompressed size. string &out) { const int NSYM = 256; // This depends on the fact we're reading from unsigned chars. int symbol[NSYM]; // The symbol in this slot. Adaptively sorted by frequency. size_t freq[NSYM]; // Its frequency. int sym_idx[NSYM]; // Lookup symbol -> index into the above two arrays. for (int i = 0; i < NSYM; i++) { freq[i] = 1; symbol[i] = sym_idx[i] = i; } size_t compr_idx = 0; unsigned char bits = 0; int nbits = 0; for (size_t i = 0; i < origlen; i++) { int start = 0, range = NSYM; size_t total_freq = i + NSYM; while (range > 1) { size_t acc_freq = 0; int j = start; do acc_freq += freq[j++]; while (acc_freq + freq[j] / 2 < total_freq / 2); unsigned char bit = 0; if (compress) { if (sym_idx[in[i]] < j) bit = 1; bits |= bit << nbits; if (++nbits == 8) { out.push_back(bits); bits = 0; nbits = 0; } } else { if (!nbits) { assert(compr_idx < inlen); bits = in[compr_idx++]; nbits = 8; } bit = bits & 1; bits >>= 1; nbits--; } if (bit) { total_freq = acc_freq; assert(j - start < range); range = j - start; } else { total_freq -= acc_freq; range -= j - start; start = j; } } if (!compress) out.push_back((unsigned char)symbol[start]); assert(range == 1 && (!compress || in[i] == symbol[start])); freq[start]++; while (start && freq[start - 1] < freq[start]) { swap(sym_idx[symbol[start - 1]], sym_idx[symbol[start]]); swap(freq[start - 1], freq[start]); swap(symbol[start - 1], symbol[start]); start--; } } if (compress) { if (nbits) out.push_back(bits); } else { assert(compr_idx == inlen); } (void)inlen; } treesheets-1.0.2/lobster/src/lobster/wfc.h000066400000000000000000000237751352107072600205510ustar00rootroot00000000000000// Copyright 2018 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Very simple tile based Wave Function Collapse ("Simple Tiled Model") implementation. // See: https://github.com/mxgmn/WaveFunctionCollapse // Derives adjacencies from an example rather than explicitly specified neighbors. // Does not do any symmetries/rotations unless they're in the example. // Algorithm has a lot of similarities to A* in how its implemented. // Uses bitmasks to store the set of possible tiles, which currently limits the number of // unique tiles to 64. This restriction cool be lifted by using std::bitset instead. // In my testing, generates a 50x50 tile map in <1 msec. 58% of such maps are conflict free. // At 100x100 that is 3 msec and 34%. // At 200x200 that is 24 msec and 13% // At 400x400 that is 205 msec and ~1% // Algorithm may need to extended to flood more than 2 neighbor levels to make it suitable // for really gigantic maps. // inmap & outmap must point to row-major 2D arrays of the given size. // each in tile char must be in range 0..127, of which max 64 may actually be in use (may be // sparse). // Returns false if too many unique tiles in input. template bool WaveFunctionCollapse(const int2 &insize, const char **inmap, const int2 &outsize, char **outmap, RandomNumberGenerator &rnd, int &num_contradictions) { num_contradictions = 0; typedef uint64_t bitmask_t; const auto nbits = sizeof(bitmask_t) * 8; array tile_lookup; tile_lookup.fill(-1); struct Tile { bitmask_t sides[4] = {}; int freq = 0; char tidx = 0; }; vector tiles; int2 neighbors[] = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } }; // Collect unique tiles and their frequency of occurrence. for (int iny = 0; iny < insize.y; iny++) { for (int inx = 0; inx < insize.x; inx++) { auto t = inmap[iny][inx]; if (tile_lookup[t] < 0) { // We use a bitmask_t mask for valid neighbors. if (tiles.size() == nbits - 1) return false; tile_lookup[t] = (int)tiles.size(); tiles.push_back(Tile()); } auto &tile = tiles[tile_lookup[t]]; tile.freq++; tile.tidx = t; } } // Construct valid neighbor bitmasks. auto to_bitmask = [](size_t idx) { return (bitmask_t)1 << idx; }; for (int iny = 0; iny < insize.y; iny++) { for (int inx = 0; inx < insize.x; inx++) { auto t = inmap[iny][inx]; auto &tile = tiles[tile_lookup[t]]; int ni = 0; for (auto n : neighbors) { auto p = (n + int2(inx, iny) + insize) % insize; auto tn = inmap[p.y][p.x]; assert(tile_lookup[tn] >= 0); tile.sides[ni] |= to_bitmask(tile_lookup[tn]); ni++; } } } size_t most_common_tile_id = 0; int most_common_tile_freq = 0; for (auto &tile : tiles) if (tile.freq > most_common_tile_freq) { most_common_tile_freq = tile.freq; most_common_tile_id = &tile - &tiles[0]; } // Track an open list (much like A*) of next options, sorted by best candidate at the end. list> open, temp; // Store a bitmask per output cell of remaining possible choices. auto max_bitmask = (1 << tiles.size()) - 1; enum class State : int { NEW, OPEN, CLOSED }; struct Cell { bitmask_t wf; int popcnt = 0; State state = State::NEW; list>::iterator it; Cell(bitmask_t wf, int popcnt) : wf(wf), popcnt(popcnt) {} }; vector> cells(outsize.y, vector(outsize.x, Cell(max_bitmask, (int)tiles.size()))); auto start = rndivec(rnd, outsize); open.push_back({ start, 0 }); // Start. auto &scell = cells[start.y][start.x]; scell.state = State::OPEN; scell.it = open.begin(); // Pick tiles until no more possible. while (!open.empty()) { // Simply picking the first list item results in the same chance of conflicts as // random picks over equal options, but it is assumed the latter could generate more // interesting maps. int num_candidates = 1; auto numopts_0 = cells[open.back().first.y][open.back().first.x].popcnt; for (auto it = ++open.rbegin(); it != open.rend(); ++it) if (numopts_0 == cells[it->first.y][it->first.x].popcnt && open.back().second == it->second) num_candidates++; else break; auto candidate_i = rnd(num_candidates); auto candidate_it = --open.end(); for (int i = 0; i < candidate_i; i++) --candidate_it; auto cur = candidate_it->first; temp.splice(temp.end(), open, candidate_it); auto &cell = cells[cur.y][cur.x]; assert(cell.state == State::OPEN); cell.state = State::CLOSED; bool contradiction = !cell.popcnt; if (contradiction) { num_contradictions++; // Rather than failing right here, fill in the whole map as best as possible just in // case a map with bad tile neighbors is still useful to the caller. // As a heuristic lets just use the most common tile, as that will likely have the // most neighbor options. cell.wf = to_bitmask(most_common_tile_id); cell.popcnt = 1; } // From our options, pick one randomly, weighted by frequency of tile occurrence. // First find total frequency. int total_freq = 0; for (size_t i = 0; i < tiles.size(); i++) if (cell.wf & to_bitmask(i)) total_freq += tiles[i].freq; auto freqpick = rnd(total_freq); // Now pick. size_t picked = 0; for (size_t i = 0; i < tiles.size(); i++) if (cell.wf & to_bitmask(i)) { picked = i; if ((freqpick -= tiles[i].freq) <= 0) break; } assert(freqpick <= 0); // Modify the picked tile. auto &tile = tiles[picked]; outmap[cur.y][cur.x] = tile.tidx; cell.wf = to_bitmask(picked); // Exactly one option remains. cell.popcnt = 1; // Now lets cycle thru neighbors, reduce their options (and maybe their neighbors options), // and add them to the open list for next pick. int ni = 0; for (auto n : neighbors) { auto p = (cur + n + outsize) % outsize; auto &ncell = cells[p.y][p.x]; if (ncell.state != State::CLOSED) { ncell.wf &= tile.sides[ni]; // Reduce options. ncell.popcnt = PopCount(ncell.wf); int totalnnumopts = 0; if (!contradiction) { // Hardcoded second level of neighbors of neighbors, to reduce chance of // contradiction. // Only do this when our current tile isn't a contradiction, to avoid // artificially shrinking options. int nni = 0; for (auto nn : neighbors) { auto pnn = (p + nn + outsize) % outsize; auto &nncell = cells[pnn.y][pnn.x]; if (nncell.state != State::CLOSED) { // Collect the superset of possible options. If we remove anything but // these, we are guaranteed the direct neigbor always has a possible //pick. bitmask_t superopts = 0; for (size_t i = 0; i < tiles.size(); i++) if (ncell.wf & to_bitmask(i)) superopts |= tiles[i].sides[nni]; nncell.wf &= superopts; nncell.popcnt = PopCount(nncell.wf); } totalnnumopts += nncell.popcnt; nni++; } } if (ncell.state == State::OPEN) { // Already in the open list, remove it for it to be re-added just in case // its location is not optimal anymore. totalnnumopts = min(totalnnumopts, ncell.it->second); temp.splice(temp.end(), open, ncell.it); // Avoid alloc. } // Insert this neighbor, sorted by lowest possibilities. // Use total possibilities of neighbors as a tie-breaker to avoid causing // contradictions by needless surrounding of tiles. list>::iterator dit = open.begin(); for (auto it = open.rbegin(); it != open.rend(); ++it) { auto onumopts = cells[it->first.y][it->first.x].popcnt; if (onumopts > ncell.popcnt || (onumopts == ncell.popcnt && it->second >= totalnnumopts)) { dit = it.base(); break; } } if (temp.empty()) temp.push_back({}); open.splice(dit, temp, ncell.it = temp.begin()); *ncell.it = { p, totalnnumopts }; ncell.state = State::OPEN; } ni++; } } return true; } treesheets-1.0.2/lobster/src/lobsterreader.cpp000066400000000000000000000172561352107072600215050ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/lex.h" namespace lobster { struct ValueParser { vector filenames; vector allocated; Lex lex; VM &vm; ValueParser(VM &vm, string_view _src) : lex("string", filenames, _src), vm(vm) {} void Parse(type_elem_t typeoff) { ParseFactor(typeoff, true); Gobble(T_LINEFEED); Expect(T_ENDOFFILE); } void ParseElems(TType end, type_elem_t typeoff, int numelems, bool push) { // Vector or struct. Gobble(T_LINEFEED); auto &ti = vm.GetTypeInfo(typeoff); auto stack_start = vm.sp; auto NumElems = [&]() { return vm.sp - stack_start; }; if (lex.token == end) lex.Next(); else { for (;;) { if (NumElems() == numelems) { ParseFactor(TYPE_ELEM_ANY, false); } else { auto eti = ti.t == V_VECTOR ? ti.subt : ti.GetElemOrParent(NumElems()); ParseFactor(eti, push); } bool haslf = lex.token == T_LINEFEED; if (haslf) lex.Next(); if (lex.token == end) break; if (!haslf) Expect(T_COMMA); } lex.Next(); } if (!push) return; if (numelems >= 0) { while (NumElems() < numelems) { switch (vm.GetTypeInfo(ti.elemtypes[NumElems()]).t) { case V_INT: vm.Push(Value(0)); break; case V_FLOAT: vm.Push(Value(0.0f)); break; case V_NIL: vm.Push(Value()); break; default: lex.Error("no default value exists for missing struct elements"); } } } if (ti.t == V_CLASS) { auto vec = vm.NewObject((intp)NumElems(), typeoff); if (NumElems()) vec->Init(vm, vm.TopPtr() - NumElems(), (intp)NumElems(), false); vm.PopN(NumElems()); allocated.push_back(vec); vm.Push(vec); } else if (ti.t == V_VECTOR) { auto &sti = vm.GetTypeInfo(ti.subt); auto width = IsStruct(sti.t) ? sti.len : 1; auto n = (intp)(NumElems() / width); auto vec = vm.NewVec(n, n, typeoff); if (NumElems()) vec->Init(vm, vm.TopPtr() - NumElems(), false); vm.PopN(NumElems()); allocated.push_back(vec); vm.Push(vec); } // else if ti.t == V_STRUCT_* then.. do nothing! } void ExpectType(ValueType given, ValueType needed) { if (given != needed && needed != V_ANY) { lex.Error("type " + BaseTypeName(needed) + " required, " + BaseTypeName(given) + " given"); } } void ParseFactor(type_elem_t typeoff, bool push) { auto &ti = vm.GetTypeInfo(typeoff); auto vt = ti.t; switch (lex.token) { case T_INT: { ExpectType(V_INT, vt); auto i = lex.IntVal(); lex.Next(); if (push) vm.Push(i); break; } case T_FLOAT: { ExpectType(V_FLOAT, vt); auto f = strtod(lex.sattr.data(), nullptr); lex.Next(); if (push) vm.Push(f); break; } case T_STR: { ExpectType(V_STRING, vt); string s = lex.StringVal(); lex.Next(); if (push) { auto str = vm.NewString(s); allocated.push_back(str); vm.Push(str); } break; } case T_NIL: { ExpectType(V_NIL, vt); lex.Next(); if (push) vm.Push(Value()); break; } case T_MINUS: { lex.Next(); ParseFactor(typeoff, push); if (push) { switch (typeoff) { case TYPE_ELEM_INT: vm.Push(vm.Pop().ival() * -1); break; case TYPE_ELEM_FLOAT: vm.Push(vm.Pop().fval() * -1); break; default: lex.Error("unary minus: numeric value expected"); } } break; } case T_LEFTBRACKET: { ExpectType(V_VECTOR, vt); lex.Next(); ParseElems(T_RIGHTBRACKET, typeoff, -1, push); break; } case T_IDENT: { if (vt == V_INT && ti.enumidx >= 0) { auto opt = vm.LookupEnum(lex.sattr, ti.enumidx); if (!opt) lex.Error("unknown enum value " + lex.sattr); lex.Next(); if (push) vm.Push(*opt); break; } if (!IsUDT(vt) && vt != V_ANY) lex.Error("class/struct type required, " + BaseTypeName(vt) + " given"); auto sname = lex.sattr; lex.Next(); Expect(T_LEFTCURLY); auto name = vm.StructName(ti); if (name != sname) lex.Error("class/struct type " + name + " required, " + sname + " given"); ParseElems(T_RIGHTCURLY, typeoff, ti.len, push); break; } default: lex.Error("illegal start of expression: " + lex.TokStr()); vm.Push(Value()); break; } } void Expect(TType t) { if (lex.token != t) lex.Error(lex.TokStr(t) + " expected, found: " + lex.TokStr()); lex.Next(); } void Gobble(TType t) { if (lex.token == t) lex.Next(); } }; static void ParseData(VM &vm, type_elem_t typeoff, string_view inp) { auto stack_level = vm.sp; ValueParser parser(vm, inp); #ifdef USE_EXCEPTION_HANDLING try #endif { parser.Parse(typeoff); vm.Push(Value()); } #ifdef USE_EXCEPTION_HANDLING catch (string &s) { vm.sp = stack_level; for (auto a : parser.allocated) a->Dec(vm); vm.Push(Value()); vm.Push(vm.NewString(s)); } #endif } void AddReader(NativeRegistry &nfr) { nfr("parse_data", "typeid,stringdata", "TS", "A1?S?", "parses a string containing a data structure in lobster syntax (what you get if you convert" " an arbitrary data structure to a string) back into a data structure. supports" " int/float/string/vector and classes. classes will be forced to be compatible with their " " current definitions, i.e. too many elements will be truncated, missing elements will be" " set to 0/nil if possible. useful for simple file formats. returns the value and an error" " string as second return value (or nil if no error)", [](VM &vm) { auto ins = vm.Pop().sval(); auto type = vm.Pop().ival(); ParseData(vm, (type_elem_t)type, ins->strv()); }); } } treesheets-1.0.2/lobster/src/main.cpp000066400000000000000000000243601352107072600175660ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/compiler.h" #include "lobster/disasm.h" #include "lobster/tonative.h" #if LOBSTER_ENGINE #include "lobster/engine.h" #endif #include "lobster/unicode.h" #if LOBSTER_ENGINE // FIXME: This makes SDL not modular, but without it it will miss the SDLMain indirection. #include "lobster/sdlincludes.h" #include "lobster/sdlinterface.h" #endif using namespace lobster; void unit_test_all(bool full) { // We don't really have unit tests, but let's collect some that always // run in debug mode: #ifdef NDEBUG return; #endif unit_test_tools(); unit_test_unicode(); unit_test_wasm(full); } int main(int argc, char* argv[]) { #ifdef _WIN32 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); InitUnhandledExceptionFilter(argc, argv); #endif LOG_INFO("Lobster running..."); bool wait = false; bool from_bundle = #ifdef __IOS__ true; #else false; #endif #ifdef USE_EXCEPTION_HANDLING try #endif { bool parsedump = false; bool disasm = false; bool to_cpp = false; bool to_wasm = false; bool dump_builtins = false; bool dump_names = false; bool compile_only = false; bool compile_bench = false; bool full_unit_test = false; int runtime_checks = RUNTIME_ASSERT; const char *default_lpak = "default.lpak"; const char *lpak = nullptr; const char *fn = nullptr; vector program_args; auto trace = TraceMode::OFF; string helptext = "\nUsage:\n" "lobster [ OPTIONS ] [ FILE ] [ -- ARGS ]\n" "Compile & run FILE, or omit FILE to load default.lpak\n" "--pak Compile to pakfile, don't run.\n" "--cpp Compile to C++ code, don't run.\n" "--wasm Compile to WASM code, don't run.\n" "--parsedump Also dump parse tree.\n" "--disasm Also dump bytecode disassembly.\n" "--verbose Output additional informational text.\n" "--debug Output compiler internal logging.\n" "--silent Only output errors.\n" "--runtime-shipping Compile with asserts off.\n" "--runtime-asserts Compile with asserts on (default).\n" "--runtime-verbose Compile with asserts on + additional debug.\n" "--noconsole Close console window (Windows).\n" "--gen-builtins-html Write builtin commands help file.\n" "--gen-builtins-names Write builtin commands - just names.\n" #if LOBSTER_ENGINE "--non-interactive-test Quit after running 1 frame.\n" #endif "--trace Log bytecode instructions (SLOW).\n" "--trace-tail Show last 50 bytecode instructions on error.\n" "--wait Wait for input before exiting.\n"; int arg = 1; for (; arg < argc; arg++) { if (argv[arg][0] == '-') { string a = argv[arg]; if (a == "--wait") { wait = true; } else if (a == "--pak") { lpak = default_lpak; } else if (a == "--cpp") { to_cpp = true; } else if (a == "--wasm") { to_wasm = true; } else if (a == "--parsedump") { parsedump = true; } else if (a == "--disasm") { disasm = true; } else if (a == "--verbose") { min_output_level = OUTPUT_INFO; } else if (a == "--debug") { min_output_level = OUTPUT_DEBUG; } else if (a == "--silent") { min_output_level = OUTPUT_ERROR; } else if (a == "--runtime-shipping") { runtime_checks = RUNTIME_NO_ASSERT; } else if (a == "--runtime-asserts") { runtime_checks = RUNTIME_ASSERT; } else if (a == "--runtime-verbose") { runtime_checks = RUNTIME_ASSERT_PLUS; } else if (a == "--noconsole") { SetConsole(false); } else if (a == "--gen-builtins-html") { dump_builtins = true; } else if (a == "--gen-builtins-names") { dump_names = true; } else if (a == "--compile-only") { compile_only = true; } else if (a == "--compile-bench") { compile_bench = true; } #if LOBSTER_ENGINE else if (a == "--non-interactive-test") { SDLTestMode(); } #endif else if (a == "--trace") { trace = TraceMode::ON; } else if (a == "--trace-tail") { trace = TraceMode::TAIL; } else if (a == "--full-unit-test") { full_unit_test = true; } else if (a == "--") { arg++; break; } // process identifier supplied by OS X else if (a.substr(0, 5) == "-psn_") { from_bundle = true; } else THROW_OR_ABORT("unknown command line argument: " + (argv[arg] + helptext)); } else { if (fn) THROW_OR_ABORT("more than one file specified" + helptext); fn = argv[arg]; } } for (; arg < argc; arg++) { program_args.push_back(argv[arg]); } unit_test_all(full_unit_test); #ifdef __IOS__ //fn = "totslike.lobster"; // FIXME: temp solution #endif if (!InitPlatform(GetMainDirFromExePath(argv[0]), fn ? fn : default_lpak, from_bundle, #if LOBSTER_ENGINE SDLLoadFile #else DefaultLoadFile #endif )) THROW_OR_ABORT(string("cannot find location to read/write data on this platform!")); NativeRegistry nfr; #if LOBSTER_ENGINE RegisterCoreEngineBuiltins(nfr); #else RegisterCoreLanguageBuiltins(nfr); #endif if (fn) fn = StripDirPart(fn); auto vmargs = VMArgs { nfr, fn ? fn : "" }; vmargs.program_args = std::move(program_args); vmargs.trace = trace; if (!fn) { if (!LoadPakDir(default_lpak)) THROW_OR_ABORT("Lobster programming language compiler/runtime (version " __DATE__ ")\nno arguments given - cannot load " + (default_lpak + helptext)); // This will now come from the pakfile. if (!LoadByteCode(vmargs.bytecode_buffer)) THROW_OR_ABORT(string("Cannot load bytecode from pakfile!")); } else { LOG_INFO("compiling..."); string dump; string pakfile; auto start_time = SecondsSinceStart(); size_t bench_iters = 1000; for (size_t i = 0; i < (compile_bench ? bench_iters : 1); i++) { dump.clear(); pakfile.clear(); vmargs.bytecode_buffer.clear(); Compile(nfr, StripDirPart(fn), {}, vmargs.bytecode_buffer, parsedump ? &dump : nullptr, lpak ? &pakfile : nullptr, dump_builtins, dump_names, false, runtime_checks); } if (compile_bench) { auto compile_time = (SecondsSinceStart() - start_time); LOG_PROGRAM("time to compile ", bench_iters, "x (seconds): ", compile_time); } if (parsedump) { WriteFile("parsedump.txt", false, dump); } if (lpak) { WriteFile(lpak, true, pakfile); return 0; } } if (disasm) { ostringstream ss; DisAsm(nfr, ss, vmargs.bytecode_buffer); WriteFile("disasm.txt", false, ss.str()); } if (to_cpp) { ostringstream ss; auto err = ToCPP(nfr, ss, vmargs.bytecode_buffer); if (!err.empty()) THROW_OR_ABORT(err); // FIXME: make less hard-coded. auto out = "dev/compiled_lobster/src/compiled_lobster.cpp"; FILE *f = fopen((MainDir() + out).c_str(), "w"); if (f) { fputs(ss.str().c_str(), f); fclose(f); } else { THROW_OR_ABORT(cat("cannot write: ", out)); } } else if (to_wasm) { vector buf; auto err = ToWASM(nfr, buf, vmargs.bytecode_buffer); if (!err.empty()) THROW_OR_ABORT(err); // FIXME: make less hard-coded. auto out = "dev/emscripten/compiled_lobster_wasm.o"; FILE *f = fopen((MainDir() + out).c_str(), "wb"); if (f) { fwrite(buf.data(), buf.size(), 1, f); fclose(f); } else { THROW_OR_ABORT(cat("cannot write: ", out)); } } else if (!compile_only) { #if LOBSTER_ENGINE EngineRunByteCode(std::move(vmargs)); #else VM vm(std::move(vmargs)); vm.EvalProgram(); #endif } } #ifdef USE_EXCEPTION_HANDLING catch (string &s) { LOG_ERROR(s); #if LOBSTER_ENGINE if (from_bundle) SDLMessageBox("Lobster", s.c_str()); #endif if (wait) { LOG_PROGRAM("press to continue:\n"); getchar(); } #ifdef _WIN32 _CrtSetDbgFlag(0); // Don't bother with memory leaks when there was an error. #endif #if LOBSTER_ENGINE EngineExit(1); #endif } #endif #if LOBSTER_ENGINE EngineExit(0); #endif return 0; } treesheets-1.0.2/lobster/src/meshgen.cpp000066400000000000000000001024711352107072600202700ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/glinterface.h" #include "lobster/meshgen.h" #include "lobster/mctables.h" #include "lobster/polyreduce.h" #include "lobster/cubegen.h" #include "lobster/simplex.h" #include "ThreadPool/ThreadPool.h" using namespace lobster; /* TODO: - a function that takes a list of cylinder widths to be able to do wineglasses etc - replace pow by something faster? - proper groups, so it can help in early culling - but big grids in compressed columns, so it can possibly run on larger dimension - turn some of the push_back vector stuff into single allocations - could work on sharp crease boolean version (see treesheets) - crease texturing: verts either get a tc of (0, rnd) when they are on a crease, or (1, rnd) when they are flat */ struct ImplicitFunction; float3 rotated_size(const float3x3 &rot, const float3 &size) { float3x3 absm = float3x3(abs(rot[0]), abs(rot[1]), abs(rot[2])); return absm * size; } struct ImplicitFunction { float3 orig; float3 size; float3x3 rot; float4 material; float smoothmink; virtual ~ImplicitFunction() {} inline float Eval(const float3 & /*pos*/) const = delete; float3 Sized(const float3 &unit_size) { return unit_size * size + smoothmink; } virtual float3 Size() { return Sized(float3_1); }; virtual void FillGrid(const int3 &start, const int3 &end, DistGrid *distgrid, const float3 &gridscale, const float3 &gridtrans, const float3x3 &gridrot, ThreadPool &threadpool) const = 0; }; static int3 axesi[] = { int3(1, 0, 0), int3(0, 1, 0), int3(0, 0, 1) }; float max_smoothmink = 0; // use curiously recurring template pattern to allow implicit function to be inlined in // rasterization loop template struct ImplicitFunctionImpl : ImplicitFunction { void FillGrid(const int3 &start, const int3 &end, DistGrid *distgrid, const float3 &gridscale, const float3 &gridtrans, const float3x3 &gridrot, ThreadPool &threadpool) const { assert(end <= distgrid->dim && int3(0) <= start); auto uniform_scale = average(size); max_smoothmink = max(max_smoothmink, uniform_scale * smoothmink); vector> results(end.x - start.x); for (int x = start.x; x < end.x; x++) { results[x - start.x] = threadpool.enqueue([&, x]() { for (int y = start.y; y < end.y; y++) { for (int z = start.z; z < end.z; z++) { int3 ipos(x, y, z); auto pos = float3(ipos); pos -= gridtrans; pos = pos * gridrot; pos /= gridscale; auto dist = static_cast(this)->Eval(pos); // dist was evaluated in the local coordinate system of the primitive. // This is correct for trans/rot, but the scale makes it give the wrong // distance globally. // Most uses of mg_scale(vec) are uniform so this should be close enough: dist *= uniform_scale; // This is our only state access, but is thread-safe: auto &dv = distgrid->Get(ipos); // Could move this if outside loop, but should be branch predicted, so // probably ok. if (material.w >= 0.5f) { auto h = smoothminh(dv.dist, dist, smoothmink); dv.dist = smoothmix(dv.dist, dist, smoothmink, h); dv.color = quantizec(dv.color.w ? mix(material, color2vec(dv.color), h) : material); } else { dv.dist = smoothmax(-dist, dv.dist, smoothmink); } } } }); } for (auto &r : results) r.get(); } }; struct IFSphere : ImplicitFunctionImpl { float rad; inline float Eval(const float3 &pos) const { return length(pos) - rad; } float3 Size() { return Sized(float3(rad)); } }; struct IFCube : ImplicitFunctionImpl { float3 extents; inline float Eval(const float3 &pos) const { auto d = abs(pos) - extents; //return max(d); return length(max(d, float3_0)) + max(min(d, float3_0)); } float3 Size() { return Sized(extents); } }; struct IFCylinder : ImplicitFunctionImpl { float radius, height; inline float Eval(const float3 &pos) const { return max(length(pos.xy()) - radius, abs(pos.z) - height); } float3 Size() { return Sized(float3(radius, radius, height)); } }; struct IFTaperedCylinder : ImplicitFunctionImpl { float bot, top, height; inline float Eval(const float3 &pos) const { auto xy = pos.xy(); auto r = mix(bot, top, pos.z / (height * 2) + 0.5f); // FIXME: this is probably not the correct distance. return max(abs(pos.z) - height, dot(xy, xy) - r * r); } float3 Size() { auto rad = max(top, bot); return Sized(float3(rad, rad, height)); } }; // TODO: pow is rather slow... struct IFSuperQuadric : ImplicitFunctionImpl { float3 exp; float3 scale; inline float Eval(const float3 &pos) const { return dot(pow(abs(pos) / scale, exp), float3_1) - 1; } float3 Size() { return Sized(scale); } }; struct IFSuperQuadricNonUniform : ImplicitFunctionImpl { float3 exppos, expneg; float3 scalepos, scaleneg; inline float Eval(const float3 &pos) const { auto d = pos.iflt(0, scaleneg, scalepos); auto e = pos.iflt(0, expneg, exppos); auto p = abs(pos) / d; // FIXME: why is max(p) even needed? not needed in IFSuperQuadric return max(max(p), dot(pow(p, e), float3_1)) - 1; } float3 Size() { return Sized(max(scalepos, scaleneg)); } }; struct IFSuperToroid : ImplicitFunctionImpl { float r; float3 exp; inline float Eval(const float3 &pos) const { auto p = pow(abs(pos), exp); auto xy = r - sqrtf(p.x + p.y); return powf(fabsf(xy), exp.z) + p.z - 1; } float3 Size() { return Sized(float3(r * 2 + 1, r * 2 + 1, 1)); } }; struct IFLandscape : ImplicitFunctionImpl { float zscale, xyscale; inline float Eval(const float3 &pos) const { if (!(abs(pos) <= 1)) return false; auto dpos = pos + float3(SimplexNoise(8, 0.5f, 1, float4(pos.xy() + 1, 0)), SimplexNoise(8, 0.5f, 1, float4(pos.xy() + 2, 0)), 0) / 2; auto f = SimplexNoise(8, 0.5f, xyscale, float4(dpos.xy(), 0)) * zscale; // FIXME: this is obviously not the correct distance for anything but peaks. return dpos.z - f; } }; struct Group : ImplicitFunctionImpl { vector children; ~Group() { for (auto c : children) delete c; } static inline float Eval(const float3 & /*pos*/) { return 0.0f; } float3 Size() { float3 p1, p2; for (auto c : children) { auto csz = c->Size(); csz = rotated_size(c->rot, csz); p1 = c == children[0] ? c->orig - csz : min(p1, c->orig - csz); p2 = c == children[0] ? c->orig + csz : max(p2, c->orig + csz); } auto sz = (p2 - p1) / 2; orig = p1 + sz; return sz; } void FillGrid(const int3 & /*start*/, const int3 & /*end*/, DistGrid *distgrid, const float3 &gridscale, const float3 &gridtrans, const float3x3 & /*gridrot*/, ThreadPool &threadpool) const { for (auto c : children) { auto csize = c->Size(); if (dot(csize, gridscale) > 3) { auto trans = gridtrans + c->orig * gridscale; auto scale = gridscale * c->size; auto rsize = rotated_size(c->rot, csize); auto start = int3(trans - rsize * gridscale - 0.01f); auto end = int3(trans + rsize * gridscale + 2.01f); auto bs = end - start; if (bs > 1) c->FillGrid(start, end, distgrid, scale, trans, c->rot, threadpool); } } } }; float noisestretch = 1; float noiseintensity = 0; float randomizeverts = 0; bool pointmode = false; float3 id_grid_to_world(const int3 &pos) { return float3(pos); } Mesh *polygonize_mc(const int3 &gridsize, float gridscale, const float3 &gridtrans, const DistGrid *distgrid, float3 (* grid_to_world)(const int3 &pos)) { struct edge { int3 iclosest; float3 fmid; byte4 material; edge() : iclosest(int3_0), fmid(float3_0), material(byte4_0) {} edge(const int3 &_iclosest, const float3 &_fmid, const byte4 &_m) : iclosest(_iclosest), fmid(_fmid), material(_m) {} }; vector mctriangles; vector edges; bool mesh_displacent = true; bool flat_triangles_opt = true; bool simple_occlusion = false; bool marching_cubes = true; bool snap_to_mid = false; if (snap_to_mid) mesh_displacent = false; auto verts2edge = [&](const int3 &p1, const int3 &p2, DistVert &dv1, DistVert &dv2) { auto wp1 = grid_to_world(p1); auto wp2 = grid_to_world(p2); float3 mid; int3 iclosest; if (snap_to_mid) { // FIXME: this create null-area triangles that should be removed. mid = (wp1 + wp2) / 2; iclosest = p1; } else { if (abs(dv1.dist) < 0.00001f || abs(dv2.dist - dv1.dist) < 0.00001f) { mid = wp1; iclosest = p1; } else if (abs(dv2.dist) < 0.00001f) { mid = wp2; iclosest = p2; } else { auto mu = -dv1.dist / (dv2.dist - dv1.dist); mid = wp1 + mu * (wp2 - wp1); iclosest = abs(mu) < 0.5 ? p1 : p2; } } return edge(iclosest, mid, dv1.dist < dv2.dist ? dv1.color : dv2.color); }; if (marching_cubes) { int3 gridpos[8]; DistVert dv[8]; int vertlist[12]; const int3 corners[8] = { int3(0, 0, 0), int3(1, 0, 0), int3(1, 1, 0), int3(0, 1, 0), int3(0, 0, 1), int3(1, 0, 1), int3(1, 1, 1), int3(0, 1, 1), }; // FIXME: this uses even more memory than the distgrid. EdgeGrid edgeidx(gridsize, int3(-1)); for (int x = 0; x < gridsize.x - 1; x++) for (int y = 0; y < gridsize.y - 1; y++) for (int z = 0; z < gridsize.z - 1; z++) { int3 pos(x, y, z); int ci = 0; for (int i = 0; i < 8; i++) { gridpos[i] = pos + corners[i]; dv[i] = distgrid->Get(gridpos[i]); ci |= (dv[i].dist < 0) << i; } if (mc_edge_table[ci] == 0) continue; for (int i = 0; i < 12; i++) if (mc_edge_table[ci] & (1 << i)) { int i1 = mc_edge_to_vert[i][0]; int i2 = mc_edge_to_vert[i][1]; auto &p1 = gridpos[i1]; auto &p2 = gridpos[i2]; int dir = p1.x < p2.x ? 0 : p1.y < p2.y ? 1 : 2; auto &ei = edgeidx.Get(p2); if (ei[dir] < 0) { ei[dir] = (int)edges.size(); auto &dv1 = dv[i1]; auto &dv2 = dv[i2]; #ifndef __EMSCRIPTEN__ // FIXME assert(dv1.dist * dv2.dist <= 0); #endif edges.push_back(verts2edge(p1, p2, dv1, dv2)); } vertlist[i] = ei[dir]; } for (int i = 0; mc_tri_table[ci][i] != -1; ) { auto e1 = vertlist[mc_tri_table[ci][i++]]; auto e2 = vertlist[mc_tri_table[ci][i++]]; auto e3 = vertlist[mc_tri_table[ci][i++]]; mctriangles.push_back(e1); mctriangles.push_back(e2); mctriangles.push_back(e3); assert(triangle_area(edges[e1].fmid, edges[e2].fmid, edges[e3].fmid) < 1); } } } else { // Experimental marching squares slices mode, unfinished and unoptimized. mesh_displacent = false; flat_triangles_opt = false; polyreductionpasses = 0; int3 gridpos[3][4]; DistVert dv[3][4]; edge edgev[4]; int3 corners[4] = { int3(0, 0, 0), int3(1, 0, 0), int3(1, 1, 0), int3(0, 1, 0), }; int linelist[16][5] = { { -1 }, { 0, 3, -1 }, { 1, 0, -1 }, { 1, 3, -1 }, { 2, 1, -1 }, { 0, 3, 2, 1, -1 }, { 2, 0, -1 }, { 2, 3, -1 }, { 3, 2, -1 }, { 0, 2, -1 }, { 1, 0, 3, 2, -1 }, { 1, 2, -1 }, { 3, 1, -1 }, { 0, 1, -1 }, { 3, 0, -1 }, { -1 }, }; // Both ambiguous cases use the minimal version since that's less polygons overal. int trilist[16][10] = { { -1 }, { 0, 7, 1, -1 }, { 1, 3, 2, -1 }, { 0, 3, 2, 3, 0, 7, -1 }, { 3, 5, 4, -1 }, { 3, 5, 4, 0, 7, 1, -1 }, { 1, 4, 2, 4, 1, 5, -1 }, { 0, 7, 2, 7, 5, 2, 5, 4, 2, -1 }, { 5, 7, 6, -1 }, { 0, 5, 1, 5, 0, 6, -1 }, { 1, 3, 2, 5, 7, 6, -1 }, { 0, 6, 5, 0, 5, 3, 0, 3, 2, -1 }, { 7, 4, 3, 4, 7, 6, -1 }, { 6, 4, 3, 6, 3, 1, 6, 1, 0, -1 }, { 4, 2, 1, 4, 1, 7, 4, 7, 6, -1 }, { 0, 4, 2, 4, 0, 6, -1 }, }; float3 zup(0, 0, 0.5f); float3 zdn(0, 0, -0.5f); for (int z = 1; z < gridsize.z - 1; z++) for (int x = 0; x < gridsize.x - 1; x++) for (int y = 0; y < gridsize.y - 1; y++) { int3 pos(x, y, z); int ci[3] = { 0, 0, 0 }; for (int lz = 0; lz < 3; lz++) { for (int i = 0; i < 4; i++) { gridpos[lz][i] = pos + corners[i] + int3(0, 0, lz - 1); dv[lz][i] = distgrid->Get(gridpos[lz][i]); ci[lz] |= (dv[lz][i].dist < 0) << i; } } if (linelist[ci[1]][0] < 0 && ci[1] == ci[0] && ci[1] == ci[2]) continue; for (int i = 0; i < 4; i++) { auto &p1 = gridpos[1][i]; auto &p2 = gridpos[1][(i + 1) & 3]; auto &dv1 = dv[1][i]; auto &dv2 = dv[1][(i + 1) & 3]; edgev[i] = verts2edge(p1, p2, dv1, dv2); } // Side polys. for (int i = 0; linelist[ci[1]][i] >= 0; i += 2) { // FIXME: disambiguate saddles? // FIXME: duplicate verts. reuse. for (int o = 0; o < 6; o++) { auto a = linelist[ci[1]][i + (o > 0 && o < 4)]; mctriangles.push_back((int)edges.size()); auto e = edgev[a]; e.fmid += o > 1 && o < 5 ? zdn : zup; edges.push_back(e); } } // Top polys. if (ci[1] != ci[2] || linelist[ci[1]][0] >= 0) { // FIXME: clip against adjacent level. for (int i = 0; trilist[ci[1]][i] >= 0; i += 3) { for (int o = 0; o < 3; o++) { auto a = trilist[ci[1]][i + o]; // FIXME: duplicate verts. mctriangles.push_back((int)edges.size()); auto e = edgev[a / 2]; if (!(a & 1)) e.fmid = float3(gridpos[1][a / 2]); else { assert(length(e.fmid - float3(gridpos[1][a / 2])) < 2); } e.fmid += zup; edges.push_back(e); } } } // FIXME: bottom polys missing. } } delete distgrid; vector triangles; vector verts; RandomNumberGenerator r; /////////// MESH DISPLACEMENT if (mesh_displacent) { // "Mesh Displacement: An Improved Contouring Method for Trivariate Data" // http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.49.5214 // reduces tris by half, and makes for more regular proportioned tris // from: http://chrishecker.com/My_liner_notes_for_spore struct dcell { float3 accum; float3 col; int n; dcell() : accum(float3_0), col(float3_0), n(0) {} }; auto dcellindices = new IntGrid(gridsize, -1); vector iverts; vector cells; for (edge &e : edges) { int &idx = dcellindices->Get(e.iclosest); if (idx < 0) { idx = (int)cells.size(); cells.push_back(dcell()); } dcell &c = cells[idx]; c.accum += e.fmid; c.col += color2vec(e.material).xyz(); c.n--; iverts.push_back(e.iclosest); } for (size_t t = 0; t < mctriangles.size(); t += 3) { int3 i[3]; i[0] = iverts[mctriangles[t + 0]]; i[1] = iverts[mctriangles[t + 1]]; i[2] = iverts[mctriangles[t + 2]]; if (i[0] != i[1] && i[0] != i[2] && i[1] != i[2]) { for (int j = 0; j < 3; j++) { dcell &c = cells[dcellindices->Get(i[j])]; if (c.n < 0) { c.accum /= (float)-c.n; c.col /= (float)-c.n; c.n = (int)verts.size(); verts.push_back(mgvert()); auto &v = verts.back(); v.pos = c.accum; v.norm = float3_0; v.col = quantizec(c.col); } triangles.push_back(c.n); } } } delete dcellindices; } else { for (edge &e : edges) { verts.push_back(mgvert()); auto &v = verts.back(); assert(e.fmid >= 0 && e.fmid <= float3(gridsize)); v.pos = e.fmid; v.col = e.material; v.norm = float3_0; } triangles.assign(mctriangles.begin(), mctriangles.end()); } /////////// CULL FLAT TRIANGLES // TODO: can bad tris simply be culled based on bad normals? if (flat_triangles_opt) { bool *problemvert = new bool[verts.size()]; memset(problemvert, 0, sizeof(bool) * verts.size()); const float threshold = -0.98f; for (size_t t = 0; t < triangles.size(); t += 3) { auto &v1 = verts[triangles[t + 0]]; auto &v2 = verts[triangles[t + 1]]; auto &v3 = verts[triangles[t + 2]]; assert(v1.pos != v2.pos && v1.pos != v3.pos && v2.pos != v3.pos); float3 d3 = normalize(cross(v3.pos - v1.pos, v2.pos - v1.pos)); // if a plane normal points away near 180 from past normals, its likely part of triangle with no volume // behind it (special case in mesh displacement) if (v1.norm != float3_0 && dot(d3, normalize(v1.norm)) < threshold) problemvert[triangles[t + 0]] = true; if (v2.norm != float3_0 && dot(d3, normalize(v2.norm)) < threshold) problemvert[triangles[t + 1]] = true; if (v3.norm != float3_0 && dot(d3, normalize(v3.norm)) < threshold) problemvert[triangles[t + 2]] = true; } for (size_t t = 0; t < triangles.size(); t += 3) { // if all corners have screwey normals, the triangle should be culled // could also cull associated verts, but generally so few of them its not worth it if ((problemvert[triangles[t + 0]] && problemvert[triangles[t + 1]] && problemvert[triangles[t + 2]])) { triangles.erase(triangles.begin() + t, triangles.begin() + t + 3); t -= 3; } } delete[] problemvert; } /////////// RANDOMIZE if (randomizeverts > 0) { for (auto &v : verts) { v.pos += random_point_in_sphere(r) * randomizeverts; } } /////////// ORIGIN/SCALE for (auto &v : verts) { v.pos -= gridtrans; v.pos /= gridscale; } /////////// CALCULATE NORMALS RecomputeNormals(triangles, verts); /////////// POLYGON REDUCTION if (polyreductionpasses) { PolyReduce(triangles, verts); } /////////// APPLY NOISE TO COLOR if (noiseintensity > 0) { float scale = noisestretch; int octaves = 8; float persistence = 0.5f; for (auto &v : verts) { auto n = float3(SimplexNoise(octaves, persistence, scale, float4(v.pos, 0.0f / scale)), SimplexNoise(octaves, persistence, scale, float4(v.pos, 0.3f / scale)), SimplexNoise(octaves, persistence, scale, float4(v.pos, 0.6f / scale))); v.col = quantizec(color2vec(v.col).xyz() * (float3_1 - (n + float3_1) / 2 * noiseintensity)); } } /////////// MODULATE LIGHTING BY CREASE FACTOR if (simple_occlusion) { float *cfactor = new float[verts.size()]; memset(cfactor, 0, sizeof(float) * verts.size()); for (size_t t = 0; t < triangles.size(); t += 3) { auto &v1 = verts[triangles[t + 0]]; auto &v2 = verts[triangles[t + 1]]; auto &v3 = verts[triangles[t + 2]]; float3 v12 = normalize(v2.pos - v1.pos); float3 v13 = normalize(v3.pos - v1.pos); float3 v23 = normalize(v3.pos - v2.pos); auto centroid = (v1.pos + v2.pos + v3.pos) / 3; cfactor[triangles[t + 0]] += dot(v1.norm, normalize(centroid - v1.pos)) * (1 - dot( v12, v13)); cfactor[triangles[t + 1]] += dot(v1.norm, normalize(centroid - v1.pos)) * (1 - dot(-v12, v23)); cfactor[triangles[t + 2]] += dot(v1.norm, normalize(centroid - v1.pos)) * (1 - dot(-v23,-v13)); } for (auto &v : verts) { float f = cfactor[&v - &verts[0]] + 1; v.col = byte4(float4(min(float3(255), max(float3_0, float3(v.col.xyz()) - 64 * f)), 255)); } delete[] cfactor; } LOG_DEBUG("meshgen verts = %lu, edgeverts = %lu, tris = %lu, mctris = %lu," " scale = %f\n", verts.size(), edges.size(), triangles.size() / 3, mctriangles.size() / 3, gridscale); auto m = new Mesh(new Geometry(make_span(verts), "PNC"), pointmode ? PRIM_POINT : PRIM_TRIS); if (pointmode) { m->pointsize = 1000 / gridscale; } else { m->surfs.push_back(new Surface(make_span(triangles), PRIM_TRIS)); } return m; } Group *root = nullptr; Group *curgroup = nullptr; float3 cursize = float3_1; float3 curorig = float3_0; float3x3 currot = float3x3_1; float cursmoothmink = 1.0f; float4 curcol = float4_1; void MeshGenClear() { if (root) delete root; root = curgroup = nullptr; cursize = float3_1; curorig = float3_0; currot = float3x3_1; curcol = float4_1; max_smoothmink = 0; } Group *GetGroup() { if (!curgroup) { assert(!root); root = new Group(); curgroup = root; } return curgroup; } Value AddShape(ImplicitFunction *f) { f->size = cursize; f->orig = curorig; f->rot = currot; f->material = curcol; f->smoothmink = cursmoothmink; GetGroup()->children.push_back(f); return Value(); } Value eval_and_polygonize(VM &vm, int targetgridsize, int zoffset, bool do_poly) { auto scenesize = root->Size() * 2; float biggestdim = max(scenesize.x, max(scenesize.y, scenesize.z)); auto gridscale = targetgridsize / biggestdim; auto gridsize = int3(scenesize * gridscale + float3(2.001f)); auto gridtrans = (float3(gridsize) - 1) / 2 - root->orig * gridscale; auto distgrid = new DistGrid(gridsize, DistVert()); ThreadPool threadpool(NumHWThreads()); root->FillGrid(int3(0), gridsize, distgrid, float3(gridscale), gridtrans, float3x3_1, threadpool); if (do_poly) { auto mesh = polygonize_mc(gridsize, gridscale, gridtrans, distgrid, id_grid_to_world); MeshGenClear(); extern ResourceType mesh_type; return Value(vm.NewResource(mesh, &mesh_type)); } else { auto cg = CubesFromMeshGen(vm, *distgrid, targetgridsize, zoffset); MeshGenClear(); delete distgrid; return cg; } } void AddMeshGen(NativeRegistry &nfr) { nfr("mg_sphere", "radius", "F", "", "a sphere", [](VM &, Value &rad) { auto s = new IFSphere(); s->rad = rad.fltval(); return AddShape(s); }); nfr("mg_cube", "extents", "F}:3", "", "a cube (extents are size from center)", [](VM &vm) { auto c = new IFCube(); c->extents = vm.PopVec(); vm.Push(AddShape(c)); }); nfr("mg_cylinder", "radius,height", "FF", "", "a unit cylinder (height is from center)", [](VM &, Value &radius, Value &height) { auto c = new IFCylinder(); c->radius = radius.fltval(); c->height = height.fltval(); return AddShape(c); }); nfr("mg_tapered_cylinder", "bot,top,height", "FFF", "", "a cyclinder where you specify the top and bottom radius (height is from center)", [](VM &, Value &bot, Value &top, Value &height) { auto tc = new IFTaperedCylinder(); tc->bot = bot.fltval(); tc->top = top.fltval(); tc->height = height.fltval(); return AddShape(tc); }); nfr("mg_superquadric", "exponents,scale", "F}:3F}:3", "", "a super quadric. specify an exponent of 2 for spherical, higher values for rounded" " squares", [](VM &vm) { auto sq = new IFSuperQuadric(); sq->scale = vm.PopVec(); sq->exp = vm.PopVec(); AddShape(sq); }); nfr("mg_superquadric_non_uniform", "posexponents,negexponents,posscale,negscale", "F}:3F}:3F}:3F}:3", "", "a superquadric that allows you to specify exponents and sizes in all 6 directions" " independently for maximum modelling possibilities", [](VM &vm) { auto sq = new IFSuperQuadricNonUniform(); sq->scaleneg = max(float3(0.01f), vm.PopVec()); sq->scalepos = max(float3(0.01f), vm.PopVec()); sq->expneg = vm.PopVec(); sq->exppos = vm.PopVec(); AddShape(sq); }); nfr("mg_supertoroid", "R,exponents", "FF}:3", "", "a super toroid. R is the distance from the origin to the center of the ring.", [](VM &vm) { auto t = new IFSuperToroid(); t->exp = vm.PopVec(); t->r = vm.Pop().fltval(); AddShape(t); }); nfr("mg_landscape", "zscale,xyscale", "FF", "", "a simplex landscape of unit size", [](VM &, Value &zscale, Value &xyscale) { auto ls = new IFLandscape(); ls->zscale = zscale.fltval(); ls->xyscale = xyscale.fltval(); return AddShape(ls); }); nfr("mg_set_polygon_reduction", "polyreductionpasses,epsilon,maxtricornerdot", "IFF", "", "controls the polygon reduction algorithm. set polyreductionpasses to 0 for off, 100 for" " max compression, or low values for generation speed or to keep the mesh uniform. epsilon" " determines how flat adjacent triangles must be to be reduced, use 0.98 as a good" " tradeoff, lower to get more compression. maxtricornerdot avoid very thin triangles, use" " 0.95 as a good tradeoff, up to 0.99 to get more compression", [](VM &, Value &_polyreductionpasses, Value &_epsilon, Value &_maxtricornerdot) { polyreductionpasses = _polyreductionpasses.intval(); epsilon = _epsilon.fltval(); maxtricornerdot = _maxtricornerdot.fltval(); return Value(); }); nfr("mg_set_color_noise", "noiseintensity,noisestretch", "FF", "", "applies simplex noise to the colors of the model. try 0.3 for intensity." " stretch scales the pattern over the model", [](VM &, Value &_noiseintensity, Value &_noisestretch) { noisestretch = _noisestretch.fltval(); noiseintensity = _noiseintensity.fltval(); return Value(); }); nfr("mg_set_vertex_randomize", "factor", "F", "", "randomizes all verts produced to give a more organic look and to hide the inherent messy" " polygons produced by the algorithm. try 0.15. note that any setting other than 0 will" " likely counteract the polygon reduction algorithm", [](VM &, Value &factor) { randomizeverts = factor.fltval(); return Value(); }); nfr("mg_set_point_mode", "on", "B", "", "generates a point mesh instead of polygons", [](VM &, Value &aspoints) { pointmode = aspoints.True(); return Value(); }); nfr("mg_polygonize", "subdiv", "I", "R", "returns a generated mesh from past mg_ commands." " subdiv determines detail and number of polygons (relative to the largest dimension of the" " model), try 30.. 300 depending on the subject." " values much higher than that will likely make you run out of memory (or take very long).", [](VM &vm, Value &subdiv) { return eval_and_polygonize(vm, subdiv.intval(), 0, true); }); nfr("mg_convert_to_cubes", "subdiv,zoffset", "II", "R", "returns a cubegen block (see cg_ functions) from past mg_ commands." " subdiv determines detail and number of cubes (relative to the largest dimension of the" " model).", [](VM &vm, Value &subdiv, Value &zoffset) { return eval_and_polygonize(vm, subdiv.intval(), zoffset.intval(), false); }); nfr("mg_translate", "vec,body", "F}:3L?", "", "translates the current coordinate system along a vector. when a body is given," " restores the previous transform afterwards", [](VM &vm) { auto body = vm.Pop(); auto v = vm.PopVec(); if (body.True()) vm.PushAnyAsString(curorig); // FIXME: not good enough if non-uniform scale, might as well forbid that before any trans curorig += currot * (v * cursize); vm.Push(body); }, [](VM &vm) { vm.PopAnyFromString(curorig); }); nfr("mg_scale", "f,body", "FL?", "", "scales the current coordinate system by the given factor." " when a body is given, restores the previous transform afterwards", [](VM &vm) { auto body = vm.Pop(); auto f = vm.Pop().fltval(); if (body.True()) vm.PushAnyAsString(cursize); cursize *= f; vm.Push(body); }, [](VM &vm) { vm.PopAnyFromString(cursize); }); nfr("mg_scale_vec", "vec,body", "F}:3L?", "", "non-unimformly scales the current coordinate system using individual factors per axis." " when a body is given, restores the previous transform afterwards", [](VM &vm) { auto body = vm.Pop(); auto v = vm.PopVec(); if (body.True()) vm.PushAnyAsString(cursize); cursize *= v; vm.Push(body); }, [](VM &vm) { vm.PopAnyFromString(cursize); }); nfr("mg_rotate", "axis,angle,body", "F}:3FL?", "", "rotates using axis/angle. when a body is given, restores the previous transform" " afterwards", [](VM &vm) { auto body = vm.Pop(); auto angle = vm.Pop().fltval(); auto axis = vm.PopVec(); if (body.True()) vm.PushAnyAsString(currot); currot *= float3x3(angle * RAD, axis); vm.Push(body); }, [](VM &vm) { vm.PopAnyFromString(currot); }); nfr("mg_color", "color,body", "F}:4L?", "", "sets the color, where an alpha of 1 means to add shapes to the scene (union), and 0" " substracts them (carves). when a body is given, restores the previous color afterwards.", [](VM &vm) { auto body = vm.Pop(); auto v = vm.PopVec(); if (body.True()) vm.PushAnyAsString(curcol); curcol = v; vm.Push(body); }, [](VM &vm) { vm.PopAnyFromString(curcol); }); nfr("mg_smooth", "smooth,body", "FL?", "", "sets the smoothness in terms of the range of distance from the shape smoothing happens." " when a body is given, restores the previous value afterwards.", [](VM &vm) { auto body = vm.Pop(); auto smooth = vm.Pop().fltval(); if (body.True()) vm.Push(Value(cursmoothmink)); cursmoothmink = smooth; vm.Push(body); }, [](VM &vm) { auto smooth = vm.Pop(); assert(smooth.type == V_FLOAT); cursmoothmink = smooth.fltval(); }); } // AddMeshGen treesheets-1.0.2/lobster/src/octree.cpp000066400000000000000000000077061352107072600201300ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/octree.h" namespace lobster { inline ResourceType *GetOcTreeType() { static ResourceType voxel_type = { "octree", [](void *v) { delete (OcTree *)v; } }; return &voxel_type; } inline OcTree &GetOcTree(VM &vm, const Value &res) { return *GetResourceDec *>(vm, res, GetOcTreeType()); } const int cur_version = 3; } // namespace lobster template bool FileWriteVal(FILE *f, const T &v) { return fwrite(&v, sizeof(T), 1, f) == 1; } template bool FileWriteVec(FILE *f, const T &v) { return FileWriteVal(f, (uint64_t)v.size()) && fwrite(v.data(), sizeof(typename T::value_type), v.size(), f) == v.size(); } template void ReadVec(const uchar *&p, T &v) { auto len = ReadMemInc(p); v.resize((size_t)len); auto blen = sizeof(typename T::value_type) * v.size(); memcpy(v.data(), p, blen); p += blen; } using namespace lobster; void AddOcTree(NativeRegistry &nfr) { nfr("oc_world_bits", "octree", "R", "I", "", [](VM &vm, Value &oc) { return Value(GetOcTree(vm, oc).world_bits); }); nfr("oc_buf", "octree", "R", "S", "", [](VM &vm, Value &oc) { auto &ocworld = GetOcTree(vm, oc); auto s = vm.NewString(string_view((char *)ocworld.nodes.data(), ocworld.nodes.size() * sizeof(OcVal))); return Value(s); }); nfr("oc_load", "name", "S", "R?", "", [](VM &vm, Value &name) { string buf; auto r = LoadFile(name.sval()->strv(), &buf); if (r < 0) return Value(); auto p = (const uchar *)buf.c_str(); auto magic = ReadMemInc(p); if (magic != 'CWFF') return Value(); auto version = ReadMemInc(p); if (version > cur_version) return Value(); auto bits = ReadMemInc(p); auto ocworld = new OcTree(bits); ReadVec(p, ocworld->nodes); ReadVec(p, ocworld->freelist); assert(p == (void *)(buf.data() + buf.size())); return Value(vm.NewResource(ocworld, GetOcTreeType())); }); nfr("oc_save", "octree,name", "RS", "B", "", [](VM &vm, Value &oc, Value &name) { auto &ocworld = GetOcTree(vm, oc); auto f = OpenForWriting(name.sval()->strv(), true); if (!f) return Value(false); auto ok = FileWriteVal(f, (uint)'CWFF') && FileWriteVal(f, cur_version) && FileWriteVal(f, ocworld.world_bits) && FileWriteVec(f, ocworld.nodes) && FileWriteVec(f, ocworld.freelist); fclose(f); return Value(ok); }); nfr("oc_new", "bits", "I", "R", "", [](VM &vm, Value &bits) { auto ocworld = new OcTree(bits.intval()); return Value(vm.NewResource(ocworld, GetOcTreeType())); }); nfr("oc_set", "octree,pos,val", "RI}:3I", "", "", [](VM &vm) { auto val = vm.Pop().intval(); auto pos = vm.PopVec(); auto &ocworld = GetOcTree(vm, vm.Pop()); OcVal v; v.SetLeafData(max(val, 0)); ocworld.Set(pos, v); }); nfr("oc_get", "octree,pos", "RI}:3", "I", "", [](VM &vm) { auto pos = vm.PopVec(); auto &ocworld = GetOcTree(vm, vm.Pop()); vm.Push(ocworld.nodes[ocworld.Get(pos).first].LeafData()); }); } treesheets-1.0.2/lobster/src/physics.cpp000066400000000000000000000356311352107072600203270ustar00rootroot00000000000000// Copyright 2014 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/glinterface.h" #include "Box2D/Box2D.h" using namespace lobster; struct Renderable : Textured { float4 color = float4_1; Shader *sh; Renderable(const char *shname) : sh(LookupShader(shname)) { assert(sh); } void Set() { sh->Set(); sh->SetTextures(textures); } }; b2World *world = nullptr; b2ParticleSystem *particlesystem = nullptr; Renderable *particlematerial = nullptr; b2Vec2 Float2ToB2(const float2 &v) { return b2Vec2(v.x, v.y); } float2 B2ToFloat2(const b2Vec2 &v) { return float2(v.x, v.y); } b2Vec2 PopB2(VM &vm) { auto v = vm.PopVec(); return Float2ToB2(v); } struct PhysicsObject { Renderable r; b2Fixture *fixture; vector *particle_contacts; PhysicsObject(const Renderable &_r, b2Fixture *_f) : r(_r), fixture(_f), particle_contacts(nullptr) {} ~PhysicsObject() { auto body = fixture->GetBody(); body->DestroyFixture(fixture); if (!body->GetFixtureList()) world->DestroyBody(body); if (particle_contacts) delete particle_contacts; } float2 Pos() { return B2ToFloat2(fixture->GetBody()->GetPosition()); } }; static ResourceType physics_type = { "physical", [](void *v) { delete ((PhysicsObject *)v); } }; PhysicsObject &GetObject(VM &vm, const Value &res) { return *GetResourceDec(vm, res, &physics_type); } void CleanPhysics() { if (world) delete world; world = nullptr; particlesystem = nullptr; delete particlematerial; particlematerial = nullptr; } void InitPhysics(const float2 &gv) { // FIXME: check that shaders are initialized, since renderables depend on that CleanPhysics(); world = new b2World(b2Vec2(gv.x, gv.y)); } void CheckPhysics() { if (!world) InitPhysics(float2(0, -10)); } void CheckParticles(float size = 0.1f) { CheckPhysics(); if (!particlesystem) { b2ParticleSystemDef psd; psd.radius = size; particlesystem = world->CreateParticleSystem(&psd); particlematerial = new Renderable("color_attr"); } } b2Body &GetBody(VM &vm, Value &id, float2 wpos) { CheckPhysics(); b2Body *body = id.True() ? GetObject(vm, id).fixture->GetBody() : nullptr; if (!body) { b2BodyDef bd; bd.type = b2_staticBody; bd.position.Set(wpos.x, wpos.y); body = world->CreateBody(&bd); } return *body; } Value CreateFixture(VM &vm, b2Body &body, b2Shape &shape) { auto fixture = body.CreateFixture(&shape, 1.0f); auto po = new PhysicsObject(Renderable("color"), fixture); fixture->SetUserData(po); return Value(vm.NewResource(po, &physics_type)); } b2Vec2 OptionalOffset(VM &vm) { return vm.Top().True() ? PopB2(vm) : (vm.Pop(), b2Vec2_zero); } Renderable &GetRenderable(VM &vm, const Value &id) { CheckPhysics(); return id.True() ? GetObject(vm, id).r : *particlematerial; } extern int GetSampler(VM &vm, Value &i); // from graphics void AddPhysics(NativeRegistry &nfr) { nfr("ph_initialize", "gravityvector", "F}:2", "", "initializes or resets the physical world, gravity typically [0, -10].", [](VM &vm) { InitPhysics(vm.PopVec()); }); nfr("ph_create_box", "position,size,offset,rotation,attachto", "F}:2F}:2F}:2?F?R?", "R", "creates a physical box shape in the world at position, with size the half-extends around" " the center, offset from the center if needed, at a particular rotation (in degrees)." " attachto is a previous physical object to attach this one to, to become a combined" " physical body.", [](VM &vm) { auto other_id = vm.Pop(); auto rot = vm.Pop().fltval(); auto offset = OptionalOffset(vm); auto sz = vm.PopVec(); auto &body = GetBody(vm, other_id, vm.PopVec()); b2PolygonShape shape; shape.SetAsBox(sz.x, sz.y, offset, rot * RAD); vm.Push(CreateFixture(vm, body, shape)); }); nfr("ph_create_circle", "position,radius,offset,attachto", "F}:2FF}:2?R?", "R", "creates a physical circle shape in the world at position, with the given radius, offset" " from the center if needed. attachto is a previous physical object to attach this one to," " to become a combined physical body.", [](VM &vm) { auto other_id = vm.Pop(); auto offset = OptionalOffset(vm); auto radius = vm.Pop().fltval(); auto &body = GetBody(vm, other_id, vm.PopVec()); b2CircleShape shape; shape.m_p.Set(offset.x, offset.y); shape.m_radius = radius; vm.Push(CreateFixture(vm, body, shape)); }); nfr("ph_create_polygon", "position,vertices,attachto", "F}:2F}:2]R?", "R", "creates a polygon circle shape in the world at position, with the given list of vertices." " attachto is a previous physical object to attach this one to, to become a combined" " physical body.", [](VM &vm) { auto other_id = vm.Pop(); auto vertices = vm.Pop().vval(); auto &body = GetBody(vm, other_id, vm.PopVec()); b2PolygonShape shape; auto verts = new b2Vec2[vertices->len]; for (int i = 0; i < vertices->len; i++) { auto vert = ValueToFLT<2>(vertices->AtSt(i), vertices->width); verts[i] = Float2ToB2(vert); } shape.Set(verts, (int)vertices->len); delete[] verts; vm.Push(CreateFixture(vm, body, shape)); }); nfr("ph_dynamic", "shape,on", "RB", "", "makes a shape dynamic (on = true) or not.", [](VM &vm, Value &fixture_id, Value &on) { CheckPhysics(); GetObject(vm, fixture_id) .fixture->GetBody() ->SetType(on.ival() ? b2_dynamicBody : b2_staticBody); return Value(); }); nfr("ph_set_color", "id,color", "R?F}:4", "", "sets a shape (or nil for particles) to be rendered with a particular color.", [](VM &vm) { auto c = vm.PopVec(); auto &r = GetRenderable(vm, vm.Pop()); r.color = c; }); nfr("ph_set_shader", "id,shadername", "R?S", "", "sets a shape (or nil for particles) to be rendered with a particular shader.", [](VM &vm, Value &fixture_id, Value &shader) { auto &r = GetRenderable(vm, fixture_id); auto sh = LookupShader(shader.sval()->strv()); if (sh) r.sh = sh; return Value(); }); nfr("ph_set_texture", "id,tex,texunit", "R?RI?", "", "sets a shape (or nil for particles) to be rendered with a particular texture" " (assigned to a texture unit, default 0).", [](VM &vm, Value &fixture_id, Value &tex, Value &tex_unit) { auto &r = GetRenderable(vm, fixture_id); extern Texture GetTexture(VM &vm, const Value &res); r.Get(GetSampler(vm, tex_unit)) = GetTexture(vm, tex); return Value(); }); nfr("ph_get_position", "id", "R", "F}:2", "gets a shape's position.", [](VM &vm) { vm.PushVec(GetObject(vm, vm.Pop()).Pos()); }); nfr("ph_create_particle", "position,velocity,color,flags", "F}:2F}:2F}:4I?", "I", "creates an individual particle. For flags, see include/physics.lobster", [](VM &vm) { CheckParticles(); b2ParticleDef pd; pd.flags = vm.Pop().intval(); auto c = vm.PopVec(); pd.color.Set(b2Color(c.x, c.y, c.z)); pd.velocity = PopB2(vm); pd.position = PopB2(vm); vm.Push(particlesystem->CreateParticle(pd)); }); nfr("ph_create_particle_circle", "position,radius,color,flags", "F}:2FF}:4I?", "", "creates a circle filled with particles. For flags, see include/physics.lobster", [](VM &vm) { CheckParticles(); b2ParticleGroupDef pgd; b2CircleShape shape; pgd.shape = &shape; pgd.flags = vm.Pop().intval(); auto c = vm.PopVec(); pgd.color.Set(b2Color(c.x, c.y, c.z)); shape.m_radius = vm.Pop().fltval(); pgd.position = PopB2(vm); particlesystem->CreateParticleGroup(pgd); }); nfr("ph_initialize_particles", "radius", "F", "", "initializes the particle system with a given particle radius.", [](VM &, Value &size) { CheckParticles(size.fltval()); return Value(); }); nfr("ph_step", "seconds,viter,piter", "FII", "", "simulates the physical world for the given period (try: gl_delta_time()). You can specify" " the amount of velocity/position iterations per step, more means more accurate but also" " more expensive computationally (try 8 and 3).", [](VM &, Value &delta, Value &viter, Value &piter) { CheckPhysics(); world->Step(min(delta.fltval(), 0.1f), viter.intval(), piter.intval()); if (particlesystem) { for (b2Body *body = world->GetBodyList(); body; body = body->GetNext()) { for (b2Fixture *fixture = body->GetFixtureList(); fixture; fixture = fixture->GetNext()) { auto pc = ((PhysicsObject *)fixture->GetUserData())->particle_contacts; if (pc) pc->clear(); } } auto contacts = particlesystem->GetBodyContacts(); for (int i = 0; i < particlesystem->GetBodyContactCount(); i++) { auto &c = contacts[i]; auto pc = ((PhysicsObject *)c.fixture->GetUserData())->particle_contacts; if (pc) pc->push_back(c.index); } } return Value(); }); nfr("ph_particle_contacts", "id", "R", "I]", "gets the particle indices that are currently contacting a giving physics object." " Call after step(). Indices may be invalid after next step().", [](VM &vm, Value &id) { CheckPhysics(); auto &po = GetObject(vm, id); if (!po.particle_contacts) po.particle_contacts = new vector(); auto numelems = (int)po.particle_contacts->size(); auto v = vm.NewVec(numelems, numelems, TYPE_ELEM_VECTOR_OF_INT); for (int i = 0; i < numelems; i++) v->At(i) = Value((*po.particle_contacts)[i]); return Value(v); }); nfr("ph_raycast", "p1,p2,n", "F}:2F}:2I", "I]", "returns a vector of the first n particle ids that intersect a ray from p1 to p2," " not including particles that overlap p1.", [](VM &vm) { CheckPhysics(); auto n = vm.Pop().ival(); auto p2v = PopB2(vm); auto p1v = PopB2(vm); auto v = vm.NewVec(0, max(n, (intp)1), TYPE_ELEM_VECTOR_OF_INT); if (!particlesystem) { vm.Push(v); return; } struct callback : b2RayCastCallback { LVector *v; VM &vm; float ReportParticle(const b2ParticleSystem *, int i, const b2Vec2 &, const b2Vec2 &, float) { v->Push(vm, Value(i)); return v->len == v->maxl ? -1.0f : 1.0f; } float ReportFixture(b2Fixture *, const b2Vec2 &, const b2Vec2 &, float) { return -1.0f; } callback(LVector *_v, VM &vm) : v(_v), vm(vm) {} } cb(v, vm); particlesystem->RayCast(&cb, p1v, p2v); vm.Push(v); }); nfr("ph_delete_particle", "i", "I", "", "deletes given particle. Deleting particles causes indices to be invalidated at next" " step().", [](VM &, Value &i) { CheckPhysics(); particlesystem->DestroyParticle(i.intval()); return Value(); }); nfr("ph_getparticle_position", "i", "I", "F}:2", "gets a particle's position.", [](VM &vm) { CheckPhysics(); auto pos = B2ToFloat2(particlesystem->GetPositionBuffer()[vm.Pop().ival()]); vm.PushVec(pos); }); nfr("ph_render", "", "", "", "renders all rigid body objects.", [](VM &) { CheckPhysics(); auto oldobject2view = otransforms.object2view; auto oldcolor = curcolor; for (b2Body *body = world->GetBodyList(); body; body = body->GetNext()) { auto pos = body->GetPosition(); auto mat = translation(float3(pos.x, pos.y, 0)) * rotationZ(body->GetAngle()); otransforms.object2view = oldobject2view * mat; for (b2Fixture *fixture = body->GetFixtureList(); fixture; fixture = fixture->GetNext()) { auto shapetype = fixture->GetType(); auto &r = ((PhysicsObject *)fixture->GetUserData())->r; curcolor = r.color; switch (shapetype) { case b2Shape::e_polygon: { r.Set(); auto polyshape = (b2PolygonShape *)fixture->GetShape(); RenderArraySlow( PRIM_FAN, make_span(polyshape->m_vertices, polyshape->m_count), "pn", span(), make_span(polyshape->m_normals, polyshape->m_count)); break; } case b2Shape::e_circle: { r.sh->SetTextures(r.textures); // FIXME auto polyshape = (b2CircleShape *)fixture->GetShape(); Transform2D(translation(float3(B2ToFloat2(polyshape->m_p), 0)), [&]() { geomcache->RenderCircle(r.sh, PRIM_FAN, 20, polyshape->m_radius); }); break; } case b2Shape::e_edge: case b2Shape::e_chain: case b2Shape::e_typeCount: assert(0); break; } } } otransforms.object2view = oldobject2view; curcolor = oldcolor; return Value(); }); nfr("ph_render_particles", "scale", "F", "", "render all particles, with the given scale.", [](VM &, Value &particlescale) { CheckPhysics(); if (!particlesystem) return Value(); // LOG_DEBUG("rendering particles: ", particlesystem->GetParticleCount()); auto verts = (float2 *)particlesystem->GetPositionBuffer(); auto colors = (byte4 *)particlesystem->GetColorBuffer(); auto scale = length(otransforms.object2view[0].xy()); SetPointSprite(scale * particlesystem->GetRadius() * particlescale.fltval()); particlematerial->Set(); RenderArraySlow(PRIM_POINT, make_span(verts, particlesystem->GetParticleCount()), "pC", span(), make_span(colors, particlesystem->GetParticleCount())); return Value(); }); } // AddPhysics treesheets-1.0.2/lobster/src/platform.cpp000066400000000000000000000420451352107072600204660ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Misc platform specific stuff. #include "lobster/stdafx.h" #include #include #ifdef _WIN32 #define VC_EXTRALEAN #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #define FILESEP '\\' #include #else #include #ifndef PLATFORM_ES3 #include #include #endif #define FILESEP '/' #endif #ifdef __linux__ #include #endif #ifdef __APPLE__ #include "CoreFoundation/CoreFoundation.h" #ifndef __IOS__ #include #endif #endif #ifdef __ANDROID__ #include #include "sdlincludes.h" // FIXME #endif // Dirs to load files relative to, they typically contain, and will be searched in this order: // - The project specific files. This is where the bytecode file you're running or the main // .lobster file you're compiling reside. // - The standard lobster files. On windows this is where lobster.exe resides, on apple // platforms it's the Resource folder in the bundle. // - The same as writedir below (to be able to load files the program has been written). // - Any additional dirs declared with "include from". vector data_dirs; // Folder to write to, usually the same as project dir, special folder on mobile platforms. string write_dir; string maindir; string projectdir; string_view ProjectDir() { return projectdir; } string_view MainDir() { return maindir; } FileLoader cur_loader = nullptr; bool have_console = true; #ifndef _WIN32 // Emulate QPC on *nix, thanks Lee. struct LARGE_INTEGER { long long int QuadPart; }; void QueryPerformanceCounter(LARGE_INTEGER *dst) { struct timeval t; gettimeofday (& t, nullptr); dst->QuadPart = t.tv_sec * 1000000LL + t.tv_usec; } void QueryPerformanceFrequency(LARGE_INTEGER *dst) { dst->QuadPart = 1000000LL; } #endif static LARGE_INTEGER time_frequency, time_start; void InitTime() { QueryPerformanceFrequency(&time_frequency); QueryPerformanceCounter(&time_start); } double SecondsSinceStart() { LARGE_INTEGER end; QueryPerformanceCounter(&end); return double(end.QuadPart - time_start.QuadPart) / double(time_frequency.QuadPart); } uint hwthreads = 2, hwcores = 1; void InitCPU() { // This can fail and return 0, so default to 2 threads: hwthreads = max(2U, thread::hardware_concurrency()); // As a baseline, assume desktop CPUs are hyperthreaded, and mobile ones are not. #ifdef PLATFORM_ES3 hwcores = hwthreads; #else hwcores = max(1U, hwthreads / 2); #endif // On Windows, we can do better and actually count cores. #ifdef _WIN32 DWORD buflen = 0; if (!GetLogicalProcessorInformation(nullptr, &buflen) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { vector buf(buflen / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)); if (GetLogicalProcessorInformation(buf.data(), &buflen)) { uint cores = 0; for (auto &lpi : buf) { if (lpi.Relationship == RelationProcessorCore) cores++; } // Only overwrite our baseline if we actually found any cores. if (cores) hwcores = cores; } } #endif } uint NumHWThreads() { return hwthreads; } uint NumHWCores() { return hwcores; } string_view StripFilePart(string_view filepath) { auto fpos = filepath.find_last_of(FILESEP); return fpos != string_view::npos ? filepath.substr(0, fpos + 1) : ""; } const char *StripDirPart(const char *filepath) { auto fpos = strrchr(filepath, FILESEP); if (!fpos) fpos = strrchr(filepath, ':'); return fpos ? fpos + 1 : filepath; } string_view StripTrailing(string_view in, string_view tail) { if (in.size() >= tail.size() && in.substr(in.size() - tail.size()) == tail) return in.substr(0, in.size() - tail.size()); return in; } string GetMainDirFromExePath(const char *argv_0) { string md = SanitizePath(argv_0); #ifdef _WIN32 // Windows can pass just the exe name without a full path, which is useless. char winfn[MAX_PATH + 1]; GetModuleFileName(NULL, winfn, MAX_PATH + 1); md = winfn; #endif #ifdef __linux__ char path[PATH_MAX]; ssize_t length = readlink("/proc/self/exe", path, sizeof(path)-1); if (length != -1) { path[length] = '\0'; md = string(path); } #endif md = StripTrailing(StripTrailing(StripFilePart(md), "bin/"), "bin\\"); return md; } int64_t DefaultLoadFile(string_view absfilename, string *dest, int64_t start, int64_t len) { LOG_INFO("DefaultLoadFile: ", absfilename); auto f = fopen(null_terminated(absfilename), "rb"); if (!f) return -1; if (fseek(f, 0, SEEK_END)) { fclose(f); return -1; } auto filelen = ftell(f); if (!len) { // Just the file length requested. fclose(f); return filelen; } if (len < 0) len = filelen; fseek(f, (long)start, SEEK_SET); // FIXME: 32-bit on WIN32. dest->resize((size_t)len); auto rlen = fread(&(*dest)[0], 1, (size_t)len, f); fclose(f); return len != (int64_t)rlen ? -1 : len; } bool InitPlatform(string _maindir, const char *auxfilepath, bool from_bundle, FileLoader loader) { maindir = _maindir; InitTime(); InitCPU(); cur_loader = loader; // FIXME: use SDL_GetBasePath() instead? #if defined(__APPLE__) if (from_bundle) { have_console = false; // Default data dir is the Resources folder inside the .app bundle. CFBundleRef mainBundle = CFBundleGetMainBundle(); CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle); char path[PATH_MAX]; auto res = CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path, PATH_MAX); CFRelease(resourcesURL); if (!res) return false; auto resources_dir = string(path) + "/"; data_dirs.push_back(resources_dir); #ifdef __IOS__ // There's probably a better way to do this in CF. write_dir = StripFilePart(path) + "Documents/"; data_dirs.push_back(write_dir); #else // FIXME: This should probably be ~/Library/Application Support/AppName, // but for now this works for non-app store apps. write_dir = resources_dir; #endif return true; } #else (void)from_bundle; #endif #if defined(__ANDROID__) // FIXME: removed SDL dependency for other platforms. So on Android, move calls to // SDL_AndroidGetInternalStoragePath into SDL and pass the results into here somehow. SDL_Init(0); // FIXME: Is this needed? bad dependency. auto internalstoragepath = SDL_AndroidGetInternalStoragePath(); auto externalstoragepath = SDL_AndroidGetExternalStoragePath(); LOG_INFO(internalstoragepath); LOG_INFO(externalstoragepath); if (internalstoragepath) data_dirs.push_back(internalstoragepath + string_view("/")); if (externalstoragepath) write_dir = externalstoragepath + string_view("/"); // For some reason, the above SDL functionality doesn't actually work, // we have to use the relative path only to access APK files: data_dirs.clear(); // FIXME. data_dirs.push_back(""); data_dirs.push_back(write_dir); #else // Linux, Windows, and OS X console mode. if (auxfilepath) { projectdir = StripFilePart(SanitizePath(auxfilepath)); data_dirs.push_back(projectdir); write_dir = projectdir; } else { write_dir = maindir; } data_dirs.push_back(maindir); #ifdef PLATFORM_DATADIR data_dirs.push_back(PLATFORM_DATADIR); #endif #ifdef _WIN32 have_console = GetConsoleWindow() != nullptr; #endif #endif return true; } void AddDataDir(string_view path) { for (auto &dir : data_dirs) if (dir == path) return; data_dirs.push_back(projectdir + SanitizePath(path)); } string SanitizePath(string_view path) { string r; for (auto c : path) { r += c == '\\' || c == '/' ? FILESEP : c; } return r; } map, less<>> pakfile_registry; void AddPakFileEntry(string_view pakfilename, string_view relfilename, int64_t off, int64_t len, int64_t uncompressed) { pakfile_registry[string(relfilename)] = make_tuple(pakfilename, off, len, uncompressed); } int64_t LoadFileFromAny(string_view srelfilename, string *dest, int64_t start, int64_t len) { for (auto &dir : data_dirs) { auto l = cur_loader(dir + srelfilename, dest, start, len); if (l >= 0) return l; } return -1; } // We don't generally load in ways that allow stdio text mode conversions, so this function // emulates them at best effort. void TextModeConvert(string &s, bool binary) { if (binary) return; #ifdef _WIN32 s.erase(remove(s.begin(), s.end(), '\r'), s.end()); #endif } int64_t LoadFile(string_view relfilename, string *dest, int64_t start, int64_t len, bool binary) { assert(cur_loader); auto it = pakfile_registry.find(relfilename); if (it != pakfile_registry.end()) { auto &[fname, foff, flen, funcompressed] = it->second; auto l = LoadFileFromAny(fname, dest, foff, flen); if (l >= 0) { if (funcompressed >= 0) { string uncomp; WEntropyCoder((const uchar *)dest->c_str(), dest->length(), (size_t)funcompressed, uncomp); dest->swap(uncomp); TextModeConvert(*dest, binary); return funcompressed; } else { TextModeConvert(*dest, binary); return l; } } } if (len > 0) LOG_INFO("load: ", relfilename); auto size = LoadFileFromAny(SanitizePath(relfilename), dest, start, len); TextModeConvert(*dest, binary); return size; } string WriteFileName(string_view relfilename) { return write_dir + SanitizePath(relfilename); } FILE *OpenForWriting(string_view relfilename, bool binary) { auto fn = WriteFileName(relfilename); LOG_INFO("write: ", fn); return fopen(fn.c_str(), binary ? "wb" : "w"); } bool WriteFile(string_view relfilename, bool binary, string_view contents) { FILE *f = OpenForWriting(relfilename, binary); size_t written = 0; if (f) { written = fwrite(contents.data(), contents.size(), 1, f); fclose(f); } return written == 1; } bool FileExists(string_view relfilename) { auto f = fopen(WriteFileName(relfilename).c_str(), "rb"); if (f) fclose(f); return f; } bool FileDelete(string_view relfilename) { return remove(WriteFileName(relfilename).c_str()) == 0; } // TODO: can now replace all this platform specific stuff with std::filesystem code. // https://github.com/tvaneerd/cpp17_in_TTs/blob/master/ALL_IN_ONE.md // http://en.cppreference.com/w/cpp/experimental/fs bool ScanDirAbs(string_view absdir, vector> &dest) { string folder = SanitizePath(absdir); #ifdef _WIN32 WIN32_FIND_DATA fdata; HANDLE fh = FindFirstFile((folder + "\\*.*").c_str(), &fdata); if (fh != INVALID_HANDLE_VALUE) { do { if (strcmp(fdata.cFileName, ".") && strcmp(fdata.cFileName, "..")) { ULONGLONG size = (static_cast(fdata.nFileSizeHigh) << (sizeof(uint) * 8)) | fdata.nFileSizeLow; dest.push_back( { fdata.cFileName, fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? -1 : (int64_t)size }); } } while(FindNextFile(fh, &fdata)); FindClose(fh); return true; } #elif !defined(PLATFORM_ES3) glob_t gl; string mask = folder + "/*"; if (!glob(mask.c_str(), GLOB_MARK | GLOB_TILDE, nullptr, &gl)) { for (size_t fi = 0; fi < gl.gl_pathc; fi++) { string xFileName = gl.gl_pathv[fi]; bool isDir = xFileName[xFileName.length()-1] == '/'; if (isDir) xFileName = xFileName.substr(0, xFileName.length() - 1); string cFileName = xFileName.substr(xFileName.find_last_of('/') + 1); struct stat st; stat(gl.gl_pathv[fi], &st); dest.push_back({ cFileName, isDir ? -1 : (int64_t)st.st_size }); } globfree(&gl); return true; } #endif return false; } bool ScanDir(string_view reldir, vector> &dest) { auto srfn = SanitizePath(reldir); for (auto &dir : data_dirs) { if (ScanDirAbs(dir + srfn, dest)) return true; } return false; } OutputType min_output_level = OUTPUT_WARN; void LogOutput(OutputType ot, const char *buf) { if (ot < min_output_level) return; #ifdef __ANDROID__ auto tag = "lobster"; switch (ot) { case OUTPUT_DEBUG: __android_log_print(ANDROID_LOG_DEBUG, tag, "%s", buf); break; case OUTPUT_INFO: __android_log_print(ANDROID_LOG_INFO, tag, "%s", buf); break; case OUTPUT_WARN: __android_log_print(ANDROID_LOG_WARN, tag, "%s", buf); break; case OUTPUT_PROGRAM: __android_log_print(ANDROID_LOG_ERROR, tag, "%s", buf); break; case OUTPUT_ERROR: __android_log_print(ANDROID_LOG_ERROR, tag, "%s", buf); break; } #elif defined(_WIN32) OutputDebugStringA(buf); OutputDebugStringA("\n"); if (ot >= OUTPUT_INFO) { printf("%s\n", buf); } #elif defined(__IOS__) extern void IOSLog(const char *msg); IOSLog(buf); #else printf("%s\n", buf); #endif if (!have_console) { auto f = fopen((maindir + "lobster.exe.con.log").c_str(), "a"); if (f) { fputs(buf, f); fputs("\n", f); fclose(f); } } } // Use this instead of assert to break on a condition and still be able to continue in the debugger. void ConditionalBreakpoint(bool shouldbreak) { if (shouldbreak) { #ifdef _WIN32 __debugbreak(); #elif __GCC__ __builtin_trap(); #endif } } // Insert without args to find out which iteration it gets to, then insert that iteration number. void CountingBreakpoint(int i) { static int j = 0; if (i < 0) LOG_DEBUG("counting breakpoint: ", j); ConditionalBreakpoint(j++ == i); } void MakeDPIAware() { #ifdef _WIN32 // Without this, Windows scales the GL window if scaling is set in display settings. #ifndef DPI_ENUMS_DECLARED typedef enum PROCESS_DPI_AWARENESS { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; #endif typedef BOOL (WINAPI * SETPROCESSDPIAWARE_T)(void); typedef HRESULT (WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS); HMODULE shcore = LoadLibraryA("Shcore.dll"); SETPROCESSDPIAWARENESS_T SetProcessDpiAwareness = NULL; if (shcore) { SetProcessDpiAwareness = (SETPROCESSDPIAWARENESS_T)GetProcAddress(shcore, "SetProcessDpiAwareness"); } HMODULE user32 = LoadLibraryA("User32.dll"); SETPROCESSDPIAWARE_T SetProcessDPIAware = NULL; if (user32) { SetProcessDPIAware = (SETPROCESSDPIAWARE_T)GetProcAddress(user32, "SetProcessDPIAware"); } if (SetProcessDpiAwareness) { SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); } else if (SetProcessDPIAware) { SetProcessDPIAware(); } if (user32) FreeLibrary(user32); if (shcore) FreeLibrary(shcore); #endif } string GetDateTime() { time_t t; time(&t); auto tm = localtime(&t); char buf[1024]; strftime(buf, sizeof(buf), "%F-%H-%M-%S", tm); return buf; } void SetConsole(bool on) { have_console = on; #ifdef _WIN32 if (on) AllocConsole(); else FreeConsole(); #endif } treesheets-1.0.2/lobster/src/sdlaudiosfxr.cpp000066400000000000000000000312431352107072600213470ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/sdlincludes.h" #include "SDL_mixer.h" #include "SDL_stdinc.h" bool sound_init = false; struct Sound { unique_ptr chunk; Sound() : chunk(nullptr, Mix_FreeChunk) {} }; map> sound_files; Mix_Chunk *RenderSFXR(string_view buf) { int wave_type = 0; float p_base_freq = 0.3f; float p_freq_limit = 0.0f; float p_freq_ramp = 0.0f; float p_freq_dramp = 0.0f; float p_duty = 0.0f; float p_duty_ramp = 0.0f; float p_vib_strength = 0.0f; float p_vib_speed = 0.0f; float p_vib_delay = 0.0f; float p_env_attack = 0.0f; float p_env_sustain = 0.3f; float p_env_decay = 0.4f; float p_env_punch = 0.0f; bool filter_on = false; float p_lpf_resonance = 0.0f; float p_lpf_freq = 1.0f; float p_lpf_ramp = 0.0f; float p_hpf_freq = 0.0f; float p_hpf_ramp = 0.0f; float p_pha_offset = 0.0f; float p_pha_ramp = 0.0f; float p_repeat_speed = 0.0f; float p_arp_speed = 0.0f; float p_arp_mod = 0.0f; float master_vol=0.05f; float sound_vol=0.5f; int phase; double fperiod; double fmaxperiod; double fslide; double fdslide; int period; float square_duty; float square_slide; int env_stage; int env_time; int env_length[3]; float env_vol; float fphase; float fdphase; int iphase; float phaser_buffer[1024]; int ipp; float noise_buffer[32]; float fltp; float fltdp; float fltw; float fltw_d; float fltdmp; float fltphp; float flthp; float flthp_d; float vib_phase; float vib_speed; float vib_amp; int rep_time; int rep_limit; int arp_time; int arp_limit; double arp_mod; auto file = buf.data(); auto fread_mem = [&](auto &dest) { assert(file < buf.data() + buf.size()); memcpy(&dest, file, sizeof(dest)); file += sizeof(dest); }; int version = 0; fread_mem(version); if(version!=102) return nullptr; fread_mem(wave_type); fread_mem(sound_vol); fread_mem(p_base_freq); fread_mem(p_freq_limit); fread_mem(p_freq_ramp); fread_mem(p_freq_dramp); fread_mem(p_duty); fread_mem(p_duty_ramp); fread_mem(p_vib_strength); fread_mem(p_vib_speed); fread_mem(p_vib_delay); fread_mem(p_env_attack); fread_mem(p_env_sustain); fread_mem(p_env_decay); fread_mem(p_env_punch); fread_mem(filter_on); fread_mem(p_lpf_resonance); fread_mem(p_lpf_freq); fread_mem(p_lpf_ramp); fread_mem(p_hpf_freq); fread_mem(p_hpf_ramp); fread_mem(p_pha_offset); fread_mem(p_pha_ramp); fread_mem(p_repeat_speed); fread_mem(p_arp_speed); fread_mem(p_arp_mod); auto frnd = [](float range) -> float { return (float)(rand()%(10000+1))/10000*range; }; auto ResetSample = [&](bool restart) { if(!restart) phase=0; fperiod=100.0/(p_base_freq*p_base_freq+0.001); period=(int)fperiod; fmaxperiod=100.0/(p_freq_limit*p_freq_limit+0.001); fslide=1.0-pow((double)p_freq_ramp, 3.0)*0.01; fdslide=-pow((double)p_freq_dramp, 3.0)*0.000001; square_duty=0.5f-p_duty*0.5f; square_slide=-p_duty_ramp*0.00005f; if(p_arp_mod>=0.0f) arp_mod=1.0-pow((double)p_arp_mod, 2.0)*0.9; else arp_mod=1.0+pow((double)p_arp_mod, 2.0)*10.0; arp_time=0; arp_limit=(int)(pow(1.0f-p_arp_speed, 2.0f)*20000+32); if(p_arp_speed==1.0f) arp_limit=0; if(!restart) { // reset filter fltp=0.0f; fltdp=0.0f; fltw=pow(p_lpf_freq, 3.0f)*0.1f; fltw_d=1.0f+p_lpf_ramp*0.0001f; fltdmp=5.0f/(1.0f+pow(p_lpf_resonance, 2.0f)*20.0f)*(0.01f+fltw); if(fltdmp>0.8f) fltdmp=0.8f; fltphp=0.0f; flthp=pow(p_hpf_freq, 2.0f)*0.1f; flthp_d=1.0f+p_hpf_ramp*0.0003f; // reset vibrato vib_phase=0.0f; vib_speed=pow(p_vib_speed, 2.0f)*0.01f; vib_amp=p_vib_strength*0.5f; // reset envelope env_vol=0.0f; env_stage=0; env_time=0; env_length[0]=(int)(p_env_attack*p_env_attack*100000.0f); env_length[1]=(int)(p_env_sustain*p_env_sustain*100000.0f); env_length[2]=(int)(p_env_decay*p_env_decay*100000.0f); fphase=pow(p_pha_offset, 2.0f)*1020.0f; if(p_pha_offset<0.0f) fphase=-fphase; fdphase=pow(p_pha_ramp, 2.0f)*1.0f; if(p_pha_ramp<0.0f) fdphase=-fdphase; iphase=abs((int)fphase); ipp=0; for(int i=0;i<1024;i++) phaser_buffer[i]=0.0f; for(int i=0;i<32;i++) noise_buffer[i]=frnd(2.0f)-1.0f; rep_time=0; rep_limit=(int)(pow(1.0f-p_repeat_speed, 2.0f)*20000+32); if(p_repeat_speed==0.0f) rep_limit=0; } }; auto SynthSample = [&](int length, float* buffer) -> int { for(int i=0;i=rep_limit) { rep_time=0; ResetSample(true); } // frequency envelopes/arpeggios arp_time++; if(arp_limit!=0 && arp_time>=arp_limit) { arp_limit=0; fperiod*=arp_mod; } fslide+=fdslide; fperiod*=fslide; if(fperiod>fmaxperiod) { fperiod=fmaxperiod; if(p_freq_limit>0.0f) { return i; } } float rfperiod=(float)fperiod; if(vib_amp>0.0f) { vib_phase+=vib_speed; rfperiod=float(fperiod*(1.0+sin(vib_phase)*vib_amp)); } period=(int)rfperiod; if(period<8) period=8; square_duty+=square_slide; if(square_duty<0.0f) square_duty=0.0f; if(square_duty>0.5f) square_duty=0.5f; // volume envelope env_time++; if(env_time>env_length[env_stage]) { env_time=0; env_stage++; if(env_stage==3) { return i; } } if(env_stage==0) env_vol=(float)env_time/env_length[0]; if(env_stage==1) env_vol=1.0f+pow(1.0f-(float)env_time/env_length[1], 1.0f)*2.0f*p_env_punch; if(env_stage==2) env_vol=1.0f-(float)env_time/env_length[2]; // phaser step fphase+=fdphase; iphase=abs((int)fphase); if(iphase>1023) iphase=1023; if(flthp_d!=0.0f) { flthp*=flthp_d; if(flthp<0.00001f) flthp=0.00001f; if(flthp>0.1f) flthp=0.1f; } float ssample=0.0f; for(int si=0;si<8;si++) { // 8x supersampling float sample=0.0f; phase++; if(phase>=period) { // phase=0; phase%=period; if(wave_type==3) for(int i=0;i<32;i++) noise_buffer[i]=frnd(2.0f)-1.0f; } // base waveform float fp=(float)phase/period; switch(wave_type) { case 0: // square if(fp0.1f) fltw=0.1f; if(p_lpf_freq!=1.0f) { fltdp+=(sample-fltp)*fltw; fltdp-=fltdp*fltdmp; } else { fltp=sample; fltdp=0.0f; } fltp+=fltdp; // hp filter fltphp+=fltp-pp; fltphp-=fltphp*flthp; sample=fltphp; // phaser phaser_buffer[ipp&1023]=sample; sample+=phaser_buffer[(ipp-iphase+1024)&1023]; ipp=(ipp+1)&1023; // final accumulation and envelope application ssample+=sample*env_vol; } ssample=ssample/8*master_vol; ssample*=2.0f*sound_vol; if(buffer!=nullptr) { if(ssample>1.0f) ssample=1.0f; if(ssample<-1.0f) ssample=-1.0f; *buffer++=ssample; } } return length; }; ResetSample(false); vector synth; for (;;) { float sample; auto gen = SynthSample(1, &sample); if (!gen) break; synth.push_back((Sint16)(sample * 0x7FFF)); } Uint16 format; Mix_QuerySpec(nullptr, &format, nullptr); if (format != AUDIO_S16SYS) { assert(false); return nullptr; } auto sbuf = SDL_malloc(synth.size() * 2); memcpy(sbuf, synth.data(), synth.size() * 2); auto chunk = Mix_QuickLoad_RAW((Uint8 *)sbuf, (Uint32)synth.size() * 2); chunk->allocated = 1; return chunk; } Sound *LoadSound(string_view filename, bool sfxr) { auto it = sound_files.find(filename); if (it != sound_files.end()) { return &it->second; } string buf; if (LoadFile(filename, &buf) < 0) return nullptr; Mix_Chunk *chunk; if (!sfxr) { auto rwops = SDL_RWFromMem((void *)buf.c_str(), (int)buf.length()); if (!rwops) return nullptr; chunk = Mix_LoadWAV_RW(rwops, 1); } else { chunk = RenderSFXR(buf); } if (!chunk) return nullptr; //Mix_VolumeChunk(chunk, MIX_MAX_VOLUME / 2); Sound snd; snd.chunk.reset(chunk); return &(sound_files.insert({ string(filename), std::move(snd) }).first->second); } bool SDLSoundInit() { if (sound_init) return true; for (int i = 0; i < SDL_GetNumAudioDrivers(); ++i) { LOG_INFO("Audio driver available ", SDL_GetAudioDriver(i)); } if (SDL_InitSubSystem(SDL_INIT_AUDIO)) return false; #ifdef _WIN32 // It defaults to wasapi which doesn't output any sound? auto err = SDL_AudioInit("directsound"); if (err) LOG_INFO("Forcing driver failed", err); #endif int count = SDL_GetNumAudioDevices(0); for (int i = 0; i < count; ++i) { LOG_INFO("Audio device ", i, ":", SDL_GetAudioDeviceName(i, 0)); } Mix_Init(0); // For some reason this distorts when set to 44100 and samples at 22050 are played. // Also SFXR seems hard-coded to 22050, so that's what we'll use for now. if (Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 1024) == -1) { LOG_ERROR("Mix_OpenAudio: ", Mix_GetError()); return false; } // This seems to be needed to not distort when multiple sounds are played. Mix_Volume(-1, MIX_MAX_VOLUME / 2); sound_init = true; return true; } void SDLSoundClose() { sound_files.clear(); Mix_CloseAudio(); while (Mix_Init(0)) Mix_Quit(); SDL_QuitSubSystem(SDL_INIT_AUDIO); } bool SDLPlaySound(string_view filename, bool sfxr, int vol) { #ifdef __EMSCRIPTEN__ // Distorted in firefox and no audio at all in chrome, disable for now. return false; #endif if (!SDLSoundInit()) return false; auto snd = LoadSound(filename, sfxr); if (snd) { Mix_Volume(Mix_PlayChannel(-1, snd->chunk.get(), 0), vol); } return !!snd; } treesheets-1.0.2/lobster/src/sdlsystem.cpp000066400000000000000000000547241352107072600207000ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/sdlincludes.h" #include "lobster/sdlinterface.h" #include "lobster/glinterface.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #ifdef _WIN32 #pragma warning(push) #pragma warning(disable: 4244) #endif #include "stb/stb_image_write.h" #ifdef _WIN32 #pragma warning(pop) #endif SDL_Window *_sdl_window = nullptr; SDL_GLContext _sdl_context = nullptr; /* // FIXME: document this, especially the ones containing spaces. mouse1 mouse2 mouse3... backspace tab clear return pause escape space delete ! " # $ & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [.] [/] [*] [-] [+] enter equals up down right left insert home end page up page down f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15 numlock caps lock scroll lock right shift left shift right ctrl left ctrl right alt left alt right meta left meta left super right super alt gr compose help print screen sys req break */ struct KeyState { TimeBool8 button; double lasttime[2]; int2 lastpos[2]; KeyState() { lasttime[0] = lasttime[1] = 0x80000000; lastpos[0] = lastpos[1] = int2(-1, -1); } void FrameReset() { button.Advance(); } }; map> keymap; int mousewheeldelta = 0; int skipmousemotion = 3; double frametime = 1.0f / 60.0f, lasttime = 0; uint64_t timefreq = 0, timestart = 0; int frames = 0; vector frametimelog; int2 screensize = int2_0; int2 inputscale = int2_1; bool fullscreen = false; bool cursor = true; bool landscape = true; bool minimized = false; bool noninteractivetestmode = false; const int MAXAXES = 8; float joyaxes[MAXAXES] = { 0 }; struct Finger { SDL_FingerID id; int2 mousepos; int2 mousedelta; bool used; Finger() : id(0), mousepos(-1), mousedelta(0), used(false) {}; }; const int MAXFINGERS = 10; Finger fingers[MAXFINGERS]; void updatebutton(string &name, bool on, int posfinger) { auto &ks = keymap[name]; ks.button.Set(on); ks.lasttime[on] = lasttime; ks.lastpos[on] = fingers[posfinger].mousepos; } void updatemousebutton(int button, int finger, bool on) { string name = "mouse"; name += '0' + (char)button; if (finger) name += '0' + (char)finger; updatebutton(name, on, finger); } void clearfingers(bool delta) { for (auto &f : fingers) (delta ? f.mousedelta : f.mousepos) = int2(0); } int findfinger(SDL_FingerID id, bool remove) { for (auto &f : fingers) if (f.id == id && f.used) { if (remove) { // would be more correct to clear mouse position here, but that doesn't work with delayed touch.. // would have to delay it too f.used = false; } return int(&f - fingers); } if (remove) return MAXFINGERS - 1; // FIXME: this is masking a bug... assert(!remove); for (auto &f : fingers) if (!f.used) { f.id = id; f.used = true; return int(&f - fingers); } assert(0); return 0; } const int2 &GetFinger(int i, bool delta) { auto &f = fingers[max(min(i, MAXFINGERS - 1), 0)]; return delta ? f.mousedelta : f.mousepos; } float GetJoyAxis(int i) { return joyaxes[max(min(i, MAXAXES - 1), 0)]; } int updatedragpos(SDL_TouchFingerEvent &e, Uint32 et) { int numfingers = SDL_GetNumTouchFingers(e.touchId); //assert(numfingers && e.fingerId < numfingers); for (int i = 0; i < numfingers; i++) { auto finger = SDL_GetTouchFinger(e.touchId, i); if (finger->id == e.fingerId) { // this is a bit clumsy as SDL has a list of fingers and so do we, but they work a bit differently int j = findfinger(e.fingerId, et == SDL_FINGERUP); auto &f = fingers[j]; auto ep = float2(e.x, e.y); auto ed = float2(e.dx, e.dy); auto xy = ep * float2(screensize); // FIXME: converting back to int coords even though touch theoretically may have higher res f.mousepos = int2(xy * float2(inputscale)); f.mousedelta += int2(ed * float2(screensize)); return j; } } //assert(0); return 0; } string SDLError(const char *msg) { string s = string_view(msg) + ": " + SDL_GetError(); LOG_WARN(s); SDLShutdown(); return s; } int SDLHandleAppEvents(void * /*userdata*/, SDL_Event *event) { switch (event->type) { case SDL_APP_TERMINATING: /* Terminate the app. Shut everything down before returning from this function. */ return 0; case SDL_APP_LOWMEMORY: /* You will get this when your app is paused and iOS wants more memory. Release as much memory as possible. */ return 0; case SDL_APP_WILLENTERBACKGROUND: minimized = true; /* Prepare your app to go into the background. Stop loops, etc. This gets called when the user hits the home button, or gets a call. */ return 0; case SDL_APP_DIDENTERBACKGROUND: /* This will get called if the user accepted whatever sent your app to the background. If the user got a phone call and canceled it, you'll instead get an SDL_APP_DIDENTERFOREGROUND event and restart your loops. When you get this, you have 5 seconds to save all your state or the app will be terminated. Your app is NOT active at this point. */ return 0; case SDL_APP_WILLENTERFOREGROUND: /* This call happens when your app is coming back to the foreground. Restore all your state here. */ return 0; case SDL_APP_DIDENTERFOREGROUND: /* Restart your loops here. Your app is interactive and getting CPU again. */ minimized = false; return 0; default: /* No special processing, add it to the event queue */ return 1; } } const int2 &GetScreenSize() { return screensize; } void ScreenSizeChanged() { int2 inputsize; SDL_GetWindowSize(_sdl_window, &inputsize.x, &inputsize.y); SDL_GL_GetDrawableSize(_sdl_window, &screensize.x, &screensize.y); inputscale = screensize / inputsize; } #ifdef PLATFORM_ES3 int gl_major = 3, gl_minor = 0; #else int gl_major = 3, gl_minor = 2; string glslversion = "150"; #endif void SDLRequireGLVersion(int major, int minor) { #ifdef PLATFORM_WINNIX gl_major = major; gl_minor = minor; glslversion = cat(major, minor, "0"); #endif }; string SDLInit(string_view title, const int2 &desired_screensize, bool isfullscreen, int vsync, int samples) { MakeDPIAware(); //SDL_SetMainReady(); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER /* | SDL_INIT_AUDIO*/) < 0) { return SDLError("Unable to initialize SDL"); } SDL_SetEventFilter(SDLHandleAppEvents, nullptr); LOG_INFO("SDL initialized..."); SDL_LogSetAllPriority(SDL_LOG_PRIORITY_WARN); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, gl_major); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl_minor); #ifdef PLATFORM_ES3 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); #else SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); #if defined(__APPLE__) || defined(_WIN32) SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, samples > 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, samples); #endif #endif //SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); // set this if we're in 2D mode for speed on mobile? SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 1); // because we redraw the screen each frame SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); LOG_INFO("SDL about to figure out display mode..."); #ifdef PLATFORM_ES3 landscape = desired_screensize.x >= desired_screensize.y; int modes = SDL_GetNumDisplayModes(0); screensize = int2(1920, 1080); for (int i = 0; i < modes; i++) { SDL_DisplayMode mode; SDL_GetDisplayMode(0, i, &mode); LOG_INFO("mode: ", mode.w, " ", mode.h); if (landscape ? mode.w > screensize.x : mode.h > screensize.y) { screensize = int2(mode.w, mode.h); } } LOG_INFO("chosen resolution: ", screensize.x, " ", screensize.y); LOG_INFO("SDL about to create window..."); _sdl_window = SDL_CreateWindow(null_terminated(title), 0, 0, screensize.x, screensize.y, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS); LOG_INFO(_sdl_window ? "SDL window passed..." : "SDL window FAILED..."); if (landscape) SDL_SetHint("SDL_HINT_ORIENTATIONS", "LandscapeLeft LandscapeRight"); #else int display = 0; // FIXME: we're not dealing with multiple displays. float dpi = 0; const float default_dpi = #ifdef __APPLE__ 72.0f; #else 96.0f; #endif if (SDL_GetDisplayDPI(display, NULL, &dpi, NULL)) dpi = default_dpi; LOG_INFO(cat("dpi: ", dpi)); screensize = desired_screensize * int(dpi) / int(default_dpi); _sdl_window = SDL_CreateWindow(null_terminated(title), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, screensize.x, screensize.y, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | (isfullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0)); #endif ScreenSizeChanged(); LOG_INFO("obtained resolution: ", screensize.x, " ", screensize.y); if (!_sdl_window) return SDLError("Unable to create window"); LOG_INFO("SDL window opened..."); _sdl_context = SDL_GL_CreateContext(_sdl_window); LOG_INFO(_sdl_context ? "SDL context passed..." : "SDL context FAILED..."); if (!_sdl_context) return SDLError("Unable to create OpenGL context"); LOG_INFO("SDL OpenGL context created..."); #ifndef __IOS__ SDL_GL_SetSwapInterval(vsync); #endif SDL_JoystickEventState(SDL_ENABLE); SDL_JoystickUpdate(); for(int i = 0; i < SDL_NumJoysticks(); i++) { SDL_Joystick *joy = SDL_JoystickOpen(i); if (joy) { LOG_INFO("Detected joystick: ", SDL_JoystickName(joy), " (", SDL_JoystickNumAxes(joy), " axes, ", SDL_JoystickNumButtons(joy), " buttons, ", SDL_JoystickNumBalls(joy), " balls, ", SDL_JoystickNumHats(joy), " hats)"); }; }; timestart = SDL_GetPerformanceCounter(); timefreq = SDL_GetPerformanceFrequency(); lasttime = -0.02f; // ensure first frame doesn't get a crazy delta OpenGLInit(samples); return ""; } double GetSeconds() { return (double)(SDL_GetPerformanceCounter() - timestart) / (double)timefreq; } void SDLShutdown() { // FIXME: SDL gives ERROR: wglMakeCurrent(): The handle is invalid. upon SDL_GL_DeleteContext if (_sdl_context) { /*SDL_GL_DeleteContext(_sdl_context);*/ _sdl_context = nullptr; } if (_sdl_window) { SDL_DestroyWindow(_sdl_window); _sdl_window = nullptr; } SDL_Quit(); } // Used to update the time when SDL isn't running. void SDLUpdateTime(double delta) { frametime = delta; lasttime += delta; frames++; frametimelog.push_back((float)delta); if (frametimelog.size() > 64) frametimelog.erase(frametimelog.begin()); } vector &SDLGetFrameTimeLog() { return frametimelog; } bool SDLFrame() { auto millis = GetSeconds(); SDLUpdateTime(millis - lasttime); for (auto &it : keymap) it.second.FrameReset(); mousewheeldelta = 0; clearfingers(true); if (minimized) { SDL_Delay(10); // save CPU/battery } else { #ifndef __EMSCRIPTEN__ SDL_GL_SwapWindow(_sdl_window); #endif } //SDL_Delay(1000); if (!cursor) clearfingers(false); bool closebutton = false; SDL_Event event; while(SDL_PollEvent(&event)) { extern pair IMGUIEvent(SDL_Event *event); auto nomousekeyb = IMGUIEvent(&event); switch(event.type) { case SDL_QUIT: closebutton = true; break; case SDL_KEYDOWN: case SDL_KEYUP: { if (nomousekeyb.second) break; const char *kn = SDL_GetKeyName(event.key.keysym.sym); if (!*kn) break; string name = kn; std::transform(name.begin(), name.end(), name.begin(), [](char c) { return (char)::tolower(c); }); updatebutton(name, event.key.state==SDL_PRESSED, 0); if (event.type == SDL_KEYDOWN) { // Built-in key-press functionality. switch (event.key.keysym.sym) { case SDLK_PRINTSCREEN: ScreenShot("screenshot-" + GetDateTime() + ".jpg"); break; } } break; } // This #ifdef is needed, because on e.g. OS X we'd otherwise get SDL_FINGERDOWN in addition to SDL_MOUSEBUTTONDOWN on laptop touch pads. #ifdef PLATFORM_TOUCH // FIXME: if we're in cursor==0 mode, only update delta, not position case SDL_FINGERDOWN: { if (nomousekeyb.first) break; int i = updatedragpos(event.tfinger, event.type); updatemousebutton(1, i, true); break; } case SDL_FINGERUP: { if (nomousekeyb.first) break; int i = findfinger(event.tfinger.fingerId, true); updatemousebutton(1, i, false); break; } case SDL_FINGERMOTION: { if (nomousekeyb.first) break; updatedragpos(event.tfinger, event.type); break; } #else case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { if (nomousekeyb.first) break; updatemousebutton(event.button.button, 0, event.button.state != 0); if (cursor) { fingers[0].mousepos = int2(event.button.x, event.button.y) * inputscale; } break; } case SDL_MOUSEMOTION: if (nomousekeyb.first) break; fingers[0].mousedelta += int2(event.motion.xrel, event.motion.yrel); if (cursor) { fingers[0].mousepos = int2(event.motion.x, event.motion.y) * inputscale; } else { //if (skipmousemotion) { skipmousemotion--; break; } //if (event.motion.x == screensize.x / 2 && event.motion.y == screensize.y / 2) break; //auto delta = int3(event.motion.xrel, event.motion.yrel); //fingers[0].mousedelta += delta; //auto delta = int3(event.motion.x, event.motion.y) - screensize / 2; //fingers[0].mousepos -= delta; //SDL_WarpMouseInWindow(_sdl_window, screensize.x / 2, screensize.y / 2); } break; case SDL_MOUSEWHEEL: { if (nomousekeyb.first) break; if (event.wheel.which == SDL_TOUCH_MOUSEID) break; // Emulated scrollwheel on touch devices? auto y = event.wheel.y; #ifdef __EMSCRIPTEN__ y = y > 0 ? 1 : -1; // For some reason, it defaults to 10 / -10 ?? #endif mousewheeldelta += event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED ? -y : y; break; } #endif case SDL_JOYAXISMOTION: { const int deadzone = 800; // FIXME if (event.jaxis.axis < MAXAXES) { joyaxes[event.jaxis.axis] = abs(event.jaxis.value) > deadzone ? event.jaxis.value / (float)0x8000 : 0; }; break; } case SDL_JOYHATMOTION: break; case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: { string name = "joy"; name += '0' + (char)event.jbutton.button; updatebutton(name, event.jbutton.state == SDL_PRESSED, 0); break; } case SDL_WINDOWEVENT: switch (event.window.event) { case SDL_WINDOWEVENT_RESIZED: { ScreenSizeChanged(); // reload and bind shaders/textures here break; } case SDL_WINDOWEVENT_LEAVE: // never gets hit? /* for (int i = 1; i <= 5; i++) updatemousebutton(i, false); */ break; } break; case SDL_WINDOWEVENT_MINIMIZED: //minimized = true; break; case SDL_WINDOWEVENT_MAXIMIZED: case SDL_WINDOWEVENT_RESTORED: /* #ifdef __IOS__ SDL_Delay(10); // IOS crashes in SDL_GL_SwapWindow if we start rendering straight away #endif minimized = false; */ break; } } // simulate mouse up events, since SDL won't send any if the mouse leaves the window while down // doesn't work /* for (int i = 1; i <= 5; i++) if (!(SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(i))) updatemousebutton(i, false); */ /* if (SDL_GetMouseFocus() != _sdl_window) { int A = 1; } */ return closebutton || (noninteractivetestmode && frames == 2 /* has rendered one full frame */); } void SDLWindowMinMax(int dir) { if (!_sdl_window) return; if (dir < 0) SDL_MinimizeWindow(_sdl_window); else if (dir > 0) SDL_MaximizeWindow(_sdl_window); else SDL_RestoreWindow(_sdl_window); } double SDLTime() { return lasttime; } double SDLDeltaTime() { return frametime; } TimeBool8 GetKS(string_view name) { auto ks = keymap.find(name); if (ks == keymap.end()) return {}; #ifdef PLATFORM_TOUCH // delayed results by one frame, that way they get 1 frame over finger hovering over target, // which makes gl_hit work correctly // FIXME: this causes more lag on mobile, instead, set a flag that this is the first frame we're touching, // and make that into a special case inside gl_hit return ks->second.button.Back(); #else return ks->second.button; #endif } double GetKeyTime(string_view name, int on) { auto ks = keymap.find(name); return ks == keymap.end() ? -3600 : ks->second.lasttime[on]; } int2 GetKeyPos(string_view name, int on) { auto ks = keymap.find(name); return ks == keymap.end() ? int2(-1, -1) : ks->second.lastpos[on]; } void SDLTitle(string_view title) { SDL_SetWindowTitle(_sdl_window, null_terminated(title)); } int SDLWheelDelta() { return mousewheeldelta; } bool SDLIsMinimized() { return minimized; } bool SDLCursor(bool on) { if (on != cursor) { cursor = !cursor; if (cursor) { if (fullscreen) SDL_SetWindowGrab(_sdl_window, SDL_FALSE); SDL_ShowCursor(1); SDL_SetRelativeMouseMode(SDL_FALSE); } else { if (fullscreen) SDL_SetWindowGrab(_sdl_window, SDL_TRUE); SDL_ShowCursor(0); SDL_SetRelativeMouseMode(SDL_TRUE); clearfingers(false); } } return cursor; } bool SDLGrab(bool on) { SDL_SetWindowGrab(_sdl_window, on ? SDL_TRUE : SDL_FALSE); return SDL_GetWindowGrab(_sdl_window) == SDL_TRUE; } void SDLMessageBox(string_view title, string_view msg) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, null_terminated<0>(title), null_terminated<1>(msg), _sdl_window); } int64_t SDLLoadFile(string_view absfilename, string *dest, int64_t start, int64_t len) { LOG_INFO("SDLLoadFile: ", absfilename); auto f = SDL_RWFromFile(null_terminated(absfilename), "rb"); if (!f) return -1; auto filelen = SDL_RWseek(f, 0, RW_SEEK_END); if (filelen < 0 || filelen == LLONG_MAX) { // If SDL_RWseek fails it is supposed to return -1, but on Linux it returns LLONG_MAX instead. SDL_RWclose(f); return -1; } if (!len) { // Just the file length requested. SDL_RWclose(f); return filelen; } if (len < 0) len = filelen; SDL_RWseek(f, start, RW_SEEK_SET); dest->resize((size_t)len); auto rlen = SDL_RWread(f, &(*dest)[0], 1, (size_t)len); SDL_RWclose(f); return len != (int64_t)rlen ? -1 : len; } bool ScreenShot(string_view filename) { auto pixels = ReadPixels(int2(0), screensize); auto ok = stbi_write_png(null_terminated(filename), screensize.x, screensize.y, 3, pixels, screensize.x * 3); delete[] pixels; return ok != 0; } void SDLTestMode() { noninteractivetestmode = true; } int SDLScreenDPI(int screen) { int screens = max(1, SDL_GetNumVideoDisplays()); float ddpi = 200; // Reasonable default just in case screen 0 gives an error. #ifndef __EMSCRIPTEN__ SDL_GetDisplayDPI(screen, &ddpi, nullptr, nullptr); #endif return screen >= screens ? 0 // Screen not present. : (int)(ddpi + 0.5f); } treesheets-1.0.2/lobster/src/simplex.cpp000066400000000000000000000443331352107072600203250ustar00rootroot00000000000000#include "lobster/stdafx.h" #include "lobster/natreg.h" using namespace lobster; /* This code was placed in the public domain by its original author, Stefan Gustavson. You may use it as you see fit, but attribution is appreciated. http://webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf */ int fastfloor( const float x ) { return x > 0 ? (int) x : (int) x - 1; } float dot( const int* g, const float x, const float y ) { return g[0]*x + g[1]*y; } float dot( const int* g, const float x, const float y, const float z ) { return g[0]*x + g[1]*y + g[2]*z; } float dot( const int* g, const float x, const float y, const float z, const float w ) { return g[0]*x + g[1]*y + g[2]*z + g[3]*w; } // The gradients are the midpoints of the vertices of a cube. static const int grad3[12][3] = { {1,1,0}, {-1,1,0}, {1,-1,0}, {-1,-1,0}, {1,0,1}, {-1,0,1}, {1,0,-1}, {-1,0,-1}, {0,1,1}, {0,-1,1}, {0,1,-1}, {0,-1,-1} }; // The gradients are the midpoints of the vertices of a hypercube. static const int grad4[32][4]= { {0,1,1,1}, {0,1,1,-1}, {0,1,-1,1}, {0,1,-1,-1}, {0,-1,1,1}, {0,-1,1,-1}, {0,-1,-1,1}, {0,-1,-1,-1}, {1,0,1,1}, {1,0,1,-1}, {1,0,-1,1}, {1,0,-1,-1}, {-1,0,1,1}, {-1,0,1,-1}, {-1,0,-1,1}, {-1,0,-1,-1}, {1,1,0,1}, {1,1,0,-1}, {1,-1,0,1}, {1,-1,0,-1}, {-1,1,0,1}, {-1,1,0,-1}, {-1,-1,0,1}, {-1,-1,0,-1}, {1,1,1,0}, {1,1,-1,0}, {1,-1,1,0}, {1,-1,-1,0}, {-1,1,1,0}, {-1,1,-1,0}, {-1,-1,1,0}, {-1,-1,-1,0} }; // Permutation table. The same list is repeated twice. static const int perm[512] = { 151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142, 8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117, 35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71, 134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41, 55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208, 89, 18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226, 250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182, 189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43, 172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97, 228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239, 107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254, 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180, 151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142, 8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117, 35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71, 134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41, 55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208, 89, 18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226, 250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182, 189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43, 172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97, 228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239, 107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254, 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 }; // A lookup table to traverse the simplex around a given point in 4D. static const int simplex[64][4] = { {0,1,2,3},{0,1,3,2},{0,0,0,0},{0,2,3,1},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,2,3,0}, {0,2,1,3},{0,0,0,0},{0,3,1,2},{0,3,2,1},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,3,2,0}, {0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}, {1,2,0,3},{0,0,0,0},{1,3,0,2},{0,0,0,0},{0,0,0,0},{0,0,0,0},{2,3,0,1},{2,3,1,0}, {1,0,2,3},{1,0,3,2},{0,0,0,0},{0,0,0,0},{0,0,0,0},{2,0,3,1},{0,0,0,0},{2,1,3,0}, {0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}, {2,0,1,3},{0,0,0,0},{0,0,0,0},{0,0,0,0},{3,0,1,2},{3,0,2,1},{0,0,0,0},{3,1,2,0}, {2,1,0,3},{0,0,0,0},{0,0,0,0},{0,0,0,0},{3,1,0,2},{0,0,0,0},{3,2,0,1},{3,2,1,0} }; // 2D raw Simplex noise float SimplexRawNoise(const float x, const float y) { // Noise contributions from the three corners float n0, n1, n2; // Skew the input space to determine which simplex cell we're in float F2 = 0.5f * (sqrtf(3.0f) - 1.0f); // Hairy factor for 2D float s = (x + y) * F2; int i = fastfloor( x + s ); int j = fastfloor( y + s ); float G2 = (3.0f - sqrtf(3.0f)) / 6.0f; float t = (i + j) * G2; // Unskew the cell origin back to (x,y) space float X0 = i-t; float Y0 = j-t; // The x,y distances from the cell origin float x0 = x-X0; float y0 = y-Y0; // For the 2D case, the simplex shape is an equilateral triangle. // Determine which simplex we are in. int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords if(x0>y0) {i1=1; j1=0;} // lower triangle, XY order: (0,0)->(1,0)->(1,1) else {i1=0; j1=1;} // upper triangle, YX order: (0,0)->(0,1)->(1,1) // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where // c = (3-sqrt(3))/6 float x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords float y1 = y0 - j1 + G2; float x2 = x0 - 1.0f + 2.0f * G2; // Offsets for last corner in (x,y) unskewed coords float y2 = y0 - 1.0f + 2.0f * G2; // Work out the hashed gradient indices of the three simplex corners int ii = i & 255; int jj = j & 255; int gi0 = perm[ii+perm[jj]] % 12; int gi1 = perm[ii+i1+perm[jj+j1]] % 12; int gi2 = perm[ii+1+perm[jj+1]] % 12; // Calculate the contribution from the three corners float t0 = 0.5f - x0*x0-y0*y0; if(t0<0) n0 = 0.0f; else { t0 *= t0; n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for 2D gradient } float t1 = 0.5f - x1*x1-y1*y1; if(t1<0) n1 = 0.0f; else { t1 *= t1; n1 = t1 * t1 * dot(grad3[gi1], x1, y1); } float t2 = 0.5f - x2*x2-y2*y2; if(t2<0) n2 = 0.0f; else { t2 *= t2; n2 = t2 * t2 * dot(grad3[gi2], x2, y2); } // Add contributions from each corner to get the final noise value. // The result is scaled to return values in the interval [-1,1]. return 70.0f * (n0 + n1 + n2); } // 3D raw Simplex noise float SimplexRawNoise(const float x, const float y, const float z) { float n0, n1, n2, n3; // Noise contributions from the four corners // Skew the input space to determine which simplex cell we're in float F3 = 1.0f/3.0f; float s = (x+y+z)*F3; // Very nice and simple skew factor for 3D int i = fastfloor(x+s); int j = fastfloor(y+s); int k = fastfloor(z+s); float G3 = 1.0f/6.0f; // Very nice and simple unskew factor, too float t = (i+j+k)*G3; float X0 = i-t; // Unskew the cell origin back to (x,y,z) space float Y0 = j-t; float Z0 = k-t; float x0 = x-X0; // The x,y,z distances from the cell origin float y0 = y-Y0; float z0 = z-Z0; // For the 3D case, the simplex shape is a slightly irregular tetrahedron. // Determine which simplex we are in. int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords if(x0>=y0) { if(y0>=z0) { i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; } // X Y Z order else if(x0>=z0) { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; } // X Z Y order else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; } // Z X Y order } else { // x0 y0) ? 32 : 0; int c2 = (x0 > z0) ? 16 : 0; int c3 = (y0 > z0) ? 8 : 0; int c4 = (x0 > w0) ? 4 : 0; int c5 = (y0 > w0) ? 2 : 0; int c6 = (z0 > w0) ? 1 : 0; int c = c1 + c2 + c3 + c4 + c5 + c6; int i1, j1, k1, l1; // The integer offsets for the second simplex corner int i2, j2, k2, l2; // The integer offsets for the third simplex corner int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. // Many values of c will never occur, since e.g. x>y>z>w makes x=3 ? 1 : 0; j1 = simplex[c][1]>=3 ? 1 : 0; k1 = simplex[c][2]>=3 ? 1 : 0; l1 = simplex[c][3]>=3 ? 1 : 0; // The number 2 in the "simplex" array is at the second largest coordinate. i2 = simplex[c][0]>=2 ? 1 : 0; j2 = simplex[c][1]>=2 ? 1 : 0; k2 = simplex[c][2]>=2 ? 1 : 0; l2 = simplex[c][3]>=2 ? 1 : 0; // The number 1 in the "simplex" array is at the second smallest coordinate. i3 = simplex[c][0]>=1 ? 1 : 0; j3 = simplex[c][1]>=1 ? 1 : 0; k3 = simplex[c][2]>=1 ? 1 : 0; l3 = simplex[c][3]>=1 ? 1 : 0; // The fifth corner has all coordinate offsets = 1, so no need to look that up. float x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords float y1 = y0 - j1 + G4; float z1 = z0 - k1 + G4; float w1 = w0 - l1 + G4; float x2 = x0 - i2 + 2.0f*G4; // Offsets for third corner in (x,y,z,w) coords float y2 = y0 - j2 + 2.0f*G4; float z2 = z0 - k2 + 2.0f*G4; float w2 = w0 - l2 + 2.0f*G4; float x3 = x0 - i3 + 3.0f*G4; // Offsets for fourth corner in (x,y,z,w) coords float y3 = y0 - j3 + 3.0f*G4; float z3 = z0 - k3 + 3.0f*G4; float w3 = w0 - l3 + 3.0f*G4; float x4 = x0 - 1.0f + 4.0f*G4; // Offsets for last corner in (x,y,z,w) coords float y4 = y0 - 1.0f + 4.0f*G4; float z4 = z0 - 1.0f + 4.0f*G4; float w4 = w0 - 1.0f + 4.0f*G4; // Work out the hashed gradient indices of the five simplex corners int ii = i & 255; int jj = j & 255; int kk = k & 255; int ll = l & 255; int gi0 = perm[ii+perm[jj+perm[kk+perm[ll]]]] % 32; int gi1 = perm[ii+i1+perm[jj+j1+perm[kk+k1+perm[ll+l1]]]] % 32; int gi2 = perm[ii+i2+perm[jj+j2+perm[kk+k2+perm[ll+l2]]]] % 32; int gi3 = perm[ii+i3+perm[jj+j3+perm[kk+k3+perm[ll+l3]]]] % 32; int gi4 = perm[ii+1+perm[jj+1+perm[kk+1+perm[ll+1]]]] % 32; // Calculate the contribution from the five corners float t0 = 0.6f - x0*x0 - y0*y0 - z0*z0 - w0*w0; if(t0<0) n0 = 0.0f; else { t0 *= t0; n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); } float t1 = 0.6f - x1*x1 - y1*y1 - z1*z1 - w1*w1; if(t1<0) n1 = 0.0f; else { t1 *= t1; n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); } float t2 = 0.6f - x2*x2 - y2*y2 - z2*z2 - w2*w2; if(t2<0) n2 = 0.0f; else { t2 *= t2; n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); } float t3 = 0.6f - x3*x3 - y3*y3 - z3*z3 - w3*w3; if(t3<0) n3 = 0.0f; else { t3 *= t3; n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); } float t4 = 0.6f - x4*x4 - y4*y4 - z4*z4 - w4*w4; if(t4<0) n4 = 0.0f; else { t4 *= t4; n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); } // Sum up and scale the result to cover the range [-1,1] return 27.0f * (n0 + n1 + n2 + n3 + n4); } // 2D Multi-octave Simplex noise. // // For each octave, a higher frequency/lower amplitude function will be added to the original. // The higher the persistence [0-1], the more of each succeeding octave will be added. float SimplexNoise(const int octaves, const float persistence, const float scale, const float2 &v) { float total = 0; float frequency = scale; float amplitude = 1; // We have to keep track of the largest possible amplitude, // because each octave adds more, and we need a value in [-1, 1]. float maxAmplitude = 0; for( int i=0; i < octaves; i++ ) { total += SimplexRawNoise(v.x * frequency, v.y * frequency) * amplitude; frequency *= 2; maxAmplitude += amplitude; amplitude *= persistence; } return total / maxAmplitude; } // 3D Multi-octave Simplex noise. // // For each octave, a higher frequency/lower amplitude function will be added to the original. // The higher the persistence [0-1], the more of each succeeding octave will be added. float SimplexNoise(const int octaves, const float persistence, const float scale, const float3 &v) { float total = 0; float frequency = scale; float amplitude = 1; // We have to keep track of the largest possible amplitude, // because each octave adds more, and we need a value in [-1, 1]. float maxAmplitude = 0; for( int i=0; i < octaves; i++ ) { total += SimplexRawNoise(v.x * frequency, v.y * frequency, v.z * frequency) * amplitude; frequency *= 2; maxAmplitude += amplitude; amplitude *= persistence; } return total / maxAmplitude; } // 4D Multi-octave Simplex noise. // // For each octave, a higher frequency/lower amplitude function will be added to the original. // The higher the persistence [0-1], the more of each succeeding octave will be added. float SimplexNoise(const int octaves, const float persistence, const float scale, const float4 &v) { float total = 0; float frequency = scale; float amplitude = 1; // We have to keep track of the largest possible amplitude, // because each octave adds more, and we need a value in [-1, 1]. float maxAmplitude = 0; for( int i=0; i < octaves; i++ ) { total += SimplexRawNoise(v.x * frequency, v.y * frequency, v.z * frequency, v.w * frequency) * amplitude; frequency *= 2; maxAmplitude += amplitude; amplitude *= persistence; } return total / maxAmplitude; } void AddNoise(NativeRegistry &nfr) { nfr("simplex", "pos,octaves,scale,persistence", "F}IFF", "F", "returns a simplex noise value [-1..1] given a 2D/3D or 4D location, the number of octaves" " (try 8), a scale (try 1), and persistence from one octave to the next (try 0.5)", [](VM &vm) { auto persistence = vm.Pop().fltval(); auto scale = vm.Pop().fltval(); auto octaves = vm.Pop().intval(); auto len = vm.Top().ival(); auto v = vm.PopVec(); switch (len) { case 2: vm.Push(SimplexNoise(octaves, persistence, scale, v.xy())); break; case 3: vm.Push(SimplexNoise(octaves, persistence, scale, v.xyz())); break; case 4: vm.Push(SimplexNoise(octaves, persistence, scale, v)); break; default: assert(false); } }); } // AddNoise treesheets-1.0.2/lobster/src/stdafx.cpp000066400000000000000000000012121352107072600201220ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" treesheets-1.0.2/lobster/src/steamworks.cpp000066400000000000000000000150271352107072600210410ustar00rootroot00000000000000// Copyright 2017 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/natreg.h" #include "lobster/glinterface.h" #include "lobster/sdlinterface.h" #ifdef PLATFORM_STEAMWORKS #include "steam/steam_api.h" struct SteamState { bool steamoverlayactive = false; STEAM_CALLBACK(SteamState, OnGameOverlayActivated, GameOverlayActivated_t); STEAM_CALLBACK(SteamState, OnScreenshotRequested, ScreenshotRequested_t); }; void SteamState::OnGameOverlayActivated(GameOverlayActivated_t *callback) { steamoverlayactive = callback->m_bActive; LOG_INFO("steam overlay toggle: ", steamoverlayactive); } void SteamState::OnScreenshotRequested(ScreenshotRequested_t *) { LOG_INFO("steam screenshot requested"); auto size = GetScreenSize(); auto pixels = ReadPixels(int2(0), size); SteamScreenshots()->WriteScreenshot(pixels, size.x * size.y * 3, size.x, size.y); delete[] pixels; } extern "C" void __cdecl SteamAPIDebugTextHook(int severity, const char *debugtext) { if (severity) LOG_WARN(debugtext) else LOG_INFO(debugtext); } SteamState *steam = nullptr; #endif // PLATFORM_STEAMWORKS void SteamShutDown() { #ifdef PLATFORM_STEAMWORKS if (steam) { delete steam; SteamAPI_Shutdown(); } steam = nullptr; #endif // PLATFORM_STEAMWORKS } int SteamInit(uint appid, bool screenshots) { SteamShutDown(); #ifdef PLATFORM_STEAMWORKS #ifndef NDEBUG (void)appid; #else if (appid && SteamAPI_RestartAppIfNecessary(appid)) { LOG_INFO("Not started from Steam"); return -1; } #endif bool steaminit = SteamAPI_Init(); LOG_INFO("Steam init: ", steaminit); if (!steaminit) return 0; SteamUtils()->SetWarningMessageHook(&SteamAPIDebugTextHook); steam = new SteamState(); SteamUserStats()->RequestCurrentStats(); if (screenshots) SteamScreenshots()->HookScreenshots(true); return 1; #else return 0; #endif // PLATFORM_STEAMWORKS } void SteamUpdate() { #ifdef PLATFORM_STEAMWORKS if (steam) SteamAPI_RunCallbacks(); #endif // PLATFORM_STEAMWORKS } const char *UserName() { #ifdef PLATFORM_STEAMWORKS if (steam) return SteamFriends()->GetPersonaName(); #endif // PLATFORM_STEAMWORKS return ""; } bool UnlockAchievement(string_view name) { #ifdef PLATFORM_STEAMWORKS if (steam) { auto ok = SteamUserStats()->SetAchievement(null_terminated(name)); return SteamUserStats()->StoreStats() && ok; // Force this to run. } #else (void)name; #endif // PLATFORM_STEAMWORKS return false; } int SteamReadFile(string_view fn, string &buf) { #ifdef PLATFORM_STEAMWORKS if (steam) { auto len = SteamRemoteStorage()->GetFileSize(null_terminated(fn)); if (len) { buf.resize(len); return SteamRemoteStorage()->FileRead(null_terminated(fn), (void *)buf.data(), len); } } #endif // PLATFORM_STEAMWORKS return 0; } bool SteamWriteFile(string_view fn, string_view buf) { #ifdef PLATFORM_STEAMWORKS if (steam) { return SteamRemoteStorage()->FileWrite(null_terminated(fn), buf.data(), (int)buf.size()); } #endif // PLATFORM_STEAMWORKS return false; } bool OverlayActive() { #ifdef PLATFORM_STEAMWORKS return steam && steam->steamoverlayactive; #endif // PLATFORM_STEAMWORKS return false; } using namespace lobster; void AddSteam(NativeRegistry &nfr) { nfr("steam_init", "appid,allowscreenshots", "IB", "I", "initializes SteamWorks. returns 1 if succesful, 0 on failure. Specify a non-0 appid if you" " want to restart from steam if this wasn't started from steam (the return value in this" " case will be -1 to indicate you should terminate this instance). If you don't specify an" " appid here or in steam_appid.txt, init will likely fail. The other functions can still be" " called even if steam isn't active." " allowscreenshots automatically uploads screenshots to steam (triggered by steam).", [](VM &, Value &appid, Value &ss) { return Value(SteamInit((uint)appid.ival(), ss.True())); }); nfr("steam_overlay", "", "", "B", "returns true if the steam overlay is currently on (you may want to auto-pause if so)", [](VM &) { return Value(OverlayActive()); }); nfr("steam_username", "", "", "S", "returns the name of the steam user, or empty string if not available.", [](VM &vm) { return Value(vm.NewString(UserName())); }); nfr("steam_unlock_achievement", "achievementname", "S", "B", "Unlocks an achievement and shows the achievement overlay if not already achieved before." " Will also Q-up saving achievement to Steam." " Returns true if succesful.", [](VM &, Value &name) { auto ok = UnlockAchievement(name.sval()->strv()); return Value(ok); }); nfr("steam_write_file", "file,contents", "SS", "B", "writes a file with the contents of a string to the steam cloud, or local storage if that" " fails, returns false if writing wasn't possible at all", [](VM &, Value &file, Value &contents) { auto fn = file.sval()->strv(); auto s = contents.sval(); auto ok = SteamWriteFile(fn, s->strv()); if (!ok) { ok = WriteFile(fn, true, s->strv()); } return Value(ok); }); nfr("steam_read_file", "file", "S", "S?", "returns the contents of a file as a string from the steam cloud if available, or otherwise" " from local storage, or nil if the file can't be found at all.", [](VM &vm, Value &file) { auto fn = file.sval()->strv(); string buf; auto len = SteamReadFile(fn, buf); if (!len) len = (int)LoadFile(fn, &buf); if (len < 0) return Value(); auto s = vm.NewString(buf); return Value(s); }); } // AddSteam treesheets-1.0.2/lobster/src/tocpp.cpp000066400000000000000000000200131352107072600177560ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/disasm.h" // Some shared bytecode utilities. #include "lobster/compiler.h" #include "lobster/tonative.h" namespace lobster { class CPPGenerator : public NativeGenerator { ostringstream &ss; const int dispatch = VM_DISPATCH_METHOD; int current_block_id = -1; int tail_calls_in_a_row = 0; string_view Block() { return dispatch == VM_DISPATCH_TRAMPOLINE ? "block" : ""; } void JumpInsVar() { if (dispatch == VM_DISPATCH_TRAMPOLINE) { ss << "return (void *)vm.next_call_target;"; } else if (dispatch == VM_DISPATCH_SWITCH_GOTO) { ss << "{ ip = vm.next_call_target; continue; }"; } } public: explicit CPPGenerator(ostringstream &ss) : ss(ss) {} void FileStart() override { ss << "#include \"lobster/stdafx.h\"\n" "#include \"lobster/vmdata.h\"\n" #if LOBSTER_ENGINE "#include \"lobster/engine.h\"\n" #else "#include \"lobster/compiler.h\"\n" #endif "\n" "#ifndef VM_COMPILED_CODE_MODE\n" " #error VM_COMPILED_CODE_MODE must be set for the entire code base.\n" "#endif\n" "\n" "#ifdef _WIN32\n" " #pragma warning (disable: 4102) // Unused label.\n" "#endif\n" "\n"; } void DeclareBlock(int id) override { if (dispatch == VM_DISPATCH_TRAMPOLINE) { ss << "static void *block" << id << "(lobster::VM &);\n"; } } void BeforeBlocks(int start_id, string_view /*bytecode_buffer*/) override { ss << "\n"; if (dispatch == VM_DISPATCH_SWITCH_GOTO) { ss << "static void *one_gigantic_function(lobster::VM &vm) {\n"; ss << " lobster::block_t ip = " << start_id; ss << ";\n for(;;) switch(ip) {\n default: assert(false); continue;\n"; } } void FunStart(const bytecode::Function *f) override { ss << "\n"; if (f) ss << "// " << f->name()->string_view() << "\n"; } void BlockStart(int id) override { if (dispatch == VM_DISPATCH_TRAMPOLINE) { ss << "static void *block" << id << "(lobster::VM &vm) {\n"; } else if (dispatch == VM_DISPATCH_SWITCH_GOTO) { ss << " case " << id << ": block_label" << id << ":\n"; } } void InstStart() override { ss << " { "; } void EmitJump(int id) override { if (dispatch == VM_DISPATCH_TRAMPOLINE) { // FIXME: if we make all forward calls tail calls, then under // WASM/Emscripten/V8, we occasionally run out of stack. // This bounds the number of tail calls in a simple way, // but this is not correct, in that call targets are not necessarily // in linear order, though it should catch most long runs of calls. // We really need to do this with an algorithm that better understands // the call structure instead. Hopefully this bounding will allow // us to keep some of the performance advantage of tail calls vs // not doing them at all. if (tail_calls_in_a_row > 10 || id <= current_block_id) { // A backwards jump, go via the trampoline to be safe // (just in-case the compiler doesn't optimize tail calls). ss << "return (void *)block" << id << ";"; tail_calls_in_a_row = 0; } else { // A forwards call, should be safe to tail-call. ss << "return block" << id << "(vm);"; tail_calls_in_a_row++; } } else if (dispatch == VM_DISPATCH_SWITCH_GOTO) { ss << "goto block_label" << id << ";"; } } void EmitConditionalJump(int opc, int id) override { ss << "if (vm.F_" << ILNames()[opc] << "()) "; EmitJump(id); } void EmitOperands(const char * /*base*/, const int *args, int arity, bool is_vararg) override { if (is_vararg && arity) { ss << "static int args[] = {"; for (int i = 0; i < arity; i++) { if (i) ss << ", "; ss << args[i]; } ss << "}; "; } } void SetNextCallTarget(int id) override { ss << "vm.next_call_target = " << Block() << id << "; "; } void EmitGenericInst(int opc, const int *args, int arity, bool is_vararg, int target) override { ss << "vm.U_" << ILNames()[opc] << "("; if (is_vararg) { ss << "args"; } else { for (int i = 0; i < arity; i++) { if (i) ss << ", "; ss << args[i]; } } if (target >= 0) { if (arity) ss << ", "; ss << Block() << target; } ss << ");"; } void EmitCall(int id) override { ss << " "; EmitJump(id); } void EmitCallIndirect() override { ss << " "; JumpInsVar(); } void EmitCallIndirectNull() override { ss << " if (vm.next_call_target) "; JumpInsVar(); } void InstEnd() override { ss << " }\n"; } void BlockEnd(int id, bool already_returned, bool is_exit) override { if (dispatch == VM_DISPATCH_TRAMPOLINE) { if (!already_returned) { ss << " { "; if (is_exit) JumpInsVar(); else EmitJump(id); ss << " }\n"; } ss << "}\n"; } } void CodeEnd() override { if (dispatch == VM_DISPATCH_SWITCH_GOTO) { ss << "}\n}\n"; // End of gigantic function. } } void VTables(vector &vtables) override { ss << "\nstatic const lobster::block_t vtables[] = {\n"; for (auto id : vtables) { ss << " "; if (id >= 0) ss << Block() << id; else ss << "0"; ss << ",\n"; } ss << "};\n"; } void FileEnd(int start_id, string_view bytecode_buffer) override { // FIXME: this obviously does NOT need to include the actual bytecode, just the metadata. // in fact, it be nice if those were in readable format in the generated code. ss << "\nstatic const int bytecodefb[] = {"; auto bytecode_ints = (const int *)bytecode_buffer.data(); for (size_t i = 0; i < bytecode_buffer.length() / sizeof(int); i++) { if ((i & 0xF) == 0) ss << "\n "; ss << bytecode_ints[i] << ", "; } ss << "\n};\n\n"; ss << "int main(int argc, char *argv[]){\n"; ss << " return "; #if LOBSTER_ENGINE ss << "EngineRunCompiledCodeMain"; #else ss << "ConsoleRunCompiledCodeMain"; #endif ss << "(argc, argv, (void *)"; if (dispatch == VM_DISPATCH_SWITCH_GOTO) { ss << "one_gigantic_function"; } else if (dispatch == VM_DISPATCH_TRAMPOLINE) { ss << Block() << start_id; } ss << ", bytecodefb, " << bytecode_buffer.size() << ", vtables);\n}\n"; } void Annotate(string_view comment) override { ss << " /* " << comment << " */"; } }; string ToCPP(NativeRegistry &natreg, ostringstream &ss, string_view bytecode_buffer) { CPPGenerator cppgen(ss); return ToNative(natreg, cppgen, bytecode_buffer); } } // namespace lobster treesheets-1.0.2/lobster/src/tonative.cpp000066400000000000000000000152301352107072600204670ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/disasm.h" // Some shared bytecode utilities. #include "lobster/compiler.h" #include "lobster/tonative.h" namespace lobster { int ParseOpAndGetArity(int opc, const int *&ip) { auto arity = ILArity()[opc]; auto ips = ip; switch(opc) { default: { assert(arity != ILUNKNOWNARITY); ip += arity; break; } case IL_CORO: { ip += 2; int n = *ip++; ip += n; arity = int(ip - ips); break; } case IL_FUNSTART: { ip++; // function idx. int n = *ip++; ip += n; int m = *ip++; ip += m; ip++; // keepvar int o = *ip++; // ownedvar ip += o; arity = int(ip - ips); break; } } return arity; } string ToNative(NativeRegistry &natreg, NativeGenerator &ng, string_view bytecode_buffer) { auto bcf = bytecode::GetBytecodeFile(bytecode_buffer.data()); assert(FLATBUFFERS_LITTLEENDIAN); auto code = (const int *)bcf->bytecode()->Data(); // Assumes we're on a little-endian machine. //auto typetable = (const type_elem_t *)bcf->typetable()->Data(); // Same. map function_lookup; for (flatbuffers::uoffset_t i = 0; i < bcf->functions()->size(); i++) { auto f = bcf->functions()->Get(i); function_lookup[f->bytecodestart()] = f; } ng.FileStart(); auto len = bcf->bytecode()->Length(); vector block_ids(bcf->bytecode_attr()->size(), -1); const int *ip = code; // Skip past 1st jump. assert(*ip == IL_JUMP); ip++; auto starting_point = *ip++; int block_id = 1; while (ip < code + len) { if (bcf->bytecode_attr()->Get((flatbuffers::uoffset_t)(ip - code)) & bytecode::Attr_SPLIT) { auto id = block_ids[ip - code] = block_id++; ng.DeclareBlock(id); } if ((false)) { // Debug corrupt bytecode. ostringstream dss; DisAsmIns(natreg, dss, ip, code, (const type_elem_t *)bcf->typetable()->Data(), bcf); LOG_DEBUG(dss.str()); } int opc = *ip++; if (opc < 0 || opc >= IL_MAX_OPS) { return cat("Corrupt bytecode: ", opc, " at: ", ip - 1 - code); } ParseOpAndGetArity(opc, ip); } ng.BeforeBlocks(block_ids[starting_point], bytecode_buffer); ip = code + 2; bool already_returned = false; while (ip < code + len) { int opc = *ip++; if (opc == IL_FUNSTART) { auto it = function_lookup.find((int)(ip - 1 - code)); ng.FunStart(it != function_lookup.end() ? it->second : nullptr); } auto args = ip; if (bcf->bytecode_attr()->Get((flatbuffers::uoffset_t)(ip - 1 - code)) & bytecode::Attr_SPLIT) { auto cid = block_ids[args - 1 - code]; ng.current_block_id = cid; ng.BlockStart(cid); already_returned = false; } auto arity = ParseOpAndGetArity(opc, ip); auto is_vararg = ILArity()[opc] == ILUNKNOWNARITY; ng.InstStart(); if (opc == IL_JUMP) { already_returned = true; ng.EmitJump(block_ids[args[0]]); } else if ((opc >= IL_JUMPFAIL && opc <= IL_JUMPNOFAILR) || (opc >= IL_IFOR && opc <= IL_VFOR)) { auto id = block_ids[args[0]]; assert(id >= 0); ng.EmitConditionalJump(opc, id); } else { ng.EmitOperands(bytecode_buffer.data(), args, arity, is_vararg); if (ISBCALL(opc) && natreg.nfuns[args[0]]->CanChangeControlFlow()) { ng.SetNextCallTarget(block_ids[ip - code]); } int target = -1; if (opc == IL_CALL || opc == IL_CALLV || opc == IL_CALLVCOND || opc == IL_YIELD || opc == IL_DDCALL) { target = block_ids[ip - code]; } else if (opc == IL_PUSHFUN || opc == IL_CORO) { target = block_ids[args[0]]; } ng.EmitGenericInst(opc, args, arity, is_vararg, target); if (ISBCALL(opc)) { ng.Annotate(natreg.nfuns[args[0]]->name); } else if (opc == IL_PUSHVAR) { ng.Annotate(IdName(bcf, args[0])); } else if (ISLVALVARINS(opc)) { ng.Annotate(IdName(bcf, args[0])); } else if (opc == IL_PUSHSTR) { ostringstream css; EscapeAndQuote(bcf->stringtable()->Get(args[0])->string_view(), css); ng.Annotate(css.str()); } else if (opc == IL_CALL) { auto fs = code + args[0]; assert(*fs == IL_FUNSTART); fs++; ng.Annotate(bcf->functions()->Get(*fs)->name()->string_view()); } if (opc == IL_CALL) { ng.EmitCall(block_ids[args[0]]); already_returned = true; } else if (opc == IL_CALLV || opc == IL_YIELD || opc == IL_COEND || opc == IL_RETURN || opc == IL_DDCALL || // FIXME: make resume a vm op. (ISBCALL(opc) && natreg.nfuns[args[0]]->CanChangeControlFlow())) { ng.EmitCallIndirect(); already_returned = true; } else if (opc == IL_CALLVCOND) { ng.EmitCallIndirectNull(); } } ng.InstEnd(); if (bcf->bytecode_attr()->Get((flatbuffers::uoffset_t)(ip - code)) & bytecode::Attr_SPLIT) { ng.BlockEnd(block_ids[ip - code], already_returned, opc == IL_EXIT); } } ng.CodeEnd(); vector vtables; for (auto bcs : *bcf->vtables()) { int id = -1; if (bcs >= 0) { id = block_ids[bcs]; assert(id >= 0); } vtables.push_back(id); } ng.VTables(vtables); ng.FileEnd(block_ids[starting_point], bytecode_buffer); return ""; } } treesheets-1.0.2/lobster/src/towasm.cpp000066400000000000000000000226361352107072600201600ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Include this first to ensure it is free of dependencies. #include "lobster/wasm_binary_writer.h" #include "lobster/wasm_binary_writer_test.h" #include "lobster/stdafx.h" #include "lobster/disasm.h" // Some shared bytecode utilities. #include "lobster/compiler.h" #include "lobster/tonative.h" namespace lobster { class WASMGenerator : public NativeGenerator { WASM::BinaryWriter bw; size_t import_erccm = 0, import_snct = 0, import_gnct = 0; const bytecode::Function *next_block = nullptr; public: explicit WASMGenerator(vector &dest) : bw(dest) {} enum { TI_I_, TI_I_I, TI_I_II, TI_I_III, TI_I_IIII, TI_I_IIIII, TI_I_IIIIII, TI_V_, TI_V_I, TI_V_II, TI_V_III, TI_V_IIII, }; void FileStart() override { bw.BeginSection(WASM::Section::Type); // NOTE: this must match the enum above. bw.AddType({}, { WASM::I32 }); bw.AddType({ WASM::I32 }, { WASM::I32 }); bw.AddType({ WASM::I32, WASM::I32 }, { WASM::I32 }); bw.AddType({ WASM::I32, WASM::I32, WASM::I32 }, { WASM::I32 }); bw.AddType({ WASM::I32, WASM::I32, WASM::I32, WASM::I32 }, { WASM::I32 }); bw.AddType({ WASM::I32, WASM::I32, WASM::I32, WASM::I32, WASM::I32 }, { WASM::I32 }); bw.AddType({ WASM::I32, WASM::I32, WASM::I32, WASM::I32, WASM::I32, WASM::I32 }, { WASM::I32 }); bw.AddType({}, {}); bw.AddType({ WASM::I32 }, {}); bw.AddType({ WASM::I32, WASM::I32 }, {}); bw.AddType({ WASM::I32, WASM::I32, WASM::I32 }, {}); bw.AddType({ WASM::I32, WASM::I32, WASM::I32, WASM::I32 }, {}); bw.EndSection(WASM::Section::Type); bw.BeginSection(WASM::Section::Import); #define S_ARGS0 TI_V_I #define S_ARGS1 TI_V_II #define S_ARGS2 TI_V_III #define S_ARGS3 TI_V_IIII #define S_ARGS9 TI_V_II // ILUNKNOWNARITY #define S_ARGSN(N) S_ARGS##N #define C_ARGS0 TI_V_II #define C_ARGS1 TI_V_III #define C_ARGS2 TI_V_IIII #define C_ARGS3 TI_V_IIIII #define C_ARGS9 TI_V_III // ILUNKNOWNARITY #define C_ARGSN(N) C_ARGS##N #define F(N, A) bw.AddImportLinkFunction("CVM_" #N, S_ARGSN(A)); LVALOPNAMES #undef F #define F(N, A) bw.AddImportLinkFunction("CVM_" #N, S_ARGSN(A)); ILBASENAMES #undef F #define F(N, A) bw.AddImportLinkFunction("CVM_" #N, C_ARGSN(A)); ILCALLNAMES #undef F #define F(N, A) bw.AddImportLinkFunction("CVM_" #N, TI_I_I); ILJUMPNAMES #undef F import_erccm = bw.AddImportLinkFunction("EngineRunCompiledCodeMain", TI_I_IIIIII); import_snct = bw.AddImportLinkFunction("CVM_SetNextCallTarget", TI_V_II); import_gnct = bw.AddImportLinkFunction("CVM_GetNextCallTarget", TI_I_I); bw.EndSection(WASM::Section::Import); bw.BeginSection(WASM::Section::Function); bw.AddFunction(TI_I_II); // main(), defined function 0. // All blocks follow here, which have id's 1..N-1. } void DeclareBlock(int /*id*/) override { bw.AddFunction(TI_I_I); } void BeforeBlocks(int start_id, string_view bytecode_buffer) override { bw.EndSection(WASM::Section::Function); // We need this (and Element below) to be able to use call_indirect. bw.BeginSection(WASM::Section::Table); bw.AddTable(); bw.EndSection(WASM::Section::Table); bw.BeginSection(WASM::Section::Memory); bw.AddMemory(1); bw.EndSection(WASM::Section::Memory); // Don't emit a Start section, since this will be determined by the // linker (and where-ever the main() symbol ends up). /* bw.BeginSection(WASM::Section::Start); bw.AddStart(0); bw.EndSection(WASM::Section::Start); */ // This initializes the Table declared above. Needed for call_indirect. // For now we use a utility function that maps all functions ids 1:1 to the table. bw.BeginSection(WASM::Section::Element); bw.AddElementAllFunctions(); bw.EndSection(WASM::Section::Element); bw.BeginSection(WASM::Section::Code); // Emit main(). bw.AddCode({}, "main", false); bw.EmitGetLocal(0 /*argc*/); bw.EmitGetLocal(1 /*argv*/); bw.EmitI32ConstFunctionRef(bw.GetNumFunctionImports() + start_id); bw.EmitI32ConstDataRef(1, 0); // Bytecode, for data refs. bw.EmitI32Const((int)bytecode_buffer.size()); bw.EmitI32ConstDataRef(0, 0); // vtables. bw.EmitCall(import_erccm); bw.EmitEndFunction(); } void FunStart(const bytecode::Function *f) override { next_block = f; } void BlockStart(int id) override { bw.AddCode({}, "block" + std::to_string(id) + (next_block ? "_" + next_block->name()->string_view() : ""), true); next_block = nullptr; } void InstStart() override { } void EmitJump(int id) override { if (id <= current_block_id) { // A backwards jump, go via the trampoline. bw.EmitI32ConstFunctionRef(bw.GetNumFunctionImports() + id); } else { // A forwards call, should be safe to tail-call. bw.EmitGetLocal(0 /*VM*/); bw.EmitCall(bw.GetNumFunctionImports() + id); } bw.EmitReturn(); } void EmitConditionalJump(int opc, int id) override { bw.EmitGetLocal(0 /*VM*/); bw.EmitCall((size_t)opc); bw.EmitIf(WASM::VOID); EmitJump(id); bw.EmitEnd(); } void EmitOperands(const char *base, const int *args, int arity, bool is_vararg) override { bw.EmitGetLocal(0 /*VM*/); if (is_vararg) { if (arity) bw.EmitI32ConstDataRef(1, (const char *)args - base); else bw.EmitI32Const(0); // nullptr } } void SetNextCallTarget(int id) override { bw.EmitGetLocal(0 /*VM*/); bw.EmitI32ConstFunctionRef(bw.GetNumFunctionImports() + id); bw.EmitCall(import_snct); } void EmitGenericInst(int opc, const int *args, int arity, bool is_vararg, int target) override { if (!is_vararg) { for (int i = 0; i < arity; i++) bw.EmitI32Const(args[i]); } if (target >= 0) { bw.EmitI32ConstFunctionRef(bw.GetNumFunctionImports() + target); } bw.EmitCall((size_t)opc); // Opcodes are the 0..N of imports. } void EmitCall(int id) override { EmitJump(id); } void EmitCallIndirect() override { bw.EmitGetLocal(0 /*VM*/); bw.EmitCall(import_gnct); bw.EmitReturn(); } void EmitCallIndirectNull() override { bw.EmitGetLocal(0 /*VM*/); bw.EmitCall(import_gnct); bw.EmitIf(WASM::VOID); bw.EmitGetLocal(0 /*VM*/); bw.EmitCall(import_gnct); bw.EmitReturn(); bw.EmitEnd(); } void InstEnd() override { } void BlockEnd(int id, bool already_returned, bool is_exit) override { if (!already_returned) { if (is_exit) { bw.EmitGetLocal(0 /*VM*/); bw.EmitCall(import_gnct); bw.EmitReturn(); } else { EmitJump(id); } } bw.EmitEndFunction(); } void CodeEnd() override { bw.EndSection(WASM::Section::Code); } void VTables(vector &vtables) override { bw.BeginSection(WASM::Section::Data); vector wid; for (auto id : vtables) { wid.push_back(id >= 0 ? (int)bw.GetNumFunctionImports() + id : -1); } bw.AddData(string_view((char *)wid.data(), wid.size() * sizeof(int)), "vtables", sizeof(int)); for (auto [i, id] : enumerate(vtables)) { if (id >= 0) bw.DataFunctionRef(bw.GetNumFunctionImports() + id, i * sizeof(int)); } } void FileEnd(int /*start_id*/, string_view bytecode_buffer) override { // TODO: don't really want to store all of this. bw.AddData(bytecode_buffer, "static_data", 16); bw.EndSection(WASM::Section::Data); bw.Finish(); } void Annotate(string_view /*comment*/) override { } }; string ToWASM(NativeRegistry &natreg, vector &dest, string_view bytecode_buffer) { if (VM_DISPATCH_METHOD != VM_DISPATCH_TRAMPOLINE) return "WASM codegen: can only use trampoline mode"; WASMGenerator wasmgen(dest); return ToNative(natreg, wasmgen, bytecode_buffer); } } void unit_test_wasm(bool full) { auto vec = WASM::SimpleBinaryWriterTest(); if (full) { auto f = OpenForWriting("simple_binary_writer_test.wasm", true); if (f) { fwrite(vec.data(), vec.size(), 1, f); fclose(f); } } } treesheets-1.0.2/lobster/src/vm.cpp000066400000000000000000001712461352107072600172720ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/disasm.h" namespace lobster { #ifndef NDEBUG #define VM_PROFILER // tiny VM slowdown and memory usage when enabled #endif #ifdef VM_COMPILED_CODE_MODE #ifdef _WIN32 #pragma warning (disable: 4458) // ip hides class member, which we rely on #pragma warning (disable: 4100) // ip may not be touched #endif #endif enum { // *8 bytes each INITSTACKSIZE = 4 * 1024, // *8 bytes each, modest on smallest handheld we support (iPhone 3GS has 256MB). DEFMAXSTACKSIZE = 128 * 1024, // *8 bytes each, max by which the stack could possibly grow in a single call. STACKMARGIN = 1 * 1024 }; #define MEASURE_INSTRUCTION_COMBINATIONS 0 #if MEASURE_INSTRUCTION_COMBINATIONS map, size_t> instruction_combinations; int last_instruction_opc = -1; #endif VM::VM(VMArgs &&vmargs) : VMArgs(std::move(vmargs)), maxstacksize(DEFMAXSTACKSIZE) { auto bcfb = (uchar *)(static_bytecode ? static_bytecode : bytecode_buffer.data()); auto bcs = static_bytecode ? static_size : bytecode_buffer.size(); flatbuffers::Verifier verifier(bcfb, bcs); auto ok = bytecode::VerifyBytecodeFileBuffer(verifier); if (!ok) THROW_OR_ABORT(string("bytecode file failed to verify")); bcf = bytecode::GetBytecodeFile(bcfb); if (bcf->bytecode_version() != LOBSTER_BYTECODE_FORMAT_VERSION) THROW_OR_ABORT(string("bytecode is from a different version of Lobster")); codelen = bcf->bytecode()->Length(); if (FLATBUFFERS_LITTLEENDIAN) { // We can use the buffer directly. codestart = (const int *)bcf->bytecode()->Data(); typetable = (const type_elem_t *)bcf->typetable()->Data(); } else { for (uint i = 0; i < codelen; i++) codebigendian.push_back(bcf->bytecode()->Get(i)); codestart = codebigendian.data(); for (uint i = 0; i < bcf->typetable()->Length(); i++) typetablebigendian.push_back((type_elem_t)bcf->typetable()->Get(i)); typetable = typetablebigendian.data(); } #ifdef VM_COMPILED_CODE_MODE compiled_code_ip = entry_point; #else ip = codestart; #endif vars = new Value[bcf->specidents()->size()]; stack = new Value[stacksize = INITSTACKSIZE]; #ifdef VM_PROFILER byteprofilecounts = new uint64_t[codelen]; memset(byteprofilecounts, 0, sizeof(uint64_t) * codelen); #endif vml.LogInit(bcfb); InstructionPointerInit(); constant_strings.resize(bcf->stringtable()->size()); #ifdef VM_COMPILED_CODE_MODE assert(native_vtables); for (size_t i = 0; i < bcf->vtables()->size(); i++) { vtables.push_back(InsPtr(native_vtables[i])); } #else assert(!native_vtables); for (auto bcs : *bcf->vtables()) { vtables.push_back(InsPtr(bcs)); } #endif } VM::~VM() { TerminateWorkers(); if (stack) delete[] stack; if (vars) delete[] vars; if (byteprofilecounts) delete[] byteprofilecounts; } void VM::OneMoreFrame() { // We just landed back into the VM after being suspended inside a gl_frame() call. // Emulate the return of gl_frame(): VM_PUSH(Value(1)); // We're not terminating yet. #ifdef VM_COMPILED_CODE_MODE // Native code generators ensure that next_call_target is set before // a native function call, and that it is returned to the trampoline // after, so do the same thing here. compiled_code_ip = (const void *)next_call_target; #endif EvalProgram(); // Continue execution as if nothing happened. } const TypeInfo &VM::GetVarTypeInfo(int varidx) { return GetTypeInfo((type_elem_t)bcf->specidents()->Get(varidx)->typeidx()); } type_elem_t VM::GetIntVectorType(int which) { auto i = bcf->default_int_vector_types()->Get(which); return type_elem_t(i < 0 ? -1 : i); } type_elem_t VM::GetFloatVectorType(int which) { auto i = bcf->default_float_vector_types()->Get(which); return type_elem_t(i < 0 ? -1 : i); } static bool _LeakSorter(void *va, void *vb) { auto a = (RefObj *)va; auto b = (RefObj *)vb; return a->refc != b->refc ? a->refc > b->refc : (a->tti != b->tti ? a->tti > b->tti : false); } void VM::DumpVal(RefObj *ro, const char *prefix) { ostringstream ss; ss << prefix << ": "; RefToString(*this, ss, ro, debugpp); ss << " (" << ro->refc << "): " << (size_t)ro; LOG_DEBUG(ss.str()); } void VM::DumpFileLine(const int *fip, ostringstream &ss) { // error is usually in the byte before the current ip. auto li = LookupLine(fip - 1, codestart, bcf); ss << bcf->filenames()->Get(li->fileidx())->string_view() << '(' << li->line() << ')'; } void VM::DumpLeaks() { vector leaks = pool.findleaks(); auto filename = "leaks.txt"; if (leaks.empty()) { if (FileExists(filename)) FileDelete(filename); } else { LOG_ERROR("LEAKS FOUND (this indicates cycles in your object graph, or a bug in" " Lobster)"); ostringstream ss; #ifndef VM_COMPILED_CODE_MODE ss << "in: "; DumpFileLine(ip, ss); ss << "\n"; #endif sort(leaks.begin(), leaks.end(), _LeakSorter); PrintPrefs leakpp = debugpp; leakpp.cycles = 0; for (auto p : leaks) { auto ro = (RefObj *)p; switch(ro->ti(*this).t) { case V_VALUEBUF: case V_STACKFRAMEBUF: break; case V_STRING: case V_COROUTINE: case V_RESOURCE: case V_VECTOR: case V_CLASS: { ro->CycleStr(ss); ss << " = "; RefToString(*this, ss, ro, leakpp); #if DELETE_DELAY ss << " "; DumpFileLine(ro->alloc_ip, ss); ss << " " << (size_t)ro; #endif ss << "\n"; break; } default: assert(false); } } #ifndef NDEBUG LOG_ERROR(ss.str()); #else if (leaks.size() < 50) { LOG_ERROR(ss.str()); } else { LOG_ERROR(leaks.size(), " leaks, details in ", filename); WriteFile(filename, false, ss.str()); } #endif } pool.printstats(false); } void VM::OnAlloc(RefObj *ro) { #if DELETE_DELAY LOG_DEBUG("alloc: ", (size_t)ro); ro->alloc_ip = ip; #else (void)ro; #endif } #undef new LVector *VM::NewVec(intp initial, intp max, type_elem_t tti) { assert(GetTypeInfo(tti).t == V_VECTOR); auto v = new (pool.alloc_small(sizeof(LVector))) LVector(*this, initial, max, tti); OnAlloc(v); return v; } LObject *VM::NewObject(intp max, type_elem_t tti) { assert(IsUDT(GetTypeInfo(tti).t)); auto s = new (pool.alloc(sizeof(LObject) + sizeof(Value) * max)) LObject(tti); OnAlloc(s); return s; } LString *VM::NewString(size_t l) { auto s = new (pool.alloc(sizeof(LString) + l + 1)) LString((int)l); OnAlloc(s); return s;\ } LCoRoutine *VM::NewCoRoutine(InsPtr rip, const int *vip, LCoRoutine *p, type_elem_t cti) { assert(GetTypeInfo(cti).t == V_COROUTINE); auto c = new (pool.alloc(sizeof(LCoRoutine))) LCoRoutine(sp + 2 /* top of sp + pushed coro */, (int)stackframes.size(), rip, vip, p, cti); OnAlloc(c); return c; } LResource *VM::NewResource(void *v, const ResourceType *t) { auto r = new (pool.alloc(sizeof(LResource))) LResource(v, t); OnAlloc(r); return r; } #ifdef _WIN32 #ifndef NDEBUG #define new DEBUG_NEW #endif #endif LString *VM::NewString(string_view s) { auto r = NewString(s.size()); auto dest = (char *)r->data(); memcpy(dest, s.data(), s.size()); #if DELETE_DELAY LOG_DEBUG("string: \"", s, "\" - ", (size_t)r); #endif return r; } LString *VM::NewString(string_view s1, string_view s2) { auto s = NewString(s1.size() + s2.size()); auto dest = (char *)s->data(); memcpy(dest, s1.data(), s1.size()); memcpy(dest + s1.size(), s2.data(), s2.size()); return s; } LString *VM::ResizeString(LString *s, intp size, int c, bool back) { auto ns = NewString(size); auto sdest = (char *)ns->data(); auto cdest = sdest; auto remain = size - s->len; if (back) sdest += remain; else cdest += s->len; memcpy(sdest, s->data(), s->len); memset(cdest, c, remain); s->Dec(*this); return ns; } // This function is now way less important than it was when the language was still dynamically // typed. But ok to leave it as-is for "index out of range" and other errors that are still dynamic. Value VM::Error(string err, const RefObj *a, const RefObj *b) { if (trace == TraceMode::TAIL && trace_output.size()) { string s; for (size_t i = trace_ring_idx; i < trace_output.size(); i++) s += trace_output[i].str(); for (size_t i = 0; i < trace_ring_idx; i++) s += trace_output[i].str(); s += err; THROW_OR_ABORT(s); } ostringstream ss; #ifndef VM_COMPILED_CODE_MODE DumpFileLine(ip, ss); ss << ": "; #endif ss << "VM error: " << err; if (a) { ss << "\n arg: "; RefToString(*this, ss, a, debugpp); } if (b) { ss << "\n arg: "; RefToString(*this, ss, b, debugpp); } while (sp >= 0 && (!stackframes.size() || sp != stackframes.back().spstart)) { // Sadly can't print this properly. ss << "\n stack: "; to_string_hex(ss, (size_t)VM_TOP().any()); if (pool.pointer_is_in_allocator(VM_TOP().any())) { ss << ", maybe: "; RefToString(*this, ss, VM_TOP().ref(), debugpp); } VM_POP(); // We don't DEC here, as we can't know what type it is. // This is ok, as we ignore leaks in case of an error anyway. } for (;;) { if (!stackframes.size()) break; int deffun = *(stackframes.back().funstart); if (deffun >= 0) { ss << "\nin function: " << bcf->functions()->Get(deffun)->name()->string_view(); } else { ss << "\nin block"; } #ifndef VM_COMPILED_CODE_MODE ss << " -> "; DumpFileLine(ip, ss); #endif VarCleanup<1>(ss.tellp() < 10000 ? &ss : nullptr, -2 /* clean up temps always */); } ss << "\nglobals:"; for (size_t i = 0; i < bcf->specidents()->size(); ) { i += DumpVar(ss, vars[i], i, true); } THROW_OR_ABORT(ss.str()); } void VM::VMAssert(const char *what) { Error(string("VM internal assertion failure: ") + what); } void VM::VMAssert(const char *what, const RefObj *a, const RefObj *b) { Error(string("VM internal assertion failure: ") + what, a, b); } #if !defined(NDEBUG) && RTT_ENABLED #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #define VMASSERT(test) { if (!(test)) VMAssert(__FILE__ ": " TOSTRING(__LINE__) ": " #test); } #else #define VMASSERT(test) {} #endif #if RTT_ENABLED #define VMTYPEEQ(val, vt) VMASSERT((val).type == (vt)) #else #define VMTYPEEQ(val, vt) { (void)(val); (void)(vt); } #endif int VM::DumpVar(ostringstream &ss, const Value &x, size_t idx, bool dumpglobals) { auto sid = bcf->specidents()->Get((uint)idx); auto id = bcf->idents()->Get(sid->ididx()); if (id->readonly() || id->global() != dumpglobals) return 1; auto name = id->name()->string_view(); auto &ti = GetVarTypeInfo((int)idx); #if RTT_ENABLED if (ti.t != x.type) return 1; // Likely uninitialized. #endif ss << "\n " << name << " = "; if (IsStruct(ti.t)) { StructToString(ss, debugpp, ti, &x); return ti.len; } else { x.ToString(*this, ss, ti, debugpp); return 1; } } void VM::FinalStackVarsCleanup() { VMASSERT(sp < 0 && !stackframes.size()); #ifndef NDEBUG LOG_INFO("stack at its highest was: ", maxsp); #endif } void VM::JumpTo(InsPtr j) { #ifdef VM_COMPILED_CODE_MODE next_call_target = j.f; #else ip = j.f + codestart; #endif } InsPtr VM::GetIP() { #ifdef VM_COMPILED_CODE_MODE return InsPtr(next_call_target); #else return InsPtr(ip - codestart); #endif } template int VM::VarCleanup(ostringstream *error, int towhere) { (void)error; auto &stf = stackframes.back(); if constexpr (!is_error) VMASSERT(sp == stf.spstart); auto fip = stf.funstart; fip++; // function id. auto nargs = *fip++; auto freevars = fip + nargs; fip += nargs; auto ndef = *fip++; fip += ndef; auto defvars = fip; auto nkeepvars = *fip++; if constexpr (is_error) { // Do this first, since values may get deleted below. for (int j = 0; j < ndef; ) { auto i = *(defvars - j - 1); j += DumpVar(*error, vars[i], i, false); } for (int j = 0; j < nargs; ) { auto i = *(freevars - j - 1); j += DumpVar(*error, vars[i], i, false); } } for (int i = 0; i < nkeepvars; i++) VM_POP().LTDECRTNIL(*this); auto ownedvars = *fip++; for (int i = 0; i < ownedvars; i++) vars[*fip++].LTDECRTNIL(*this); while (ndef--) { auto i = *--defvars; vars[i] = VM_POP(); } while (nargs--) { auto i = *--freevars; vars[i] = VM_POP(); } JumpTo(stf.retip); bool lastunwind = towhere == *stf.funstart; stackframes.pop_back(); if (!lastunwind) { // This kills any temps on the stack. If these are refs these should not be // owners, since a var or keepvar owns them instead. sp = stackframes.size() ? stackframes.back().spstart : -1; } return lastunwind; } // Initializes only 3 fields of the stack frame, FunIntro must be called right after. void VM::StartStackFrame(InsPtr retip) { stackframes.push_back(StackFrame()); auto &stf = stackframes.back(); stf.retip = retip; } void VM::FunIntroPre(InsPtr fun) { JumpTo(fun); #ifdef VM_COMPILED_CODE_MODE // We don't call FunIntro() here, instead the compiled code for FUNSTART/FUNMULTI actually // does that. #else VMASSERT(*ip == IL_FUNSTART); ip++; FunIntro(); #endif } // Only valid to be called right after StartStackFrame, with no bytecode in-between. void VM::FunIntro(VM_OP_ARGS) { #ifdef VM_PROFILER vm_count_fcalls++; #endif auto funstart = ip; ip++; // definedfunction if (sp > stacksize - STACKMARGIN) { // per function call increment should be small // FIXME: not safe for untrusted scripts, could simply add lots of locals // could record max number of locals? not allow more than N locals? if (stacksize >= maxstacksize) Error("stack overflow! (use set_max_stack_size() if needed)"); auto nstack = new Value[stacksize *= 2]; t_memcpy(nstack, stack, sp + 1); delete[] stack; stack = nstack; LOG_DEBUG("stack grew to: ", stacksize); } auto nargs_fun = *ip++; for (int i = 0; i < nargs_fun; i++) swap(vars[ip[i]], stack[sp - nargs_fun + i + 1]); ip += nargs_fun; auto ndef = *ip++; for (int i = 0; i < ndef; i++) { // for most locals, this just saves an nil, only in recursive cases it has an actual value. auto varidx = *ip++; VM_PUSH(vars[varidx]); vars[varidx] = Value(); } auto nkeepvars = *ip++; for (int i = 0; i < nkeepvars; i++) VM_PUSH(Value()); auto nownedvars = *ip++; ip += nownedvars; auto &stf = stackframes.back(); stf.funstart = funstart; stf.spstart = sp; #ifndef NDEBUG if (sp > maxsp) maxsp = sp; #endif } void VM::FunOut(int towhere, int nrv) { sp -= nrv; // Have to store these off the stack, since VarCleanup() may cause stack activity if coroutines // are destructed. ts_memcpy(retvalstemp, VM_TOPPTR(), nrv); for(;;) { if (!stackframes.size()) { Error("\"return from " + bcf->functions()->Get(towhere)->name()->string_view() + "\" outside of function"); } if (VarCleanup<0>(nullptr, towhere)) break; } ts_memcpy(VM_TOPPTR(), retvalstemp, nrv); sp += nrv; } void VM::CoVarCleanup(LCoRoutine *co) { // Convenient way to copy everything back onto the stack. InsPtr tip(0); auto copylen = co->Resume(sp + 1, stack, stackframes, tip, nullptr); auto startsp = sp; sp += copylen; for (int i = co->stackframecopylen - 1; i >= 0 ; i--) { auto &stf = stackframes.back(); sp = stf.spstart; // Kill any temps on top of the stack. // Save the ip, because VarCleanup will jump to it. auto bip = GetIP(); VarCleanup<0>(nullptr, !i ? *stf.funstart : -2); JumpTo(bip); } assert(sp == startsp); (void)startsp; } void VM::CoNonRec(const int *varip) { // probably could be skipped in a "release" mode for (auto co = curcoroutine; co; co = co->parent) if (co->varip == varip) { // if allowed, inner coro would save vars of outer, and then possibly restore them outside // of scope of parent Error("cannot create coroutine recursively"); } // this check guarantees all saved stack vars are undef. } void VM::CoNew(VM_OP_ARGS VM_COMMA VM_OP_ARGS_CALL) { #ifdef VM_COMPILED_CODE_MODE ip++; InsPtr returnip(fcont); #else InsPtr returnip(*ip++); #endif auto ctidx = (type_elem_t)*ip++; CoNonRec(ip); curcoroutine = NewCoRoutine(returnip, ip, curcoroutine, ctidx); curcoroutine->BackupParentVars(*this, vars); int nvars = *ip++; ip += nvars; // Always have the active coroutine at top of the stack, retaining 1 refcount. This is // because it is not guaranteed that there any other references, and we can't have this drop // to 0 while active. VM_PUSH(Value(curcoroutine)); } void VM::CoSuspend(InsPtr retip) { int newtop = curcoroutine->Suspend(*this, sp + 1, stack, stackframes, retip, curcoroutine); JumpTo(retip); sp = newtop - 1; // top of stack is now coro value from create or resume } void VM::CoClean() { // This function is like yield, except happens implicitly when the coroutine returns. // It will jump back to the resume (or create) that invoked it. for (int i = 1; i <= *curcoroutine->varip; i++) { auto &var = vars[curcoroutine->varip[i]]; var = curcoroutine->stackcopy[i - 1]; } auto co = curcoroutine; CoSuspend(InsPtr(0)); VMASSERT(co->stackcopylen == 1); co->active = false; } void VM::CoYield(VM_OP_ARGS_CALL) { assert(curcoroutine); // Should not be possible since yield calls are statically checked. #ifdef VM_COMPILED_CODE_MODE InsPtr retip(fcont); #else InsPtr retip(ip - codestart); #endif auto ret = VM_POP(); for (int i = 1; i <= *curcoroutine->varip; i++) { auto &var = vars[curcoroutine->varip[i]]; VM_PUSH(var); //var.type = V_NIL; var = curcoroutine->stackcopy[i - 1]; } VM_PUSH(ret); // current value always top of the stack, saved as part of suspended coroutine. CoSuspend(retip); // Actual top of stack here is coroutine itself, that we placed here with CoResume. } void VM::CoResume(LCoRoutine *co) { if (co->stackstart >= 0) Error("cannot resume running coroutine"); if (!co->active) Error("cannot resume finished coroutine"); // This will be the return value for the corresponding yield, and holds the ref for gc. VM_PUSH(Value(co)); CoNonRec(co->varip); auto rip = GetIP(); sp += co->Resume(sp + 1, stack, stackframes, rip, curcoroutine); JumpTo(rip); curcoroutine = co; // must be, since those vars got backed up in it before VMASSERT(curcoroutine->stackcopymax >= *curcoroutine->varip); curcoroutine->stackcopylen = *curcoroutine->varip; //curcoroutine->BackupParentVars(vars); VM_POP().LTDECTYPE(*this, GetTypeInfo(curcoroutine->ti(*this).yieldtype).t); // previous current value for (int i = *curcoroutine->varip; i > 0; i--) { auto &var = vars[curcoroutine->varip[i]]; // No INC, since parent is still on the stack and hold ref for us. curcoroutine->stackcopy[i - 1] = var; var = VM_POP(); } // the builtin call takes care of the return value } void VM::EndEval(const Value &ret, const TypeInfo &ti) { TerminateWorkers(); ostringstream ss; ret.ToString(*this, ss, ti, programprintprefs); evalret = ss.str(); ret.LTDECTYPE(*this, ti.t); assert(sp == -1); FinalStackVarsCleanup(); vml.LogCleanup(); for (auto s : constant_strings) { if (s) s->Dec(*this); } while (!delete_delay.empty()) { auto ro = delete_delay.back(); delete_delay.pop_back(); ro->DECDELETENOW(*this); } DumpLeaks(); VMASSERT(!curcoroutine); #ifdef VM_PROFILER LOG_INFO("Profiler statistics:"); uint64_t total = 0; auto fraction = 200; // Line needs at least 0.5% to be counted. vector lineprofilecounts(bcf->lineinfo()->size()); for (size_t i = 0; i < codelen; i++) { auto li = LookupLine(codestart + i, codestart, bcf); // FIXME: can do faster size_t j = li - bcf->lineinfo()->Get(0); lineprofilecounts[j] += byteprofilecounts[i]; total += byteprofilecounts[i]; } struct LineRange { int line, lastline, fileidx; uint64_t count; }; vector uniques; for (uint i = 0; i < bcf->lineinfo()->size(); i++) { uint64_t c = lineprofilecounts[i]; if (c > total / fraction) { auto li = bcf->lineinfo()->Get(i); uniques.push_back(LineRange{ li->line(), li->line(), li->fileidx(), c }); } } std::sort(uniques.begin(), uniques.end(), [&] (const LineRange &a, const LineRange &b) { return a.fileidx != b.fileidx ? a.fileidx < b.fileidx : a.line < b.line; }); for (auto it = uniques.begin(); it != uniques.end();) { if (it != uniques.begin()) { auto pit = it - 1; if (it->fileidx == pit->fileidx && ((it->line == pit->lastline) || (it->line == pit->lastline + 1 && pit->lastline++))) { pit->count += it->count; it = uniques.erase(it); continue; } } ++it; } for (auto &u : uniques) { LOG_INFO(bcf->filenames()->Get(u.fileidx)->string_view(), "(", u.line, u.lastline != u.line ? "-" + to_string(u.lastline) : "", "): ", u.count * 100.0f / total, " %"); } if (vm_count_fcalls) // remove trivial VM executions from output LOG_INFO("ins ", vm_count_ins, ", fcall ", vm_count_fcalls, ", bcall ", vm_count_bcalls, ", decref ", vm_count_decref); #endif #if MEASURE_INSTRUCTION_COMBINATIONS struct trip { size_t freq; int opc1, opc2; }; vector combinations; for (auto &p : instruction_combinations) combinations.push_back({ p.second, p.first.first, p.first.second }); sort(combinations.begin(), combinations.end(), [](const trip &a, const trip &b) { return a.freq > b.freq; }); combinations.resize(50); for (auto &c : combinations) { LOG_PROGRAM("instruction ", ILNames()[c.opc1], " -> ", ILNames()[c.opc2], " (", c.freq, "x)"); } instruction_combinations.clear(); #endif #ifndef VM_ERROR_RET_EXPERIMENT THROW_OR_ABORT(string("end-eval")); #endif } void VM::EvalProgram() { // Keep exception handling code in seperate function from hot loop in EvalProgramInner() // just in case it affects the compiler. #ifdef USE_EXCEPTION_HANDLING try #endif { EvalProgramInner(); } #ifdef USE_EXCEPTION_HANDLING catch (string &s) { if (s != "end-eval") THROW_OR_ABORT(s); } #endif } ostringstream &VM::TraceStream() { size_t trace_size = trace == TraceMode::TAIL ? 50 : 1; if (trace_output.size() < trace_size) trace_output.resize(trace_size); if (trace_ring_idx == trace_size) trace_ring_idx = 0; auto &ss = trace_output[trace_ring_idx++]; ss.str(string()); return ss; } void VM::EvalProgramInner() { for (;;) { #ifdef VM_COMPILED_CODE_MODE #if VM_DISPATCH_METHOD == VM_DISPATCH_TRAMPOLINE compiled_code_ip = ((block_t)compiled_code_ip)(*this); #elif VM_DISPATCH_METHOD == VM_DISPATCH_SWITCH_GOTO ((block_base_t)compiled_code_ip)(*this); assert(false); // Should not return here. #endif #else #ifndef NDEBUG if (trace != TraceMode::OFF) { auto &ss = TraceStream(); DisAsmIns(nfr, ss, ip, codestart, typetable, bcf); ss << " [" << (sp + 1) << "] -"; #if RTT_ENABLED #if DELETE_DELAY ss << ' ' << (size_t)VM_TOP().any(); #endif for (int i = 0; i < 3 && sp - i >= 0; i++) { auto x = VM_TOPM(i); ss << ' '; x.ToStringBase(*this, ss, x.type, debugpp); } #endif if (trace == TraceMode::TAIL) ss << '\n'; else LOG_PROGRAM(ss.str()); } //currentline = LookupLine(ip).line; #endif #ifdef VM_PROFILER auto code_idx = size_t(ip - codestart); assert(code_idx < codelen); byteprofilecounts[code_idx]++; vm_count_ins++; #endif auto op = *ip++; #if MEASURE_INSTRUCTION_COMBINATIONS instruction_combinations[{ last_instruction_opc, op }]++; last_instruction_opc = op; #endif #ifndef NDEBUG if (op < 0 || op >= IL_MAX_OPS) Error(cat("bytecode format problem: ", op)); #endif #ifdef VM_ERROR_RET_EXPERIMENT bool terminate = #endif ((*this).*(f_ins_pointers[op]))(); #ifdef VM_ERROR_RET_EXPERIMENT if (terminate) return; #endif #endif } } VM_INS_RET VM::U_PUSHINT(int x) { VM_PUSH(Value(x)); VM_RET; } VM_INS_RET VM::U_PUSHFLT(int x) { int2float i2f; i2f.i = x; VM_PUSH(Value(i2f.f)); VM_RET; } VM_INS_RET VM::U_PUSHNIL() { VM_PUSH(Value()); VM_RET; } VM_INS_RET VM::U_PUSHINT64(int a, int b) { #if !VALUE_MODEL_64 Error("Code containing 64-bit constants cannot run on a 32-bit build."); #endif auto v = Int64FromInts(a, b); VM_PUSH(Value(v)); VM_RET; } VM_INS_RET VM::U_PUSHFLT64(int a, int b) { int2float64 i2f; i2f.i = Int64FromInts(a, b); VM_PUSH(Value(i2f.f)); VM_RET; } VM_INS_RET VM::U_PUSHFUN(int start VM_COMMA VM_OP_ARGS_CALL) { #ifdef VM_COMPILED_CODE_MODE (void)start; #else auto fcont = start; #endif VM_PUSH(Value(InsPtr(fcont))); VM_RET; } VM_INS_RET VM::U_PUSHSTR(int i) { // FIXME: have a way that constant strings can stay in the bytecode, // or at least preallocate them all auto &s = constant_strings[i]; if (!s) { auto fb_s = bcf->stringtable()->Get(i); s = NewString(fb_s->string_view()); } #if STRING_CONSTANTS_KEEP s->Inc(); #endif VM_PUSH(Value(s)); VM_RET; } VM_INS_RET VM::U_INCREF(int off) { VM_TOPM(off).LTINCRTNIL(); VM_RET; } VM_INS_RET VM::U_KEEPREF(int off, int ki) { VM_TOPM(ki).LTDECRTNIL(*this); // FIXME: this is only here for inlined for bodies! VM_TOPM(ki) = VM_TOPM(off); VM_RET; } VM_INS_RET VM::U_CALL(int f VM_COMMA VM_OP_ARGS_CALL) { #ifdef VM_COMPILED_CODE_MODE (void)f; block_t fun = 0; // Dynamic calls need this set, but for CALL it is ignored. #else auto fun = f; auto fcont = ip - codestart; #endif StartStackFrame(InsPtr(fcont)); FunIntroPre(InsPtr(fun)); VM_RET; } VM_INS_RET VM::U_CALLVCOND(VM_OP_ARGS_CALL) { // FIXME: don't need to check for function value again below if false if (!VM_TOP().True()) { VM_POP(); #ifdef VM_COMPILED_CODE_MODE next_call_target = 0; #endif } else { U_CALLV(VM_FC_PASS_THRU); } VM_RET; } VM_INS_RET VM::U_CALLV(VM_OP_ARGS_CALL) { Value fun = VM_POP(); VMTYPEEQ(fun, V_FUNCTION); #ifndef VM_COMPILED_CODE_MODE auto fcont = ip - codestart; #endif StartStackFrame(InsPtr(fcont)); FunIntroPre(fun.ip()); VM_RET; } VM_INS_RET VM::U_DDCALL(int vtable_idx, int stack_idx VM_COMMA VM_OP_ARGS_CALL) { auto self = VM_TOPM(stack_idx); VMTYPEEQ(self, V_CLASS); auto start = self.oval()->ti(*this).vtable_start; auto fun = vtables[start + vtable_idx]; #ifdef VM_COMPILED_CODE_MODE #else auto fcont = ip - codestart; assert(fun.f >= 0); #endif StartStackFrame(InsPtr(fcont)); FunIntroPre(fun); VM_RET; } VM_INS_RET VM::U_FUNSTART(VM_OP_ARGS) { #ifdef VM_COMPILED_CODE_MODE FunIntro(ip); #else VMASSERT(false); #endif VM_RET; } VM_INS_RET VM::U_RETURN(int df, int nrv) { FunOut(df, nrv); VM_RET; } VM_INS_RET VM::U_ENDSTATEMENT(int line, int fileidx) { #ifdef NDEBUG (void)line; (void)fileidx; #else if (trace != TraceMode::OFF) { auto &ss = TraceStream(); ss << bcf->filenames()->Get(fileidx)->string_view() << '(' << line << ')'; if (trace == TraceMode::TAIL) ss << '\n'; else LOG_PROGRAM(ss.str()); } #endif assert(sp == stackframes.back().spstart); VM_RET; } VM_INS_RET VM::U_EXIT(int tidx) { if (tidx >= 0) EndEval(VM_POP(), GetTypeInfo((type_elem_t)tidx)); else EndEval(Value(), GetTypeInfo(TYPE_ELEM_ANY)); VM_TERMINATE; } VM_INS_RET VM::U_CONT1(int nfi) { auto nf = nfr.nfuns[nfi]; nf->cont1(*this); VM_RET; } VM_JMP_RET VM::ForLoop(intp len) { #ifndef VM_COMPILED_CODE_MODE auto cont = *ip++; #endif auto &i = VM_TOPM(1); assert(i.type == V_INT); i.setival(i.ival() + 1); if (i.ival() < len) { #ifdef VM_COMPILED_CODE_MODE return true; #else ip = cont + codestart; #endif } else { (void)VM_POP(); /* iter */ (void)VM_POP(); /* i */ #ifdef VM_COMPILED_CODE_MODE return false; #endif } } #define FORELEM(L) \ auto &iter = VM_TOP(); \ auto i = VM_TOPM(1).ival(); \ assert(i < L); \ VM_JMP_RET VM::U_IFOR() { return ForLoop(VM_TOP().ival()); VM_RET; } VM_JMP_RET VM::U_VFOR() { return ForLoop(VM_TOP().vval()->len); VM_RET; } VM_JMP_RET VM::U_SFOR() { return ForLoop(VM_TOP().sval()->len); VM_RET; } VM_INS_RET VM::U_IFORELEM() { FORELEM(iter.ival()); (void)iter; VM_PUSH(i); VM_RET; } VM_INS_RET VM::U_VFORELEM() { FORELEM(iter.vval()->len); iter.vval()->AtVW(*this, i); VM_RET; } VM_INS_RET VM::U_VFORELEMREF() { FORELEM(iter.vval()->len); auto el = iter.vval()->At(i); el.LTINCRTNIL(); VM_PUSH(el); VM_RET; } VM_INS_RET VM::U_SFORELEM() { FORELEM(iter.sval()->len); VM_PUSH(Value((int)((uchar *)iter.sval()->data())[i])); VM_RET; } VM_INS_RET VM::U_FORLOOPI() { auto &i = VM_TOPM(1); // This relies on for being inlined, otherwise it would be 2. assert(i.type == V_INT); VM_PUSH(i); VM_RET; } VM_INS_RET VM::U_BCALLRETV(int nfi) { BCallProf(); auto nf = nfr.nfuns[nfi]; nf->fun.fV(*this); VM_RET; } VM_INS_RET VM::U_BCALLREFV(int nfi) { BCallProf(); auto nf = nfr.nfuns[nfi]; nf->fun.fV(*this); // This can only pop a single value, not called for structs. VM_POP().LTDECRTNIL(*this); VM_RET; } VM_INS_RET VM::U_BCALLUNBV(int nfi) { BCallProf(); auto nf = nfr.nfuns[nfi]; nf->fun.fV(*this); // This can only pop a single value, not called for structs. VM_POP(); VM_RET; } #define BCALLOPH(PRE,N,DECLS,ARGS,RETOP) VM_INS_RET VM::U_BCALL##PRE##N(int nfi) { \ BCallProf(); \ auto nf = nfr.nfuns[nfi]; \ DECLS; \ Value v = nf->fun.f##N ARGS; \ RETOP; \ VM_RET; \ } #define BCALLOP(N,DECLS,ARGS) \ BCALLOPH(RET,N,DECLS,ARGS,VM_PUSH(v);BCallRetCheck(nf)) \ BCALLOPH(REF,N,DECLS,ARGS,v.LTDECRTNIL(*this)) \ BCALLOPH(UNB,N,DECLS,ARGS,(void)v) BCALLOP(0, {}, (*this)); BCALLOP(1, auto a0 = VM_POP(), (*this, a0)); BCALLOP(2, auto a1 = VM_POP();auto a0 = VM_POP(), (*this, a0, a1)); BCALLOP(3, auto a2 = VM_POP();auto a1 = VM_POP();auto a0 = VM_POP(), (*this, a0, a1, a2)); BCALLOP(4, auto a3 = VM_POP();auto a2 = VM_POP();auto a1 = VM_POP();auto a0 = VM_POP(), (*this, a0, a1, a2, a3)); BCALLOP(5, auto a4 = VM_POP();auto a3 = VM_POP();auto a2 = VM_POP();auto a1 = VM_POP();auto a0 = VM_POP(), (*this, a0, a1, a2, a3, a4)); BCALLOP(6, auto a5 = VM_POP();auto a4 = VM_POP();auto a3 = VM_POP();auto a2 = VM_POP();auto a1 = VM_POP();auto a0 = VM_POP(), (*this, a0, a1, a2, a3, a4, a5)); BCALLOP(7, auto a6 = VM_POP();auto a5 = VM_POP();auto a4 = VM_POP();auto a3 = VM_POP();auto a2 = VM_POP();auto a1 = VM_POP();auto a0 = VM_POP(), (*this, a0, a1, a2, a3, a4, a5, a6)); VM_INS_RET VM::U_ASSERTR(int line, int fileidx, int stringidx) { (void)line; (void)fileidx; if (!VM_TOP().True()) { Error(cat( #ifdef VM_COMPILED_CODE_MODE bcf->filenames()->Get(fileidx)->string_view(), "(", line, "): ", #endif "assertion failed: ", bcf->stringtable()->Get(stringidx)->string_view())); } VM_RET; } VM_INS_RET VM::U_ASSERT(int line, int fileidx, int stringidx) { U_ASSERTR(line, fileidx, stringidx); VM_POP(); VM_RET; } VM_INS_RET VM::U_NEWVEC(int ty, int len) { auto type = (type_elem_t)ty; auto vec = NewVec(len, len, type); if (len) vec->Init(*this, VM_TOPPTR() - len * vec->width, false); VM_POPN(len * (int)vec->width); VM_PUSH(Value(vec)); VM_RET; } VM_INS_RET VM::U_NEWOBJECT(int ty) { auto type = (type_elem_t)ty; auto len = GetTypeInfo(type).len; auto vec = NewObject(len, type); if (len) vec->Init(*this, VM_TOPPTR() - len, len, false); VM_POPN(len); VM_PUSH(Value(vec)); VM_RET; } VM_INS_RET VM::U_POP() { VM_POP(); VM_RET; } VM_INS_RET VM::U_POPREF() { auto x = VM_POP(); x.LTDECRTNIL(*this); VM_RET; } VM_INS_RET VM::U_POPV(int len) { VM_POPN(len); VM_RET; } VM_INS_RET VM::U_POPVREF(int len) { while (len--) VM_POP().LTDECRTNIL(*this); VM_RET; } VM_INS_RET VM::U_DUP() { auto x = VM_TOP(); VM_PUSH(x); VM_RET; } #define GETARGS() Value b = VM_POP(); Value a = VM_POP() #define TYPEOP(op, extras, field, errstat) Value res; errstat; \ if (extras & 1 && b.field == 0) Div0(); res = a.field op b.field; #define _IOP(op, extras) \ TYPEOP(op, extras, ival(), assert(a.type == V_INT && b.type == V_INT)) #define _FOP(op, extras) \ TYPEOP(op, extras, fval(), assert(a.type == V_FLOAT && b.type == V_FLOAT)) #define _GETA() VM_TOPPTR() - len #define _VOP(op, extras, V_T, field, withscalar, geta) { \ if (withscalar) { \ auto b = VM_POP(); \ VMTYPEEQ(b, V_T) \ auto veca = geta; \ for (int j = 0; j < len; j++) { \ auto &a = veca[j]; \ VMTYPEEQ(a, V_T) \ auto bv = b.field(); \ if (extras&1 && bv == 0) Div0(); \ a = Value(a.field() op bv); \ } \ } else { \ VM_POPN(len); \ auto vecb = VM_TOPPTR(); \ auto veca = geta; \ for (int j = 0; j < len; j++) { \ auto b = vecb[j]; \ VMTYPEEQ(b, V_T) \ auto &a = veca[j]; \ VMTYPEEQ(a, V_T) \ auto bv = b.field(); \ if (extras & 1 && bv == 0) Div0(); \ a = Value(a.field() op bv); \ } \ } \ } #define STCOMPEN(op, init, andor) { \ VM_POPN(len); \ auto vecb = VM_TOPPTR(); \ VM_POPN(len); \ auto veca = VM_TOPPTR(); \ auto all = init; \ for (int j = 0; j < len; j++) { \ all = all andor veca[j].any() op vecb[j].any(); \ } \ VM_PUSH(all); \ VM_RET; \ } #define _IVOP(op, extras, withscalar, geta) _VOP(op, extras, V_INT, ival, withscalar, geta) #define _FVOP(op, extras, withscalar, geta) _VOP(op, extras, V_FLOAT, fval, withscalar, geta) #define _SOP(op) Value res = *a.sval() op *b.sval() #define _SCAT() Value res = NewString(a.sval()->strv(), b.sval()->strv()) #define ACOMPEN(op) { GETARGS(); Value res = a.any() op b.any(); VM_PUSH(res); VM_RET; } #define IOP(op, extras) { GETARGS(); _IOP(op, extras); VM_PUSH(res); VM_RET; } #define FOP(op, extras) { GETARGS(); _FOP(op, extras); VM_PUSH(res); VM_RET; } #define LOP(op) { GETARGS(); auto res = a.ip() op b.ip(); VM_PUSH(res); VM_RET; } #define IVVOP(op, extras) { _IVOP(op, extras, false, _GETA()); VM_RET; } #define FVVOP(op, extras) { _FVOP(op, extras, false, _GETA()); VM_RET; } #define IVSOP(op, extras) { _IVOP(op, extras, true, _GETA()); VM_RET; } #define FVSOP(op, extras) { _FVOP(op, extras, true, _GETA()); VM_RET; } #define SOP(op) { GETARGS(); _SOP(op); VM_PUSH(res); VM_RET; } #define SCAT() { GETARGS(); _SCAT(); VM_PUSH(res); VM_RET; } // + += I F Vif S // - -= I F Vif // * *= I F Vif // / /= I F Vif // % %= I Vi // < I F Vif S // > I F Vif S // <= I F Vif S // >= I F Vif S // == I F V S // FIXME differentiate struct / value / vector // != I F V S // U- I F Vif // U! A VM_INS_RET VM::U_IVVADD(int len) { IVVOP(+, 0); } VM_INS_RET VM::U_IVVSUB(int len) { IVVOP(-, 0); } VM_INS_RET VM::U_IVVMUL(int len) { IVVOP(*, 0); } VM_INS_RET VM::U_IVVDIV(int len) { IVVOP(/, 1); } VM_INS_RET VM::U_IVVMOD(int) { VMASSERT(0); VM_RET; } VM_INS_RET VM::U_IVVLT(int len) { IVVOP(<, 0); } VM_INS_RET VM::U_IVVGT(int len) { IVVOP(>, 0); } VM_INS_RET VM::U_IVVLE(int len) { IVVOP(<=, 0); } VM_INS_RET VM::U_IVVGE(int len) { IVVOP(>=, 0); } VM_INS_RET VM::U_FVVADD(int len) { FVVOP(+, 0); } VM_INS_RET VM::U_FVVSUB(int len) { FVVOP(-, 0); } VM_INS_RET VM::U_FVVMUL(int len) { FVVOP(*, 0); } VM_INS_RET VM::U_FVVDIV(int len) { FVVOP(/, 1); } VM_INS_RET VM::U_FVVMOD(int) { VMASSERT(0); VM_RET; } VM_INS_RET VM::U_FVVLT(int len) { FVVOP(<, 0); } VM_INS_RET VM::U_FVVGT(int len) { FVVOP(>, 0); } VM_INS_RET VM::U_FVVLE(int len) { FVVOP(<=, 0); } VM_INS_RET VM::U_FVVGE(int len) { FVVOP(>=, 0); } VM_INS_RET VM::U_IVSADD(int len) { IVSOP(+, 0); } VM_INS_RET VM::U_IVSSUB(int len) { IVSOP(-, 0); } VM_INS_RET VM::U_IVSMUL(int len) { IVSOP(*, 0); } VM_INS_RET VM::U_IVSDIV(int len) { IVSOP(/, 1); } VM_INS_RET VM::U_IVSMOD(int) { VMASSERT(0); VM_RET; } VM_INS_RET VM::U_IVSLT(int len) { IVSOP(<, 0); } VM_INS_RET VM::U_IVSGT(int len) { IVSOP(>, 0); } VM_INS_RET VM::U_IVSLE(int len) { IVSOP(<=, 0); } VM_INS_RET VM::U_IVSGE(int len) { IVSOP(>=, 0); } VM_INS_RET VM::U_FVSADD(int len) { FVSOP(+, 0); } VM_INS_RET VM::U_FVSSUB(int len) { FVSOP(-, 0); } VM_INS_RET VM::U_FVSMUL(int len) { FVSOP(*, 0); } VM_INS_RET VM::U_FVSDIV(int len) { FVSOP(/, 1); } VM_INS_RET VM::U_FVSMOD(int) { VMASSERT(0); VM_RET; } VM_INS_RET VM::U_FVSLT(int len) { FVSOP(<, 0); } VM_INS_RET VM::U_FVSGT(int len) { FVSOP(>, 0); } VM_INS_RET VM::U_FVSLE(int len) { FVSOP(<=, 0); } VM_INS_RET VM::U_FVSGE(int len) { FVSOP(>=, 0); } VM_INS_RET VM::U_AEQ() { ACOMPEN(==); } VM_INS_RET VM::U_ANE() { ACOMPEN(!=); } VM_INS_RET VM::U_STEQ(int len) { STCOMPEN(==, true, &&); } VM_INS_RET VM::U_STNE(int len) { STCOMPEN(!=, false, ||); } VM_INS_RET VM::U_LEQ() { LOP(==); } VM_INS_RET VM::U_LNE() { LOP(!=); } VM_INS_RET VM::U_IADD() { IOP(+, 0); } VM_INS_RET VM::U_ISUB() { IOP(-, 0); } VM_INS_RET VM::U_IMUL() { IOP(*, 0); } VM_INS_RET VM::U_IDIV() { IOP(/ , 1); } VM_INS_RET VM::U_IMOD() { IOP(%, 1); } VM_INS_RET VM::U_ILT() { IOP(<, 0); } VM_INS_RET VM::U_IGT() { IOP(>, 0); } VM_INS_RET VM::U_ILE() { IOP(<=, 0); } VM_INS_RET VM::U_IGE() { IOP(>=, 0); } VM_INS_RET VM::U_IEQ() { IOP(==, 0); } VM_INS_RET VM::U_INE() { IOP(!=, 0); } VM_INS_RET VM::U_FADD() { FOP(+, 0); } VM_INS_RET VM::U_FSUB() { FOP(-, 0); } VM_INS_RET VM::U_FMUL() { FOP(*, 0); } VM_INS_RET VM::U_FDIV() { FOP(/, 1); } VM_INS_RET VM::U_FMOD() { VMASSERT(0); VM_RET; } VM_INS_RET VM::U_FLT() { FOP(<, 0); } VM_INS_RET VM::U_FGT() { FOP(>, 0); } VM_INS_RET VM::U_FLE() { FOP(<=, 0); } VM_INS_RET VM::U_FGE() { FOP(>=, 0); } VM_INS_RET VM::U_FEQ() { FOP(==, 0); } VM_INS_RET VM::U_FNE() { FOP(!=, 0); } VM_INS_RET VM::U_SADD() { SCAT(); } VM_INS_RET VM::U_SSUB() { VMASSERT(0); VM_RET; } VM_INS_RET VM::U_SMUL() { VMASSERT(0); VM_RET; } VM_INS_RET VM::U_SDIV() { VMASSERT(0); VM_RET; } VM_INS_RET VM::U_SMOD() { VMASSERT(0); VM_RET; } VM_INS_RET VM::U_SLT() { SOP(<); } VM_INS_RET VM::U_SGT() { SOP(>); } VM_INS_RET VM::U_SLE() { SOP(<=); } VM_INS_RET VM::U_SGE() { SOP(>=); } VM_INS_RET VM::U_SEQ() { SOP(==); } VM_INS_RET VM::U_SNE() { SOP(!=); } VM_INS_RET VM::U_IUMINUS() { Value a = VM_POP(); VM_PUSH(Value(-a.ival())); VM_RET; } VM_INS_RET VM::U_FUMINUS() { Value a = VM_POP(); VM_PUSH(Value(-a.fval())); VM_RET; } VM_INS_RET VM::U_IVUMINUS(int len) { auto vec = VM_TOPPTR() - len; for (int i = 0; i < len; i++) { auto &a = vec[i]; VMTYPEEQ(a, V_INT); a = -a.ival(); } VM_RET; } VM_INS_RET VM::U_FVUMINUS(int len) { auto vec = VM_TOPPTR() - len; for (int i = 0; i < len; i++) { auto &a = vec[i]; VMTYPEEQ(a, V_FLOAT); a = -a.fval(); } VM_RET; } VM_INS_RET VM::U_LOGNOT() { Value a = VM_POP(); VM_PUSH(!a.True()); VM_RET; } VM_INS_RET VM::U_LOGNOTREF() { Value a = VM_POP(); bool b = a.True(); VM_PUSH(!b); VM_RET; } #define BITOP(op) { GETARGS(); VM_PUSH(a.ival() op b.ival()); VM_RET; } VM_INS_RET VM::U_BINAND() { BITOP(&); } VM_INS_RET VM::U_BINOR() { BITOP(|); } VM_INS_RET VM::U_XOR() { BITOP(^); } VM_INS_RET VM::U_ASL() { BITOP(<<); } VM_INS_RET VM::U_ASR() { BITOP(>>); } VM_INS_RET VM::U_NEG() { auto a = VM_POP(); VM_PUSH(~a.ival()); VM_RET; } VM_INS_RET VM::U_I2F() { Value a = VM_POP(); VMTYPEEQ(a, V_INT); VM_PUSH((float)a.ival()); VM_RET; } VM_INS_RET VM::U_A2S(int ty) { Value a = VM_POP(); VM_PUSH(ToString(a, GetTypeInfo((type_elem_t)ty))); VM_RET; } VM_INS_RET VM::U_ST2S(int ty) { auto &ti = GetTypeInfo((type_elem_t)ty); VM_POPN(ti.len); auto top = VM_TOPPTR(); VM_PUSH(StructToString(top, ti)); VM_RET; } VM_INS_RET VM::U_E2B() { Value a = VM_POP(); VM_PUSH(a.True()); VM_RET; } VM_INS_RET VM::U_E2BREF() { Value a = VM_POP(); VM_PUSH(a.True()); VM_RET; } VM_INS_RET VM::U_PUSHVAR(int vidx) { VM_PUSH(vars[vidx]); VM_RET; } VM_INS_RET VM::U_PUSHVARV(int vidx, int l) { tsnz_memcpy(VM_TOPPTR(), &vars[vidx], l); VM_PUSHN(l); VM_RET; } VM_INS_RET VM::U_PUSHFLD(int i) { Value r = VM_POP(); VMASSERT(r.ref()); assert(i < r.oval()->Len(*this)); VM_PUSH(r.oval()->AtS(i)); VM_RET; } VM_INS_RET VM::U_PUSHFLDMREF(int i) { Value r = VM_POP(); if (!r.ref()) { VM_PUSH(r); } else { assert(i < r.oval()->Len(*this)); VM_PUSH(r.oval()->AtS(i)); } VM_RET; } VM_INS_RET VM::U_PUSHFLD2V(int i, int l) { Value r = VM_POP(); VMASSERT(r.ref()); assert(i + l <= r.oval()->Len(*this)); tsnz_memcpy(VM_TOPPTR(), &r.oval()->AtS(i), l); VM_PUSHN(l); VM_RET; } VM_INS_RET VM::U_PUSHFLDV(int i, int l) { VM_POPN(l); auto val = *(VM_TOPPTR() + i); VM_PUSH(val); VM_RET; } VM_INS_RET VM::U_PUSHFLDV2V(int i, int rl, int l) { VM_POPN(l); t_memmove(VM_TOPPTR(), VM_TOPPTR() + i, rl); VM_PUSHN(rl); VM_RET; } VM_INS_RET VM::U_VPUSHIDXI() { PushDerefIdxVector(VM_POP().ival()); VM_RET; } VM_INS_RET VM::U_VPUSHIDXV(int l) { PushDerefIdxVector(GrabIndex(l)); VM_RET; } VM_INS_RET VM::U_VPUSHIDXIS(int w, int o) { PushDerefIdxVectorSub(VM_POP().ival(), w, o); VM_RET; } VM_INS_RET VM::U_VPUSHIDXVS(int l, int w, int o) { PushDerefIdxVectorSub(GrabIndex(l), w, o); VM_RET; } VM_INS_RET VM::U_NPUSHIDXI(int l) { PushDerefIdxStruct(VM_POP().ival(), l); VM_RET; } VM_INS_RET VM::U_SPUSHIDXI() { PushDerefIdxString(VM_POP().ival()); VM_RET; } VM_INS_RET VM::U_PUSHLOC(int i) { auto coro = VM_POP().cval(); VM_PUSH(coro->GetVar(*this, i)); VM_RET; } VM_INS_RET VM::U_PUSHLOCV(int i, int l) { auto coro = VM_POP().cval(); tsnz_memcpy(VM_TOPPTR(), &coro->GetVar(*this, i), l); VM_PUSHN(l); VM_RET; } #ifdef VM_COMPILED_CODE_MODE #define GJUMP(N, V, C, P) VM_JMP_RET VM::U_##N() \ { V; if (C) { P; return true; } else { return false; } } #else #define GJUMP(N, V, C, P) VM_JMP_RET VM::U_##N() \ { V; auto nip = *ip++; if (C) { ip = codestart + nip; P; } VM_RET; } #endif GJUMP(JUMP , , true , ) GJUMP(JUMPFAIL , auto x = VM_POP(), !x.True(), ) GJUMP(JUMPFAILR , auto x = VM_POP(), !x.True(), VM_PUSH(x) ) GJUMP(JUMPFAILN , auto x = VM_POP(), !x.True(), VM_PUSH(Value())) GJUMP(JUMPNOFAIL , auto x = VM_POP(), x.True(), ) GJUMP(JUMPNOFAILR, auto x = VM_POP(), x.True(), VM_PUSH(x) ) VM_INS_RET VM::U_ISTYPE(int ty) { auto to = (type_elem_t)ty; auto v = VM_POP(); // Optimizer guarantees we don't have to deal with scalars. if (v.refnil()) VM_PUSH(v.ref()->tti == to); else VM_PUSH(GetTypeInfo(to).t == V_NIL); // FIXME: can replace by fixed type_elem_t ? VM_RET; } VM_INS_RET VM::U_YIELD(VM_OP_ARGS_CALL) { CoYield(VM_FC_PASS_THRU); VM_RET; } // This value never gets used anywhere, just a placeholder. VM_INS_RET VM::U_COCL() { VM_PUSH(Value(0, V_YIELD)); VM_RET; } VM_INS_RET VM::U_CORO(VM_OP_ARGS VM_COMMA VM_OP_ARGS_CALL) { CoNew(VM_IP_PASS_THRU VM_COMMA VM_FC_PASS_THRU); VM_RET; } VM_INS_RET VM::U_COEND() { CoClean(); VM_RET; } VM_INS_RET VM::U_LOGREAD(int vidx) { auto val = VM_POP(); VM_PUSH(vml.LogGet(val, vidx)); VM_RET; } VM_INS_RET VM::U_LOGWRITE(int vidx, int lidx) { vml.LogWrite(vars[vidx], lidx); VM_RET; } VM_INS_RET VM::U_ABORT() { Error("VM internal error: abort"); VM_RET; } void VM::IDXErr(intp i, intp n, const RefObj *v) { Error(cat("index ", i, " out of range ", n), v); } #define RANGECHECK(I, BOUND, VEC) if ((uintp)I >= (uintp)BOUND) IDXErr(I, BOUND, VEC); void VM::PushDerefIdxVector(intp i) { Value r = VM_POP(); VMASSERT(r.ref()); auto v = r.vval(); RANGECHECK(i, v->len, v); v->AtVW(*this, i); } void VM::PushDerefIdxVectorSub(intp i, int width, int offset) { Value r = VM_POP(); VMASSERT(r.ref()); auto v = r.vval(); RANGECHECK(i, v->len, v); v->AtVWSub(*this, i, width, offset); } void VM::PushDerefIdxStruct(intp i, int l) { VM_POPN(l); auto val = *(VM_TOPPTR() + i); VM_PUSH(val); } void VM::PushDerefIdxString(intp i) { Value r = VM_POP(); VMASSERT(r.ref()); // Allow access of the terminating 0-byte. RANGECHECK(i, r.sval()->len + 1, r.sval()); VM_PUSH(Value((int)((uchar *)r.sval()->data())[i])); } Value &VM::GetFieldLVal(intp i) { Value vec = VM_POP(); #ifndef NDEBUG RANGECHECK(i, vec.oval()->Len(*this), vec.oval()); #endif return vec.oval()->AtS(i); } Value &VM::GetFieldILVal(intp i) { Value vec = VM_POP(); RANGECHECK(i, vec.oval()->Len(*this), vec.oval()); return vec.oval()->AtS(i); } Value &VM::GetVecLVal(intp i) { Value vec = VM_POP(); auto v = vec.vval(); RANGECHECK(i, v->len, v); return *v->AtSt(i); } Value &VM::GetLocLVal(int i) { Value coro = VM_POP(); VMTYPEEQ(coro, V_COROUTINE); return coro.cval()->GetVar(*this, i); } #pragma push_macro("LVAL") #undef LVAL #define LVAL(N, V) VM_INS_RET VM::U_VAR_##N(int vidx VM_COMMA_IF(V) VM_OP_ARGSN(V)) \ { LV_##N(vars[vidx] VM_COMMA_IF(V) VM_OP_PASSN(V)); VM_RET; } LVALOPNAMES #undef LVAL #define LVAL(N, V) VM_INS_RET VM::U_FLD_##N(int i VM_COMMA_IF(V) VM_OP_ARGSN(V)) \ { LV_##N(GetFieldLVal(i) VM_COMMA_IF(V) VM_OP_PASSN(V)); VM_RET; } LVALOPNAMES #undef LVAL #define LVAL(N, V) VM_INS_RET VM::U_LOC_##N(int i VM_COMMA_IF(V) VM_OP_ARGSN(V)) \ { LV_##N(GetLocLVal(i) VM_COMMA_IF(V) VM_OP_PASSN(V)); VM_RET; } LVALOPNAMES #undef LVAL #define LVAL(N, V) VM_INS_RET VM::U_IDXVI_##N(VM_OP_ARGSN(V)) \ { LV_##N(GetVecLVal(VM_POP().ival()) VM_COMMA_IF(V) VM_OP_PASSN(V)); VM_RET; } LVALOPNAMES #undef LVAL #define LVAL(N, V) VM_INS_RET VM::U_IDXVV_##N(int l VM_COMMA_IF(V) VM_OP_ARGSN(V)) \ { LV_##N(GetVecLVal(GrabIndex(l)) VM_COMMA_IF(V) VM_OP_PASSN(V)); VM_RET; } LVALOPNAMES #undef LVAL #define LVAL(N, V) VM_INS_RET VM::U_IDXNI_##N(VM_OP_ARGSN(V)) \ { LV_##N(GetFieldILVal(VM_POP().ival()) VM_COMMA_IF(V) VM_OP_PASSN(V)); VM_RET; } LVALOPNAMES #undef LVAL #pragma pop_macro("LVAL") #define LVALCASES(N, B) void VM::LV_##N(Value &a) { Value b = VM_POP(); B; } #define LVALCASER(N, B) void VM::LV_##N(Value &fa, int len) { B; } #define LVALCASESTR(N, B, B2) void VM::LV_##N(Value &a) { Value b = VM_POP(); B; a.LTDECRTNIL(*this); B2; } LVALCASER(IVVADD , _IVOP(+, 0, false, &fa)) LVALCASER(IVVADDR, _IVOP(+, 0, false, &fa)) LVALCASER(IVVSUB , _IVOP(-, 0, false, &fa)) LVALCASER(IVVSUBR, _IVOP(-, 0, false, &fa)) LVALCASER(IVVMUL , _IVOP(*, 0, false, &fa)) LVALCASER(IVVMULR, _IVOP(*, 0, false, &fa)) LVALCASER(IVVDIV , _IVOP(/, 1, false, &fa)) LVALCASER(IVVDIVR, _IVOP(/, 1, false, &fa)) LVALCASER(IVVMOD , VMASSERT(0); (void)fa; (void)len) LVALCASER(IVVMODR, VMASSERT(0); (void)fa; (void)len) LVALCASER(FVVADD , _FVOP(+, 0, false, &fa)) LVALCASER(FVVADDR, _FVOP(+, 0, false, &fa)) LVALCASER(FVVSUB , _FVOP(-, 0, false, &fa)) LVALCASER(FVVSUBR, _FVOP(-, 0, false, &fa)) LVALCASER(FVVMUL , _FVOP(*, 0, false, &fa)) LVALCASER(FVVMULR, _FVOP(*, 0, false, &fa)) LVALCASER(FVVDIV , _FVOP(/, 1, false, &fa)) LVALCASER(FVVDIVR, _FVOP(/, 1, false, &fa)) LVALCASER(IVSADD , _IVOP(+, 0, true, &fa)) LVALCASER(IVSADDR, _IVOP(+, 0, true, &fa)) LVALCASER(IVSSUB , _IVOP(-, 0, true, &fa)) LVALCASER(IVSSUBR, _IVOP(-, 0, true, &fa)) LVALCASER(IVSMUL , _IVOP(*, 0, true, &fa)) LVALCASER(IVSMULR, _IVOP(*, 0, true, &fa)) LVALCASER(IVSDIV , _IVOP(/, 1, true, &fa)) LVALCASER(IVSDIVR, _IVOP(/, 1, true, &fa)) LVALCASER(IVSMOD , VMASSERT(0); (void)fa; (void)len) LVALCASER(IVSMODR, VMASSERT(0); (void)fa; (void)len) LVALCASER(FVSADD , _FVOP(+, 0, true, &fa)) LVALCASER(FVSADDR, _FVOP(+, 0, true, &fa)) LVALCASER(FVSSUB , _FVOP(-, 0, true, &fa)) LVALCASER(FVSSUBR, _FVOP(-, 0, true, &fa)) LVALCASER(FVSMUL , _FVOP(*, 0, true, &fa)) LVALCASER(FVSMULR, _FVOP(*, 0, true, &fa)) LVALCASER(FVSDIV , _FVOP(/, 1, true, &fa)) LVALCASER(FVSDIVR, _FVOP(/, 1, true, &fa)) LVALCASES(IADD , _IOP(+, 0); a = res; ) LVALCASES(IADDR , _IOP(+, 0); a = res; VM_PUSH(res)) LVALCASES(ISUB , _IOP(-, 0); a = res; ) LVALCASES(ISUBR , _IOP(-, 0); a = res; VM_PUSH(res)) LVALCASES(IMUL , _IOP(*, 0); a = res; ) LVALCASES(IMULR , _IOP(*, 0); a = res; VM_PUSH(res)) LVALCASES(IDIV , _IOP(/, 1); a = res; ) LVALCASES(IDIVR , _IOP(/, 1); a = res; VM_PUSH(res)) LVALCASES(IMOD , _IOP(%, 1); a = res; ) LVALCASES(IMODR , _IOP(%, 1); a = res; VM_PUSH(res)) LVALCASES(FADD , _FOP(+, 0); a = res; ) LVALCASES(FADDR , _FOP(+, 0); a = res; VM_PUSH(res)) LVALCASES(FSUB , _FOP(-, 0); a = res; ) LVALCASES(FSUBR , _FOP(-, 0); a = res; VM_PUSH(res)) LVALCASES(FMUL , _FOP(*, 0); a = res; ) LVALCASES(FMULR , _FOP(*, 0); a = res; VM_PUSH(res)) LVALCASES(FDIV , _FOP(/, 1); a = res; ) LVALCASES(FDIVR , _FOP(/, 1); a = res; VM_PUSH(res)) LVALCASESTR(SADD , _SCAT(), a = res; ) LVALCASESTR(SADDR, _SCAT(), a = res; VM_PUSH(res)) #define OVERWRITE_VAR(a, b) { assert(a.type == b.type || a.type == V_NIL || b.type == V_NIL); a = b; } void VM::LV_WRITE (Value &a) { auto b = VM_POP(); OVERWRITE_VAR(a, b); } void VM::LV_WRITER (Value &a) { auto &b = VM_TOP(); OVERWRITE_VAR(a, b); } void VM::LV_WRITEREF (Value &a) { auto b = VM_POP(); a.LTDECRTNIL(*this); OVERWRITE_VAR(a, b); } void VM::LV_WRITERREF(Value &a) { auto &b = VM_TOP(); a.LTDECRTNIL(*this); OVERWRITE_VAR(a, b); } #define WRITESTRUCT(DECS) \ auto b = VM_TOPPTR() - l; \ if (DECS) for (int i = 0; i < l; i++) (&a)[i].LTDECRTNIL(*this); \ tsnz_memcpy(&a, b, l); void VM::LV_WRITEV (Value &a, int l) { WRITESTRUCT(false); VM_POPN(l); } void VM::LV_WRITERV (Value &a, int l) { WRITESTRUCT(false); } void VM::LV_WRITEREFV (Value &a, int l) { WRITESTRUCT(true); VM_POPN(l); } void VM::LV_WRITERREFV(Value &a, int l) { WRITESTRUCT(true); } #define PPOP(name, ret, op, pre, accessor) void VM::LV_##name(Value &a) { \ if (ret && !pre) VM_PUSH(a); \ a.set##accessor(a.accessor() op 1); \ if (ret && pre) VM_PUSH(a); \ } PPOP(IPP , false, +, true , ival) PPOP(IPPR , true , +, true , ival) PPOP(IMM , false, -, true , ival) PPOP(IMMR , true , -, true , ival) PPOP(IPPP , false, +, false, ival) PPOP(IPPPR, true , +, false, ival) PPOP(IMMP , false, -, false, ival) PPOP(IMMPR, true , -, false, ival) PPOP(FPP , false, +, true , fval) PPOP(FPPR , true , +, true , fval) PPOP(FMM , false, -, true , fval) PPOP(FMMR , true , -, true , fval) PPOP(FPPP , false, +, false, fval) PPOP(FPPPR, true , +, false, fval) PPOP(FMMP , false, -, false, fval) PPOP(FMMPR, true , -, false, fval) string VM::ProperTypeName(const TypeInfo &ti) { switch (ti.t) { case V_STRUCT_R: case V_STRUCT_S: case V_CLASS: return string(ReverseLookupType(ti.structidx)); case V_NIL: return ProperTypeName(GetTypeInfo(ti.subt)) + "?"; case V_VECTOR: return "[" + ProperTypeName(GetTypeInfo(ti.subt)) + "]"; case V_INT: return ti.enumidx >= 0 ? string(EnumName(ti.enumidx)) : "int"; default: return string(BaseTypeName(ti.t)); } } void VM::BCallProf() { #ifdef VM_PROFILER vm_count_bcalls++; #endif } void VM::BCallRetCheck(const NativeFun *nf) { #if RTT_ENABLED // See if any builtin function is lying about what type it returns // other function types return intermediary values that don't correspond to final return // values. if (!nf->cont1) { for (size_t i = 0; i < nf->retvals.v.size(); i++) { auto t = (VM_TOPPTR() - nf->retvals.v.size() + i)->type; auto u = nf->retvals.v[i].type->t; assert(t == u || u == V_ANY || u == V_NIL || (u == V_VECTOR && IsUDT(t))); } assert(nf->retvals.v.size() || VM_TOP().type == V_NIL); } #else (void)nf; #endif } intp VM::GrabIndex(int len) { auto &v = VM_TOPM(len); for (len--; ; len--) { auto sidx = VM_POP().ival(); if (!len) return sidx; RANGECHECK(sidx, v.vval()->len, v.vval()); v = v.vval()->At(sidx); } } string_view VM::StructName(const TypeInfo &ti) { return bcf->udts()->Get(ti.structidx)->name()->string_view(); } string_view VM::ReverseLookupType(uint v) { return bcf->udts()->Get(v)->name()->string_view(); } string_view VM::EnumName(intp val, int enumidx) { auto &vals = *bcf->enums()->Get(enumidx)->vals(); // FIXME: can store a bool that says wether this enum is contiguous, so we just index instead. for (auto v : vals) if (v->val() == val) return v->name()->string_view(); return {}; } string_view VM::EnumName(int enumidx) { return bcf->enums()->Get(enumidx)->name()->string_view(); } optional VM::LookupEnum(string_view name, int enumidx) { auto &vals = *bcf->enums()->Get(enumidx)->vals(); for (auto v : vals) if (v->name()->string_view() == name) return v->val(); return {}; } void VM::StartWorkers(size_t numthreads) { if (is_worker) Error("workers can\'t start more worker threads"); if (tuple_space) Error("workers already running"); // Stop bad values from locking up the machine :) numthreads = min(numthreads, (size_t)256); tuple_space = new TupleSpace(bcf->udts()->size()); for (size_t i = 0; i < numthreads; i++) { // Create a new VM that should own all its own memory and be completely independent // from this one. // We share nfr and programname for now since they're fully read-only. // FIXME: have to copy bytecode buffer even though it is read-only. auto vmargs = *(VMArgs *)this; vmargs.program_args.resize(0); vmargs.trace = TraceMode::OFF; auto wvm = new VM(std::move(vmargs)); wvm->is_worker = true; wvm->tuple_space = tuple_space; workers.emplace_back([wvm] { string err; #ifdef USE_EXCEPTION_HANDLING try #endif { wvm->EvalProgram(); } #ifdef USE_EXCEPTION_HANDLING catch (string &s) { if (s != "end-eval") err = s; } #endif delete wvm; // FIXME: instead return err to main thread? if (!err.empty()) LOG_ERROR("worker error: ", err); }); } } void VM::TerminateWorkers() { if (is_worker || !tuple_space) return; tuple_space->alive = false; for (auto &tt : tuple_space->tupletypes) tt.condition.notify_all(); for (auto &worker : workers) worker.join(); workers.clear(); delete tuple_space; tuple_space = nullptr; } void VM::WorkerWrite(RefObj *ref) { if (!tuple_space) return; if (!ref) Error("thread write: nil reference"); auto &ti = ref->ti(*this); if (ti.t != V_CLASS) Error("thread write: must be a class"); auto st = (LObject *)ref; auto buf = new Value[ti.len]; for (int i = 0; i < ti.len; i++) { // FIXME: lift this restriction. if (IsRefNil(GetTypeInfo(ti.elemtypes[i]).t)) Error("thread write: only scalar class members supported for now"); buf[i] = st->AtS(i); } auto &tt = tuple_space->tupletypes[ti.structidx]; { unique_lock lock(tt.mtx); tt.tuples.push_back(buf); } tt.condition.notify_one(); } LObject *VM::WorkerRead(type_elem_t tti) { auto &ti = GetTypeInfo(tti); if (ti.t != V_CLASS) Error("thread read: must be a class type"); Value *buf = nullptr; auto &tt = tuple_space->tupletypes[ti.structidx]; { unique_lock lock(tt.mtx); tt.condition.wait(lock, [&] { return !tuple_space->alive || !tt.tuples.empty(); }); if (!tt.tuples.empty()) { buf = tt.tuples.front(); tt.tuples.pop_front(); } } if (!buf) return nullptr; auto ns = NewObject(ti.len, tti); ns->Init(*this, buf, ti.len, false); delete[] buf; return ns; } } // namespace lobster // Make VM ops available as C functions for linking purposes: #ifdef VM_COMPILED_CODE_MODE extern "C" { using namespace lobster; #ifndef NDEBUG #define CHECKI(B) \ if (vm->trace != TraceMode::OFF) { \ auto &ss = vm->TraceStream(); \ ss << B; \ if (vm->trace == TraceMode::TAIL) ss << '\n'; else LOG_PROGRAM(ss.str()); \ } // FIXME: add spaces. #define CHECK(N, A) CHECKI(#N << cat A) #define CHECKJ(N) CHECKI(#N) #else #define CHECK(N, A) #define CHECKJ(N) #endif void CVM_SetNextCallTarget(VM *vm, block_t fcont) { vm->next_call_target = fcont; } block_t CVM_GetNextCallTarget(VM *vm) { return vm->next_call_target; } #define F(N, A) \ void CVM_##N(VM *vm VM_COMMA_IF(A) VM_OP_ARGSN(A)) { \ CHECK(N, (VM_OP_PASSN(A))); vm->U_##N(VM_OP_PASSN(A)); } LVALOPNAMES #undef F #define F(N, A) \ void CVM_##N(VM *vm VM_COMMA_IF(A) VM_OP_ARGSN(A)) { \ CHECK(N, (VM_OP_PASSN(A))); vm->U_##N(VM_OP_PASSN(A)); } ILBASENAMES #undef F #define F(N, A) \ void CVM_##N(VM *vm VM_COMMA_IF(A) VM_OP_ARGSN(A), block_t fcont) { \ CHECK(N, (VM_OP_PASSN(A))); vm->U_##N(VM_OP_PASSN(A) VM_COMMA_IF(A) fcont); } ILCALLNAMES #undef F #define F(N, A) \ bool CVM_##N(VM *vm) { \ CHECKJ(N); return vm->U_##N(); } ILJUMPNAMES #undef F } // extern "C" #endif // VM_COMPILED_CODE_MODE treesheets-1.0.2/lobster/src/vmdata.cpp000066400000000000000000000277751352107072600201330ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/vmdata.h" namespace lobster { LString::LString(intp _l) : RefObj(TYPE_ELEM_STRING), len(_l) { ((char *)data())[_l] = 0; } LResource::LResource(void *v, const ResourceType *t) : RefObj(TYPE_ELEM_RESOURCE), val(v), type(t) {} char HexChar(char i) { return i + (i < 10 ? '0' : 'A' - 10); } void EscapeAndQuote(string_view s, ostringstream &ss) { ss << '\"'; for (auto c : s) switch(c) { case '\n': ss << "\\n"; break; case '\t': ss << "\\t"; break; case '\r': ss << "\\r"; break; case '\\': ss << "\\\\"; break; case '\"': ss << "\\\""; break; case '\'': ss << "\\\'"; break; default: if (c >= ' ' && c <= '~') ss << c; else ss << "\\x" << HexChar(((uchar)c) >> 4) << HexChar(c & 0xF); break; } ss << "\""; } void LString::DeleteSelf(VM &vm) { vm.pool.dealloc(this, sizeof(LString) + len + 1); } void LString::ToString(ostringstream &ss, PrintPrefs &pp) { if (CycleCheck(ss, pp)) return; auto sv = strv(); auto dd = string_view(); if (len > pp.budget) { sv = sv.substr(0, pp.budget); dd = ".."; } if (pp.quoted) { EscapeAndQuote(sv, ss); } else { ss << sv; } ss << dd; } LVector::LVector(VM &vm, intp _initial, intp _max, type_elem_t _tti) : RefObj(_tti), len(_initial), maxl(_max) { auto &sti = vm.GetTypeInfo(ti(vm).subt); width = IsStruct(sti.t) ? sti.len : 1; v = maxl ? AllocSubBuf(vm, maxl * width, TYPE_ELEM_VALUEBUF) : nullptr; } void LVector::Resize(VM &vm, intp newmax) { // FIXME: check overflow auto mem = AllocSubBuf(vm, newmax * width, TYPE_ELEM_VALUEBUF); if (len) t_memcpy(mem, v, len * width); DeallocBuf(vm); maxl = newmax; v = mem; } void LVector::Append(VM &vm, LVector *from, intp start, intp amount) { if (len + amount > maxl) Resize(vm, len + amount); // FIXME: check overflow assert(width == from->width); t_memcpy(v + len * width, from->v + start * width, amount * width); auto et = from->ElemType(vm).t; if (IsRefNil(et)) { for (int i = 0; i < amount * width; i++) { v[len * width + i].LTINCRTNIL(); } } len += amount; } void LVector::Remove(VM &vm, intp i, intp n, intp decfrom, bool stack_ret) { assert(n >= 0 && n <= len && i >= 0 && i <= len - n); if (stack_ret) { tsnz_memcpy(vm.TopPtr(), v + i * width, width); vm.PushN((int)width); } auto et = ElemType(vm).t; if (IsRefNil(et)) { for (intp j = decfrom * width; j < n * width; j++) DecSlot(vm, i * width + j, et); } t_memmove(v + i * width, v + (i + n) * width, (len - i - n) * width); len -= n; } void LVector::AtVW(VM &vm, intp i) const { auto src = AtSt(i); // TODO: split this up for the width==1 case? tsnz_memcpy(vm.TopPtr(), src, width); vm.PushN((int)width); } void LVector::AtVWSub(VM &vm, intp i, int w, int off) const { auto src = AtSt(i); tsnz_memcpy(vm.TopPtr(), src + off, w); vm.PushN(w); } void LVector::DeleteSelf(VM &vm) { auto et = ElemType(vm).t; if (IsRefNil(et)) { for (intp i = 0; i < len * width; i++) DecSlot(vm, i, et); } DeallocBuf(vm); vm.pool.dealloc_small(this); } void LObject::DeleteSelf(VM &vm) { auto len = Len(vm); for (intp i = 0; i < len; i++) { AtS(i).LTDECTYPE(vm, ElemTypeS(vm, i).t); } vm.pool.dealloc(this, sizeof(LObject) + sizeof(Value) * len); } void LResource::DeleteSelf(VM &vm) { type->deletefun(val); vm.pool.dealloc(this, sizeof(LResource)); } void RefObj::DECDELETENOW(VM &vm) { switch (ti(vm).t) { case V_STRING: ((LString *)this)->DeleteSelf(vm); break; case V_COROUTINE: ((LCoRoutine *)this)->DeleteSelf(vm); break; case V_VECTOR: ((LVector *)this)->DeleteSelf(vm); break; case V_CLASS: ((LObject *)this)->DeleteSelf(vm); break; case V_RESOURCE: ((LResource *)this)->DeleteSelf(vm); break; default: assert(false); } } void RefObj::DECDELETE(VM &vm) { if (refc) { vm.DumpVal(this, "double delete"); assert(false); } #if DELETE_DELAY vm.DumpVal(this, "delay delete"); vm.delete_delay.push_back(this); #else DECDELETENOW(vm); #endif } void RefObj::DECSTAT(VM &vm) { vm.vm_count_decref++; } bool RefEqual(VM &vm, const RefObj *a, const RefObj *b, bool structural) { if (a == b) return true; if (!a || !b) return false; if (a->tti != b->tti) return false; switch (a->ti(vm).t) { case V_STRING: return *((LString *)a) == *((LString *)b); case V_COROUTINE: return false; case V_VECTOR: return structural && ((LVector *)a)->Equal(vm, *(LVector *)b); case V_CLASS: return structural && ((LObject *)a)->Equal(vm, *(LObject *)b); default: assert(0); return false; } } bool Value::Equal(VM &vm, ValueType vtype, const Value &o, ValueType otype, bool structural) const { if (vtype != otype) return false; switch (vtype) { case V_INT: return ival_ == o.ival_; case V_FLOAT: return fval_ == o.fval_; case V_FUNCTION: return ip_ == o.ip_; default: return RefEqual(vm, refnil(), o.ref_, structural); } } void RefToString(VM &vm, ostringstream &ss, const RefObj *ro, PrintPrefs &pp) { if (!ro) { ss << "nil"; return; } auto &roti = ro->ti(vm); switch (roti.t) { case V_STRING: ((LString *)ro)->ToString(ss, pp); break; case V_COROUTINE: ss << "(coroutine)"; break; case V_VECTOR: ((LVector *)ro)->ToString(vm, ss, pp); break; case V_CLASS: ((LObject *)ro)->ToString(vm, ss, pp); break; case V_RESOURCE: ((LResource *)ro)->ToString(ss); break; default: ss << '(' << BaseTypeName(roti.t) << ')'; break; } } void Value::ToString(VM &vm, ostringstream &ss, const TypeInfo &ti, PrintPrefs &pp) const { if (ti.t == V_INT && ti.enumidx >= 0) { auto name = vm.EnumName(ival(), ti.enumidx); if (!name.empty()) { ss << name; return; } } ToStringBase(vm, ss, ti.t, pp); } void Value::ToStringBase(VM &vm, ostringstream &ss, ValueType t, PrintPrefs &pp) const { if (IsRefNil(t)) { RefToString(vm, ss, ref_, pp); } else switch (t) { case V_INT: ss << ival(); break; case V_FLOAT: ss << to_string_float(fval(), (int)pp.decimals); break; case V_FUNCTION: ss << ""; break; default: ss << '(' << BaseTypeName(t) << ')'; break; } } intp RefObj::Hash(VM &vm) { switch (ti(vm).t) { case V_STRING: return ((LString *)this)->Hash(); case V_VECTOR: return ((LVector *)this)->Hash(vm); case V_CLASS: return ((LObject *)this)->Hash(vm); default: return (int)(size_t)this; } } intp LString::Hash() { return (int)FNV1A(strv()); } intp Value::Hash(VM &vm, ValueType vtype) { switch (vtype) { case V_INT: return ival_; case V_FLOAT: return ReadMem(&fval_); case V_FUNCTION: return (intp)(size_t)ip_.f; default: return refnil() ? ref()->Hash(vm) : 0; } } Value Value::Copy(VM &vm) { if (!refnil()) return Value(); auto &ti = ref()->ti(vm); switch (ti.t) { case V_VECTOR: { auto len = vval()->len; auto nv = vm.NewVec(len, len, vval()->tti); if (len) nv->Init(vm, vval()->Elems(), true); return Value(nv); } case V_CLASS: { auto len = oval()->Len(vm); auto nv = vm.NewObject(len, oval()->tti); if (len) nv->Init(vm, oval()->Elems(), len, true); return Value(nv); } case V_STRING: { auto s = vm.NewString(sval()->strv()); return Value(s); } case V_COROUTINE: vm.Error("cannot copy coroutine"); return Value(); default: assert(false); return Value(); } } string TypeInfo::Debug(VM &vm, bool rec) const { string s; s += BaseTypeName(t); if (t == V_VECTOR || t == V_NIL) { s += "[" + vm.GetTypeInfo(subt).Debug(vm, false) + "]"; } else if (IsUDT(t)) { auto sname = vm.StructName(*this); s += ":" + sname; if (rec) { s += "{"; for (int i = 0; i < len; i++) s += vm.GetTypeInfo(elemtypes[i]).Debug(vm, false) + ","; s += "}"; } } return s; } #define ELEMTYPE(acc, ass) \ auto &_ti = ti(vm); \ ass; \ auto &sti = vm.GetTypeInfo(_ti.acc); \ return sti.t == V_NIL ? vm.GetTypeInfo(sti.subt) : sti; const TypeInfo &LObject::ElemTypeS(VM &vm, intp i) const { ELEMTYPE(elemtypes[i], assert(i < _ti.len)); } const TypeInfo &LObject::ElemTypeSP(VM &vm, intp i) const { ELEMTYPE(GetElemOrParent(i), assert(i < _ti.len)); } const TypeInfo &LVector::ElemType(VM &vm) const { ELEMTYPE(subt, {}) } void VectorOrObjectToString(VM &vm, ostringstream &ss, PrintPrefs &pp, char openb, char closeb, intp len, intp width, const Value *elems, bool is_vector, std::function getti) { ss << openb; if (pp.indent) ss << '\n'; auto start_size = ss.tellp(); pp.cur_indent += pp.indent; auto Indent = [&]() { for (int i = 0; i < pp.cur_indent; i++) ss << ' '; }; for (intp i = 0; i < len; i++) { if (i) { ss << ','; ss << (pp.indent ? '\n' : ' '); } if (pp.indent) Indent(); if ((int)ss.tellp() - start_size > pp.budget) { ss << "...."; break; } auto &ti = getti(i); if (pp.depth || !IsRef(ti.t)) { PrintPrefs subpp(pp.depth - 1, pp.budget - (int)(ss.tellp() - start_size), true, pp.decimals); subpp.indent = pp.indent; subpp.cur_indent = pp.cur_indent; if (IsStruct(ti.t)) { vm.StructToString(ss, subpp, ti, elems + i * width); if (!is_vector) i += ti.len - 1; } else { elems[i].ToString(vm, ss, ti, subpp); } } else { ss << ".."; } } pp.cur_indent -= pp.indent; if (pp.indent) { ss << '\n'; Indent(); } ss << closeb; } void LObject::ToString(VM &vm, ostringstream &ss, PrintPrefs &pp) { if (CycleCheck(ss, pp)) return; ss << vm.ReverseLookupType(ti(vm).structidx); if (pp.indent) ss << ' '; VectorOrObjectToString(vm, ss, pp, '{', '}', Len(vm), 1, Elems(), false, [&](intp i) -> const TypeInfo & { return ElemTypeSP(vm, i); } ); } void LVector::ToString(VM &vm, ostringstream &ss, PrintPrefs &pp) { if (CycleCheck(ss, pp)) return; VectorOrObjectToString(vm, ss, pp, '[', ']', len, width, v, true, [&](intp) ->const TypeInfo & { return ElemType(vm); } ); } void VM::StructToString(ostringstream &ss, PrintPrefs &pp, const TypeInfo &ti, const Value *elems) { ss << ReverseLookupType(ti.structidx); if (pp.indent) ss << ' '; VectorOrObjectToString(*this, ss, pp, '{', '}', ti.len, 1, elems, false, [&](intp i) -> const TypeInfo & { return GetTypeInfo(ti.GetElemOrParent(i)); } ); } } // namespace lobster treesheets-1.0.2/lobster/src/vmlog.cpp000066400000000000000000000046011352107072600177620ustar00rootroot00000000000000// Copyright 2014 Wouter van Oortmerssen. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "lobster/stdafx.h" #include "lobster/vmdata.h" #define FLATBUFFERS_DEBUG_VERIFICATION_FAILURE #include "lobster/bytecode_generated.h" namespace lobster { VMLog::VMLog(VM &_vm) : vm(_vm) {} void VMLog::LogInit(const uchar *bcfb) { auto bcf = bytecode::GetBytecodeFile(bcfb); logvars.resize(bcf->logvars()->size()); flatbuffers::uoffset_t i = 0; for (auto &l : logvars) { auto sid = bcf->logvars()->Get(i++); l.read = 0; l.type = &vm.GetTypeInfo((type_elem_t)bcf->specidents()->Get(sid)->typeidx()); } } void VMLog::LogPurge() { for (auto &l : logvars) { if (IsRefNil(l.type->t)) { for (size_t i = l.read; i < l.values.size(); i++) l.values[i].LTDECRTNIL(vm); } l.values.resize(l.read); } } void VMLog::LogFrame() { LogPurge(); for (auto &l : logvars) l.read = 0; }; Value VMLog::LogGet(Value def, int idx) { auto &l = logvars[idx]; bool isref = IsRefNil(l.type->t); if (l.read == l.values.size()) { // Value doesn't exist yet. // Already write value, so it can be written to regardless of wether it existed or not. l.values.push_back(def); if (isref) { def.LTINCRTNIL(); } l.read++; return def; } else { // Get existing value, ignore default. auto v = l.values[l.read++]; if (isref) { v.LTINCRTNIL(); def.LTDECRTNIL(vm); } return v; } } void VMLog::LogWrite(Value newval, int idx) { auto &l = logvars[idx]; bool isref = IsRefNil(l.type->t); assert(l.read > 0); auto &slot = l.values[l.read - 1]; if (isref) { newval.LTINCRTNIL(); slot.LTDECRTNIL(vm); } slot = newval; } void VMLog::LogCleanup() { for (auto &l : logvars) l.read = 0; LogPurge(); } } // namespace lobster treesheets-1.0.2/osx/000077500000000000000000000000001352107072600145015ustar00rootroot00000000000000treesheets-1.0.2/osx/App.icns000077500000000000000000000610201352107072600161010ustar00rootroot00000000000000icnsbis32լ€Čvᕟeʙ+ މլ€Čvᕟeʙ+ މլ€Čvᕟeʙ+ މs8mkil32Ϊouopop owou؂πԂ עuppp>[W>@Q@pp-QޣoppQNppMeH?ppNgppQNppN?NE{,ppppLpoooop,ppppLpoooop,pppp pp pp pp pw wouopopowo͕Ϊouopop owou؂πԂ עuppp>[W>@Q@pp-QޣoppQNppMeH?ppNgppQNppN?NE{,ppppLpoooop,ppppLpoooop,pppp pp pp pp pw wouopopowo͕Ϊouopop owou؂πԂ עuppp>[W>@Q@pp-QޣoppQNppMeH?ppNgppQNppN?NE{,ppppLpoooop,ppppLpoooop,pppp pp pp pp pw wouopopowo͕l8mkit32F큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍t8mk@icnV Btreesheets-1.0.2/osx/Doc.icns000077500000000000000000000752431352107072600161020ustar00rootroot00000000000000icnszicm8ICN#il32׎'=3ʂBß9¼<Ͷ?дCӷ?ָux? ƨlʀ Ċ{ʑݿÕʑDăÔʑp|Ôʑn̠~Ôʑ{wÔʑÔ ʒÔ ʑ Ô ʑÔ ʒĔ ʑÔ ʑÔ ʑŔ ʆʅʀΦ Փ  '=3ʂBß9¼<Ͷ?дCӷ?ָux? ƨlʀ Ċ{ʑݿÕʑDăÔʑp|Ôʑn̠~Ôʑ{wÔʑÔ ʒÔ ʑ Ô ʑÔ ʒĔ ʑÔ ʑÔ ʑŔ ʆʅʀΦ Փ  '=3ʂBß9¼<Ͷ?дCӷ?ָux? ƨlʀ Ċ{ʑݿÕʑDăÔʑp|Ôʑn̠~Ôʑ{wÔʑÔ ʒÔ ʑ Ô ʑÔ ʒĔ ʑÔ ʑÔ ʑŔ ʆʅʀΦ Փ  l8mk(_ccccccccccco`oItXtYtXtWtXtUt$titotmtmtmtmtmtmtmtmtmtmtmtmtmtmtmtmtmtmoj3yy4it32-ɼM¾cſNOP»ǾRüʿQƾʿUǾʿUɿʿUʾUùʾUŻʾWȼʾXɿʾWʾX ¶ʾY ķʾ\ ĸʾ] źʾ\ ƺʾ\ ǻʾ^ Ǽʾ^Ⱦzyʾa ʿ~xspooqtuz~ʿ_~ywv y{~`"ĹZ"ǽ+"¸$ǿ <ˀ Ղ ܀݁ £ȣˣϣӣڣݣ~~~~~~~~~ہӅȁ~~iH~~kM~~F41.ׁ~~WA~~U@~~U@~~U@ ~~U@V~~~U@Er~~U@j}~~U@~~U@!~~U@!~~U@~~U@MJ{~~P:o~~EsHE~~뻿~~~~~~~~~ ~ɼځѻҁں ü~ ~ށÀց ~~~~~~~~뻿~~~~~~뻿~~~~~~~ ~ʽځҼҁڻ ý~ ~܁Կԁܾ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ɼM¾cſNOP»ǾRüʿQƾʿUǾʿUɿʿUʾUùʾUŻʾWȼʾXɿʾWʾX ¶ʾY ķʾ\ ĸʾ] źʾ\ ƺʾ\ ǻʾ^ Ǽʾ^Ⱦzyʾa ʿ~xspooqtuz~ʿ_~ywv y{~`"ĹZ"ǽ+"¸$ǿ <ˀ Ղ ܀݁ £ȣˣϣӣڣݣ~~~~~~~~~ہӅȁ~~iH~~kM~~F41.ׁ~~WA~~U@~~U@~~U@ ~~U@V~~~U@Er~~U@j}~~U@~~U@!~~U@!~~U@~~U@MJ{~~P:o~~EsHE~~뻿~~~~~~~~~ ~ɼځѻҁں ü~ ~ށÀց ~~~~~~~~뻿~~~~~~뻿~~~~~~~ ~ʽځҼҁڻ ý~ ~܁Կԁܾ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ɼM¾cſNOP»ǾRüʿQƾʿUǾʿUɿʿUʾUùʾUŻʾWȼʾXɿʾWʾX ¶ʾY ķʾ\ ĸʾ] źʾ\ ƺʾ\ ǻʾ^ Ǽʾ^Ⱦzyʾa ʿ~xspooqtuz~ʿ_~ywv y{~`"ĹZ"ǽ+"¸$ǿ <ˀ Ղ ܀݁ £ȣˣϣӣڣݣ~~~~~~~~~ہӅȁ~~iH~~kM~~F41.ׁ~~WA~~U@~~U@~~U@ ~~U@V~~~U@Er~~U@j}~~U@~~U@!~~U@!~~U@~~U@MJ{~~P:o~~EsHE~~뻿~~~~~~~~~ ~ɼځѻҁں ü~ ~ށÀց ~~~~~~~~뻿~~~~~~뻿~~~~~~~ ~ʽځҼҁڻ ý~ ~܁Կԁܾ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~t8mk@#056777777777777777777777777777777777777777777777777640'  !ӆ> /= 9_Bq#Ja&M`(O_(O`(O^(O_(O`(O_(O`(O`(O`(N_(N`(N_(Ma(M`(M_(M^(L^(L^(L\(L\(L[(LZ(KZ(KA(K+(J! (JC(J#(I1(I< (IC(IH"(IK$(IM&(IM&(HM'(HM'(HM'(HM'(GM'(GM((GM((GM((GL((GL((GL((GL((GL((GL((GL((FL((FL((FL((FK((FK((FK((FK((FJ((FJ((FJ((FJ((FJ((FJ((FI((FI((FI((FI((FI((FI((FI((EI((EI((EI((EH((EH((EH((EH((EH((EH((EH((EG((EG((DG((DG((DG((DG((DG((DF((DF((DF((DF((DF((DF((DF((DF((DF((DF((DF((CF((CF((CF((CF((CF((CE((CE((CE((CE((CE((CE((CE(&AC&#<=#3Mapz{qbN4 ([W>@Q@pp-QޣoppQNppMeH?ppNgppQNppN?NE{,ppppLpoooop,ppppLpoooop,pppp pp pp pp pw wouopopowo͕Ϊouopop owou؂πԂ עuppp>[W>@Q@pp-QޣoppQNppMeH?ppNgppQNppN?NE{,ppppLpoooop,ppppLpoooop,pppp pp pp pp pw wouopopowo͕Ϊouopop owou؂πԂ עuppp>[W>@Q@pp-QޣoppQNppMeH?ppNgppQNppN?NE{,ppppLpoooop,ppppLpoooop,pppp pp pp pp pw wouopopowo͕l8mkit32F큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍큍t8mk@icnV Btreesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/000077500000000000000000000000001352107072600226235ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/project.pbxproj000066400000000000000000001027261352107072600257070ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 33306EBF17695BE000AF5315 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33306EBE17695BE000AF5315 /* Cocoa.framework */; }; 33306EC917695BE000AF5315 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 33306EC717695BE000AF5315 /* InfoPlist.strings */; }; 33306EDC17695C5500AF5315 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33306EDB17695C5500AF5315 /* main.cpp */; }; 33306EF017695EE900AF5315 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33306EEB17695EE900AF5315 /* AudioToolbox.framework */; }; 33306EF117695EE900AF5315 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33306EEC17695EE900AF5315 /* Carbon.framework */; }; 33306EF217695EE900AF5315 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33306EED17695EE900AF5315 /* IOKit.framework */; }; 33306EF317695EE900AF5315 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33306EEE17695EE900AF5315 /* OpenGL.framework */; }; 33306EF91769625C00AF5315 /* images in Resources */ = {isa = PBXBuildFile; fileRef = 33306EF51769625C00AF5315 /* images */; }; 33306EFA1769625C00AF5315 /* docs in Resources */ = {isa = PBXBuildFile; fileRef = 33306EF61769625C00AF5315 /* docs */; }; 33306EFB1769625C00AF5315 /* examples in Resources */ = {isa = PBXBuildFile; fileRef = 33306EF71769625C00AF5315 /* examples */; }; 33306EFC1769625C00AF5315 /* readme.html in Resources */ = {isa = PBXBuildFile; fileRef = 33306EF81769625C00AF5315 /* readme.html */; }; 33306EFF1769648100AF5315 /* App.icns in Resources */ = {isa = PBXBuildFile; fileRef = 33306EFE1769648100AF5315 /* App.icns */; }; 33306F01176965AD00AF5315 /* TODO.txt in Resources */ = {isa = PBXBuildFile; fileRef = 33306F00176965AD00AF5315 /* TODO.txt */; }; 33820B9E1E97123400D2DF4E /* translations in Resources */ = {isa = PBXBuildFile; fileRef = 33820B9D1E97123400D2DF4E /* translations */; }; 338633A822F224DE00DB0737 /* idl_gen_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 338633A522F224DE00DB0737 /* idl_gen_text.cpp */; }; 338633A922F224DE00DB0737 /* idl_parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 338633A622F224DE00DB0737 /* idl_parser.cpp */; }; 338633AA22F224DE00DB0737 /* util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 338633A722F224DE00DB0737 /* util.cpp */; }; 338633AC22F2250200DB0737 /* disasm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 338633AB22F2250200DB0737 /* disasm.cpp */; }; 3387001E204C5FE700612ED7 /* vmlog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3387FFE1204C5FE400612ED7 /* vmlog.cpp */; }; 3387002B204C5FE700612ED7 /* vm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3387FFEE204C5FE500612ED7 /* vm.cpp */; }; 3387002E204C5FE700612ED7 /* compiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3387FFF1204C5FE500612ED7 /* compiler.cpp */; }; 3387002F204C5FE700612ED7 /* builtins.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3387FFF2204C5FE500612ED7 /* builtins.cpp */; }; 33870031204C5FE700612ED7 /* file.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3387FFF4204C5FE600612ED7 /* file.cpp */; }; 33870032204C5FE700612ED7 /* vmdata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3387FFF5204C5FE600612ED7 /* vmdata.cpp */; }; 33870034204C5FE700612ED7 /* lobsterreader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33870019204C5FE600612ED7 /* lobsterreader.cpp */; }; 33870037204C5FE700612ED7 /* platform.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3387001C204C5FE600612ED7 /* platform.cpp */; }; 3387003A204C67AF00612ED7 /* scripts in Resources */ = {isa = PBXBuildFile; fileRef = 33870039204C67AE00612ED7 /* scripts */; }; 3387FFDF204C5FBF00612ED7 /* lobster_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3387FFDC204C5FBE00612ED7 /* lobster_impl.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 33306EBB17695BE000AF5315 /* TreeSheets.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TreeSheets.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33306EBE17695BE000AF5315 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 33306EC117695BE000AF5315 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 33306EC217695BE000AF5315 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 33306EC317695BE000AF5315 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 33306EC617695BE000AF5315 /* TreeSheets-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TreeSheets-Info.plist"; sourceTree = ""; }; 33306EC817695BE000AF5315 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 33306EDB17695C5500AF5315 /* main.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = main.cpp; path = ../../src/main.cpp; sourceTree = ""; }; 33306EDD17695C7500AF5315 /* cell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cell.h; path = ../../src/cell.h; sourceTree = ""; }; 33306EDE17695C7500AF5315 /* document.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = document.h; path = ../../src/document.h; sourceTree = ""; }; 33306EDF17695C7500AF5315 /* evaluator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = evaluator.h; path = ../../src/evaluator.h; sourceTree = ""; }; 33306EE017695C7500AF5315 /* grid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = grid.h; path = ../../src/grid.h; sourceTree = ""; }; 33306EE117695C7500AF5315 /* myapp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = myapp.h; path = ../../src/myapp.h; sourceTree = ""; }; 33306EE217695C7500AF5315 /* mycanvas.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mycanvas.h; path = ../../src/mycanvas.h; sourceTree = ""; }; 33306EE317695C7500AF5315 /* myevents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = myevents.h; path = ../../src/myevents.h; sourceTree = ""; }; 33306EE417695C7500AF5315 /* myframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = myframe.h; path = ../../src/myframe.h; sourceTree = ""; }; 33306EE517695C7500AF5315 /* mywxtools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mywxtools.h; path = ../../src/mywxtools.h; sourceTree = ""; }; 33306EE617695C7500AF5315 /* selection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = selection.h; path = ../../src/selection.h; sourceTree = ""; }; 33306EE717695C7500AF5315 /* stdafx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stdafx.h; path = ../../src/stdafx.h; sourceTree = ""; }; 33306EE817695C7500AF5315 /* system.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = system.h; path = ../../src/system.h; sourceTree = ""; }; 33306EE917695C7500AF5315 /* text.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = text.h; path = ../../src/text.h; sourceTree = ""; }; 33306EEA17695C7500AF5315 /* tools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = tools.h; path = ../../src/tools.h; sourceTree = ""; }; 33306EEB17695EE900AF5315 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 33306EEC17695EE900AF5315 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; 33306EED17695EE900AF5315 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 33306EEE17695EE900AF5315 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; 33306EEF17695EE900AF5315 /* QuickTime.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickTime.framework; path = System/Library/Frameworks/QuickTime.framework; sourceTree = SDKROOT; }; 33306EF51769625C00AF5315 /* images */ = {isa = PBXFileReference; lastKnownFileType = folder; name = images; path = ../../TS/images; sourceTree = ""; }; 33306EF61769625C00AF5315 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = docs; path = ../../TS/docs; sourceTree = ""; }; 33306EF71769625C00AF5315 /* examples */ = {isa = PBXFileReference; lastKnownFileType = folder; name = examples; path = ../../TS/examples; sourceTree = ""; }; 33306EF81769625C00AF5315 /* readme.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = readme.html; path = ../../TS/readme.html; sourceTree = ""; }; 33306EFE1769648100AF5315 /* App.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = App.icns; sourceTree = ""; }; 33306F00176965AD00AF5315 /* TODO.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = TODO.txt; path = ../../TODO.txt; sourceTree = ""; }; 33820B9D1E97123400D2DF4E /* translations */ = {isa = PBXFileReference; lastKnownFileType = folder; name = translations; path = ../../TS/translations; sourceTree = ""; }; 338633A522F224DE00DB0737 /* idl_gen_text.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = idl_gen_text.cpp; path = ../../../lobster/external/flatbuffers/src/idl_gen_text.cpp; sourceTree = ""; }; 338633A622F224DE00DB0737 /* idl_parser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = idl_parser.cpp; path = ../../../lobster/external/flatbuffers/src/idl_parser.cpp; sourceTree = ""; }; 338633A722F224DE00DB0737 /* util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = util.cpp; path = ../../../lobster/external/flatbuffers/src/util.cpp; sourceTree = ""; }; 338633AB22F2250200DB0737 /* disasm.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = disasm.cpp; path = ../../lobster/src/disasm.cpp; sourceTree = ""; }; 33870001204C5FE600612ED7 /* ttypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ttypes.h; sourceTree = ""; }; 33870002204C5FE600612ED7 /* vmdata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vmdata.h; sourceTree = ""; }; 33870003204C5FE600612ED7 /* node.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = node.h; sourceTree = ""; }; 33870004204C5FE600612ED7 /* wentropy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wentropy.h; sourceTree = ""; }; 33870008204C5FE600612ED7 /* codegen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = codegen.h; sourceTree = ""; }; 33870009204C5FE600612ED7 /* unicode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = unicode.h; sourceTree = ""; }; 3387000A204C5FE600612ED7 /* optimizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = optimizer.h; sourceTree = ""; }; 3387000D204C5FE600612ED7 /* lex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lex.h; sourceTree = ""; }; 3387000E204C5FE600612ED7 /* disasm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = disasm.h; sourceTree = ""; }; 3387000F204C5FE600612ED7 /* tools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tools.h; sourceTree = ""; }; 33870010204C5FE600612ED7 /* idents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = idents.h; sourceTree = ""; }; 33870012204C5FE600612ED7 /* vm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vm.h; sourceTree = ""; }; 33870013204C5FE600612ED7 /* bytecode_generated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bytecode_generated.h; sourceTree = ""; }; 33870014204C5FE600612ED7 /* platform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = platform.h; sourceTree = ""; }; 33870015204C5FE600612ED7 /* il.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = il.h; sourceTree = ""; }; 33870018204C5FE600612ED7 /* geom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = geom.h; sourceTree = ""; }; 33870019204C5FE600612ED7 /* lobsterreader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lobsterreader.cpp; path = ../../lobster/src/lobsterreader.cpp; sourceTree = ""; }; 3387001C204C5FE600612ED7 /* platform.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = platform.cpp; path = ../../lobster/src/platform.cpp; sourceTree = ""; }; 33870039204C67AE00612ED7 /* scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; name = scripts; path = ../../TS/scripts; sourceTree = ""; }; 3387FFDC204C5FBE00612ED7 /* lobster_impl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = lobster_impl.cpp; path = ../../src/lobster_impl.cpp; sourceTree = ""; }; 3387FFDD204C5FBF00612ED7 /* script_interface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = script_interface.h; path = ../../src/script_interface.h; sourceTree = ""; }; 3387FFDE204C5FBF00612ED7 /* treesheets_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = treesheets_impl.h; path = ../../src/treesheets_impl.h; sourceTree = ""; }; 3387FFE1204C5FE400612ED7 /* vmlog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vmlog.cpp; path = ../../lobster/src/vmlog.cpp; sourceTree = ""; }; 3387FFEE204C5FE500612ED7 /* vm.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vm.cpp; path = ../../lobster/src/vm.cpp; sourceTree = ""; }; 3387FFF1204C5FE500612ED7 /* compiler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = compiler.cpp; path = ../../lobster/src/compiler.cpp; sourceTree = ""; }; 3387FFF2204C5FE500612ED7 /* builtins.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = builtins.cpp; path = ../../lobster/src/builtins.cpp; sourceTree = ""; }; 3387FFF4204C5FE600612ED7 /* file.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = file.cpp; path = ../../lobster/src/file.cpp; sourceTree = ""; }; 3387FFF5204C5FE600612ED7 /* vmdata.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = vmdata.cpp; path = ../../lobster/src/vmdata.cpp; sourceTree = ""; }; 3387FFF8204C5FE600612ED7 /* slaballoc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = slaballoc.h; sourceTree = ""; }; 3387FFF9204C5FE600612ED7 /* typecheck.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = typecheck.h; sourceTree = ""; }; 3387FFFA204C5FE600612ED7 /* tocpp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tocpp.h; sourceTree = ""; }; 3387FFFC204C5FE600612ED7 /* stdafx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stdafx.h; sourceTree = ""; }; 3387FFFD204C5FE600612ED7 /* compiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = compiler.h; sourceTree = ""; }; 3387FFFE204C5FE600612ED7 /* natreg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = natreg.h; sourceTree = ""; }; 3387FFFF204C5FE600612ED7 /* parser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parser.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33306EB817695BE000AF5315 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 33306EF017695EE900AF5315 /* AudioToolbox.framework in Frameworks */, 33306EF117695EE900AF5315 /* Carbon.framework in Frameworks */, 33306EF217695EE900AF5315 /* IOKit.framework in Frameworks */, 33306EF317695EE900AF5315 /* OpenGL.framework in Frameworks */, 33306EBF17695BE000AF5315 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33306EB217695BE000AF5315 = { isa = PBXGroup; children = ( 33870039204C67AE00612ED7 /* scripts */, 3387FFDB204C5F9200612ED7 /* ScriptBinding */, 33820B9D1E97123400D2DF4E /* translations */, 33306F00176965AD00AF5315 /* TODO.txt */, 33306EFE1769648100AF5315 /* App.icns */, 33306EFD1769626D00AF5315 /* bundle resources */, 33306EDD17695C7500AF5315 /* cell.h */, 33306EDE17695C7500AF5315 /* document.h */, 33306EDF17695C7500AF5315 /* evaluator.h */, 33306EE017695C7500AF5315 /* grid.h */, 33306EE117695C7500AF5315 /* myapp.h */, 33306EE217695C7500AF5315 /* mycanvas.h */, 33306EE317695C7500AF5315 /* myevents.h */, 33306EE417695C7500AF5315 /* myframe.h */, 33306EE517695C7500AF5315 /* mywxtools.h */, 33306EE617695C7500AF5315 /* selection.h */, 33306EE717695C7500AF5315 /* stdafx.h */, 33306EE817695C7500AF5315 /* system.h */, 33306EE917695C7500AF5315 /* text.h */, 33306EEA17695C7500AF5315 /* tools.h */, 33306EDB17695C5500AF5315 /* main.cpp */, 33306EC417695BE000AF5315 /* TreeSheets */, 33306EBD17695BE000AF5315 /* Frameworks */, 33306EBC17695BE000AF5315 /* Products */, ); sourceTree = ""; }; 33306EBC17695BE000AF5315 /* Products */ = { isa = PBXGroup; children = ( 33306EBB17695BE000AF5315 /* TreeSheets.app */, ); name = Products; sourceTree = ""; }; 33306EBD17695BE000AF5315 /* Frameworks */ = { isa = PBXGroup; children = ( 33306EEB17695EE900AF5315 /* AudioToolbox.framework */, 33306EEC17695EE900AF5315 /* Carbon.framework */, 33306EED17695EE900AF5315 /* IOKit.framework */, 33306EEE17695EE900AF5315 /* OpenGL.framework */, 33306EEF17695EE900AF5315 /* QuickTime.framework */, 33306EBE17695BE000AF5315 /* Cocoa.framework */, 33306EC017695BE000AF5315 /* Other Frameworks */, ); name = Frameworks; sourceTree = ""; }; 33306EC017695BE000AF5315 /* Other Frameworks */ = { isa = PBXGroup; children = ( 33306EC117695BE000AF5315 /* AppKit.framework */, 33306EC217695BE000AF5315 /* CoreData.framework */, 33306EC317695BE000AF5315 /* Foundation.framework */, ); name = "Other Frameworks"; sourceTree = ""; }; 33306EC417695BE000AF5315 /* TreeSheets */ = { isa = PBXGroup; children = ( 33306EC517695BE000AF5315 /* Supporting Files */, ); path = TreeSheets; sourceTree = ""; }; 33306EC517695BE000AF5315 /* Supporting Files */ = { isa = PBXGroup; children = ( 33306EC617695BE000AF5315 /* TreeSheets-Info.plist */, 33306EC717695BE000AF5315 /* InfoPlist.strings */, ); name = "Supporting Files"; sourceTree = ""; }; 33306EFD1769626D00AF5315 /* bundle resources */ = { isa = PBXGroup; children = ( 33306EF51769625C00AF5315 /* images */, 33306EF61769625C00AF5315 /* docs */, 33306EF71769625C00AF5315 /* examples */, 33306EF81769625C00AF5315 /* readme.html */, ); name = "bundle resources"; sourceTree = ""; }; 338633A422F224BD00DB0737 /* flatbuffers */ = { isa = PBXGroup; children = ( 338633A522F224DE00DB0737 /* idl_gen_text.cpp */, 338633A622F224DE00DB0737 /* idl_parser.cpp */, 338633A722F224DE00DB0737 /* util.cpp */, ); path = flatbuffers; sourceTree = ""; }; 3387FFDB204C5F9200612ED7 /* ScriptBinding */ = { isa = PBXGroup; children = ( 3387FFE0204C5FC800612ED7 /* Lobster */, 3387FFDC204C5FBE00612ED7 /* lobster_impl.cpp */, 3387FFDD204C5FBF00612ED7 /* script_interface.h */, 3387FFDE204C5FBF00612ED7 /* treesheets_impl.h */, ); name = ScriptBinding; sourceTree = ""; }; 3387FFE0204C5FC800612ED7 /* Lobster */ = { isa = PBXGroup; children = ( 338633AB22F2250200DB0737 /* disasm.cpp */, 338633A422F224BD00DB0737 /* flatbuffers */, 3387FFF2204C5FE500612ED7 /* builtins.cpp */, 3387FFF1204C5FE500612ED7 /* compiler.cpp */, 3387FFF4204C5FE600612ED7 /* file.cpp */, 3387FFF7204C5FE600612ED7 /* lobster */, 33870019204C5FE600612ED7 /* lobsterreader.cpp */, 3387001C204C5FE600612ED7 /* platform.cpp */, 3387FFEE204C5FE500612ED7 /* vm.cpp */, 3387FFF5204C5FE600612ED7 /* vmdata.cpp */, 3387FFE1204C5FE400612ED7 /* vmlog.cpp */, ); name = Lobster; sourceTree = ""; }; 3387FFF7204C5FE600612ED7 /* lobster */ = { isa = PBXGroup; children = ( 3387FFF8204C5FE600612ED7 /* slaballoc.h */, 3387FFF9204C5FE600612ED7 /* typecheck.h */, 3387FFFA204C5FE600612ED7 /* tocpp.h */, 3387FFFC204C5FE600612ED7 /* stdafx.h */, 3387FFFD204C5FE600612ED7 /* compiler.h */, 3387FFFE204C5FE600612ED7 /* natreg.h */, 3387FFFF204C5FE600612ED7 /* parser.h */, 33870001204C5FE600612ED7 /* ttypes.h */, 33870002204C5FE600612ED7 /* vmdata.h */, 33870003204C5FE600612ED7 /* node.h */, 33870004204C5FE600612ED7 /* wentropy.h */, 33870008204C5FE600612ED7 /* codegen.h */, 33870009204C5FE600612ED7 /* unicode.h */, 3387000A204C5FE600612ED7 /* optimizer.h */, 3387000D204C5FE600612ED7 /* lex.h */, 3387000E204C5FE600612ED7 /* disasm.h */, 3387000F204C5FE600612ED7 /* tools.h */, 33870010204C5FE600612ED7 /* idents.h */, 33870012204C5FE600612ED7 /* vm.h */, 33870013204C5FE600612ED7 /* bytecode_generated.h */, 33870014204C5FE600612ED7 /* platform.h */, 33870015204C5FE600612ED7 /* il.h */, 33870018204C5FE600612ED7 /* geom.h */, ); name = lobster; path = ../../lobster/src/lobster; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33306EBA17695BE000AF5315 /* TreeSheets */ = { isa = PBXNativeTarget; buildConfigurationList = 33306ED817695BE000AF5315 /* Build configuration list for PBXNativeTarget "TreeSheets" */; buildPhases = ( 33306EB717695BE000AF5315 /* Sources */, 33306EB817695BE000AF5315 /* Frameworks */, 33306EB917695BE000AF5315 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = TreeSheets; productName = TreeSheets; productReference = 33306EBB17695BE000AF5315 /* TreeSheets.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33306EB317695BE000AF5315 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0460; ORGANIZATIONNAME = "Wouter van Oortmerssen"; }; buildConfigurationList = 33306EB617695BE000AF5315 /* Build configuration list for PBXProject "TreeSheets" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( English, en, ); mainGroup = 33306EB217695BE000AF5315; productRefGroup = 33306EBC17695BE000AF5315 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33306EBA17695BE000AF5315 /* TreeSheets */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33306EB917695BE000AF5315 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3387003A204C67AF00612ED7 /* scripts in Resources */, 33820B9E1E97123400D2DF4E /* translations in Resources */, 33306EF91769625C00AF5315 /* images in Resources */, 33306EFA1769625C00AF5315 /* docs in Resources */, 33306EFB1769625C00AF5315 /* examples in Resources */, 33306EFC1769625C00AF5315 /* readme.html in Resources */, 33306EC917695BE000AF5315 /* InfoPlist.strings in Resources */, 33306EFF1769648100AF5315 /* App.icns in Resources */, 33306F01176965AD00AF5315 /* TODO.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33306EB717695BE000AF5315 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 338633AC22F2250200DB0737 /* disasm.cpp in Sources */, 338633A822F224DE00DB0737 /* idl_gen_text.cpp in Sources */, 3387002E204C5FE700612ED7 /* compiler.cpp in Sources */, 33870037204C5FE700612ED7 /* platform.cpp in Sources */, 33870034204C5FE700612ED7 /* lobsterreader.cpp in Sources */, 33306EDC17695C5500AF5315 /* main.cpp in Sources */, 33870031204C5FE700612ED7 /* file.cpp in Sources */, 3387001E204C5FE700612ED7 /* vmlog.cpp in Sources */, 3387FFDF204C5FBF00612ED7 /* lobster_impl.cpp in Sources */, 338633A922F224DE00DB0737 /* idl_parser.cpp in Sources */, 338633AA22F224DE00DB0737 /* util.cpp in Sources */, 3387002B204C5FE700612ED7 /* vm.cpp in Sources */, 3387002F204C5FE700612ED7 /* builtins.cpp in Sources */, 33870032204C5FE700612ED7 /* vmdata.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 33306EC717695BE000AF5315 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 33306EC817695BE000AF5315 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 33306ED617695BE000AF5315 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = YES; ARCHS = "$(ARCHS_STANDARD)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( ../../lobster/src, ../../lobster/include, ); LIBRARY_SEARCH_PATHS = ""; MACOSX_DEPLOYMENT_TARGET = 10.7; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; USER_HEADER_SEARCH_PATHS = "../../lobster/src ../../lobster/include"; VALID_ARCHS = x86_64; }; name = Debug; }; 33306ED717695BE000AF5315 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = YES; ARCHS = "$(ARCHS_STANDARD)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ( ../../lobster/src, ../../lobster/include, ); LIBRARY_SEARCH_PATHS = ""; MACOSX_DEPLOYMENT_TARGET = 10.7; SDKROOT = macosx; USER_HEADER_SEARCH_PATHS = "../../lobster/src ../../lobster/include"; VALID_ARCHS = x86_64; }; name = Release; }; 33306ED917695BE000AF5315 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD)"; CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; COMBINE_HIDPI_IMAGES = YES; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_PREFIX_HEADER = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "_DEBUG=1", "DEBUG=1", "$(inherited)", "_FILE_OFFSET_BITS=64", __WXMAC__, __WXOSX__, __WXOSX_COCOA__, ); GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; GCC_WARN_PEDANTIC = NO; HEADER_SEARCH_PATHS = ( "/usr/local/lib/wx/include/osx_cocoa-unicode-static-3.1", "/usr/local/include/wx-3.1", ); INFOPLIST_FILE = "TreeSheets/TreeSheets-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.7; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ( "-L/usr/local/lib", "/usr/local/lib/libwx_osx_cocoau_xrc-3.1.a", "/usr/local/lib/libwx_osx_cocoau_webview-3.1.a", "/usr/local/lib/libwx_osx_cocoau_qa-3.1.a", "/usr/local/lib/libwx_baseu_net-3.1.a", "/usr/local/lib/libwx_osx_cocoau_html-3.1.a", "/usr/local/lib/libwx_osx_cocoau_adv-3.1.a", "/usr/local/lib/libwx_osx_cocoau_core-3.1.a", "/usr/local/lib/libwx_baseu_xml-3.1.a", "/usr/local/lib/libwx_osx_cocoau_aui-3.1.a", "/usr/local/lib/libwx_baseu-3.1.a", "-lexpat", "-lwxregexu-3.1", "-lwxjpeg-3.1", "-lwxpng-3.1", "-lz", "-lpthread", "-liconv", ); PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; VALID_ARCHS = x86_64; WARNING_CFLAGS = ""; WRAPPER_EXTENSION = app; }; name = Debug; }; 33306EDA17695BE000AF5315 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD)"; CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; COMBINE_HIDPI_IMAGES = YES; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_PREFIX_HEADER = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "_FILE_OFFSET_BITS=64", __WXMAC__, __WXOSX__, __WXOSX_COCOA__, ); GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; GCC_WARN_PEDANTIC = NO; HEADER_SEARCH_PATHS = ( "/usr/local/lib/wx/include/osx_cocoa-unicode-static-3.1", "/usr/local/include/wx-3.1", ); INFOPLIST_FILE = "TreeSheets/TreeSheets-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.7; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ( "-L/usr/local/lib", "/usr/local/lib/libwx_osx_cocoau_xrc-3.1.a", "/usr/local/lib/libwx_osx_cocoau_webview-3.1.a", "/usr/local/lib/libwx_osx_cocoau_qa-3.1.a", "/usr/local/lib/libwx_baseu_net-3.1.a", "/usr/local/lib/libwx_osx_cocoau_html-3.1.a", "/usr/local/lib/libwx_osx_cocoau_adv-3.1.a", "/usr/local/lib/libwx_osx_cocoau_core-3.1.a", "/usr/local/lib/libwx_baseu_xml-3.1.a", "/usr/local/lib/libwx_osx_cocoau_aui-3.1.a", "/usr/local/lib/libwx_baseu-3.1.a", "-lexpat", "-lwxregexu-3.1", "-lwxjpeg-3.1", "-lwxpng-3.1", "-lz", "-lpthread", "-liconv", ); PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; VALID_ARCHS = x86_64; WARNING_CFLAGS = ""; WRAPPER_EXTENSION = app; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33306EB617695BE000AF5315 /* Build configuration list for PBXProject "TreeSheets" */ = { isa = XCConfigurationList; buildConfigurations = ( 33306ED617695BE000AF5315 /* Debug */, 33306ED717695BE000AF5315 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33306ED817695BE000AF5315 /* Build configuration list for PBXNativeTarget "TreeSheets" */ = { isa = XCConfigurationList; buildConfigurations = ( 33306ED917695BE000AF5315 /* Debug */, 33306EDA17695BE000AF5315 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33306EB317695BE000AF5315 /* Project object */; } treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/project.xcworkspace/000077500000000000000000000000001352107072600266215ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/project.xcworkspace/contents.xcworkspacedata000066400000000000000000000002331352107072600335610ustar00rootroot00000000000000 treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/project.xcworkspace/xcshareddata/000077500000000000000000000000001352107072600312545ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/project.xcworkspace/xcshareddata/.gitignore000066400000000000000000000000271352107072600332430ustar00rootroot00000000000000/TreeSheets.xccheckout IDEWorkspaceChecks.plist000066400000000000000000000003561352107072600356570ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/project.xcworkspace/xcshareddata IDEDidComputeMac32BitWarning treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/xcuserdata/000077500000000000000000000000001352107072600247665ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/xcuserdata/aardappel.xcuserdatad/000077500000000000000000000000001352107072600312255ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/xcuserdata/aardappel.xcuserdatad/xcdebugger/000077500000000000000000000000001352107072600333445ustar00rootroot00000000000000.gitignore000066400000000000000000000000631352107072600352540ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/xcuserdata/aardappel.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist /Breakpoints_v2.xcbkptlist treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/xcuserdata/aardappel.xcuserdatad/xcschemes/000077500000000000000000000000001352107072600332075ustar00rootroot00000000000000TreeSheets release.xcscheme000066400000000000000000000064151352107072600403330ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/xcuserdata/aardappel.xcuserdatad/xcschemes TreeSheets.xcscheme000066400000000000000000000064131352107072600367300ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/xcuserdata/aardappel.xcuserdatad/xcschemes xcschememanagement.plist000066400000000000000000000011161352107072600400400ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets.xcodeproj/xcuserdata/aardappel.xcuserdatad/xcschemes SchemeUserState TreeSheets release.xcscheme orderHint 1 TreeSheets.xcscheme orderHint 0 SuppressBuildableAutocreation 33306EBA17695BE000AF5315 primary treesheets-1.0.2/osx/TreeSheets/TreeSheets/000077500000000000000000000000001352107072600206275ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets/TreeSheets-Info.plist000066400000000000000000000030551352107072600246530ustar00rootroot00000000000000 NSHighResolutionCapable CFBundleDevelopmentRegion en CFBundleDocumentTypes CFBundleTypeExtensions cts CFBundleTypeIconFile App CFBundleTypeName TreeSheets CFBundleTypeRole Editor CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile App CFBundleIdentifier dot3labs.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSApplicationCategoryType public.app-category.productivity LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSHumanReadableCopyright Copyright © 2013 Wouter van Oortmerssen. All rights reserved. NSMainNibFile MainMenu NSPrincipalClass NSApplication treesheets-1.0.2/osx/TreeSheets/TreeSheets/en.lproj/000077500000000000000000000000001352107072600223565ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheets/TreeSheets/en.lproj/InfoPlist.strings000066400000000000000000000000551352107072600257000ustar00rootroot00000000000000/* Localized versions of Info.plist keys */ treesheets-1.0.2/osx/TreeSheetsBeta/000077500000000000000000000000001352107072600173505ustar00rootroot00000000000000treesheets-1.0.2/osx/TreeSheetsBeta/readme_first_macosx.txt000077500000000000000000000010561352107072600241340ustar00rootroot00000000000000TreeSheets for Mac OS X Beta (treesheets.com) This is mostly a direct port from the windows/linux version, and does NOT always adhere to Mac UI standards! Do NOT use this software if you want a polished experience. It is Beta quality. The main functionality all works, so ok for those who want to be using TreeSheets on a Mac also, but expect things to work differently in the UI (mostly due to how wxWidgets implements them). Thanks to Robert Pointon (fernlightning.com) for his help getting the initial port done. Wouter van Oortmerssen (strlen.com) treesheets-1.0.2/src/000077500000000000000000000000001352107072600144575ustar00rootroot00000000000000treesheets-1.0.2/src/Makefile000066400000000000000000000027401352107072600161220ustar00rootroot00000000000000# Created by: lightside # This file released into the public domain WX_CONFIG?= wx-config WX_CXXFLAGS= `$(WX_CONFIG) --cxxflags` WX_LIBS= `$(WX_CONFIG) --libs aui adv core xml net` PACKAGE_VERSION?= __DATE__ CXX?= c++ CXXFLAGS+= -DPACKAGE_VERSION=$(PACKAGE_VERSION) -std=c++17 $(WX_CXXFLAGS) -I ../lobster/include -I ../lobster/src LDFLAGS+= $(WX_LIBS) ARCH= $(firstword $(shell uname -m)) SYS= $(firstword $(shell uname -s)) DISTFILE?= $(DISTNAME)$(DISTEXT) DISTNAME?= $(APPNAME)_$(SYS)_$(ARCH) DISTEXT?= .tar.gz DIST_CMD?= tar DIST_ARGS?= -caf APPNAME= treesheets SRCS= main.cpp \ lobster_impl.cpp \ ../lobster/src/builtins.cpp \ ../lobster/src/compiler.cpp \ ../lobster/src/file.cpp \ ../lobster/src/lobsterreader.cpp \ ../lobster/src/platform.cpp \ ../lobster/src/vm.cpp \ ../lobster/src/vmdata.cpp \ ../lobster/src/vmlog.cpp OBJS= $(SRCS:.cpp=.o) all: $(SRCS) $(APPNAME) release: CXXFLAGS+= -O3 release: install clean debug: CXXFLAGS+= -g debug: install clean $(APPNAME): $(OBJS) $(CXX) $(OBJS) $(LDFLAGS) -o $@ install: all cp -f $(APPNAME) ../TS deinstall: rm -f ../TS/$(APPNAME) clean: rm -f $(APPNAME) *.o dist-release: release dist dist-debug: debug dist dist-all: install clean dist dist: cd .. && $(DIST_CMD) $(DIST_ARGS) $(DISTFILE) TS dist-clean: cd .. && rm -f $(DISTFILE) .PHONY: all debug release clean install deinstall dist dist-all dist-debug \ dist-release dist-clean treesheets-1.0.2/src/StackWalker/000077500000000000000000000000001352107072600166725ustar00rootroot00000000000000treesheets-1.0.2/src/StackWalker/StackWalker.cpp000066400000000000000000001404421352107072600216160ustar00rootroot00000000000000/********************************************************************** * * StackWalker.cpp * http://stackwalker.codeplex.com/ * * * History: * 2005-07-27 v1 - First public release on http://www.codeproject.com/ * http://www.codeproject.com/threads/StackWalker.asp * 2005-07-28 v2 - Changed the params of the constructor and ShowCallstack * (to simplify the usage) * 2005-08-01 v3 - Changed to use 'CONTEXT_FULL' instead of CONTEXT_ALL * (should also be enough) * - Changed to compile correctly with the PSDK of VC7.0 * (GetFileVersionInfoSizeA and GetFileVersionInfoA is wrongly defined: * it uses LPSTR instead of LPCSTR as first paremeter) * - Added declarations to support VC5/6 without using 'dbghelp.h' * - Added a 'pUserData' member to the ShowCallstack function and the * PReadProcessMemoryRoutine declaration (to pass some user-defined data, * which can be used in the readMemoryFunction-callback) * 2005-08-02 v4 - OnSymInit now also outputs the OS-Version by default * - Added example for doing an exception-callstack-walking in main.cpp * (thanks to owillebo: http://www.codeproject.com/script/profile/whos_who.asp?id=536268) * 2005-08-05 v5 - Removed most Lint (http://www.gimpel.com/) errors... thanks to Okko Willeboordse! * 2008-08-04 v6 - Fixed Bug: Missing LEAK-end-tag * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=2502890#xx2502890xx * Fixed Bug: Compiled with "WIN32_LEAN_AND_MEAN" * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=1824718#xx1824718xx * Fixed Bug: Compiling with "/Wall" * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=2638243#xx2638243xx * Fixed Bug: Now checking SymUseSymSrv * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1388979#xx1388979xx * Fixed Bug: Support for recursive function calls * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1434538#xx1434538xx * Fixed Bug: Missing FreeLibrary call in "GetModuleListTH32" * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1326923#xx1326923xx * Fixed Bug: SymDia is number 7, not 9! * 2008-09-11 v7 For some (undocumented) reason, dbhelp.h is needing a packing of 8! * Thanks to Teajay which reported the bug... * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=2718933#xx2718933xx * 2008-11-27 v8 Debugging Tools for Windows are now stored in a different directory * Thanks to Luiz Salamon which reported this "bug"... * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=2822736#xx2822736xx * 2009-04-10 v9 License slihtly corrected ( replaced) * 2009-11-01 v10 Moved to http://stackwalker.codeplex.com/ * 2009-11-02 v11 Now try to use IMAGEHLP_MODULE64_V3 if available * 2010-04-15 v12 Added support for VS2010 RTM * 2010-05-25 v13 Now using secure MyStrcCpy. Thanks to luke.simon: * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=3477467#xx3477467xx * 2013-01-07 v14 Runtime Check Error VS2010 Debug Builds fixed: * http://stackwalker.codeplex.com/workitem/10511 * * * LICENSE (http://www.opensource.org/licenses/bsd-license.php) * * Copyright (c) 2005-2013, Jochen Kalmbach * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * Neither the name of Jochen Kalmbach nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **********************************************************************/ #include #include #include #include #pragma comment(lib, "version.lib") // for "VerQueryValue" #pragma warning(disable:4826) #include "StackWalker.h" // If VC7 and later, then use the shipped 'dbghelp.h'-file #pragma pack(push,8) #if _MSC_VER >= 1300 #include #else // inline the important dbghelp.h-declarations... typedef enum { SymNone = 0, SymCoff, SymCv, SymPdb, SymExport, SymDeferred, SymSym, SymDia, SymVirtual, NumSymTypes } SYM_TYPE; typedef struct _IMAGEHLP_LINE64 { DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64) PVOID Key; // internal DWORD LineNumber; // line number in file PCHAR FileName; // full filename DWORD64 Address; // first instruction of line } IMAGEHLP_LINE64, *PIMAGEHLP_LINE64; typedef struct _IMAGEHLP_MODULE64 { DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) DWORD64 BaseOfImage; // base load address of module DWORD ImageSize; // virtual size of the loaded module DWORD TimeDateStamp; // date/time stamp from pe header DWORD CheckSum; // checksum from the pe header DWORD NumSyms; // number of symbols in the symbol table SYM_TYPE SymType; // type of symbols loaded CHAR ModuleName[32]; // module name CHAR ImageName[256]; // image name CHAR LoadedImageName[256]; // symbol file name } IMAGEHLP_MODULE64, *PIMAGEHLP_MODULE64; typedef struct _IMAGEHLP_SYMBOL64 { DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_SYMBOL64) DWORD64 Address; // virtual address including dll base address DWORD Size; // estimated size of symbol, can be zero DWORD Flags; // info about the symbols, see the SYMF defines DWORD MaxNameLength; // maximum size of symbol name in 'Name' CHAR Name[1]; // symbol name (null terminated string) } IMAGEHLP_SYMBOL64, *PIMAGEHLP_SYMBOL64; typedef enum { AddrMode1616, AddrMode1632, AddrModeReal, AddrModeFlat } ADDRESS_MODE; typedef struct _tagADDRESS64 { DWORD64 Offset; WORD Segment; ADDRESS_MODE Mode; } ADDRESS64, *LPADDRESS64; typedef struct _KDHELP64 { DWORD64 Thread; DWORD ThCallbackStack; DWORD ThCallbackBStore; DWORD NextCallback; DWORD FramePointer; DWORD64 KiCallUserMode; DWORD64 KeUserCallbackDispatcher; DWORD64 SystemRangeStart; DWORD64 Reserved[8]; } KDHELP64, *PKDHELP64; typedef struct _tagSTACKFRAME64 { ADDRESS64 AddrPC; // program counter ADDRESS64 AddrReturn; // return address ADDRESS64 AddrFrame; // frame pointer ADDRESS64 AddrStack; // stack pointer ADDRESS64 AddrBStore; // backing store pointer PVOID FuncTableEntry; // pointer to pdata/fpo or NULL DWORD64 Params[4]; // possible arguments to the function BOOL Far; // WOW far call BOOL Virtual; // is this a virtual frame? DWORD64 Reserved[3]; KDHELP64 KdHelp; } STACKFRAME64, *LPSTACKFRAME64; typedef BOOL (__stdcall *PREAD_PROCESS_MEMORY_ROUTINE64)( HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead ); typedef PVOID (__stdcall *PFUNCTION_TABLE_ACCESS_ROUTINE64)( HANDLE hProcess, DWORD64 AddrBase ); typedef DWORD64 (__stdcall *PGET_MODULE_BASE_ROUTINE64)( HANDLE hProcess, DWORD64 Address ); typedef DWORD64 (__stdcall *PTRANSLATE_ADDRESS_ROUTINE64)( HANDLE hProcess, HANDLE hThread, LPADDRESS64 lpaddr ); #define SYMOPT_CASE_INSENSITIVE 0x00000001 #define SYMOPT_UNDNAME 0x00000002 #define SYMOPT_DEFERRED_LOADS 0x00000004 #define SYMOPT_NO_CPP 0x00000008 #define SYMOPT_LOAD_LINES 0x00000010 #define SYMOPT_OMAP_FIND_NEAREST 0x00000020 #define SYMOPT_LOAD_ANYTHING 0x00000040 #define SYMOPT_IGNORE_CVREC 0x00000080 #define SYMOPT_NO_UNQUALIFIED_LOADS 0x00000100 #define SYMOPT_FAIL_CRITICAL_ERRORS 0x00000200 #define SYMOPT_EXACT_SYMBOLS 0x00000400 #define SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 0x00000800 #define SYMOPT_IGNORE_NT_SYMPATH 0x00001000 #define SYMOPT_INCLUDE_32BIT_MODULES 0x00002000 #define SYMOPT_PUBLICS_ONLY 0x00004000 #define SYMOPT_NO_PUBLICS 0x00008000 #define SYMOPT_AUTO_PUBLICS 0x00010000 #define SYMOPT_NO_IMAGE_SEARCH 0x00020000 #define SYMOPT_SECURE 0x00040000 #define SYMOPT_DEBUG 0x80000000 #define UNDNAME_COMPLETE (0x0000) // Enable full undecoration #define UNDNAME_NAME_ONLY (0x1000) // Crack only the name for primary declaration; #endif // _MSC_VER < 1300 #pragma pack(pop) // Some missing defines (for VC5/6): #ifndef INVALID_FILE_ATTRIBUTES #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) #endif // secure-CRT_functions are only available starting with VC8 #if _MSC_VER < 1400 #define strcpy_s(dst, len, src) strcpy(dst, src) #define strncpy_s(dst, len, src, maxLen) strncpy(dst, len, src) #define strcat_s(dst, len, src) strcat(dst, src) #define _snprintf_s _snprintf #define _tcscat_s _tcscat #endif static void MyStrCpy(char* szDest, size_t nMaxDestSize, const char* szSrc) { if (nMaxDestSize <= 0) return; strncpy_s(szDest, nMaxDestSize, szSrc, _TRUNCATE); szDest[nMaxDestSize-1] = 0; // INFO: _TRUNCATE will ensure that it is nul-terminated; but with older compilers (<1400) it uses "strncpy" and this does not!) } // MyStrCpy // Normally it should be enough to use 'CONTEXT_FULL' (better would be 'CONTEXT_ALL') #define USED_CONTEXT_FLAGS CONTEXT_FULL class StackWalkerInternal { public: StackWalkerInternal(StackWalker *parent, HANDLE hProcess) { m_parent = parent; m_hDbhHelp = NULL; pSC = NULL; m_hProcess = hProcess; m_szSymPath = NULL; pSFTA = NULL; pSGLFA = NULL; pSGMB = NULL; pSGMI = NULL; pSGO = NULL; pSGSFA = NULL; pSI = NULL; pSLM = NULL; pSSO = NULL; pSW = NULL; pUDSN = NULL; pSGSP = NULL; } ~StackWalkerInternal() { if (pSC != NULL) pSC(m_hProcess); // SymCleanup if (m_hDbhHelp != NULL) FreeLibrary(m_hDbhHelp); m_hDbhHelp = NULL; m_parent = NULL; if(m_szSymPath != NULL) free(m_szSymPath); m_szSymPath = NULL; } BOOL Init(LPCSTR szSymPath) { if (m_parent == NULL) return FALSE; // Dynamically load the Entry-Points for dbghelp.dll: // First try to load the newsest one from TCHAR szTemp[4096]; // But before wqe do this, we first check if the ".local" file exists if (GetModuleFileName(NULL, szTemp, 4096) > 0) { _tcscat_s(szTemp, _T(".local")); if (GetFileAttributes(szTemp) == INVALID_FILE_ATTRIBUTES) { // ".local" file does not exist, so we can try to load the dbghelp.dll from the "Debugging Tools for Windows" // Ok, first try the new path according to the archtitecture: #ifdef _M_IX86 if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) ) { _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x86)\\dbghelp.dll")); // now check if the file exists: if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { m_hDbhHelp = LoadLibrary(szTemp); } } #elif _M_X64 if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) ) { _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x64)\\dbghelp.dll")); // now check if the file exists: if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { m_hDbhHelp = LoadLibrary(szTemp); } } #elif _M_IA64 if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) ) { _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (ia64)\\dbghelp.dll")); // now check if the file exists: if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { m_hDbhHelp = LoadLibrary(szTemp); } } #endif // If still not found, try the old directories... if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) ) { _tcscat_s(szTemp, _T("\\Debugging Tools for Windows\\dbghelp.dll")); // now check if the file exists: if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { m_hDbhHelp = LoadLibrary(szTemp); } } #if defined _M_X64 || defined _M_IA64 // Still not found? Then try to load the (old) 64-Bit version: if ( (m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0) ) { _tcscat_s(szTemp, _T("\\Debugging Tools for Windows 64-Bit\\dbghelp.dll")); if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) { m_hDbhHelp = LoadLibrary(szTemp); } } #endif } } if (m_hDbhHelp == NULL) // if not already loaded, try to load a default-one m_hDbhHelp = LoadLibrary( _T("dbghelp.dll") ); if (m_hDbhHelp == NULL) return FALSE; pSI = (tSI) GetProcAddress(m_hDbhHelp, "SymInitialize" ); pSC = (tSC) GetProcAddress(m_hDbhHelp, "SymCleanup" ); pSW = (tSW) GetProcAddress(m_hDbhHelp, "StackWalk64" ); pSGO = (tSGO) GetProcAddress(m_hDbhHelp, "SymGetOptions" ); pSSO = (tSSO) GetProcAddress(m_hDbhHelp, "SymSetOptions" ); pSFTA = (tSFTA) GetProcAddress(m_hDbhHelp, "SymFunctionTableAccess64" ); pSGLFA = (tSGLFA) GetProcAddress(m_hDbhHelp, "SymGetLineFromAddr64" ); pSGMB = (tSGMB) GetProcAddress(m_hDbhHelp, "SymGetModuleBase64" ); pSGMI = (tSGMI) GetProcAddress(m_hDbhHelp, "SymGetModuleInfo64" ); pSGSFA = (tSGSFA) GetProcAddress(m_hDbhHelp, "SymGetSymFromAddr64" ); pUDSN = (tUDSN) GetProcAddress(m_hDbhHelp, "UnDecorateSymbolName" ); pSLM = (tSLM) GetProcAddress(m_hDbhHelp, "SymLoadModule64" ); pSGSP =(tSGSP) GetProcAddress(m_hDbhHelp, "SymGetSearchPath" ); if ( pSC == NULL || pSFTA == NULL || pSGMB == NULL || pSGMI == NULL || pSGO == NULL || pSGSFA == NULL || pSI == NULL || pSSO == NULL || pSW == NULL || pUDSN == NULL || pSLM == NULL ) { FreeLibrary(m_hDbhHelp); m_hDbhHelp = NULL; pSC = NULL; return FALSE; } // SymInitialize if (szSymPath != NULL) m_szSymPath = _strdup(szSymPath); if (this->pSI(m_hProcess, m_szSymPath, FALSE) == FALSE) this->m_parent->OnDbgHelpErr("SymInitialize", GetLastError(), 0); DWORD symOptions = this->pSGO(); // SymGetOptions symOptions |= SYMOPT_LOAD_LINES; symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; //symOptions |= SYMOPT_NO_PROMPTS; // SymSetOptions symOptions = this->pSSO(symOptions); char buf[StackWalker::STACKWALK_MAX_NAMELEN] = {0}; if (this->pSGSP != NULL) { if (this->pSGSP(m_hProcess, buf, StackWalker::STACKWALK_MAX_NAMELEN) == FALSE) this->m_parent->OnDbgHelpErr("SymGetSearchPath", GetLastError(), 0); } char szUserName[1024] = {0}; DWORD dwSize = 1024; GetUserNameA(szUserName, &dwSize); this->m_parent->OnSymInit(buf, symOptions, szUserName); return TRUE; } StackWalker *m_parent; HMODULE m_hDbhHelp; HANDLE m_hProcess; LPSTR m_szSymPath; #pragma pack(push,8) typedef struct IMAGEHLP_MODULE64_V3 { DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) DWORD64 BaseOfImage; // base load address of module DWORD ImageSize; // virtual size of the loaded module DWORD TimeDateStamp; // date/time stamp from pe header DWORD CheckSum; // checksum from the pe header DWORD NumSyms; // number of symbols in the symbol table SYM_TYPE SymType; // type of symbols loaded CHAR ModuleName[32]; // module name CHAR ImageName[256]; // image name CHAR LoadedImageName[256]; // symbol file name // new elements: 07-Jun-2002 CHAR LoadedPdbName[256]; // pdb file name DWORD CVSig; // Signature of the CV record in the debug directories CHAR CVData[MAX_PATH * 3]; // Contents of the CV record DWORD PdbSig; // Signature of PDB GUID PdbSig70; // Signature of PDB (VC 7 and up) DWORD PdbAge; // DBI age of pdb BOOL PdbUnmatched; // loaded an unmatched pdb BOOL DbgUnmatched; // loaded an unmatched dbg BOOL LineNumbers; // we have line number information BOOL GlobalSymbols; // we have internal symbol information BOOL TypeInfo; // we have type information // new elements: 17-Dec-2003 BOOL SourceIndexed; // pdb supports source server BOOL Publics; // contains public symbols }; typedef struct IMAGEHLP_MODULE64_V2 { DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) DWORD64 BaseOfImage; // base load address of module DWORD ImageSize; // virtual size of the loaded module DWORD TimeDateStamp; // date/time stamp from pe header DWORD CheckSum; // checksum from the pe header DWORD NumSyms; // number of symbols in the symbol table SYM_TYPE SymType; // type of symbols loaded CHAR ModuleName[32]; // module name CHAR ImageName[256]; // image name CHAR LoadedImageName[256]; // symbol file name }; #pragma pack(pop) // SymCleanup() typedef BOOL (__stdcall *tSC)( IN HANDLE hProcess ); tSC pSC; // SymFunctionTableAccess64() typedef PVOID (__stdcall *tSFTA)( HANDLE hProcess, DWORD64 AddrBase ); tSFTA pSFTA; // SymGetLineFromAddr64() typedef BOOL (__stdcall *tSGLFA)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line ); tSGLFA pSGLFA; // SymGetModuleBase64() typedef DWORD64 (__stdcall *tSGMB)( IN HANDLE hProcess, IN DWORD64 dwAddr ); tSGMB pSGMB; // SymGetModuleInfo64() typedef BOOL (__stdcall *tSGMI)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT IMAGEHLP_MODULE64_V3 *ModuleInfo ); tSGMI pSGMI; // SymGetOptions() typedef DWORD (__stdcall *tSGO)( VOID ); tSGO pSGO; // SymGetSymFromAddr64() typedef BOOL (__stdcall *tSGSFA)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD64 pdwDisplacement, OUT PIMAGEHLP_SYMBOL64 Symbol ); tSGSFA pSGSFA; // SymInitialize() typedef BOOL (__stdcall *tSI)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess ); tSI pSI; // SymLoadModule64() typedef DWORD64 (__stdcall *tSLM)( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ); tSLM pSLM; // SymSetOptions() typedef DWORD (__stdcall *tSSO)( IN DWORD SymOptions ); tSSO pSSO; // StackWalk64() typedef BOOL (__stdcall *tSW)( DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ); tSW pSW; // UnDecorateSymbolName() typedef DWORD (__stdcall WINAPI *tUDSN)( PCSTR DecoratedName, PSTR UnDecoratedName, DWORD UndecoratedLength, DWORD Flags ); tUDSN pUDSN; typedef BOOL (__stdcall WINAPI *tSGSP)(HANDLE hProcess, PSTR SearchPath, DWORD SearchPathLength); tSGSP pSGSP; private: // **************************************** ToolHelp32 ************************ #define MAX_MODULE_NAME32 255 #define TH32CS_SNAPMODULE 0x00000008 #pragma pack( push, 8 ) typedef struct tagMODULEENTRY32 { DWORD dwSize; DWORD th32ModuleID; // This module DWORD th32ProcessID; // owning process DWORD GlblcntUsage; // Global usage count on the module DWORD ProccntUsage; // Module usage count in th32ProcessID's context BYTE * modBaseAddr; // Base address of module in th32ProcessID's context DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr HMODULE hModule; // The hModule of this module in th32ProcessID's context char szModule[MAX_MODULE_NAME32 + 1]; char szExePath[MAX_PATH]; } MODULEENTRY32; typedef MODULEENTRY32 * PMODULEENTRY32; typedef MODULEENTRY32 * LPMODULEENTRY32; #pragma pack( pop ) BOOL GetModuleListTH32(HANDLE hProcess, DWORD pid) { // CreateToolhelp32Snapshot() typedef HANDLE (__stdcall *tCT32S)(DWORD dwFlags, DWORD th32ProcessID); // Module32First() typedef BOOL (__stdcall *tM32F)(HANDLE hSnapshot, LPMODULEENTRY32 lpme); // Module32Next() typedef BOOL (__stdcall *tM32N)(HANDLE hSnapshot, LPMODULEENTRY32 lpme); // try both dlls... const TCHAR *dllname[] = { _T("kernel32.dll"), _T("tlhelp32.dll") }; HINSTANCE hToolhelp = NULL; tCT32S pCT32S = NULL; tM32F pM32F = NULL; tM32N pM32N = NULL; HANDLE hSnap; MODULEENTRY32 me; me.dwSize = sizeof(me); BOOL keepGoing; size_t i; for (i = 0; i<(sizeof(dllname) / sizeof(dllname[0])); i++ ) { hToolhelp = LoadLibrary( dllname[i] ); if (hToolhelp == NULL) continue; pCT32S = (tCT32S) GetProcAddress(hToolhelp, "CreateToolhelp32Snapshot"); pM32F = (tM32F) GetProcAddress(hToolhelp, "Module32First"); pM32N = (tM32N) GetProcAddress(hToolhelp, "Module32Next"); if ( (pCT32S != NULL) && (pM32F != NULL) && (pM32N != NULL) ) break; // found the functions! FreeLibrary(hToolhelp); hToolhelp = NULL; } if (hToolhelp == NULL) return FALSE; hSnap = pCT32S( TH32CS_SNAPMODULE, pid ); if (hSnap == (HANDLE) -1) { FreeLibrary(hToolhelp); return FALSE; } keepGoing = !!pM32F( hSnap, &me ); int cnt = 0; while (keepGoing) { this->LoadModule(hProcess, me.szExePath, me.szModule, (DWORD64) me.modBaseAddr, me.modBaseSize); cnt++; keepGoing = !!pM32N( hSnap, &me ); } CloseHandle(hSnap); FreeLibrary(hToolhelp); if (cnt <= 0) return FALSE; return TRUE; } // GetModuleListTH32 // **************************************** PSAPI ************************ typedef struct _MODULEINFO { LPVOID lpBaseOfDll; DWORD SizeOfImage; LPVOID EntryPoint; } MODULEINFO, *LPMODULEINFO; BOOL GetModuleListPSAPI(HANDLE hProcess) { // EnumProcessModules() typedef BOOL (__stdcall *tEPM)(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded ); // GetModuleFileNameEx() typedef DWORD (__stdcall *tGMFNE)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize ); // GetModuleBaseName() typedef DWORD (__stdcall *tGMBN)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize ); // GetModuleInformation() typedef BOOL (__stdcall *tGMI)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize ); HINSTANCE hPsapi; tEPM pEPM; tGMFNE pGMFNE; tGMBN pGMBN; tGMI pGMI; DWORD i; //ModuleEntry e; DWORD cbNeeded; MODULEINFO mi; HMODULE *hMods = 0; char *tt = NULL; char *tt2 = NULL; const SIZE_T TTBUFLEN = 8096; int cnt = 0; hPsapi = LoadLibrary( _T("psapi.dll") ); if (hPsapi == NULL) return FALSE; pEPM = (tEPM) GetProcAddress( hPsapi, "EnumProcessModules" ); pGMFNE = (tGMFNE) GetProcAddress( hPsapi, "GetModuleFileNameExA" ); pGMBN = (tGMFNE) GetProcAddress( hPsapi, "GetModuleBaseNameA" ); pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" ); if ( (pEPM == NULL) || (pGMFNE == NULL) || (pGMBN == NULL) || (pGMI == NULL) ) { // we couldnt find all functions FreeLibrary(hPsapi); return FALSE; } hMods = (HMODULE*) malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof(HMODULE))); tt = (char*) malloc(sizeof(char) * TTBUFLEN); tt2 = (char*) malloc(sizeof(char) * TTBUFLEN); if ( (hMods == NULL) || (tt == NULL) || (tt2 == NULL) ) goto cleanup; if ( ! pEPM( hProcess, hMods, TTBUFLEN, &cbNeeded ) ) { //_ftprintf(fLogFile, _T("%lu: EPM failed, GetLastError = %lu\n"), g_dwShowCount, gle ); goto cleanup; } if ( cbNeeded > TTBUFLEN ) { //_ftprintf(fLogFile, _T("%lu: More than %lu module handles. Huh?\n"), g_dwShowCount, lenof( hMods ) ); goto cleanup; } for ( i = 0; i < cbNeeded / sizeof(hMods[0]); i++ ) { // base address, size pGMI(hProcess, hMods[i], &mi, sizeof(mi)); // image file name tt[0] = 0; pGMFNE(hProcess, hMods[i], tt, TTBUFLEN ); // module name tt2[0] = 0; pGMBN(hProcess, hMods[i], tt2, TTBUFLEN ); DWORD dwRes = this->LoadModule(hProcess, tt, tt2, (DWORD64) mi.lpBaseOfDll, mi.SizeOfImage); if (dwRes != ERROR_SUCCESS) this->m_parent->OnDbgHelpErr("LoadModule", dwRes, 0); cnt++; } cleanup: if (hPsapi != NULL) FreeLibrary(hPsapi); if (tt2 != NULL) free(tt2); if (tt != NULL) free(tt); if (hMods != NULL) free(hMods); return cnt != 0; } // GetModuleListPSAPI DWORD LoadModule(HANDLE hProcess, LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size) { CHAR *szImg = _strdup(img); CHAR *szMod = _strdup(mod); DWORD result = ERROR_SUCCESS; if ( (szImg == NULL) || (szMod == NULL) ) result = ERROR_NOT_ENOUGH_MEMORY; else { if (pSLM(hProcess, 0, szImg, szMod, baseAddr, size) == 0) result = GetLastError(); } ULONGLONG fileVersion = 0; if ( (m_parent != NULL) && (szImg != NULL) ) { // try to retrive the file-version: if ( (this->m_parent->m_options & StackWalker::RetrieveFileVersion) != 0) { VS_FIXEDFILEINFO *fInfo = NULL; DWORD dwHandle; DWORD dwSize = GetFileVersionInfoSizeA(szImg, &dwHandle); if (dwSize > 0) { LPVOID vData = malloc(dwSize); if (vData != NULL) { if (GetFileVersionInfoA(szImg, dwHandle, dwSize, vData) != 0) { UINT len; TCHAR szSubBlock[] = _T("\\"); if (VerQueryValue(vData, szSubBlock, (LPVOID*) &fInfo, &len) == 0) fInfo = NULL; else { fileVersion = ((ULONGLONG)fInfo->dwFileVersionLS) + ((ULONGLONG)fInfo->dwFileVersionMS << 32); } } free(vData); } } } // Retrive some additional-infos about the module IMAGEHLP_MODULE64_V3 Module; const char *szSymType = "-unknown-"; if (this->GetModuleInfo(hProcess, baseAddr, &Module) != FALSE) { switch(Module.SymType) { case SymNone: szSymType = "-nosymbols-"; break; case SymCoff: // 1 szSymType = "COFF"; break; case SymCv: // 2 szSymType = "CV"; break; case SymPdb: // 3 szSymType = "PDB"; break; case SymExport: // 4 szSymType = "-exported-"; break; case SymDeferred: // 5 szSymType = "-deferred-"; break; case SymSym: // 6 szSymType = "SYM"; break; case 7: // SymDia: szSymType = "DIA"; break; case 8: //SymVirtual: szSymType = "Virtual"; break; } } LPCSTR pdbName = Module.LoadedImageName; if (Module.LoadedPdbName[0] != 0) pdbName = Module.LoadedPdbName; this->m_parent->OnLoadModule(img, mod, baseAddr, size, result, szSymType, pdbName, fileVersion); } if (szImg != NULL) free(szImg); if (szMod != NULL) free(szMod); return result; } public: BOOL LoadModules(HANDLE hProcess, DWORD dwProcessId) { // first try toolhelp32 if (GetModuleListTH32(hProcess, dwProcessId)) return true; // then try psapi return GetModuleListPSAPI(hProcess); } BOOL GetModuleInfo(HANDLE hProcess, DWORD64 baseAddr, IMAGEHLP_MODULE64_V3 *pModuleInfo) { memset(pModuleInfo, 0, sizeof(IMAGEHLP_MODULE64_V3)); if(this->pSGMI == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; } // First try to use the larger ModuleInfo-Structure pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3); void *pData = malloc(4096); // reserve enough memory, so the bug in v6.3.5.1 does not lead to memory-overwrites... if (pData == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V3)); static bool s_useV3Version = true; if (s_useV3Version) { if (this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V3*) pData) != FALSE) { // only copy as much memory as is reserved... memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V3)); pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3); free(pData); return TRUE; } s_useV3Version = false; // to prevent unneccessarry calls with the larger struct... } // could not retrive the bigger structure, try with the smaller one (as defined in VC7.1)... pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2); memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V2)); if (this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V3*) pData) != FALSE) { // only copy as much memory as is reserved... memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V2)); pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2); free(pData); return TRUE; } free(pData); SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; } }; // ############################################################# StackWalker::StackWalker(DWORD dwProcessId, HANDLE hProcess) { this->m_options = OptionsAll; this->m_modulesLoaded = FALSE; this->m_hProcess = hProcess; this->m_sw = new StackWalkerInternal(this, this->m_hProcess); this->m_dwProcessId = dwProcessId; this->m_szSymPath = NULL; this->m_MaxRecursionCount = 1000; } StackWalker::StackWalker(int options, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess) { this->m_options = options; this->m_modulesLoaded = FALSE; this->m_hProcess = hProcess; this->m_sw = new StackWalkerInternal(this, this->m_hProcess); this->m_dwProcessId = dwProcessId; if (szSymPath != NULL) { this->m_szSymPath = _strdup(szSymPath); this->m_options |= SymBuildPath; } else this->m_szSymPath = NULL; this->m_MaxRecursionCount = 1000; } StackWalker::~StackWalker() { if (m_szSymPath != NULL) free(m_szSymPath); m_szSymPath = NULL; if (this->m_sw != NULL) delete this->m_sw; this->m_sw = NULL; } BOOL StackWalker::LoadModules() { if (this->m_sw == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; } if (m_modulesLoaded != FALSE) return TRUE; // Build the sym-path: char *szSymPath = NULL; if ( (this->m_options & SymBuildPath) != 0) { const size_t nSymPathLen = 4096; szSymPath = (char*) malloc(nSymPathLen); if (szSymPath == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } szSymPath[0] = 0; // Now first add the (optional) provided sympath: if (this->m_szSymPath != NULL) { strcat_s(szSymPath, nSymPathLen, this->m_szSymPath); strcat_s(szSymPath, nSymPathLen, ";"); } strcat_s(szSymPath, nSymPathLen, ".;"); const size_t nTempLen = 1024; char szTemp[nTempLen]; // Now add the current directory: if (GetCurrentDirectoryA(nTempLen, szTemp) > 0) { szTemp[nTempLen-1] = 0; strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); } // Now add the path for the main-module: if (GetModuleFileNameA(NULL, szTemp, nTempLen) > 0) { szTemp[nTempLen-1] = 0; for (char *p = (szTemp+strlen(szTemp)-1); p >= szTemp; --p) { // locate the rightmost path separator if ( (*p == '\\') || (*p == '/') || (*p == ':') ) { *p = 0; break; } } // for (search for path separator...) if (strlen(szTemp) > 0) { strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); } } if (GetEnvironmentVariableA("_NT_SYMBOL_PATH", szTemp, nTempLen) > 0) { szTemp[nTempLen-1] = 0; strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); } if (GetEnvironmentVariableA("_NT_ALTERNATE_SYMBOL_PATH", szTemp, nTempLen) > 0) { szTemp[nTempLen-1] = 0; strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); } if (GetEnvironmentVariableA("SYSTEMROOT", szTemp, nTempLen) > 0) { szTemp[nTempLen-1] = 0; strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); // also add the "system32"-directory: strcat_s(szTemp, nTempLen, "\\system32"); strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, ";"); } if ( (this->m_options & SymUseSymSrv) != 0) { if (GetEnvironmentVariableA("SYSTEMDRIVE", szTemp, nTempLen) > 0) { szTemp[nTempLen-1] = 0; strcat_s(szSymPath, nSymPathLen, "SRV*"); strcat_s(szSymPath, nSymPathLen, szTemp); strcat_s(szSymPath, nSymPathLen, "\\websymbols"); strcat_s(szSymPath, nSymPathLen, "*http://msdl.microsoft.com/download/symbols;"); } else strcat_s(szSymPath, nSymPathLen, "SRV*c:\\websymbols*http://msdl.microsoft.com/download/symbols;"); } } // if SymBuildPath // First Init the whole stuff... BOOL bRet = this->m_sw->Init(szSymPath); if (szSymPath != NULL) free(szSymPath); szSymPath = NULL; if (bRet == FALSE) { this->OnDbgHelpErr("Error while initializing dbghelp.dll", 0, 0); SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; } bRet = this->m_sw->LoadModules(this->m_hProcess, this->m_dwProcessId); if (bRet != FALSE) m_modulesLoaded = TRUE; return bRet; } // The following is used to pass the "userData"-Pointer to the user-provided readMemoryFunction // This has to be done due to a problem with the "hProcess"-parameter in x64... // Because this class is in no case multi-threading-enabled (because of the limitations // of dbghelp.dll) it is "safe" to use a static-variable static StackWalker::PReadProcessMemoryRoutine s_readMemoryFunction = NULL; static LPVOID s_readMemoryFunction_UserData = NULL; BOOL StackWalker::ShowCallstack(HANDLE hThread, const CONTEXT *context, PReadProcessMemoryRoutine readMemoryFunction, LPVOID pUserData) { CONTEXT c; CallstackEntry csEntry; IMAGEHLP_SYMBOL64 *pSym = NULL; StackWalkerInternal::IMAGEHLP_MODULE64_V3 Module; IMAGEHLP_LINE64 Line; int frameNum; bool bLastEntryCalled = true; int curRecursionCount = 0; if (m_modulesLoaded == FALSE) this->LoadModules(); // ignore the result... if (this->m_sw->m_hDbhHelp == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); return FALSE; } s_readMemoryFunction = readMemoryFunction; s_readMemoryFunction_UserData = pUserData; if (context == NULL) { // If no context is provided, capture the context // See: https://stackwalker.codeplex.com/discussions/446958 #if true || _WIN32_WINNT <= 0x0501 // If we need to support XP, we need to use the "old way", because "GetThreadId" is not available! if (hThread == GetCurrentThread()) #else if (GetThreadId(hThread) == GetCurrentThreadId()) #endif { GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, USED_CONTEXT_FLAGS); } else { SuspendThread(hThread); memset(&c, 0, sizeof(CONTEXT)); c.ContextFlags = USED_CONTEXT_FLAGS; if (GetThreadContext(hThread, &c) == FALSE) { ResumeThread(hThread); return FALSE; } } } else c = *context; // init STACKFRAME for first call STACKFRAME64 s; // in/out stackframe memset(&s, 0, sizeof(s)); DWORD imageType; #ifdef _M_IX86 // normally, call ImageNtHeader() and use machine info from PE header imageType = IMAGE_FILE_MACHINE_I386; s.AddrPC.Offset = c.Eip; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.Ebp; s.AddrFrame.Mode = AddrModeFlat; s.AddrStack.Offset = c.Esp; s.AddrStack.Mode = AddrModeFlat; #elif _M_X64 imageType = IMAGE_FILE_MACHINE_AMD64; s.AddrPC.Offset = c.Rip; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.Rsp; s.AddrFrame.Mode = AddrModeFlat; s.AddrStack.Offset = c.Rsp; s.AddrStack.Mode = AddrModeFlat; #elif _M_IA64 imageType = IMAGE_FILE_MACHINE_IA64; s.AddrPC.Offset = c.StIIP; s.AddrPC.Mode = AddrModeFlat; s.AddrFrame.Offset = c.IntSp; s.AddrFrame.Mode = AddrModeFlat; s.AddrBStore.Offset = c.RsBSP; s.AddrBStore.Mode = AddrModeFlat; s.AddrStack.Offset = c.IntSp; s.AddrStack.Mode = AddrModeFlat; #else #error "Platform not supported!" #endif pSym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN); if (!pSym) goto cleanup; // not enough memory... memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN); pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); pSym->MaxNameLength = STACKWALK_MAX_NAMELEN; memset(&Line, 0, sizeof(Line)); Line.SizeOfStruct = sizeof(Line); memset(&Module, 0, sizeof(Module)); Module.SizeOfStruct = sizeof(Module); for (frameNum = 0; ; ++frameNum ) { // get next stack frame (StackWalk64(), SymFunctionTableAccess64(), SymGetModuleBase64()) // if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can // assume that either you are done, or that the stack is so hosed that the next // deeper frame could not be found. // CONTEXT need not to be suplied if imageTyp is IMAGE_FILE_MACHINE_I386! if ( ! this->m_sw->pSW(imageType, this->m_hProcess, hThread, &s, &c, myReadProcMem, this->m_sw->pSFTA, this->m_sw->pSGMB, NULL) ) { // INFO: "StackWalk64" does not set "GetLastError"... this->OnDbgHelpErr("StackWalk64", 0, s.AddrPC.Offset); break; } csEntry.offset = s.AddrPC.Offset; csEntry.name[0] = 0; csEntry.undName[0] = 0; csEntry.undFullName[0] = 0; csEntry.offsetFromSmybol = 0; csEntry.offsetFromLine = 0; csEntry.lineFileName[0] = 0; csEntry.lineNumber = 0; csEntry.loadedImageName[0] = 0; csEntry.moduleName[0] = 0; if (s.AddrPC.Offset == s.AddrReturn.Offset) { if ( (this->m_MaxRecursionCount > 0) && (curRecursionCount > m_MaxRecursionCount) ) { this->OnDbgHelpErr("StackWalk64-Endless-Callstack!", 0, s.AddrPC.Offset); break; } curRecursionCount++; } else curRecursionCount = 0; if (s.AddrPC.Offset != 0) { // we seem to have a valid PC // show procedure info (SymGetSymFromAddr64()) if (this->m_sw->pSGSFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromSmybol), pSym) != FALSE) { MyStrCpy(csEntry.name, STACKWALK_MAX_NAMELEN, pSym->Name); // UnDecorateSymbolName() this->m_sw->pUDSN( pSym->Name, csEntry.undName, STACKWALK_MAX_NAMELEN, UNDNAME_NAME_ONLY ); this->m_sw->pUDSN( pSym->Name, csEntry.undFullName, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE ); } else { this->OnDbgHelpErr("SymGetSymFromAddr64", GetLastError(), s.AddrPC.Offset); } // show line number info, NT5.0-method (SymGetLineFromAddr64()) if (this->m_sw->pSGLFA != NULL ) { // yes, we have SymGetLineFromAddr64() if (this->m_sw->pSGLFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromLine), &Line) != FALSE) { csEntry.lineNumber = Line.LineNumber; MyStrCpy(csEntry.lineFileName, STACKWALK_MAX_NAMELEN, Line.FileName); } else { this->OnDbgHelpErr("SymGetLineFromAddr64", GetLastError(), s.AddrPC.Offset); } } // yes, we have SymGetLineFromAddr64() // show module info (SymGetModuleInfo64()) if (this->m_sw->GetModuleInfo(this->m_hProcess, s.AddrPC.Offset, &Module ) != FALSE) { // got module info OK switch ( Module.SymType ) { case SymNone: csEntry.symTypeString = "-nosymbols-"; break; case SymCoff: csEntry.symTypeString = "COFF"; break; case SymCv: csEntry.symTypeString = "CV"; break; case SymPdb: csEntry.symTypeString = "PDB"; break; case SymExport: csEntry.symTypeString = "-exported-"; break; case SymDeferred: csEntry.symTypeString = "-deferred-"; break; case SymSym: csEntry.symTypeString = "SYM"; break; #if API_VERSION_NUMBER >= 9 case SymDia: csEntry.symTypeString = "DIA"; break; #endif case 8: //SymVirtual: csEntry.symTypeString = "Virtual"; break; default: //_snprintf( ty, sizeof(ty), "symtype=%ld", (long) Module.SymType ); csEntry.symTypeString = NULL; break; } MyStrCpy(csEntry.moduleName, STACKWALK_MAX_NAMELEN, Module.ModuleName); csEntry.baseOfImage = Module.BaseOfImage; MyStrCpy(csEntry.loadedImageName, STACKWALK_MAX_NAMELEN, Module.LoadedImageName); } // got module info OK else { this->OnDbgHelpErr("SymGetModuleInfo64", GetLastError(), s.AddrPC.Offset); } } // we seem to have a valid PC CallstackEntryType et = nextEntry; if (frameNum == 0) et = firstEntry; bLastEntryCalled = false; this->OnCallstackEntry(et, csEntry); if (s.AddrReturn.Offset == 0) { bLastEntryCalled = true; this->OnCallstackEntry(lastEntry, csEntry); SetLastError(ERROR_SUCCESS); break; } } // for ( frameNum ) cleanup: if (pSym) free( pSym ); if (bLastEntryCalled == false) this->OnCallstackEntry(lastEntry, csEntry); if (context == NULL) ResumeThread(hThread); return TRUE; } BOOL __stdcall StackWalker::myReadProcMem( HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead ) { if (s_readMemoryFunction == NULL) { SIZE_T st; BOOL bRet = ReadProcessMemory(hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, &st); *lpNumberOfBytesRead = (DWORD) st; //printf("ReadMemory: hProcess: %p, baseAddr: %p, buffer: %p, size: %d, read: %d, result: %d\n", hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, (DWORD) st, (DWORD) bRet); return bRet; } else { return s_readMemoryFunction(hProcess, qwBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead, s_readMemoryFunction_UserData); } } void StackWalker::OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion) { CHAR buffer[STACKWALK_MAX_NAMELEN]; if (fileVersion == 0) _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s'\n", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName); else { DWORD v4 = (DWORD) (fileVersion & 0xFFFF); DWORD v3 = (DWORD) ((fileVersion>>16) & 0xFFFF); DWORD v2 = (DWORD) ((fileVersion>>32) & 0xFFFF); DWORD v1 = (DWORD) ((fileVersion>>48) & 0xFFFF); _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s', fileVersion: %d.%d.%d.%d\n", img, mod, (LPVOID) baseAddr, size, result, symType, pdbName, v1, v2, v3, v4); } OnOutput(buffer); } void StackWalker::OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry) { CHAR buffer[STACKWALK_MAX_NAMELEN]; if ( (eType != lastEntry) && (entry.offset != 0) ) { if (entry.name[0] == 0) MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, "(function-name not available)"); if (entry.undName[0] != 0) MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undName); if (entry.undFullName[0] != 0) MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undFullName); if (entry.lineFileName[0] == 0) { MyStrCpy(entry.lineFileName, STACKWALK_MAX_NAMELEN, "(filename not available)"); if (entry.moduleName[0] == 0) MyStrCpy(entry.moduleName, STACKWALK_MAX_NAMELEN, "(module-name not available)"); _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%p (%s): %s: %s\n", (LPVOID) entry.offset, entry.moduleName, entry.lineFileName, entry.name); } else _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%s (%d): %s\n", entry.lineFileName, entry.lineNumber, entry.name); buffer[STACKWALK_MAX_NAMELEN-1] = 0; OnOutput(buffer); } } void StackWalker::OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr) { CHAR buffer[STACKWALK_MAX_NAMELEN]; _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "ERROR: %s, GetLastError: %d (Address: %p)\n", szFuncName, gle, (LPVOID) addr); OnOutput(buffer); } void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName) { CHAR buffer[STACKWALK_MAX_NAMELEN]; _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "SymInit: Symbol-SearchPath: '%s', symOptions: %d, UserName: '%s'\n", szSearchPath, symOptions, szUserName); OnOutput(buffer); // Also display the OS-version #if _MSC_VER <= 1200 OSVERSIONINFOA ver; ZeroMemory(&ver, sizeof(OSVERSIONINFOA)); ver.dwOSVersionInfoSize = sizeof(ver); if (GetVersionExA(&ver) != FALSE) { _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s)\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion); OnOutput(buffer); } #else OSVERSIONINFOEXA ver; ZeroMemory(&ver, sizeof(OSVERSIONINFOEXA)); ver.dwOSVersionInfoSize = sizeof(ver); if (GetVersionExA( (OSVERSIONINFOA*) &ver) != FALSE) { _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion, ver.wSuiteMask, ver.wProductType); OnOutput(buffer); } #endif } void StackWalker::OnOutput(LPCSTR buffer) { OutputDebugStringA(buffer); } treesheets-1.0.2/src/StackWalker/StackWalker.h000066400000000000000000000167151352107072600212700ustar00rootroot00000000000000/********************************************************************** * * StackWalker.h * * * * LICENSE (http://www.opensource.org/licenses/bsd-license.php) * * Copyright (c) 2005-2009, Jochen Kalmbach * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * Neither the name of Jochen Kalmbach nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * **********************************************************************/ // #pragma once is supported starting with _MCS_VER 1000, // so we need not to check the version (because we only support _MSC_VER >= 1100)! #pragma once #include // special defines for VC5/6 (if no actual PSDK is installed): #if _MSC_VER < 1300 typedef unsigned __int64 DWORD64, *PDWORD64; #if defined(_WIN64) typedef unsigned __int64 SIZE_T, *PSIZE_T; #else typedef unsigned long SIZE_T, *PSIZE_T; #endif #endif // _MSC_VER < 1300 class StackWalkerInternal; // forward class StackWalker { public: typedef enum StackWalkOptions { // No addition info will be retrived // (only the address is available) RetrieveNone = 0, // Try to get the symbol-name RetrieveSymbol = 1, // Try to get the line for this symbol RetrieveLine = 2, // Try to retrieve the module-infos RetrieveModuleInfo = 4, // Also retrieve the version for the DLL/EXE RetrieveFileVersion = 8, // Contains all the abouve RetrieveVerbose = 0xF, // Generate a "good" symbol-search-path SymBuildPath = 0x10, // Also use the public Microsoft-Symbol-Server SymUseSymSrv = 0x20, // Contains all the abouve "Sym"-options SymAll = 0x30, // Contains all options (default) OptionsAll = 0x3F } StackWalkOptions; StackWalker( int options = OptionsAll, // 'int' is by design, to combine the enum-flags LPCSTR szSymPath = NULL, DWORD dwProcessId = GetCurrentProcessId(), HANDLE hProcess = GetCurrentProcess() ); StackWalker(DWORD dwProcessId, HANDLE hProcess); virtual ~StackWalker(); typedef BOOL (__stdcall *PReadProcessMemoryRoutine)( HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead, LPVOID pUserData // optional data, which was passed in "ShowCallstack" ); BOOL LoadModules(); BOOL ShowCallstack( HANDLE hThread = GetCurrentThread(), const CONTEXT *context = NULL, PReadProcessMemoryRoutine readMemoryFunction = NULL, LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback ); #if _MSC_VER >= 1300 // due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public" // in older compilers in order to use it... starting with VC7 we can declare it as "protected" protected: #endif enum { STACKWALK_MAX_NAMELEN = 1024 }; // max name length for found symbols protected: // Entry for each Callstack-Entry typedef struct CallstackEntry { DWORD64 offset; // if 0, we have no valid entry CHAR name[STACKWALK_MAX_NAMELEN]; CHAR undName[STACKWALK_MAX_NAMELEN]; CHAR undFullName[STACKWALK_MAX_NAMELEN]; DWORD64 offsetFromSmybol; DWORD offsetFromLine; DWORD lineNumber; CHAR lineFileName[STACKWALK_MAX_NAMELEN]; DWORD symType; LPCSTR symTypeString; CHAR moduleName[STACKWALK_MAX_NAMELEN]; DWORD64 baseOfImage; CHAR loadedImageName[STACKWALK_MAX_NAMELEN]; } CallstackEntry; typedef enum CallstackEntryType {firstEntry, nextEntry, lastEntry}; virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName); virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion); virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry); virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr); virtual void OnOutput(LPCSTR szText); StackWalkerInternal *m_sw; HANDLE m_hProcess; DWORD m_dwProcessId; BOOL m_modulesLoaded; LPSTR m_szSymPath; int m_options; int m_MaxRecursionCount; static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead); friend StackWalkerInternal; }; // class StackWalker // The "ugly" assembler-implementation is needed for systems before XP // If you have a new PSDK and you only compile for XP and later, then you can use // the "RtlCaptureContext" // Currently there is no define which determines the PSDK-Version... // So we just use the compiler-version (and assumes that the PSDK is // the one which was installed by the VS-IDE) // INFO: If you want, you can use the RtlCaptureContext if you only target XP and later... // But I currently use it in x64/IA64 environments... //#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400) #if defined(_M_IX86) #ifdef CURRENT_THREAD_VIA_EXCEPTION // TODO: The following is not a "good" implementation, // because the callstack is only valid in the "__except" block... #define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ do { \ memset(&c, 0, sizeof(CONTEXT)); \ EXCEPTION_POINTERS *pExp = NULL; \ __try { \ throw 0; \ } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \ if (pExp != NULL) \ memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \ c.ContextFlags = contextFlags; \ } while(0); #else // The following should be enough for walking the callstack... #define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ do { \ memset(&c, 0, sizeof(CONTEXT)); \ c.ContextFlags = contextFlags; \ __asm call x \ __asm x: pop eax \ __asm mov c.Eip, eax \ __asm mov c.Ebp, ebp \ __asm mov c.Esp, esp \ } while(0); #endif #else // The following is defined for x86 (XP and higher), x64 and IA64: #define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ do { \ memset(&c, 0, sizeof(CONTEXT)); \ c.ContextFlags = contextFlags; \ RtlCaptureContext(&c); \ } while(0); #endif treesheets-1.0.2/src/StackWalker/StackWalkerHelpers.cpp000066400000000000000000000112701352107072600231350ustar00rootroot00000000000000/********************************************************************** * * main.cpp * * * History: * 2008-11-27 v1 - Header added * Samples for Exception-Crashes added... * 2009-11-01 v2 - Moved to stackwalker.codeplex.com * **********************************************************************/ #include "stackwalker.h" #include #include static TCHAR s_szExceptionLogFileName[_MAX_PATH] = _T("\\exceptions.log"); // default static BOOL s_bUnhandledExeptionFilterSet = FALSE; // Specialized stackwalker-output classes class CustomStackWalker : public StackWalker { public: virtual void OnOutput(LPCSTR szText) { auto hAppend = CreateFile(TEXT(s_szExceptionLogFileName), FILE_APPEND_DATA, // open for writing FILE_SHARE_READ, // allow multiple readers NULL, // no security OPEN_ALWAYS, // open or create FILE_ATTRIBUTE_NORMAL, // normal file NULL); // no attr. template WriteFile(hAppend, szText, strlen(szText), nullptr, nullptr); CloseHandle(hAppend); } // Don't care about all the module ouput. void OnLoadModule(LPCSTR, LPCSTR, DWORD64, DWORD, DWORD, LPCSTR, LPCSTR, ULONGLONG) {} }; // For more info about "PreventSetUnhandledExceptionFilter" see: // "SetUnhandledExceptionFilter" and VC8 // http://blog.kalmbachnet.de/?postid=75 // and // Unhandled exceptions in VC8 and above for x86 and x64 // http://blog.kalmbach-software.de/2008/04/02/unhandled-exceptions-in-vc8-and-above-for-x86-and-x64/ // Even better: http://blog.kalmbach-software.de/2013/05/23/improvedpreventsetunhandledexceptionfilter/ #if defined _M_X64 || defined _M_IX86 static BOOL PreventSetUnhandledExceptionFilter() { HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll")); if (hKernel32 == NULL) return FALSE; void *pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter"); if (pOrgEntry == NULL) return FALSE; #ifdef _M_IX86 // Code for x86: // 33 C0 xor eax,eax // C2 04 00 ret 4 unsigned char szExecute[] = { 0x33, 0xC0, 0xC2, 0x04, 0x00 }; #elif _M_X64 // 33 C0 xor eax,eax // C3 ret unsigned char szExecute[] = { 0x33, 0xC0, 0xC3 }; #else #error "The following code only works for x86 and x64!" #endif DWORD dwOldProtect = 0; BOOL bProt = VirtualProtect(pOrgEntry, sizeof(szExecute), PAGE_EXECUTE_READWRITE, &dwOldProtect); SIZE_T bytesWritten = 0; BOOL bRet = WriteProcessMemory(GetCurrentProcess(), pOrgEntry, szExecute, sizeof(szExecute), &bytesWritten); if ( (bProt != FALSE) && (dwOldProtect != PAGE_EXECUTE_READWRITE)) { DWORD dwBuf; VirtualProtect(pOrgEntry, sizeof(szExecute), dwOldProtect, &dwBuf); } return bRet; } #else #pragma message("This code works only for x86 and x64!") #endif static LONG __stdcall CrashHandlerExceptionFilter(EXCEPTION_POINTERS* pExPtrs) { #ifdef _M_IX86 if (pExPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) { static char MyStack[1024*128]; // be sure that we have enought space... // it assumes that DS and SS are the same!!! (this is the case for Win32) // change the stack only if the selectors are the same (this is the case for Win32) //__asm push offset MyStack[1024*128]; //__asm pop esp; __asm mov eax,offset MyStack[1024*128]; __asm mov esp,eax; } #endif CustomStackWalker sw; // output to console sw.ShowCallstack(GetCurrentThread(), pExPtrs->ContextRecord); TCHAR lString[500]; _stprintf_s(lString, _T("ExpCode: 0x%8.8X ExpFlags: %d ExpAddress: 0x%8.8X\n"), pExPtrs->ExceptionRecord->ExceptionCode, pExPtrs->ExceptionRecord->ExceptionFlags, pExPtrs->ExceptionRecord->ExceptionAddress); sw.OnOutput(lString); _stprintf_s(lString, _T("Please send %s to the developer!\n"), s_szExceptionLogFileName); FatalAppExit(-1, lString); return EXCEPTION_CONTINUE_SEARCH; } void InitUnhandledExceptionFilter() { TCHAR szModName[_MAX_PATH]; if (GetModuleFileName(NULL, szModName, sizeof(szModName)/sizeof(TCHAR)) != 0) { _tcscpy_s(s_szExceptionLogFileName, szModName); _tcscat_s(s_szExceptionLogFileName, _T(".exp.log")); } if (s_bUnhandledExeptionFilterSet == FALSE) { // set global exception handler (for handling all unhandled exceptions) SetUnhandledExceptionFilter(CrashHandlerExceptionFilter); #if defined _M_X64 || defined _M_IX86 PreventSetUnhandledExceptionFilter(); #endif s_bUnhandledExeptionFilterSet = TRUE; } } treesheets-1.0.2/src/StackWalker/StackWalkerHelpers.h000066400000000000000000000000551352107072600226010ustar00rootroot00000000000000extern void InitUnhandledExceptionFilter(); treesheets-1.0.2/src/TreeSheets.config000066400000000000000000000001261352107072600177200ustar00rootroot00000000000000// Add predefined macros for your project here. For example: // #define THE_ANSWER 42 treesheets-1.0.2/src/TreeSheets.creator000066400000000000000000000000121352107072600201040ustar00rootroot00000000000000[General] treesheets-1.0.2/src/TreeSheets.creator.user000066400000000000000000000302171352107072600210730ustar00rootroot00000000000000 EnvironmentId {7a18bbb5-5e3c-47a5-88b8-c779f9d8f851} ProjectExplorer.Project.ActiveTarget 0 ProjectExplorer.Project.EditorSettings true false true Cpp CppGlobal QmlJS QmlJSGlobal 2 UTF-8 false 4 false 80 true true 1 true false 0 true 0 8 true 1 true true true false ProjectExplorer.Project.PluginSettings ProjectExplorer.Project.Target.0 Desktop Desktop {f421bd90-44fa-4b38-a558-d1f30b66c98e} 0 0 0 /home/ubuntu/treesheets/src false debug true Make GenericProjectManager.GenericMakeStep 1 Build ProjectExplorer.BuildSteps.Build clean true true Make GenericProjectManager.GenericMakeStep 1 Clean ProjectExplorer.BuildSteps.Clean 2 false Default Default GenericProjectManager.GenericBuildConfiguration 1 0 Deploy ProjectExplorer.BuildSteps.Deploy 1 Deploy locally ProjectExplorer.DefaultDeployConfiguration 1 false false false false true 0.01 10 true 1 25 1 true false true valgrind 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 2 %{buildDir}/../TS/treesheets false %{buildDir}/../TS Run %{buildDir}/../TS/treesheets ProjectExplorer.CustomExecutableRunConfiguration 3768 false true false false true 1 ProjectExplorer.Project.TargetCount 1 ProjectExplorer.Project.Updater.FileVersion 18 Version 18 treesheets-1.0.2/src/TreeSheets.files000066400000000000000000000002431352107072600175550ustar00rootroot00000000000000cell.h document.h evaluator.h grid.h main.cpp myapp.h mycanvas.h myevents.h myframe.h mywxtools.h selection.h stdafx.cpp stdafx.h system.h text.h tools.h Makefile treesheets-1.0.2/src/TreeSheets.includes000066400000000000000000000000021352107072600202520ustar00rootroot00000000000000. treesheets-1.0.2/src/cell.h000077500000000000000000000423361352107072600155620ustar00rootroot00000000000000 /* The evaluation types for a cell. CT_DATA: "Data" CT_CODE: "Operation" CT_VARD: "Variable Assign" CT_VARU: "Variable Read" CT_VIEWH: "Horizontal View" CT_VIEWV: "Vertical View" */ enum { CT_DATA = 0, CT_CODE, CT_VARD, CT_VIEWH, CT_VARU, CT_VIEWV }; /* The drawstyles for a cell: */ enum { DS_GRID, DS_BLOBSHIER, DS_BLOBLINE }; /** The Cell structure represents the editable cells in the sheet. They are mutable structures containing a text and grid object. Along with formatting information. */ struct Cell { Cell *parent; int sx, sy, ox, oy, minx, miny, ycenteroff, txs, tys; int celltype; Text text; Grid *grid; uint cellcolor, textcolor, actualcellcolor; bool tiny; bool verticaltextandgrid; wxUint8 drawstyle; Cell(Cell *_p = nullptr, Cell const *_clonefrom = nullptr, int _ct = CT_DATA, Grid *_g = nullptr) : parent(_p), sx(0), sy(0), ox(0), oy(0), minx(0), miny(0), celltype(_ct), grid(_g), tiny(false), verticaltextandgrid(true), drawstyle(DS_GRID), cellcolor(0xFFFFFF), textcolor(0x000000) { text.cell = this; if (_g) _g->cell = this; if (_p) { text.relsize = _p->text.relsize; verticaltextandgrid = _p->verticaltextandgrid; } if (_clonefrom) CloneStyleFrom(_clonefrom); } ~Cell() { DELETEP(grid); } void Clear() { DELETEP(grid); text.t.Clear(); text.image = nullptr; Reset(); } bool HasText() const { return !text.t.empty(); } bool HasTextSize() const { return HasText() || text.relsize; } bool HasTextState() const { return HasTextSize() || text.image; } bool HasHeader() const { return HasText() || text.image; } bool HasContent() const { return HasHeader() || grid; } bool GridShown(Document *doc) const { return grid && (!grid->folded || this == doc->curdrawroot); } int MinRelsize() // the smallest relsize is actually the biggest text { int rs = INT_MAX; if (grid) { rs = grid->MinRelsize(rs); } else if (HasText()) { // the "else" causes oversized titles but a readable grid when you zoom, if only // the grid has been shrunk rs = text.MinRelsize(rs); } return rs; } size_t EstimatedMemoryUse() { return sizeof(Cell) + text.EstimatedMemoryUse() + (grid ? grid->EstimatedMemoryUse() : 0); } void Layout(Document *doc, wxDC &dc, int depth, int maxcolwidth, bool forcetiny) { tiny = (text.filtered && !grid) || forcetiny || doc->PickFont(dc, depth, text.relsize, text.stylebits); int ixs = 0, iys = 0; if (!tiny) sys->ImageSize(text.DisplayImage(), ixs, iys); int leftoffset = 0; if (!HasText()) { if (!ixs || !iys) { sx = sy = tiny ? 1 : dc.GetCharHeight(); } else { leftoffset = dc.GetCharHeight(); } } else { text.TextSize(dc, sx, sy, tiny, leftoffset, maxcolwidth); } if (ixs && iys) { sx += ixs + 2; sy = max(iys + 2, sy); } text.extent = sx + depth * dc.GetCharHeight(); txs = sx; tys = sy; if (GridShown(doc)) { if (HasHeader()) { if (verticaltextandgrid) { int osx = sx; if (drawstyle == DS_BLOBLINE && !tiny) sy += 4; grid->Layout(doc, dc, depth, sx, sy, leftoffset, sy, tiny || forcetiny); sx = max(sx, osx); } else { int osy = sy; if (drawstyle == DS_BLOBLINE && !tiny) sx += 18; grid->Layout(doc, dc, depth, sx, sy, sx, 0, tiny || forcetiny); sy = max(sy, osy); } } else tiny = grid->Layout(doc, dc, depth, sx, sy, 0, 0, forcetiny); } ycenteroff = !verticaltextandgrid ? (sy - tys) / 2 : 0; if (!tiny) { sx += g_margin_extra * 2; sy += g_margin_extra * 2; } } void Render(Document *doc, int bx, int by, wxDC &dc, int depth, int ml, int mr, int mt, int mb, int maxcolwidth, int cell_margin) { // Choose color from celltype (program operations) switch (celltype) { case CT_VARD: actualcellcolor = 0xFF8080; break; case CT_VARU: actualcellcolor = 0xFFA0A0; break; case CT_VIEWH: case CT_VIEWV: actualcellcolor = 0x80FF80; break; case CT_CODE: actualcellcolor = 0x8080FF; break; default: actualcellcolor = cellcolor; break; } uint parentcolor = doc->Background(); if (parent && this != doc->curdrawroot) { Cell *p = parent; while (p && p->drawstyle == DS_BLOBLINE) p = p == doc->curdrawroot ? nullptr : p->parent; if (p) parentcolor = p->actualcellcolor; } if (drawstyle == DS_GRID && actualcellcolor != parentcolor) { DrawRectangle(dc, actualcellcolor, bx - ml, by - mt, sx + ml + mr, sy + mt + mb); } if (drawstyle != DS_GRID && HasContent() && !tiny) { if (actualcellcolor == parentcolor) { uchar *cp = (uchar *)&actualcellcolor; loop(i, 4) cp[i] = cp[i] * 850 / 1000; } dc.SetBrush(wxBrush(actualcellcolor)); dc.SetPen(wxPen(actualcellcolor)); if (drawstyle == DS_BLOBSHIER) dc.DrawRoundedRectangle(bx - cell_margin, by - cell_margin, minx + cell_margin * 2, miny + cell_margin * 2, sys->roundness); else if (HasHeader()) dc.DrawRoundedRectangle(bx - cell_margin + g_margin_extra / 2, by - cell_margin + ycenteroff + g_margin_extra / 2, txs + cell_margin * 2 + g_margin_extra, tys + cell_margin * 2 + g_margin_extra, sys->roundness); // FIXME: this half a g_margin_extra is a bit of hack } dc.SetTextBackground(wxColour(actualcellcolor)); int xoff = verticaltextandgrid ? 0 : text.extent - depth * dc.GetCharHeight(); int yoff = text.Render(doc, bx, by + ycenteroff, depth, dc, xoff, maxcolwidth); yoff = verticaltextandgrid ? yoff : 0; if (GridShown(doc)) grid->Render(doc, bx, by, dc, depth, sx - xoff, sy - yoff, xoff, yoff); } void CloneStyleFrom(Cell const *o) { cellcolor = o->cellcolor; textcolor = o->textcolor; verticaltextandgrid = o->verticaltextandgrid; drawstyle = o->drawstyle; text.stylebits = o->text.stylebits; } /* Clones _p making a new copy of it. This does not mutate the called on cell */ Cell *Clone(Cell *_p) const { Cell *c = new Cell(_p, this, celltype, grid ? new Grid(grid->xs, grid->ys) : nullptr); c->text = text; c->text.cell = c; if (grid) { grid->Clone(c->grid); } return c; } bool IsInside(int x, int y) const { return x >= 0 && y >= 0 && x < sx && y < sy; } int GetX(Document *doc) const { return ox + (parent ? parent->GetX(doc) : doc->hierarchysize); } int GetY(Document *doc) const { return oy + (parent ? parent->GetY(doc) : doc->hierarchysize); } int Depth() const { return parent ? parent->Depth() + 1 : 0; } Cell *Parent(int i) { return i ? parent->Parent(i - 1) : this; } Cell *SetParent(Cell *g) { parent = g; return this; } bool IsParentOf(const Cell *c) { return c->parent == this || (c->parent && IsParentOf(c->parent)); } uint SwapColor(uint c) { return ((c & 0xFF) << 16) | (c & 0xFF00) | ((c & 0xFF0000) >> 16); } wxString ToText(int indent, const Selection &s, int format, Document *doc) { wxString str = text.ToText(indent, s, format); if (format == A_EXPCSV) { if (grid) return grid->ToText(indent, s, format, doc); str.Replace(L"\"", L"\"\""); return L"\"" + str + L"\""; } if (s.cursor != s.cursorend) return str; str.Append(L"\n"); if (grid) str.Append(grid->ToText(indent, s, format, doc)); if (format == A_EXPXML) { str.Prepend(L">"); if (text.relsize) { str.Prepend(L"\""); str.Prepend(wxString() << -text.relsize); str.Prepend(L" relsize=\""); } if (text.stylebits) { str.Prepend(L"\""); str.Prepend(wxString() << text.stylebits); str.Prepend(L" stylebits=\""); } if (cellcolor != doc->Background()) { str.Prepend(L"\""); str.Prepend(wxString() << cellcolor); str.Prepend(L" colorbg=\""); } if (textcolor != 0x000000) { str.Prepend(L"\""); str.Prepend(wxString() << textcolor); str.Prepend(L" colorfg=\""); } str.Prepend(L"\n"); } else if (format == A_EXPHTMLT) { wxString style; if (text.stylebits & STYLE_BOLD) style += L"font-weight: bold;"; if (text.stylebits & STYLE_ITALIC) style += L"font-style: italic;"; if (text.stylebits & STYLE_FIXED) style += L"font-family: monospace;"; if (text.stylebits & STYLE_UNDERLINE) style += L"text-decoration: underline;"; if (cellcolor != doc->Background()) style += wxString::Format(L"background-color: #%06X;", SwapColor(cellcolor)); if (textcolor != 0x000000) style += wxString::Format(L"color: #%06X;", SwapColor(textcolor)); str.Prepend(L""); str.Append(L' ', indent); str.Append(L"\n"); } else if (format == A_EXPHTMLO && text.t.Len()) { wxString h = wxString(L"h") + wxChar(L'0' + indent / 2) + L">"; str.Prepend(L"<" + h); str.Append(L' ', indent); str.Append(L"RelSize(dir, zoomdepth); } void Reset() { ox = oy = sx = sy = minx = miny = 0; } void ResetChildren() { Reset(); if (grid) grid->ResetChildren(); } void ResetLayout() { Reset(); if (parent) parent->ResetLayout(); } void LazyLayout(Document *doc, wxDC &dc, int depth, int maxcolwidth, bool forcetiny) { if (sx == 0) { Layout(doc, dc, depth, maxcolwidth, forcetiny); minx = sx; miny = sy; } else { sx = minx; sy = miny; } } void AddUndo(Document *doc) { ResetLayout(); doc->AddUndo(this); } void Save(wxDataOutputStream &dos) const { dos.Write8(celltype); dos.Write32(cellcolor); dos.Write32(textcolor); dos.Write8(drawstyle); if (HasTextState()) { dos.Write8(grid ? TS_BOTH : TS_TEXT); text.Save(dos); if (grid) grid->Save(dos); } else if (grid) { dos.Write8(TS_GRID); grid->Save(dos); } else { dos.Write8(TS_NEITHER); } } Grid *AddGrid(int x = 1, int y = 1) { if (!grid) { grid = new Grid(x, y, this); grid->InitCells(this); if (parent) grid->CloneStyleFrom(parent->grid); } return grid; } Cell *LoadGrid(wxDataInputStream &dis, int &numcells, int &textbytes) { int xs = dis.Read32(); Grid *g = new Grid(xs, dis.Read32()); grid = g; g->cell = this; if (!g->LoadContents(dis, numcells, textbytes)) return nullptr; return this; } static Cell *LoadWhich(wxDataInputStream &dis, Cell *_p, int &numcells, int &textbytes) { Cell *c = new Cell(_p, nullptr, dis.Read8()); numcells++; if (sys->versionlastloaded >= 8) { c->cellcolor = dis.Read32() & 0xFFFFFF; c->textcolor = dis.Read32() & 0xFFFFFF; } if (sys->versionlastloaded >= 15) c->drawstyle = dis.Read8(); int ts; switch (ts = dis.Read8()) { case TS_BOTH: case TS_TEXT: c->text.Load(dis); textbytes += c->text.t.Len(); if (ts == TS_TEXT) return c; case TS_GRID: return c->LoadGrid(dis, numcells, textbytes); case TS_NEITHER: return c; default: return nullptr; } } Cell *Eval(Evaluator &ev) { // Evaluates the internal grid if it exists, otherwise, evaluate the text. return grid ? grid->Eval(ev) : text.Eval(ev); } void Paste(Document *doc, const Cell *c, Selection &s) { parent->AddUndo(doc); ResetLayout(); if (c->HasText()) { if (!HasText() || !s.TextEdit()) { cellcolor = c->cellcolor; textcolor = c->textcolor; text.stylebits = c->text.stylebits; } text.Insert(doc, c->text.t, s); } if (c->text.image) text.image = c->text.image; if (c->grid) { auto cg = new Grid(c->grid->xs, c->grid->ys); cg->cell = this; c->grid->Clone(cg); // Note: deleting grid may invalidate c if its a child of grid, so clear it. c = nullptr; DELETEP(grid); // FIXME: could merge instead? grid = cg; if (!HasText()) grid->MergeWithParent(parent->grid, s); // deletes grid/this. } } Cell *FindNextSearchMatch(wxString &search, Cell *best, Cell *selected, bool &lastwasselected) { if (text.t.Lower().Find(search) >= 0) { if (lastwasselected) best = this; lastwasselected = false; } if (selected == this) lastwasselected = true; if (grid) best = grid->FindNextSearchMatch(search, best, selected, lastwasselected); return best; } Cell *FindLink(Selection &s, Cell *link, Cell *best, bool &lastthis, bool &stylematch, bool forward) { if (grid) best = grid->FindLink(s, link, best, lastthis, stylematch, forward); if (link == this) { lastthis = true; return best; } if (link->text.ToText(0, s, A_EXPTEXT) == text.t) { if (link->text.stylebits != text.stylebits || link->cellcolor != cellcolor || link->textcolor != textcolor) { if (!stylematch) best = nullptr; stylematch = true; } else if (stylematch) { return best; } if (!best || lastthis) { lastthis = false; return this; } } return best; } void FindReplaceAll(const wxString &str) { if (grid) grid->FindReplaceAll(str); text.ReplaceStr(str); } Cell *FindExact(wxString &s) { return text.t == s ? this : (grid ? grid->FindExact(s) : nullptr); } void ImageRefCount() { if (grid) grid->ImageRefCount(); if (text.image) text.image->trefc++; } void SetBorder(int width) { if (grid) grid->user_grid_outer_spacing = width; } void ColorChange(int which, uint color) { switch (which) { case A_CELLCOLOR: cellcolor = color; break; case A_TEXTCOLOR: textcolor = color; break; case A_BORDCOLOR: if (grid) grid->bordercolor = color; break; } } void SetGridTextLayout(int ds, bool vert, bool noset) { if (!noset) verticaltextandgrid = vert; if (ds != -1) drawstyle = ds; if (grid) grid->SetGridTextLayout(ds, vert, noset, grid->SelectAll()); } bool IsTag(Document *doc) { return doc->tags.find(text.t) != doc->tags.end(); } void MaxDepthLeaves(int curdepth, int &maxdepth, int &leaves) { if (curdepth > maxdepth) maxdepth = curdepth; if (grid) grid->MaxDepthLeaves(curdepth + 1, maxdepth, leaves); else leaves++; } int ColWidth() { return parent ? parent->grid->colwidths[parent->grid->FindCell(this).x] : sys->defaultmaxcolwidth; } void CollectCells(Vector &itercells, bool recurse = true) { itercells.push() = this; if (grid && recurse) grid->CollectCells(itercells); } }; treesheets-1.0.2/src/document.h000077500000000000000000002223401352107072600164540ustar00rootroot00000000000000#ifndef PACKAGE_VERSION #define PACKAGE_VERSION __DATE__ #endif struct UndoItem { Vector path, selpath; Selection sel; Cell *clone; size_t estimated_size; UndoItem() : clone(nullptr), estimated_size(0) {} ~UndoItem() { DELETEP(clone); } }; struct Document { TSCanvas *sw; Cell *rootgrid; Selection hover, selected, begindrag; int isctrlshiftdrag; int originx, originy, maxx, maxy, centerx, centery; int layoutxs, layoutys, hierarchysize, fgutter; int lasttextsize, laststylebits; Cell *curdrawroot; // for use during Render() calls Vector undolist, redolist; Vector drawpath; int pathscalebias; wxString filename; long lastmodsinceautosave, undolistsizeatfullsave, lastsave; bool modified, tmpsavesuccess; wxDataObjectComposite *dataobjc; wxTextDataObject *dataobjt; wxBitmapDataObject *dataobji; wxFileDataObject *dataobjf; //wxHTMLDataObject *dataobjh; //wxRichTextBufferDataObject *dataobjr; struct MyPrintout : wxPrintout { Document *doc; MyPrintout(Document *d) : doc(d), wxPrintout(L"printout") {} bool OnPrintPage(int page) { wxDC *dc = GetDC(); if (!dc) return false; doc->Print(*dc, *this); return true; } bool OnBeginDocument(int startPage, int endPage) { return wxPrintout::OnBeginDocument(startPage, endPage); } void GetPageInfo(int *minPage, int *maxPage, int *selPageFrom, int *selPageTo) { *minPage = 1; *maxPage = 1; *selPageFrom = 1; *selPageTo = 1; } bool HasPage(int pageNum) { return pageNum == 1; } }; bool while_printing; wxPrintData printData; wxPageSetupDialogData pageSetupData; uint printscale; bool blink; bool redrawpending; bool scaledviewingmode; double currentviewscale; bool searchfilter; std::map tags; int editfilter; Vector itercells; wxDateTime lastmodificationtime; #define loopcellsin(par, c) \ CollectCells(par); \ loopv(_i, itercells) for (Cell *c = itercells[_i]; c; c = nullptr) #define loopallcells(c) \ CollectCells(rootgrid); \ loopv(_i, itercells) for (Cell *c = itercells[_i]; c; c = nullptr) #define loopallcellssel(c, rec) \ CollectCellsSel(rec); \ loopv(_i, itercells) for (Cell *c = itercells[_i]; c; c = nullptr) Document() : sw(nullptr), rootgrid(nullptr), pathscalebias(0), filename(L""), lastmodsinceautosave(0), undolistsizeatfullsave(0), lastsave(wxGetLocalTime()), modified(false), centerx(0), centery(0), while_printing(false), printscale(0), blink(true), redrawpending(false), scaledviewingmode(false), currentviewscale(1), editfilter(0), searchfilter(false), tmpsavesuccess(true), fgutter(6) { dataobjc = new wxDataObjectComposite(); // deleted by DropTarget dataobjc->Add(dataobji = new wxBitmapDataObject()); dataobjc->Add(dataobjt = new wxTextDataObject()); dataobjc->Add(dataobjf = new wxFileDataObject()); //dataobjc->Add(dataobjh = new wxHTMLDataObject(), true); // Prefer HTML over text, doesn't seem to work. //dataobjc->Add(dataobjr = new wxRichTextBufferDataObject()); ResetFont(); pageSetupData = printData; pageSetupData.SetMarginTopLeft(wxPoint(15, 15)); pageSetupData.SetMarginBottomRight(wxPoint(15, 15)); } ~Document() { itercells.setsize_nd(0); DELETEP(rootgrid); } uint Background() { return rootgrid ? rootgrid->cellcolor : 0xFFFFFF; } void InitWith(Cell *r, wxString filename) { rootgrid = r; selected = Selection(r->grid, 0, 0, 1, 1); ChangeFileName(filename, false); } void UpdateFileName(int page = -1) { sys->frame->SetPageTitle(filename, modified ? (lastmodsinceautosave ? L"*" : L"+") : L"", page); } void ChangeFileName(const wxString &fn, bool checkext) { filename = fn; if (checkext) { wxFileName wxfn(filename); if (!wxfn.HasExt()) filename.Append(L".cts"); } UpdateFileName(); } const wxChar *SaveDB(bool *success, bool istempfile = false, int page = -1) { if (filename.empty()) return _(L"Save cancelled."); { // limit destructors wxBusyCursor wait; if (!istempfile && sys->makebaks && ::wxFileExists(filename)) { ::wxRenameFile(filename, sys->BakName(filename)); } wxString sfn = istempfile ? sys->TmpName(filename) : filename; wxFFileOutputStream fos(sfn); if (!fos.IsOk()) { if (!istempfile) wxMessageBox( _(L"Error writing TreeSheets file! (try saving under new filename)."), sfn.wx_str(), wxOK, sys->frame); return _(L"Error writing to file."); } wxDataOutputStream sos(fos); fos.Write("TSFF", 4); char vers = TS_VERSION; fos.Write(&vers, 1); loopv(i, sys->imagelist) sys->imagelist[i]->trefc = 0; rootgrid->ImageRefCount(); int realindex = 0; loopv(i, sys->imagelist) { Image &image = *sys->imagelist[i]; if (image.trefc) { fos.Write("I", 1); sos.WriteDouble(image.display_scale); wxImage im = image.bm_orig.ConvertToImage(); im.SaveFile(fos, wxBITMAP_TYPE_PNG); image.savedindex = realindex++; } } fos.Write("D", 1); wxZlibOutputStream zos(fos, 9); if (!zos.IsOk()) return _(L"Zlib error while writing file."); wxDataOutputStream dos(zos); rootgrid->Save(dos); for (auto tagit = tags.begin(); tagit != tags.end(); ++tagit) { dos.WriteString(tagit->first); } dos.WriteString(wxEmptyString); } lastmodsinceautosave = 0; lastsave = wxGetLocalTime(); if (!istempfile) { undolistsizeatfullsave = undolist.size(); modified = false; tmpsavesuccess = true; sys->FileUsed(filename, this); if (::wxFileExists(sys->TmpName(filename))) ::wxRemoveFile(sys->TmpName(filename)); } if (sys->autohtmlexport) { ExportFile(sys->ExtName(filename, L".html"), A_EXPHTMLT, false); } UpdateFileName(page); if (success) *success = true; return _(L"File saved succesfully."); } void DrawSelect(wxDC &dc, Selection &s, bool refreshinstead = false, bool cursoronly = false) { #ifdef SIMPLERENDER if (refreshinstead) { Refresh(); return; } #endif if (!s.g) return; ResetFont(); s.g->DrawSelect(this, dc, s, cursoronly); } void DrawSelectMove(wxDC &dc, Selection &s, bool refreshalways = false, bool refreshinstead = true) { if (ScrollIfSelectionOutOfView(dc, s)) return; if (refreshalways) RefreshReset(); else DrawSelect(dc, s, refreshinstead); } bool ScrollIfSelectionOutOfView(wxDC &dc, Selection &s, bool refreshalways = false) { if (!scaledviewingmode) { // required, since sizes of things may have been reset by the last editing operation Layout(dc); int canvasw, canvash; sw->GetClientSize(&canvasw, &canvash); if ((layoutys > canvash || layoutxs > canvasw) && s.g) { wxRect r = s.g->GetRect(this, s, true); if (r.y < originy || r.y + r.height > maxy || r.x < originx || r.x + r.width > maxx) { int curx, cury; sw->GetViewStart(&curx, &cury); sw->SetScrollbars(1, 1, layoutxs, layoutys, r.width > canvasw || r.x < originx ? r.x : r.x + r.width > maxx ? r.x + r.width - canvasw : curx, r.height > canvash || r.y < originy ? r.y : r.y + r.height > maxy ? r.y + r.height - canvash : cury, true); RefreshReset(); return true; } } } if (refreshalways) Refresh(); return refreshalways; } void ScrollOrZoom(wxDC &dc, bool zoomiftiny = false) { if (!selected.g) return; Cell *drawroot = WalkPath(drawpath); // If we jumped to a cell which may be insided a folded cell, we have to unfold it // because the rest of the code doesn't deal with a selection that is invisible :) for (Cell *cg = selected.g->cell; cg; cg = cg->parent) { // Unless we're under the drawroot, no need to unfold further. if (cg == drawroot) break; if (cg->grid->folded) { cg->grid->folded = false; cg->ResetLayout(); cg->ResetChildren(); } } for (Cell *cg = selected.g->cell; cg; cg = cg->parent) if (cg == drawroot) { if (zoomiftiny) ZoomTiny(dc); DrawSelectMove(dc, selected, true); return; } Zoom(-100, dc, false, false); if (zoomiftiny) ZoomTiny(dc); DrawSelectMove(dc, selected, true); } void ZoomTiny(wxDC &dc) { Cell *c = selected.GetCell(); if (c && c->tiny) { int rels = c->text.relsize; while (FontIsMini(TextSize(c->Depth(), rels))) rels--; Zoom(c->text.relsize - rels, dc); // seems to leave selection box in a weird location? } } void Blink() { if (redrawpending) return; #ifndef SIMPLERENDER wxClientDC dc(sw); sw->DoPrepareDC(dc); ShiftToCenter(dc); DrawSelect(dc, selected, false, true); blink = !blink; DrawSelect(dc, selected, true, true); #endif } void ResetCursor() { if (selected.g) selected.SetCursorEdit(this, selected.TextEdit()); } void Hover(int x, int y, wxDC &dc) { if (redrawpending) return; ShiftToCenter(dc); ResetFont(); Selection prev = hover; hover = Selection(); auto drawroot = WalkPath(drawpath); if (drawroot->grid) drawroot->grid->FindXY(this, x - centerx / currentviewscale - hierarchysize, y - centery / currentviewscale - hierarchysize, dc); if (!(prev == hover)) { if (prev.g) prev.g->DrawHover(this, dc, prev); if (hover.g) hover.g->DrawHover(this, dc, hover); } sys->UpdateStatus(hover); } void Select(wxDC &dc, bool right, int isctrlshift) { begindrag = Selection(); if (right && hover.IsInside(selected)) return; ShiftToCenter(dc); DrawSelect(dc, selected); if (selected.GetCell() == hover.GetCell() && hover.GetCell()) hover.EnterEditOnly(this); selected = hover; begindrag = hover; isctrlshiftdrag = isctrlshift; DrawSelectMove(dc, selected); ResetCursor(); return; } void SelectUp() { if (!isctrlshiftdrag || isctrlshiftdrag == 3 || begindrag.EqLoc(selected)) return; Cell *c = selected.GetCell(); if (!c) return; Cell *tc = begindrag.ThinExpand(this); selected = begindrag; if (tc) { auto is_parent = tc->IsParentOf(c); auto tc_parent = tc->parent; // tc may be deleted. tc->Paste(this, c, begindrag); // If is_parent, c has been deleted already. if (isctrlshiftdrag == 1 && !is_parent) { c->parent->AddUndo(this); Selection cs = c->parent->grid->FindCell(c); c->parent->grid->MultiCellDeleteSub(this, cs); } hover = selected = tc_parent ? tc_parent->grid->FindCell(tc) : Selection(); } Refresh(); } void Drag(wxDC &dc) { if (!selected.g || !hover.g || !begindrag.g) return; if (isctrlshiftdrag) { begindrag = hover; return; } if (hover.Thin()) return; ShiftToCenter(dc); if (begindrag.Thin() || selected.Thin()) { DrawSelect(dc, selected); begindrag = selected = hover; DrawSelect(dc, selected, true); } else { Selection old = selected; selected.Merge(begindrag, hover); if (!(old == selected)) { DrawSelect(dc, old); DrawSelect(dc, selected, true); } } ResetCursor(); return; } void Zoom(int dir, wxDC &dc, bool fromroot = false, bool selectionmaybedrawroot = true) { int len = max(0, (fromroot ? 0 : drawpath.size()) + dir); if (!len && !drawpath.size()) return; if (dir > 0) { if (!selected.g) return; Cell *c = selected.GetCell(); CreatePath(c && c->grid ? c : selected.g->cell, drawpath); } else if (dir < 0) { Cell *drawroot = WalkPath(drawpath); if (drawroot->grid && drawroot->grid->folded && selectionmaybedrawroot) selected = drawroot->parent->grid->FindCell(drawroot); } while (len < drawpath.size()) drawpath.remove(0); Cell *drawroot = WalkPath(drawpath); if (selected.GetCell() == drawroot && drawroot->grid) { selected = Selection(drawroot->grid, 0, 0, drawroot->grid->xs, drawroot->grid->ys); } drawroot->ResetLayout(); drawroot->ResetChildren(); Layout(dc); DrawSelectMove(dc, selected, true, false); } const wxChar *NoSel() { return _(L"This operation requires a selection."); } const wxChar *OneCell() { return _(L"This operation works on a single selected cell only."); } const wxChar *NoThin() { return _(L"This operation doesn't work on thin selections."); } const wxChar *NoGrid() { return _(L"This operation requires a cell that contains a grid."); } const wxChar *Wheel(wxDC &dc, int dir, bool alt, bool ctrl, bool shift, bool hierarchical = true) { if (!dir) return nullptr; ShiftToCenter(dc); if (alt) { if (!selected.g) return NoSel(); if (selected.xs > 0) { // FIXME: should do undo, but this is a lot of undos that need to coalesced, same // for relsize selected.g->ResizeColWidths(dir, selected, hierarchical); selected.g->cell->ResetLayout(); selected.g->cell->ResetChildren(); sys->UpdateStatus(selected); Refresh(); return dir > 0 ? _(L"Column width increased.") : _(L"Column width decreased."); } return L"nothing to resize"; } else if (shift) { if (!selected.g) return NoSel(); selected.g->cell->AddUndo(this); selected.g->ResetChildren(); selected.g->RelSize(-dir, selected, pathscalebias); sys->UpdateStatus(selected); Refresh(); return dir > 0 ? _(L"Text size increased.") : _(L"Text size decreased."); } else if (ctrl) { int steps = abs(dir); dir = sign(dir); loop(i, steps) Zoom(dir, dc); return dir > 0 ? _(L"Zoomed in.") : _(L"Zoomed out."); } else { ASSERT(0); return nullptr; } } void Layout(wxDC &dc) { ResetFont(); dc.SetUserScale(1, 1); curdrawroot = WalkPath(drawpath); int psb = curdrawroot == rootgrid ? 0 : curdrawroot->MinRelsize(); if (psb < 0 || psb == INT_MAX) psb = 0; if (psb != pathscalebias) curdrawroot->ResetChildren(); pathscalebias = psb; curdrawroot->LazyLayout(this, dc, 0, curdrawroot->ColWidth(), false); ResetFont(); PickFont(dc, 0, 0, 0); hierarchysize = 0; for (Cell *p = curdrawroot->parent; p; p = p->parent) if (p->text.t.Len()) hierarchysize += dc.GetCharHeight(); hierarchysize += fgutter; layoutxs = curdrawroot->sx + hierarchysize + fgutter; layoutys = curdrawroot->sy + hierarchysize + fgutter; } void ShiftToCenter(wxDC &dc) { int dlx = dc.DeviceToLogicalX(0); int dly = dc.DeviceToLogicalY(0); dc.SetDeviceOrigin(dlx > 0 ? -dlx : centerx, dly > 0 ? -dly : centery); dc.SetUserScale(currentviewscale, currentviewscale); } void Render(wxDC &dc) { ResetFont(); PickFont(dc, 0, 0, 0); dc.SetTextForeground(*wxLIGHT_GREY); int i = 0; for (Cell *p = curdrawroot->parent; p; p = p->parent) if (p->text.t.Len()) { int off = hierarchysize - dc.GetCharHeight() * ++i; wxString s = p->text.t; if ((int)s.Len() > sys->defaultmaxcolwidth) { // should take the width of these into account for layoutys, but really, the // worst that can happen on a thin window is that its rendering gets cut off s = s.Left(sys->defaultmaxcolwidth) + L"..."; } dc.DrawText(s, off, off); } dc.SetTextForeground(*wxBLACK); curdrawroot->Render(this, hierarchysize, hierarchysize, dc, 0, 0, 0, 0, 0, curdrawroot->ColWidth(), 0); } void Draw(wxDC &dc) { redrawpending = false; dc.SetBackground(wxBrush(wxColor(Background()))); dc.Clear(); if (!rootgrid) return; sw->GetClientSize(&maxx, &maxy); Layout(dc); double xscale = maxx / (double)layoutxs; double yscale = maxy / (double)layoutys; currentviewscale = min(xscale, yscale); if (currentviewscale > 5) currentviewscale = 5; else if (currentviewscale < 1) currentviewscale = 1; if (scaledviewingmode && currentviewscale > 1) { sw->SetVirtualSize(maxx, maxy); dc.SetUserScale(currentviewscale, currentviewscale); maxx /= currentviewscale; maxy /= currentviewscale; originx = originy = 0; } else { currentviewscale = 1; dc.SetUserScale(1, 1); int drx = max(layoutxs, maxx); int dry = max(layoutys, maxy); sw->SetVirtualSize(drx, dry); sw->CalcUnscrolledPosition(0, 0, &originx, &originy); maxx += originx; maxy += originy; } centerx = sys->centered && !originx && maxx > layoutxs ? (maxx - layoutxs) / 2 * currentviewscale : 0; centery = sys->centered && !originy && maxy > layoutys ? (maxy - layoutys) / 2 * currentviewscale : 0; sw->DoPrepareDC(dc); ShiftToCenter(dc); Render(dc); DrawSelect(dc, selected); if (hover.g) hover.g->DrawHover(this, dc, hover); if (scaledviewingmode) { dc.SetUserScale(1, 1); } } void Print(wxDC &dc, wxPrintout &po) { Layout(dc); maxx = layoutxs; maxy = layoutys; originx = originy = 0; po.FitThisSizeToPage(printscale ? wxSize(printscale, 1) : wxSize(maxx, maxy)); wxRect fitRect = po.GetLogicalPageRect(); wxCoord xoff = (fitRect.width - maxx) / 2; wxCoord yoff = (fitRect.height - maxy) / 2; po.OffsetLogicalOrigin(xoff, yoff); while_printing = true; Render(dc); while_printing = false; } int TextSize(int depth, int relsize) { return max(g_mintextsize(), g_deftextsize - depth - relsize + pathscalebias); } bool FontIsMini(int textsize) { return textsize == g_mintextsize(); } bool PickFont(wxDC &dc, int depth, int relsize, int stylebits) { int textsize = TextSize(depth, relsize); if (textsize != lasttextsize || stylebits != laststylebits) { wxFont font(textsize - (while_printing || scaledviewingmode), stylebits & STYLE_FIXED ? wxFONTFAMILY_TELETYPE : wxFONTFAMILY_DEFAULT, stylebits & STYLE_ITALIC ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL, stylebits & STYLE_BOLD ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL, (stylebits & STYLE_UNDERLINE) != 0, stylebits & STYLE_FIXED ? L"" : sys->defaultfont); if (stylebits & STYLE_STRIKETHRU) font.SetStrikethrough(true); dc.SetFont(font); lasttextsize = textsize; laststylebits = stylebits; } return FontIsMini(textsize); } void ResetFont() { lasttextsize = INT_MAX; laststylebits = -1; } void RefreshReset() { Refresh(); } void Refresh() { hover.g = nullptr; RefreshHover(); } void RefreshHover() { redrawpending = true; #ifndef __WXMSW__ if (sw) sw->Refresh(false); #endif sys->UpdateStatus(selected); sys->frame->nb->Refresh(false); } void ClearSelectionRefresh() { selected.g = nullptr; Refresh(); } bool CheckForChanges() { if (modified) { ThreeChoiceDialog tcd(sys->frame, filename, _(L"Changes have been made, are you sure you wish to continue?"), _(L"Save and Close"), _(L"Discard Changes"), _(L"Cancel")); switch (tcd.Run()) { case 0: { bool success = false; Save(false, &success); return !success; } case 1: return false; default: case 2: return true; } } return false; } bool CloseDocument() { bool keep = CheckForChanges(); if (!keep && !filename.empty() && ::wxFileExists(sys->TmpName(filename))) ::wxRemoveFile(sys->TmpName(filename)); return keep; } const wxChar *DoubleClick(wxDC &dc) { if (!selected.g) return nullptr; ShiftToCenter(dc); Cell *c = selected.GetCell(); if (selected.Thin()) { selected.SelAll(); Refresh(); } else if (c) { DrawSelect(dc, selected); if (selected.TextEdit()) { c->text.SelectWord(selected); begindrag = selected; } else { selected.EnterEditOnly(this); } DrawSelect(dc, selected, true); } return nullptr; } const wxChar *Export(const wxChar *fmt, const wxChar *pat, const wxChar *msg, int k) { wxString fn = ::wxFileSelector(msg, L"", L"", fmt, pat, wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxFD_CHANGE_DIR); if (fn.empty()) return _(L"Export cancelled."); return ExportFile(fn, k, true); } const wxChar *ExportFile(const wxString &fn, int k, bool currentview) { auto root = currentview ? curdrawroot : rootgrid; if (k == A_EXPCSV) { int maxdepth = 0, leaves = 0; root->MaxDepthLeaves(0, maxdepth, leaves); if (maxdepth > 1) return _(L"Cannot export grid that is not flat (zoom the view to the desired grid, " L"and/or use Flatten)."); } if (k == A_EXPIMAGE) { maxx = layoutxs; maxy = layoutys; originx = originy = 0; wxBitmap bm(maxx, maxy, 24); wxMemoryDC mdc(bm); DrawRectangle(mdc, Background(), 0, 0, maxx, maxy); Render(mdc); Refresh(); if (!bm.SaveFile(fn, wxBITMAP_TYPE_PNG)) return _(L"Error writing PNG file!"); } else { wxFFileOutputStream fos(fn, L"w+b"); if (!fos.IsOk()) { wxMessageBox(_(L"Error exporting file!"), fn.wx_str(), wxOK, sys->frame); return _(L"Error writing to file!"); } wxTextOutputStream dos(fos); wxString content = root->ToText(0, Selection(), k, this); switch (k) { case A_EXPXML: dos.WriteString( L"\n" L"\n" L"\n" L"\n" L"]>\n"); dos.WriteString(content); break; case A_EXPHTMLT: case A_EXPHTMLO: dos.WriteString( L"\n\n\nexport of " L"TreeSheets file "); dos.WriteString(filename); dos.WriteString( L"\n\n\n\n"); dos.WriteString(content); dos.WriteString(L"\n\n"); break; case A_EXPCSV: case A_EXPTEXT: dos.WriteString(content); break; } } return _(L"File exported successfully."); } const wxChar *Save(bool saveas, bool *success = nullptr) { if (!saveas && !filename.empty()) { return SaveDB(success); } wxString fn = ::wxFileSelector(_(L"Choose TreeSheets file to save:"), L"", L"", L"cts", L"TreeSheets Files (*.cts)|*.cts|All Files (*.*)|*.*", wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxFD_CHANGE_DIR); if (fn.empty()) return _(L"Save cancelled."); // avoid name being set to "" ChangeFileName(fn, true); return SaveDB(success); } void AutoSave(bool minimized, int page) { if (tmpsavesuccess && !filename.empty() && lastmodsinceautosave && (lastmodsinceautosave + 60 < wxGetLocalTime() || lastsave + 300 < wxGetLocalTime() || minimized)) { tmpsavesuccess = false; SaveDB(&tmpsavesuccess, true, page); } } const wxChar *Key(wxDC &dc, wxChar uk, int k, bool alt, bool ctrl, bool shift, bool &unprocessed) { Cell *c = selected.GetCell(); if (uk == WXK_NONE || (k < ' ' && k)) { switch (k) { case WXK_BACK: // no menu shortcut available in wxwidgets return Action(dc, A_BACKSPACE); case WXK_RETURN: return Action(dc, shift ? A_ENTERGRID : A_ENTERCELL); case WXK_ESCAPE: // docs say it can be used as a menu accelerator, but it does not // trigger from there? return Action(dc, A_CANCELEDIT); #ifdef WIN32 // works fine on Linux, not sure OS X case WXK_PAGEDOWN: sw->CursorScroll(0, g_scrollratecursor); return nullptr; case WXK_PAGEUP: sw->CursorScroll(0, -g_scrollratecursor); return nullptr; #endif #ifdef __WXGTK__ // should not be needed... on Windows / OS X they arrive as menu event and never // arrive here, on Linux // we have to process these manually? case WXK_LEFT: return Action(dc, shift ? (ctrl ? A_SCLEFT : A_SLEFT) : (ctrl ? A_MLEFT : A_LEFT)); case WXK_RIGHT: return Action( dc, shift ? (ctrl ? A_SCRIGHT : A_SRIGHT) : (ctrl ? A_MRIGHT : A_RIGHT)); case WXK_UP: return Action(dc, shift ? (ctrl ? A_SCUP : A_SUP) : (ctrl ? A_MUP : A_UP)); case WXK_DOWN: return Action(dc, shift ? (ctrl ? A_SCDOWN : A_SDOWN) : (ctrl ? A_MDOWN : A_DOWN)); case WXK_HOME: return Action(dc, shift ? (ctrl ? A_SHOME : A_SHOME) : (ctrl ? A_CHOME : A_HOME)); case WXK_END: return Action(dc, shift ? (ctrl ? A_SEND : A_SEND) : (ctrl ? A_CEND : A_END)); case WXK_TAB: if (ctrl && !shift) { // WXK_CONTROL_I (italics) arrives as the same keycode as WXK_TAB + ctrl on // Linux?? // They're both keycode 9 in defs.h // We ignore it here, such that CTRL+I works, but it means only // SHIFT+CTRL+TAB works on Linux as // a way to switch tabs. // Also, even though we ignore CTRL+TAB, and it is not assigned in the // menus, it still has the // effect of de-selecting // the current tab (requires a click to re-activate). FIXME?? break; } return Action( dc, shift ? (ctrl ? A_PREVFILE : A_PREV) : (ctrl ? A_NEXTFILE : A_NEXT)); #endif } } else if (uk >= ' ') { if (!selected.g) return NoSel(); if (!(c = selected.ThinExpand(this))) return OneCell(); ShiftToCenter(dc); c->AddUndo(this); // FIXME: not needed for all keystrokes, or at least, merge all // keystroke undos within same cell if (!selected.TextEdit()) c->text.Clear(this, selected); c->text.Key(uk, selected); ScrollIfSelectionOutOfView(dc, selected, true); return nullptr; } unprocessed = true; return nullptr; } const wxChar *Action(wxDC &dc, int k) { ShiftToCenter(dc); switch (k) { case A_RUN: sys->ev.Eval(rootgrid); rootgrid->ResetChildren(); ClearSelectionRefresh(); return _(L"Evaluation finished."); case A_UNDO: if (undolist.size()) { Undo(dc, undolist, redolist); return nullptr; } else { return _(L"Nothing more to undo."); } case A_REDO: if (redolist.size()) { Undo(dc, redolist, undolist, true); return nullptr; } else { return _(L"Nothing more to redo."); } case A_SAVE: return Save(false); case A_SAVEAS: return Save(true); case A_EXPXML: return Export(L"xml", L"*.xml", _(L"Choose XML file to write"), k); case A_EXPHTMLT: case A_EXPHTMLO: return Export(L"html", L"*.html", _(L"Choose HTML file to write"), k); case A_EXPTEXT: return Export(L"txt", L"*.txt", _(L"Choose Text file to write"), k); case A_EXPIMAGE: return Export(L"png", L"*.png", _(L"Choose PNG file to write"), k); case A_EXPCSV: return Export(L"csv", L"*.csv", _(L"Choose CSV file to write"), k); case A_IMPXML: case A_IMPXMLA: case A_IMPTXTI: case A_IMPTXTC: case A_IMPTXTS: case A_IMPTXTT: return sys->Import(k); case A_OPEN: { wxString fn = ::wxFileSelector(_(L"Please select a TreeSheets file to load:"), L"", L"", L"cts", L"TreeSheets Files (*.cts)|*.cts|All Files (*.*)|*.*", wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_CHANGE_DIR); return sys->Open(fn); } case A_CLOSE: { if (sys->frame->nb->GetPageCount() <= 1) { sys->frame->fromclosebox = false; sys->frame->Close(); return nullptr; } if (!CloseDocument()) { int p = sys->frame->nb->GetSelection(); // sys->frame->nb->AdvanceSelection(); sys->frame->nb->DeletePage(p); sys->RememberOpenFiles(); } return nullptr; } case A_NEW: { int size = (int)::wxGetNumberFromUser(_(L"What size grid would you like to start with?"), _(L"size:"), _(L"New Sheet"), 10, 1, 25, sys->frame); if (size < 0) return _(L"New file cancelled."); sys->InitDB(size); sys->frame->GetCurTab()->doc->Refresh(); return nullptr; } case A_ABOUT: { wxAboutDialogInfo info; info.SetName(L"TreeSheets"); info.SetVersion(wxT(PACKAGE_VERSION)); info.SetCopyright(L"(C) 2009 Wouter van Oortmerssen"); info.SetDescription(_(L"The Free Form Hierarchical Information Organizer")); wxAboutBox(info); return nullptr; } case A_HELPI: sys->LoadTut(); return nullptr; case A_HELP: #ifdef __WXMAC__ wxLaunchDefaultBrowser(L"file://" + sys->frame->GetPath(L"docs/tutorial.html")); // RbrtPntn #else wxLaunchDefaultBrowser(sys->frame->GetPath(L"docs/tutorial.html")); #endif return nullptr; case A_ZOOMIN: return Wheel(dc, 1, false, true, false); // Zoom( 1, dc); return "zoomed in (menu)"; case A_ZOOMOUT: return Wheel(dc, -1, false, true, false); // Zoom(-1, dc); return "zoomed out (menu)"; case A_INCSIZE: return Wheel(dc, 1, false, false, true); case A_DECSIZE: return Wheel(dc, -1, false, false, true); case A_INCWIDTH: return Wheel(dc, 1, true, false, false); case A_DECWIDTH: return Wheel(dc, -1, true, false, false); case A_INCWIDTHNH: return Wheel(dc, 1, true, false, false, false); case A_DECWIDTHNH: return Wheel(dc, -1, true, false, false, false); case A_DEFFONT: { wxFontData fdat; fdat.SetInitialFont(wxFont(g_deftextsize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, sys->defaultfont)); wxFontDialog fd(sys->frame, fdat); if (fd.ShowModal() == wxID_OK) { wxFont font = fd.GetFontData().GetChosenFont(); sys->defaultfont = font.GetFaceName(); g_deftextsize = min(20, max(10, font.GetPointSize())); sys->cfg->Write(L"defaultfont", sys->defaultfont); sys->cfg->Write(L"defaultfontsize", g_deftextsize); // rootgrid->ResetChildren(); sys->frame->TabsReset(); // ResetChildren on all Refresh(); } return nullptr; } case A_PRINT: { wxPrintDialogData printDialogData(printData); wxPrinter printer(&printDialogData); MyPrintout printout(this); if (printer.Print(sys->frame, &printout, true)) { printData = printer.GetPrintDialogData().GetPrintData(); } return nullptr; } case A_PRINTSCALE: { printscale = (uint)::wxGetNumberFromUser( _(L"How many pixels wide should a page be? (0 for auto fit)"), _(L"scale:"), _(L"Set Print Scale"), 0, 0, 5000, sys->frame); return nullptr; } case A_PREVIEW: { wxPrintDialogData printDialogData(printData); wxPrintPreview *preview = new wxPrintPreview( new MyPrintout(this), new MyPrintout(this), &printDialogData); wxPreviewFrame *pframe = new wxPreviewFrame(preview, sys->frame, _(L"Print Preview"), wxPoint(100, 100), wxSize(600, 650)); pframe->Centre(wxBOTH); pframe->Initialize(); pframe->Show(true); return nullptr; } case A_PAGESETUP: { pageSetupData = printData; wxPageSetupDialog pageSetupDialog(sys->frame, &pageSetupData); pageSetupDialog.ShowModal(); printData = pageSetupDialog.GetPageSetupDialogData().GetPrintData(); pageSetupData = pageSetupDialog.GetPageSetupDialogData(); return nullptr; } case A_NEXTFILE: sys->frame->CycleTabs(1); return nullptr; case A_PREVFILE: sys->frame->CycleTabs(-1); return nullptr; case A_CUSTCOL: { uint c = PickColor(sys->frame, sys->customcolor); if (c != (uint)-1) sys->customcolor = c; return nullptr; } case A_DEFBGCOL: { uint oldbg = Background(); uint c = PickColor(sys->frame, oldbg); if (c != (uint)-1) { rootgrid->AddUndo(this); loopallcells(lc) { if (lc->cellcolor == oldbg && (!lc->parent || lc->parent->cellcolor == c)) lc->cellcolor = c; } Refresh(); } return nullptr; } case A_SEARCHNEXT: { return SearchNext(dc); } case A_ROUND0: case A_ROUND1: case A_ROUND2: case A_ROUND3: case A_ROUND4: case A_ROUND5: case A_ROUND6: sys->cfg->Write(L"roundness", long(sys->roundness = k - A_ROUND0)); Refresh(); return nullptr; case A_REPLACEALL: { if (!sys->searchstring.Len()) return _(L"No search."); rootgrid->AddUndo(this); // expensive? rootgrid->FindReplaceAll(sys->frame->replaces->GetValue()); rootgrid->ResetChildren(); Refresh(); return nullptr; } case A_SCALED: scaledviewingmode = !scaledviewingmode; rootgrid->ResetChildren(); Refresh(); return scaledviewingmode ? _(L"Now viewing TreeSheet to fit to the screen exactly, " L"press F12 to return to normal.") : _(L"1:1 scale restored."); case A_FILTER5: editfilter = 5; ApplyEditFilter(); return nullptr; case A_FILTER10: editfilter = 10; ApplyEditFilter(); return nullptr; case A_FILTER20: editfilter = 20; ApplyEditFilter(); return nullptr; case A_FILTER50: editfilter = 50; ApplyEditFilter(); return nullptr; case A_FILTERM: editfilter++; ApplyEditFilter(); return nullptr; case A_FILTERL: editfilter--; ApplyEditFilter(); return nullptr; case A_FILTERS: SetSearchFilter(true); return nullptr; case A_FILTEROFF: SetSearchFilter(false); return nullptr; case A_CUSTKEY: { wxArrayString strs; MyFrame::MenuString &ms = sys->frame->menustrings; for (MyFrame::MenuStringIterator it = ms.begin(); it != ms.end(); ++it) strs.push_back(it->first); wxSingleChoiceDialog choice( sys->frame, _(L"Please pick a menu item to change the key binding for"), _(L"Key binding"), strs); if (choice.ShowModal() == wxID_OK) { int sel = choice.GetSelection(); wxTextEntryDialog textentry(sys->frame, "Please enter the new key binding string", "Key binding", ms[sel].second); if (textentry.ShowModal() == wxID_OK) { wxString key = textentry.GetValue(); ms[sel].second = key; sys->cfg->Write(ms[sel].first, key); return _(L"NOTE: key binding will take effect next run of TreeSheets."); } } return _(L"Keybinding cancelled."); } } if (!selected.g) return NoSel(); Cell *c = selected.GetCell(); switch (k) { case A_BACKSPACE: if (selected.Thin()) { if (selected.xs) DelRowCol(selected.y, 0, selected.g->ys, 1, -1, selected.y - 1, 0, -1); else DelRowCol(selected.x, 0, selected.g->xs, 1, selected.x - 1, -1, -1, 0); } else if (c && selected.TextEdit()) { if (selected.cursorend == 0) return nullptr; c->AddUndo(this); c->text.Backspace(selected); Refresh(); } else selected.g->MultiCellDelete(this, selected); ZoomOutIfNoGrid(dc); return nullptr; case A_DELETE: if (selected.Thin()) { if (selected.xs) DelRowCol(selected.y, selected.g->ys, selected.g->ys, 0, -1, selected.y, 0, -1); else DelRowCol(selected.x, selected.g->xs, selected.g->xs, 0, selected.x, -1, -1, 0); } else if (c && selected.TextEdit()) { if (selected.cursor == c->text.t.Len()) return nullptr; c->AddUndo(this); c->text.Delete(selected); Refresh(); } else selected.g->MultiCellDelete(this, selected); ZoomOutIfNoGrid(dc); return nullptr; case A_COPYCT: case A_CUT: case A_COPY: DELETEP(sys->cellclipboard); sys->clipboardcopy = wxEmptyString; if (selected.Thin()) return NoThin(); if (selected.TextEdit()) { if (selected.cursor == selected.cursorend) return _(L"No text selected."); } else if (k != A_COPYCT) sys->cellclipboard = c ? c->Clone(nullptr) : selected.g->CloneSel(selected); if (wxTheClipboard->Open()) { wxString s; if (k == A_COPYCT) { loopallcellssel(c, true) if (c->text.t.Len()) s += c->text.t + " "; } else { s = selected.g->ConvertToText(selected, 0, A_EXPTEXT, this); } sys->clipboardcopy = s; wxTheClipboard->SetData(new wxTextDataObject(s)); wxTheClipboard->Close(); } if (k == A_CUT) { if (!selected.TextEdit()) { selected.g->cell->AddUndo(this); selected.g->MultiCellDelete(this, selected); } else if (c) { c->AddUndo(this); c->text.Backspace(selected); } Refresh(); } ZoomOutIfNoGrid(dc); return nullptr; case A_SELALL: selected.SelAll(); Refresh(); return nullptr; case A_UP: case A_DOWN: case A_LEFT: case A_RIGHT: selected.Cursor(this, k, false, false, dc); return nullptr; case A_MUP: case A_MDOWN: case A_MLEFT: case A_MRIGHT: selected.Cursor(this, k - A_MUP + A_UP, true, false, dc); return nullptr; case A_SUP: case A_SDOWN: case A_SLEFT: case A_SRIGHT: selected.Cursor(this, k - A_SUP + A_UP, false, true, dc); return nullptr; case A_SCLEFT: case A_SCRIGHT: selected.Cursor(this, k - A_SCUP + A_UP, true, true, dc); return nullptr; case A_BOLD: selected.g->SetStyle(this, selected, STYLE_BOLD); return nullptr; case A_ITALIC: selected.g->SetStyle(this, selected, STYLE_ITALIC); return nullptr; case A_TT: selected.g->SetStyle(this, selected, STYLE_FIXED); return nullptr; case A_UNDERL: selected.g->SetStyle(this, selected, STYLE_UNDERLINE); return nullptr; case A_STRIKET: selected.g->SetStyle(this, selected, STYLE_STRIKETHRU); return nullptr; case A_MARKDATA: case A_MARKVARD: case A_MARKVARU: case A_MARKVIEWH: case A_MARKVIEWV: case A_MARKCODE: { int newcelltype; switch (k) { case A_MARKDATA: newcelltype = CT_DATA; break; case A_MARKVARD: newcelltype = CT_VARD; break; case A_MARKVARU: newcelltype = CT_VARU; break; case A_MARKVIEWH: newcelltype = CT_VIEWH; break; case A_MARKVIEWV: newcelltype = CT_VIEWV; break; case A_MARKCODE: newcelltype = CT_CODE; break; } selected.g->cell->AddUndo(this); loopallcellssel(c, false) { c->celltype = (newcelltype == CT_CODE) ? sys->ev.InferCellType(c->text) : newcelltype; Refresh(); } return nullptr; } case A_CANCELEDIT: if (selected.TextEdit()) break; if (selected.g->cell->parent) { selected = selected.g->cell->parent->grid->FindCell(selected.g->cell); } else { selected.SelAll(); } ScrollOrZoom(dc); return nullptr; case A_NEWGRID: if (!(c = selected.ThinExpand(this))) return OneCell(); if (c->grid) { selected = Selection(c->grid, 0, c->grid->ys, 1, 0); ScrollOrZoom(dc, true); } else { c->AddUndo(this); c->AddGrid(); selected = Selection(c->grid, 0, 0, 1, 1); DrawSelectMove(dc, selected, true); } return nullptr; case A_PASTE: if (!(c = selected.ThinExpand(this))) return OneCell(); if (wxTheClipboard->Open()) { wxTheClipboard->GetData(*dataobjc); PasteOrDrop(); wxTheClipboard->Close(); } else if (sys->cellclipboard) { c->Paste(this, sys->cellclipboard, selected); Refresh(); } return nullptr; case A_PASTESTYLE: if (!sys->cellclipboard) return _(L"No style to paste."); selected.g->cell->AddUndo(this); selected.g->SetStyles(selected, sys->cellclipboard); selected.g->cell->ResetChildren(); Refresh(); return nullptr; case A_ENTERCELL: if (!(c = selected.ThinExpand(this))) return OneCell(); if (selected.TextEdit()) { selected.Cursor(this, A_DOWN, false, false, dc, true); } else { selected.EnterEdit(this, 0, (int)c->text.t.Len()); DrawSelectMove(dc, selected, true); } return nullptr; case A_IMAGE: { if (!(c = selected.ThinExpand(this))) return OneCell(); wxString fn = ::wxFileSelector(_(L"Please select an image file:"), L"", L"", L"", L"*.*", wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_CHANGE_DIR); c->AddUndo(this); LoadImageIntoCell(fn, c, sys->frame->csf); Refresh(); return nullptr; } case A_IMAGER: { selected.g->cell->AddUndo(this); selected.g->ClearImages(selected); selected.g->cell->ResetChildren(); Refresh(); return nullptr; } case A_SORTD: return Sort(true); case A_SORT: return Sort(false); case A_REPLACEONCE: case A_REPLACEONCEJ: { if (!sys->searchstring.Len()) return _(L"No search."); selected.g->ReplaceStr(this, sys->frame->replaces->GetValue(), selected); if (k == A_REPLACEONCEJ) return SearchNext(dc); return nullptr; } case A_SCOLS: selected.y = 0; selected.ys = selected.g->ys; Refresh(); return nullptr; case A_SROWS: selected.x = 0; selected.xs = selected.g->xs; Refresh(); return nullptr; case A_BORD0: case A_BORD1: case A_BORD2: case A_BORD3: case A_BORD4: case A_BORD5: selected.g->cell->AddUndo(this); selected.g->SetBorder(k - A_BORD0 + 1, selected); selected.g->cell->ResetChildren(); Refresh(); return nullptr; case A_TEXTGRID: return layrender(-1, true, true); case A_V_GS: return layrender(DS_GRID, true); case A_V_BS: return layrender(DS_BLOBSHIER, true); case A_V_LS: return layrender(DS_BLOBLINE, true); case A_H_GS: return layrender(DS_GRID, false); case A_H_BS: return layrender(DS_BLOBSHIER, false); case A_H_LS: return layrender(DS_BLOBLINE, false); case A_GS: return layrender(DS_GRID, true, false, true); case A_BS: return layrender(DS_BLOBSHIER, true, false, true); case A_LS: return layrender(DS_BLOBLINE, true, false, true); case A_WRAP: return selected.Wrap(this); case A_RESETSIZE: case A_RESETWIDTH: case A_RESETSTYLE: case A_RESETCOLOR: selected.g->cell->AddUndo(this); loopallcellssel(c, true) switch (k) { case A_RESETSIZE: c->text.relsize = 0; break; case A_RESETWIDTH: if (c->grid) c->grid->InitColWidths(); break; case A_RESETSTYLE: c->text.stylebits = 0; break; case A_RESETCOLOR: c->cellcolor = 0xFFFFFF; c->textcolor = 0; if (c->grid) c->grid->bordercolor = 0xA0A0A0; break; } selected.g->cell->ResetChildren(); Refresh(); return nullptr; case A_MINISIZE: { selected.g->cell->AddUndo(this); CollectCellsSel(false); Vector outer; outer.append(itercells); loopv(i, outer) { Cell *o = outer[i]; if (o->grid) { loopcellsin(o, c) if (_i) { c->text.relsize = g_deftextsize - g_mintextsize() - c->Depth(); } } } outer.setsize_nd(0); selected.g->cell->ResetChildren(); Refresh(); return nullptr; } case A_FOLD: case A_FOLDALL: case A_UNFOLDALL: loopallcellssel(c, k != A_FOLD) if (c->grid) { c->AddUndo(this); c->grid->folded = k == A_FOLD ? !c->grid->folded : k == A_FOLDALL; c->ResetChildren(); } Refresh(); return nullptr; case A_HOME: case A_END: case A_CHOME: case A_CEND: if (selected.TextEdit()) break; selected.HomeEnd(this, dc, k == A_HOME || k == A_CHOME); return nullptr; } if (c || (!c && selected.IsAll())) { Cell *ac = c ? c : selected.g->cell; switch (k) { case A_TRANSPOSE: if (ac->grid) { ac->AddUndo(this); ac->grid->Transpose(); ac->ResetChildren(); selected = ac->parent ? ac->parent->grid->FindCell(ac) : Selection(); Refresh(); return nullptr; } else return NoGrid(); case A_HIFY: if (!ac->grid) return NoGrid(); if (!ac->grid->IsTable()) return _(L"Selected grid is not a table: cells must not already have " L"sub-grids."); ac->AddUndo(this); ac->grid->Hierarchify(this); ac->ResetChildren(); ClearSelectionRefresh(); return nullptr; case A_FLATTEN: { if (!ac->grid) return NoGrid(); ac->AddUndo(this); int maxdepth = 0, leaves = 0; ac->MaxDepthLeaves(0, maxdepth, leaves); Grid *g = new Grid(maxdepth, leaves); g->InitCells(); ac->grid->Flatten(0, 0, g); DELETEP(ac->grid); ac->grid = g; g->ReParent(ac); ac->ResetChildren(); ClearSelectionRefresh(); return nullptr; } } } if (!c) return OneCell(); switch (k) { case A_NEXT: selected.Next(this, dc, false); return nullptr; case A_PREV: selected.Next(this, dc, true); return nullptr; case A_BROWSE: if (!wxLaunchDefaultBrowser(c->text.ToText(0, selected, A_EXPTEXT))) return _(L"Cannot launch browser for this link."); return nullptr; case A_BROWSEF: { wxString f = c->text.ToText(0, selected, A_EXPTEXT); wxFileName fn(f); if (fn.IsRelative()) fn.MakeAbsolute(wxFileName(filename).GetPath()); if (!wxLaunchDefaultApplication(fn.GetFullPath())) return _(L"Cannot find file."); return nullptr; } case A_IMAGESCP: case A_IMAGESCF: case A_IMAGESCN: { if (!c->text.image) return _(L"No image in this cell."); if (k == A_IMAGESCN) { c->text.image->ResetScale(sys->frame->csf); } else { long v = wxGetNumberFromUser( _(L"Please enter the percentage you want the image scaled by:"), L"%", _(L"Image Resize"), 50, 5, 400, sys->frame); if (v < 0) return nullptr; auto sc = v / 100.0; if (k == A_IMAGESCP) { c->text.image->BitmapScale(sc); } else { c->text.image->DisplayScale(sc); } } c->ResetLayout(); Refresh(); return nullptr; } case A_ENTERGRID: if (!c->grid) Action(dc, A_NEWGRID); selected = Selection(c->grid, 0, 0, 1, 1); ScrollOrZoom(dc, true); return nullptr; case A_LINK: case A_LINKREV: { if (!c->text.t.Len()) return _(L"No text in this cell."); bool t1 = false, t2 = false; Cell *link = rootgrid->FindLink(selected, c, nullptr, t1, t2, k == A_LINK); if (!link || !link->parent) return _(L"No matching cell found!"); selected = link->parent->grid->FindCell(link); ScrollOrZoom(dc, true); return nullptr; } case A_COLCELL: sys->customcolor = c->cellcolor; return nullptr; case A_HSWAP: { Cell *pp = c->parent->parent; if (!pp) return _(L"Cannot move this cell up in the hierarchy."); if (pp->grid->xs != 1 && pp->grid->ys != 1) return _(L"Can only move this cell into a Nx1 or 1xN grid."); if (c->parent->grid->xs != 1 && c->parent->grid->ys != 1) return _(L"Can only move this cell from a Nx1 or 1xN grid."); pp->AddUndo(this); selected = pp->grid->HierarchySwap(c->text.t); pp->ResetChildren(); pp->ResetLayout(); Refresh(); return nullptr; } case A_TAGADD: if (!c->text.t.Len()) return _(L"Empty strings cannot be tags."); tags[c->text.t] = true; Refresh(); return nullptr; case A_TAGREMOVE: tags.erase(c->text.t); Refresh(); return nullptr; } if (!selected.TextEdit()) return _(L"only works in cell text mode"); switch (k) { case A_CANCELEDIT: if (LastUndoSameCell(c)) Undo(dc, undolist, redolist); else Refresh(); selected.ExitEdit(this); return nullptr; // FIXME: this functionality is really SCHOME, SHOME should be within line case A_SHOME: DrawSelect(dc, selected); selected.cursor = 0; DrawSelectMove(dc, selected); return nullptr; case A_SEND: DrawSelect(dc, selected); selected.cursorend = (int)c->text.t.Len(); DrawSelectMove(dc, selected); return nullptr; case A_CHOME: DrawSelect(dc, selected); selected.cursor = selected.cursorend = 0; DrawSelectMove(dc, selected); return nullptr; case A_CEND: DrawSelect(dc, selected); selected.cursor = selected.cursorend = selected.MaxCursor(); DrawSelectMove(dc, selected); return nullptr; case A_HOME: DrawSelect(dc, selected); c->text.HomeEnd(selected, true); DrawSelectMove(dc, selected); return nullptr; case A_END: DrawSelect(dc, selected); c->text.HomeEnd(selected, false); DrawSelectMove(dc, selected); return nullptr; default: return _(L"Internal error: unimplemented operation!"); } } const wxChar *SearchNext(wxDC &dc) { if (!sys->searchstring.Len()) return _(L"No search string."); bool lastsel = true; Cell *next = rootgrid->FindNextSearchMatch(sys->searchstring, nullptr, selected.GetCell(), lastsel); if (!next) return _(L"No matches for search."); selected = next->parent->grid->FindCell(next); sw->SetFocus(); ScrollOrZoom(dc, true); return nullptr; } uint PickColor(wxFrame *fr, uint defcol) { wxColour col = wxGetColourFromUser(fr, wxColour(defcol)); if (col.IsOk()) return #ifdef __WXMAC__ (col.Red() << 16) + (col.Green() << 8) + col.Blue(); #else (col.Blue() << 16) + (col.Green() << 8) + col.Red(); #endif return -1; } const wxChar *layrender(int ds, bool vert, bool toggle = false, bool noset = false) { if (selected.Thin()) return NoThin(); selected.g->cell->AddUndo(this); bool v = toggle ? !selected.GetFirst()->verticaltextandgrid : vert; if (ds >= 0 && selected.IsAll()) selected.g->cell->drawstyle = ds; selected.g->SetGridTextLayout(ds, v, noset, selected); selected.g->cell->ResetChildren(); Refresh(); return nullptr; } void ZoomOutIfNoGrid(wxDC &dc) { if (!WalkPath(drawpath)->grid) Zoom(-1, dc); } void PasteSingleText(Cell *c, const wxString &t) { c->text.Insert(this, t, selected); } void PasteOrDrop() { Cell *c = selected.GetCell(); if (!(c = selected.ThinExpand(this))) return; wxBusyCursor wait; switch (dataobjc->GetReceivedFormat().GetType()) { case wxDF_FILENAME: { const wxArrayString &as = dataobjf->GetFilenames(); if (as.size()) { if (as.size() > 1) sw->Status(_(L"Cannot drag & drop more than 1 file.")); c->AddUndo(this); if (!LoadImageIntoCell(as[0], c, sys->frame->csf)) PasteSingleText(c, as[0]); Refresh(); } break; } case wxDF_BITMAP: case wxDF_DIB: case wxDF_TIFF: if (dataobji->GetBitmap().GetRefData() != wxNullBitmap.GetRefData()) { c->AddUndo(this); SetImageBM(c, dataobji->GetBitmap().ConvertToImage(), sys->frame->csf); dataobji->SetBitmap(wxNullBitmap); c->Reset(); Refresh(); } break; /* case wxDF_HTML: { auto s = dataobjh->GetHTML(); // Would have to somehow parse HTML here to get images and styled text. break; } case wxDF_RTF: { // Would have to somehow parse RTF here to get images and styled text. break; } */ default: // several text formats if (dataobjt->GetText() != wxEmptyString) { wxString s = dataobjt->GetText(); if ((sys->clipboardcopy == s) && sys->cellclipboard) { c->Paste(this, sys->cellclipboard, selected); Refresh(); } else { const wxArrayString &as = wxStringTokenize(s, LINE_SEPERATOR); if (as.size()) { if (as.size() <= 1) { c->AddUndo(this); PasteSingleText(c, as[0]); } else { c->parent->AddUndo(this); c->ResetLayout(); DELETEP(c->grid); sys->FillRows(c->AddGrid(), as, sys->CountCol(as[0]), 0, 0); if (!c->HasText()) c->grid->MergeWithParent(c->parent->grid, selected); } Refresh(); } } dataobjt->SetText(wxEmptyString); } break; } } const wxChar *Sort(bool descending) { if (selected.xs != 1 && selected.ys <= 1) return _(L"Can't sort: make a 1xN selection to indicate what column to sort on, and " L"what rows to affect"); selected.g->cell->AddUndo(this); selected.g->Sort(selected, descending); Refresh(); return nullptr; } void DelRowCol(int &v, int e, int gvs, int dec, int dx, int dy, int nxs, int nys) { if (v != e) { selected.g->cell->AddUndo(this); if (gvs == 1) { selected.g->DelSelf(this, selected); } else { selected.g->DeleteCells(dx, dy, nxs, nys); v -= dec; } Refresh(); } } void CreatePath(Cell *c, Vector &path) { path.setsize(0); while (c->parent) { const Selection &s = c->parent->grid->FindCell(c); ASSERT(s.g); path.push() = s; c = c->parent; } } Cell *WalkPath(Vector &path) { Cell *c = rootgrid; loopvrev(i, path) { Selection &s = path[i]; Grid *g = c->grid; ASSERT(g && s.x < g->xs && s.y < g->ys); c = g->C(s.x, s.y); } return c; } bool LastUndoSameCell(Cell *c) { // hacky way to detect word boundaries to stop coalescing, but works, and // not a big deal if selected is not actually related to this cell return undolist.size() && !c->grid && undolist.size() != undolistsizeatfullsave && undolist.last()->sel.EqLoc(c->parent->grid->FindCell(c)) && (!c->text.t.EndsWith(" ") || c->text.t.Len() != selected.cursor); } void AddUndo(Cell *c) { redolist.setsize(0); lastmodsinceautosave = wxGetLocalTime(); if (!modified) { modified = true; UpdateFileName(); } if (LastUndoSameCell(c)) return; UndoItem *ui = new UndoItem(); undolist.push() = ui; ui->clone = c->Clone(nullptr); ui->estimated_size = c->EstimatedMemoryUse(); ui->sel = selected; CreatePath(c, ui->path); if (selected.g) CreatePath(selected.g->cell, ui->selpath); size_t total_usage = 0; size_t old_list_size = undolist.size(); // Cull undolist. Always at least keeps last item. for (int i = (int)undolist.size() - 1; i >= 0; i--) { // Cull old items if using more than 25MB or 100 items, whichever comes first. // TODO: make configurable? if (total_usage < 25 * 1024 * 1024 && undolist.size() - i < 100) { total_usage += undolist[i]->estimated_size; } else { undolist.remove(0, i + 1); break; } } size_t items_culled = old_list_size - undolist.size(); undolistsizeatfullsave -= items_culled; // Allowed to go < 0 } void Undo(wxDC &dc, Vector &fromlist, Vector &tolist, bool redo = false) { Selection beforesel = selected; Vector beforepath; if (beforesel.g) CreatePath(beforesel.g->cell, beforepath); UndoItem *ui = fromlist.pop(); Cell *c = WalkPath(ui->path); if (c->parent && c->parent->grid) { c->parent->grid->ReplaceCell(c, ui->clone); ui->clone->parent = c->parent; } else rootgrid = ui->clone; ui->clone->ResetLayout(); ui->clone = c; selected = ui->sel; if (selected.g) selected.g = WalkPath(ui->selpath)->grid; ui->sel = beforesel; ui->selpath.setsize(0); ui->selpath.append(beforepath); tolist.push() = ui; if (undolistsizeatfullsave > undolist.size()) undolistsizeatfullsave = -1; // gone beyond the save point, always modified modified = undolistsizeatfullsave != undolist.size(); if (selected.g) ScrollOrZoom(dc); else Refresh(); UpdateFileName(); } void ColorChange(int which, int idx) { if (!selected.g) return; selected.g->ColorChange( this, which, idx == CUSTOMCOLORIDX ? sys->customcolor : celltextcolors[idx], selected); } void SetImageBM(Cell *c, const wxImage &im, double sc) { c->text.image = sys->imagelist[sys->AddImageToList(im, sc)]; } bool LoadImageIntoCell(const wxString &fn, Cell *c, double sc) { if (fn.empty()) return false; wxImage im; if (!im.LoadFile(fn)) return false; SetImageBM(c, im, sc); c->Reset(); return true; } void ImageChange(wxString &fn, double sc) { if (!selected.g) return; selected.g->cell->AddUndo(this); loopallcellssel(c, false) LoadImageIntoCell(fn, c, sc); Refresh(); } void RecreateTagMenu(wxMenu &menu) { int i = A_TAGSET; for (auto tagit = tags.begin(); tagit != tags.end(); ++tagit) { menu.Append(i++, tagit->first); } } const wxChar *TagSet(int tagno) { int i = 0; for (auto tagit = tags.begin(); tagit != tags.end(); ++tagit) if (i++ == tagno) { Cell *c = selected.GetCell(); if (!c) return OneCell(); c->AddUndo(this); c->text.Clear(this, selected); c->text.Insert(this, tagit->first, selected); Refresh(); return nullptr; } ASSERT(0); return nullptr; } void CollectCells(Cell *c) { itercells.setsize_nd(0); c->CollectCells(itercells); } void CollectCellsSel(bool recurse) { itercells.setsize_nd(0); if (selected.g) selected.g->CollectCellsSel(itercells, selected, recurse); } static int _timesort(const Cell **a, const Cell **b) { return ((*a)->text.lastedit < (*b)->text.lastedit) * 2 - 1; } void ApplyEditFilter() { searchfilter = false; editfilter = min(max(editfilter, 1), 99); CollectCells(rootgrid); itercells.sort((void *)(int(__cdecl *)(const void *, const void *))_timesort); loopv(i, itercells) itercells[i]->text.filtered = i > itercells.size() * editfilter / 100; rootgrid->ResetChildren(); Refresh(); } void SetSearchFilter(bool on) { searchfilter = on; loopallcells(c) c->text.filtered = on && !c->text.IsInSearch(); rootgrid->ResetChildren(); Refresh(); } }; treesheets-1.0.2/src/evaluator.h000077500000000000000000000153011352107072600166350ustar00rootroot00000000000000 /* A structure describing an operation. */ struct Operation { const char *args; virtual Cell *run() { return nullptr; } virtual double runn(double a) { return 0; } virtual Cell *runt(Text *t) { return nullptr; } virtual Cell *runl(Grid *l) { return nullptr; } virtual Cell *rung(Grid *g) { return nullptr; } virtual Cell *runc(Cell *c) { return nullptr; } virtual double runnn(double a, double b) { return 0; } }; WX_DECLARE_STRING_HASH_MAP(Operation *, wxHashMapOperation); WX_DECLARE_STRING_HASH_MAP(Cell *, wxHashMapCell); /* Provides running evaluation of a grid. */ struct Evaluator { wxHashMapOperation ops; wxHashMapCell vars; bool vert; ~Evaluator() { while (ops.size()) { delete ops.begin()->second; ops.erase(ops.begin()); } while (vars.size()) { delete vars.begin()->second; vars.erase(vars.begin()); } } #define OP(_n, _c, _args, _f) \ { \ struct _op : Operation { \ _op() { args = _args; }; \ _f { return _c; }; \ }; \ ops[L## #_n] = new _op(); \ } #define OPNN(_n, _c) OP(_n, _c, "nn", double runnn(double a, double b)) #define OPN(_n, _c) OP(_n, _c, "n", double runn(double a)) #define OPT(_n, _c) OP(_n, _c, "t", Cell *runt(Text *t)) #define OPL(_n, _c) OP(_n, _c, "l", Cell *runl(Grid *a)) #define OPG(_n, _c) OP(_n, _c, "g", Cell *rung(Grid *a)) void Init() { OPNN(+, a + b); OPNN(-, a - b); OPNN(*, a * b); OPNN(/, b != 0 ? a / b : 0); OPNN(<, double(a < b)); OPNN(>, double(a > b)); OPNN(<=, double(a <= b)); OPNN(>=, double(a >= b)); OPNN(=, double(a == b)); OPNN(==, double(a == b)); OPNN(!=, double(a != b)); OPNN(<>, double(a != b)); OPN(inc, a + 1); OPN(dec, a - 1); OPN(neg, -a); OPT(graph, t->Graph()); OPL(sum, a->Sum()) OPG(transpose, a->Transpose()) struct _if : Operation { _if() { args = "nLL"; }; }; ops[L"if"] = new _if(); } int InferCellType(Text &t) { if (ops[t.t]) return CT_CODE; else return CT_DATA; } Cell *Lookup(wxString &name) { wxHashMapCell::iterator lookup = vars.find(name); return (lookup != vars.end()) ? lookup->second->Clone(nullptr) : nullptr; } bool IsValidSymbol(wxString const &symbol) const { return !symbol.IsEmpty(); } void SetSymbol(wxString const &symbol, Cell *val) { if (!this->IsValidSymbol(symbol)) { DELETEP(val); return; } Cell *old = vars[symbol]; DELETEP(old); vars[symbol] = val; } void Assign(Cell const *sym, Cell const *val) { this->SetSymbol(sym->text.t, val->Clone(nullptr)); if (sym->grid && val->grid) this->DestructuringAssign(sym->grid, val->Clone(nullptr)); } void DestructuringAssign(Grid const *names, Cell *val) { Grid const *ng = names; Grid const *vg = val->grid; if (ng->xs == vg->xs && ng->ys == vg->ys) { loop(x, ng->xs) loop(y, ng->ys) { Cell *nc = ng->C(x, y); Cell *vc = vg->C(x, y); this->SetSymbol(nc->text.t, vc->Clone(nullptr)); } } DELETEP(val); } Operation *FindOp(wxString &name) { return ops[name]; } Cell *Execute(Operation *op) { return op->run(); } Cell *Execute(Operation *op, Cell *left) { Text &t = left->text; Grid *g = left->grid; switch (op->args[0]) { case 'n': if (t.t.Len()) return t.SetNum(op->runn(t.GetNum())); else if (g) foreachcellingrid(c, g) c = Execute(op, c)->SetParent(left); break; case 't': if (t.t.Len()) return op->runt(&t); else if (g) foreachcellingrid(c, g) c = Execute(op, c)->SetParent(left); break; case 'l': if (g) { if (g->xs == 1 || g->ys == 1) return op->runl(g); Vector gs; g->Split(gs, vert); g = new Grid(vert ? gs.size() : 1, vert ? 1 : gs.size()); Cell *c = new Cell(nullptr, left, CT_DATA, g); loopv(i, gs) g->C(vert ? i : 0, vert ? 0 : i) = op->runl(gs[i])->SetParent(c); gs.setsize_nd(0); return c; } break; case 'g': if (g) return op->rung(g); break; case 'c': return op->runc(left); } return left; } Cell *Execute(Operation *op, Cell *left, Cell *right) { if (!(right = right->Eval(*this))) return left; Text &t1 = left->text; Text &t2 = right->text; Grid *g1 = left->grid; Grid *g2 = right->grid; switch (op->args[0]) { case 'n': if (t1.t.Len() && t2.t.Len()) t1.SetNum(op->runnn(t1.GetNum(), t2.GetNum())); else if (g1 && g2 && g1->xs == g2->xs && g1->ys == g2->ys) { Grid *g = new Grid(g1->xs, g1->ys); Cell *c = new Cell(nullptr, left, CT_DATA, g); loop(x, g->xs) loop(y, g->ys) { Cell *&c1 = g1->C(x, y); Cell *&c2 = g2->C(x, y); g->C(x, y) = Execute(op, c1, c2)->SetParent(c); c1 = c2 = nullptr; } delete g1; delete g2; return c; } else if (g1 && t2.t.Len()) { foreachcellingrid(c, g1) c = Execute(op, c, right->Clone(nullptr))->SetParent(left); } break; } delete right; return left; } Cell *Execute(Operation *op, Cell *left, Cell *a, Cell *b) // IF is sofar the only ternary { Text &l = left->text; if (!l.t.Len()) return left; bool cond = l.GetNum() != 0; delete left; return (cond ? a : b)->Eval(*this); } void Eval(Cell *root) { Cell *c = root->Eval(*this); DELETEP(c); } }; treesheets-1.0.2/src/genpot.bat000066400000000000000000000001241352107072600164400ustar00rootroot00000000000000xgettext --keyword=_ --sort-output -o ../TS/po/ts.pot myframe.h document.h system.h treesheets-1.0.2/src/grid.h000077500000000000000000001207621352107072600155700ustar00rootroot00000000000000 struct Grid { // owning cell. Cell *cell; // subcells Cell **cells; // widths for each column int *colwidths; // xsize, ysize int xs, ys; int view_margin, view_grid_outer_spacing, user_grid_outer_spacing, cell_margin; int bordercolor; bool horiz; bool tinyborder; bool folded; Cell *&C(int x, int y) const { ASSERT(x >= 0 && y >= 0 && x < xs && y < ys); return cells[x + y * xs]; } #define foreachcell(c) \ for (int y = 0; y < ys; y++) \ for (int x = 0; x < xs; x++) \ for (bool _f = true; _f;) \ for (Cell *&c = C(x, y); _f; _f = false) #define foreachcellrev(c) \ for (int y = ys - 1; y >= 0; y--) \ for (int x = xs - 1; x >= 0; x--) \ for (bool _f = true; _f;) \ for (Cell *&c = C(x, y); _f; _f = false) #define foreachcelly(c) \ for (int y = 0; y < ys; y++) \ for (bool _f = true; _f;) \ for (Cell *&c = C(0, y); _f; _f = false) #define foreachcellcolumn(c) \ for (int x = 0; x < xs; x++) \ for (int y = 0; y < ys; y++) \ for (bool _f = true; _f;) \ for (Cell *&c = C(x, y); _f; _f = false) #define foreachcellinsel(c, s) \ for (int y = s.y; y < s.y + s.ys; y++) \ for (int x = s.x; x < s.x + s.xs; x++) \ for (bool _f = true; _f;) \ for (Cell *&c = C(x, y); _f; _f = false) #define foreachcellinselrev(c, s) \ for (int y = s.y + s.ys - 1; y >= s.y; y--) \ for (int x = s.x + s.xs - 1; x >= s.x; x--) \ for (bool _f = true; _f;) \ for (Cell *&c = C(x, y); _f; _f = false) #define foreachcellingrid(c, g) \ for (int y = 0; y < g->ys; y++) \ for (int x = 0; x < g->xs; x++) \ for (bool _f = true; _f;) \ for (Cell *&c = g->C(x, y); _f; _f = false) Grid(int _xs, int _ys, Cell *_c = nullptr) : cell(_c), xs(_xs), ys(_ys), cells(new Cell *[_xs * _ys]), colwidths(nullptr), user_grid_outer_spacing(3), bordercolor(0xA0A0A0), horiz(false), folded(false) { foreachcell(c) c = nullptr; InitColWidths(); SetOrient(); } ~Grid() { foreachcell(c) if (c) delete c; delete[] cells; delete[] colwidths; } void InitCells(Cell *clonestylefrom = nullptr) { foreachcell(c) c = new Cell(cell, clonestylefrom); } void CloneStyleFrom(Grid *o) { bordercolor = o->bordercolor; // TODO: what others? } void InitColWidths() { if (colwidths) delete[] colwidths; colwidths = new int[xs]; int cw = cell ? cell->ColWidth() : sys->defaultmaxcolwidth; loop(x, xs) colwidths[x] = cw; } /* Clones g into this grid. This mutates the grid this function is called on. */ void Clone(Grid *g) { g->bordercolor = bordercolor; g->user_grid_outer_spacing = user_grid_outer_spacing; g->folded = folded; foreachcell(c) g->C(x, y) = c->Clone(g->cell); loop(x, xs) g->colwidths[x] = colwidths[x]; } Cell *CloneSel(const Selection &s) { Cell *cl = new Cell(nullptr, s.g->cell, CT_DATA, new Grid(s.xs, s.ys)); foreachcellinsel(c, s) cl->grid->C(x - s.x, y - s.y) = c->Clone(cl); return cl; } size_t EstimatedMemoryUse() { size_t sum = 0; foreachcell(c) sum += c->EstimatedMemoryUse(); return sizeof(Grid) + xs * ys * sizeof(Cell *) + sum; } void SetOrient() { if (xs > ys) horiz = true; if (ys > xs) horiz = false; } bool Layout(Document *doc, wxDC &dc, int depth, int &sx, int &sy, int startx, int starty, bool forcetiny) { int *xa = new int[xs]; int *ya = new int[ys]; loop(i, xs) xa[i] = 0; loop(i, ys) ya[i] = 0; tinyborder = true; foreachcell(c) { c->LazyLayout(doc, dc, depth + 1, colwidths[x], forcetiny); tinyborder = c->tiny && tinyborder; xa[x] = max(xa[x], c->sx); ya[y] = max(ya[y], c->sy); } view_grid_outer_spacing = tinyborder || cell->drawstyle != DS_GRID ? 0 : user_grid_outer_spacing; view_margin = tinyborder || cell->drawstyle != DS_GRID ? 0 : g_grid_margin; cell_margin = tinyborder ? 0 : (cell->drawstyle == DS_GRID ? 0 : g_cell_margin); sx = (xs + 1) * g_line_width + xs * cell_margin * 2 + 2 * (view_grid_outer_spacing + view_margin) + startx; sy = (ys + 1) * g_line_width + ys * cell_margin * 2 + 2 * (view_grid_outer_spacing + view_margin) + starty; loop(i, xs) sx += xa[i]; loop(i, ys) sy += ya[i]; int cx = view_grid_outer_spacing + view_margin + g_line_width + cell_margin + startx; int cy = view_grid_outer_spacing + view_margin + g_line_width + cell_margin + starty; if (!cell->tiny) { cx += g_margin_extra; cy += g_margin_extra; } foreachcell(c) { c->ox = cx; c->oy = cy; if (c->sy < ya[y] && c->drawstyle == DS_BLOBLINE) { if (!c->grid && !c->ycenteroff) c->ycenteroff = (ya[y] - c->sy) / 2; } c->sx = xa[x]; c->sy = ya[y]; cx += xa[x] + g_line_width + cell_margin * 2; if (x == xs - 1) { cy += ya[y] + g_line_width + cell_margin * 2; cx = view_grid_outer_spacing + view_margin + g_line_width + cell_margin + startx; if (!cell->tiny) cx += g_margin_extra; } } delete[] xa; delete[] ya; return tinyborder; } void Render(Document *doc, int bx, int by, wxDC &dc, int depth, int sx, int sy, int xoff, int yoff) { xoff = C(0, 0)->ox - view_margin - view_grid_outer_spacing - 1; yoff = C(0, 0)->oy - view_margin - view_grid_outer_spacing - 1; int maxx = C(xs - 1, 0)->ox + C(xs - 1, 0)->sx; int maxy = C(0, ys - 1)->oy + C(0, ys - 1)->sy; if (tinyborder || cell->drawstyle == DS_GRID) { int ldelta = view_grid_outer_spacing != 0; auto drawlines = [&]() { for (int x = ldelta; x <= xs - ldelta; x++) { int xl = (x == xs ? maxx : C(x, 0)->ox - g_line_width) + bx; if (xl >= doc->originx && xl <= doc->maxx) loop(line, g_line_width) { dc.DrawLine( xl + line, max(doc->originy, by + yoff + view_grid_outer_spacing), xl + line, min(doc->maxy, by + maxy + g_line_width) + view_margin); } } for (int y = ldelta; y <= ys - ldelta; y++) { int yl = (y == ys ? maxy : C(0, y)->oy - g_line_width) + by; if (yl >= doc->originy && yl <= doc->maxy) loop(line, g_line_width) { dc.DrawLine(max(doc->originx, bx + xoff + view_grid_outer_spacing + g_line_width), yl + line, min(doc->maxx, bx + maxx) + view_margin, yl + line); } } }; if (!sys->fastrender && view_grid_outer_spacing && cell->cellcolor != 0xFFFFFF) { dc.SetPen(*wxWHITE_PEN); drawlines(); } // dotted lines result in very expensive drawline calls dc.SetPen(view_grid_outer_spacing && !sys->fastrender ? sys->pen_gridlines : sys->pen_tinygridlines); drawlines(); } foreachcell(c) { int cx = bx + c->ox; int cy = by + c->oy; if (cx < doc->maxx && cx + c->sx > doc->originx && cy < doc->maxy && cy + c->sy > doc->originy) { c->Render(doc, cx, cy, dc, depth + 1, (x == 0) * view_margin, (x == xs - 1) * view_margin, (y == 0) * view_margin, (y == ys - 1) * view_margin, colwidths[x], cell_margin); } } if (cell->drawstyle == DS_BLOBLINE && !tinyborder && cell->HasHeader() && !cell->tiny) { const int arcsize = 8; int srcy = by + cell->ycenteroff + (cell->verticaltextandgrid ? cell->tys + 2 : cell->tys / 2) + g_margin_extra; // fixme: the 8 is chosen to fit the smallest text size, not very portable int srcx = bx + (cell->verticaltextandgrid ? 8 : cell->txs + 4) + g_margin_extra; int destyfirst = -1, destylast = -1; dc.SetPen(*wxGREY_PEN); foreachcelly(c) if (c->HasContent() && !c->tiny) { int desty = c->ycenteroff + by + c->oy + c->tys / 2 + g_margin_extra; int destx = bx + c->ox - 2 + g_margin_extra; bool visible = srcx < doc->maxx && destx > doc->originx && desty - arcsize < doc->maxy && desty + arcsize > doc->originy; if (abs(srcy - desty) < arcsize && !cell->verticaltextandgrid) { if (destyfirst < 0) destyfirst = desty; destylast = desty; if (visible) dc.DrawLine(srcx, desty, destx, desty); } else { if (desty < srcy) { if (destyfirst < 0) destyfirst = desty + arcsize; destylast = desty + arcsize; if (visible) dc.DrawBitmap(sys->frame->line_nw, srcx, desty, true); } else { destylast = desty - arcsize; if (visible) dc.DrawBitmap(sys->frame->line_sw, srcx, desty - arcsize, true); desty--; } if (visible) dc.DrawLine(srcx + arcsize, desty, destx, desty); } } if (cell->verticaltextandgrid) { if (destylast > 0) dc.DrawLine(srcx, srcy, srcx, destylast); } else { if (destyfirst >= 0 && destylast >= 0 && destyfirst < destylast) dc.DrawLine(srcx, destyfirst, srcx, destylast); } } if (view_grid_outer_spacing && cell->drawstyle == DS_GRID) { dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetPen(wxPen(wxColour(bordercolor))); loop(i, view_grid_outer_spacing - 1) dc.DrawRoundedRectangle( bx + xoff + view_grid_outer_spacing - i, by + yoff + view_grid_outer_spacing - i, maxx - xoff - view_grid_outer_spacing + 1 + i * 2 + view_margin, maxy - yoff - view_grid_outer_spacing + 1 + i * 2 + view_margin, sys->roundness + i); } } void FindXY(Document *doc, int px, int py, wxDC &dc) { foreachcell(c) { int bx = px - c->ox; int by = py - c->oy; if (bx >= 0 && by >= -g_line_width - g_selmargin && bx < c->sx && by < g_selmargin) { doc->hover = Selection(this, x, y, 1, 0); return; } if (bx >= 0 && by >= c->sy - g_selmargin && bx < c->sx && by < c->sy + g_line_width + g_selmargin) { doc->hover = Selection(this, x, y + 1, 1, 0); return; } if (bx >= -g_line_width - g_selmargin && by >= 0 && bx < g_selmargin && by < c->sy) { doc->hover = Selection(this, x, y, 0, 1); return; } if (bx >= c->sx - g_selmargin && by >= 0 && bx < c->sx + g_line_width + g_selmargin && by < c->sy) { doc->hover = Selection(this, x + 1, y, 0, 1); return; } if (c->IsInside(bx, by)) { if (c->GridShown(doc)) c->grid->FindXY(doc, bx, by, dc); if (doc->hover.g) return; doc->hover = Selection(this, x, y, 1, 1); if (c->HasText()) { c->text.FindCursor(doc, bx, by - c->ycenteroff, dc, doc->hover, colwidths[x]); } return; } } } Cell *FindLink(Selection &s, Cell *link, Cell *best, bool &lastthis, bool &stylematch, bool forward) { if (forward) { foreachcell(c) best = c->FindLink(s, link, best, lastthis, stylematch, forward); } else { foreachcellrev(c) best = c->FindLink(s, link, best, lastthis, stylematch, forward); } return best; } Cell *FindNextSearchMatch(wxString &search, Cell *best, Cell *selected, bool &lastwasselected) { foreachcell(c) best = c->FindNextSearchMatch(search, best, selected, lastwasselected); return best; } void FindReplaceAll(const wxString &str) { foreachcell(c) c->FindReplaceAll(str); } void ReplaceCell(Cell *o, Cell *n) { foreachcell(c) if (c == o) c = n; } Selection FindCell(Cell *o) { foreachcell(c) if (c == o) return Selection(this, x, y, 1, 1); return Selection(); } Selection SelectAll() { return Selection(this, 0, 0, xs, ys); } void ImageRefCount() { foreachcell(c) c->ImageRefCount(); } void DrawHover(Document *doc, wxDC &dc, Selection &s) { #ifndef SIMPLERENDER #ifdef __WXMAC__ const uint thincol = 0xFFFFFF; const uint bgcol = 0xFFFFFF; #else const uint thincol = 0x555555; const uint bgcol = 0x101014; #endif dc.SetLogicalFunction(wxXOR); if (s.Thin()) { DrawInsert(doc, dc, s, thincol); } else { Cell *c = C(s.x, s.y); DrawRectangle(dc, bgcol, c->GetX(doc) - cell_margin, c->GetY(doc) - cell_margin, c->sx + cell_margin * 2, c->sy + cell_margin * 2); } dc.SetLogicalFunction(wxCOPY); #endif } void DrawCursor(Document *doc, wxDC &dc, Selection &s, bool full, uint color, bool cursoronly) { Cell *c = s.GetCell(); if (c && !c->tiny && (c->HasText() || !c->grid)) c->text.DrawCursor(doc, dc, s, full, color, cursoronly, colwidths[s.x]); } void DrawInsert(Document *doc, wxDC &dc, Selection &s, uint colour) { dc.SetPen(sys->pen_thinselect); if (!s.xs) { Cell *c = C(s.x - (s.x == xs), s.y); int x = c->GetX(doc) + (c->sx + g_line_width + cell_margin) * (s.x == xs) - g_line_width - cell_margin; loop(line, g_line_width) dc.DrawLine(x + line, max(cell->GetY(doc), doc->originy), x + line, min(cell->GetY(doc) + cell->sy, doc->maxy)); DrawRectangle(dc, colour, x - 1, c->GetY(doc), g_line_width + 2, c->sy); } else { Cell *c = C(s.x, s.y - (s.y == ys)); int y = c->GetY(doc) + (c->sy + g_line_width + cell_margin) * (s.y == ys) - g_line_width - cell_margin; loop(line, g_line_width) dc.DrawLine(max(cell->GetX(doc), doc->originx), y + line, min(cell->GetX(doc) + cell->sx, doc->maxx), y + line); DrawRectangle(dc, colour, c->GetX(doc), y - 1, c->sx, g_line_width + 2); } } wxRect GetRect(Document *doc, Selection &s, bool minimal = false) { if (s.Thin()) { if (s.xs) { if (s.y < ys) { Cell *tl = C(s.x, s.y); return wxRect(tl->GetX(doc), tl->GetY(doc), tl->sx, 0); } else { Cell *br = C(s.x, ys - 1); return wxRect(br->GetX(doc), br->GetY(doc) + br->sy, br->sx, 0); } } else { if (s.x < xs) { Cell *tl = C(s.x, s.y); return wxRect(tl->GetX(doc), tl->GetY(doc), 0, tl->sy); } else { Cell *br = C(xs - 1, s.y); return wxRect(br->GetX(doc) + br->sx, br->GetY(doc), 0, br->sy); } } } else { Cell *tl = C(s.x, s.y); Cell *br = C(s.x + s.xs - 1, s.y + s.ys - 1); wxRect r(tl->GetX(doc) - cell_margin, tl->GetY(doc) - cell_margin, br->GetX(doc) + br->sx - tl->GetX(doc) + cell_margin * 2, br->GetY(doc) + br->sy - tl->GetY(doc) + cell_margin * 2); if (minimal && tl == br) r.width -= tl->sx - tl->minx; return r; } } void DrawSelect(Document *doc, wxDC &dc, Selection &s, bool cursoronly) { #ifndef SIMPLERENDER dc.SetLogicalFunction(wxINVERT); #endif if (s.Thin()) { if (!cursoronly) DrawInsert(doc, dc, s, 0); } else { if (!cursoronly) { dc.SetBrush(*wxBLACK_BRUSH); dc.SetPen(*wxBLACK_PEN); wxRect g = GetRect(doc, s); int lw = g_line_width; int te = s.TextEdit(); dc.DrawRectangle(g.x - 1 - lw, g.y - 1 - lw, g.width + 2 + 2 * lw, 2 + lw - te); dc.DrawRectangle(g.x - 1 - lw, g.y - 1 + g.height + te, g.width + 2 + 2 * lw - 5, 2 + lw - te); dc.DrawRectangle(g.x - 1 - lw, g.y + 1 - te, 2 + lw - te, g.height - 2 + 2 * te); dc.DrawRectangle(g.x - 1 + g.width + te, g.y + 1 - te, 2 + lw - te, g.height - 2 + 2 * te - 2 - te); dc.DrawRectangle(g.x + g.width, g.y + g.height - 2, lw + 2, lw + 4); dc.DrawRectangle(g.x + g.width - lw - 1, g.y + g.height - 2 + 2 * te, lw + 1, lw + 4 - 2 * te); } #ifndef SIMPLERENDER dc.SetLogicalFunction(wxXOR); #endif if (s.TextEdit()) #ifdef SIMPLERENDER DrawCursor(doc, dc, s, true, 0x00FF00, cursoronly); #else DrawCursor(doc, dc, s, true, 0xFFFF, cursoronly); #endif } #ifndef SIMPLERENDER dc.SetLogicalFunction(wxCOPY); #endif } void DeleteCells(int dx, int dy, int nxs, int nys) { Cell **ncells = new Cell *[(xs + nxs) * (ys + nys)]; Cell **ncp = ncells; int *ncw = new int[xs + nxs]; int *ncwp = ncw; foreachcell(c) if (x == dx || y == dy) DELETEP(c) else *ncp++ = c; loop(x, xs) if (x != dx) *ncwp++ = colwidths[x]; delete[] cells; cells = ncells; delete[] colwidths; colwidths = ncw; xs += nxs; ys += nys; SetOrient(); } void MultiCellDelete(Document *doc, Selection &s) { cell->AddUndo(doc); MultiCellDeleteSub(doc, s); doc->Refresh(); } void MultiCellDeleteSub(Document *doc, Selection &s) { foreachcellinsel(c, s) c->Clear(); bool delhoriz = true, delvert = true; foreachcell(c) { if (c->HasContent()) { if (y >= s.y && y < s.y + s.ys) delhoriz = false; if (x >= s.x && x < s.x + s.xs) delvert = false; } } if (delhoriz && (!delvert || s.xs >= s.ys)) { if (s.ys == ys) { DelSelf(doc, s); } else { loop(i, s.ys) DeleteCells(-1, s.y, 0, -1); s.ys = 0; s.xs = 1; } } else if (delvert) { if (s.xs == xs) { DelSelf(doc, s); } else { loop(i, s.xs) DeleteCells(s.x, -1, -1, 0); s.xs = 0; s.ys = 1; } } else { Cell *c = s.GetCell(); if (c) s.EnterEdit(doc); } } void DelSelf(Document *doc, Selection &s) { for (auto c = doc->curdrawroot; c; c = c->parent) if (c == cell) return; // FIXME, if drawroot, auto zoom out instead (needs dc) s = cell->parent->grid->FindCell(cell); Grid *&pthis = cell->grid; DELETEP(pthis); } void InsertCells(int dx, int dy, int nxs, int nys, Cell *nc = nullptr) { assert(((dx < 0) == (nxs == 0)) && ((dy < 0) == (nys == 0))); assert(nxs + nys == 1); Cell **ocells = cells; cells = new Cell *[(xs + nxs) * (ys + nys)]; int *ocw = colwidths; colwidths = new int[xs + nxs]; xs += nxs; ys += nys; Cell **ncp = ocells; SetOrient(); foreachcell(c) if (x == dx || y == dy) { if (nc) c = nc; else { Cell *colcell = ocells[(nxs ? min(dx, xs - nxs - 1) : x) + (nxs ? y : min(dy, ys - nys - 1)) * (xs - nxs)]; c = new Cell(cell, colcell); c->text.relsize = colcell->text.relsize; } } else c = *ncp++; int *cwp = ocw; loop(x, xs) colwidths[x] = x == dx ? cell->ColWidth() : *cwp++; delete[] ocells; delete[] ocw; } void Save(wxDataOutputStream &dos) const { dos.Write32(xs); dos.Write32(ys); dos.Write32(bordercolor); dos.Write32(user_grid_outer_spacing); dos.Write8(cell->verticaltextandgrid); dos.Write8(folded); loop(x, xs) dos.Write32(colwidths[x]); foreachcell(c) c->Save(dos); } bool LoadContents(wxDataInputStream &dis, int &numcells, int &textbytes) { if (sys->versionlastloaded >= 10) { bordercolor = dis.Read32() & 0xFFFFFF; user_grid_outer_spacing = dis.Read32(); if (sys->versionlastloaded >= 11) { cell->verticaltextandgrid = dis.Read8() != 0; if (sys->versionlastloaded >= 13) { if (sys->versionlastloaded >= 16) { folded = dis.Read8() != 0; if (folded && sys->versionlastloaded <= 17) { // Before v18, folding would use the image slot. So if this cell // contains an image, clear it. cell->text.image = nullptr; } } loop(x, xs) colwidths[x] = dis.Read32(); } } } foreachcell(c) if (!(c = Cell::LoadWhich(dis, cell, numcells, textbytes))) return false; return true; } void Formatter(wxString &r, int format, int indent, const wxChar *xml, const wxChar *html) { if (format == A_EXPXML) { r.Append(L' ', indent); r.Append(xml); } else if (format == A_EXPHTMLT) { r.Append(L' ', indent); r.Append(html); } } wxString ToText(int indent, const Selection &s, int format, Document *doc) { return ConvertToText(SelectAll(), indent + 2, format, doc); }; wxString ConvertToText(const Selection &s, int indent, int format, Document *doc) { wxString r; Formatter(r, format, indent, L"\n", wxString::Format( L"\n", cell == doc->rootgrid ? 0 : // Until the user_grid_outer_spacing of the root cell can be adjusted, // using its default value doesn't make sense here user_grid_outer_spacing-1, 12 - indent / 2) .wc_str()); foreachcellinsel(c, s) { if (x == 0) Formatter(r, format, indent, L"\n", L"\n"); r.Append(c->ToText(indent, s, format, doc)); if (format == A_EXPCSV) r.Append(x == xs - 1 ? '\n' : ','); if (x == xs - 1) Formatter(r, format, indent, L"\n", L"\n"); } Formatter(r, format, indent, L"\n", L"
\n"); return r; } void RelSize(int dir, int zoomdepth) { foreachcell(c) c->RelSize(dir, zoomdepth); } void RelSize(int dir, Selection &s, int zoomdepth) { foreachcellinsel(c, s) c->RelSize(dir, zoomdepth); } void SetBorder(int width, Selection &s) { foreachcellinsel(c, s) c->SetBorder(width); } int MinRelsize(int rs) { foreachcell(c) { int crs = c->MinRelsize(); rs = min(rs, crs); } return rs; } void ResetChildren() { cell->Reset(); foreachcell(c) c->ResetChildren(); } void Move(int dx, int dy, Selection &s) { if (dx < 0 || dy < 0) foreachcellinsel(c, s) swap_(c, C((x + dx + xs) % xs, (y + dy + ys) % ys)); else foreachcellinselrev(c, s) swap_(c, C((x + dx + xs) % xs, (y + dy + ys) % ys)); } void Add(Cell *c) { if (horiz) InsertCells(xs, -1, 1, 0, c); else InsertCells(-1, ys, 0, 1, c); c->parent = cell; } void MergeWithParent(Grid *p, Selection &s) { cell->grid = nullptr; foreachcell(c) { if (x + s.x >= p->xs) p->InsertCells(p->xs, -1, 1, 0); if (y + s.y >= p->ys) p->InsertCells(-1, p->ys, 0, 1); Cell *pc = p->C(x + s.x, y + s.y); if (pc->HasContent()) { if (x) p->InsertCells(s.x + x, -1, 1, 0); pc = p->C(x + s.x, y + s.y); if (pc->HasContent()) { if (y) p->InsertCells(-1, s.y + y, 0, 1); pc = p->C(x + s.x, y + s.y); } } delete pc; p->C(x + s.x, y + s.y) = c; c->parent = p->cell; c = nullptr; } s.g = p; s.xs += xs - 1; s.ys += ys - 1; delete this; } void SetStyle(Document *doc, Selection &s, int sb) { cell->AddUndo(doc); cell->ResetChildren(); foreachcellinsel(c, s) c->text.stylebits ^= sb; doc->Refresh(); } void ColorChange(Document *doc, int which, uint color, Selection &s) { cell->AddUndo(doc); cell->ResetChildren(); foreachcellinsel(c, s) c->ColorChange(which, color); doc->Refresh(); } void ReplaceStr(Document *doc, const wxString &str, Selection &s) { cell->AddUndo(doc); cell->ResetChildren(); foreachcellinsel(c, s) c->text.ReplaceStr(str); doc->Refresh(); } void CSVImport(const wxArrayString &as, wxChar sep) { int cy = 0; loop(y, (int)as.size()) { wxString s = as[y]; wxString word; for (int x = 0; s[0]; x++) { if (s[0] == '\"') { word = L""; for (int i = 1;; i++) { if (!s[i]) { if (y < (int)as.size() - 1) { s = as[++y]; i = 0; } else { s = L""; break; } } else if (s[i] == '\"') { if (s[i + 1] == '\"') word += s[++i]; else { s = s.size() == i + 1 ? L"" : s.Mid(i + 2); break; } } else word += s[i]; } } else { int pos = s.Find(sep); if (pos < 0) { word = s; s = L""; } else { word = s.Left(pos); s = s.Mid(pos + 1); } } if (x >= xs) InsertCells(x, -1, 1, 0); Cell *c = C(x, cy); c->text.t = word; } cy++; } ys = cy; // throws memory away, but doesn't matter } Cell *EvalGridCell(Evaluator &ev, Cell *&c, Cell *acc, int &x, int &y, bool &alldata, bool vert) { int ct = c->celltype; // Type of subcell being evaluated // Update alldata condition (variable reads act like data) alldata = alldata && (ct == CT_DATA || ct == CT_VARU); ev.vert = vert; // Inform evaluatour of vert status. (?) switch (ct) { // Var assign case CT_VARD: { if (vert) return acc; // (Reject vertical assignments) Cell *tmpacc = acc; // If we have no data, lets see if we can generate something useful from the // subgrid. if (!tmpacc->grid && tmpacc->text.t.IsEmpty()) { tmpacc = c->Eval(ev); if (!tmpacc) { return nullptr; } } // Assign the current data temporary to the text ev.Assign(c, tmpacc->Clone(nullptr)); // Pass the original data onwards return tmpacc; } // View case CT_VIEWV: case CT_VIEWH: if (vert ? ct == CT_VIEWH : ct == CT_VIEWV) { DELETEP(acc); return c->Clone(nullptr); } delete c; c = acc ? acc->Clone(cell) : new Cell(cell); c->celltype = ct; return acc; // Operation case CT_CODE: { Operation *op = ev.FindOp(c->text.t); switch (op ? strlen(op->args) : -1) { default: DELETEP(acc); return nullptr; case 0: DELETEP(acc); return ev.Execute(op); case 1: return acc ? ev.Execute(op, acc) : nullptr; case 2: if (vert) return acc && y + 1 < ys ? ev.Execute(op, acc, C(x, ++y)) : nullptr; else return acc && x + 1 < xs ? ev.Execute(op, acc, C(++x, y)) : nullptr; case 3: if (vert) return acc && y + 2 < ys ? (y += 2), ev.Execute(op, acc, C(x, y - 2), C(x, y - 1)) : nullptr; else return acc && x + 2 < xs ? (x += 2), ev.Execute(op, acc, C(x - 2, y), C(x - 1, y)) : nullptr; } } // Var read, Data default: DELETEP(acc); return c->Eval(ev); } } Cell *Eval(Evaluator &ev) { Cell *acc = nullptr; // Actual/Accumulating data temporary bool alldata = true; // Is the grid all data? // Do left to right processing if (xs > 1 || ys == 1) foreachcell(c) { if (x == 0) DELETEP(acc); acc = EvalGridCell(ev, c, acc, x, y, alldata, false); } // Do top to bottom processing if (ys > 1) foreachcellcolumn(c) { if (y == 0) DELETEP(acc); acc = EvalGridCell(ev, c, acc, x, y, alldata, true); } // If all data is true then we can exit now. if (alldata) { DELETEP(acc); Cell *result = cell->Clone(nullptr); // Potential result if all data. foreachcellingrid(c, result->grid) { Cell *temp = c->Eval(ev); DELETEP(c); c = temp; } return result; } return acc; } void Split(Vector &gs, bool vert) { loop(i, vert ? xs : ys) gs.push() = new Grid(vert ? 1 : xs, vert ? ys : 1); foreachcell(c) { Grid *g = gs[vert ? x : y]; g->cells[vert ? y : x] = c->SetParent(g->cell); c = nullptr; } delete this; } Cell *Sum() { double total = 0; foreachcell(c) { if (c->HasText()) total += c->text.GetNum(); } delete this; Cell *c = new Cell(); c->text.SetNum(total); return c; } Cell *Transpose() { Cell **tr = new Cell *[xs * ys]; foreachcell(c) tr[y + x * ys] = c; delete[] cells; cells = tr; swap_(xs, ys); SetOrient(); InitColWidths(); return cell; } static int sortfunc(const Cell **a, const Cell **b) { loop(i, sys->sortxs) { int off = (i + sys->sortcolumn) % sys->sortxs; int cmp = (*(a + off))->text.t.CmpNoCase((*(b + off))->text.t); if (cmp) return sys->sortdescending ? -cmp : cmp; } return 0; } void Sort(Selection &s, bool descending) { sys->sortcolumn = s.x; sys->sortxs = xs; sys->sortdescending = descending; qsort(cells + s.y * xs, s.ys, sizeof(Cell *) * xs, (int(__cdecl *)(const void *, const void *))sortfunc); } Cell *FindExact(wxString &s) { foreachcell(c) { Cell *f = c->FindExact(s); if (f) return f; } return nullptr; } Selection HierarchySwap(wxString tag) { Cell *selcell = nullptr; bool done = false; lookformore: foreachcell(c) if (c->grid && !done) { Cell *f = c->grid->FindExact(tag); if (f) { // add all parent tags as extra hierarchy inside the cell for (Cell *p = f->parent; p != cell; p = p->parent) { // Special case check: if parents have same name, this would cause infinite // swapping. if (p->text.t == tag) done = true; Cell *t = new Cell(f, p); t->text = p->text; t->text.cell = t; t->grid = f->grid; if (t->grid) t->grid->ReParent(t); f->grid = new Grid(1, 1); f->grid->cell = f; *f->grid->cells = t; } // remove cell from parent, recursively if parent becomes empty for (Cell *r = f; r && r != cell; r = r->parent->grid->DeleteTagParent(r, cell, f)) ; // merge newly constructed hierarchy at this level if (!*cells) { *cells = f; f->parent = cell; selcell = f; } else { MergeTagCell(f, selcell); } goto lookformore; } } ASSERT(selcell); return FindCell(selcell); } void ReParent(Cell *p) { cell = p; foreachcell(c) c->parent = p; } Cell *DeleteTagParent(Cell *tag, Cell *basecell, Cell *found) { ReplaceCell(tag, nullptr); if (xs * ys == 1) { if (cell != basecell) { cell->grid = nullptr; delete this; } Cell *next = tag->parent; if (tag != found) delete tag; return next; } else foreachcell(c) if (c == nullptr) { if (ys > 1) DeleteCells(-1, y, 0, -1); else DeleteCells(x, -1, -1, 0); return nullptr; } ASSERT(0); return nullptr; } void MergeTagCell(Cell *f, Cell *&selcell) { foreachcell(c) if (c->text.t == f->text.t) { if (!selcell) selcell = c; if (f->grid) { if (c->grid) { f->grid->MergeTagAll(c); } else { c->grid = f->grid; c->grid->ReParent(c); f->grid = nullptr; } delete f; } return; } if (!selcell) selcell = f; Add(f); } void MergeTagAll(Cell *into) { foreachcell(c) { into->grid->MergeTagCell(c, into /*dummy*/); c = nullptr; } } void SetGridTextLayout(int ds, bool vert, bool noset, const Selection &s) { foreachcellinsel(c, s) c->SetGridTextLayout(ds, vert, noset); } bool IsTable() { foreachcell(c) if (c->grid) return false; return true; } void Hierarchify(Document *doc) { loop(y, ys) { Cell *rest = nullptr; if (xs > 1) { Selection s(this, 1, y, xs - 1, 1); rest = CloneSel(s); } Cell *c = C(0, y); loop(prevy, y) { Cell *prev = C(0, prevy); if (prev->text.t == c->text.t) { if (rest) { ASSERT(prev->grid); prev->grid->MergeRow(rest->grid); delete rest; } Selection s(this, 0, y, xs, 1); MultiCellDeleteSub(doc, s); y--; goto done; } } if (rest) { swap_(c->grid, rest->grid); delete rest; c->grid->ReParent(c); } done:; } Selection s(this, 1, 0, xs - 1, ys); MultiCellDeleteSub(doc, s); foreachcell(c) if (c->grid && c->grid->xs > 1) c->grid->Hierarchify(doc); } void MergeRow(Grid *tm) { ASSERT(xs == tm->xs && tm->ys == 1); InsertCells(-1, ys, 0, 1, nullptr); loop(x, xs) { swap_(C(x, ys - 1), tm->C(x, 0)); C(x, ys - 1)->parent = cell; } } void MaxDepthLeaves(int curdepth, int &maxdepth, int &leaves) { foreachcell(c) c->MaxDepthLeaves(curdepth, maxdepth, leaves); } int Flatten(int curdepth, int cury, Grid *g) { foreachcell(c) if (c->grid) { cury = c->grid->Flatten(curdepth + 1, cury, g); } else { Cell *ic = c; for (int i = curdepth; i >= 0; i--) { Cell *dest = g->C(i, cury); dest->text = ic->text; dest->text.cell = dest; ic = ic->parent; } cury++; } return cury; } void ResizeColWidths(int dir, const Selection &s, bool hierarchical) { for (int x = s.x; x < s.x + s.xs; x++) { colwidths[x] += dir * 5; if (colwidths[x] < 5) colwidths[x] = 5; loop(y, ys) { Cell *c = C(x, y); if (c->grid && hierarchical) c->grid->ResizeColWidths(dir, c->grid->SelectAll(), hierarchical); } } } void CollectCells(Vector &itercells) { foreachcell(c) c->CollectCells(itercells); } void CollectCellsSel(Vector &itercells, Selection &s, bool recurse) { foreachcellinsel(c, s) c->CollectCells(itercells, recurse); } void SetStyles(Selection &s, Cell *o) { foreachcellinsel(c, s) { c->cellcolor = o->cellcolor; c->textcolor = o->textcolor; c->text.stylebits = o->text.stylebits; c->text.image = o->text.image; } } void ClearImages(Selection &s) { foreachcellinsel(c, s) c->text.image = nullptr; } }; treesheets-1.0.2/src/lobster_impl.cpp000066400000000000000000000134041352107072600176600ustar00rootroot00000000000000 #include "lobster/stdafx.h" #include "script_interface.h" #include "lobster/compiler.h" using namespace lobster; namespace script { ScriptInterface *si = nullptr; void AddTreeSheets(NativeRegistry &nfr) { nfr("ts_goto_root", "", "", "", "makes the root of the document the current cell. this is the default at the start" "of any script, so this function is only needed to return there.", [](VM &) { si->GoToRoot(); return Value(); }); nfr("ts_goto_view", "", "", "", "makes what the user has zoomed into the current cell.", [](VM &) { si->GoToView(); return Value(); }); nfr("ts_has_selection", "", "", "I", "wether there is a selection.", [](VM &) { return Value(si->HasSelection()); }); nfr("ts_goto_selection", "", "", "", "makes the current cell the one containing the selection, or does nothing on no" "selection.", [](VM &) { si->GoToSelection(); return Value(); }); nfr("ts_has_parent", "", "", "I", "wether the current cell has a parent (is the root cell).", [](VM &) { return Value(si->HasParent()); }); nfr("ts_goto_parent", "", "", "", "makes the current cell the parent of the current cell, if any.", [](VM &) { si->GoToParent(); return Value(); }); nfr("ts_num_children", "", "", "I", "returns the total number of children of the current cell (rows * columns)." "returns 0 if this cell doesn't have a sub-grid at all.", [](VM &) { return Value(si->NumChildren()); }); nfr("ts_num_columns_rows", "", "", "I}:2", "returns the number of columns & rows in the current cell.", [](VM &vm) { vm.PushVec(int2(si->NumColumnsRows())); }); nfr("ts_selection", "", "", "I}:2I}:2", "returns the (x,y) and (xs,ys) of the current selection, or zeroes if none.", [](VM &vm) { auto b = si->SelectionBox(); vm.PushVec(int2(b.second)); vm.PushVec(int2(b.first)); }); nfr("ts_goto_child", "n", "I", "", "makes the current cell the nth child of the current cell.", [](VM &, Value &n) { si->GoToChild(n.intval()); return Value(); }); nfr("ts_goto_column_row", "col,row", "II", "", "makes the current cell the child at col / row.", [](VM &, Value &x, Value &y) { si->GoToColumnRow(x.intval(), y.intval()); return Value(); }); nfr("ts_get_text", "", "", "S", "gets the text of the current cell.", [](VM &vm) { return Value(vm.NewString(si->GetText())); }); nfr("ts_set_text", "text", "S", "", "sets the text of the current cell.", [](VM &, Value &s) { si->SetText(s.sval()->strv()); return Value(); }); nfr("ts_create_grid", "cols,rows", "II", "", "creates a grid in the current cell if there isn't one yet.", [](VM &, Value &x, Value &y) { si->CreateGrid(x.intval(), y.intval()); return Value(); }); nfr("ts_insert_columns", "c,n", "II", "", "insert n columns before column c in an existing grid.", [](VM &, Value &x, Value &n) { si->InsertColumns(x.intval(), n.intval()); return Value(); }); nfr("ts_insert_rows", "r,n", "II", "", "insert n rows before row r in an existing grid.", [](VM &, Value &x, Value &n) { si->InsertRows(x.intval(), n.intval()); return Value(); }); nfr("ts_delete", "pos,size", "I}:2I}:2", "", "clears the cells denoted by pos/size. also removes columns/rows if they become" "completely empty, or the entire grid.", [](VM &vm) { auto s = vm.PopVec(); auto p = vm.PopVec(); si->Delete(p.x, p.y, s.x, s.y); }); nfr("ts_set_background_color", "col", "F}:4", "", "sets the background color of the current cell", [](VM &vm) { auto col = vm.PopVec(); si->SetBackgroundColor(*(uint *)quantizec(col).data()); }); nfr("ts_set_text_color", "col", "F}:4", "", "sets the text color of the current cell", [](VM &vm) { auto col = vm.PopVec(); si->SetTextColor(*(uint *)quantizec(col).data()); }); nfr("ts_set_relative_size", "s", "I", "", "sets the relative size (0 is normal, -1 is smaller etc.) of the current cell", [](VM &, Value &s) { si->SetRelativeSize(geom::clamp(s.intval(), -10, 10)); return Value(); }); nfr("ts_set_style_bits", "s", "I", "", "sets one or more styles (bold = 1, italic = 2, fixed = 4, underline = 8," " strikethru = 16) on the current cell.", [](VM &, Value &s) { si->SetStyle(s.intval()); return Value(); }); nfr("ts_set_status_message", "msg", "S", "", "sets the status message in TreeSheets.", [](VM &vm, Value &s) { si->SetStatusMessage(s.sval()->strv()); return Value(); }); nfr("ts_get_filename_from_user", "is_save", "I", "S", "gets a filename using a file dialog. empty string if cancelled.", [](VM &vm, Value &is_save) { return Value(vm.NewString(si->GetFileNameFromUser(is_save.True()))); }); } NativeRegistry natreg; // FIXME: global. string InitLobster(ScriptInterface *_si, const char *exefilepath, const char *auxfilepath, bool from_bundle, ScriptLoader sl) { si = _si; min_output_level = OUTPUT_PROGRAM; string err = ""; try { InitPlatform(exefilepath, auxfilepath, from_bundle, sl); RegisterBuiltin(natreg, "treesheets", AddTreeSheets); RegisterCoreLanguageBuiltins(natreg); } catch (string &s) { err = s; } return err; } string RunLobster(std::string_view filename, std::string_view code, bool dump_builtins) { string err = ""; try { string bytecode; Compile(natreg, filename, code, bytecode, nullptr, nullptr, dump_builtins, false, false, RUNTIME_ASSERT); VM vm(VMArgs { natreg, filename, move(bytecode) }); vm.EvalProgram(); } catch (string &s) { err = s; } return err; } } treesheets-1.0.2/src/main.cpp000077500000000000000000000121031352107072600161070ustar00rootroot00000000000000 #include "stdafx.h" #ifndef __WXMSW__ #define SIMPLERENDER #endif //#define SIMPLERENDER // for testing int g_grid_margin = 1; int g_cell_margin = 2; int g_margin_extra = 2; // TODO, could make this configurable: 0/2/4/6 int g_line_width = 1; int g_selmargin = 2; int g_deftextsize = 12; int g_mintextsize() { return g_deftextsize - 8; } int g_maxtextsize() { return g_deftextsize + 32; } int g_grid_left_offset = 15; int g_scrollratecursor = 240; // FIXME: must be configurable int g_scrollratewheel = 2; // relative to 1 step on a fixed wheel usually being 120 static uint celltextcolors[] = { 0xFFFFFF, // CUSTOM COLOR! 0xFFFFFF, 0x000000, 0x202020, 0x404040, 0x606060, 0x808080, 0xA0A0A0, 0xC0C0C0, 0xD0D0D0, 0xE0E0E0, 0xE8E8E8, 0x000080, 0x0000FF, 0x8080FF, 0xC0C0FF, 0xC0C0E0, 0x008000, 0x00FF00, 0x80FF80, 0xC0FFC0, 0xC0E0C0, 0x800000, 0xFF0000, 0xFF8080, 0xFFC0C0, 0xE0C0C0, 0x800080, 0xFF00FF, 0xFF80FF, 0xFFC0FF, 0xE0C0E0, 0x008080, 0x00FFFF, 0x80FFFF, 0xC0FFFF, 0xC0E0E0, 0x808000, 0xFFFF00, 0xFFFF80, 0xFFFFC0, 0xE0E0C0, }; #define CUSTOMCOLORIDX 0 enum { TS_VERSION = 19, TS_TEXT = 0, TS_GRID, TS_BOTH, TS_NEITHER }; enum { A_NEW = 500, A_OPEN, A_CLOSE, A_SAVE, A_SAVEAS, A_CUT, A_COPY, A_PASTE, A_NEWGRID, A_UNDO, A_ABOUT, A_RUN, A_CLRVIEW, A_MARKDATA, A_MARKVIEWH, A_MARKVIEWV, A_MARKCODE, A_IMAGE, A_EXPIMAGE, A_EXPXML, A_EXPHTMLT, A_EXPHTMLO, A_EXPTEXT, A_ZOOMIN, A_ZOOMOUT, A_TRANSPOSE, A_DELETE, A_BACKSPACE, A_LEFT, A_RIGHT, A_UP, A_DOWN, A_MLEFT, A_MRIGHT, A_MUP, A_MDOWN, A_SLEFT, A_SRIGHT, A_SUP, A_SDOWN, A_ALEFT, A_ARIGHT, A_AUP, A_ADOWN, A_SCLEFT, A_SCRIGHT, A_SCUP, A_SCDOWN, A_DEFFONT, A_IMPXML, A_IMPXMLA, A_IMPTXTI, A_IMPTXTC, A_IMPTXTS, A_IMPTXTT, A_HELP, A_MARKVARD, A_MARKVARU, A_SHOWSBAR, A_SHOWTBAR, A_LEFTTABS, A_TRADSCROLL, A_HOME, A_END, A_CHOME, A_CEND, A_PRINT, A_PREVIEW, A_PAGESETUP, A_PRINTSCALE, A_EXIT, A_NEXT, A_PREV, A_BOLD, A_ITALIC, A_TT, A_UNDERL, A_SEARCH, A_REPLACE, A_REPLACEONCE, A_REPLACEONCEJ, A_REPLACEALL, A_SELALL, A_CANCELEDIT, A_BROWSE, A_ENTERCELL, A_CELLCOLOR, A_TEXTCOLOR, A_BORDCOLOR, A_INCSIZE, A_DECSIZE, A_INCWIDTH, A_DECWIDTH, A_ENTERGRID, A_LINK, A_LINKREV, A_SEARCHNEXT, A_CUSTCOL, A_COLCELL, A_SORT, A_SEARCHF, A_MAKEBAKS, A_TOTRAY, A_AUTOSAVE, A_FULLSCREEN, A_SCALED, A_SCOLS, A_SROWS, A_SHOME, A_SEND, A_BORD0, A_BORD1, A_BORD2, A_BORD3, A_BORD4, A_BORD5, A_HSWAP, A_TEXTGRID, A_TAGADD, A_TAGREMOVE, A_WRAP, A_HIFY, A_FLATTEN, A_BROWSEF, A_ROUND0, A_ROUND1, A_ROUND2, A_ROUND3, A_ROUND4, A_ROUND5, A_ROUND6, A_FILTER5, A_FILTER10, A_FILTER20, A_FILTER50, A_FILTERM, A_FILTERL, A_FILTERS, A_FILTEROFF, A_FASTRENDER, A_EXPCSV, A_PASTESTYLE, A_PREVFILE, A_NEXTFILE, A_IMAGER, A_INCWIDTHNH, A_DECWIDTHNH, A_ZOOMSCR, A_ICONSET, A_V_GS, A_V_BS, A_V_LS, A_H_GS, A_H_BS, A_H_LS, A_GS, A_BS, A_LS, A_RESETSIZE, A_RESETWIDTH, A_RESETSTYLE, A_RESETCOLOR, A_DDIMAGE, A_MINCLOSE, A_SINGLETRAY, A_CENTERED, A_SORTD, A_STRIKET, A_FOLD, A_FOLDALL, A_UNFOLDALL, A_IMAGESCP, A_IMAGESCF, A_IMAGESCN, A_HELPI, A_REDO, A_FSWATCH, A_DEFBGCOL, A_THINSELC, A_COPYCT, A_MINISIZE, A_CUSTKEY, A_AUTOEXPORT, A_NOP, A_TAGSET = 1000, // and all values from here on A_SCRIPT = 2000, // and all values from here on A_MAXACTION = 3000 }; enum { STYLE_BOLD = 1, STYLE_ITALIC = 2, STYLE_FIXED = 4, STYLE_UNDERLINE = 8, STYLE_STRIKETHRU = 16 }; #include "script_interface.h" using namespace script; struct treesheets { struct TreeSheetsScriptImpl; struct Cell; struct Grid; struct Text; struct Evaluator; struct Image; struct Document; class Selection; struct System; struct MyApp; struct MyFrame; struct TSCanvas; static System *sys; #include "treesheets_impl.h" #include "selection.h" #include "text.h" #include "cell.h" #include "grid.h" #include "evaluator.h" #include "document.h" #include "system.h" #include "mywxtools.h" #include "mycanvas.h" #include "myframe.h" #include "myapp.h" }; treesheets::System *treesheets::sys = nullptr; treesheets::TreeSheetsScriptImpl treesheets::tssi; IMPLEMENT_APP(treesheets::MyApp) #include "myevents.h" treesheets-1.0.2/src/myapp.h000077500000000000000000000102621352107072600157620ustar00rootroot00000000000000 struct IPCServer : wxServer { wxConnectionBase *OnAcceptConnection(const wxString &topic) { sys->frame->DeIconize(); if (topic.Len() && topic != L"*") sys->Open(topic); return new wxConnection(); } }; struct MyApp : wxApp { MyFrame *frame; IPCServer *serv; wxString filename; bool initateventloop; wxLocale locale; wxSingleInstanceChecker *instance_checker = nullptr; MyApp() : frame(nullptr), serv(nullptr), initateventloop(false) {} void AddTranslation(const wxString &basepath) { #ifdef __WXGTK__ locale.AddCatalogLookupPathPrefix(L"/usr"); locale.AddCatalogLookupPathPrefix(L"/usr/local"); wxString prefix = wxStandardPaths::Get().GetInstallPrefix(); locale.AddCatalogLookupPathPrefix(prefix); #endif locale.AddCatalogLookupPathPrefix(basepath); locale.AddCatalog(L"ts", (wxLanguage)locale.GetLanguage()); } bool OnInit() { #if wxUSE_UNICODE == 0 #error "must use unicode version of wx libs to ensure data integrity of .cts files" #endif ASSERT(wxUSE_UNICODE); #ifdef __WXMAC__ wxDisableAsserts(); #endif #ifdef __WXMSW__ InitUnhandledExceptionFilter(); // wxWidgets should really be doing this itself, but it doesn't (or expects you to // want to use a manifest), so we have to call it ourselves. #ifndef DPI_ENUMS_DECLARED typedef enum PROCESS_DPI_AWARENESS { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS; #endif typedef BOOL (WINAPI * SETPROCESSDPIAWARE_T)(void); typedef HRESULT (WINAPI * SETPROCESSDPIAWARENESS_T)(PROCESS_DPI_AWARENESS); HMODULE shcore = LoadLibraryA("Shcore.dll"); SETPROCESSDPIAWARENESS_T SetProcessDpiAwareness = NULL; if (shcore) { SetProcessDpiAwareness = (SETPROCESSDPIAWARENESS_T)GetProcAddress(shcore, "SetProcessDpiAwareness"); } HMODULE user32 = LoadLibraryA("User32.dll"); SETPROCESSDPIAWARE_T SetProcessDPIAware = NULL; if (user32) { SetProcessDPIAware = (SETPROCESSDPIAWARE_T)GetProcAddress(user32, "SetProcessDPIAware"); } if (SetProcessDpiAwareness) { SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); } else if (SetProcessDPIAware) { SetProcessDPIAware(); } if (user32) FreeLibrary(user32); if (shcore) FreeLibrary(shcore); #endif locale.Init(); bool portable = false; for (int i = 1; i < argc; i++) { if (argv[i][0] == '-') { switch ((int)argv[i][1]) { case 'p': portable = true; break; } } else { filename = argv[i]; } } instance_checker = new wxSingleInstanceChecker(); if (instance_checker->IsAnotherRunning()) { wxClient client; client.MakeConnection(L"localhost", L"4242", filename.Len() ? filename.wc_str() : L"*"); // fire and forget return false; } sys = new System(portable); frame = new MyFrame(argv[0], this); SetTopWindow(frame); serv = new IPCServer(); serv->Create(L"4242"); return true; } void OnEventLoopEnter(wxEventLoopBase* WXUNUSED(loop)) { if (!initateventloop) { initateventloop = true; frame->AppOnEventLoopEnter(); sys->Init(filename); } } int OnExit() { DELETEP(serv); DELETEP(sys); DELETEP(instance_checker); return 0; } void MacOpenFile(const wxString &fn) { if (sys) sys->Open(fn); } DECLARE_EVENT_TABLE() }; treesheets-1.0.2/src/mycanvas.h000077500000000000000000000132311352107072600164540ustar00rootroot00000000000000struct TSCanvas : public wxScrolledWindow { MyFrame *frame; Document *doc; int mousewheelaccum; bool lastrmbwaswithctrl; wxPoint lastmousepos; TSCanvas(MyFrame *fr, wxWindow *parent, const wxSize &size = wxDefaultSize) : frame(fr), wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, size, wxScrolledWindowStyle | wxWANTS_CHARS), mousewheelaccum(0), doc(nullptr), lastrmbwaswithctrl(false) { SetBackgroundStyle(wxBG_STYLE_PAINT); SetBackgroundColour(*wxWHITE); DisableKeyboardScrolling(); // Without this, ScrolledWindow does its own scrolling upon mousewheel events, which // interferes with our own. EnableScrolling(false, false); } ~TSCanvas() { DELETEP(doc); frame = nullptr; } void OnPaint(wxPaintEvent &event) { #ifdef __WXMAC__ wxPaintDC dc(this); #else auto sz = GetClientSize(); wxBitmap buffer(sz.GetX(), sz.GetY(), 24); wxBufferedPaintDC dc(this, buffer); #endif // DoPrepareDC(dc); doc->Draw(dc); }; void UpdateHover(int mx, int my, wxDC &dc) { int x, y; CalcUnscrolledPosition(mx, my, &x, &y); DoPrepareDC(dc); doc->Hover(x / doc->currentviewscale, y / doc->currentviewscale, dc); } void OnMotion(wxMouseEvent &me) { wxClientDC dc(this); UpdateHover(me.GetX(), me.GetY(), dc); if (me.LeftIsDown() || me.RightIsDown()) { doc->Drag(dc); } else if (me.MiddleIsDown()) { wxPoint p = me.GetPosition() - lastmousepos; CursorScroll(-p.x, -p.y); } lastmousepos = me.GetPosition(); } void SelectClick(int mx, int my, bool right, int isctrlshift) { if (mx < 0 || my < 0) return; // for some reason, using just the "menu" key sends a right-click at (-1, -1) wxClientDC dc(this); UpdateHover(mx, my, dc); doc->Select(dc, right, isctrlshift); } void OnLeftDown(wxMouseEvent &me) { #ifndef __WXMSW__ // seems to not want to give the sw focus otherwise (thinks its already in focus // when its not?) if (frame->filter) frame->filter->SetFocus(); #endif SetFocus(); if (me.ShiftDown()) OnMotion(me); else SelectClick(me.GetX(), me.GetY(), false, me.CmdDown() + me.AltDown() * 2); } void OnLeftUp(wxMouseEvent &me) { if (me.CmdDown() || me.AltDown()) doc->SelectUp(); } void OnRightDown(wxMouseEvent &me) { SetFocus(); SelectClick(me.GetX(), me.GetY(), true, 0); lastrmbwaswithctrl = me.CmdDown(); #ifndef __WXMSW__ me.Skip(); // otherwise EVT_CONTEXT_MENU won't be triggered? #endif } void OnLeftDoubleClick(wxMouseEvent &me) { wxClientDC dc(this); UpdateHover(me.GetX(), me.GetY(), dc); Status(doc->DoubleClick(dc)); } void OnKeyDown(wxKeyEvent &ce) { ce.Skip(); } void OnChar(wxKeyEvent &ce) { /* if (sys->insidefiledialog) { ce.Skip(); return; } */ // Without this check, Alt+F (keyboard menu nav) Alt+1..6 (style changes), Alt+cursor // (scrolling) don't work. // The 128 makes sure unicode entry on e.g. Polish keyboards still works. // (on Linux in particular). if ((ce.GetModifiers() == wxMOD_ALT) && (ce.GetUnicodeKey() < 128)) { ce.Skip(); return; } wxClientDC dc(this); DoPrepareDC(dc); bool unprocessed = false; Status(doc->Key(dc, ce.GetUnicodeKey(), ce.GetKeyCode(), ce.AltDown(), ce.CmdDown(), ce.ShiftDown(), unprocessed)); if (unprocessed) ce.Skip(); } void OnMouseWheel(wxMouseEvent &me) { bool ctrl = me.CmdDown(); if (sys->zoomscroll) ctrl = !ctrl; wxClientDC dc(this); if (me.AltDown() || ctrl || me.ShiftDown()) { mousewheelaccum += me.GetWheelRotation(); int steps = mousewheelaccum / me.GetWheelDelta(); if (!steps) return; mousewheelaccum -= steps * me.GetWheelDelta(); UpdateHover(me.GetX(), me.GetY(), dc); Status(doc->Wheel(dc, steps, me.AltDown(), ctrl, me.ShiftDown())); } else if (me.GetWheelAxis()) { CursorScroll(me.GetWheelRotation() * g_scrollratewheel, 0); UpdateHover(me.GetX(), me.GetY(), dc); } else { CursorScroll(0, -me.GetWheelRotation() * g_scrollratewheel); UpdateHover(me.GetX(), me.GetY(), dc); } } void OnSize(wxSizeEvent &se) { doc->Refresh(); } void OnContextMenuClick(wxContextMenuEvent &cme) { if (lastrmbwaswithctrl) { wxMenu *tagmenu = new wxMenu(); doc->RecreateTagMenu(*tagmenu); PopupMenu(tagmenu); delete tagmenu; } else { PopupMenu(frame->editmenupopup); } } void CursorScroll(int dx, int dy) { int x, y; GetViewStart(&x, &y); x += dx; y += dy; // EnableScrolling(true, true); Scroll(x, y); // EnableScrolling(false, false); } void Status(const wxChar *msg = nullptr) { if (frame->GetStatusBar() && (!msg || *msg)) frame->SetStatusText(msg ? msg : L"", 0); } DECLARE_EVENT_TABLE() }; treesheets-1.0.2/src/myevents.h000077500000000000000000000031641352107072600165110ustar00rootroot00000000000000 BEGIN_EVENT_TABLE(treesheets::MyFrame, wxFrame) EVT_SIZING(treesheets::MyFrame::OnSizing) EVT_MENU(wxID_ANY, treesheets::MyFrame::OnMenu) EVT_TEXT(A_SEARCH, treesheets::MyFrame::OnSearch) EVT_CLOSE(treesheets::MyFrame::OnClosing) EVT_MAXIMIZE(treesheets::MyFrame::OnMaximize) EVT_ACTIVATE(treesheets::MyFrame::OnActivate) EVT_COMBOBOX(A_CELLCOLOR, treesheets::MyFrame::OnCellColor) EVT_COMBOBOX(A_TEXTCOLOR, treesheets::MyFrame::OnTextColor) EVT_COMBOBOX(A_BORDCOLOR, treesheets::MyFrame::OnBordColor) EVT_COMBOBOX(A_DDIMAGE, treesheets::MyFrame::OnDDImage) EVT_ICONIZE(treesheets::MyFrame::OnIconize) EVT_AUINOTEBOOK_PAGE_CHANGED(wxID_ANY, treesheets::MyFrame::OnTabChange) EVT_AUINOTEBOOK_PAGE_CLOSE(wxID_ANY, treesheets::MyFrame::OnTabClose) END_EVENT_TABLE() BEGIN_EVENT_TABLE(treesheets::MyApp, wxApp) END_EVENT_TABLE() BEGIN_EVENT_TABLE(treesheets::TSCanvas, wxScrolledWindow) EVT_MOUSEWHEEL(treesheets::TSCanvas::OnMouseWheel) EVT_PAINT(treesheets::TSCanvas::OnPaint) EVT_MOTION(treesheets::TSCanvas::OnMotion) EVT_LEFT_DOWN(treesheets::TSCanvas::OnLeftDown) EVT_LEFT_UP(treesheets::TSCanvas::OnLeftUp) EVT_RIGHT_DOWN(treesheets::TSCanvas::OnRightDown) EVT_LEFT_DCLICK(treesheets::TSCanvas::OnLeftDoubleClick) EVT_CHAR(treesheets::TSCanvas::OnChar) EVT_KEY_DOWN(treesheets::TSCanvas::OnKeyDown) EVT_CONTEXT_MENU(treesheets::TSCanvas::OnContextMenuClick) EVT_SIZE(treesheets::TSCanvas::OnSize) END_EVENT_TABLE() BEGIN_EVENT_TABLE(treesheets::ThreeChoiceDialog, wxDialog) EVT_BUTTON(wxID_ANY, treesheets::ThreeChoiceDialog::OnButton) END_EVENT_TABLE() treesheets-1.0.2/src/myframe.h000077500000000000000000001503541352107072600163030ustar00rootroot00000000000000struct MyFrame : wxFrame { typedef std::vector> MenuString; typedef MenuString::iterator MenuStringIterator; wxMenu *editmenupopup; wxString exepath_; wxFileHistory filehistory; wxTextCtrl *filter, *replaces; wxToolBar *tb; int refreshhack, refreshhackinstances; BlinkTimer bt; wxTaskBarIcon tbi; wxIcon icon; ImageDropdown *idd; wxAuiNotebook *nb; wxAuiManager *aui; wxBitmap line_nw, line_sw; wxBitmap foldicon; bool fromclosebox; MyApp *app; wxFileSystemWatcher *watcher; bool watcherwaitingforuser; double csf, csf_orig; std::vector scripts_in_menu; wxString GetPath(const wxString &relpath) { if (!exepath_.Length()) return relpath; return exepath_ + "/" + relpath; } MenuString menustrings; void MyAppend(wxMenu *menu, int tag, const wxString &contents, const wchar_t *help = L"") { wxString item = contents; wxString key = L""; int pos = contents.Find("\t"); if (pos >= 0) { item = contents.Mid(0, pos); key = contents.Mid(pos + 1); } key = sys->cfg->Read(item, key); wxString newcontents = item; if (key.Length()) newcontents += "\t" + key; menu->Append(tag, newcontents, help); menustrings.push_back(std::make_pair(item, key)); } MyFrame(wxString exename, MyApp *_app) : wxFrame((wxFrame *)nullptr, wxID_ANY, L"TreeSheets", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE), filter(nullptr), replaces(nullptr), tb(nullptr), nb(nullptr), idd(nullptr), refreshhack(0), refreshhackinstances(0), aui(nullptr), fromclosebox(true), app(_app), watcherwaitingforuser(false), watcher(nullptr) { sys->frame = this; exepath_ = wxFileName(exename).GetPath(); #ifdef __WXMAC__ int cut = exepath_.Find("/MacOS"); if (cut > 0) { exepath_ = exepath_.SubString(0, cut) + "/Resources"; } #endif class MyLog : public wxLog { void DoLogString(const wxChar *msg, time_t timestamp) { DoLogText(*msg); } void DoLogText(const wxString &msg) { #ifdef WIN32 OutputDebugString(msg.c_str()); OutputDebugString(L"\n"); #else fputws(msg.c_str(), stderr); fputws(L"\n", stderr); #endif } }; wxLog::SetActiveTarget(new MyLog()); wxLogMessage(L"%s", wxVERSION_STRING); wxLogMessage(L"locale: %s", std::setlocale(LC_CTYPE, nullptr)); app->AddTranslation(GetPath("translations")); csf = GetContentScaleFactor(); wxLogMessage(L"content scale: %f", csf); csf_orig = csf; #ifdef __WXMSW__ // On Windows, I get csf == 1.25, as indicated in the display properties. // With this factor set, bitmaps display. At their same physical sizes as when // TreeSheets was a non-DPI-aware app, and extra resolution is used. #endif #ifdef __WXMAC__ // Typically csf == 2 on a retina mac. But on the mac, unlike Windows, image rendering // *already* does scaling, and no way to turn that behavior off for now? csf = 1.0; // FIXME: This gives us low res images even though the display is capable of better! // Apparently still not fixed: http://trac.wxwidgets.org/ticket/15808 // wxBitmap::CreateScaled could be the way to solve this, but it is not obvious // how to use it, since you can't pass this scale to LoadFile etc. Could possibly // blit it over via a MemoryDC? #endif #ifdef __WXGTK__ // Currently on Linux csf is always 1, so this is useless. // FIXME: On a high-DPI display we get low res images even though the display is // capable of better! #endif wxInitAllImageHandlers(); wxIconBundle icons; wxIcon iconbig; icon.LoadFile(GetPath(L"images/icon16.png"), wxBITMAP_TYPE_PNG); iconbig.LoadFile(GetPath(L"images/icon32.png"), wxBITMAP_TYPE_PNG); if (!icon.IsOk() || !iconbig.IsOk()) { wxMessageBox(_(L"Error loading core data file (TreeSheets not installed correctly?)"), _(L"Initialization Error"), wxOK, this); exit(1); } #ifdef WIN32 int iconsmall = ::GetSystemMetrics(SM_CXSMICON); int iconlarge = ::GetSystemMetrics(SM_CXICON); icon.SetSize(iconsmall, iconsmall); // this shouldn't be necessary... iconbig.SetSize(iconlarge, iconlarge); #endif icons.AddIcon(icon); icons.AddIcon(iconbig); SetIcons(icons); wxImage foldiconi; line_nw.LoadFile(GetPath(L"images/render/line_nw.png"), wxBITMAP_TYPE_PNG); line_sw.LoadFile(GetPath(L"images/render/line_sw.png"), wxBITMAP_TYPE_PNG); foldiconi.LoadFile(GetPath(L"images/nuvola/fold.png")); foldicon = wxBitmap(foldiconi); ScaleBitmap(foldicon, csf / 3.0, foldicon); if (sys->singletray) tbi.Connect(wxID_ANY, wxEVT_TASKBAR_LEFT_UP, wxTaskBarIconEventHandler(MyFrame::OnTBIDBLClick), nullptr, this); else tbi.Connect(wxID_ANY, wxEVT_TASKBAR_LEFT_DCLICK, wxTaskBarIconEventHandler(MyFrame::OnTBIDBLClick), nullptr, this); aui = new wxAuiManager(this); bool mergetbar = false; bool showtbar, showsbar, lefttabs, iconset; sys->cfg->Read(L"showtbar", &showtbar, true); sys->cfg->Read(L"showsbar", &showsbar, true); sys->cfg->Read(L"lefttabs", &lefttabs, true); sys->cfg->Read(L"iconset", &iconset, false); filehistory.Load(*sys->cfg); wxMenu *expmenu = new wxMenu(); MyAppend(expmenu, A_EXPXML, _(L"&XML..."), _(L"Export the current view as XML (which can also be reimported without losing " L"structure)")); MyAppend(expmenu, A_EXPHTMLT, _(L"&HTML (Tables+Styling)..."), _(L"Export the current view as HTML using nested tables, that will look somewhat " L"like the TreeSheet")); MyAppend(expmenu, A_EXPHTMLO, _(L"HTML (&Outline)..."), _(L"Export the current view as HTML as nested headers, suitable for importing into " L"Word's outline mode")); MyAppend(expmenu, A_EXPTEXT, _(L"Indented &Text..."), _(L"Export the current view as tree structured text, using spaces for each " L"indentation level. " L"Suitable for importing into mindmanagers and general text programs")); MyAppend(expmenu, A_EXPCSV, _(L"&Comma delimited text (CSV)..."), _(L"Export the current view as CSV. Good for spreadsheets and databases. Only works " L"on grids " L"with no sub-grids (use the Flatten operation first if need be)")); MyAppend(expmenu, A_EXPIMAGE, _(L"&Image..."), _(L"Export the current view as an image. Useful for faithfull renderings of the " L"TreeSheet, and " L"programs that don't accept any of the above options")); wxMenu *impmenu = new wxMenu(); MyAppend(impmenu, A_IMPXML, _(L"XML...")); MyAppend(impmenu, A_IMPXMLA, _(L"XML (attributes too, for OPML etc)...")); MyAppend(impmenu, A_IMPTXTI, _(L"Indented text...")); MyAppend(impmenu, A_IMPTXTC, _(L"Comma delimited text (CSV)...")); MyAppend(impmenu, A_IMPTXTS, _(L"Semi-Colon delimited text (CSV)...")); MyAppend(impmenu, A_IMPTXTT, _(L"Tab delimited text...")); wxMenu *recentmenu = new wxMenu(); filehistory.UseMenu(recentmenu); filehistory.AddFilesToMenu(); wxMenu *filemenu = new wxMenu(); MyAppend(filemenu, A_NEW, _(L"&New\tCTRL+n")); MyAppend(filemenu, A_OPEN, _(L"&Open...\tCTRL+o")); MyAppend(filemenu, A_CLOSE, _(L"&Close\tCTRL+w")); filemenu->AppendSubMenu(recentmenu, _(L"&Recent files")); MyAppend(filemenu, A_SAVE, _(L"&Save\tCTRL+s")); MyAppend(filemenu, A_SAVEAS, _(L"Save &As...")); filemenu->AppendSeparator(); MyAppend(filemenu, A_PAGESETUP, _(L"Page Setup...")); MyAppend(filemenu, A_PRINTSCALE, _(L"Set Print Scale...")); MyAppend(filemenu, A_PREVIEW, _(L"Print preview...")); MyAppend(filemenu, A_PRINT, _(L"&Print...\tCTRL+p")); filemenu->AppendSeparator(); filemenu->AppendSubMenu(expmenu, _(L"Export &view as")); filemenu->AppendSubMenu(impmenu, _(L"Import file from")); filemenu->AppendSeparator(); MyAppend(filemenu, A_EXIT, _(L"&Exit\tCTRL+q")); wxMenu *editmenu; loop(twoeditmenus, 2) { wxMenu *sizemenu = new wxMenu(); MyAppend(sizemenu, A_INCSIZE, _(L"&Increase text size (SHIFT+mousewheel)\tSHIFT+PGUP")); MyAppend(sizemenu, A_DECSIZE, _(L"&Decrease text size (SHIFT+mousewheel)\tSHIFT+PGDN")); MyAppend(sizemenu, A_RESETSIZE, _(L"&Reset text sizes\tSHIFT+CTRL+s")); MyAppend(sizemenu, A_MINISIZE, _(L"&Shrink text of all sub-grids\tSHIFT+CTRL+m")); sizemenu->AppendSeparator(); MyAppend(sizemenu, A_INCWIDTH, _(L"Increase column width (ALT+mousewheel)\tALT+PGUP")); MyAppend(sizemenu, A_DECWIDTH, _(L"Decrease column width (ALT+mousewheel)\tALT+PGDN")); MyAppend(sizemenu, A_INCWIDTHNH, _(L"Increase column width (no sub grids)\tCTRL+ALT+PGUP")); MyAppend(sizemenu, A_DECWIDTHNH, _(L"Decrease column width (no sub grids)\tCTRL+ALT+PGDN")); MyAppend(sizemenu, A_RESETWIDTH, _(L"Reset column widths\tSHIFT+CTRL+w")); wxMenu *bordmenu = new wxMenu(); MyAppend(bordmenu, A_BORD0, L"&0\tCTRL+SHIFT+0"); MyAppend(bordmenu, A_BORD1, L"&1\tCTRL+SHIFT+1"); MyAppend(bordmenu, A_BORD2, L"&2\tCTRL+SHIFT+2"); MyAppend(bordmenu, A_BORD3, L"&3\tCTRL+SHIFT+3"); MyAppend(bordmenu, A_BORD4, L"&4\tCTRL+SHIFT+4"); MyAppend(bordmenu, A_BORD5, L"&5\tCTRL+SHIFT+5"); wxMenu *selmenu = new wxMenu(); MyAppend(selmenu, A_NEXT, _(L"Move to next cell\tTAB")); MyAppend(selmenu, A_PREV, _(L"Move to previous cell\tSHIFT+TAB")); selmenu->AppendSeparator(); MyAppend(selmenu, A_SELALL, _(L"Select &all in current grid\tCTRL+a")); selmenu->AppendSeparator(); MyAppend(selmenu, A_LEFT, _(L"Move Selection Left\tLEFT")); MyAppend(selmenu, A_RIGHT, _(L"Move Selection Right\tRIGHT")); MyAppend(selmenu, A_UP, _(L"Move Selection Up\tUP")); MyAppend(selmenu, A_DOWN, _(L"Move Selection Down\tDOWN")); selmenu->AppendSeparator(); MyAppend(selmenu, A_MLEFT, _(L"Move Cells Left\tCTRL+LEFT")); MyAppend(selmenu, A_MRIGHT, _(L"Move Cells Right\tCTRL+RIGHT")); MyAppend(selmenu, A_MUP, _(L"Move Cells Up\tCTRL+UP")); MyAppend(selmenu, A_MDOWN, _(L"Move Cells Down\tCTRL+DOWN")); selmenu->AppendSeparator(); MyAppend(selmenu, A_SLEFT, _(L"Extend Selection Left\tSHIFT+LEFT")); MyAppend(selmenu, A_SRIGHT, _(L"Extend Selection Right\tSHIFT+RIGHT")); MyAppend(selmenu, A_SUP, _(L"Extend Selection Up\tSHIFT+UP")); MyAppend(selmenu, A_SDOWN, _(L"Extend Selection Down\tSHIFT+DOWN")); MyAppend(selmenu, A_SCOLS, _(L"Extend Selection Full Columns")); MyAppend(selmenu, A_SROWS, _(L"Extend Selection Full Rows")); selmenu->AppendSeparator(); MyAppend(selmenu, A_CANCELEDIT, _(L"Select &Parent\tESC")); MyAppend(selmenu, A_ENTERGRID, _(L"Select First &Child\tSHIFT+ENTER")); selmenu->AppendSeparator(); MyAppend(selmenu, A_LINK, _(L"Go To &Matching Cell\tF6")); MyAppend(selmenu, A_LINKREV, _(L"Go To Matching Cell (Reverse)\tSHIFT+F6")); wxMenu *temenu = new wxMenu(); MyAppend(temenu, A_LEFT, _(L"Cursor Left\tLEFT")); MyAppend(temenu, A_RIGHT, _(L"Cursor Right\tRIGHT")); MyAppend(temenu, A_MLEFT, _(L"Word Left\tCTRL+LEFT")); MyAppend(temenu, A_MRIGHT, _(L"Word Right\tCTRL+RIGHT")); temenu->AppendSeparator(); MyAppend(temenu, A_SLEFT, _(L"Extend Selection Left\tSHIFT+LEFT")); MyAppend(temenu, A_SRIGHT, _(L"Extend Selection Right\tSHIFT+RIGHT")); MyAppend(temenu, A_SCLEFT, _(L"Extend Selection Word Left\tSHIFT+CTRL+LEFT")); MyAppend(temenu, A_SCRIGHT, _(L"Extend Selection Word Right\tSHIFT+CTRL+RIGHT")); MyAppend(temenu, A_SHOME, _(L"Extend Selection to Start\tSHIFT+HOME")); MyAppend(temenu, A_SEND, _(L"Extend Selection to End\tSHIFT+END")); temenu->AppendSeparator(); MyAppend(temenu, A_HOME, _(L"Start of line of text\tHOME")); MyAppend(temenu, A_END, _(L"End of line of text\tEND")); MyAppend(temenu, A_CHOME, _(L"Start of text\tCTRL+HOME")); MyAppend(temenu, A_CEND, _(L"End of text\tCTRL+END")); temenu->AppendSeparator(); MyAppend(temenu, A_ENTERCELL, _(L"Enter/exit text edit mode\tENTER")); MyAppend(temenu, A_ENTERCELL, _(L"Enter/exit text edit mode\tF2")); MyAppend(temenu, A_CANCELEDIT, _(L"Cancel text edits\tESC")); wxMenu *stmenu = new wxMenu(); MyAppend(stmenu, A_BOLD, _(L"Toggle cell &BOLD\tCTRL+b")); MyAppend(stmenu, A_ITALIC, _(L"Toggle cell &ITALIC\tCTRL+i")); MyAppend(stmenu, A_TT, _(L"Toggle cell &typewriter\tCTRL+ALT+t")); MyAppend(stmenu, A_UNDERL, _(L"Toggle cell &underlined\tCTRL+u")); MyAppend(stmenu, A_STRIKET, _(L"Toggle cell &strikethrough\tCTRL+t")); stmenu->AppendSeparator(); MyAppend(stmenu, A_RESETSTYLE, _(L"&Reset text styles\tSHIFT+CTRL+r")); MyAppend(stmenu, A_RESETCOLOR, _(L"Reset &colors\tSHIFT+CTRL+c")); wxMenu *tagmenu = new wxMenu(); MyAppend(tagmenu, A_TAGADD, _(L"&Add Cell Text as Tag")); MyAppend(tagmenu, A_TAGREMOVE, _(L"&Remove Cell Text from Tags")); MyAppend(tagmenu, A_NOP, _(L"&Set Cell Text to tag (use CTRL+RMB)"), _(L"Hold CTRL while pressing right mouse button to quickly set a tag for the " L"current cell " L"using a popup menu")); wxMenu *orgmenu = new wxMenu(); MyAppend(orgmenu, A_TRANSPOSE, _(L"&Transpose\tSHIFT+CTRL+t"), _(L"changes the orientation of a grid")); MyAppend(orgmenu, A_SORT, _(L"Sort &Ascending"), _(L"Make a 1xN selection to indicate which column to sort on, and which rows to " L"affect")); MyAppend(orgmenu, A_SORTD, _(L"Sort &Descending"), _(L"Make a 1xN selection to indicate which column to sort on, and which rows to " L"affect")); MyAppend(orgmenu, A_HSWAP, _(L"Hierarchy &Swap\tF8"), _(L"Swap all cells with this text at this level (or above) with the parent")); MyAppend(orgmenu, A_HIFY, _(L"&Hierarchify"), _(L"Convert an NxN grid with repeating elements per column into an 1xN grid " L"with hierarchy, " L"useful to convert data from spreadsheets")); MyAppend(orgmenu, A_FLATTEN, _(L"&Flatten"), _(L"Takes a hierarchy (nested 1xN or Nx1 grids) and converts it into a flat NxN " L"grid, useful " L"for export to spreadsheets")); wxMenu *imgmenu = new wxMenu(); MyAppend(imgmenu, A_IMAGE, _(L"&Add Image"), _(L"Adds an image to the selected cell")); MyAppend(imgmenu, A_IMAGESCP, _(L"&Scale Image (re-sample pixels)"), _(L"Change the image size if it is too big, by reducing the amount of " L"pixels")); MyAppend(imgmenu, A_IMAGESCF, _(L"&Scale Image (display only)"), _(L"Change the image size if it is too big or too small, by changing the size " L"shown on screen. Applies to all uses of this image.")); MyAppend(imgmenu, A_IMAGESCN, _(L"&Reset Scale (display only)"), _(L"Change the scale to match DPI of the current display. " L"Applies to all uses of this image.")); MyAppend(imgmenu, A_IMAGER, _(L"&Remove Image(s)"), _(L"Remove image(s) from the selected cells")); wxMenu *navmenu = new wxMenu(); MyAppend(navmenu, A_BROWSE, _(L"Open link in &browser\tF5"), _(L"Opens up the text from the selected cell in browser (should start be a " L"valid URL)")); MyAppend(navmenu, A_BROWSEF, _(L"Open &file\tF4"), _(L"Opens up the text from the selected cell in default application for the " L"file type")); wxMenu *laymenu = new wxMenu(); MyAppend(laymenu, A_V_GS, _(L"Vertical Layout with Grid Style Rendering\tALT+1")); MyAppend(laymenu, A_V_BS, _(L"Vertical Layout with Bubble Style Rendering\tALT+2")); MyAppend(laymenu, A_V_LS, _(L"Vertical Layout with Line Style Rendering\tALT+3")); laymenu->AppendSeparator(); MyAppend(laymenu, A_H_GS, _(L"Horizontal Layout with Grid Style Rendering\tALT+4")); MyAppend(laymenu, A_H_BS, _(L"Horizontal Layout with Bubble Style Rendering\tALT+5")); MyAppend(laymenu, A_H_LS, _(L"Horizontal Layout with Line Style Rendering\tALT+6")); laymenu->AppendSeparator(); MyAppend(laymenu, A_GS, _(L"Grid Style Rendering\tALT+7")); MyAppend(laymenu, A_BS, _(L"Bubble Style Rendering\tALT+8")); MyAppend(laymenu, A_LS, _(L"Line Style Rendering\tALT+9")); laymenu->AppendSeparator(); MyAppend(laymenu, A_TEXTGRID, _(L"Toggle Vertical Layout\tF7"), _(L"Make a hierarchy layout more vertical (default) or more horizontal")); editmenu = new wxMenu(); MyAppend(editmenu, A_CUT, _(L"Cu&t\tCTRL+x")); MyAppend(editmenu, A_COPY, _(L"&Copy\tCTRL+c")); MyAppend(editmenu, A_COPYCT, _(L"Copy As Continuous Text")); MyAppend(editmenu, A_PASTE, _(L"&Paste\tCTRL+v")); MyAppend(editmenu, A_PASTESTYLE, _(L"Paste Style Only\tCTRL+SHIFT+v"), _(L"only sets the colors and style of the copied cell, and keeps the text")); editmenu->AppendSeparator(); MyAppend(editmenu, A_UNDO, _(L"&Undo\tCTRL+z"), _(L"revert the changes, one step at a time")); MyAppend(editmenu, A_REDO, _(L"&Redo\tCTRL+y"), _(L"redo any undo steps, if you haven't made changes since")); editmenu->AppendSeparator(); MyAppend(editmenu, A_DELETE, _(L"&Delete After\tDEL"), _(L"Deletes the column of cells after the selected grid line, or the row below")); MyAppend( editmenu, A_BACKSPACE, _(L"Delete Before\tBACK"), _(L"Deletes the column of cells before the selected grid line, or the row above")); editmenu->AppendSeparator(); MyAppend(editmenu, A_NEWGRID, #ifdef __WXMAC__ _(L"&Insert New Grid\tCTRL+g"), #else _(L"&Insert New Grid\tINS"), #endif _(L"Adds a grid to the selected cell")); MyAppend(editmenu, A_WRAP, _(L"&Wrap in new parent\tF9"), _(L"Creates a new level of hierarchy around the current selection")); editmenu->AppendSeparator(); // F10 is tied to the OS on both Ubuntu and OS X, and SHIFT+F10 is now right // click on all platforms? MyAppend(editmenu, A_FOLD, #ifndef WIN32 _(L"Toggle Fold\tCTRL+F10"), #else _(L"Toggle Fold\tF10"), #endif _("Toggles showing the grid of the selected cell(s)")); MyAppend(editmenu, A_FOLDALL, _(L"Fold All\tCTRL+SHIFT+F10"), _(L"Folds the grid of the selected cell(s) recursively")); MyAppend(editmenu, A_UNFOLDALL, _(L"Unfold All\tCTRL+ALT+F10"), _(L"Unfolds the grid of the selected cell(s) recursively")); editmenu->AppendSeparator(); editmenu->AppendSubMenu(selmenu, _(L"&Selection...")); editmenu->AppendSubMenu(orgmenu, _(L"&Grid Reorganization...")); editmenu->AppendSubMenu(laymenu, _(L"&Layout && Render Style...")); editmenu->AppendSubMenu(imgmenu, _(L"&Images...")); editmenu->AppendSubMenu(navmenu, _(L"&Browsing...")); editmenu->AppendSubMenu(temenu, _(L"Text &Editing...")); editmenu->AppendSubMenu(sizemenu, _(L"Text Sizing...")); editmenu->AppendSubMenu(stmenu, _(L"Text Style...")); editmenu->AppendSubMenu(bordmenu, _(L"Set Grid Border Width...")); editmenu->AppendSubMenu(tagmenu, _(L"Tag...")); if (!twoeditmenus) editmenupopup = editmenu; } wxMenu *semenu = new wxMenu(); MyAppend(semenu, A_SEARCHF, _(L"&Search\tCTRL+f")); MyAppend(semenu, A_SEARCHNEXT, _(L"&Go To Next Search Result\tF3")); MyAppend(semenu, A_REPLACEONCE, _(L"&Replace in Current Selection\tCTRL+h")); MyAppend(semenu, A_REPLACEONCEJ, _(L"&Replace in Current Selection & Jump Next\tCTRL+j")); MyAppend(semenu, A_REPLACEALL, _(L"Replace &All")); wxMenu *scrollmenu = new wxMenu(); MyAppend(scrollmenu, A_AUP, _(L"Scroll Up (mousewheel)\tPGUP")); MyAppend(scrollmenu, A_AUP, _(L"Scroll Up (mousewheel)\tALT+UP")); MyAppend(scrollmenu, A_ADOWN, _(L"Scroll Down (mousewheel)\tPGDN")); MyAppend(scrollmenu, A_ADOWN, _(L"Scroll Down (mousewheel)\tALT+DOWN")); MyAppend(scrollmenu, A_ALEFT, _(L"Scroll Left\tALT+LEFT")); MyAppend(scrollmenu, A_ARIGHT, _(L"Scroll Right\tALT+RIGHT")); wxMenu *filtermenu = new wxMenu(); MyAppend(filtermenu, A_FILTEROFF, _(L"Turn filter &off")); MyAppend(filtermenu, A_FILTERS, _(L"Show only cells in current search")); MyAppend(filtermenu, A_FILTER5, _(L"Show 5% of last edits")); MyAppend(filtermenu, A_FILTER10, _(L"Show 10% of last edits")); MyAppend(filtermenu, A_FILTER20, _(L"Show 20% of last edits")); MyAppend(filtermenu, A_FILTER50, _(L"Show 50% of last edits")); MyAppend(filtermenu, A_FILTERM, _(L"Show 1% more than the last filter")); MyAppend(filtermenu, A_FILTERL, _(L"Show 1% less than the last filter")); wxMenu *viewmenu = new wxMenu(); MyAppend(viewmenu, A_ZOOMIN, _(L"Zoom &In (CTRL+mousewheel)\tCTRL+PGUP")); MyAppend(viewmenu, A_ZOOMOUT, _(L"Zoom &Out (CTRL+mousewheel)\tCTRL+PGDN")); MyAppend(viewmenu, A_NEXTFILE, #ifndef __WXGTK__ _(L"Switch to &next file/tab\tCTRL+TAB")); #else // On Linux, this conflicts with CTRL+I, see Document::Key() // CTRL+SHIFT+TAB below still works, so that will have to be used to switch tabs. _(L"Switch to &next file/tab")); #endif MyAppend(viewmenu, A_PREVFILE, _(L"Switch to &previous file/tab\tSHIFT+CTRL+TAB")); MyAppend(viewmenu, A_FULLSCREEN, #ifdef __WXMAC__ _(L"Toggle &Fullscreen View\tCTRL+F11")); #else _(L"Toggle &Fullscreen View\tF11")); #endif MyAppend(viewmenu, A_SCALED, #ifdef __WXMAC__ _(L"Toggle &Scaled Presentation View\tCTRL+F12")); #else _(L"Toggle &Scaled Presentation View\tF12")); #endif viewmenu->AppendSubMenu(scrollmenu, _(L"Scroll Sheet...")); viewmenu->AppendSubMenu(filtermenu, _(L"Filter...")); wxMenu *roundmenu = new wxMenu(); roundmenu->AppendRadioItem(A_ROUND0, _(L"Radius &0")); roundmenu->AppendRadioItem(A_ROUND1, _(L"Radius &1")); roundmenu->AppendRadioItem(A_ROUND2, _(L"Radius &2")); roundmenu->AppendRadioItem(A_ROUND3, _(L"Radius &3")); roundmenu->AppendRadioItem(A_ROUND4, _(L"Radius &4")); roundmenu->AppendRadioItem(A_ROUND5, _(L"Radius &5")); roundmenu->AppendRadioItem(A_ROUND6, _(L"Radius &6")); roundmenu->Check(sys->roundness + A_ROUND0, true); wxMenu *optmenu = new wxMenu(); MyAppend(optmenu, A_DEFFONT, _(L"Pick Default Font...")); MyAppend(optmenu, A_CUSTKEY, _(L"Change a key binding...")); MyAppend(optmenu, A_CUSTCOL, _(L"Pick Custom &Color...")); MyAppend(optmenu, A_COLCELL, _(L"&Set Custom Color From Cell BG")); MyAppend(optmenu, A_DEFBGCOL, _(L"Pick Document Background...")); optmenu->AppendSeparator(); optmenu->AppendCheckItem(A_SHOWSBAR, _(L"Show Statusbar")); optmenu->Check(A_SHOWSBAR, showsbar); optmenu->AppendCheckItem(A_SHOWTBAR, _(L"Show Toolbar")); optmenu->Check(A_SHOWTBAR, showtbar); optmenu->AppendCheckItem(A_LEFTTABS, _(L"File Tabs on the bottom")); optmenu->Check(A_LEFTTABS, lefttabs); optmenu->AppendCheckItem(A_TOTRAY, _(L"Minimize to tray")); optmenu->Check(A_TOTRAY, sys->totray); optmenu->AppendCheckItem(A_MINCLOSE, _(L"Minimize on close")); optmenu->Check(A_MINCLOSE, sys->minclose); optmenu->AppendCheckItem(A_SINGLETRAY, _(L"Single click maximize from tray")); optmenu->Check(A_SINGLETRAY, sys->singletray); optmenu->AppendSeparator(); optmenu->AppendCheckItem(A_ZOOMSCR, _(L"Swap mousewheel scrolling and zooming")); optmenu->Check(A_ZOOMSCR, sys->zoomscroll); optmenu->AppendCheckItem(A_THINSELC, _(L"Navigate in between cells with cursor keys")); optmenu->Check(A_THINSELC, sys->thinselc); optmenu->AppendSeparator(); optmenu->AppendCheckItem(A_MAKEBAKS, _(L"Create .bak files")); optmenu->Check(A_MAKEBAKS, sys->makebaks); optmenu->AppendCheckItem(A_AUTOSAVE, _(L"Autosave to .tmp")); optmenu->Check(A_AUTOSAVE, sys->autosave); optmenu->AppendCheckItem( A_FSWATCH, _(L"Auto reload documents"), _(L"Reloads when another computer has changed a file (if you have made changes, asks)")); optmenu->Check(A_FSWATCH, sys->fswatch); optmenu->AppendCheckItem(A_AUTOEXPORT, _(L"Automatically export a .html on every save")); optmenu->Check(A_AUTOEXPORT, sys->autohtmlexport); optmenu->AppendSeparator(); optmenu->AppendCheckItem(A_CENTERED, _(L"Render document centered")); optmenu->Check(A_CENTERED, sys->centered); optmenu->AppendCheckItem(A_FASTRENDER, _(L"Faster line rendering")); optmenu->Check(A_FASTRENDER, sys->fastrender); optmenu->AppendCheckItem(A_ICONSET, _(L"Black and white toolbar icons")); optmenu->Check(A_ICONSET, iconset); optmenu->AppendSubMenu(roundmenu, _(L"&Roundness of grid borders...")); wxMenu *scriptmenu = new wxMenu(); auto scriptpath = GetPath("scripts/"); wxString sf = wxFindFirstFile(scriptpath + L"*.lobster"); int sidx = 0; while (!sf.empty()) { auto fn = wxFileName::FileName(sf).GetFullName(); auto ms = fn.BeforeFirst('.'); if (sidx < 26) { ms += L"\tCTRL+SHIFT+ALT+"; ms += wxChar('A' + sidx); } MyAppend(scriptmenu, A_SCRIPT + sidx, ms); auto ss = fn.utf8_str(); scripts_in_menu.push_back(std::string(ss.data(), ss.length())); sf = wxFindNextFile(); sidx++; } wxMenu *markmenu = new wxMenu(); MyAppend(markmenu, A_MARKDATA, _(L"&Data")); MyAppend(markmenu, A_MARKCODE, _(L"&Operation")); MyAppend(markmenu, A_MARKVARD, _(L"Variable &Assign")); MyAppend(markmenu, A_MARKVARU, _(L"Variable &Read")); MyAppend(markmenu, A_MARKVIEWH, _(L"&Horizontal View")); MyAppend(markmenu, A_MARKVIEWV, _(L"&Vertical View")); wxMenu *langmenu = new wxMenu(); MyAppend(langmenu, A_RUN, _(L"&Run")); langmenu->AppendSubMenu(markmenu, _(L"&Mark as")); MyAppend(langmenu, A_CLRVIEW, _(L"&Clear Views")); wxMenu *helpmenu = new wxMenu(); MyAppend(helpmenu, A_ABOUT, _(L"&About...")); MyAppend(helpmenu, A_HELPI, _(L"Load interactive &tutorial...\tF1")); MyAppend(helpmenu, A_HELP, _(L"View tutorial &web page...")); wxAcceleratorEntry entries[3]; entries[0].Set(wxACCEL_SHIFT, WXK_DELETE, A_CUT); entries[1].Set(wxACCEL_SHIFT, WXK_INSERT, A_PASTE); entries[2].Set(wxACCEL_CTRL, WXK_INSERT, A_COPY); wxAcceleratorTable accel(3, entries); SetAcceleratorTable(accel); if (!mergetbar) { wxMenuBar *menubar = new wxMenuBar(); menubar->Append(filemenu, _(L"&File")); menubar->Append(editmenu, _(L"&Edit")); menubar->Append(semenu, _(L"&Search")); menubar->Append(viewmenu, _(L"&View")); menubar->Append(optmenu, _(L"&Options")); menubar->Append(scriptmenu, _(L"Script")); menubar->Append(langmenu, _(L"&Program")); menubar->Append(helpmenu, #ifdef __WXMAC__ wxApp::s_macHelpMenuTitleName // so merges with osx provided help #else _(L"&Help") #endif ); #ifdef __WXMAC__ // these don't seem to work anymore in the newer wxWidgets, handled in the menu event // handler below instead wxApp::s_macAboutMenuItemId = A_ABOUT; wxApp::s_macExitMenuItemId = A_EXIT; wxApp::s_macPreferencesMenuItemId = A_DEFFONT; // we have no prefs, so for now just select the font #endif SetMenuBar(menubar); } wxColour toolbgcol(iconset ? 0xF0ECE8 : 0xD8C7BC); if (showtbar || mergetbar) { tb = CreateToolBar(wxBORDER_NONE | wxTB_HORIZONTAL | wxTB_FLAT | wxTB_NODIVIDER); tb->SetOwnBackgroundColour(toolbgcol); #ifdef __WXMAC__ #define SEPARATOR #else #define SEPARATOR tb->AddSeparator() #endif wxString iconpath = GetPath(iconset ? L"images/webalys/toolbar/" : L"images/nuvola/toolbar/"); auto sz = (iconset ? wxSize(18, 18) : wxSize(22, 22)) * csf; tb->SetToolBitmapSize(sz); double sc = iconset ? 1.0 : 22.0 / 48.0; auto AddTBIcon = [&](const wxChar *name, int action, wxString file) { wxBitmap bm; if (bm.LoadFile(file, wxBITMAP_TYPE_PNG)) { auto ns = csf_orig * sc; ScaleBitmap(bm, ns, bm); MakeInternallyScaled(bm, tb->GetBackgroundColour(), csf_orig); tb->AddTool(action, name, bm, bm, wxITEM_NORMAL, name); } }; AddTBIcon(_(L"New (CTRL+n)"), A_NEW, iconpath + L"filenew.png"); AddTBIcon(_(L"Open (CTRL+o)"), A_OPEN, iconpath + L"fileopen.png"); AddTBIcon(_(L"Save (CTRL+s)"), A_SAVE, iconpath + L"filesave.png"); AddTBIcon(_(L"Save As"), A_SAVEAS, iconpath + L"filesaveas.png"); SEPARATOR; AddTBIcon(_(L"Undo (CTRL+z)"), A_UNDO, iconpath + L"undo.png"); AddTBIcon(_(L"Copy (CTRL+c)"), A_COPY, iconpath + L"editcopy.png"); AddTBIcon(_(L"Paste (CTRL+v)"), A_PASTE, iconpath + L"editpaste.png"); SEPARATOR; AddTBIcon(_(L"Zoom In (CTRL+mousewheel)"), A_ZOOMIN, iconpath + L"zoomin.png"); AddTBIcon(_(L"Zoom Out (CTRL+mousewheel)"), A_ZOOMOUT, iconpath + L"zoomout.png"); SEPARATOR; AddTBIcon(_(L"New Grid (INS)"), A_NEWGRID, iconpath + L"newgrid.png"); AddTBIcon(_(L"Add Image"), A_IMAGE, iconpath + L"image.png"); SEPARATOR; AddTBIcon(_(L"Run"), A_RUN, iconpath + L"run.png"); tb->AddSeparator(); tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Search "))); tb->AddControl(filter = new wxTextCtrl(tb, A_SEARCH, "", wxDefaultPosition, wxSize(80, 22) * csf)); SEPARATOR; tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Replace "))); tb->AddControl(replaces = new wxTextCtrl(tb, A_REPLACE, "", wxDefaultPosition, wxSize(60, 22) * csf)); tb->AddSeparator(); tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Cell "))); tb->AddControl(new ColorDropdown(tb, A_CELLCOLOR, csf, 1)); SEPARATOR; tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Text "))); tb->AddControl(new ColorDropdown(tb, A_TEXTCOLOR, csf, 2)); SEPARATOR; tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Border "))); tb->AddControl(new ColorDropdown(tb, A_BORDCOLOR, csf, 7)); tb->AddSeparator(); tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Image "))); wxString imagepath = GetPath("images/nuvola/dropdown/"); idd = new ImageDropdown(tb, imagepath); tb->AddControl(idd); tb->Realize(); } if (showsbar) { wxStatusBar *sb = CreateStatusBar(4); sb->SetOwnBackgroundColour(toolbgcol); SetStatusBarPane(0); int swidths[] = {-1, 200, 120, 100}; SetStatusWidths(4, swidths); } nb = new wxAuiNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxAUI_NB_TAB_MOVE | wxAUI_NB_SCROLL_BUTTONS | wxAUI_NB_WINDOWLIST_BUTTON | wxAUI_NB_CLOSE_ON_ALL_TABS | (lefttabs ? wxAUI_NB_BOTTOM : wxAUI_NB_TOP)); nb->SetOwnBackgroundColour(toolbgcol); int display_id = wxDisplay::GetFromWindow(this); wxRect disprect = wxDisplay(display_id == wxNOT_FOUND ? 0 : display_id).GetClientArea(); const int screenx = disprect.width - disprect.x; const int screeny = disprect.height - disprect.y; const int boundary = 64; const int defx = screenx - 2 * boundary; const int defy = screeny - 2 * boundary; int resx, resy, posx, posy; sys->cfg->Read(L"resx", &resx, defx); sys->cfg->Read(L"resy", &resy, defy); sys->cfg->Read(L"posx", &posx, boundary + disprect.x); sys->cfg->Read(L"posy", &posy, boundary + disprect.y); if (resx > screenx || resy > screeny || posx < disprect.x || posy < disprect.y || posx + resx > disprect.width + disprect.x || posy + resy > disprect.height + disprect.y) { // Screen res has been resized since we last ran, set sizes to default to avoid being // off-screen. resx = defx; resy = defy; posx = posy = boundary; posx += disprect.x; posy += disprect.y; } SetSize(resx, resy); SetPosition(wxPoint(posx, posy)); bool ismax; sys->cfg->Read(L"maximized", &ismax, true); aui->AddPane(nb, wxCENTER); aui->Update(); Show(TRUE); // needs to be after Show() to avoid scrollbars rendered in the wrong place? if (ismax) Maximize(true); SetFileAssoc(exename); wxSafeYield(); } void AppOnEventLoopEnter() { // Have to do this here, if we do it in the Frame constructor above, it crashes on OS X. watcher = new wxFileSystemWatcher(); watcher->SetOwner(this); Connect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(MyFrame::OnFileSystemEvent)); } ~MyFrame() { filehistory.Save(*sys->cfg); if (!IsIconized()) { sys->cfg->Write(L"maximized", IsMaximized()); if (!IsMaximized()) { sys->cfg->Write(L"resx", GetSize().x); sys->cfg->Write(L"resy", GetSize().y); sys->cfg->Write(L"posx", GetPosition().x); sys->cfg->Write(L"posy", GetPosition().y); } } aui->ClearEventHashTable(); aui->UnInit(); DELETEP(aui); DELETEP(editmenupopup); DELETEP(watcher); } TSCanvas *NewTab(Document *doc, bool append = false) { TSCanvas *sw = new TSCanvas(this, nb); sw->doc = doc; doc->sw = sw; sw->SetScrollRate(1, 1); if (append) nb->AddPage(sw, L"", true, wxNullBitmap); else nb->InsertPage(0, sw, L"", true, wxNullBitmap); sw->SetDropTarget(new DropTarget(doc->dataobjc)); sw->SetFocus(); return sw; } TSCanvas *GetCurTab() { return nb && nb->GetSelection() >= 0 ? (TSCanvas *)nb->GetPage(nb->GetSelection()) : nullptr; } TSCanvas *GetTabByFileName(const wxString &fn) { if (nb) loop(i, nb->GetPageCount()) { TSCanvas *p = (TSCanvas *)nb->GetPage(i); if (p->doc->filename == fn) { nb->SetSelection(i); return p; } } return nullptr; } void OnTabChange(wxAuiNotebookEvent &nbe) { TSCanvas *sw = (TSCanvas *)nb->GetPage(nbe.GetSelection()); sw->Status(); sys->TabChange(sw->doc); } void TabsReset() { if (nb) loop(i, nb->GetPageCount()) { TSCanvas *p = (TSCanvas *)nb->GetPage(i); p->doc->rootgrid->ResetChildren(); } } void OnTabClose(wxAuiNotebookEvent &nbe) { TSCanvas *sw = (TSCanvas *)nb->GetPage(nbe.GetSelection()); sys->RememberOpenFiles(); if (nb->GetPageCount() <= 1) { nbe.Veto(); Close(); } else if (sw->doc->CloseDocument()) { nbe.Veto(); } } void CycleTabs(int offset = 1) { auto numtabs = (int)nb->GetPageCount(); offset = ((offset >= 0) ? 1 : numtabs - 1); // normalize to non-negative wrt modulo nb->SetSelection((nb->GetSelection() + offset) % numtabs); } void SetPageTitle(const wxString &fn, wxString mods, int page = -1) { if (page < 0) page = nb->GetSelection(); if (page < 0) return; if (page == nb->GetSelection()) SetTitle(L"TreeSheets - " + fn + mods); nb->SetPageText(page, (fn.empty() ? L"" : wxFileName(fn).GetName()) + mods); } void TBMenu(wxToolBar *tb, wxMenu *menu, const wxChar *name, int id = 0) { tb->AddTool(id, name, wxNullBitmap, wxEmptyString, wxITEM_DROPDOWN); tb->SetDropdownMenu(id, menu); } void OnMenu(wxCommandEvent &ce) { wxTextCtrl *tc; if (((tc = filter) && filter == wxWindow::FindFocus()) || ((tc = replaces) && replaces == wxWindow::FindFocus())) { // FIXME: have to emulate this behavior because menu always captures these events (??) long from, to; tc->GetSelection(&from, &to); switch (ce.GetId()) { case A_MLEFT: case A_LEFT: if (from != to) tc->SetInsertionPoint(from); else if (from) tc->SetInsertionPoint(from - 1); return; case A_MRIGHT: case A_RIGHT: if (from != to) tc->SetInsertionPoint(to); else if (to < tc->GetLineLength(0)) tc->SetInsertionPoint(to + 1); return; case A_SHOME: tc->SetSelection(0, to); return; case A_SEND: tc->SetSelection(from, 1000); return; case A_SCLEFT: case A_SLEFT: if (from) tc->SetSelection(from - 1, to); return; case A_SCRIGHT: case A_SRIGHT: if (to < tc->GetLineLength(0)) tc->SetSelection(from, to + 1); return; case A_BACKSPACE: tc->Remove(from - (from == to), to); return; case A_DELETE: tc->Remove(from, to + (from == to)); return; case A_HOME: tc->SetSelection(0, 0); return; case A_END: tc->SetSelection(1000, 1000); return; case A_SELALL: tc->SetSelection(0, 1000); return; } } TSCanvas *sw = GetCurTab(); wxClientDC dc(sw); sw->DoPrepareDC(dc); sw->doc->ShiftToCenter(dc); auto Check = [&](const wxChar *cfg) { sys->cfg->Write(cfg, ce.IsChecked()); sw->Status(_(L"change will take effect next run of TreeSheets")); }; switch (ce.GetId()) { case A_NOP: break; case A_ALEFT: sw->CursorScroll(-g_scrollratecursor, 0); break; case A_ARIGHT: sw->CursorScroll(g_scrollratecursor, 0); break; case A_AUP: sw->CursorScroll(0, -g_scrollratecursor); break; case A_ADOWN: sw->CursorScroll(0, g_scrollratecursor); break; case A_ICONSET: Check(L"iconset"); break; case A_SHOWSBAR: Check(L"showsbar"); break; case A_SHOWTBAR: Check(L"showtbar"); break; case A_LEFTTABS: Check(L"lefttabs"); break; case A_SINGLETRAY: Check(L"singletray"); break; case A_MAKEBAKS: sys->cfg->Write(L"makebaks", sys->makebaks = ce.IsChecked()); break; case A_TOTRAY: sys->cfg->Write(L"totray", sys->totray = ce.IsChecked()); break; case A_MINCLOSE: sys->cfg->Write(L"minclose", sys->minclose = ce.IsChecked()); break; case A_ZOOMSCR: sys->cfg->Write(L"zoomscroll", sys->zoomscroll = ce.IsChecked()); break; case A_THINSELC: sys->cfg->Write(L"thinselc", sys->thinselc = ce.IsChecked()); break; case A_AUTOSAVE: sys->cfg->Write(L"autosave", sys->autosave = ce.IsChecked()); break; case A_CENTERED: sys->cfg->Write(L"centered", sys->centered = ce.IsChecked()); Refresh(); break; case A_FSWATCH: Check(L"fswatch"); sys->fswatch = ce.IsChecked(); break; case A_AUTOEXPORT: sys->cfg->Write(L"autohtmlexport", sys->autohtmlexport = ce.IsChecked()); break; case A_FASTRENDER: sys->cfg->Write(L"fastrender", sys->fastrender = ce.IsChecked()); Refresh(); break; case A_FULLSCREEN: ShowFullScreen(!IsFullScreen()); if (IsFullScreen()) sw->Status(_(L"Press F11 to exit fullscreen mode.")); break; case A_SEARCHF: if (filter) { filter->SetFocus(); filter->SetSelection(0, 1000); } else { sw->Status(_(L"Please enable (Options -> Show Toolbar) to use search.")); } break; #ifdef __WXMAC__ case wxID_OSX_HIDE: Iconize(true); break; case wxID_OSX_HIDEOTHERS: sw->Status(L"NOT IMPLEMENTED"); break; case wxID_OSX_SHOWALL: Iconize(false); break; case wxID_ABOUT: sw->doc->Action(dc, A_ABOUT); break; case wxID_PREFERENCES: sw->doc->Action(dc, A_DEFFONT); break; case wxID_EXIT: // FALL THRU: #endif case A_EXIT: fromclosebox = false; this->Close(); break; case A_CLOSE: sw->doc->Action(dc, ce.GetId()); break; // sw dangling pointer on return default: if (ce.GetId() >= wxID_FILE1 && ce.GetId() <= wxID_FILE9) { wxString f(filehistory.GetHistoryFile(ce.GetId() - wxID_FILE1)); sw->Status(sys->Open(f)); } else if (ce.GetId() >= A_TAGSET && ce.GetId() < A_SCRIPT) { sw->Status(sw->doc->TagSet(ce.GetId() - A_TAGSET)); } else if (ce.GetId() >= A_SCRIPT && ce.GetId() < A_MAXACTION) { auto msg = tssi.ScriptRun(scripts_in_menu[ce.GetId() - A_SCRIPT].c_str()); sw->Status(wxString(msg)); } else { sw->Status(sw->doc->Action(dc, ce.GetId())); break; } } } void OnSearch(wxCommandEvent &ce) { sys->searchstring = ce.GetString().Lower(); Document *doc = GetCurTab()->doc; doc->selected.g = nullptr; if (doc->searchfilter) doc->SetSearchFilter(sys->searchstring.Len() != 0); else doc->Refresh(); GetCurTab()->Status(); } void ReFocus() { if (GetCurTab()) GetCurTab()->SetFocus(); } void OnCellColor(wxCommandEvent &ce) { GetCurTab()->doc->ColorChange(A_CELLCOLOR, ce.GetInt()); ReFocus(); } void OnTextColor(wxCommandEvent &ce) { GetCurTab()->doc->ColorChange(A_TEXTCOLOR, ce.GetInt()); ReFocus(); } void OnBordColor(wxCommandEvent &ce) { GetCurTab()->doc->ColorChange(A_BORDCOLOR, ce.GetInt()); ReFocus(); } void OnDDImage(wxCommandEvent &ce) { GetCurTab()->doc->ImageChange(idd->as[ce.GetInt()], dd_icon_res_scale); ReFocus(); } void OnSizing(wxSizeEvent &se) { se.Skip(); } void OnMaximize(wxMaximizeEvent &me) { ReFocus(); me.Skip(); } void OnActivate(wxActivateEvent &ae) { // This causes warnings in the debug log, but without it keyboard entry upon window select // doesn't work. ReFocus(); } void OnIconize(wxIconizeEvent &me) { if (me.IsIconized()) { #ifdef WIN32 if (sys->totray) { tbi.SetIcon(icon, L"TreeSheets"); Show(false); Iconize(); } #endif } else { if (GetCurTab()) GetCurTab()->SetFocus(); } } void DeIconize() { if (!IsIconized()) { RequestUserAttention(); return; } Show(true); Iconize(false); tbi.RemoveIcon(); } void OnTBIDBLClick(wxTaskBarIconEvent &e) { DeIconize(); } void OnClosing(wxCloseEvent &ce) { bool fcb = fromclosebox; fromclosebox = true; if (fcb && sys->minclose) { ce.Veto(); Iconize(); return; } sys->RememberOpenFiles(); if (ce.CanVeto()) while (nb->GetPageCount()) { if (GetCurTab()->doc->CloseDocument()) { ce.Veto(); sys->RememberOpenFiles(); // may have closed some, but not all return; } else { nb->DeletePage(nb->GetSelection()); } } bt.Stop(); sys->savechecker.Stop(); Destroy(); } #ifdef WIN32 void SetRegKey(wxChar *key, wxString val) { wxRegKey rk(key); rk.Create(); rk.SetValue(L"", val); } #endif void SetFileAssoc(wxString &exename) { #ifdef WIN32 SetRegKey(L"HKEY_CLASSES_ROOT\\.cts", L"TreeSheets"); SetRegKey(L"HKEY_CLASSES_ROOT\\TreeSheets", L"TreeSheets file"); SetRegKey(L"HKEY_CLASSES_ROOT\\TreeSheets\\Shell\\Open\\Command", wxString(L"\"") + exename + L"\" \"%1\""); SetRegKey(L"HKEY_CLASSES_ROOT\\TreeSheets\\DefaultIcon", wxString(L"\"") + exename + L"\",0"); #else // TODO: do something similar for mac/kde/gnome? #endif } void OnFileSystemEvent(wxFileSystemWatcherEvent &event) { // 0xF == create/delete/rename/modify if ((event.GetChangeType() & 0xF) == 0 || watcherwaitingforuser || !nb) return; const wxString &modfile = event.GetPath().GetFullPath(); loop(i, nb->GetPageCount()) { Document *doc = ((TSCanvas *)nb->GetPage(i))->doc; if (modfile == doc->filename) { wxDateTime modtime = wxFileName(modfile).GetModificationTime(); // Compare with last modified to trigger multiple times. if (!modtime.IsValid() || !doc->lastmodificationtime.IsValid() || modtime == doc->lastmodificationtime) { return; } if (doc->modified) { // TODO: this dialog is problematic since it may be on an unattended // computer and more of these events may fire. since the occurrence of this // situation is rare, it may be better to just take the most // recently changed version (which is the one that has just been modified // on disk) this potentially throws away local changes, but this can only // happen if the user left changes unsaved, then decided to go edit an older // version on another computer. // for now, we leave this code active, and guard it with // watcherwaitingforuser wxString msg = wxString::Format( _(L"%s\nhas been modified on disk by another program / computer:\nWould " L"you like to discard " L"your changes and re-load from disk?"), doc->filename); watcherwaitingforuser = true; int res = wxMessageBox(msg, _(L"File modification conflict!"), wxYES_NO | wxICON_QUESTION, this); watcherwaitingforuser = false; if (res != wxYES) return; } auto msg = sys->LoadDB(doc->filename, false, true); assert(msg); if (*msg) { GetCurTab()->Status(msg); } else { loop(j, nb->GetPageCount()) if (((TSCanvas *)nb->GetPage(j))->doc == doc) nb->DeletePage(j); ::wxRemoveFile(sys->TmpName(modfile)); GetCurTab()->Status( _(L"File has been re-loaded because of modifications of another program / " L"computer")); } return; } } } DECLARE_EVENT_TABLE() }; treesheets-1.0.2/src/mywxtools.h000077500000000000000000000160101352107072600167160ustar00rootroot00000000000000 static void DrawRectangle(wxDC &dc, uint c, int x, int y, int xs, int ys, bool outline = false) { if (outline) dc.SetBrush(*wxTRANSPARENT_BRUSH); else dc.SetBrush(wxBrush(wxColour(c))); dc.SetPen(wxPen(wxColour(c))); dc.DrawRectangle(x, y, xs, ys); } static void MyDrawText(wxDC &dc, const wxString &s, wxCoord x, wxCoord y, wxCoord w, wxCoord h) { #ifdef __WXMSW__ // this special purpose implementation is because the MSW implementation calls // TextExtent, which costs // 25% of all cpu time dc.CalcBoundingBox(x, y); dc.CalcBoundingBox(x + w, y + h); HDC hdc = (HDC)dc.GetHDC(); ::SetTextColor(hdc, dc.GetTextForeground().GetPixel()); ::SetBkColor(hdc, dc.GetTextBackground().GetPixel()); ::ExtTextOut(hdc, x, y, 0, nullptr, s.c_str(), s.length(), nullptr); #else dc.DrawText(s, x, y); #endif } struct DropTarget : wxDropTarget { DropTarget(wxDataObject *data) : wxDropTarget(data){}; wxDragResult OnDragOver(wxCoord x, wxCoord y, wxDragResult def) { TSCanvas *sw = sys->frame->GetCurTab(); wxClientDC dc(sw); sw->UpdateHover(x, y, dc); return sw->doc->hover.g ? wxDragCopy : wxDragNone; } bool OnDrop(wxCoord x, wxCoord y) { return sys->frame->GetCurTab()->doc->hover.g != nullptr; } wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def) { GetData(); TSCanvas *sw = sys->frame->GetCurTab(); sw->SelectClick(x, y, false, 0); sw->doc->PasteOrDrop(); return wxDragCopy; } }; struct BlinkTimer : wxTimer { void Notify() { TSCanvas *tsc = sys->frame->GetCurTab(); if (tsc) tsc->doc->Blink(); } }; struct ThreeChoiceDialog : public wxDialog { ThreeChoiceDialog(wxWindow *parent, const wxString &title, const wxString &msg, const wxString &ch1, const wxString &ch2, const wxString &ch3) : wxDialog(parent, wxID_ANY, title) { wxBoxSizer *bsv = new wxBoxSizer(wxVERTICAL); bsv->Add(new wxStaticText(this, -1, msg), 0, wxALL, 5); wxBoxSizer *bsb = new wxBoxSizer(wxHORIZONTAL); bsb->Prepend(new wxButton(this, 2, ch3), 0, wxALL, 5); bsb->PrependStretchSpacer(1); bsb->Prepend(new wxButton(this, 1, ch2), 0, wxALL, 5); bsb->PrependStretchSpacer(1); bsb->Prepend(new wxButton(this, 0, ch1), 0, wxALL, 5); bsv->Add(bsb, 1, wxEXPAND); SetSizer(bsv); bsv->SetSizeHints(this); } void OnButton(wxCommandEvent &ce) { EndModal(ce.GetId()); } int Run() { return ShowModal(); } DECLARE_EVENT_TABLE() }; struct ColorPopup : wxVListBoxComboPopup { ColorPopup(wxWindow *parent) { } void OnComboDoubleClick() { sys->frame->GetCurTab()->doc->ColorChange(m_combo->GetId(), GetSelection()); } }; struct ColorDropdown : wxOwnerDrawnComboBox { double csf; ColorDropdown(wxWindow *parent, wxWindowID id, double _csf, int sel) { csf = _csf; wxArrayString as; as.Add(L"", sizeof(celltextcolors) / sizeof(uint)); Create(parent, id, L"", wxDefaultPosition, wxSize(44, 22) * csf, as, wxCB_READONLY | wxCC_SPECIAL_DCLICK); SetPopupControl(new ColorPopup(this)); SetSelection(sel); SetPopupMaxHeight(wxDisplay().GetGeometry().GetHeight() * 3 / 4); } wxCoord OnMeasureItem(size_t item) const { return 22 * csf; } wxCoord OnMeasureItemWidth(size_t item) const { return 40 * csf; } void OnDrawBackground(wxDC &dc, const wxRect &rect, int item, int flags) const { DrawRectangle(dc, 0xFFFFFF, rect.x, rect.y, rect.width, rect.height); } void OnDrawItem(wxDC &dc, const wxRect &rect, int item, int flags) const { DrawRectangle(dc, item == CUSTOMCOLORIDX ? sys->customcolor : celltextcolors[item], rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2); if (item == CUSTOMCOLORIDX) { dc.SetTextForeground(*wxBLACK); dc.SetFont(wxFont(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, L"")); dc.DrawText(L"Custom", rect.x + 1, rect.y + 1); } } }; #define dd_icon_res_scale 3.0 struct ImagePopup : wxVListBoxComboPopup { void OnComboDoubleClick() { wxString s = GetString(GetSelection()); sys->frame->GetCurTab()->doc->ImageChange(s, dd_icon_res_scale); } }; struct ImageDropdown : wxOwnerDrawnComboBox { // FIXME: delete these somewhere Vector bitmaps_display; wxArrayString as; double csf, csf_orig; const int image_space = 22; ImageDropdown(wxWindow *parent, wxString &path) { csf = sys->frame->csf; csf_orig = sys->frame->csf; wxString f = wxFindFirstFile(path + L"*.*"); while (!f.empty()) { wxBitmap bm; if (bm.LoadFile(f, wxBITMAP_TYPE_PNG)) { auto dbm = new wxBitmap(); ScaleBitmap(bm, csf_orig / dd_icon_res_scale, *dbm); MakeInternallyScaled(*dbm, *wxWHITE, csf_orig); bitmaps_display.push() = dbm; as.Add(f); } f = wxFindNextFile(); } Create(parent, A_DDIMAGE, L"", wxDefaultPosition, wxSize(image_space * 2, image_space) * csf, as, wxCB_READONLY | wxCC_SPECIAL_DCLICK); SetPopupControl(new ImagePopup()); SetSelection(0); SetPopupMaxHeight(wxDisplay().GetGeometry().GetHeight() * 3 / 4); } wxCoord OnMeasureItem(size_t item) const { return image_space * csf; } wxCoord OnMeasureItemWidth(size_t item) const { return image_space * csf; } void OnDrawBackground(wxDC &dc, const wxRect &rect, int item, int flags) const { DrawRectangle(dc, 0xFFFFFF, rect.x, rect.y, rect.width, rect.height); } void OnDrawItem(wxDC &dc, const wxRect &rect, int item, int flags) const { auto bm = bitmaps_display[item]; sys->ImageDraw(bm, dc, rect.x + 3 * csf, rect.y + 3 * csf); } }; static void ScaleBitmap(const wxBitmap &src, double sc, wxBitmap &dest) { dest = wxBitmap(src.ConvertToImage().Scale(src.GetWidth() * sc, src.GetHeight() * sc, wxIMAGE_QUALITY_HIGH)); } static void MakeInternallyScaled(wxBitmap &bm, const wxColour bg, double sc) { #ifdef __WXMAC__ // What a mess.. is there a simpler way? wxBitmap sbm; sbm.CreateScaled(bm.GetWidth() / sc, bm.GetHeight() / sc, bm.GetDepth(), sc); wxMemoryDC sdc(sbm); //wxMemoryDC dc(bm); // FIXME: this should really be transparent. sdc.SetBackground(wxBrush(bg)); sdc.Clear(); //sdc.Blit(0, 0, bm.GetWidth(), bm.GetHeight(), &dc, 0, 0, wxCOPY); sdc.SetUserScale(1.0 / sc, 1.0 / sc); sdc.DrawBitmap(bm, wxPoint(0, 0)); bm = sbm; #endif } treesheets-1.0.2/src/script_interface.h000066400000000000000000000030151352107072600201530ustar00rootroot00000000000000 namespace script { typedef std::pair icoord; typedef std::pair ibox; struct ScriptInterface { virtual void GoToRoot() = 0; virtual void GoToView() = 0; virtual bool HasSelection() = 0; virtual void GoToSelection() = 0; virtual bool HasParent() = 0; virtual void GoToParent() = 0; virtual int NumChildren() = 0; virtual icoord NumColumnsRows() = 0; virtual ibox SelectionBox() = 0; virtual void GoToChild(int n) = 0; virtual void GoToColumnRow(int x, int y) = 0; virtual std::string GetText() = 0; virtual void SetText(std::string_view t) = 0; virtual void CreateGrid(int x, int n) = 0; virtual void InsertColumns(int x, int n) = 0; virtual void InsertRows(int y, int n) = 0; virtual void Delete(int x, int y, int xs, int ys) = 0; virtual void SetBackgroundColor(uint col) = 0; virtual void SetTextColor(uint col) = 0; virtual void SetRelativeSize(int s) = 0; virtual void SetStyle(int s) = 0; virtual void SetStatusMessage(std::string_view msg) = 0; virtual std::string GetFileNameFromUser(bool is_save) = 0; }; typedef int64_t(*ScriptLoader)(std::string_view absfilename, std::string *dest, int64_t start, int64_t len); extern std::string InitLobster(ScriptInterface *_si, const char *exefilepath, const char *auxfilepath, bool from_bundle, ScriptLoader sl); extern std::string RunLobster(std::string_view filename, std::string_view code, bool dump_builtins); } treesheets-1.0.2/src/selection.h000077500000000000000000000324271352107072600166300ustar00rootroot00000000000000 class Selection { bool textedit; public: Grid *g; int x, y, xs, ys; int cursor, cursorend; int firstdx, firstdy; Selection() { memset(this, 0, sizeof(Selection)); } Selection(Grid *_g, int _x, int _y, int _xs, int _ys) : g(_g), x(_x), y(_y), xs(_xs), ys(_ys), cursor(0), cursorend(0), textedit(false), firstdx(0), firstdy(0) {} void SelAll() { x = y = 0; xs = g->xs; ys = g->ys; } Cell *GetCell() const { return g && xs == 1 && ys == 1 ? g->C(x, y) : nullptr; } Cell *GetFirst() const { return g && xs >= 1 && ys >= 1 ? g->C(x, y) : nullptr; } bool EqLoc(const Selection &s) { return g == s.g && x == s.x && y == s.y && xs == s.xs && ys == s.ys; } bool operator==(const Selection &s) { return EqLoc(s) && cursor == s.cursor && cursorend == s.cursorend; } bool Thin() const { return !(xs * ys); } bool IsAll() const { return xs == g->xs && ys == g->ys; } void SetCursorEdit(Document *doc, bool edit) { wxCursor c(edit ? wxCURSOR_IBEAM : wxCURSOR_ARROW); #ifdef WIN32 // this changes the cursor instantly, but gets overridden by the local window cursor ::SetCursor((HCURSOR)c.GetHCURSOR()); #endif // this doesn't change the cursor immediately, only on mousemove: doc->sw->SetCursor(c); firstdx = firstdy = 0; } bool TextEdit() { return textedit; } void EnterEditOnly(Document *doc) { textedit = true; SetCursorEdit(doc, true); } void EnterEdit(Document *doc, int c = 0, int ce = 0) { EnterEditOnly(doc); cursor = c; cursorend = ce; } void ExitEdit(Document *doc) { textedit = false; cursor = cursorend = 0; SetCursorEdit(doc, false); } bool IsInside(Selection &o) { if (!o.g || !g) return false; if (g != o.g) return g->cell->parent && g->cell->parent->grid->FindCell(g->cell).IsInside(o); return x >= o.x && y >= o.y && x + xs <= o.x + o.xs && y + ys <= o.y + o.ys; } void Merge(const Selection &a, const Selection &b) { textedit = false; if (a.g == b.g) { if (a.GetCell() == b.GetCell() && a.GetCell() && (a.textedit || b.textedit)) { if (a.cursor != a.cursorend) { Selection c = b; a.GetCell()->text.SelectWord(c); cursor = min(a.cursor, c.cursor); cursorend = max(a.cursorend, c.cursorend); } else { cursor = min(a.cursor, b.cursor); cursorend = max(a.cursor, b.cursor); } textedit = true; } else { cursor = cursorend = 0; } } else { Cell *at = a.GetCell(); Cell *bt = b.GetCell(); int ad = at->Depth(); int bd = bt->Depth(); int i = 0; while (i < ad && i < bd && at->Parent(ad - i) == bt->Parent(bd - i)) i++; Grid *g = at->Parent(ad - i + 1)->grid; Merge(g->FindCell(at->Parent(ad - i)), g->FindCell(bt->Parent(bd - i))); return; } g = a.g; x = min(a.x, b.x); y = min(a.y, b.y); xs = abs(a.x - b.x) + 1; ys = abs(a.y - b.y) + 1; } int MaxCursor() { return int(GetCell()->text.t.Len()); } void Dir(Document *doc, bool ctrl, bool shift, wxDC &dc, int dx, int dy, int &v, int &vs, int &ovs, bool notboundaryperp, bool notboundarypar, bool exitedit) { if (ctrl && !textedit) { g->cell->AddUndo(doc); g->Move(dx, dy, *this); x = (x + dx + g->xs) % g->xs; y = (y + dy + g->ys) % g->ys; if (x + xs > g->xs || y + ys > g->ys) g = nullptr; doc->ScrollIfSelectionOutOfView(dc, *this, true); } else { doc->DrawSelect(dc, *this); if (ctrl && dx) // implies textedit { if (cursor == cursorend) firstdx = dx; int &curs = firstdx < 0 ? cursor : cursorend; for (int c = curs, start = curs;;) { c += dx; if (c < 0 || c > MaxCursor()) break; wxChar ch = GetCell()->text.t[(c + curs) / 2]; if (!wxIsalnum(ch) && curs != start) break; curs = c; if (!wxIsalnum(ch) && !wxIsspace(ch)) break; } if (shift) { if (cursorend < cursor) swap_(cursorend, cursor); } else cursorend = cursor = curs; } else if (shift) { if (textedit) { if (cursor == cursorend) firstdx = dx; (firstdx < 0 ? cursor : cursorend) += dx; if (cursor < 0) cursor = 0; if (cursorend > MaxCursor()) cursorend = MaxCursor(); } else { if (!xs) firstdx = 0; // redundant: just in case someone else changed it if (!ys) firstdy = 0; if (!firstdx) firstdx = dx; if (!firstdy) firstdy = dy; if (firstdx < 0) { x += dx; xs += -dx; } else xs += dx; if (firstdy < 0) { y += dy; ys += -dy; } else ys += dy; if (x < 0) { x = 0; xs--; } if (y < 0) { y = 0; ys--; } if (x + xs > g->xs) xs--; if (y + ys > g->ys) ys--; if (!xs) firstdx = 0; if (!ys) firstdy = 0; if (!xs && !ys) g = nullptr; } } else { if (vs) { if (ovs) // (multi) cell selection { bool intracell = true; if (textedit && !exitedit && GetCell()) { if (dy) { cursorend = cursor; Text &text = GetCell()->text; int maxcolwidth = GetCell()->parent->grid->colwidths[x]; int i = 0; int laststart, lastlen; int nextoffset = -1; for (int l = 0;; l++) { int start = i; wxString ls = text.GetLine(i, maxcolwidth); int len = (int)ls.Len(); int end = start + len; if (len && nextoffset >= 0) { cursor = cursorend = start + (nextoffset > len ? len : nextoffset); intracell = false; break; } if (cursor >= start && cursor <= end) { if (dy < 0) { if (l != 0) { cursor = cursorend = laststart + (cursor - start > lastlen ? lastlen : cursor - start); intracell = false; } break; } else { nextoffset = cursor - start; } } laststart = start; lastlen = len; if (!len) break; } } else { intracell = false; if (cursor != cursorend) { if (dx < 0) cursorend = cursor; else cursor = cursorend; } else { if ((dx < 0 && cursor) || (dx > 0 && MaxCursor() > cursor)) cursorend = cursor += dx; } } } if (intracell) { if (sys->thinselc) { if (dx + dy > 0) v += vs; vs = 0; // make it a thin selection, in direction ovs = 1; } else { if (x + dx >= 0 && x + dx + xs <= g->xs && y + dy >= 0 && y + dy + ys <= g->ys) { x += dx; y += dy; } } ExitEdit(doc); } } else if (notboundarypar) // thin selection, moving in parallel direction { v += dx + dy; } } else if (notboundaryperp) // thin selection, moving in perpendicular direction { if (dx + dy < 0) v--; vs = 1; // make it a cell selection }; } doc->DrawSelectMove(dc, *this); }; } void Cursor(Document *doc, int k, bool ctrl, bool shift, wxDC &dc, bool exitedit = false) { switch (k) { case A_UP: Dir(doc, ctrl, shift, dc, 0, -1, y, ys, xs, y != 0, y != 0, exitedit); break; case A_DOWN: Dir(doc, ctrl, shift, dc, 0, 1, y, ys, xs, y < g->ys, y < g->ys - 1, exitedit); break; case A_LEFT: Dir(doc, ctrl, shift, dc, -1, 0, x, xs, ys, x != 0, x != 0, exitedit); break; case A_RIGHT: Dir(doc, ctrl, shift, dc, 1, 0, x, xs, ys, x < g->xs, x < g->xs - 1, exitedit); break; } } void Next(Document *doc, wxDC &dc, bool backwards) { doc->DrawSelect(dc, *this); ExitEdit(doc); if (backwards) { if (x > 0) x--; else if (y > 0) { y--; x = g->xs - 1; } else { x = g->xs - 1; y = g->ys - 1; } } else { if (x < g->xs - 1) x++; else if (y < g->ys - 1) { y++; x = 0; } else x = y = 0; } EnterEdit(doc, 0, MaxCursor()); doc->DrawSelectMove(dc, *this); } const wxChar *Wrap(Document *doc) { if (Thin()) return doc->NoThin(); g->cell->AddUndo(doc); Cell *np = g->CloneSel(*this); g->C(x, y)->text.t = "."; // avoid this cell getting deleted if (xs > 1) { Selection s(g, x + 1, y, xs - 1, ys); g->MultiCellDeleteSub(doc, s); } if (ys > 1) { Selection s(g, x, y + 1, 1, ys - 1); g->MultiCellDeleteSub(doc, s); } Cell *old = g->C(x, y); np->text.relsize = old->text.relsize; np->CloneStyleFrom(old); g->ReplaceCell(old, np); np->parent = g->cell; delete old; xs = ys = 1; EnterEdit(doc, MaxCursor(), MaxCursor()); doc->Refresh(); return nullptr; } Cell *ThinExpand(Document *doc) { if (Thin()) { if (xs) { g->cell->AddUndo(doc); g->InsertCells(-1, y, 0, 1); ys = 1; } else { g->cell->AddUndo(doc); g->InsertCells(x, -1, 1, 0); xs = 1; } } return GetCell(); } void HomeEnd(Document *doc, wxDC &dc, bool ishome) { doc->DrawSelect(dc, *this); xs = ys = 1; if (ishome) x = y = 0; else { x = g->xs - 1; y = g->ys - 1; } doc->DrawSelectMove(dc, *this); } }; treesheets-1.0.2/src/stdafx.cpp000077500000000000000000000000251352107072600164540ustar00rootroot00000000000000#include "stdafx.h" treesheets-1.0.2/src/stdafx.h000077500000000000000000000026201352107072600161240ustar00rootroot00000000000000 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include WX_DECLARE_STRING_HASH_MAP(bool, wxHashMapBool); #include #include #include #include #include #include #include #include #include "tools.h" #ifdef _WIN32 #include "..\treesheets\resource.h" #include "StackWalker\StackWalkerHelpers.h" #endif treesheets-1.0.2/src/system.h000077500000000000000000000506271352107072600161710ustar00rootroot00000000000000 struct Image { wxBitmap bm_orig; wxBitmap bm_display; int trefc; int savedindex; int checksum; // This indicates a relative scale, where 1.0 means bitmap pixels match display pixels on // a low res 96 dpi display. On a high dpi screen it will look scaled up. Higher values // look better on most screens. // This is all relative to GetContentScalingFactor. double display_scale; Image(wxBitmap _bm, int _cs, double _sc) : bm_orig(_bm), checksum(_cs), display_scale(_sc) {} void BitmapScale(double sc) { ScaleBitmap(bm_orig, sc, bm_orig); bm_display = wxNullBitmap; } void DisplayScale(double sc) { display_scale /= sc; bm_display = wxNullBitmap; } void ResetScale(double sc) { display_scale = sc; bm_display = wxNullBitmap; } wxBitmap &Display() { if (!bm_display.IsOk()) { ScaleBitmap(bm_orig, 1.0 / display_scale * sys->frame->csf, bm_display); // FIXME: this won't work because it will ignore the cell's bg color. //MakeInternallyScaled(bm_display, *wxWHITE, sys->frame->csf_orig); } return bm_display; } }; struct System { MyFrame *frame; wxString defaultfont, searchstring; wxConfigBase *cfg; Evaluator ev; wxString clipboardcopy; Cell *cellclipboard; Vector imagelist; Vector loadimageids; uchar versionlastloaded; wxLongLong fakelasteditonload; wxPen pen_tinytext, pen_gridborder, pen_tinygridlines, pen_gridlines, pen_thinselect; uint customcolor; int roundness; int defaultmaxcolwidth; bool makebaks; bool totray; bool autosave; bool zoomscroll; bool thinselc; bool minclose; bool singletray; bool centered; bool fswatch; bool autohtmlexport; int sortcolumn, sortxs, sortdescending; bool fastrender; wxHashMapBool watchedpaths; bool insidefiledialog; struct SaveChecker : wxTimer { void Notify() { sys->SaveCheck(); sys->cfg->Flush(); } } savechecker; System(bool portable) : cfg(portable ? (wxConfigBase *)new wxFileConfig( L"", wxT(""), wxGetCwd() + wxT("/TreeSheets.ini"), wxT(""), 0) : (wxConfigBase *)new wxConfig(L"TreeSheets")), cellclipboard(nullptr), defaultfont( #ifdef WIN32 L"Lucida Sans Unicode" #else L"Verdana" #endif ), pen_tinytext(wxColour(0x808080ul)), pen_gridborder(wxColour(0xb5a6a4)), pen_tinygridlines(wxColour(0xf2dcd8)), pen_gridlines(wxColour(0xe5b7b0)), pen_thinselect(*wxLIGHT_GREY), versionlastloaded(0), customcolor(0xFFFFFF), roundness(3), defaultmaxcolwidth(80), makebaks(true), totray(false), autosave(true), fastrender(true), zoomscroll(false), thinselc(true), minclose(false), singletray(false), centered(true), fswatch(true), autohtmlexport(false), insidefiledialog(false) { static const wxDash glpattern[] = {1, 3}; pen_gridlines.SetDashes(2, glpattern); pen_gridlines.SetStyle(wxPENSTYLE_USER_DASH); static const wxDash tspattern[] = {2, 4}; pen_thinselect.SetDashes(2, tspattern); pen_thinselect.SetStyle(wxPENSTYLE_USER_DASH); roundness = (int)cfg->Read(L"roundness", roundness); defaultfont = cfg->Read(L"defaultfont", defaultfont); cfg->Read(L"makebaks", &makebaks, makebaks); cfg->Read(L"totray", &totray, totray); cfg->Read(L"zoomscroll", &zoomscroll, zoomscroll); cfg->Read(L"thinselc", &thinselc, thinselc); cfg->Read(L"autosave", &autosave, autosave); cfg->Read(L"fastrender", &fastrender, fastrender); cfg->Read(L"minclose", &minclose, minclose); cfg->Read(L"singletray", &singletray, singletray); cfg->Read(L"centered", ¢ered, centered); cfg->Read(L"fswatch", &fswatch, fswatch); cfg->Read(L"autohtmlexport", &autohtmlexport, autohtmlexport); cfg->Read(L"defaultfontsize", &g_deftextsize, g_deftextsize); // fsw.Connect(wxID_ANY, wxID_ANY, wxEVT_FSWATCHER, // wxFileSystemWatcherEventHandler(System::OnFileChanged)); } ~System() { DELETEP(cfg); DELETEP(cellclipboard); } Document *NewTabDoc(bool append = false) { Document *doc = new Document(); frame->NewTab(doc, append); return doc; } void TabChange(Document *newdoc) { // hover = selected = begindrag = Selection(); newdoc->sw->SetFocus(); newdoc->UpdateFileName(); } void Init(const wxString &filename) { ev.Init(); if (filename.Len()) LoadDB(filename); if (!frame->nb->GetPageCount()) { int numfiles = (int)cfg->Read(L"numopenfiles", (long)0); loop(i, numfiles) { wxString fn; cfg->Read(wxString::Format(L"lastopenfile_%d", i), &fn); LoadDB(fn, true); } } if (!frame->nb->GetPageCount()) LoadTut(); if (!frame->nb->GetPageCount()) InitDB(10); // Refresh(); frame->bt.Start(400); savechecker.Start(1000); ScriptInit(frame); } void LoadTut() { auto lang = frame->app->locale.GetCanonicalName(); lang.Truncate(2); if (LoadDB(frame->GetPath(L"examples/tutorial-" + lang + ".cts"))[0]) { LoadDB(frame->GetPath(L"examples/tutorial.cts")); } } Cell *&InitDB(int sizex, int sizey = 0) { Cell *c = new Cell(nullptr, nullptr, CT_DATA, new Grid(sizex, sizey ? sizey : sizex)); c->cellcolor = 0xCCDCE2; c->grid->InitCells(); Document *doc = NewTabDoc(); doc->InitWith(c, L""); return doc->rootgrid; } wxString BakName(const wxString &filename) { return ExtName(filename, L".bak"); } wxString TmpName(const wxString &filename) { return ExtName(filename, L".tmp"); } wxString ExtName(const wxString &filename, wxString ext) { wxFileName fn(filename); return fn.GetPathWithSep() + fn.GetName() + ext; } const wxChar *LoadDB(const wxString &filename, bool frominit = false, bool fromreload = false) { wxString fn = filename; bool loadedfromtmp = false; if (!fromreload) { if (frame->GetTabByFileName(filename)) return nullptr; //"this file is already loaded"; if (::wxFileExists(TmpName(filename))) { if (::wxMessageBox( _(L"A temporary autosave file exists, would you like to load it instead?"), _(L"Autosave load"), wxYES_NO, frame) == wxYES) { fn = TmpName(filename); loadedfromtmp = true; } } } Document *doc = nullptr; bool anyimagesfailed = false; { // limit destructors wxBusyCursor wait; wxFFileInputStream fis(fn); if (!fis.IsOk()) return _(L"Cannot open file."); char buf[4]; fis.Read(buf, 4); if (strncmp(buf, "TSFF", 4)) return _(L"Not a TreeSheets file."); fis.Read(&versionlastloaded, 1); if (versionlastloaded > TS_VERSION) return _(L"File of newer version."); fakelasteditonload = wxDateTime::Now().GetValue(); loadimageids.setsize(0); for (;;) { fis.Read(buf, 1); switch (*buf) { case 'I': { wxDataInputStream dis(fis); if (versionlastloaded < 9) dis.ReadString(); wxImage im; double sc = versionlastloaded >= 19 ? dis.ReadDouble() : 1.0; off_t beforepng = fis.TellI(); bool ok = im.LoadFile(fis); // ok = false; if (!ok) { // Uhoh.. the decoder failed. Try to save the situation by skipping this // PNG. anyimagesfailed = true; if (beforepng == wxInvalidOffset) return _(L"Cannot tell/seek document?"); fis.SeekI(beforepng); // Now try to skip past this PNG uchar header[8]; fis.Read(header, 8); uchar expected[] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'}; if (memcmp(header, expected, 8)) return _(L"Corrupt PNG header."); dis.BigEndianOrdered(true); for (;;) // Skip all chunks. { wxInt32 len = dis.Read32(); char fourcc[4]; fis.Read(fourcc, 4); fis.SeekI(len, wxFromCurrent); // skip data dis.Read32(); // skip CRC if (memcmp(fourcc, "IEND", 4) == 0) break; } // Set empty image here, since document expect there to be one here. int sz = 32; im.Create(sz, sz, false); im.SetRGB(wxRect(0, 0, sz, sz), 0xFF, 0, 0); // Set to red to indicate error. } loadimageids.push() = AddImageToList(im, sc); break; } case 'D': { wxZlibInputStream zis(fis); if (!zis.IsOk()) return _(L"Cannot decompress file."); wxDataInputStream dis(zis); int numcells = 0, textbytes = 0; Cell *root = Cell::LoadWhich(dis, nullptr, numcells, textbytes); if (!root) return _(L"File corrupted!"); doc = NewTabDoc(true); if (loadedfromtmp) { doc->undolistsizeatfullsave = -1; // if not, user will lose tmp without warning when he closes doc->modified = true; } doc->InitWith(root, filename); if (versionlastloaded >= 11) { for (;;) { wxString s = dis.ReadString(); if (!s.Len()) break; doc->tags[s] = true; } } doc->sw->Status(wxString::Format(_(L"Loaded %s (%d cells, %d characters)."), filename.c_str(), numcells, textbytes) .c_str()); goto done; } default: return _(L"Corrupt block header."); } } } done: FileUsed(filename, doc); doc->ClearSelectionRefresh(); if (anyimagesfailed) wxMessageBox( _(L"PNG decode failed on some images in this document\nThey have been replaced by " L"red squares."), _(L"PNG decoder failure"), wxOK, frame); return L""; } void FileUsed(const wxString &filename, Document *doc) { frame->filehistory.AddFileToHistory(filename); RememberOpenFiles(); if (fswatch) { doc->lastmodificationtime = wxFileName(filename).GetModificationTime(); const wxString &d = wxFileName(filename).GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR); if (watchedpaths.find(d) == watchedpaths.end()) { watchedpaths[d] = true; frame->watcher->Add(wxFileName(d), wxFSW_EVENT_ALL); } } } const wxChar *Open(const wxString &fn) { if (!fn.empty()) { auto msg = LoadDB(fn); if (msg && *msg) wxMessageBox(msg, fn.wx_str(), wxOK, frame); return msg; } return _(L"Open file cancelled."); } void RememberOpenFiles() { int n = (int)frame->nb->GetPageCount(); int namedfiles = 0; loop(i, n) { TSCanvas *p = (TSCanvas *)frame->nb->GetPage(i); if (p->doc->filename.Len()) { cfg->Write(wxString::Format(L"lastopenfile_%d", namedfiles), p->doc->filename); namedfiles++; } } cfg->Write(L"numopenfiles", namedfiles); cfg->Flush(); } void UpdateStatus(Selection &s) { if (frame->GetStatusBar()) { Cell *c = s.GetCell(); if (c && s.xs) { frame->SetStatusText(wxString::Format(_(L"Size %d"), -c->text.relsize), 3); frame->SetStatusText(wxString::Format(_(L"Width %d"), s.g->colwidths[s.x]), 2); frame->SetStatusText( wxString::Format(_(L"Edited %s"), c->text.lastedit.FormatDate().c_str()), 1); } } } void SaveCheck() { loop(i, frame->nb->GetPageCount()) { ((TSCanvas *)frame->nb->GetPage(i))->doc->AutoSave(!frame->IsActive(), i); } } const wxChar *Import(int k) { wxString fn = ::wxFileSelector(_(L"Please select file to import:"), L"", L"", L"", L"*.*", wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_CHANGE_DIR); if (!fn.empty()) { wxBusyCursor wait; switch (k) { case A_IMPXML: case A_IMPXMLA: { wxXmlDocument doc; if (!doc.Load(fn)) goto problem; Cell *&r = InitDB(1); Cell *c = *r->grid->cells; FillXML(c, doc.GetRoot(), k == A_IMPXMLA); if (!c->HasText() && c->grid) { *r->grid->cells = nullptr; delete r; r = c; c->parent = nullptr; } break; } case A_IMPTXTI: case A_IMPTXTC: case A_IMPTXTS: case A_IMPTXTT: { wxFFile f(fn); if (!f.IsOpened()) goto problem; wxString s; if (!f.ReadAll(&s)) goto problem; const wxArrayString &as = wxStringTokenize(s, LINE_SEPERATOR); if (as.size()) switch (k) { case A_IMPTXTI: { Cell *r = InitDB(1); FillRows(r->grid, as, CountCol(as[0]), 0, 0); }; break; case A_IMPTXTC: InitDB(1, (int)as.size())->grid->CSVImport(as, L','); break; case A_IMPTXTS: InitDB(1, (int)as.size())->grid->CSVImport(as, L';'); break; case A_IMPTXTT: InitDB(1, (int)as.size())->grid->CSVImport(as, L'\t'); break; } break; } } frame->GetCurTab()->doc->ChangeFileName(fn.Find(L'.') >= 0 ? fn.BeforeLast(L'.') : fn, true); frame->GetCurTab()->doc->ClearSelectionRefresh(); } return nullptr; problem: wxMessageBox(_(L"couldn't import file!"), fn, wxOK, frame); return _(L"File load error."); } int GetXMLNodes(wxXmlNode *n, Vector &ns, Vector *ps = nullptr, bool attributestoo = false) { for (wxXmlNode *child = n->GetChildren(); child; child = child->GetNext()) { if (child->GetType() == wxXML_ELEMENT_NODE) ns.push() = child; } if (attributestoo && ps) for (wxXmlAttribute *child = n->GetAttributes(); child; child = child->GetNext()) { ps->push() = child; } return ns.size() + (ps ? ps->size() : 0); } void FillXML(Cell *c, wxXmlNode *n, bool attributestoo) { const wxArrayString &as = wxStringTokenize( n->GetType() == wxXML_ELEMENT_NODE ? n->GetNodeContent() : n->GetContent()); loop(i, as.GetCount()) { if (c->text.t.Len()) c->text.t.Append(L' '); c->text.t.Append(as[i]); } if (n->GetName() == L"cell") { c->text.relsize = -wxAtoi(n->GetAttribute(L"relsize", L"0")); c->text.stylebits = wxAtoi(n->GetAttribute(L"stylebits", L"0")); c->cellcolor = wxAtoi(n->GetAttribute(L"colorbg", L"16777215")); c->textcolor = wxAtoi(n->GetAttribute(L"colorfg", L"0")); } Vector ns; Vector ps; int numrows = GetXMLNodes(n, ns, &ps, attributestoo); if (!numrows) return; if (ns.size() == 1 && (!c->text.t.Len() || ns[0]->IsWhitespaceOnly()) && ns[0]->GetName() != L"row") { FillXML(c, ns[0], attributestoo); } else { bool allrow = n->GetName() == L"grid"; loopv(i, ns) if (ns[i]->GetName() != L"row") allrow = false; if (allrow) { int desiredxs; loopv(i, ns) { Vector ins; int xs = GetXMLNodes(ns[i], ins); if (!i) { desiredxs = xs ? xs : 1; c->AddGrid(desiredxs, ns.size()); } loop(j, desiredxs) if (ins.size() > j) FillXML(c->grid->C(j, i), ins[j], attributestoo); ins.setsize_nd(0); } } else { c->AddGrid(1, numrows); loopv(i, ps) c->grid->C(0, i)->text.t = ps[i]->GetValue(); loopv(i, ns) FillXML(c->grid->C(0, i + ps.size()), ns[i], attributestoo); } } ns.setsize_nd(0); ps.setsize_nd(0); } int CountCol(const wxString &s) { int col = 0; while (s[col] == ' ' || s[col] == '\t') col++; return col; } int FillRows(Grid *g, const wxArrayString &as, int column, int startrow, int starty) { int y = starty; for (int i = startrow; i < (int)as.size(); i++) { wxString s = as[i]; int col = CountCol(s); if (col < column && startrow != 0) return i; if (col > column) { Cell *c = g->C(0, y - 1); Grid *sg = c->grid; i = FillRows(sg ? sg : c->AddGrid(), as, col, i, sg ? sg->ys : 0) - 1; } else { if (g->ys <= y) g->InsertCells(-1, y, 0, 1); Text &t = g->C(0, y)->text; t.t = s.Trim(false); y++; } } return (int)as.size(); } int AddImageToList(const wxImage &im, double sc) { uint *p = (uint *)im.GetData(); uint checksum = im.GetWidth() | (im.GetHeight() << 16); loop(i, im.GetWidth() * im.GetHeight() * 3 / 4) checksum ^= *p++; loopv(i, imagelist) { if (imagelist[i]->checksum == checksum) return i; } imagelist.push() = new Image(wxBitmap(im), checksum, sc); return imagelist.size() - 1; } void ImageSize(wxBitmap *bm, int &xs, int &ys) { if (!bm) return; xs = bm->GetWidth(); ys = bm->GetHeight(); } void ImageDraw(wxBitmap *bm, wxDC &dc, int x, int y) { dc.DrawBitmap(*bm, x, y); } }; treesheets-1.0.2/src/text.h000077500000000000000000000406351352107072600156270ustar00rootroot00000000000000 struct Text { Cell *cell; wxString t; int relsize, stylebits, extent; Image *image; wxDateTime lastedit; bool filtered; Text() : cell(nullptr), t(wxEmptyString), relsize(0), stylebits(0), extent(0), image(nullptr), filtered(false) { WasEdited(); } wxBitmap * DisplayImage() { return cell->grid && cell->grid->folded ? &sys->frame->foldicon : (image ? &image->Display() : nullptr); } size_t EstimatedMemoryUse() { return sizeof(Text) + t.Length() * #if wxUSE_UNICODE sizeof(wchar_t); #else sizeof(char); #endif } double GetNum() { std::wstringstream ss(t.ToStdWstring()); double r; ss >> r; return r; } Cell *SetNum(double d) { std::wstringstream ss; ss << std::fixed; // We're going to use at most 19 digits after '.'. Add small value round remainder. size_t max_significant = 10; d += 0.00000000005; ss << d; auto s = ss.str(); // First trim whatever lies beyond the precision to avoid garbage digits. max_significant += 2; // "0." if (s[0] == '-') max_significant++; if (s.length() > max_significant) s.erase(max_significant); // Now strip unnecessary trailing zeroes. while (s.back() == '0') s.pop_back(); // If there were only zeroes, remove '.'. if (s.back() == '.') s.pop_back(); t = s; return cell; } wxString htmlify(wxString &str) { wxString r; loop(i, str.Len()) { switch (str[i].GetValue()) { case '&': r += L"&"; break; case '<': r += L"<"; break; case '>': r += L">"; break; default: r += str[i]; } } return r; } wxString ToText(int indent, const Selection &s, int format) { wxString str = s.cursor != s.cursorend ? t.Mid(s.cursor, s.cursorend - s.cursor) : t; if (format == A_EXPXML || format == A_EXPHTMLT || format == A_EXPHTMLO) str = htmlify(str); return str; }; void WasEdited() { lastedit = wxDateTime::Now(); } int MinRelsize(int rs) { return min(relsize, rs); } void RelSize(int dir, int zoomdepth) { relsize = max(min(relsize + dir, g_deftextsize - g_mintextsize() + zoomdepth), g_deftextsize - g_maxtextsize() - zoomdepth); } bool IsWord(wxChar c) { return wxIsalnum(c) || wxStrchr(L"_\"\'()", c); } wxString GetLinePart(int &i, int p, int l) { int start = i; i = p; while (i < l && t[i] != L' ' && !IsWord(t[i])) { i++; p++; }; // gobble up any trailing punctuation if (i != start && i < l && (t[i] == '\"' || t[i] == '\'')) { i++; p++; } // special case: if punctuation followed by quote, quote is meant to be part of word while (i < l && t[i] == L' ') // gobble spaces, but do not copy them { i++; if (i == l) p = i; // happens with a space at the last line, user is most likely about to type // another word, so // need to show space. Alternatively could check if the cursor is actually on this spot. // Simply // showing a blank new line would not be a good idea, unless the cursor is here for // sure, and // even then, placing the cursor there again after deselect may be hard. } ASSERT(start != i); return t.Mid(start, p - start); } wxString GetLine(int &i, int maxcolwidth) { int l = (int)t.Len(); if (i >= l) return wxEmptyString; if (!i && l <= maxcolwidth) { i = l; return t; } // subsumed by the case below, but this case happens 90% of the time, so more optimal if (l - i <= maxcolwidth) return GetLinePart(i, l, l); for (int p = i + maxcolwidth; p >= i; p--) if (!IsWord(t[p])) return GetLinePart(i, p, l); // A single word is > maxcolwidth. We split it up anyway. // This happens with long urls and e.g. Japanese text without spaces. // Should really do proper unicode linebreaking instead (see libunibreak), // but for now this is better than the old code below which allowed for arbitrary long // words. return GetLinePart(i, min(i + maxcolwidth, l), l); // for(int p = i+maxcolwidth; psearchstring.Len() && t.Lower().Find(sys->searchstring) >= 0; } int Render(Document *doc, int bx, int by, int depth, wxDC &dc, int &leftoffset, int maxcolwidth) { int ixs = 0, iys = 0; if (!cell->tiny) sys->ImageSize(DisplayImage(), ixs, iys); if (ixs && iys) { sys->ImageDraw(DisplayImage(), dc, bx + 1 + g_margin_extra, by + (cell->tys - iys) / 2 + g_margin_extra); ixs += 2; iys += 2; } if (t.empty()) return iys; doc->PickFont(dc, depth, relsize, stylebits); int h = cell->tiny ? 1 : dc.GetCharHeight(); leftoffset = h; int i = 0; int lines = 0; bool searchfound = IsInSearch(); bool istag = cell->IsTag(doc); if (cell->tiny) { if (searchfound) dc.SetPen(*wxRED_PEN); else if (filtered) dc.SetPen(*wxLIGHT_GREY_PEN); else if (istag) dc.SetPen(*wxBLUE_PEN); else dc.SetPen(sys->pen_tinytext); } for (;;) { wxString curl = GetLine(i, maxcolwidth); if (!curl.Len()) break; if (cell->tiny) { if (sys->fastrender) { dc.DrawLine(bx + ixs, by + lines * h, bx + ixs + (int)curl.Len(), by + lines * h); /* wxPoint points[] = { wxPoint(bx + ixs, by + lines * h), wxPoint(bx + ixs + curl.Len(), by + lines * h) }; dc.DrawLines(1, points, 0, 0); */ } else { int word = 0; loop(p, (int)curl.Len() + 1) { if ((int)curl.Len() <= p || curl[p] == ' ') { if (word) dc.DrawLine(bx + p - word + ixs, by + lines * h, bx + p, by + lines * h); word = 0; } else word++; } } } else { if (searchfound) dc.SetTextForeground(*wxRED); else if (filtered) dc.SetTextForeground(*wxLIGHT_GREY); else if (istag) dc.SetTextForeground(*wxBLUE); else if (cell->textcolor) dc.SetTextForeground(cell->textcolor); // FIXME: clean up int tx = bx + 2 + ixs; int ty = by + lines * h; MyDrawText(dc, curl, tx + g_margin_extra, ty + g_margin_extra, cell->sx, h); if (searchfound || filtered || istag || cell->textcolor) dc.SetTextForeground(*wxBLACK); } lines++; } return max(lines * h, iys); } void FindCursor(Document *doc, int bx, int by, wxDC &dc, Selection &s, int maxcolwidth) { bx -= g_margin_extra; by -= g_margin_extra; int ixs = 0, iys = 0; if (!cell->tiny) sys->ImageSize(DisplayImage(), ixs, iys); if (ixs) ixs += 2; doc->PickFont(dc, cell->Depth() - doc->drawpath.size(), relsize, stylebits); int i = 0, linestart = 0; int line = by / dc.GetCharHeight(); wxString ls; loop(l, line + 1) { linestart = i; ls = GetLine(i, maxcolwidth); } for (;;) { int x, y; dc.GetTextExtent(ls, &x, &y); // FIXME: can we do this more intelligently? if (x <= bx - ixs + 2 || !x) break; ls.Truncate(ls.Len() - 1); } s.cursor = s.cursorend = linestart + (int)ls.Len(); ASSERT(s.cursor >= 0 && s.cursor <= (int)t.Len()); } void DrawCursor(Document *doc, wxDC &dc, Selection &s, bool full, uint color, bool cursoronly, int maxcolwidth) { int ixs = 0, iys = 0; if (!cell->tiny) sys->ImageSize(DisplayImage(), ixs, iys); if (ixs) ixs += 2; doc->PickFont(dc, cell->Depth() - doc->drawpath.size(), relsize, stylebits); int h = dc.GetCharHeight(); { int i = 0; for (int l = 0;; l++) { int start = i; wxString ls = GetLine(i, maxcolwidth); int len = (int)ls.Len(); int end = start + len; if (s.cursor != s.cursorend) { if (s.cursor <= end && s.cursorend >= start && !cursoronly) { ls.Truncate(min(s.cursorend, end) - start); int x1, x2; dc.GetTextExtent(ls, &x2, nullptr); ls.Truncate(max(s.cursor, start) - start); dc.GetTextExtent(ls, &x1, nullptr); if (x1 != x2) DrawRectangle( dc, color, cell->GetX(doc) + x1 + 2 + ixs + g_margin_extra, cell->GetY(doc) + l * h + 1 + cell->ycenteroff + g_margin_extra, x2 - x1, h - 1 #ifdef SIMPLERENDER , true #endif ); } } else if (s.cursor >= start && s.cursor <= end) { ls.Truncate(s.cursor - start); int x; dc.GetTextExtent(ls, &x, nullptr); if (doc->blink) { #ifdef SIMPLERENDER DrawRectangle( dc, color, cell->GetX(doc) + x + 1 + ixs + g_margin_extra, cell->GetY(doc) + l * h + 1 + cell->ycenteroff + g_margin_extra, 2, h - 2); #else auto lx = cell->GetX(doc) + x + 2 + ixs + g_margin_extra; auto ly = cell->GetY(doc) + l * h + cell->ycenteroff + g_margin_extra; dc.SetPen(wxPen(wxColour(0xFFFFFF))); dc.DrawLine(lx, ly, lx, ly + h); #endif } break; } if (!len) break; } } } void SelectWord(Selection &s) { if (s.cursor >= (int)t.Len()) return; s.cursorend = s.cursor + 1; if (!wxIsalnum(t[s.cursor])) return; while (s.cursor > 0 && wxIsalnum(t[s.cursor - 1])) s.cursor--; while (s.cursorend < (int)t.Len() && wxIsalnum(t[s.cursorend])) s.cursorend++; } bool RangeSelRemove(Selection &s) { WasEdited(); if (s.cursor != s.cursorend) { t.Remove(s.cursor, s.cursorend - s.cursor); s.cursorend = s.cursor; return true; } return false; } void SetRelSize(Selection &s) { if (t.Len() || !cell->parent) return; int dd[] = { 0, 1, 1, 0, 0, -1, -1, 0 }; for (int i = 0; i < 4; i++) { int x = max(0, min(s.x + dd[i * 2], s.g->xs - 1)); int y = max(0, min(s.y + dd[i * 2 + 1], s.g->ys - 1)); auto c = s.g->C(x, y); if (c->text.t.Len()) { relsize = c->text.relsize; break; } } } void Insert(Document *doc, const wxString &ins, Selection &s) { if (!s.TextEdit()) Clear(doc, s); RangeSelRemove(s); SetRelSize(s); t.insert(s.cursor, ins); s.cursor = s.cursorend = s.cursor + (int)ins.Len(); } void Delete(Selection &s) { if (!RangeSelRemove(s)) if (s.cursor < (int)t.Len()) { t.Remove(s.cursor, 1); }; } void Backspace(Selection &s) { if (!RangeSelRemove(s)) if (s.cursor > 0) { t.Remove(--s.cursor, 1); --s.cursorend; }; } void Key(int k, Selection &s) { RangeSelRemove(s); SetRelSize(s); t.insert(s.cursor++, 1, k); s.cursorend = s.cursor; } void ReplaceStr(const wxString &str) { for (int i = 0, j; (j = t.Mid(i).Lower().Find(sys->searchstring)) >= 0;) { // does this need WasEdited()? i += j; t.Remove(i, sys->searchstring.Len()); t.insert(i, str); i += str.Len(); } } void Clear(Document *doc, Selection &s) { t.Clear(); s.EnterEdit(doc); } void HomeEnd(Selection &s, bool home) { int i = 0; int cw = cell->ColWidth(); int findwhere = home ? s.cursor : s.cursorend; for (;;) { int start = i; wxString curl = GetLine(i, cw); if (!curl.Len()) break; int end = i == t.Len() ? i : i - 1; if (findwhere >= start && findwhere <= end) { s.cursor = s.cursorend = home ? start : end; break; } } } void Save(wxDataOutputStream &dos) const { dos.WriteString(t.wx_str()); dos.Write32(relsize); dos.Write32(image ? image->savedindex : -1); dos.Write32(stylebits); wxLongLong le = lastedit.GetValue(); dos.Write64(&le, 1); } void Load(wxDataInputStream &dis) { t = dis.ReadString(); // if (t.length() > 10000) // printf(""); if (sys->versionlastloaded <= 11) dis.Read32(); // numlines relsize = dis.Read32(); int i = dis.Read32(); image = i >= 0 ? sys->imagelist[sys->loadimageids[i]] : nullptr; if (sys->versionlastloaded >= 7) stylebits = dis.Read32(); wxLongLong time; if (sys->versionlastloaded >= 14) { dis.Read64(&time, 1); } else { time = sys->fakelasteditonload--; } lastedit = wxDateTime(time); } Cell *Eval(Evaluator &ev) { switch (cell->celltype) { // Load variable's data. case CT_VARU: { Cell *temp = ev.Lookup(t); if (!temp) { temp = cell->Clone(nullptr); temp->celltype = CT_DATA; temp->text.t = "**Variable Load Error**"; } return temp; } // Return our current data. case CT_DATA: return cell->Clone(nullptr); default: return nullptr; } } Cell *Graph() { Cell *c = new Cell(); c->text.t.Append(L'|', (int)GetNum()); return c; } }; treesheets-1.0.2/src/tools.h000077500000000000000000000203741352107072600160010ustar00rootroot00000000000000typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; #ifdef _DEBUG #define ASSERT(c) assert(c) #else #define ASSERT(c) \ if (c) {} #endif #define loop(i, m) for (int i = 0; i < int(m); i++) #define loopv(i, v) for (int i = 0; i < (v).size(); i++) #define loopvrev(i, v) for (int i = (v).size() - 1; i >= 0; i--) #define max(a, b) ((a) < (b) ? (b) : (a)) #define min(a, b) ((a) > (b) ? (b) : (a)) #define sign(x) ((x) < 0 ? -1 : 1) #define varargs(v, fmt, body) \ { \ va_list v; \ va_start(v, fmt); \ body; \ va_end(v); \ } #define DELETEP(p) \ { \ if (p) { \ delete p; \ p = nullptr; \ } \ } #define DELETEA(a) \ { \ if (a) { \ delete[] a; \ a = nullptr; \ } \ } #define bound(v, a, s, e) \ { \ v += a; \ if (v > (e)) v = (e); \ if (v < (s)) v = (s); \ } #ifdef WIN32 #define PATH_SEPERATOR L"\\" #define LINE_SEPERATOR L"\r\n" #else #define PATH_SEPERATOR L"/" #ifdef __WXMAC__ #define LINE_SEPERATOR L"\r" #else #define LINE_SEPERATOR L"\n" #endif #define __cdecl #define _vsnprintf vsnprintf #endif template inline void swap_(T &a, T &b) { T c = a; a = b; b = c; }; #ifdef WIN32 #pragma warning(3 : 4189) // local variable is initialized but not referenced #pragma warning(disable : 4244) // conversion from 'int' to 'float', possible loss of data #pragma warning(disable : 4355) // 'this' : used in base member initializer list #pragma warning(disable : 4996) // 'strncpy' was declared deprecated #endif inline uchar *loadfile(const char *fn, size_t *lenret = nullptr) { FILE *f = fopen(fn, "rb"); if (!f) return nullptr; fseek(f, 0, SEEK_END); size_t len = ftell(f); fseek(f, 0, SEEK_SET); uchar *buf = (uchar *)malloc(len+1); if (!buf) { fclose(f); return nullptr; } buf[len] = 0; size_t rlen = fread(buf, 1, len, f); fclose(f); if (len!=rlen || len<=0) { free(buf); return nullptr; } if (lenret) *lenret = len; return buf; } // from boost, stops a class from being accidental victim to default copy + destruct twice problem // class that inherits from NonCopyable will work correctly with Vector, but give compile time error // with std::vector class NonCopyable { NonCopyable(const NonCopyable &); const NonCopyable &operator=(const NonCopyable &); protected: NonCopyable() {} virtual ~NonCopyable() {} }; // helper function for containers below to delete members if they are of pointer type only template void DelPtr(X &) {} template void DelPtr(X *&m) { DELETEP(m); } // replacement for STL vector // for any T that has a non-trivial destructor, STL requires a correct copy constructor / assignment // op // or otherwise it will free things twice on a reallocate/by value push_back. // The vector below will behave correctly irrespective of whether a copy constructor is available // or not and avoids the massive unnecessary (de/re)allocations the STL forces you to. // The class itself never calls T's copy constructor, however the user of the class is still // responsable for making sure of correct copying of elements obtained from the vector, or setting // NonCopyable // also automatically deletes pointer members and other neat things template class Vector : public NonCopyable { T *buf; uint alen, ulen; void reallocate(uint n) { ASSERT(n > ulen); T *obuf = buf; // use malloc instead of new to avoid constructor buf = (T *)malloc(sizeof(T) * (alen = n)); ASSERT(buf); if (ulen) memcpy(buf, obuf, ulen * sizeof(T)); // memcpy avoids copy constructor if (obuf) free(obuf); } T &push_nocons() { if (ulen == alen) reallocate(alen * 2); return buf[ulen++]; } void destruct(uint i) { buf[i].~T(); DelPtr(buf[i]); } public: T &operator[](uint i) { ASSERT(i < ulen); return buf[i]; } T &operator[](uint i) const { ASSERT(i < ulen); return buf[i]; } Vector() { new (this) Vector(8); } Vector(uint n) : ulen(0), buf(nullptr) { reallocate(n); } Vector(uint n, int c) { new (this) Vector(n); loop(i, c) push(); } ~Vector() { setsize(0); free(buf); } T &push() { return *new (&push_nocons()) T; } // only way to create an element, to avoid copy constructor T *getbuf() { return buf; } T &last() { return (*this)[ulen - 1]; } T &pop() { return buf[--ulen]; } void drop() { ASSERT(ulen); destruct(--ulen); } bool empty() { return ulen == 0; } int size() { return ulen; } void setsize(uint i) { while (ulen > i) drop(); } // explicitly destruct elements void setsize_nd(uint i) { while (ulen > i) pop(); } void sort(void *cf) { qsort(buf, ulen, sizeof(T), (int(__cdecl *)(const void *, const void *))cf); } void add_unique(T x) { loop(i, ulen) if (buf[i] == x) return; push() = x; } void remove(uint i) { ASSERT(i < ulen); destruct(i); ulen--; memmove(buf + i, buf + i + 1, sizeof(T) * (ulen - i)); } void remove(uint i, uint n) { ASSERT(i + n <= ulen); for (uint d = i; d < i + n; d++) destruct(d); ulen -= n; memmove(buf + i, buf + i + n, sizeof(T) * (ulen - i)); } void removeobj(T o) { loop(i, ulen) if (buf[i] == o) { remove(i); return; } } void append(Vector &o) { loopv(i, o) push() = o[i]; } }; class MTRnd { const static uint N = 624; const static uint M = 397; const static uint K = 0x9908B0DFU; uint hiBit(uint u) { return u & 0x80000000U; } uint loBit(uint u) { return u & 0x00000001U; } uint loBits(uint u) { return u & 0x7FFFFFFFU; } uint mixBits(uint u, uint v) { return hiBit(u) | loBits(v); } uint state[N + 1]; uint *next; int left; public: MTRnd() : left(-1) {} void SeedMT(uint seed) { uint x = (seed | 1U) & 0xFFFFFFFFU, *s = state; int j; for (left = 0, *s++ = x, j = N; --j; *s++ = (x *= 69069U) & 0xFFFFFFFFU) ; } uint ReloadMT() { uint *p0 = state, *p2 = state + 2, *pM = state + M, s0, s1; int j; if (left < -1) SeedMT(4357U); left = N - 1, next = state + 1; for (s0 = state[0], s1 = state[1], j = N - M + 1; --j; s0 = s1, s1 = *p2++) *p0++ = *pM++ ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? K : 0U); for (pM = state, j = M; --j; s0 = s1, s1 = *p2++) *p0++ = *pM++ ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? K : 0U); s1 = state[0], *p0 = *pM ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? K : 0U); s1 ^= (s1 >> 11); s1 ^= (s1 << 7) & 0x9D2C5680U; s1 ^= (s1 << 15) & 0xEFC60000U; return (s1 ^ (s1 >> 18)); } uint RandomMT() { uint y; if (--left < 0) return (ReloadMT()); y = *next++; y ^= (y >> 11); y ^= (y << 7) & 0x9D2C5680U; y ^= (y << 15) & 0xEFC60000U; return (y ^ (y >> 18)); } int operator()(int max) { return RandomMT() % max; } }; // for use with vc++ crtdbg #ifdef _DEBUG inline void *__cdecl operator new(size_t n, const char *fn, int l) { return ::operator new(n, 1, fn, l); } inline void *__cdecl operator new[](size_t n, const char *fn, int l) { return ::operator new[](n, 1, fn, l); } inline void __cdecl operator delete(void *p, const char *fn, int l) { ::operator delete(p, 1, fn, l); } inline void __cdecl operator delete[](void *p, const char *fn, int l) { ::operator delete[](p, 1, fn, l); } #define new new (__FILE__, __LINE__) #endif treesheets-1.0.2/src/treesheets_impl.h000066400000000000000000000076761352107072600200440ustar00rootroot00000000000000 struct TreeSheetsScriptImpl : public ScriptInterface { Document *doc = nullptr; Cell *cur = nullptr; enum { max_new_grid_dim = 256 }; // Don't allow crazy sizes. std::string ScriptRun(const char *filename) { doc = sys->frame->GetCurTab()->doc; cur = doc->rootgrid; doc->AddUndo(cur); bool dump_builtins = false; #ifdef _DEBUG //dump_builtins = true; #endif auto err = RunLobster(filename, {}, dump_builtins); doc = nullptr; cur = nullptr; return err; } void GoToRoot() { cur = doc->rootgrid; } void GoToView() { cur = doc->curdrawroot; } bool HasSelection() { return doc->selected.g; } void GoToSelection() { if (doc->selected.g) cur = doc->selected.g->cell; } bool HasParent() { return cur->parent; } void GoToParent() { if (cur->parent) cur = cur->parent; } int NumChildren() { return cur->grid ? cur->grid->xs * cur->grid->ys : 0; } icoord NumColumnsRows() { return cur->grid ? icoord(cur->grid->xs, cur->grid->ys) : icoord(0, 0); } ibox SelectionBox() { auto &s = doc->selected; return s.g ? ibox(icoord(s.x, s.y), icoord(s.xs, s.ys)) : ibox(icoord(0, 0), icoord(0, 0)); } void GoToChild(int n) { if (cur->grid && n < cur->grid->xs * cur->grid->ys) cur = cur->grid->cells[n]; } void GoToColumnRow(int x, int y) { if (cur->grid && x < cur->grid->xs && y < cur->grid->ys) cur = cur->grid->C(x, y); } std::string GetText() { auto s = cur->text.t.utf8_str(); return std::string(s.data(), s.length()); } void SetText(std::string_view t) { if (cur->parent) cur->text.t = wxString(t.data(), t.size()); } void CreateGrid(int x, int y) { if (x > 0 && y > 0 && x < max_new_grid_dim && y < max_new_grid_dim) cur->AddGrid(x, y); } void InsertColumns(int x, int n) { if (cur->grid && x >= 0 && x <= cur->grid->xs && n > 0 && n < max_new_grid_dim) cur->grid->InsertCells(x, -1, 1 /* FIXME n */, 0); } void InsertRows(int y, int n) { if (cur->grid && y >= 0 && y <= cur->grid->ys && n > 0 && n < max_new_grid_dim) cur->grid->InsertCells(-1, y, 0, 1 /* FIXME n */); } void Delete(int x, int y, int xs, int ys) { if (cur->grid && x >= 0 && x + xs <= cur->grid->xs && y >= 0 && y + ys <= cur->grid->ys) { Selection s(cur->grid, x, y, xs, ys); cur->grid->MultiCellDeleteSub(doc, s); } } void SetBackgroundColor(uint col) { cur->cellcolor = col; } void SetTextColor(uint col) { cur->textcolor = col; } void SetRelativeSize(int s) { cur->text.relsize = s; } void SetStyle(int s) { cur->text.stylebits = s; } void SetStatusMessage(std::string_view msg) { auto ws = wxString(msg.data(), msg.size()); sys->frame->GetCurTab()->Status(ws); } std::string GetFileNameFromUser(bool is_save) { int flags = wxFD_CHANGE_DIR; if (is_save) flags |= wxFD_OVERWRITE_PROMPT | wxFD_SAVE; else flags |= wxFD_OPEN | wxFD_FILE_MUST_EXIST; wxString fn = ::wxFileSelector(_(L"Choose file:"), L"", L"", L"", L"*.*", flags); auto s = fn.utf8_str(); return std::string(s.data(), s.length()); } }; static int64_t TreeSheetsLoader(std::string_view absfilename, std::string *dest, int64_t start, int64_t len) { size_t l = 0; auto buf = (char *)loadfile(std::string(absfilename).c_str(), &l); if (!buf) return -1; dest->assign(buf, l); free(buf); return l; } static TreeSheetsScriptImpl tssi; static void ScriptInit(MyFrame *frame) { auto serr = InitLobster(&tssi, frame->GetPath("scripts/"), "", false, TreeSheetsLoader); if (!serr.empty()) frame->GetCurTab()->Status(wxString("Script system could not initialize: " + serr)); } treesheets-1.0.2/treesheets/000077500000000000000000000000001352107072600160435ustar00rootroot00000000000000treesheets-1.0.2/treesheets/boar.aps000066400000000000000000000450001352107072600174720ustar00rootroot00000000000000 '$HWB PHc:\W\treesheets\treesheets\boar.rc 4TEXTINCLUDE0 resource.h4TEXTINCLUDE0 #include "afxres.h" 4TEXTINCLUDE0  (  ( wwwwwwwwwwwwpwwwwwwwwwwww  ( @wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww" e0 ( ?$HWB0 !!resource.htsicon101_APS_NEXT_RESOURCE_VALUE102 _APS_NEXT_COMMAND_VALUE40001 _APS_NEXT_CONTROL_VALUE1001 _APS_NEXT_SYMED_VALUE101!!afxres.h!winres.hVS_VERSION_INFO1IDC_STATIC(-1))!_MFC_FILENAME_VER"90"CBRS_ALIGN_LEFT0x1000L:CBRS_ALIGN_TOP0x2000L;CBRS_ALIGN_RIGHT0x4000L<CBRS_ALIGN_BOTTOM0x8000L=CBRS_ALIGN_ANY0xF000L>CBRS_BORDER_LEFT0x0100L@CBRS_BORDER_TOP0x0200LACBRS_BORDER_RIGHT0x0400LBCBRS_BORDER_BOTTOM0x0800LCCBRS_BORDER_ANY0x0F00LDCBRS_TOOLTIPS0x0010LFCBRS_FLYBY0x0020LGCBRS_FLOAT_MULTI0x0040LHCBRS_BORDER_3D0x0080LICBRS_HIDE_INPLACE0x0008LJCBRS_SIZE_DYNAMIC0x0004LKCBRS_SIZE_FIXED0x0002LLCBRS_FLOATING0x0001LMCBRS_GRIPPER0x00400000LOCBRS_ORIENT_HORZ(CBRS_ALIGN_TOP|CBRS_ALIGN_BOTTOM)QCBRS_ORIENT_VERT(CBRS_ALIGN_LEFT|CBRS_ALIGN_RIGHT)RCBRS_ORIENT_ANY(CBRS_ORIENT_HORZ|CBRS_ORIENT_VERT)SCBRS_ALL0x0040FFFFLUCBRS_NOALIGN0x00000000LZCBRS_LEFT(CBRS_ALIGN_LEFT|CBRS_BORDER_RIGHT)[CBRS_TOP(CBRS_ALIGN_TOP|CBRS_BORDER_BOTTOM)\CBRS_RIGHT(CBRS_ALIGN_RIGHT|CBRS_BORDER_LEFT)]CBRS_BOTTOM(CBRS_ALIGN_BOTTOM|CBRS_BORDER_TOP)^ID_MFCLOC_MANIFEST1000bID_INDICATOR_EXT0xE700hID_INDICATOR_CAPS0xE701iID_INDICATOR_NUM0xE702jID_INDICATOR_SCRL0xE703kID_INDICATOR_OVR0xE704lID_INDICATOR_REC0xE705mID_INDICATOR_KANA0xE706nID_SEPARATOR0pAFX_IDS_APP_TITLE0xE000AFX_IDS_IDLEMESSAGE0xE001AFX_IDS_HELPMODEMESSAGE0xE002AFX_IDS_APP_TITLE_EMBEDDING0xE003AFX_IDS_COMPANY_NAME0xE004AFX_IDS_OBJ_TITLE_INPLACE0xE005ID_FILE_NEW0xE100ID_FILE_OPEN0xE101ID_FILE_CLOSE0xE102ID_FILE_SAVE0xE103ID_FILE_SAVE_AS0xE104ID_FILE_PAGE_SETUP0xE105ID_FILE_PRINT_SETUP0xE106ID_FILE_PRINT0xE107ID_FILE_PRINT_DIRECT0xE108ID_FILE_PRINT_PREVIEW0xE109ID_FILE_UPDATE0xE10AID_FILE_SAVE_COPY_AS0xE10BID_FILE_SEND_MAIL0xE10CID_FILE_NEW_FRAME0xE10DID_FILE_MRU_FIRST0xE110ID_FILE_MRU_FILE10xE110ID_FILE_MRU_FILE20xE111ID_FILE_MRU_FILE30xE112ID_FILE_MRU_FILE40xE113ID_FILE_MRU_FILE50xE114ID_FILE_MRU_FILE60xE115ID_FILE_MRU_FILE70xE116ID_FILE_MRU_FILE80xE117ID_FILE_MRU_FILE90xE118ID_FILE_MRU_FILE100xE119ID_FILE_MRU_FILE110xE11AID_FILE_MRU_FILE120xE11BID_FILE_MRU_FILE130xE11CID_FILE_MRU_FILE140xE11DID_FILE_MRU_FILE150xE11EID_FILE_MRU_FILE160xE11FID_FILE_MRU_LAST0xE11FID_EDIT_CLEAR0xE120ID_EDIT_CLEAR_ALL0xE121ID_EDIT_COPY0xE122ID_EDIT_CUT0xE123ID_EDIT_FIND0xE124ID_EDIT_PASTE0xE125ID_EDIT_PASTE_LINK0xE126ID_EDIT_PASTE_SPECIAL0xE127ID_EDIT_REPEAT0xE128ID_EDIT_REPLACE0xE129ID_EDIT_SELECT_ALL0xE12AID_EDIT_UNDO0xE12BID_EDIT_REDO0xE12CID_WINDOW_NEW0xE130ID_WINDOW_ARRANGE0xE131ID_WINDOW_CASCADE0xE132ID_WINDOW_TILE_HORZ0xE133ID_WINDOW_TILE_VERT0xE134ID_WINDOW_SPLIT0xE135ID_APP_ABOUT0xE140ID_APP_EXIT0xE141ID_HELP_INDEX0xE142ID_HELP_FINDER0xE143ID_HELP_USING0xE144ID_CONTEXT_HELP0xE145ID_HELP0xE146ID_DEFAULT_HELP0xE147ID_NEXT_PANE0xE150ID_PREV_PANE0xE151ID_FORMAT_FONT0xE160ID_OLE_INSERT_NEW0xE200ID_OLE_EDIT_LINKS0xE201ID_OLE_EDIT_CONVERT0xE202ID_OLE_EDIT_CHANGE_ICON0xE203ID_OLE_EDIT_PROPERTIES0xE204ID_OLE_VERB_FIRST0xE210AFX_ID_PREVIEW_CLOSE0xE300AFX_ID_PREVIEW_NUMPAGE0xE301AFX_ID_PREVIEW_NEXT0xE302AFX_ID_PREVIEW_PREV0xE303AFX_ID_PREVIEW_PRINT0xE304AFX_ID_PREVIEW_ZOOMIN0xE305AFX_ID_PREVIEW_ZOOMOUT0xE306ID_VIEW_TOOLBAR0xE800ID_VIEW_STATUS_BAR0xE801ID_VIEW_REBAR0xE804ID_VIEW_AUTOARRANGE0xE805ID_VIEW_SMALLICON0xE810ID_VIEW_LARGEICON0xE811ID_VIEW_LIST0xE812ID_VIEW_DETAILS0xE813ID_VIEW_LINEUP0xE814 ID_VIEW_BYNAME0xE815!AFX_ID_VIEW_MINIMUMID_VIEW_SMALLICON"AFX_ID_VIEW_MAXIMUMID_VIEW_BYNAME#ID_RECORD_FIRST0xE900'ID_RECORD_LAST0xE901(ID_RECORD_NEXT0xE902)ID_RECORD_PREV0xE903*IDC_STATIC(-1)2AFX_IDS_SCSIZE0xEF00;AFX_IDS_SCMOVE0xEF01<AFX_IDS_SCMINIMIZE0xEF02=AFX_IDS_SCMAXIMIZE0xEF03>AFX_IDS_SCNEXTWINDOW0xEF04?AFX_IDS_SCPREVWINDOW0xEF05@AFX_IDS_SCCLOSE0xEF06AAFX_IDS_SCRESTORE0xEF12BAFX_IDS_SCTASKLIST0xEF13CAFX_IDS_MDICHILD0xEF1FEAFX_IDS_DESKACCESSORY0xEFDAGAFX_IDS_OPENFILE0xF000JAFX_IDS_SAVEFILE0xF001KAFX_IDS_ALLFILTER0xF002LAFX_IDS_UNTITLED0xF003MAFX_IDS_SAVEFILECOPY0xF004NAFX_IDS_PREVIEW_CLOSE0xF005OAFX_IDS_UNNAMED_FILE0xF006PAFX_IDS_HIDE0xF011QAFX_IDP_NO_ERROR_AVAILABLE0xF020TAFX_IDS_NOT_SUPPORTED_EXCEPTION0xF021UAFX_IDS_RESOURCE_EXCEPTION0xF022VAFX_IDS_MEMORY_EXCEPTION0xF023WAFX_IDS_USER_EXCEPTION0xF024XAFX_IDS_INVALID_ARG_EXCEPTION0xF025YAFX_IDS_PRINTONPORT0xF040\AFX_IDS_ONEPAGE0xF041]AFX_IDS_TWOPAGE0xF042^AFX_IDS_PRINTPAGENUM0xF043_AFX_IDS_PREVIEWPAGEDESC0xF044`AFX_IDS_PRINTDEFAULTEXT0xF045aAFX_IDS_PRINTDEFAULT0xF046bAFX_IDS_PRINTFILTER0xF047cAFX_IDS_PRINTCAPTION0xF048dAFX_IDS_PRINTTOFILE0xF049eAFX_IDS_OBJECT_MENUITEM0xF080iAFX_IDS_EDIT_VERB0xF081jAFX_IDS_ACTIVATE_VERB0xF082kAFX_IDS_CHANGE_LINK0xF083lAFX_IDS_AUTO0xF084mAFX_IDS_MANUAL0xF085nAFX_IDS_FROZEN0xF086oAFX_IDS_ALL_FILES0xF087pAFX_IDS_SAVE_MENU0xF088rAFX_IDS_UPDATE_MENU0xF089sAFX_IDS_SAVE_AS_MENU0xF08AtAFX_IDS_SAVE_COPY_AS_MENU0xF08BuAFX_IDS_EXIT_MENU0xF08CvAFX_IDS_UPDATING_ITEMS0xF08DwAFX_IDS_METAFILE_FORMAT0xF08EyAFX_IDS_DIB_FORMAT0xF08FzAFX_IDS_BITMAP_FORMAT0xF090{AFX_IDS_LINKSOURCE_FORMAT0xF091|AFX_IDS_EMBED_FORMAT0xF092}AFX_IDS_PASTELINKEDTYPE0xF094AFX_IDS_UNKNOWNTYPE0xF095AFX_IDS_RTF_FORMAT0xF096AFX_IDS_TEXT_FORMAT0xF097AFX_IDS_INVALID_CURRENCY0xF098AFX_IDS_INVALID_DATETIME0xF099AFX_IDS_INVALID_DATETIMESPAN0xF09AAFX_IDP_INVALID_FILENAME0xF100AFX_IDP_FAILED_TO_OPEN_DOC0xF101AFX_IDP_FAILED_TO_SAVE_DOC0xF102AFX_IDP_ASK_TO_SAVE0xF103AFX_IDP_FAILED_TO_CREATE_DOC0xF104AFX_IDP_FILE_TOO_LARGE0xF105AFX_IDP_FAILED_TO_START_PRINT0xF106AFX_IDP_FAILED_TO_LAUNCH_HELP0xF107AFX_IDP_INTERNAL_FAILURE0xF108AFX_IDP_COMMAND_FAILURE0xF109AFX_IDP_FAILED_MEMORY_ALLOC0xF10AAFX_IDP_UNREG_DONE0xF10BAFX_IDP_UNREG_FAILURE0xF10CAFX_IDP_DLL_LOAD_FAILED0xF10DAFX_IDP_DLL_BAD_VERSION0xF10EAFX_IDP_PARSE_INT0xF110AFX_IDP_PARSE_REAL0xF111AFX_IDP_PARSE_INT_RANGE0xF112AFX_IDP_PARSE_REAL_RANGE0xF113AFX_IDP_PARSE_STRING_SIZE0xF114AFX_IDP_PARSE_RADIO_BUTTON0xF115AFX_IDP_PARSE_BYTE0xF116AFX_IDP_PARSE_UINT0xF117AFX_IDP_PARSE_DATETIME0xF118AFX_IDP_PARSE_CURRENCY0xF119AFX_IDP_PARSE_GUID0xF11AAFX_IDP_PARSE_TIME0xF11BAFX_IDP_PARSE_DATE0xF11CAFX_IDP_FAILED_INVALID_FORMAT0xF120AFX_IDP_FAILED_INVALID_PATH0xF121AFX_IDP_FAILED_DISK_FULL0xF122AFX_IDP_FAILED_ACCESS_READ0xF123AFX_IDP_FAILED_ACCESS_WRITE0xF124AFX_IDP_FAILED_IO_ERROR_READ0xF125AFX_IDP_FAILED_IO_ERROR_WRITE0xF126AFX_IDP_SCRIPT_ERROR0xF130AFX_IDP_SCRIPT_DISPATCH_EXCEPTION0xF131AFX_IDP_STATIC_OBJECT0xF180AFX_IDP_FAILED_TO_CONNECT0xF181AFX_IDP_SERVER_BUSY0xF182AFX_IDP_BAD_VERB0xF183AFX_IDS_NOT_DOCOBJECT0xF184AFX_IDP_FAILED_TO_NOTIFY0xF185AFX_IDP_FAILED_TO_LAUNCH0xF186AFX_IDP_ASK_TO_UPDATE0xF187AFX_IDP_FAILED_TO_UPDATE0xF188AFX_IDP_FAILED_TO_REGISTER0xF189AFX_IDP_FAILED_TO_AUTO_REGISTER0xF18AAFX_IDP_FAILED_TO_CONVERT0xF18BAFX_IDP_GET_NOT_SUPPORTED0xF18CAFX_IDP_SET_NOT_SUPPORTED0xF18DAFX_IDP_ASK_TO_DISCARD0xF18EAFX_IDP_FAILED_TO_CREATE0xF18FAFX_IDP_FAILED_MAPI_LOAD0xF190AFX_IDP_INVALID_MAPI_DLL0xF191AFX_IDP_FAILED_MAPI_SEND0xF192AFX_IDP_FILE_NONE0xF1A0AFX_IDP_FILE_GENERIC0xF1A1AFX_IDP_FILE_NOT_FOUND0xF1A2AFX_IDP_FILE_BAD_PATH0xF1A3AFX_IDP_FILE_TOO_MANY_OPEN0xF1A4AFX_IDP_FILE_ACCESS_DENIED0xF1A5AFX_IDP_FILE_INVALID_FILE0xF1A6AFX_IDP_FILE_REMOVE_CURRENT0xF1A7AFX_IDP_FILE_DIR_FULL0xF1A8AFX_IDP_FILE_BAD_SEEK0xF1A9AFX_IDP_FILE_HARD_IO0xF1AAAFX_IDP_FILE_SHARING0xF1ABAFX_IDP_FILE_LOCKING0xF1ACAFX_IDP_FILE_DISKFULL0xF1ADAFX_IDP_FILE_EOF0xF1AEAFX_IDP_ARCH_NONE0xF1B0AFX_IDP_ARCH_GENERIC0xF1B1AFX_IDP_ARCH_READONLY0xF1B2AFX_IDP_ARCH_ENDOFFILE0xF1B3AFX_IDP_ARCH_WRITEONLY0xF1B4AFX_IDP_ARCH_BADINDEX0xF1B5AFX_IDP_ARCH_BADCLASS0xF1B6AFX_IDP_ARCH_BADSCHEMA0xF1B7AFX_IDS_OCC_SCALEUNITS_PIXELS0xF1C0AFX_IDS_STATUS_FONT0xF230AFX_IDS_TOOLTIP_FONT0xF231AFX_IDS_UNICODE_FONT0xF232AFX_IDS_MINI_FONT0xF233AFX_IDP_SQL_CONNECT_FAIL0xF281AFX_IDP_SQL_RECORDSET_FORWARD_ONLY0xF282AFX_IDP_SQL_EMPTY_COLUMN_LIST0xF283AFX_IDP_SQL_FIELD_SCHEMA_MISMATCH0xF284AFX_IDP_SQL_ILLEGAL_MODE0xF285AFX_IDP_SQL_MULTIPLE_ROWS_AFFECTED0xF286AFX_IDP_SQL_NO_CURRENT_RECORD0xF287AFX_IDP_SQL_NO_ROWS_AFFECTED0xF288AFX_IDP_SQL_RECORDSET_READONLY0xF289AFX_IDP_SQL_SQL_NO_TOTAL0xF28AAFX_IDP_SQL_ODBC_LOAD_FAILED0xF28BAFX_IDP_SQL_DYNASET_NOT_SUPPORTED0xF28CAFX_IDP_SQL_SNAPSHOT_NOT_SUPPORTED0xF28DAFX_IDP_SQL_API_CONFORMANCE0xF28EAFX_IDP_SQL_SQL_CONFORMANCE0xF28FAFX_IDP_SQL_NO_DATA_FOUND0xF290AFX_IDP_SQL_ROW_UPDATE_NOT_SUPPORTED0xF291AFX_IDP_SQL_ODBC_V2_REQUIRED0xF292AFX_IDP_SQL_NO_POSITIONED_UPDATES0xF293AFX_IDP_SQL_LOCK_MODE_NOT_SUPPORTED0xF294AFX_IDP_SQL_DATA_TRUNCATED0xF295AFX_IDP_SQL_ROW_FETCH0xF296AFX_IDP_SQL_INCORRECT_ODBC0xF297 AFX_IDP_SQL_UPDATE_DELETE_FAILED0xF298 AFX_IDP_SQL_DYNAMIC_CURSOR_NOT_SUPPORTED0xF299 AFX_IDP_SQL_FIELD_NOT_FOUND0xF29A AFX_IDP_SQL_BOOKMARKS_NOT_SUPPORTED0xF29B AFX_IDP_SQL_BOOKMARKS_NOT_ENABLED0xF29CAFX_IDS_DELETED0xF29DAFX_IDP_DAO_ENGINE_INITIALIZATION0xF2B0AFX_IDP_DAO_DFX_BIND0xF2B1AFX_IDP_DAO_OBJECT_NOT_OPEN0xF2B2AFX_IDP_DAO_ROWTOOSHORT0xF2B3AFX_IDP_DAO_BADBINDINFO0xF2B4AFX_IDP_DAO_COLUMNUNAVAILABLE0xF2B5 AFX_IDS_HTTP_TITLE0xF2D1%AFX_IDS_HTTP_NO_TEXT0xF2D2&AFX_IDS_HTTP_BAD_REQUEST0xF2D3'AFX_IDS_HTTP_AUTH_REQUIRED0xF2D4(AFX_IDS_HTTP_FORBIDDEN0xF2D5)AFX_IDS_HTTP_NOT_FOUND0xF2D6*AFX_IDS_HTTP_SERVER_ERROR0xF2D7+AFX_IDS_HTTP_NOT_IMPLEMENTED0xF2D8,AFX_IDS_CHECKLISTBOX_UNCHECK0xF2E10AFX_IDS_CHECKLISTBOX_CHECK0xF2E21AFX_IDS_CHECKLISTBOX_MIXED0xF2E32AFX_IDC_LISTBOX1008AFX_IDC_CHANGE1019AFX_IDC_BROWSER102:AFX_IDC_PRINT_DOCNAME201=AFX_IDC_PRINT_PRINTERNAME202>AFX_IDC_PRINT_PORTNAME203?AFX_IDC_PRINT_PAGENUM204@ID_APPLY_NOW0x3021CID_WIZBACK0x3023DID_WIZNEXT0x3024EID_WIZFINISH0x3025FAFX_IDC_TAB_CONTROL0x3020GAFX_IDD_NEWTYPEDLG30721ZAFX_IDD_PRINTDLG30722[AFX_IDD_PREVIEW_TOOLBAR30723\AFX_IDD_INSERTOBJECT30724_AFX_IDD_CHANGEICON30725`AFX_IDD_CONVERT30726aAFX_IDD_PASTESPECIAL30727bAFX_IDD_EDITLINKS30728cAFX_IDD_FILEBROWSE30729dAFX_IDD_BUSY30730eAFX_IDD_OBJECTPROPERTIES30732gAFX_IDD_CHANGESOURCE30733hAFX_IDD_EMPTYDIALOG30734kAFX_IDC_CONTEXTHELP30977oAFX_IDC_MAGNIFY30978pAFX_IDC_SMALLARROWS30979qAFX_IDC_HSPLITBAR30980rAFX_IDC_VSPLITBAR30981sAFX_IDC_NODROPCRSR30982tAFX_IDC_TRACKNWSE30983uAFX_IDC_TRACKNESW30984vAFX_IDC_TRACKNS30985wAFX_IDC_TRACKWE30986xAFX_IDC_TRACK4WAY30987yAFX_IDC_MOVE4WAY30988zAFX_IDC_MOUSE_PAN_NW30998~AFX_IDC_MOUSE_PAN_N30999AFX_IDC_MOUSE_PAN_NE31000AFX_IDC_MOUSE_PAN_W31001AFX_IDC_MOUSE_PAN_HV31002AFX_IDC_MOUSE_PAN_E31003AFX_IDC_MOUSE_PAN_SW31004AFX_IDC_MOUSE_PAN_S31005AFX_IDC_MOUSE_PAN_SE31006AFX_IDC_MOUSE_PAN_HORZ31007AFX_IDC_MOUSE_PAN_VERT31008AFX_IDC_MOUSE_ORG_HORZ31009AFX_IDC_MOUSE_ORG_VERT31010AFX_IDC_MOUSE_ORG_HV31011AFX_IDC_MOUSE_MASK31012AFX_IDB_MINIFRAME_MENU30994AFX_IDB_CHECKLISTBOX_9530996AFX_IDR_PREVIEW_ACCEL30997AFX_IDI_STD_MDIFRAME31233AFX_IDI_STD_FRAME31234AFX_IDC_FONTPROP1000AFX_IDC_FONTNAMES1001AFX_IDC_FONTSTYLES1002AFX_IDC_FONTSIZES1003AFX_IDC_STRIKEOUT1004AFX_IDC_UNDERLINE1005AFX_IDC_SAMPLEBOX1006AFX_IDC_COLOR_BLACK1100AFX_IDC_COLOR_WHITE1101AFX_IDC_COLOR_RED1102AFX_IDC_COLOR_GREEN1103AFX_IDC_COLOR_BLUE1104AFX_IDC_COLOR_YELLOW1105AFX_IDC_COLOR_MAGENTA1106AFX_IDC_COLOR_CYAN1107AFX_IDC_COLOR_GRAY1108AFX_IDC_COLOR_LIGHTGRAY1109AFX_IDC_COLOR_DARKRED1110AFX_IDC_COLOR_DARKGREEN1111AFX_IDC_COLOR_DARKBLUE1112AFX_IDC_COLOR_LIGHTBROWN1113AFX_IDC_COLOR_DARKMAGENTA1114AFX_IDC_COLOR_DARKCYAN1115AFX_IDC_COLORPROP1116AFX_IDC_SYSTEMCOLORS1117AFX_IDC_PROPNAME1201AFX_IDC_PICTURE1202AFX_IDC_BROWSE1203AFX_IDC_CLEAR1204AFX_IDD_PROPPAGE_COLOR32257AFX_IDD_PROPPAGE_FONT32258AFX_IDD_PROPPAGE_PICTURE32259AFX_IDB_TRUETYPE32384AFX_IDS_PROPPAGE_UNKNOWN0xFE01AFX_IDS_COLOR_DESKTOP0xFE04AFX_IDS_COLOR_APPWORKSPACE0xFE05AFX_IDS_COLOR_WNDBACKGND0xFE06AFX_IDS_COLOR_WNDTEXT0xFE07AFX_IDS_COLOR_MENUBAR0xFE08AFX_IDS_COLOR_MENUTEXT0xFE09AFX_IDS_COLOR_ACTIVEBAR0xFE0AAFX_IDS_COLOR_INACTIVEBAR0xFE0BAFX_IDS_COLOR_ACTIVETEXT0xFE0CAFX_IDS_COLOR_INACTIVETEXT0xFE0DAFX_IDS_COLOR_ACTIVEBORDER0xFE0EAFX_IDS_COLOR_INACTIVEBORDER0xFE0FAFX_IDS_COLOR_WNDFRAME0xFE10AFX_IDS_COLOR_SCROLLBARS0xFE11AFX_IDS_COLOR_BTNFACE0xFE12AFX_IDS_COLOR_BTNSHADOW0xFE13AFX_IDS_COLOR_BTNTEXT0xFE14AFX_IDS_COLOR_BTNHIGHLIGHT0xFE15AFX_IDS_COLOR_DISABLEDTEXT0xFE16AFX_IDS_COLOR_HIGHLIGHT0xFE17AFX_IDS_COLOR_HIGHLIGHTTEXT0xFE18AFX_IDS_REGULAR0xFE19AFX_IDS_BOLD0xFE1AAFX_IDS_ITALIC0xFE1BAFX_IDS_BOLDITALIC0xFE1CAFX_IDS_SAMPLETEXT0xFE1DAFX_IDS_DISPLAYSTRING_FONT0xFE1EAFX_IDS_DISPLAYSTRING_COLOR0xFE1FAFX_IDS_DISPLAYSTRING_PICTURE0xFE20AFX_IDS_PICTUREFILTER0xFE21AFX_IDS_PICTYPE_UNKNOWN0xFE22AFX_IDS_PICTYPE_NONE0xFE23AFX_IDS_PICTYPE_BITMAP0xFE24AFX_IDS_PICTYPE_METAFILE0xFE25AFX_IDS_PICTYPE_ICON0xFE26AFX_IDS_COLOR_PPG0xFE28AFX_IDS_COLOR_PPG_CAPTION0xFE29AFX_IDS_FONT_PPG0xFE2AAFX_IDS_FONT_PPG_CAPTION0xFE2BAFX_IDS_PICTURE_PPG0xFE2CAFX_IDS_PICTURE_PPG_CAPTION0xFE2DAFX_IDS_PICTUREBROWSETITLE0xFE30AFX_IDS_BORDERSTYLE_00xFE31AFX_IDS_BORDERSTYLE_10xFE32AFX_IDS_VERB_EDIT0xFE40AFX_IDS_VERB_PROPERTIES0xFE41AFX_IDP_PICTURECANTOPEN0xFE83AFX_IDP_PICTURECANTLOAD0xFE84AFX_IDP_PICTURETOOLARGE0xFE85AFX_IDP_PICTUREREADFAILED0xFE86AFX_IDP_E_ILLEGALFUNCTIONCALL0xFEA0 AFX_IDP_E_OVERFLOW0xFEA1 AFX_IDP_E_OUTOFMEMORY0xFEA2 AFX_IDP_E_DIVISIONBYZERO0xFEA3 AFX_IDP_E_OUTOFSTRINGSPACE0xFEA4AFX_IDP_E_OUTOFSTACKSPACE0xFEA5AFX_IDP_E_BADFILENAMEORNUMBER0xFEA6AFX_IDP_E_FILENOTFOUND0xFEA7AFX_IDP_E_BADFILEMODE0xFEA8AFX_IDP_E_FILEALREADYOPEN0xFEA9AFX_IDP_E_DEVICEIOERROR0xFEAAAFX_IDP_E_FILEALREADYEXISTS0xFEABAFX_IDP_E_BADRECORDLENGTH0xFEACAFX_IDP_E_DISKFULL0xFEADAFX_IDP_E_BADRECORDNUMBER0xFEAEAFX_IDP_E_BADFILENAME0xFEAFAFX_IDP_E_TOOMANYFILES0xFEB0AFX_IDP_E_DEVICEUNAVAILABLE0xFEB1AFX_IDP_E_PERMISSIONDENIED0xFEB2AFX_IDP_E_DISKNOTREADY0xFEB3AFX_IDP_E_PATHFILEACCESSERROR0xFEB4AFX_IDP_E_PATHNOTFOUND0xFEB5AFX_IDP_E_INVALIDPATTERNSTRING0xFEB6 AFX_IDP_E_INVALIDUSEOFNULL0xFEB7!AFX_IDP_E_INVALIDFILEFORMAT0xFEB8"AFX_IDP_E_INVALIDPROPERTYVALUE0xFEB9#AFX_IDP_E_INVALIDPROPERTYARRAYINDEX0xFEBA$AFX_IDP_E_SETNOTSUPPORTEDATRUNTIME0xFEBB%AFX_IDP_E_SETNOTSUPPORTED0xFEBC&AFX_IDP_E_NEEDPROPERTYARRAYINDEX0xFEBD'AFX_IDP_E_SETNOTPERMITTED0xFEBE(AFX_IDP_E_GETNOTSUPPORTEDATRUNTIME0xFEBF)AFX_IDP_E_GETNOTSUPPORTED0xFEC0*AFX_IDP_E_PROPERTYNOTFOUND0xFEC1+AFX_IDP_E_INVALIDCLIPBOARDFORMAT0xFEC2,AFX_IDP_E_INVALIDPICTURE0xFEC3-AFX_IDP_E_PRINTERERROR0xFEC4.AFX_IDP_E_CANTSAVEFILETOTEMP0xFEC5/AFX_IDP_E_SEARCHTEXTNOTFOUND0xFEC60AFX_IDP_E_REPLACEMENTSTOOLONG0xFEC71!!$HWB0  c:\W\treesheets\treesheets\resource.h c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\atlmfc\include\afxres.h c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\atlmfc\include\winres.he icon1.ico}$HWB0 TEXTINCLUDE1$TEXTINCLUDE2$TEXTINCLUDE3$14101tsiconc:\W\treesheets\treesheets\boar.rc9$$$HWB0 'DHWB  TEXTINCLUDE0 TEXTINCLUDE0 ,TEXTINCLUDE0 (P  "e0 ?HWB0 EHWB0 }GHWB0 treesheets-1.0.2/treesheets/boar.rc000066400000000000000000000031261352107072600173160ustar00rootroot00000000000000// Microsoft Visual C++ generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. tsicon ICON "icon1.ico" //#include "wx/msw/wx.rc" #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED treesheets-1.0.2/treesheets/dot3inst.bmp000066400000000000000000000623321352107072600203200ustar00rootroot00000000000000BMd6(9  ֎欬񯯯榦եؙ000   $$$ &&& *** ::::::nnn(((WWWWWWYYY[[[ lll jjjjjjZZZ}}} '''YYY"""UUUpppddd aaa QQQ***(((YYYdddhhh'''XXXbbbBBB444,,,;;; yyyCCC%%% 888QQQiii999'''WWWhhhCCC444...888 777iii  ///zzzggg(((ZZZ999888uuu[[[ $$$vvv\\\+++___777***WWWyyybbbmmm䨨蔔ϛ (((xxx```000333+++555 $$$$$$~~~ %%% VVV 999FFF;;;ĠԎФ贴ÜЎʿǷowMoDmAn@nArH_ǙhmBqDxMzP{R{R{QyOtIm>yQȿrJrD|Q{RxOxNxMxMxMxNzP|SwLl@|걱ttt°nBxM{RxNxNxNxNxNxNxNxNxMxNzQ{Rm?xלzzzzzz톆111ωsJxMzQxNxNxNzN|SzRzP{S{QxNxNxMyN{Tl>ZZZXXXEEEooojpD{RxNxNxNzQwMn?n@oDm?sG{QxNxNxNyOyOqE???vvvƴnB{QxNxNxMzPrFxP°ƷboByOxNxNxM{QrCv  {{{nsGzQxNxN{PrFi𶫕qD{OxNxNxNzNuL333*** EEECCC>>>։vOyNxOxNyPtIaoCzQxNxNzQoA tttllloDzOxNxNzPpEλ^uIzPxNzQrCv===***666MMMiii333oBzPxNzPtHcm?{RxNzOuHa  333lllvvviiipDzQxM{RnBϾpCzQxNyPvJ}V+++>>> ///kkk```pDzQxN{RnALjqGyOxOzPwJ}U666kkkeeesssoCzQxN{RpC{ȸnA{QxNyPvIX  \\\iiinB{OxNyOuK~VnA{QxNzPsHfsss::: ȺpF{OxNxNzQnCڍxOxLyOxNzQqDVVV"""III(((''']]]VVVZZZGGGccc|SwMyNxNyNyMvNorFzPxNxNzRoCǽ NNN[[[WWWXXXXXXXXXZZZRRRpD{QxMxNzOvKvQżʜiqFzQxNxNxNxLzSYYYVVVxxẍrEyOxOxNxNzNxLnB]}kqGrFzQxNxNwM|Rn@777 222 lA|SxMxOxOyN|QtHoBnAqEzO{PxNxNxNzQuJ}VIIILLL___%%%zzz蕃`sD|RxNxNxNwOzQ|R|S{RyNxNxNxNzPyPoAʻAAA ???Փ}VpG|SzPxMxMxMwMxMxMxMxNyO|SwNn@֛el@vL|S{RyPyOxOyOzQ|RzOoBuKǿ﷫yPm>pEuIxLwMvKrFn@pEu¸p~ZwNxJ|Tgtreesheets-1.0.2/treesheets/icon1.ico000066400000000000000000000020661352107072600175540ustar00rootroot00000000000000(& N( wwwwwwwwwwwwpwwwwwwwwwwww( @wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwtreesheets-1.0.2/treesheets/resource.h000066400000000000000000000006541352107072600200500ustar00rootroot00000000000000//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by boar.rc // #define tsicon 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif treesheets-1.0.2/treesheets/treesheets-16x16.png000066400000000000000000000003051352107072600215050ustar00rootroot00000000000000PNG  IHDR:gAMA a cHRMz&u0`:pQ<bKGD̿BIDATӍA RÁ؞VBYeDUz[!@Mxt{xuw?iIENDB`treesheets-1.0.2/treesheets/treesheets-32x32.png000066400000000000000000000004111352107072600214770ustar00rootroot00000000000000PNG  IHDR V%(gAMA a cHRMz&u0`:pQ<bKGD̿IDAT8˭ @ C_}4DiX@N2RNۣ3Z.XpM<ď0C&_Y`k+ Debug Win32 Debug x64 Release Win32 Release x64 treesheets {A4C3C894-079D-4C11-9AE1-CA41875A4AFC} boar Win32Proj 10.0 Application MultiByte false v142 Application MultiByte false v141_xp Application MultiByte v142 Application MultiByte v141_xp <_ProjectFileVersion>10.0.30319.1 ..\TS\ ..\TS\ $(Configuration)\ $(Configuration)\ true true ..\TS\ ..\TS\ $(Configuration)\ $(Configuration)\ false false TreeSheets TreeSheets64 TreeSheetsDBG TreeSheets64DBG Disabled ..\wxWidgets\include;..\wxWidgets\lib\vc_lib\mswud;%(AdditionalIncludeDirectories) WINVER=0x0400;wxUSE_GUI=1;__WXDEBUG__;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebug Level3 ProgramDatabase 4127;4512;4201;4146;4456;4702 /std:c++latest %(AdditionalOptions) C:\Program Files\wxWidgets-2.8.8\include\;C:\Program Files\wxWidgets-2.8.8\lib\vc_lib\mswd;%(AdditionalIncludeDirectories) ../build/treesheets/language/languageDBG.lib;libcmtd.lib;libcpmtd.lib;comctl32.lib;rpcrt4.lib;winmm.lib;advapi32.lib;wsock32.lib;wxmsw31ud_core.lib;wxbase31ud.lib;opengl32.lib;wxmsw31ud_gl.lib;wxmsw31ud_adv.lib;wxpngd.lib;wxzlibd.lib;wxjpegd.lib;wxtiffd.lib;wxbase31ud_xml.lib;wxexpatd.lib;wxmsw31ud_aui.lib;%(AdditionalDependencies) ..\TS\TreeSheetsDBG.exe ..\wxWidgets\lib\vc_lib\;%(AdditionalLibraryDirectories) false true Windows false MachineX86 Disabled ..\wxwidgets294\include;..\wxwidgets294\lib\vc_lib\mswud;%(AdditionalIncludeDirectories) WINVER=0x0400;wxUSE_GUI=1;__WXDEBUG__;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebug Level3 ProgramDatabase 4127;4512;4201;4146;4456;4702 /std:c++latest %(AdditionalOptions) C:\Program Files\wxWidgets-2.8.8\include\;C:\Program Files\wxWidgets-2.8.8\lib\vc_lib\mswd;%(AdditionalIncludeDirectories) ../build/treesheets/language/language64DBG.lib;libcmtd.lib;libcpmtd.lib;comctl32.lib;rpcrt4.lib;winmm.lib;advapi32.lib;wsock32.lib;wxmsw29ud_core.lib;wxbase29ud.lib;opengl32.lib;wxmsw29ud_gl.lib;wxmsw29ud_adv.lib;wxpngd.lib;wxzlibd.lib;wxjpegd.lib;wxtiffd.lib;wxbase29ud_xml.lib;wxexpatd.lib;wxmsw29ud_aui.lib;%(AdditionalDependencies) ..\TS\TreeSheets64DBG.exe ..\wxwidgets294\lib\vc_lib64\;%(AdditionalLibraryDirectories) true true Windows false false ..\wxWidgets\include;..\wxWidgets\lib\vc_lib\mswu;%(AdditionalIncludeDirectories) WINVER=0x0400;wxUSE_GUI=1;%(PreprocessorDefinitions) MultiThreaded Level3 ProgramDatabase 4127;4512;4201;4146;4456;4702 /std:c++latest %(AdditionalOptions) C:\Program Files\wxWidgets-2.8.8\include\;C:\Program Files\wxWidgets-2.8.8\lib\vc_lib\msw;%(AdditionalIncludeDirectories) ../build/treesheets/language/language.lib;libcmt.lib;libcpmt.lib;comctl32.lib;rpcrt4.lib;winmm.lib;advapi32.lib;wsock32.lib;wxmsw31u_core.lib;wxbase31u.lib;wxmsw31u_adv.lib;wxpng.lib;wxzlib.lib;wxjpeg.lib;wxtiff.lib;wxbase31u_xml.lib;wxexpat.lib;wxmsw31u_aui.lib;%(AdditionalDependencies) ..\TS\TreeSheets.exe ..\wxWidgets\lib\vc_lib\;%(AdditionalLibraryDirectories) false %(IgnoreSpecificDefaultLibraries) true Windows true true false MachineX86 false ..\wxwidgets294\include;..\wxwidgets294\lib\vc_lib\mswu;%(AdditionalIncludeDirectories) WINVER=0x0400;wxUSE_GUI=1;%(PreprocessorDefinitions) MultiThreaded Level3 ProgramDatabase 4127;4512;4201;4146;4456;4702 /std:c++latest %(AdditionalOptions) C:\Program Files\wxWidgets-2.8.8\include\;C:\Program Files\wxWidgets-2.8.8\lib\vc_lib\msw;%(AdditionalIncludeDirectories) ../build/treesheets/language/language64.lib;libcmt.lib;libcpmt.lib;comctl32.lib;rpcrt4.lib;winmm.lib;advapi32.lib;wsock32.lib;wxmsw29u_core.lib;wxbase29u.lib;wxmsw29u_adv.lib;wxpng.lib;wxzlib.lib;wxjpeg.lib;wxtiff.lib;wxbase29u_xml.lib;wxexpat.lib;wxmsw29u_aui.lib;%(AdditionalDependencies) ..\TS\TreeSheets64.exe ..\wxwidgets294\lib\vc_lib64\;%(AdditionalLibraryDirectories) true %(IgnoreSpecificDefaultLibraries) true Windows true true false NotUsing NotUsing NotUsing NotUsing ..\lobster\src;..\lobster\include ..\lobster\src;..\lobster\include ..\lobster\src;..\lobster\include ..\lobster\src;..\lobster\include ignore.h ignore.h ignore.h ignore.h false Use Use Use Use TurnOffAllWarnings TurnOffAllWarnings TurnOffAllWarnings TurnOffAllWarnings TurnOffAllWarnings TurnOffAllWarnings TurnOffAllWarnings TurnOffAllWarnings Create Create Create Create treesheets-1.0.2/treesheets/treesheets.vcxproj.filters000066400000000000000000000066251352107072600233130ustar00rootroot00000000000000 {f0d0d8ce-94f8-4b97-b0e9-03d326d1cd6c} {5287dd75-05fd-4552-9817-9991f1f308c9} {6ddefa42-3c63-43b7-907a-1bb8eb3fd4e7} {0aa8c6f3-6433-4862-a6a0-6dafbe5877bf} {9b8cdb4f-6231-4699-9022-2cefe3c2d3eb} {ce2a8986-7979-4f28-a5b2-4e94d938f4a6} std std std\StackWalker std\StackWalker script std std treesheets treesheets treesheets treesheets treesheets treesheets treesheets wxwidgets wxwidgets wxwidgets wxwidgets wxwidgets std\StackWalker std\StackWalker script script data treesheets-1.0.2/treesheets/treesheets.vcxproj.user000066400000000000000000000011231352107072600226050ustar00rootroot00000000000000 WindowsLocalDebugger WindowsLocalDebugger treesheets-1.0.2/treesheets/tsinst.bmp000066400000000000000000000760021352107072600200740ustar00rootroot00000000000000BM|6(9  ¶ɩŪxxx tttxxx %%%$$$HHHggg 666999yyy nnn 222$$$cccaaafffYYYTTT 111PPPjjj###iii[[[qqqfffccciii NNNZZZWWWXXXXXXZZZPPP~~~ UUUYYYXXXXXXXXXZZZNNN jjjbbb&&&lll!!!]]]WWWXXXXXXXXXZZZOOO___---^^^WWWXXXXXXXXXYYYTTT \\\ī444eeebbbhhh/// ,,, )))SSS%%%kkkiii^^^VVVpppeeebbbhhh111 ***hhh%%%kkkddd^^^???eeebbbhhh ***%%%jjjddd^^^eeebbbhhh___UUU ,,,&&&mmmiii^^^eeebbbhhh///uuuooohhh ]]]qqqmmmnnnpppeee gggpppnnnnnnpppeee===888 eeepppmmmnnnmmmrrr%%%$$$sssmmmnnnnnnooojjj^^^222tttmmmnnnooolllvvv """eeebbbhhhWWW WWW;;;^^^ uuueeebbbhhh777333666&&&HHH oooeee &&&^^^""":::llleeebbbhhh}}} $$$WWW\\\YYY^^^yyyzzz%%%eeebbbhhhvvvppp +++tttSSS&&&mmmhhh^^^...NNNeeebbbhhhfff )))SSSHHH%%%jjjddd^^^eeebbbhhh555 ---YYY%%%kkkkkk^^^ (((ssseeebbbiiiBBB777 &&&kkk222___FFF򙙙 yyyeee\\\VVVeeebbbhhh 111QQQ%%%jjj]]]  yyyeeebbblll ??? 999 ZZZ$$$)))nnn bbb   *** dddaaahhhfffqqq333ccclllsssqqqxxx***'''wwwqqqtttkkkaaaYYYʩ000###ZZZFFFmmm寯ńddd___fffʿ