aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff McGlynn <jwmcglynn@google.com>2017-11-16 17:40:22 -0800
committerJeff McGlynn <jwmcglynn@google.com>2017-11-16 17:40:22 -0800
commitd16a940067fbc191364fc1d3e2a2d907209c9320 (patch)
treef41c4b7a0660ee902ec007954d705c5659117219
parent2de00aa4ef5314cb202427175e85f1a9f9f8bd89 (diff)
parentb434c2497fcb52aa1497b84aa8aeb12bb590492d (diff)
downloadtinyobjloader-emu-2.8-release.tar.gz
-rw-r--r--.bintray.in37
-rw-r--r--.clang-format7
-rw-r--r--.drone.yml10
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml81
-rw-r--r--CMakeLists.txt146
-rw-r--r--LICENSE21
-rw-r--r--README.md269
-rw-r--r--appveyor.yml22
-rw-r--r--build.ninja53
-rwxr-xr-xdeps/cpplint.py6323
-rw-r--r--examples/callback_api/Makefile2
-rw-r--r--examples/callback_api/main.cc166
-rw-r--r--examples/obj_sticher/obj_sticher.cc110
-rw-r--r--examples/obj_sticher/obj_writer.cc176
-rw-r--r--examples/obj_sticher/obj_writer.h9
-rw-r--r--examples/obj_sticher/premake4.lua38
-rw-r--r--examples/viewer/README.md42
-rw-r--r--examples/viewer/premake4.lua44
-rw-r--r--examples/viewer/stb_image.h6755
-rw-r--r--examples/viewer/trackball.cc292
-rw-r--r--examples/viewer/trackball.h75
-rw-r--r--examples/viewer/viewer.cc713
-rw-r--r--examples/voxelize/Makefile2
-rw-r--r--examples/voxelize/README.md5
-rw-r--r--examples/voxelize/main.cc74
-rw-r--r--examples/voxelize/voxelizer.h764
-rw-r--r--experimental/README.md16
-rw-r--r--experimental/lfpAlloc/Allocator.hpp89
-rw-r--r--experimental/lfpAlloc/ChunkList.hpp116
-rw-r--r--experimental/lfpAlloc/LICENSE21
-rw-r--r--experimental/lfpAlloc/Pool.hpp48
-rw-r--r--experimental/lfpAlloc/PoolDispatcher.hpp79
-rw-r--r--experimental/lfpAlloc/Utils.hpp20
-rw-r--r--experimental/premake5.lua94
-rw-r--r--experimental/tinyobj_loader_opt.h1684
-rw-r--r--experimental/trackball.cc292
-rw-r--r--experimental/trackball.h75
-rw-r--r--experimental/viewer.cc748
-rw-r--r--images/rungholt.jpgbin0 -> 96018 bytes
-rw-r--r--images/sanmugel.pngbin0 -> 192538 bytes
-rw-r--r--jni/Android.mk12
-rw-r--r--jni/Application.mk2
-rw-r--r--jni/Makefile2
-rw-r--r--jni/README1
-rw-r--r--loader_example.cc419
-rw-r--r--models/catmark_torus_creases0.obj101
-rw-r--r--models/cornell_box.mtl24
-rw-r--r--models/cornell_box.obj145
-rw-r--r--models/cornell_box_multimaterial.obj146
-rw-r--r--models/cube-vertexcol.obj31
-rw-r--r--models/cube.mtl24
-rw-r--r--models/cube.obj31
-rw-r--r--models/issue-138.mtl23
-rw-r--r--models/issue-138.obj51
-rw-r--r--models/issue-140-zero-face-idx.mtl2
-rw-r--r--models/issue-140-zero-face-idx.obj17
-rw-r--r--models/issue-92.mtl6
-rw-r--r--models/issue-92.obj7
-rw-r--r--models/issue-95-2.mtl5
-rw-r--r--models/issue-95-2.obj7
-rw-r--r--models/issue-95.mtl5
-rw-r--r--models/issue-95.obj7
-rw-r--r--models/map-bump.mtl10
-rw-r--r--models/map-bump.obj817
-rw-r--r--models/missing_material_file.obj145
-rw-r--r--models/mtllib-multiple-files-issue-112.mtl6
-rw-r--r--models/mtllib-multiple-files-issue-112.obj7
-rw-r--r--models/no_material.obj133
-rw-r--r--models/norm-texopt.mtl7
-rw-r--r--models/norm-texopt.obj7
-rw-r--r--models/pbr-mat-ext.mtl19
-rw-r--r--models/pbr-mat-ext.obj10
-rw-r--r--models/refl.mtl25
-rw-r--r--models/refl.obj32
-rw-r--r--models/test-nan.obj145
-rw-r--r--models/texture-filename-with-whitespace.mtl28
-rw-r--r--models/texture-filename-with-whitespace.obj31
-rw-r--r--models/texture-options-issue-85.mtl36
-rw-r--r--models/texture-options-issue-85.obj7
-rw-r--r--models/tr-and-d-issue-43.mtl13
-rw-r--r--models/tr-and-d-issue-43.obj817
-rw-r--r--models/usemtl-issue-104.obj30
-rw-r--r--models/usemtl-issue-68.mtl9
-rw-r--r--models/usemtl-issue-68.obj817
-rw-r--r--premake4.lua29
-rw-r--r--python/TODO.md2
-rw-r--r--python/cornell_box_multimaterial_output.json581
-rw-r--r--python/howto.py9
-rw-r--r--python/main.cpp206
-rw-r--r--python/setup.py13
-rw-r--r--tests/Makefile16
-rw-r--r--tests/README.md37
-rw-r--r--tests/catch.hpp10445
-rw-r--r--tests/config-msvc.py52
-rw-r--r--tests/config-posix.py53
-rwxr-xr-xtests/kuroga.py312
-rw-r--r--tests/tester.cc741
-rw-r--r--tests/vcbuild.bat4
-rw-r--r--tiny_obj_loader.cc2
-rw-r--r--tiny_obj_loader.h2105
-rw-r--r--tinyobjloader-config.cmake.in9
-rw-r--r--tinyobjloader.pc.in15
-rwxr-xr-xtools/travis_postbuild.sh12
-rw-r--r--tools/windows/premake5.exebin0 -> 912896 bytes
-rw-r--r--vcsetup.bat1
-rw-r--r--wercker.yml10
107 files changed, 38393 insertions, 0 deletions
diff --git a/.bintray.in b/.bintray.in
new file mode 100644
index 0000000..4336d65
--- /dev/null
+++ b/.bintray.in
@@ -0,0 +1,37 @@
+{
+ /* Bintray package information.
+ In case the package already exists on Bintray, only the name, repo and subject
+ fields are mandatory. */
+
+ "package": {
+ "name": "releases", // Bintray package name
+ "repo": "tinyobjloader", // Bintray repository name
+ "subject": "syoyo" // Bintray subject (user or organization)
+ },
+
+ /* Package version information.
+ In case the version already exists on Bintray, only the name fields is mandatory. */
+
+ "version": {
+ "name": "@VERSION@",
+ "desc": "@VERSION@",
+ "released": "@DATE@",
+ "vcs_tag": "@VERSION@",
+ "gpgSign": false
+ },
+
+ /* Configure the files you would like to upload to Bintray and their upload path.
+ You can define one or more groups of patterns.
+ Each group contains three patterns:
+
+ includePattern: Pattern in the form of Ruby regular expression, indicating the path of files to be uploaded to Bintray.
+ excludePattern: Optional. Pattern in the form of Ruby regular expression, indicating the path of files to be removed from the list of files specified by the includePattern.
+ uploadPattern: Upload path on Bintray. The path can contain symbols in the form of $1, $2,... that are replaced with capturing groups defined in the include pattern.
+
+ Note: Regular expressions defined as part of the includePattern property must be wrapped with brackets. */
+
+ "files":
+ [ {"includePattern": "dist/(.*)", "uploadPattern": "$1"} ],
+ "publish": true
+}
+
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..74210b0
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,7 @@
+---
+BasedOnStyle: Google
+IndentWidth: 2
+TabWidth: 2
+UseTab: Never
+BreakBeforeBraces: Attach
+Standard: Cpp03
diff --git a/.drone.yml b/.drone.yml
new file mode 100644
index 0000000..971557f
--- /dev/null
+++ b/.drone.yml
@@ -0,0 +1,10 @@
+image: syoyo/ubu-dev
+script:
+ - curl -L -o premake4 https://github.com/syoyo/orebuildenv/blob/master/build/linux/bin/premake4?raw=true
+ - chmod +x ./premake4
+ - ./premake4 gmake
+ - make
+notify:
+ email:
+ recipients:
+ - syoyo@lighttransport.com
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..493e888
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+#Common folder for building objects
+build/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..06b2d75
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,81 @@
+language: cpp
+sudo: required
+matrix:
+ include:
+ - addons: &1
+ apt:
+ sources:
+ - george-edison55-precise-backports
+ - ubuntu-toolchain-r-test
+ - llvm-toolchain-precise-3.7
+ packages:
+ - cmake
+ - cmake-data
+ - ninja-build
+ - g++-4.9
+ - clang-3.7
+ compiler: clang
+ env: DEPLOY_BUILD=1 COMPILER_VERSION=3.7 BUILD_TYPE=Debug
+ - addons: *1
+ compiler: clang
+ env: COMPILER_VERSION=3.7 BUILD_TYPE=Release
+ - addons: &2
+ apt:
+ sources:
+ - george-edison55-precise-backports
+ - ubuntu-toolchain-r-test
+ packages:
+ - cmake
+ - cmake-data
+ - ninja-build
+ - g++-4.9
+ compiler: gcc
+ env: COMPILER_VERSION=4.9 BUILD_TYPE=Debug
+ - addons: *2
+ compiler: gcc
+ env: COMPILER_VERSION=4.9 BUILD_TYPE=Release
+ - addons: *1
+ compiler: clang
+ env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug
+before_install:
+- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi
+- if [ -n "$REPORT_COVERAGE" ]; then sudo apt-get update python; fi
+- if [ -n "$REPORT_COVERAGE" ]; then sudo apt-get install python-dev libffi-dev libssl-dev; fi
+- if [ -n "$REPORT_COVERAGE" ]; then sudo pip install --upgrade pip; fi
+- if [ -n "$REPORT_COVERAGE" ]; then CXX=g++ pip install --user requests[security]; fi
+- if [ -n "$REPORT_COVERAGE" ]; then CXX=g++ pip install --user cpp-coveralls; fi
+script:
+- cd tests
+- make check
+- if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r .. -e examples -e tools -e
+ jni -e python -e images -E ".*CompilerId.*" -E ".*feature_tests.*" ; fi
+- cd ..
+- rm -rf dist
+- mkdir dist
+- cp tiny_obj_loader.h dist/
+
+before_deploy:
+ - echo "Creating description file for bintray."
+ - ./tools/travis_postbuild.sh
+
+deploy:
+ - provider: bintray
+ file: ".bintray.json"
+ user: "syoyo"
+ key:
+ secure: W4F1VZcDcVOMe8Ymvo0bHery/JSmVhadl1NgAnGus6o7zVw7ChElKA1ho/NtqUbtoW8o1qUKMJdLQeh786jolocZJEJlns9JZ5FCet6H2b3kITfUa4GR5T11V/ZYwL3SajW8vZ1xu5UrpP5HHgFMYtxb1MFrNLDI60sh0RnyV/qFFBnCJGZPagF/M1mzbJeDml5xK5lShH0r8QpH+7MeQ1J8ungEyJ7UCyr1ao8gY9eq1/05IpHR9vri/d48EXQWHbqtI8EwCc7064oCYQGyYcLsD4yPEokwrdelkCvDquSpJLmbJENfZCc4vZGXsykjnQ8+gltJomBAivQFB9vc06ETEJssMzitbrfEZUrqFwZj/HZM7CYGXfGQWltL828SppCjsuWrgQ/VYXM5UgRpmhlxbqnuyxnYvKZ9EDW4+EnMkOmIl7WSDovp8E/4CZ0ghs+YyFS4SrgeqFCXS8bvxrkDUUPSipHuGBOt02fRnccKzU+3zU6Q5fghyLczz4ZtnOdk+Niz/njyF0SZfPYTUgb3GzAJ8Su6kvWJCAGdedON3n1F/TtybCE2dIdATxaO2uFQbwYjSOCiq209oCJ7MrsQZibRsa5a9YXyjlLkPxwOeVwo8wJyJclqWswIkhdSO8xvTnkwESv4yLzLutEOlBVlQbJzpyuS6vx0yHOYkwc=
+ all_branches: true
+ on:
+ repo: syoyo/tinyobjloader
+ condition: -n "$DEPLOY_BUILD"
+ tags: true
+ skip_cleanup: true
+ - provider: releases
+ api_key:
+ secure: AsXameK4GJn6h6wMmDrKTr7q/o9EI7hX7zWg1W6VaFBQKfkBvOmjJolWimjl6HMoRZ1NpMmK5GDm3zBlTUeABtgVBIyNWgE9vWS39ff6D5iQKcgScFsJkyILt0GikBqbN2pLGQ2t/M1Qh6n1sEIfzqekiCcF5Qvy5yYlYvHtaRGV02QeYAej/xx15/9SMuKTncHhjf63ClYPu8ODid7QUegJUvlQUeXoPsBDbaXMH2uDWoBWF7etX7G2Iob4NE8GX+ZP6dj+Ogi7p4HXThK650mzLL/pUl584EjjY/vePqx0cFhtpiRwvrW8SNPI1aJ1Phwa1enLRUgfS3bnkwQAMw/SCXSK2lnCvkUAXyTgpG03HWrZURj4vhEPXc7qHooO+dsfmi+JanYLaSDyrGpgQznLGjCMnVATimry0KxSufUY8Wt72Wh+nf7N0IgTUCjl32sWnQd/MRZPkxFuaf1h7r9RoH9KZY0yIOV09gABEFCGrOIZA2FcyhC2G26Bc4zyNrfMFpZ2DI76qdcWNdJGkRkpxtH9sGU8JgZu6Em2f1e6+SLgkBsPxbhRk5PwdhA9AXE2p9PmQqhO3jJKusGBZSoHAF7TlwagRY2J01yJxF7ge6zG9U8QuBqs1bB1zdnE34fHWOgs4st3inC+oBDOhvnEg1Nm/qeYVWMBzpwclSg=
+ file: tiny_obj_loader.h
+ all_branches: true
+ on:
+ repo: syoyo/tinyobjloader
+ tags: true
+ skip_cleanup: true
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..acfcd3a
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,146 @@
+#Tiny Object Loader Cmake configuration file.
+#This configures the Cmake system with multiple properties, depending
+#on the platform and configuration it is set to build in.
+project(tinyobjloader)
+cmake_minimum_required(VERSION 2.8.11)
+set(TINYOBJLOADER_SOVERSION 1)
+set(TINYOBJLOADER_VERSION 1.0.4)
+
+#optional double precision support
+option(TINYOBJLOADER_USE_DOUBLE "Build library with double precision instead of single (float)" OFF)
+
+if(TINYOBJLOADER_USE_DOUBLE)
+ add_definitions(-DTINYOBJLOADER_USE_DOUBLE)
+ set(LIBRARY_NAME ${PROJECT_NAME}_double)
+else()
+ set(LIBRARY_NAME ${PROJECT_NAME})
+endif()
+
+
+#Folder Shortcuts
+set(TINYOBJLOADEREXAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples)
+
+set(tinyobjloader-Source
+ ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.cc
+ )
+
+set(tinyobjloader-Example-Source
+ ${CMAKE_CURRENT_SOURCE_DIR}/loader_example.cc
+ )
+
+set(tinyobjloader-examples-objsticher
+ ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.h
+ ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.cc
+ ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_sticher.cc
+ )
+
+#Install destinations
+include(GNUInstallDirs)
+
+set(TINYOBJLOADER_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}/cmake)
+set(TINYOBJLOADER_DOC_DIR ${CMAKE_INSTALL_DOCDIR})
+set(TINYOBJLOADER_INCLUDE_DIR ${CMAKE_INSTALL_INCLUDEDIR})
+set(TINYOBJLOADER_LIBRARY_DIR ${CMAKE_INSTALL_LIBDIR})
+set(TINYOBJLOADER_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+set(TINYOBJLOADER_RUNTIME_DIR ${CMAKE_INSTALL_BINDIR})
+
+option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Example Loader Application" OFF)
+option(TINYOBJLOADER_COMPILATION_SHARED "Build as shared library" OFF)
+
+if(TINYOBJLOADER_COMPILATION_SHARED)
+ add_library(${LIBRARY_NAME} SHARED ${tinyobjloader-Source})
+ set_target_properties(${LIBRARY_NAME} PROPERTIES
+ SOVERSION ${TINYOBJLOADER_SOVERSION}
+ )
+else()
+ add_library(${LIBRARY_NAME} STATIC ${tinyobjloader-Source})
+endif()
+
+set_target_properties(${LIBRARY_NAME} PROPERTIES VERSION ${TINYOBJLOADER_VERSION})
+
+target_include_directories(${LIBRARY_NAME} INTERFACE
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
+ $<INSTALL_INTERFACE:${TINYOBJLOADER_INCLUDE_DIR}>
+ )
+
+export(TARGETS ${LIBRARY_NAME} FILE ${PROJECT_NAME}-targets.cmake)
+
+if(TINYOBJLOADER_BUILD_TEST_LOADER)
+ add_executable(test_loader ${tinyobjloader-Example-Source})
+ target_link_libraries(test_loader ${LIBRARY_NAME})
+endif()
+
+option(TINYOBJLOADER_BUILD_OBJ_STICHER "Build OBJ Sticher Application" OFF)
+if(TINYOBJLOADER_BUILD_OBJ_STICHER)
+ add_executable(obj_sticher ${tinyobjloader-examples-objsticher})
+ target_link_libraries(obj_sticher ${LIBRARY_NAME})
+
+ install(TARGETS
+ obj_sticher
+ DESTINATION
+ ${TINYOBJLOADER_RUNTIME_DIR}
+ )
+endif()
+
+#Write CMake package config files
+include(CMakePackageConfigHelpers)
+
+configure_package_config_file(
+ ${PROJECT_NAME}-config.cmake.in
+ ${LIBRARY_NAME}-config.cmake
+ INSTALL_DESTINATION
+ ${TINYOBJLOADER_CMAKE_DIR}
+ PATH_VARS
+ TINYOBJLOADER_INCLUDE_DIR
+ TINYOBJLOADER_LIBRARY_DIR
+ NO_CHECK_REQUIRED_COMPONENTS_MACRO
+ )
+
+write_basic_package_version_file(${LIBRARY_NAME}-config-version.cmake
+ VERSION
+ ${TINYOBJLOADER_VERSION}
+ COMPATIBILITY
+ SameMajorVersion
+ )
+
+#pkg-config file
+configure_file(${PROJECT_NAME}.pc.in ${LIBRARY_NAME}.pc @ONLY)
+
+#Installation
+install(TARGETS
+ ${LIBRARY_NAME}
+ EXPORT ${PROJECT_NAME}-targets
+ DESTINATION
+ ${TINYOBJLOADER_LIBRARY_DIR}
+ PUBLIC_HEADER DESTINATION
+ ${TINYOBJLOADER_INCLUDE_DIR}
+ RUNTIME DESTINATION
+ ${TINYOBJLOADER_RUNTIME_DIR}
+ )
+install(EXPORT
+ ${PROJECT_NAME}-targets
+ DESTINATION
+ ${TINYOBJLOADER_CMAKE_DIR}
+ )
+install(FILES
+ tiny_obj_loader.h
+ DESTINATION
+ ${TINYOBJLOADER_INCLUDE_DIR}
+ )
+install(FILES
+ LICENSE
+ DESTINATION
+ ${TINYOBJLOADER_DOC_DIR}
+ )
+install(FILES
+ "${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}-config.cmake"
+ "${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}-config-version.cmake"
+ DESTINATION
+ ${TINYOBJLOADER_CMAKE_DIR}
+ )
+install(FILES
+ "${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}.pc"
+ DESTINATION
+ ${TINYOBJLOADER_PKGCONFIG_DIR}
+ )
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..707594d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012-2016 Syoyo Fujita and many contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..47477f8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,269 @@
+# tinyobjloader
+
+[![Join the chat at https://gitter.im/syoyo/tinyobjloader](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/syoyo/tinyobjloader?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+[![Build Status](https://travis-ci.org/syoyo/tinyobjloader.svg)](https://travis-ci.org/syoyo/tinyobjloader)
+
+[![wercker status](https://app.wercker.com/status/495a3bac400212cdacdeb4dd9397bf4f/m "wercker status")](https://app.wercker.com/project/bykey/495a3bac400212cdacdeb4dd9397bf4f)
+
+[![Build status](https://ci.appveyor.com/api/projects/status/tlb421q3t2oyobcn/branch/master?svg=true)](https://ci.appveyor.com/project/syoyo/tinyobjloader/branch/master)
+
+[![Coverage Status](https://coveralls.io/repos/github/syoyo/tinyobjloader/badge.svg?branch=master)](https://coveralls.io/github/syoyo/tinyobjloader?branch=master)
+
+[https://github.com/syoyo/tinyobjloader](https://github.com/syoyo/tinyobjloader)
+
+Tiny but powerful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse over 10M polygons with moderate memory and time.
+
+`tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-)
+
+If you are looking for C89 version, please see https://github.com/syoyo/tinyobjloader-c .
+
+Notice!
+-------
+
+We have released new version v1.0.0 on 20 Aug, 2016.
+Old version is available `v0.9.x` branch https://github.com/syoyo/tinyobjloader/tree/v0.9.x
+
+## What's new
+
+* 20 Aug, 2016 : Bump version v1.0.0. New data structure and API!
+
+### Old version
+
+Previous old version is avaiable in `v0.9.x` branch.
+
+## Example
+
+![Rungholt](images/rungholt.jpg)
+
+tinyobjloader can successfully load 6M triangles Rungholt scene.
+http://casual-effects.com/data/index.html
+
+![](images/sanmugel.png)
+
+* [examples/viewer/](examples/viewer) OpenGL .obj viewer
+* [examples/callback_api/](examples/callback_api/) Callback API example
+* [examples/voxelize/](examples/voxelize/) Voxelizer example
+
+## Use case
+
+TinyObjLoader is successfully used in ...
+
+### New version(v1.0.x)
+
+* Double precision support through `TINYOBJLOADER_USE_DOUBLE` thanks to noma
+* Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models
+* .obj viewer with Metal https://github.com/middlefeng/NuoModelViewer/tree/master
+* Vulkan Cookbook https://github.com/PacktPublishing/Vulkan-Cookbook
+* cudabox: CUDA Solid Voxelizer Engine https://github.com/gaspardzoss/cudavox
+* Drake: A planning, control, and analysis toolbox for nonlinear dynamical systems https://github.com/RobotLocomotion/drake
+* VFPR - a Vulkan Forward Plus Renderer : https://github.com/WindyDarian/Vulkan-Forward-Plus-Renderer
+* Your project here! (Letting us know via github issue is welcome!)
+
+### Old version(v0.9.x)
+
+* bullet3 https://github.com/erwincoumans/bullet3
+* pbrt-v2 https://github.com/mmp/pbrt-v2
+* OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01
+* mallie https://lighttransport.github.io/mallie
+* IBLBaker (Image Based Lighting Baker). http://www.derkreature.com/iblbaker/
+* Stanford CS148 http://web.stanford.edu/class/cs148/assignments/assignment3.pdf
+* Awesome Bump http://awesomebump.besaba.com/about/
+* sdlgl3-wavefront OpenGL .obj viewer https://github.com/chrisliebert/sdlgl3-wavefront
+* pbrt-v3 https://github.com/mmp/pbrt-v3
+* cocos2d-x https://github.com/cocos2d/cocos2d-x/
+* Android Vulkan demo https://github.com/SaschaWillems/Vulkan
+* voxelizer https://github.com/karimnaaji/voxelizer
+* Probulator https://github.com/kayru/Probulator
+* OptiX Prime baking https://github.com/nvpro-samples/optix_prime_baking
+* FireRays SDK https://github.com/GPUOpen-LibrariesAndSDKs/FireRays_SDK
+* parg, tiny C library of various graphics utilities and GL demos https://github.com/prideout/parg
+* Opengl unit of ChronoEngine https://github.com/projectchrono/chrono-opengl
+* Point Based Global Illumination on modern GPU https://pbgi.wordpress.com/code-source/
+* Fast OBJ file importing and parsing in CUDA http://researchonline.jcu.edu.au/42515/1/2015.CVM.OBJCUDA.pdf
+* Sorted Shading for Uni-Directional Pathtracing by Joshua Bainbridge https://nccastaff.bournemouth.ac.uk/jmacey/MastersProjects/MSc15/02Josh/joshua_bainbridge_thesis.pdf
+* GeeXLab http://www.geeks3d.com/hacklab/20160531/geexlab-0-12-0-0-released-for-windows/
+
+
+## Features
+
+* Group(parse multiple group name)
+* Vertex
+ * Vertex color(as an extension: https://blender.stackexchange.com/questions/31997/how-can-i-get-vertex-painted-obj-files-to-import-into-blender)
+* Texcoord
+* Normal
+* Material
+ * Unknown material attributes are returned as key-value(value is string) map.
+* Crease tag('t'). This is OpenSubdiv specific(not in wavefront .obj specification)
+* PBR material extension for .MTL. Its proposed here: http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
+* Callback API for custom loading.
+* Double precision support(for HPC application).
+
+
+## TODO
+
+* [ ] Fix obj_sticker example.
+* [ ] More unit test codes.
+* [x] Texture options
+* [ ] Normal vector generation
+ * [ ] Support smoothing groups
+
+## License
+
+Licensed under MIT license.
+
+## Usage
+
+### Data format
+
+`attrib_t` contains single and linear array of vertex data(position, normal and texcoord).
+
+```
+attrib_t::vertices => 3 floats per vertex
+
+ v[0] v[1] v[2] v[3] v[n-1]
+ +-----------+-----------+-----------+-----------+ +-----------+
+ | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
+ +-----------+-----------+-----------+-----------+ +-----------+
+
+attrib_t::normals => 3 floats per vertex
+
+ n[0] n[1] n[2] n[3] n[n-1]
+ +-----------+-----------+-----------+-----------+ +-----------+
+ | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
+ +-----------+-----------+-----------+-----------+ +-----------+
+
+attrib_t::texcoords => 2 floats per vertex
+
+ t[0] t[1] t[2] t[3] t[n-1]
+ +-----------+-----------+-----------+-----------+ +-----------+
+ | u | v | u | v | u | v | u | v | .... | u | v |
+ +-----------+-----------+-----------+-----------+ +-----------+
+
+attrib_t::colors => 3 floats per vertex(vertex color. optional)
+
+ c[0] c[1] c[2] c[3] c[n-1]
+ +-----------+-----------+-----------+-----------+ +-----------+
+ | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z |
+ +-----------+-----------+-----------+-----------+ +-----------+
+
+```
+
+Each `shape_t::mesh_t` does not contain vertex data but contains array index to `attrib_t`.
+See `loader_example.cc` for more details.
+
+
+```
+
+mesh_t::indices => array of vertex indices.
+
+ +----+----+----+----+----+----+----+----+----+----+ +--------+
+ | i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-1) |
+ +----+----+----+----+----+----+----+----+----+----+ +--------+
+
+Each index has an array index to attrib_t::vertices, attrib_t::normals and attrib_t::texcoords.
+
+mesh_t::num_face_vertices => array of the number of vertices per face(e.g. 3 = triangle, 4 = quad , 5 or more = N-gons).
+
+
+ +---+---+---+ +---+
+ | 3 | 4 | 3 | ...... | 3 |
+ +---+---+---+ +---+
+ | | | |
+ | | | +-----------------------------------------+
+ | | | |
+ | | +------------------------------+ |
+ | | | |
+ | +------------------+ | |
+ | | | |
+ |/ |/ |/ |/
+
+ mesh_t::indices
+
+ | face[0] | face[1] | face[2] | | face[n-1] |
+ +----+----+----+----+----+----+----+----+----+----+ +--------+--------+--------+
+ | i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-3) | i(n-2) | i(n-1) |
+ +----+----+----+----+----+----+----+----+----+----+ +--------+--------+--------+
+
+```
+
+Note that when `triangulate` flas is true in `tinyobj::LoadObj()` argument, `num_face_vertices` are all filled with 3(triangle).
+
+### float data type
+
+TinyObjLoader now use `real_t` for floating point data type.
+Default is `float(32bit)`.
+You can enable `double(64bit)` precision by using `TINYOBJLOADER_USE_DOUBLE` define.
+
+#### Example code
+
+```c++
+#define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc
+#include "tiny_obj_loader.h"
+
+std::string inputfile = "cornell_box.obj";
+tinyobj::attrib_t attrib;
+std::vector<tinyobj::shape_t> shapes;
+std::vector<tinyobj::material_t> materials;
+
+std::string err;
+bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, inputfile.c_str());
+
+if (!err.empty()) { // `err` may contain warning message.
+ std::cerr << err << std::endl;
+}
+
+if (!ret) {
+ exit(1);
+}
+
+// Loop over shapes
+for (size_t s = 0; s < shapes.size(); s++) {
+ // Loop over faces(polygon)
+ size_t index_offset = 0;
+ for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
+ int fv = shapes[s].mesh.num_face_vertices[f];
+
+ // Loop over vertices in the face.
+ for (size_t v = 0; v < fv; v++) {
+ // access to vertex
+ tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
+ tinyobj::real_t vx = attrib.vertices[3*idx.vertex_index+0];
+ tinyobj::real_t vy = attrib.vertices[3*idx.vertex_index+1];
+ tinyobj::real_t vz = attrib.vertices[3*idx.vertex_index+2];
+ tinyobj::real_t nx = attrib.normals[3*idx.normal_index+0];
+ tinyobj::real_t ny = attrib.normals[3*idx.normal_index+1];
+ tinyobj::real_t nz = attrib.normals[3*idx.normal_index+2];
+ tinyobj::real_t tx = attrib.texcoords[2*idx.texcoord_index+0];
+ tinyobj::real_t ty = attrib.texcoords[2*idx.texcoord_index+1];
+ // Optional: vertex colors
+ // tinyobj::real_t red = attrib.colors[3*idx.vertex_index+0];
+ // tinyobj::real_t green = attrib.colors[3*idx.vertex_index+1];
+ // tinyobj::real_t blue = attrib.colors[3*idx.vertex_index+2];
+ }
+ index_offset += fv;
+
+ // per-face material
+ shapes[s].mesh.material_ids[f];
+ }
+}
+
+```
+
+## Optimized loader
+
+Optimized multi-threaded .obj loader is available at `experimental/` directory.
+If you want absolute performance to load .obj data, this optimized loader will fit your purpose.
+Note that the optimized loader uses C++11 thread and it does less error checks but may work most .obj data.
+
+Here is some benchmark result. Time are measured on MacBook 12(Early 2016, Core m5 1.2GHz).
+
+* Rungholt scene(6M triangles)
+ * old version(v0.9.x): 15500 msecs.
+ * baseline(v1.0.x): 6800 msecs(2.3x faster than old version)
+ * optimised: 1500 msecs(10x faster than old version, 4.5x faster than baseline)
+
+
+## Tests
+
+Unit tests are provided in `tests` directory. See `tests/README.md` for details.
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..89fd100
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,22 @@
+version: 1.0.{build}
+
+platform: x64
+
+install:
+ #######################################################################################
+ # All external dependencies are installed in C:\projects\deps
+ #######################################################################################
+ - mkdir C:\projects\deps
+
+ #######################################################################################
+ # Install Ninja
+ #######################################################################################
+ - set NINJA_URL="https://github.com/ninja-build/ninja/releases/download/v1.6.0/ninja-win.zip"
+ - appveyor DownloadFile %NINJA_URL% -FileName ninja.zip
+ - 7z x ninja.zip -oC:\projects\deps\ninja > nul
+ - set PATH=C:\projects\deps\ninja;%PATH%
+ - ninja --version
+
+build_script:
+ - cd tests
+ - vcbuild.bat
diff --git a/build.ninja b/build.ninja
new file mode 100644
index 0000000..77801bb
--- /dev/null
+++ b/build.ninja
@@ -0,0 +1,53 @@
+ninja_required_version = 1.4
+
+gnubuilddir = build
+gnudefines =
+gnuincludes = -I.
+gnucflags = -O2 -g
+gnucxxflags = -O2 -g -pedantic -Wall -Wextra -Wcast-align -Wcast-qual $
+ -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self $
+ -Wmissing-declarations -Wmissing-include-dirs -Wold-style-cast $
+ -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion $
+ -Wsign-promo -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror $
+ -Wno-unused -fsanitize=address
+gnuldflags = -fsanitize=address
+
+pool link_pool
+ depth = 1
+
+rule gnucxx
+ command = $gnucxx -MMD -MF $out.d $gnudefines $gnuincludes $gnucxxflags $
+ -c $in -o $out
+ description = CXX $out
+ depfile = $out.d
+ deps = gcc
+rule gnucc
+ command = $gnucc -MMD -MF $out.d $gnudefines $gnuincludes $gnucflags -c $
+ $in -o $out
+ description = CC $out
+ depfile = $out.d
+ deps = gcc
+rule gnulink
+ command = $gnuld -o $out $in $libs $gnuldflags
+ description = LINK $out
+ pool = link_pool
+rule gnuar
+ command = $gnuar rsc $out $in
+ description = AR $out
+ pool = link_pool
+rule gnustamp
+ command = touch $out
+ description = STAMP $out
+
+gnucxx = g++
+gnucc = gcc
+gnuld = $gnucxx
+gnuar = ar
+
+build loader_example.o: gnucxx loader_example.cc
+
+
+build loader_example: gnulink loader_example.o
+build all: phony loader_example
+
+default all
diff --git a/deps/cpplint.py b/deps/cpplint.py
new file mode 100755
index 0000000..ccc25d4
--- /dev/null
+++ b/deps/cpplint.py
@@ -0,0 +1,6323 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2009 Google Inc. 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 Google Inc. 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.
+
+"""Does google-lint on c++ files.
+
+The goal of this script is to identify places in the code that *may*
+be in non-compliance with google style. It does not attempt to fix
+up these problems -- the point is to educate. It does also not
+attempt to find all problems, or to ensure that everything it does
+find is legitimately a problem.
+
+In particular, we can get very confused by /* and // inside strings!
+We do a small hack, which is to ignore //'s with "'s after them on the
+same line, but it is far from perfect (in either direction).
+"""
+
+import codecs
+import copy
+import getopt
+import math # for log
+import os
+import re
+import sre_compile
+import string
+import sys
+import unicodedata
+
+
+_USAGE = """
+Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]
+ [--counting=total|toplevel|detailed] [--root=subdir]
+ [--linelength=digits]
+ <file> [file] ...
+
+ The style guidelines this tries to follow are those in
+ http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
+
+ Every problem is given a confidence score from 1-5, with 5 meaning we are
+ certain of the problem, and 1 meaning it could be a legitimate construct.
+ This will miss some errors, and is not a substitute for a code review.
+
+ To suppress false-positive errors of a certain category, add a
+ 'NOLINT(category)' comment to the line. NOLINT or NOLINT(*)
+ suppresses errors of all categories on that line.
+
+ The files passed in will be linted; at least one file must be provided.
+ Default linted extensions are .cc, .cpp, .cu, .cuh and .h. Change the
+ extensions with the --extensions flag.
+
+ Flags:
+
+ output=vs7
+ By default, the output is formatted to ease emacs parsing. Visual Studio
+ compatible output (vs7) may also be used. Other formats are unsupported.
+
+ verbose=#
+ Specify a number 0-5 to restrict errors to certain verbosity levels.
+
+ filter=-x,+y,...
+ Specify a comma-separated list of category-filters to apply: only
+ error messages whose category names pass the filters will be printed.
+ (Category names are printed with the message and look like
+ "[whitespace/indent]".) Filters are evaluated left to right.
+ "-FOO" and "FOO" means "do not print categories that start with FOO".
+ "+FOO" means "do print categories that start with FOO".
+
+ Examples: --filter=-whitespace,+whitespace/braces
+ --filter=whitespace,runtime/printf,+runtime/printf_format
+ --filter=-,+build/include_what_you_use
+
+ To see a list of all the categories used in cpplint, pass no arg:
+ --filter=
+
+ counting=total|toplevel|detailed
+ The total number of errors found is always printed. If
+ 'toplevel' is provided, then the count of errors in each of
+ the top-level categories like 'build' and 'whitespace' will
+ also be printed. If 'detailed' is provided, then a count
+ is provided for each category like 'build/class'.
+
+ root=subdir
+ The root directory used for deriving header guard CPP variable.
+ By default, the header guard CPP variable is calculated as the relative
+ path to the directory that contains .git, .hg, or .svn. When this flag
+ is specified, the relative path is calculated from the specified
+ directory. If the specified directory does not exist, this flag is
+ ignored.
+
+ Examples:
+ Assuming that src/.git exists, the header guard CPP variables for
+ src/chrome/browser/ui/browser.h are:
+
+ No flag => CHROME_BROWSER_UI_BROWSER_H_
+ --root=chrome => BROWSER_UI_BROWSER_H_
+ --root=chrome/browser => UI_BROWSER_H_
+
+ linelength=digits
+ This is the allowed line length for the project. The default value is
+ 80 characters.
+
+ Examples:
+ --linelength=120
+
+ extensions=extension,extension,...
+ The allowed file extensions that cpplint will check
+
+ Examples:
+ --extensions=hpp,cpp
+
+ cpplint.py supports per-directory configurations specified in CPPLINT.cfg
+ files. CPPLINT.cfg file can contain a number of key=value pairs.
+ Currently the following options are supported:
+
+ set noparent
+ filter=+filter1,-filter2,...
+ exclude_files=regex
+ linelength=80
+
+ "set noparent" option prevents cpplint from traversing directory tree
+ upwards looking for more .cfg files in parent directories. This option
+ is usually placed in the top-level project directory.
+
+ The "filter" option is similar in function to --filter flag. It specifies
+ message filters in addition to the |_DEFAULT_FILTERS| and those specified
+ through --filter command-line flag.
+
+ "exclude_files" allows to specify a regular expression to be matched against
+ a file name. If the expression matches, the file is skipped and not run
+ through liner.
+
+ "linelength" allows to specify the allowed line length for the project.
+
+ CPPLINT.cfg has an effect on files in the same directory and all
+ sub-directories, unless overridden by a nested configuration file.
+
+ Example file:
+ filter=-build/include_order,+build/include_alpha
+ exclude_files=.*\.cc
+
+ The above example disables build/include_order warning and enables
+ build/include_alpha as well as excludes all .cc from being
+ processed by linter, in the current directory (where the .cfg
+ file is located) and all sub-directories.
+"""
+
+# We categorize each error message we print. Here are the categories.
+# We want an explicit list so we can list them all in cpplint --filter=.
+# If you add a new error message with a new category, add it to the list
+# here! cpplint_unittest.py should tell you if you forget to do this.
+_ERROR_CATEGORIES = [
+ 'build/class',
+ 'build/c++11',
+ 'build/deprecated',
+ 'build/endif_comment',
+ 'build/explicit_make_pair',
+ 'build/forward_decl',
+ 'build/header_guard',
+ 'build/include',
+ 'build/include_alpha',
+ 'build/include_order',
+ 'build/include_what_you_use',
+ 'build/namespaces',
+ 'build/printf_format',
+ 'build/storage_class',
+ 'legal/copyright',
+ 'readability/alt_tokens',
+ 'readability/braces',
+ 'readability/casting',
+ 'readability/check',
+ 'readability/constructors',
+ 'readability/fn_size',
+ 'readability/function',
+ 'readability/inheritance',
+ 'readability/multiline_comment',
+ 'readability/multiline_string',
+ 'readability/namespace',
+ 'readability/nolint',
+ 'readability/nul',
+ 'readability/strings',
+ 'readability/todo',
+ 'readability/utf8',
+ 'runtime/arrays',
+ 'runtime/casting',
+ 'runtime/explicit',
+ 'runtime/int',
+ 'runtime/init',
+ 'runtime/invalid_increment',
+ 'runtime/member_string_references',
+ 'runtime/memset',
+ 'runtime/indentation_namespace',
+ 'runtime/operator',
+ 'runtime/printf',
+ 'runtime/printf_format',
+ 'runtime/references',
+ 'runtime/string',
+ 'runtime/threadsafe_fn',
+ 'runtime/vlog',
+ 'whitespace/blank_line',
+ 'whitespace/braces',
+ 'whitespace/comma',
+ 'whitespace/comments',
+ 'whitespace/empty_conditional_body',
+ 'whitespace/empty_loop_body',
+ 'whitespace/end_of_line',
+ 'whitespace/ending_newline',
+ 'whitespace/forcolon',
+ 'whitespace/indent',
+ 'whitespace/line_length',
+ 'whitespace/newline',
+ 'whitespace/operators',
+ 'whitespace/parens',
+ 'whitespace/semicolon',
+ 'whitespace/tab',
+ 'whitespace/todo',
+ ]
+
+# These error categories are no longer enforced by cpplint, but for backwards-
+# compatibility they may still appear in NOLINT comments.
+_LEGACY_ERROR_CATEGORIES = [
+ 'readability/streams',
+ ]
+
+# The default state of the category filter. This is overridden by the --filter=
+# flag. By default all errors are on, so only add here categories that should be
+# off by default (i.e., categories that must be enabled by the --filter= flags).
+# All entries here should start with a '-' or '+', as in the --filter= flag.
+_DEFAULT_FILTERS = ['-build/include_alpha']
+
+# We used to check for high-bit characters, but after much discussion we
+# decided those were OK, as long as they were in UTF-8 and didn't represent
+# hard-coded international strings, which belong in a separate i18n file.
+
+# C++ headers
+_CPP_HEADERS = frozenset([
+ # Legacy
+ 'algobase.h',
+ 'algo.h',
+ 'alloc.h',
+ 'builtinbuf.h',
+ 'bvector.h',
+ 'complex.h',
+ 'defalloc.h',
+ 'deque.h',
+ 'editbuf.h',
+ 'fstream.h',
+ 'function.h',
+ 'hash_map',
+ 'hash_map.h',
+ 'hash_set',
+ 'hash_set.h',
+ 'hashtable.h',
+ 'heap.h',
+ 'indstream.h',
+ 'iomanip.h',
+ 'iostream.h',
+ 'istream.h',
+ 'iterator.h',
+ 'list.h',
+ 'map.h',
+ 'multimap.h',
+ 'multiset.h',
+ 'ostream.h',
+ 'pair.h',
+ 'parsestream.h',
+ 'pfstream.h',
+ 'procbuf.h',
+ 'pthread_alloc',
+ 'pthread_alloc.h',
+ 'rope',
+ 'rope.h',
+ 'ropeimpl.h',
+ 'set.h',
+ 'slist',
+ 'slist.h',
+ 'stack.h',
+ 'stdiostream.h',
+ 'stl_alloc.h',
+ 'stl_relops.h',
+ 'streambuf.h',
+ 'stream.h',
+ 'strfile.h',
+ 'strstream.h',
+ 'tempbuf.h',
+ 'tree.h',
+ 'type_traits.h',
+ 'vector.h',
+ # 17.6.1.2 C++ library headers
+ 'algorithm',
+ 'array',
+ 'atomic',
+ 'bitset',
+ 'chrono',
+ 'codecvt',
+ 'complex',
+ 'condition_variable',
+ 'deque',
+ 'exception',
+ 'forward_list',
+ 'fstream',
+ 'functional',
+ 'future',
+ 'initializer_list',
+ 'iomanip',
+ 'ios',
+ 'iosfwd',
+ 'iostream',
+ 'istream',
+ 'iterator',
+ 'limits',
+ 'list',
+ 'locale',
+ 'map',
+ 'memory',
+ 'mutex',
+ 'new',
+ 'numeric',
+ 'ostream',
+ 'queue',
+ 'random',
+ 'ratio',
+ 'regex',
+ 'set',
+ 'sstream',
+ 'stack',
+ 'stdexcept',
+ 'streambuf',
+ 'string',
+ 'strstream',
+ 'system_error',
+ 'thread',
+ 'tuple',
+ 'typeindex',
+ 'typeinfo',
+ 'type_traits',
+ 'unordered_map',
+ 'unordered_set',
+ 'utility',
+ 'valarray',
+ 'vector',
+ # 17.6.1.2 C++ headers for C library facilities
+ 'cassert',
+ 'ccomplex',
+ 'cctype',
+ 'cerrno',
+ 'cfenv',
+ 'cfloat',
+ 'cinttypes',
+ 'ciso646',
+ 'climits',
+ 'clocale',
+ 'cmath',
+ 'csetjmp',
+ 'csignal',
+ 'cstdalign',
+ 'cstdarg',
+ 'cstdbool',
+ 'cstddef',
+ 'cstdint',
+ 'cstdio',
+ 'cstdlib',
+ 'cstring',
+ 'ctgmath',
+ 'ctime',
+ 'cuchar',
+ 'cwchar',
+ 'cwctype',
+ ])
+
+
+# These headers are excluded from [build/include] and [build/include_order]
+# checks:
+# - Anything not following google file name conventions (containing an
+# uppercase character, such as Python.h or nsStringAPI.h, for example).
+# - Lua headers.
+_THIRD_PARTY_HEADERS_PATTERN = re.compile(
+ r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$')
+
+
+# Assertion macros. These are defined in base/logging.h and
+# testing/base/gunit.h. Note that the _M versions need to come first
+# for substring matching to work.
+_CHECK_MACROS = [
+ 'DCHECK', 'CHECK',
+ 'EXPECT_TRUE_M', 'EXPECT_TRUE',
+ 'ASSERT_TRUE_M', 'ASSERT_TRUE',
+ 'EXPECT_FALSE_M', 'EXPECT_FALSE',
+ 'ASSERT_FALSE_M', 'ASSERT_FALSE',
+ ]
+
+# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE
+_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS])
+
+for op, replacement in [('==', 'EQ'), ('!=', 'NE'),
+ ('>=', 'GE'), ('>', 'GT'),
+ ('<=', 'LE'), ('<', 'LT')]:
+ _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement
+ _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement
+ _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement
+ _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement
+ _CHECK_REPLACEMENT['EXPECT_TRUE_M'][op] = 'EXPECT_%s_M' % replacement
+ _CHECK_REPLACEMENT['ASSERT_TRUE_M'][op] = 'ASSERT_%s_M' % replacement
+
+for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'),
+ ('>=', 'LT'), ('>', 'LE'),
+ ('<=', 'GT'), ('<', 'GE')]:
+ _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement
+ _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement
+ _CHECK_REPLACEMENT['EXPECT_FALSE_M'][op] = 'EXPECT_%s_M' % inv_replacement
+ _CHECK_REPLACEMENT['ASSERT_FALSE_M'][op] = 'ASSERT_%s_M' % inv_replacement
+
+# Alternative tokens and their replacements. For full list, see section 2.5
+# Alternative tokens [lex.digraph] in the C++ standard.
+#
+# Digraphs (such as '%:') are not included here since it's a mess to
+# match those on a word boundary.
+_ALT_TOKEN_REPLACEMENT = {
+ 'and': '&&',
+ 'bitor': '|',
+ 'or': '||',
+ 'xor': '^',
+ 'compl': '~',
+ 'bitand': '&',
+ 'and_eq': '&=',
+ 'or_eq': '|=',
+ 'xor_eq': '^=',
+ 'not': '!',
+ 'not_eq': '!='
+ }
+
+# Compile regular expression that matches all the above keywords. The "[ =()]"
+# bit is meant to avoid matching these keywords outside of boolean expressions.
+#
+# False positives include C-style multi-line comments and multi-line strings
+# but those have always been troublesome for cpplint.
+_ALT_TOKEN_REPLACEMENT_PATTERN = re.compile(
+ r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)')
+
+
+# These constants define types of headers for use with
+# _IncludeState.CheckNextIncludeOrder().
+_C_SYS_HEADER = 1
+_CPP_SYS_HEADER = 2
+_LIKELY_MY_HEADER = 3
+_POSSIBLE_MY_HEADER = 4
+_OTHER_HEADER = 5
+
+# These constants define the current inline assembly state
+_NO_ASM = 0 # Outside of inline assembly block
+_INSIDE_ASM = 1 # Inside inline assembly block
+_END_ASM = 2 # Last line of inline assembly block
+_BLOCK_ASM = 3 # The whole block is an inline assembly block
+
+# Match start of assembly blocks
+_MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)'
+ r'(?:\s+(volatile|__volatile__))?'
+ r'\s*[{(]')
+
+
+_regexp_compile_cache = {}
+
+# {str, set(int)}: a map from error categories to sets of linenumbers
+# on which those errors are expected and should be suppressed.
+_error_suppressions = {}
+
+# The root directory used for deriving header guard CPP variable.
+# This is set by --root flag.
+_root = None
+
+# The allowed line length of files.
+# This is set by --linelength flag.
+_line_length = 80
+
+# The allowed extensions for file names
+# This is set by --extensions flag.
+_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh'])
+
+def ParseNolintSuppressions(filename, raw_line, linenum, error):
+ """Updates the global list of error-suppressions.
+
+ Parses any NOLINT comments on the current line, updating the global
+ error_suppressions store. Reports an error if the NOLINT comment
+ was malformed.
+
+ Args:
+ filename: str, the name of the input file.
+ raw_line: str, the line of input text, with comments.
+ linenum: int, the number of the current line.
+ error: function, an error handler.
+ """
+ matched = Search(r'\bNOLINT(NEXTLINE)?\b(\([^)]+\))?', raw_line)
+ if matched:
+ if matched.group(1):
+ suppressed_line = linenum + 1
+ else:
+ suppressed_line = linenum
+ category = matched.group(2)
+ if category in (None, '(*)'): # => "suppress all"
+ _error_suppressions.setdefault(None, set()).add(suppressed_line)
+ else:
+ if category.startswith('(') and category.endswith(')'):
+ category = category[1:-1]
+ if category in _ERROR_CATEGORIES:
+ _error_suppressions.setdefault(category, set()).add(suppressed_line)
+ elif category not in _LEGACY_ERROR_CATEGORIES:
+ error(filename, linenum, 'readability/nolint', 5,
+ 'Unknown NOLINT error category: %s' % category)
+
+
+def ResetNolintSuppressions():
+ """Resets the set of NOLINT suppressions to empty."""
+ _error_suppressions.clear()
+
+
+def IsErrorSuppressedByNolint(category, linenum):
+ """Returns true if the specified error category is suppressed on this line.
+
+ Consults the global error_suppressions map populated by
+ ParseNolintSuppressions/ResetNolintSuppressions.
+
+ Args:
+ category: str, the category of the error.
+ linenum: int, the current line number.
+ Returns:
+ bool, True iff the error should be suppressed due to a NOLINT comment.
+ """
+ return (linenum in _error_suppressions.get(category, set()) or
+ linenum in _error_suppressions.get(None, set()))
+
+
+def Match(pattern, s):
+ """Matches the string with the pattern, caching the compiled regexp."""
+ # The regexp compilation caching is inlined in both Match and Search for
+ # performance reasons; factoring it out into a separate function turns out
+ # to be noticeably expensive.
+ if pattern not in _regexp_compile_cache:
+ _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
+ return _regexp_compile_cache[pattern].match(s)
+
+
+def ReplaceAll(pattern, rep, s):
+ """Replaces instances of pattern in a string with a replacement.
+
+ The compiled regex is kept in a cache shared by Match and Search.
+
+ Args:
+ pattern: regex pattern
+ rep: replacement text
+ s: search string
+
+ Returns:
+ string with replacements made (or original string if no replacements)
+ """
+ if pattern not in _regexp_compile_cache:
+ _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
+ return _regexp_compile_cache[pattern].sub(rep, s)
+
+
+def Search(pattern, s):
+ """Searches the string for the pattern, caching the compiled regexp."""
+ if pattern not in _regexp_compile_cache:
+ _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
+ return _regexp_compile_cache[pattern].search(s)
+
+
+class _IncludeState(object):
+ """Tracks line numbers for includes, and the order in which includes appear.
+
+ include_list contains list of lists of (header, line number) pairs.
+ It's a lists of lists rather than just one flat list to make it
+ easier to update across preprocessor boundaries.
+
+ Call CheckNextIncludeOrder() once for each header in the file, passing
+ in the type constants defined above. Calls in an illegal order will
+ raise an _IncludeError with an appropriate error message.
+
+ """
+ # self._section will move monotonically through this set. If it ever
+ # needs to move backwards, CheckNextIncludeOrder will raise an error.
+ _INITIAL_SECTION = 0
+ _MY_H_SECTION = 1
+ _C_SECTION = 2
+ _CPP_SECTION = 3
+ _OTHER_H_SECTION = 4
+
+ _TYPE_NAMES = {
+ _C_SYS_HEADER: 'C system header',
+ _CPP_SYS_HEADER: 'C++ system header',
+ _LIKELY_MY_HEADER: 'header this file implements',
+ _POSSIBLE_MY_HEADER: 'header this file may implement',
+ _OTHER_HEADER: 'other header',
+ }
+ _SECTION_NAMES = {
+ _INITIAL_SECTION: "... nothing. (This can't be an error.)",
+ _MY_H_SECTION: 'a header this file implements',
+ _C_SECTION: 'C system header',
+ _CPP_SECTION: 'C++ system header',
+ _OTHER_H_SECTION: 'other header',
+ }
+
+ def __init__(self):
+ self.include_list = [[]]
+ self.ResetSection('')
+
+ def FindHeader(self, header):
+ """Check if a header has already been included.
+
+ Args:
+ header: header to check.
+ Returns:
+ Line number of previous occurrence, or -1 if the header has not
+ been seen before.
+ """
+ for section_list in self.include_list:
+ for f in section_list:
+ if f[0] == header:
+ return f[1]
+ return -1
+
+ def ResetSection(self, directive):
+ """Reset section checking for preprocessor directive.
+
+ Args:
+ directive: preprocessor directive (e.g. "if", "else").
+ """
+ # The name of the current section.
+ self._section = self._INITIAL_SECTION
+ # The path of last found header.
+ self._last_header = ''
+
+ # Update list of includes. Note that we never pop from the
+ # include list.
+ if directive in ('if', 'ifdef', 'ifndef'):
+ self.include_list.append([])
+ elif directive in ('else', 'elif'):
+ self.include_list[-1] = []
+
+ def SetLastHeader(self, header_path):
+ self._last_header = header_path
+
+ def CanonicalizeAlphabeticalOrder(self, header_path):
+ """Returns a path canonicalized for alphabetical comparison.
+
+ - replaces "-" with "_" so they both cmp the same.
+ - removes '-inl' since we don't require them to be after the main header.
+ - lowercase everything, just in case.
+
+ Args:
+ header_path: Path to be canonicalized.
+
+ Returns:
+ Canonicalized path.
+ """
+ return header_path.replace('-inl.h', '.h').replace('-', '_').lower()
+
+ def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path):
+ """Check if a header is in alphabetical order with the previous header.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ header_path: Canonicalized header to be checked.
+
+ Returns:
+ Returns true if the header is in alphabetical order.
+ """
+ # If previous section is different from current section, _last_header will
+ # be reset to empty string, so it's always less than current header.
+ #
+ # If previous line was a blank line, assume that the headers are
+ # intentionally sorted the way they are.
+ if (self._last_header > header_path and
+ Match(r'^\s*#\s*include\b', clean_lines.elided[linenum - 1])):
+ return False
+ return True
+
+ def CheckNextIncludeOrder(self, header_type):
+ """Returns a non-empty error message if the next header is out of order.
+
+ This function also updates the internal state to be ready to check
+ the next include.
+
+ Args:
+ header_type: One of the _XXX_HEADER constants defined above.
+
+ Returns:
+ The empty string if the header is in the right order, or an
+ error message describing what's wrong.
+
+ """
+ error_message = ('Found %s after %s' %
+ (self._TYPE_NAMES[header_type],
+ self._SECTION_NAMES[self._section]))
+
+ last_section = self._section
+
+ if header_type == _C_SYS_HEADER:
+ if self._section <= self._C_SECTION:
+ self._section = self._C_SECTION
+ else:
+ self._last_header = ''
+ return error_message
+ elif header_type == _CPP_SYS_HEADER:
+ if self._section <= self._CPP_SECTION:
+ self._section = self._CPP_SECTION
+ else:
+ self._last_header = ''
+ return error_message
+ elif header_type == _LIKELY_MY_HEADER:
+ if self._section <= self._MY_H_SECTION:
+ self._section = self._MY_H_SECTION
+ else:
+ self._section = self._OTHER_H_SECTION
+ elif header_type == _POSSIBLE_MY_HEADER:
+ if self._section <= self._MY_H_SECTION:
+ self._section = self._MY_H_SECTION
+ else:
+ # This will always be the fallback because we're not sure
+ # enough that the header is associated with this file.
+ self._section = self._OTHER_H_SECTION
+ else:
+ assert header_type == _OTHER_HEADER
+ self._section = self._OTHER_H_SECTION
+
+ if last_section != self._section:
+ self._last_header = ''
+
+ return ''
+
+
+class _CppLintState(object):
+ """Maintains module-wide state.."""
+
+ def __init__(self):
+ self.verbose_level = 1 # global setting.
+ self.error_count = 0 # global count of reported errors
+ # filters to apply when emitting error messages
+ self.filters = _DEFAULT_FILTERS[:]
+ # backup of filter list. Used to restore the state after each file.
+ self._filters_backup = self.filters[:]
+ self.counting = 'total' # In what way are we counting errors?
+ self.errors_by_category = {} # string to int dict storing error counts
+
+ # output format:
+ # "emacs" - format that emacs can parse (default)
+ # "vs7" - format that Microsoft Visual Studio 7 can parse
+ self.output_format = 'emacs'
+
+ def SetOutputFormat(self, output_format):
+ """Sets the output format for errors."""
+ self.output_format = output_format
+
+ def SetVerboseLevel(self, level):
+ """Sets the module's verbosity, and returns the previous setting."""
+ last_verbose_level = self.verbose_level
+ self.verbose_level = level
+ return last_verbose_level
+
+ def SetCountingStyle(self, counting_style):
+ """Sets the module's counting options."""
+ self.counting = counting_style
+
+ def SetFilters(self, filters):
+ """Sets the error-message filters.
+
+ These filters are applied when deciding whether to emit a given
+ error message.
+
+ Args:
+ filters: A string of comma-separated filters (eg "+whitespace/indent").
+ Each filter should start with + or -; else we die.
+
+ Raises:
+ ValueError: The comma-separated filters did not all start with '+' or '-'.
+ E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter"
+ """
+ # Default filters always have less priority than the flag ones.
+ self.filters = _DEFAULT_FILTERS[:]
+ self.AddFilters(filters)
+
+ def AddFilters(self, filters):
+ """ Adds more filters to the existing list of error-message filters. """
+ for filt in filters.split(','):
+ clean_filt = filt.strip()
+ if clean_filt:
+ self.filters.append(clean_filt)
+ for filt in self.filters:
+ if not (filt.startswith('+') or filt.startswith('-')):
+ raise ValueError('Every filter in --filters must start with + or -'
+ ' (%s does not)' % filt)
+
+ def BackupFilters(self):
+ """ Saves the current filter list to backup storage."""
+ self._filters_backup = self.filters[:]
+
+ def RestoreFilters(self):
+ """ Restores filters previously backed up."""
+ self.filters = self._filters_backup[:]
+
+ def ResetErrorCounts(self):
+ """Sets the module's error statistic back to zero."""
+ self.error_count = 0
+ self.errors_by_category = {}
+
+ def IncrementErrorCount(self, category):
+ """Bumps the module's error statistic."""
+ self.error_count += 1
+ if self.counting in ('toplevel', 'detailed'):
+ if self.counting != 'detailed':
+ category = category.split('/')[0]
+ if category not in self.errors_by_category:
+ self.errors_by_category[category] = 0
+ self.errors_by_category[category] += 1
+
+ def PrintErrorCounts(self):
+ """Print a summary of errors by category, and the total."""
+ for category, count in self.errors_by_category.iteritems():
+ sys.stderr.write('Category \'%s\' errors found: %d\n' %
+ (category, count))
+ sys.stderr.write('Total errors found: %d\n' % self.error_count)
+
+_cpplint_state = _CppLintState()
+
+
+def _OutputFormat():
+ """Gets the module's output format."""
+ return _cpplint_state.output_format
+
+
+def _SetOutputFormat(output_format):
+ """Sets the module's output format."""
+ _cpplint_state.SetOutputFormat(output_format)
+
+
+def _VerboseLevel():
+ """Returns the module's verbosity setting."""
+ return _cpplint_state.verbose_level
+
+
+def _SetVerboseLevel(level):
+ """Sets the module's verbosity, and returns the previous setting."""
+ return _cpplint_state.SetVerboseLevel(level)
+
+
+def _SetCountingStyle(level):
+ """Sets the module's counting options."""
+ _cpplint_state.SetCountingStyle(level)
+
+
+def _Filters():
+ """Returns the module's list of output filters, as a list."""
+ return _cpplint_state.filters
+
+
+def _SetFilters(filters):
+ """Sets the module's error-message filters.
+
+ These filters are applied when deciding whether to emit a given
+ error message.
+
+ Args:
+ filters: A string of comma-separated filters (eg "whitespace/indent").
+ Each filter should start with + or -; else we die.
+ """
+ _cpplint_state.SetFilters(filters)
+
+def _AddFilters(filters):
+ """Adds more filter overrides.
+
+ Unlike _SetFilters, this function does not reset the current list of filters
+ available.
+
+ Args:
+ filters: A string of comma-separated filters (eg "whitespace/indent").
+ Each filter should start with + or -; else we die.
+ """
+ _cpplint_state.AddFilters(filters)
+
+def _BackupFilters():
+ """ Saves the current filter list to backup storage."""
+ _cpplint_state.BackupFilters()
+
+def _RestoreFilters():
+ """ Restores filters previously backed up."""
+ _cpplint_state.RestoreFilters()
+
+class _FunctionState(object):
+ """Tracks current function name and the number of lines in its body."""
+
+ _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc.
+ _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER.
+
+ def __init__(self):
+ self.in_a_function = False
+ self.lines_in_function = 0
+ self.current_function = ''
+
+ def Begin(self, function_name):
+ """Start analyzing function body.
+
+ Args:
+ function_name: The name of the function being tracked.
+ """
+ self.in_a_function = True
+ self.lines_in_function = 0
+ self.current_function = function_name
+
+ def Count(self):
+ """Count line in current function body."""
+ if self.in_a_function:
+ self.lines_in_function += 1
+
+ def Check(self, error, filename, linenum):
+ """Report if too many lines in function body.
+
+ Args:
+ error: The function to call with any errors found.
+ filename: The name of the current file.
+ linenum: The number of the line to check.
+ """
+ if Match(r'T(EST|est)', self.current_function):
+ base_trigger = self._TEST_TRIGGER
+ else:
+ base_trigger = self._NORMAL_TRIGGER
+ trigger = base_trigger * 2**_VerboseLevel()
+
+ if self.lines_in_function > trigger:
+ error_level = int(math.log(self.lines_in_function / base_trigger, 2))
+ # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ...
+ if error_level > 5:
+ error_level = 5
+ error(filename, linenum, 'readability/fn_size', error_level,
+ 'Small and focused functions are preferred:'
+ ' %s has %d non-comment lines'
+ ' (error triggered by exceeding %d lines).' % (
+ self.current_function, self.lines_in_function, trigger))
+
+ def End(self):
+ """Stop analyzing function body."""
+ self.in_a_function = False
+
+
+class _IncludeError(Exception):
+ """Indicates a problem with the include order in a file."""
+ pass
+
+
+class FileInfo(object):
+ """Provides utility functions for filenames.
+
+ FileInfo provides easy access to the components of a file's path
+ relative to the project root.
+ """
+
+ def __init__(self, filename):
+ self._filename = filename
+
+ def FullName(self):
+ """Make Windows paths like Unix."""
+ return os.path.abspath(self._filename).replace('\\', '/')
+
+ def RepositoryName(self):
+ """FullName after removing the local path to the repository.
+
+ If we have a real absolute path name here we can try to do something smart:
+ detecting the root of the checkout and truncating /path/to/checkout from
+ the name so that we get header guards that don't include things like
+ "C:\Documents and Settings\..." or "/home/username/..." in them and thus
+ people on different computers who have checked the source out to different
+ locations won't see bogus errors.
+ """
+ fullname = self.FullName()
+
+ if os.path.exists(fullname):
+ project_dir = os.path.dirname(fullname)
+
+ if os.path.exists(os.path.join(project_dir, ".svn")):
+ # If there's a .svn file in the current directory, we recursively look
+ # up the directory tree for the top of the SVN checkout
+ root_dir = project_dir
+ one_up_dir = os.path.dirname(root_dir)
+ while os.path.exists(os.path.join(one_up_dir, ".svn")):
+ root_dir = os.path.dirname(root_dir)
+ one_up_dir = os.path.dirname(one_up_dir)
+
+ prefix = os.path.commonprefix([root_dir, project_dir])
+ return fullname[len(prefix) + 1:]
+
+ # Not SVN <= 1.6? Try to find a git, hg, or svn top level directory by
+ # searching up from the current path.
+ root_dir = os.path.dirname(fullname)
+ while (root_dir != os.path.dirname(root_dir) and
+ not os.path.exists(os.path.join(root_dir, ".git")) and
+ not os.path.exists(os.path.join(root_dir, ".hg")) and
+ not os.path.exists(os.path.join(root_dir, ".svn"))):
+ root_dir = os.path.dirname(root_dir)
+
+ if (os.path.exists(os.path.join(root_dir, ".git")) or
+ os.path.exists(os.path.join(root_dir, ".hg")) or
+ os.path.exists(os.path.join(root_dir, ".svn"))):
+ prefix = os.path.commonprefix([root_dir, project_dir])
+ return fullname[len(prefix) + 1:]
+
+ # Don't know what to do; header guard warnings may be wrong...
+ return fullname
+
+ def Split(self):
+ """Splits the file into the directory, basename, and extension.
+
+ For 'chrome/browser/browser.cc', Split() would
+ return ('chrome/browser', 'browser', '.cc')
+
+ Returns:
+ A tuple of (directory, basename, extension).
+ """
+
+ googlename = self.RepositoryName()
+ project, rest = os.path.split(googlename)
+ return (project,) + os.path.splitext(rest)
+
+ def BaseName(self):
+ """File base name - text after the final slash, before the final period."""
+ return self.Split()[1]
+
+ def Extension(self):
+ """File extension - text following the final period."""
+ return self.Split()[2]
+
+ def NoExtension(self):
+ """File has no source file extension."""
+ return '/'.join(self.Split()[0:2])
+
+ def IsSource(self):
+ """File has a source file extension."""
+ return self.Extension()[1:] in ('c', 'cc', 'cpp', 'cxx')
+
+
+def _ShouldPrintError(category, confidence, linenum):
+ """If confidence >= verbose, category passes filter and is not suppressed."""
+
+ # There are three ways we might decide not to print an error message:
+ # a "NOLINT(category)" comment appears in the source,
+ # the verbosity level isn't high enough, or the filters filter it out.
+ if IsErrorSuppressedByNolint(category, linenum):
+ return False
+
+ if confidence < _cpplint_state.verbose_level:
+ return False
+
+ is_filtered = False
+ for one_filter in _Filters():
+ if one_filter.startswith('-'):
+ if category.startswith(one_filter[1:]):
+ is_filtered = True
+ elif one_filter.startswith('+'):
+ if category.startswith(one_filter[1:]):
+ is_filtered = False
+ else:
+ assert False # should have been checked for in SetFilter.
+ if is_filtered:
+ return False
+
+ return True
+
+
+def Error(filename, linenum, category, confidence, message):
+ """Logs the fact we've found a lint error.
+
+ We log where the error was found, and also our confidence in the error,
+ that is, how certain we are this is a legitimate style regression, and
+ not a misidentification or a use that's sometimes justified.
+
+ False positives can be suppressed by the use of
+ "cpplint(category)" comments on the offending line. These are
+ parsed into _error_suppressions.
+
+ Args:
+ filename: The name of the file containing the error.
+ linenum: The number of the line containing the error.
+ category: A string used to describe the "category" this bug
+ falls under: "whitespace", say, or "runtime". Categories
+ may have a hierarchy separated by slashes: "whitespace/indent".
+ confidence: A number from 1-5 representing a confidence score for
+ the error, with 5 meaning that we are certain of the problem,
+ and 1 meaning that it could be a legitimate construct.
+ message: The error message.
+ """
+ if _ShouldPrintError(category, confidence, linenum):
+ _cpplint_state.IncrementErrorCount(category)
+ if _cpplint_state.output_format == 'vs7':
+ sys.stderr.write('%s(%s): %s [%s] [%d]\n' % (
+ filename, linenum, message, category, confidence))
+ elif _cpplint_state.output_format == 'eclipse':
+ sys.stderr.write('%s:%s: warning: %s [%s] [%d]\n' % (
+ filename, linenum, message, category, confidence))
+ else:
+ sys.stderr.write('%s:%s: %s [%s] [%d]\n' % (
+ filename, linenum, message, category, confidence))
+
+
+# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard.
+_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile(
+ r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)')
+# Match a single C style comment on the same line.
+_RE_PATTERN_C_COMMENTS = r'/\*(?:[^*]|\*(?!/))*\*/'
+# Matches multi-line C style comments.
+# This RE is a little bit more complicated than one might expect, because we
+# have to take care of space removals tools so we can handle comments inside
+# statements better.
+# The current rule is: We only clear spaces from both sides when we're at the
+# end of the line. Otherwise, we try to remove spaces from the right side,
+# if this doesn't work we try on left side but only if there's a non-character
+# on the right.
+_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile(
+ r'(\s*' + _RE_PATTERN_C_COMMENTS + r'\s*$|' +
+ _RE_PATTERN_C_COMMENTS + r'\s+|' +
+ r'\s+' + _RE_PATTERN_C_COMMENTS + r'(?=\W)|' +
+ _RE_PATTERN_C_COMMENTS + r')')
+
+
+def IsCppString(line):
+ """Does line terminate so, that the next symbol is in string constant.
+
+ This function does not consider single-line nor multi-line comments.
+
+ Args:
+ line: is a partial line of code starting from the 0..n.
+
+ Returns:
+ True, if next character appended to 'line' is inside a
+ string constant.
+ """
+
+ line = line.replace(r'\\', 'XX') # after this, \\" does not match to \"
+ return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1
+
+
+def CleanseRawStrings(raw_lines):
+ """Removes C++11 raw strings from lines.
+
+ Before:
+ static const char kData[] = R"(
+ multi-line string
+ )";
+
+ After:
+ static const char kData[] = ""
+ (replaced by blank line)
+ "";
+
+ Args:
+ raw_lines: list of raw lines.
+
+ Returns:
+ list of lines with C++11 raw strings replaced by empty strings.
+ """
+
+ delimiter = None
+ lines_without_raw_strings = []
+ for line in raw_lines:
+ if delimiter:
+ # Inside a raw string, look for the end
+ end = line.find(delimiter)
+ if end >= 0:
+ # Found the end of the string, match leading space for this
+ # line and resume copying the original lines, and also insert
+ # a "" on the last line.
+ leading_space = Match(r'^(\s*)\S', line)
+ line = leading_space.group(1) + '""' + line[end + len(delimiter):]
+ delimiter = None
+ else:
+ # Haven't found the end yet, append a blank line.
+ line = '""'
+
+ # Look for beginning of a raw string, and replace them with
+ # empty strings. This is done in a loop to handle multiple raw
+ # strings on the same line.
+ while delimiter is None:
+ # Look for beginning of a raw string.
+ # See 2.14.15 [lex.string] for syntax.
+ matched = Match(r'^(.*)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line)
+ if matched:
+ delimiter = ')' + matched.group(2) + '"'
+
+ end = matched.group(3).find(delimiter)
+ if end >= 0:
+ # Raw string ended on same line
+ line = (matched.group(1) + '""' +
+ matched.group(3)[end + len(delimiter):])
+ delimiter = None
+ else:
+ # Start of a multi-line raw string
+ line = matched.group(1) + '""'
+ else:
+ break
+
+ lines_without_raw_strings.append(line)
+
+ # TODO(unknown): if delimiter is not None here, we might want to
+ # emit a warning for unterminated string.
+ return lines_without_raw_strings
+
+
+def FindNextMultiLineCommentStart(lines, lineix):
+ """Find the beginning marker for a multiline comment."""
+ while lineix < len(lines):
+ if lines[lineix].strip().startswith('/*'):
+ # Only return this marker if the comment goes beyond this line
+ if lines[lineix].strip().find('*/', 2) < 0:
+ return lineix
+ lineix += 1
+ return len(lines)
+
+
+def FindNextMultiLineCommentEnd(lines, lineix):
+ """We are inside a comment, find the end marker."""
+ while lineix < len(lines):
+ if lines[lineix].strip().endswith('*/'):
+ return lineix
+ lineix += 1
+ return len(lines)
+
+
+def RemoveMultiLineCommentsFromRange(lines, begin, end):
+ """Clears a range of lines for multi-line comments."""
+ # Having // dummy comments makes the lines non-empty, so we will not get
+ # unnecessary blank line warnings later in the code.
+ for i in range(begin, end):
+ lines[i] = '/**/'
+
+
+def RemoveMultiLineComments(filename, lines, error):
+ """Removes multiline (c-style) comments from lines."""
+ lineix = 0
+ while lineix < len(lines):
+ lineix_begin = FindNextMultiLineCommentStart(lines, lineix)
+ if lineix_begin >= len(lines):
+ return
+ lineix_end = FindNextMultiLineCommentEnd(lines, lineix_begin)
+ if lineix_end >= len(lines):
+ error(filename, lineix_begin + 1, 'readability/multiline_comment', 5,
+ 'Could not find end of multi-line comment')
+ return
+ RemoveMultiLineCommentsFromRange(lines, lineix_begin, lineix_end + 1)
+ lineix = lineix_end + 1
+
+
+def CleanseComments(line):
+ """Removes //-comments and single-line C-style /* */ comments.
+
+ Args:
+ line: A line of C++ source.
+
+ Returns:
+ The line with single-line comments removed.
+ """
+ commentpos = line.find('//')
+ if commentpos != -1 and not IsCppString(line[:commentpos]):
+ line = line[:commentpos].rstrip()
+ # get rid of /* ... */
+ return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line)
+
+
+class CleansedLines(object):
+ """Holds 4 copies of all lines with different preprocessing applied to them.
+
+ 1) elided member contains lines without strings and comments.
+ 2) lines member contains lines without comments.
+ 3) raw_lines member contains all the lines without processing.
+ 4) lines_without_raw_strings member is same as raw_lines, but with C++11 raw
+ strings removed.
+ All these members are of <type 'list'>, and of the same length.
+ """
+
+ def __init__(self, lines):
+ self.elided = []
+ self.lines = []
+ self.raw_lines = lines
+ self.num_lines = len(lines)
+ self.lines_without_raw_strings = CleanseRawStrings(lines)
+ for linenum in range(len(self.lines_without_raw_strings)):
+ self.lines.append(CleanseComments(
+ self.lines_without_raw_strings[linenum]))
+ elided = self._CollapseStrings(self.lines_without_raw_strings[linenum])
+ self.elided.append(CleanseComments(elided))
+
+ def NumLines(self):
+ """Returns the number of lines represented."""
+ return self.num_lines
+
+ @staticmethod
+ def _CollapseStrings(elided):
+ """Collapses strings and chars on a line to simple "" or '' blocks.
+
+ We nix strings first so we're not fooled by text like '"http://"'
+
+ Args:
+ elided: The line being processed.
+
+ Returns:
+ The line with collapsed strings.
+ """
+ if _RE_PATTERN_INCLUDE.match(elided):
+ return elided
+
+ # Remove escaped characters first to make quote/single quote collapsing
+ # basic. Things that look like escaped characters shouldn't occur
+ # outside of strings and chars.
+ elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided)
+
+ # Replace quoted strings and digit separators. Both single quotes
+ # and double quotes are processed in the same loop, otherwise
+ # nested quotes wouldn't work.
+ collapsed = ''
+ while True:
+ # Find the first quote character
+ match = Match(r'^([^\'"]*)([\'"])(.*)$', elided)
+ if not match:
+ collapsed += elided
+ break
+ head, quote, tail = match.groups()
+
+ if quote == '"':
+ # Collapse double quoted strings
+ second_quote = tail.find('"')
+ if second_quote >= 0:
+ collapsed += head + '""'
+ elided = tail[second_quote + 1:]
+ else:
+ # Unmatched double quote, don't bother processing the rest
+ # of the line since this is probably a multiline string.
+ collapsed += elided
+ break
+ else:
+ # Found single quote, check nearby text to eliminate digit separators.
+ #
+ # There is no special handling for floating point here, because
+ # the integer/fractional/exponent parts would all be parsed
+ # correctly as long as there are digits on both sides of the
+ # separator. So we are fine as long as we don't see something
+ # like "0.'3" (gcc 4.9.0 will not allow this literal).
+ if Search(r'\b(?:0[bBxX]?|[1-9])[0-9a-fA-F]*$', head):
+ match_literal = Match(r'^((?:\'?[0-9a-zA-Z_])*)(.*)$', "'" + tail)
+ collapsed += head + match_literal.group(1).replace("'", '')
+ elided = match_literal.group(2)
+ else:
+ second_quote = tail.find('\'')
+ if second_quote >= 0:
+ collapsed += head + "''"
+ elided = tail[second_quote + 1:]
+ else:
+ # Unmatched single quote
+ collapsed += elided
+ break
+
+ return collapsed
+
+
+def FindEndOfExpressionInLine(line, startpos, stack):
+ """Find the position just after the end of current parenthesized expression.
+
+ Args:
+ line: a CleansedLines line.
+ startpos: start searching at this position.
+ stack: nesting stack at startpos.
+
+ Returns:
+ On finding matching end: (index just after matching end, None)
+ On finding an unclosed expression: (-1, None)
+ Otherwise: (-1, new stack at end of this line)
+ """
+ for i in xrange(startpos, len(line)):
+ char = line[i]
+ if char in '([{':
+ # Found start of parenthesized expression, push to expression stack
+ stack.append(char)
+ elif char == '<':
+ # Found potential start of template argument list
+ if i > 0 and line[i - 1] == '<':
+ # Left shift operator
+ if stack and stack[-1] == '<':
+ stack.pop()
+ if not stack:
+ return (-1, None)
+ elif i > 0 and Search(r'\boperator\s*$', line[0:i]):
+ # operator<, don't add to stack
+ continue
+ else:
+ # Tentative start of template argument list
+ stack.append('<')
+ elif char in ')]}':
+ # Found end of parenthesized expression.
+ #
+ # If we are currently expecting a matching '>', the pending '<'
+ # must have been an operator. Remove them from expression stack.
+ while stack and stack[-1] == '<':
+ stack.pop()
+ if not stack:
+ return (-1, None)
+ if ((stack[-1] == '(' and char == ')') or
+ (stack[-1] == '[' and char == ']') or
+ (stack[-1] == '{' and char == '}')):
+ stack.pop()
+ if not stack:
+ return (i + 1, None)
+ else:
+ # Mismatched parentheses
+ return (-1, None)
+ elif char == '>':
+ # Found potential end of template argument list.
+
+ # Ignore "->" and operator functions
+ if (i > 0 and
+ (line[i - 1] == '-' or Search(r'\boperator\s*$', line[0:i - 1]))):
+ continue
+
+ # Pop the stack if there is a matching '<'. Otherwise, ignore
+ # this '>' since it must be an operator.
+ if stack:
+ if stack[-1] == '<':
+ stack.pop()
+ if not stack:
+ return (i + 1, None)
+ elif char == ';':
+ # Found something that look like end of statements. If we are currently
+ # expecting a '>', the matching '<' must have been an operator, since
+ # template argument list should not contain statements.
+ while stack and stack[-1] == '<':
+ stack.pop()
+ if not stack:
+ return (-1, None)
+
+ # Did not find end of expression or unbalanced parentheses on this line
+ return (-1, stack)
+
+
+def CloseExpression(clean_lines, linenum, pos):
+ """If input points to ( or { or [ or <, finds the position that closes it.
+
+ If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the
+ linenum/pos that correspond to the closing of the expression.
+
+ TODO(unknown): cpplint spends a fair bit of time matching parentheses.
+ Ideally we would want to index all opening and closing parentheses once
+ and have CloseExpression be just a simple lookup, but due to preprocessor
+ tricks, this is not so easy.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ pos: A position on the line.
+
+ Returns:
+ A tuple (line, linenum, pos) pointer *past* the closing brace, or
+ (line, len(lines), -1) if we never find a close. Note we ignore
+ strings and comments when matching; and the line we return is the
+ 'cleansed' line at linenum.
+ """
+
+ line = clean_lines.elided[linenum]
+ if (line[pos] not in '({[<') or Match(r'<[<=]', line[pos:]):
+ return (line, clean_lines.NumLines(), -1)
+
+ # Check first line
+ (end_pos, stack) = FindEndOfExpressionInLine(line, pos, [])
+ if end_pos > -1:
+ return (line, linenum, end_pos)
+
+ # Continue scanning forward
+ while stack and linenum < clean_lines.NumLines() - 1:
+ linenum += 1
+ line = clean_lines.elided[linenum]
+ (end_pos, stack) = FindEndOfExpressionInLine(line, 0, stack)
+ if end_pos > -1:
+ return (line, linenum, end_pos)
+
+ # Did not find end of expression before end of file, give up
+ return (line, clean_lines.NumLines(), -1)
+
+
+def FindStartOfExpressionInLine(line, endpos, stack):
+ """Find position at the matching start of current expression.
+
+ This is almost the reverse of FindEndOfExpressionInLine, but note
+ that the input position and returned position differs by 1.
+
+ Args:
+ line: a CleansedLines line.
+ endpos: start searching at this position.
+ stack: nesting stack at endpos.
+
+ Returns:
+ On finding matching start: (index at matching start, None)
+ On finding an unclosed expression: (-1, None)
+ Otherwise: (-1, new stack at beginning of this line)
+ """
+ i = endpos
+ while i >= 0:
+ char = line[i]
+ if char in ')]}':
+ # Found end of expression, push to expression stack
+ stack.append(char)
+ elif char == '>':
+ # Found potential end of template argument list.
+ #
+ # Ignore it if it's a "->" or ">=" or "operator>"
+ if (i > 0 and
+ (line[i - 1] == '-' or
+ Match(r'\s>=\s', line[i - 1:]) or
+ Search(r'\boperator\s*$', line[0:i]))):
+ i -= 1
+ else:
+ stack.append('>')
+ elif char == '<':
+ # Found potential start of template argument list
+ if i > 0 and line[i - 1] == '<':
+ # Left shift operator
+ i -= 1
+ else:
+ # If there is a matching '>', we can pop the expression stack.
+ # Otherwise, ignore this '<' since it must be an operator.
+ if stack and stack[-1] == '>':
+ stack.pop()
+ if not stack:
+ return (i, None)
+ elif char in '([{':
+ # Found start of expression.
+ #
+ # If there are any unmatched '>' on the stack, they must be
+ # operators. Remove those.
+ while stack and stack[-1] == '>':
+ stack.pop()
+ if not stack:
+ return (-1, None)
+ if ((char == '(' and stack[-1] == ')') or
+ (char == '[' and stack[-1] == ']') or
+ (char == '{' and stack[-1] == '}')):
+ stack.pop()
+ if not stack:
+ return (i, None)
+ else:
+ # Mismatched parentheses
+ return (-1, None)
+ elif char == ';':
+ # Found something that look like end of statements. If we are currently
+ # expecting a '<', the matching '>' must have been an operator, since
+ # template argument list should not contain statements.
+ while stack and stack[-1] == '>':
+ stack.pop()
+ if not stack:
+ return (-1, None)
+
+ i -= 1
+
+ return (-1, stack)
+
+
+def ReverseCloseExpression(clean_lines, linenum, pos):
+ """If input points to ) or } or ] or >, finds the position that opens it.
+
+ If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the
+ linenum/pos that correspond to the opening of the expression.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ pos: A position on the line.
+
+ Returns:
+ A tuple (line, linenum, pos) pointer *at* the opening brace, or
+ (line, 0, -1) if we never find the matching opening brace. Note
+ we ignore strings and comments when matching; and the line we
+ return is the 'cleansed' line at linenum.
+ """
+ line = clean_lines.elided[linenum]
+ if line[pos] not in ')}]>':
+ return (line, 0, -1)
+
+ # Check last line
+ (start_pos, stack) = FindStartOfExpressionInLine(line, pos, [])
+ if start_pos > -1:
+ return (line, linenum, start_pos)
+
+ # Continue scanning backward
+ while stack and linenum > 0:
+ linenum -= 1
+ line = clean_lines.elided[linenum]
+ (start_pos, stack) = FindStartOfExpressionInLine(line, len(line) - 1, stack)
+ if start_pos > -1:
+ return (line, linenum, start_pos)
+
+ # Did not find start of expression before beginning of file, give up
+ return (line, 0, -1)
+
+
+def CheckForCopyright(filename, lines, error):
+ """Logs an error if no Copyright message appears at the top of the file."""
+
+ # We'll say it should occur by line 10. Don't forget there's a
+ # dummy line at the front.
+ for line in xrange(1, min(len(lines), 11)):
+ if re.search(r'Copyright', lines[line], re.I): break
+ else: # means no copyright line was found
+ error(filename, 0, 'legal/copyright', 5,
+ 'No copyright message found. '
+ 'You should have a line: "Copyright [year] <Copyright Owner>"')
+
+
+def GetIndentLevel(line):
+ """Return the number of leading spaces in line.
+
+ Args:
+ line: A string to check.
+
+ Returns:
+ An integer count of leading spaces, possibly zero.
+ """
+ indent = Match(r'^( *)\S', line)
+ if indent:
+ return len(indent.group(1))
+ else:
+ return 0
+
+
+def GetHeaderGuardCPPVariable(filename):
+ """Returns the CPP variable that should be used as a header guard.
+
+ Args:
+ filename: The name of a C++ header file.
+
+ Returns:
+ The CPP variable that should be used as a header guard in the
+ named file.
+
+ """
+
+ # Restores original filename in case that cpplint is invoked from Emacs's
+ # flymake.
+ filename = re.sub(r'_flymake\.h$', '.h', filename)
+ filename = re.sub(r'/\.flymake/([^/]*)$', r'/\1', filename)
+ # Replace 'c++' with 'cpp'.
+ filename = filename.replace('C++', 'cpp').replace('c++', 'cpp')
+
+ fileinfo = FileInfo(filename)
+ file_path_from_root = fileinfo.RepositoryName()
+ if _root:
+ file_path_from_root = re.sub('^' + _root + os.sep, '', file_path_from_root)
+ return re.sub(r'[^a-zA-Z0-9]', '_', file_path_from_root).upper() + '_'
+
+
+def CheckForHeaderGuard(filename, clean_lines, error):
+ """Checks that the file contains a header guard.
+
+ Logs an error if no #ifndef header guard is present. For other
+ headers, checks that the full pathname is used.
+
+ Args:
+ filename: The name of the C++ header file.
+ clean_lines: A CleansedLines instance containing the file.
+ error: The function to call with any errors found.
+ """
+
+ # Don't check for header guards if there are error suppression
+ # comments somewhere in this file.
+ #
+ # Because this is silencing a warning for a nonexistent line, we
+ # only support the very specific NOLINT(build/header_guard) syntax,
+ # and not the general NOLINT or NOLINT(*) syntax.
+ raw_lines = clean_lines.lines_without_raw_strings
+ for i in raw_lines:
+ if Search(r'//\s*NOLINT\(build/header_guard\)', i):
+ return
+
+ cppvar = GetHeaderGuardCPPVariable(filename)
+
+ ifndef = ''
+ ifndef_linenum = 0
+ define = ''
+ endif = ''
+ endif_linenum = 0
+ for linenum, line in enumerate(raw_lines):
+ linesplit = line.split()
+ if len(linesplit) >= 2:
+ # find the first occurrence of #ifndef and #define, save arg
+ if not ifndef and linesplit[0] == '#ifndef':
+ # set ifndef to the header guard presented on the #ifndef line.
+ ifndef = linesplit[1]
+ ifndef_linenum = linenum
+ if not define and linesplit[0] == '#define':
+ define = linesplit[1]
+ # find the last occurrence of #endif, save entire line
+ if line.startswith('#endif'):
+ endif = line
+ endif_linenum = linenum
+
+ if not ifndef or not define or ifndef != define:
+ error(filename, 0, 'build/header_guard', 5,
+ 'No #ifndef header guard found, suggested CPP variable is: %s' %
+ cppvar)
+ return
+
+ # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__
+ # for backward compatibility.
+ if ifndef != cppvar:
+ error_level = 0
+ if ifndef != cppvar + '_':
+ error_level = 5
+
+ ParseNolintSuppressions(filename, raw_lines[ifndef_linenum], ifndef_linenum,
+ error)
+ error(filename, ifndef_linenum, 'build/header_guard', error_level,
+ '#ifndef header guard has wrong style, please use: %s' % cppvar)
+
+ # Check for "//" comments on endif line.
+ ParseNolintSuppressions(filename, raw_lines[endif_linenum], endif_linenum,
+ error)
+ match = Match(r'#endif\s*//\s*' + cppvar + r'(_)?\b', endif)
+ if match:
+ if match.group(1) == '_':
+ # Issue low severity warning for deprecated double trailing underscore
+ error(filename, endif_linenum, 'build/header_guard', 0,
+ '#endif line should be "#endif // %s"' % cppvar)
+ return
+
+ # Didn't find the corresponding "//" comment. If this file does not
+ # contain any "//" comments at all, it could be that the compiler
+ # only wants "/**/" comments, look for those instead.
+ no_single_line_comments = True
+ for i in xrange(1, len(raw_lines) - 1):
+ line = raw_lines[i]
+ if Match(r'^(?:(?:\'(?:\.|[^\'])*\')|(?:"(?:\.|[^"])*")|[^\'"])*//', line):
+ no_single_line_comments = False
+ break
+
+ if no_single_line_comments:
+ match = Match(r'#endif\s*/\*\s*' + cppvar + r'(_)?\s*\*/', endif)
+ if match:
+ if match.group(1) == '_':
+ # Low severity warning for double trailing underscore
+ error(filename, endif_linenum, 'build/header_guard', 0,
+ '#endif line should be "#endif /* %s */"' % cppvar)
+ return
+
+ # Didn't find anything
+ error(filename, endif_linenum, 'build/header_guard', 5,
+ '#endif line should be "#endif // %s"' % cppvar)
+
+
+def CheckHeaderFileIncluded(filename, include_state, error):
+ """Logs an error if a .cc file does not include its header."""
+
+ # Do not check test files
+ if filename.endswith('_test.cc') or filename.endswith('_unittest.cc'):
+ return
+
+ fileinfo = FileInfo(filename)
+ headerfile = filename[0:len(filename) - 2] + 'h'
+ if not os.path.exists(headerfile):
+ return
+ headername = FileInfo(headerfile).RepositoryName()
+ first_include = 0
+ for section_list in include_state.include_list:
+ for f in section_list:
+ if headername in f[0] or f[0] in headername:
+ return
+ if not first_include:
+ first_include = f[1]
+
+ error(filename, first_include, 'build/include', 5,
+ '%s should include its header file %s' % (fileinfo.RepositoryName(),
+ headername))
+
+
+def CheckForBadCharacters(filename, lines, error):
+ """Logs an error for each line containing bad characters.
+
+ Two kinds of bad characters:
+
+ 1. Unicode replacement characters: These indicate that either the file
+ contained invalid UTF-8 (likely) or Unicode replacement characters (which
+ it shouldn't). Note that it's possible for this to throw off line
+ numbering if the invalid UTF-8 occurred adjacent to a newline.
+
+ 2. NUL bytes. These are problematic for some tools.
+
+ Args:
+ filename: The name of the current file.
+ lines: An array of strings, each representing a line of the file.
+ error: The function to call with any errors found.
+ """
+ for linenum, line in enumerate(lines):
+ if u'\ufffd' in line:
+ error(filename, linenum, 'readability/utf8', 5,
+ 'Line contains invalid UTF-8 (or Unicode replacement character).')
+ if '\0' in line:
+ error(filename, linenum, 'readability/nul', 5, 'Line contains NUL byte.')
+
+
+def CheckForNewlineAtEOF(filename, lines, error):
+ """Logs an error if there is no newline char at the end of the file.
+
+ Args:
+ filename: The name of the current file.
+ lines: An array of strings, each representing a line of the file.
+ error: The function to call with any errors found.
+ """
+
+ # The array lines() was created by adding two newlines to the
+ # original file (go figure), then splitting on \n.
+ # To verify that the file ends in \n, we just have to make sure the
+ # last-but-two element of lines() exists and is empty.
+ if len(lines) < 3 or lines[-2]:
+ error(filename, len(lines) - 2, 'whitespace/ending_newline', 5,
+ 'Could not find a newline character at the end of the file.')
+
+
+def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error):
+ """Logs an error if we see /* ... */ or "..." that extend past one line.
+
+ /* ... */ comments are legit inside macros, for one line.
+ Otherwise, we prefer // comments, so it's ok to warn about the
+ other. Likewise, it's ok for strings to extend across multiple
+ lines, as long as a line continuation character (backslash)
+ terminates each line. Although not currently prohibited by the C++
+ style guide, it's ugly and unnecessary. We don't do well with either
+ in this lint program, so we warn about both.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Remove all \\ (escaped backslashes) from the line. They are OK, and the
+ # second (escaped) slash may trigger later \" detection erroneously.
+ line = line.replace('\\\\', '')
+
+ if line.count('/*') > line.count('*/'):
+ error(filename, linenum, 'readability/multiline_comment', 5,
+ 'Complex multi-line /*...*/-style comment found. '
+ 'Lint may give bogus warnings. '
+ 'Consider replacing these with //-style comments, '
+ 'with #if 0...#endif, '
+ 'or with more clearly structured multi-line comments.')
+
+ if (line.count('"') - line.count('\\"')) % 2:
+ error(filename, linenum, 'readability/multiline_string', 5,
+ 'Multi-line string ("...") found. This lint script doesn\'t '
+ 'do well with such strings, and may give bogus warnings. '
+ 'Use C++11 raw strings or concatenation instead.')
+
+
+# (non-threadsafe name, thread-safe alternative, validation pattern)
+#
+# The validation pattern is used to eliminate false positives such as:
+# _rand(); // false positive due to substring match.
+# ->rand(); // some member function rand().
+# ACMRandom rand(seed); // some variable named rand.
+# ISAACRandom rand(); // another variable named rand.
+#
+# Basically we require the return value of these functions to be used
+# in some expression context on the same line by matching on some
+# operator before the function name. This eliminates constructors and
+# member function calls.
+_UNSAFE_FUNC_PREFIX = r'(?:[-+*/=%^&|(<]\s*|>\s+)'
+_THREADING_LIST = (
+ ('asctime(', 'asctime_r(', _UNSAFE_FUNC_PREFIX + r'asctime\([^)]+\)'),
+ ('ctime(', 'ctime_r(', _UNSAFE_FUNC_PREFIX + r'ctime\([^)]+\)'),
+ ('getgrgid(', 'getgrgid_r(', _UNSAFE_FUNC_PREFIX + r'getgrgid\([^)]+\)'),
+ ('getgrnam(', 'getgrnam_r(', _UNSAFE_FUNC_PREFIX + r'getgrnam\([^)]+\)'),
+ ('getlogin(', 'getlogin_r(', _UNSAFE_FUNC_PREFIX + r'getlogin\(\)'),
+ ('getpwnam(', 'getpwnam_r(', _UNSAFE_FUNC_PREFIX + r'getpwnam\([^)]+\)'),
+ ('getpwuid(', 'getpwuid_r(', _UNSAFE_FUNC_PREFIX + r'getpwuid\([^)]+\)'),
+ ('gmtime(', 'gmtime_r(', _UNSAFE_FUNC_PREFIX + r'gmtime\([^)]+\)'),
+ ('localtime(', 'localtime_r(', _UNSAFE_FUNC_PREFIX + r'localtime\([^)]+\)'),
+ ('rand(', 'rand_r(', _UNSAFE_FUNC_PREFIX + r'rand\(\)'),
+ ('strtok(', 'strtok_r(',
+ _UNSAFE_FUNC_PREFIX + r'strtok\([^)]+\)'),
+ ('ttyname(', 'ttyname_r(', _UNSAFE_FUNC_PREFIX + r'ttyname\([^)]+\)'),
+ )
+
+
+def CheckPosixThreading(filename, clean_lines, linenum, error):
+ """Checks for calls to thread-unsafe functions.
+
+ Much code has been originally written without consideration of
+ multi-threading. Also, engineers are relying on their old experience;
+ they have learned posix before threading extensions were added. These
+ tests guide the engineers to use thread-safe functions (when using
+ posix directly).
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+ for single_thread_func, multithread_safe_func, pattern in _THREADING_LIST:
+ # Additional pattern matching check to confirm that this is the
+ # function we are looking for
+ if Search(pattern, line):
+ error(filename, linenum, 'runtime/threadsafe_fn', 2,
+ 'Consider using ' + multithread_safe_func +
+ '...) instead of ' + single_thread_func +
+ '...) for improved thread safety.')
+
+
+def CheckVlogArguments(filename, clean_lines, linenum, error):
+ """Checks that VLOG() is only used for defining a logging level.
+
+ For example, VLOG(2) is correct. VLOG(INFO), VLOG(WARNING), VLOG(ERROR), and
+ VLOG(FATAL) are not.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+ if Search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line):
+ error(filename, linenum, 'runtime/vlog', 5,
+ 'VLOG() should be used with numeric verbosity level. '
+ 'Use LOG() if you want symbolic severity levels.')
+
+# Matches invalid increment: *count++, which moves pointer instead of
+# incrementing a value.
+_RE_PATTERN_INVALID_INCREMENT = re.compile(
+ r'^\s*\*\w+(\+\+|--);')
+
+
+def CheckInvalidIncrement(filename, clean_lines, linenum, error):
+ """Checks for invalid increment *count++.
+
+ For example following function:
+ void increment_counter(int* count) {
+ *count++;
+ }
+ is invalid, because it effectively does count++, moving pointer, and should
+ be replaced with ++*count, (*count)++ or *count += 1.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+ if _RE_PATTERN_INVALID_INCREMENT.match(line):
+ error(filename, linenum, 'runtime/invalid_increment', 5,
+ 'Changing pointer instead of value (or unused value of operator*).')
+
+
+def IsMacroDefinition(clean_lines, linenum):
+ if Search(r'^#define', clean_lines[linenum]):
+ return True
+
+ if linenum > 0 and Search(r'\\$', clean_lines[linenum - 1]):
+ return True
+
+ return False
+
+
+def IsForwardClassDeclaration(clean_lines, linenum):
+ return Match(r'^\s*(\btemplate\b)*.*class\s+\w+;\s*$', clean_lines[linenum])
+
+
+class _BlockInfo(object):
+ """Stores information about a generic block of code."""
+
+ def __init__(self, seen_open_brace):
+ self.seen_open_brace = seen_open_brace
+ self.open_parentheses = 0
+ self.inline_asm = _NO_ASM
+ self.check_namespace_indentation = False
+
+ def CheckBegin(self, filename, clean_lines, linenum, error):
+ """Run checks that applies to text up to the opening brace.
+
+ This is mostly for checking the text after the class identifier
+ and the "{", usually where the base class is specified. For other
+ blocks, there isn't much to check, so we always pass.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ pass
+
+ def CheckEnd(self, filename, clean_lines, linenum, error):
+ """Run checks that applies to text after the closing brace.
+
+ This is mostly used for checking end of namespace comments.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ pass
+
+ def IsBlockInfo(self):
+ """Returns true if this block is a _BlockInfo.
+
+ This is convenient for verifying that an object is an instance of
+ a _BlockInfo, but not an instance of any of the derived classes.
+
+ Returns:
+ True for this class, False for derived classes.
+ """
+ return self.__class__ == _BlockInfo
+
+
+class _ExternCInfo(_BlockInfo):
+ """Stores information about an 'extern "C"' block."""
+
+ def __init__(self):
+ _BlockInfo.__init__(self, True)
+
+
+class _ClassInfo(_BlockInfo):
+ """Stores information about a class."""
+
+ def __init__(self, name, class_or_struct, clean_lines, linenum):
+ _BlockInfo.__init__(self, False)
+ self.name = name
+ self.starting_linenum = linenum
+ self.is_derived = False
+ self.check_namespace_indentation = True
+ if class_or_struct == 'struct':
+ self.access = 'public'
+ self.is_struct = True
+ else:
+ self.access = 'private'
+ self.is_struct = False
+
+ # Remember initial indentation level for this class. Using raw_lines here
+ # instead of elided to account for leading comments.
+ self.class_indent = GetIndentLevel(clean_lines.raw_lines[linenum])
+
+ # Try to find the end of the class. This will be confused by things like:
+ # class A {
+ # } *x = { ...
+ #
+ # But it's still good enough for CheckSectionSpacing.
+ self.last_line = 0
+ depth = 0
+ for i in range(linenum, clean_lines.NumLines()):
+ line = clean_lines.elided[i]
+ depth += line.count('{') - line.count('}')
+ if not depth:
+ self.last_line = i
+ break
+
+ def CheckBegin(self, filename, clean_lines, linenum, error):
+ # Look for a bare ':'
+ if Search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]):
+ self.is_derived = True
+
+ def CheckEnd(self, filename, clean_lines, linenum, error):
+ # If there is a DISALLOW macro, it should appear near the end of
+ # the class.
+ seen_last_thing_in_class = False
+ for i in xrange(linenum - 1, self.starting_linenum, -1):
+ match = Search(
+ r'\b(DISALLOW_COPY_AND_ASSIGN|DISALLOW_IMPLICIT_CONSTRUCTORS)\(' +
+ self.name + r'\)',
+ clean_lines.elided[i])
+ if match:
+ if seen_last_thing_in_class:
+ error(filename, i, 'readability/constructors', 3,
+ match.group(1) + ' should be the last thing in the class')
+ break
+
+ if not Match(r'^\s*$', clean_lines.elided[i]):
+ seen_last_thing_in_class = True
+
+ # Check that closing brace is aligned with beginning of the class.
+ # Only do this if the closing brace is indented by only whitespaces.
+ # This means we will not check single-line class definitions.
+ indent = Match(r'^( *)\}', clean_lines.elided[linenum])
+ if indent and len(indent.group(1)) != self.class_indent:
+ if self.is_struct:
+ parent = 'struct ' + self.name
+ else:
+ parent = 'class ' + self.name
+ error(filename, linenum, 'whitespace/indent', 3,
+ 'Closing brace should be aligned with beginning of %s' % parent)
+
+
+class _NamespaceInfo(_BlockInfo):
+ """Stores information about a namespace."""
+
+ def __init__(self, name, linenum):
+ _BlockInfo.__init__(self, False)
+ self.name = name or ''
+ self.starting_linenum = linenum
+ self.check_namespace_indentation = True
+
+ def CheckEnd(self, filename, clean_lines, linenum, error):
+ """Check end of namespace comments."""
+ line = clean_lines.raw_lines[linenum]
+
+ # Check how many lines is enclosed in this namespace. Don't issue
+ # warning for missing namespace comments if there aren't enough
+ # lines. However, do apply checks if there is already an end of
+ # namespace comment and it's incorrect.
+ #
+ # TODO(unknown): We always want to check end of namespace comments
+ # if a namespace is large, but sometimes we also want to apply the
+ # check if a short namespace contained nontrivial things (something
+ # other than forward declarations). There is currently no logic on
+ # deciding what these nontrivial things are, so this check is
+ # triggered by namespace size only, which works most of the time.
+ if (linenum - self.starting_linenum < 10
+ and not Match(r'};*\s*(//|/\*).*\bnamespace\b', line)):
+ return
+
+ # Look for matching comment at end of namespace.
+ #
+ # Note that we accept C style "/* */" comments for terminating
+ # namespaces, so that code that terminate namespaces inside
+ # preprocessor macros can be cpplint clean.
+ #
+ # We also accept stuff like "// end of namespace <name>." with the
+ # period at the end.
+ #
+ # Besides these, we don't accept anything else, otherwise we might
+ # get false negatives when existing comment is a substring of the
+ # expected namespace.
+ if self.name:
+ # Named namespace
+ if not Match((r'};*\s*(//|/\*).*\bnamespace\s+' + re.escape(self.name) +
+ r'[\*/\.\\\s]*$'),
+ line):
+ error(filename, linenum, 'readability/namespace', 5,
+ 'Namespace should be terminated with "// namespace %s"' %
+ self.name)
+ else:
+ # Anonymous namespace
+ if not Match(r'};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line):
+ # If "// namespace anonymous" or "// anonymous namespace (more text)",
+ # mention "// anonymous namespace" as an acceptable form
+ if Match(r'}.*\b(namespace anonymous|anonymous namespace)\b', line):
+ error(filename, linenum, 'readability/namespace', 5,
+ 'Anonymous namespace should be terminated with "// namespace"'
+ ' or "// anonymous namespace"')
+ else:
+ error(filename, linenum, 'readability/namespace', 5,
+ 'Anonymous namespace should be terminated with "// namespace"')
+
+
+class _PreprocessorInfo(object):
+ """Stores checkpoints of nesting stacks when #if/#else is seen."""
+
+ def __init__(self, stack_before_if):
+ # The entire nesting stack before #if
+ self.stack_before_if = stack_before_if
+
+ # The entire nesting stack up to #else
+ self.stack_before_else = []
+
+ # Whether we have already seen #else or #elif
+ self.seen_else = False
+
+
+class NestingState(object):
+ """Holds states related to parsing braces."""
+
+ def __init__(self):
+ # Stack for tracking all braces. An object is pushed whenever we
+ # see a "{", and popped when we see a "}". Only 3 types of
+ # objects are possible:
+ # - _ClassInfo: a class or struct.
+ # - _NamespaceInfo: a namespace.
+ # - _BlockInfo: some other type of block.
+ self.stack = []
+
+ # Top of the previous stack before each Update().
+ #
+ # Because the nesting_stack is updated at the end of each line, we
+ # had to do some convoluted checks to find out what is the current
+ # scope at the beginning of the line. This check is simplified by
+ # saving the previous top of nesting stack.
+ #
+ # We could save the full stack, but we only need the top. Copying
+ # the full nesting stack would slow down cpplint by ~10%.
+ self.previous_stack_top = []
+
+ # Stack of _PreprocessorInfo objects.
+ self.pp_stack = []
+
+ def SeenOpenBrace(self):
+ """Check if we have seen the opening brace for the innermost block.
+
+ Returns:
+ True if we have seen the opening brace, False if the innermost
+ block is still expecting an opening brace.
+ """
+ return (not self.stack) or self.stack[-1].seen_open_brace
+
+ def InNamespaceBody(self):
+ """Check if we are currently one level inside a namespace body.
+
+ Returns:
+ True if top of the stack is a namespace block, False otherwise.
+ """
+ return self.stack and isinstance(self.stack[-1], _NamespaceInfo)
+
+ def InExternC(self):
+ """Check if we are currently one level inside an 'extern "C"' block.
+
+ Returns:
+ True if top of the stack is an extern block, False otherwise.
+ """
+ return self.stack and isinstance(self.stack[-1], _ExternCInfo)
+
+ def InClassDeclaration(self):
+ """Check if we are currently one level inside a class or struct declaration.
+
+ Returns:
+ True if top of the stack is a class/struct, False otherwise.
+ """
+ return self.stack and isinstance(self.stack[-1], _ClassInfo)
+
+ def InAsmBlock(self):
+ """Check if we are currently one level inside an inline ASM block.
+
+ Returns:
+ True if the top of the stack is a block containing inline ASM.
+ """
+ return self.stack and self.stack[-1].inline_asm != _NO_ASM
+
+ def InTemplateArgumentList(self, clean_lines, linenum, pos):
+ """Check if current position is inside template argument list.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ pos: position just after the suspected template argument.
+ Returns:
+ True if (linenum, pos) is inside template arguments.
+ """
+ while linenum < clean_lines.NumLines():
+ # Find the earliest character that might indicate a template argument
+ line = clean_lines.elided[linenum]
+ match = Match(r'^[^{};=\[\]\.<>]*(.)', line[pos:])
+ if not match:
+ linenum += 1
+ pos = 0
+ continue
+ token = match.group(1)
+ pos += len(match.group(0))
+
+ # These things do not look like template argument list:
+ # class Suspect {
+ # class Suspect x; }
+ if token in ('{', '}', ';'): return False
+
+ # These things look like template argument list:
+ # template <class Suspect>
+ # template <class Suspect = default_value>
+ # template <class Suspect[]>
+ # template <class Suspect...>
+ if token in ('>', '=', '[', ']', '.'): return True
+
+ # Check if token is an unmatched '<'.
+ # If not, move on to the next character.
+ if token != '<':
+ pos += 1
+ if pos >= len(line):
+ linenum += 1
+ pos = 0
+ continue
+
+ # We can't be sure if we just find a single '<', and need to
+ # find the matching '>'.
+ (_, end_line, end_pos) = CloseExpression(clean_lines, linenum, pos - 1)
+ if end_pos < 0:
+ # Not sure if template argument list or syntax error in file
+ return False
+ linenum = end_line
+ pos = end_pos
+ return False
+
+ def UpdatePreprocessor(self, line):
+ """Update preprocessor stack.
+
+ We need to handle preprocessors due to classes like this:
+ #ifdef SWIG
+ struct ResultDetailsPageElementExtensionPoint {
+ #else
+ struct ResultDetailsPageElementExtensionPoint : public Extension {
+ #endif
+
+ We make the following assumptions (good enough for most files):
+ - Preprocessor condition evaluates to true from #if up to first
+ #else/#elif/#endif.
+
+ - Preprocessor condition evaluates to false from #else/#elif up
+ to #endif. We still perform lint checks on these lines, but
+ these do not affect nesting stack.
+
+ Args:
+ line: current line to check.
+ """
+ if Match(r'^\s*#\s*(if|ifdef|ifndef)\b', line):
+ # Beginning of #if block, save the nesting stack here. The saved
+ # stack will allow us to restore the parsing state in the #else case.
+ self.pp_stack.append(_PreprocessorInfo(copy.deepcopy(self.stack)))
+ elif Match(r'^\s*#\s*(else|elif)\b', line):
+ # Beginning of #else block
+ if self.pp_stack:
+ if not self.pp_stack[-1].seen_else:
+ # This is the first #else or #elif block. Remember the
+ # whole nesting stack up to this point. This is what we
+ # keep after the #endif.
+ self.pp_stack[-1].seen_else = True
+ self.pp_stack[-1].stack_before_else = copy.deepcopy(self.stack)
+
+ # Restore the stack to how it was before the #if
+ self.stack = copy.deepcopy(self.pp_stack[-1].stack_before_if)
+ else:
+ # TODO(unknown): unexpected #else, issue warning?
+ pass
+ elif Match(r'^\s*#\s*endif\b', line):
+ # End of #if or #else blocks.
+ if self.pp_stack:
+ # If we saw an #else, we will need to restore the nesting
+ # stack to its former state before the #else, otherwise we
+ # will just continue from where we left off.
+ if self.pp_stack[-1].seen_else:
+ # Here we can just use a shallow copy since we are the last
+ # reference to it.
+ self.stack = self.pp_stack[-1].stack_before_else
+ # Drop the corresponding #if
+ self.pp_stack.pop()
+ else:
+ # TODO(unknown): unexpected #endif, issue warning?
+ pass
+
+ # TODO(unknown): Update() is too long, but we will refactor later.
+ def Update(self, filename, clean_lines, linenum, error):
+ """Update nesting state with current line.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Remember top of the previous nesting stack.
+ #
+ # The stack is always pushed/popped and not modified in place, so
+ # we can just do a shallow copy instead of copy.deepcopy. Using
+ # deepcopy would slow down cpplint by ~28%.
+ if self.stack:
+ self.previous_stack_top = self.stack[-1]
+ else:
+ self.previous_stack_top = None
+
+ # Update pp_stack
+ self.UpdatePreprocessor(line)
+
+ # Count parentheses. This is to avoid adding struct arguments to
+ # the nesting stack.
+ if self.stack:
+ inner_block = self.stack[-1]
+ depth_change = line.count('(') - line.count(')')
+ inner_block.open_parentheses += depth_change
+
+ # Also check if we are starting or ending an inline assembly block.
+ if inner_block.inline_asm in (_NO_ASM, _END_ASM):
+ if (depth_change != 0 and
+ inner_block.open_parentheses == 1 and
+ _MATCH_ASM.match(line)):
+ # Enter assembly block
+ inner_block.inline_asm = _INSIDE_ASM
+ else:
+ # Not entering assembly block. If previous line was _END_ASM,
+ # we will now shift to _NO_ASM state.
+ inner_block.inline_asm = _NO_ASM
+ elif (inner_block.inline_asm == _INSIDE_ASM and
+ inner_block.open_parentheses == 0):
+ # Exit assembly block
+ inner_block.inline_asm = _END_ASM
+
+ # Consume namespace declaration at the beginning of the line. Do
+ # this in a loop so that we catch same line declarations like this:
+ # namespace proto2 { namespace bridge { class MessageSet; } }
+ while True:
+ # Match start of namespace. The "\b\s*" below catches namespace
+ # declarations even if it weren't followed by a whitespace, this
+ # is so that we don't confuse our namespace checker. The
+ # missing spaces will be flagged by CheckSpacing.
+ namespace_decl_match = Match(r'^\s*namespace\b\s*([:\w]+)?(.*)$', line)
+ if not namespace_decl_match:
+ break
+
+ new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum)
+ self.stack.append(new_namespace)
+
+ line = namespace_decl_match.group(2)
+ if line.find('{') != -1:
+ new_namespace.seen_open_brace = True
+ line = line[line.find('{') + 1:]
+
+ # Look for a class declaration in whatever is left of the line
+ # after parsing namespaces. The regexp accounts for decorated classes
+ # such as in:
+ # class LOCKABLE API Object {
+ # };
+ class_decl_match = Match(
+ r'^(\s*(?:template\s*<[\w\s<>,:]*>\s*)?'
+ r'(class|struct)\s+(?:[A-Z_]+\s+)*(\w+(?:::\w+)*))'
+ r'(.*)$', line)
+ if (class_decl_match and
+ (not self.stack or self.stack[-1].open_parentheses == 0)):
+ # We do not want to accept classes that are actually template arguments:
+ # template <class Ignore1,
+ # class Ignore2 = Default<Args>,
+ # template <Args> class Ignore3>
+ # void Function() {};
+ #
+ # To avoid template argument cases, we scan forward and look for
+ # an unmatched '>'. If we see one, assume we are inside a
+ # template argument list.
+ end_declaration = len(class_decl_match.group(1))
+ if not self.InTemplateArgumentList(clean_lines, linenum, end_declaration):
+ self.stack.append(_ClassInfo(
+ class_decl_match.group(3), class_decl_match.group(2),
+ clean_lines, linenum))
+ line = class_decl_match.group(4)
+
+ # If we have not yet seen the opening brace for the innermost block,
+ # run checks here.
+ if not self.SeenOpenBrace():
+ self.stack[-1].CheckBegin(filename, clean_lines, linenum, error)
+
+ # Update access control if we are inside a class/struct
+ if self.stack and isinstance(self.stack[-1], _ClassInfo):
+ classinfo = self.stack[-1]
+ access_match = Match(
+ r'^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?'
+ r':(?:[^:]|$)',
+ line)
+ if access_match:
+ classinfo.access = access_match.group(2)
+
+ # Check that access keywords are indented +1 space. Skip this
+ # check if the keywords are not preceded by whitespaces.
+ indent = access_match.group(1)
+ if (len(indent) != classinfo.class_indent + 1 and
+ Match(r'^\s*$', indent)):
+ if classinfo.is_struct:
+ parent = 'struct ' + classinfo.name
+ else:
+ parent = 'class ' + classinfo.name
+ slots = ''
+ if access_match.group(3):
+ slots = access_match.group(3)
+ error(filename, linenum, 'whitespace/indent', 3,
+ '%s%s: should be indented +1 space inside %s' % (
+ access_match.group(2), slots, parent))
+
+ # Consume braces or semicolons from what's left of the line
+ while True:
+ # Match first brace, semicolon, or closed parenthesis.
+ matched = Match(r'^[^{;)}]*([{;)}])(.*)$', line)
+ if not matched:
+ break
+
+ token = matched.group(1)
+ if token == '{':
+ # If namespace or class hasn't seen a opening brace yet, mark
+ # namespace/class head as complete. Push a new block onto the
+ # stack otherwise.
+ if not self.SeenOpenBrace():
+ self.stack[-1].seen_open_brace = True
+ elif Match(r'^extern\s*"[^"]*"\s*\{', line):
+ self.stack.append(_ExternCInfo())
+ else:
+ self.stack.append(_BlockInfo(True))
+ if _MATCH_ASM.match(line):
+ self.stack[-1].inline_asm = _BLOCK_ASM
+
+ elif token == ';' or token == ')':
+ # If we haven't seen an opening brace yet, but we already saw
+ # a semicolon, this is probably a forward declaration. Pop
+ # the stack for these.
+ #
+ # Similarly, if we haven't seen an opening brace yet, but we
+ # already saw a closing parenthesis, then these are probably
+ # function arguments with extra "class" or "struct" keywords.
+ # Also pop these stack for these.
+ if not self.SeenOpenBrace():
+ self.stack.pop()
+ else: # token == '}'
+ # Perform end of block checks and pop the stack.
+ if self.stack:
+ self.stack[-1].CheckEnd(filename, clean_lines, linenum, error)
+ self.stack.pop()
+ line = matched.group(2)
+
+ def InnermostClass(self):
+ """Get class info on the top of the stack.
+
+ Returns:
+ A _ClassInfo object if we are inside a class, or None otherwise.
+ """
+ for i in range(len(self.stack), 0, -1):
+ classinfo = self.stack[i - 1]
+ if isinstance(classinfo, _ClassInfo):
+ return classinfo
+ return None
+
+ def CheckCompletedBlocks(self, filename, error):
+ """Checks that all classes and namespaces have been completely parsed.
+
+ Call this when all lines in a file have been processed.
+ Args:
+ filename: The name of the current file.
+ error: The function to call with any errors found.
+ """
+ # Note: This test can result in false positives if #ifdef constructs
+ # get in the way of brace matching. See the testBuildClass test in
+ # cpplint_unittest.py for an example of this.
+ for obj in self.stack:
+ if isinstance(obj, _ClassInfo):
+ error(filename, obj.starting_linenum, 'build/class', 5,
+ 'Failed to find complete declaration of class %s' %
+ obj.name)
+ elif isinstance(obj, _NamespaceInfo):
+ error(filename, obj.starting_linenum, 'build/namespaces', 5,
+ 'Failed to find complete declaration of namespace %s' %
+ obj.name)
+
+
+def CheckForNonStandardConstructs(filename, clean_lines, linenum,
+ nesting_state, error):
+ r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2.
+
+ Complain about several constructs which gcc-2 accepts, but which are
+ not standard C++. Warning about these in lint is one way to ease the
+ transition to new compilers.
+ - put storage class first (e.g. "static const" instead of "const static").
+ - "%lld" instead of %qd" in printf-type functions.
+ - "%1$d" is non-standard in printf-type functions.
+ - "\%" is an undefined character escape sequence.
+ - text after #endif is not allowed.
+ - invalid inner-style forward declaration.
+ - >? and <? operators, and their >?= and <?= cousins.
+
+ Additionally, check for constructor/destructor style violations and reference
+ members, as it is very convenient to do so while checking for
+ gcc-2 compliance.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: A callable to which errors are reported, which takes 4 arguments:
+ filename, line number, error level, and message
+ """
+
+ # Remove comments from the line, but leave in strings for now.
+ line = clean_lines.lines[linenum]
+
+ if Search(r'printf\s*\(.*".*%[-+ ]?\d*q', line):
+ error(filename, linenum, 'runtime/printf_format', 3,
+ '%q in format strings is deprecated. Use %ll instead.')
+
+ if Search(r'printf\s*\(.*".*%\d+\$', line):
+ error(filename, linenum, 'runtime/printf_format', 2,
+ '%N$ formats are unconventional. Try rewriting to avoid them.')
+
+ # Remove escaped backslashes before looking for undefined escapes.
+ line = line.replace('\\\\', '')
+
+ if Search(r'("|\').*\\(%|\[|\(|{)', line):
+ error(filename, linenum, 'build/printf_format', 3,
+ '%, [, (, and { are undefined character escapes. Unescape them.')
+
+ # For the rest, work with both comments and strings removed.
+ line = clean_lines.elided[linenum]
+
+ if Search(r'\b(const|volatile|void|char|short|int|long'
+ r'|float|double|signed|unsigned'
+ r'|schar|u?int8|u?int16|u?int32|u?int64)'
+ r'\s+(register|static|extern|typedef)\b',
+ line):
+ error(filename, linenum, 'build/storage_class', 5,
+ 'Storage class (static, extern, typedef, etc) should be first.')
+
+ if Match(r'\s*#\s*endif\s*[^/\s]+', line):
+ error(filename, linenum, 'build/endif_comment', 5,
+ 'Uncommented text after #endif is non-standard. Use a comment.')
+
+ if Match(r'\s*class\s+(\w+\s*::\s*)+\w+\s*;', line):
+ error(filename, linenum, 'build/forward_decl', 5,
+ 'Inner-style forward declarations are invalid. Remove this line.')
+
+ if Search(r'(\w+|[+-]?\d+(\.\d*)?)\s*(<|>)\?=?\s*(\w+|[+-]?\d+)(\.\d*)?',
+ line):
+ error(filename, linenum, 'build/deprecated', 3,
+ '>? and <? (max and min) operators are non-standard and deprecated.')
+
+ if Search(r'^\s*const\s*string\s*&\s*\w+\s*;', line):
+ # TODO(unknown): Could it be expanded safely to arbitrary references,
+ # without triggering too many false positives? The first
+ # attempt triggered 5 warnings for mostly benign code in the regtest, hence
+ # the restriction.
+ # Here's the original regexp, for the reference:
+ # type_name = r'\w+((\s*::\s*\w+)|(\s*<\s*\w+?\s*>))?'
+ # r'\s*const\s*' + type_name + '\s*&\s*\w+\s*;'
+ error(filename, linenum, 'runtime/member_string_references', 2,
+ 'const string& members are dangerous. It is much better to use '
+ 'alternatives, such as pointers or simple constants.')
+
+ # Everything else in this function operates on class declarations.
+ # Return early if the top of the nesting stack is not a class, or if
+ # the class head is not completed yet.
+ classinfo = nesting_state.InnermostClass()
+ if not classinfo or not classinfo.seen_open_brace:
+ return
+
+ # The class may have been declared with namespace or classname qualifiers.
+ # The constructor and destructor will not have those qualifiers.
+ base_classname = classinfo.name.split('::')[-1]
+
+ # Look for single-argument constructors that aren't marked explicit.
+ # Technically a valid construct, but against style. Also look for
+ # non-single-argument constructors which are also technically valid, but
+ # strongly suggest something is wrong.
+ explicit_constructor_match = Match(
+ r'\s+(?:inline\s+)?(explicit\s+)?(?:inline\s+)?%s\s*'
+ r'\(((?:[^()]|\([^()]*\))*)\)'
+ % re.escape(base_classname),
+ line)
+
+ if explicit_constructor_match:
+ is_marked_explicit = explicit_constructor_match.group(1)
+
+ if not explicit_constructor_match.group(2):
+ constructor_args = []
+ else:
+ constructor_args = explicit_constructor_match.group(2).split(',')
+
+ # collapse arguments so that commas in template parameter lists and function
+ # argument parameter lists don't split arguments in two
+ i = 0
+ while i < len(constructor_args):
+ constructor_arg = constructor_args[i]
+ while (constructor_arg.count('<') > constructor_arg.count('>') or
+ constructor_arg.count('(') > constructor_arg.count(')')):
+ constructor_arg += ',' + constructor_args[i + 1]
+ del constructor_args[i + 1]
+ constructor_args[i] = constructor_arg
+ i += 1
+
+ defaulted_args = [arg for arg in constructor_args if '=' in arg]
+ noarg_constructor = (not constructor_args or # empty arg list
+ # 'void' arg specifier
+ (len(constructor_args) == 1 and
+ constructor_args[0].strip() == 'void'))
+ onearg_constructor = ((len(constructor_args) == 1 and # exactly one arg
+ not noarg_constructor) or
+ # all but at most one arg defaulted
+ (len(constructor_args) >= 1 and
+ not noarg_constructor and
+ len(defaulted_args) >= len(constructor_args) - 1))
+ initializer_list_constructor = bool(
+ onearg_constructor and
+ Search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0]))
+ copy_constructor = bool(
+ onearg_constructor and
+ Match(r'(const\s+)?%s(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&'
+ % re.escape(base_classname), constructor_args[0].strip()))
+
+ if (not is_marked_explicit and
+ onearg_constructor and
+ not initializer_list_constructor and
+ not copy_constructor):
+ if defaulted_args:
+ error(filename, linenum, 'runtime/explicit', 5,
+ 'Constructors callable with one argument '
+ 'should be marked explicit.')
+ else:
+ error(filename, linenum, 'runtime/explicit', 5,
+ 'Single-parameter constructors should be marked explicit.')
+ elif is_marked_explicit and not onearg_constructor:
+ if noarg_constructor:
+ error(filename, linenum, 'runtime/explicit', 5,
+ 'Zero-parameter constructors should not be marked explicit.')
+ else:
+ error(filename, linenum, 'runtime/explicit', 0,
+ 'Constructors that require multiple arguments '
+ 'should not be marked explicit.')
+
+
+def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error):
+ """Checks for the correctness of various spacing around function calls.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Since function calls often occur inside if/for/while/switch
+ # expressions - which have their own, more liberal conventions - we
+ # first see if we should be looking inside such an expression for a
+ # function call, to which we can apply more strict standards.
+ fncall = line # if there's no control flow construct, look at whole line
+ for pattern in (r'\bif\s*\((.*)\)\s*{',
+ r'\bfor\s*\((.*)\)\s*{',
+ r'\bwhile\s*\((.*)\)\s*[{;]',
+ r'\bswitch\s*\((.*)\)\s*{'):
+ match = Search(pattern, line)
+ if match:
+ fncall = match.group(1) # look inside the parens for function calls
+ break
+
+ # Except in if/for/while/switch, there should never be space
+ # immediately inside parens (eg "f( 3, 4 )"). We make an exception
+ # for nested parens ( (a+b) + c ). Likewise, there should never be
+ # a space before a ( when it's a function argument. I assume it's a
+ # function argument when the char before the whitespace is legal in
+ # a function name (alnum + _) and we're not starting a macro. Also ignore
+ # pointers and references to arrays and functions coz they're too tricky:
+ # we use a very simple way to recognize these:
+ # " (something)(maybe-something)" or
+ # " (something)(maybe-something," or
+ # " (something)[something]"
+ # Note that we assume the contents of [] to be short enough that
+ # they'll never need to wrap.
+ if ( # Ignore control structures.
+ not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b',
+ fncall) and
+ # Ignore pointers/references to functions.
+ not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and
+ # Ignore pointers/references to arrays.
+ not Search(r' \([^)]+\)\[[^\]]+\]', fncall)):
+ if Search(r'\w\s*\(\s(?!\s*\\$)', fncall): # a ( used for a fn call
+ error(filename, linenum, 'whitespace/parens', 4,
+ 'Extra space after ( in function call')
+ elif Search(r'\(\s+(?!(\s*\\)|\()', fncall):
+ error(filename, linenum, 'whitespace/parens', 2,
+ 'Extra space after (')
+ if (Search(r'\w\s+\(', fncall) and
+ not Search(r'#\s*define|typedef|using\s+\w+\s*=', fncall) and
+ not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall) and
+ not Search(r'\bcase\s+\(', fncall)):
+ # TODO(unknown): Space after an operator function seem to be a common
+ # error, silence those for now by restricting them to highest verbosity.
+ if Search(r'\boperator_*\b', line):
+ error(filename, linenum, 'whitespace/parens', 0,
+ 'Extra space before ( in function call')
+ else:
+ error(filename, linenum, 'whitespace/parens', 4,
+ 'Extra space before ( in function call')
+ # If the ) is followed only by a newline or a { + newline, assume it's
+ # part of a control statement (if/while/etc), and don't complain
+ if Search(r'[^)]\s+\)\s*[^{\s]', fncall):
+ # If the closing parenthesis is preceded by only whitespaces,
+ # try to give a more descriptive error message.
+ if Search(r'^\s+\)', fncall):
+ error(filename, linenum, 'whitespace/parens', 2,
+ 'Closing ) should be moved to the previous line')
+ else:
+ error(filename, linenum, 'whitespace/parens', 2,
+ 'Extra space before )')
+
+
+def IsBlankLine(line):
+ """Returns true if the given line is blank.
+
+ We consider a line to be blank if the line is empty or consists of
+ only white spaces.
+
+ Args:
+ line: A line of a string.
+
+ Returns:
+ True, if the given line is blank.
+ """
+ return not line or line.isspace()
+
+
+def CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line,
+ error):
+ is_namespace_indent_item = (
+ len(nesting_state.stack) > 1 and
+ nesting_state.stack[-1].check_namespace_indentation and
+ isinstance(nesting_state.previous_stack_top, _NamespaceInfo) and
+ nesting_state.previous_stack_top == nesting_state.stack[-2])
+
+ if ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item,
+ clean_lines.elided, line):
+ CheckItemIndentationInNamespace(filename, clean_lines.elided,
+ line, error)
+
+
+def CheckForFunctionLengths(filename, clean_lines, linenum,
+ function_state, error):
+ """Reports for long function bodies.
+
+ For an overview why this is done, see:
+ http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions
+
+ Uses a simplistic algorithm assuming other style guidelines
+ (especially spacing) are followed.
+ Only checks unindented functions, so class members are unchecked.
+ Trivial bodies are unchecked, so constructors with huge initializer lists
+ may be missed.
+ Blank/comment lines are not counted so as to avoid encouraging the removal
+ of vertical space and comments just to get through a lint check.
+ NOLINT *on the last line of a function* disables this check.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ function_state: Current function name and lines in body so far.
+ error: The function to call with any errors found.
+ """
+ lines = clean_lines.lines
+ line = lines[linenum]
+ joined_line = ''
+
+ starting_func = False
+ regexp = r'(\w(\w|::|\*|\&|\s)*)\(' # decls * & space::name( ...
+ match_result = Match(regexp, line)
+ if match_result:
+ # If the name is all caps and underscores, figure it's a macro and
+ # ignore it, unless it's TEST or TEST_F.
+ function_name = match_result.group(1).split()[-1]
+ if function_name == 'TEST' or function_name == 'TEST_F' or (
+ not Match(r'[A-Z_]+$', function_name)):
+ starting_func = True
+
+ if starting_func:
+ body_found = False
+ for start_linenum in xrange(linenum, clean_lines.NumLines()):
+ start_line = lines[start_linenum]
+ joined_line += ' ' + start_line.lstrip()
+ if Search(r'(;|})', start_line): # Declarations and trivial functions
+ body_found = True
+ break # ... ignore
+ elif Search(r'{', start_line):
+ body_found = True
+ function = Search(r'((\w|:)*)\(', line).group(1)
+ if Match(r'TEST', function): # Handle TEST... macros
+ parameter_regexp = Search(r'(\(.*\))', joined_line)
+ if parameter_regexp: # Ignore bad syntax
+ function += parameter_regexp.group(1)
+ else:
+ function += '()'
+ function_state.Begin(function)
+ break
+ if not body_found:
+ # No body for the function (or evidence of a non-function) was found.
+ error(filename, linenum, 'readability/fn_size', 5,
+ 'Lint failed to find start of function body.')
+ elif Match(r'^\}\s*$', line): # function end
+ function_state.Check(error, filename, linenum)
+ function_state.End()
+ elif not Match(r'^\s*$', line):
+ function_state.Count() # Count non-blank/non-comment lines.
+
+
+_RE_PATTERN_TODO = re.compile(r'^//(\s*)TODO(\(.+?\))?:?(\s|$)?')
+
+
+def CheckComment(line, filename, linenum, next_line_start, error):
+ """Checks for common mistakes in comments.
+
+ Args:
+ line: The line in question.
+ filename: The name of the current file.
+ linenum: The number of the line to check.
+ next_line_start: The first non-whitespace column of the next line.
+ error: The function to call with any errors found.
+ """
+ commentpos = line.find('//')
+ if commentpos != -1:
+ # Check if the // may be in quotes. If so, ignore it
+ # Comparisons made explicit for clarity -- pylint: disable=g-explicit-bool-comparison
+ if (line.count('"', 0, commentpos) -
+ line.count('\\"', 0, commentpos)) % 2 == 0: # not in quotes
+ # Allow one space for new scopes, two spaces otherwise:
+ if (not (Match(r'^.*{ *//', line) and next_line_start == commentpos) and
+ ((commentpos >= 1 and
+ line[commentpos-1] not in string.whitespace) or
+ (commentpos >= 2 and
+ line[commentpos-2] not in string.whitespace))):
+ error(filename, linenum, 'whitespace/comments', 2,
+ 'At least two spaces is best between code and comments')
+
+ # Checks for common mistakes in TODO comments.
+ comment = line[commentpos:]
+ match = _RE_PATTERN_TODO.match(comment)
+ if match:
+ # One whitespace is correct; zero whitespace is handled elsewhere.
+ leading_whitespace = match.group(1)
+ if len(leading_whitespace) > 1:
+ error(filename, linenum, 'whitespace/todo', 2,
+ 'Too many spaces before TODO')
+
+ username = match.group(2)
+ if not username:
+ error(filename, linenum, 'readability/todo', 2,
+ 'Missing username in TODO; it should look like '
+ '"// TODO(my_username): Stuff."')
+
+ middle_whitespace = match.group(3)
+ # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison
+ if middle_whitespace != ' ' and middle_whitespace != '':
+ error(filename, linenum, 'whitespace/todo', 2,
+ 'TODO(my_username) should be followed by a space')
+
+ # If the comment contains an alphanumeric character, there
+ # should be a space somewhere between it and the // unless
+ # it's a /// or //! Doxygen comment.
+ if (Match(r'//[^ ]*\w', comment) and
+ not Match(r'(///|//\!)(\s+|$)', comment)):
+ error(filename, linenum, 'whitespace/comments', 4,
+ 'Should have a space between // and comment')
+
+
+def CheckAccess(filename, clean_lines, linenum, nesting_state, error):
+ """Checks for improper use of DISALLOW* macros.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum] # get rid of comments and strings
+
+ matched = Match((r'\s*(DISALLOW_COPY_AND_ASSIGN|'
+ r'DISALLOW_IMPLICIT_CONSTRUCTORS)'), line)
+ if not matched:
+ return
+ if nesting_state.stack and isinstance(nesting_state.stack[-1], _ClassInfo):
+ if nesting_state.stack[-1].access != 'private':
+ error(filename, linenum, 'readability/constructors', 3,
+ '%s must be in the private: section' % matched.group(1))
+
+ else:
+ # Found DISALLOW* macro outside a class declaration, or perhaps it
+ # was used inside a function when it should have been part of the
+ # class declaration. We could issue a warning here, but it
+ # probably resulted in a compiler error already.
+ pass
+
+
+def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
+ """Checks for the correctness of various spacing issues in the code.
+
+ Things we check for: spaces around operators, spaces after
+ if/for/while/switch, no spaces around parens in function calls, two
+ spaces between code and comment, don't start a block with a blank
+ line, don't end a function with a blank line, don't add a blank line
+ after public/protected/private, don't have too many blank lines in a row.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: The function to call with any errors found.
+ """
+
+ # Don't use "elided" lines here, otherwise we can't check commented lines.
+ # Don't want to use "raw" either, because we don't want to check inside C++11
+ # raw strings,
+ raw = clean_lines.lines_without_raw_strings
+ line = raw[linenum]
+
+ # Before nixing comments, check if the line is blank for no good
+ # reason. This includes the first line after a block is opened, and
+ # blank lines at the end of a function (ie, right before a line like '}'
+ #
+ # Skip all the blank line checks if we are immediately inside a
+ # namespace body. In other words, don't issue blank line warnings
+ # for this block:
+ # namespace {
+ #
+ # }
+ #
+ # A warning about missing end of namespace comments will be issued instead.
+ #
+ # Also skip blank line checks for 'extern "C"' blocks, which are formatted
+ # like namespaces.
+ if (IsBlankLine(line) and
+ not nesting_state.InNamespaceBody() and
+ not nesting_state.InExternC()):
+ elided = clean_lines.elided
+ prev_line = elided[linenum - 1]
+ prevbrace = prev_line.rfind('{')
+ # TODO(unknown): Don't complain if line before blank line, and line after,
+ # both start with alnums and are indented the same amount.
+ # This ignores whitespace at the start of a namespace block
+ # because those are not usually indented.
+ if prevbrace != -1 and prev_line[prevbrace:].find('}') == -1:
+ # OK, we have a blank line at the start of a code block. Before we
+ # complain, we check if it is an exception to the rule: The previous
+ # non-empty line has the parameters of a function header that are indented
+ # 4 spaces (because they did not fit in a 80 column line when placed on
+ # the same line as the function name). We also check for the case where
+ # the previous line is indented 6 spaces, which may happen when the
+ # initializers of a constructor do not fit into a 80 column line.
+ exception = False
+ if Match(r' {6}\w', prev_line): # Initializer list?
+ # We are looking for the opening column of initializer list, which
+ # should be indented 4 spaces to cause 6 space indentation afterwards.
+ search_position = linenum-2
+ while (search_position >= 0
+ and Match(r' {6}\w', elided[search_position])):
+ search_position -= 1
+ exception = (search_position >= 0
+ and elided[search_position][:5] == ' :')
+ else:
+ # Search for the function arguments or an initializer list. We use a
+ # simple heuristic here: If the line is indented 4 spaces; and we have a
+ # closing paren, without the opening paren, followed by an opening brace
+ # or colon (for initializer lists) we assume that it is the last line of
+ # a function header. If we have a colon indented 4 spaces, it is an
+ # initializer list.
+ exception = (Match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)',
+ prev_line)
+ or Match(r' {4}:', prev_line))
+
+ if not exception:
+ error(filename, linenum, 'whitespace/blank_line', 2,
+ 'Redundant blank line at the start of a code block '
+ 'should be deleted.')
+ # Ignore blank lines at the end of a block in a long if-else
+ # chain, like this:
+ # if (condition1) {
+ # // Something followed by a blank line
+ #
+ # } else if (condition2) {
+ # // Something else
+ # }
+ if linenum + 1 < clean_lines.NumLines():
+ next_line = raw[linenum + 1]
+ if (next_line
+ and Match(r'\s*}', next_line)
+ and next_line.find('} else ') == -1):
+ error(filename, linenum, 'whitespace/blank_line', 3,
+ 'Redundant blank line at the end of a code block '
+ 'should be deleted.')
+
+ matched = Match(r'\s*(public|protected|private):', prev_line)
+ if matched:
+ error(filename, linenum, 'whitespace/blank_line', 3,
+ 'Do not leave a blank line after "%s:"' % matched.group(1))
+
+ # Next, check comments
+ next_line_start = 0
+ if linenum + 1 < clean_lines.NumLines():
+ next_line = raw[linenum + 1]
+ next_line_start = len(next_line) - len(next_line.lstrip())
+ CheckComment(line, filename, linenum, next_line_start, error)
+
+ # get rid of comments and strings
+ line = clean_lines.elided[linenum]
+
+ # You shouldn't have spaces before your brackets, except maybe after
+ # 'delete []' or 'return []() {};'
+ if Search(r'\w\s+\[', line) and not Search(r'(?:delete|return)\s+\[', line):
+ error(filename, linenum, 'whitespace/braces', 5,
+ 'Extra space before [')
+
+ # In range-based for, we wanted spaces before and after the colon, but
+ # not around "::" tokens that might appear.
+ if (Search(r'for *\(.*[^:]:[^: ]', line) or
+ Search(r'for *\(.*[^: ]:[^:]', line)):
+ error(filename, linenum, 'whitespace/forcolon', 2,
+ 'Missing space around colon in range-based for loop')
+
+
+def CheckOperatorSpacing(filename, clean_lines, linenum, error):
+ """Checks for horizontal spacing around operators.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Don't try to do spacing checks for operator methods. Do this by
+ # replacing the troublesome characters with something else,
+ # preserving column position for all other characters.
+ #
+ # The replacement is done repeatedly to avoid false positives from
+ # operators that call operators.
+ while True:
+ match = Match(r'^(.*\boperator\b)(\S+)(\s*\(.*)$', line)
+ if match:
+ line = match.group(1) + ('_' * len(match.group(2))) + match.group(3)
+ else:
+ break
+
+ # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )".
+ # Otherwise not. Note we only check for non-spaces on *both* sides;
+ # sometimes people put non-spaces on one side when aligning ='s among
+ # many lines (not that this is behavior that I approve of...)
+ if ((Search(r'[\w.]=', line) or
+ Search(r'=[\w.]', line))
+ and not Search(r'\b(if|while|for) ', line)
+ # Operators taken from [lex.operators] in C++11 standard.
+ and not Search(r'(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)', line)
+ and not Search(r'operator=', line)):
+ error(filename, linenum, 'whitespace/operators', 4,
+ 'Missing spaces around =')
+
+ # It's ok not to have spaces around binary operators like + - * /, but if
+ # there's too little whitespace, we get concerned. It's hard to tell,
+ # though, so we punt on this one for now. TODO.
+
+ # You should always have whitespace around binary operators.
+ #
+ # Check <= and >= first to avoid false positives with < and >, then
+ # check non-include lines for spacing around < and >.
+ #
+ # If the operator is followed by a comma, assume it's be used in a
+ # macro context and don't do any checks. This avoids false
+ # positives.
+ #
+ # Note that && is not included here. Those are checked separately
+ # in CheckRValueReference
+ match = Search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line)
+ if match:
+ error(filename, linenum, 'whitespace/operators', 3,
+ 'Missing spaces around %s' % match.group(1))
+ elif not Match(r'#.*include', line):
+ # Look for < that is not surrounded by spaces. This is only
+ # triggered if both sides are missing spaces, even though
+ # technically should should flag if at least one side is missing a
+ # space. This is done to avoid some false positives with shifts.
+ match = Match(r'^(.*[^\s<])<[^\s=<,]', line)
+ if match:
+ (_, _, end_pos) = CloseExpression(
+ clean_lines, linenum, len(match.group(1)))
+ if end_pos <= -1:
+ error(filename, linenum, 'whitespace/operators', 3,
+ 'Missing spaces around <')
+
+ # Look for > that is not surrounded by spaces. Similar to the
+ # above, we only trigger if both sides are missing spaces to avoid
+ # false positives with shifts.
+ match = Match(r'^(.*[^-\s>])>[^\s=>,]', line)
+ if match:
+ (_, _, start_pos) = ReverseCloseExpression(
+ clean_lines, linenum, len(match.group(1)))
+ if start_pos <= -1:
+ error(filename, linenum, 'whitespace/operators', 3,
+ 'Missing spaces around >')
+
+ # We allow no-spaces around << when used like this: 10<<20, but
+ # not otherwise (particularly, not when used as streams)
+ #
+ # We also allow operators following an opening parenthesis, since
+ # those tend to be macros that deal with operators.
+ match = Search(r'(operator|[^\s(<])(?:L|UL|ULL|l|ul|ull)?<<([^\s,=<])', line)
+ if (match and not (match.group(1).isdigit() and match.group(2).isdigit()) and
+ not (match.group(1) == 'operator' and match.group(2) == ';')):
+ error(filename, linenum, 'whitespace/operators', 3,
+ 'Missing spaces around <<')
+
+ # We allow no-spaces around >> for almost anything. This is because
+ # C++11 allows ">>" to close nested templates, which accounts for
+ # most cases when ">>" is not followed by a space.
+ #
+ # We still warn on ">>" followed by alpha character, because that is
+ # likely due to ">>" being used for right shifts, e.g.:
+ # value >> alpha
+ #
+ # When ">>" is used to close templates, the alphanumeric letter that
+ # follows would be part of an identifier, and there should still be
+ # a space separating the template type and the identifier.
+ # type<type<type>> alpha
+ match = Search(r'>>[a-zA-Z_]', line)
+ if match:
+ error(filename, linenum, 'whitespace/operators', 3,
+ 'Missing spaces around >>')
+
+ # There shouldn't be space around unary operators
+ match = Search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line)
+ if match:
+ error(filename, linenum, 'whitespace/operators', 4,
+ 'Extra space for operator %s' % match.group(1))
+
+
+def CheckParenthesisSpacing(filename, clean_lines, linenum, error):
+ """Checks for horizontal spacing around parentheses.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # No spaces after an if, while, switch, or for
+ match = Search(r' (if\(|for\(|while\(|switch\()', line)
+ if match:
+ error(filename, linenum, 'whitespace/parens', 5,
+ 'Missing space before ( in %s' % match.group(1))
+
+ # For if/for/while/switch, the left and right parens should be
+ # consistent about how many spaces are inside the parens, and
+ # there should either be zero or one spaces inside the parens.
+ # We don't want: "if ( foo)" or "if ( foo )".
+ # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed.
+ match = Search(r'\b(if|for|while|switch)\s*'
+ r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$',
+ line)
+ if match:
+ if len(match.group(2)) != len(match.group(4)):
+ if not (match.group(3) == ';' and
+ len(match.group(2)) == 1 + len(match.group(4)) or
+ not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)):
+ error(filename, linenum, 'whitespace/parens', 5,
+ 'Mismatching spaces inside () in %s' % match.group(1))
+ if len(match.group(2)) not in [0, 1]:
+ error(filename, linenum, 'whitespace/parens', 5,
+ 'Should have zero or one spaces inside ( and ) in %s' %
+ match.group(1))
+
+
+def CheckCommaSpacing(filename, clean_lines, linenum, error):
+ """Checks for horizontal spacing near commas and semicolons.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ raw = clean_lines.lines_without_raw_strings
+ line = clean_lines.elided[linenum]
+
+ # You should always have a space after a comma (either as fn arg or operator)
+ #
+ # This does not apply when the non-space character following the
+ # comma is another comma, since the only time when that happens is
+ # for empty macro arguments.
+ #
+ # We run this check in two passes: first pass on elided lines to
+ # verify that lines contain missing whitespaces, second pass on raw
+ # lines to confirm that those missing whitespaces are not due to
+ # elided comments.
+ if (Search(r',[^,\s]', ReplaceAll(r'\boperator\s*,\s*\(', 'F(', line)) and
+ Search(r',[^,\s]', raw[linenum])):
+ error(filename, linenum, 'whitespace/comma', 3,
+ 'Missing space after ,')
+
+ # You should always have a space after a semicolon
+ # except for few corner cases
+ # TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more
+ # space after ;
+ if Search(r';[^\s};\\)/]', line):
+ error(filename, linenum, 'whitespace/semicolon', 3,
+ 'Missing space after ;')
+
+
+def CheckBracesSpacing(filename, clean_lines, linenum, error):
+ """Checks for horizontal spacing near commas.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Except after an opening paren, or after another opening brace (in case of
+ # an initializer list, for instance), you should have spaces before your
+ # braces. And since you should never have braces at the beginning of a line,
+ # this is an easy test.
+ match = Match(r'^(.*[^ ({>]){', line)
+ if match:
+ # Try a bit harder to check for brace initialization. This
+ # happens in one of the following forms:
+ # Constructor() : initializer_list_{} { ... }
+ # Constructor{}.MemberFunction()
+ # Type variable{};
+ # FunctionCall(type{}, ...);
+ # LastArgument(..., type{});
+ # LOG(INFO) << type{} << " ...";
+ # map_of_type[{...}] = ...;
+ # ternary = expr ? new type{} : nullptr;
+ # OuterTemplate<InnerTemplateConstructor<Type>{}>
+ #
+ # We check for the character following the closing brace, and
+ # silence the warning if it's one of those listed above, i.e.
+ # "{.;,)<>]:".
+ #
+ # To account for nested initializer list, we allow any number of
+ # closing braces up to "{;,)<". We can't simply silence the
+ # warning on first sight of closing brace, because that would
+ # cause false negatives for things that are not initializer lists.
+ # Silence this: But not this:
+ # Outer{ if (...) {
+ # Inner{...} if (...){ // Missing space before {
+ # }; }
+ #
+ # There is a false negative with this approach if people inserted
+ # spurious semicolons, e.g. "if (cond){};", but we will catch the
+ # spurious semicolon with a separate check.
+ (endline, endlinenum, endpos) = CloseExpression(
+ clean_lines, linenum, len(match.group(1)))
+ trailing_text = ''
+ if endpos > -1:
+ trailing_text = endline[endpos:]
+ for offset in xrange(endlinenum + 1,
+ min(endlinenum + 3, clean_lines.NumLines() - 1)):
+ trailing_text += clean_lines.elided[offset]
+ if not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text):
+ error(filename, linenum, 'whitespace/braces', 5,
+ 'Missing space before {')
+
+ # Make sure '} else {' has spaces.
+ if Search(r'}else', line):
+ error(filename, linenum, 'whitespace/braces', 5,
+ 'Missing space before else')
+
+ # You shouldn't have a space before a semicolon at the end of the line.
+ # There's a special case for "for" since the style guide allows space before
+ # the semicolon there.
+ if Search(r':\s*;\s*$', line):
+ error(filename, linenum, 'whitespace/semicolon', 5,
+ 'Semicolon defining empty statement. Use {} instead.')
+ elif Search(r'^\s*;\s*$', line):
+ error(filename, linenum, 'whitespace/semicolon', 5,
+ 'Line contains only semicolon. If this should be an empty statement, '
+ 'use {} instead.')
+ elif (Search(r'\s+;\s*$', line) and
+ not Search(r'\bfor\b', line)):
+ error(filename, linenum, 'whitespace/semicolon', 5,
+ 'Extra space before last semicolon. If this should be an empty '
+ 'statement, use {} instead.')
+
+
+def IsDecltype(clean_lines, linenum, column):
+ """Check if the token ending on (linenum, column) is decltype().
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: the number of the line to check.
+ column: end column of the token to check.
+ Returns:
+ True if this token is decltype() expression, False otherwise.
+ """
+ (text, _, start_col) = ReverseCloseExpression(clean_lines, linenum, column)
+ if start_col < 0:
+ return False
+ if Search(r'\bdecltype\s*$', text[0:start_col]):
+ return True
+ return False
+
+
+def IsTemplateParameterList(clean_lines, linenum, column):
+ """Check if the token ending on (linenum, column) is the end of template<>.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: the number of the line to check.
+ column: end column of the token to check.
+ Returns:
+ True if this token is end of a template parameter list, False otherwise.
+ """
+ (_, startline, startpos) = ReverseCloseExpression(
+ clean_lines, linenum, column)
+ if (startpos > -1 and
+ Search(r'\btemplate\s*$', clean_lines.elided[startline][0:startpos])):
+ return True
+ return False
+
+
+def IsRValueType(typenames, clean_lines, nesting_state, linenum, column):
+ """Check if the token ending on (linenum, column) is a type.
+
+ Assumes that text to the right of the column is "&&" or a function
+ name.
+
+ Args:
+ typenames: set of type names from template-argument-list.
+ clean_lines: A CleansedLines instance containing the file.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ linenum: the number of the line to check.
+ column: end column of the token to check.
+ Returns:
+ True if this token is a type, False if we are not sure.
+ """
+ prefix = clean_lines.elided[linenum][0:column]
+
+ # Get one word to the left. If we failed to do so, this is most
+ # likely not a type, since it's unlikely that the type name and "&&"
+ # would be split across multiple lines.
+ match = Match(r'^(.*)(\b\w+|[>*)&])\s*$', prefix)
+ if not match:
+ return False
+
+ # Check text following the token. If it's "&&>" or "&&," or "&&...", it's
+ # most likely a rvalue reference used inside a template.
+ suffix = clean_lines.elided[linenum][column:]
+ if Match(r'&&\s*(?:[>,]|\.\.\.)', suffix):
+ return True
+
+ # Check for known types and end of templates:
+ # int&& variable
+ # vector<int>&& variable
+ #
+ # Because this function is called recursively, we also need to
+ # recognize pointer and reference types:
+ # int* Function()
+ # int& Function()
+ if (match.group(2) in typenames or
+ match.group(2) in ['char', 'char16_t', 'char32_t', 'wchar_t', 'bool',
+ 'short', 'int', 'long', 'signed', 'unsigned',
+ 'float', 'double', 'void', 'auto', '>', '*', '&']):
+ return True
+
+ # If we see a close parenthesis, look for decltype on the other side.
+ # decltype would unambiguously identify a type, anything else is
+ # probably a parenthesized expression and not a type.
+ if match.group(2) == ')':
+ return IsDecltype(
+ clean_lines, linenum, len(match.group(1)) + len(match.group(2)) - 1)
+
+ # Check for casts and cv-qualifiers.
+ # match.group(1) remainder
+ # -------------- ---------
+ # const_cast< type&&
+ # const type&&
+ # type const&&
+ if Search(r'\b(?:const_cast\s*<|static_cast\s*<|dynamic_cast\s*<|'
+ r'reinterpret_cast\s*<|\w+\s)\s*$',
+ match.group(1)):
+ return True
+
+ # Look for a preceding symbol that might help differentiate the context.
+ # These are the cases that would be ambiguous:
+ # match.group(1) remainder
+ # -------------- ---------
+ # Call ( expression &&
+ # Declaration ( type&&
+ # sizeof ( type&&
+ # if ( expression &&
+ # while ( expression &&
+ # for ( type&&
+ # for( ; expression &&
+ # statement ; type&&
+ # block { type&&
+ # constructor { expression &&
+ start = linenum
+ line = match.group(1)
+ match_symbol = None
+ while start >= 0:
+ # We want to skip over identifiers and commas to get to a symbol.
+ # Commas are skipped so that we can find the opening parenthesis
+ # for function parameter lists.
+ match_symbol = Match(r'^(.*)([^\w\s,])[\w\s,]*$', line)
+ if match_symbol:
+ break
+ start -= 1
+ line = clean_lines.elided[start]
+
+ if not match_symbol:
+ # Probably the first statement in the file is an rvalue reference
+ return True
+
+ if match_symbol.group(2) == '}':
+ # Found closing brace, probably an indicate of this:
+ # block{} type&&
+ return True
+
+ if match_symbol.group(2) == ';':
+ # Found semicolon, probably one of these:
+ # for(; expression &&
+ # statement; type&&
+
+ # Look for the previous 'for(' in the previous lines.
+ before_text = match_symbol.group(1)
+ for i in xrange(start - 1, max(start - 6, 0), -1):
+ before_text = clean_lines.elided[i] + before_text
+ if Search(r'for\s*\([^{};]*$', before_text):
+ # This is the condition inside a for-loop
+ return False
+
+ # Did not find a for-init-statement before this semicolon, so this
+ # is probably a new statement and not a condition.
+ return True
+
+ if match_symbol.group(2) == '{':
+ # Found opening brace, probably one of these:
+ # block{ type&& = ... ; }
+ # constructor{ expression && expression }
+
+ # Look for a closing brace or a semicolon. If we see a semicolon
+ # first, this is probably a rvalue reference.
+ line = clean_lines.elided[start][0:len(match_symbol.group(1)) + 1]
+ end = start
+ depth = 1
+ while True:
+ for ch in line:
+ if ch == ';':
+ return True
+ elif ch == '{':
+ depth += 1
+ elif ch == '}':
+ depth -= 1
+ if depth == 0:
+ return False
+ end += 1
+ if end >= clean_lines.NumLines():
+ break
+ line = clean_lines.elided[end]
+ # Incomplete program?
+ return False
+
+ if match_symbol.group(2) == '(':
+ # Opening parenthesis. Need to check what's to the left of the
+ # parenthesis. Look back one extra line for additional context.
+ before_text = match_symbol.group(1)
+ if linenum > 1:
+ before_text = clean_lines.elided[linenum - 1] + before_text
+ before_text = match_symbol.group(1)
+
+ # Patterns that are likely to be types:
+ # [](type&&
+ # for (type&&
+ # sizeof(type&&
+ # operator=(type&&
+ #
+ if Search(r'(?:\]|\bfor|\bsizeof|\boperator\s*\S+\s*)\s*$', before_text):
+ return True
+
+ # Patterns that are likely to be expressions:
+ # if (expression &&
+ # while (expression &&
+ # : initializer(expression &&
+ # , initializer(expression &&
+ # ( FunctionCall(expression &&
+ # + FunctionCall(expression &&
+ # + (expression &&
+ #
+ # The last '+' represents operators such as '+' and '-'.
+ if Search(r'(?:\bif|\bwhile|[-+=%^(<!?:,&*]\s*)$', before_text):
+ return False
+
+ # Something else. Check that tokens to the left look like
+ # return_type function_name
+ match_func = Match(r'^(.*\S.*)\s+\w(?:\w|::)*(?:<[^<>]*>)?\s*$',
+ match_symbol.group(1))
+ if match_func:
+ # Check for constructors, which don't have return types.
+ if Search(r'\b(?:explicit|inline)$', match_func.group(1)):
+ return True
+ implicit_constructor = Match(r'\s*(\w+)\((?:const\s+)?(\w+)', prefix)
+ if (implicit_constructor and
+ implicit_constructor.group(1) == implicit_constructor.group(2)):
+ return True
+ return IsRValueType(typenames, clean_lines, nesting_state, linenum,
+ len(match_func.group(1)))
+
+ # Nothing before the function name. If this is inside a block scope,
+ # this is probably a function call.
+ return not (nesting_state.previous_stack_top and
+ nesting_state.previous_stack_top.IsBlockInfo())
+
+ if match_symbol.group(2) == '>':
+ # Possibly a closing bracket, check that what's on the other side
+ # looks like the start of a template.
+ return IsTemplateParameterList(
+ clean_lines, start, len(match_symbol.group(1)))
+
+ # Some other symbol, usually something like "a=b&&c". This is most
+ # likely not a type.
+ return False
+
+
+def IsDeletedOrDefault(clean_lines, linenum):
+ """Check if current constructor or operator is deleted or default.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ Returns:
+ True if this is a deleted or default constructor.
+ """
+ open_paren = clean_lines.elided[linenum].find('(')
+ if open_paren < 0:
+ return False
+ (close_line, _, close_paren) = CloseExpression(
+ clean_lines, linenum, open_paren)
+ if close_paren < 0:
+ return False
+ return Match(r'\s*=\s*(?:delete|default)\b', close_line[close_paren:])
+
+
+def IsRValueAllowed(clean_lines, linenum, typenames):
+ """Check if RValue reference is allowed on a particular line.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ typenames: set of type names from template-argument-list.
+ Returns:
+ True if line is within the region where RValue references are allowed.
+ """
+ # Allow region marked by PUSH/POP macros
+ for i in xrange(linenum, 0, -1):
+ line = clean_lines.elided[i]
+ if Match(r'GOOGLE_ALLOW_RVALUE_REFERENCES_(?:PUSH|POP)', line):
+ if not line.endswith('PUSH'):
+ return False
+ for j in xrange(linenum, clean_lines.NumLines(), 1):
+ line = clean_lines.elided[j]
+ if Match(r'GOOGLE_ALLOW_RVALUE_REFERENCES_(?:PUSH|POP)', line):
+ return line.endswith('POP')
+
+ # Allow operator=
+ line = clean_lines.elided[linenum]
+ if Search(r'\boperator\s*=\s*\(', line):
+ return IsDeletedOrDefault(clean_lines, linenum)
+
+ # Allow constructors
+ match = Match(r'\s*(?:[\w<>]+::)*([\w<>]+)\s*::\s*([\w<>]+)\s*\(', line)
+ if match and match.group(1) == match.group(2):
+ return IsDeletedOrDefault(clean_lines, linenum)
+ if Search(r'\b(?:explicit|inline)\s+[\w<>]+\s*\(', line):
+ return IsDeletedOrDefault(clean_lines, linenum)
+
+ if Match(r'\s*[\w<>]+\s*\(', line):
+ previous_line = 'ReturnType'
+ if linenum > 0:
+ previous_line = clean_lines.elided[linenum - 1]
+ if Match(r'^\s*$', previous_line) or Search(r'[{}:;]\s*$', previous_line):
+ return IsDeletedOrDefault(clean_lines, linenum)
+
+ # Reject types not mentioned in template-argument-list
+ while line:
+ match = Match(r'^.*?(\w+)\s*&&(.*)$', line)
+ if not match:
+ break
+ if match.group(1) not in typenames:
+ return False
+ line = match.group(2)
+
+ # All RValue types that were in template-argument-list should have
+ # been removed by now. Those were allowed, assuming that they will
+ # be forwarded.
+ #
+ # If there are no remaining RValue types left (i.e. types that were
+ # not found in template-argument-list), flag those as not allowed.
+ return line.find('&&') < 0
+
+
+def GetTemplateArgs(clean_lines, linenum):
+ """Find list of template arguments associated with this function declaration.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: Line number containing the start of the function declaration,
+ usually one line after the end of the template-argument-list.
+ Returns:
+ Set of type names, or empty set if this does not appear to have
+ any template parameters.
+ """
+ # Find start of function
+ func_line = linenum
+ while func_line > 0:
+ line = clean_lines.elided[func_line]
+ if Match(r'^\s*$', line):
+ return set()
+ if line.find('(') >= 0:
+ break
+ func_line -= 1
+ if func_line == 0:
+ return set()
+
+ # Collapse template-argument-list into a single string
+ argument_list = ''
+ match = Match(r'^(\s*template\s*)<', clean_lines.elided[func_line])
+ if match:
+ # template-argument-list on the same line as function name
+ start_col = len(match.group(1))
+ _, end_line, end_col = CloseExpression(clean_lines, func_line, start_col)
+ if end_col > -1 and end_line == func_line:
+ start_col += 1 # Skip the opening bracket
+ argument_list = clean_lines.elided[func_line][start_col:end_col]
+
+ elif func_line > 1:
+ # template-argument-list one line before function name
+ match = Match(r'^(.*)>\s*$', clean_lines.elided[func_line - 1])
+ if match:
+ end_col = len(match.group(1))
+ _, start_line, start_col = ReverseCloseExpression(
+ clean_lines, func_line - 1, end_col)
+ if start_col > -1:
+ start_col += 1 # Skip the opening bracket
+ while start_line < func_line - 1:
+ argument_list += clean_lines.elided[start_line][start_col:]
+ start_col = 0
+ start_line += 1
+ argument_list += clean_lines.elided[func_line - 1][start_col:end_col]
+
+ if not argument_list:
+ return set()
+
+ # Extract type names
+ typenames = set()
+ while True:
+ match = Match(r'^[,\s]*(?:typename|class)(?:\.\.\.)?\s+(\w+)(.*)$',
+ argument_list)
+ if not match:
+ break
+ typenames.add(match.group(1))
+ argument_list = match.group(2)
+ return typenames
+
+
+def CheckRValueReference(filename, clean_lines, linenum, nesting_state, error):
+ """Check for rvalue references.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: The function to call with any errors found.
+ """
+ # Find lines missing spaces around &&.
+ # TODO(unknown): currently we don't check for rvalue references
+ # with spaces surrounding the && to avoid false positives with
+ # boolean expressions.
+ line = clean_lines.elided[linenum]
+ match = Match(r'^(.*\S)&&', line)
+ if not match:
+ match = Match(r'(.*)&&\S', line)
+ if (not match) or '(&&)' in line or Search(r'\boperator\s*$', match.group(1)):
+ return
+
+ # Either poorly formed && or an rvalue reference, check the context
+ # to get a more accurate error message. Mostly we want to determine
+ # if what's to the left of "&&" is a type or not.
+ typenames = GetTemplateArgs(clean_lines, linenum)
+ and_pos = len(match.group(1))
+ if IsRValueType(typenames, clean_lines, nesting_state, linenum, and_pos):
+ if not IsRValueAllowed(clean_lines, linenum, typenames):
+ error(filename, linenum, 'build/c++11', 3,
+ 'RValue references are an unapproved C++ feature.')
+ else:
+ error(filename, linenum, 'whitespace/operators', 3,
+ 'Missing spaces around &&')
+
+
+def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error):
+ """Checks for additional blank line issues related to sections.
+
+ Currently the only thing checked here is blank line before protected/private.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ class_info: A _ClassInfo objects.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ # Skip checks if the class is small, where small means 25 lines or less.
+ # 25 lines seems like a good cutoff since that's the usual height of
+ # terminals, and any class that can't fit in one screen can't really
+ # be considered "small".
+ #
+ # Also skip checks if we are on the first line. This accounts for
+ # classes that look like
+ # class Foo { public: ... };
+ #
+ # If we didn't find the end of the class, last_line would be zero,
+ # and the check will be skipped by the first condition.
+ if (class_info.last_line - class_info.starting_linenum <= 24 or
+ linenum <= class_info.starting_linenum):
+ return
+
+ matched = Match(r'\s*(public|protected|private):', clean_lines.lines[linenum])
+ if matched:
+ # Issue warning if the line before public/protected/private was
+ # not a blank line, but don't do this if the previous line contains
+ # "class" or "struct". This can happen two ways:
+ # - We are at the beginning of the class.
+ # - We are forward-declaring an inner class that is semantically
+ # private, but needed to be public for implementation reasons.
+ # Also ignores cases where the previous line ends with a backslash as can be
+ # common when defining classes in C macros.
+ prev_line = clean_lines.lines[linenum - 1]
+ if (not IsBlankLine(prev_line) and
+ not Search(r'\b(class|struct)\b', prev_line) and
+ not Search(r'\\$', prev_line)):
+ # Try a bit harder to find the beginning of the class. This is to
+ # account for multi-line base-specifier lists, e.g.:
+ # class Derived
+ # : public Base {
+ end_class_head = class_info.starting_linenum
+ for i in range(class_info.starting_linenum, linenum):
+ if Search(r'\{\s*$', clean_lines.lines[i]):
+ end_class_head = i
+ break
+ if end_class_head < linenum - 1:
+ error(filename, linenum, 'whitespace/blank_line', 3,
+ '"%s:" should be preceded by a blank line' % matched.group(1))
+
+
+def GetPreviousNonBlankLine(clean_lines, linenum):
+ """Return the most recent non-blank line and its line number.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file contents.
+ linenum: The number of the line to check.
+
+ Returns:
+ A tuple with two elements. The first element is the contents of the last
+ non-blank line before the current line, or the empty string if this is the
+ first non-blank line. The second is the line number of that line, or -1
+ if this is the first non-blank line.
+ """
+
+ prevlinenum = linenum - 1
+ while prevlinenum >= 0:
+ prevline = clean_lines.elided[prevlinenum]
+ if not IsBlankLine(prevline): # if not a blank line...
+ return (prevline, prevlinenum)
+ prevlinenum -= 1
+ return ('', -1)
+
+
+def CheckBraces(filename, clean_lines, linenum, error):
+ """Looks for misplaced braces (e.g. at the end of line).
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+
+ line = clean_lines.elided[linenum] # get rid of comments and strings
+
+ if Match(r'\s*{\s*$', line):
+ # We allow an open brace to start a line in the case where someone is using
+ # braces in a block to explicitly create a new scope, which is commonly used
+ # to control the lifetime of stack-allocated variables. Braces are also
+ # used for brace initializers inside function calls. We don't detect this
+ # perfectly: we just don't complain if the last non-whitespace character on
+ # the previous non-blank line is ',', ';', ':', '(', '{', or '}', or if the
+ # previous line starts a preprocessor block.
+ prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
+ if (not Search(r'[,;:}{(]\s*$', prevline) and
+ not Match(r'\s*#', prevline)):
+ error(filename, linenum, 'whitespace/braces', 4,
+ '{ should almost always be at the end of the previous line')
+
+ # An else clause should be on the same line as the preceding closing brace.
+ if Match(r'\s*else\b\s*(?:if\b|\{|$)', line):
+ prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
+ if Match(r'\s*}\s*$', prevline):
+ error(filename, linenum, 'whitespace/newline', 4,
+ 'An else should appear on the same line as the preceding }')
+
+ # If braces come on one side of an else, they should be on both.
+ # However, we have to worry about "else if" that spans multiple lines!
+ if Search(r'else if\s*\(', line): # could be multi-line if
+ brace_on_left = bool(Search(r'}\s*else if\s*\(', line))
+ # find the ( after the if
+ pos = line.find('else if')
+ pos = line.find('(', pos)
+ if pos > 0:
+ (endline, _, endpos) = CloseExpression(clean_lines, linenum, pos)
+ brace_on_right = endline[endpos:].find('{') != -1
+ if brace_on_left != brace_on_right: # must be brace after if
+ error(filename, linenum, 'readability/braces', 5,
+ 'If an else has a brace on one side, it should have it on both')
+ elif Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line):
+ error(filename, linenum, 'readability/braces', 5,
+ 'If an else has a brace on one side, it should have it on both')
+
+ # Likewise, an else should never have the else clause on the same line
+ if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line):
+ error(filename, linenum, 'whitespace/newline', 4,
+ 'Else clause should never be on same line as else (use 2 lines)')
+
+ # In the same way, a do/while should never be on one line
+ if Match(r'\s*do [^\s{]', line):
+ error(filename, linenum, 'whitespace/newline', 4,
+ 'do/while clauses should not be on a single line')
+
+ # Check single-line if/else bodies. The style guide says 'curly braces are not
+ # required for single-line statements'. We additionally allow multi-line,
+ # single statements, but we reject anything with more than one semicolon in
+ # it. This means that the first semicolon after the if should be at the end of
+ # its line, and the line after that should have an indent level equal to or
+ # lower than the if. We also check for ambiguous if/else nesting without
+ # braces.
+ if_else_match = Search(r'\b(if\s*\(|else\b)', line)
+ if if_else_match and not Match(r'\s*#', line):
+ if_indent = GetIndentLevel(line)
+ endline, endlinenum, endpos = line, linenum, if_else_match.end()
+ if_match = Search(r'\bif\s*\(', line)
+ if if_match:
+ # This could be a multiline if condition, so find the end first.
+ pos = if_match.end() - 1
+ (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, pos)
+ # Check for an opening brace, either directly after the if or on the next
+ # line. If found, this isn't a single-statement conditional.
+ if (not Match(r'\s*{', endline[endpos:])
+ and not (Match(r'\s*$', endline[endpos:])
+ and endlinenum < (len(clean_lines.elided) - 1)
+ and Match(r'\s*{', clean_lines.elided[endlinenum + 1]))):
+ while (endlinenum < len(clean_lines.elided)
+ and ';' not in clean_lines.elided[endlinenum][endpos:]):
+ endlinenum += 1
+ endpos = 0
+ if endlinenum < len(clean_lines.elided):
+ endline = clean_lines.elided[endlinenum]
+ # We allow a mix of whitespace and closing braces (e.g. for one-liner
+ # methods) and a single \ after the semicolon (for macros)
+ endpos = endline.find(';')
+ if not Match(r';[\s}]*(\\?)$', endline[endpos:]):
+ # Semicolon isn't the last character, there's something trailing.
+ # Output a warning if the semicolon is not contained inside
+ # a lambda expression.
+ if not Match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}]*\}\s*\)*[;,]\s*$',
+ endline):
+ error(filename, linenum, 'readability/braces', 4,
+ 'If/else bodies with multiple statements require braces')
+ elif endlinenum < len(clean_lines.elided) - 1:
+ # Make sure the next line is dedented
+ next_line = clean_lines.elided[endlinenum + 1]
+ next_indent = GetIndentLevel(next_line)
+ # With ambiguous nested if statements, this will error out on the
+ # if that *doesn't* match the else, regardless of whether it's the
+ # inner one or outer one.
+ if (if_match and Match(r'\s*else\b', next_line)
+ and next_indent != if_indent):
+ error(filename, linenum, 'readability/braces', 4,
+ 'Else clause should be indented at the same level as if. '
+ 'Ambiguous nested if/else chains require braces.')
+ elif next_indent > if_indent:
+ error(filename, linenum, 'readability/braces', 4,
+ 'If/else bodies with multiple statements require braces')
+
+
+def CheckTrailingSemicolon(filename, clean_lines, linenum, error):
+ """Looks for redundant trailing semicolon.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+
+ line = clean_lines.elided[linenum]
+
+ # Block bodies should not be followed by a semicolon. Due to C++11
+ # brace initialization, there are more places where semicolons are
+ # required than not, so we use a whitelist approach to check these
+ # rather than a blacklist. These are the places where "};" should
+ # be replaced by just "}":
+ # 1. Some flavor of block following closing parenthesis:
+ # for (;;) {};
+ # while (...) {};
+ # switch (...) {};
+ # Function(...) {};
+ # if (...) {};
+ # if (...) else if (...) {};
+ #
+ # 2. else block:
+ # if (...) else {};
+ #
+ # 3. const member function:
+ # Function(...) const {};
+ #
+ # 4. Block following some statement:
+ # x = 42;
+ # {};
+ #
+ # 5. Block at the beginning of a function:
+ # Function(...) {
+ # {};
+ # }
+ #
+ # Note that naively checking for the preceding "{" will also match
+ # braces inside multi-dimensional arrays, but this is fine since
+ # that expression will not contain semicolons.
+ #
+ # 6. Block following another block:
+ # while (true) {}
+ # {};
+ #
+ # 7. End of namespaces:
+ # namespace {};
+ #
+ # These semicolons seems far more common than other kinds of
+ # redundant semicolons, possibly due to people converting classes
+ # to namespaces. For now we do not warn for this case.
+ #
+ # Try matching case 1 first.
+ match = Match(r'^(.*\)\s*)\{', line)
+ if match:
+ # Matched closing parenthesis (case 1). Check the token before the
+ # matching opening parenthesis, and don't warn if it looks like a
+ # macro. This avoids these false positives:
+ # - macro that defines a base class
+ # - multi-line macro that defines a base class
+ # - macro that defines the whole class-head
+ #
+ # But we still issue warnings for macros that we know are safe to
+ # warn, specifically:
+ # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P
+ # - TYPED_TEST
+ # - INTERFACE_DEF
+ # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED:
+ #
+ # We implement a whitelist of safe macros instead of a blacklist of
+ # unsafe macros, even though the latter appears less frequently in
+ # google code and would have been easier to implement. This is because
+ # the downside for getting the whitelist wrong means some extra
+ # semicolons, while the downside for getting the blacklist wrong
+ # would result in compile errors.
+ #
+ # In addition to macros, we also don't want to warn on
+ # - Compound literals
+ # - Lambdas
+ # - alignas specifier with anonymous structs:
+ closing_brace_pos = match.group(1).rfind(')')
+ opening_parenthesis = ReverseCloseExpression(
+ clean_lines, linenum, closing_brace_pos)
+ if opening_parenthesis[2] > -1:
+ line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]]
+ macro = Search(r'\b([A-Z_]+)\s*$', line_prefix)
+ func = Match(r'^(.*\])\s*$', line_prefix)
+ if ((macro and
+ macro.group(1) not in (
+ 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST',
+ 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED',
+ 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or
+ (func and not Search(r'\boperator\s*\[\s*\]', func.group(1))) or
+ Search(r'\b(?:struct|union)\s+alignas\s*$', line_prefix) or
+ Search(r'\s+=\s*$', line_prefix)):
+ match = None
+ if (match and
+ opening_parenthesis[1] > 1 and
+ Search(r'\]\s*$', clean_lines.elided[opening_parenthesis[1] - 1])):
+ # Multi-line lambda-expression
+ match = None
+
+ else:
+ # Try matching cases 2-3.
+ match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line)
+ if not match:
+ # Try matching cases 4-6. These are always matched on separate lines.
+ #
+ # Note that we can't simply concatenate the previous line to the
+ # current line and do a single match, otherwise we may output
+ # duplicate warnings for the blank line case:
+ # if (cond) {
+ # // blank line
+ # }
+ prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0]
+ if prevline and Search(r'[;{}]\s*$', prevline):
+ match = Match(r'^(\s*)\{', line)
+
+ # Check matching closing brace
+ if match:
+ (endline, endlinenum, endpos) = CloseExpression(
+ clean_lines, linenum, len(match.group(1)))
+ if endpos > -1 and Match(r'^\s*;', endline[endpos:]):
+ # Current {} pair is eligible for semicolon check, and we have found
+ # the redundant semicolon, output warning here.
+ #
+ # Note: because we are scanning forward for opening braces, and
+ # outputting warnings for the matching closing brace, if there are
+ # nested blocks with trailing semicolons, we will get the error
+ # messages in reversed order.
+ error(filename, endlinenum, 'readability/braces', 4,
+ "You don't need a ; after a }")
+
+
+def CheckEmptyBlockBody(filename, clean_lines, linenum, error):
+ """Look for empty loop/conditional body with only a single semicolon.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+
+ # Search for loop keywords at the beginning of the line. Because only
+ # whitespaces are allowed before the keywords, this will also ignore most
+ # do-while-loops, since those lines should start with closing brace.
+ #
+ # We also check "if" blocks here, since an empty conditional block
+ # is likely an error.
+ line = clean_lines.elided[linenum]
+ matched = Match(r'\s*(for|while|if)\s*\(', line)
+ if matched:
+ # Find the end of the conditional expression
+ (end_line, end_linenum, end_pos) = CloseExpression(
+ clean_lines, linenum, line.find('('))
+
+ # Output warning if what follows the condition expression is a semicolon.
+ # No warning for all other cases, including whitespace or newline, since we
+ # have a separate check for semicolons preceded by whitespace.
+ if end_pos >= 0 and Match(r';', end_line[end_pos:]):
+ if matched.group(1) == 'if':
+ error(filename, end_linenum, 'whitespace/empty_conditional_body', 5,
+ 'Empty conditional bodies should use {}')
+ else:
+ error(filename, end_linenum, 'whitespace/empty_loop_body', 5,
+ 'Empty loop bodies should use {} or continue')
+
+
+def FindCheckMacro(line):
+ """Find a replaceable CHECK-like macro.
+
+ Args:
+ line: line to search on.
+ Returns:
+ (macro name, start position), or (None, -1) if no replaceable
+ macro is found.
+ """
+ for macro in _CHECK_MACROS:
+ i = line.find(macro)
+ if i >= 0:
+ # Find opening parenthesis. Do a regular expression match here
+ # to make sure that we are matching the expected CHECK macro, as
+ # opposed to some other macro that happens to contain the CHECK
+ # substring.
+ matched = Match(r'^(.*\b' + macro + r'\s*)\(', line)
+ if not matched:
+ continue
+ return (macro, len(matched.group(1)))
+ return (None, -1)
+
+
+def CheckCheck(filename, clean_lines, linenum, error):
+ """Checks the use of CHECK and EXPECT macros.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+
+ # Decide the set of replacement macros that should be suggested
+ lines = clean_lines.elided
+ (check_macro, start_pos) = FindCheckMacro(lines[linenum])
+ if not check_macro:
+ return
+
+ # Find end of the boolean expression by matching parentheses
+ (last_line, end_line, end_pos) = CloseExpression(
+ clean_lines, linenum, start_pos)
+ if end_pos < 0:
+ return
+
+ # If the check macro is followed by something other than a
+ # semicolon, assume users will log their own custom error messages
+ # and don't suggest any replacements.
+ if not Match(r'\s*;', last_line[end_pos:]):
+ return
+
+ if linenum == end_line:
+ expression = lines[linenum][start_pos + 1:end_pos - 1]
+ else:
+ expression = lines[linenum][start_pos + 1:]
+ for i in xrange(linenum + 1, end_line):
+ expression += lines[i]
+ expression += last_line[0:end_pos - 1]
+
+ # Parse expression so that we can take parentheses into account.
+ # This avoids false positives for inputs like "CHECK((a < 4) == b)",
+ # which is not replaceable by CHECK_LE.
+ lhs = ''
+ rhs = ''
+ operator = None
+ while expression:
+ matched = Match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||'
+ r'==|!=|>=|>|<=|<|\()(.*)$', expression)
+ if matched:
+ token = matched.group(1)
+ if token == '(':
+ # Parenthesized operand
+ expression = matched.group(2)
+ (end, _) = FindEndOfExpressionInLine(expression, 0, ['('])
+ if end < 0:
+ return # Unmatched parenthesis
+ lhs += '(' + expression[0:end]
+ expression = expression[end:]
+ elif token in ('&&', '||'):
+ # Logical and/or operators. This means the expression
+ # contains more than one term, for example:
+ # CHECK(42 < a && a < b);
+ #
+ # These are not replaceable with CHECK_LE, so bail out early.
+ return
+ elif token in ('<<', '<<=', '>>', '>>=', '->*', '->'):
+ # Non-relational operator
+ lhs += token
+ expression = matched.group(2)
+ else:
+ # Relational operator
+ operator = token
+ rhs = matched.group(2)
+ break
+ else:
+ # Unparenthesized operand. Instead of appending to lhs one character
+ # at a time, we do another regular expression match to consume several
+ # characters at once if possible. Trivial benchmark shows that this
+ # is more efficient when the operands are longer than a single
+ # character, which is generally the case.
+ matched = Match(r'^([^-=!<>()&|]+)(.*)$', expression)
+ if not matched:
+ matched = Match(r'^(\s*\S)(.*)$', expression)
+ if not matched:
+ break
+ lhs += matched.group(1)
+ expression = matched.group(2)
+
+ # Only apply checks if we got all parts of the boolean expression
+ if not (lhs and operator and rhs):
+ return
+
+ # Check that rhs do not contain logical operators. We already know
+ # that lhs is fine since the loop above parses out && and ||.
+ if rhs.find('&&') > -1 or rhs.find('||') > -1:
+ return
+
+ # At least one of the operands must be a constant literal. This is
+ # to avoid suggesting replacements for unprintable things like
+ # CHECK(variable != iterator)
+ #
+ # The following pattern matches decimal, hex integers, strings, and
+ # characters (in that order).
+ lhs = lhs.strip()
+ rhs = rhs.strip()
+ match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$'
+ if Match(match_constant, lhs) or Match(match_constant, rhs):
+ # Note: since we know both lhs and rhs, we can provide a more
+ # descriptive error message like:
+ # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42)
+ # Instead of:
+ # Consider using CHECK_EQ instead of CHECK(a == b)
+ #
+ # We are still keeping the less descriptive message because if lhs
+ # or rhs gets long, the error message might become unreadable.
+ error(filename, linenum, 'readability/check', 2,
+ 'Consider using %s instead of %s(a %s b)' % (
+ _CHECK_REPLACEMENT[check_macro][operator],
+ check_macro, operator))
+
+
+def CheckAltTokens(filename, clean_lines, linenum, error):
+ """Check alternative keywords being used in boolean expressions.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Avoid preprocessor lines
+ if Match(r'^\s*#', line):
+ return
+
+ # Last ditch effort to avoid multi-line comments. This will not help
+ # if the comment started before the current line or ended after the
+ # current line, but it catches most of the false positives. At least,
+ # it provides a way to workaround this warning for people who use
+ # multi-line comments in preprocessor macros.
+ #
+ # TODO(unknown): remove this once cpplint has better support for
+ # multi-line comments.
+ if line.find('/*') >= 0 or line.find('*/') >= 0:
+ return
+
+ for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line):
+ error(filename, linenum, 'readability/alt_tokens', 2,
+ 'Use operator %s instead of %s' % (
+ _ALT_TOKEN_REPLACEMENT[match.group(1)], match.group(1)))
+
+
+def GetLineWidth(line):
+ """Determines the width of the line in column positions.
+
+ Args:
+ line: A string, which may be a Unicode string.
+
+ Returns:
+ The width of the line in column positions, accounting for Unicode
+ combining characters and wide characters.
+ """
+ if isinstance(line, unicode):
+ width = 0
+ for uc in unicodedata.normalize('NFC', line):
+ if unicodedata.east_asian_width(uc) in ('W', 'F'):
+ width += 2
+ elif not unicodedata.combining(uc):
+ width += 1
+ return width
+ else:
+ return len(line)
+
+
+def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state,
+ error):
+ """Checks rules from the 'C++ style rules' section of cppguide.html.
+
+ Most of these rules are hard to test (naming, comment style), but we
+ do what we can. In particular we check for 2-space indents, line lengths,
+ tab usage, spaces inside code, etc.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ file_extension: The extension (without the dot) of the filename.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: The function to call with any errors found.
+ """
+
+ # Don't use "elided" lines here, otherwise we can't check commented lines.
+ # Don't want to use "raw" either, because we don't want to check inside C++11
+ # raw strings,
+ raw_lines = clean_lines.lines_without_raw_strings
+ line = raw_lines[linenum]
+
+ if line.find('\t') != -1:
+ error(filename, linenum, 'whitespace/tab', 1,
+ 'Tab found; better to use spaces')
+
+ # One or three blank spaces at the beginning of the line is weird; it's
+ # hard to reconcile that with 2-space indents.
+ # NOTE: here are the conditions rob pike used for his tests. Mine aren't
+ # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces
+ # if(RLENGTH > 20) complain = 0;
+ # if(match($0, " +(error|private|public|protected):")) complain = 0;
+ # if(match(prev, "&& *$")) complain = 0;
+ # if(match(prev, "\\|\\| *$")) complain = 0;
+ # if(match(prev, "[\",=><] *$")) complain = 0;
+ # if(match($0, " <<")) complain = 0;
+ # if(match(prev, " +for \\(")) complain = 0;
+ # if(prevodd && match(prevprev, " +for \\(")) complain = 0;
+ scope_or_label_pattern = r'\s*\w+\s*:\s*\\?$'
+ classinfo = nesting_state.InnermostClass()
+ initial_spaces = 0
+ cleansed_line = clean_lines.elided[linenum]
+ while initial_spaces < len(line) and line[initial_spaces] == ' ':
+ initial_spaces += 1
+ if line and line[-1].isspace():
+ error(filename, linenum, 'whitespace/end_of_line', 4,
+ 'Line ends in whitespace. Consider deleting these extra spaces.')
+ # There are certain situations we allow one space, notably for
+ # section labels, and also lines containing multi-line raw strings.
+ elif ((initial_spaces == 1 or initial_spaces == 3) and
+ not Match(scope_or_label_pattern, cleansed_line) and
+ not (clean_lines.raw_lines[linenum] != line and
+ Match(r'^\s*""', line))):
+ error(filename, linenum, 'whitespace/indent', 3,
+ 'Weird number of spaces at line-start. '
+ 'Are you using a 2-space indent?')
+
+ # Check if the line is a header guard.
+ is_header_guard = False
+ if file_extension == 'h':
+ cppvar = GetHeaderGuardCPPVariable(filename)
+ if (line.startswith('#ifndef %s' % cppvar) or
+ line.startswith('#define %s' % cppvar) or
+ line.startswith('#endif // %s' % cppvar)):
+ is_header_guard = True
+ # #include lines and header guards can be long, since there's no clean way to
+ # split them.
+ #
+ # URLs can be long too. It's possible to split these, but it makes them
+ # harder to cut&paste.
+ #
+ # The "$Id:...$" comment may also get very long without it being the
+ # developers fault.
+ if (not line.startswith('#include') and not is_header_guard and
+ not Match(r'^\s*//.*http(s?)://\S*$', line) and
+ not Match(r'^// \$Id:.*#[0-9]+ \$$', line)):
+ line_width = GetLineWidth(line)
+ extended_length = int((_line_length * 1.25))
+ if line_width > extended_length:
+ error(filename, linenum, 'whitespace/line_length', 4,
+ 'Lines should very rarely be longer than %i characters' %
+ extended_length)
+ elif line_width > _line_length:
+ error(filename, linenum, 'whitespace/line_length', 2,
+ 'Lines should be <= %i characters long' % _line_length)
+
+ if (cleansed_line.count(';') > 1 and
+ # for loops are allowed two ;'s (and may run over two lines).
+ cleansed_line.find('for') == -1 and
+ (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or
+ GetPreviousNonBlankLine(clean_lines, linenum)[0].find(';') != -1) and
+ # It's ok to have many commands in a switch case that fits in 1 line
+ not ((cleansed_line.find('case ') != -1 or
+ cleansed_line.find('default:') != -1) and
+ cleansed_line.find('break;') != -1)):
+ error(filename, linenum, 'whitespace/newline', 0,
+ 'More than one command on the same line')
+
+ # Some more style checks
+ CheckBraces(filename, clean_lines, linenum, error)
+ CheckTrailingSemicolon(filename, clean_lines, linenum, error)
+ CheckEmptyBlockBody(filename, clean_lines, linenum, error)
+ CheckAccess(filename, clean_lines, linenum, nesting_state, error)
+ CheckSpacing(filename, clean_lines, linenum, nesting_state, error)
+ CheckOperatorSpacing(filename, clean_lines, linenum, error)
+ CheckParenthesisSpacing(filename, clean_lines, linenum, error)
+ CheckCommaSpacing(filename, clean_lines, linenum, error)
+ CheckBracesSpacing(filename, clean_lines, linenum, error)
+ CheckSpacingForFunctionCall(filename, clean_lines, linenum, error)
+ CheckRValueReference(filename, clean_lines, linenum, nesting_state, error)
+ CheckCheck(filename, clean_lines, linenum, error)
+ CheckAltTokens(filename, clean_lines, linenum, error)
+ classinfo = nesting_state.InnermostClass()
+ if classinfo:
+ CheckSectionSpacing(filename, clean_lines, classinfo, linenum, error)
+
+
+_RE_PATTERN_INCLUDE = re.compile(r'^\s*#\s*include\s*([<"])([^>"]*)[>"].*$')
+# Matches the first component of a filename delimited by -s and _s. That is:
+# _RE_FIRST_COMPONENT.match('foo').group(0) == 'foo'
+# _RE_FIRST_COMPONENT.match('foo.cc').group(0) == 'foo'
+# _RE_FIRST_COMPONENT.match('foo-bar_baz.cc').group(0) == 'foo'
+# _RE_FIRST_COMPONENT.match('foo_bar-baz.cc').group(0) == 'foo'
+_RE_FIRST_COMPONENT = re.compile(r'^[^-_.]+')
+
+
+def _DropCommonSuffixes(filename):
+ """Drops common suffixes like _test.cc or -inl.h from filename.
+
+ For example:
+ >>> _DropCommonSuffixes('foo/foo-inl.h')
+ 'foo/foo'
+ >>> _DropCommonSuffixes('foo/bar/foo.cc')
+ 'foo/bar/foo'
+ >>> _DropCommonSuffixes('foo/foo_internal.h')
+ 'foo/foo'
+ >>> _DropCommonSuffixes('foo/foo_unusualinternal.h')
+ 'foo/foo_unusualinternal'
+
+ Args:
+ filename: The input filename.
+
+ Returns:
+ The filename with the common suffix removed.
+ """
+ for suffix in ('test.cc', 'regtest.cc', 'unittest.cc',
+ 'inl.h', 'impl.h', 'internal.h'):
+ if (filename.endswith(suffix) and len(filename) > len(suffix) and
+ filename[-len(suffix) - 1] in ('-', '_')):
+ return filename[:-len(suffix) - 1]
+ return os.path.splitext(filename)[0]
+
+
+def _IsTestFilename(filename):
+ """Determines if the given filename has a suffix that identifies it as a test.
+
+ Args:
+ filename: The input filename.
+
+ Returns:
+ True if 'filename' looks like a test, False otherwise.
+ """
+ if (filename.endswith('_test.cc') or
+ filename.endswith('_unittest.cc') or
+ filename.endswith('_regtest.cc')):
+ return True
+ else:
+ return False
+
+
+def _ClassifyInclude(fileinfo, include, is_system):
+ """Figures out what kind of header 'include' is.
+
+ Args:
+ fileinfo: The current file cpplint is running over. A FileInfo instance.
+ include: The path to a #included file.
+ is_system: True if the #include used <> rather than "".
+
+ Returns:
+ One of the _XXX_HEADER constants.
+
+ For example:
+ >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'stdio.h', True)
+ _C_SYS_HEADER
+ >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True)
+ _CPP_SYS_HEADER
+ >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False)
+ _LIKELY_MY_HEADER
+ >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'),
+ ... 'bar/foo_other_ext.h', False)
+ _POSSIBLE_MY_HEADER
+ >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/bar.h', False)
+ _OTHER_HEADER
+ """
+ # This is a list of all standard c++ header files, except
+ # those already checked for above.
+ is_cpp_h = include in _CPP_HEADERS
+
+ if is_system:
+ if is_cpp_h:
+ return _CPP_SYS_HEADER
+ else:
+ return _C_SYS_HEADER
+
+ # If the target file and the include we're checking share a
+ # basename when we drop common extensions, and the include
+ # lives in . , then it's likely to be owned by the target file.
+ target_dir, target_base = (
+ os.path.split(_DropCommonSuffixes(fileinfo.RepositoryName())))
+ include_dir, include_base = os.path.split(_DropCommonSuffixes(include))
+ if target_base == include_base and (
+ include_dir == target_dir or
+ include_dir == os.path.normpath(target_dir + '/../public')):
+ return _LIKELY_MY_HEADER
+
+ # If the target and include share some initial basename
+ # component, it's possible the target is implementing the
+ # include, so it's allowed to be first, but we'll never
+ # complain if it's not there.
+ target_first_component = _RE_FIRST_COMPONENT.match(target_base)
+ include_first_component = _RE_FIRST_COMPONENT.match(include_base)
+ if (target_first_component and include_first_component and
+ target_first_component.group(0) ==
+ include_first_component.group(0)):
+ return _POSSIBLE_MY_HEADER
+
+ return _OTHER_HEADER
+
+
+
+def CheckIncludeLine(filename, clean_lines, linenum, include_state, error):
+ """Check rules that are applicable to #include lines.
+
+ Strings on #include lines are NOT removed from elided line, to make
+ certain tasks easier. However, to prevent false positives, checks
+ applicable to #include lines in CheckLanguage must be put here.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ include_state: An _IncludeState instance in which the headers are inserted.
+ error: The function to call with any errors found.
+ """
+ fileinfo = FileInfo(filename)
+ line = clean_lines.lines[linenum]
+
+ # "include" should use the new style "foo/bar.h" instead of just "bar.h"
+ # Only do this check if the included header follows google naming
+ # conventions. If not, assume that it's a 3rd party API that
+ # requires special include conventions.
+ #
+ # We also make an exception for Lua headers, which follow google
+ # naming convention but not the include convention.
+ match = Match(r'#include\s*"([^/]+\.h)"', line)
+ if match and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)):
+ error(filename, linenum, 'build/include', 4,
+ 'Include the directory when naming .h files')
+
+ # we shouldn't include a file more than once. actually, there are a
+ # handful of instances where doing so is okay, but in general it's
+ # not.
+ match = _RE_PATTERN_INCLUDE.search(line)
+ if match:
+ include = match.group(2)
+ is_system = (match.group(1) == '<')
+ duplicate_line = include_state.FindHeader(include)
+ if duplicate_line >= 0:
+ error(filename, linenum, 'build/include', 4,
+ '"%s" already included at %s:%s' %
+ (include, filename, duplicate_line))
+ elif (include.endswith('.cc') and
+ os.path.dirname(fileinfo.RepositoryName()) != os.path.dirname(include)):
+ error(filename, linenum, 'build/include', 4,
+ 'Do not include .cc files from other packages')
+ elif not _THIRD_PARTY_HEADERS_PATTERN.match(include):
+ include_state.include_list[-1].append((include, linenum))
+
+ # We want to ensure that headers appear in the right order:
+ # 1) for foo.cc, foo.h (preferred location)
+ # 2) c system files
+ # 3) cpp system files
+ # 4) for foo.cc, foo.h (deprecated location)
+ # 5) other google headers
+ #
+ # We classify each include statement as one of those 5 types
+ # using a number of techniques. The include_state object keeps
+ # track of the highest type seen, and complains if we see a
+ # lower type after that.
+ error_message = include_state.CheckNextIncludeOrder(
+ _ClassifyInclude(fileinfo, include, is_system))
+ if error_message:
+ error(filename, linenum, 'build/include_order', 4,
+ '%s. Should be: %s.h, c system, c++ system, other.' %
+ (error_message, fileinfo.BaseName()))
+ canonical_include = include_state.CanonicalizeAlphabeticalOrder(include)
+ if not include_state.IsInAlphabeticalOrder(
+ clean_lines, linenum, canonical_include):
+ error(filename, linenum, 'build/include_alpha', 4,
+ 'Include "%s" not in alphabetical order' % include)
+ include_state.SetLastHeader(canonical_include)
+
+
+
+def _GetTextInside(text, start_pattern):
+ r"""Retrieves all the text between matching open and close parentheses.
+
+ Given a string of lines and a regular expression string, retrieve all the text
+ following the expression and between opening punctuation symbols like
+ (, [, or {, and the matching close-punctuation symbol. This properly nested
+ occurrences of the punctuations, so for the text like
+ printf(a(), b(c()));
+ a call to _GetTextInside(text, r'printf\(') will return 'a(), b(c())'.
+ start_pattern must match string having an open punctuation symbol at the end.
+
+ Args:
+ text: The lines to extract text. Its comments and strings must be elided.
+ It can be single line and can span multiple lines.
+ start_pattern: The regexp string indicating where to start extracting
+ the text.
+ Returns:
+ The extracted text.
+ None if either the opening string or ending punctuation could not be found.
+ """
+ # TODO(unknown): Audit cpplint.py to see what places could be profitably
+ # rewritten to use _GetTextInside (and use inferior regexp matching today).
+
+ # Give opening punctuations to get the matching close-punctuations.
+ matching_punctuation = {'(': ')', '{': '}', '[': ']'}
+ closing_punctuation = set(matching_punctuation.itervalues())
+
+ # Find the position to start extracting text.
+ match = re.search(start_pattern, text, re.M)
+ if not match: # start_pattern not found in text.
+ return None
+ start_position = match.end(0)
+
+ assert start_position > 0, (
+ 'start_pattern must ends with an opening punctuation.')
+ assert text[start_position - 1] in matching_punctuation, (
+ 'start_pattern must ends with an opening punctuation.')
+ # Stack of closing punctuations we expect to have in text after position.
+ punctuation_stack = [matching_punctuation[text[start_position - 1]]]
+ position = start_position
+ while punctuation_stack and position < len(text):
+ if text[position] == punctuation_stack[-1]:
+ punctuation_stack.pop()
+ elif text[position] in closing_punctuation:
+ # A closing punctuation without matching opening punctuations.
+ return None
+ elif text[position] in matching_punctuation:
+ punctuation_stack.append(matching_punctuation[text[position]])
+ position += 1
+ if punctuation_stack:
+ # Opening punctuations left without matching close-punctuations.
+ return None
+ # punctuations match.
+ return text[start_position:position - 1]
+
+
+# Patterns for matching call-by-reference parameters.
+#
+# Supports nested templates up to 2 levels deep using this messy pattern:
+# < (?: < (?: < [^<>]*
+# >
+# | [^<>] )*
+# >
+# | [^<>] )*
+# >
+_RE_PATTERN_IDENT = r'[_a-zA-Z]\w*' # =~ [[:alpha:]][[:alnum:]]*
+_RE_PATTERN_TYPE = (
+ r'(?:const\s+)?(?:typename\s+|class\s+|struct\s+|union\s+|enum\s+)?'
+ r'(?:\w|'
+ r'\s*<(?:<(?:<[^<>]*>|[^<>])*>|[^<>])*>|'
+ r'::)+')
+# A call-by-reference parameter ends with '& identifier'.
+_RE_PATTERN_REF_PARAM = re.compile(
+ r'(' + _RE_PATTERN_TYPE + r'(?:\s*(?:\bconst\b|[*]))*\s*'
+ r'&\s*' + _RE_PATTERN_IDENT + r')\s*(?:=[^,()]+)?[,)]')
+# A call-by-const-reference parameter either ends with 'const& identifier'
+# or looks like 'const type& identifier' when 'type' is atomic.
+_RE_PATTERN_CONST_REF_PARAM = (
+ r'(?:.*\s*\bconst\s*&\s*' + _RE_PATTERN_IDENT +
+ r'|const\s+' + _RE_PATTERN_TYPE + r'\s*&\s*' + _RE_PATTERN_IDENT + r')')
+
+
+def CheckLanguage(filename, clean_lines, linenum, file_extension,
+ include_state, nesting_state, error):
+ """Checks rules from the 'C++ language rules' section of cppguide.html.
+
+ Some of these rules are hard to test (function overloading, using
+ uint32 inappropriately), but we do the best we can.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ file_extension: The extension (without the dot) of the filename.
+ include_state: An _IncludeState instance in which the headers are inserted.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: The function to call with any errors found.
+ """
+ # If the line is empty or consists of entirely a comment, no need to
+ # check it.
+ line = clean_lines.elided[linenum]
+ if not line:
+ return
+
+ match = _RE_PATTERN_INCLUDE.search(line)
+ if match:
+ CheckIncludeLine(filename, clean_lines, linenum, include_state, error)
+ return
+
+ # Reset include state across preprocessor directives. This is meant
+ # to silence warnings for conditional includes.
+ match = Match(r'^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b', line)
+ if match:
+ include_state.ResetSection(match.group(1))
+
+ # Make Windows paths like Unix.
+ fullname = os.path.abspath(filename).replace('\\', '/')
+
+ # Perform other checks now that we are sure that this is not an include line
+ CheckCasts(filename, clean_lines, linenum, error)
+ CheckGlobalStatic(filename, clean_lines, linenum, error)
+ CheckPrintf(filename, clean_lines, linenum, error)
+
+ if file_extension == 'h':
+ # TODO(unknown): check that 1-arg constructors are explicit.
+ # How to tell it's a constructor?
+ # (handled in CheckForNonStandardConstructs for now)
+ # TODO(unknown): check that classes declare or disable copy/assign
+ # (level 1 error)
+ pass
+
+ # Check if people are using the verboten C basic types. The only exception
+ # we regularly allow is "unsigned short port" for port.
+ if Search(r'\bshort port\b', line):
+ if not Search(r'\bunsigned short port\b', line):
+ error(filename, linenum, 'runtime/int', 4,
+ 'Use "unsigned short" for ports, not "short"')
+ else:
+ match = Search(r'\b(short|long(?! +double)|long long)\b', line)
+ if match:
+ error(filename, linenum, 'runtime/int', 4,
+ 'Use int16/int64/etc, rather than the C type %s' % match.group(1))
+
+ # Check if some verboten operator overloading is going on
+ # TODO(unknown): catch out-of-line unary operator&:
+ # class X {};
+ # int operator&(const X& x) { return 42; } // unary operator&
+ # The trick is it's hard to tell apart from binary operator&:
+ # class Y { int operator&(const Y& x) { return 23; } }; // binary operator&
+ if Search(r'\boperator\s*&\s*\(\s*\)', line):
+ error(filename, linenum, 'runtime/operator', 4,
+ 'Unary operator& is dangerous. Do not use it.')
+
+ # Check for suspicious usage of "if" like
+ # } if (a == b) {
+ if Search(r'\}\s*if\s*\(', line):
+ error(filename, linenum, 'readability/braces', 4,
+ 'Did you mean "else if"? If not, start a new line for "if".')
+
+ # Check for potential format string bugs like printf(foo).
+ # We constrain the pattern not to pick things like DocidForPrintf(foo).
+ # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str())
+ # TODO(unknown): Catch the following case. Need to change the calling
+ # convention of the whole function to process multiple line to handle it.
+ # printf(
+ # boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line);
+ printf_args = _GetTextInside(line, r'(?i)\b(string)?printf\s*\(')
+ if printf_args:
+ match = Match(r'([\w.\->()]+)$', printf_args)
+ if match and match.group(1) != '__VA_ARGS__':
+ function_name = re.search(r'\b((?:string)?printf)\s*\(',
+ line, re.I).group(1)
+ error(filename, linenum, 'runtime/printf', 4,
+ 'Potential format string bug. Do %s("%%s", %s) instead.'
+ % (function_name, match.group(1)))
+
+ # Check for potential memset bugs like memset(buf, sizeof(buf), 0).
+ match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line)
+ if match and not Match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)):
+ error(filename, linenum, 'runtime/memset', 4,
+ 'Did you mean "memset(%s, 0, %s)"?'
+ % (match.group(1), match.group(2)))
+
+ if Search(r'\busing namespace\b', line):
+ error(filename, linenum, 'build/namespaces', 5,
+ 'Do not use namespace using-directives. '
+ 'Use using-declarations instead.')
+
+ # Detect variable-length arrays.
+ match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line)
+ if (match and match.group(2) != 'return' and match.group(2) != 'delete' and
+ match.group(3).find(']') == -1):
+ # Split the size using space and arithmetic operators as delimiters.
+ # If any of the resulting tokens are not compile time constants then
+ # report the error.
+ tokens = re.split(r'\s|\+|\-|\*|\/|<<|>>]', match.group(3))
+ is_const = True
+ skip_next = False
+ for tok in tokens:
+ if skip_next:
+ skip_next = False
+ continue
+
+ if Search(r'sizeof\(.+\)', tok): continue
+ if Search(r'arraysize\(\w+\)', tok): continue
+
+ tok = tok.lstrip('(')
+ tok = tok.rstrip(')')
+ if not tok: continue
+ if Match(r'\d+', tok): continue
+ if Match(r'0[xX][0-9a-fA-F]+', tok): continue
+ if Match(r'k[A-Z0-9]\w*', tok): continue
+ if Match(r'(.+::)?k[A-Z0-9]\w*', tok): continue
+ if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue
+ # A catch all for tricky sizeof cases, including 'sizeof expression',
+ # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)'
+ # requires skipping the next token because we split on ' ' and '*'.
+ if tok.startswith('sizeof'):
+ skip_next = True
+ continue
+ is_const = False
+ break
+ if not is_const:
+ error(filename, linenum, 'runtime/arrays', 1,
+ 'Do not use variable-length arrays. Use an appropriately named '
+ "('k' followed by CamelCase) compile-time constant for the size.")
+
+ # Check for use of unnamed namespaces in header files. Registration
+ # macros are typically OK, so we allow use of "namespace {" on lines
+ # that end with backslashes.
+ if (file_extension == 'h'
+ and Search(r'\bnamespace\s*{', line)
+ and line[-1] != '\\'):
+ error(filename, linenum, 'build/namespaces', 4,
+ 'Do not use unnamed namespaces in header files. See '
+ 'http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces'
+ ' for more information.')
+
+
+def CheckGlobalStatic(filename, clean_lines, linenum, error):
+ """Check for unsafe global or static objects.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Match two lines at a time to support multiline declarations
+ if linenum + 1 < clean_lines.NumLines() and not Search(r'[;({]', line):
+ line += clean_lines.elided[linenum + 1].strip()
+
+ # Check for people declaring static/global STL strings at the top level.
+ # This is dangerous because the C++ language does not guarantee that
+ # globals with constructors are initialized before the first access.
+ match = Match(
+ r'((?:|static +)(?:|const +))string +([a-zA-Z0-9_:]+)\b(.*)',
+ line)
+
+ # Remove false positives:
+ # - String pointers (as opposed to values).
+ # string *pointer
+ # const string *pointer
+ # string const *pointer
+ # string *const pointer
+ #
+ # - Functions and template specializations.
+ # string Function<Type>(...
+ # string Class<Type>::Method(...
+ #
+ # - Operators. These are matched separately because operator names
+ # cross non-word boundaries, and trying to match both operators
+ # and functions at the same time would decrease accuracy of
+ # matching identifiers.
+ # string Class::operator*()
+ if (match and
+ not Search(r'\bstring\b(\s+const)?\s*\*\s*(const\s+)?\w', line) and
+ not Search(r'\boperator\W', line) and
+ not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(3))):
+ error(filename, linenum, 'runtime/string', 4,
+ 'For a static/global string constant, use a C style string instead: '
+ '"%schar %s[]".' %
+ (match.group(1), match.group(2)))
+
+ if Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line):
+ error(filename, linenum, 'runtime/init', 4,
+ 'You seem to be initializing a member variable with itself.')
+
+
+def CheckPrintf(filename, clean_lines, linenum, error):
+ """Check for printf related issues.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # When snprintf is used, the second argument shouldn't be a literal.
+ match = Search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line)
+ if match and match.group(2) != '0':
+ # If 2nd arg is zero, snprintf is used to calculate size.
+ error(filename, linenum, 'runtime/printf', 3,
+ 'If you can, use sizeof(%s) instead of %s as the 2nd arg '
+ 'to snprintf.' % (match.group(1), match.group(2)))
+
+ # Check if some verboten C functions are being used.
+ if Search(r'\bsprintf\s*\(', line):
+ error(filename, linenum, 'runtime/printf', 5,
+ 'Never use sprintf. Use snprintf instead.')
+ match = Search(r'\b(strcpy|strcat)\s*\(', line)
+ if match:
+ error(filename, linenum, 'runtime/printf', 4,
+ 'Almost always, snprintf is better than %s' % match.group(1))
+
+
+def IsDerivedFunction(clean_lines, linenum):
+ """Check if current line contains an inherited function.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ Returns:
+ True if current line contains a function with "override"
+ virt-specifier.
+ """
+ # Scan back a few lines for start of current function
+ for i in xrange(linenum, max(-1, linenum - 10), -1):
+ match = Match(r'^([^()]*\w+)\(', clean_lines.elided[i])
+ if match:
+ # Look for "override" after the matching closing parenthesis
+ line, _, closing_paren = CloseExpression(
+ clean_lines, i, len(match.group(1)))
+ return (closing_paren >= 0 and
+ Search(r'\boverride\b', line[closing_paren:]))
+ return False
+
+
+def IsOutOfLineMethodDefinition(clean_lines, linenum):
+ """Check if current line contains an out-of-line method definition.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ Returns:
+ True if current line contains an out-of-line method definition.
+ """
+ # Scan back a few lines for start of current function
+ for i in xrange(linenum, max(-1, linenum - 10), -1):
+ if Match(r'^([^()]*\w+)\(', clean_lines.elided[i]):
+ return Match(r'^[^()]*\w+::\w+\(', clean_lines.elided[i]) is not None
+ return False
+
+
+def IsInitializerList(clean_lines, linenum):
+ """Check if current line is inside constructor initializer list.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ Returns:
+ True if current line appears to be inside constructor initializer
+ list, False otherwise.
+ """
+ for i in xrange(linenum, 1, -1):
+ line = clean_lines.elided[i]
+ if i == linenum:
+ remove_function_body = Match(r'^(.*)\{\s*$', line)
+ if remove_function_body:
+ line = remove_function_body.group(1)
+
+ if Search(r'\s:\s*\w+[({]', line):
+ # A lone colon tend to indicate the start of a constructor
+ # initializer list. It could also be a ternary operator, which
+ # also tend to appear in constructor initializer lists as
+ # opposed to parameter lists.
+ return True
+ if Search(r'\}\s*,\s*$', line):
+ # A closing brace followed by a comma is probably the end of a
+ # brace-initialized member in constructor initializer list.
+ return True
+ if Search(r'[{};]\s*$', line):
+ # Found one of the following:
+ # - A closing brace or semicolon, probably the end of the previous
+ # function.
+ # - An opening brace, probably the start of current class or namespace.
+ #
+ # Current line is probably not inside an initializer list since
+ # we saw one of those things without seeing the starting colon.
+ return False
+
+ # Got to the beginning of the file without seeing the start of
+ # constructor initializer list.
+ return False
+
+
+def CheckForNonConstReference(filename, clean_lines, linenum,
+ nesting_state, error):
+ """Check for non-const references.
+
+ Separate from CheckLanguage since it scans backwards from current
+ line, instead of scanning forward.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: The function to call with any errors found.
+ """
+ # Do nothing if there is no '&' on current line.
+ line = clean_lines.elided[linenum]
+ if '&' not in line:
+ return
+
+ # If a function is inherited, current function doesn't have much of
+ # a choice, so any non-const references should not be blamed on
+ # derived function.
+ if IsDerivedFunction(clean_lines, linenum):
+ return
+
+ # Don't warn on out-of-line method definitions, as we would warn on the
+ # in-line declaration, if it isn't marked with 'override'.
+ if IsOutOfLineMethodDefinition(clean_lines, linenum):
+ return
+
+ # Long type names may be broken across multiple lines, usually in one
+ # of these forms:
+ # LongType
+ # ::LongTypeContinued &identifier
+ # LongType::
+ # LongTypeContinued &identifier
+ # LongType<
+ # ...>::LongTypeContinued &identifier
+ #
+ # If we detected a type split across two lines, join the previous
+ # line to current line so that we can match const references
+ # accordingly.
+ #
+ # Note that this only scans back one line, since scanning back
+ # arbitrary number of lines would be expensive. If you have a type
+ # that spans more than 2 lines, please use a typedef.
+ if linenum > 1:
+ previous = None
+ if Match(r'\s*::(?:[\w<>]|::)+\s*&\s*\S', line):
+ # previous_line\n + ::current_line
+ previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$',
+ clean_lines.elided[linenum - 1])
+ elif Match(r'\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S', line):
+ # previous_line::\n + current_line
+ previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$',
+ clean_lines.elided[linenum - 1])
+ if previous:
+ line = previous.group(1) + line.lstrip()
+ else:
+ # Check for templated parameter that is split across multiple lines
+ endpos = line.rfind('>')
+ if endpos > -1:
+ (_, startline, startpos) = ReverseCloseExpression(
+ clean_lines, linenum, endpos)
+ if startpos > -1 and startline < linenum:
+ # Found the matching < on an earlier line, collect all
+ # pieces up to current line.
+ line = ''
+ for i in xrange(startline, linenum + 1):
+ line += clean_lines.elided[i].strip()
+
+ # Check for non-const references in function parameters. A single '&' may
+ # found in the following places:
+ # inside expression: binary & for bitwise AND
+ # inside expression: unary & for taking the address of something
+ # inside declarators: reference parameter
+ # We will exclude the first two cases by checking that we are not inside a
+ # function body, including one that was just introduced by a trailing '{'.
+ # TODO(unknown): Doesn't account for 'catch(Exception& e)' [rare].
+ if (nesting_state.previous_stack_top and
+ not (isinstance(nesting_state.previous_stack_top, _ClassInfo) or
+ isinstance(nesting_state.previous_stack_top, _NamespaceInfo))):
+ # Not at toplevel, not within a class, and not within a namespace
+ return
+
+ # Avoid initializer lists. We only need to scan back from the
+ # current line for something that starts with ':'.
+ #
+ # We don't need to check the current line, since the '&' would
+ # appear inside the second set of parentheses on the current line as
+ # opposed to the first set.
+ if linenum > 0:
+ for i in xrange(linenum - 1, max(0, linenum - 10), -1):
+ previous_line = clean_lines.elided[i]
+ if not Search(r'[),]\s*$', previous_line):
+ break
+ if Match(r'^\s*:\s+\S', previous_line):
+ return
+
+ # Avoid preprocessors
+ if Search(r'\\\s*$', line):
+ return
+
+ # Avoid constructor initializer lists
+ if IsInitializerList(clean_lines, linenum):
+ return
+
+ # We allow non-const references in a few standard places, like functions
+ # called "swap()" or iostream operators like "<<" or ">>". Do not check
+ # those function parameters.
+ #
+ # We also accept & in static_assert, which looks like a function but
+ # it's actually a declaration expression.
+ whitelisted_functions = (r'(?:[sS]wap(?:<\w:+>)?|'
+ r'operator\s*[<>][<>]|'
+ r'static_assert|COMPILE_ASSERT'
+ r')\s*\(')
+ if Search(whitelisted_functions, line):
+ return
+ elif not Search(r'\S+\([^)]*$', line):
+ # Don't see a whitelisted function on this line. Actually we
+ # didn't see any function name on this line, so this is likely a
+ # multi-line parameter list. Try a bit harder to catch this case.
+ for i in xrange(2):
+ if (linenum > i and
+ Search(whitelisted_functions, clean_lines.elided[linenum - i - 1])):
+ return
+
+ decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body
+ for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls):
+ if not Match(_RE_PATTERN_CONST_REF_PARAM, parameter):
+ error(filename, linenum, 'runtime/references', 2,
+ 'Is this a non-const reference? '
+ 'If so, make const or use a pointer: ' +
+ ReplaceAll(' *<', '<', parameter))
+
+
+def CheckCasts(filename, clean_lines, linenum, error):
+ """Various cast related checks.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Check to see if they're using an conversion function cast.
+ # I just try to capture the most common basic types, though there are more.
+ # Parameterless conversion functions, such as bool(), are allowed as they are
+ # probably a member operator declaration or default constructor.
+ match = Search(
+ r'(\bnew\s+|\S<\s*(?:const\s+)?)?\b'
+ r'(int|float|double|bool|char|int32|uint32|int64|uint64)'
+ r'(\([^)].*)', line)
+ expecting_function = ExpectingFunctionArgs(clean_lines, linenum)
+ if match and not expecting_function:
+ matched_type = match.group(2)
+
+ # matched_new_or_template is used to silence two false positives:
+ # - New operators
+ # - Template arguments with function types
+ #
+ # For template arguments, we match on types immediately following
+ # an opening bracket without any spaces. This is a fast way to
+ # silence the common case where the function type is the first
+ # template argument. False negative with less-than comparison is
+ # avoided because those operators are usually followed by a space.
+ #
+ # function<double(double)> // bracket + no space = false positive
+ # value < double(42) // bracket + space = true positive
+ matched_new_or_template = match.group(1)
+
+ # Avoid arrays by looking for brackets that come after the closing
+ # parenthesis.
+ if Match(r'\([^()]+\)\s*\[', match.group(3)):
+ return
+
+ # Other things to ignore:
+ # - Function pointers
+ # - Casts to pointer types
+ # - Placement new
+ # - Alias declarations
+ matched_funcptr = match.group(3)
+ if (matched_new_or_template is None and
+ not (matched_funcptr and
+ (Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(',
+ matched_funcptr) or
+ matched_funcptr.startswith('(*)'))) and
+ not Match(r'\s*using\s+\S+\s*=\s*' + matched_type, line) and
+ not Search(r'new\(\S+\)\s*' + matched_type, line)):
+ error(filename, linenum, 'readability/casting', 4,
+ 'Using deprecated casting style. '
+ 'Use static_cast<%s>(...) instead' %
+ matched_type)
+
+ if not expecting_function:
+ CheckCStyleCast(filename, clean_lines, linenum, 'static_cast',
+ r'\((int|float|double|bool|char|u?int(16|32|64))\)', error)
+
+ # This doesn't catch all cases. Consider (const char * const)"hello".
+ #
+ # (char *) "foo" should always be a const_cast (reinterpret_cast won't
+ # compile).
+ if CheckCStyleCast(filename, clean_lines, linenum, 'const_cast',
+ r'\((char\s?\*+\s?)\)\s*"', error):
+ pass
+ else:
+ # Check pointer casts for other than string constants
+ CheckCStyleCast(filename, clean_lines, linenum, 'reinterpret_cast',
+ r'\((\w+\s?\*+\s?)\)', error)
+
+ # In addition, we look for people taking the address of a cast. This
+ # is dangerous -- casts can assign to temporaries, so the pointer doesn't
+ # point where you think.
+ #
+ # Some non-identifier character is required before the '&' for the
+ # expression to be recognized as a cast. These are casts:
+ # expression = &static_cast<int*>(temporary());
+ # function(&(int*)(temporary()));
+ #
+ # This is not a cast:
+ # reference_type&(int* function_param);
+ match = Search(
+ r'(?:[^\w]&\(([^)*][^)]*)\)[\w(])|'
+ r'(?:[^\w]&(static|dynamic|down|reinterpret)_cast\b)', line)
+ if match:
+ # Try a better error message when the & is bound to something
+ # dereferenced by the casted pointer, as opposed to the casted
+ # pointer itself.
+ parenthesis_error = False
+ match = Match(r'^(.*&(?:static|dynamic|down|reinterpret)_cast\b)<', line)
+ if match:
+ _, y1, x1 = CloseExpression(clean_lines, linenum, len(match.group(1)))
+ if x1 >= 0 and clean_lines.elided[y1][x1] == '(':
+ _, y2, x2 = CloseExpression(clean_lines, y1, x1)
+ if x2 >= 0:
+ extended_line = clean_lines.elided[y2][x2:]
+ if y2 < clean_lines.NumLines() - 1:
+ extended_line += clean_lines.elided[y2 + 1]
+ if Match(r'\s*(?:->|\[)', extended_line):
+ parenthesis_error = True
+
+ if parenthesis_error:
+ error(filename, linenum, 'readability/casting', 4,
+ ('Are you taking an address of something dereferenced '
+ 'from a cast? Wrapping the dereferenced expression in '
+ 'parentheses will make the binding more obvious'))
+ else:
+ error(filename, linenum, 'runtime/casting', 4,
+ ('Are you taking an address of a cast? '
+ 'This is dangerous: could be a temp var. '
+ 'Take the address before doing the cast, rather than after'))
+
+
+def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error):
+ """Checks for a C-style cast by looking for the pattern.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ cast_type: The string for the C++ cast to recommend. This is either
+ reinterpret_cast, static_cast, or const_cast, depending.
+ pattern: The regular expression used to find C-style casts.
+ error: The function to call with any errors found.
+
+ Returns:
+ True if an error was emitted.
+ False otherwise.
+ """
+ line = clean_lines.elided[linenum]
+ match = Search(pattern, line)
+ if not match:
+ return False
+
+ # Exclude lines with keywords that tend to look like casts
+ context = line[0:match.start(1) - 1]
+ if Match(r'.*\b(?:sizeof|alignof|alignas|[_A-Z][_A-Z0-9]*)\s*$', context):
+ return False
+
+ # Try expanding current context to see if we one level of
+ # parentheses inside a macro.
+ if linenum > 0:
+ for i in xrange(linenum - 1, max(0, linenum - 5), -1):
+ context = clean_lines.elided[i] + context
+ if Match(r'.*\b[_A-Z][_A-Z0-9]*\s*\((?:\([^()]*\)|[^()])*$', context):
+ return False
+
+ # operator++(int) and operator--(int)
+ if context.endswith(' operator++') or context.endswith(' operator--'):
+ return False
+
+ # A single unnamed argument for a function tends to look like old
+ # style cast. If we see those, don't issue warnings for deprecated
+ # casts, instead issue warnings for unnamed arguments where
+ # appropriate.
+ #
+ # These are things that we want warnings for, since the style guide
+ # explicitly require all parameters to be named:
+ # Function(int);
+ # Function(int) {
+ # ConstMember(int) const;
+ # ConstMember(int) const {
+ # ExceptionMember(int) throw (...);
+ # ExceptionMember(int) throw (...) {
+ # PureVirtual(int) = 0;
+ # [](int) -> bool {
+ #
+ # These are functions of some sort, where the compiler would be fine
+ # if they had named parameters, but people often omit those
+ # identifiers to reduce clutter:
+ # (FunctionPointer)(int);
+ # (FunctionPointer)(int) = value;
+ # Function((function_pointer_arg)(int))
+ # Function((function_pointer_arg)(int), int param)
+ # <TemplateArgument(int)>;
+ # <(FunctionPointerTemplateArgument)(int)>;
+ remainder = line[match.end(0):]
+ if Match(r'^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)',
+ remainder):
+ # Looks like an unnamed parameter.
+
+ # Don't warn on any kind of template arguments.
+ if Match(r'^\s*>', remainder):
+ return False
+
+ # Don't warn on assignments to function pointers, but keep warnings for
+ # unnamed parameters to pure virtual functions. Note that this pattern
+ # will also pass on assignments of "0" to function pointers, but the
+ # preferred values for those would be "nullptr" or "NULL".
+ matched_zero = Match(r'^\s=\s*(\S+)\s*;', remainder)
+ if matched_zero and matched_zero.group(1) != '0':
+ return False
+
+ # Don't warn on function pointer declarations. For this we need
+ # to check what came before the "(type)" string.
+ if Match(r'.*\)\s*$', line[0:match.start(0)]):
+ return False
+
+ # Don't warn if the parameter is named with block comments, e.g.:
+ # Function(int /*unused_param*/);
+ raw_line = clean_lines.raw_lines[linenum]
+ if '/*' in raw_line:
+ return False
+
+ # Passed all filters, issue warning here.
+ error(filename, linenum, 'readability/function', 3,
+ 'All parameters should be named in a function')
+ return True
+
+ # At this point, all that should be left is actual casts.
+ error(filename, linenum, 'readability/casting', 4,
+ 'Using C-style cast. Use %s<%s>(...) instead' %
+ (cast_type, match.group(1)))
+
+ return True
+
+
+def ExpectingFunctionArgs(clean_lines, linenum):
+ """Checks whether where function type arguments are expected.
+
+ Args:
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+
+ Returns:
+ True if the line at 'linenum' is inside something that expects arguments
+ of function types.
+ """
+ line = clean_lines.elided[linenum]
+ return (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or
+ (linenum >= 2 and
+ (Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$',
+ clean_lines.elided[linenum - 1]) or
+ Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$',
+ clean_lines.elided[linenum - 2]) or
+ Search(r'\bstd::m?function\s*\<\s*$',
+ clean_lines.elided[linenum - 1]))))
+
+
+_HEADERS_CONTAINING_TEMPLATES = (
+ ('<deque>', ('deque',)),
+ ('<functional>', ('unary_function', 'binary_function',
+ 'plus', 'minus', 'multiplies', 'divides', 'modulus',
+ 'negate',
+ 'equal_to', 'not_equal_to', 'greater', 'less',
+ 'greater_equal', 'less_equal',
+ 'logical_and', 'logical_or', 'logical_not',
+ 'unary_negate', 'not1', 'binary_negate', 'not2',
+ 'bind1st', 'bind2nd',
+ 'pointer_to_unary_function',
+ 'pointer_to_binary_function',
+ 'ptr_fun',
+ 'mem_fun_t', 'mem_fun', 'mem_fun1_t', 'mem_fun1_ref_t',
+ 'mem_fun_ref_t',
+ 'const_mem_fun_t', 'const_mem_fun1_t',
+ 'const_mem_fun_ref_t', 'const_mem_fun1_ref_t',
+ 'mem_fun_ref',
+ )),
+ ('<limits>', ('numeric_limits',)),
+ ('<list>', ('list',)),
+ ('<map>', ('map', 'multimap',)),
+ ('<memory>', ('allocator',)),
+ ('<queue>', ('queue', 'priority_queue',)),
+ ('<set>', ('set', 'multiset',)),
+ ('<stack>', ('stack',)),
+ ('<string>', ('char_traits', 'basic_string',)),
+ ('<tuple>', ('tuple',)),
+ ('<utility>', ('pair',)),
+ ('<vector>', ('vector',)),
+
+ # gcc extensions.
+ # Note: std::hash is their hash, ::hash is our hash
+ ('<hash_map>', ('hash_map', 'hash_multimap',)),
+ ('<hash_set>', ('hash_set', 'hash_multiset',)),
+ ('<slist>', ('slist',)),
+ )
+
+_RE_PATTERN_STRING = re.compile(r'\bstring\b')
+
+_re_pattern_algorithm_header = []
+for _template in ('copy', 'max', 'min', 'min_element', 'sort', 'swap',
+ 'transform'):
+ # Match max<type>(..., ...), max(..., ...), but not foo->max, foo.max or
+ # type::max().
+ _re_pattern_algorithm_header.append(
+ (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'),
+ _template,
+ '<algorithm>'))
+
+_re_pattern_templates = []
+for _header, _templates in _HEADERS_CONTAINING_TEMPLATES:
+ for _template in _templates:
+ _re_pattern_templates.append(
+ (re.compile(r'(\<|\b)' + _template + r'\s*\<'),
+ _template + '<>',
+ _header))
+
+
+def FilesBelongToSameModule(filename_cc, filename_h):
+ """Check if these two filenames belong to the same module.
+
+ The concept of a 'module' here is a as follows:
+ foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the
+ same 'module' if they are in the same directory.
+ some/path/public/xyzzy and some/path/internal/xyzzy are also considered
+ to belong to the same module here.
+
+ If the filename_cc contains a longer path than the filename_h, for example,
+ '/absolute/path/to/base/sysinfo.cc', and this file would include
+ 'base/sysinfo.h', this function also produces the prefix needed to open the
+ header. This is used by the caller of this function to more robustly open the
+ header file. We don't have access to the real include paths in this context,
+ so we need this guesswork here.
+
+ Known bugs: tools/base/bar.cc and base/bar.h belong to the same module
+ according to this implementation. Because of this, this function gives
+ some false positives. This should be sufficiently rare in practice.
+
+ Args:
+ filename_cc: is the path for the .cc file
+ filename_h: is the path for the header path
+
+ Returns:
+ Tuple with a bool and a string:
+ bool: True if filename_cc and filename_h belong to the same module.
+ string: the additional prefix needed to open the header file.
+ """
+
+ if not filename_cc.endswith('.cc'):
+ return (False, '')
+ filename_cc = filename_cc[:-len('.cc')]
+ if filename_cc.endswith('_unittest'):
+ filename_cc = filename_cc[:-len('_unittest')]
+ elif filename_cc.endswith('_test'):
+ filename_cc = filename_cc[:-len('_test')]
+ filename_cc = filename_cc.replace('/public/', '/')
+ filename_cc = filename_cc.replace('/internal/', '/')
+
+ if not filename_h.endswith('.h'):
+ return (False, '')
+ filename_h = filename_h[:-len('.h')]
+ if filename_h.endswith('-inl'):
+ filename_h = filename_h[:-len('-inl')]
+ filename_h = filename_h.replace('/public/', '/')
+ filename_h = filename_h.replace('/internal/', '/')
+
+ files_belong_to_same_module = filename_cc.endswith(filename_h)
+ common_path = ''
+ if files_belong_to_same_module:
+ common_path = filename_cc[:-len(filename_h)]
+ return files_belong_to_same_module, common_path
+
+
+def UpdateIncludeState(filename, include_dict, io=codecs):
+ """Fill up the include_dict with new includes found from the file.
+
+ Args:
+ filename: the name of the header to read.
+ include_dict: a dictionary in which the headers are inserted.
+ io: The io factory to use to read the file. Provided for testability.
+
+ Returns:
+ True if a header was successfully added. False otherwise.
+ """
+ headerfile = None
+ try:
+ headerfile = io.open(filename, 'r', 'utf8', 'replace')
+ except IOError:
+ return False
+ linenum = 0
+ for line in headerfile:
+ linenum += 1
+ clean_line = CleanseComments(line)
+ match = _RE_PATTERN_INCLUDE.search(clean_line)
+ if match:
+ include = match.group(2)
+ include_dict.setdefault(include, linenum)
+ return True
+
+
+def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error,
+ io=codecs):
+ """Reports for missing stl includes.
+
+ This function will output warnings to make sure you are including the headers
+ necessary for the stl containers and functions that you use. We only give one
+ reason to include a header. For example, if you use both equal_to<> and
+ less<> in a .h file, only one (the latter in the file) of these will be
+ reported as a reason to include the <functional>.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ include_state: An _IncludeState instance.
+ error: The function to call with any errors found.
+ io: The IO factory to use to read the header file. Provided for unittest
+ injection.
+ """
+ required = {} # A map of header name to linenumber and the template entity.
+ # Example of required: { '<functional>': (1219, 'less<>') }
+
+ for linenum in xrange(clean_lines.NumLines()):
+ line = clean_lines.elided[linenum]
+ if not line or line[0] == '#':
+ continue
+
+ # String is special -- it is a non-templatized type in STL.
+ matched = _RE_PATTERN_STRING.search(line)
+ if matched:
+ # Don't warn about strings in non-STL namespaces:
+ # (We check only the first match per line; good enough.)
+ prefix = line[:matched.start()]
+ if prefix.endswith('std::') or not prefix.endswith('::'):
+ required['<string>'] = (linenum, 'string')
+
+ for pattern, template, header in _re_pattern_algorithm_header:
+ if pattern.search(line):
+ required[header] = (linenum, template)
+
+ # The following function is just a speed up, no semantics are changed.
+ if not '<' in line: # Reduces the cpu time usage by skipping lines.
+ continue
+
+ for pattern, template, header in _re_pattern_templates:
+ if pattern.search(line):
+ required[header] = (linenum, template)
+
+ # The policy is that if you #include something in foo.h you don't need to
+ # include it again in foo.cc. Here, we will look at possible includes.
+ # Let's flatten the include_state include_list and copy it into a dictionary.
+ include_dict = dict([item for sublist in include_state.include_list
+ for item in sublist])
+
+ # Did we find the header for this file (if any) and successfully load it?
+ header_found = False
+
+ # Use the absolute path so that matching works properly.
+ abs_filename = FileInfo(filename).FullName()
+
+ # For Emacs's flymake.
+ # If cpplint is invoked from Emacs's flymake, a temporary file is generated
+ # by flymake and that file name might end with '_flymake.cc'. In that case,
+ # restore original file name here so that the corresponding header file can be
+ # found.
+ # e.g. If the file name is 'foo_flymake.cc', we should search for 'foo.h'
+ # instead of 'foo_flymake.h'
+ abs_filename = re.sub(r'_flymake\.cc$', '.cc', abs_filename)
+
+ # include_dict is modified during iteration, so we iterate over a copy of
+ # the keys.
+ header_keys = include_dict.keys()
+ for header in header_keys:
+ (same_module, common_path) = FilesBelongToSameModule(abs_filename, header)
+ fullpath = common_path + header
+ if same_module and UpdateIncludeState(fullpath, include_dict, io):
+ header_found = True
+
+ # If we can't find the header file for a .cc, assume it's because we don't
+ # know where to look. In that case we'll give up as we're not sure they
+ # didn't include it in the .h file.
+ # TODO(unknown): Do a better job of finding .h files so we are confident that
+ # not having the .h file means there isn't one.
+ if filename.endswith('.cc') and not header_found:
+ return
+
+ # All the lines have been processed, report the errors found.
+ for required_header_unstripped in required:
+ template = required[required_header_unstripped][1]
+ if required_header_unstripped.strip('<>"') not in include_dict:
+ error(filename, required[required_header_unstripped][0],
+ 'build/include_what_you_use', 4,
+ 'Add #include ' + required_header_unstripped + ' for ' + template)
+
+
+_RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r'\bmake_pair\s*<')
+
+
+def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error):
+ """Check that make_pair's template arguments are deduced.
+
+ G++ 4.6 in C++11 mode fails badly if make_pair's template arguments are
+ specified explicitly, and such use isn't intended in any case.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+ match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line)
+ if match:
+ error(filename, linenum, 'build/explicit_make_pair',
+ 4, # 4 = high confidence
+ 'For C++11-compatibility, omit template arguments from make_pair'
+ ' OR use pair directly OR if appropriate, construct a pair directly')
+
+
+def CheckDefaultLambdaCaptures(filename, clean_lines, linenum, error):
+ """Check that default lambda captures are not used.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # A lambda introducer specifies a default capture if it starts with "[="
+ # or if it starts with "[&" _not_ followed by an identifier.
+ match = Match(r'^(.*)\[\s*(?:=|&[^\w])', line)
+ if match:
+ # Found a potential error, check what comes after the lambda-introducer.
+ # If it's not open parenthesis (for lambda-declarator) or open brace
+ # (for compound-statement), it's not a lambda.
+ line, _, pos = CloseExpression(clean_lines, linenum, len(match.group(1)))
+ if pos >= 0 and Match(r'^\s*[{(]', line[pos:]):
+ error(filename, linenum, 'build/c++11',
+ 4, # 4 = high confidence
+ 'Default lambda captures are an unapproved C++ feature.')
+
+
+def CheckRedundantVirtual(filename, clean_lines, linenum, error):
+ """Check if line contains a redundant "virtual" function-specifier.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ # Look for "virtual" on current line.
+ line = clean_lines.elided[linenum]
+ virtual = Match(r'^(.*)(\bvirtual\b)(.*)$', line)
+ if not virtual: return
+
+ # Ignore "virtual" keywords that are near access-specifiers. These
+ # are only used in class base-specifier and do not apply to member
+ # functions.
+ if (Search(r'\b(public|protected|private)\s+$', virtual.group(1)) or
+ Match(r'^\s+(public|protected|private)\b', virtual.group(3))):
+ return
+
+ # Ignore the "virtual" keyword from virtual base classes. Usually
+ # there is a column on the same line in these cases (virtual base
+ # classes are rare in google3 because multiple inheritance is rare).
+ if Match(r'^.*[^:]:[^:].*$', line): return
+
+ # Look for the next opening parenthesis. This is the start of the
+ # parameter list (possibly on the next line shortly after virtual).
+ # TODO(unknown): doesn't work if there are virtual functions with
+ # decltype() or other things that use parentheses, but csearch suggests
+ # that this is rare.
+ end_col = -1
+ end_line = -1
+ start_col = len(virtual.group(2))
+ for start_line in xrange(linenum, min(linenum + 3, clean_lines.NumLines())):
+ line = clean_lines.elided[start_line][start_col:]
+ parameter_list = Match(r'^([^(]*)\(', line)
+ if parameter_list:
+ # Match parentheses to find the end of the parameter list
+ (_, end_line, end_col) = CloseExpression(
+ clean_lines, start_line, start_col + len(parameter_list.group(1)))
+ break
+ start_col = 0
+
+ if end_col < 0:
+ return # Couldn't find end of parameter list, give up
+
+ # Look for "override" or "final" after the parameter list
+ # (possibly on the next few lines).
+ for i in xrange(end_line, min(end_line + 3, clean_lines.NumLines())):
+ line = clean_lines.elided[i][end_col:]
+ match = Search(r'\b(override|final)\b', line)
+ if match:
+ error(filename, linenum, 'readability/inheritance', 4,
+ ('"virtual" is redundant since function is '
+ 'already declared as "%s"' % match.group(1)))
+
+ # Set end_col to check whole lines after we are done with the
+ # first line.
+ end_col = 0
+ if Search(r'[^\w]\s*$', line):
+ break
+
+
+def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error):
+ """Check if line contains a redundant "override" or "final" virt-specifier.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ # Look for closing parenthesis nearby. We need one to confirm where
+ # the declarator ends and where the virt-specifier starts to avoid
+ # false positives.
+ line = clean_lines.elided[linenum]
+ declarator_end = line.rfind(')')
+ if declarator_end >= 0:
+ fragment = line[declarator_end:]
+ else:
+ if linenum > 1 and clean_lines.elided[linenum - 1].rfind(')') >= 0:
+ fragment = line
+ else:
+ return
+
+ # Check that at most one of "override" or "final" is present, not both
+ if Search(r'\boverride\b', fragment) and Search(r'\bfinal\b', fragment):
+ error(filename, linenum, 'readability/inheritance', 4,
+ ('"override" is redundant since function is '
+ 'already declared as "final"'))
+
+
+
+
+# Returns true if we are at a new block, and it is directly
+# inside of a namespace.
+def IsBlockInNameSpace(nesting_state, is_forward_declaration):
+ """Checks that the new block is directly in a namespace.
+
+ Args:
+ nesting_state: The _NestingState object that contains info about our state.
+ is_forward_declaration: If the class is a forward declared class.
+ Returns:
+ Whether or not the new block is directly in a namespace.
+ """
+ if is_forward_declaration:
+ if len(nesting_state.stack) >= 1 and (
+ isinstance(nesting_state.stack[-1], _NamespaceInfo)):
+ return True
+ else:
+ return False
+
+ return (len(nesting_state.stack) > 1 and
+ nesting_state.stack[-1].check_namespace_indentation and
+ isinstance(nesting_state.stack[-2], _NamespaceInfo))
+
+
+def ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item,
+ raw_lines_no_comments, linenum):
+ """This method determines if we should apply our namespace indentation check.
+
+ Args:
+ nesting_state: The current nesting state.
+ is_namespace_indent_item: If we just put a new class on the stack, True.
+ If the top of the stack is not a class, or we did not recently
+ add the class, False.
+ raw_lines_no_comments: The lines without the comments.
+ linenum: The current line number we are processing.
+
+ Returns:
+ True if we should apply our namespace indentation check. Currently, it
+ only works for classes and namespaces inside of a namespace.
+ """
+
+ is_forward_declaration = IsForwardClassDeclaration(raw_lines_no_comments,
+ linenum)
+
+ if not (is_namespace_indent_item or is_forward_declaration):
+ return False
+
+ # If we are in a macro, we do not want to check the namespace indentation.
+ if IsMacroDefinition(raw_lines_no_comments, linenum):
+ return False
+
+ return IsBlockInNameSpace(nesting_state, is_forward_declaration)
+
+
+# Call this method if the line is directly inside of a namespace.
+# If the line above is blank (excluding comments) or the start of
+# an inner namespace, it cannot be indented.
+def CheckItemIndentationInNamespace(filename, raw_lines_no_comments, linenum,
+ error):
+ line = raw_lines_no_comments[linenum]
+ if Match(r'^\s+', line):
+ error(filename, linenum, 'runtime/indentation_namespace', 4,
+ 'Do not indent within a namespace')
+
+
+def ProcessLine(filename, file_extension, clean_lines, line,
+ include_state, function_state, nesting_state, error,
+ extra_check_functions=[]):
+ """Processes a single line in the file.
+
+ Args:
+ filename: Filename of the file that is being processed.
+ file_extension: The extension (dot not included) of the file.
+ clean_lines: An array of strings, each representing a line of the file,
+ with comments stripped.
+ line: Number of line being processed.
+ include_state: An _IncludeState instance in which the headers are inserted.
+ function_state: A _FunctionState instance which counts function lines, etc.
+ nesting_state: A NestingState instance which maintains information about
+ the current stack of nested blocks being parsed.
+ error: A callable to which errors are reported, which takes 4 arguments:
+ filename, line number, error level, and message
+ extra_check_functions: An array of additional check functions that will be
+ run on each source line. Each function takes 4
+ arguments: filename, clean_lines, line, error
+ """
+ raw_lines = clean_lines.raw_lines
+ ParseNolintSuppressions(filename, raw_lines[line], line, error)
+ nesting_state.Update(filename, clean_lines, line, error)
+ CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line,
+ error)
+ if nesting_state.InAsmBlock(): return
+ CheckForFunctionLengths(filename, clean_lines, line, function_state, error)
+ CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error)
+ CheckStyle(filename, clean_lines, line, file_extension, nesting_state, error)
+ CheckLanguage(filename, clean_lines, line, file_extension, include_state,
+ nesting_state, error)
+ CheckForNonConstReference(filename, clean_lines, line, nesting_state, error)
+ CheckForNonStandardConstructs(filename, clean_lines, line,
+ nesting_state, error)
+ CheckVlogArguments(filename, clean_lines, line, error)
+ CheckPosixThreading(filename, clean_lines, line, error)
+ CheckInvalidIncrement(filename, clean_lines, line, error)
+ CheckMakePairUsesDeduction(filename, clean_lines, line, error)
+ CheckDefaultLambdaCaptures(filename, clean_lines, line, error)
+ CheckRedundantVirtual(filename, clean_lines, line, error)
+ CheckRedundantOverrideOrFinal(filename, clean_lines, line, error)
+ for check_fn in extra_check_functions:
+ check_fn(filename, clean_lines, line, error)
+
+def FlagCxx11Features(filename, clean_lines, linenum, error):
+ """Flag those c++11 features that we only allow in certain places.
+
+ Args:
+ filename: The name of the current file.
+ clean_lines: A CleansedLines instance containing the file.
+ linenum: The number of the line to check.
+ error: The function to call with any errors found.
+ """
+ line = clean_lines.elided[linenum]
+
+ # Flag unapproved C++11 headers.
+ include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line)
+ if include and include.group(1) in ('cfenv',
+ 'condition_variable',
+ 'fenv.h',
+ 'future',
+ 'mutex',
+ 'thread',
+ 'chrono',
+ 'ratio',
+ 'regex',
+ 'system_error',
+ ):
+ error(filename, linenum, 'build/c++11', 5,
+ ('<%s> is an unapproved C++11 header.') % include.group(1))
+
+ # The only place where we need to worry about C++11 keywords and library
+ # features in preprocessor directives is in macro definitions.
+ if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return
+
+ # These are classes and free functions. The classes are always
+ # mentioned as std::*, but we only catch the free functions if
+ # they're not found by ADL. They're alphabetical by header.
+ for top_name in (
+ # type_traits
+ 'alignment_of',
+ 'aligned_union',
+ ):
+ if Search(r'\bstd::%s\b' % top_name, line):
+ error(filename, linenum, 'build/c++11', 5,
+ ('std::%s is an unapproved C++11 class or function. Send c-style '
+ 'an example of where it would make your code more readable, and '
+ 'they may let you use it.') % top_name)
+
+
+def ProcessFileData(filename, file_extension, lines, error,
+ extra_check_functions=[]):
+ """Performs lint checks and reports any errors to the given error function.
+
+ Args:
+ filename: Filename of the file that is being processed.
+ file_extension: The extension (dot not included) of the file.
+ lines: An array of strings, each representing a line of the file, with the
+ last element being empty if the file is terminated with a newline.
+ error: A callable to which errors are reported, which takes 4 arguments:
+ filename, line number, error level, and message
+ extra_check_functions: An array of additional check functions that will be
+ run on each source line. Each function takes 4
+ arguments: filename, clean_lines, line, error
+ """
+ lines = (['// marker so line numbers and indices both start at 1'] + lines +
+ ['// marker so line numbers end in a known way'])
+
+ include_state = _IncludeState()
+ function_state = _FunctionState()
+ nesting_state = NestingState()
+
+ ResetNolintSuppressions()
+
+ CheckForCopyright(filename, lines, error)
+
+ RemoveMultiLineComments(filename, lines, error)
+ clean_lines = CleansedLines(lines)
+
+ if file_extension == 'h':
+ CheckForHeaderGuard(filename, clean_lines, error)
+
+ for line in xrange(clean_lines.NumLines()):
+ ProcessLine(filename, file_extension, clean_lines, line,
+ include_state, function_state, nesting_state, error,
+ extra_check_functions)
+ FlagCxx11Features(filename, clean_lines, line, error)
+ nesting_state.CheckCompletedBlocks(filename, error)
+
+ CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error)
+
+ # Check that the .cc file has included its header if it exists.
+ if file_extension == 'cc':
+ CheckHeaderFileIncluded(filename, include_state, error)
+
+ # We check here rather than inside ProcessLine so that we see raw
+ # lines rather than "cleaned" lines.
+ CheckForBadCharacters(filename, lines, error)
+
+ CheckForNewlineAtEOF(filename, lines, error)
+
+def ProcessConfigOverrides(filename):
+ """ Loads the configuration files and processes the config overrides.
+
+ Args:
+ filename: The name of the file being processed by the linter.
+
+ Returns:
+ False if the current |filename| should not be processed further.
+ """
+
+ abs_filename = os.path.abspath(filename)
+ cfg_filters = []
+ keep_looking = True
+ while keep_looking:
+ abs_path, base_name = os.path.split(abs_filename)
+ if not base_name:
+ break # Reached the root directory.
+
+ cfg_file = os.path.join(abs_path, "CPPLINT.cfg")
+ abs_filename = abs_path
+ if not os.path.isfile(cfg_file):
+ continue
+
+ try:
+ with open(cfg_file) as file_handle:
+ for line in file_handle:
+ line, _, _ = line.partition('#') # Remove comments.
+ if not line.strip():
+ continue
+
+ name, _, val = line.partition('=')
+ name = name.strip()
+ val = val.strip()
+ if name == 'set noparent':
+ keep_looking = False
+ elif name == 'filter':
+ cfg_filters.append(val)
+ elif name == 'exclude_files':
+ # When matching exclude_files pattern, use the base_name of
+ # the current file name or the directory name we are processing.
+ # For example, if we are checking for lint errors in /foo/bar/baz.cc
+ # and we found the .cfg file at /foo/CPPLINT.cfg, then the config
+ # file's "exclude_files" filter is meant to be checked against "bar"
+ # and not "baz" nor "bar/baz.cc".
+ if base_name:
+ pattern = re.compile(val)
+ if pattern.match(base_name):
+ sys.stderr.write('Ignoring "%s": file excluded by "%s". '
+ 'File path component "%s" matches '
+ 'pattern "%s"\n' %
+ (filename, cfg_file, base_name, val))
+ return False
+ elif name == 'linelength':
+ global _line_length
+ try:
+ _line_length = int(val)
+ except ValueError:
+ sys.stderr.write('Line length must be numeric.')
+ else:
+ sys.stderr.write(
+ 'Invalid configuration option (%s) in file %s\n' %
+ (name, cfg_file))
+
+ except IOError:
+ sys.stderr.write(
+ "Skipping config file '%s': Can't open for reading\n" % cfg_file)
+ keep_looking = False
+
+ # Apply all the accumulated filters in reverse order (top-level directory
+ # config options having the least priority).
+ for filter in reversed(cfg_filters):
+ _AddFilters(filter)
+
+ return True
+
+
+def ProcessFile(filename, vlevel, extra_check_functions=[]):
+ """Does google-lint on a single file.
+
+ Args:
+ filename: The name of the file to parse.
+
+ vlevel: The level of errors to report. Every error of confidence
+ >= verbose_level will be reported. 0 is a good default.
+
+ extra_check_functions: An array of additional check functions that will be
+ run on each source line. Each function takes 4
+ arguments: filename, clean_lines, line, error
+ """
+
+ _SetVerboseLevel(vlevel)
+ _BackupFilters()
+
+ if not ProcessConfigOverrides(filename):
+ _RestoreFilters()
+ return
+
+ lf_lines = []
+ crlf_lines = []
+ try:
+ # Support the UNIX convention of using "-" for stdin. Note that
+ # we are not opening the file with universal newline support
+ # (which codecs doesn't support anyway), so the resulting lines do
+ # contain trailing '\r' characters if we are reading a file that
+ # has CRLF endings.
+ # If after the split a trailing '\r' is present, it is removed
+ # below.
+ if filename == '-':
+ lines = codecs.StreamReaderWriter(sys.stdin,
+ codecs.getreader('utf8'),
+ codecs.getwriter('utf8'),
+ 'replace').read().split('\n')
+ else:
+ lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n')
+
+ # Remove trailing '\r'.
+ # The -1 accounts for the extra trailing blank line we get from split()
+ for linenum in range(len(lines) - 1):
+ if lines[linenum].endswith('\r'):
+ lines[linenum] = lines[linenum].rstrip('\r')
+ crlf_lines.append(linenum + 1)
+ else:
+ lf_lines.append(linenum + 1)
+
+ except IOError:
+ sys.stderr.write(
+ "Skipping input '%s': Can't open for reading\n" % filename)
+ _RestoreFilters()
+ return
+
+ # Note, if no dot is found, this will give the entire filename as the ext.
+ file_extension = filename[filename.rfind('.') + 1:]
+
+ # When reading from stdin, the extension is unknown, so no cpplint tests
+ # should rely on the extension.
+ if filename != '-' and file_extension not in _valid_extensions:
+ sys.stderr.write('Ignoring %s; not a valid file name '
+ '(%s)\n' % (filename, ', '.join(_valid_extensions)))
+ else:
+ ProcessFileData(filename, file_extension, lines, Error,
+ extra_check_functions)
+
+ # If end-of-line sequences are a mix of LF and CR-LF, issue
+ # warnings on the lines with CR.
+ #
+ # Don't issue any warnings if all lines are uniformly LF or CR-LF,
+ # since critique can handle these just fine, and the style guide
+ # doesn't dictate a particular end of line sequence.
+ #
+ # We can't depend on os.linesep to determine what the desired
+ # end-of-line sequence should be, since that will return the
+ # server-side end-of-line sequence.
+ if lf_lines and crlf_lines:
+ # Warn on every line with CR. An alternative approach might be to
+ # check whether the file is mostly CRLF or just LF, and warn on the
+ # minority, we bias toward LF here since most tools prefer LF.
+ for linenum in crlf_lines:
+ Error(filename, linenum, 'whitespace/newline', 1,
+ 'Unexpected \\r (^M) found; better to use only \\n')
+
+ sys.stderr.write('Done processing %s\n' % filename)
+ _RestoreFilters()
+
+
+def PrintUsage(message):
+ """Prints a brief usage string and exits, optionally with an error message.
+
+ Args:
+ message: The optional error message.
+ """
+ sys.stderr.write(_USAGE)
+ if message:
+ sys.exit('\nFATAL ERROR: ' + message)
+ else:
+ sys.exit(1)
+
+
+def PrintCategories():
+ """Prints a list of all the error-categories used by error messages.
+
+ These are the categories used to filter messages via --filter.
+ """
+ sys.stderr.write(''.join(' %s\n' % cat for cat in _ERROR_CATEGORIES))
+ sys.exit(0)
+
+
+def ParseArguments(args):
+ """Parses the command line arguments.
+
+ This may set the output format and verbosity level as side-effects.
+
+ Args:
+ args: The command line arguments:
+
+ Returns:
+ The list of filenames to lint.
+ """
+ try:
+ (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=',
+ 'counting=',
+ 'filter=',
+ 'root=',
+ 'linelength=',
+ 'extensions='])
+ except getopt.GetoptError:
+ PrintUsage('Invalid arguments.')
+
+ verbosity = _VerboseLevel()
+ output_format = _OutputFormat()
+ filters = ''
+ counting_style = ''
+
+ for (opt, val) in opts:
+ if opt == '--help':
+ PrintUsage(None)
+ elif opt == '--output':
+ if val not in ('emacs', 'vs7', 'eclipse'):
+ PrintUsage('The only allowed output formats are emacs, vs7 and eclipse.')
+ output_format = val
+ elif opt == '--verbose':
+ verbosity = int(val)
+ elif opt == '--filter':
+ filters = val
+ if not filters:
+ PrintCategories()
+ elif opt == '--counting':
+ if val not in ('total', 'toplevel', 'detailed'):
+ PrintUsage('Valid counting options are total, toplevel, and detailed')
+ counting_style = val
+ elif opt == '--root':
+ global _root
+ _root = val
+ elif opt == '--linelength':
+ global _line_length
+ try:
+ _line_length = int(val)
+ except ValueError:
+ PrintUsage('Line length must be digits.')
+ elif opt == '--extensions':
+ global _valid_extensions
+ try:
+ _valid_extensions = set(val.split(','))
+ except ValueError:
+ PrintUsage('Extensions must be comma seperated list.')
+
+ if not filenames:
+ PrintUsage('No files were specified.')
+
+ _SetOutputFormat(output_format)
+ _SetVerboseLevel(verbosity)
+ _SetFilters(filters)
+ _SetCountingStyle(counting_style)
+
+ return filenames
+
+
+def main():
+ filenames = ParseArguments(sys.argv[1:])
+
+ # Change stderr to write with replacement characters so we don't die
+ # if we try to print something containing non-ASCII characters.
+ sys.stderr = codecs.StreamReaderWriter(sys.stderr,
+ codecs.getreader('utf8'),
+ codecs.getwriter('utf8'),
+ 'replace')
+
+ _cpplint_state.ResetErrorCounts()
+ for filename in filenames:
+ ProcessFile(filename, _cpplint_state.verbose_level)
+ _cpplint_state.PrintErrorCounts()
+
+ sys.exit(_cpplint_state.error_count > 0)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/examples/callback_api/Makefile b/examples/callback_api/Makefile
new file mode 100644
index 0000000..45d60d8
--- /dev/null
+++ b/examples/callback_api/Makefile
@@ -0,0 +1,2 @@
+all:
+ clang++ -I../../ -Wall -g -o example main.cc
diff --git a/examples/callback_api/main.cc b/examples/callback_api/main.cc
new file mode 100644
index 0000000..a7a7422
--- /dev/null
+++ b/examples/callback_api/main.cc
@@ -0,0 +1,166 @@
+//
+// An example of how to use callback API.
+// This example is minimum and incomplete. Just showing the usage of callback
+// API.
+// You need to implement your own Mesh data struct constrution based on this
+// example in practical.
+//
+#define TINYOBJLOADER_IMPLEMENTATION
+#include "tiny_obj_loader.h"
+
+#include <cassert>
+#include <cstdio>
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+typedef struct {
+ std::vector<float> vertices;
+ std::vector<float> normals;
+ std::vector<float> texcoords;
+ std::vector<int> v_indices;
+ std::vector<int> vn_indices;
+ std::vector<int> vt_indices;
+
+ std::vector<tinyobj::material_t> materials;
+
+} MyMesh;
+
+void vertex_cb(void *user_data, float x, float y, float z, float w) {
+ MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
+ printf("v[%ld] = %f, %f, %f (w %f)\n", mesh->vertices.size() / 3, x, y, z, w);
+
+ mesh->vertices.push_back(x);
+ mesh->vertices.push_back(y);
+ mesh->vertices.push_back(z);
+ // Discard w
+}
+
+void normal_cb(void *user_data, float x, float y, float z) {
+ MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
+ printf("vn[%ld] = %f, %f, %f\n", mesh->normals.size() / 3, x, y, z);
+
+ mesh->normals.push_back(x);
+ mesh->normals.push_back(y);
+ mesh->normals.push_back(z);
+}
+
+void texcoord_cb(void *user_data, float x, float y, float z) {
+ MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
+ printf("vt[%ld] = %f, %f, %f\n", mesh->texcoords.size() / 3, x, y, z);
+
+ mesh->texcoords.push_back(x);
+ mesh->texcoords.push_back(y);
+ mesh->texcoords.push_back(z);
+}
+
+void index_cb(void *user_data, tinyobj::index_t *indices, int num_indices) {
+ // NOTE: the value of each index is raw value.
+ // For example, the application must manually adjust the index with offset
+ // (e.g. v_indices.size()) when the value is negative(whic means relative
+ // index).
+ // Also, the first index starts with 1, not 0.
+ // See fixIndex() function in tiny_obj_loader.h for details.
+ // Also, 0 is set for the index value which
+ // does not exist in .obj
+ MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
+
+ for (int i = 0; i < num_indices; i++) {
+ tinyobj::index_t idx = indices[i];
+ printf("idx[%ld] = %d, %d, %d\n", mesh->v_indices.size(), idx.vertex_index,
+ idx.normal_index, idx.texcoord_index);
+
+ if (idx.vertex_index != 0) {
+ mesh->v_indices.push_back(idx.vertex_index);
+ }
+ if (idx.normal_index != 0) {
+ mesh->vn_indices.push_back(idx.normal_index);
+ }
+ if (idx.texcoord_index != 0) {
+ mesh->vt_indices.push_back(idx.texcoord_index);
+ }
+ }
+}
+
+void usemtl_cb(void *user_data, const char *name, int material_idx) {
+ MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
+ if ((material_idx > -1) && (material_idx < mesh->materials.size())) {
+ printf("usemtl. material id = %d(name = %s)\n", material_idx,
+ mesh->materials[material_idx].name.c_str());
+ } else {
+ printf("usemtl. name = %s\n", name);
+ }
+}
+
+void mtllib_cb(void *user_data, const tinyobj::material_t *materials,
+ int num_materials) {
+ MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
+ printf("mtllib. # of materials = %d\n", num_materials);
+
+ for (int i = 0; i < num_materials; i++) {
+ mesh->materials.push_back(materials[i]);
+ }
+}
+
+void group_cb(void *user_data, const char **names, int num_names) {
+ // MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
+ printf("group : name = \n");
+
+ for (int i = 0; i < num_names; i++) {
+ printf(" %s\n", names[i]);
+ }
+}
+
+void object_cb(void *user_data, const char *name) {
+ // MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
+ printf("object : name = %s\n", name);
+}
+
+int main(int argc, char **argv) {
+ tinyobj::callback_t cb;
+ cb.vertex_cb = vertex_cb;
+ cb.normal_cb = normal_cb;
+ cb.texcoord_cb = texcoord_cb;
+ cb.index_cb = index_cb;
+ cb.usemtl_cb = usemtl_cb;
+ cb.mtllib_cb = mtllib_cb;
+ cb.group_cb = group_cb;
+ cb.object_cb = object_cb;
+
+ MyMesh mesh;
+ std::string err;
+ std::string filename = "../../models/cornell_box.obj";
+ if (argc > 1) {
+ filename = std::string(argv[1]);
+ }
+ std::ifstream ifs(filename.c_str());
+
+ if (ifs.fail()) {
+ std::cerr << "file not found." << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ tinyobj::MaterialFileReader mtlReader("../../models/");
+
+ bool ret = tinyobj::LoadObjWithCallback(ifs, cb, &mesh, &mtlReader, &err);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ if (!ret) {
+ std::cerr << "Failed to parse .obj" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ printf("# of vertices = %ld\n", mesh.vertices.size() / 3);
+ printf("# of normals = %ld\n", mesh.normals.size() / 3);
+ printf("# of texcoords = %ld\n", mesh.texcoords.size() / 2);
+ printf("# of vertex indices = %ld\n", mesh.v_indices.size());
+ printf("# of normal indices = %ld\n", mesh.vn_indices.size());
+ printf("# of texcoord indices = %ld\n", mesh.vt_indices.size());
+ printf("# of materials = %ld\n", mesh.materials.size());
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/obj_sticher/obj_sticher.cc b/examples/obj_sticher/obj_sticher.cc
new file mode 100644
index 0000000..f59fee4
--- /dev/null
+++ b/examples/obj_sticher/obj_sticher.cc
@@ -0,0 +1,110 @@
+//
+// Stiches multiple .obj files into one .obj.
+//
+#include "../../tiny_obj_loader.h"
+#include "obj_writer.h"
+
+#include <cassert>
+#include <iostream>
+#include <cstdlib>
+#include <cstdio>
+
+typedef std::vector<tinyobj::shape_t> Shape;
+typedef std::vector<tinyobj::material_t> Material;
+
+void
+StichObjs(
+ std::vector<tinyobj::shape_t>& out_shape,
+ std::vector<tinyobj::material_t>& out_material,
+ const std::vector<Shape>& shapes,
+ const std::vector<Material>& materials)
+{
+ int numShapes = 0;
+ for (size_t i = 0; i < shapes.size(); i++) {
+ numShapes += (int)shapes[i].size();
+ }
+
+ printf("Total # of shapes = %d\n", numShapes);
+ int materialIdOffset = 0;
+
+ size_t face_offset = 0;
+ for (size_t i = 0; i < shapes.size(); i++) {
+
+ for (size_t k = 0; k < shapes[i].size(); k++) {
+
+ std::string new_name = shapes[i][k].name;
+ // Add suffix
+ char buf[1024];
+ sprintf(buf, "_%04d", (int)i);
+ new_name += std::string(buf);
+
+ printf("shape[%ld][%ld].name = %s\n", i, k, shapes[i][k].name.c_str());
+ assert((shapes[i][k].mesh.indices.size() % 3) == 0);
+ assert((shapes[i][k].mesh.positions.size() % 3) == 0);
+
+ tinyobj::shape_t new_shape = shapes[i][k];
+ // Add offset.
+ for (size_t f = 0; f < new_shape.mesh.material_ids.size(); f++) {
+ new_shape.mesh.material_ids[f] += materialIdOffset;
+ }
+
+ new_shape.name = new_name;
+ printf("shape[%ld][%ld].new_name = %s\n", i, k, new_shape.name.c_str());
+
+ out_shape.push_back(new_shape);
+ }
+
+ materialIdOffset += materials[i].size();
+ }
+
+ for (size_t i = 0; i < materials.size(); i++) {
+ for (size_t k = 0; k < materials[i].size(); k++) {
+ out_material.push_back(materials[i][k]);
+ }
+ }
+
+}
+
+int
+main(
+ int argc,
+ char **argv)
+{
+ if (argc < 3) {
+ printf("Usage: obj_sticher input0.obj input1.obj ... output.obj\n");
+ exit(1);
+ }
+
+ int num_objfiles = argc - 2;
+ std::string out_filename = std::string(argv[argc-1]); // last element
+
+ std::vector<Shape> shapes;
+ std::vector<Material> materials;
+ shapes.resize(num_objfiles);
+ materials.resize(num_objfiles);
+
+ for (int i = 0; i < num_objfiles; i++) {
+ std::cout << "Loading " << argv[i+1] << " ... " << std::flush;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(shapes[i], materials[i], err, argv[i+1]);
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+ if (!ret) {
+ exit(1);
+ }
+
+ std::cout << "DONE." << std::endl;
+ }
+
+ std::vector<tinyobj::shape_t> out_shape;
+ std::vector<tinyobj::material_t> out_material;
+ StichObjs(out_shape, out_material, shapes, materials);
+
+ bool coordTransform = true;
+ bool ret = WriteObj(out_filename, out_shape, out_material, coordTransform);
+ assert(ret);
+
+ return 0;
+}
diff --git a/examples/obj_sticher/obj_writer.cc b/examples/obj_sticher/obj_writer.cc
new file mode 100644
index 0000000..2c8bd7b
--- /dev/null
+++ b/examples/obj_sticher/obj_writer.cc
@@ -0,0 +1,176 @@
+//
+// Simple wavefront .obj writer
+//
+#include "obj_writer.h"
+#include <cstdio>
+
+static std::string GetFileBasename(const std::string& FileName)
+{
+ if(FileName.find_last_of(".") != std::string::npos)
+ return FileName.substr(0, FileName.find_last_of("."));
+ return "";
+}
+
+bool WriteMat(const std::string& filename, const std::vector<tinyobj::material_t>& materials) {
+ FILE* fp = fopen(filename.c_str(), "w");
+ if (!fp) {
+ fprintf(stderr, "Failed to open file [ %s ] for write.\n", filename.c_str());
+ return false;
+ }
+
+ for (size_t i = 0; i < materials.size(); i++) {
+
+ tinyobj::material_t mat = materials[i];
+
+ fprintf(fp, "newmtl %s\n", mat.name.c_str());
+ fprintf(fp, "Ka %f %f %f\n", mat.ambient[0], mat.ambient[1], mat.ambient[2]);
+ fprintf(fp, "Kd %f %f %f\n", mat.diffuse[0], mat.diffuse[1], mat.diffuse[2]);
+ fprintf(fp, "Ks %f %f %f\n", mat.specular[0], mat.specular[1], mat.specular[2]);
+ fprintf(fp, "Kt %f %f %f\n", mat.transmittance[0], mat.specular[1], mat.specular[2]);
+ fprintf(fp, "Ke %f %f %f\n", mat.emission[0], mat.emission[1], mat.emission[2]);
+ fprintf(fp, "Ns %f\n", mat.shininess);
+ fprintf(fp, "Ni %f\n", mat.ior);
+ // @todo { texture }
+ }
+
+ fclose(fp);
+
+ return true;
+}
+
+bool WriteObj(const std::string& filename, const std::vector<tinyobj::shape_t>& shapes, const std::vector<tinyobj::material_t>& materials, bool coordTransform) {
+ FILE* fp = fopen(filename.c_str(), "w");
+ if (!fp) {
+ fprintf(stderr, "Failed to open file [ %s ] for write.\n", filename.c_str());
+ return false;
+ }
+
+ std::string basename = GetFileBasename(filename);
+ std::string material_filename = basename + ".mtl";
+
+ int v_offset = 0;
+ int vn_offset = 0;
+ int vt_offset = 0;
+ int prev_material_id = -1;
+
+ fprintf(fp, "mtllib %s\n", material_filename.c_str());
+
+ for (size_t i = 0; i < shapes.size(); i++) {
+
+ bool has_vn = false;
+ bool has_vt = false;
+
+ if (shapes[i].name.empty()) {
+ fprintf(fp, "g Unknown\n");
+ } else {
+ fprintf(fp, "g %s\n", shapes[i].name.c_str());
+ }
+
+ //if (!shapes[i].material.name.empty()) {
+ // fprintf(fp, "usemtl %s\n", shapes[i].material.name.c_str());
+ //}
+
+ // facevarying vtx
+ for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) {
+ for (int j = 0; j < 3; j++) {
+ int idx = shapes[i].mesh.indices[3*k+j];
+ if (coordTransform) {
+ fprintf(fp, "v %f %f %f\n",
+ shapes[i].mesh.positions[3*idx+0],
+ shapes[i].mesh.positions[3*idx+2],
+ -shapes[i].mesh.positions[3*idx+1]);
+ } else {
+ fprintf(fp, "v %f %f %f\n",
+ shapes[i].mesh.positions[3*idx+0],
+ shapes[i].mesh.positions[3*idx+1],
+ shapes[i].mesh.positions[3*idx+2]);
+ }
+ }
+ }
+
+ // facevarying normal
+ if (shapes[i].mesh.normals.size() > 0) {
+ for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) {
+ for (int j = 0; j < 3; j++) {
+ int idx = shapes[i].mesh.indices[3*k+j];
+ if (coordTransform) {
+ fprintf(fp, "vn %f %f %f\n",
+ shapes[i].mesh.normals[3*idx+0],
+ shapes[i].mesh.normals[3*idx+2],
+ -shapes[i].mesh.normals[3*idx+1]);
+ } else {
+ fprintf(fp, "vn %f %f %f\n",
+ shapes[i].mesh.normals[3*idx+0],
+ shapes[i].mesh.normals[3*idx+1],
+ shapes[i].mesh.normals[3*idx+2]);
+ }
+ }
+ }
+ }
+ if (shapes[i].mesh.normals.size() > 0) has_vn = true;
+
+ // facevarying texcoord
+ if (shapes[i].mesh.texcoords.size() > 0) {
+ for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) {
+ for (int j = 0; j < 3; j++) {
+ int idx = shapes[i].mesh.indices[3*k+j];
+ fprintf(fp, "vt %f %f\n",
+ shapes[i].mesh.texcoords[2*idx+0],
+ shapes[i].mesh.texcoords[2*idx+1]);
+ }
+ }
+ }
+ if (shapes[i].mesh.texcoords.size() > 0) has_vt = true;
+
+ // face
+ for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) {
+
+ // Face index is 1-base.
+ //int v0 = shapes[i].mesh.indices[3*k+0] + 1 + v_offset;
+ //int v1 = shapes[i].mesh.indices[3*k+1] + 1 + v_offset;
+ //int v2 = shapes[i].mesh.indices[3*k+2] + 1 + v_offset;
+ int v0 = (3*k + 0) + 1 + v_offset;
+ int v1 = (3*k + 1) + 1 + v_offset;
+ int v2 = (3*k + 2) + 1 + v_offset;
+
+ int vt0 = (3*k + 0) + 1 + vt_offset;
+ int vt1 = (3*k + 1) + 1 + vt_offset;
+ int vt2 = (3*k + 2) + 1 + vt_offset;
+
+ int material_id = shapes[i].mesh.material_ids[k];
+ if (material_id != prev_material_id) {
+ std::string material_name = materials[material_id].name;
+ fprintf(fp, "usemtl %s\n", material_name.c_str());
+ prev_material_id = material_id;
+ }
+
+ if (has_vn && has_vt) {
+ fprintf(fp, "f %d/%d/%d %d/%d/%d %d/%d/%d\n",
+ v0, vt0, v0, v1, vt1, v1, v2, vt2, v2);
+ } else if (has_vn && !has_vt) {
+ fprintf(fp, "f %d//%d %d//%d %d//%d\n", v0, v0, v1, v1, v2, v2);
+ } else if (!has_vn && has_vt) {
+ fprintf(fp, "f %d/%d %d/%d %d/%d\n", v0, v0, v1, v1, v2, v2);
+ } else {
+ fprintf(fp, "f %d %d %d\n", v0, v1, v2);
+ }
+
+ }
+
+ v_offset += shapes[i].mesh.indices.size();
+ //vn_offset += shapes[i].mesh.normals.size() / 3;
+ vt_offset += shapes[i].mesh.texcoords.size() / 2;
+
+ }
+
+ fclose(fp);
+
+ //
+ // Write material file
+ //
+ bool ret = WriteMat(material_filename, materials);
+
+ return ret;
+}
+
+
diff --git a/examples/obj_sticher/obj_writer.h b/examples/obj_sticher/obj_writer.h
new file mode 100644
index 0000000..bb367b6
--- /dev/null
+++ b/examples/obj_sticher/obj_writer.h
@@ -0,0 +1,9 @@
+#ifndef __OBJ_WRITER_H__
+#define __OBJ_WRITER_H__
+
+#include "../../tiny_obj_loader.h"
+
+extern bool WriteObj(const std::string& filename, const std::vector<tinyobj::shape_t>& shapes, const std::vector<tinyobj::material_t>& materials, bool coordTransform = false);
+
+
+#endif // __OBJ_WRITER_H__
diff --git a/examples/obj_sticher/premake4.lua b/examples/obj_sticher/premake4.lua
new file mode 100644
index 0000000..9c2deb6
--- /dev/null
+++ b/examples/obj_sticher/premake4.lua
@@ -0,0 +1,38 @@
+lib_sources = {
+ "../../tiny_obj_loader.cc"
+}
+
+sources = {
+ "obj_sticher.cc",
+ "obj_writer.cc",
+ }
+
+-- premake4.lua
+solution "ObjStickerSolution"
+ configurations { "Release", "Debug" }
+
+ if (os.is("windows")) then
+ platforms { "x32", "x64" }
+ else
+ platforms { "native", "x32", "x64" }
+ end
+
+ includedirs {
+ "../../"
+ }
+
+ -- A project defines one build target
+ project "obj_sticher"
+ kind "ConsoleApp"
+ language "C++"
+ files { lib_sources, sources }
+
+ configuration "Debug"
+ defines { "DEBUG" } -- -DDEBUG
+ flags { "Symbols" }
+ targetname "obj_sticher_debug"
+
+ configuration "Release"
+ -- defines { "NDEBUG" } -- -NDEBUG
+ flags { "Symbols", "Optimize" }
+ targetname "obj_sticher"
diff --git a/examples/viewer/README.md b/examples/viewer/README.md
new file mode 100644
index 0000000..9cb032c
--- /dev/null
+++ b/examples/viewer/README.md
@@ -0,0 +1,42 @@
+# Simple .obj viewer with glew + glfw3 + OpenGL
+
+## Requirements
+
+* premake5
+* glfw3
+* glew
+
+## Build on MaCOSX
+
+Install glfw3 and glew using brew.
+Then,
+
+ $ premake5 gmake
+ $ make
+
+## Build on Linux
+
+Set `PKG_CONFIG_PATH` or Edit path to glfw3 and glew in `premake4.lua`
+
+Then,
+
+ $ premake5 gmake
+ $ make
+
+## Build on Windows.
+
+* Visual Studio 2013
+* Windows 64bit
+ * 32bit may work.
+
+Put glfw3 and glew library somewhere and replace include and lib path in `premake4.lua`
+
+Then,
+
+ > premake5.exe vs2013
+
+## TODO
+
+* [ ] Support per-face material.
+* [ ] Use shader-based GL rendering.
+* [ ] PBR shader support.
diff --git a/examples/viewer/premake4.lua b/examples/viewer/premake4.lua
new file mode 100644
index 0000000..4e1e54f
--- /dev/null
+++ b/examples/viewer/premake4.lua
@@ -0,0 +1,44 @@
+solution "objview"
+ -- location ( "build" )
+ configurations { "Release", "Debug" }
+ platforms {"native", "x64", "x32"}
+
+ project "objview"
+
+ kind "ConsoleApp"
+ language "C++"
+ files { "viewer.cc", "trackball.cc" }
+ includedirs { "./" }
+ includedirs { "../../" }
+
+ configuration { "linux" }
+ linkoptions { "`pkg-config --libs glfw3`" }
+ links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" }
+ linkoptions { "-pthread" }
+
+ configuration { "windows" }
+ -- Path to GLFW3
+ includedirs { '../../../../local/glfw-3.1.2.bin.WIN64/include' }
+ libdirs { '../../../../local/glfw-3.1.2.bin.WIN64/lib-vc2013' }
+ -- Path to GLEW
+ includedirs { '../../../../local/glew-1.13.0/include' }
+ libdirs { '../../../../local/glew-1.13.0/lib/Release/x64' }
+
+ links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" }
+ defines { "_CRT_SECURE_NO_WARNINGS" }
+
+ configuration { "macosx" }
+ includedirs { "/usr/local/include" }
+ buildoptions { "-Wno-deprecated-declarations" }
+ libdirs { "/usr/local/lib" }
+ links { "glfw3", "GLEW" }
+ linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" }
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols", "ExtraWarnings"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize", "ExtraWarnings"}
+
diff --git a/examples/viewer/stb_image.h b/examples/viewer/stb_image.h
new file mode 100644
index 0000000..a3c1129
--- /dev/null
+++ b/examples/viewer/stb_image.h
@@ -0,0 +1,6755 @@
+/* stb_image - v2.12 - public domain image loader - http://nothings.org/stb_image.h
+ no warranty implied; use at your own risk
+
+ Do this:
+ #define STB_IMAGE_IMPLEMENTATION
+ before you include this file in *one* C or C++ file to create the implementation.
+
+ // i.e. it should look like this:
+ #include ...
+ #include ...
+ #include ...
+ #define STB_IMAGE_IMPLEMENTATION
+ #include "stb_image.h"
+
+ You can #define STBI_ASSERT(x) before the #include to avoid using assert.h.
+ And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free
+
+
+ QUICK NOTES:
+ Primarily of interest to game developers and other people who can
+ avoid problematic images and only need the trivial interface
+
+ JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib)
+ PNG 1/2/4/8-bit-per-channel (16 bpc not supported)
+
+ TGA (not sure what subset, if a subset)
+ BMP non-1bpp, non-RLE
+ PSD (composited view only, no extra channels, 8/16 bit-per-channel)
+
+ GIF (*comp always reports as 4-channel)
+ HDR (radiance rgbE format)
+ PIC (Softimage PIC)
+ PNM (PPM and PGM binary only)
+
+ Animated GIF still needs a proper API, but here's one way to do it:
+ http://gist.github.com/urraka/685d9a6340b26b830d49
+
+ - decode from memory or through FILE (define STBI_NO_STDIO to remove code)
+ - decode from arbitrary I/O callbacks
+ - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)
+
+ Full documentation under "DOCUMENTATION" below.
+
+
+ Revision 2.00 release notes:
+
+ - Progressive JPEG is now supported.
+
+ - PPM and PGM binary formats are now supported, thanks to Ken Miller.
+
+ - x86 platforms now make use of SSE2 SIMD instructions for
+ JPEG decoding, and ARM platforms can use NEON SIMD if requested.
+ This work was done by Fabian "ryg" Giesen. SSE2 is used by
+ default, but NEON must be enabled explicitly; see docs.
+
+ With other JPEG optimizations included in this version, we see
+ 2x speedup on a JPEG on an x86 machine, and a 1.5x speedup
+ on a JPEG on an ARM machine, relative to previous versions of this
+ library. The same results will not obtain for all JPGs and for all
+ x86/ARM machines. (Note that progressive JPEGs are significantly
+ slower to decode than regular JPEGs.) This doesn't mean that this
+ is the fastest JPEG decoder in the land; rather, it brings it
+ closer to parity with standard libraries. If you want the fastest
+ decode, look elsewhere. (See "Philosophy" section of docs below.)
+
+ See final bullet items below for more info on SIMD.
+
+ - Added STBI_MALLOC, STBI_REALLOC, and STBI_FREE macros for replacing
+ the memory allocator. Unlike other STBI libraries, these macros don't
+ support a context parameter, so if you need to pass a context in to
+ the allocator, you'll have to store it in a global or a thread-local
+ variable.
+
+ - Split existing STBI_NO_HDR flag into two flags, STBI_NO_HDR and
+ STBI_NO_LINEAR.
+ STBI_NO_HDR: suppress implementation of .hdr reader format
+ STBI_NO_LINEAR: suppress high-dynamic-range light-linear float API
+
+ - You can suppress implementation of any of the decoders to reduce
+ your code footprint by #defining one or more of the following
+ symbols before creating the implementation.
+
+ STBI_NO_JPEG
+ STBI_NO_PNG
+ STBI_NO_BMP
+ STBI_NO_PSD
+ STBI_NO_TGA
+ STBI_NO_GIF
+ STBI_NO_HDR
+ STBI_NO_PIC
+ STBI_NO_PNM (.ppm and .pgm)
+
+ - You can request *only* certain decoders and suppress all other ones
+ (this will be more forward-compatible, as addition of new decoders
+ doesn't require you to disable them explicitly):
+
+ STBI_ONLY_JPEG
+ STBI_ONLY_PNG
+ STBI_ONLY_BMP
+ STBI_ONLY_PSD
+ STBI_ONLY_TGA
+ STBI_ONLY_GIF
+ STBI_ONLY_HDR
+ STBI_ONLY_PIC
+ STBI_ONLY_PNM (.ppm and .pgm)
+
+ Note that you can define multiples of these, and you will get all
+ of them ("only x" and "only y" is interpreted to mean "only x&y").
+
+ - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still
+ want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB
+
+ - Compilation of all SIMD code can be suppressed with
+ #define STBI_NO_SIMD
+ It should not be necessary to disable SIMD unless you have issues
+ compiling (e.g. using an x86 compiler which doesn't support SSE
+ intrinsics or that doesn't support the method used to detect
+ SSE2 support at run-time), and even those can be reported as
+ bugs so I can refine the built-in compile-time checking to be
+ smarter.
+
+ - The old STBI_SIMD system which allowed installing a user-defined
+ IDCT etc. has been removed. If you need this, don't upgrade. My
+ assumption is that almost nobody was doing this, and those who
+ were will find the built-in SIMD more satisfactory anyway.
+
+ - RGB values computed for JPEG images are slightly different from
+ previous versions of stb_image. (This is due to using less
+ integer precision in SIMD.) The C code has been adjusted so
+ that the same RGB values will be computed regardless of whether
+ SIMD support is available, so your app should always produce
+ consistent results. But these results are slightly different from
+ previous versions. (Specifically, about 3% of available YCbCr values
+ will compute different RGB results from pre-1.49 versions by +-1;
+ most of the deviating values are one smaller in the G channel.)
+
+ - If you must produce consistent results with previous versions of
+ stb_image, #define STBI_JPEG_OLD and you will get the same results
+ you used to; however, you will not get the SIMD speedups for
+ the YCbCr-to-RGB conversion step (although you should still see
+ significant JPEG speedup from the other changes).
+
+ Please note that STBI_JPEG_OLD is a temporary feature; it will be
+ removed in future versions of the library. It is only intended for
+ near-term back-compatibility use.
+
+
+ Latest revision history:
+ 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
+ 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64
+ RGB-format JPEG; remove white matting in PSD;
+ allocate large structures on the stack;
+ correct channel count for PNG & BMP
+ 2.10 (2016-01-22) avoid warning introduced in 2.09
+ 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED
+ 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA
+ 2.07 (2015-09-13) partial animated GIF support
+ limited 16-bit PSD support
+ minor bugs, code cleanup, and compiler warnings
+ 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value
+ 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning
+ 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit
+ 2.03 (2015-04-12) additional corruption checking
+ stbi_set_flip_vertically_on_load
+ fix NEON support; fix mingw support
+ 2.02 (2015-01-19) fix incorrect assert, fix warning
+ 2.01 (2015-01-17) fix various warnings
+ 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG
+ 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD
+ progressive JPEG
+ PGM/PPM support
+ STBI_MALLOC,STBI_REALLOC,STBI_FREE
+ STBI_NO_*, STBI_ONLY_*
+ GIF bugfix
+
+ See end of file for full revision history.
+
+
+ ============================ Contributors =========================
+
+ Image formats Extensions, features
+ Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info)
+ Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info)
+ Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG)
+ Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks)
+ Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG)
+ Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip)
+ Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD)
+ urraka@github (animated gif) Junggon Kim (PNM comments)
+ Daniel Gibson (16-bit TGA)
+
+ Optimizations & bugfixes
+ Fabian "ryg" Giesen
+ Arseny Kapoulkine
+
+ Bug & warning fixes
+ Marc LeBlanc David Woo Guillaume George Martins Mozeiko
+ Christpher Lloyd Martin Golini Jerry Jansson Joseph Thomson
+ Dave Moore Roy Eltham Hayaki Saito Phil Jordan
+ Won Chun Luke Graham Johan Duparc Nathan Reed
+ the Horde3D community Thomas Ruf Ronny Chevalier Nick Verigakis
+ Janez Zemva John Bartholomew Michal Cichon svdijk@github
+ Jonathan Blow Ken Hamada Tero Hanninen Baldur Karlsson
+ Laurent Gomila Cort Stratton Sergio Gonzalez romigrou@github
+ Aruelien Pocheville Thibault Reuille Cass Everitt Matthew Gregan
+ Ryamond Barbiero Paul Du Bois Engin Manap snagar@github
+ Michaelangel007@github Oriol Ferrer Mesia socks-the-fox
+ Blazej Dariusz Roszkowski
+
+
+LICENSE
+
+This software is dual-licensed to the public domain and under the following
+license: you are granted a perpetual, irrevocable license to copy, modify,
+publish, and distribute this file as you see fit.
+
+*/
+
+#ifndef STBI_INCLUDE_STB_IMAGE_H
+#define STBI_INCLUDE_STB_IMAGE_H
+
+// DOCUMENTATION
+//
+// Limitations:
+// - no 16-bit-per-channel PNG
+// - no 12-bit-per-channel JPEG
+// - no JPEGs with arithmetic coding
+// - no 1-bit BMP
+// - GIF always returns *comp=4
+//
+// Basic usage (see HDR discussion below for HDR usage):
+// int x,y,n;
+// unsigned char *data = stbi_load(filename, &x, &y, &n, 0);
+// // ... process data if not NULL ...
+// // ... x = width, y = height, n = # 8-bit components per pixel ...
+// // ... replace '0' with '1'..'4' to force that many components per pixel
+// // ... but 'n' will always be the number that it would have been if you said 0
+// stbi_image_free(data)
+//
+// Standard parameters:
+// int *x -- outputs image width in pixels
+// int *y -- outputs image height in pixels
+// int *comp -- outputs # of image components in image file
+// int req_comp -- if non-zero, # of image components requested in result
+//
+// The return value from an image loader is an 'unsigned char *' which points
+// to the pixel data, or NULL on an allocation failure or if the image is
+// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels,
+// with each pixel consisting of N interleaved 8-bit components; the first
+// pixel pointed to is top-left-most in the image. There is no padding between
+// image scanlines or between pixels, regardless of format. The number of
+// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise.
+// If req_comp is non-zero, *comp has the number of components that _would_
+// have been output otherwise. E.g. if you set req_comp to 4, you will always
+// get RGBA output, but you can check *comp to see if it's trivially opaque
+// because e.g. there were only 3 channels in the source image.
+//
+// An output image with N components has the following components interleaved
+// in this order in each pixel:
+//
+// N=#comp components
+// 1 grey
+// 2 grey, alpha
+// 3 red, green, blue
+// 4 red, green, blue, alpha
+//
+// If image loading fails for any reason, the return value will be NULL,
+// and *x, *y, *comp will be unchanged. The function stbi_failure_reason()
+// can be queried for an extremely brief, end-user unfriendly explanation
+// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid
+// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly
+// more user-friendly ones.
+//
+// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized.
+//
+// ===========================================================================
+//
+// Philosophy
+//
+// stb libraries are designed with the following priorities:
+//
+// 1. easy to use
+// 2. easy to maintain
+// 3. good performance
+//
+// Sometimes I let "good performance" creep up in priority over "easy to maintain",
+// and for best performance I may provide less-easy-to-use APIs that give higher
+// performance, in addition to the easy to use ones. Nevertheless, it's important
+// to keep in mind that from the standpoint of you, a client of this library,
+// all you care about is #1 and #3, and stb libraries do not emphasize #3 above all.
+//
+// Some secondary priorities arise directly from the first two, some of which
+// make more explicit reasons why performance can't be emphasized.
+//
+// - Portable ("ease of use")
+// - Small footprint ("easy to maintain")
+// - No dependencies ("ease of use")
+//
+// ===========================================================================
+//
+// I/O callbacks
+//
+// I/O callbacks allow you to read from arbitrary sources, like packaged
+// files or some other source. Data read from callbacks are processed
+// through a small internal buffer (currently 128 bytes) to try to reduce
+// overhead.
+//
+// The three functions you must define are "read" (reads some bytes of data),
+// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end).
+//
+// ===========================================================================
+//
+// SIMD support
+//
+// The JPEG decoder will try to automatically use SIMD kernels on x86 when
+// supported by the compiler. For ARM Neon support, you must explicitly
+// request it.
+//
+// (The old do-it-yourself SIMD API is no longer supported in the current
+// code.)
+//
+// On x86, SSE2 will automatically be used when available based on a run-time
+// test; if not, the generic C versions are used as a fall-back. On ARM targets,
+// the typical path is to have separate builds for NEON and non-NEON devices
+// (at least this is true for iOS and Android). Therefore, the NEON support is
+// toggled by a build flag: define STBI_NEON to get NEON loops.
+//
+// The output of the JPEG decoder is slightly different from versions where
+// SIMD support was introduced (that is, for versions before 1.49). The
+// difference is only +-1 in the 8-bit RGB channels, and only on a small
+// fraction of pixels. You can force the pre-1.49 behavior by defining
+// STBI_JPEG_OLD, but this will disable some of the SIMD decoding path
+// and hence cost some performance.
+//
+// If for some reason you do not want to use any of SIMD code, or if
+// you have issues compiling it, you can disable it entirely by
+// defining STBI_NO_SIMD.
+//
+// ===========================================================================
+//
+// HDR image support (disable by defining STBI_NO_HDR)
+//
+// stb_image now supports loading HDR images in general, and currently
+// the Radiance .HDR file format, although the support is provided
+// generically. You can still load any file through the existing interface;
+// if you attempt to load an HDR file, it will be automatically remapped to
+// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1;
+// both of these constants can be reconfigured through this interface:
+//
+// stbi_hdr_to_ldr_gamma(2.2f);
+// stbi_hdr_to_ldr_scale(1.0f);
+//
+// (note, do not use _inverse_ constants; stbi_image will invert them
+// appropriately).
+//
+// Additionally, there is a new, parallel interface for loading files as
+// (linear) floats to preserve the full dynamic range:
+//
+// float *data = stbi_loadf(filename, &x, &y, &n, 0);
+//
+// If you load LDR images through this interface, those images will
+// be promoted to floating point values, run through the inverse of
+// constants corresponding to the above:
+//
+// stbi_ldr_to_hdr_scale(1.0f);
+// stbi_ldr_to_hdr_gamma(2.2f);
+//
+// Finally, given a filename (or an open file or memory block--see header
+// file for details) containing image data, you can query for the "most
+// appropriate" interface to use (that is, whether the image is HDR or
+// not), using:
+//
+// stbi_is_hdr(char *filename);
+//
+// ===========================================================================
+//
+// iPhone PNG support:
+//
+// By default we convert iphone-formatted PNGs back to RGB, even though
+// they are internally encoded differently. You can disable this conversion
+// by by calling stbi_convert_iphone_png_to_rgb(0), in which case
+// you will always just get the native iphone "format" through (which
+// is BGR stored in RGB).
+//
+// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per
+// pixel to remove any premultiplied alpha *only* if the image file explicitly
+// says there's premultiplied data (currently only happens in iPhone images,
+// and only if iPhone convert-to-rgb processing is on).
+//
+
+
+#ifndef STBI_NO_STDIO
+#include <stdio.h>
+#endif // STBI_NO_STDIO
+
+#define STBI_VERSION 1
+
+enum
+{
+ STBI_default = 0, // only used for req_comp
+
+ STBI_grey = 1,
+ STBI_grey_alpha = 2,
+ STBI_rgb = 3,
+ STBI_rgb_alpha = 4
+};
+
+typedef unsigned char stbi_uc;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef STB_IMAGE_STATIC
+#define STBIDEF static
+#else
+#define STBIDEF extern
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// PRIMARY API - works on images of any type
+//
+
+//
+// load image by filename, open file, or memory buffer
+//
+
+typedef struct
+{
+ int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read
+ void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative
+ int (*eof) (void *user); // returns nonzero if we are at end of file/data
+} stbi_io_callbacks;
+
+STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp);
+STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *comp, int req_comp);
+STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *comp, int req_comp);
+
+#ifndef STBI_NO_STDIO
+STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
+// for stbi_load_from_file, file pointer is left pointing immediately after image
+#endif
+
+#ifndef STBI_NO_LINEAR
+ STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp);
+ STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp);
+ STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp);
+
+ #ifndef STBI_NO_STDIO
+ STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
+ #endif
+#endif
+
+#ifndef STBI_NO_HDR
+ STBIDEF void stbi_hdr_to_ldr_gamma(float gamma);
+ STBIDEF void stbi_hdr_to_ldr_scale(float scale);
+#endif // STBI_NO_HDR
+
+#ifndef STBI_NO_LINEAR
+ STBIDEF void stbi_ldr_to_hdr_gamma(float gamma);
+ STBIDEF void stbi_ldr_to_hdr_scale(float scale);
+#endif // STBI_NO_LINEAR
+
+// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR
+STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user);
+STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len);
+#ifndef STBI_NO_STDIO
+STBIDEF int stbi_is_hdr (char const *filename);
+STBIDEF int stbi_is_hdr_from_file(FILE *f);
+#endif // STBI_NO_STDIO
+
+
+// get a VERY brief reason for failure
+// NOT THREADSAFE
+STBIDEF const char *stbi_failure_reason (void);
+
+// free the loaded image -- this is just free()
+STBIDEF void stbi_image_free (void *retval_from_stbi_load);
+
+// get image dimensions & components without fully decoding
+STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);
+STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp);
+
+#ifndef STBI_NO_STDIO
+STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp);
+STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp);
+
+#endif
+
+
+
+// for image formats that explicitly notate that they have premultiplied alpha,
+// we just return the colors as stored in the file. set this flag to force
+// unpremultiplication. results are undefined if the unpremultiply overflow.
+STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);
+
+// indicate whether we should process iphone images back to canonical format,
+// or just pass them through "as-is"
+STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert);
+
+// flip the image vertically, so the first pixel in the output array is the bottom left
+STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip);
+
+// ZLIB client - used by PNG, available for other purposes
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);
+STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header);
+STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen);
+STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
+
+STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen);
+STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+//
+//
+//// end header file /////////////////////////////////////////////////////
+#endif // STBI_INCLUDE_STB_IMAGE_H
+
+#ifdef STB_IMAGE_IMPLEMENTATION
+
+#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \
+ || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \
+ || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \
+ || defined(STBI_ONLY_ZLIB)
+ #ifndef STBI_ONLY_JPEG
+ #define STBI_NO_JPEG
+ #endif
+ #ifndef STBI_ONLY_PNG
+ #define STBI_NO_PNG
+ #endif
+ #ifndef STBI_ONLY_BMP
+ #define STBI_NO_BMP
+ #endif
+ #ifndef STBI_ONLY_PSD
+ #define STBI_NO_PSD
+ #endif
+ #ifndef STBI_ONLY_TGA
+ #define STBI_NO_TGA
+ #endif
+ #ifndef STBI_ONLY_GIF
+ #define STBI_NO_GIF
+ #endif
+ #ifndef STBI_ONLY_HDR
+ #define STBI_NO_HDR
+ #endif
+ #ifndef STBI_ONLY_PIC
+ #define STBI_NO_PIC
+ #endif
+ #ifndef STBI_ONLY_PNM
+ #define STBI_NO_PNM
+ #endif
+#endif
+
+#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB)
+#define STBI_NO_ZLIB
+#endif
+
+
+#include <stdarg.h>
+#include <stddef.h> // ptrdiff_t on osx
+#include <stdlib.h>
+#include <string.h>
+
+#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
+#include <math.h> // ldexp
+#endif
+
+#ifndef STBI_NO_STDIO
+#include <stdio.h>
+#endif
+
+#ifndef STBI_ASSERT
+#include <assert.h>
+#define STBI_ASSERT(x) assert(x)
+#endif
+
+
+#ifndef _MSC_VER
+ #ifdef __cplusplus
+ #define stbi_inline inline
+ #else
+ #define stbi_inline
+ #endif
+#else
+ #define stbi_inline __forceinline
+#endif
+
+
+#ifdef _MSC_VER
+typedef unsigned short stbi__uint16;
+typedef signed short stbi__int16;
+typedef unsigned int stbi__uint32;
+typedef signed int stbi__int32;
+#else
+#include <stdint.h>
+typedef uint16_t stbi__uint16;
+typedef int16_t stbi__int16;
+typedef uint32_t stbi__uint32;
+typedef int32_t stbi__int32;
+#endif
+
+// should produce compiler error if size is wrong
+typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1];
+
+#ifdef _MSC_VER
+#define STBI_NOTUSED(v) (void)(v)
+#else
+#define STBI_NOTUSED(v) (void)sizeof(v)
+#endif
+
+#ifdef _MSC_VER
+#define STBI_HAS_LROTL
+#endif
+
+#ifdef STBI_HAS_LROTL
+ #define stbi_lrot(x,y) _lrotl(x,y)
+#else
+ #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y))))
+#endif
+
+#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED))
+// ok
+#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED)
+// ok
+#else
+#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)."
+#endif
+
+#ifndef STBI_MALLOC
+#define STBI_MALLOC(sz) malloc(sz)
+#define STBI_REALLOC(p,newsz) realloc(p,newsz)
+#define STBI_FREE(p) free(p)
+#endif
+
+#ifndef STBI_REALLOC_SIZED
+#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz)
+#endif
+
+// x86/x64 detection
+#if defined(__x86_64__) || defined(_M_X64)
+#define STBI__X64_TARGET
+#elif defined(__i386) || defined(_M_IX86)
+#define STBI__X86_TARGET
+#endif
+
+#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD)
+// NOTE: not clear do we actually need this for the 64-bit path?
+// gcc doesn't support sse2 intrinsics unless you compile with -msse2,
+// (but compiling with -msse2 allows the compiler to use SSE2 everywhere;
+// this is just broken and gcc are jerks for not fixing it properly
+// http://www.virtualdub.org/blog/pivot/entry.php?id=363 )
+#define STBI_NO_SIMD
+#endif
+
+#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD)
+// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET
+//
+// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the
+// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant.
+// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not
+// simultaneously enabling "-mstackrealign".
+//
+// See https://github.com/nothings/stb/issues/81 for more information.
+//
+// So default to no SSE2 on 32-bit MinGW. If you've read this far and added
+// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2.
+#define STBI_NO_SIMD
+#endif
+
+#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET))
+#define STBI_SSE2
+#include <emmintrin.h>
+
+#ifdef _MSC_VER
+
+#if _MSC_VER >= 1400 // not VC6
+#include <intrin.h> // __cpuid
+static int stbi__cpuid3(void)
+{
+ int info[4];
+ __cpuid(info,1);
+ return info[3];
+}
+#else
+static int stbi__cpuid3(void)
+{
+ int res;
+ __asm {
+ mov eax,1
+ cpuid
+ mov res,edx
+ }
+ return res;
+}
+#endif
+
+#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name
+
+static int stbi__sse2_available()
+{
+ int info3 = stbi__cpuid3();
+ return ((info3 >> 26) & 1) != 0;
+}
+#else // assume GCC-style if not VC++
+#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
+
+static int stbi__sse2_available()
+{
+#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later
+ // GCC 4.8+ has a nice way to do this
+ return __builtin_cpu_supports("sse2");
+#else
+ // portable way to do this, preferably without using GCC inline ASM?
+ // just bail for now.
+ return 0;
+#endif
+}
+#endif
+#endif
+
+// ARM NEON
+#if defined(STBI_NO_SIMD) && defined(STBI_NEON)
+#undef STBI_NEON
+#endif
+
+#ifdef STBI_NEON
+#include <arm_neon.h>
+// assume GCC or Clang on ARM targets
+#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
+#endif
+
+#ifndef STBI_SIMD_ALIGN
+#define STBI_SIMD_ALIGN(type, name) type name
+#endif
+
+///////////////////////////////////////////////
+//
+// stbi__context struct and start_xxx functions
+
+// stbi__context structure is our basic context used by all images, so it
+// contains all the IO context, plus some basic image information
+typedef struct
+{
+ stbi__uint32 img_x, img_y;
+ int img_n, img_out_n;
+
+ stbi_io_callbacks io;
+ void *io_user_data;
+
+ int read_from_callbacks;
+ int buflen;
+ stbi_uc buffer_start[128];
+
+ stbi_uc *img_buffer, *img_buffer_end;
+ stbi_uc *img_buffer_original, *img_buffer_original_end;
+} stbi__context;
+
+
+static void stbi__refill_buffer(stbi__context *s);
+
+// initialize a memory-decode context
+static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len)
+{
+ s->io.read = NULL;
+ s->read_from_callbacks = 0;
+ s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer;
+ s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len;
+}
+
+// initialize a callback-based context
+static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user)
+{
+ s->io = *c;
+ s->io_user_data = user;
+ s->buflen = sizeof(s->buffer_start);
+ s->read_from_callbacks = 1;
+ s->img_buffer_original = s->buffer_start;
+ stbi__refill_buffer(s);
+ s->img_buffer_original_end = s->img_buffer_end;
+}
+
+#ifndef STBI_NO_STDIO
+
+static int stbi__stdio_read(void *user, char *data, int size)
+{
+ return (int) fread(data,1,size,(FILE*) user);
+}
+
+static void stbi__stdio_skip(void *user, int n)
+{
+ fseek((FILE*) user, n, SEEK_CUR);
+}
+
+static int stbi__stdio_eof(void *user)
+{
+ return feof((FILE*) user);
+}
+
+static stbi_io_callbacks stbi__stdio_callbacks =
+{
+ stbi__stdio_read,
+ stbi__stdio_skip,
+ stbi__stdio_eof,
+};
+
+static void stbi__start_file(stbi__context *s, FILE *f)
+{
+ stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f);
+}
+
+//static void stop_file(stbi__context *s) { }
+
+#endif // !STBI_NO_STDIO
+
+static void stbi__rewind(stbi__context *s)
+{
+ // conceptually rewind SHOULD rewind to the beginning of the stream,
+ // but we just rewind to the beginning of the initial buffer, because
+ // we only use it after doing 'test', which only ever looks at at most 92 bytes
+ s->img_buffer = s->img_buffer_original;
+ s->img_buffer_end = s->img_buffer_original_end;
+}
+
+#ifndef STBI_NO_JPEG
+static int stbi__jpeg_test(stbi__context *s);
+static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PNG
+static int stbi__png_test(stbi__context *s);
+static stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_BMP
+static int stbi__bmp_test(stbi__context *s);
+static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_TGA
+static int stbi__tga_test(stbi__context *s);
+static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PSD
+static int stbi__psd_test(stbi__context *s);
+static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_HDR
+static int stbi__hdr_test(stbi__context *s);
+static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PIC
+static int stbi__pic_test(stbi__context *s);
+static stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_GIF
+static int stbi__gif_test(stbi__context *s);
+static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PNM
+static int stbi__pnm_test(stbi__context *s);
+static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp);
+static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+// this is not threadsafe
+static const char *stbi__g_failure_reason;
+
+STBIDEF const char *stbi_failure_reason(void)
+{
+ return stbi__g_failure_reason;
+}
+
+static int stbi__err(const char *str)
+{
+ stbi__g_failure_reason = str;
+ return 0;
+}
+
+static void *stbi__malloc(size_t size)
+{
+ return STBI_MALLOC(size);
+}
+
+// stbi__err - error
+// stbi__errpf - error returning pointer to float
+// stbi__errpuc - error returning pointer to unsigned char
+
+#ifdef STBI_NO_FAILURE_STRINGS
+ #define stbi__err(x,y) 0
+#elif defined(STBI_FAILURE_USERMSG)
+ #define stbi__err(x,y) stbi__err(y)
+#else
+ #define stbi__err(x,y) stbi__err(x)
+#endif
+
+#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL))
+#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL))
+
+STBIDEF void stbi_image_free(void *retval_from_stbi_load)
+{
+ STBI_FREE(retval_from_stbi_load);
+}
+
+#ifndef STBI_NO_LINEAR
+static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp);
+#endif
+
+#ifndef STBI_NO_HDR
+static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp);
+#endif
+
+static int stbi__vertically_flip_on_load = 0;
+
+STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)
+{
+ stbi__vertically_flip_on_load = flag_true_if_should_flip;
+}
+
+static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ #ifndef STBI_NO_JPEG
+ if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_PNG
+ if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_BMP
+ if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_GIF
+ if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_PSD
+ if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_PIC
+ if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp);
+ #endif
+ #ifndef STBI_NO_PNM
+ if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp);
+ #endif
+
+ #ifndef STBI_NO_HDR
+ if (stbi__hdr_test(s)) {
+ float *hdr = stbi__hdr_load(s, x,y,comp,req_comp);
+ return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp);
+ }
+ #endif
+
+ #ifndef STBI_NO_TGA
+ // test tga last because it's a crappy test!
+ if (stbi__tga_test(s))
+ return stbi__tga_load(s,x,y,comp,req_comp);
+ #endif
+
+ return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt");
+}
+
+static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ unsigned char *result = stbi__load_main(s, x, y, comp, req_comp);
+
+ if (stbi__vertically_flip_on_load && result != NULL) {
+ int w = *x, h = *y;
+ int depth = req_comp ? req_comp : *comp;
+ int row,col,z;
+ stbi_uc temp;
+
+ // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once
+ for (row = 0; row < (h>>1); row++) {
+ for (col = 0; col < w; col++) {
+ for (z = 0; z < depth; z++) {
+ temp = result[(row * w + col) * depth + z];
+ result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z];
+ result[((h - row - 1) * w + col) * depth + z] = temp;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+#ifndef STBI_NO_HDR
+static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)
+{
+ if (stbi__vertically_flip_on_load && result != NULL) {
+ int w = *x, h = *y;
+ int depth = req_comp ? req_comp : *comp;
+ int row,col,z;
+ float temp;
+
+ // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once
+ for (row = 0; row < (h>>1); row++) {
+ for (col = 0; col < w; col++) {
+ for (z = 0; z < depth; z++) {
+ temp = result[(row * w + col) * depth + z];
+ result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z];
+ result[((h - row - 1) * w + col) * depth + z] = temp;
+ }
+ }
+ }
+ }
+}
+#endif
+
+#ifndef STBI_NO_STDIO
+
+static FILE *stbi__fopen(char const *filename, char const *mode)
+{
+ FILE *f;
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+ if (0 != fopen_s(&f, filename, mode))
+ f=0;
+#else
+ f = fopen(filename, mode);
+#endif
+ return f;
+}
+
+
+STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+ FILE *f = stbi__fopen(filename, "rb");
+ unsigned char *result;
+ if (!f) return stbi__errpuc("can't fopen", "Unable to open file");
+ result = stbi_load_from_file(f,x,y,comp,req_comp);
+ fclose(f);
+ return result;
+}
+
+STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+ unsigned char *result;
+ stbi__context s;
+ stbi__start_file(&s,f);
+ result = stbi__load_flip(&s,x,y,comp,req_comp);
+ if (result) {
+ // need to 'unget' all the characters in the IO buffer
+ fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
+ }
+ return result;
+}
+#endif //!STBI_NO_STDIO
+
+STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__context s;
+ stbi__start_mem(&s,buffer,len);
+ return stbi__load_flip(&s,x,y,comp,req_comp);
+}
+
+STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__context s;
+ stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+ return stbi__load_flip(&s,x,y,comp,req_comp);
+}
+
+#ifndef STBI_NO_LINEAR
+static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ unsigned char *data;
+ #ifndef STBI_NO_HDR
+ if (stbi__hdr_test(s)) {
+ float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp);
+ if (hdr_data)
+ stbi__float_postprocess(hdr_data,x,y,comp,req_comp);
+ return hdr_data;
+ }
+ #endif
+ data = stbi__load_flip(s, x, y, comp, req_comp);
+ if (data)
+ return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp);
+ return stbi__errpf("unknown image type", "Image not of any known type, or corrupt");
+}
+
+STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__context s;
+ stbi__start_mem(&s,buffer,len);
+ return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+
+STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__context s;
+ stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+ return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+ float *result;
+ FILE *f = stbi__fopen(filename, "rb");
+ if (!f) return stbi__errpf("can't fopen", "Unable to open file");
+ result = stbi_loadf_from_file(f,x,y,comp,req_comp);
+ fclose(f);
+ return result;
+}
+
+STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__context s;
+ stbi__start_file(&s,f);
+ return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+#endif // !STBI_NO_STDIO
+
+#endif // !STBI_NO_LINEAR
+
+// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is
+// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always
+// reports false!
+
+STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len)
+{
+ #ifndef STBI_NO_HDR
+ stbi__context s;
+ stbi__start_mem(&s,buffer,len);
+ return stbi__hdr_test(&s);
+ #else
+ STBI_NOTUSED(buffer);
+ STBI_NOTUSED(len);
+ return 0;
+ #endif
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF int stbi_is_hdr (char const *filename)
+{
+ FILE *f = stbi__fopen(filename, "rb");
+ int result=0;
+ if (f) {
+ result = stbi_is_hdr_from_file(f);
+ fclose(f);
+ }
+ return result;
+}
+
+STBIDEF int stbi_is_hdr_from_file(FILE *f)
+{
+ #ifndef STBI_NO_HDR
+ stbi__context s;
+ stbi__start_file(&s,f);
+ return stbi__hdr_test(&s);
+ #else
+ STBI_NOTUSED(f);
+ return 0;
+ #endif
+}
+#endif // !STBI_NO_STDIO
+
+STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user)
+{
+ #ifndef STBI_NO_HDR
+ stbi__context s;
+ stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+ return stbi__hdr_test(&s);
+ #else
+ STBI_NOTUSED(clbk);
+ STBI_NOTUSED(user);
+ return 0;
+ #endif
+}
+
+#ifndef STBI_NO_LINEAR
+static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f;
+
+STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; }
+STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; }
+#endif
+
+static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f;
+
+STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; }
+STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; }
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Common code used by all image loaders
+//
+
+enum
+{
+ STBI__SCAN_load=0,
+ STBI__SCAN_type,
+ STBI__SCAN_header
+};
+
+static void stbi__refill_buffer(stbi__context *s)
+{
+ int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen);
+ if (n == 0) {
+ // at end of file, treat same as if from memory, but need to handle case
+ // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file
+ s->read_from_callbacks = 0;
+ s->img_buffer = s->buffer_start;
+ s->img_buffer_end = s->buffer_start+1;
+ *s->img_buffer = 0;
+ } else {
+ s->img_buffer = s->buffer_start;
+ s->img_buffer_end = s->buffer_start + n;
+ }
+}
+
+stbi_inline static stbi_uc stbi__get8(stbi__context *s)
+{
+ if (s->img_buffer < s->img_buffer_end)
+ return *s->img_buffer++;
+ if (s->read_from_callbacks) {
+ stbi__refill_buffer(s);
+ return *s->img_buffer++;
+ }
+ return 0;
+}
+
+stbi_inline static int stbi__at_eof(stbi__context *s)
+{
+ if (s->io.read) {
+ if (!(s->io.eof)(s->io_user_data)) return 0;
+ // if feof() is true, check if buffer = end
+ // special case: we've only got the special 0 character at the end
+ if (s->read_from_callbacks == 0) return 1;
+ }
+
+ return s->img_buffer >= s->img_buffer_end;
+}
+
+static void stbi__skip(stbi__context *s, int n)
+{
+ if (n < 0) {
+ s->img_buffer = s->img_buffer_end;
+ return;
+ }
+ if (s->io.read) {
+ int blen = (int) (s->img_buffer_end - s->img_buffer);
+ if (blen < n) {
+ s->img_buffer = s->img_buffer_end;
+ (s->io.skip)(s->io_user_data, n - blen);
+ return;
+ }
+ }
+ s->img_buffer += n;
+}
+
+static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)
+{
+ if (s->io.read) {
+ int blen = (int) (s->img_buffer_end - s->img_buffer);
+ if (blen < n) {
+ int res, count;
+
+ memcpy(buffer, s->img_buffer, blen);
+
+ count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen);
+ res = (count == (n-blen));
+ s->img_buffer = s->img_buffer_end;
+ return res;
+ }
+ }
+
+ if (s->img_buffer+n <= s->img_buffer_end) {
+ memcpy(buffer, s->img_buffer, n);
+ s->img_buffer += n;
+ return 1;
+ } else
+ return 0;
+}
+
+static int stbi__get16be(stbi__context *s)
+{
+ int z = stbi__get8(s);
+ return (z << 8) + stbi__get8(s);
+}
+
+static stbi__uint32 stbi__get32be(stbi__context *s)
+{
+ stbi__uint32 z = stbi__get16be(s);
+ return (z << 16) + stbi__get16be(s);
+}
+
+#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF)
+// nothing
+#else
+static int stbi__get16le(stbi__context *s)
+{
+ int z = stbi__get8(s);
+ return z + (stbi__get8(s) << 8);
+}
+#endif
+
+#ifndef STBI_NO_BMP
+static stbi__uint32 stbi__get32le(stbi__context *s)
+{
+ stbi__uint32 z = stbi__get16le(s);
+ return z + (stbi__get16le(s) << 16);
+}
+#endif
+
+#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// generic converter from built-in img_n to req_comp
+// individual types do this automatically as much as possible (e.g. jpeg
+// does all cases internally since it needs to colorspace convert anyway,
+// and it never has alpha, so very few cases ). png can automatically
+// interleave an alpha=255 channel, but falls back to this for other cases
+//
+// assume data buffer is malloced, so malloc a new one and free that one
+// only failure mode is malloc failing
+
+static stbi_uc stbi__compute_y(int r, int g, int b)
+{
+ return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8);
+}
+
+static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y)
+{
+ int i,j;
+ unsigned char *good;
+
+ if (req_comp == img_n) return data;
+ STBI_ASSERT(req_comp >= 1 && req_comp <= 4);
+
+ good = (unsigned char *) stbi__malloc(req_comp * x * y);
+ if (good == NULL) {
+ STBI_FREE(data);
+ return stbi__errpuc("outofmem", "Out of memory");
+ }
+
+ for (j=0; j < (int) y; ++j) {
+ unsigned char *src = data + j * x * img_n ;
+ unsigned char *dest = good + j * x * req_comp;
+
+ #define COMBO(a,b) ((a)*8+(b))
+ #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
+ // convert source image with img_n components to one with req_comp components;
+ // avoid switch per pixel, so use switch per scanline and massive macros
+ switch (COMBO(img_n, req_comp)) {
+ CASE(1,2) dest[0]=src[0], dest[1]=255; break;
+ CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break;
+ CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break;
+ CASE(2,1) dest[0]=src[0]; break;
+ CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break;
+ CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break;
+ CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break;
+ CASE(3,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break;
+ CASE(3,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; break;
+ CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break;
+ CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break;
+ CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break;
+ default: STBI_ASSERT(0);
+ }
+ #undef CASE
+ }
+
+ STBI_FREE(data);
+ return good;
+}
+
+#ifndef STBI_NO_LINEAR
+static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp)
+{
+ int i,k,n;
+ float *output = (float *) stbi__malloc(x * y * comp * sizeof(float));
+ if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); }
+ // compute number of non-alpha components
+ if (comp & 1) n = comp; else n = comp-1;
+ for (i=0; i < x*y; ++i) {
+ for (k=0; k < n; ++k) {
+ output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale);
+ }
+ if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f;
+ }
+ STBI_FREE(data);
+ return output;
+}
+#endif
+
+#ifndef STBI_NO_HDR
+#define stbi__float2int(x) ((int) (x))
+static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp)
+{
+ int i,k,n;
+ stbi_uc *output = (stbi_uc *) stbi__malloc(x * y * comp);
+ if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); }
+ // compute number of non-alpha components
+ if (comp & 1) n = comp; else n = comp-1;
+ for (i=0; i < x*y; ++i) {
+ for (k=0; k < n; ++k) {
+ float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f;
+ if (z < 0) z = 0;
+ if (z > 255) z = 255;
+ output[i*comp + k] = (stbi_uc) stbi__float2int(z);
+ }
+ if (k < comp) {
+ float z = data[i*comp+k] * 255 + 0.5f;
+ if (z < 0) z = 0;
+ if (z > 255) z = 255;
+ output[i*comp + k] = (stbi_uc) stbi__float2int(z);
+ }
+ }
+ STBI_FREE(data);
+ return output;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// "baseline" JPEG/JFIF decoder
+//
+// simple implementation
+// - doesn't support delayed output of y-dimension
+// - simple interface (only one output format: 8-bit interleaved RGB)
+// - doesn't try to recover corrupt jpegs
+// - doesn't allow partial loading, loading multiple at once
+// - still fast on x86 (copying globals into locals doesn't help x86)
+// - allocates lots of intermediate memory (full size of all components)
+// - non-interleaved case requires this anyway
+// - allows good upsampling (see next)
+// high-quality
+// - upsampled channels are bilinearly interpolated, even across blocks
+// - quality integer IDCT derived from IJG's 'slow'
+// performance
+// - fast huffman; reasonable integer IDCT
+// - some SIMD kernels for common paths on targets with SSE2/NEON
+// - uses a lot of intermediate memory, could cache poorly
+
+#ifndef STBI_NO_JPEG
+
+// huffman decoding acceleration
+#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache
+
+typedef struct
+{
+ stbi_uc fast[1 << FAST_BITS];
+ // weirdly, repacking this into AoS is a 10% speed loss, instead of a win
+ stbi__uint16 code[256];
+ stbi_uc values[256];
+ stbi_uc size[257];
+ unsigned int maxcode[18];
+ int delta[17]; // old 'firstsymbol' - old 'firstcode'
+} stbi__huffman;
+
+typedef struct
+{
+ stbi__context *s;
+ stbi__huffman huff_dc[4];
+ stbi__huffman huff_ac[4];
+ stbi_uc dequant[4][64];
+ stbi__int16 fast_ac[4][1 << FAST_BITS];
+
+// sizes for components, interleaved MCUs
+ int img_h_max, img_v_max;
+ int img_mcu_x, img_mcu_y;
+ int img_mcu_w, img_mcu_h;
+
+// definition of jpeg image component
+ struct
+ {
+ int id;
+ int h,v;
+ int tq;
+ int hd,ha;
+ int dc_pred;
+
+ int x,y,w2,h2;
+ stbi_uc *data;
+ void *raw_data, *raw_coeff;
+ stbi_uc *linebuf;
+ short *coeff; // progressive only
+ int coeff_w, coeff_h; // number of 8x8 coefficient blocks
+ } img_comp[4];
+
+ stbi__uint32 code_buffer; // jpeg entropy-coded buffer
+ int code_bits; // number of valid bits
+ unsigned char marker; // marker seen while filling entropy buffer
+ int nomore; // flag if we saw a marker so must stop
+
+ int progressive;
+ int spec_start;
+ int spec_end;
+ int succ_high;
+ int succ_low;
+ int eob_run;
+ int rgb;
+
+ int scan_n, order[4];
+ int restart_interval, todo;
+
+// kernels
+ void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]);
+ void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step);
+ stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs);
+} stbi__jpeg;
+
+static int stbi__build_huffman(stbi__huffman *h, int *count)
+{
+ int i,j,k=0,code;
+ // build size list for each symbol (from JPEG spec)
+ for (i=0; i < 16; ++i)
+ for (j=0; j < count[i]; ++j)
+ h->size[k++] = (stbi_uc) (i+1);
+ h->size[k] = 0;
+
+ // compute actual symbols (from jpeg spec)
+ code = 0;
+ k = 0;
+ for(j=1; j <= 16; ++j) {
+ // compute delta to add to code to compute symbol id
+ h->delta[j] = k - code;
+ if (h->size[k] == j) {
+ while (h->size[k] == j)
+ h->code[k++] = (stbi__uint16) (code++);
+ if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG");
+ }
+ // compute largest code + 1 for this size, preshifted as needed later
+ h->maxcode[j] = code << (16-j);
+ code <<= 1;
+ }
+ h->maxcode[j] = 0xffffffff;
+
+ // build non-spec acceleration table; 255 is flag for not-accelerated
+ memset(h->fast, 255, 1 << FAST_BITS);
+ for (i=0; i < k; ++i) {
+ int s = h->size[i];
+ if (s <= FAST_BITS) {
+ int c = h->code[i] << (FAST_BITS-s);
+ int m = 1 << (FAST_BITS-s);
+ for (j=0; j < m; ++j) {
+ h->fast[c+j] = (stbi_uc) i;
+ }
+ }
+ }
+ return 1;
+}
+
+// build a table that decodes both magnitude and value of small ACs in
+// one go.
+static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h)
+{
+ int i;
+ for (i=0; i < (1 << FAST_BITS); ++i) {
+ stbi_uc fast = h->fast[i];
+ fast_ac[i] = 0;
+ if (fast < 255) {
+ int rs = h->values[fast];
+ int run = (rs >> 4) & 15;
+ int magbits = rs & 15;
+ int len = h->size[fast];
+
+ if (magbits && len + magbits <= FAST_BITS) {
+ // magnitude code followed by receive_extend code
+ int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits);
+ int m = 1 << (magbits - 1);
+ if (k < m) k += (-1 << magbits) + 1;
+ // if the result is small enough, we can fit it in fast_ac table
+ if (k >= -128 && k <= 127)
+ fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits));
+ }
+ }
+ }
+}
+
+static void stbi__grow_buffer_unsafe(stbi__jpeg *j)
+{
+ do {
+ int b = j->nomore ? 0 : stbi__get8(j->s);
+ if (b == 0xff) {
+ int c = stbi__get8(j->s);
+ if (c != 0) {
+ j->marker = (unsigned char) c;
+ j->nomore = 1;
+ return;
+ }
+ }
+ j->code_buffer |= b << (24 - j->code_bits);
+ j->code_bits += 8;
+ } while (j->code_bits <= 24);
+}
+
+// (1 << n) - 1
+static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535};
+
+// decode a jpeg huffman value from the bitstream
+stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h)
+{
+ unsigned int temp;
+ int c,k;
+
+ if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+
+ // look at the top FAST_BITS and determine what symbol ID it is,
+ // if the code is <= FAST_BITS
+ c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+ k = h->fast[c];
+ if (k < 255) {
+ int s = h->size[k];
+ if (s > j->code_bits)
+ return -1;
+ j->code_buffer <<= s;
+ j->code_bits -= s;
+ return h->values[k];
+ }
+
+ // naive test is to shift the code_buffer down so k bits are
+ // valid, then test against maxcode. To speed this up, we've
+ // preshifted maxcode left so that it has (16-k) 0s at the
+ // end; in other words, regardless of the number of bits, it
+ // wants to be compared against something shifted to have 16;
+ // that way we don't need to shift inside the loop.
+ temp = j->code_buffer >> 16;
+ for (k=FAST_BITS+1 ; ; ++k)
+ if (temp < h->maxcode[k])
+ break;
+ if (k == 17) {
+ // error! code not found
+ j->code_bits -= 16;
+ return -1;
+ }
+
+ if (k > j->code_bits)
+ return -1;
+
+ // convert the huffman code to the symbol id
+ c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k];
+ STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]);
+
+ // convert the id to a symbol
+ j->code_bits -= k;
+ j->code_buffer <<= k;
+ return h->values[c];
+}
+
+// bias[n] = (-1<<n) + 1
+static int const stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767};
+
+// combined JPEG 'receive' and JPEG 'extend', since baseline
+// always extends everything it receives.
+stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n)
+{
+ unsigned int k;
+ int sgn;
+ if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
+
+ sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB
+ k = stbi_lrot(j->code_buffer, n);
+ STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask)));
+ j->code_buffer = k & ~stbi__bmask[n];
+ k &= stbi__bmask[n];
+ j->code_bits -= n;
+ return k + (stbi__jbias[n] & ~sgn);
+}
+
+// get some unsigned bits
+stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n)
+{
+ unsigned int k;
+ if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
+ k = stbi_lrot(j->code_buffer, n);
+ j->code_buffer = k & ~stbi__bmask[n];
+ k &= stbi__bmask[n];
+ j->code_bits -= n;
+ return k;
+}
+
+stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j)
+{
+ unsigned int k;
+ if (j->code_bits < 1) stbi__grow_buffer_unsafe(j);
+ k = j->code_buffer;
+ j->code_buffer <<= 1;
+ --j->code_bits;
+ return k & 0x80000000;
+}
+
+// given a value that's at position X in the zigzag stream,
+// where does it appear in the 8x8 matrix coded as row-major?
+static stbi_uc stbi__jpeg_dezigzag[64+15] =
+{
+ 0, 1, 8, 16, 9, 2, 3, 10,
+ 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63,
+ // let corrupt input sample past end
+ 63, 63, 63, 63, 63, 63, 63, 63,
+ 63, 63, 63, 63, 63, 63, 63
+};
+
+// decode one 64-entry block--
+static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *dequant)
+{
+ int diff,dc,k;
+ int t;
+
+ if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+ t = stbi__jpeg_huff_decode(j, hdc);
+ if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+
+ // 0 all the ac values now so we can do it 32-bits at a time
+ memset(data,0,64*sizeof(data[0]));
+
+ diff = t ? stbi__extend_receive(j, t) : 0;
+ dc = j->img_comp[b].dc_pred + diff;
+ j->img_comp[b].dc_pred = dc;
+ data[0] = (short) (dc * dequant[0]);
+
+ // decode AC components, see JPEG spec
+ k = 1;
+ do {
+ unsigned int zig;
+ int c,r,s;
+ if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+ c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+ r = fac[c];
+ if (r) { // fast-AC path
+ k += (r >> 4) & 15; // run
+ s = r & 15; // combined length
+ j->code_buffer <<= s;
+ j->code_bits -= s;
+ // decode into unzigzag'd location
+ zig = stbi__jpeg_dezigzag[k++];
+ data[zig] = (short) ((r >> 8) * dequant[zig]);
+ } else {
+ int rs = stbi__jpeg_huff_decode(j, hac);
+ if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+ s = rs & 15;
+ r = rs >> 4;
+ if (s == 0) {
+ if (rs != 0xf0) break; // end block
+ k += 16;
+ } else {
+ k += r;
+ // decode into unzigzag'd location
+ zig = stbi__jpeg_dezigzag[k++];
+ data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]);
+ }
+ }
+ } while (k < 64);
+ return 1;
+}
+
+static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b)
+{
+ int diff,dc;
+ int t;
+ if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
+
+ if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+
+ if (j->succ_high == 0) {
+ // first scan for DC coefficient, must be first
+ memset(data,0,64*sizeof(data[0])); // 0 all the ac values now
+ t = stbi__jpeg_huff_decode(j, hdc);
+ diff = t ? stbi__extend_receive(j, t) : 0;
+
+ dc = j->img_comp[b].dc_pred + diff;
+ j->img_comp[b].dc_pred = dc;
+ data[0] = (short) (dc << j->succ_low);
+ } else {
+ // refinement scan for DC coefficient
+ if (stbi__jpeg_get_bit(j))
+ data[0] += (short) (1 << j->succ_low);
+ }
+ return 1;
+}
+
+// @OPTIMIZE: store non-zigzagged during the decode passes,
+// and only de-zigzag when dequantizing
+static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac)
+{
+ int k;
+ if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
+
+ if (j->succ_high == 0) {
+ int shift = j->succ_low;
+
+ if (j->eob_run) {
+ --j->eob_run;
+ return 1;
+ }
+
+ k = j->spec_start;
+ do {
+ unsigned int zig;
+ int c,r,s;
+ if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+ c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+ r = fac[c];
+ if (r) { // fast-AC path
+ k += (r >> 4) & 15; // run
+ s = r & 15; // combined length
+ j->code_buffer <<= s;
+ j->code_bits -= s;
+ zig = stbi__jpeg_dezigzag[k++];
+ data[zig] = (short) ((r >> 8) << shift);
+ } else {
+ int rs = stbi__jpeg_huff_decode(j, hac);
+ if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+ s = rs & 15;
+ r = rs >> 4;
+ if (s == 0) {
+ if (r < 15) {
+ j->eob_run = (1 << r);
+ if (r)
+ j->eob_run += stbi__jpeg_get_bits(j, r);
+ --j->eob_run;
+ break;
+ }
+ k += 16;
+ } else {
+ k += r;
+ zig = stbi__jpeg_dezigzag[k++];
+ data[zig] = (short) (stbi__extend_receive(j,s) << shift);
+ }
+ }
+ } while (k <= j->spec_end);
+ } else {
+ // refinement scan for these AC coefficients
+
+ short bit = (short) (1 << j->succ_low);
+
+ if (j->eob_run) {
+ --j->eob_run;
+ for (k = j->spec_start; k <= j->spec_end; ++k) {
+ short *p = &data[stbi__jpeg_dezigzag[k]];
+ if (*p != 0)
+ if (stbi__jpeg_get_bit(j))
+ if ((*p & bit)==0) {
+ if (*p > 0)
+ *p += bit;
+ else
+ *p -= bit;
+ }
+ }
+ } else {
+ k = j->spec_start;
+ do {
+ int r,s;
+ int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh
+ if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+ s = rs & 15;
+ r = rs >> 4;
+ if (s == 0) {
+ if (r < 15) {
+ j->eob_run = (1 << r) - 1;
+ if (r)
+ j->eob_run += stbi__jpeg_get_bits(j, r);
+ r = 64; // force end of block
+ } else {
+ // r=15 s=0 should write 16 0s, so we just do
+ // a run of 15 0s and then write s (which is 0),
+ // so we don't have to do anything special here
+ }
+ } else {
+ if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG");
+ // sign bit
+ if (stbi__jpeg_get_bit(j))
+ s = bit;
+ else
+ s = -bit;
+ }
+
+ // advance by r
+ while (k <= j->spec_end) {
+ short *p = &data[stbi__jpeg_dezigzag[k++]];
+ if (*p != 0) {
+ if (stbi__jpeg_get_bit(j))
+ if ((*p & bit)==0) {
+ if (*p > 0)
+ *p += bit;
+ else
+ *p -= bit;
+ }
+ } else {
+ if (r == 0) {
+ *p = (short) s;
+ break;
+ }
+ --r;
+ }
+ }
+ } while (k <= j->spec_end);
+ }
+ }
+ return 1;
+}
+
+// take a -128..127 value and stbi__clamp it and convert to 0..255
+stbi_inline static stbi_uc stbi__clamp(int x)
+{
+ // trick to use a single test to catch both cases
+ if ((unsigned int) x > 255) {
+ if (x < 0) return 0;
+ if (x > 255) return 255;
+ }
+ return (stbi_uc) x;
+}
+
+#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5)))
+#define stbi__fsh(x) ((x) << 12)
+
+// derived from jidctint -- DCT_ISLOW
+#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \
+ int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \
+ p2 = s2; \
+ p3 = s6; \
+ p1 = (p2+p3) * stbi__f2f(0.5411961f); \
+ t2 = p1 + p3*stbi__f2f(-1.847759065f); \
+ t3 = p1 + p2*stbi__f2f( 0.765366865f); \
+ p2 = s0; \
+ p3 = s4; \
+ t0 = stbi__fsh(p2+p3); \
+ t1 = stbi__fsh(p2-p3); \
+ x0 = t0+t3; \
+ x3 = t0-t3; \
+ x1 = t1+t2; \
+ x2 = t1-t2; \
+ t0 = s7; \
+ t1 = s5; \
+ t2 = s3; \
+ t3 = s1; \
+ p3 = t0+t2; \
+ p4 = t1+t3; \
+ p1 = t0+t3; \
+ p2 = t1+t2; \
+ p5 = (p3+p4)*stbi__f2f( 1.175875602f); \
+ t0 = t0*stbi__f2f( 0.298631336f); \
+ t1 = t1*stbi__f2f( 2.053119869f); \
+ t2 = t2*stbi__f2f( 3.072711026f); \
+ t3 = t3*stbi__f2f( 1.501321110f); \
+ p1 = p5 + p1*stbi__f2f(-0.899976223f); \
+ p2 = p5 + p2*stbi__f2f(-2.562915447f); \
+ p3 = p3*stbi__f2f(-1.961570560f); \
+ p4 = p4*stbi__f2f(-0.390180644f); \
+ t3 += p1+p4; \
+ t2 += p2+p3; \
+ t1 += p2+p4; \
+ t0 += p1+p3;
+
+static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64])
+{
+ int i,val[64],*v=val;
+ stbi_uc *o;
+ short *d = data;
+
+ // columns
+ for (i=0; i < 8; ++i,++d, ++v) {
+ // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing
+ if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0
+ && d[40]==0 && d[48]==0 && d[56]==0) {
+ // no shortcut 0 seconds
+ // (1|2|3|4|5|6|7)==0 0 seconds
+ // all separate -0.047 seconds
+ // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds
+ int dcterm = d[0] << 2;
+ v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm;
+ } else {
+ STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56])
+ // constants scaled things up by 1<<12; let's bring them back
+ // down, but keep 2 extra bits of precision
+ x0 += 512; x1 += 512; x2 += 512; x3 += 512;
+ v[ 0] = (x0+t3) >> 10;
+ v[56] = (x0-t3) >> 10;
+ v[ 8] = (x1+t2) >> 10;
+ v[48] = (x1-t2) >> 10;
+ v[16] = (x2+t1) >> 10;
+ v[40] = (x2-t1) >> 10;
+ v[24] = (x3+t0) >> 10;
+ v[32] = (x3-t0) >> 10;
+ }
+ }
+
+ for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) {
+ // no fast case since the first 1D IDCT spread components out
+ STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7])
+ // constants scaled things up by 1<<12, plus we had 1<<2 from first
+ // loop, plus horizontal and vertical each scale by sqrt(8) so together
+ // we've got an extra 1<<3, so 1<<17 total we need to remove.
+ // so we want to round that, which means adding 0.5 * 1<<17,
+ // aka 65536. Also, we'll end up with -128 to 127 that we want
+ // to encode as 0..255 by adding 128, so we'll add that before the shift
+ x0 += 65536 + (128<<17);
+ x1 += 65536 + (128<<17);
+ x2 += 65536 + (128<<17);
+ x3 += 65536 + (128<<17);
+ // tried computing the shifts into temps, or'ing the temps to see
+ // if any were out of range, but that was slower
+ o[0] = stbi__clamp((x0+t3) >> 17);
+ o[7] = stbi__clamp((x0-t3) >> 17);
+ o[1] = stbi__clamp((x1+t2) >> 17);
+ o[6] = stbi__clamp((x1-t2) >> 17);
+ o[2] = stbi__clamp((x2+t1) >> 17);
+ o[5] = stbi__clamp((x2-t1) >> 17);
+ o[3] = stbi__clamp((x3+t0) >> 17);
+ o[4] = stbi__clamp((x3-t0) >> 17);
+ }
+}
+
+#ifdef STBI_SSE2
+// sse2 integer IDCT. not the fastest possible implementation but it
+// produces bit-identical results to the generic C version so it's
+// fully "transparent".
+static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
+{
+ // This is constructed to match our regular (generic) integer IDCT exactly.
+ __m128i row0, row1, row2, row3, row4, row5, row6, row7;
+ __m128i tmp;
+
+ // dot product constant: even elems=x, odd elems=y
+ #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y))
+
+ // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit)
+ // out(1) = c1[even]*x + c1[odd]*y
+ #define dct_rot(out0,out1, x,y,c0,c1) \
+ __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \
+ __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \
+ __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \
+ __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \
+ __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \
+ __m128i out1##_h = _mm_madd_epi16(c0##hi, c1)
+
+ // out = in << 12 (in 16-bit, out 32-bit)
+ #define dct_widen(out, in) \
+ __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \
+ __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4)
+
+ // wide add
+ #define dct_wadd(out, a, b) \
+ __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \
+ __m128i out##_h = _mm_add_epi32(a##_h, b##_h)
+
+ // wide sub
+ #define dct_wsub(out, a, b) \
+ __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \
+ __m128i out##_h = _mm_sub_epi32(a##_h, b##_h)
+
+ // butterfly a/b, add bias, then shift by "s" and pack
+ #define dct_bfly32o(out0, out1, a,b,bias,s) \
+ { \
+ __m128i abiased_l = _mm_add_epi32(a##_l, bias); \
+ __m128i abiased_h = _mm_add_epi32(a##_h, bias); \
+ dct_wadd(sum, abiased, b); \
+ dct_wsub(dif, abiased, b); \
+ out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \
+ out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \
+ }
+
+ // 8-bit interleave step (for transposes)
+ #define dct_interleave8(a, b) \
+ tmp = a; \
+ a = _mm_unpacklo_epi8(a, b); \
+ b = _mm_unpackhi_epi8(tmp, b)
+
+ // 16-bit interleave step (for transposes)
+ #define dct_interleave16(a, b) \
+ tmp = a; \
+ a = _mm_unpacklo_epi16(a, b); \
+ b = _mm_unpackhi_epi16(tmp, b)
+
+ #define dct_pass(bias,shift) \
+ { \
+ /* even part */ \
+ dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \
+ __m128i sum04 = _mm_add_epi16(row0, row4); \
+ __m128i dif04 = _mm_sub_epi16(row0, row4); \
+ dct_widen(t0e, sum04); \
+ dct_widen(t1e, dif04); \
+ dct_wadd(x0, t0e, t3e); \
+ dct_wsub(x3, t0e, t3e); \
+ dct_wadd(x1, t1e, t2e); \
+ dct_wsub(x2, t1e, t2e); \
+ /* odd part */ \
+ dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \
+ dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \
+ __m128i sum17 = _mm_add_epi16(row1, row7); \
+ __m128i sum35 = _mm_add_epi16(row3, row5); \
+ dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \
+ dct_wadd(x4, y0o, y4o); \
+ dct_wadd(x5, y1o, y5o); \
+ dct_wadd(x6, y2o, y5o); \
+ dct_wadd(x7, y3o, y4o); \
+ dct_bfly32o(row0,row7, x0,x7,bias,shift); \
+ dct_bfly32o(row1,row6, x1,x6,bias,shift); \
+ dct_bfly32o(row2,row5, x2,x5,bias,shift); \
+ dct_bfly32o(row3,row4, x3,x4,bias,shift); \
+ }
+
+ __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f));
+ __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f));
+ __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f));
+ __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f));
+ __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f));
+ __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f));
+ __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f));
+ __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f));
+
+ // rounding biases in column/row passes, see stbi__idct_block for explanation.
+ __m128i bias_0 = _mm_set1_epi32(512);
+ __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17));
+
+ // load
+ row0 = _mm_load_si128((const __m128i *) (data + 0*8));
+ row1 = _mm_load_si128((const __m128i *) (data + 1*8));
+ row2 = _mm_load_si128((const __m128i *) (data + 2*8));
+ row3 = _mm_load_si128((const __m128i *) (data + 3*8));
+ row4 = _mm_load_si128((const __m128i *) (data + 4*8));
+ row5 = _mm_load_si128((const __m128i *) (data + 5*8));
+ row6 = _mm_load_si128((const __m128i *) (data + 6*8));
+ row7 = _mm_load_si128((const __m128i *) (data + 7*8));
+
+ // column pass
+ dct_pass(bias_0, 10);
+
+ {
+ // 16bit 8x8 transpose pass 1
+ dct_interleave16(row0, row4);
+ dct_interleave16(row1, row5);
+ dct_interleave16(row2, row6);
+ dct_interleave16(row3, row7);
+
+ // transpose pass 2
+ dct_interleave16(row0, row2);
+ dct_interleave16(row1, row3);
+ dct_interleave16(row4, row6);
+ dct_interleave16(row5, row7);
+
+ // transpose pass 3
+ dct_interleave16(row0, row1);
+ dct_interleave16(row2, row3);
+ dct_interleave16(row4, row5);
+ dct_interleave16(row6, row7);
+ }
+
+ // row pass
+ dct_pass(bias_1, 17);
+
+ {
+ // pack
+ __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7
+ __m128i p1 = _mm_packus_epi16(row2, row3);
+ __m128i p2 = _mm_packus_epi16(row4, row5);
+ __m128i p3 = _mm_packus_epi16(row6, row7);
+
+ // 8bit 8x8 transpose pass 1
+ dct_interleave8(p0, p2); // a0e0a1e1...
+ dct_interleave8(p1, p3); // c0g0c1g1...
+
+ // transpose pass 2
+ dct_interleave8(p0, p1); // a0c0e0g0...
+ dct_interleave8(p2, p3); // b0d0f0h0...
+
+ // transpose pass 3
+ dct_interleave8(p0, p2); // a0b0c0d0...
+ dct_interleave8(p1, p3); // a4b4c4d4...
+
+ // store
+ _mm_storel_epi64((__m128i *) out, p0); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, p2); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, p1); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, p3); out += out_stride;
+ _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e));
+ }
+
+#undef dct_const
+#undef dct_rot
+#undef dct_widen
+#undef dct_wadd
+#undef dct_wsub
+#undef dct_bfly32o
+#undef dct_interleave8
+#undef dct_interleave16
+#undef dct_pass
+}
+
+#endif // STBI_SSE2
+
+#ifdef STBI_NEON
+
+// NEON integer IDCT. should produce bit-identical
+// results to the generic C version.
+static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
+{
+ int16x8_t row0, row1, row2, row3, row4, row5, row6, row7;
+
+ int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f));
+ int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f));
+ int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f));
+ int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f));
+ int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f));
+ int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f));
+ int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f));
+ int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f));
+ int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f));
+ int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f));
+ int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f));
+ int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f));
+
+#define dct_long_mul(out, inq, coeff) \
+ int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \
+ int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff)
+
+#define dct_long_mac(out, acc, inq, coeff) \
+ int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \
+ int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff)
+
+#define dct_widen(out, inq) \
+ int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \
+ int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12)
+
+// wide add
+#define dct_wadd(out, a, b) \
+ int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \
+ int32x4_t out##_h = vaddq_s32(a##_h, b##_h)
+
+// wide sub
+#define dct_wsub(out, a, b) \
+ int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \
+ int32x4_t out##_h = vsubq_s32(a##_h, b##_h)
+
+// butterfly a/b, then shift using "shiftop" by "s" and pack
+#define dct_bfly32o(out0,out1, a,b,shiftop,s) \
+ { \
+ dct_wadd(sum, a, b); \
+ dct_wsub(dif, a, b); \
+ out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \
+ out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \
+ }
+
+#define dct_pass(shiftop, shift) \
+ { \
+ /* even part */ \
+ int16x8_t sum26 = vaddq_s16(row2, row6); \
+ dct_long_mul(p1e, sum26, rot0_0); \
+ dct_long_mac(t2e, p1e, row6, rot0_1); \
+ dct_long_mac(t3e, p1e, row2, rot0_2); \
+ int16x8_t sum04 = vaddq_s16(row0, row4); \
+ int16x8_t dif04 = vsubq_s16(row0, row4); \
+ dct_widen(t0e, sum04); \
+ dct_widen(t1e, dif04); \
+ dct_wadd(x0, t0e, t3e); \
+ dct_wsub(x3, t0e, t3e); \
+ dct_wadd(x1, t1e, t2e); \
+ dct_wsub(x2, t1e, t2e); \
+ /* odd part */ \
+ int16x8_t sum15 = vaddq_s16(row1, row5); \
+ int16x8_t sum17 = vaddq_s16(row1, row7); \
+ int16x8_t sum35 = vaddq_s16(row3, row5); \
+ int16x8_t sum37 = vaddq_s16(row3, row7); \
+ int16x8_t sumodd = vaddq_s16(sum17, sum35); \
+ dct_long_mul(p5o, sumodd, rot1_0); \
+ dct_long_mac(p1o, p5o, sum17, rot1_1); \
+ dct_long_mac(p2o, p5o, sum35, rot1_2); \
+ dct_long_mul(p3o, sum37, rot2_0); \
+ dct_long_mul(p4o, sum15, rot2_1); \
+ dct_wadd(sump13o, p1o, p3o); \
+ dct_wadd(sump24o, p2o, p4o); \
+ dct_wadd(sump23o, p2o, p3o); \
+ dct_wadd(sump14o, p1o, p4o); \
+ dct_long_mac(x4, sump13o, row7, rot3_0); \
+ dct_long_mac(x5, sump24o, row5, rot3_1); \
+ dct_long_mac(x6, sump23o, row3, rot3_2); \
+ dct_long_mac(x7, sump14o, row1, rot3_3); \
+ dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \
+ dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \
+ dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \
+ dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \
+ }
+
+ // load
+ row0 = vld1q_s16(data + 0*8);
+ row1 = vld1q_s16(data + 1*8);
+ row2 = vld1q_s16(data + 2*8);
+ row3 = vld1q_s16(data + 3*8);
+ row4 = vld1q_s16(data + 4*8);
+ row5 = vld1q_s16(data + 5*8);
+ row6 = vld1q_s16(data + 6*8);
+ row7 = vld1q_s16(data + 7*8);
+
+ // add DC bias
+ row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0));
+
+ // column pass
+ dct_pass(vrshrn_n_s32, 10);
+
+ // 16bit 8x8 transpose
+ {
+// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively.
+// whether compilers actually get this is another story, sadly.
+#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; }
+#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); }
+#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); }
+
+ // pass 1
+ dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6
+ dct_trn16(row2, row3);
+ dct_trn16(row4, row5);
+ dct_trn16(row6, row7);
+
+ // pass 2
+ dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4
+ dct_trn32(row1, row3);
+ dct_trn32(row4, row6);
+ dct_trn32(row5, row7);
+
+ // pass 3
+ dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0
+ dct_trn64(row1, row5);
+ dct_trn64(row2, row6);
+ dct_trn64(row3, row7);
+
+#undef dct_trn16
+#undef dct_trn32
+#undef dct_trn64
+ }
+
+ // row pass
+ // vrshrn_n_s32 only supports shifts up to 16, we need
+ // 17. so do a non-rounding shift of 16 first then follow
+ // up with a rounding shift by 1.
+ dct_pass(vshrn_n_s32, 16);
+
+ {
+ // pack and round
+ uint8x8_t p0 = vqrshrun_n_s16(row0, 1);
+ uint8x8_t p1 = vqrshrun_n_s16(row1, 1);
+ uint8x8_t p2 = vqrshrun_n_s16(row2, 1);
+ uint8x8_t p3 = vqrshrun_n_s16(row3, 1);
+ uint8x8_t p4 = vqrshrun_n_s16(row4, 1);
+ uint8x8_t p5 = vqrshrun_n_s16(row5, 1);
+ uint8x8_t p6 = vqrshrun_n_s16(row6, 1);
+ uint8x8_t p7 = vqrshrun_n_s16(row7, 1);
+
+ // again, these can translate into one instruction, but often don't.
+#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; }
+#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); }
+#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); }
+
+ // sadly can't use interleaved stores here since we only write
+ // 8 bytes to each scan line!
+
+ // 8x8 8-bit transpose pass 1
+ dct_trn8_8(p0, p1);
+ dct_trn8_8(p2, p3);
+ dct_trn8_8(p4, p5);
+ dct_trn8_8(p6, p7);
+
+ // pass 2
+ dct_trn8_16(p0, p2);
+ dct_trn8_16(p1, p3);
+ dct_trn8_16(p4, p6);
+ dct_trn8_16(p5, p7);
+
+ // pass 3
+ dct_trn8_32(p0, p4);
+ dct_trn8_32(p1, p5);
+ dct_trn8_32(p2, p6);
+ dct_trn8_32(p3, p7);
+
+ // store
+ vst1_u8(out, p0); out += out_stride;
+ vst1_u8(out, p1); out += out_stride;
+ vst1_u8(out, p2); out += out_stride;
+ vst1_u8(out, p3); out += out_stride;
+ vst1_u8(out, p4); out += out_stride;
+ vst1_u8(out, p5); out += out_stride;
+ vst1_u8(out, p6); out += out_stride;
+ vst1_u8(out, p7);
+
+#undef dct_trn8_8
+#undef dct_trn8_16
+#undef dct_trn8_32
+ }
+
+#undef dct_long_mul
+#undef dct_long_mac
+#undef dct_widen
+#undef dct_wadd
+#undef dct_wsub
+#undef dct_bfly32o
+#undef dct_pass
+}
+
+#endif // STBI_NEON
+
+#define STBI__MARKER_none 0xff
+// if there's a pending marker from the entropy stream, return that
+// otherwise, fetch from the stream and get a marker. if there's no
+// marker, return 0xff, which is never a valid marker value
+static stbi_uc stbi__get_marker(stbi__jpeg *j)
+{
+ stbi_uc x;
+ if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; }
+ x = stbi__get8(j->s);
+ if (x != 0xff) return STBI__MARKER_none;
+ while (x == 0xff)
+ x = stbi__get8(j->s);
+ return x;
+}
+
+// in each scan, we'll have scan_n components, and the order
+// of the components is specified by order[]
+#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7)
+
+// after a restart interval, stbi__jpeg_reset the entropy decoder and
+// the dc prediction
+static void stbi__jpeg_reset(stbi__jpeg *j)
+{
+ j->code_bits = 0;
+ j->code_buffer = 0;
+ j->nomore = 0;
+ j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0;
+ j->marker = STBI__MARKER_none;
+ j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff;
+ j->eob_run = 0;
+ // no more than 1<<31 MCUs if no restart_interal? that's plenty safe,
+ // since we don't even allow 1<<30 pixels
+}
+
+static int stbi__parse_entropy_coded_data(stbi__jpeg *z)
+{
+ stbi__jpeg_reset(z);
+ if (!z->progressive) {
+ if (z->scan_n == 1) {
+ int i,j;
+ STBI_SIMD_ALIGN(short, data[64]);
+ int n = z->order[0];
+ // non-interleaved data, we just need to process one block at a time,
+ // in trivial scanline order
+ // number of blocks to do just depends on how many actual "pixels" this
+ // component has, independent of interleaved MCU blocking and such
+ int w = (z->img_comp[n].x+7) >> 3;
+ int h = (z->img_comp[n].y+7) >> 3;
+ for (j=0; j < h; ++j) {
+ for (i=0; i < w; ++i) {
+ int ha = z->img_comp[n].ha;
+ if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;
+ z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
+ // every data block is an MCU, so countdown the restart interval
+ if (--z->todo <= 0) {
+ if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+ // if it's NOT a restart, then just bail, so we get corrupt data
+ // rather than no data
+ if (!STBI__RESTART(z->marker)) return 1;
+ stbi__jpeg_reset(z);
+ }
+ }
+ }
+ return 1;
+ } else { // interleaved
+ int i,j,k,x,y;
+ STBI_SIMD_ALIGN(short, data[64]);
+ for (j=0; j < z->img_mcu_y; ++j) {
+ for (i=0; i < z->img_mcu_x; ++i) {
+ // scan an interleaved mcu... process scan_n components in order
+ for (k=0; k < z->scan_n; ++k) {
+ int n = z->order[k];
+ // scan out an mcu's worth of this component; that's just determined
+ // by the basic H and V specified for the component
+ for (y=0; y < z->img_comp[n].v; ++y) {
+ for (x=0; x < z->img_comp[n].h; ++x) {
+ int x2 = (i*z->img_comp[n].h + x)*8;
+ int y2 = (j*z->img_comp[n].v + y)*8;
+ int ha = z->img_comp[n].ha;
+ if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;
+ z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data);
+ }
+ }
+ }
+ // after all interleaved components, that's an interleaved MCU,
+ // so now count down the restart interval
+ if (--z->todo <= 0) {
+ if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+ if (!STBI__RESTART(z->marker)) return 1;
+ stbi__jpeg_reset(z);
+ }
+ }
+ }
+ return 1;
+ }
+ } else {
+ if (z->scan_n == 1) {
+ int i,j;
+ int n = z->order[0];
+ // non-interleaved data, we just need to process one block at a time,
+ // in trivial scanline order
+ // number of blocks to do just depends on how many actual "pixels" this
+ // component has, independent of interleaved MCU blocking and such
+ int w = (z->img_comp[n].x+7) >> 3;
+ int h = (z->img_comp[n].y+7) >> 3;
+ for (j=0; j < h; ++j) {
+ for (i=0; i < w; ++i) {
+ short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
+ if (z->spec_start == 0) {
+ if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
+ return 0;
+ } else {
+ int ha = z->img_comp[n].ha;
+ if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha]))
+ return 0;
+ }
+ // every data block is an MCU, so countdown the restart interval
+ if (--z->todo <= 0) {
+ if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+ if (!STBI__RESTART(z->marker)) return 1;
+ stbi__jpeg_reset(z);
+ }
+ }
+ }
+ return 1;
+ } else { // interleaved
+ int i,j,k,x,y;
+ for (j=0; j < z->img_mcu_y; ++j) {
+ for (i=0; i < z->img_mcu_x; ++i) {
+ // scan an interleaved mcu... process scan_n components in order
+ for (k=0; k < z->scan_n; ++k) {
+ int n = z->order[k];
+ // scan out an mcu's worth of this component; that's just determined
+ // by the basic H and V specified for the component
+ for (y=0; y < z->img_comp[n].v; ++y) {
+ for (x=0; x < z->img_comp[n].h; ++x) {
+ int x2 = (i*z->img_comp[n].h + x);
+ int y2 = (j*z->img_comp[n].v + y);
+ short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w);
+ if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
+ return 0;
+ }
+ }
+ }
+ // after all interleaved components, that's an interleaved MCU,
+ // so now count down the restart interval
+ if (--z->todo <= 0) {
+ if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+ if (!STBI__RESTART(z->marker)) return 1;
+ stbi__jpeg_reset(z);
+ }
+ }
+ }
+ return 1;
+ }
+ }
+}
+
+static void stbi__jpeg_dequantize(short *data, stbi_uc *dequant)
+{
+ int i;
+ for (i=0; i < 64; ++i)
+ data[i] *= dequant[i];
+}
+
+static void stbi__jpeg_finish(stbi__jpeg *z)
+{
+ if (z->progressive) {
+ // dequantize and idct the data
+ int i,j,n;
+ for (n=0; n < z->s->img_n; ++n) {
+ int w = (z->img_comp[n].x+7) >> 3;
+ int h = (z->img_comp[n].y+7) >> 3;
+ for (j=0; j < h; ++j) {
+ for (i=0; i < w; ++i) {
+ short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
+ stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]);
+ z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
+ }
+ }
+ }
+ }
+}
+
+static int stbi__process_marker(stbi__jpeg *z, int m)
+{
+ int L;
+ switch (m) {
+ case STBI__MARKER_none: // no marker found
+ return stbi__err("expected marker","Corrupt JPEG");
+
+ case 0xDD: // DRI - specify restart interval
+ if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG");
+ z->restart_interval = stbi__get16be(z->s);
+ return 1;
+
+ case 0xDB: // DQT - define quantization table
+ L = stbi__get16be(z->s)-2;
+ while (L > 0) {
+ int q = stbi__get8(z->s);
+ int p = q >> 4;
+ int t = q & 15,i;
+ if (p != 0) return stbi__err("bad DQT type","Corrupt JPEG");
+ if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG");
+ for (i=0; i < 64; ++i)
+ z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s);
+ L -= 65;
+ }
+ return L==0;
+
+ case 0xC4: // DHT - define huffman table
+ L = stbi__get16be(z->s)-2;
+ while (L > 0) {
+ stbi_uc *v;
+ int sizes[16],i,n=0;
+ int q = stbi__get8(z->s);
+ int tc = q >> 4;
+ int th = q & 15;
+ if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG");
+ for (i=0; i < 16; ++i) {
+ sizes[i] = stbi__get8(z->s);
+ n += sizes[i];
+ }
+ L -= 17;
+ if (tc == 0) {
+ if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0;
+ v = z->huff_dc[th].values;
+ } else {
+ if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0;
+ v = z->huff_ac[th].values;
+ }
+ for (i=0; i < n; ++i)
+ v[i] = stbi__get8(z->s);
+ if (tc != 0)
+ stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th);
+ L -= n;
+ }
+ return L==0;
+ }
+ // check for comment block or APP blocks
+ if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) {
+ stbi__skip(z->s, stbi__get16be(z->s)-2);
+ return 1;
+ }
+ return 0;
+}
+
+// after we see SOS
+static int stbi__process_scan_header(stbi__jpeg *z)
+{
+ int i;
+ int Ls = stbi__get16be(z->s);
+ z->scan_n = stbi__get8(z->s);
+ if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG");
+ if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG");
+ for (i=0; i < z->scan_n; ++i) {
+ int id = stbi__get8(z->s), which;
+ int q = stbi__get8(z->s);
+ for (which = 0; which < z->s->img_n; ++which)
+ if (z->img_comp[which].id == id)
+ break;
+ if (which == z->s->img_n) return 0; // no match
+ z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG");
+ z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG");
+ z->order[i] = which;
+ }
+
+ {
+ int aa;
+ z->spec_start = stbi__get8(z->s);
+ z->spec_end = stbi__get8(z->s); // should be 63, but might be 0
+ aa = stbi__get8(z->s);
+ z->succ_high = (aa >> 4);
+ z->succ_low = (aa & 15);
+ if (z->progressive) {
+ if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13)
+ return stbi__err("bad SOS", "Corrupt JPEG");
+ } else {
+ if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG");
+ if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG");
+ z->spec_end = 63;
+ }
+ }
+
+ return 1;
+}
+
+static int stbi__process_frame_header(stbi__jpeg *z, int scan)
+{
+ stbi__context *s = z->s;
+ int Lf,p,i,q, h_max=1,v_max=1,c;
+ Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG
+ p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline
+ s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG
+ s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires
+ c = stbi__get8(s);
+ if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires
+ s->img_n = c;
+ for (i=0; i < c; ++i) {
+ z->img_comp[i].data = NULL;
+ z->img_comp[i].linebuf = NULL;
+ }
+
+ if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG");
+
+ z->rgb = 0;
+ for (i=0; i < s->img_n; ++i) {
+ static unsigned char rgb[3] = { 'R', 'G', 'B' };
+ z->img_comp[i].id = stbi__get8(s);
+ if (z->img_comp[i].id != i+1) // JFIF requires
+ if (z->img_comp[i].id != i) { // some version of jpegtran outputs non-JFIF-compliant files!
+ // somethings output this (see http://fileformats.archiveteam.org/wiki/JPEG#Color_format)
+ if (z->img_comp[i].id != rgb[i])
+ return stbi__err("bad component ID","Corrupt JPEG");
+ ++z->rgb;
+ }
+ q = stbi__get8(s);
+ z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG");
+ z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG");
+ z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG");
+ }
+
+ if (scan != STBI__SCAN_load) return 1;
+
+ if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode");
+
+ for (i=0; i < s->img_n; ++i) {
+ if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h;
+ if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v;
+ }
+
+ // compute interleaved mcu info
+ z->img_h_max = h_max;
+ z->img_v_max = v_max;
+ z->img_mcu_w = h_max * 8;
+ z->img_mcu_h = v_max * 8;
+ z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w;
+ z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h;
+
+ for (i=0; i < s->img_n; ++i) {
+ // number of effective pixels (e.g. for non-interleaved MCU)
+ z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max;
+ z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max;
+ // to simplify generation, we'll allocate enough memory to decode
+ // the bogus oversized data from using interleaved MCUs and their
+ // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't
+ // discard the extra data until colorspace conversion
+ z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8;
+ z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8;
+ z->img_comp[i].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15);
+
+ if (z->img_comp[i].raw_data == NULL) {
+ for(--i; i >= 0; --i) {
+ STBI_FREE(z->img_comp[i].raw_data);
+ z->img_comp[i].raw_data = NULL;
+ }
+ return stbi__err("outofmem", "Out of memory");
+ }
+ // align blocks for idct using mmx/sse
+ z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15);
+ z->img_comp[i].linebuf = NULL;
+ if (z->progressive) {
+ z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3;
+ z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3;
+ z->img_comp[i].raw_coeff = STBI_MALLOC(z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof(short) + 15);
+ z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15);
+ } else {
+ z->img_comp[i].coeff = 0;
+ z->img_comp[i].raw_coeff = 0;
+ }
+ }
+
+ return 1;
+}
+
+// use comparisons since in some cases we handle more than one case (e.g. SOF)
+#define stbi__DNL(x) ((x) == 0xdc)
+#define stbi__SOI(x) ((x) == 0xd8)
+#define stbi__EOI(x) ((x) == 0xd9)
+#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2)
+#define stbi__SOS(x) ((x) == 0xda)
+
+#define stbi__SOF_progressive(x) ((x) == 0xc2)
+
+static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)
+{
+ int m;
+ z->marker = STBI__MARKER_none; // initialize cached marker to empty
+ m = stbi__get_marker(z);
+ if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG");
+ if (scan == STBI__SCAN_type) return 1;
+ m = stbi__get_marker(z);
+ while (!stbi__SOF(m)) {
+ if (!stbi__process_marker(z,m)) return 0;
+ m = stbi__get_marker(z);
+ while (m == STBI__MARKER_none) {
+ // some files have extra padding after their blocks, so ok, we'll scan
+ if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG");
+ m = stbi__get_marker(z);
+ }
+ }
+ z->progressive = stbi__SOF_progressive(m);
+ if (!stbi__process_frame_header(z, scan)) return 0;
+ return 1;
+}
+
+// decode image to YCbCr format
+static int stbi__decode_jpeg_image(stbi__jpeg *j)
+{
+ int m;
+ for (m = 0; m < 4; m++) {
+ j->img_comp[m].raw_data = NULL;
+ j->img_comp[m].raw_coeff = NULL;
+ }
+ j->restart_interval = 0;
+ if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0;
+ m = stbi__get_marker(j);
+ while (!stbi__EOI(m)) {
+ if (stbi__SOS(m)) {
+ if (!stbi__process_scan_header(j)) return 0;
+ if (!stbi__parse_entropy_coded_data(j)) return 0;
+ if (j->marker == STBI__MARKER_none ) {
+ // handle 0s at the end of image data from IP Kamera 9060
+ while (!stbi__at_eof(j->s)) {
+ int x = stbi__get8(j->s);
+ if (x == 255) {
+ j->marker = stbi__get8(j->s);
+ break;
+ } else if (x != 0) {
+ return stbi__err("junk before marker", "Corrupt JPEG");
+ }
+ }
+ // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0
+ }
+ } else {
+ if (!stbi__process_marker(j, m)) return 0;
+ }
+ m = stbi__get_marker(j);
+ }
+ if (j->progressive)
+ stbi__jpeg_finish(j);
+ return 1;
+}
+
+// static jfif-centered resampling (across block boundaries)
+
+typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1,
+ int w, int hs);
+
+#define stbi__div4(x) ((stbi_uc) ((x) >> 2))
+
+static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ STBI_NOTUSED(out);
+ STBI_NOTUSED(in_far);
+ STBI_NOTUSED(w);
+ STBI_NOTUSED(hs);
+ return in_near;
+}
+
+static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ // need to generate two samples vertically for every one in input
+ int i;
+ STBI_NOTUSED(hs);
+ for (i=0; i < w; ++i)
+ out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2);
+ return out;
+}
+
+static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ // need to generate two samples horizontally for every one in input
+ int i;
+ stbi_uc *input = in_near;
+
+ if (w == 1) {
+ // if only one sample, can't do any interpolation
+ out[0] = out[1] = input[0];
+ return out;
+ }
+
+ out[0] = input[0];
+ out[1] = stbi__div4(input[0]*3 + input[1] + 2);
+ for (i=1; i < w-1; ++i) {
+ int n = 3*input[i]+2;
+ out[i*2+0] = stbi__div4(n+input[i-1]);
+ out[i*2+1] = stbi__div4(n+input[i+1]);
+ }
+ out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2);
+ out[i*2+1] = input[w-1];
+
+ STBI_NOTUSED(in_far);
+ STBI_NOTUSED(hs);
+
+ return out;
+}
+
+#define stbi__div16(x) ((stbi_uc) ((x) >> 4))
+
+static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ // need to generate 2x2 samples for every one in input
+ int i,t0,t1;
+ if (w == 1) {
+ out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
+ return out;
+ }
+
+ t1 = 3*in_near[0] + in_far[0];
+ out[0] = stbi__div4(t1+2);
+ for (i=1; i < w; ++i) {
+ t0 = t1;
+ t1 = 3*in_near[i]+in_far[i];
+ out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
+ out[i*2 ] = stbi__div16(3*t1 + t0 + 8);
+ }
+ out[w*2-1] = stbi__div4(t1+2);
+
+ STBI_NOTUSED(hs);
+
+ return out;
+}
+
+#if defined(STBI_SSE2) || defined(STBI_NEON)
+static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ // need to generate 2x2 samples for every one in input
+ int i=0,t0,t1;
+
+ if (w == 1) {
+ out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
+ return out;
+ }
+
+ t1 = 3*in_near[0] + in_far[0];
+ // process groups of 8 pixels for as long as we can.
+ // note we can't handle the last pixel in a row in this loop
+ // because we need to handle the filter boundary conditions.
+ for (; i < ((w-1) & ~7); i += 8) {
+#if defined(STBI_SSE2)
+ // load and perform the vertical filtering pass
+ // this uses 3*x + y = 4*x + (y - x)
+ __m128i zero = _mm_setzero_si128();
+ __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i));
+ __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i));
+ __m128i farw = _mm_unpacklo_epi8(farb, zero);
+ __m128i nearw = _mm_unpacklo_epi8(nearb, zero);
+ __m128i diff = _mm_sub_epi16(farw, nearw);
+ __m128i nears = _mm_slli_epi16(nearw, 2);
+ __m128i curr = _mm_add_epi16(nears, diff); // current row
+
+ // horizontal filter works the same based on shifted vers of current
+ // row. "prev" is current row shifted right by 1 pixel; we need to
+ // insert the previous pixel value (from t1).
+ // "next" is current row shifted left by 1 pixel, with first pixel
+ // of next block of 8 pixels added in.
+ __m128i prv0 = _mm_slli_si128(curr, 2);
+ __m128i nxt0 = _mm_srli_si128(curr, 2);
+ __m128i prev = _mm_insert_epi16(prv0, t1, 0);
+ __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7);
+
+ // horizontal filter, polyphase implementation since it's convenient:
+ // even pixels = 3*cur + prev = cur*4 + (prev - cur)
+ // odd pixels = 3*cur + next = cur*4 + (next - cur)
+ // note the shared term.
+ __m128i bias = _mm_set1_epi16(8);
+ __m128i curs = _mm_slli_epi16(curr, 2);
+ __m128i prvd = _mm_sub_epi16(prev, curr);
+ __m128i nxtd = _mm_sub_epi16(next, curr);
+ __m128i curb = _mm_add_epi16(curs, bias);
+ __m128i even = _mm_add_epi16(prvd, curb);
+ __m128i odd = _mm_add_epi16(nxtd, curb);
+
+ // interleave even and odd pixels, then undo scaling.
+ __m128i int0 = _mm_unpacklo_epi16(even, odd);
+ __m128i int1 = _mm_unpackhi_epi16(even, odd);
+ __m128i de0 = _mm_srli_epi16(int0, 4);
+ __m128i de1 = _mm_srli_epi16(int1, 4);
+
+ // pack and write output
+ __m128i outv = _mm_packus_epi16(de0, de1);
+ _mm_storeu_si128((__m128i *) (out + i*2), outv);
+#elif defined(STBI_NEON)
+ // load and perform the vertical filtering pass
+ // this uses 3*x + y = 4*x + (y - x)
+ uint8x8_t farb = vld1_u8(in_far + i);
+ uint8x8_t nearb = vld1_u8(in_near + i);
+ int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb));
+ int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2));
+ int16x8_t curr = vaddq_s16(nears, diff); // current row
+
+ // horizontal filter works the same based on shifted vers of current
+ // row. "prev" is current row shifted right by 1 pixel; we need to
+ // insert the previous pixel value (from t1).
+ // "next" is current row shifted left by 1 pixel, with first pixel
+ // of next block of 8 pixels added in.
+ int16x8_t prv0 = vextq_s16(curr, curr, 7);
+ int16x8_t nxt0 = vextq_s16(curr, curr, 1);
+ int16x8_t prev = vsetq_lane_s16(t1, prv0, 0);
+ int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7);
+
+ // horizontal filter, polyphase implementation since it's convenient:
+ // even pixels = 3*cur + prev = cur*4 + (prev - cur)
+ // odd pixels = 3*cur + next = cur*4 + (next - cur)
+ // note the shared term.
+ int16x8_t curs = vshlq_n_s16(curr, 2);
+ int16x8_t prvd = vsubq_s16(prev, curr);
+ int16x8_t nxtd = vsubq_s16(next, curr);
+ int16x8_t even = vaddq_s16(curs, prvd);
+ int16x8_t odd = vaddq_s16(curs, nxtd);
+
+ // undo scaling and round, then store with even/odd phases interleaved
+ uint8x8x2_t o;
+ o.val[0] = vqrshrun_n_s16(even, 4);
+ o.val[1] = vqrshrun_n_s16(odd, 4);
+ vst2_u8(out + i*2, o);
+#endif
+
+ // "previous" value for next iter
+ t1 = 3*in_near[i+7] + in_far[i+7];
+ }
+
+ t0 = t1;
+ t1 = 3*in_near[i] + in_far[i];
+ out[i*2] = stbi__div16(3*t1 + t0 + 8);
+
+ for (++i; i < w; ++i) {
+ t0 = t1;
+ t1 = 3*in_near[i]+in_far[i];
+ out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
+ out[i*2 ] = stbi__div16(3*t1 + t0 + 8);
+ }
+ out[w*2-1] = stbi__div4(t1+2);
+
+ STBI_NOTUSED(hs);
+
+ return out;
+}
+#endif
+
+static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+ // resample with nearest-neighbor
+ int i,j;
+ STBI_NOTUSED(in_far);
+ for (i=0; i < w; ++i)
+ for (j=0; j < hs; ++j)
+ out[i*hs+j] = in_near[i];
+ return out;
+}
+
+#ifdef STBI_JPEG_OLD
+// this is the same YCbCr-to-RGB calculation that stb_image has used
+// historically before the algorithm changes in 1.49
+#define float2fixed(x) ((int) ((x) * 65536 + 0.5))
+static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step)
+{
+ int i;
+ for (i=0; i < count; ++i) {
+ int y_fixed = (y[i] << 16) + 32768; // rounding
+ int r,g,b;
+ int cr = pcr[i] - 128;
+ int cb = pcb[i] - 128;
+ r = y_fixed + cr*float2fixed(1.40200f);
+ g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f);
+ b = y_fixed + cb*float2fixed(1.77200f);
+ r >>= 16;
+ g >>= 16;
+ b >>= 16;
+ if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
+ if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
+ if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
+ out[0] = (stbi_uc)r;
+ out[1] = (stbi_uc)g;
+ out[2] = (stbi_uc)b;
+ out[3] = 255;
+ out += step;
+ }
+}
+#else
+// this is a reduced-precision calculation of YCbCr-to-RGB introduced
+// to make sure the code produces the same results in both SIMD and scalar
+#define float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8)
+static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step)
+{
+ int i;
+ for (i=0; i < count; ++i) {
+ int y_fixed = (y[i] << 20) + (1<<19); // rounding
+ int r,g,b;
+ int cr = pcr[i] - 128;
+ int cb = pcb[i] - 128;
+ r = y_fixed + cr* float2fixed(1.40200f);
+ g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000);
+ b = y_fixed + cb* float2fixed(1.77200f);
+ r >>= 20;
+ g >>= 20;
+ b >>= 20;
+ if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
+ if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
+ if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
+ out[0] = (stbi_uc)r;
+ out[1] = (stbi_uc)g;
+ out[2] = (stbi_uc)b;
+ out[3] = 255;
+ out += step;
+ }
+}
+#endif
+
+#if defined(STBI_SSE2) || defined(STBI_NEON)
+static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step)
+{
+ int i = 0;
+
+#ifdef STBI_SSE2
+ // step == 3 is pretty ugly on the final interleave, and i'm not convinced
+ // it's useful in practice (you wouldn't use it for textures, for example).
+ // so just accelerate step == 4 case.
+ if (step == 4) {
+ // this is a fairly straightforward implementation and not super-optimized.
+ __m128i signflip = _mm_set1_epi8(-0x80);
+ __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f));
+ __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f));
+ __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f));
+ __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f));
+ __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128);
+ __m128i xw = _mm_set1_epi16(255); // alpha channel
+
+ for (; i+7 < count; i += 8) {
+ // load
+ __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i));
+ __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i));
+ __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i));
+ __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128
+ __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128
+
+ // unpack to short (and left-shift cr, cb by 8)
+ __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes);
+ __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased);
+ __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased);
+
+ // color transform
+ __m128i yws = _mm_srli_epi16(yw, 4);
+ __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw);
+ __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw);
+ __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1);
+ __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1);
+ __m128i rws = _mm_add_epi16(cr0, yws);
+ __m128i gwt = _mm_add_epi16(cb0, yws);
+ __m128i bws = _mm_add_epi16(yws, cb1);
+ __m128i gws = _mm_add_epi16(gwt, cr1);
+
+ // descale
+ __m128i rw = _mm_srai_epi16(rws, 4);
+ __m128i bw = _mm_srai_epi16(bws, 4);
+ __m128i gw = _mm_srai_epi16(gws, 4);
+
+ // back to byte, set up for transpose
+ __m128i brb = _mm_packus_epi16(rw, bw);
+ __m128i gxb = _mm_packus_epi16(gw, xw);
+
+ // transpose to interleave channels
+ __m128i t0 = _mm_unpacklo_epi8(brb, gxb);
+ __m128i t1 = _mm_unpackhi_epi8(brb, gxb);
+ __m128i o0 = _mm_unpacklo_epi16(t0, t1);
+ __m128i o1 = _mm_unpackhi_epi16(t0, t1);
+
+ // store
+ _mm_storeu_si128((__m128i *) (out + 0), o0);
+ _mm_storeu_si128((__m128i *) (out + 16), o1);
+ out += 32;
+ }
+ }
+#endif
+
+#ifdef STBI_NEON
+ // in this version, step=3 support would be easy to add. but is there demand?
+ if (step == 4) {
+ // this is a fairly straightforward implementation and not super-optimized.
+ uint8x8_t signflip = vdup_n_u8(0x80);
+ int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f));
+ int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f));
+ int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f));
+ int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f));
+
+ for (; i+7 < count; i += 8) {
+ // load
+ uint8x8_t y_bytes = vld1_u8(y + i);
+ uint8x8_t cr_bytes = vld1_u8(pcr + i);
+ uint8x8_t cb_bytes = vld1_u8(pcb + i);
+ int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip));
+ int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip));
+
+ // expand to s16
+ int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4));
+ int16x8_t crw = vshll_n_s8(cr_biased, 7);
+ int16x8_t cbw = vshll_n_s8(cb_biased, 7);
+
+ // color transform
+ int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0);
+ int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0);
+ int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1);
+ int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1);
+ int16x8_t rws = vaddq_s16(yws, cr0);
+ int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1);
+ int16x8_t bws = vaddq_s16(yws, cb1);
+
+ // undo scaling, round, convert to byte
+ uint8x8x4_t o;
+ o.val[0] = vqrshrun_n_s16(rws, 4);
+ o.val[1] = vqrshrun_n_s16(gws, 4);
+ o.val[2] = vqrshrun_n_s16(bws, 4);
+ o.val[3] = vdup_n_u8(255);
+
+ // store, interleaving r/g/b/a
+ vst4_u8(out, o);
+ out += 8*4;
+ }
+ }
+#endif
+
+ for (; i < count; ++i) {
+ int y_fixed = (y[i] << 20) + (1<<19); // rounding
+ int r,g,b;
+ int cr = pcr[i] - 128;
+ int cb = pcb[i] - 128;
+ r = y_fixed + cr* float2fixed(1.40200f);
+ g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000);
+ b = y_fixed + cb* float2fixed(1.77200f);
+ r >>= 20;
+ g >>= 20;
+ b >>= 20;
+ if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
+ if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
+ if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
+ out[0] = (stbi_uc)r;
+ out[1] = (stbi_uc)g;
+ out[2] = (stbi_uc)b;
+ out[3] = 255;
+ out += step;
+ }
+}
+#endif
+
+// set up the kernels
+static void stbi__setup_jpeg(stbi__jpeg *j)
+{
+ j->idct_block_kernel = stbi__idct_block;
+ j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row;
+ j->resample_row_hv_2_kernel = stbi__resample_row_hv_2;
+
+#ifdef STBI_SSE2
+ if (stbi__sse2_available()) {
+ j->idct_block_kernel = stbi__idct_simd;
+ #ifndef STBI_JPEG_OLD
+ j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
+ #endif
+ j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
+ }
+#endif
+
+#ifdef STBI_NEON
+ j->idct_block_kernel = stbi__idct_simd;
+ #ifndef STBI_JPEG_OLD
+ j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
+ #endif
+ j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
+#endif
+}
+
+// clean up the temporary component buffers
+static void stbi__cleanup_jpeg(stbi__jpeg *j)
+{
+ int i;
+ for (i=0; i < j->s->img_n; ++i) {
+ if (j->img_comp[i].raw_data) {
+ STBI_FREE(j->img_comp[i].raw_data);
+ j->img_comp[i].raw_data = NULL;
+ j->img_comp[i].data = NULL;
+ }
+ if (j->img_comp[i].raw_coeff) {
+ STBI_FREE(j->img_comp[i].raw_coeff);
+ j->img_comp[i].raw_coeff = 0;
+ j->img_comp[i].coeff = 0;
+ }
+ if (j->img_comp[i].linebuf) {
+ STBI_FREE(j->img_comp[i].linebuf);
+ j->img_comp[i].linebuf = NULL;
+ }
+ }
+}
+
+typedef struct
+{
+ resample_row_func resample;
+ stbi_uc *line0,*line1;
+ int hs,vs; // expansion factor in each axis
+ int w_lores; // horizontal pixels pre-expansion
+ int ystep; // how far through vertical expansion we are
+ int ypos; // which pre-expansion row we're on
+} stbi__resample;
+
+static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp)
+{
+ int n, decode_n;
+ z->s->img_n = 0; // make stbi__cleanup_jpeg safe
+
+ // validate req_comp
+ if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
+
+ // load a jpeg image from whichever source, but leave in YCbCr format
+ if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; }
+
+ // determine actual number of components to generate
+ n = req_comp ? req_comp : z->s->img_n;
+
+ if (z->s->img_n == 3 && n < 3)
+ decode_n = 1;
+ else
+ decode_n = z->s->img_n;
+
+ // resample and color-convert
+ {
+ int k;
+ unsigned int i,j;
+ stbi_uc *output;
+ stbi_uc *coutput[4];
+
+ stbi__resample res_comp[4];
+
+ for (k=0; k < decode_n; ++k) {
+ stbi__resample *r = &res_comp[k];
+
+ // allocate line buffer big enough for upsampling off the edges
+ // with upsample factor of 4
+ z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3);
+ if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
+
+ r->hs = z->img_h_max / z->img_comp[k].h;
+ r->vs = z->img_v_max / z->img_comp[k].v;
+ r->ystep = r->vs >> 1;
+ r->w_lores = (z->s->img_x + r->hs-1) / r->hs;
+ r->ypos = 0;
+ r->line0 = r->line1 = z->img_comp[k].data;
+
+ if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1;
+ else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2;
+ else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2;
+ else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel;
+ else r->resample = stbi__resample_row_generic;
+ }
+
+ // can't error after this so, this is safe
+ output = (stbi_uc *) stbi__malloc(n * z->s->img_x * z->s->img_y + 1);
+ if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
+
+ // now go ahead and resample
+ for (j=0; j < z->s->img_y; ++j) {
+ stbi_uc *out = output + n * z->s->img_x * j;
+ for (k=0; k < decode_n; ++k) {
+ stbi__resample *r = &res_comp[k];
+ int y_bot = r->ystep >= (r->vs >> 1);
+ coutput[k] = r->resample(z->img_comp[k].linebuf,
+ y_bot ? r->line1 : r->line0,
+ y_bot ? r->line0 : r->line1,
+ r->w_lores, r->hs);
+ if (++r->ystep >= r->vs) {
+ r->ystep = 0;
+ r->line0 = r->line1;
+ if (++r->ypos < z->img_comp[k].y)
+ r->line1 += z->img_comp[k].w2;
+ }
+ }
+ if (n >= 3) {
+ stbi_uc *y = coutput[0];
+ if (z->s->img_n == 3) {
+ if (z->rgb == 3) {
+ for (i=0; i < z->s->img_x; ++i) {
+ out[0] = y[i];
+ out[1] = coutput[1][i];
+ out[2] = coutput[2][i];
+ out[3] = 255;
+ out += n;
+ }
+ } else {
+ z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+ }
+ } else
+ for (i=0; i < z->s->img_x; ++i) {
+ out[0] = out[1] = out[2] = y[i];
+ out[3] = 255; // not used if n==3
+ out += n;
+ }
+ } else {
+ stbi_uc *y = coutput[0];
+ if (n == 1)
+ for (i=0; i < z->s->img_x; ++i) out[i] = y[i];
+ else
+ for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255;
+ }
+ }
+ stbi__cleanup_jpeg(z);
+ *out_x = z->s->img_x;
+ *out_y = z->s->img_y;
+ if (comp) *comp = z->s->img_n; // report original components, not output
+ return output;
+ }
+}
+
+static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ unsigned char* result;
+ stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));
+ j->s = s;
+ stbi__setup_jpeg(j);
+ result = load_jpeg_image(j, x,y,comp,req_comp);
+ STBI_FREE(j);
+ return result;
+}
+
+static int stbi__jpeg_test(stbi__context *s)
+{
+ int r;
+ stbi__jpeg j;
+ j.s = s;
+ stbi__setup_jpeg(&j);
+ r = stbi__decode_jpeg_header(&j, STBI__SCAN_type);
+ stbi__rewind(s);
+ return r;
+}
+
+static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp)
+{
+ if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) {
+ stbi__rewind( j->s );
+ return 0;
+ }
+ if (x) *x = j->s->img_x;
+ if (y) *y = j->s->img_y;
+ if (comp) *comp = j->s->img_n;
+ return 1;
+}
+
+static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ int result;
+ stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg)));
+ j->s = s;
+ result = stbi__jpeg_info_raw(j, x, y, comp);
+ STBI_FREE(j);
+ return result;
+}
+#endif
+
+// public domain zlib decode v0.2 Sean Barrett 2006-11-18
+// simple implementation
+// - all input must be provided in an upfront buffer
+// - all output is written to a single output buffer (can malloc/realloc)
+// performance
+// - fast huffman
+
+#ifndef STBI_NO_ZLIB
+
+// fast-way is faster to check than jpeg huffman, but slow way is slower
+#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables
+#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1)
+
+// zlib-style huffman encoding
+// (jpegs packs from left, zlib from right, so can't share code)
+typedef struct
+{
+ stbi__uint16 fast[1 << STBI__ZFAST_BITS];
+ stbi__uint16 firstcode[16];
+ int maxcode[17];
+ stbi__uint16 firstsymbol[16];
+ stbi_uc size[288];
+ stbi__uint16 value[288];
+} stbi__zhuffman;
+
+stbi_inline static int stbi__bitreverse16(int n)
+{
+ n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1);
+ n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2);
+ n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4);
+ n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8);
+ return n;
+}
+
+stbi_inline static int stbi__bit_reverse(int v, int bits)
+{
+ STBI_ASSERT(bits <= 16);
+ // to bit reverse n bits, reverse 16 and shift
+ // e.g. 11 bits, bit reverse and shift away 5
+ return stbi__bitreverse16(v) >> (16-bits);
+}
+
+static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num)
+{
+ int i,k=0;
+ int code, next_code[16], sizes[17];
+
+ // DEFLATE spec for generating codes
+ memset(sizes, 0, sizeof(sizes));
+ memset(z->fast, 0, sizeof(z->fast));
+ for (i=0; i < num; ++i)
+ ++sizes[sizelist[i]];
+ sizes[0] = 0;
+ for (i=1; i < 16; ++i)
+ if (sizes[i] > (1 << i))
+ return stbi__err("bad sizes", "Corrupt PNG");
+ code = 0;
+ for (i=1; i < 16; ++i) {
+ next_code[i] = code;
+ z->firstcode[i] = (stbi__uint16) code;
+ z->firstsymbol[i] = (stbi__uint16) k;
+ code = (code + sizes[i]);
+ if (sizes[i])
+ if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG");
+ z->maxcode[i] = code << (16-i); // preshift for inner loop
+ code <<= 1;
+ k += sizes[i];
+ }
+ z->maxcode[16] = 0x10000; // sentinel
+ for (i=0; i < num; ++i) {
+ int s = sizelist[i];
+ if (s) {
+ int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s];
+ stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i);
+ z->size [c] = (stbi_uc ) s;
+ z->value[c] = (stbi__uint16) i;
+ if (s <= STBI__ZFAST_BITS) {
+ int j = stbi__bit_reverse(next_code[s],s);
+ while (j < (1 << STBI__ZFAST_BITS)) {
+ z->fast[j] = fastv;
+ j += (1 << s);
+ }
+ }
+ ++next_code[s];
+ }
+ }
+ return 1;
+}
+
+// zlib-from-memory implementation for PNG reading
+// because PNG allows splitting the zlib stream arbitrarily,
+// and it's annoying structurally to have PNG call ZLIB call PNG,
+// we require PNG read all the IDATs and combine them into a single
+// memory buffer
+
+typedef struct
+{
+ stbi_uc *zbuffer, *zbuffer_end;
+ int num_bits;
+ stbi__uint32 code_buffer;
+
+ char *zout;
+ char *zout_start;
+ char *zout_end;
+ int z_expandable;
+
+ stbi__zhuffman z_length, z_distance;
+} stbi__zbuf;
+
+stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z)
+{
+ if (z->zbuffer >= z->zbuffer_end) return 0;
+ return *z->zbuffer++;
+}
+
+static void stbi__fill_bits(stbi__zbuf *z)
+{
+ do {
+ STBI_ASSERT(z->code_buffer < (1U << z->num_bits));
+ z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits;
+ z->num_bits += 8;
+ } while (z->num_bits <= 24);
+}
+
+stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n)
+{
+ unsigned int k;
+ if (z->num_bits < n) stbi__fill_bits(z);
+ k = z->code_buffer & ((1 << n) - 1);
+ z->code_buffer >>= n;
+ z->num_bits -= n;
+ return k;
+}
+
+static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z)
+{
+ int b,s,k;
+ // not resolved by fast table, so compute it the slow way
+ // use jpeg approach, which requires MSbits at top
+ k = stbi__bit_reverse(a->code_buffer, 16);
+ for (s=STBI__ZFAST_BITS+1; ; ++s)
+ if (k < z->maxcode[s])
+ break;
+ if (s == 16) return -1; // invalid code!
+ // code size is s, so:
+ b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s];
+ STBI_ASSERT(z->size[b] == s);
+ a->code_buffer >>= s;
+ a->num_bits -= s;
+ return z->value[b];
+}
+
+stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
+{
+ int b,s;
+ if (a->num_bits < 16) stbi__fill_bits(a);
+ b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
+ if (b) {
+ s = b >> 9;
+ a->code_buffer >>= s;
+ a->num_bits -= s;
+ return b & 511;
+ }
+ return stbi__zhuffman_decode_slowpath(a, z);
+}
+
+static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes
+{
+ char *q;
+ int cur, limit, old_limit;
+ z->zout = zout;
+ if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG");
+ cur = (int) (z->zout - z->zout_start);
+ limit = old_limit = (int) (z->zout_end - z->zout_start);
+ while (cur + n > limit)
+ limit *= 2;
+ q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit);
+ STBI_NOTUSED(old_limit);
+ if (q == NULL) return stbi__err("outofmem", "Out of memory");
+ z->zout_start = q;
+ z->zout = q + cur;
+ z->zout_end = q + limit;
+ return 1;
+}
+
+static int stbi__zlength_base[31] = {
+ 3,4,5,6,7,8,9,10,11,13,
+ 15,17,19,23,27,31,35,43,51,59,
+ 67,83,99,115,131,163,195,227,258,0,0 };
+
+static int stbi__zlength_extra[31]=
+{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 };
+
+static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,
+257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0};
+
+static int stbi__zdist_extra[32] =
+{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
+
+static int stbi__parse_huffman_block(stbi__zbuf *a)
+{
+ char *zout = a->zout;
+ for(;;) {
+ int z = stbi__zhuffman_decode(a, &a->z_length);
+ if (z < 256) {
+ if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes
+ if (zout >= a->zout_end) {
+ if (!stbi__zexpand(a, zout, 1)) return 0;
+ zout = a->zout;
+ }
+ *zout++ = (char) z;
+ } else {
+ stbi_uc *p;
+ int len,dist;
+ if (z == 256) {
+ a->zout = zout;
+ return 1;
+ }
+ z -= 257;
+ len = stbi__zlength_base[z];
+ if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]);
+ z = stbi__zhuffman_decode(a, &a->z_distance);
+ if (z < 0) return stbi__err("bad huffman code","Corrupt PNG");
+ dist = stbi__zdist_base[z];
+ if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);
+ if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG");
+ if (zout + len > a->zout_end) {
+ if (!stbi__zexpand(a, zout, len)) return 0;
+ zout = a->zout;
+ }
+ p = (stbi_uc *) (zout - dist);
+ if (dist == 1) { // run of one byte; common in images.
+ stbi_uc v = *p;
+ if (len) { do *zout++ = v; while (--len); }
+ } else {
+ if (len) { do *zout++ = *p++; while (--len); }
+ }
+ }
+ }
+}
+
+static int stbi__compute_huffman_codes(stbi__zbuf *a)
+{
+ static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 };
+ stbi__zhuffman z_codelength;
+ stbi_uc lencodes[286+32+137];//padding for maximum single op
+ stbi_uc codelength_sizes[19];
+ int i,n;
+
+ int hlit = stbi__zreceive(a,5) + 257;
+ int hdist = stbi__zreceive(a,5) + 1;
+ int hclen = stbi__zreceive(a,4) + 4;
+
+ memset(codelength_sizes, 0, sizeof(codelength_sizes));
+ for (i=0; i < hclen; ++i) {
+ int s = stbi__zreceive(a,3);
+ codelength_sizes[length_dezigzag[i]] = (stbi_uc) s;
+ }
+ if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0;
+
+ n = 0;
+ while (n < hlit + hdist) {
+ int c = stbi__zhuffman_decode(a, &z_codelength);
+ if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG");
+ if (c < 16)
+ lencodes[n++] = (stbi_uc) c;
+ else if (c == 16) {
+ c = stbi__zreceive(a,2)+3;
+ memset(lencodes+n, lencodes[n-1], c);
+ n += c;
+ } else if (c == 17) {
+ c = stbi__zreceive(a,3)+3;
+ memset(lencodes+n, 0, c);
+ n += c;
+ } else {
+ STBI_ASSERT(c == 18);
+ c = stbi__zreceive(a,7)+11;
+ memset(lencodes+n, 0, c);
+ n += c;
+ }
+ }
+ if (n != hlit+hdist) return stbi__err("bad codelengths","Corrupt PNG");
+ if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0;
+ if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0;
+ return 1;
+}
+
+static int stbi__parse_uncompressed_block(stbi__zbuf *a)
+{
+ stbi_uc header[4];
+ int len,nlen,k;
+ if (a->num_bits & 7)
+ stbi__zreceive(a, a->num_bits & 7); // discard
+ // drain the bit-packed data into header
+ k = 0;
+ while (a->num_bits > 0) {
+ header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check
+ a->code_buffer >>= 8;
+ a->num_bits -= 8;
+ }
+ STBI_ASSERT(a->num_bits == 0);
+ // now fill header the normal way
+ while (k < 4)
+ header[k++] = stbi__zget8(a);
+ len = header[1] * 256 + header[0];
+ nlen = header[3] * 256 + header[2];
+ if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG");
+ if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG");
+ if (a->zout + len > a->zout_end)
+ if (!stbi__zexpand(a, a->zout, len)) return 0;
+ memcpy(a->zout, a->zbuffer, len);
+ a->zbuffer += len;
+ a->zout += len;
+ return 1;
+}
+
+static int stbi__parse_zlib_header(stbi__zbuf *a)
+{
+ int cmf = stbi__zget8(a);
+ int cm = cmf & 15;
+ /* int cinfo = cmf >> 4; */
+ int flg = stbi__zget8(a);
+ if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec
+ if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png
+ if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png
+ // window = 1 << (8 + cinfo)... but who cares, we fully buffer output
+ return 1;
+}
+
+// @TODO: should statically initialize these for optimal thread safety
+static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32];
+static void stbi__init_zdefaults(void)
+{
+ int i; // use <= to match clearly with spec
+ for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8;
+ for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9;
+ for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7;
+ for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8;
+
+ for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5;
+}
+
+static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
+{
+ int final, type;
+ if (parse_header)
+ if (!stbi__parse_zlib_header(a)) return 0;
+ a->num_bits = 0;
+ a->code_buffer = 0;
+ do {
+ final = stbi__zreceive(a,1);
+ type = stbi__zreceive(a,2);
+ if (type == 0) {
+ if (!stbi__parse_uncompressed_block(a)) return 0;
+ } else if (type == 3) {
+ return 0;
+ } else {
+ if (type == 1) {
+ // use fixed code lengths
+ if (!stbi__zdefault_distance[31]) stbi__init_zdefaults();
+ if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0;
+ if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0;
+ } else {
+ if (!stbi__compute_huffman_codes(a)) return 0;
+ }
+ if (!stbi__parse_huffman_block(a)) return 0;
+ }
+ } while (!final);
+ return 1;
+}
+
+static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header)
+{
+ a->zout_start = obuf;
+ a->zout = obuf;
+ a->zout_end = obuf + olen;
+ a->z_expandable = exp;
+
+ return stbi__parse_zlib(a, parse_header);
+}
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen)
+{
+ stbi__zbuf a;
+ char *p = (char *) stbi__malloc(initial_size);
+ if (p == NULL) return NULL;
+ a.zbuffer = (stbi_uc *) buffer;
+ a.zbuffer_end = (stbi_uc *) buffer + len;
+ if (stbi__do_zlib(&a, p, initial_size, 1, 1)) {
+ if (outlen) *outlen = (int) (a.zout - a.zout_start);
+ return a.zout_start;
+ } else {
+ STBI_FREE(a.zout_start);
+ return NULL;
+ }
+}
+
+STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen)
+{
+ return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);
+}
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header)
+{
+ stbi__zbuf a;
+ char *p = (char *) stbi__malloc(initial_size);
+ if (p == NULL) return NULL;
+ a.zbuffer = (stbi_uc *) buffer;
+ a.zbuffer_end = (stbi_uc *) buffer + len;
+ if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) {
+ if (outlen) *outlen = (int) (a.zout - a.zout_start);
+ return a.zout_start;
+ } else {
+ STBI_FREE(a.zout_start);
+ return NULL;
+ }
+}
+
+STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen)
+{
+ stbi__zbuf a;
+ a.zbuffer = (stbi_uc *) ibuffer;
+ a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
+ if (stbi__do_zlib(&a, obuffer, olen, 0, 1))
+ return (int) (a.zout - a.zout_start);
+ else
+ return -1;
+}
+
+STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen)
+{
+ stbi__zbuf a;
+ char *p = (char *) stbi__malloc(16384);
+ if (p == NULL) return NULL;
+ a.zbuffer = (stbi_uc *) buffer;
+ a.zbuffer_end = (stbi_uc *) buffer+len;
+ if (stbi__do_zlib(&a, p, 16384, 1, 0)) {
+ if (outlen) *outlen = (int) (a.zout - a.zout_start);
+ return a.zout_start;
+ } else {
+ STBI_FREE(a.zout_start);
+ return NULL;
+ }
+}
+
+STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen)
+{
+ stbi__zbuf a;
+ a.zbuffer = (stbi_uc *) ibuffer;
+ a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
+ if (stbi__do_zlib(&a, obuffer, olen, 0, 0))
+ return (int) (a.zout - a.zout_start);
+ else
+ return -1;
+}
+#endif
+
+// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18
+// simple implementation
+// - only 8-bit samples
+// - no CRC checking
+// - allocates lots of intermediate memory
+// - avoids problem of streaming data between subsystems
+// - avoids explicit window management
+// performance
+// - uses stb_zlib, a PD zlib implementation with fast huffman decoding
+
+#ifndef STBI_NO_PNG
+typedef struct
+{
+ stbi__uint32 length;
+ stbi__uint32 type;
+} stbi__pngchunk;
+
+static stbi__pngchunk stbi__get_chunk_header(stbi__context *s)
+{
+ stbi__pngchunk c;
+ c.length = stbi__get32be(s);
+ c.type = stbi__get32be(s);
+ return c;
+}
+
+static int stbi__check_png_header(stbi__context *s)
+{
+ static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 };
+ int i;
+ for (i=0; i < 8; ++i)
+ if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG");
+ return 1;
+}
+
+typedef struct
+{
+ stbi__context *s;
+ stbi_uc *idata, *expanded, *out;
+ int depth;
+} stbi__png;
+
+
+enum {
+ STBI__F_none=0,
+ STBI__F_sub=1,
+ STBI__F_up=2,
+ STBI__F_avg=3,
+ STBI__F_paeth=4,
+ // synthetic filters used for first scanline to avoid needing a dummy row of 0s
+ STBI__F_avg_first,
+ STBI__F_paeth_first
+};
+
+static stbi_uc first_row_filter[5] =
+{
+ STBI__F_none,
+ STBI__F_sub,
+ STBI__F_none,
+ STBI__F_avg_first,
+ STBI__F_paeth_first
+};
+
+static int stbi__paeth(int a, int b, int c)
+{
+ int p = a + b - c;
+ int pa = abs(p-a);
+ int pb = abs(p-b);
+ int pc = abs(p-c);
+ if (pa <= pb && pa <= pc) return a;
+ if (pb <= pc) return b;
+ return c;
+}
+
+static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
+
+// create the png data from post-deflated data
+static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)
+{
+ int bytes = (depth == 16? 2 : 1);
+ stbi__context *s = a->s;
+ stbi__uint32 i,j,stride = x*out_n*bytes;
+ stbi__uint32 img_len, img_width_bytes;
+ int k;
+ int img_n = s->img_n; // copy it into a local for later
+
+ int output_bytes = out_n*bytes;
+ int filter_bytes = img_n*bytes;
+ int width = x;
+
+ STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1);
+ a->out = (stbi_uc *) stbi__malloc(x * y * output_bytes); // extra bytes to write off the end into
+ if (!a->out) return stbi__err("outofmem", "Out of memory");
+
+ img_width_bytes = (((img_n * x * depth) + 7) >> 3);
+ img_len = (img_width_bytes + 1) * y;
+ if (s->img_x == x && s->img_y == y) {
+ if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG");
+ } else { // interlaced:
+ if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
+ }
+
+ for (j=0; j < y; ++j) {
+ stbi_uc *cur = a->out + stride*j;
+ stbi_uc *prior = cur - stride;
+ int filter = *raw++;
+
+ if (filter > 4)
+ return stbi__err("invalid filter","Corrupt PNG");
+
+ if (depth < 8) {
+ STBI_ASSERT(img_width_bytes <= x);
+ cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place
+ filter_bytes = 1;
+ width = img_width_bytes;
+ }
+
+ // if first row, use special filter that doesn't sample previous row
+ if (j == 0) filter = first_row_filter[filter];
+
+ // handle first byte explicitly
+ for (k=0; k < filter_bytes; ++k) {
+ switch (filter) {
+ case STBI__F_none : cur[k] = raw[k]; break;
+ case STBI__F_sub : cur[k] = raw[k]; break;
+ case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
+ case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;
+ case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break;
+ case STBI__F_avg_first : cur[k] = raw[k]; break;
+ case STBI__F_paeth_first: cur[k] = raw[k]; break;
+ }
+ }
+
+ if (depth == 8) {
+ if (img_n != out_n)
+ cur[img_n] = 255; // first pixel
+ raw += img_n;
+ cur += out_n;
+ prior += out_n;
+ } else if (depth == 16) {
+ if (img_n != out_n) {
+ cur[filter_bytes] = 255; // first pixel top byte
+ cur[filter_bytes+1] = 255; // first pixel bottom byte
+ }
+ raw += filter_bytes;
+ cur += output_bytes;
+ prior += output_bytes;
+ } else {
+ raw += 1;
+ cur += 1;
+ prior += 1;
+ }
+
+ // this is a little gross, so that we don't switch per-pixel or per-component
+ if (depth < 8 || img_n == out_n) {
+ int nk = (width - 1)*filter_bytes;
+ #define CASE(f) \
+ case f: \
+ for (k=0; k < nk; ++k)
+ switch (filter) {
+ // "none" filter turns into a memcpy here; make that explicit.
+ case STBI__F_none: memcpy(cur, raw, nk); break;
+ CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); break;
+ CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
+ CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); break;
+ CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); break;
+ CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); break;
+ CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); break;
+ }
+ #undef CASE
+ raw += nk;
+ } else {
+ STBI_ASSERT(img_n+1 == out_n);
+ #define CASE(f) \
+ case f: \
+ for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
+ for (k=0; k < filter_bytes; ++k)
+ switch (filter) {
+ CASE(STBI__F_none) cur[k] = raw[k]; break;
+ CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); break;
+ CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
+ CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); break;
+ CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); break;
+ CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); break;
+ CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); break;
+ }
+ #undef CASE
+
+ // the loop above sets the high byte of the pixels' alpha, but for
+ // 16 bit png files we also need the low byte set. we'll do that here.
+ if (depth == 16) {
+ cur = a->out + stride*j; // start at the beginning of the row again
+ for (i=0; i < x; ++i,cur+=output_bytes) {
+ cur[filter_bytes+1] = 255;
+ }
+ }
+ }
+ }
+
+ // we make a separate pass to expand bits to pixels; for performance,
+ // this could run two scanlines behind the above code, so it won't
+ // intefere with filtering but will still be in the cache.
+ if (depth < 8) {
+ for (j=0; j < y; ++j) {
+ stbi_uc *cur = a->out + stride*j;
+ stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes;
+ // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit
+ // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop
+ stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
+
+ // note that the final byte might overshoot and write more data than desired.
+ // we can allocate enough data that this never writes out of memory, but it
+ // could also overwrite the next scanline. can it overwrite non-empty data
+ // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel.
+ // so we need to explicitly clamp the final ones
+
+ if (depth == 4) {
+ for (k=x*img_n; k >= 2; k-=2, ++in) {
+ *cur++ = scale * ((*in >> 4) );
+ *cur++ = scale * ((*in ) & 0x0f);
+ }
+ if (k > 0) *cur++ = scale * ((*in >> 4) );
+ } else if (depth == 2) {
+ for (k=x*img_n; k >= 4; k-=4, ++in) {
+ *cur++ = scale * ((*in >> 6) );
+ *cur++ = scale * ((*in >> 4) & 0x03);
+ *cur++ = scale * ((*in >> 2) & 0x03);
+ *cur++ = scale * ((*in ) & 0x03);
+ }
+ if (k > 0) *cur++ = scale * ((*in >> 6) );
+ if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
+ if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
+ } else if (depth == 1) {
+ for (k=x*img_n; k >= 8; k-=8, ++in) {
+ *cur++ = scale * ((*in >> 7) );
+ *cur++ = scale * ((*in >> 6) & 0x01);
+ *cur++ = scale * ((*in >> 5) & 0x01);
+ *cur++ = scale * ((*in >> 4) & 0x01);
+ *cur++ = scale * ((*in >> 3) & 0x01);
+ *cur++ = scale * ((*in >> 2) & 0x01);
+ *cur++ = scale * ((*in >> 1) & 0x01);
+ *cur++ = scale * ((*in ) & 0x01);
+ }
+ if (k > 0) *cur++ = scale * ((*in >> 7) );
+ if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);
+ if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);
+ if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);
+ if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);
+ if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);
+ if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
+ }
+ if (img_n != out_n) {
+ int q;
+ // insert alpha = 255
+ cur = a->out + stride*j;
+ if (img_n == 1) {
+ for (q=x-1; q >= 0; --q) {
+ cur[q*2+1] = 255;
+ cur[q*2+0] = cur[q];
+ }
+ } else {
+ STBI_ASSERT(img_n == 3);
+ for (q=x-1; q >= 0; --q) {
+ cur[q*4+3] = 255;
+ cur[q*4+2] = cur[q*3+2];
+ cur[q*4+1] = cur[q*3+1];
+ cur[q*4+0] = cur[q*3+0];
+ }
+ }
+ }
+ }
+ } else if (depth == 16) {
+ // force the image data from big-endian to platform-native.
+ // this is done in a separate pass due to the decoding relying
+ // on the data being untouched, but could probably be done
+ // per-line during decode if care is taken.
+ stbi_uc *cur = a->out;
+ stbi__uint16 *cur16 = (stbi__uint16*)cur;
+
+ for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
+ *cur16 = (cur[0] << 8) | cur[1];
+ }
+ }
+
+ return 1;
+}
+
+static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced)
+{
+ stbi_uc *final;
+ int p;
+ if (!interlaced)
+ return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color);
+
+ // de-interlacing
+ final = (stbi_uc *) stbi__malloc(a->s->img_x * a->s->img_y * out_n);
+ for (p=0; p < 7; ++p) {
+ int xorig[] = { 0,4,0,2,0,1,0 };
+ int yorig[] = { 0,0,4,0,2,0,1 };
+ int xspc[] = { 8,8,4,4,2,2,1 };
+ int yspc[] = { 8,8,8,4,4,2,2 };
+ int i,j,x,y;
+ // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1
+ x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p];
+ y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p];
+ if (x && y) {
+ stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y;
+ if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) {
+ STBI_FREE(final);
+ return 0;
+ }
+ for (j=0; j < y; ++j) {
+ for (i=0; i < x; ++i) {
+ int out_y = j*yspc[p]+yorig[p];
+ int out_x = i*xspc[p]+xorig[p];
+ memcpy(final + out_y*a->s->img_x*out_n + out_x*out_n,
+ a->out + (j*x+i)*out_n, out_n);
+ }
+ }
+ STBI_FREE(a->out);
+ image_data += img_len;
+ image_data_len -= img_len;
+ }
+ }
+ a->out = final;
+
+ return 1;
+}
+
+static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n)
+{
+ stbi__context *s = z->s;
+ stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+ stbi_uc *p = z->out;
+
+ // compute color-based transparency, assuming we've
+ // already got 255 as the alpha value in the output
+ STBI_ASSERT(out_n == 2 || out_n == 4);
+
+ if (out_n == 2) {
+ for (i=0; i < pixel_count; ++i) {
+ p[1] = (p[0] == tc[0] ? 0 : 255);
+ p += 2;
+ }
+ } else {
+ for (i=0; i < pixel_count; ++i) {
+ if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+ p[3] = 0;
+ p += 4;
+ }
+ }
+ return 1;
+}
+
+static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n)
+{
+ stbi__context *s = z->s;
+ stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+ stbi__uint16 *p = (stbi__uint16*) z->out;
+
+ // compute color-based transparency, assuming we've
+ // already got 65535 as the alpha value in the output
+ STBI_ASSERT(out_n == 2 || out_n == 4);
+
+ if (out_n == 2) {
+ for (i = 0; i < pixel_count; ++i) {
+ p[1] = (p[0] == tc[0] ? 0 : 65535);
+ p += 2;
+ }
+ } else {
+ for (i = 0; i < pixel_count; ++i) {
+ if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+ p[3] = 0;
+ p += 4;
+ }
+ }
+ return 1;
+}
+
+static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n)
+{
+ stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y;
+ stbi_uc *p, *temp_out, *orig = a->out;
+
+ p = (stbi_uc *) stbi__malloc(pixel_count * pal_img_n);
+ if (p == NULL) return stbi__err("outofmem", "Out of memory");
+
+ // between here and free(out) below, exitting would leak
+ temp_out = p;
+
+ if (pal_img_n == 3) {
+ for (i=0; i < pixel_count; ++i) {
+ int n = orig[i]*4;
+ p[0] = palette[n ];
+ p[1] = palette[n+1];
+ p[2] = palette[n+2];
+ p += 3;
+ }
+ } else {
+ for (i=0; i < pixel_count; ++i) {
+ int n = orig[i]*4;
+ p[0] = palette[n ];
+ p[1] = palette[n+1];
+ p[2] = palette[n+2];
+ p[3] = palette[n+3];
+ p += 4;
+ }
+ }
+ STBI_FREE(a->out);
+ a->out = temp_out;
+
+ STBI_NOTUSED(len);
+
+ return 1;
+}
+
+static int stbi__reduce_png(stbi__png *p)
+{
+ int i;
+ int img_len = p->s->img_x * p->s->img_y * p->s->img_out_n;
+ stbi_uc *reduced;
+ stbi__uint16 *orig = (stbi__uint16*)p->out;
+
+ if (p->depth != 16) return 1; // don't need to do anything if not 16-bit data
+
+ reduced = (stbi_uc *)stbi__malloc(img_len);
+ if (p == NULL) return stbi__err("outofmem", "Out of memory");
+
+ for (i = 0; i < img_len; ++i) reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is a decent approx of 16->8 bit scaling
+
+ p->out = reduced;
+ STBI_FREE(orig);
+
+ return 1;
+}
+
+static int stbi__unpremultiply_on_load = 0;
+static int stbi__de_iphone_flag = 0;
+
+STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)
+{
+ stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply;
+}
+
+STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
+{
+ stbi__de_iphone_flag = flag_true_if_should_convert;
+}
+
+static void stbi__de_iphone(stbi__png *z)
+{
+ stbi__context *s = z->s;
+ stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+ stbi_uc *p = z->out;
+
+ if (s->img_out_n == 3) { // convert bgr to rgb
+ for (i=0; i < pixel_count; ++i) {
+ stbi_uc t = p[0];
+ p[0] = p[2];
+ p[2] = t;
+ p += 3;
+ }
+ } else {
+ STBI_ASSERT(s->img_out_n == 4);
+ if (stbi__unpremultiply_on_load) {
+ // convert bgr to rgb and unpremultiply
+ for (i=0; i < pixel_count; ++i) {
+ stbi_uc a = p[3];
+ stbi_uc t = p[0];
+ if (a) {
+ p[0] = p[2] * 255 / a;
+ p[1] = p[1] * 255 / a;
+ p[2] = t * 255 / a;
+ } else {
+ p[0] = p[2];
+ p[2] = t;
+ }
+ p += 4;
+ }
+ } else {
+ // convert bgr to rgb
+ for (i=0; i < pixel_count; ++i) {
+ stbi_uc t = p[0];
+ p[0] = p[2];
+ p[2] = t;
+ p += 4;
+ }
+ }
+ }
+}
+
+#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d))
+
+static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
+{
+ stbi_uc palette[1024], pal_img_n=0;
+ stbi_uc has_trans=0, tc[3];
+ stbi__uint16 tc16[3];
+ stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;
+ int first=1,k,interlace=0, color=0, is_iphone=0;
+ stbi__context *s = z->s;
+
+ z->expanded = NULL;
+ z->idata = NULL;
+ z->out = NULL;
+
+ if (!stbi__check_png_header(s)) return 0;
+
+ if (scan == STBI__SCAN_type) return 1;
+
+ for (;;) {
+ stbi__pngchunk c = stbi__get_chunk_header(s);
+ switch (c.type) {
+ case STBI__PNG_TYPE('C','g','B','I'):
+ is_iphone = 1;
+ stbi__skip(s, c.length);
+ break;
+ case STBI__PNG_TYPE('I','H','D','R'): {
+ int comp,filter;
+ if (!first) return stbi__err("multiple IHDR","Corrupt PNG");
+ first = 0;
+ if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG");
+ s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)");
+ s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)");
+ z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only");
+ color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG");
+ if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG");
+ if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG");
+ comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG");
+ filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG");
+ interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG");
+ if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG");
+ if (!pal_img_n) {
+ s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);
+ if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode");
+ if (scan == STBI__SCAN_header) return 1;
+ } else {
+ // if paletted, then pal_n is our final components, and
+ // img_n is # components to decompress/filter.
+ s->img_n = 1;
+ if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG");
+ // if SCAN_header, have to scan to see if we have a tRNS
+ }
+ break;
+ }
+
+ case STBI__PNG_TYPE('P','L','T','E'): {
+ if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+ if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG");
+ pal_len = c.length / 3;
+ if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG");
+ for (i=0; i < pal_len; ++i) {
+ palette[i*4+0] = stbi__get8(s);
+ palette[i*4+1] = stbi__get8(s);
+ palette[i*4+2] = stbi__get8(s);
+ palette[i*4+3] = 255;
+ }
+ break;
+ }
+
+ case STBI__PNG_TYPE('t','R','N','S'): {
+ if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+ if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG");
+ if (pal_img_n) {
+ if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; }
+ if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG");
+ if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG");
+ pal_img_n = 4;
+ for (i=0; i < c.length; ++i)
+ palette[i*4+3] = stbi__get8(s);
+ } else {
+ if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG");
+ if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG");
+ has_trans = 1;
+ if (z->depth == 16) {
+ for (k = 0; k < s->img_n; ++k) tc16[k] = stbi__get16be(s); // copy the values as-is
+ } else {
+ for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
+ }
+ }
+ break;
+ }
+
+ case STBI__PNG_TYPE('I','D','A','T'): {
+ if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+ if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG");
+ if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; }
+ if ((int)(ioff + c.length) < (int)ioff) return 0;
+ if (ioff + c.length > idata_limit) {
+ stbi__uint32 idata_limit_old = idata_limit;
+ stbi_uc *p;
+ if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;
+ while (ioff + c.length > idata_limit)
+ idata_limit *= 2;
+ STBI_NOTUSED(idata_limit_old);
+ p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory");
+ z->idata = p;
+ }
+ if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG");
+ ioff += c.length;
+ break;
+ }
+
+ case STBI__PNG_TYPE('I','E','N','D'): {
+ stbi__uint32 raw_len, bpl;
+ if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+ if (scan != STBI__SCAN_load) return 1;
+ if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG");
+ // initial guess for decoded data size to avoid unnecessary reallocs
+ bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component
+ raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */;
+ z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone);
+ if (z->expanded == NULL) return 0; // zlib should set error
+ STBI_FREE(z->idata); z->idata = NULL;
+ if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans)
+ s->img_out_n = s->img_n+1;
+ else
+ s->img_out_n = s->img_n;
+ if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0;
+ if (has_trans) {
+ if (z->depth == 16) {
+ if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0;
+ } else {
+ if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0;
+ }
+ }
+ if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2)
+ stbi__de_iphone(z);
+ if (pal_img_n) {
+ // pal_img_n == 3 or 4
+ s->img_n = pal_img_n; // record the actual colors we had
+ s->img_out_n = pal_img_n;
+ if (req_comp >= 3) s->img_out_n = req_comp;
+ if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n))
+ return 0;
+ }
+ STBI_FREE(z->expanded); z->expanded = NULL;
+ return 1;
+ }
+
+ default:
+ // if critical, fail
+ if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+ if ((c.type & (1 << 29)) == 0) {
+ #ifndef STBI_NO_FAILURE_STRINGS
+ // not threadsafe
+ static char invalid_chunk[] = "XXXX PNG chunk not known";
+ invalid_chunk[0] = STBI__BYTECAST(c.type >> 24);
+ invalid_chunk[1] = STBI__BYTECAST(c.type >> 16);
+ invalid_chunk[2] = STBI__BYTECAST(c.type >> 8);
+ invalid_chunk[3] = STBI__BYTECAST(c.type >> 0);
+ #endif
+ return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type");
+ }
+ stbi__skip(s, c.length);
+ break;
+ }
+ // end of PNG chunk, read and skip CRC
+ stbi__get32be(s);
+ }
+}
+
+static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp)
+{
+ unsigned char *result=NULL;
+ if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
+ if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) {
+ if (p->depth == 16) {
+ if (!stbi__reduce_png(p)) {
+ return result;
+ }
+ }
+ result = p->out;
+ p->out = NULL;
+ if (req_comp && req_comp != p->s->img_out_n) {
+ result = stbi__convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
+ p->s->img_out_n = req_comp;
+ if (result == NULL) return result;
+ }
+ *x = p->s->img_x;
+ *y = p->s->img_y;
+ if (n) *n = p->s->img_n;
+ }
+ STBI_FREE(p->out); p->out = NULL;
+ STBI_FREE(p->expanded); p->expanded = NULL;
+ STBI_FREE(p->idata); p->idata = NULL;
+
+ return result;
+}
+
+static unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ stbi__png p;
+ p.s = s;
+ return stbi__do_png(&p, x,y,comp,req_comp);
+}
+
+static int stbi__png_test(stbi__context *s)
+{
+ int r;
+ r = stbi__check_png_header(s);
+ stbi__rewind(s);
+ return r;
+}
+
+static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp)
+{
+ if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) {
+ stbi__rewind( p->s );
+ return 0;
+ }
+ if (x) *x = p->s->img_x;
+ if (y) *y = p->s->img_y;
+ if (comp) *comp = p->s->img_n;
+ return 1;
+}
+
+static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ stbi__png p;
+ p.s = s;
+ return stbi__png_info_raw(&p, x, y, comp);
+}
+#endif
+
+// Microsoft/Windows BMP image
+
+#ifndef STBI_NO_BMP
+static int stbi__bmp_test_raw(stbi__context *s)
+{
+ int r;
+ int sz;
+ if (stbi__get8(s) != 'B') return 0;
+ if (stbi__get8(s) != 'M') return 0;
+ stbi__get32le(s); // discard filesize
+ stbi__get16le(s); // discard reserved
+ stbi__get16le(s); // discard reserved
+ stbi__get32le(s); // discard data offset
+ sz = stbi__get32le(s);
+ r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124);
+ return r;
+}
+
+static int stbi__bmp_test(stbi__context *s)
+{
+ int r = stbi__bmp_test_raw(s);
+ stbi__rewind(s);
+ return r;
+}
+
+
+// returns 0..31 for the highest set bit
+static int stbi__high_bit(unsigned int z)
+{
+ int n=0;
+ if (z == 0) return -1;
+ if (z >= 0x10000) n += 16, z >>= 16;
+ if (z >= 0x00100) n += 8, z >>= 8;
+ if (z >= 0x00010) n += 4, z >>= 4;
+ if (z >= 0x00004) n += 2, z >>= 2;
+ if (z >= 0x00002) n += 1, z >>= 1;
+ return n;
+}
+
+static int stbi__bitcount(unsigned int a)
+{
+ a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2
+ a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4
+ a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits
+ a = (a + (a >> 8)); // max 16 per 8 bits
+ a = (a + (a >> 16)); // max 32 per 8 bits
+ return a & 0xff;
+}
+
+static int stbi__shiftsigned(int v, int shift, int bits)
+{
+ int result;
+ int z=0;
+
+ if (shift < 0) v <<= -shift;
+ else v >>= shift;
+ result = v;
+
+ z = bits;
+ while (z < 8) {
+ result += v >> z;
+ z += bits;
+ }
+ return result;
+}
+
+typedef struct
+{
+ int bpp, offset, hsz;
+ unsigned int mr,mg,mb,ma, all_a;
+} stbi__bmp_data;
+
+static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
+{
+ int hsz;
+ if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP");
+ stbi__get32le(s); // discard filesize
+ stbi__get16le(s); // discard reserved
+ stbi__get16le(s); // discard reserved
+ info->offset = stbi__get32le(s);
+ info->hsz = hsz = stbi__get32le(s);
+ info->mr = info->mg = info->mb = info->ma = 0;
+
+ if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown");
+ if (hsz == 12) {
+ s->img_x = stbi__get16le(s);
+ s->img_y = stbi__get16le(s);
+ } else {
+ s->img_x = stbi__get32le(s);
+ s->img_y = stbi__get32le(s);
+ }
+ if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP");
+ info->bpp = stbi__get16le(s);
+ if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit");
+ if (hsz != 12) {
+ int compress = stbi__get32le(s);
+ if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE");
+ stbi__get32le(s); // discard sizeof
+ stbi__get32le(s); // discard hres
+ stbi__get32le(s); // discard vres
+ stbi__get32le(s); // discard colorsused
+ stbi__get32le(s); // discard max important
+ if (hsz == 40 || hsz == 56) {
+ if (hsz == 56) {
+ stbi__get32le(s);
+ stbi__get32le(s);
+ stbi__get32le(s);
+ stbi__get32le(s);
+ }
+ if (info->bpp == 16 || info->bpp == 32) {
+ if (compress == 0) {
+ if (info->bpp == 32) {
+ info->mr = 0xffu << 16;
+ info->mg = 0xffu << 8;
+ info->mb = 0xffu << 0;
+ info->ma = 0xffu << 24;
+ info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0
+ } else {
+ info->mr = 31u << 10;
+ info->mg = 31u << 5;
+ info->mb = 31u << 0;
+ }
+ } else if (compress == 3) {
+ info->mr = stbi__get32le(s);
+ info->mg = stbi__get32le(s);
+ info->mb = stbi__get32le(s);
+ // not documented, but generated by photoshop and handled by mspaint
+ if (info->mr == info->mg && info->mg == info->mb) {
+ // ?!?!?
+ return stbi__errpuc("bad BMP", "bad BMP");
+ }
+ } else
+ return stbi__errpuc("bad BMP", "bad BMP");
+ }
+ } else {
+ int i;
+ if (hsz != 108 && hsz != 124)
+ return stbi__errpuc("bad BMP", "bad BMP");
+ info->mr = stbi__get32le(s);
+ info->mg = stbi__get32le(s);
+ info->mb = stbi__get32le(s);
+ info->ma = stbi__get32le(s);
+ stbi__get32le(s); // discard color space
+ for (i=0; i < 12; ++i)
+ stbi__get32le(s); // discard color space parameters
+ if (hsz == 124) {
+ stbi__get32le(s); // discard rendering intent
+ stbi__get32le(s); // discard offset of profile data
+ stbi__get32le(s); // discard size of profile data
+ stbi__get32le(s); // discard reserved
+ }
+ }
+ }
+ return (void *) 1;
+}
+
+
+static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ stbi_uc *out;
+ unsigned int mr=0,mg=0,mb=0,ma=0, all_a;
+ stbi_uc pal[256][4];
+ int psize=0,i,j,width;
+ int flip_vertically, pad, target;
+ stbi__bmp_data info;
+
+ info.all_a = 255;
+ if (stbi__bmp_parse_header(s, &info) == NULL)
+ return NULL; // error code already set
+
+ flip_vertically = ((int) s->img_y) > 0;
+ s->img_y = abs((int) s->img_y);
+
+ mr = info.mr;
+ mg = info.mg;
+ mb = info.mb;
+ ma = info.ma;
+ all_a = info.all_a;
+
+ if (info.hsz == 12) {
+ if (info.bpp < 24)
+ psize = (info.offset - 14 - 24) / 3;
+ } else {
+ if (info.bpp < 16)
+ psize = (info.offset - 14 - info.hsz) >> 2;
+ }
+
+ s->img_n = ma ? 4 : 3;
+ if (req_comp && req_comp >= 3) // we can directly decode 3 or 4
+ target = req_comp;
+ else
+ target = s->img_n; // if they want monochrome, we'll post-convert
+
+ out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y);
+ if (!out) return stbi__errpuc("outofmem", "Out of memory");
+ if (info.bpp < 16) {
+ int z=0;
+ if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); }
+ for (i=0; i < psize; ++i) {
+ pal[i][2] = stbi__get8(s);
+ pal[i][1] = stbi__get8(s);
+ pal[i][0] = stbi__get8(s);
+ if (info.hsz != 12) stbi__get8(s);
+ pal[i][3] = 255;
+ }
+ stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4));
+ if (info.bpp == 4) width = (s->img_x + 1) >> 1;
+ else if (info.bpp == 8) width = s->img_x;
+ else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); }
+ pad = (-width)&3;
+ for (j=0; j < (int) s->img_y; ++j) {
+ for (i=0; i < (int) s->img_x; i += 2) {
+ int v=stbi__get8(s),v2=0;
+ if (info.bpp == 4) {
+ v2 = v & 15;
+ v >>= 4;
+ }
+ out[z++] = pal[v][0];
+ out[z++] = pal[v][1];
+ out[z++] = pal[v][2];
+ if (target == 4) out[z++] = 255;
+ if (i+1 == (int) s->img_x) break;
+ v = (info.bpp == 8) ? stbi__get8(s) : v2;
+ out[z++] = pal[v][0];
+ out[z++] = pal[v][1];
+ out[z++] = pal[v][2];
+ if (target == 4) out[z++] = 255;
+ }
+ stbi__skip(s, pad);
+ }
+ } else {
+ int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0;
+ int z = 0;
+ int easy=0;
+ stbi__skip(s, info.offset - 14 - info.hsz);
+ if (info.bpp == 24) width = 3 * s->img_x;
+ else if (info.bpp == 16) width = 2*s->img_x;
+ else /* bpp = 32 and pad = 0 */ width=0;
+ pad = (-width) & 3;
+ if (info.bpp == 24) {
+ easy = 1;
+ } else if (info.bpp == 32) {
+ if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000)
+ easy = 2;
+ }
+ if (!easy) {
+ if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); }
+ // right shift amt to put high bit in position #7
+ rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr);
+ gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg);
+ bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb);
+ ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma);
+ }
+ for (j=0; j < (int) s->img_y; ++j) {
+ if (easy) {
+ for (i=0; i < (int) s->img_x; ++i) {
+ unsigned char a;
+ out[z+2] = stbi__get8(s);
+ out[z+1] = stbi__get8(s);
+ out[z+0] = stbi__get8(s);
+ z += 3;
+ a = (easy == 2 ? stbi__get8(s) : 255);
+ all_a |= a;
+ if (target == 4) out[z++] = a;
+ }
+ } else {
+ int bpp = info.bpp;
+ for (i=0; i < (int) s->img_x; ++i) {
+ stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s));
+ int a;
+ out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount));
+ out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount));
+ out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount));
+ a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255);
+ all_a |= a;
+ if (target == 4) out[z++] = STBI__BYTECAST(a);
+ }
+ }
+ stbi__skip(s, pad);
+ }
+ }
+
+ // if alpha channel is all 0s, replace with all 255s
+ if (target == 4 && all_a == 0)
+ for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4)
+ out[i] = 255;
+
+ if (flip_vertically) {
+ stbi_uc t;
+ for (j=0; j < (int) s->img_y>>1; ++j) {
+ stbi_uc *p1 = out + j *s->img_x*target;
+ stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target;
+ for (i=0; i < (int) s->img_x*target; ++i) {
+ t = p1[i], p1[i] = p2[i], p2[i] = t;
+ }
+ }
+ }
+
+ if (req_comp && req_comp != target) {
+ out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y);
+ if (out == NULL) return out; // stbi__convert_format frees input on failure
+ }
+
+ *x = s->img_x;
+ *y = s->img_y;
+ if (comp) *comp = s->img_n;
+ return out;
+}
+#endif
+
+// Targa Truevision - TGA
+// by Jonathan Dummer
+#ifndef STBI_NO_TGA
+// returns STBI_rgb or whatever, 0 on error
+static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16)
+{
+ // only RGB or RGBA (incl. 16bit) or grey allowed
+ if(is_rgb16) *is_rgb16 = 0;
+ switch(bits_per_pixel) {
+ case 8: return STBI_grey;
+ case 16: if(is_grey) return STBI_grey_alpha;
+ // else: fall-through
+ case 15: if(is_rgb16) *is_rgb16 = 1;
+ return STBI_rgb;
+ case 24: // fall-through
+ case 32: return bits_per_pixel/8;
+ default: return 0;
+ }
+}
+
+static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp;
+ int sz, tga_colormap_type;
+ stbi__get8(s); // discard Offset
+ tga_colormap_type = stbi__get8(s); // colormap type
+ if( tga_colormap_type > 1 ) {
+ stbi__rewind(s);
+ return 0; // only RGB or indexed allowed
+ }
+ tga_image_type = stbi__get8(s); // image type
+ if ( tga_colormap_type == 1 ) { // colormapped (paletted) image
+ if (tga_image_type != 1 && tga_image_type != 9) {
+ stbi__rewind(s);
+ return 0;
+ }
+ stbi__skip(s,4); // skip index of first colormap entry and number of entries
+ sz = stbi__get8(s); // check bits per palette color entry
+ if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) {
+ stbi__rewind(s);
+ return 0;
+ }
+ stbi__skip(s,4); // skip image x and y origin
+ tga_colormap_bpp = sz;
+ } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE
+ if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) {
+ stbi__rewind(s);
+ return 0; // only RGB or grey allowed, +/- RLE
+ }
+ stbi__skip(s,9); // skip colormap specification and image x/y origin
+ tga_colormap_bpp = 0;
+ }
+ tga_w = stbi__get16le(s);
+ if( tga_w < 1 ) {
+ stbi__rewind(s);
+ return 0; // test width
+ }
+ tga_h = stbi__get16le(s);
+ if( tga_h < 1 ) {
+ stbi__rewind(s);
+ return 0; // test height
+ }
+ tga_bits_per_pixel = stbi__get8(s); // bits per pixel
+ stbi__get8(s); // ignore alpha bits
+ if (tga_colormap_bpp != 0) {
+ if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) {
+ // when using a colormap, tga_bits_per_pixel is the size of the indexes
+ // I don't think anything but 8 or 16bit indexes makes sense
+ stbi__rewind(s);
+ return 0;
+ }
+ tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL);
+ } else {
+ tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL);
+ }
+ if(!tga_comp) {
+ stbi__rewind(s);
+ return 0;
+ }
+ if (x) *x = tga_w;
+ if (y) *y = tga_h;
+ if (comp) *comp = tga_comp;
+ return 1; // seems to have passed everything
+}
+
+static int stbi__tga_test(stbi__context *s)
+{
+ int res = 0;
+ int sz, tga_color_type;
+ stbi__get8(s); // discard Offset
+ tga_color_type = stbi__get8(s); // color type
+ if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed
+ sz = stbi__get8(s); // image type
+ if ( tga_color_type == 1 ) { // colormapped (paletted) image
+ if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9
+ stbi__skip(s,4); // skip index of first colormap entry and number of entries
+ sz = stbi__get8(s); // check bits per palette color entry
+ if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
+ stbi__skip(s,4); // skip image x and y origin
+ } else { // "normal" image w/o colormap
+ if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE
+ stbi__skip(s,9); // skip colormap specification and image x/y origin
+ }
+ if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width
+ if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height
+ sz = stbi__get8(s); // bits per pixel
+ if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index
+ if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
+
+ res = 1; // if we got this far, everything's good and we can return 1 instead of 0
+
+errorEnd:
+ stbi__rewind(s);
+ return res;
+}
+
+// read 16bit value and convert to 24bit RGB
+void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out)
+{
+ stbi__uint16 px = stbi__get16le(s);
+ stbi__uint16 fiveBitMask = 31;
+ // we have 3 channels with 5bits each
+ int r = (px >> 10) & fiveBitMask;
+ int g = (px >> 5) & fiveBitMask;
+ int b = px & fiveBitMask;
+ // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later
+ out[0] = (r * 255)/31;
+ out[1] = (g * 255)/31;
+ out[2] = (b * 255)/31;
+
+ // some people claim that the most significant bit might be used for alpha
+ // (possibly if an alpha-bit is set in the "image descriptor byte")
+ // but that only made 16bit test images completely translucent..
+ // so let's treat all 15 and 16bit TGAs as RGB with no alpha.
+}
+
+static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ // read in the TGA header stuff
+ int tga_offset = stbi__get8(s);
+ int tga_indexed = stbi__get8(s);
+ int tga_image_type = stbi__get8(s);
+ int tga_is_RLE = 0;
+ int tga_palette_start = stbi__get16le(s);
+ int tga_palette_len = stbi__get16le(s);
+ int tga_palette_bits = stbi__get8(s);
+ int tga_x_origin = stbi__get16le(s);
+ int tga_y_origin = stbi__get16le(s);
+ int tga_width = stbi__get16le(s);
+ int tga_height = stbi__get16le(s);
+ int tga_bits_per_pixel = stbi__get8(s);
+ int tga_comp, tga_rgb16=0;
+ int tga_inverted = stbi__get8(s);
+ // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?)
+ // image data
+ unsigned char *tga_data;
+ unsigned char *tga_palette = NULL;
+ int i, j;
+ unsigned char raw_data[4];
+ int RLE_count = 0;
+ int RLE_repeating = 0;
+ int read_next_pixel = 1;
+
+ // do a tiny bit of precessing
+ if ( tga_image_type >= 8 )
+ {
+ tga_image_type -= 8;
+ tga_is_RLE = 1;
+ }
+ tga_inverted = 1 - ((tga_inverted >> 5) & 1);
+
+ // If I'm paletted, then I'll use the number of bits from the palette
+ if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16);
+ else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16);
+
+ if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency
+ return stbi__errpuc("bad format", "Can't find out TGA pixelformat");
+
+ // tga info
+ *x = tga_width;
+ *y = tga_height;
+ if (comp) *comp = tga_comp;
+
+ tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp );
+ if (!tga_data) return stbi__errpuc("outofmem", "Out of memory");
+
+ // skip to the data's starting position (offset usually = 0)
+ stbi__skip(s, tga_offset );
+
+ if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) {
+ for (i=0; i < tga_height; ++i) {
+ int row = tga_inverted ? tga_height -i - 1 : i;
+ stbi_uc *tga_row = tga_data + row*tga_width*tga_comp;
+ stbi__getn(s, tga_row, tga_width * tga_comp);
+ }
+ } else {
+ // do I need to load a palette?
+ if ( tga_indexed)
+ {
+ // any data to skip? (offset usually = 0)
+ stbi__skip(s, tga_palette_start );
+ // load the palette
+ tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_comp );
+ if (!tga_palette) {
+ STBI_FREE(tga_data);
+ return stbi__errpuc("outofmem", "Out of memory");
+ }
+ if (tga_rgb16) {
+ stbi_uc *pal_entry = tga_palette;
+ STBI_ASSERT(tga_comp == STBI_rgb);
+ for (i=0; i < tga_palette_len; ++i) {
+ stbi__tga_read_rgb16(s, pal_entry);
+ pal_entry += tga_comp;
+ }
+ } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) {
+ STBI_FREE(tga_data);
+ STBI_FREE(tga_palette);
+ return stbi__errpuc("bad palette", "Corrupt TGA");
+ }
+ }
+ // load the data
+ for (i=0; i < tga_width * tga_height; ++i)
+ {
+ // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk?
+ if ( tga_is_RLE )
+ {
+ if ( RLE_count == 0 )
+ {
+ // yep, get the next byte as a RLE command
+ int RLE_cmd = stbi__get8(s);
+ RLE_count = 1 + (RLE_cmd & 127);
+ RLE_repeating = RLE_cmd >> 7;
+ read_next_pixel = 1;
+ } else if ( !RLE_repeating )
+ {
+ read_next_pixel = 1;
+ }
+ } else
+ {
+ read_next_pixel = 1;
+ }
+ // OK, if I need to read a pixel, do it now
+ if ( read_next_pixel )
+ {
+ // load however much data we did have
+ if ( tga_indexed )
+ {
+ // read in index, then perform the lookup
+ int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s);
+ if ( pal_idx >= tga_palette_len ) {
+ // invalid index
+ pal_idx = 0;
+ }
+ pal_idx *= tga_comp;
+ for (j = 0; j < tga_comp; ++j) {
+ raw_data[j] = tga_palette[pal_idx+j];
+ }
+ } else if(tga_rgb16) {
+ STBI_ASSERT(tga_comp == STBI_rgb);
+ stbi__tga_read_rgb16(s, raw_data);
+ } else {
+ // read in the data raw
+ for (j = 0; j < tga_comp; ++j) {
+ raw_data[j] = stbi__get8(s);
+ }
+ }
+ // clear the reading flag for the next pixel
+ read_next_pixel = 0;
+ } // end of reading a pixel
+
+ // copy data
+ for (j = 0; j < tga_comp; ++j)
+ tga_data[i*tga_comp+j] = raw_data[j];
+
+ // in case we're in RLE mode, keep counting down
+ --RLE_count;
+ }
+ // do I need to invert the image?
+ if ( tga_inverted )
+ {
+ for (j = 0; j*2 < tga_height; ++j)
+ {
+ int index1 = j * tga_width * tga_comp;
+ int index2 = (tga_height - 1 - j) * tga_width * tga_comp;
+ for (i = tga_width * tga_comp; i > 0; --i)
+ {
+ unsigned char temp = tga_data[index1];
+ tga_data[index1] = tga_data[index2];
+ tga_data[index2] = temp;
+ ++index1;
+ ++index2;
+ }
+ }
+ }
+ // clear my palette, if I had one
+ if ( tga_palette != NULL )
+ {
+ STBI_FREE( tga_palette );
+ }
+ }
+
+ // swap RGB - if the source data was RGB16, it already is in the right order
+ if (tga_comp >= 3 && !tga_rgb16)
+ {
+ unsigned char* tga_pixel = tga_data;
+ for (i=0; i < tga_width * tga_height; ++i)
+ {
+ unsigned char temp = tga_pixel[0];
+ tga_pixel[0] = tga_pixel[2];
+ tga_pixel[2] = temp;
+ tga_pixel += tga_comp;
+ }
+ }
+
+ // convert to target component count
+ if (req_comp && req_comp != tga_comp)
+ tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height);
+
+ // the things I do to get rid of an error message, and yet keep
+ // Microsoft's C compilers happy... [8^(
+ tga_palette_start = tga_palette_len = tga_palette_bits =
+ tga_x_origin = tga_y_origin = 0;
+ // OK, done
+ return tga_data;
+}
+#endif
+
+// *************************************************************************************************
+// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB
+
+#ifndef STBI_NO_PSD
+static int stbi__psd_test(stbi__context *s)
+{
+ int r = (stbi__get32be(s) == 0x38425053);
+ stbi__rewind(s);
+ return r;
+}
+
+static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ int pixelCount;
+ int channelCount, compression;
+ int channel, i, count, len;
+ int bitdepth;
+ int w,h;
+ stbi_uc *out;
+
+ // Check identifier
+ if (stbi__get32be(s) != 0x38425053) // "8BPS"
+ return stbi__errpuc("not PSD", "Corrupt PSD image");
+
+ // Check file type version.
+ if (stbi__get16be(s) != 1)
+ return stbi__errpuc("wrong version", "Unsupported version of PSD image");
+
+ // Skip 6 reserved bytes.
+ stbi__skip(s, 6 );
+
+ // Read the number of channels (R, G, B, A, etc).
+ channelCount = stbi__get16be(s);
+ if (channelCount < 0 || channelCount > 16)
+ return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image");
+
+ // Read the rows and columns of the image.
+ h = stbi__get32be(s);
+ w = stbi__get32be(s);
+
+ // Make sure the depth is 8 bits.
+ bitdepth = stbi__get16be(s);
+ if (bitdepth != 8 && bitdepth != 16)
+ return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit");
+
+ // Make sure the color mode is RGB.
+ // Valid options are:
+ // 0: Bitmap
+ // 1: Grayscale
+ // 2: Indexed color
+ // 3: RGB color
+ // 4: CMYK color
+ // 7: Multichannel
+ // 8: Duotone
+ // 9: Lab color
+ if (stbi__get16be(s) != 3)
+ return stbi__errpuc("wrong color format", "PSD is not in RGB color format");
+
+ // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.)
+ stbi__skip(s,stbi__get32be(s) );
+
+ // Skip the image resources. (resolution, pen tool paths, etc)
+ stbi__skip(s, stbi__get32be(s) );
+
+ // Skip the reserved data.
+ stbi__skip(s, stbi__get32be(s) );
+
+ // Find out if the data is compressed.
+ // Known values:
+ // 0: no compression
+ // 1: RLE compressed
+ compression = stbi__get16be(s);
+ if (compression > 1)
+ return stbi__errpuc("bad compression", "PSD has an unknown compression format");
+
+ // Create the destination image.
+ out = (stbi_uc *) stbi__malloc(4 * w*h);
+ if (!out) return stbi__errpuc("outofmem", "Out of memory");
+ pixelCount = w*h;
+
+ // Initialize the data to zero.
+ //memset( out, 0, pixelCount * 4 );
+
+ // Finally, the image data.
+ if (compression) {
+ // RLE as used by .PSD and .TIFF
+ // Loop until you get the number of unpacked bytes you are expecting:
+ // Read the next source byte into n.
+ // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.
+ // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times.
+ // Else if n is 128, noop.
+ // Endloop
+
+ // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data,
+ // which we're going to just skip.
+ stbi__skip(s, h * channelCount * 2 );
+
+ // Read the RLE data by channel.
+ for (channel = 0; channel < 4; channel++) {
+ stbi_uc *p;
+
+ p = out+channel;
+ if (channel >= channelCount) {
+ // Fill this channel with default data.
+ for (i = 0; i < pixelCount; i++, p += 4)
+ *p = (channel == 3 ? 255 : 0);
+ } else {
+ // Read the RLE data.
+ count = 0;
+ while (count < pixelCount) {
+ len = stbi__get8(s);
+ if (len == 128) {
+ // No-op.
+ } else if (len < 128) {
+ // Copy next len+1 bytes literally.
+ len++;
+ count += len;
+ while (len) {
+ *p = stbi__get8(s);
+ p += 4;
+ len--;
+ }
+ } else if (len > 128) {
+ stbi_uc val;
+ // Next -len+1 bytes in the dest are replicated from next source byte.
+ // (Interpret len as a negative 8-bit int.)
+ len ^= 0x0FF;
+ len += 2;
+ val = stbi__get8(s);
+ count += len;
+ while (len) {
+ *p = val;
+ p += 4;
+ len--;
+ }
+ }
+ }
+ }
+ }
+
+ } else {
+ // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...)
+ // where each channel consists of an 8-bit value for each pixel in the image.
+
+ // Read the data by channel.
+ for (channel = 0; channel < 4; channel++) {
+ stbi_uc *p;
+
+ p = out + channel;
+ if (channel >= channelCount) {
+ // Fill this channel with default data.
+ stbi_uc val = channel == 3 ? 255 : 0;
+ for (i = 0; i < pixelCount; i++, p += 4)
+ *p = val;
+ } else {
+ // Read the data.
+ if (bitdepth == 16) {
+ for (i = 0; i < pixelCount; i++, p += 4)
+ *p = (stbi_uc) (stbi__get16be(s) >> 8);
+ } else {
+ for (i = 0; i < pixelCount; i++, p += 4)
+ *p = stbi__get8(s);
+ }
+ }
+ }
+ }
+
+ if (channelCount >= 4) {
+ for (i=0; i < w*h; ++i) {
+ unsigned char *pixel = out + 4*i;
+ if (pixel[3] != 0 && pixel[3] != 255) {
+ // remove weird white matte from PSD
+ float a = pixel[3] / 255.0f;
+ float ra = 1.0f / a;
+ float inv_a = 255.0f * (1 - ra);
+ pixel[0] = (unsigned char) (pixel[0]*ra + inv_a);
+ pixel[1] = (unsigned char) (pixel[1]*ra + inv_a);
+ pixel[2] = (unsigned char) (pixel[2]*ra + inv_a);
+ }
+ }
+ }
+
+ if (req_comp && req_comp != 4) {
+ out = stbi__convert_format(out, 4, req_comp, w, h);
+ if (out == NULL) return out; // stbi__convert_format frees input on failure
+ }
+
+ if (comp) *comp = 4;
+ *y = h;
+ *x = w;
+
+ return out;
+}
+#endif
+
+// *************************************************************************************************
+// Softimage PIC loader
+// by Tom Seddon
+//
+// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format
+// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/
+
+#ifndef STBI_NO_PIC
+static int stbi__pic_is4(stbi__context *s,const char *str)
+{
+ int i;
+ for (i=0; i<4; ++i)
+ if (stbi__get8(s) != (stbi_uc)str[i])
+ return 0;
+
+ return 1;
+}
+
+static int stbi__pic_test_core(stbi__context *s)
+{
+ int i;
+
+ if (!stbi__pic_is4(s,"\x53\x80\xF6\x34"))
+ return 0;
+
+ for(i=0;i<84;++i)
+ stbi__get8(s);
+
+ if (!stbi__pic_is4(s,"PICT"))
+ return 0;
+
+ return 1;
+}
+
+typedef struct
+{
+ stbi_uc size,type,channel;
+} stbi__pic_packet;
+
+static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest)
+{
+ int mask=0x80, i;
+
+ for (i=0; i<4; ++i, mask>>=1) {
+ if (channel & mask) {
+ if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short");
+ dest[i]=stbi__get8(s);
+ }
+ }
+
+ return dest;
+}
+
+static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src)
+{
+ int mask=0x80,i;
+
+ for (i=0;i<4; ++i, mask>>=1)
+ if (channel&mask)
+ dest[i]=src[i];
+}
+
+static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result)
+{
+ int act_comp=0,num_packets=0,y,chained;
+ stbi__pic_packet packets[10];
+
+ // this will (should...) cater for even some bizarre stuff like having data
+ // for the same channel in multiple packets.
+ do {
+ stbi__pic_packet *packet;
+
+ if (num_packets==sizeof(packets)/sizeof(packets[0]))
+ return stbi__errpuc("bad format","too many packets");
+
+ packet = &packets[num_packets++];
+
+ chained = stbi__get8(s);
+ packet->size = stbi__get8(s);
+ packet->type = stbi__get8(s);
+ packet->channel = stbi__get8(s);
+
+ act_comp |= packet->channel;
+
+ if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)");
+ if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp");
+ } while (chained);
+
+ *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel?
+
+ for(y=0; y<height; ++y) {
+ int packet_idx;
+
+ for(packet_idx=0; packet_idx < num_packets; ++packet_idx) {
+ stbi__pic_packet *packet = &packets[packet_idx];
+ stbi_uc *dest = result+y*width*4;
+
+ switch (packet->type) {
+ default:
+ return stbi__errpuc("bad format","packet has bad compression type");
+
+ case 0: {//uncompressed
+ int x;
+
+ for(x=0;x<width;++x, dest+=4)
+ if (!stbi__readval(s,packet->channel,dest))
+ return 0;
+ break;
+ }
+
+ case 1://Pure RLE
+ {
+ int left=width, i;
+
+ while (left>0) {
+ stbi_uc count,value[4];
+
+ count=stbi__get8(s);
+ if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)");
+
+ if (count > left)
+ count = (stbi_uc) left;
+
+ if (!stbi__readval(s,packet->channel,value)) return 0;
+
+ for(i=0; i<count; ++i,dest+=4)
+ stbi__copyval(packet->channel,dest,value);
+ left -= count;
+ }
+ }
+ break;
+
+ case 2: {//Mixed RLE
+ int left=width;
+ while (left>0) {
+ int count = stbi__get8(s), i;
+ if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)");
+
+ if (count >= 128) { // Repeated
+ stbi_uc value[4];
+
+ if (count==128)
+ count = stbi__get16be(s);
+ else
+ count -= 127;
+ if (count > left)
+ return stbi__errpuc("bad file","scanline overrun");
+
+ if (!stbi__readval(s,packet->channel,value))
+ return 0;
+
+ for(i=0;i<count;++i, dest += 4)
+ stbi__copyval(packet->channel,dest,value);
+ } else { // Raw
+ ++count;
+ if (count>left) return stbi__errpuc("bad file","scanline overrun");
+
+ for(i=0;i<count;++i, dest+=4)
+ if (!stbi__readval(s,packet->channel,dest))
+ return 0;
+ }
+ left-=count;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp)
+{
+ stbi_uc *result;
+ int i, x,y;
+
+ for (i=0; i<92; ++i)
+ stbi__get8(s);
+
+ x = stbi__get16be(s);
+ y = stbi__get16be(s);
+ if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)");
+ if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to decode");
+
+ stbi__get32be(s); //skip `ratio'
+ stbi__get16be(s); //skip `fields'
+ stbi__get16be(s); //skip `pad'
+
+ // intermediate buffer is RGBA
+ result = (stbi_uc *) stbi__malloc(x*y*4);
+ memset(result, 0xff, x*y*4);
+
+ if (!stbi__pic_load_core(s,x,y,comp, result)) {
+ STBI_FREE(result);
+ result=0;
+ }
+ *px = x;
+ *py = y;
+ if (req_comp == 0) req_comp = *comp;
+ result=stbi__convert_format(result,4,req_comp,x,y);
+
+ return result;
+}
+
+static int stbi__pic_test(stbi__context *s)
+{
+ int r = stbi__pic_test_core(s);
+ stbi__rewind(s);
+ return r;
+}
+#endif
+
+// *************************************************************************************************
+// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb
+
+#ifndef STBI_NO_GIF
+typedef struct
+{
+ stbi__int16 prefix;
+ stbi_uc first;
+ stbi_uc suffix;
+} stbi__gif_lzw;
+
+typedef struct
+{
+ int w,h;
+ stbi_uc *out, *old_out; // output buffer (always 4 components)
+ int flags, bgindex, ratio, transparent, eflags, delay;
+ stbi_uc pal[256][4];
+ stbi_uc lpal[256][4];
+ stbi__gif_lzw codes[4096];
+ stbi_uc *color_table;
+ int parse, step;
+ int lflags;
+ int start_x, start_y;
+ int max_x, max_y;
+ int cur_x, cur_y;
+ int line_size;
+} stbi__gif;
+
+static int stbi__gif_test_raw(stbi__context *s)
+{
+ int sz;
+ if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0;
+ sz = stbi__get8(s);
+ if (sz != '9' && sz != '7') return 0;
+ if (stbi__get8(s) != 'a') return 0;
+ return 1;
+}
+
+static int stbi__gif_test(stbi__context *s)
+{
+ int r = stbi__gif_test_raw(s);
+ stbi__rewind(s);
+ return r;
+}
+
+static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp)
+{
+ int i;
+ for (i=0; i < num_entries; ++i) {
+ pal[i][2] = stbi__get8(s);
+ pal[i][1] = stbi__get8(s);
+ pal[i][0] = stbi__get8(s);
+ pal[i][3] = transp == i ? 0 : 255;
+ }
+}
+
+static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info)
+{
+ stbi_uc version;
+ if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8')
+ return stbi__err("not GIF", "Corrupt GIF");
+
+ version = stbi__get8(s);
+ if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF");
+ if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF");
+
+ stbi__g_failure_reason = "";
+ g->w = stbi__get16le(s);
+ g->h = stbi__get16le(s);
+ g->flags = stbi__get8(s);
+ g->bgindex = stbi__get8(s);
+ g->ratio = stbi__get8(s);
+ g->transparent = -1;
+
+ if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments
+
+ if (is_info) return 1;
+
+ if (g->flags & 0x80)
+ stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1);
+
+ return 1;
+}
+
+static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp)
+{
+ stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif));
+ if (!stbi__gif_header(s, g, comp, 1)) {
+ STBI_FREE(g);
+ stbi__rewind( s );
+ return 0;
+ }
+ if (x) *x = g->w;
+ if (y) *y = g->h;
+ STBI_FREE(g);
+ return 1;
+}
+
+static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)
+{
+ stbi_uc *p, *c;
+
+ // recurse to decode the prefixes, since the linked-list is backwards,
+ // and working backwards through an interleaved image would be nasty
+ if (g->codes[code].prefix >= 0)
+ stbi__out_gif_code(g, g->codes[code].prefix);
+
+ if (g->cur_y >= g->max_y) return;
+
+ p = &g->out[g->cur_x + g->cur_y];
+ c = &g->color_table[g->codes[code].suffix * 4];
+
+ if (c[3] >= 128) {
+ p[0] = c[2];
+ p[1] = c[1];
+ p[2] = c[0];
+ p[3] = c[3];
+ }
+ g->cur_x += 4;
+
+ if (g->cur_x >= g->max_x) {
+ g->cur_x = g->start_x;
+ g->cur_y += g->step;
+
+ while (g->cur_y >= g->max_y && g->parse > 0) {
+ g->step = (1 << g->parse) * g->line_size;
+ g->cur_y = g->start_y + (g->step >> 1);
+ --g->parse;
+ }
+ }
+}
+
+static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)
+{
+ stbi_uc lzw_cs;
+ stbi__int32 len, init_code;
+ stbi__uint32 first;
+ stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;
+ stbi__gif_lzw *p;
+
+ lzw_cs = stbi__get8(s);
+ if (lzw_cs > 12) return NULL;
+ clear = 1 << lzw_cs;
+ first = 1;
+ codesize = lzw_cs + 1;
+ codemask = (1 << codesize) - 1;
+ bits = 0;
+ valid_bits = 0;
+ for (init_code = 0; init_code < clear; init_code++) {
+ g->codes[init_code].prefix = -1;
+ g->codes[init_code].first = (stbi_uc) init_code;
+ g->codes[init_code].suffix = (stbi_uc) init_code;
+ }
+
+ // support no starting clear code
+ avail = clear+2;
+ oldcode = -1;
+
+ len = 0;
+ for(;;) {
+ if (valid_bits < codesize) {
+ if (len == 0) {
+ len = stbi__get8(s); // start new block
+ if (len == 0)
+ return g->out;
+ }
+ --len;
+ bits |= (stbi__int32) stbi__get8(s) << valid_bits;
+ valid_bits += 8;
+ } else {
+ stbi__int32 code = bits & codemask;
+ bits >>= codesize;
+ valid_bits -= codesize;
+ // @OPTIMIZE: is there some way we can accelerate the non-clear path?
+ if (code == clear) { // clear code
+ codesize = lzw_cs + 1;
+ codemask = (1 << codesize) - 1;
+ avail = clear + 2;
+ oldcode = -1;
+ first = 0;
+ } else if (code == clear + 1) { // end of stream code
+ stbi__skip(s, len);
+ while ((len = stbi__get8(s)) > 0)
+ stbi__skip(s,len);
+ return g->out;
+ } else if (code <= avail) {
+ if (first) return stbi__errpuc("no clear code", "Corrupt GIF");
+
+ if (oldcode >= 0) {
+ p = &g->codes[avail++];
+ if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF");
+ p->prefix = (stbi__int16) oldcode;
+ p->first = g->codes[oldcode].first;
+ p->suffix = (code == avail) ? p->first : g->codes[code].first;
+ } else if (code == avail)
+ return stbi__errpuc("illegal code in raster", "Corrupt GIF");
+
+ stbi__out_gif_code(g, (stbi__uint16) code);
+
+ if ((avail & codemask) == 0 && avail <= 0x0FFF) {
+ codesize++;
+ codemask = (1 << codesize) - 1;
+ }
+
+ oldcode = code;
+ } else {
+ return stbi__errpuc("illegal code in raster", "Corrupt GIF");
+ }
+ }
+ }
+}
+
+static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1)
+{
+ int x, y;
+ stbi_uc *c = g->pal[g->bgindex];
+ for (y = y0; y < y1; y += 4 * g->w) {
+ for (x = x0; x < x1; x += 4) {
+ stbi_uc *p = &g->out[y + x];
+ p[0] = c[2];
+ p[1] = c[1];
+ p[2] = c[0];
+ p[3] = 0;
+ }
+ }
+}
+
+// this function is designed to support animated gifs, although stb_image doesn't support it
+static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp)
+{
+ int i;
+ stbi_uc *prev_out = 0;
+
+ if (g->out == 0 && !stbi__gif_header(s, g, comp,0))
+ return 0; // stbi__g_failure_reason set by stbi__gif_header
+
+ prev_out = g->out;
+ g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h);
+ if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory");
+
+ switch ((g->eflags & 0x1C) >> 2) {
+ case 0: // unspecified (also always used on 1st frame)
+ stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h);
+ break;
+ case 1: // do not dispose
+ if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h);
+ g->old_out = prev_out;
+ break;
+ case 2: // dispose to background
+ if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h);
+ stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y);
+ break;
+ case 3: // dispose to previous
+ if (g->old_out) {
+ for (i = g->start_y; i < g->max_y; i += 4 * g->w)
+ memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x);
+ }
+ break;
+ }
+
+ for (;;) {
+ switch (stbi__get8(s)) {
+ case 0x2C: /* Image Descriptor */
+ {
+ int prev_trans = -1;
+ stbi__int32 x, y, w, h;
+ stbi_uc *o;
+
+ x = stbi__get16le(s);
+ y = stbi__get16le(s);
+ w = stbi__get16le(s);
+ h = stbi__get16le(s);
+ if (((x + w) > (g->w)) || ((y + h) > (g->h)))
+ return stbi__errpuc("bad Image Descriptor", "Corrupt GIF");
+
+ g->line_size = g->w * 4;
+ g->start_x = x * 4;
+ g->start_y = y * g->line_size;
+ g->max_x = g->start_x + w * 4;
+ g->max_y = g->start_y + h * g->line_size;
+ g->cur_x = g->start_x;
+ g->cur_y = g->start_y;
+
+ g->lflags = stbi__get8(s);
+
+ if (g->lflags & 0x40) {
+ g->step = 8 * g->line_size; // first interlaced spacing
+ g->parse = 3;
+ } else {
+ g->step = g->line_size;
+ g->parse = 0;
+ }
+
+ if (g->lflags & 0x80) {
+ stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1);
+ g->color_table = (stbi_uc *) g->lpal;
+ } else if (g->flags & 0x80) {
+ if (g->transparent >= 0 && (g->eflags & 0x01)) {
+ prev_trans = g->pal[g->transparent][3];
+ g->pal[g->transparent][3] = 0;
+ }
+ g->color_table = (stbi_uc *) g->pal;
+ } else
+ return stbi__errpuc("missing color table", "Corrupt GIF");
+
+ o = stbi__process_gif_raster(s, g);
+ if (o == NULL) return NULL;
+
+ if (prev_trans != -1)
+ g->pal[g->transparent][3] = (stbi_uc) prev_trans;
+
+ return o;
+ }
+
+ case 0x21: // Comment Extension.
+ {
+ int len;
+ if (stbi__get8(s) == 0xF9) { // Graphic Control Extension.
+ len = stbi__get8(s);
+ if (len == 4) {
+ g->eflags = stbi__get8(s);
+ g->delay = stbi__get16le(s);
+ g->transparent = stbi__get8(s);
+ } else {
+ stbi__skip(s, len);
+ break;
+ }
+ }
+ while ((len = stbi__get8(s)) != 0)
+ stbi__skip(s, len);
+ break;
+ }
+
+ case 0x3B: // gif stream termination code
+ return (stbi_uc *) s; // using '1' causes warning on some compilers
+
+ default:
+ return stbi__errpuc("unknown code", "Corrupt GIF");
+ }
+ }
+
+ STBI_NOTUSED(req_comp);
+}
+
+static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ stbi_uc *u = 0;
+ stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif));
+ memset(g, 0, sizeof(*g));
+
+ u = stbi__gif_load_next(s, g, comp, req_comp);
+ if (u == (stbi_uc *) s) u = 0; // end of animated gif marker
+ if (u) {
+ *x = g->w;
+ *y = g->h;
+ if (req_comp && req_comp != 4)
+ u = stbi__convert_format(u, 4, req_comp, g->w, g->h);
+ }
+ else if (g->out)
+ STBI_FREE(g->out);
+ STBI_FREE(g);
+ return u;
+}
+
+static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ return stbi__gif_info_raw(s,x,y,comp);
+}
+#endif
+
+// *************************************************************************************************
+// Radiance RGBE HDR loader
+// originally by Nicolas Schulz
+#ifndef STBI_NO_HDR
+static int stbi__hdr_test_core(stbi__context *s)
+{
+ const char *signature = "#?RADIANCE\n";
+ int i;
+ for (i=0; signature[i]; ++i)
+ if (stbi__get8(s) != signature[i])
+ return 0;
+ return 1;
+}
+
+static int stbi__hdr_test(stbi__context* s)
+{
+ int r = stbi__hdr_test_core(s);
+ stbi__rewind(s);
+ return r;
+}
+
+#define STBI__HDR_BUFLEN 1024
+static char *stbi__hdr_gettoken(stbi__context *z, char *buffer)
+{
+ int len=0;
+ char c = '\0';
+
+ c = (char) stbi__get8(z);
+
+ while (!stbi__at_eof(z) && c != '\n') {
+ buffer[len++] = c;
+ if (len == STBI__HDR_BUFLEN-1) {
+ // flush to end of line
+ while (!stbi__at_eof(z) && stbi__get8(z) != '\n')
+ ;
+ break;
+ }
+ c = (char) stbi__get8(z);
+ }
+
+ buffer[len] = 0;
+ return buffer;
+}
+
+static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp)
+{
+ if ( input[3] != 0 ) {
+ float f1;
+ // Exponent
+ f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8));
+ if (req_comp <= 2)
+ output[0] = (input[0] + input[1] + input[2]) * f1 / 3;
+ else {
+ output[0] = input[0] * f1;
+ output[1] = input[1] * f1;
+ output[2] = input[2] * f1;
+ }
+ if (req_comp == 2) output[1] = 1;
+ if (req_comp == 4) output[3] = 1;
+ } else {
+ switch (req_comp) {
+ case 4: output[3] = 1; /* fallthrough */
+ case 3: output[0] = output[1] = output[2] = 0;
+ break;
+ case 2: output[1] = 1; /* fallthrough */
+ case 1: output[0] = 0;
+ break;
+ }
+ }
+}
+
+static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ char buffer[STBI__HDR_BUFLEN];
+ char *token;
+ int valid = 0;
+ int width, height;
+ stbi_uc *scanline;
+ float *hdr_data;
+ int len;
+ unsigned char count, value;
+ int i, j, k, c1,c2, z;
+
+
+ // Check identifier
+ if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0)
+ return stbi__errpf("not HDR", "Corrupt HDR image");
+
+ // Parse header
+ for(;;) {
+ token = stbi__hdr_gettoken(s,buffer);
+ if (token[0] == 0) break;
+ if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
+ }
+
+ if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format");
+
+ // Parse width and height
+ // can't use sscanf() if we're not using stdio!
+ token = stbi__hdr_gettoken(s,buffer);
+ if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format");
+ token += 3;
+ height = (int) strtol(token, &token, 10);
+ while (*token == ' ') ++token;
+ if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format");
+ token += 3;
+ width = (int) strtol(token, NULL, 10);
+
+ *x = width;
+ *y = height;
+
+ if (comp) *comp = 3;
+ if (req_comp == 0) req_comp = 3;
+
+ // Read data
+ hdr_data = (float *) stbi__malloc(height * width * req_comp * sizeof(float));
+
+ // Load image data
+ // image data is stored as some number of sca
+ if ( width < 8 || width >= 32768) {
+ // Read flat data
+ for (j=0; j < height; ++j) {
+ for (i=0; i < width; ++i) {
+ stbi_uc rgbe[4];
+ main_decode_loop:
+ stbi__getn(s, rgbe, 4);
+ stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);
+ }
+ }
+ } else {
+ // Read RLE-encoded data
+ scanline = NULL;
+
+ for (j = 0; j < height; ++j) {
+ c1 = stbi__get8(s);
+ c2 = stbi__get8(s);
+ len = stbi__get8(s);
+ if (c1 != 2 || c2 != 2 || (len & 0x80)) {
+ // not run-length encoded, so we have to actually use THIS data as a decoded
+ // pixel (note this can't be a valid pixel--one of RGB must be >= 128)
+ stbi_uc rgbe[4];
+ rgbe[0] = (stbi_uc) c1;
+ rgbe[1] = (stbi_uc) c2;
+ rgbe[2] = (stbi_uc) len;
+ rgbe[3] = (stbi_uc) stbi__get8(s);
+ stbi__hdr_convert(hdr_data, rgbe, req_comp);
+ i = 1;
+ j = 0;
+ STBI_FREE(scanline);
+ goto main_decode_loop; // yes, this makes no sense
+ }
+ len <<= 8;
+ len |= stbi__get8(s);
+ if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); }
+ if (scanline == NULL) scanline = (stbi_uc *) stbi__malloc(width * 4);
+
+ for (k = 0; k < 4; ++k) {
+ i = 0;
+ while (i < width) {
+ count = stbi__get8(s);
+ if (count > 128) {
+ // Run
+ value = stbi__get8(s);
+ count -= 128;
+ for (z = 0; z < count; ++z)
+ scanline[i++ * 4 + k] = value;
+ } else {
+ // Dump
+ for (z = 0; z < count; ++z)
+ scanline[i++ * 4 + k] = stbi__get8(s);
+ }
+ }
+ }
+ for (i=0; i < width; ++i)
+ stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp);
+ }
+ STBI_FREE(scanline);
+ }
+
+ return hdr_data;
+}
+
+static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ char buffer[STBI__HDR_BUFLEN];
+ char *token;
+ int valid = 0;
+
+ if (stbi__hdr_test(s) == 0) {
+ stbi__rewind( s );
+ return 0;
+ }
+
+ for(;;) {
+ token = stbi__hdr_gettoken(s,buffer);
+ if (token[0] == 0) break;
+ if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
+ }
+
+ if (!valid) {
+ stbi__rewind( s );
+ return 0;
+ }
+ token = stbi__hdr_gettoken(s,buffer);
+ if (strncmp(token, "-Y ", 3)) {
+ stbi__rewind( s );
+ return 0;
+ }
+ token += 3;
+ *y = (int) strtol(token, &token, 10);
+ while (*token == ' ') ++token;
+ if (strncmp(token, "+X ", 3)) {
+ stbi__rewind( s );
+ return 0;
+ }
+ token += 3;
+ *x = (int) strtol(token, NULL, 10);
+ *comp = 3;
+ return 1;
+}
+#endif // STBI_NO_HDR
+
+#ifndef STBI_NO_BMP
+static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ void *p;
+ stbi__bmp_data info;
+
+ info.all_a = 255;
+ p = stbi__bmp_parse_header(s, &info);
+ stbi__rewind( s );
+ if (p == NULL)
+ return 0;
+ *x = s->img_x;
+ *y = s->img_y;
+ *comp = info.ma ? 4 : 3;
+ return 1;
+}
+#endif
+
+#ifndef STBI_NO_PSD
+static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ int channelCount;
+ if (stbi__get32be(s) != 0x38425053) {
+ stbi__rewind( s );
+ return 0;
+ }
+ if (stbi__get16be(s) != 1) {
+ stbi__rewind( s );
+ return 0;
+ }
+ stbi__skip(s, 6);
+ channelCount = stbi__get16be(s);
+ if (channelCount < 0 || channelCount > 16) {
+ stbi__rewind( s );
+ return 0;
+ }
+ *y = stbi__get32be(s);
+ *x = stbi__get32be(s);
+ if (stbi__get16be(s) != 8) {
+ stbi__rewind( s );
+ return 0;
+ }
+ if (stbi__get16be(s) != 3) {
+ stbi__rewind( s );
+ return 0;
+ }
+ *comp = 4;
+ return 1;
+}
+#endif
+
+#ifndef STBI_NO_PIC
+static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ int act_comp=0,num_packets=0,chained;
+ stbi__pic_packet packets[10];
+
+ if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) {
+ stbi__rewind(s);
+ return 0;
+ }
+
+ stbi__skip(s, 88);
+
+ *x = stbi__get16be(s);
+ *y = stbi__get16be(s);
+ if (stbi__at_eof(s)) {
+ stbi__rewind( s);
+ return 0;
+ }
+ if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) {
+ stbi__rewind( s );
+ return 0;
+ }
+
+ stbi__skip(s, 8);
+
+ do {
+ stbi__pic_packet *packet;
+
+ if (num_packets==sizeof(packets)/sizeof(packets[0]))
+ return 0;
+
+ packet = &packets[num_packets++];
+ chained = stbi__get8(s);
+ packet->size = stbi__get8(s);
+ packet->type = stbi__get8(s);
+ packet->channel = stbi__get8(s);
+ act_comp |= packet->channel;
+
+ if (stbi__at_eof(s)) {
+ stbi__rewind( s );
+ return 0;
+ }
+ if (packet->size != 8) {
+ stbi__rewind( s );
+ return 0;
+ }
+ } while (chained);
+
+ *comp = (act_comp & 0x10 ? 4 : 3);
+
+ return 1;
+}
+#endif
+
+// *************************************************************************************************
+// Portable Gray Map and Portable Pixel Map loader
+// by Ken Miller
+//
+// PGM: http://netpbm.sourceforge.net/doc/pgm.html
+// PPM: http://netpbm.sourceforge.net/doc/ppm.html
+//
+// Known limitations:
+// Does not support comments in the header section
+// Does not support ASCII image data (formats P2 and P3)
+// Does not support 16-bit-per-channel
+
+#ifndef STBI_NO_PNM
+
+static int stbi__pnm_test(stbi__context *s)
+{
+ char p, t;
+ p = (char) stbi__get8(s);
+ t = (char) stbi__get8(s);
+ if (p != 'P' || (t != '5' && t != '6')) {
+ stbi__rewind( s );
+ return 0;
+ }
+ return 1;
+}
+
+static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+ stbi_uc *out;
+ if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n))
+ return 0;
+ *x = s->img_x;
+ *y = s->img_y;
+ *comp = s->img_n;
+
+ out = (stbi_uc *) stbi__malloc(s->img_n * s->img_x * s->img_y);
+ if (!out) return stbi__errpuc("outofmem", "Out of memory");
+ stbi__getn(s, out, s->img_n * s->img_x * s->img_y);
+
+ if (req_comp && req_comp != s->img_n) {
+ out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
+ if (out == NULL) return out; // stbi__convert_format frees input on failure
+ }
+ return out;
+}
+
+static int stbi__pnm_isspace(char c)
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
+}
+
+static void stbi__pnm_skip_whitespace(stbi__context *s, char *c)
+{
+ for (;;) {
+ while (!stbi__at_eof(s) && stbi__pnm_isspace(*c))
+ *c = (char) stbi__get8(s);
+
+ if (stbi__at_eof(s) || *c != '#')
+ break;
+
+ while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' )
+ *c = (char) stbi__get8(s);
+ }
+}
+
+static int stbi__pnm_isdigit(char c)
+{
+ return c >= '0' && c <= '9';
+}
+
+static int stbi__pnm_getinteger(stbi__context *s, char *c)
+{
+ int value = 0;
+
+ while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) {
+ value = value*10 + (*c - '0');
+ *c = (char) stbi__get8(s);
+ }
+
+ return value;
+}
+
+static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)
+{
+ int maxv;
+ char c, p, t;
+
+ stbi__rewind( s );
+
+ // Get identifier
+ p = (char) stbi__get8(s);
+ t = (char) stbi__get8(s);
+ if (p != 'P' || (t != '5' && t != '6')) {
+ stbi__rewind( s );
+ return 0;
+ }
+
+ *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm
+
+ c = (char) stbi__get8(s);
+ stbi__pnm_skip_whitespace(s, &c);
+
+ *x = stbi__pnm_getinteger(s, &c); // read width
+ stbi__pnm_skip_whitespace(s, &c);
+
+ *y = stbi__pnm_getinteger(s, &c); // read height
+ stbi__pnm_skip_whitespace(s, &c);
+
+ maxv = stbi__pnm_getinteger(s, &c); // read max value
+
+ if (maxv > 255)
+ return stbi__err("max value > 255", "PPM image not 8-bit");
+ else
+ return 1;
+}
+#endif
+
+static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp)
+{
+ #ifndef STBI_NO_JPEG
+ if (stbi__jpeg_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_PNG
+ if (stbi__png_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_GIF
+ if (stbi__gif_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_BMP
+ if (stbi__bmp_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_PSD
+ if (stbi__psd_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_PIC
+ if (stbi__pic_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_PNM
+ if (stbi__pnm_info(s, x, y, comp)) return 1;
+ #endif
+
+ #ifndef STBI_NO_HDR
+ if (stbi__hdr_info(s, x, y, comp)) return 1;
+ #endif
+
+ // test tga last because it's a crappy test!
+ #ifndef STBI_NO_TGA
+ if (stbi__tga_info(s, x, y, comp))
+ return 1;
+ #endif
+ return stbi__err("unknown image type", "Image not of any known type, or corrupt");
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp)
+{
+ FILE *f = stbi__fopen(filename, "rb");
+ int result;
+ if (!f) return stbi__err("can't fopen", "Unable to open file");
+ result = stbi_info_from_file(f, x, y, comp);
+ fclose(f);
+ return result;
+}
+
+STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp)
+{
+ int r;
+ stbi__context s;
+ long pos = ftell(f);
+ stbi__start_file(&s, f);
+ r = stbi__info_main(&s,x,y,comp);
+ fseek(f,pos,SEEK_SET);
+ return r;
+}
+#endif // !STBI_NO_STDIO
+
+STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp)
+{
+ stbi__context s;
+ stbi__start_mem(&s,buffer,len);
+ return stbi__info_main(&s,x,y,comp);
+}
+
+STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp)
+{
+ stbi__context s;
+ stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
+ return stbi__info_main(&s,x,y,comp);
+}
+
+#endif // STB_IMAGE_IMPLEMENTATION
+
+/*
+ revision history:
+ 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
+ 2.11 (2016-04-02) allocate large structures on the stack
+ remove white matting for transparent PSD
+ fix reported channel count for PNG & BMP
+ re-enable SSE2 in non-gcc 64-bit
+ support RGB-formatted JPEG
+ read 16-bit PNGs (only as 8-bit)
+ 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED
+ 2.09 (2016-01-16) allow comments in PNM files
+ 16-bit-per-pixel TGA (not bit-per-component)
+ info() for TGA could break due to .hdr handling
+ info() for BMP to shares code instead of sloppy parse
+ can use STBI_REALLOC_SIZED if allocator doesn't support realloc
+ code cleanup
+ 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA
+ 2.07 (2015-09-13) fix compiler warnings
+ partial animated GIF support
+ limited 16-bpc PSD support
+ #ifdef unused functions
+ bug with < 92 byte PIC,PNM,HDR,TGA
+ 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value
+ 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning
+ 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit
+ 2.03 (2015-04-12) extra corruption checking (mmozeiko)
+ stbi_set_flip_vertically_on_load (nguillemot)
+ fix NEON support; fix mingw support
+ 2.02 (2015-01-19) fix incorrect assert, fix warning
+ 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2
+ 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG
+ 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg)
+ progressive JPEG (stb)
+ PGM/PPM support (Ken Miller)
+ STBI_MALLOC,STBI_REALLOC,STBI_FREE
+ GIF bugfix -- seemingly never worked
+ STBI_NO_*, STBI_ONLY_*
+ 1.48 (2014-12-14) fix incorrectly-named assert()
+ 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb)
+ optimize PNG (ryg)
+ fix bug in interlaced PNG with user-specified channel count (stb)
+ 1.46 (2014-08-26)
+ fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG
+ 1.45 (2014-08-16)
+ fix MSVC-ARM internal compiler error by wrapping malloc
+ 1.44 (2014-08-07)
+ various warning fixes from Ronny Chevalier
+ 1.43 (2014-07-15)
+ fix MSVC-only compiler problem in code changed in 1.42
+ 1.42 (2014-07-09)
+ don't define _CRT_SECURE_NO_WARNINGS (affects user code)
+ fixes to stbi__cleanup_jpeg path
+ added STBI_ASSERT to avoid requiring assert.h
+ 1.41 (2014-06-25)
+ fix search&replace from 1.36 that messed up comments/error messages
+ 1.40 (2014-06-22)
+ fix gcc struct-initialization warning
+ 1.39 (2014-06-15)
+ fix to TGA optimization when req_comp != number of components in TGA;
+ fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite)
+ add support for BMP version 5 (more ignored fields)
+ 1.38 (2014-06-06)
+ suppress MSVC warnings on integer casts truncating values
+ fix accidental rename of 'skip' field of I/O
+ 1.37 (2014-06-04)
+ remove duplicate typedef
+ 1.36 (2014-06-03)
+ convert to header file single-file library
+ if de-iphone isn't set, load iphone images color-swapped instead of returning NULL
+ 1.35 (2014-05-27)
+ various warnings
+ fix broken STBI_SIMD path
+ fix bug where stbi_load_from_file no longer left file pointer in correct place
+ fix broken non-easy path for 32-bit BMP (possibly never used)
+ TGA optimization by Arseny Kapoulkine
+ 1.34 (unknown)
+ use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case
+ 1.33 (2011-07-14)
+ make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements
+ 1.32 (2011-07-13)
+ support for "info" function for all supported filetypes (SpartanJ)
+ 1.31 (2011-06-20)
+ a few more leak fixes, bug in PNG handling (SpartanJ)
+ 1.30 (2011-06-11)
+ added ability to load files via callbacks to accomidate custom input streams (Ben Wenger)
+ removed deprecated format-specific test/load functions
+ removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway
+ error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha)
+ fix inefficiency in decoding 32-bit BMP (David Woo)
+ 1.29 (2010-08-16)
+ various warning fixes from Aurelien Pocheville
+ 1.28 (2010-08-01)
+ fix bug in GIF palette transparency (SpartanJ)
+ 1.27 (2010-08-01)
+ cast-to-stbi_uc to fix warnings
+ 1.26 (2010-07-24)
+ fix bug in file buffering for PNG reported by SpartanJ
+ 1.25 (2010-07-17)
+ refix trans_data warning (Won Chun)
+ 1.24 (2010-07-12)
+ perf improvements reading from files on platforms with lock-heavy fgetc()
+ minor perf improvements for jpeg
+ deprecated type-specific functions so we'll get feedback if they're needed
+ attempt to fix trans_data warning (Won Chun)
+ 1.23 fixed bug in iPhone support
+ 1.22 (2010-07-10)
+ removed image *writing* support
+ stbi_info support from Jetro Lauha
+ GIF support from Jean-Marc Lienher
+ iPhone PNG-extensions from James Brown
+ warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva)
+ 1.21 fix use of 'stbi_uc' in header (reported by jon blow)
+ 1.20 added support for Softimage PIC, by Tom Seddon
+ 1.19 bug in interlaced PNG corruption check (found by ryg)
+ 1.18 (2008-08-02)
+ fix a threading bug (local mutable static)
+ 1.17 support interlaced PNG
+ 1.16 major bugfix - stbi__convert_format converted one too many pixels
+ 1.15 initialize some fields for thread safety
+ 1.14 fix threadsafe conversion bug
+ header-file-only version (#define STBI_HEADER_FILE_ONLY before including)
+ 1.13 threadsafe
+ 1.12 const qualifiers in the API
+ 1.11 Support installable IDCT, colorspace conversion routines
+ 1.10 Fixes for 64-bit (don't use "unsigned long")
+ optimized upsampling by Fabian "ryg" Giesen
+ 1.09 Fix format-conversion for PSD code (bad global variables!)
+ 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz
+ 1.07 attempt to fix C++ warning/errors again
+ 1.06 attempt to fix C++ warning/errors again
+ 1.05 fix TGA loading to return correct *comp and use good luminance calc
+ 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free
+ 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR
+ 1.02 support for (subset of) HDR files, float interface for preferred access to them
+ 1.01 fix bug: possible bug in handling right-side up bmps... not sure
+ fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all
+ 1.00 interface to zlib that skips zlib header
+ 0.99 correct handling of alpha in palette
+ 0.98 TGA loader by lonesock; dynamically add loaders (untested)
+ 0.97 jpeg errors on too large a file; also catch another malloc failure
+ 0.96 fix detection of invalid v value - particleman@mollyrocket forum
+ 0.95 during header scan, seek to markers in case of padding
+ 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same
+ 0.93 handle jpegtran output; verbose errors
+ 0.92 read 4,8,16,24,32-bit BMP files of several formats
+ 0.91 output 24-bit Windows 3.0 BMP files
+ 0.90 fix a few more warnings; bump version number to approach 1.0
+ 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd
+ 0.60 fix compiling as c++
+ 0.59 fix warnings: merge Dave Moore's -Wall fixes
+ 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian
+ 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available
+ 0.56 fix bug: zlib uncompressed mode len vs. nlen
+ 0.55 fix bug: restart_interval not initialized to 0
+ 0.54 allow NULL for 'int *comp'
+ 0.53 fix bug in png 3->4; speedup png decoding
+ 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments
+ 0.51 obey req_comp requests, 1-component jpegs return as 1-component,
+ on 'test' only check type, not whether we support this variant
+ 0.50 (2006-11-19)
+ first released version
+*/
diff --git a/examples/viewer/trackball.cc b/examples/viewer/trackball.cc
new file mode 100644
index 0000000..86ff3b3
--- /dev/null
+++ b/examples/viewer/trackball.cc
@@ -0,0 +1,292 @@
+/*
+ * (c) Copyright 1993, 1994, Silicon Graphics, Inc.
+ * ALL RIGHTS RESERVED
+ * Permission to use, copy, modify, and distribute this software for
+ * any purpose and without fee is hereby granted, provided that the above
+ * copyright notice appear in all copies and that both the copyright notice
+ * and this permission notice appear in supporting documentation, and that
+ * the name of Silicon Graphics, Inc. not be used in advertising
+ * or publicity pertaining to distribution of the software without specific,
+ * written prior permission.
+ *
+ * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
+ * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
+ * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
+ * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
+ * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
+ * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
+ * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
+ * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN
+ * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
+ * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * US Government Users Restricted Rights
+ * Use, duplication, or disclosure by the Government is subject to
+ * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
+ * (c)(1)(ii) of the Rights in Technical Data and Computer Software
+ * clause at DFARS 252.227-7013 and/or in similar or successor
+ * clauses in the FAR or the DOD or NASA FAR Supplement.
+ * Unpublished-- rights reserved under the copyright laws of the
+ * United States. Contractor/manufacturer is Silicon Graphics,
+ * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311.
+ *
+ * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
+ */
+/*
+ * Trackball code:
+ *
+ * Implementation of a virtual trackball.
+ * Implemented by Gavin Bell, lots of ideas from Thant Tessman and
+ * the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129.
+ *
+ * Vector manip code:
+ *
+ * Original code from:
+ * David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli
+ *
+ * Much mucking with by:
+ * Gavin Bell
+ */
+#include <math.h>
+#include "trackball.h"
+
+/*
+ * This size should really be based on the distance from the center of
+ * rotation to the point on the object underneath the mouse. That
+ * point would then track the mouse as closely as possible. This is a
+ * simple example, though, so that is left as an Exercise for the
+ * Programmer.
+ */
+#define TRACKBALLSIZE (0.8)
+
+/*
+ * Local function prototypes (not defined in trackball.h)
+ */
+static float tb_project_to_sphere(float, float, float);
+static void normalize_quat(float[4]);
+
+static void vzero(float *v) {
+ v[0] = 0.0;
+ v[1] = 0.0;
+ v[2] = 0.0;
+}
+
+static void vset(float *v, float x, float y, float z) {
+ v[0] = x;
+ v[1] = y;
+ v[2] = z;
+}
+
+static void vsub(const float *src1, const float *src2, float *dst) {
+ dst[0] = src1[0] - src2[0];
+ dst[1] = src1[1] - src2[1];
+ dst[2] = src1[2] - src2[2];
+}
+
+static void vcopy(const float *v1, float *v2) {
+ register int i;
+ for (i = 0; i < 3; i++)
+ v2[i] = v1[i];
+}
+
+static void vcross(const float *v1, const float *v2, float *cross) {
+ float temp[3];
+
+ temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
+ temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
+ temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
+ vcopy(temp, cross);
+}
+
+static float vlength(const float *v) {
+ return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+}
+
+static void vscale(float *v, float div) {
+ v[0] *= div;
+ v[1] *= div;
+ v[2] *= div;
+}
+
+static void vnormal(float *v) { vscale(v, 1.0 / vlength(v)); }
+
+static float vdot(const float *v1, const float *v2) {
+ return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
+}
+
+static void vadd(const float *src1, const float *src2, float *dst) {
+ dst[0] = src1[0] + src2[0];
+ dst[1] = src1[1] + src2[1];
+ dst[2] = src1[2] + src2[2];
+}
+
+/*
+ * Ok, simulate a track-ball. Project the points onto the virtual
+ * trackball, then figure out the axis of rotation, which is the cross
+ * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0)
+ * Note: This is a deformed trackball-- is a trackball in the center,
+ * but is deformed into a hyperbolic sheet of rotation away from the
+ * center. This particular function was chosen after trying out
+ * several variations.
+ *
+ * It is assumed that the arguments to this routine are in the range
+ * (-1.0 ... 1.0)
+ */
+void trackball(float q[4], float p1x, float p1y, float p2x, float p2y) {
+ float a[3]; /* Axis of rotation */
+ float phi; /* how much to rotate about axis */
+ float p1[3], p2[3], d[3];
+ float t;
+
+ if (p1x == p2x && p1y == p2y) {
+ /* Zero rotation */
+ vzero(q);
+ q[3] = 1.0;
+ return;
+ }
+
+ /*
+ * First, figure out z-coordinates for projection of P1 and P2 to
+ * deformed sphere
+ */
+ vset(p1, p1x, p1y, tb_project_to_sphere(TRACKBALLSIZE, p1x, p1y));
+ vset(p2, p2x, p2y, tb_project_to_sphere(TRACKBALLSIZE, p2x, p2y));
+
+ /*
+ * Now, we want the cross product of P1 and P2
+ */
+ vcross(p2, p1, a);
+
+ /*
+ * Figure out how much to rotate around that axis.
+ */
+ vsub(p1, p2, d);
+ t = vlength(d) / (2.0 * TRACKBALLSIZE);
+
+ /*
+ * Avoid problems with out-of-control values...
+ */
+ if (t > 1.0)
+ t = 1.0;
+ if (t < -1.0)
+ t = -1.0;
+ phi = 2.0 * asin(t);
+
+ axis_to_quat(a, phi, q);
+}
+
+/*
+ * Given an axis and angle, compute quaternion.
+ */
+void axis_to_quat(float a[3], float phi, float q[4]) {
+ vnormal(a);
+ vcopy(a, q);
+ vscale(q, sin(phi / 2.0));
+ q[3] = cos(phi / 2.0);
+}
+
+/*
+ * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
+ * if we are away from the center of the sphere.
+ */
+static float tb_project_to_sphere(float r, float x, float y) {
+ float d, t, z;
+
+ d = sqrt(x * x + y * y);
+ if (d < r * 0.70710678118654752440) { /* Inside sphere */
+ z = sqrt(r * r - d * d);
+ } else { /* On hyperbola */
+ t = r / 1.41421356237309504880;
+ z = t * t / d;
+ }
+ return z;
+}
+
+/*
+ * Given two rotations, e1 and e2, expressed as quaternion rotations,
+ * figure out the equivalent single rotation and stuff it into dest.
+ *
+ * This routine also normalizes the result every RENORMCOUNT times it is
+ * called, to keep error from creeping in.
+ *
+ * NOTE: This routine is written so that q1 or q2 may be the same
+ * as dest (or each other).
+ */
+
+#define RENORMCOUNT 97
+
+void add_quats(float q1[4], float q2[4], float dest[4]) {
+ static int count = 0;
+ float t1[4], t2[4], t3[4];
+ float tf[4];
+
+ vcopy(q1, t1);
+ vscale(t1, q2[3]);
+
+ vcopy(q2, t2);
+ vscale(t2, q1[3]);
+
+ vcross(q2, q1, t3);
+ vadd(t1, t2, tf);
+ vadd(t3, tf, tf);
+ tf[3] = q1[3] * q2[3] - vdot(q1, q2);
+
+ dest[0] = tf[0];
+ dest[1] = tf[1];
+ dest[2] = tf[2];
+ dest[3] = tf[3];
+
+ if (++count > RENORMCOUNT) {
+ count = 0;
+ normalize_quat(dest);
+ }
+}
+
+/*
+ * Quaternions always obey: a^2 + b^2 + c^2 + d^2 = 1.0
+ * If they don't add up to 1.0, dividing by their magnitued will
+ * renormalize them.
+ *
+ * Note: See the following for more information on quaternions:
+ *
+ * - Shoemake, K., Animating rotation with quaternion curves, Computer
+ * Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985.
+ * - Pletinckx, D., Quaternion calculus as a basic tool in computer
+ * graphics, The Visual Computer 5, 2-13, 1989.
+ */
+static void normalize_quat(float q[4]) {
+ int i;
+ float mag;
+
+ mag = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
+ for (i = 0; i < 4; i++)
+ q[i] /= mag;
+}
+
+/*
+ * Build a rotation matrix, given a quaternion rotation.
+ *
+ */
+void build_rotmatrix(float m[4][4], const float q[4]) {
+ m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]);
+ m[0][1] = 2.0 * (q[0] * q[1] - q[2] * q[3]);
+ m[0][2] = 2.0 * (q[2] * q[0] + q[1] * q[3]);
+ m[0][3] = 0.0;
+
+ m[1][0] = 2.0 * (q[0] * q[1] + q[2] * q[3]);
+ m[1][1] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]);
+ m[1][2] = 2.0 * (q[1] * q[2] - q[0] * q[3]);
+ m[1][3] = 0.0;
+
+ m[2][0] = 2.0 * (q[2] * q[0] - q[1] * q[3]);
+ m[2][1] = 2.0 * (q[1] * q[2] + q[0] * q[3]);
+ m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]);
+ m[2][3] = 0.0;
+
+ m[3][0] = 0.0;
+ m[3][1] = 0.0;
+ m[3][2] = 0.0;
+ m[3][3] = 1.0;
+}
diff --git a/examples/viewer/trackball.h b/examples/viewer/trackball.h
new file mode 100644
index 0000000..b1f9437
--- /dev/null
+++ b/examples/viewer/trackball.h
@@ -0,0 +1,75 @@
+/*
+ * (c) Copyright 1993, 1994, Silicon Graphics, Inc.
+ * ALL RIGHTS RESERVED
+ * Permission to use, copy, modify, and distribute this software for
+ * any purpose and without fee is hereby granted, provided that the above
+ * copyright notice appear in all copies and that both the copyright notice
+ * and this permission notice appear in supporting documentation, and that
+ * the name of Silicon Graphics, Inc. not be used in advertising
+ * or publicity pertaining to distribution of the software without specific,
+ * written prior permission.
+ *
+ * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
+ * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
+ * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
+ * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
+ * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
+ * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
+ * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
+ * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN
+ * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
+ * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * US Government Users Restricted Rights
+ * Use, duplication, or disclosure by the Government is subject to
+ * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
+ * (c)(1)(ii) of the Rights in Technical Data and Computer Software
+ * clause at DFARS 252.227-7013 and/or in similar or successor
+ * clauses in the FAR or the DOD or NASA FAR Supplement.
+ * Unpublished-- rights reserved under the copyright laws of the
+ * United States. Contractor/manufacturer is Silicon Graphics,
+ * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311.
+ *
+ * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
+ */
+/*
+ * trackball.h
+ * A virtual trackball implementation
+ * Written by Gavin Bell for Silicon Graphics, November 1988.
+ */
+
+/*
+ * Pass the x and y coordinates of the last and current positions of
+ * the mouse, scaled so they are from (-1.0 ... 1.0).
+ *
+ * The resulting rotation is returned as a quaternion rotation in the
+ * first paramater.
+ */
+void trackball(float q[4], float p1x, float p1y, float p2x, float p2y);
+
+void negate_quat(float *q, float *qn);
+
+/*
+ * Given two quaternions, add them together to get a third quaternion.
+ * Adding quaternions to get a compound rotation is analagous to adding
+ * translations to get a compound translation. When incrementally
+ * adding rotations, the first argument here should be the new
+ * rotation, the second and third the total rotation (which will be
+ * over-written with the resulting new total rotation).
+ */
+void add_quats(float *q1, float *q2, float *dest);
+
+/*
+ * A useful function, builds a rotation matrix in Matrix based on
+ * given quaternion.
+ */
+void build_rotmatrix(float m[4][4], const float q[4]);
+
+/*
+ * This function computes a quaternion based on an axis (defined by
+ * the given vector) and an angle about which to rotate. The angle is
+ * expressed in radians. The result is put into the third argument.
+ */
+void axis_to_quat(float a[3], float phi, float q[4]);
diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc
new file mode 100644
index 0000000..4747232
--- /dev/null
+++ b/examples/viewer/viewer.cc
@@ -0,0 +1,713 @@
+//
+// Simple .obj viewer(vertex only)
+//
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <GL/glew.h>
+
+#ifdef __APPLE__
+#include <OpenGL/glu.h>
+#else
+#include <GL/glu.h>
+#endif
+
+#include <GLFW/glfw3.h>
+
+#define TINYOBJLOADER_IMPLEMENTATION
+#include "../../tiny_obj_loader.h"
+
+#include "trackball.h"
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
+
+#ifdef _WIN32
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <windows.h>
+
+#ifdef max
+ #undef max
+#endif
+
+#ifdef min
+ #undef min
+#endif
+
+#include <mmsystem.h>
+#ifdef __cplusplus
+}
+#endif
+#pragma comment(lib, "winmm.lib")
+#else
+#if defined(__unix__) || defined(__APPLE__)
+#include <sys/time.h>
+#else
+#include <ctime>
+#endif
+#endif
+
+class timerutil {
+ public:
+#ifdef _WIN32
+ typedef DWORD time_t;
+
+ timerutil() { ::timeBeginPeriod(1); }
+ ~timerutil() { ::timeEndPeriod(1); }
+
+ void start() { t_[0] = ::timeGetTime(); }
+ void end() { t_[1] = ::timeGetTime(); }
+
+ time_t sec() { return (time_t)((t_[1] - t_[0]) / 1000); }
+ time_t msec() { return (time_t)((t_[1] - t_[0])); }
+ time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000); }
+ time_t current() { return ::timeGetTime(); }
+
+#else
+#if defined(__unix__) || defined(__APPLE__)
+ typedef unsigned long int time_t;
+
+ void start() { gettimeofday(tv + 0, &tz); }
+ void end() { gettimeofday(tv + 1, &tz); }
+
+ time_t sec() { return (time_t)(tv[1].tv_sec - tv[0].tv_sec); }
+ time_t msec() {
+ return this->sec() * 1000 +
+ (time_t)((tv[1].tv_usec - tv[0].tv_usec) / 1000);
+ }
+ time_t usec() {
+ return this->sec() * 1000000 + (time_t)(tv[1].tv_usec - tv[0].tv_usec);
+ }
+ time_t current() {
+ struct timeval t;
+ gettimeofday(&t, NULL);
+ return (time_t)(t.tv_sec * 1000 + t.tv_usec);
+ }
+
+#else // C timer
+ // using namespace std;
+ typedef clock_t time_t;
+
+ void start() { t_[0] = clock(); }
+ void end() { t_[1] = clock(); }
+
+ time_t sec() { return (time_t)((t_[1] - t_[0]) / CLOCKS_PER_SEC); }
+ time_t msec() { return (time_t)((t_[1] - t_[0]) * 1000 / CLOCKS_PER_SEC); }
+ time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000000 / CLOCKS_PER_SEC); }
+ time_t current() { return (time_t)clock(); }
+
+#endif
+#endif
+
+ private:
+#ifdef _WIN32
+ DWORD t_[2];
+#else
+#if defined(__unix__) || defined(__APPLE__)
+ struct timeval tv[2];
+ struct timezone tz;
+#else
+ time_t t_[2];
+#endif
+#endif
+};
+
+typedef struct {
+ GLuint vb_id; // vertex buffer id
+ int numTriangles;
+ size_t material_id;
+} DrawObject;
+
+std::vector<DrawObject> gDrawObjects;
+
+int width = 768;
+int height = 768;
+
+double prevMouseX, prevMouseY;
+bool mouseLeftPressed;
+bool mouseMiddlePressed;
+bool mouseRightPressed;
+float curr_quat[4];
+float prev_quat[4];
+float eye[3], lookat[3], up[3];
+
+GLFWwindow* window;
+
+static std::string GetBaseDir(const std::string &filepath) {
+ if (filepath.find_last_of("/\\") != std::string::npos)
+ return filepath.substr(0, filepath.find_last_of("/\\"));
+ return "";
+}
+
+static bool FileExists(const std::string &abs_filename) {
+ bool ret;
+ FILE *fp = fopen(abs_filename.c_str(), "rb");
+ if (fp) {
+ ret = true;
+ fclose(fp);
+ } else {
+ ret = false;
+ }
+
+ return ret;
+}
+
+static void CheckErrors(std::string desc) {
+ GLenum e = glGetError();
+ if (e != GL_NO_ERROR) {
+ fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e);
+ exit(20);
+ }
+}
+
+static void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) {
+ float v10[3];
+ v10[0] = v1[0] - v0[0];
+ v10[1] = v1[1] - v0[1];
+ v10[2] = v1[2] - v0[2];
+
+ float v20[3];
+ v20[0] = v2[0] - v0[0];
+ v20[1] = v2[1] - v0[1];
+ v20[2] = v2[2] - v0[2];
+
+ N[0] = v20[1] * v10[2] - v20[2] * v10[1];
+ N[1] = v20[2] * v10[0] - v20[0] * v10[2];
+ N[2] = v20[0] * v10[1] - v20[1] * v10[0];
+
+ float len2 = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
+ if (len2 > 0.0f) {
+ float len = sqrtf(len2);
+
+ N[0] /= len;
+ N[1] /= len;
+ }
+}
+
+static bool LoadObjAndConvert(float bmin[3], float bmax[3],
+ std::vector<DrawObject>* drawObjects,
+ std::vector<tinyobj::material_t>& materials,
+ std::map<std::string, GLuint>& textures,
+ const char* filename) {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+
+ timerutil tm;
+
+ tm.start();
+
+ std::string base_dir = GetBaseDir(filename);
+ if (base_dir.empty()) {
+ base_dir = ".";
+ }
+#ifdef _WIN32
+ base_dir += "\\";
+#else
+ base_dir += "/";
+#endif
+
+ std::string err;
+ bool ret =
+ tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, base_dir.c_str());
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ tm.end();
+
+ if (!ret) {
+ std::cerr << "Failed to load " << filename << std::endl;
+ return false;
+ }
+
+ printf("Parsing time: %d [ms]\n", (int)tm.msec());
+
+ printf("# of vertices = %d\n", (int)(attrib.vertices.size()) / 3);
+ printf("# of normals = %d\n", (int)(attrib.normals.size()) / 3);
+ printf("# of texcoords = %d\n", (int)(attrib.texcoords.size()) / 2);
+ printf("# of materials = %d\n", (int)materials.size());
+ printf("# of shapes = %d\n", (int)shapes.size());
+
+ // Append `default` material
+ materials.push_back(tinyobj::material_t());
+
+ for (size_t i = 0; i < materials.size(); i++) {
+ printf("material[%d].diffuse_texname = %s\n", int(i), materials[i].diffuse_texname.c_str());
+ }
+
+ // Load diffuse textures
+ {
+ for (size_t m = 0; m < materials.size(); m++) {
+ tinyobj::material_t* mp = &materials[m];
+
+ if (mp->diffuse_texname.length() > 0) {
+ // Only load the texture if it is not already loaded
+ if (textures.find(mp->diffuse_texname) == textures.end()) {
+ GLuint texture_id;
+ int w, h;
+ int comp;
+
+ std::string texture_filename = mp->diffuse_texname;
+ if (!FileExists(texture_filename)) {
+ // Append base dir.
+ texture_filename = base_dir + mp->diffuse_texname;
+ if (!FileExists(texture_filename)) {
+ std::cerr << "Unable to find file: " << mp->diffuse_texname << std::endl;
+ exit(1);
+ }
+ }
+
+ unsigned char* image = stbi_load(texture_filename.c_str(), &w, &h, &comp, STBI_default);
+ if (!image) {
+ std::cerr << "Unable to load texture: " << texture_filename << std::endl;
+ exit(1);
+ }
+ std::cout << "Loaded texture: " << texture_filename << ", w = " << w << ", h = " << h << ", comp = " << comp << std::endl;
+
+ glGenTextures(1, &texture_id);
+ glBindTexture(GL_TEXTURE_2D, texture_id);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ if (comp == 3) {
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
+ }
+ else if (comp == 4) {
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
+ } else {
+ assert(0); // TODO
+ }
+ glBindTexture(GL_TEXTURE_2D, 0);
+ stbi_image_free(image);
+ textures.insert(std::make_pair(mp->diffuse_texname, texture_id));
+ }
+ }
+ }
+ }
+
+ bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max();
+ bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
+
+ {
+ for (size_t s = 0; s < shapes.size(); s++) {
+ DrawObject o;
+ std::vector<float> buffer; // pos(3float), normal(3float), color(3float)
+ for (size_t f = 0; f < shapes[s].mesh.indices.size() / 3; f++) {
+ tinyobj::index_t idx0 = shapes[s].mesh.indices[3 * f + 0];
+ tinyobj::index_t idx1 = shapes[s].mesh.indices[3 * f + 1];
+ tinyobj::index_t idx2 = shapes[s].mesh.indices[3 * f + 2];
+
+ int current_material_id = shapes[s].mesh.material_ids[f];
+
+ if ((current_material_id < 0) || (current_material_id >= static_cast<int>(materials.size()))) {
+ // Invaid material ID. Use default material.
+ current_material_id = materials.size() - 1; // Default material is added to the last item in `materials`.
+ }
+ //if (current_material_id >= materials.size()) {
+ // std::cerr << "Invalid material index: " << current_material_id << std::endl;
+ //}
+ //
+ float diffuse[3];
+ for (size_t i = 0; i < 3; i++) {
+ diffuse[i] = materials[current_material_id].diffuse[i];
+ }
+ float tc[3][2];
+ if (attrib.texcoords.size() > 0) {
+ assert(attrib.texcoords.size() > 2 * idx0.texcoord_index + 1);
+ assert(attrib.texcoords.size() > 2 * idx1.texcoord_index + 1);
+ assert(attrib.texcoords.size() > 2 * idx2.texcoord_index + 1);
+
+ // Flip Y coord.
+ tc[0][0] = attrib.texcoords[2 * idx0.texcoord_index];
+ tc[0][1] = 1.0f - attrib.texcoords[2 * idx0.texcoord_index + 1];
+ tc[1][0] = attrib.texcoords[2 * idx1.texcoord_index];
+ tc[1][1] = 1.0f - attrib.texcoords[2 * idx1.texcoord_index + 1];
+ tc[2][0] = attrib.texcoords[2 * idx2.texcoord_index];
+ tc[2][1] = 1.0f - attrib.texcoords[2 * idx2.texcoord_index + 1];
+ } else {
+ tc[0][0] = 0.0f;
+ tc[0][1] = 0.0f;
+ tc[1][0] = 0.0f;
+ tc[1][1] = 0.0f;
+ tc[2][0] = 0.0f;
+ tc[2][1] = 0.0f;
+ }
+
+ float v[3][3];
+ for (int k = 0; k < 3; k++) {
+ int f0 = idx0.vertex_index;
+ int f1 = idx1.vertex_index;
+ int f2 = idx2.vertex_index;
+ assert(f0 >= 0);
+ assert(f1 >= 0);
+ assert(f2 >= 0);
+
+ v[0][k] = attrib.vertices[3 * f0 + k];
+ v[1][k] = attrib.vertices[3 * f1 + k];
+ v[2][k] = attrib.vertices[3 * f2 + k];
+ bmin[k] = std::min(v[0][k], bmin[k]);
+ bmin[k] = std::min(v[1][k], bmin[k]);
+ bmin[k] = std::min(v[2][k], bmin[k]);
+ bmax[k] = std::max(v[0][k], bmax[k]);
+ bmax[k] = std::max(v[1][k], bmax[k]);
+ bmax[k] = std::max(v[2][k], bmax[k]);
+ }
+
+ float n[3][3];
+ if (attrib.normals.size() > 0) {
+ int f0 = idx0.normal_index;
+ int f1 = idx1.normal_index;
+ int f2 = idx2.normal_index;
+ assert(f0 >= 0);
+ assert(f1 >= 0);
+ assert(f2 >= 0);
+ for (int k = 0; k < 3; k++) {
+ n[0][k] = attrib.normals[3 * f0 + k];
+ n[1][k] = attrib.normals[3 * f1 + k];
+ n[2][k] = attrib.normals[3 * f2 + k];
+ }
+ } else {
+ // compute geometric normal
+ CalcNormal(n[0], v[0], v[1], v[2]);
+ n[1][0] = n[0][0];
+ n[1][1] = n[0][1];
+ n[1][2] = n[0][2];
+ n[2][0] = n[0][0];
+ n[2][1] = n[0][1];
+ n[2][2] = n[0][2];
+ }
+
+ for (int k = 0; k < 3; k++) {
+ buffer.push_back(v[k][0]);
+ buffer.push_back(v[k][1]);
+ buffer.push_back(v[k][2]);
+ buffer.push_back(n[k][0]);
+ buffer.push_back(n[k][1]);
+ buffer.push_back(n[k][2]);
+ // Combine normal and diffuse to get color.
+ float normal_factor = 0.2;
+ float diffuse_factor = 1 - normal_factor;
+ float c[3] = {
+ n[k][0] * normal_factor + diffuse[0] * diffuse_factor,
+ n[k][1] * normal_factor + diffuse[1] * diffuse_factor,
+ n[k][2] * normal_factor + diffuse[2] * diffuse_factor
+ };
+ float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2];
+ if (len2 > 0.0f) {
+ float len = sqrtf(len2);
+
+ c[0] /= len;
+ c[1] /= len;
+ c[2] /= len;
+ }
+ buffer.push_back(c[0] * 0.5 + 0.5);
+ buffer.push_back(c[1] * 0.5 + 0.5);
+ buffer.push_back(c[2] * 0.5 + 0.5);
+
+ buffer.push_back(tc[k][0]);
+ buffer.push_back(tc[k][1]);
+ }
+ }
+
+ o.vb_id = 0;
+ o.numTriangles = 0;
+
+ // OpenGL viewer does not support texturing with per-face material.
+ if (shapes[s].mesh.material_ids.size() > 0 && shapes[s].mesh.material_ids.size() > s) {
+ o.material_id = shapes[s].mesh.material_ids[0]; // use the material ID of the first face.
+ } else {
+ o.material_id = materials.size() - 1; // = ID for default material.
+ }
+ printf("shape[%d] material_id %d\n", int(s), int(o.material_id));
+
+ if (buffer.size() > 0) {
+ glGenBuffers(1, &o.vb_id);
+ glBindBuffer(GL_ARRAY_BUFFER, o.vb_id);
+ glBufferData(GL_ARRAY_BUFFER, buffer.size() * sizeof(float), &buffer.at(0),
+ GL_STATIC_DRAW);
+ o.numTriangles = buffer.size() / (3 + 3 + 3 + 2) / 3; // 3:vtx, 3:normal, 3:col, 2:texcoord
+
+ printf("shape[%d] # of triangles = %d\n", static_cast<int>(s),
+ o.numTriangles);
+ }
+
+ drawObjects->push_back(o);
+ }
+ }
+
+ printf("bmin = %f, %f, %f\n", bmin[0], bmin[1], bmin[2]);
+ printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]);
+
+ return true;
+}
+
+static void reshapeFunc(GLFWwindow* window, int w, int h) {
+ int fb_w, fb_h;
+ // Get actual framebuffer size.
+ glfwGetFramebufferSize(window, &fb_w, &fb_h);
+
+ glViewport(0, 0, fb_w, fb_h);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ gluPerspective(45.0, (float)w / (float)h, 0.01f, 100.0f);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ width = w;
+ height = h;
+}
+
+static void keyboardFunc(GLFWwindow* window, int key, int scancode, int action,
+ int mods) {
+ (void)window;
+ (void)scancode;
+ (void)mods;
+ if (action == GLFW_PRESS || action == GLFW_REPEAT) {
+ // Move camera
+ float mv_x = 0, mv_y = 0, mv_z = 0;
+ if (key == GLFW_KEY_K)
+ mv_x += 1;
+ else if (key == GLFW_KEY_J)
+ mv_x += -1;
+ else if (key == GLFW_KEY_L)
+ mv_y += 1;
+ else if (key == GLFW_KEY_H)
+ mv_y += -1;
+ else if (key == GLFW_KEY_P)
+ mv_z += 1;
+ else if (key == GLFW_KEY_N)
+ mv_z += -1;
+ // camera.move(mv_x * 0.05, mv_y * 0.05, mv_z * 0.05);
+ // Close window
+ if (key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE)
+ glfwSetWindowShouldClose(window, GL_TRUE);
+
+ // init_frame = true;
+ }
+}
+
+static void clickFunc(GLFWwindow* window, int button, int action, int mods) {
+ (void)window;
+ (void)mods;
+ if (button == GLFW_MOUSE_BUTTON_LEFT) {
+ if (action == GLFW_PRESS) {
+ mouseLeftPressed = true;
+ trackball(prev_quat, 0.0, 0.0, 0.0, 0.0);
+ } else if (action == GLFW_RELEASE) {
+ mouseLeftPressed = false;
+ }
+ }
+ if (button == GLFW_MOUSE_BUTTON_RIGHT) {
+ if (action == GLFW_PRESS) {
+ mouseRightPressed = true;
+ } else if (action == GLFW_RELEASE) {
+ mouseRightPressed = false;
+ }
+ }
+ if (button == GLFW_MOUSE_BUTTON_MIDDLE) {
+ if (action == GLFW_PRESS) {
+ mouseMiddlePressed = true;
+ } else if (action == GLFW_RELEASE) {
+ mouseMiddlePressed = false;
+ }
+ }
+}
+
+static void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) {
+ (void)window;
+ float rotScale = 1.0f;
+ float transScale = 2.0f;
+
+ if (mouseLeftPressed) {
+ trackball(prev_quat, rotScale * (2.0f * prevMouseX - width) / (float)width,
+ rotScale * (height - 2.0f * prevMouseY) / (float)height,
+ rotScale * (2.0f * mouse_x - width) / (float)width,
+ rotScale * (height - 2.0f * mouse_y) / (float)height);
+
+ add_quats(prev_quat, curr_quat, curr_quat);
+ } else if (mouseMiddlePressed) {
+ eye[0] -= transScale * (mouse_x - prevMouseX) / (float)width;
+ lookat[0] -= transScale * (mouse_x - prevMouseX) / (float)width;
+ eye[1] += transScale * (mouse_y - prevMouseY) / (float)height;
+ lookat[1] += transScale * (mouse_y - prevMouseY) / (float)height;
+ } else if (mouseRightPressed) {
+ eye[2] += transScale * (mouse_y - prevMouseY) / (float)height;
+ lookat[2] += transScale * (mouse_y - prevMouseY) / (float)height;
+ }
+
+ // Update mouse point
+ prevMouseX = mouse_x;
+ prevMouseY = mouse_y;
+}
+
+static void Draw(const std::vector<DrawObject>& drawObjects, std::vector<tinyobj::material_t>& materials, std::map<std::string, GLuint>& textures) {
+ glPolygonMode(GL_FRONT, GL_FILL);
+ glPolygonMode(GL_BACK, GL_FILL);
+
+ glEnable(GL_POLYGON_OFFSET_FILL);
+ glPolygonOffset(1.0, 1.0);
+ GLsizei stride = (3 + 3 + 3 + 2) * sizeof(float);
+ for (size_t i = 0; i < drawObjects.size(); i++) {
+ DrawObject o = drawObjects[i];
+ if (o.vb_id < 1) {
+ continue;
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, o.vb_id);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_NORMAL_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+ if ((o.material_id < materials.size())) {
+ std::string diffuse_texname = materials[o.material_id].diffuse_texname;
+ if (textures.find(diffuse_texname) != textures.end()) {
+ glBindTexture(GL_TEXTURE_2D, textures[diffuse_texname]);
+ }
+ }
+ glVertexPointer(3, GL_FLOAT, stride, (const void*)0);
+ glNormalPointer(GL_FLOAT, stride, (const void*)(sizeof(float) * 3));
+ glColorPointer(3, GL_FLOAT, stride, (const void*)(sizeof(float) * 6));
+ glTexCoordPointer(2, GL_FLOAT, stride, (const void*)(sizeof(float) * 9));
+
+ glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles);
+ CheckErrors("drawarrays");
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ // draw wireframe
+ glDisable(GL_POLYGON_OFFSET_FILL);
+ glPolygonMode(GL_FRONT, GL_LINE);
+ glPolygonMode(GL_BACK, GL_LINE);
+
+ glColor3f(0.0f, 0.0f, 0.4f);
+ for (size_t i = 0; i < drawObjects.size(); i++) {
+ DrawObject o = drawObjects[i];
+ if (o.vb_id < 1) {
+ continue;
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, o.vb_id);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_NORMAL_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+ glVertexPointer(3, GL_FLOAT, stride, (const void*)0);
+ glNormalPointer(GL_FLOAT, stride, (const void*)(sizeof(float) * 3));
+ glColorPointer(3, GL_FLOAT, stride, (const void*)(sizeof(float) * 6));
+ glTexCoordPointer(2, GL_FLOAT, stride, (const void*)(sizeof(float) * 9));
+
+ glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles);
+ CheckErrors("drawarrays");
+ }
+}
+
+static void Init() {
+ trackball(curr_quat, 0, 0, 0, 0);
+
+ eye[0] = 0.0f;
+ eye[1] = 0.0f;
+ eye[2] = 3.0f;
+
+ lookat[0] = 0.0f;
+ lookat[1] = 0.0f;
+ lookat[2] = 0.0f;
+
+ up[0] = 0.0f;
+ up[1] = 1.0f;
+ up[2] = 0.0f;
+}
+
+int main(int argc, char** argv) {
+ if (argc < 2) {
+ std::cout << "Needs input.obj\n" << std::endl;
+ return 0;
+ }
+
+ Init();
+
+ if (!glfwInit()) {
+ std::cerr << "Failed to initialize GLFW." << std::endl;
+ return -1;
+ }
+
+ window = glfwCreateWindow(width, height, "Obj viewer", NULL, NULL);
+ if (window == NULL) {
+ std::cerr << "Failed to open GLFW window. " << std::endl;
+ glfwTerminate();
+ return 1;
+ }
+
+ glfwMakeContextCurrent(window);
+ glfwSwapInterval(1);
+
+ // Callback
+ glfwSetWindowSizeCallback(window, reshapeFunc);
+ glfwSetKeyCallback(window, keyboardFunc);
+ glfwSetMouseButtonCallback(window, clickFunc);
+ glfwSetCursorPosCallback(window, motionFunc);
+
+ glewExperimental = true;
+ if (glewInit() != GLEW_OK) {
+ std::cerr << "Failed to initialize GLEW." << std::endl;
+ return -1;
+ }
+
+ reshapeFunc(window, width, height);
+
+ float bmin[3], bmax[3];
+ std::vector<tinyobj::material_t> materials;
+ std::map<std::string, GLuint> textures;
+ if (false == LoadObjAndConvert(bmin, bmax, &gDrawObjects, materials, textures, argv[1])) {
+ return -1;
+ }
+
+ float maxExtent = 0.5f * (bmax[0] - bmin[0]);
+ if (maxExtent < 0.5f * (bmax[1] - bmin[1])) {
+ maxExtent = 0.5f * (bmax[1] - bmin[1]);
+ }
+ if (maxExtent < 0.5f * (bmax[2] - bmin[2])) {
+ maxExtent = 0.5f * (bmax[2] - bmin[2]);
+ }
+
+ while (glfwWindowShouldClose(window) == GL_FALSE) {
+ glfwPollEvents();
+ glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_TEXTURE_2D);
+
+ // camera & rotate
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ GLfloat mat[4][4];
+ gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0],
+ up[1], up[2]);
+ build_rotmatrix(mat, curr_quat);
+ glMultMatrixf(&mat[0][0]);
+
+ // Fit to -1, 1
+ glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent);
+
+ // Centerize object.
+ glTranslatef(-0.5 * (bmax[0] + bmin[0]), -0.5 * (bmax[1] + bmin[1]),
+ -0.5 * (bmax[2] + bmin[2]));
+
+ Draw(gDrawObjects, materials, textures);
+
+ glfwSwapBuffers(window);
+ }
+
+ glfwTerminate();
+}
diff --git a/examples/voxelize/Makefile b/examples/voxelize/Makefile
new file mode 100644
index 0000000..98189d9
--- /dev/null
+++ b/examples/voxelize/Makefile
@@ -0,0 +1,2 @@
+all:
+ g++ -o voxelizer main.cc
diff --git a/examples/voxelize/README.md b/examples/voxelize/README.md
new file mode 100644
index 0000000..8a113b6
--- /dev/null
+++ b/examples/voxelize/README.md
@@ -0,0 +1,5 @@
+# Voxelize .obj and export it as also in .obj
+
+## Third party library
+
+This example uses https://github.com/karimnaaji/voxelizer, which is licensed under MIT Liense.
diff --git a/examples/voxelize/main.cc b/examples/voxelize/main.cc
new file mode 100644
index 0000000..035e3a1
--- /dev/null
+++ b/examples/voxelize/main.cc
@@ -0,0 +1,74 @@
+#define VOXELIZER_IMPLEMENTATION
+#include "voxelizer.h"
+
+#define TINYOBJLOADER_IMPLEMENTATION
+#include "../../tiny_obj_loader.h"
+
+bool Voxelize(const char* filename, float voxelsizex, float voxelsizey, float voxelsizez, float precision)
+{
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename);
+
+ if (!err.empty()) {
+ printf("err: %s\n", err.c_str());
+ }
+
+ if (!ret) {
+ printf("failed to load : %s\n", filename);
+ return false;
+ }
+
+ if (shapes.size() == 0) {
+ printf("err: # of shapes are zero.\n");
+ return false;
+ }
+
+ // Only use first shape.
+ {
+ vx_mesh_t* mesh;
+ vx_mesh_t* result;
+
+ mesh = vx_mesh_alloc(attrib.vertices.size(), shapes[0].mesh.indices.size());
+
+ for (size_t f = 0; f < shapes[0].mesh.indices.size(); f++) {
+ mesh->indices[f] = shapes[0].mesh.indices[f].vertex_index;
+ }
+
+ for (size_t v = 0; v < attrib.vertices.size() / 3; v++) {
+ mesh->vertices[v].x = attrib.vertices[3*v+0];
+ mesh->vertices[v].y = attrib.vertices[3*v+1];
+ mesh->vertices[v].z = attrib.vertices[3*v+2];
+ }
+
+ result = vx_voxelize(mesh, voxelsizex, voxelsizey, voxelsizez, precision);
+
+ printf("Number of vertices: %ld\n", result->nvertices);
+ printf("Number of indices: %ld\n", result->nindices);
+ }
+ return true;
+}
+
+
+int
+main(
+ int argc,
+ char** argv)
+{
+ if (argc < 4) {
+ printf("Usage: voxelize input.obj voxelsizex voxelsizey voxelsizez precision\n");
+ exit(-1);
+ }
+
+ const char* filename = argv[1];
+ float voxelsizex = atof(argv[2]);
+ float voxelsizey = atof(argv[3]);
+ float voxelsizez = atof(argv[4]);
+ float prec = atof(argv[5]);
+ bool ret = Voxelize(filename, voxelsizex, voxelsizey, voxelsizez, prec);
+
+ return ret ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
diff --git a/examples/voxelize/voxelizer.h b/examples/voxelize/voxelizer.h
new file mode 100644
index 0000000..4958695
--- /dev/null
+++ b/examples/voxelize/voxelizer.h
@@ -0,0 +1,764 @@
+//
+// LICENCE:
+// The MIT License (MIT)
+//
+// Copyright (c) 2016 Karim Naaji, karim.naaji@gmail.com
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// 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
+//
+// REFERENCES:
+// http://matthias-mueller-fischer.ch/publications/tetraederCollision.pdf
+// http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/code/tribox2.txt
+//
+// HOWTO:
+// #define VOXELIZER_IMPLEMENTATION
+// #define VOXELIZER_DEBUG // Only if assertions need to be checked
+// #include "voxelizer.h"
+//
+// HISTORY:
+// - version 0.9.0: Initial
+//
+// TODO:
+// - Triangle face merging
+// - Add colors from input mesh
+// - Potential issue with voxel bigger than triangle
+//
+
+#ifndef VOXELIZER_H
+#define VOXELIZER_H
+
+// ------------------------------------------------------------------------------------------------
+// VOXELIZER PUBLIC API
+//
+
+#ifndef VOXELIZER_HELPERS
+#include <stdlib.h> // malloc, calloc, free
+#endif
+
+typedef struct vx_vertex {
+ union {
+ float v[3];
+ struct {
+ float x;
+ float y;
+ float z;
+ };
+ };
+} vx_vertex_t;
+
+typedef struct vx_mesh {
+ vx_vertex_t* vertices; // Contiguous mesh vertices
+ vx_vertex_t* normals; // Contiguous mesh normals
+ unsigned int* indices; // Mesh indices
+ unsigned int* normalindices; // Mesh normal indices
+ size_t nindices; // The number of normal indices
+ size_t nvertices; // The number of vertices
+ size_t nnormals; // The number of normals
+} vx_mesh_t;
+
+vx_mesh_t* vx_voxelize(vx_mesh_t* _mesh, // The input mesh
+ float voxelsizex, // Voxel size on X-axis
+ float voxelsizey, // Voxel size on Y-axis
+ float voxelsizez, // Voxel size on Z-axis
+ float precision); // A precision factor that reduces "holes" artifact
+ // usually a precision = voxelsize / 10. works ok.
+
+void vx_mesh_free(vx_mesh_t* _mesh);
+vx_mesh_t* vx_mesh_alloc(int nindices, int nvertices);
+
+// Voxelizer Helpers, define your own if needed
+#ifndef VOXELIZER_HELPERS
+#define VOXELIZER_HELPERS 1
+#define VX_MIN(a, b) (a > b ? b : a)
+#define VX_MAX(a, b) (a > b ? a : b)
+#define VX_FINDMINMAX(x0, x1, x2, min, max) \
+ min = max = x0; \
+ if (x1 < min) min = x1; \
+ if (x1 > max) max = x1; \
+ if (x2 < min) min = x2; \
+ if (x2 > max) max = x2;
+#define VX_CLAMP(v, lo, hi) VX_MAX(lo, VX_MIN(hi, v))
+#define VX_MALLOC(T, N) ((T*) malloc(N * sizeof(T)))
+#define VX_FREE(T) free(T)
+#define VX_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1))
+#define VX_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; }
+#ifdef VOXELIZER_DEBUG
+#define VX_ASSERT(STMT) if (!(STMT)) { *(int *)0 = 0; }
+#else
+#define VX_ASSERT(STMT)
+#endif // VOXELIZER_DEBUG
+#endif // VOXELIZER_HELPERS
+
+//
+// END VOXELIZER PUBLIC API
+// ------------------------------------------------------------------------------------------------
+
+#endif // VOXELIZER_H
+
+#ifdef VOXELIZER_IMPLEMENTATION
+
+#include <math.h> // ceil, fabs & al.
+#include <stdbool.h> // hughh
+#include <string.h> // memcpy
+
+#define VOXELIZER_EPSILON (0.0000001)
+#define VOXELIZER_NORMAL_INDICES_SIZE (6)
+#define VOXELIZER_INDICES_SIZE (36)
+#define VOXELIZER_HASH_TABLE_SIZE (4096)
+
+unsigned int vx_voxel_indices[VOXELIZER_INDICES_SIZE] = {
+ 0, 1, 2,
+ 0, 2, 3,
+ 3, 2, 6,
+ 3, 6, 7,
+ 0, 7, 4,
+ 0, 3, 7,
+ 4, 7, 5,
+ 7, 6, 5,
+ 0, 4, 5,
+ 0, 5, 1,
+ 1, 5, 6,
+ 1, 6, 2,
+};
+
+float vx_normals[18] = {
+ 0.0, -1.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 1.0, 0.0, 0.0,
+ 0.0, 0.0, 1.0,
+ -1.0, 0.0, 0.0,
+ 0.0, 0.0, -1.0,
+};
+
+unsigned int vx_normal_indices[VOXELIZER_NORMAL_INDICES_SIZE] = {
+ 3, 2, 1, 5, 4, 0,
+};
+
+typedef struct vx_aabb {
+ vx_vertex_t min;
+ vx_vertex_t max;
+} vx_aabb_t;
+
+typedef struct vx_edge {
+ vx_vertex_t p1;
+ vx_vertex_t p2;
+} vx_edge_t;
+
+typedef struct vx_triangle {
+ vx_vertex_t p1;
+ vx_vertex_t p2;
+ vx_vertex_t p3;
+} vx_triangle_t;
+
+typedef struct vx_hash_table_node {
+ struct vx_hash_table_node* next;
+ struct vx_hash_table_node* prev;
+ void* data;
+} vx_hash_table_node_t;
+
+typedef struct vx_hash_table {
+ vx_hash_table_node_t** elements;
+ size_t size;
+} vx_hash_table_t;
+
+vx_hash_table_t* vx__hash_table_alloc(size_t size)
+{
+ vx_hash_table_t* table = VX_MALLOC(vx_hash_table_t, 1);
+ if (!table) { return NULL; }
+ table->size = size;
+ table->elements = VX_MALLOC(vx_hash_table_node_t*, size);
+ if (!table->elements) { return NULL; }
+ for (size_t i = 0; i < table->size; ++i) {
+ table->elements[i] = NULL;
+ }
+ return table;
+}
+
+void vx__hash_table_free(vx_hash_table_t* table, bool freedata)
+{
+ for (size_t i = 0; i < table->size; ++i) {
+ vx_hash_table_node_t* node = table->elements[i];
+
+ if (node) {
+ if (node->next) {
+ while (node->next) {
+ node = node->next;
+ if (freedata) {
+ VX_FREE(node->prev->data);
+ }
+ VX_FREE(node->prev);
+ }
+ VX_FREE(node);
+ } else {
+ VX_FREE(node->data);
+ VX_FREE(node);
+ }
+ }
+ }
+
+ VX_FREE(table->elements);
+ VX_FREE(table);
+}
+
+bool vx__hash_table_insert(vx_hash_table_t* table,
+ size_t hash,
+ void* data,
+ bool (*compfunc)(void* d1, void* d2))
+{
+ if (!table->elements[hash]) {
+ table->elements[hash] = VX_MALLOC(vx_hash_table_node_t, 1);
+ table->elements[hash]->prev = NULL;
+ table->elements[hash]->next = NULL;
+ table->elements[hash]->data = data;
+ } else {
+ vx_hash_table_node_t* node = table->elements[hash];
+
+ if (compfunc && compfunc(node->data, data)) {
+ return false;
+ }
+
+ while (node->next) {
+ node = node->next;
+ if (compfunc && compfunc(node->data, data)) {
+ return false;
+ }
+ }
+
+ vx_hash_table_node_t* nnode = VX_MALLOC(vx_hash_table_node_t, 1);
+
+ nnode->prev = node;
+ nnode->next = NULL;
+ nnode->data = data;
+ node->next = nnode;
+ }
+ return true;
+}
+
+void vx_mesh_free(vx_mesh_t* m)
+{
+ VX_FREE(m->vertices);
+ m->vertices = NULL;
+ m->nvertices = 0;
+ VX_FREE(m->indices);
+ m->indices = NULL;
+ m->nindices = 0;
+ if (m->normals) { VX_FREE(m->normals); }
+ VX_FREE(m);
+}
+
+vx_mesh_t* vx_mesh_alloc(int nvertices, int nindices)
+{
+ vx_mesh_t* m = VX_MALLOC(vx_mesh_t, 1);
+ if (!m) { return NULL; }
+ m->indices = VX_CALLOC(unsigned int, nindices);
+ if (!m->indices) { return NULL; }
+ m->vertices = VX_CALLOC(vx_vertex_t, nvertices);
+ if (!m->vertices) { return NULL; }
+ m->normals = NULL;
+ m->nindices = nindices;
+ m->nvertices = nvertices;
+ return m;
+}
+
+float vx__map_to_voxel(float position, float voxelSize, bool min)
+{
+ float vox = (position + (position < 0.f ? -1.f : 1.f) * voxelSize * 0.5f) / voxelSize;
+ return (min ? floor(vox) : ceil(vox)) * voxelSize;
+}
+
+vx_vertex_t vx__vertex_cross(vx_vertex_t* v1, vx_vertex_t* v2)
+{
+ vx_vertex_t cross;
+ cross.x = v1->y * v2->z - v1->z * v2->y;
+ cross.y = v1->z * v2->x - v1->x * v2->z;
+ cross.z = v1->x * v2->y - v1->y * v2->x;
+ return cross;
+}
+
+bool vx__vertex_equals(vx_vertex_t* v1, vx_vertex_t* v2) {
+ return fabs(v1->x - v2->x) < VOXELIZER_EPSILON &&
+ fabs(v1->y - v2->y) < VOXELIZER_EPSILON &&
+ fabs(v1->z - v2->z) < VOXELIZER_EPSILON;
+}
+
+bool vx__vertex_comp_func(void* a, void* b)
+{
+ return vx__vertex_equals((vx_vertex_t*) a, (vx_vertex_t*) b);
+}
+
+void vx__vertex_sub(vx_vertex_t* a, vx_vertex_t* b)
+{
+ a->x -= b->x;
+ a->y -= b->y;
+ a->z -= b->z;
+}
+
+void vx__vertex_add(vx_vertex_t* a, vx_vertex_t* b)
+{
+ a->x += b->x;
+ a->y += b->y;
+ a->z += b->z;
+}
+
+void vx__vertex_multiply(vx_vertex_t* a, float v)
+{
+ a->x *= v;
+ a->y *= v;
+ a->z *= v;
+}
+
+float vx__vertex_dot(vx_vertex_t* v1, vx_vertex_t* v2)
+{
+ return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z;
+}
+
+int vx__plane_box_overlap(vx_vertex_t* normal,
+ float d,
+ vx_vertex_t* halfboxsize)
+{
+ vx_vertex_t vmin, vmax;
+
+ for (int dim = 0; dim <= 2; dim++) {
+ if (normal->v[dim] > 0.0f) {
+ vmin.v[dim] = -halfboxsize->v[dim];
+ vmax.v[dim] = halfboxsize->v[dim];
+ } else {
+ vmin.v[dim] = halfboxsize->v[dim];
+ vmax.v[dim] = -halfboxsize->v[dim];
+ }
+ }
+
+ if (vx__vertex_dot(normal, &vmin) + d > 0.0f) {
+ return false;
+ }
+
+ if (vx__vertex_dot(normal, &vmax) + d >= 0.0f) {
+ return true;
+ }
+
+ return false;
+}
+
+#define AXISTEST_X01(a, b, fa, fb) \
+ p1 = a * v1.y - b * v1.z; \
+ p3 = a * v3.y - b * v3.z; \
+ if (p1 < p3) { \
+ min = p1; max = p3; \
+ } else { \
+ min = p3; max = p1; \
+ } \
+ rad = fa * halfboxsize.y + fb * halfboxsize.z; \
+ if (min > rad || max < -rad) { \
+ return false; \
+ } \
+
+#define AXISTEST_X2(a, b, fa, fb) \
+ p1 = a * v1.y - b * v1.z; \
+ p2 = a * v2.y - b * v2.z; \
+ if (p1 < p2) { \
+ min = p1; max = p2; \
+ } else { \
+ min = p2; max = p1; \
+ } \
+ rad = fa * halfboxsize.y + fb * halfboxsize.z; \
+ if (min > rad || max < -rad) { \
+ return false; \
+ } \
+
+#define AXISTEST_Y02(a, b, fa, fb) \
+ p1 = -a * v1.x + b * v1.z; \
+ p3 = -a * v3.x + b * v3.z; \
+ if (p1 < p3) { \
+ min = p1; max = p3; \
+ } else { \
+ min = p3; max = p1; \
+ } \
+ rad = fa * halfboxsize.x + fb * halfboxsize.z; \
+ if (min > rad || max < -rad) { \
+ return false; \
+ } \
+
+#define AXISTEST_Y1(a, b, fa, fb) \
+ p1 = -a * v1.x + b * v1.z; \
+ p2 = -a * v2.x + b * v2.z; \
+ if (p1 < p2) { \
+ min = p1; max = p2; \
+ } else { \
+ min = p2; max = p1; \
+ } \
+ rad = fa * halfboxsize.x + fb * halfboxsize.z; \
+ if (min > rad || max < -rad) { \
+ return false; \
+ }
+
+#define AXISTEST_Z12(a, b, fa, fb) \
+ p2 = a * v2.x - b * v2.y; \
+ p3 = a * v3.x - b * v3.y; \
+ if (p3 < p2) { \
+ min = p3; max = p2; \
+ } else { \
+ min = p2; max = p3; \
+ } \
+ rad = fa * halfboxsize.x + fb * halfboxsize.y; \
+ if (min > rad || max < -rad) { \
+ return false; \
+ }
+
+#define AXISTEST_Z0(a, b, fa, fb) \
+ p1 = a * v1.x - b * v1.y; \
+ p2 = a * v2.x - b * v2.y; \
+ if (p1 < p2) { \
+ min = p1; max = p2; \
+ } else { \
+ min = p2; max = p1; \
+ } \
+ rad = fa * halfboxsize.x + fb * halfboxsize.y; \
+ if (min > rad || max < -rad) { \
+ return false; \
+ }
+
+int vx__triangle_box_overlap(vx_vertex_t boxcenter,
+ vx_vertex_t halfboxsize,
+ vx_triangle_t triangle)
+{
+ vx_vertex_t v1, v2, v3, normal, e1, e2, e3;
+ float min, max, d, p1, p2, p3, rad, fex, fey, fez;
+
+ v1 = triangle.p1;
+ v2 = triangle.p2;
+ v3 = triangle.p3;
+
+ vx__vertex_sub(&v1, &boxcenter);
+ vx__vertex_sub(&v2, &boxcenter);
+ vx__vertex_sub(&v3, &boxcenter);
+
+ e1 = v2;
+ e2 = v3;
+ e3 = v1;
+
+ vx__vertex_sub(&e1, &v1);
+ vx__vertex_sub(&e2, &v2);
+ vx__vertex_sub(&e3, &v3);
+
+ fex = fabs(e1.x);
+ fey = fabs(e1.y);
+ fez = fabs(e1.z);
+
+ AXISTEST_X01(e1.z, e1.y, fez, fey);
+ AXISTEST_Y02(e1.z, e1.x, fez, fex);
+ AXISTEST_Z12(e1.y, e1.x, fey, fex);
+
+ fex = fabs(e2.x);
+ fey = fabs(e2.y);
+ fez = fabs(e2.z);
+
+ AXISTEST_X01(e2.z, e2.y, fez, fey);
+ AXISTEST_Y02(e2.z, e2.x, fez, fex);
+ AXISTEST_Z0(e2.y, e2.x, fey, fex);
+
+ fex = fabs(e3.x);
+ fey = fabs(e3.y);
+ fez = fabs(e3.z);
+
+ AXISTEST_X2(e3.z, e3.y, fez, fey);
+ AXISTEST_Y1(e3.z, e3.x, fez, fex);
+ AXISTEST_Z12(e3.y, e3.x, fey, fex);
+
+ VX_FINDMINMAX(v1.x, v2.x, v3.x, min, max);
+ if (min > halfboxsize.x || max < -halfboxsize.x) {
+ return false;
+ }
+
+ VX_FINDMINMAX(v1.y, v2.y, v3.y, min, max);
+ if (min > halfboxsize.y || max < -halfboxsize.y) {
+ return false;
+ }
+
+ VX_FINDMINMAX(v1.z, v2.z, v3.z, min, max);
+ if (min > halfboxsize.z || max < -halfboxsize.z) {
+ return false;
+ }
+
+ normal = vx__vertex_cross(&e1, &e2);
+ d = -vx__vertex_dot(&normal, &v1);
+
+ if (!vx__plane_box_overlap(&normal, d, &halfboxsize)) {
+ return false;
+ }
+
+ return true;
+}
+
+#undef AXISTEST_X2
+#undef AXISTEST_X01
+#undef AXISTEST_Y1
+#undef AXISTEST_Y02
+#undef AXISTEST_Z0
+#undef AXISTEST_Z12
+
+float vx__triangle_area(vx_triangle_t* triangle) {
+ vx_vertex_t ab = triangle->p2;
+ vx_vertex_t ac = triangle->p3;
+
+ vx__vertex_sub(&ab, &triangle->p1);
+ vx__vertex_sub(&ac, &triangle->p1);
+
+ float a0 = ab.y * ac.z - ab.z * ac.y;
+ float a1 = ab.z * ac.x - ab.x * ac.z;
+ float a2 = ab.x * ac.y - ab.y * ac.x;
+
+ return sqrtf(powf(a0, 2.f) + powf(a1, 2.f) + powf(a2, 2.f)) * 0.5f;
+}
+
+void vx__aabb_init(vx_aabb_t* aabb)
+{
+ aabb->max.x = aabb->max.y = aabb->max.z = -INFINITY;
+ aabb->min.x = aabb->min.y = aabb->min.z = INFINITY;
+}
+
+vx_aabb_t vx__triangle_aabb(vx_triangle_t* triangle)
+{
+ vx_aabb_t aabb;
+
+ vx__aabb_init(&aabb);
+
+ aabb.max.x = VX_MAX(aabb.max.x, triangle->p1.x); aabb.max.x = VX_MAX(aabb.max.x,
+ triangle->p2.x); aabb.max.x = VX_MAX(aabb.max.x, triangle->p3.x);
+ aabb.max.y = VX_MAX(aabb.max.y, triangle->p1.y); aabb.max.y = VX_MAX(aabb.max.y,
+ triangle->p2.y); aabb.max.y = VX_MAX(aabb.max.y, triangle->p3.y);
+ aabb.max.z = VX_MAX(aabb.max.z, triangle->p1.z); aabb.max.z = VX_MAX(aabb.max.z,
+ triangle->p2.z); aabb.max.z = VX_MAX(aabb.max.z, triangle->p3.z);
+
+ aabb.min.x = VX_MIN(aabb.min.x, triangle->p1.x); aabb.min.x = VX_MIN(aabb.min.x,
+ triangle->p2.x); aabb.min.x = VX_MIN(aabb.min.x, triangle->p3.x);
+ aabb.min.y = VX_MIN(aabb.min.y, triangle->p1.y); aabb.min.y = VX_MIN(aabb.min.y,
+ triangle->p2.y); aabb.min.y = VX_MIN(aabb.min.y, triangle->p3.y);
+ aabb.min.z = VX_MIN(aabb.min.z, triangle->p1.z); aabb.min.z = VX_MIN(aabb.min.z,
+ triangle->p2.z); aabb.min.z = VX_MIN(aabb.min.z, triangle->p3.z);
+
+ return aabb;
+}
+
+vx_vertex_t vx__aabb_center(vx_aabb_t* a)
+{
+ vx_vertex_t boxcenter = a->min;
+ vx__vertex_add(&boxcenter, &a->max);
+ vx__vertex_multiply(&boxcenter, 0.5f);
+
+ return boxcenter;
+}
+
+vx_vertex_t vx__aabb_half_size(vx_aabb_t* a)
+{
+ vx_vertex_t size;
+
+ size.x = fabs(a->max.x - a->min.x) * 0.5f;
+ size.y = fabs(a->max.y - a->min.y) * 0.5f;
+ size.z = fabs(a->max.z - a->min.z) * 0.5f;
+
+ return size;
+}
+
+vx_aabb_t vx__aabb_merge(vx_aabb_t* a, vx_aabb_t* b)
+{
+ vx_aabb_t merge;
+
+ merge.min.x = VX_MIN(a->min.x, b->min.x);
+ merge.min.y = VX_MIN(a->min.y, b->min.y);
+ merge.min.z = VX_MIN(a->min.z, b->min.z);
+
+ merge.max.x = VX_MAX(a->max.x, b->max.x);
+ merge.max.y = VX_MAX(a->max.y, b->max.y);
+ merge.max.z = VX_MAX(a->max.z, b->max.z);
+
+ return merge;
+}
+
+size_t vx__vertex_hash(vx_vertex_t pos, size_t n)
+{
+ size_t a = (size_t)(pos.x * 73856093);
+ size_t b = (size_t)(pos.y * 19349663);
+ size_t c = (size_t)(pos.z * 83492791);
+
+ return (a ^ b ^ c) % n;
+}
+
+void vx__add_voxel(vx_mesh_t* mesh,
+ vx_vertex_t* pos,
+ float* vertices)
+{
+ for (size_t i = 0; i < 8; ++i) {
+ size_t index = i+mesh->nvertices;
+
+ mesh->vertices[index].x = vertices[i*3+0] + pos->x;
+ mesh->vertices[index].y = vertices[i*3+1] + pos->y;
+ mesh->vertices[index].z = vertices[i*3+2] + pos->z;
+ }
+
+ int j = -1;
+ for (size_t i = 0; i < VOXELIZER_INDICES_SIZE; ++i) {
+ if (i % 6 == 0) {
+ j++;
+ }
+ mesh->normalindices[i+mesh->nindices] = vx_normal_indices[j];
+ }
+
+ for (size_t i = 0; i < VOXELIZER_INDICES_SIZE; ++i) {
+ mesh->indices[i+mesh->nindices] = vx_voxel_indices[i] + mesh->nvertices;
+ }
+
+ mesh->nindices += VOXELIZER_INDICES_SIZE;
+ mesh->nvertices += 8;
+}
+
+vx_mesh_t* vx_voxelize(vx_mesh_t* m,
+ float voxelsizex,
+ float voxelsizey,
+ float voxelsizez,
+ float precision)
+{
+ vx_mesh_t* outmesh = NULL;
+ vx_hash_table_t* table = NULL;
+ size_t voxels = 0;
+
+ float halfsizex = voxelsizex * 0.5f;
+ float halfsizey = voxelsizey * 0.5f;
+ float halfsizez = voxelsizez * 0.5f;
+
+ table = vx__hash_table_alloc(VOXELIZER_HASH_TABLE_SIZE);
+
+ for (int i = 0; i < m->nindices; i += 3) {
+ vx_triangle_t triangle;
+
+ VX_ASSERT(m->indices[i+0] < m->nvertices);
+ VX_ASSERT(m->indices[i+1] < m->nvertices);
+ VX_ASSERT(m->indices[i+2] < m->nvertices);
+
+ triangle.p1 = m->vertices[m->indices[i+0]];
+ triangle.p2 = m->vertices[m->indices[i+1]];
+ triangle.p3 = m->vertices[m->indices[i+2]];
+
+ if (vx__triangle_area(&triangle) < VOXELIZER_EPSILON) {
+ // triangle with 0 area
+ continue;
+ }
+
+ vx_aabb_t aabb = vx__triangle_aabb(&triangle);
+
+ aabb.min.x = vx__map_to_voxel(aabb.min.x, voxelsizex, true);
+ aabb.min.y = vx__map_to_voxel(aabb.min.y, voxelsizey, true);
+ aabb.min.z = vx__map_to_voxel(aabb.min.z, voxelsizez, true);
+
+ aabb.max.x = vx__map_to_voxel(aabb.max.x, voxelsizex, false);
+ aabb.max.y = vx__map_to_voxel(aabb.max.y, voxelsizey, false);
+ aabb.max.z = vx__map_to_voxel(aabb.max.z, voxelsizez, false);
+
+ for (float x = aabb.min.x; x < aabb.max.x; x += voxelsizex) {
+ for (float y = aabb.min.y; y < aabb.max.y; y += voxelsizey) {
+ for (float z = aabb.min.z; z < aabb.max.z; z += voxelsizez) {
+ vx_aabb_t saabb;
+
+ saabb.min.x = x - halfsizex;
+ saabb.min.y = y - halfsizey;
+ saabb.min.z = z - halfsizez;
+ saabb.max.x = x + halfsizex;
+ saabb.max.y = y + halfsizey;
+ saabb.max.z = z + halfsizez;
+
+ vx_vertex_t boxcenter = vx__aabb_center(&saabb);
+ vx_vertex_t halfsize = vx__aabb_half_size(&saabb);
+
+ // HACK: some holes might appear, this
+ // precision factor reduces the artifact
+ halfsize.x += precision;
+ halfsize.y += precision;
+ halfsize.z += precision;
+
+ if (vx__triangle_box_overlap(boxcenter, halfsize, triangle)) {
+ vx_vertex_t* nodedata = VX_MALLOC(vx_vertex_t, 1);
+ *nodedata = boxcenter;
+
+ size_t hash = vx__vertex_hash(boxcenter, VOXELIZER_HASH_TABLE_SIZE);
+
+ bool insert = vx__hash_table_insert(table,
+ hash,
+ nodedata,
+ vx__vertex_comp_func);
+
+ if (insert) {
+ voxels++;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ outmesh = VX_MALLOC(vx_mesh_t, 1);
+ size_t nvertices = voxels * 8;
+ size_t nindices = voxels * VOXELIZER_INDICES_SIZE;
+ outmesh->nnormals = VOXELIZER_NORMAL_INDICES_SIZE;
+ outmesh->vertices = VX_CALLOC(vx_vertex_t, nvertices);
+ outmesh->normals = VX_CALLOC(vx_vertex_t, 6);
+ outmesh->indices = VX_CALLOC(unsigned int, nindices);
+ outmesh->normalindices = VX_CALLOC(unsigned int, nindices);
+ outmesh->nindices = 0;
+ outmesh->nvertices = 0;
+
+ memcpy(outmesh->normals, vx_normals, 18 * sizeof(float));
+
+ float vertices[24] = {
+ -halfsizex, halfsizey, halfsizez,
+ -halfsizex, -halfsizey, halfsizez,
+ halfsizex, -halfsizey, halfsizez,
+ halfsizex, halfsizey, halfsizez,
+ -halfsizex, halfsizey, -halfsizez,
+ -halfsizex, -halfsizey, -halfsizez,
+ halfsizex, -halfsizey, -halfsizez,
+ halfsizex, halfsizey, -halfsizez,
+ };
+
+ for (size_t i = 0; i < table->size; ++i) {
+ if (table->elements[i] != NULL) {
+ vx_hash_table_node_t* node = table->elements[i];
+
+ if (!node) {
+ continue;
+ }
+
+ vx_vertex_t* p = (vx_vertex_t*) node->data;
+ vx__add_voxel(outmesh, p, vertices);
+
+ while (node->next) {
+ node = node->next;
+ p = (vx_vertex_t*) node->data;
+ vx__add_voxel(outmesh, p, vertices);
+ }
+ }
+ }
+
+ vx__hash_table_free(table, true);
+
+ return outmesh;
+}
+
+#undef VOXELIZER_EPSILON
+#undef VOXELIZER_INDICES_SIZE
+#undef VOXELIZER_HASH_TABLE_SIZE
+
+#endif // VX_VOXELIZER_IMPLEMENTATION
diff --git a/experimental/README.md b/experimental/README.md
new file mode 100644
index 0000000..99378ce
--- /dev/null
+++ b/experimental/README.md
@@ -0,0 +1,16 @@
+# Experimental code for .obj loader.
+
+* Multi-threaded optimized parser : tinyobj_loader_opt.h
+
+## Requirements
+
+* C++-11 compiler
+
+## Compile options
+
+* zstd compressed .obj support. `--with-zstd` premake option.
+* gzip compressed .obj support. `--with-zlib` premake option.
+
+## Licenses
+
+* lfpAlloc : MIT license.
diff --git a/experimental/lfpAlloc/Allocator.hpp b/experimental/lfpAlloc/Allocator.hpp
new file mode 100644
index 0000000..4dddaab
--- /dev/null
+++ b/experimental/lfpAlloc/Allocator.hpp
@@ -0,0 +1,89 @@
+#ifndef LF_POOL_ALLOCATOR
+#define LF_POOL_ALLOCATOR
+
+#include <memory>
+#include <thread>
+#include <lfpAlloc/PoolDispatcher.hpp>
+
+namespace lfpAlloc {
+template <typename T, std::size_t NumPools = 70>
+class lfpAllocator {
+public:
+ using value_type = T;
+ using pointer = T*;
+ using const_pointer = const T*;
+ using reference = T&;
+ using const_reference = T const&;
+
+ template <typename U>
+ struct rebind {
+ typedef lfpAllocator<U, NumPools> other;
+ };
+
+ lfpAllocator() {}
+
+ template <typename U>
+ lfpAllocator(lfpAllocator<U, NumPools>&&) noexcept {}
+
+ template <typename U>
+ lfpAllocator(const lfpAllocator<U, NumPools>&) noexcept {}
+
+ T* allocate(std::size_t count) {
+ if (sizeof(T) * count <=
+ alignof(std::max_align_t) * NumPools - sizeof(void*)) {
+ return reinterpret_cast<T*>(
+ dispatcher_.allocate(sizeof(T) * count));
+ } else {
+ return new T[count];
+ }
+ }
+
+ void deallocate(T* p, std::size_t count) noexcept {
+ if (sizeof(T) * count <=
+ alignof(std::max_align_t) * NumPools - sizeof(void*)) {
+ dispatcher_.deallocate(p, sizeof(T) * count);
+ } else {
+ delete[] p;
+ }
+ }
+
+ // Should not be required, but allocator_traits is not complete in
+ // gcc 4.9.1
+ template <typename U>
+ void destroy(U* p) {
+ p->~U();
+ }
+
+ template <typename U, typename... Args>
+ void construct(U* p, Args&&... args) {
+ new (p) U(std::forward<Args>(args)...);
+ }
+
+ template <typename Ty, typename U, std::size_t N, std::size_t M>
+ friend bool operator==(const lfpAllocator<Ty, N>&,
+ const lfpAllocator<U, M>&) noexcept;
+
+ template <typename U, std::size_t M>
+ friend class lfpAllocator;
+
+private:
+ static PoolDispatcher<NumPools> dispatcher_;
+};
+
+template <typename T, std::size_t N>
+PoolDispatcher<N> lfpAllocator<T, N>::dispatcher_;
+
+template <typename T, typename U, std::size_t N, std::size_t M>
+inline bool operator==(const lfpAllocator<T, N>&,
+ const lfpAllocator<U, M>&) noexcept {
+ return N == M;
+}
+
+template <typename T, typename U, std::size_t N, std::size_t M>
+inline bool operator!=(const lfpAllocator<T, N>& left,
+ const lfpAllocator<U, M>& right) noexcept {
+ return !(left == right);
+}
+}
+
+#endif
diff --git a/experimental/lfpAlloc/ChunkList.hpp b/experimental/lfpAlloc/ChunkList.hpp
new file mode 100644
index 0000000..760c670
--- /dev/null
+++ b/experimental/lfpAlloc/ChunkList.hpp
@@ -0,0 +1,116 @@
+#ifndef LF_POOL_ALLOC_CHUNK_LIST
+#define LF_POOL_ALLOC_CHUNK_LIST
+
+#include <cstdint>
+#include <atomic>
+#include <type_traits>
+
+#ifndef LFP_ALLOW_BLOCKING
+static_assert(ATOMIC_POINTER_LOCK_FREE == 2,
+ "Atomic pointer is not lock-free.");
+#endif
+
+namespace lfpAlloc {
+
+template <std::size_t Size>
+struct Cell {
+ uint8_t val_[Size];
+ Cell* next_ = this + 1;
+};
+
+// For small types (less than the size of void*), no additional
+// space is needed, so union val_ with next_ to avoid overhead.
+template <>
+struct Cell<0> {
+ Cell() : next_{this + 1} {}
+ union {
+ uint8_t val_[sizeof(Cell*)];
+ Cell* next_;
+ };
+};
+
+template <std::size_t Size, std::size_t AllocationsPerChunk>
+struct Chunk {
+ Chunk() noexcept {
+ auto& last = memBlock_[AllocationsPerChunk - 1];
+ last.next_ = nullptr;
+ }
+ Cell<Size> memBlock_[AllocationsPerChunk];
+};
+
+template <typename T>
+struct Node {
+ Node() : val_(), next_(nullptr) {}
+ Node(const T& val) : val_(val), next_(nullptr) {}
+ T val_;
+ std::atomic<Node<T>*> next_;
+};
+
+template <std::size_t Size, std::size_t AllocationsPerChunk>
+class ChunkList {
+ static constexpr auto CellSize =
+ (Size > sizeof(void*)) ? Size - sizeof(void*) : 0;
+ using Chunk_t = Chunk<CellSize, AllocationsPerChunk>;
+ using Cell_t = Cell<CellSize>;
+
+ using ChunkNode = Node<Chunk_t>;
+ using CellNode = Node<Cell_t*>;
+
+public:
+ static ChunkList& getInstance() {
+ static ChunkList c;
+ return c;
+ }
+
+ Cell_t* allocateChain() {
+ CellNode* recentHead = head_.load();
+ CellNode* currentNext = nullptr;
+ do {
+ // If there are no available chains, allocate a new chunk
+ if (!recentHead) {
+ ChunkNode* currentHandle;
+
+ // Make a new node
+ auto newChunk = new ChunkNode();
+
+ // Add the chunk to the chain
+ do {
+ currentHandle = handle_.load();
+ newChunk->next_ = currentHandle;
+ } while (
+ !handle_.compare_exchange_weak(currentHandle, newChunk));
+ return &newChunk->val_.memBlock_[0];
+ }
+
+ currentNext = recentHead->next_;
+ } while (!head_.compare_exchange_weak(recentHead, currentNext));
+
+ auto retnValue = recentHead->val_;
+ delete recentHead;
+ return retnValue;
+ }
+
+ void deallocateChain(Cell_t* newCell) {
+ if (!newCell) {
+ return;
+ }
+ CellNode* currentHead = head_.load();
+
+ // Construct a new node to be added to the linked list
+ CellNode* newHead = new CellNode(newCell);
+
+ // Add the chain to the linked list
+ do {
+ newHead->next_.store(currentHead, std::memory_order_release);
+ } while (!head_.compare_exchange_weak(currentHead, newHead));
+ }
+
+private:
+ ChunkList() : handle_(nullptr), head_(nullptr) {}
+
+ std::atomic<ChunkNode*> handle_;
+ std::atomic<CellNode*> head_;
+};
+}
+
+#endif
diff --git a/experimental/lfpAlloc/LICENSE b/experimental/lfpAlloc/LICENSE
new file mode 100644
index 0000000..b9e2c10
--- /dev/null
+++ b/experimental/lfpAlloc/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Adam Schwalm
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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. \ No newline at end of file
diff --git a/experimental/lfpAlloc/Pool.hpp b/experimental/lfpAlloc/Pool.hpp
new file mode 100644
index 0000000..370dab7
--- /dev/null
+++ b/experimental/lfpAlloc/Pool.hpp
@@ -0,0 +1,48 @@
+#ifndef LF_POOL_ALLOC_POOL
+#define LF_POOL_ALLOC_POOL
+
+#include <lfpAlloc/Utils.hpp>
+#include <lfpAlloc/ChunkList.hpp>
+
+namespace lfpAlloc {
+template <std::size_t Size, std::size_t AllocationsPerChunk>
+class Pool {
+ using ChunkList_t = ChunkList<Size, AllocationsPerChunk>;
+
+public:
+ static constexpr auto CellSize =
+ (Size > sizeof(void*)) ? Size - sizeof(void*) : 0;
+ using Cell_t = Cell<CellSize>;
+
+ Pool() : head_(nullptr) {}
+
+ ~Pool() { ChunkList_t::getInstance().deallocateChain(head_); }
+
+ void* allocate() {
+ // Head loaded from head_
+ Cell_t* currentHead = head_;
+ Cell_t* next;
+
+ // Out of cells to allocate
+ if (!currentHead) {
+ currentHead = ChunkList_t::getInstance().allocateChain();
+ }
+
+ next = currentHead->next_;
+ head_ = next;
+ return &currentHead->val_;
+ }
+
+ void deallocate(void* p) noexcept {
+ auto newHead = reinterpret_cast<Cell_t*>(p);
+ Cell_t* currentHead = head_;
+ newHead->next_ = currentHead;
+ head_ = newHead;
+ }
+
+private:
+ Cell_t* head_;
+};
+}
+
+#endif
diff --git a/experimental/lfpAlloc/PoolDispatcher.hpp b/experimental/lfpAlloc/PoolDispatcher.hpp
new file mode 100644
index 0000000..e4d1427
--- /dev/null
+++ b/experimental/lfpAlloc/PoolDispatcher.hpp
@@ -0,0 +1,79 @@
+#ifndef LF_POOL_DISPATCHER
+#define LF_POOL_DISPATCHER
+
+#include <tuple>
+#include <cassert>
+#include <cstddef>
+#include <lfpAlloc/Pool.hpp>
+
+#ifndef LFP_ALLOCATIONS_PER_CHUNK
+#define LFP_ALLOCATIONS_PER_CHUNK 64 * 100
+#endif
+
+namespace lfpAlloc {
+namespace detail {
+
+template <std::size_t Num, uint16_t... Ts>
+struct Pools : Pools<Num - 1, alignof(std::max_align_t) * Num, Ts...> {};
+
+template <uint16_t... Size>
+struct Pools<0, Size...> {
+ using type = std::tuple<Pool<Size, LFP_ALLOCATIONS_PER_CHUNK>...>;
+};
+}
+
+template <std::size_t NumPools>
+class PoolDispatcher {
+public:
+ void* allocate(std::size_t size) { return dispatchAllocate<0>(size); }
+
+ void deallocate(void* p, std::size_t size) noexcept {
+ dispatchDeallocate<0>(p, size);
+ }
+
+private:
+ thread_local static typename detail::Pools<NumPools>::type pools_;
+ static_assert(NumPools > 0, "Invalid number of pools");
+
+ template <std::size_t Index>
+ typename std::enable_if <
+ Index<NumPools, void*>::type
+ dispatchAllocate(std::size_t const& requestSize) {
+ if (requestSize <= std::get<Index>(pools_).CellSize) {
+ return std::get<Index>(pools_).allocate();
+ } else {
+ return dispatchAllocate<Index + 1>(requestSize);
+ }
+ }
+
+ template <std::size_t Index>
+ typename std::enable_if<!(Index < NumPools), void*>::type
+ dispatchAllocate(std::size_t const&) {
+ assert(false && "Invalid allocation size.");
+ return nullptr;
+ }
+
+ template <std::size_t Index>
+ typename std::enable_if <
+ Index<NumPools>::type
+ dispatchDeallocate(void* p, std::size_t const& requestSize) noexcept {
+ if (requestSize <= std::get<Index>(pools_).CellSize) {
+ std::get<Index>(pools_).deallocate(p);
+ } else {
+ dispatchDeallocate<Index + 1>(p, requestSize);
+ }
+ }
+
+ template <std::size_t Index>
+ typename std::enable_if<!(Index < NumPools)>::type
+ dispatchDeallocate(void*, std::size_t const&) noexcept {
+ assert(false && "Invalid deallocation size.");
+ }
+};
+
+template <std::size_t NumPools>
+thread_local typename detail::Pools<NumPools>::type
+ PoolDispatcher<NumPools>::pools_;
+}
+
+#endif
diff --git a/experimental/lfpAlloc/Utils.hpp b/experimental/lfpAlloc/Utils.hpp
new file mode 100644
index 0000000..8740a79
--- /dev/null
+++ b/experimental/lfpAlloc/Utils.hpp
@@ -0,0 +1,20 @@
+#include <cstdint>
+
+namespace lfpAlloc {
+namespace detail {
+template <std::size_t Val, std::size_t base = 2>
+struct Log {
+ enum { value = 1 + Log<Val / base, base>::value };
+};
+
+template <std::size_t base>
+struct Log<1, base> {
+ enum { value = 0 };
+};
+
+template <std::size_t base>
+struct Log<0, base> {
+ enum { value = 0 };
+};
+}
+}
diff --git a/experimental/premake5.lua b/experimental/premake5.lua
new file mode 100644
index 0000000..29511e1
--- /dev/null
+++ b/experimental/premake5.lua
@@ -0,0 +1,94 @@
+newoption {
+ trigger = "with-zlib",
+ description = "Build with zlib."
+}
+
+newoption {
+ trigger = "with-zstd",
+ description = "Build with ZStandard compression."
+}
+
+newoption {
+ trigger = "clang",
+ description = "Use clang compiler."
+}
+
+newoption {
+ trigger = "asan",
+ description = "Enable AddressSanitizer(gcc or clang only)."
+}
+
+solution "objview"
+ -- location ( "build" )
+ configurations { "Release", "Debug" }
+ platforms {"native", "x64", "x32"}
+
+ project "objview"
+
+ kind "ConsoleApp"
+ language "C++"
+ files { "viewer.cc", "trackball.cc" }
+ includedirs { "./" }
+ includedirs { "../../" }
+
+ flags { "c++11" }
+
+ if _OPTIONS['clang'] then
+ toolset "clang"
+ end
+
+ if _OPTIONS['with-zlib'] then
+ defines { 'ENABLE_ZLIB' }
+ links { 'z' }
+ end
+
+ if _OPTIONS['asan'] then
+ buildoptions { '-fsanitize=address' }
+ linkoptions { '-fsanitize=address' }
+ end
+
+ if _OPTIONS['with-zstd'] then
+ print("with-zstd")
+ defines { 'ENABLE_ZSTD' }
+ -- Set path to zstd installed dir.
+ includedirs { '$$HOME/local/include' }
+ libdirs { '$$HOME/local/lib' }
+ links { 'zstd' }
+ end
+
+ -- Uncomment if you want address sanitizer(gcc/clang only)
+ --buildoptions { "-fsanitize=address" }
+ --linkoptions { "-fsanitize=address" }
+
+ configuration { "linux" }
+ linkoptions { "`pkg-config --libs glfw3`" }
+ links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" }
+ linkoptions { "-pthread" }
+
+ configuration { "windows" }
+ -- Path to GLFW3
+ includedirs { '../../../local/glfw-3.2.bin.WIN64/include' }
+ libdirs { '../../../local/glfw-3.2.bin.WIN64/lib-vc2015' }
+ -- Path to GLEW
+ includedirs { '../../../local/glew-1.13.0/include' }
+ libdirs { '../../../local/glew-1.13.0/lib/Release/x64' }
+
+ links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" }
+ defines { "_CRT_SECURE_NO_WARNINGS" }
+ defines { "NOMINMAX" }
+
+ configuration { "macosx" }
+ includedirs { "/usr/local/include" }
+ buildoptions { "-Wno-deprecated-declarations" }
+ libdirs { "/usr/local/lib" }
+ links { "glfw3", "GLEW" }
+ linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" }
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize"}
+
diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h
new file mode 100644
index 0000000..f86b482
--- /dev/null
+++ b/experimental/tinyobj_loader_opt.h
@@ -0,0 +1,1684 @@
+//
+// Optimized wavefront .obj loader.
+// Requires lfpAlloc and C++11
+//
+
+/*
+The MIT License (MIT)
+
+Copyright (c) 2012-2017 Syoyo Fujita and many contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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.
+*/
+#ifndef TINOBJ_LOADER_OPT_H_
+#define TINOBJ_LOADER_OPT_H_
+
+#ifdef _WIN32
+#define atoll(S) _atoi64(S)
+#include <windows.h>
+#else
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#include <cassert>
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <vector>
+
+#include <atomic> // C++11
+#include <chrono> // C++11
+#include <thread> // C++11
+
+#include "lfpAlloc/Allocator.hpp"
+
+namespace tinyobj_opt {
+
+// ----------------------------------------------------------------------------
+// Small vector class useful for multi-threaded environment.
+//
+// stack_container.h
+//
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This allocator can be used with STL containers to provide a stack buffer
+// from which to allocate memory and overflows onto the heap. This stack buffer
+// would be allocated on the stack and allows us to avoid heap operations in
+// some situations.
+//
+// STL likes to make copies of allocators, so the allocator itself can't hold
+// the data. Instead, we make the creator responsible for creating a
+// StackAllocator::Source which contains the data. Copying the allocator
+// merely copies the pointer to this shared source, so all allocators created
+// based on our allocator will share the same stack buffer.
+//
+// This stack buffer implementation is very simple. The first allocation that
+// fits in the stack buffer will use the stack buffer. Any subsequent
+// allocations will not use the stack buffer, even if there is unused room.
+// This makes it appropriate for array-like containers, but the caller should
+// be sure to reserve() in the container up to the stack buffer size. Otherwise
+// the container will allocate a small array which will "use up" the stack
+// buffer.
+template <typename T, size_t stack_capacity>
+class StackAllocator : public std::allocator<T> {
+ public:
+ typedef typename std::allocator<T>::pointer pointer;
+ typedef typename std::allocator<T>::size_type size_type;
+
+ // Backing store for the allocator. The container owner is responsible for
+ // maintaining this for as long as any containers using this allocator are
+ // live.
+ struct Source {
+ Source() : used_stack_buffer_(false) {}
+
+ // Casts the buffer in its right type.
+ T *stack_buffer() { return reinterpret_cast<T *>(stack_buffer_); }
+ const T *stack_buffer() const {
+ return reinterpret_cast<const T *>(stack_buffer_);
+ }
+
+ //
+ // IMPORTANT: Take care to ensure that stack_buffer_ is aligned
+ // since it is used to mimic an array of T.
+ // Be careful while declaring any unaligned types (like bool)
+ // before stack_buffer_.
+ //
+
+ // The buffer itself. It is not of type T because we don't want the
+ // constructors and destructors to be automatically called. Define a POD
+ // buffer of the right size instead.
+ char stack_buffer_[sizeof(T[stack_capacity])];
+
+ // Set when the stack buffer is used for an allocation. We do not track
+ // how much of the buffer is used, only that somebody is using it.
+ bool used_stack_buffer_;
+ };
+
+ // Used by containers when they want to refer to an allocator of type U.
+ template <typename U>
+ struct rebind {
+ typedef StackAllocator<U, stack_capacity> other;
+ };
+
+ // For the straight up copy c-tor, we can share storage.
+ StackAllocator(const StackAllocator<T, stack_capacity> &rhs)
+ : source_(rhs.source_) {}
+
+ // ISO C++ requires the following constructor to be defined,
+ // and std::vector in VC++2008SP1 Release fails with an error
+ // in the class _Container_base_aux_alloc_real (from <xutility>)
+ // if the constructor does not exist.
+ // For this constructor, we cannot share storage; there's
+ // no guarantee that the Source buffer of Ts is large enough
+ // for Us.
+ // TODO(Google): If we were fancy pants, perhaps we could share storage
+ // iff sizeof(T) == sizeof(U).
+ template <typename U, size_t other_capacity>
+ StackAllocator(const StackAllocator<U, other_capacity> &other)
+ : source_(NULL) {
+ (void)other;
+ }
+
+ explicit StackAllocator(Source *source) : source_(source) {}
+
+ // Actually do the allocation. Use the stack buffer if nobody has used it yet
+ // and the size requested fits. Otherwise, fall through to the standard
+ // allocator.
+ pointer allocate(size_type n, void *hint = 0) {
+ if (source_ != NULL && !source_->used_stack_buffer_ &&
+ n <= stack_capacity) {
+ source_->used_stack_buffer_ = true;
+ return source_->stack_buffer();
+ } else {
+ return std::allocator<T>::allocate(n, hint);
+ }
+ }
+
+ // Free: when trying to free the stack buffer, just mark it as free. For
+ // non-stack-buffer pointers, just fall though to the standard allocator.
+ void deallocate(pointer p, size_type n) {
+ if (source_ != NULL && p == source_->stack_buffer())
+ source_->used_stack_buffer_ = false;
+ else
+ std::allocator<T>::deallocate(p, n);
+ }
+
+ private:
+ Source *source_;
+};
+
+// A wrapper around STL containers that maintains a stack-sized buffer that the
+// initial capacity of the vector is based on. Growing the container beyond the
+// stack capacity will transparently overflow onto the heap. The container must
+// support reserve().
+//
+// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this
+// type. This object is really intended to be used only internally. You'll want
+// to use the wrappers below for different types.
+template <typename TContainerType, int stack_capacity>
+class StackContainer {
+ public:
+ typedef TContainerType ContainerType;
+ typedef typename ContainerType::value_type ContainedType;
+ typedef StackAllocator<ContainedType, stack_capacity> Allocator;
+
+ // Allocator must be constructed before the container!
+ StackContainer() : allocator_(&stack_data_), container_(allocator_) {
+ // Make the container use the stack allocation by reserving our buffer size
+ // before doing anything else.
+ container_.reserve(stack_capacity);
+ }
+
+ // Getters for the actual container.
+ //
+ // Danger: any copies of this made using the copy constructor must have
+ // shorter lifetimes than the source. The copy will share the same allocator
+ // and therefore the same stack buffer as the original. Use std::copy to
+ // copy into a "real" container for longer-lived objects.
+ ContainerType &container() { return container_; }
+ const ContainerType &container() const { return container_; }
+
+ // Support operator-> to get to the container. This allows nicer syntax like:
+ // StackContainer<...> foo;
+ // std::sort(foo->begin(), foo->end());
+ ContainerType *operator->() { return &container_; }
+ const ContainerType *operator->() const { return &container_; }
+
+#ifdef UNIT_TEST
+ // Retrieves the stack source so that that unit tests can verify that the
+ // buffer is being used properly.
+ const typename Allocator::Source &stack_data() const { return stack_data_; }
+#endif
+
+ protected:
+ typename Allocator::Source stack_data_;
+ unsigned char pad_[7];
+ Allocator allocator_;
+ ContainerType container_;
+
+ // DISALLOW_EVIL_CONSTRUCTORS(StackContainer);
+ StackContainer(const StackContainer &);
+ void operator=(const StackContainer &);
+};
+
+// StackVector
+//
+// Example:
+// StackVector<int, 16> foo;
+// foo->push_back(22); // we have overloaded operator->
+// foo[0] = 10; // as well as operator[]
+template <typename T, size_t stack_capacity>
+class StackVector
+ : public StackContainer<std::vector<T, StackAllocator<T, stack_capacity> >,
+ stack_capacity> {
+ public:
+ StackVector()
+ : StackContainer<std::vector<T, StackAllocator<T, stack_capacity> >,
+ stack_capacity>() {}
+
+ // We need to put this in STL containers sometimes, which requires a copy
+ // constructor. We can't call the regular copy constructor because that will
+ // take the stack buffer from the original. Here, we create an empty object
+ // and make a stack buffer of its own.
+ StackVector(const StackVector<T, stack_capacity> &other)
+ : StackContainer<std::vector<T, StackAllocator<T, stack_capacity> >,
+ stack_capacity>() {
+ this->container().assign(other->begin(), other->end());
+ }
+
+ StackVector<T, stack_capacity> &operator=(
+ const StackVector<T, stack_capacity> &other) {
+ this->container().assign(other->begin(), other->end());
+ return *this;
+ }
+
+ // Vectors are commonly indexed, which isn't very convenient even with
+ // operator-> (using "->at()" does exception stuff we don't want).
+ T &operator[](size_t i) { return this->container().operator[](i); }
+ const T &operator[](size_t i) const {
+ return this->container().operator[](i);
+ }
+};
+
+// ----------------------------------------------------------------------------
+
+typedef struct {
+ std::string name;
+
+ float ambient[3];
+ float diffuse[3];
+ float specular[3];
+ float transmittance[3];
+ float emission[3];
+ float shininess;
+ float ior; // index of refraction
+ float dissolve; // 1 == opaque; 0 == fully transparent
+ // illumination model (see http://www.fileformat.info/format/material/)
+ int illum;
+
+ int dummy; // Suppress padding warning.
+
+ std::string ambient_texname; // map_Ka
+ std::string diffuse_texname; // map_Kd
+ std::string specular_texname; // map_Ks
+ std::string specular_highlight_texname; // map_Ns
+ std::string bump_texname; // map_bump, bump
+ std::string displacement_texname; // disp
+ std::string alpha_texname; // map_d
+
+ // PBR extension
+ // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
+ float roughness; // [0, 1] default 0
+ float metallic; // [0, 1] default 0
+ float sheen; // [0, 1] default 0
+ float clearcoat_thickness; // [0, 1] default 0
+ float clearcoat_roughness; // [0, 1] default 0
+ float anisotropy; // aniso. [0, 1] default 0
+ float anisotropy_rotation; // anisor. [0, 1] default 0
+ std::string roughness_texname; // map_Pr
+ std::string metallic_texname; // map_Pm
+ std::string sheen_texname; // map_Ps
+ std::string emissive_texname; // map_Ke
+ std::string normal_texname; // norm. For normal mapping.
+
+ std::map<std::string, std::string> unknown_parameter;
+} material_t;
+
+typedef struct {
+ std::string name; // group name or object name.
+ unsigned int face_offset;
+ unsigned int length;
+} shape_t;
+
+struct index_t {
+ int vertex_index, texcoord_index, normal_index;
+ index_t() : vertex_index(-1), texcoord_index(-1), normal_index(-1) {}
+ explicit index_t(int idx)
+ : vertex_index(idx), texcoord_index(idx), normal_index(idx) {}
+ index_t(int vidx, int vtidx, int vnidx)
+ : vertex_index(vidx), texcoord_index(vtidx), normal_index(vnidx) {}
+};
+
+typedef struct {
+ std::vector<float, lfpAlloc::lfpAllocator<float> > vertices;
+ std::vector<float, lfpAlloc::lfpAllocator<float> > normals;
+ std::vector<float, lfpAlloc::lfpAllocator<float> > texcoords;
+ std::vector<index_t, lfpAlloc::lfpAllocator<index_t> > indices;
+ std::vector<int, lfpAlloc::lfpAllocator<int> > face_num_verts;
+ std::vector<int, lfpAlloc::lfpAllocator<int> > material_ids;
+} attrib_t;
+
+typedef StackVector<char, 256> ShortString;
+
+#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t'))
+#define IS_DIGIT(x) \
+ (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10))
+#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0'))
+
+static inline void skip_space(const char **token) {
+ while ((*token)[0] == ' ' || (*token)[0] == '\t') {
+ (*token)++;
+ }
+}
+
+static inline void skip_space_and_cr(const char **token) {
+ while ((*token)[0] == ' ' || (*token)[0] == '\t' || (*token)[0] == '\r') {
+ (*token)++;
+ }
+}
+
+static inline int until_space(const char *token) {
+ const char *p = token;
+ while (p[0] != '\0' && p[0] != ' ' && p[0] != '\t' && p[0] != '\r') {
+ p++;
+ }
+
+ return p - token;
+}
+
+static inline int length_until_newline(const char *token, int n) {
+ int len = 0;
+
+ // Assume token[n-1] = '\0'
+ for (len = 0; len < n - 1; len++) {
+ if (token[len] == '\n') {
+ break;
+ }
+ if ((token[len] == '\r') && ((len < (n - 2)) && (token[len + 1] != '\n'))) {
+ break;
+ }
+ }
+
+ return len;
+}
+
+// http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work
+static inline int my_atoi(const char *c) {
+ int value = 0;
+ int sign = 1;
+ if (*c == '+' || *c == '-') {
+ if (*c == '-') sign = -1;
+ c++;
+ }
+ while (((*c) >= '0') && ((*c) <= '9')) { // isdigit(*c)
+ value *= 10;
+ value += (int)(*c - '0');
+ c++;
+ }
+ return value * sign;
+}
+
+// Make index zero-base, and also support relative index.
+static inline int fixIndex(int idx, int n) {
+ if (idx > 0) return idx - 1;
+ if (idx == 0) return 0;
+ return n + idx; // negative value = relative
+}
+
+// Parse raw triples: i, i/j/k, i//k, i/j
+static index_t parseRawTriple(const char **token) {
+ index_t vi(
+ static_cast<int>(0x80000000)); // 0x80000000 = -2147483648 = invalid
+
+ vi.vertex_index = my_atoi((*token));
+ while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
+ (*token)[0] != '\t' && (*token)[0] != '\r') {
+ (*token)++;
+ }
+ if ((*token)[0] != '/') {
+ return vi;
+ }
+ (*token)++;
+
+ // i//k
+ if ((*token)[0] == '/') {
+ (*token)++;
+ vi.normal_index = my_atoi((*token));
+ while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
+ (*token)[0] != '\t' && (*token)[0] != '\r') {
+ (*token)++;
+ }
+ return vi;
+ }
+
+ // i/j/k or i/j
+ vi.texcoord_index = my_atoi((*token));
+ while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
+ (*token)[0] != '\t' && (*token)[0] != '\r') {
+ (*token)++;
+ }
+ if ((*token)[0] != '/') {
+ return vi;
+ }
+
+ // i/j/k
+ (*token)++; // skip '/'
+ vi.normal_index = my_atoi((*token));
+ while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
+ (*token)[0] != '\t' && (*token)[0] != '\r') {
+ (*token)++;
+ }
+ return vi;
+}
+
+static inline bool parseString(ShortString *s, const char **token) {
+ skip_space(token);
+ size_t e = until_space((*token));
+ (*s)->insert((*s)->end(), (*token), (*token) + e);
+ (*token) += e;
+ return true;
+}
+
+static inline int parseInt(const char **token) {
+ skip_space(token);
+ int i = my_atoi((*token));
+ (*token) += until_space((*token));
+ return i;
+}
+
+// Tries to parse a floating point number located at s.
+//
+// s_end should be a location in the string where reading should absolutely
+// stop. For example at the end of the string, to prevent buffer overflows.
+//
+// Parses the following EBNF grammar:
+// sign = "+" | "-" ;
+// END = ? anything not in digit ?
+// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
+// integer = [sign] , digit , {digit} ;
+// decimal = integer , ["." , integer] ;
+// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ;
+//
+// Valid strings are for example:
+// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2
+//
+// If the parsing is a success, result is set to the parsed value and true
+// is returned.
+//
+// The function is greedy and will parse until any of the following happens:
+// - a non-conforming character is encountered.
+// - s_end is reached.
+//
+// The following situations triggers a failure:
+// - s >= s_end.
+// - parse failure.
+//
+static bool tryParseDouble(const char *s, const char *s_end, double *result) {
+ if (s >= s_end) {
+ return false;
+ }
+
+ double mantissa = 0.0;
+ // This exponent is base 2 rather than 10.
+ // However the exponent we parse is supposed to be one of ten,
+ // thus we must take care to convert the exponent/and or the
+ // mantissa to a * 2^E, where a is the mantissa and E is the
+ // exponent.
+ // To get the final double we will use ldexp, it requires the
+ // exponent to be in base 2.
+ int exponent = 0;
+
+ // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED
+ // TO JUMP OVER DEFINITIONS.
+ char sign = '+';
+ char exp_sign = '+';
+ char const *curr = s;
+
+ // How many characters were read in a loop.
+ int read = 0;
+ // Tells whether a loop terminated due to reaching s_end.
+ bool end_not_reached = false;
+
+ /*
+ BEGIN PARSING.
+ */
+
+ // Find out what sign we've got.
+ if (*curr == '+' || *curr == '-') {
+ sign = *curr;
+ curr++;
+ } else if (IS_DIGIT(*curr)) { /* Pass through. */
+ } else {
+ goto fail;
+ }
+
+ // Read the integer part.
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ mantissa *= 10;
+ mantissa += static_cast<int>(*curr - 0x30);
+ curr++;
+ read++;
+ end_not_reached = (curr != s_end);
+ }
+
+ // We must make sure we actually got something.
+ if (read == 0) goto fail;
+ // We allow numbers of form "#", "###" etc.
+ if (!end_not_reached) goto assemble;
+
+ // Read the decimal part.
+ if (*curr == '.') {
+ curr++;
+ read = 1;
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ // pow(10.0, -read)
+ double frac_value = 1.0;
+ for (int f = 0; f < read; f++) {
+ frac_value *= 0.1;
+ }
+ mantissa += static_cast<int>(*curr - 0x30) * frac_value;
+ read++;
+ curr++;
+ end_not_reached = (curr != s_end);
+ }
+ } else if (*curr == 'e' || *curr == 'E') {
+ } else {
+ goto assemble;
+ }
+
+ if (!end_not_reached) goto assemble;
+
+ // Read the exponent part.
+ if (*curr == 'e' || *curr == 'E') {
+ curr++;
+ // Figure out if a sign is present and if it is.
+ end_not_reached = (curr != s_end);
+ if (end_not_reached && (*curr == '+' || *curr == '-')) {
+ exp_sign = *curr;
+ curr++;
+ } else if (IS_DIGIT(*curr)) { /* Pass through. */
+ } else {
+ // Empty E is not allowed.
+ goto fail;
+ }
+
+ read = 0;
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ exponent *= 10;
+ exponent += static_cast<int>(*curr - 0x30);
+ curr++;
+ read++;
+ end_not_reached = (curr != s_end);
+ }
+ exponent *= (exp_sign == '+' ? 1 : -1);
+ if (read == 0) goto fail;
+ }
+
+assemble :
+
+{
+ // = pow(5.0, exponent);
+ double a = 1.0;
+ for (int i = 0; i < exponent; i++) {
+ a = a * 5.0;
+ }
+ *result =
+ //(sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent);
+ (sign == '+' ? 1 : -1) * (mantissa * a) *
+ static_cast<double>(1ULL << exponent); // 5.0^exponent * 2^exponent
+}
+
+ return true;
+fail:
+ return false;
+}
+
+static inline float parseFloat(const char **token) {
+ skip_space(token);
+#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER
+ float f = static_cast<float>(atof(*token));
+ (*token) += strcspn((*token), " \t\r");
+#else
+ const char *end = (*token) + until_space((*token));
+ double val = 0.0;
+ tryParseDouble((*token), end, &val);
+ float f = static_cast<float>(val);
+ (*token) = end;
+#endif
+ return f;
+}
+
+static inline void parseFloat2(float *x, float *y, const char **token) {
+ (*x) = parseFloat(token);
+ (*y) = parseFloat(token);
+}
+
+static inline void parseFloat3(float *x, float *y, float *z,
+ const char **token) {
+ (*x) = parseFloat(token);
+ (*y) = parseFloat(token);
+ (*z) = parseFloat(token);
+}
+
+static void InitMaterial(material_t *material) {
+ material->name = "";
+ material->ambient_texname = "";
+ material->diffuse_texname = "";
+ material->specular_texname = "";
+ material->specular_highlight_texname = "";
+ material->bump_texname = "";
+ material->displacement_texname = "";
+ material->alpha_texname = "";
+ for (int i = 0; i < 3; i++) {
+ material->ambient[i] = 0.f;
+ material->diffuse[i] = 0.f;
+ material->specular[i] = 0.f;
+ material->transmittance[i] = 0.f;
+ material->emission[i] = 0.f;
+ }
+ material->illum = 0;
+ material->dissolve = 1.f;
+ material->shininess = 1.f;
+ material->ior = 1.f;
+ material->unknown_parameter.clear();
+}
+
+static void LoadMtl(std::map<std::string, int> *material_map,
+ std::vector<material_t> *materials,
+ std::istream *inStream) {
+ // Create a default material anyway.
+ material_t material;
+ InitMaterial(&material);
+
+ size_t maxchars = 8192; // Alloc enough size.
+ std::vector<char> buf(maxchars); // Alloc enough size.
+ while (inStream->peek() != -1) {
+ inStream->getline(&buf[0], static_cast<std::streamsize>(maxchars));
+
+ std::string linebuf(&buf[0]);
+
+ // Trim trailing whitespace.
+ if (linebuf.size() > 0) {
+ linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1);
+ }
+
+ // Trim newline '\r\n' or '\n'
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\n')
+ linebuf.erase(linebuf.size() - 1);
+ }
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\r')
+ linebuf.erase(linebuf.size() - 1);
+ }
+
+ // Skip if empty line.
+ if (linebuf.empty()) {
+ continue;
+ }
+
+ // Skip leading space.
+ const char *token = linebuf.c_str();
+ token += strspn(token, " \t");
+
+ assert(token);
+ if (token[0] == '\0') continue; // empty line
+
+ if (token[0] == '#') continue; // comment line
+
+ // new mtl
+ if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) {
+ // flush previous material.
+ if (!material.name.empty()) {
+ material_map->insert(std::pair<std::string, int>(
+ material.name, static_cast<int>(materials->size())));
+ materials->push_back(material);
+ }
+
+ // initial temporary material
+ InitMaterial(&material);
+
+ // set new mtl name
+ char namebuf[4096];
+ token += 7;
+#ifdef _MSC_VER
+ sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
+#else
+ sscanf(token, "%s", namebuf);
+#endif
+ material.name = namebuf;
+ continue;
+ }
+
+ // ambient
+ if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) {
+ token += 2;
+ float r, g, b;
+ parseFloat3(&r, &g, &b, &token);
+ material.ambient[0] = r;
+ material.ambient[1] = g;
+ material.ambient[2] = b;
+ continue;
+ }
+
+ // diffuse
+ if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) {
+ token += 2;
+ float r, g, b;
+ parseFloat3(&r, &g, &b, &token);
+ material.diffuse[0] = r;
+ material.diffuse[1] = g;
+ material.diffuse[2] = b;
+ continue;
+ }
+
+ // specular
+ if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) {
+ token += 2;
+ float r, g, b;
+ parseFloat3(&r, &g, &b, &token);
+ material.specular[0] = r;
+ material.specular[1] = g;
+ material.specular[2] = b;
+ continue;
+ }
+
+ // transmittance
+ if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) ||
+ (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) {
+ token += 2;
+ float r, g, b;
+ parseFloat3(&r, &g, &b, &token);
+ material.transmittance[0] = r;
+ material.transmittance[1] = g;
+ material.transmittance[2] = b;
+ continue;
+ }
+
+ // ior(index of refraction)
+ if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) {
+ token += 2;
+ material.ior = parseFloat(&token);
+ continue;
+ }
+
+ // emission
+ if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) {
+ token += 2;
+ float r, g, b;
+ parseFloat3(&r, &g, &b, &token);
+ material.emission[0] = r;
+ material.emission[1] = g;
+ material.emission[2] = b;
+ continue;
+ }
+
+ // shininess
+ if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) {
+ token += 2;
+ material.shininess = parseFloat(&token);
+ continue;
+ }
+
+ // illum model
+ if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) {
+ token += 6;
+ material.illum = parseInt(&token);
+ continue;
+ }
+
+ // dissolve
+ if ((token[0] == 'd' && IS_SPACE(token[1]))) {
+ token += 1;
+ material.dissolve = parseFloat(&token);
+ continue;
+ }
+
+ if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) {
+ token += 2;
+ // Invert value of Tr(assume Tr is in range [0, 1])
+ material.dissolve = 1.0f - parseFloat(&token);
+ continue;
+ }
+
+ // PBR: roughness
+ if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) {
+ token += 2;
+ material.roughness = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: metallic
+ if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) {
+ token += 2;
+ material.metallic = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: sheen
+ if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) {
+ token += 2;
+ material.sheen = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: clearcoat thickness
+ if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) {
+ token += 2;
+ material.clearcoat_thickness = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: clearcoat roughness
+ if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) {
+ token += 4;
+ material.clearcoat_roughness = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: anisotropy
+ if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) {
+ token += 6;
+ material.anisotropy = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: anisotropy rotation
+ if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.anisotropy_rotation = parseFloat(&token);
+ continue;
+ }
+
+ // ambient texture
+ if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.ambient_texname = token;
+ continue;
+ }
+
+ // diffuse texture
+ if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.diffuse_texname = token;
+ continue;
+ }
+
+ // specular texture
+ if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.specular_texname = token;
+ continue;
+ }
+
+ // specular highlight texture
+ if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.specular_highlight_texname = token;
+ continue;
+ }
+
+ // bump texture
+ if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) {
+ token += 9;
+ material.bump_texname = token;
+ continue;
+ }
+
+ // alpha texture
+ if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) {
+ token += 6;
+ material.alpha_texname = token;
+ continue;
+ }
+
+ // bump texture
+ if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ material.bump_texname = token;
+ continue;
+ }
+
+ // displacement texture
+ if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ material.displacement_texname = token;
+ continue;
+ }
+
+ // PBR: roughness texture
+ if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.roughness_texname = token;
+ continue;
+ }
+
+ // PBR: metallic texture
+ if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.metallic_texname = token;
+ continue;
+ }
+
+ // PBR: sheen texture
+ if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.sheen_texname = token;
+ continue;
+ }
+
+ // PBR: emissive texture
+ if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.emissive_texname = token;
+ continue;
+ }
+
+ // PBR: normal map texture
+ if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ material.normal_texname = token;
+ continue;
+ }
+
+ // unknown parameter
+ const char *_space = strchr(token, ' ');
+ if (!_space) {
+ _space = strchr(token, '\t');
+ }
+ if (_space) {
+ std::ptrdiff_t len = _space - token;
+ std::string key(token, static_cast<size_t>(len));
+ std::string value = _space + 1;
+ material.unknown_parameter.insert(
+ std::pair<std::string, std::string>(key, value));
+ }
+ }
+ // flush last material.
+ material_map->insert(std::pair<std::string, int>(
+ material.name, static_cast<int>(materials->size())));
+ materials->push_back(material);
+}
+
+typedef enum {
+ COMMAND_EMPTY,
+ COMMAND_V,
+ COMMAND_VN,
+ COMMAND_VT,
+ COMMAND_F,
+ COMMAND_G,
+ COMMAND_O,
+ COMMAND_USEMTL,
+ COMMAND_MTLLIB,
+
+} CommandType;
+
+typedef struct {
+ float vx, vy, vz;
+ float nx, ny, nz;
+ float tx, ty;
+
+ // for f
+ std::vector<index_t, lfpAlloc::lfpAllocator<index_t> > f;
+ // std::vector<index_t> f;
+ std::vector<int, lfpAlloc::lfpAllocator<int> > f_num_verts;
+
+ const char *group_name;
+ unsigned int group_name_len;
+ const char *object_name;
+ unsigned int object_name_len;
+ const char *material_name;
+ unsigned int material_name_len;
+
+ const char *mtllib_name;
+ unsigned int mtllib_name_len;
+
+ CommandType type;
+} Command;
+
+struct CommandCount {
+ size_t num_v;
+ size_t num_vn;
+ size_t num_vt;
+ size_t num_f;
+ size_t num_indices;
+ CommandCount() {
+ num_v = 0;
+ num_vn = 0;
+ num_vt = 0;
+ num_f = 0;
+ num_indices = 0;
+ }
+};
+
+class LoadOption {
+ public:
+ LoadOption() : req_num_threads(-1), triangulate(true), verbose(false) {}
+
+ int req_num_threads;
+ bool triangulate;
+ bool verbose;
+};
+
+/// Parse wavefront .obj(.obj string data is expanded to linear char array
+/// `buf')
+/// -1 to req_num_threads use the number of HW threads in the running system.
+bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, const char *buf, size_t len,
+ const LoadOption &option);
+
+} // namespace tinyobj_opt
+
+#endif // TINOBJ_LOADER_OPT_H_
+
+#ifdef TINYOBJ_LOADER_OPT_IMPLEMENTATION
+
+namespace tinyobj_opt {
+
+static bool parseLine(Command *command, const char *p, size_t p_len,
+ bool triangulate = true) {
+ // @todo { operate directly on pointer `p'. to do that, add range check for
+ // string operatoion against `p', since `p' is not null-terminated at p[p_len]
+ // }
+ char linebuf[4096];
+ assert(p_len < 4095);
+ memcpy(linebuf, p, p_len);
+ linebuf[p_len] = '\0';
+
+ const char *token = linebuf;
+
+ command->type = COMMAND_EMPTY;
+
+ // Skip leading space.
+ skip_space(&token);
+
+ assert(token);
+ if (token[0] == '\0') { // empty line
+ return false;
+ }
+
+ if (token[0] == '#') { // comment line
+ return false;
+ }
+
+ // vertex
+ if (token[0] == 'v' && IS_SPACE((token[1]))) {
+ token += 2;
+ float x = 0.0f, y = 0.0f, z = 0.0f;
+ parseFloat3(&x, &y, &z, &token);
+ command->vx = x;
+ command->vy = y;
+ command->vz = z;
+ command->type = COMMAND_V;
+ return true;
+ }
+
+ // normal
+ if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
+ token += 3;
+ float x = 0.0f, y = 0.0f, z = 0.0f;
+ parseFloat3(&x, &y, &z, &token);
+ command->nx = x;
+ command->ny = y;
+ command->nz = z;
+ command->type = COMMAND_VN;
+ return true;
+ }
+
+ // texcoord
+ if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
+ token += 3;
+ float x = 0.0f, y = 0.0f;
+ parseFloat2(&x, &y, &token);
+ command->tx = x;
+ command->ty = y;
+ command->type = COMMAND_VT;
+ return true;
+ }
+
+ // face
+ if (token[0] == 'f' && IS_SPACE((token[1]))) {
+ token += 2;
+ skip_space(&token);
+
+ StackVector<index_t, 8> f;
+
+ while (!IS_NEW_LINE(token[0])) {
+ index_t vi = parseRawTriple(&token);
+ skip_space_and_cr(&token);
+
+ f->push_back(vi);
+ }
+
+ command->type = COMMAND_F;
+
+ if (triangulate) {
+ index_t i0 = f[0];
+ index_t i1(-1);
+ index_t i2 = f[1];
+
+ for (size_t k = 2; k < f->size(); k++) {
+ i1 = i2;
+ i2 = f[k];
+ command->f.emplace_back(i0);
+ command->f.emplace_back(i1);
+ command->f.emplace_back(i2);
+
+ command->f_num_verts.emplace_back(3);
+ }
+
+ } else {
+ for (size_t k = 0; k < f->size(); k++) {
+ command->f.emplace_back(f[k]);
+ }
+
+ command->f_num_verts.emplace_back(f->size());
+ }
+
+ return true;
+ }
+
+ // use mtl
+ if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) {
+ token += 7;
+
+ // int newMaterialId = -1;
+ // if (material_map.find(namebuf) != material_map.end()) {
+ // newMaterialId = material_map[namebuf];
+ //} else {
+ // // { error!! material not found }
+ //}
+
+ // if (newMaterialId != materialId) {
+ // materialId = newMaterialId;
+ //}
+
+ // command->material_name = .insert(command->material_name->end(), namebuf,
+ // namebuf + strlen(namebuf));
+ // command->material_name->push_back('\0');
+ skip_space(&token);
+ command->material_name = p + (token - linebuf);
+ command->material_name_len =
+ length_until_newline(token, p_len - (token - linebuf)) + 1;
+ command->type = COMMAND_USEMTL;
+
+ return true;
+ }
+
+ // load mtl
+ if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
+ // By specification, `mtllib` should be appear only once in .obj
+ token += 7;
+
+ skip_space(&token);
+ command->mtllib_name = p + (token - linebuf);
+ command->mtllib_name_len =
+ length_until_newline(token, p_len - (token - linebuf)) + 1;
+ command->type = COMMAND_MTLLIB;
+
+ return true;
+ }
+
+ // group name
+ if (token[0] == 'g' && IS_SPACE((token[1]))) {
+ // @todo { multiple group name. }
+ token += 2;
+
+ command->group_name = p + (token - linebuf);
+ command->group_name_len =
+ length_until_newline(token, p_len - (token - linebuf)) + 1;
+ command->type = COMMAND_G;
+
+ return true;
+ }
+
+ // object name
+ if (token[0] == 'o' && IS_SPACE((token[1]))) {
+ // @todo { multiple object name? }
+ token += 2;
+
+ command->object_name = p + (token - linebuf);
+ command->object_name_len =
+ length_until_newline(token, p_len - (token - linebuf)) + 1;
+ command->type = COMMAND_O;
+
+ return true;
+ }
+
+ return false;
+}
+
+typedef struct {
+ size_t pos;
+ size_t len;
+} LineInfo;
+
+// Idea come from https://github.com/antonmks/nvParse
+// 1. mmap file
+// 2. find newline(\n, \r\n, \r) and list of line data.
+// 3. Do parallel parsing for each line.
+// 4. Reconstruct final mesh data structure.
+
+#define kMaxThreads (32)
+
+static inline bool is_line_ending(const char *p, size_t i, size_t end_i) {
+ if (p[i] == '\0') return true;
+ if (p[i] == '\n') return true; // this includes \r\n
+ if (p[i] == '\r') {
+ if (((i + 1) < end_i) && (p[i + 1] != '\n')) { // detect only \r case
+ return true;
+ }
+ }
+ return false;
+}
+
+bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, const char *buf, size_t len,
+ const LoadOption &option) {
+ attrib->vertices.clear();
+ attrib->normals.clear();
+ attrib->texcoords.clear();
+ attrib->indices.clear();
+ attrib->face_num_verts.clear();
+ attrib->material_ids.clear();
+ shapes->clear();
+
+ if (len < 1) return false;
+
+ auto num_threads = (option.req_num_threads < 0)
+ ? std::thread::hardware_concurrency()
+ : option.req_num_threads;
+ num_threads =
+ (std::max)(1, (std::min)(static_cast<int>(num_threads), kMaxThreads));
+
+ if (option.verbose) {
+ std::cout << "# of threads = " << num_threads << std::endl;
+ }
+
+ auto t1 = std::chrono::high_resolution_clock::now();
+
+ std::vector<LineInfo, lfpAlloc::lfpAllocator<LineInfo> >
+ line_infos[kMaxThreads];
+ for (size_t t = 0; t < static_cast<size_t>(num_threads); t++) {
+ // Pre allocate enough memory. len / 128 / num_threads is just a heuristic
+ // value.
+ line_infos[t].reserve(len / 128 / num_threads);
+ }
+
+ std::chrono::duration<double, std::milli> ms_linedetection;
+ std::chrono::duration<double, std::milli> ms_alloc;
+ std::chrono::duration<double, std::milli> ms_parse;
+ std::chrono::duration<double, std::milli> ms_load_mtl;
+ std::chrono::duration<double, std::milli> ms_merge;
+ std::chrono::duration<double, std::milli> ms_construct;
+
+ // 1. Find '\n' and create line data.
+ {
+ StackVector<std::thread, 16> workers;
+
+ auto start_time = std::chrono::high_resolution_clock::now();
+ auto chunk_size = len / num_threads;
+
+ for (size_t t = 0; t < static_cast<size_t>(num_threads); t++) {
+ workers->push_back(std::thread([&, t]() {
+ auto start_idx = (t + 0) * chunk_size;
+ auto end_idx = (std::min)((t + 1) * chunk_size, len - 1);
+ if (t == static_cast<size_t>((num_threads - 1))) {
+ end_idx = len - 1;
+ }
+
+ size_t prev_pos = start_idx;
+ for (size_t i = start_idx; i < end_idx; i++) {
+ if (is_line_ending(buf, i, end_idx)) {
+ if ((t > 0) && (prev_pos == start_idx) &&
+ (!is_line_ending(buf, start_idx - 1, end_idx))) {
+ // first linebreak found in (chunk > 0), and a line before this
+ // linebreak belongs to previous chunk, so skip it.
+ prev_pos = i + 1;
+ continue;
+ } else {
+ LineInfo info;
+ info.pos = prev_pos;
+ info.len = i - prev_pos;
+
+ if (info.len > 0) {
+ line_infos[t].push_back(info);
+ }
+
+ prev_pos = i + 1;
+ }
+ }
+ }
+
+ // Find extra line which spand across chunk boundary.
+ if ((t < num_threads) && (buf[end_idx - 1] != '\n')) {
+ auto extra_span_idx = (std::min)(end_idx - 1 + chunk_size, len);
+ for (size_t i = end_idx; i < extra_span_idx; i++) {
+ if (is_line_ending(buf, i, extra_span_idx)) {
+ LineInfo info;
+ info.pos = prev_pos;
+ info.len = i - prev_pos;
+
+ if (info.len > 0) {
+ line_infos[t].push_back(info);
+ }
+
+ break;
+ }
+ }
+ }
+ }));
+ }
+
+ for (size_t t = 0; t < workers->size(); t++) {
+ workers[t].join();
+ }
+
+ auto end_time = std::chrono::high_resolution_clock::now();
+
+ ms_linedetection = end_time - start_time;
+ }
+
+ auto line_sum = 0;
+ for (size_t t = 0; t < num_threads; t++) {
+ // std::cout << t << ": # of lines = " << line_infos[t].size() << std::endl;
+ line_sum += line_infos[t].size();
+ }
+ // std::cout << "# of lines = " << line_sum << std::endl;
+
+ std::vector<Command> commands[kMaxThreads];
+
+ // 2. allocate buffer
+ auto t_alloc_start = std::chrono::high_resolution_clock::now();
+ {
+ for (size_t t = 0; t < num_threads; t++) {
+ commands[t].reserve(line_infos[t].size());
+ }
+ }
+
+ CommandCount command_count[kMaxThreads];
+ // Array index to `mtllib` line. According to wavefront .obj spec, `mtllib'
+ // should appear only once in .obj.
+ int mtllib_t_index = -1;
+ int mtllib_i_index = -1;
+
+ ms_alloc = std::chrono::high_resolution_clock::now() - t_alloc_start;
+
+ // 2. parse each line in parallel.
+ {
+ StackVector<std::thread, 16> workers;
+ auto t_start = std::chrono::high_resolution_clock::now();
+
+ for (size_t t = 0; t < num_threads; t++) {
+ workers->push_back(std::thread([&, t]() {
+
+ for (size_t i = 0; i < line_infos[t].size(); i++) {
+ Command command;
+ bool ret = parseLine(&command, &buf[line_infos[t][i].pos],
+ line_infos[t][i].len, option.triangulate);
+ if (ret) {
+ if (command.type == COMMAND_V) {
+ command_count[t].num_v++;
+ } else if (command.type == COMMAND_VN) {
+ command_count[t].num_vn++;
+ } else if (command.type == COMMAND_VT) {
+ command_count[t].num_vt++;
+ } else if (command.type == COMMAND_F) {
+ command_count[t].num_f += command.f.size();
+ command_count[t].num_indices += command.f_num_verts.size();
+ }
+
+ if (command.type == COMMAND_MTLLIB) {
+ mtllib_t_index = t;
+ mtllib_i_index = commands->size();
+ }
+
+ commands[t].emplace_back(std::move(command));
+ }
+ }
+
+ }));
+ }
+
+ for (size_t t = 0; t < workers->size(); t++) {
+ workers[t].join();
+ }
+
+ auto t_end = std::chrono::high_resolution_clock::now();
+
+ ms_parse = t_end - t_start;
+ }
+
+ std::map<std::string, int> material_map;
+
+ // Load material(if exits)
+ if (mtllib_i_index >= 0 && mtllib_t_index >= 0 &&
+ commands[mtllib_t_index][mtllib_i_index].mtllib_name &&
+ commands[mtllib_t_index][mtllib_i_index].mtllib_name_len > 0) {
+ std::string material_filename =
+ std::string(commands[mtllib_t_index][mtllib_i_index].mtllib_name,
+ commands[mtllib_t_index][mtllib_i_index].mtllib_name_len);
+ // std::cout << "mtllib :" << material_filename << std::endl;
+
+ auto t1 = std::chrono::high_resolution_clock::now();
+
+ std::ifstream ifs(material_filename);
+ if (ifs.good()) {
+ LoadMtl(&material_map, materials, &ifs);
+
+ // std::cout << "maetrials = " << materials.size() << std::endl;
+
+ ifs.close();
+ }
+
+ auto t2 = std::chrono::high_resolution_clock::now();
+
+ ms_load_mtl = t2 - t1;
+ }
+
+ auto command_sum = 0;
+ for (size_t t = 0; t < num_threads; t++) {
+ // std::cout << t << ": # of commands = " << commands[t].size() <<
+ // std::endl;
+ command_sum += commands[t].size();
+ }
+ // std::cout << "# of commands = " << command_sum << std::endl;
+
+ size_t num_v = 0;
+ size_t num_vn = 0;
+ size_t num_vt = 0;
+ size_t num_f = 0;
+ size_t num_indices = 0;
+ for (size_t t = 0; t < num_threads; t++) {
+ num_v += command_count[t].num_v;
+ num_vn += command_count[t].num_vn;
+ num_vt += command_count[t].num_vt;
+ num_f += command_count[t].num_f;
+ num_indices += command_count[t].num_indices;
+ }
+
+ // std::cout << "# v " << num_v << std::endl;
+ // std::cout << "# vn " << num_vn << std::endl;
+ // std::cout << "# vt " << num_vt << std::endl;
+ // std::cout << "# f " << num_f << std::endl;
+
+ // 4. merge
+ // @todo { parallelize merge. }
+ {
+ auto t_start = std::chrono::high_resolution_clock::now();
+
+ attrib->vertices.resize(num_v * 3);
+ attrib->normals.resize(num_vn * 3);
+ attrib->texcoords.resize(num_vt * 2);
+ attrib->indices.resize(num_f);
+ attrib->face_num_verts.resize(num_indices);
+ attrib->material_ids.resize(num_indices);
+
+ size_t v_offsets[kMaxThreads];
+ size_t n_offsets[kMaxThreads];
+ size_t t_offsets[kMaxThreads];
+ size_t f_offsets[kMaxThreads];
+ size_t face_offsets[kMaxThreads];
+
+ v_offsets[0] = 0;
+ n_offsets[0] = 0;
+ t_offsets[0] = 0;
+ f_offsets[0] = 0;
+ face_offsets[0] = 0;
+
+ for (size_t t = 1; t < num_threads; t++) {
+ v_offsets[t] = v_offsets[t - 1] + command_count[t - 1].num_v;
+ n_offsets[t] = n_offsets[t - 1] + command_count[t - 1].num_vn;
+ t_offsets[t] = t_offsets[t - 1] + command_count[t - 1].num_vt;
+ f_offsets[t] = f_offsets[t - 1] + command_count[t - 1].num_f;
+ face_offsets[t] = face_offsets[t - 1] + command_count[t - 1].num_indices;
+ }
+
+ StackVector<std::thread, 16> workers;
+
+ for (size_t t = 0; t < num_threads; t++) {
+ int material_id = -1; // -1 = default unknown material.
+ workers->push_back(std::thread([&, t]() {
+ size_t v_count = v_offsets[t];
+ size_t n_count = n_offsets[t];
+ size_t t_count = t_offsets[t];
+ size_t f_count = f_offsets[t];
+ size_t face_count = face_offsets[t];
+
+ for (size_t i = 0; i < commands[t].size(); i++) {
+ if (commands[t][i].type == COMMAND_EMPTY) {
+ continue;
+ } else if (commands[t][i].type == COMMAND_USEMTL) {
+ if (commands[t][i].material_name &&
+ commands[t][i].material_name_len > 0) {
+ std::string material_name(commands[t][i].material_name,
+ commands[t][i].material_name_len);
+
+ if (material_map.find(material_name) != material_map.end()) {
+ material_id = material_map[material_name];
+ } else {
+ // Assign invalid material ID
+ material_id = -1;
+ }
+ }
+ } else if (commands[t][i].type == COMMAND_V) {
+ attrib->vertices[3 * v_count + 0] = commands[t][i].vx;
+ attrib->vertices[3 * v_count + 1] = commands[t][i].vy;
+ attrib->vertices[3 * v_count + 2] = commands[t][i].vz;
+ v_count++;
+ } else if (commands[t][i].type == COMMAND_VN) {
+ attrib->normals[3 * n_count + 0] = commands[t][i].nx;
+ attrib->normals[3 * n_count + 1] = commands[t][i].ny;
+ attrib->normals[3 * n_count + 2] = commands[t][i].nz;
+ n_count++;
+ } else if (commands[t][i].type == COMMAND_VT) {
+ attrib->texcoords[2 * t_count + 0] = commands[t][i].tx;
+ attrib->texcoords[2 * t_count + 1] = commands[t][i].ty;
+ t_count++;
+ } else if (commands[t][i].type == COMMAND_F) {
+ for (size_t k = 0; k < commands[t][i].f.size(); k++) {
+ index_t &vi = commands[t][i].f[k];
+ int vertex_index = fixIndex(vi.vertex_index, v_count);
+ int texcoord_index = fixIndex(vi.texcoord_index, t_count);
+ int normal_index = fixIndex(vi.normal_index, n_count);
+ attrib->indices[f_count + k] =
+ index_t(vertex_index, texcoord_index, normal_index);
+ }
+ for (size_t k = 0; k < commands[t][i].f_num_verts.size(); k++) {
+ attrib->material_ids[face_count + k] = material_id;
+ attrib->face_num_verts[face_count + k] =
+ commands[t][i].f_num_verts[k];
+ }
+
+ f_count += commands[t][i].f.size();
+ face_count += commands[t][i].f_num_verts.size();
+ }
+ }
+ }));
+ }
+
+ for (size_t t = 0; t < workers->size(); t++) {
+ workers[t].join();
+ }
+
+ auto t_end = std::chrono::high_resolution_clock::now();
+ ms_merge = t_end - t_start;
+ }
+
+ auto t4 = std::chrono::high_resolution_clock::now();
+
+ // 5. Construct shape information.
+ {
+ auto t_start = std::chrono::high_resolution_clock::now();
+
+ // @todo { Can we boost the performance by multi-threaded execution? }
+ int face_count = 0;
+ shape_t shape;
+ shape.face_offset = 0;
+ shape.length = 0;
+ int face_prev_offset = 0;
+ for (size_t t = 0; t < num_threads; t++) {
+ for (size_t i = 0; i < commands[t].size(); i++) {
+ if (commands[t][i].type == COMMAND_O ||
+ commands[t][i].type == COMMAND_G) {
+ std::string name;
+ if (commands[t][i].type == COMMAND_O) {
+ name = std::string(commands[t][i].object_name,
+ commands[t][i].object_name_len);
+ } else {
+ name = std::string(commands[t][i].group_name,
+ commands[t][i].group_name_len);
+ }
+
+ if (face_count == 0) {
+ // 'o' or 'g' appears before any 'f'
+ shape.name = name;
+ shape.face_offset = face_count;
+ face_prev_offset = face_count;
+ } else {
+ if (shapes->size() == 0) {
+ // 'o' or 'g' after some 'v' lines.
+ // create a shape with null name
+ shape.length = face_count - face_prev_offset;
+ face_prev_offset = face_count;
+
+ shapes->push_back(shape);
+
+ } else {
+ if ((face_count - face_prev_offset) > 0) {
+ // push previous shape
+ shape.length = face_count - face_prev_offset;
+ shapes->push_back(shape);
+ face_prev_offset = face_count;
+ }
+ }
+
+ // redefine shape.
+ shape.name = name;
+ shape.face_offset = face_count;
+ shape.length = 0;
+ }
+ }
+ if (commands[t][i].type == COMMAND_F) {
+ face_count++;
+ }
+ }
+ }
+
+ if ((face_count - face_prev_offset) > 0) {
+ shape.length = face_count - shape.face_offset;
+ if (shape.length > 0) {
+ shapes->push_back(shape);
+ }
+ } else {
+ // Guess no 'v' line occurrence after 'o' or 'g', so discards current
+ // shape information.
+ }
+
+ auto t_end = std::chrono::high_resolution_clock::now();
+
+ ms_construct = t_end - t_start;
+ }
+
+ std::chrono::duration<double, std::milli> ms_total = t4 - t1;
+ if (option.verbose) {
+ std::cout << "total parsing time: " << ms_total.count() << " ms\n";
+ std::cout << " line detection : " << ms_linedetection.count() << " ms\n";
+ std::cout << " alloc buf : " << ms_alloc.count() << " ms\n";
+ std::cout << " parse : " << ms_parse.count() << " ms\n";
+ std::cout << " merge : " << ms_merge.count() << " ms\n";
+ std::cout << " construct : " << ms_construct.count() << " ms\n";
+ std::cout << " mtl load : " << ms_load_mtl.count() << " ms\n";
+ std::cout << "# of vertices = " << attrib->vertices.size() << std::endl;
+ std::cout << "# of normals = " << attrib->normals.size() << std::endl;
+ std::cout << "# of texcoords = " << attrib->texcoords.size() << std::endl;
+ std::cout << "# of face indices = " << attrib->indices.size() << std::endl;
+ std::cout << "# of indices = " << attrib->material_ids.size() << std::endl;
+ std::cout << "# of shapes = " << shapes->size() << std::endl;
+ }
+
+ return true;
+}
+
+} // namespace tinyobj_opt
+
+#endif // TINYOBJ_LOADER_OPT_IMPLEMENTATION
diff --git a/experimental/trackball.cc b/experimental/trackball.cc
new file mode 100644
index 0000000..27642e8
--- /dev/null
+++ b/experimental/trackball.cc
@@ -0,0 +1,292 @@
+/*
+ * (c) Copyright 1993, 1994, Silicon Graphics, Inc.
+ * ALL RIGHTS RESERVED
+ * Permission to use, copy, modify, and distribute this software for
+ * any purpose and without fee is hereby granted, provided that the above
+ * copyright notice appear in all copies and that both the copyright notice
+ * and this permission notice appear in supporting documentation, and that
+ * the name of Silicon Graphics, Inc. not be used in advertising
+ * or publicity pertaining to distribution of the software without specific,
+ * written prior permission.
+ *
+ * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
+ * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
+ * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
+ * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
+ * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
+ * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
+ * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
+ * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN
+ * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
+ * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * US Government Users Restricted Rights
+ * Use, duplication, or disclosure by the Government is subject to
+ * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
+ * (c)(1)(ii) of the Rights in Technical Data and Computer Software
+ * clause at DFARS 252.227-7013 and/or in similar or successor
+ * clauses in the FAR or the DOD or NASA FAR Supplement.
+ * Unpublished-- rights reserved under the copyright laws of the
+ * United States. Contractor/manufacturer is Silicon Graphics,
+ * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311.
+ *
+ * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
+ */
+/*
+ * Trackball code:
+ *
+ * Implementation of a virtual trackball.
+ * Implemented by Gavin Bell, lots of ideas from Thant Tessman and
+ * the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129.
+ *
+ * Vector manip code:
+ *
+ * Original code from:
+ * David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli
+ *
+ * Much mucking with by:
+ * Gavin Bell
+ */
+#include <math.h>
+#include "trackball.h"
+
+/*
+ * This size should really be based on the distance from the center of
+ * rotation to the point on the object underneath the mouse. That
+ * point would then track the mouse as closely as possible. This is a
+ * simple example, though, so that is left as an Exercise for the
+ * Programmer.
+ */
+#define TRACKBALLSIZE (0.8)
+
+/*
+ * Local function prototypes (not defined in trackball.h)
+ */
+static float tb_project_to_sphere(float, float, float);
+static void normalize_quat(float[4]);
+
+static void vzero(float *v) {
+ v[0] = 0.0;
+ v[1] = 0.0;
+ v[2] = 0.0;
+}
+
+static void vset(float *v, float x, float y, float z) {
+ v[0] = x;
+ v[1] = y;
+ v[2] = z;
+}
+
+static void vsub(const float *src1, const float *src2, float *dst) {
+ dst[0] = src1[0] - src2[0];
+ dst[1] = src1[1] - src2[1];
+ dst[2] = src1[2] - src2[2];
+}
+
+static void vcopy(const float *v1, float *v2) {
+ int i;
+ for (i = 0; i < 3; i++)
+ v2[i] = v1[i];
+}
+
+static void vcross(const float *v1, const float *v2, float *cross) {
+ float temp[3];
+
+ temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
+ temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
+ temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
+ vcopy(temp, cross);
+}
+
+static float vlength(const float *v) {
+ return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+}
+
+static void vscale(float *v, float div) {
+ v[0] *= div;
+ v[1] *= div;
+ v[2] *= div;
+}
+
+static void vnormal(float *v) { vscale(v, 1.0 / vlength(v)); }
+
+static float vdot(const float *v1, const float *v2) {
+ return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
+}
+
+static void vadd(const float *src1, const float *src2, float *dst) {
+ dst[0] = src1[0] + src2[0];
+ dst[1] = src1[1] + src2[1];
+ dst[2] = src1[2] + src2[2];
+}
+
+/*
+ * Ok, simulate a track-ball. Project the points onto the virtual
+ * trackball, then figure out the axis of rotation, which is the cross
+ * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0)
+ * Note: This is a deformed trackball-- is a trackball in the center,
+ * but is deformed into a hyperbolic sheet of rotation away from the
+ * center. This particular function was chosen after trying out
+ * several variations.
+ *
+ * It is assumed that the arguments to this routine are in the range
+ * (-1.0 ... 1.0)
+ */
+void trackball(float q[4], float p1x, float p1y, float p2x, float p2y) {
+ float a[3]; /* Axis of rotation */
+ float phi; /* how much to rotate about axis */
+ float p1[3], p2[3], d[3];
+ float t;
+
+ if (p1x == p2x && p1y == p2y) {
+ /* Zero rotation */
+ vzero(q);
+ q[3] = 1.0;
+ return;
+ }
+
+ /*
+ * First, figure out z-coordinates for projection of P1 and P2 to
+ * deformed sphere
+ */
+ vset(p1, p1x, p1y, tb_project_to_sphere(TRACKBALLSIZE, p1x, p1y));
+ vset(p2, p2x, p2y, tb_project_to_sphere(TRACKBALLSIZE, p2x, p2y));
+
+ /*
+ * Now, we want the cross product of P1 and P2
+ */
+ vcross(p2, p1, a);
+
+ /*
+ * Figure out how much to rotate around that axis.
+ */
+ vsub(p1, p2, d);
+ t = vlength(d) / (2.0 * TRACKBALLSIZE);
+
+ /*
+ * Avoid problems with out-of-control values...
+ */
+ if (t > 1.0)
+ t = 1.0;
+ if (t < -1.0)
+ t = -1.0;
+ phi = 2.0 * asin(t);
+
+ axis_to_quat(a, phi, q);
+}
+
+/*
+ * Given an axis and angle, compute quaternion.
+ */
+void axis_to_quat(float a[3], float phi, float q[4]) {
+ vnormal(a);
+ vcopy(a, q);
+ vscale(q, sin(phi / 2.0));
+ q[3] = cos(phi / 2.0);
+}
+
+/*
+ * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
+ * if we are away from the center of the sphere.
+ */
+static float tb_project_to_sphere(float r, float x, float y) {
+ float d, t, z;
+
+ d = sqrt(x * x + y * y);
+ if (d < r * 0.70710678118654752440) { /* Inside sphere */
+ z = sqrt(r * r - d * d);
+ } else { /* On hyperbola */
+ t = r / 1.41421356237309504880;
+ z = t * t / d;
+ }
+ return z;
+}
+
+/*
+ * Given two rotations, e1 and e2, expressed as quaternion rotations,
+ * figure out the equivalent single rotation and stuff it into dest.
+ *
+ * This routine also normalizes the result every RENORMCOUNT times it is
+ * called, to keep error from creeping in.
+ *
+ * NOTE: This routine is written so that q1 or q2 may be the same
+ * as dest (or each other).
+ */
+
+#define RENORMCOUNT 97
+
+void add_quats(float q1[4], float q2[4], float dest[4]) {
+ static int count = 0;
+ float t1[4], t2[4], t3[4];
+ float tf[4];
+
+ vcopy(q1, t1);
+ vscale(t1, q2[3]);
+
+ vcopy(q2, t2);
+ vscale(t2, q1[3]);
+
+ vcross(q2, q1, t3);
+ vadd(t1, t2, tf);
+ vadd(t3, tf, tf);
+ tf[3] = q1[3] * q2[3] - vdot(q1, q2);
+
+ dest[0] = tf[0];
+ dest[1] = tf[1];
+ dest[2] = tf[2];
+ dest[3] = tf[3];
+
+ if (++count > RENORMCOUNT) {
+ count = 0;
+ normalize_quat(dest);
+ }
+}
+
+/*
+ * Quaternions always obey: a^2 + b^2 + c^2 + d^2 = 1.0
+ * If they don't add up to 1.0, dividing by their magnitued will
+ * renormalize them.
+ *
+ * Note: See the following for more information on quaternions:
+ *
+ * - Shoemake, K., Animating rotation with quaternion curves, Computer
+ * Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985.
+ * - Pletinckx, D., Quaternion calculus as a basic tool in computer
+ * graphics, The Visual Computer 5, 2-13, 1989.
+ */
+static void normalize_quat(float q[4]) {
+ int i;
+ float mag;
+
+ mag = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
+ for (i = 0; i < 4; i++)
+ q[i] /= mag;
+}
+
+/*
+ * Build a rotation matrix, given a quaternion rotation.
+ *
+ */
+void build_rotmatrix(float m[4][4], const float q[4]) {
+ m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]);
+ m[0][1] = 2.0 * (q[0] * q[1] - q[2] * q[3]);
+ m[0][2] = 2.0 * (q[2] * q[0] + q[1] * q[3]);
+ m[0][3] = 0.0;
+
+ m[1][0] = 2.0 * (q[0] * q[1] + q[2] * q[3]);
+ m[1][1] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]);
+ m[1][2] = 2.0 * (q[1] * q[2] - q[0] * q[3]);
+ m[1][3] = 0.0;
+
+ m[2][0] = 2.0 * (q[2] * q[0] - q[1] * q[3]);
+ m[2][1] = 2.0 * (q[1] * q[2] + q[0] * q[3]);
+ m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]);
+ m[2][3] = 0.0;
+
+ m[3][0] = 0.0;
+ m[3][1] = 0.0;
+ m[3][2] = 0.0;
+ m[3][3] = 1.0;
+}
diff --git a/experimental/trackball.h b/experimental/trackball.h
new file mode 100644
index 0000000..b1f9437
--- /dev/null
+++ b/experimental/trackball.h
@@ -0,0 +1,75 @@
+/*
+ * (c) Copyright 1993, 1994, Silicon Graphics, Inc.
+ * ALL RIGHTS RESERVED
+ * Permission to use, copy, modify, and distribute this software for
+ * any purpose and without fee is hereby granted, provided that the above
+ * copyright notice appear in all copies and that both the copyright notice
+ * and this permission notice appear in supporting documentation, and that
+ * the name of Silicon Graphics, Inc. not be used in advertising
+ * or publicity pertaining to distribution of the software without specific,
+ * written prior permission.
+ *
+ * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
+ * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
+ * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
+ * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
+ * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
+ * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
+ * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
+ * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN
+ * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
+ * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * US Government Users Restricted Rights
+ * Use, duplication, or disclosure by the Government is subject to
+ * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
+ * (c)(1)(ii) of the Rights in Technical Data and Computer Software
+ * clause at DFARS 252.227-7013 and/or in similar or successor
+ * clauses in the FAR or the DOD or NASA FAR Supplement.
+ * Unpublished-- rights reserved under the copyright laws of the
+ * United States. Contractor/manufacturer is Silicon Graphics,
+ * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311.
+ *
+ * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
+ */
+/*
+ * trackball.h
+ * A virtual trackball implementation
+ * Written by Gavin Bell for Silicon Graphics, November 1988.
+ */
+
+/*
+ * Pass the x and y coordinates of the last and current positions of
+ * the mouse, scaled so they are from (-1.0 ... 1.0).
+ *
+ * The resulting rotation is returned as a quaternion rotation in the
+ * first paramater.
+ */
+void trackball(float q[4], float p1x, float p1y, float p2x, float p2y);
+
+void negate_quat(float *q, float *qn);
+
+/*
+ * Given two quaternions, add them together to get a third quaternion.
+ * Adding quaternions to get a compound rotation is analagous to adding
+ * translations to get a compound translation. When incrementally
+ * adding rotations, the first argument here should be the new
+ * rotation, the second and third the total rotation (which will be
+ * over-written with the resulting new total rotation).
+ */
+void add_quats(float *q1, float *q2, float *dest);
+
+/*
+ * A useful function, builds a rotation matrix in Matrix based on
+ * given quaternion.
+ */
+void build_rotmatrix(float m[4][4], const float q[4]);
+
+/*
+ * This function computes a quaternion based on an axis (defined by
+ * the given vector) and an angle about which to rotate. The angle is
+ * expressed in radians. The result is put into the third argument.
+ */
+void axis_to_quat(float a[3], float phi, float q[4]);
diff --git a/experimental/viewer.cc b/experimental/viewer.cc
new file mode 100644
index 0000000..be5053f
--- /dev/null
+++ b/experimental/viewer.cc
@@ -0,0 +1,748 @@
+//
+// Simple .obj viewer(vertex only)
+//
+#include <vector>
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+#include <limits>
+#include <cmath>
+#include <cassert>
+#include <cstring>
+#include <algorithm>
+
+#if defined(ENABLE_ZLIB)
+#include <zlib.h>
+#endif
+
+#if defined(ENABLE_ZSTD)
+#include <zstd.h>
+#endif
+
+#include <GL/glew.h>
+
+#ifdef __APPLE__
+#include <OpenGL/glu.h>
+#else
+#include <GL/glu.h>
+#endif
+
+#include <GLFW/glfw3.h>
+
+#include "trackball.h"
+
+#define TINYOBJ_LOADER_OPT_IMPLEMENTATION
+#include "tinyobj_loader_opt.h"
+
+typedef struct {
+ GLuint vb; // vertex buffer
+ int numTriangles;
+} DrawObject;
+
+std::vector<DrawObject> gDrawObjects;
+
+int width = 768;
+int height = 768;
+
+double prevMouseX, prevMouseY;
+bool mouseLeftPressed;
+bool mouseMiddlePressed;
+bool mouseRightPressed;
+float curr_quat[4];
+float prev_quat[4];
+float eye[3], lookat[3], up[3];
+
+GLFWwindow* window;
+
+void CheckErrors(std::string desc) {
+ GLenum e = glGetError();
+ if (e != GL_NO_ERROR) {
+ fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e);
+ exit(20);
+ }
+}
+
+void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) {
+ float v10[3];
+ v10[0] = v1[0] - v0[0];
+ v10[1] = v1[1] - v0[1];
+ v10[2] = v1[2] - v0[2];
+
+ float v20[3];
+ v20[0] = v2[0] - v0[0];
+ v20[1] = v2[1] - v0[1];
+ v20[2] = v2[2] - v0[2];
+
+ N[0] = v20[1] * v10[2] - v20[2] * v10[1];
+ N[1] = v20[2] * v10[0] - v20[0] * v10[2];
+ N[2] = v20[0] * v10[1] - v20[1] * v10[0];
+
+ float len2 = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
+ if (len2 > 0.0f) {
+ float len = sqrtf(len2);
+
+ N[0] /= len;
+ N[1] /= len;
+ }
+}
+
+const char *mmap_file(size_t *len, const char* filename)
+{
+ (*len) = 0;
+#ifdef _WIN32
+ HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
+ assert(file != INVALID_HANDLE_VALUE);
+
+ HANDLE fileMapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL);
+ assert(fileMapping != INVALID_HANDLE_VALUE);
+
+ LPVOID fileMapView = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0);
+ auto fileMapViewChar = (const char*)fileMapView;
+ assert(fileMapView != NULL);
+
+ LARGE_INTEGER fileSize;
+ fileSize.QuadPart = 0;
+ GetFileSizeEx(file, &fileSize);
+
+ (*len) = static_cast<size_t>(fileSize.QuadPart);
+ return fileMapViewChar;
+
+#else
+
+ FILE* f = fopen(filename, "rb" );
+ if (!f) {
+ fprintf(stderr, "Failed to open file : %s\n", filename);
+ return nullptr;
+ }
+ fseek(f, 0, SEEK_END);
+ long fileSize = ftell(f);
+ fclose(f);
+
+ if (fileSize < 16) {
+ fprintf(stderr, "Empty or invalid .obj : %s\n", filename);
+ return nullptr;
+ }
+
+ struct stat sb;
+ char *p;
+ int fd;
+
+ fd = open (filename, O_RDONLY);
+ if (fd == -1) {
+ perror ("open");
+ return nullptr;
+ }
+
+ if (fstat (fd, &sb) == -1) {
+ perror ("fstat");
+ return nullptr;
+ }
+
+ if (!S_ISREG (sb.st_mode)) {
+ fprintf (stderr, "%s is not a file\n", "lineitem.tbl");
+ return nullptr;
+ }
+
+ p = (char*)mmap (0, fileSize, PROT_READ, MAP_SHARED, fd, 0);
+
+ if (p == MAP_FAILED) {
+ perror ("mmap");
+ return nullptr;
+ }
+
+ if (close (fd) == -1) {
+ perror ("close");
+ return nullptr;
+ }
+
+ (*len) = fileSize;
+
+ return p;
+
+#endif
+}
+
+bool gz_load(std::vector<char>* buf, const char* filename)
+{
+#ifdef ENABLE_ZLIB
+ gzFile file;
+ file = gzopen (filename, "r");
+ if (! file) {
+ fprintf (stderr, "gzopen of '%s' failed: %s.\n", filename,
+ strerror (errno));
+ exit (EXIT_FAILURE);
+ return false;
+ }
+ while (1) {
+ int err;
+ int bytes_read;
+ unsigned char buffer[1024];
+ bytes_read = gzread (file, buffer, 1024);
+ buf->insert(buf->end(), buffer, buffer + 1024);
+ //printf ("%s", buffer);
+ if (bytes_read < 1024) {
+ if (gzeof (file)) {
+ break;
+ }
+ else {
+ const char * error_string;
+ error_string = gzerror (file, & err);
+ if (err) {
+ fprintf (stderr, "Error: %s.\n", error_string);
+ exit (EXIT_FAILURE);
+ return false;
+ }
+ }
+ }
+ }
+ gzclose (file);
+ return true;
+#else
+ return false;
+#endif
+}
+
+#ifdef ENABLE_ZSTD
+static off_t fsize_X(const char *filename)
+{
+ struct stat st;
+ if (stat(filename, &st) == 0) return st.st_size;
+ /* error */
+ printf("stat: %s : %s \n", filename, strerror(errno));
+ exit(1);
+}
+
+static FILE* fopen_X(const char *filename, const char *instruction)
+{
+ FILE* const inFile = fopen(filename, instruction);
+ if (inFile) return inFile;
+ /* error */
+ printf("fopen: %s : %s \n", filename, strerror(errno));
+ exit(2);
+}
+
+static void* malloc_X(size_t size)
+{
+ void* const buff = malloc(size);
+ if (buff) return buff;
+ /* error */
+ printf("malloc: %s \n", strerror(errno));
+ exit(3);
+}
+#endif
+
+bool zstd_load(std::vector<char>* buf, const char* filename)
+{
+#ifdef ENABLE_ZSTD
+ off_t const buffSize = fsize_X(filename);
+ FILE* const inFile = fopen_X(filename, "rb");
+ void* const buffer = malloc_X(buffSize);
+ size_t const readSize = fread(buffer, 1, buffSize, inFile);
+ if (readSize != (size_t)buffSize) {
+ printf("fread: %s : %s \n", filename, strerror(errno));
+ exit(4);
+ }
+ fclose(inFile);
+
+ unsigned long long const rSize = ZSTD_getDecompressedSize(buffer, buffSize);
+ if (rSize==0) {
+ printf("%s : original size unknown \n", filename);
+ exit(5);
+ }
+
+ buf->resize(rSize);
+
+ size_t const dSize = ZSTD_decompress(buf->data(), rSize, buffer, buffSize);
+
+ if (dSize != rSize) {
+ printf("error decoding %s : %s \n", filename, ZSTD_getErrorName(dSize));
+ exit(7);
+ }
+
+ free(buffer);
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+const char* get_file_data(size_t *len, const char* filename)
+{
+
+ const char *ext = strrchr(filename, '.');
+
+ size_t data_len = 0;
+ const char* data = nullptr;
+
+ if (strcmp(ext, ".gz") == 0) {
+ // gzipped data.
+
+ std::vector<char> buf;
+ bool ret = gz_load(&buf, filename);
+
+ if (ret) {
+ char *p = static_cast<char*>(malloc(buf.size() + 1)); // @fixme { implement deleter }
+ memcpy(p, &buf.at(0), buf.size());
+ p[buf.size()] = '\0';
+ data = p;
+ data_len = buf.size();
+ }
+
+ } else if (strcmp(ext, ".zst") == 0) {
+ printf("zstd\n");
+ // Zstandard data.
+
+ std::vector<char> buf;
+ bool ret = zstd_load(&buf, filename);
+
+ if (ret) {
+ char *p = static_cast<char*>(malloc(buf.size() + 1)); // @fixme { implement deleter }
+ memcpy(p, &buf.at(0), buf.size());
+ p[buf.size()] = '\0';
+ data = p;
+ data_len = buf.size();
+ }
+ } else {
+
+ data = mmap_file(&data_len, filename);
+
+ }
+
+ (*len) = data_len;
+ return data;
+}
+
+
+bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int num_threads, bool verbose)
+{
+ tinyobj_opt::attrib_t attrib;
+ std::vector<tinyobj_opt::shape_t> shapes;
+ std::vector<tinyobj_opt::material_t> materials;
+
+ auto load_t_begin = std::chrono::high_resolution_clock::now();
+ size_t data_len = 0;
+ const char* data = get_file_data(&data_len, filename);
+ if (data == nullptr) {
+ printf("failed to load file\n");
+ exit(-1);
+ return false;
+ }
+ auto load_t_end = std::chrono::high_resolution_clock::now();
+ std::chrono::duration<double, std::milli> load_ms = load_t_end - load_t_begin;
+ if (verbose) {
+ std::cout << "filesize: " << data_len << std::endl;
+ std::cout << "load time: " << load_ms.count() << " [msecs]" << std::endl;
+ }
+
+
+ tinyobj_opt::LoadOption option;
+ option.req_num_threads = num_threads;
+ option.verbose = verbose;
+ bool ret = parseObj(&attrib, &shapes, &materials, data, data_len, option);
+
+ if (!ret) {
+ std::cerr << "Failed to parse .obj" << std::endl;
+ return false;
+ }
+ bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max();
+ bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
+
+ //std::cout << "vertices.size() = " << attrib.vertices.size() << std::endl;
+ //std::cout << "normals.size() = " << attrib.normals.size() << std::endl;
+
+ {
+ DrawObject o;
+ std::vector<float> vb; // pos(3float), normal(3float), color(3float)
+ size_t face_offset = 0;
+ for (size_t v = 0; v < attrib.face_num_verts.size(); v++) {
+ assert(attrib.face_num_verts[v] % 3 == 0); // assume all triangle face.
+ for (size_t f = 0; f < attrib.face_num_verts[v] / 3; f++) {
+ tinyobj_opt::index_t idx0 = attrib.indices[face_offset+3*f+0];
+ tinyobj_opt::index_t idx1 = attrib.indices[face_offset+3*f+1];
+ tinyobj_opt::index_t idx2 = attrib.indices[face_offset+3*f+2];
+
+ float v[3][3];
+ for (int k = 0; k < 3; k++) {
+ int f0 = idx0.vertex_index;
+ int f1 = idx1.vertex_index;
+ int f2 = idx2.vertex_index;
+ assert(f0 >= 0);
+ assert(f1 >= 0);
+ assert(f2 >= 0);
+
+ v[0][k] = attrib.vertices[3*f0+k];
+ v[1][k] = attrib.vertices[3*f1+k];
+ v[2][k] = attrib.vertices[3*f2+k];
+ bmin[k] = std::min(v[0][k], bmin[k]);
+ bmin[k] = std::min(v[1][k], bmin[k]);
+ bmin[k] = std::min(v[2][k], bmin[k]);
+ bmax[k] = std::max(v[0][k], bmax[k]);
+ bmax[k] = std::max(v[1][k], bmax[k]);
+ bmax[k] = std::max(v[2][k], bmax[k]);
+ }
+
+ float n[3][3];
+
+ if (attrib.normals.size() > 0) {
+ int nf0 = idx0.normal_index;
+ int nf1 = idx1.normal_index;
+ int nf2 = idx2.normal_index;
+
+ if (nf0 >= 0 && nf1 >= 0 && nf2 >= 0) {
+ assert(3*nf0+2 < attrib.normals.size());
+ assert(3*nf1+2 < attrib.normals.size());
+ assert(3*nf2+2 < attrib.normals.size());
+ for (int k = 0; k < 3; k++) {
+ n[0][k] = attrib.normals[3*nf0+k];
+ n[1][k] = attrib.normals[3*nf1+k];
+ n[2][k] = attrib.normals[3*nf2+k];
+ }
+ } else {
+ // compute geometric normal
+ CalcNormal(n[0], v[0], v[1], v[2]);
+ n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2];
+ n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2];
+ }
+ } else {
+ // compute geometric normal
+ CalcNormal(n[0], v[0], v[1], v[2]);
+ n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2];
+ n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2];
+ }
+
+ for (int k = 0; k < 3; k++) {
+ vb.push_back(v[k][0]);
+ vb.push_back(v[k][1]);
+ vb.push_back(v[k][2]);
+ vb.push_back(n[k][0]);
+ vb.push_back(n[k][1]);
+ vb.push_back(n[k][2]);
+ // Use normal as color.
+ float c[3] = {n[k][0], n[k][1], n[k][2]};
+ float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2];
+ if (len2 > 1.0e-6f) {
+ float len = sqrtf(len2);
+
+ c[0] /= len;
+ c[1] /= len;
+ c[2] /= len;
+ }
+ vb.push_back(c[0] * 0.5 + 0.5);
+ vb.push_back(c[1] * 0.5 + 0.5);
+ vb.push_back(c[2] * 0.5 + 0.5);
+ }
+ }
+ face_offset += attrib.face_num_verts[v];
+ }
+
+ o.vb = 0;
+ o.numTriangles = 0;
+ if (vb.size() > 0) {
+ glGenBuffers(1, &o.vb);
+ glBindBuffer(GL_ARRAY_BUFFER, o.vb);
+ glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), GL_STATIC_DRAW);
+ o.numTriangles = vb.size() / 9 / 3;
+ }
+
+ gDrawObjects.push_back(o);
+ }
+
+ printf("bmin = %f, %f, %f\n", bmin[0], bmin[1], bmin[2]);
+ printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]);
+
+ return true;
+}
+
+void reshapeFunc(GLFWwindow* window, int w, int h)
+{
+ (void)window;
+ // for retinal display.
+ int fb_w, fb_h;
+ glfwGetFramebufferSize(window, &fb_w, &fb_h);
+
+ glViewport(0, 0, fb_w, fb_h);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ gluPerspective(45.0, (float)w / (float)h, 0.01f, 100.0f);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ width = w;
+ height = h;
+}
+
+void keyboardFunc(GLFWwindow *window, int key, int scancode, int action, int mods) {
+ (void)window;
+ (void)scancode;
+ (void)mods;
+ if(action == GLFW_PRESS || action == GLFW_REPEAT){
+ // Move camera
+ float mv_x = 0, mv_y = 0, mv_z = 0;
+ if(key == GLFW_KEY_K) mv_x += 1;
+ else if(key == GLFW_KEY_J) mv_x += -1;
+ else if(key == GLFW_KEY_L) mv_y += 1;
+ else if(key == GLFW_KEY_H) mv_y += -1;
+ else if(key == GLFW_KEY_P) mv_z += 1;
+ else if(key == GLFW_KEY_N) mv_z += -1;
+ //camera.move(mv_x * 0.05, mv_y * 0.05, mv_z * 0.05);
+ // Close window
+ if(key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) glfwSetWindowShouldClose(window, GL_TRUE);
+
+ //init_frame = true;
+ }
+}
+
+void clickFunc(GLFWwindow* window, int button, int action, int mods){
+ (void)window;
+ (void)mods;
+ if(button == GLFW_MOUSE_BUTTON_LEFT){
+ if(action == GLFW_PRESS){
+ mouseLeftPressed = true;
+ trackball(prev_quat, 0.0, 0.0, 0.0, 0.0);
+ } else if(action == GLFW_RELEASE){
+ mouseLeftPressed = false;
+ }
+ }
+ if(button == GLFW_MOUSE_BUTTON_RIGHT){
+ if(action == GLFW_PRESS){
+ mouseRightPressed = true;
+ } else if(action == GLFW_RELEASE){
+ mouseRightPressed = false;
+ }
+ }
+ if(button == GLFW_MOUSE_BUTTON_MIDDLE){
+ if(action == GLFW_PRESS){
+ mouseMiddlePressed = true;
+ } else if(action == GLFW_RELEASE){
+ mouseMiddlePressed = false;
+ }
+ }
+}
+
+void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y){
+ (void)window;
+ float rotScale = 1.0f;
+ float transScale = 2.0f;
+
+ if(mouseLeftPressed){
+ trackball(prev_quat,
+ rotScale * (2.0f * prevMouseX - width) / (float)width,
+ rotScale * (height - 2.0f * prevMouseY) / (float)height,
+ rotScale * (2.0f * mouse_x - width) / (float)width,
+ rotScale * (height - 2.0f * mouse_y) / (float)height);
+
+ add_quats(prev_quat, curr_quat, curr_quat);
+ } else if (mouseMiddlePressed) {
+ eye[0] -= transScale * (mouse_x - prevMouseX) / (float)width;
+ lookat[0] -= transScale * (mouse_x - prevMouseX) / (float)width;
+ eye[1] += transScale * (mouse_y - prevMouseY) / (float)height;
+ lookat[1] += transScale * (mouse_y - prevMouseY) / (float)height;
+ } else if (mouseRightPressed) {
+ eye[2] += transScale * (mouse_y - prevMouseY) / (float)height;
+ lookat[2] += transScale * (mouse_y - prevMouseY) / (float)height;
+ }
+
+ // Update mouse point
+ prevMouseX = mouse_x;
+ prevMouseY = mouse_y;
+}
+
+void Draw(const std::vector<DrawObject>& drawObjects)
+{
+ glPolygonMode(GL_FRONT, GL_FILL);
+ glPolygonMode(GL_BACK, GL_FILL);
+
+ glEnable(GL_POLYGON_OFFSET_FILL);
+ glPolygonOffset(1.0, 1.0);
+ glColor3f(1.0f, 1.0f, 1.0f);
+ for (size_t i = 0; i < drawObjects.size(); i++) {
+ DrawObject o = drawObjects[i];
+ if (o.vb < 1) {
+ continue;
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, o.vb);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_NORMAL_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+ glVertexPointer(3, GL_FLOAT, 36, (const void*)0);
+ glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3));
+ glColorPointer(3, GL_FLOAT, 36, (const void*)(sizeof(float)*6));
+
+ glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles);
+ CheckErrors("drawarrays");
+ }
+
+ // draw wireframe
+ glDisable(GL_POLYGON_OFFSET_FILL);
+ glPolygonMode(GL_FRONT, GL_LINE);
+ glPolygonMode(GL_BACK, GL_LINE);
+
+ glColor3f(0.0f, 0.0f, 0.4f);
+ for (size_t i = 0; i < drawObjects.size(); i++) {
+ DrawObject o = drawObjects[i];
+ if (o.vb < 1) {
+ continue;
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, o.vb);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_NORMAL_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+ glVertexPointer(3, GL_FLOAT, 36, (const void*)0);
+ glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3));
+
+ glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles);
+ CheckErrors("drawarrays");
+ }
+}
+
+static void Init() {
+ trackball(curr_quat, 0, 0, 0, 0);
+
+ eye[0] = 0.0f;
+ eye[1] = 0.0f;
+ eye[2] = 3.0f;
+
+ lookat[0] = 0.0f;
+ lookat[1] = 0.0f;
+ lookat[2] = 0.0f;
+
+ up[0] = 0.0f;
+ up[1] = 1.0f;
+ up[2] = 0.0f;
+}
+
+
+int main(int argc, char **argv)
+{
+ if (argc < 2) {
+ std::cout << "view input.obj <num_threads> <benchark_only> <verbose>" << std::endl;
+ return 0;
+ }
+
+ bool benchmark_only = false;
+ int num_threads = -1;
+ bool verbose = false;
+
+ if (argc > 2) {
+ num_threads = atoi(argv[2]);
+ }
+
+ if (argc > 3) {
+ benchmark_only = (atoi(argv[3]) > 0) ? true : false;
+ }
+
+ if (argc > 4) {
+ verbose = true;
+ }
+
+ if (benchmark_only) {
+
+ tinyobj_opt::attrib_t attrib;
+ std::vector<tinyobj_opt::shape_t> shapes;
+ std::vector<tinyobj_opt::material_t> materials;
+
+ size_t data_len = 0;
+ const char* data = get_file_data(&data_len, argv[1]);
+ if (data == nullptr) {
+ printf("failed to load file\n");
+ exit(-1);
+ return false;
+ }
+
+ if (data_len < 4) {
+ printf("Empty file\n");
+ exit(-1);
+ return false;
+ }
+ printf("filesize: %d\n", (int)data_len);
+ tinyobj_opt::LoadOption option;
+ option.req_num_threads = num_threads;
+ option.verbose = true;
+
+ bool ret = parseObj(&attrib, &shapes, &materials, data, data_len, option);
+
+ return ret;
+ }
+
+ Init();
+
+ std::cout << "Initialize GLFW..." << std::endl;
+
+ if(!glfwInit()){
+ std::cerr << "Failed to initialize GLFW." << std::endl;
+ return -1;
+ }
+
+ std::cout << "GLFW OK." << std::endl;
+
+
+ window = glfwCreateWindow(width, height, "Obj viewer", NULL, NULL);
+ if(window == NULL){
+ std::cerr << "Failed to open GLFW window. " << std::endl;
+ glfwTerminate();
+ return 1;
+ }
+
+ glfwMakeContextCurrent(window);
+ glfwSwapInterval(1);
+
+ // Callback
+ glfwSetWindowSizeCallback(window, reshapeFunc);
+ glfwSetKeyCallback(window, keyboardFunc);
+ glfwSetMouseButtonCallback(window, clickFunc);
+ glfwSetCursorPosCallback(window, motionFunc);
+
+ glewExperimental = true;
+ if (glewInit() != GLEW_OK) {
+ std::cerr << "Failed to initialize GLEW." << std::endl;
+ return -1;
+ }
+
+ reshapeFunc(window, width, height);
+
+ float bmin[3], bmax[3];
+ if (false == LoadObjAndConvert(bmin, bmax, argv[1], num_threads, verbose)) {
+ printf("failed to load & conv\n");
+ return -1;
+ }
+
+ float maxExtent = 0.5f * (bmax[0] - bmin[0]);
+ if (maxExtent < 0.5f * (bmax[1] - bmin[1])) {
+ maxExtent = 0.5f * (bmax[1] - bmin[1]);
+ }
+ if (maxExtent < 0.5f * (bmax[2] - bmin[2])) {
+ maxExtent = 0.5f * (bmax[2] - bmin[2]);
+ }
+
+ while(glfwWindowShouldClose(window) == GL_FALSE) {
+ glfwPollEvents();
+ glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glEnable(GL_DEPTH_TEST);
+
+ // camera & rotate
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ GLfloat mat[4][4];
+ gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], up[1], up[2]);
+ build_rotmatrix(mat, curr_quat);
+ glMultMatrixf(&mat[0][0]);
+
+ // Fit to -1, 1
+ glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent);
+
+ // Centerize object.
+ glTranslatef(-0.5*(bmax[0] + bmin[0]), -0.5*(bmax[1] + bmin[1]), -0.5*(bmax[2] + bmin[2]));
+
+ Draw(gDrawObjects);
+
+ glfwSwapBuffers(window);
+ }
+
+ glfwTerminate();
+}
diff --git a/images/rungholt.jpg b/images/rungholt.jpg
new file mode 100644
index 0000000..17718eb
--- /dev/null
+++ b/images/rungholt.jpg
Binary files differ
diff --git a/images/sanmugel.png b/images/sanmugel.png
new file mode 100644
index 0000000..32ea150
--- /dev/null
+++ b/images/sanmugel.png
Binary files differ
diff --git a/jni/Android.mk b/jni/Android.mk
new file mode 100644
index 0000000..bc81f8f
--- /dev/null
+++ b/jni/Android.mk
@@ -0,0 +1,12 @@
+# A simple test for the minimal standard C++ library
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := tinyobjloader
+LOCAL_SRC_FILES := ../tiny_obj_loader.cc
+
+LOCAL_C_INCLUDES := ../
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/jni/Application.mk b/jni/Application.mk
new file mode 100644
index 0000000..e5d3191
--- /dev/null
+++ b/jni/Application.mk
@@ -0,0 +1,2 @@
+APP_ABI := all
+APP_STL := stlport_static
diff --git a/jni/Makefile b/jni/Makefile
new file mode 100644
index 0000000..1f13853
--- /dev/null
+++ b/jni/Makefile
@@ -0,0 +1,2 @@
+all:
+ ndk-build
diff --git a/jni/README b/jni/README
new file mode 100644
index 0000000..f93bc08
--- /dev/null
+++ b/jni/README
@@ -0,0 +1 @@
+Just tests compilation with Android NDK r10.
diff --git a/loader_example.cc b/loader_example.cc
new file mode 100644
index 0000000..203fbf8
--- /dev/null
+++ b/loader_example.cc
@@ -0,0 +1,419 @@
+//
+// g++ loader_example.cc
+//
+#define TINYOBJLOADER_IMPLEMENTATION
+#include "tiny_obj_loader.h"
+
+#include <cassert>
+#include <cstdio>
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#ifdef _WIN32
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <windows.h>
+#include <mmsystem.h>
+#ifdef __cplusplus
+}
+#endif
+#pragma comment(lib, "winmm.lib")
+#else
+#if defined(__unix__) || defined(__APPLE__)
+#include <sys/time.h>
+#else
+#include <ctime>
+#endif
+#endif
+
+class timerutil {
+ public:
+#ifdef _WIN32
+ typedef DWORD time_t;
+
+ timerutil() { ::timeBeginPeriod(1); }
+ ~timerutil() { ::timeEndPeriod(1); }
+
+ void start() { t_[0] = ::timeGetTime(); }
+ void end() { t_[1] = ::timeGetTime(); }
+
+ time_t sec() { return (time_t)((t_[1] - t_[0]) / 1000); }
+ time_t msec() { return (time_t)((t_[1] - t_[0])); }
+ time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000); }
+ time_t current() { return ::timeGetTime(); }
+
+#else
+#if defined(__unix__) || defined(__APPLE__)
+ typedef unsigned long int time_t;
+
+ void start() { gettimeofday(tv + 0, &tz); }
+ void end() { gettimeofday(tv + 1, &tz); }
+
+ time_t sec() { return static_cast<time_t>(tv[1].tv_sec - tv[0].tv_sec); }
+ time_t msec() {
+ return this->sec() * 1000 +
+ static_cast<time_t>((tv[1].tv_usec - tv[0].tv_usec) / 1000);
+ }
+ time_t usec() {
+ return this->sec() * 1000000 +
+ static_cast<time_t>(tv[1].tv_usec - tv[0].tv_usec);
+ }
+ time_t current() {
+ struct timeval t;
+ gettimeofday(&t, NULL);
+ return static_cast<time_t>(t.tv_sec * 1000 + t.tv_usec);
+ }
+
+#else // C timer
+ // using namespace std;
+ typedef clock_t time_t;
+
+ void start() { t_[0] = clock(); }
+ void end() { t_[1] = clock(); }
+
+ time_t sec() { return (time_t)((t_[1] - t_[0]) / CLOCKS_PER_SEC); }
+ time_t msec() { return (time_t)((t_[1] - t_[0]) * 1000 / CLOCKS_PER_SEC); }
+ time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000000 / CLOCKS_PER_SEC); }
+ time_t current() { return (time_t)clock(); }
+
+#endif
+#endif
+
+ private:
+#ifdef _WIN32
+ DWORD t_[2];
+#else
+#if defined(__unix__) || defined(__APPLE__)
+ struct timeval tv[2];
+ struct timezone tz;
+#else
+ time_t t_[2];
+#endif
+#endif
+};
+
+static void PrintInfo(const tinyobj::attrib_t& attrib,
+ const std::vector<tinyobj::shape_t>& shapes,
+ const std::vector<tinyobj::material_t>& materials) {
+ std::cout << "# of vertices : " << (attrib.vertices.size() / 3) << std::endl;
+ std::cout << "# of normals : " << (attrib.normals.size() / 3) << std::endl;
+ std::cout << "# of texcoords : " << (attrib.texcoords.size() / 2)
+ << std::endl;
+
+ std::cout << "# of shapes : " << shapes.size() << std::endl;
+ std::cout << "# of materials : " << materials.size() << std::endl;
+
+ for (size_t v = 0; v < attrib.vertices.size() / 3; v++) {
+ printf(" v[%ld] = (%f, %f, %f)\n", static_cast<long>(v),
+ static_cast<const double>(attrib.vertices[3 * v + 0]),
+ static_cast<const double>(attrib.vertices[3 * v + 1]),
+ static_cast<const double>(attrib.vertices[3 * v + 2]));
+ }
+
+ for (size_t v = 0; v < attrib.normals.size() / 3; v++) {
+ printf(" n[%ld] = (%f, %f, %f)\n", static_cast<long>(v),
+ static_cast<const double>(attrib.normals[3 * v + 0]),
+ static_cast<const double>(attrib.normals[3 * v + 1]),
+ static_cast<const double>(attrib.normals[3 * v + 2]));
+ }
+
+ for (size_t v = 0; v < attrib.texcoords.size() / 2; v++) {
+ printf(" uv[%ld] = (%f, %f)\n", static_cast<long>(v),
+ static_cast<const double>(attrib.texcoords[2 * v + 0]),
+ static_cast<const double>(attrib.texcoords[2 * v + 1]));
+ }
+
+ // For each shape
+ for (size_t i = 0; i < shapes.size(); i++) {
+ printf("shape[%ld].name = %s\n", static_cast<long>(i),
+ shapes[i].name.c_str());
+ printf("Size of shape[%ld].indices: %lu\n", static_cast<long>(i),
+ static_cast<unsigned long>(shapes[i].mesh.indices.size()));
+
+ size_t index_offset = 0;
+
+ assert(shapes[i].mesh.num_face_vertices.size() ==
+ shapes[i].mesh.material_ids.size());
+
+ printf("shape[%ld].num_faces: %lu\n", static_cast<long>(i),
+ static_cast<unsigned long>(shapes[i].mesh.num_face_vertices.size()));
+
+ // For each face
+ for (size_t f = 0; f < shapes[i].mesh.num_face_vertices.size(); f++) {
+ size_t fnum = shapes[i].mesh.num_face_vertices[f];
+
+ printf(" face[%ld].fnum = %ld\n", static_cast<long>(f),
+ static_cast<unsigned long>(fnum));
+
+ // For each vertex in the face
+ for (size_t v = 0; v < fnum; v++) {
+ tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v];
+ printf(" face[%ld].v[%ld].idx = %d/%d/%d\n", static_cast<long>(f),
+ static_cast<long>(v), idx.vertex_index, idx.normal_index,
+ idx.texcoord_index);
+ }
+
+ printf(" face[%ld].material_id = %d\n", static_cast<long>(f),
+ shapes[i].mesh.material_ids[f]);
+
+ index_offset += fnum;
+ }
+
+ printf("shape[%ld].num_tags: %lu\n", static_cast<long>(i),
+ static_cast<unsigned long>(shapes[i].mesh.tags.size()));
+ for (size_t t = 0; t < shapes[i].mesh.tags.size(); t++) {
+ printf(" tag[%ld] = %s ", static_cast<long>(t),
+ shapes[i].mesh.tags[t].name.c_str());
+ printf(" ints: [");
+ for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) {
+ printf("%ld", static_cast<long>(shapes[i].mesh.tags[t].intValues[j]));
+ if (j < (shapes[i].mesh.tags[t].intValues.size() - 1)) {
+ printf(", ");
+ }
+ }
+ printf("]");
+
+ printf(" floats: [");
+ for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) {
+ printf("%f", static_cast<const double>(
+ shapes[i].mesh.tags[t].floatValues[j]));
+ if (j < (shapes[i].mesh.tags[t].floatValues.size() - 1)) {
+ printf(", ");
+ }
+ }
+ printf("]");
+
+ printf(" strings: [");
+ for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) {
+ printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str());
+ if (j < (shapes[i].mesh.tags[t].stringValues.size() - 1)) {
+ printf(", ");
+ }
+ }
+ printf("]");
+ printf("\n");
+ }
+ }
+
+ for (size_t i = 0; i < materials.size(); i++) {
+ printf("material[%ld].name = %s\n", static_cast<long>(i),
+ materials[i].name.c_str());
+ printf(" material.Ka = (%f, %f ,%f)\n",
+ static_cast<const double>(materials[i].ambient[0]),
+ static_cast<const double>(materials[i].ambient[1]),
+ static_cast<const double>(materials[i].ambient[2]));
+ printf(" material.Kd = (%f, %f ,%f)\n",
+ static_cast<const double>(materials[i].diffuse[0]),
+ static_cast<const double>(materials[i].diffuse[1]),
+ static_cast<const double>(materials[i].diffuse[2]));
+ printf(" material.Ks = (%f, %f ,%f)\n",
+ static_cast<const double>(materials[i].specular[0]),
+ static_cast<const double>(materials[i].specular[1]),
+ static_cast<const double>(materials[i].specular[2]));
+ printf(" material.Tr = (%f, %f ,%f)\n",
+ static_cast<const double>(materials[i].transmittance[0]),
+ static_cast<const double>(materials[i].transmittance[1]),
+ static_cast<const double>(materials[i].transmittance[2]));
+ printf(" material.Ke = (%f, %f ,%f)\n",
+ static_cast<const double>(materials[i].emission[0]),
+ static_cast<const double>(materials[i].emission[1]),
+ static_cast<const double>(materials[i].emission[2]));
+ printf(" material.Ns = %f\n",
+ static_cast<const double>(materials[i].shininess));
+ printf(" material.Ni = %f\n", static_cast<const double>(materials[i].ior));
+ printf(" material.dissolve = %f\n",
+ static_cast<const double>(materials[i].dissolve));
+ printf(" material.illum = %d\n", materials[i].illum);
+ printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str());
+ printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str());
+ printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str());
+ printf(" material.map_Ns = %s\n",
+ materials[i].specular_highlight_texname.c_str());
+ printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str());
+ printf(" bump_multiplier = %f\n", static_cast<const double>(materials[i].bump_texopt.bump_multiplier));
+ printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str());
+ printf(" material.disp = %s\n", materials[i].displacement_texname.c_str());
+ printf(" <<PBR>>\n");
+ printf(" material.Pr = %f\n", static_cast<const double>(materials[i].roughness));
+ printf(" material.Pm = %f\n", static_cast<const double>(materials[i].metallic));
+ printf(" material.Ps = %f\n", static_cast<const double>(materials[i].sheen));
+ printf(" material.Pc = %f\n", static_cast<const double>(materials[i].clearcoat_thickness));
+ printf(" material.Pcr = %f\n", static_cast<const double>(materials[i].clearcoat_thickness));
+ printf(" material.aniso = %f\n", static_cast<const double>(materials[i].anisotropy));
+ printf(" material.anisor = %f\n", static_cast<const double>(materials[i].anisotropy_rotation));
+ printf(" material.map_Ke = %s\n", materials[i].emissive_texname.c_str());
+ printf(" material.map_Pr = %s\n", materials[i].roughness_texname.c_str());
+ printf(" material.map_Pm = %s\n", materials[i].metallic_texname.c_str());
+ printf(" material.map_Ps = %s\n", materials[i].sheen_texname.c_str());
+ printf(" material.norm = %s\n", materials[i].normal_texname.c_str());
+ std::map<std::string, std::string>::const_iterator it(
+ materials[i].unknown_parameter.begin());
+ std::map<std::string, std::string>::const_iterator itEnd(
+ materials[i].unknown_parameter.end());
+
+ for (; it != itEnd; it++) {
+ printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str());
+ }
+ printf("\n");
+ }
+}
+
+static bool TestLoadObj(const char* filename, const char* basepath = NULL,
+ bool triangulate = true) {
+ std::cout << "Loading " << filename << std::endl;
+
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ timerutil t;
+ t.start();
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename,
+ basepath, triangulate);
+ t.end();
+ printf("Parsing time: %lu [msecs]\n", t.msec());
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ if (!ret) {
+ printf("Failed to load/parse .obj.\n");
+ return false;
+ }
+
+ PrintInfo(attrib, shapes, materials);
+
+ return true;
+}
+
+static bool TestStreamLoadObj() {
+ std::cout << "Stream Loading " << std::endl;
+
+ std::stringstream objStream;
+ objStream << "mtllib cube.mtl\n"
+ "\n"
+ "v 0.000000 2.000000 2.000000\n"
+ "v 0.000000 0.000000 2.000000\n"
+ "v 2.000000 0.000000 2.000000\n"
+ "v 2.000000 2.000000 2.000000\n"
+ "v 0.000000 2.000000 0.000000\n"
+ "v 0.000000 0.000000 0.000000\n"
+ "v 2.000000 0.000000 0.000000\n"
+ "v 2.000000 2.000000 0.000000\n"
+ "# 8 vertices\n"
+ "\n"
+ "g front cube\n"
+ "usemtl white\n"
+ "f 1 2 3 4\n"
+ "g back cube\n"
+ "# expects white material\n"
+ "f 8 7 6 5\n"
+ "g right cube\n"
+ "usemtl red\n"
+ "f 4 3 7 8\n"
+ "g top cube\n"
+ "usemtl white\n"
+ "f 5 1 4 8\n"
+ "g left cube\n"
+ "usemtl green\n"
+ "f 5 6 2 1\n"
+ "g bottom cube\n"
+ "usemtl white\n"
+ "f 2 6 7 3\n"
+ "# 6 elements";
+
+ std::string matStream(
+ "newmtl white\n"
+ "Ka 0 0 0\n"
+ "Kd 1 1 1\n"
+ "Ks 0 0 0\n"
+ "\n"
+ "newmtl red\n"
+ "Ka 0 0 0\n"
+ "Kd 1 0 0\n"
+ "Ks 0 0 0\n"
+ "\n"
+ "newmtl green\n"
+ "Ka 0 0 0\n"
+ "Kd 0 1 0\n"
+ "Ks 0 0 0\n"
+ "\n"
+ "newmtl blue\n"
+ "Ka 0 0 0\n"
+ "Kd 0 0 1\n"
+ "Ks 0 0 0\n"
+ "\n"
+ "newmtl light\n"
+ "Ka 20 20 20\n"
+ "Kd 1 1 1\n"
+ "Ks 0 0 0");
+
+ using namespace tinyobj;
+ class MaterialStringStreamReader : public MaterialReader {
+ public:
+ MaterialStringStreamReader(const std::string& matSStream)
+ : m_matSStream(matSStream) {}
+ virtual ~MaterialStringStreamReader() {}
+ virtual bool operator()(const std::string& matId,
+ std::vector<material_t>* materials,
+ std::map<std::string, int>* matMap,
+ std::string* err) {
+ (void)matId;
+ std::string warning;
+ LoadMtl(matMap, materials, &m_matSStream, &warning);
+
+ if (!warning.empty()) {
+ if (err) {
+ (*err) += warning;
+ }
+ }
+ return true;
+ }
+
+ private:
+ std::stringstream m_matSStream;
+ };
+
+ MaterialStringStreamReader matSSReader(matStream);
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream,
+ &matSSReader);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ if (!ret) {
+ return false;
+ }
+
+ PrintInfo(attrib, shapes, materials);
+
+ return true;
+}
+
+int main(int argc, char** argv) {
+ if (argc > 1) {
+ const char* basepath = "models/";
+ if (argc > 2) {
+ basepath = argv[2];
+ }
+ assert(true == TestLoadObj(argv[1], basepath));
+ } else {
+ // assert(true == TestLoadObj("cornell_box.obj"));
+ // assert(true == TestLoadObj("cube.obj"));
+ assert(true == TestStreamLoadObj());
+ assert(true ==
+ TestLoadObj("models/catmark_torus_creases0.obj", "models/", false));
+ }
+
+ return 0;
+}
diff --git a/models/catmark_torus_creases0.obj b/models/catmark_torus_creases0.obj
new file mode 100644
index 0000000..bf18f15
--- /dev/null
+++ b/models/catmark_torus_creases0.obj
@@ -0,0 +1,101 @@
+#
+# Copyright 2013 Pixar
+#
+# Licensed under the Apache License, Version 2.0 (the "Apache License")
+# with the following modification; you may not use this file except in
+# compliance with the Apache License and the following modification to it:
+# Section 6. Trademarks. is deleted and replaced with:
+#
+# 6. Trademarks. This License does not grant permission to use the trade
+# names, trademarks, service marks, or product names of the Licensor
+# and its affiliates, except as required to comply with Section 4(c) of
+# the License and to reproduce the content of the NOTICE file.
+#
+# You may obtain a copy of the Apache License at
+#
+# http:#www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the Apache License with the above modification is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the Apache License for the specific
+# language governing permissions and limitations under the Apache License.
+#
+# This file uses centimeters as units for non-parametric coordinates.
+
+v 1.25052 0.517982 0.353553
+v 0.597239 0.247384 0.353553
+v 0.597239 0.247384 -0.353553
+v 1.25052 0.517982 -0.353553
+v 0.517982 1.25052 0.353553
+v 0.247384 0.597239 0.353553
+v 0.247384 0.597239 -0.353553
+v 0.517982 1.25052 -0.353553
+v -0.517982 1.25052 0.353553
+v -0.247384 0.597239 0.353553
+v -0.247384 0.597239 -0.353553
+v -0.517982 1.25052 -0.353553
+v -1.25052 0.517982 0.353553
+v -0.597239 0.247384 0.353553
+v -0.597239 0.247384 -0.353553
+v -1.25052 0.517982 -0.353553
+v -1.25052 -0.517982 0.353553
+v -0.597239 -0.247384 0.353553
+v -0.597239 -0.247384 -0.353553
+v -1.25052 -0.517982 -0.353553
+v -0.517982 -1.25052 0.353553
+v -0.247384 -0.597239 0.353553
+v -0.247384 -0.597239 -0.353553
+v -0.517982 -1.25052 -0.353553
+v 0.517982 -1.25052 0.353553
+v 0.247384 -0.597239 0.353553
+v 0.247384 -0.597239 -0.353553
+v 0.517982 -1.25052 -0.353553
+v 1.25052 -0.517982 0.353553
+v 0.597239 -0.247384 0.353553
+v 0.597239 -0.247384 -0.353553
+v 1.25052 -0.517982 -0.353553
+vt 0 0
+vt 1 0
+vt 1 1
+vt 0 1
+f 5/1/1 6/2/2 2/3/3 1/4/4
+f 6/1/5 7/2/6 3/3/7 2/4/8
+f 7/1/9 8/2/10 4/3/11 3/4/12
+f 8/1/13 5/2/14 1/3/15 4/4/16
+f 9/1/17 10/2/18 6/3/19 5/4/20
+f 10/1/21 11/2/22 7/3/23 6/4/24
+f 11/1/25 12/2/26 8/3/27 7/4/28
+f 12/1/29 9/2/30 5/3/31 8/4/32
+f 13/1/33 14/2/34 10/3/35 9/4/36
+f 14/1/37 15/2/38 11/3/39 10/4/40
+f 15/1/41 16/2/42 12/3/43 11/4/44
+f 16/1/45 13/2/46 9/3/47 12/4/48
+f 17/1/49 18/2/50 14/3/51 13/4/52
+f 18/1/53 19/2/54 15/3/55 14/4/56
+f 19/1/57 20/2/58 16/3/59 15/4/60
+f 20/1/61 17/2/62 13/3/63 16/4/64
+f 21/1/65 22/2/66 18/3/67 17/4/68
+f 22/1/69 23/2/70 19/3/71 18/4/72
+f 23/1/73 24/2/74 20/3/75 19/4/76
+f 24/1/77 21/2/78 17/3/79 20/4/80
+f 25/1/81 26/2/82 22/3/83 21/4/84
+f 26/1/85 27/2/86 23/3/87 22/4/88
+f 27/1/89 28/2/90 24/3/91 23/4/92
+f 28/1/93 25/2/94 21/3/95 24/4/96
+f 29/1/97 30/2/98 26/3/99 25/4/100
+f 30/1/101 31/2/102 27/3/103 26/4/104
+f 31/1/105 32/2/106 28/3/107 27/4/108
+f 32/1/109 29/2/110 25/3/111 28/4/112
+f 1/1/113 2/2/114 30/3/115 29/4/116
+f 2/1/117 3/2/118 31/3/119 30/4/120
+f 3/1/121 4/2/122 32/3/123 31/4/124
+f 4/1/125 1/2/126 29/3/127 32/4/128
+t crease 2/1/0 1 5 4.7
+t crease 2/1/0 5 9 4.7
+t crease 2/1/0 9 13 4.7
+t crease 2/1/0 13 17 4.7
+t crease 2/1/0 17 21 4.7
+t crease 2/1/0 21 25 4.7
+t crease 2/1/0 25 29 4.7
+t crease 2/1/0 29 1 4.7
diff --git a/models/cornell_box.mtl b/models/cornell_box.mtl
new file mode 100644
index 0000000..d3a1c7a
--- /dev/null
+++ b/models/cornell_box.mtl
@@ -0,0 +1,24 @@
+newmtl white
+Ka 0 0 0
+Kd 1 1 1
+Ks 0 0 0
+
+newmtl red
+Ka 0 0 0
+Kd 1 0 0
+Ks 0 0 0
+
+newmtl green
+Ka 0 0 0
+Kd 0 1 0
+Ks 0 0 0
+
+newmtl blue
+Ka 0 0 0
+Kd 0 0 1
+Ks 0 0 0
+
+newmtl light
+Ka 20 20 20
+Kd 1 1 1
+Ks 0 0 0
diff --git a/models/cornell_box.obj b/models/cornell_box.obj
new file mode 100644
index 0000000..43e021f
--- /dev/null
+++ b/models/cornell_box.obj
@@ -0,0 +1,145 @@
+# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project.
+# original cornell box data
+ # comment
+
+# empty line including some space
+
+
+mtllib cornell_box.mtl
+
+o floor
+usemtl white
+v 552.8 0.0 0.0
+v 0.0 0.0 0.0
+v 0.0 0.0 559.2
+v 549.6 0.0 559.2
+
+v 130.0 0.0 65.0
+v 82.0 0.0 225.0
+v 240.0 0.0 272.0
+v 290.0 0.0 114.0
+
+v 423.0 0.0 247.0
+v 265.0 0.0 296.0
+v 314.0 0.0 456.0
+v 472.0 0.0 406.0
+
+f 1 2 3 4
+f 8 7 6 5
+f 12 11 10 9
+
+o light
+usemtl light
+v 343.0 548.0 227.0
+v 343.0 548.0 332.0
+v 213.0 548.0 332.0
+v 213.0 548.0 227.0
+f -4 -3 -2 -1
+
+o ceiling
+usemtl white
+v 556.0 548.8 0.0
+v 556.0 548.8 559.2
+v 0.0 548.8 559.2
+v 0.0 548.8 0.0
+f -4 -3 -2 -1
+
+o back_wall
+usemtl white
+v 549.6 0.0 559.2
+v 0.0 0.0 559.2
+v 0.0 548.8 559.2
+v 556.0 548.8 559.2
+f -4 -3 -2 -1
+
+o front_wall
+usemtl blue
+v 549.6 0.0 0
+v 0.0 0.0 0
+v 0.0 548.8 0
+v 556.0 548.8 0
+#f -1 -2 -3 -4
+
+o green_wall
+usemtl green
+v 0.0 0.0 559.2
+v 0.0 0.0 0.0
+v 0.0 548.8 0.0
+v 0.0 548.8 559.2
+f -4 -3 -2 -1
+
+o red_wall
+usemtl red
+v 552.8 0.0 0.0
+v 549.6 0.0 559.2
+v 556.0 548.8 559.2
+v 556.0 548.8 0.0
+f -4 -3 -2 -1
+
+o short_block
+usemtl white
+
+v 130.0 165.0 65.0
+v 82.0 165.0 225.0
+v 240.0 165.0 272.0
+v 290.0 165.0 114.0
+f -4 -3 -2 -1
+
+v 290.0 0.0 114.0
+v 290.0 165.0 114.0
+v 240.0 165.0 272.0
+v 240.0 0.0 272.0
+f -4 -3 -2 -1
+
+v 130.0 0.0 65.0
+v 130.0 165.0 65.0
+v 290.0 165.0 114.0
+v 290.0 0.0 114.0
+f -4 -3 -2 -1
+
+v 82.0 0.0 225.0
+v 82.0 165.0 225.0
+v 130.0 165.0 65.0
+v 130.0 0.0 65.0
+f -4 -3 -2 -1
+
+v 240.0 0.0 272.0
+v 240.0 165.0 272.0
+v 82.0 165.0 225.0
+v 82.0 0.0 225.0
+f -4 -3 -2 -1
+
+o tall_block
+usemtl white
+
+v 423.0 330.0 247.0
+v 265.0 330.0 296.0
+v 314.0 330.0 456.0
+v 472.0 330.0 406.0
+f -4 -3 -2 -1
+
+usemtl white
+v 423.0 0.0 247.0
+v 423.0 330.0 247.0
+v 472.0 330.0 406.0
+v 472.0 0.0 406.0
+f -4 -3 -2 -1
+
+v 472.0 0.0 406.0
+v 472.0 330.0 406.0
+v 314.0 330.0 456.0
+v 314.0 0.0 456.0
+f -4 -3 -2 -1
+
+v 314.0 0.0 456.0
+v 314.0 330.0 456.0
+v 265.0 330.0 296.0
+v 265.0 0.0 296.0
+f -4 -3 -2 -1
+
+v 265.0 0.0 296.0
+v 265.0 330.0 296.0
+v 423.0 330.0 247.0
+v 423.0 0.0 247.0
+f -4 -3 -2 -1
+
diff --git a/models/cornell_box_multimaterial.obj b/models/cornell_box_multimaterial.obj
new file mode 100644
index 0000000..68093be
--- /dev/null
+++ b/models/cornell_box_multimaterial.obj
@@ -0,0 +1,146 @@
+# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project.
+# original cornell box data
+ # comment
+
+# empty line including some space
+
+
+mtllib cornell_box.mtl
+
+o floor
+usemtl white
+v 552.8 0.0 0.0
+v 0.0 0.0 0.0
+v 0.0 0.0 559.2
+v 549.6 0.0 559.2
+
+v 130.0 0.0 65.0
+v 82.0 0.0 225.0
+v 240.0 0.0 272.0
+v 290.0 0.0 114.0
+
+v 423.0 0.0 247.0
+v 265.0 0.0 296.0
+v 314.0 0.0 456.0
+v 472.0 0.0 406.0
+
+f 1 2 3 4
+f 8 7 6 5
+f 12 11 10 9
+
+o light
+usemtl light
+v 343.0 548.0 227.0
+v 343.0 548.0 332.0
+v 213.0 548.0 332.0
+v 213.0 548.0 227.0
+f -4 -3 -2 -1
+
+o ceiling
+usemtl white
+v 556.0 548.8 0.0
+v 556.0 548.8 559.2
+v 0.0 548.8 559.2
+v 0.0 548.8 0.0
+f -4 -3 -2 -1
+
+o back_wall
+usemtl white
+v 549.6 0.0 559.2
+v 0.0 0.0 559.2
+v 0.0 548.8 559.2
+v 556.0 548.8 559.2
+f -4 -3 -2 -1
+
+o front_wall
+usemtl blue
+v 549.6 0.0 0
+v 0.0 0.0 0
+v 0.0 548.8 0
+v 556.0 548.8 0
+#f -1 -2 -3 -4
+
+o green_wall
+usemtl green
+v 0.0 0.0 559.2
+v 0.0 0.0 0.0
+v 0.0 548.8 0.0
+v 0.0 548.8 559.2
+f -4 -3 -2 -1
+
+o red_wall
+usemtl red
+v 552.8 0.0 0.0
+v 549.6 0.0 559.2
+v 556.0 548.8 559.2
+v 556.0 548.8 0.0
+f -4 -3 -2 -1
+
+o short_block
+usemtl white
+
+v 130.0 165.0 65.0
+v 82.0 165.0 225.0
+v 240.0 165.0 272.0
+v 290.0 165.0 114.0
+f -4 -3 -2 -1
+
+v 290.0 0.0 114.0
+v 290.0 165.0 114.0
+v 240.0 165.0 272.0
+v 240.0 0.0 272.0
+f -4 -3 -2 -1
+
+v 130.0 0.0 65.0
+v 130.0 165.0 65.0
+v 290.0 165.0 114.0
+v 290.0 0.0 114.0
+f -4 -3 -2 -1
+
+v 82.0 0.0 225.0
+v 82.0 165.0 225.0
+v 130.0 165.0 65.0
+v 130.0 0.0 65.0
+f -4 -3 -2 -1
+
+v 240.0 0.0 272.0
+v 240.0 165.0 272.0
+v 82.0 165.0 225.0
+v 82.0 0.0 225.0
+f -4 -3 -2 -1
+
+o tall_block
+usemtl white
+
+v 423.0 330.0 247.0
+v 265.0 330.0 296.0
+v 314.0 330.0 456.0
+v 472.0 330.0 406.0
+f -4 -3 -2 -1
+
+usemtl white
+v 423.0 0.0 247.0
+v 423.0 330.0 247.0
+v 472.0 330.0 406.0
+v 472.0 0.0 406.0
+f -4 -3 -2 -1
+
+v 472.0 0.0 406.0
+v 472.0 330.0 406.0
+v 314.0 330.0 456.0
+v 314.0 0.0 456.0
+f -4 -3 -2 -1
+usemtl green
+
+v 314.0 0.0 456.0
+v 314.0 330.0 456.0
+v 265.0 330.0 296.0
+v 265.0 0.0 296.0
+f -4 -3 -2 -1
+
+v 265.0 0.0 296.0
+v 265.0 330.0 296.0
+v 423.0 330.0 247.0
+v 423.0 0.0 247.0
+f -4 -3 -2 -1
+
diff --git a/models/cube-vertexcol.obj b/models/cube-vertexcol.obj
new file mode 100644
index 0000000..494ce21
--- /dev/null
+++ b/models/cube-vertexcol.obj
@@ -0,0 +1,31 @@
+mtllib cube.mtl
+
+v 0.000000 2.000000 2.000000 0 0 0
+v 0.000000 0.000000 2.000000 0 0 1
+v 2.000000 0.000000 2.000000 0 1 0
+v 2.000000 2.000000 2.000000 0 1 1
+v 0.000000 2.000000 0.000000 1 0 0
+v 0.000000 0.000000 0.000000 1 0 1
+v 2.000000 0.000000 0.000000 1 1 0
+v 2.000000 2.000000 0.000000 1 1 1
+# 8 vertices
+
+g front cube
+usemtl white
+f 1 2 3 4
+g back cube
+# expects white material
+f 8 7 6 5
+g right cube
+usemtl red
+f 4 3 7 8
+g top cube
+usemtl white
+f 5 1 4 8
+g left cube
+usemtl green
+f 5 6 2 1
+g bottom cube
+usemtl white
+f 2 6 7 3
+# 6 elements
diff --git a/models/cube.mtl b/models/cube.mtl
new file mode 100644
index 0000000..d3a1c7a
--- /dev/null
+++ b/models/cube.mtl
@@ -0,0 +1,24 @@
+newmtl white
+Ka 0 0 0
+Kd 1 1 1
+Ks 0 0 0
+
+newmtl red
+Ka 0 0 0
+Kd 1 0 0
+Ks 0 0 0
+
+newmtl green
+Ka 0 0 0
+Kd 0 1 0
+Ks 0 0 0
+
+newmtl blue
+Ka 0 0 0
+Kd 0 0 1
+Ks 0 0 0
+
+newmtl light
+Ka 20 20 20
+Kd 1 1 1
+Ks 0 0 0
diff --git a/models/cube.obj b/models/cube.obj
new file mode 100644
index 0000000..9213e17
--- /dev/null
+++ b/models/cube.obj
@@ -0,0 +1,31 @@
+mtllib cube.mtl
+
+v 0.000000 2.000000 2.000000
+v 0.000000 0.000000 2.000000
+v 2.000000 0.000000 2.000000
+v 2.000000 2.000000 2.000000
+v 0.000000 2.000000 0.000000
+v 0.000000 0.000000 0.000000
+v 2.000000 0.000000 0.000000
+v 2.000000 2.000000 0.000000
+# 8 vertices
+
+g front cube
+usemtl white
+f 1 2 3 4
+g back cube
+# expects white material
+f 8 7 6 5
+g right cube
+usemtl red
+f 4 3 7 8
+g top cube
+usemtl white
+f 5 1 4 8
+g left cube
+usemtl green
+f 5 6 2 1
+g bottom cube
+usemtl white
+f 2 6 7 3
+# 6 elements
diff --git a/models/issue-138.mtl b/models/issue-138.mtl
new file mode 100644
index 0000000..8894d7e
--- /dev/null
+++ b/models/issue-138.mtl
@@ -0,0 +1,23 @@
+newmtl test1
+ Ns 10.0000
+ Ni 1.5000
+ d 1.0000
+ Tr 0.0000
+ Tf 1.0000 1.0000 1.0000
+ illum 2
+ Ka 0.0000 0.0000 0.0000
+ Kd 0.5 0.2 0.2
+ Ks 0.0000 0.0000 0.0000
+ Ke 0.0000 0.0000 0.0000
+
+ newmtl test2
+ Ns 10.0000
+ Ni 1.5000
+ d 1.0000
+ Tr 0.0000
+ Tf 1.0000 1.0000 1.0000
+ illum 2
+ Ka 0.0000 0.0000 0.0000
+ Kd 0.2 0.5 0.2
+ Ks 0.0000 0.0000 0.0000
+ Ke 0.0000 0.0000 0.0000
diff --git a/models/issue-138.obj b/models/issue-138.obj
new file mode 100644
index 0000000..2465920
--- /dev/null
+++ b/models/issue-138.obj
@@ -0,0 +1,51 @@
+
+# cube.obj
+#
+
+mtllib issue-138.mtl
+
+v -0.500000 -0.500000 0.500000
+v 0.500000 -0.500000 0.500000
+v -0.500000 0.500000 0.500000
+v 0.500000 0.500000 0.500000
+v -0.500000 0.500000 -0.500000
+v 0.500000 0.500000 -0.500000
+v -0.500000 -0.500000 -0.500000
+v 0.500000 -0.500000 -0.500000
+
+vt 0.000000 0.000000
+vt 1.000000 0.000000
+vt 0.000000 1.000000
+vt 1.000000 1.000000
+
+vn 0.000000 0.000000 1.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 -1.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+
+usemtl test1
+g test1
+s 1
+f 1/1/1 2/2/1 3/3/1
+f 3/3/1 2/2/1 4/4/1
+
+usemtl test2
+g test2
+
+s 2
+f 3/1/2 4/2/2 5/3/2
+f 5/3/2 4/2/2 6/4/2
+s 3
+f 5/4/3 6/3/3 7/2/3
+f 7/2/3 6/3/3 8/1/3
+s 4
+f 7/1/4 8/2/4 1/3/4
+f 1/3/4 8/2/4 2/4/4
+s 5
+f 2/1/5 8/2/5 4/3/5
+f 4/3/5 8/2/5 6/4/5
+s 6
+f 7/1/6 1/2/6 5/3/6
+f 5/3/6 1/2/6 3/4/6
diff --git a/models/issue-140-zero-face-idx.mtl b/models/issue-140-zero-face-idx.mtl
new file mode 100644
index 0000000..990a345
--- /dev/null
+++ b/models/issue-140-zero-face-idx.mtl
@@ -0,0 +1,2 @@
+newmtl main
+Kd 1 1 1
diff --git a/models/issue-140-zero-face-idx.obj b/models/issue-140-zero-face-idx.obj
new file mode 100644
index 0000000..21a6060
--- /dev/null
+++ b/models/issue-140-zero-face-idx.obj
@@ -0,0 +1,17 @@
+mtllib issue-140-zero-face-idx.mtl
+
+v -0.5 -0.5 0
+v 0.5 -0.5 0
+v 0.5 0.5 0
+v -0.5 0.5 0
+
+vt 0 0 0
+vt 1 0 0
+vt 1 1 0
+vt 0 1 0
+
+vn 0 0 -1
+
+usemtl main
+f 0/0/0 1/1/0 3/3/0
+f 1/1/0 3/3/0 2/2/0
diff --git a/models/issue-92.mtl b/models/issue-92.mtl
new file mode 100644
index 0000000..5ebd668
--- /dev/null
+++ b/models/issue-92.mtl
@@ -0,0 +1,6 @@
+newmtl default
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+map_Kd tmp.png
+
diff --git a/models/issue-92.obj b/models/issue-92.obj
new file mode 100644
index 0000000..f7be3b6
--- /dev/null
+++ b/models/issue-92.obj
@@ -0,0 +1,7 @@
+mtllib issue-92.mtl
+o Test
+v 1.864151 -1.219172 -5.532511
+v 0.575869 -0.666304 5.896140
+v 0.940448 1.000000 -1.971128
+usemtl default
+f 1 2 3
diff --git a/models/issue-95-2.mtl b/models/issue-95-2.mtl
new file mode 100644
index 0000000..68d484c
--- /dev/null
+++ b/models/issue-95-2.mtl
@@ -0,0 +1,5 @@
+newmtl default
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+Tf 0.1 0.2 0.3
diff --git a/models/issue-95-2.obj b/models/issue-95-2.obj
new file mode 100644
index 0000000..456f854
--- /dev/null
+++ b/models/issue-95-2.obj
@@ -0,0 +1,7 @@
+mtllib issue-95-2.mtl
+o Test
+v 1.864151 -1.219172 -5.532511
+v 0.575869 -0.666304 5.896140
+v 0.940448 1.000000 -1.971128
+usemtl default
+f 1 2 3
diff --git a/models/issue-95.mtl b/models/issue-95.mtl
new file mode 100644
index 0000000..1d29fee
--- /dev/null
+++ b/models/issue-95.mtl
@@ -0,0 +1,5 @@
+newmtl default
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+Kt 0.1 0.2 0.3
diff --git a/models/issue-95.obj b/models/issue-95.obj
new file mode 100644
index 0000000..8ee267e
--- /dev/null
+++ b/models/issue-95.obj
@@ -0,0 +1,7 @@
+mtllib issue-95.mtl
+o Test
+v 1.864151 -1.219172 -5.532511
+v 0.575869 -0.666304 5.896140
+v 0.940448 1.000000 -1.971128
+usemtl default
+f 1 2 3
diff --git a/models/map-bump.mtl b/models/map-bump.mtl
new file mode 100644
index 0000000..6fb1291
--- /dev/null
+++ b/models/map-bump.mtl
@@ -0,0 +1,10 @@
+newmtl Material.001
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+map_Bump bump.jpg
+
+newmtl Material.003
+Ka 0 0 0
+Kd 1 1 1
+Ks 0 0 0
diff --git a/models/map-bump.obj b/models/map-bump.obj
new file mode 100644
index 0000000..03071f1
--- /dev/null
+++ b/models/map-bump.obj
@@ -0,0 +1,817 @@
+# https://github.com/syoyo/tinyobjloader/issues/68
+# Blender v2.73 (sub 0) OBJ File: 'enemy.blend'
+# www.blender.org
+mtllib map-bump.mtl
+o Cube
+v 1.864151 -1.219172 -5.532511
+v 0.575869 -0.666304 5.896140
+v 0.940448 1.000000 -1.971128
+v 1.620345 1.000000 -5.815706
+v 1.864152 1.000000 -6.334323
+v 0.575869 -0.129842 5.896143
+v 5.440438 -1.462153 -5.818601
+v 4.896782 -1.462153 -2.744413
+v 1.000825 -0.677484 1.899605
+v 5.440438 -1.246362 -5.818600
+v 1.000825 0.852342 1.899608
+v 4.896782 -1.246362 -2.744412
+v 1.160660 -0.450871 -2.356325
+v 1.704316 -0.450871 -5.430513
+v 1.000825 -0.351920 -1.293797
+v 1.000825 1.000000 -1.293794
+v 1.160660 -0.877888 -2.356326
+v 1.704316 -0.877888 -5.430514
+v 1.000825 -1.219172 -1.452514
+v 1.000825 1.000000 -1.452511
+v 1.000825 -0.351920 1.759410
+v 1.000825 1.000000 1.759413
+v 9.097919 1.221145 -6.212147
+v 8.356775 1.221145 -2.021231
+v 1.864151 -0.109586 -6.334325
+v 0.575869 -0.398073 5.896141
+v 9.097919 0.943958 -6.212148
+v 8.356775 0.943958 -2.021233
+v 1.061916 0.113661 -1.797961
+v 1.000825 0.161258 1.899606
+v 1.000825 0.324040 -1.293795
+v 1.803060 0.113661 -5.988876
+v 1.000825 -0.109586 -1.452513
+v 1.061916 0.776753 -1.797960
+v 1.803061 0.776753 -5.988875
+v 1.000825 0.324040 1.759412
+v 0.000825 -1.219172 -5.532512
+v 0.000825 -0.666304 5.896139
+v 0.000826 1.000000 -6.334325
+v 0.000825 -0.129842 5.896140
+v 0.000825 0.852342 1.899606
+v 0.000825 -0.677484 1.899604
+v 0.000825 -0.351920 -1.293797
+v 0.000825 1.000000 -1.293796
+v 0.000825 1.000000 -1.452513
+v 0.000825 -1.219172 -1.452515
+v 0.000825 -0.351920 1.759409
+v 0.000825 1.000000 1.759411
+v 0.000826 -0.109586 -6.334326
+v 0.000825 -0.398073 5.896140
+v 0.152918 1.000000 -5.815708
+v 0.152917 1.000000 -1.971130
+v 0.940448 1.168419 -1.971128
+v 1.620345 1.168419 -5.815706
+v 0.152918 1.168419 -5.815708
+v 0.152917 1.168419 -1.971130
+v 0.921118 1.091883 -1.050430
+v 0.921118 1.091883 1.516050
+v 0.080533 1.091883 -1.050432
+v 0.080533 1.091883 1.516048
+v 0.613003 -0.553430 5.546911
+v 0.963691 -0.559956 2.248834
+v 0.613003 -0.396857 5.546912
+v 0.963691 -0.070362 2.248835
+v 1.499370 -0.994317 3.966028
+v 1.850058 -0.997914 0.667950
+v 1.499370 -0.908021 3.966029
+v 1.850058 -0.728071 0.667951
+v 1.601022 0.760960 -6.334324
+v 1.601021 0.129454 -6.334325
+v 0.263955 0.760960 -6.334325
+v 0.263955 0.129454 -6.334325
+v 1.334809 0.760960 -7.515329
+v 1.334809 0.129455 -7.515330
+v 0.530168 0.760960 -7.515330
+v 0.530168 0.129455 -7.515330
+v 1.192720 0.649445 -7.515329
+v 1.192720 0.240971 -7.515330
+v 0.672258 0.649445 -7.515330
+v 0.672258 0.240971 -7.515330
+v 1.192719 0.649444 -6.524630
+v 1.192719 0.240970 -6.524631
+v 0.672257 0.649444 -6.524631
+v 0.672257 0.240970 -6.524631
+v 3.851026 0.431116 -1.883326
+v 3.851026 0.946662 -1.883325
+v 4.592170 0.946662 -6.074241
+v 4.592169 0.431116 -6.074242
+v 4.995714 0.561404 -1.918362
+v 4.995714 1.016394 -1.918360
+v 5.736857 1.016394 -6.109276
+v 5.736857 0.561404 -6.109277
+v 3.975454 0.471731 -2.162156
+v 3.975454 0.919244 -2.162155
+v 4.618796 0.919244 -5.800034
+v 4.618795 0.471730 -5.800035
+v 4.969088 0.584825 -2.192568
+v 4.969088 0.979775 -2.192567
+v 5.612430 0.979775 -5.830446
+v 5.612429 0.584825 -5.830447
+v 0.864214 -0.673890 3.184381
+v 0.864213 0.489129 3.184384
+v 0.864213 -0.018552 3.184383
+v 0.000825 0.489129 3.184382
+v 0.000825 -0.673890 3.184381
+v 0.850955 -0.557858 3.309075
+v 0.850955 -0.175321 3.309076
+v 1.737321 -0.996758 1.728192
+v 1.737321 -0.785920 1.728193
+v -1.864151 -1.219172 -5.532511
+v -0.575869 -0.666304 5.896140
+v -0.940448 1.000000 -1.971128
+v -1.620345 1.000000 -5.815706
+v -1.864152 1.000000 -6.334323
+v -0.575869 -0.129842 5.896143
+v -5.440438 -1.462153 -5.818601
+v -4.896782 -1.462153 -2.744413
+v -1.000825 -0.677484 1.899605
+v -5.440438 -1.246362 -5.818600
+v -1.000825 0.852342 1.899608
+v -4.896782 -1.246362 -2.744412
+v -1.160660 -0.450871 -2.356325
+v -1.704316 -0.450871 -5.430513
+v -1.000825 -0.351920 -1.293797
+v -1.000825 1.000000 -1.293794
+v -1.160660 -0.877888 -2.356326
+v -1.704316 -0.877888 -5.430514
+v -1.000825 -1.219172 -1.452514
+v -1.000825 1.000000 -1.452511
+v -1.000825 -0.351920 1.759410
+v -1.000825 1.000000 1.759413
+v -9.097919 1.221145 -6.212147
+v -8.356775 1.221145 -2.021231
+v -1.864151 -0.109586 -6.334325
+v -0.575869 -0.398073 5.896141
+v -9.097919 0.943958 -6.212148
+v -8.356775 0.943958 -2.021233
+v -1.061916 0.113661 -1.797961
+v -1.000825 0.161258 1.899606
+v -1.000825 0.324040 -1.293795
+v -1.803060 0.113661 -5.988876
+v -1.000825 -0.109586 -1.452513
+v -1.061916 0.776753 -1.797960
+v -1.803061 0.776753 -5.988875
+v -1.000825 0.324040 1.759412
+v -0.000825 -1.219172 -5.532512
+v -0.000825 -0.666304 5.896139
+v -0.000826 1.000000 -6.334325
+v -0.000825 -0.129842 5.896140
+v -0.000825 0.852342 1.899606
+v -0.000825 -0.677484 1.899604
+v -0.000825 -0.351920 -1.293797
+v -0.000825 1.000000 -1.293796
+v -0.000825 1.000000 -1.452513
+v -0.000825 -1.219172 -1.452515
+v -0.000825 -0.351920 1.759409
+v -0.000825 1.000000 1.759411
+v -0.000826 -0.109586 -6.334326
+v -0.000825 -0.398073 5.896140
+v -0.152918 1.000000 -5.815708
+v -0.152917 1.000000 -1.971130
+v -0.940448 1.168419 -1.971128
+v -1.620345 1.168419 -5.815706
+v -0.152918 1.168419 -5.815708
+v -0.152917 1.168419 -1.971130
+v -0.921118 1.091883 -1.050430
+v -0.921118 1.091883 1.516050
+v -0.080533 1.091883 -1.050432
+v -0.080533 1.091883 1.516048
+v -0.613003 -0.553430 5.546911
+v -0.963691 -0.559956 2.248834
+v -0.613003 -0.396857 5.546912
+v -0.963691 -0.070362 2.248835
+v -1.499370 -0.994317 3.966028
+v -1.850058 -0.997914 0.667950
+v -1.499370 -0.908021 3.966029
+v -1.850058 -0.728071 0.667951
+v -1.601022 0.760960 -6.334324
+v -1.601021 0.129454 -6.334325
+v -0.263955 0.760960 -6.334325
+v -0.263955 0.129454 -6.334325
+v -1.334809 0.760960 -7.515329
+v -1.334809 0.129455 -7.515330
+v -0.530168 0.760960 -7.515330
+v -0.530168 0.129455 -7.515330
+v -1.192720 0.649445 -7.515329
+v -1.192720 0.240971 -7.515330
+v -0.672258 0.649445 -7.515330
+v -0.672258 0.240971 -7.515330
+v -1.192719 0.649444 -6.524630
+v -1.192719 0.240970 -6.524631
+v -0.672257 0.649444 -6.524631
+v -0.672257 0.240970 -6.524631
+v -3.851026 0.431116 -1.883326
+v -3.851026 0.946662 -1.883325
+v -4.592170 0.946662 -6.074241
+v -4.592169 0.431116 -6.074242
+v -4.995714 0.561404 -1.918362
+v -4.995714 1.016394 -1.918360
+v -5.736857 1.016394 -6.109276
+v -5.736857 0.561404 -6.109277
+v -3.975454 0.471731 -2.162156
+v -3.975454 0.919244 -2.162155
+v -4.618796 0.919244 -5.800034
+v -4.618795 0.471730 -5.800035
+v -4.969088 0.584825 -2.192568
+v -4.969088 0.979775 -2.192567
+v -5.612430 0.979775 -5.830446
+v -5.612429 0.584825 -5.830447
+v -0.864214 -0.673890 3.184381
+v -0.864213 0.489129 3.184384
+v -0.864213 -0.018552 3.184383
+v -0.000825 0.489129 3.184382
+v -0.000825 -0.673890 3.184381
+v -0.850955 -0.557858 3.309075
+v -0.850955 -0.175321 3.309076
+v -1.737321 -0.996758 1.728192
+v -1.737321 -0.785920 1.728193
+vt 0.135351 -0.558072
+vt 0.003035 -0.363507
+vt 0.092282 -0.976844
+vt -0.081322 0.947351
+vt 0.100058 1.958891
+vt 0.050091 1.852185
+vt -0.092752 1.055565
+vt -0.251711 1.059474
+vt 0.075587 0.041384
+vt -0.086008 0.279003
+vt -0.086212 0.249830
+vt -0.276044 1.968137
+vt -0.246101 1.859467
+vt 0.009828 1.911388
+vt -0.133014 1.114769
+vt 0.413322 1.261595
+vt 0.299103 0.624605
+vt 1.243955 0.407183
+vt 0.515404 1.111487
+vt 1.358173 1.044173
+vt -0.081553 0.914324
+vt 0.080042 0.676706
+vt 0.401185 0.474498
+vt 1.295541 0.331328
+vt 0.365315 1.568841
+vt 0.299111 1.575740
+vt 0.143401 0.707357
+vt 0.629403 1.011947
+vt 0.449192 0.167251
+vt 1.409760 0.968317
+vt 0.986264 1.738667
+vt 1.573373 1.877873
+vt 1.417663 1.009490
+vt 0.237182 -0.196235
+vt 0.721785 1.030226
+vt 0.830554 0.870285
+vt 0.877494 1.898608
+vt 1.351399 1.106930
+vt 0.183935 0.557301
+vt 1.507109 1.975312
+vt 0.241636 0.439088
+vt 0.114297 -0.045011
+vt 0.140593 1.808834
+vt -0.015118 0.940452
+vt 0.156405 -1.071134
+vt 0.164119 -0.998223
+vt 0.040336 -1.068281
+vt 0.104459 -1.162571
+vt -0.165787 1.882802
+vt -0.014821 1.660811
+vt -0.287852 0.283965
+vt -0.293374 0.366508
+vt -0.289630 0.900550
+vt 0.035337 -0.191272
+vt 0.247348 0.172213
+vt 0.253300 1.021193
+vt -0.283166 0.952313
+vt -0.283398 0.919286
+vt 0.039792 0.444050
+vt 0.314806 -0.339851
+vt 0.112962 -0.334889
+vt -0.288056 0.254793
+vt -0.023788 -0.973990
+vt -0.155922 -0.359599
+vt 0.220528 -1.165425
+vt 0.108710 -0.748730
+vt -0.286364 1.918670
+vt -0.291973 1.118678
+vt -0.119962 0.896379
+vt -0.123707 0.362337
+vt 0.162891 -0.598569
+vt 0.467532 -0.853353
+vt 0.201549 -1.053262
+vt 0.161663 -0.198915
+vt 0.267667 -0.752638
+vt 0.278705 -0.371021
+vt 0.526390 -0.542053
+vt 0.483821 -0.479457
+vt 0.488162 -0.883689
+vt 0.500110 -0.105561
+vt 0.564618 -0.200418
+vt -0.110331 2.127229
+vt 0.040636 1.905238
+vt -0.010786 1.578087
+vt 0.104092 1.876168
+vt 0.255058 1.654176
+vt -0.054992 2.087323
+vt 0.203048 1.901245
+vt 0.052081 2.123235
+vt 0.042658 1.943733
+vt -0.056437 1.881175
+vt 0.147710 1.941151
+vt 0.050060 2.084741
+vt 0.146264 1.735002
+vt 0.041212 1.737584
+vt 0.048615 1.878591
+vt 0.663065 1.872485
+vt 0.786311 1.691257
+vt 0.507355 1.004102
+vt 0.630601 0.822874
+vt 0.955144 1.689498
+vt 0.860727 1.828333
+vt 0.725565 1.074543
+vt 0.819981 0.935708
+vt 0.674594 1.805657
+vt 0.539432 1.051867
+vt 0.646413 0.894554
+vt 0.781576 1.648344
+vt 0.240127 -0.712141
+vn 0.994400 0.000000 0.105700
+vn 0.000000 1.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.984700 0.000000 0.174100
+vn 0.211800 0.976600 0.037500
+vn -0.103300 0.000000 -0.994600
+vn 0.103300 -0.000000 0.994600
+vn 0.911400 0.378700 0.161200
+vn -0.157300 -0.987200 -0.027800
+vn 0.113700 -0.993300 0.020100
+vn 0.030600 -0.000000 0.999500
+vn -0.061100 0.998100 -0.010800
+vn -0.030600 0.000000 -0.999500
+vn -0.000000 -0.000000 1.000000
+vn 0.000000 0.000000 -1.000000
+vn -0.755400 0.655300 0.000000
+vn 0.000000 -1.000000 0.000000
+vn -0.000000 -0.180000 0.983700
+vn 0.000000 -0.395500 -0.918500
+vn -0.000000 0.688500 0.725200
+vn 0.000000 -0.585700 -0.810500
+vn -0.000000 0.974900 0.222500
+vn -0.000000 -1.000000 0.002800
+vn -1.000000 0.000000 -0.000000
+vn -0.000000 0.935500 0.353200
+vn 0.755400 0.655300 0.000000
+vn 0.000000 0.935500 -0.353200
+vn 0.673800 0.724900 0.143400
+vn 0.872300 -0.000000 0.489100
+vn -0.872300 0.000000 -0.489100
+vn -0.518300 -0.853500 -0.054200
+vn -0.975500 0.000000 -0.219900
+vn 0.975500 0.000000 -0.219900
+vn -0.913200 0.000000 -0.407500
+vn -0.436900 0.896200 -0.077300
+vn -0.995300 -0.000000 0.096600
+vn -0.297300 -0.953400 -0.052600
+vn 0.473900 -0.876600 0.083800
+vn 0.913200 0.000000 0.407500
+vn 0.342200 0.937700 0.060500
+vn 0.995300 -0.000000 -0.096600
+vn -0.519200 -0.853000 -0.054300
+vn 0.722400 0.676400 0.143800
+vn -0.994400 0.000000 0.105700
+vn -0.984700 0.000000 0.174100
+vn -0.211800 0.976600 0.037500
+vn 0.103300 0.000000 -0.994600
+vn -0.103300 -0.000000 0.994600
+vn -0.911400 0.378700 0.161200
+vn 0.157300 -0.987200 -0.027800
+vn -0.113700 -0.993300 0.020100
+vn -0.030600 -0.000000 0.999500
+vn 0.061100 0.998100 -0.010800
+vn 0.030600 0.000000 -0.999500
+vn -0.691900 0.713200 0.112500
+vn -0.872300 -0.000000 0.489100
+vn 0.872300 0.000000 -0.489100
+vn 0.518300 -0.853500 -0.054200
+vn 0.913200 0.000000 -0.407500
+vn 0.436900 0.896200 -0.077300
+vn 0.995300 0.000000 0.096600
+vn 0.297300 -0.953300 -0.052600
+vn -0.473900 -0.876600 0.083800
+vn -0.913200 -0.000000 0.407500
+vn -0.342200 0.937700 0.060500
+vn -0.995300 -0.000000 -0.096600
+vn 0.519200 -0.853000 -0.054300
+vn -0.714800 0.690100 0.113700
+vn 0.974400 0.089700 0.206200
+vn 0.870400 0.288400 0.399100
+vn 0.691900 0.713200 0.112500
+vn -0.518000 -0.853700 -0.053400
+vn -0.519700 -0.852700 -0.053600
+vn 0.714800 0.690100 0.113700
+vn -0.974400 0.089700 0.206200
+vn -0.870400 0.288400 0.399100
+vn -0.673800 0.724900 0.143400
+vn 0.518000 -0.853700 -0.053400
+vn 0.297300 -0.953400 -0.052600
+vn 0.519700 -0.852700 -0.053600
+vn -0.722400 0.676400 0.143800
+vn -0.000000 0.962300 0.272000
+usemtl Material.001
+s off
+f 103/1/1 102/2/1 6/3/1
+f 20/4/2 5/5/2 4/6/2
+f 20/4/2 3/7/2 52/8/2
+f 36/9/3 22/10/3 11/11/3
+f 39/12/2 51/13/2 4/6/2
+f 4/6/4 54/14/4 53/15/4
+f 14/16/5 13/17/5 12/18/5
+f 18/19/6 14/16/6 10/20/6
+f 20/4/3 16/21/3 31/22/3
+f 17/23/7 8/24/7 12/18/7
+f 25/25/4 32/26/4 29/27/4
+f 10/20/4 12/18/4 8/24/4
+f 1/28/8 18/19/8 17/23/8
+f 19/29/4 17/23/4 13/17/4
+f 25/25/4 14/16/4 18/19/4
+f 18/19/9 7/30/9 8/24/9
+f 92/31/10 27/32/10 28/33/10
+f 16/21/3 22/10/3 36/9/3
+f 31/22/3 36/9/3 21/34/3
+f 90/35/11 89/36/11 28/33/11
+f 91/37/12 90/35/12 24/38/12
+f 33/39/4 13/17/4 14/16/4
+f 23/40/4 24/38/4 28/33/4
+f 33/39/3 31/22/3 15/41/3
+f 21/34/3 36/9/3 30/42/3
+f 5/5/4 35/43/4 32/26/4
+f 5/5/4 20/4/4 34/44/4
+f 33/39/4 29/27/4 34/44/4
+f 91/37/13 23/40/13 27/32/13
+f 103/1/1 26/45/1 63/46/1
+f 26/45/14 50/47/14 38/48/14
+f 39/12/15 71/49/15 72/50/15
+f 48/51/16 60/52/16 59/53/16
+f 15/41/17 21/34/17 47/54/17
+f 19/29/17 46/55/17 37/56/17
+f 39/12/2 45/57/2 52/8/2
+f 20/4/2 45/57/2 44/58/2
+f 19/29/18 15/41/18 43/59/18
+f 9/60/19 42/61/19 47/54/19
+f 22/10/20 48/51/20 41/62/20
+f 25/25/21 1/28/21 37/56/21
+f 6/3/14 40/63/14 50/47/14
+f 104/64/22 40/63/22 6/3/22
+f 2/65/23 38/48/23 105/66/23
+f 55/67/2 56/68/2 53/15/2
+f 3/7/14 53/15/14 56/68/14
+f 51/13/15 55/67/15 54/14/15
+f 52/8/24 56/68/24 55/67/24
+f 57/69/2 59/53/2 60/52/2
+f 48/51/25 22/10/25 58/70/25
+f 16/21/26 57/69/26 58/70/26
+f 16/21/27 44/58/27 59/53/27
+f 107/71/28 63/46/28 67/72/28
+f 26/45/1 2/65/1 61/73/1
+f 9/60/1 30/42/1 64/74/1
+f 101/75/1 9/60/1 62/76/1
+f 108/77/1 109/78/1 67/72/1
+f 61/73/29 65/79/29 67/72/29
+f 62/76/30 64/74/30 68/80/30
+f 62/76/31 66/81/31 108/77/31
+f 71/49/32 75/82/32 76/83/32
+f 25/25/15 49/84/15 72/50/15
+f 5/5/15 69/85/15 71/49/15
+f 25/25/15 70/86/15 69/85/15
+f 76/83/15 75/82/15 79/87/15
+f 72/50/17 76/83/17 74/88/17
+f 71/49/2 69/85/2 73/89/2
+f 70/86/33 74/88/33 73/89/33
+f 80/90/3 79/87/3 83/91/3
+f 76/83/15 80/90/15 78/92/15
+f 75/82/15 73/89/15 77/93/15
+f 74/88/15 78/92/15 77/93/15
+f 82/94/15 84/95/15 83/91/15
+f 80/90/2 84/95/2 82/94/2
+f 77/93/17 81/96/17 83/91/17
+f 77/93/24 78/92/24 82/94/24
+f 35/43/13 87/97/13 88/98/13
+f 35/43/12 34/44/12 86/99/12
+f 34/44/11 29/27/11 85/100/11
+f 32/26/10 88/98/10 85/100/10
+f 92/31/34 100/101/34 99/102/34
+f 90/35/35 91/37/35 99/102/35
+f 89/36/36 90/35/36 98/103/36
+f 89/36/37 97/104/37 100/101/37
+f 95/105/13 99/102/13 100/101/13
+f 95/105/12 94/106/12 98/103/12
+f 94/106/11 93/107/11 97/104/11
+f 96/108/10 100/101/10 97/104/10
+f 88/98/38 96/108/38 93/107/38
+f 86/99/39 85/100/39 93/107/39
+f 87/97/40 86/99/40 94/106/40
+f 87/97/41 95/105/41 96/108/41
+f 106/109/42 108/77/42 65/79/42
+f 66/81/1 68/80/1 109/78/1
+f 101/75/1 106/109/1 61/73/1
+f 64/74/43 107/71/43 109/78/43
+f 101/75/23 105/66/23 42/61/23
+f 103/1/1 107/71/1 64/74/1
+f 30/42/1 11/11/1 102/2/1
+f 212/1/44 135/45/44 115/3/44
+f 129/4/2 112/7/2 113/6/2
+f 161/8/2 112/7/2 129/4/2
+f 145/9/24 139/42/24 120/11/24
+f 113/6/2 160/13/2 148/12/2
+f 162/15/45 163/14/45 113/6/45
+f 123/16/46 119/20/46 121/18/46
+f 127/19/47 116/30/47 119/20/47
+f 140/22/24 125/21/24 129/4/24
+f 121/18/48 117/24/48 126/23/48
+f 138/27/45 141/26/45 134/25/45
+f 117/24/45 121/18/45 119/20/45
+f 126/23/49 127/19/49 110/28/49
+f 122/17/45 126/23/45 128/29/45
+f 127/19/45 123/16/45 134/25/45
+f 117/24/50 116/30/50 127/19/50
+f 137/33/51 136/32/51 201/31/51
+f 145/9/24 131/10/24 125/21/24
+f 130/34/24 145/9/24 140/22/24
+f 199/35/52 133/38/52 137/33/52
+f 200/37/53 132/40/53 133/38/53
+f 123/16/45 122/17/45 142/39/45
+f 137/33/45 133/38/45 132/40/45
+f 124/41/24 140/22/24 142/39/24
+f 130/34/24 118/60/24 139/42/24
+f 141/26/45 144/43/45 114/5/45
+f 114/5/45 144/43/45 143/44/45
+f 143/44/45 138/27/45 142/39/45
+f 136/32/54 132/40/54 200/37/54
+f 212/1/44 216/71/44 172/46/44
+f 147/48/14 159/47/14 135/45/14
+f 181/50/15 180/49/15 148/12/15
+f 168/53/26 169/52/26 157/51/26
+f 124/41/17 152/59/17 156/54/17
+f 146/56/17 155/55/17 128/29/17
+f 148/12/2 160/13/2 161/8/2
+f 129/4/2 125/21/2 153/58/2
+f 155/55/18 152/59/18 124/41/18
+f 130/34/19 156/54/19 151/61/19
+f 131/10/20 120/11/20 150/62/20
+f 134/25/21 158/84/21 146/56/21
+f 159/47/14 149/63/14 115/3/14
+f 115/3/22 149/63/22 213/64/22
+f 214/66/23 147/48/23 111/65/23
+f 162/15/2 165/68/2 164/67/2
+f 165/68/14 162/15/14 112/7/14
+f 163/14/15 164/67/15 160/13/15
+f 164/67/3 165/68/3 161/8/3
+f 166/69/2 167/70/2 169/52/2
+f 157/51/25 169/52/25 167/70/25
+f 167/70/16 166/69/16 125/21/16
+f 125/21/27 166/69/27 168/53/27
+f 216/71/55 218/78/55 176/72/55
+f 135/45/44 172/46/44 170/73/44
+f 118/60/44 171/76/44 173/74/44
+f 210/75/44 215/109/44 171/76/44
+f 217/77/44 174/79/44 176/72/44
+f 176/72/56 174/79/56 170/73/56
+f 171/76/57 175/81/57 177/80/57
+f 217/77/58 175/81/58 171/76/58
+f 185/83/33 184/82/33 180/49/33
+f 134/25/15 179/86/15 181/50/15
+f 180/49/15 178/85/15 114/5/15
+f 178/85/15 179/86/15 134/25/15
+f 189/90/15 188/87/15 184/82/15
+f 183/88/17 185/83/17 181/50/17
+f 180/49/2 184/82/2 182/89/2
+f 182/89/32 183/88/32 179/86/32
+f 189/90/24 193/95/24 192/91/24
+f 187/92/15 189/90/15 185/83/15
+f 184/82/15 188/87/15 186/93/15
+f 186/93/15 187/92/15 183/88/15
+f 192/91/15 193/95/15 191/94/15
+f 191/94/2 193/95/2 189/90/2
+f 192/91/17 190/96/17 186/93/17
+f 186/93/3 190/96/3 191/94/3
+f 197/98/54 196/97/54 144/43/54
+f 144/43/53 196/97/53 195/99/53
+f 143/44/52 195/99/52 194/100/52
+f 194/100/51 197/98/51 141/26/51
+f 208/102/59 209/101/59 201/31/59
+f 199/35/60 207/103/60 208/102/60
+f 198/36/61 206/104/61 207/103/61
+f 209/101/62 206/104/62 198/36/62
+f 209/101/54 208/102/54 204/105/54
+f 204/105/53 208/102/53 207/103/53
+f 203/106/52 207/103/52 206/104/52
+f 206/104/51 209/101/51 205/108/51
+f 202/107/63 205/108/63 197/98/63
+f 195/99/64 203/106/64 202/107/64
+f 196/97/65 204/105/65 203/106/65
+f 205/108/66 204/105/66 196/97/66
+f 174/79/67 217/77/67 215/109/67
+f 175/81/44 217/77/44 218/78/44
+f 170/73/44 215/109/44 210/75/44
+f 173/74/68 177/80/68 218/78/68
+f 151/61/23 214/66/23 210/75/23
+f 173/74/44 216/71/44 212/1/44
+f 139/42/44 212/1/44 211/2/44
+f 26/45/1 103/1/1 6/3/1
+f 3/7/2 20/4/2 4/6/2
+f 45/57/2 20/4/2 52/8/2
+f 30/42/3 36/9/3 11/11/3
+f 5/5/2 39/12/2 4/6/2
+f 3/7/4 4/6/4 53/15/4
+f 10/20/5 14/16/5 12/18/5
+f 7/30/6 18/19/6 10/20/6
+f 33/39/3 20/4/3 31/22/3
+f 13/17/7 17/23/7 12/18/7
+f 33/39/4 25/25/4 29/27/4
+f 7/30/4 10/20/4 8/24/4
+f 19/29/69 1/28/69 17/23/69
+f 33/39/4 19/29/4 13/17/4
+f 1/28/70 25/25/70 18/19/70
+f 17/23/9 18/19/9 8/24/9
+f 89/36/10 92/31/10 28/33/10
+f 31/22/3 16/21/3 36/9/3
+f 15/41/3 31/22/3 21/34/3
+f 24/38/11 90/35/11 28/33/11
+f 23/40/12 91/37/12 24/38/12
+f 25/25/4 33/39/4 14/16/4
+f 27/32/4 23/40/4 28/33/4
+f 19/29/3 33/39/3 15/41/3
+f 9/60/3 21/34/3 30/42/3
+f 25/25/4 5/5/4 32/26/4
+f 35/43/4 5/5/4 34/44/4
+f 20/4/4 33/39/4 34/44/4
+f 92/31/13 91/37/13 27/32/13
+f 107/71/1 103/1/1 63/46/1
+f 2/65/14 26/45/14 38/48/14
+f 49/84/15 39/12/15 72/50/15
+f 44/58/16 48/51/16 59/53/16
+f 43/59/17 15/41/17 47/54/17
+f 1/28/17 19/29/17 37/56/17
+f 51/13/2 39/12/2 52/8/2
+f 16/21/2 20/4/2 44/58/2
+f 46/55/18 19/29/18 43/59/18
+f 21/34/19 9/60/19 47/54/19
+f 11/11/20 22/10/20 41/62/20
+f 49/84/21 25/25/21 37/56/21
+f 26/45/14 6/3/14 50/47/14
+f 102/2/22 104/64/22 6/3/22
+f 101/75/23 2/65/23 105/66/23
+f 54/14/2 55/67/2 53/15/2
+f 52/8/14 3/7/14 56/68/14
+f 4/6/15 51/13/15 54/14/15
+f 51/13/24 52/8/24 55/67/24
+f 58/70/2 57/69/2 60/52/2
+f 60/52/25 48/51/25 58/70/25
+f 22/10/26 16/21/26 58/70/26
+f 57/69/27 16/21/27 59/53/27
+f 109/78/71 107/71/71 67/72/71
+f 63/46/1 26/45/1 61/73/1
+f 62/76/1 9/60/1 64/74/1
+f 106/109/1 101/75/1 62/76/1
+f 65/79/1 108/77/1 67/72/1
+f 63/46/29 61/73/29 67/72/29
+f 66/81/30 62/76/30 68/80/30
+f 106/109/72 62/76/72 108/77/72
+f 72/50/32 71/49/32 76/83/32
+f 70/86/15 25/25/15 72/50/15
+f 39/12/15 5/5/15 71/49/15
+f 5/5/15 25/25/15 69/85/15
+f 80/90/15 76/83/15 79/87/15
+f 70/86/17 72/50/17 74/88/17
+f 75/82/2 71/49/2 73/89/2
+f 69/85/33 70/86/33 73/89/33
+f 84/95/3 80/90/3 83/91/3
+f 74/88/15 76/83/15 78/92/15
+f 79/87/15 75/82/15 77/93/15
+f 73/89/15 74/88/15 77/93/15
+f 81/96/15 82/94/15 83/91/15
+f 78/92/2 80/90/2 82/94/2
+f 79/87/17 77/93/17 83/91/17
+f 81/96/24 77/93/24 82/94/24
+f 32/26/13 35/43/13 88/98/13
+f 87/97/12 35/43/12 86/99/12
+f 86/99/11 34/44/11 85/100/11
+f 29/27/10 32/26/10 85/100/10
+f 91/37/34 92/31/34 99/102/34
+f 98/103/35 90/35/35 99/102/35
+f 97/104/36 89/36/36 98/103/36
+f 92/31/37 89/36/37 100/101/37
+f 96/108/13 95/105/13 100/101/13
+f 99/102/12 95/105/12 98/103/12
+f 98/103/11 94/106/11 97/104/11
+f 93/107/10 96/108/10 97/104/10
+f 85/100/38 88/98/38 93/107/38
+f 94/106/39 86/99/39 93/107/39
+f 95/105/40 87/97/40 94/106/40
+f 88/98/41 87/97/41 96/108/41
+f 61/73/73 106/109/73 65/79/73
+f 108/77/1 66/81/1 109/78/1
+f 2/65/1 101/75/1 61/73/1
+f 68/80/74 64/74/74 109/78/74
+f 9/60/23 101/75/23 42/61/23
+f 30/42/1 103/1/1 64/74/1
+f 103/1/1 30/42/1 102/2/1
+f 211/2/44 212/1/44 115/3/44
+f 114/5/2 129/4/2 113/6/2
+f 154/57/2 161/8/2 129/4/2
+f 131/10/24 145/9/24 120/11/24
+f 114/5/2 113/6/2 148/12/2
+f 112/7/45 162/15/45 113/6/45
+f 122/17/46 123/16/46 121/18/46
+f 123/16/47 127/19/47 119/20/47
+f 142/39/24 140/22/24 129/4/24
+f 122/17/48 121/18/48 126/23/48
+f 142/39/45 138/27/45 134/25/45
+f 116/30/45 117/24/45 119/20/45
+f 128/29/75 126/23/75 110/28/75
+f 142/39/45 122/17/45 128/29/45
+f 110/28/76 127/19/76 134/25/76
+f 126/23/50 117/24/50 127/19/50
+f 198/36/51 137/33/51 201/31/51
+f 140/22/24 145/9/24 125/21/24
+f 124/41/24 130/34/24 140/22/24
+f 198/36/52 199/35/52 137/33/52
+f 199/35/53 200/37/53 133/38/53
+f 134/25/45 123/16/45 142/39/45
+f 136/32/45 137/33/45 132/40/45
+f 128/29/24 124/41/24 142/39/24
+f 145/9/24 130/34/24 139/42/24
+f 134/25/45 141/26/45 114/5/45
+f 129/4/45 114/5/45 143/44/45
+f 129/4/45 143/44/45 142/39/45
+f 201/31/54 136/32/54 200/37/54
+f 135/45/44 212/1/44 172/46/44
+f 111/65/14 147/48/14 135/45/14
+f 158/84/15 181/50/15 148/12/15
+f 153/58/26 168/53/26 157/51/26
+f 130/34/17 124/41/17 156/54/17
+f 110/28/17 146/56/17 128/29/17
+f 154/57/2 148/12/2 161/8/2
+f 154/57/2 129/4/2 153/58/2
+f 128/29/18 155/55/18 124/41/18
+f 118/60/19 130/34/19 151/61/19
+f 157/51/20 131/10/20 150/62/20
+f 110/28/21 134/25/21 146/56/21
+f 135/45/14 159/47/14 115/3/14
+f 211/2/22 115/3/22 213/64/22
+f 210/75/23 214/66/23 111/65/23
+f 163/14/2 162/15/2 164/67/2
+f 161/8/14 165/68/14 112/7/14
+f 113/6/15 163/14/15 160/13/15
+f 160/13/3 164/67/3 161/8/3
+f 168/53/2 166/69/2 169/52/2
+f 131/10/25 157/51/25 167/70/25
+f 131/10/16 167/70/16 125/21/16
+f 153/58/27 125/21/27 168/53/27
+f 172/46/77 216/71/77 176/72/77
+f 111/65/44 135/45/44 170/73/44
+f 139/42/44 118/60/44 173/74/44
+f 118/60/44 210/75/44 171/76/44
+f 218/78/44 217/77/44 176/72/44
+f 172/46/56 176/72/56 170/73/56
+f 173/74/57 171/76/57 177/80/57
+f 215/109/78 217/77/78 171/76/78
+f 181/50/33 185/83/33 180/49/33
+f 158/84/15 134/25/15 181/50/15
+f 148/12/15 180/49/15 114/5/15
+f 114/5/15 178/85/15 134/25/15
+f 185/83/15 189/90/15 184/82/15
+f 179/86/17 183/88/17 181/50/17
+f 178/85/2 180/49/2 182/89/2
+f 178/85/32 182/89/32 179/86/32
+f 188/87/24 189/90/24 192/91/24
+f 183/88/15 187/92/15 185/83/15
+f 182/89/15 184/82/15 186/93/15
+f 182/89/15 186/93/15 183/88/15
+f 190/96/15 192/91/15 191/94/15
+f 187/92/2 191/94/2 189/90/2
+f 188/87/17 192/91/17 186/93/17
+f 187/92/3 186/93/3 191/94/3
+f 141/26/54 197/98/54 144/43/54
+f 143/44/53 144/43/53 195/99/53
+f 138/27/52 143/44/52 194/100/52
+f 138/27/51 194/100/51 141/26/51
+f 200/37/59 208/102/59 201/31/59
+f 200/37/60 199/35/60 208/102/60
+f 199/35/61 198/36/61 207/103/61
+f 201/31/79 209/101/79 198/36/79
+f 205/108/54 209/101/54 204/105/54
+f 203/106/53 204/105/53 207/103/53
+f 202/107/52 203/106/52 206/104/52
+f 202/107/51 206/104/51 205/108/51
+f 194/100/63 202/107/63 197/98/63
+f 194/100/64 195/99/64 202/107/64
+f 195/99/65 196/97/65 203/106/65
+f 197/98/66 205/108/66 196/97/66
+f 170/73/80 174/79/80 215/109/80
+f 177/80/44 175/81/44 218/78/44
+f 111/65/44 170/73/44 210/75/44
+f 216/71/81 173/74/81 218/78/81
+f 118/60/23 151/61/23 210/75/23
+f 139/42/44 173/74/44 212/1/44
+f 120/11/44 139/42/44 211/2/44
+usemtl Material.003
+f 41/62/82 104/64/82 102/2/82
+f 211/2/82 213/64/82 150/62/82
+f 11/11/82 41/62/82 102/2/82
+f 120/11/82 211/2/82 150/62/82
diff --git a/models/missing_material_file.obj b/models/missing_material_file.obj
new file mode 100644
index 0000000..9e1d98c
--- /dev/null
+++ b/models/missing_material_file.obj
@@ -0,0 +1,145 @@
+# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project.
+# original cornell box data
+ # comment
+
+# empty line including some space
+
+
+#mtllib no_material.mtl
+
+o floor
+usemtl white
+v 552.8 0.0 0.0
+v 0.0 0.0 0.0
+v 0.0 0.0 559.2
+v 549.6 0.0 559.2
+
+v 130.0 0.0 65.0
+v 82.0 0.0 225.0
+v 240.0 0.0 272.0
+v 290.0 0.0 114.0
+
+v 423.0 0.0 247.0
+v 265.0 0.0 296.0
+v 314.0 0.0 456.0
+v 472.0 0.0 406.0
+
+f 1 2 3 4
+f 8 7 6 5
+f 12 11 10 9
+
+o light
+usemtl light
+v 343.0 548.0 227.0
+v 343.0 548.0 332.0
+v 213.0 548.0 332.0
+v 213.0 548.0 227.0
+f -4 -3 -2 -1
+
+o ceiling
+usemtl white
+v 556.0 548.8 0.0
+v 556.0 548.8 559.2
+v 0.0 548.8 559.2
+v 0.0 548.8 0.0
+f -4 -3 -2 -1
+
+o back_wall
+usemtl white
+v 549.6 0.0 559.2
+v 0.0 0.0 559.2
+v 0.0 548.8 559.2
+v 556.0 548.8 559.2
+f -4 -3 -2 -1
+
+o front_wall
+usemtl blue
+v 549.6 0.0 0
+v 0.0 0.0 0
+v 0.0 548.8 0
+v 556.0 548.8 0
+#f -1 -2 -3 -4
+
+o green_wall
+usemtl green
+v 0.0 0.0 559.2
+v 0.0 0.0 0.0
+v 0.0 548.8 0.0
+v 0.0 548.8 559.2
+f -4 -3 -2 -1
+
+o red_wall
+usemtl red
+v 552.8 0.0 0.0
+v 549.6 0.0 559.2
+v 556.0 548.8 559.2
+v 556.0 548.8 0.0
+f -4 -3 -2 -1
+
+o short_block
+usemtl white
+
+v 130.0 165.0 65.0
+v 82.0 165.0 225.0
+v 240.0 165.0 272.0
+v 290.0 165.0 114.0
+f -4 -3 -2 -1
+
+v 290.0 0.0 114.0
+v 290.0 165.0 114.0
+v 240.0 165.0 272.0
+v 240.0 0.0 272.0
+f -4 -3 -2 -1
+
+v 130.0 0.0 65.0
+v 130.0 165.0 65.0
+v 290.0 165.0 114.0
+v 290.0 0.0 114.0
+f -4 -3 -2 -1
+
+v 82.0 0.0 225.0
+v 82.0 165.0 225.0
+v 130.0 165.0 65.0
+v 130.0 0.0 65.0
+f -4 -3 -2 -1
+
+v 240.0 0.0 272.0
+v 240.0 165.0 272.0
+v 82.0 165.0 225.0
+v 82.0 0.0 225.0
+f -4 -3 -2 -1
+
+o tall_block
+usemtl white
+
+v 423.0 330.0 247.0
+v 265.0 330.0 296.0
+v 314.0 330.0 456.0
+v 472.0 330.0 406.0
+f -4 -3 -2 -1
+
+usemtl white
+v 423.0 0.0 247.0
+v 423.0 330.0 247.0
+v 472.0 330.0 406.0
+v 472.0 0.0 406.0
+f -4 -3 -2 -1
+
+v 472.0 0.0 406.0
+v 472.0 330.0 406.0
+v 314.0 330.0 456.0
+v 314.0 0.0 456.0
+f -4 -3 -2 -1
+
+v 314.0 0.0 456.0
+v 314.0 330.0 456.0
+v 265.0 330.0 296.0
+v 265.0 0.0 296.0
+f -4 -3 -2 -1
+
+v 265.0 0.0 296.0
+v 265.0 330.0 296.0
+v 423.0 330.0 247.0
+v 423.0 0.0 247.0
+f -4 -3 -2 -1
+
diff --git a/models/mtllib-multiple-files-issue-112.mtl b/models/mtllib-multiple-files-issue-112.mtl
new file mode 100644
index 0000000..5ebd668
--- /dev/null
+++ b/models/mtllib-multiple-files-issue-112.mtl
@@ -0,0 +1,6 @@
+newmtl default
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+map_Kd tmp.png
+
diff --git a/models/mtllib-multiple-files-issue-112.obj b/models/mtllib-multiple-files-issue-112.obj
new file mode 100644
index 0000000..9966dfb
--- /dev/null
+++ b/models/mtllib-multiple-files-issue-112.obj
@@ -0,0 +1,7 @@
+mtllib invalid-file-aaa.mtl invalid-file-bbb.mtl mtllib-multiple-files-issue-112.mtl
+o Test
+v 1.864151 -1.219172 -5.532511
+v 0.575869 -0.666304 5.896140
+v 0.940448 1.000000 -1.971128
+usemtl default
+f 1 2 3
diff --git a/models/no_material.obj b/models/no_material.obj
new file mode 100644
index 0000000..6f3688f
--- /dev/null
+++ b/models/no_material.obj
@@ -0,0 +1,133 @@
+# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project.
+# original cornell box data
+ # comment
+
+# empty line including some space
+
+
+o floor
+v 552.8 0.0 0.0
+v 0.0 0.0 0.0
+v 0.0 0.0 559.2
+v 549.6 0.0 559.2
+
+v 130.0 0.0 65.0
+v 82.0 0.0 225.0
+v 240.0 0.0 272.0
+v 290.0 0.0 114.0
+
+v 423.0 0.0 247.0
+v 265.0 0.0 296.0
+v 314.0 0.0 456.0
+v 472.0 0.0 406.0
+
+f 1 2 3 4
+f 8 7 6 5
+f 12 11 10 9
+
+o light
+v 343.0 548.0 227.0
+v 343.0 548.0 332.0
+v 213.0 548.0 332.0
+v 213.0 548.0 227.0
+f -4 -3 -2 -1
+
+o ceiling
+v 556.0 548.8 0.0
+v 556.0 548.8 559.2
+v 0.0 548.8 559.2
+v 0.0 548.8 0.0
+f -4 -3 -2 -1
+
+o back_wall
+v 549.6 0.0 559.2
+v 0.0 0.0 559.2
+v 0.0 548.8 559.2
+v 556.0 548.8 559.2
+f -4 -3 -2 -1
+
+o front_wall
+v 549.6 0.0 0
+v 0.0 0.0 0
+v 0.0 548.8 0
+v 556.0 548.8 0
+#f -1 -2 -3 -4
+
+o green_wall
+v 0.0 0.0 559.2
+v 0.0 0.0 0.0
+v 0.0 548.8 0.0
+v 0.0 548.8 559.2
+f -4 -3 -2 -1
+
+o red_wall
+v 552.8 0.0 0.0
+v 549.6 0.0 559.2
+v 556.0 548.8 559.2
+v 556.0 548.8 0.0
+f -4 -3 -2 -1
+
+o short_block
+
+v 130.0 165.0 65.0
+v 82.0 165.0 225.0
+v 240.0 165.0 272.0
+v 290.0 165.0 114.0
+f -4 -3 -2 -1
+
+v 290.0 0.0 114.0
+v 290.0 165.0 114.0
+v 240.0 165.0 272.0
+v 240.0 0.0 272.0
+f -4 -3 -2 -1
+
+v 130.0 0.0 65.0
+v 130.0 165.0 65.0
+v 290.0 165.0 114.0
+v 290.0 0.0 114.0
+f -4 -3 -2 -1
+
+v 82.0 0.0 225.0
+v 82.0 165.0 225.0
+v 130.0 165.0 65.0
+v 130.0 0.0 65.0
+f -4 -3 -2 -1
+
+v 240.0 0.0 272.0
+v 240.0 165.0 272.0
+v 82.0 165.0 225.0
+v 82.0 0.0 225.0
+f -4 -3 -2 -1
+
+o tall_block
+
+v 423.0 330.0 247.0
+v 265.0 330.0 296.0
+v 314.0 330.0 456.0
+v 472.0 330.0 406.0
+f -4 -3 -2 -1
+
+v 423.0 0.0 247.0
+v 423.0 330.0 247.0
+v 472.0 330.0 406.0
+v 472.0 0.0 406.0
+f -4 -3 -2 -1
+
+v 472.0 0.0 406.0
+v 472.0 330.0 406.0
+v 314.0 330.0 456.0
+v 314.0 0.0 456.0
+f -4 -3 -2 -1
+
+v 314.0 0.0 456.0
+v 314.0 330.0 456.0
+v 265.0 330.0 296.0
+v 265.0 0.0 296.0
+f -4 -3 -2 -1
+
+v 265.0 0.0 296.0
+v 265.0 330.0 296.0
+v 423.0 330.0 247.0
+v 423.0 0.0 247.0
+f -4 -3 -2 -1
+
diff --git a/models/norm-texopt.mtl b/models/norm-texopt.mtl
new file mode 100644
index 0000000..e2d4a2c
--- /dev/null
+++ b/models/norm-texopt.mtl
@@ -0,0 +1,7 @@
+newmtl default
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+Kt 0.1 0.2 0.3
+norm -bm 3 normalmap.jpg
+
diff --git a/models/norm-texopt.obj b/models/norm-texopt.obj
new file mode 100644
index 0000000..babe94d
--- /dev/null
+++ b/models/norm-texopt.obj
@@ -0,0 +1,7 @@
+mtllib norm-texopt.mtl
+o Test
+v 1.864151 -1.219172 -5.532511
+v 0.575869 -0.666304 5.896140
+v 0.940448 1.000000 -1.971128
+usemtl default
+f 1 2 3
diff --git a/models/pbr-mat-ext.mtl b/models/pbr-mat-ext.mtl
new file mode 100644
index 0000000..bed905d
--- /dev/null
+++ b/models/pbr-mat-ext.mtl
@@ -0,0 +1,19 @@
+# .MTL with PBR extension.
+newmtl pbr
+Ka 0 0 0
+Kd 1 1 1
+Ks 0 0 0
+Ke 0.1 0.1 0.1
+Pr 0.2
+Pm 0.3
+Ps 0.4
+Pc 0.5
+Pcr 0.6
+aniso 0.7
+anisor 0.8
+map_Pr roughness.tex
+map_Pm metallic.tex
+map_Ps sheen.tex
+map_Ke emissive.tex
+norm normalmap.tex
+
diff --git a/models/pbr-mat-ext.obj b/models/pbr-mat-ext.obj
new file mode 100644
index 0000000..bb3e371
--- /dev/null
+++ b/models/pbr-mat-ext.obj
@@ -0,0 +1,10 @@
+mtllib pbr-mat-ext.mtl
+
+o floor
+usemtl pbr
+v 552.8 0.0 0.0
+v 0.0 0.0 0.0
+v 0.0 0.0 559.2
+v 549.6 0.0 559.2
+
+f 1 2 3 4
diff --git a/models/refl.mtl b/models/refl.mtl
new file mode 100644
index 0000000..7e04f28
--- /dev/null
+++ b/models/refl.mtl
@@ -0,0 +1,25 @@
+newmtl white
+Ka 0 0 0
+Kd 1 1 1
+Ks 0 0 0
+refl reflection.tga
+
+newmtl red
+Ka 0 0 0
+Kd 1 0 0
+Ks 0 0 0
+
+newmtl green
+Ka 0 0 0
+Kd 0 1 0
+Ks 0 0 0
+
+newmtl blue
+Ka 0 0 0
+Kd 0 0 1
+Ks 0 0 0
+
+newmtl light
+Ka 20 20 20
+Kd 1 1 1
+Ks 0 0 0
diff --git a/models/refl.obj b/models/refl.obj
new file mode 100644
index 0000000..e9715f5
--- /dev/null
+++ b/models/refl.obj
@@ -0,0 +1,32 @@
+# Test for `refl` material parameter
+mtllib refl.mtl
+
+v 0.000000 2.000000 2.000000
+v 0.000000 0.000000 2.000000
+v 2.000000 0.000000 2.000000
+v 2.000000 2.000000 2.000000
+v 0.000000 2.000000 0.000000
+v 0.000000 0.000000 0.000000
+v 2.000000 0.000000 0.000000
+v 2.000000 2.000000 0.000000
+# 8 vertices
+
+g front cube
+usemtl white
+f 1 2 3 4
+g back cube
+# expects white material
+f 8 7 6 5
+g right cube
+usemtl red
+f 4 3 7 8
+g top cube
+usemtl white
+f 5 1 4 8
+g left cube
+usemtl green
+f 5 6 2 1
+g bottom cube
+usemtl white
+f 2 6 7 3
+# 6 elements
diff --git a/models/test-nan.obj b/models/test-nan.obj
new file mode 100644
index 0000000..3c68925
--- /dev/null
+++ b/models/test-nan.obj
@@ -0,0 +1,145 @@
+# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project.
+# original cornell box data
+ # comment
+
+# empty line including some space
+
+
+mtllib cornell_box.mtl
+
+o floor
+usemtl white
+v nan -nan nan
+v inf -inf inf
+v 1.#IND -1.#IND 1.#IND
+v 1.#INF -1.#INF 1.#INF
+
+v 130.0 0.0 65.0
+v 82.0 0.0 225.0
+v 240.0 0.0 272.0
+v 290.0 0.0 114.0
+
+v 423.0 0.0 247.0
+v 265.0 0.0 296.0
+v 314.0 0.0 456.0
+v 472.0 0.0 406.0
+
+f 1 2 3 4
+f 8 7 6 5
+f 12 11 10 9
+
+o light
+usemtl light
+v 343.0 548.0 227.0
+v 343.0 548.0 332.0
+v 213.0 548.0 332.0
+v 213.0 548.0 227.0
+f -4 -3 -2 -1
+
+o ceiling
+usemtl white
+v 556.0 548.8 0.0
+v 556.0 548.8 559.2
+v 0.0 548.8 559.2
+v 0.0 548.8 0.0
+f -4 -3 -2 -1
+
+o back_wall
+usemtl white
+v 549.6 0.0 559.2
+v 0.0 0.0 559.2
+v 0.0 548.8 559.2
+v 556.0 548.8 559.2
+f -4 -3 -2 -1
+
+o front_wall
+usemtl blue
+v 549.6 0.0 0
+v 0.0 0.0 0
+v 0.0 548.8 0
+v 556.0 548.8 0
+#f -1 -2 -3 -4
+
+o green_wall
+usemtl green
+v 0.0 0.0 559.2
+v 0.0 0.0 0.0
+v 0.0 548.8 0.0
+v 0.0 548.8 559.2
+f -4 -3 -2 -1
+
+o red_wall
+usemtl red
+v 552.8 0.0 0.0
+v 549.6 0.0 559.2
+v 556.0 548.8 559.2
+v 556.0 548.8 0.0
+f -4 -3 -2 -1
+
+o short_block
+usemtl white
+
+v 130.0 165.0 65.0
+v 82.0 165.0 225.0
+v 240.0 165.0 272.0
+v 290.0 165.0 114.0
+f -4 -3 -2 -1
+
+v 290.0 0.0 114.0
+v 290.0 165.0 114.0
+v 240.0 165.0 272.0
+v 240.0 0.0 272.0
+f -4 -3 -2 -1
+
+v 130.0 0.0 65.0
+v 130.0 165.0 65.0
+v 290.0 165.0 114.0
+v 290.0 0.0 114.0
+f -4 -3 -2 -1
+
+v 82.0 0.0 225.0
+v 82.0 165.0 225.0
+v 130.0 165.0 65.0
+v 130.0 0.0 65.0
+f -4 -3 -2 -1
+
+v 240.0 0.0 272.0
+v 240.0 165.0 272.0
+v 82.0 165.0 225.0
+v 82.0 0.0 225.0
+f -4 -3 -2 -1
+
+o tall_block
+usemtl white
+
+v 423.0 330.0 247.0
+v 265.0 330.0 296.0
+v 314.0 330.0 456.0
+v 472.0 330.0 406.0
+f -4 -3 -2 -1
+
+usemtl white
+v 423.0 0.0 247.0
+v 423.0 330.0 247.0
+v 472.0 330.0 406.0
+v 472.0 0.0 406.0
+f -4 -3 -2 -1
+
+v 472.0 0.0 406.0
+v 472.0 330.0 406.0
+v 314.0 330.0 456.0
+v 314.0 0.0 456.0
+f -4 -3 -2 -1
+
+v 314.0 0.0 456.0
+v 314.0 330.0 456.0
+v 265.0 330.0 296.0
+v 265.0 0.0 296.0
+f -4 -3 -2 -1
+
+v 265.0 0.0 296.0
+v 265.0 330.0 296.0
+v 423.0 330.0 247.0
+v 423.0 0.0 247.0
+f -4 -3 -2 -1
+
diff --git a/models/texture-filename-with-whitespace.mtl b/models/texture-filename-with-whitespace.mtl
new file mode 100644
index 0000000..70b1a4e
--- /dev/null
+++ b/models/texture-filename-with-whitespace.mtl
@@ -0,0 +1,28 @@
+newmtl white
+Ka 0 0 0
+Kd 1 1 1
+Ks 0 0 0
+# filename with white space.
+map_Kd texture 01.png
+
+newmtl red
+Ka 0 0 0
+Kd 1 0 0
+Ks 0 0 0
+# texture option + filename with white space.
+bump -bm 2 bump 01.png
+
+newmtl green
+Ka 0 0 0
+Kd 0 1 0
+Ks 0 0 0
+
+newmtl blue
+Ka 0 0 0
+Kd 0 0 1
+Ks 0 0 0
+
+newmtl light
+Ka 20 20 20
+Kd 1 1 1
+Ks 0 0 0
diff --git a/models/texture-filename-with-whitespace.obj b/models/texture-filename-with-whitespace.obj
new file mode 100644
index 0000000..46e61e7
--- /dev/null
+++ b/models/texture-filename-with-whitespace.obj
@@ -0,0 +1,31 @@
+mtllib texture-filename-with-whitespace.mtl
+
+v 0.000000 2.000000 2.000000
+v 0.000000 0.000000 2.000000
+v 2.000000 0.000000 2.000000
+v 2.000000 2.000000 2.000000
+v 0.000000 2.000000 0.000000
+v 0.000000 0.000000 0.000000
+v 2.000000 0.000000 0.000000
+v 2.000000 2.000000 0.000000
+# 8 vertices
+
+g front cube
+usemtl white
+f 1 2 3 4
+g back cube
+# expects white material
+f 8 7 6 5
+g right cube
+usemtl red
+f 4 3 7 8
+g top cube
+usemtl white
+f 5 1 4 8
+g left cube
+usemtl green
+f 5 6 2 1
+g bottom cube
+usemtl white
+f 2 6 7 3
+# 6 elements
diff --git a/models/texture-options-issue-85.mtl b/models/texture-options-issue-85.mtl
new file mode 100644
index 0000000..d4d62ad
--- /dev/null
+++ b/models/texture-options-issue-85.mtl
@@ -0,0 +1,36 @@
+newmtl default
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+Kt 0.1 0.2 0.3
+map_Ka -clamp on ambient.jpg
+map_Kd -o 0.1 diffuse.jpg
+map_Ks -s 0.1 0.2 specular.jpg
+map_Ns -t 0.1 0.2 0.3 specular_highlight.jpg
+map_bump -bm 3 bumpmap.jpg
+
+newmtl bm2
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+Kt 0.1 0.2 0.3
+# blendu
+map_Kd -blendu on diffuse.jpg
+map_Ks -blendv off specular.jpg
+map_Ns -mm 0.1 0.3 specular_highlight.jpg
+# -bm after filename
+map_bump -imfchan r bumpmap2.jpg -bm 1.5
+
+newmtl bm3
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+Kt 0.1 0.2 0.3
+# type
+map_Kd -type sphere diffuse.jpg
+map_Ks -type cube_top specular.jpg
+map_Ns -type cube_bottom specular_highlight.jpg
+map_Ka -type cube_left ambient.jpg
+map_d -type cube_right alpha.jpg
+map_bump -type cube_front bump.jpg
+disp -type cube_back displacement.jpg
diff --git a/models/texture-options-issue-85.obj b/models/texture-options-issue-85.obj
new file mode 100644
index 0000000..f7abb0c
--- /dev/null
+++ b/models/texture-options-issue-85.obj
@@ -0,0 +1,7 @@
+mtllib texture-options-issue-85.mtl
+o Test
+v 1.864151 -1.219172 -5.532511
+v 0.575869 -0.666304 5.896140
+v 0.940448 1.000000 -1.971128
+usemtl default
+f 1 2 3
diff --git a/models/tr-and-d-issue-43.mtl b/models/tr-and-d-issue-43.mtl
new file mode 100644
index 0000000..44fd7ac
--- /dev/null
+++ b/models/tr-and-d-issue-43.mtl
@@ -0,0 +1,13 @@
+newmtl Material.001
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+d 0.75
+Tr 0.5
+
+newmtl Material.002
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+Tr 0.5
+d 0.75
diff --git a/models/tr-and-d-issue-43.obj b/models/tr-and-d-issue-43.obj
new file mode 100644
index 0000000..d86c3cf
--- /dev/null
+++ b/models/tr-and-d-issue-43.obj
@@ -0,0 +1,817 @@
+# https://github.com/syoyo/tinyobjloader/issues/68
+# Blender v2.73 (sub 0) OBJ File: 'enemy.blend'
+# www.blender.org
+mtllib tr-and-d-issue-43.mtl
+o Cube
+v 1.864151 -1.219172 -5.532511
+v 0.575869 -0.666304 5.896140
+v 0.940448 1.000000 -1.971128
+v 1.620345 1.000000 -5.815706
+v 1.864152 1.000000 -6.334323
+v 0.575869 -0.129842 5.896143
+v 5.440438 -1.462153 -5.818601
+v 4.896782 -1.462153 -2.744413
+v 1.000825 -0.677484 1.899605
+v 5.440438 -1.246362 -5.818600
+v 1.000825 0.852342 1.899608
+v 4.896782 -1.246362 -2.744412
+v 1.160660 -0.450871 -2.356325
+v 1.704316 -0.450871 -5.430513
+v 1.000825 -0.351920 -1.293797
+v 1.000825 1.000000 -1.293794
+v 1.160660 -0.877888 -2.356326
+v 1.704316 -0.877888 -5.430514
+v 1.000825 -1.219172 -1.452514
+v 1.000825 1.000000 -1.452511
+v 1.000825 -0.351920 1.759410
+v 1.000825 1.000000 1.759413
+v 9.097919 1.221145 -6.212147
+v 8.356775 1.221145 -2.021231
+v 1.864151 -0.109586 -6.334325
+v 0.575869 -0.398073 5.896141
+v 9.097919 0.943958 -6.212148
+v 8.356775 0.943958 -2.021233
+v 1.061916 0.113661 -1.797961
+v 1.000825 0.161258 1.899606
+v 1.000825 0.324040 -1.293795
+v 1.803060 0.113661 -5.988876
+v 1.000825 -0.109586 -1.452513
+v 1.061916 0.776753 -1.797960
+v 1.803061 0.776753 -5.988875
+v 1.000825 0.324040 1.759412
+v 0.000825 -1.219172 -5.532512
+v 0.000825 -0.666304 5.896139
+v 0.000826 1.000000 -6.334325
+v 0.000825 -0.129842 5.896140
+v 0.000825 0.852342 1.899606
+v 0.000825 -0.677484 1.899604
+v 0.000825 -0.351920 -1.293797
+v 0.000825 1.000000 -1.293796
+v 0.000825 1.000000 -1.452513
+v 0.000825 -1.219172 -1.452515
+v 0.000825 -0.351920 1.759409
+v 0.000825 1.000000 1.759411
+v 0.000826 -0.109586 -6.334326
+v 0.000825 -0.398073 5.896140
+v 0.152918 1.000000 -5.815708
+v 0.152917 1.000000 -1.971130
+v 0.940448 1.168419 -1.971128
+v 1.620345 1.168419 -5.815706
+v 0.152918 1.168419 -5.815708
+v 0.152917 1.168419 -1.971130
+v 0.921118 1.091883 -1.050430
+v 0.921118 1.091883 1.516050
+v 0.080533 1.091883 -1.050432
+v 0.080533 1.091883 1.516048
+v 0.613003 -0.553430 5.546911
+v 0.963691 -0.559956 2.248834
+v 0.613003 -0.396857 5.546912
+v 0.963691 -0.070362 2.248835
+v 1.499370 -0.994317 3.966028
+v 1.850058 -0.997914 0.667950
+v 1.499370 -0.908021 3.966029
+v 1.850058 -0.728071 0.667951
+v 1.601022 0.760960 -6.334324
+v 1.601021 0.129454 -6.334325
+v 0.263955 0.760960 -6.334325
+v 0.263955 0.129454 -6.334325
+v 1.334809 0.760960 -7.515329
+v 1.334809 0.129455 -7.515330
+v 0.530168 0.760960 -7.515330
+v 0.530168 0.129455 -7.515330
+v 1.192720 0.649445 -7.515329
+v 1.192720 0.240971 -7.515330
+v 0.672258 0.649445 -7.515330
+v 0.672258 0.240971 -7.515330
+v 1.192719 0.649444 -6.524630
+v 1.192719 0.240970 -6.524631
+v 0.672257 0.649444 -6.524631
+v 0.672257 0.240970 -6.524631
+v 3.851026 0.431116 -1.883326
+v 3.851026 0.946662 -1.883325
+v 4.592170 0.946662 -6.074241
+v 4.592169 0.431116 -6.074242
+v 4.995714 0.561404 -1.918362
+v 4.995714 1.016394 -1.918360
+v 5.736857 1.016394 -6.109276
+v 5.736857 0.561404 -6.109277
+v 3.975454 0.471731 -2.162156
+v 3.975454 0.919244 -2.162155
+v 4.618796 0.919244 -5.800034
+v 4.618795 0.471730 -5.800035
+v 4.969088 0.584825 -2.192568
+v 4.969088 0.979775 -2.192567
+v 5.612430 0.979775 -5.830446
+v 5.612429 0.584825 -5.830447
+v 0.864214 -0.673890 3.184381
+v 0.864213 0.489129 3.184384
+v 0.864213 -0.018552 3.184383
+v 0.000825 0.489129 3.184382
+v 0.000825 -0.673890 3.184381
+v 0.850955 -0.557858 3.309075
+v 0.850955 -0.175321 3.309076
+v 1.737321 -0.996758 1.728192
+v 1.737321 -0.785920 1.728193
+v -1.864151 -1.219172 -5.532511
+v -0.575869 -0.666304 5.896140
+v -0.940448 1.000000 -1.971128
+v -1.620345 1.000000 -5.815706
+v -1.864152 1.000000 -6.334323
+v -0.575869 -0.129842 5.896143
+v -5.440438 -1.462153 -5.818601
+v -4.896782 -1.462153 -2.744413
+v -1.000825 -0.677484 1.899605
+v -5.440438 -1.246362 -5.818600
+v -1.000825 0.852342 1.899608
+v -4.896782 -1.246362 -2.744412
+v -1.160660 -0.450871 -2.356325
+v -1.704316 -0.450871 -5.430513
+v -1.000825 -0.351920 -1.293797
+v -1.000825 1.000000 -1.293794
+v -1.160660 -0.877888 -2.356326
+v -1.704316 -0.877888 -5.430514
+v -1.000825 -1.219172 -1.452514
+v -1.000825 1.000000 -1.452511
+v -1.000825 -0.351920 1.759410
+v -1.000825 1.000000 1.759413
+v -9.097919 1.221145 -6.212147
+v -8.356775 1.221145 -2.021231
+v -1.864151 -0.109586 -6.334325
+v -0.575869 -0.398073 5.896141
+v -9.097919 0.943958 -6.212148
+v -8.356775 0.943958 -2.021233
+v -1.061916 0.113661 -1.797961
+v -1.000825 0.161258 1.899606
+v -1.000825 0.324040 -1.293795
+v -1.803060 0.113661 -5.988876
+v -1.000825 -0.109586 -1.452513
+v -1.061916 0.776753 -1.797960
+v -1.803061 0.776753 -5.988875
+v -1.000825 0.324040 1.759412
+v -0.000825 -1.219172 -5.532512
+v -0.000825 -0.666304 5.896139
+v -0.000826 1.000000 -6.334325
+v -0.000825 -0.129842 5.896140
+v -0.000825 0.852342 1.899606
+v -0.000825 -0.677484 1.899604
+v -0.000825 -0.351920 -1.293797
+v -0.000825 1.000000 -1.293796
+v -0.000825 1.000000 -1.452513
+v -0.000825 -1.219172 -1.452515
+v -0.000825 -0.351920 1.759409
+v -0.000825 1.000000 1.759411
+v -0.000826 -0.109586 -6.334326
+v -0.000825 -0.398073 5.896140
+v -0.152918 1.000000 -5.815708
+v -0.152917 1.000000 -1.971130
+v -0.940448 1.168419 -1.971128
+v -1.620345 1.168419 -5.815706
+v -0.152918 1.168419 -5.815708
+v -0.152917 1.168419 -1.971130
+v -0.921118 1.091883 -1.050430
+v -0.921118 1.091883 1.516050
+v -0.080533 1.091883 -1.050432
+v -0.080533 1.091883 1.516048
+v -0.613003 -0.553430 5.546911
+v -0.963691 -0.559956 2.248834
+v -0.613003 -0.396857 5.546912
+v -0.963691 -0.070362 2.248835
+v -1.499370 -0.994317 3.966028
+v -1.850058 -0.997914 0.667950
+v -1.499370 -0.908021 3.966029
+v -1.850058 -0.728071 0.667951
+v -1.601022 0.760960 -6.334324
+v -1.601021 0.129454 -6.334325
+v -0.263955 0.760960 -6.334325
+v -0.263955 0.129454 -6.334325
+v -1.334809 0.760960 -7.515329
+v -1.334809 0.129455 -7.515330
+v -0.530168 0.760960 -7.515330
+v -0.530168 0.129455 -7.515330
+v -1.192720 0.649445 -7.515329
+v -1.192720 0.240971 -7.515330
+v -0.672258 0.649445 -7.515330
+v -0.672258 0.240971 -7.515330
+v -1.192719 0.649444 -6.524630
+v -1.192719 0.240970 -6.524631
+v -0.672257 0.649444 -6.524631
+v -0.672257 0.240970 -6.524631
+v -3.851026 0.431116 -1.883326
+v -3.851026 0.946662 -1.883325
+v -4.592170 0.946662 -6.074241
+v -4.592169 0.431116 -6.074242
+v -4.995714 0.561404 -1.918362
+v -4.995714 1.016394 -1.918360
+v -5.736857 1.016394 -6.109276
+v -5.736857 0.561404 -6.109277
+v -3.975454 0.471731 -2.162156
+v -3.975454 0.919244 -2.162155
+v -4.618796 0.919244 -5.800034
+v -4.618795 0.471730 -5.800035
+v -4.969088 0.584825 -2.192568
+v -4.969088 0.979775 -2.192567
+v -5.612430 0.979775 -5.830446
+v -5.612429 0.584825 -5.830447
+v -0.864214 -0.673890 3.184381
+v -0.864213 0.489129 3.184384
+v -0.864213 -0.018552 3.184383
+v -0.000825 0.489129 3.184382
+v -0.000825 -0.673890 3.184381
+v -0.850955 -0.557858 3.309075
+v -0.850955 -0.175321 3.309076
+v -1.737321 -0.996758 1.728192
+v -1.737321 -0.785920 1.728193
+vt 0.135351 -0.558072
+vt 0.003035 -0.363507
+vt 0.092282 -0.976844
+vt -0.081322 0.947351
+vt 0.100058 1.958891
+vt 0.050091 1.852185
+vt -0.092752 1.055565
+vt -0.251711 1.059474
+vt 0.075587 0.041384
+vt -0.086008 0.279003
+vt -0.086212 0.249830
+vt -0.276044 1.968137
+vt -0.246101 1.859467
+vt 0.009828 1.911388
+vt -0.133014 1.114769
+vt 0.413322 1.261595
+vt 0.299103 0.624605
+vt 1.243955 0.407183
+vt 0.515404 1.111487
+vt 1.358173 1.044173
+vt -0.081553 0.914324
+vt 0.080042 0.676706
+vt 0.401185 0.474498
+vt 1.295541 0.331328
+vt 0.365315 1.568841
+vt 0.299111 1.575740
+vt 0.143401 0.707357
+vt 0.629403 1.011947
+vt 0.449192 0.167251
+vt 1.409760 0.968317
+vt 0.986264 1.738667
+vt 1.573373 1.877873
+vt 1.417663 1.009490
+vt 0.237182 -0.196235
+vt 0.721785 1.030226
+vt 0.830554 0.870285
+vt 0.877494 1.898608
+vt 1.351399 1.106930
+vt 0.183935 0.557301
+vt 1.507109 1.975312
+vt 0.241636 0.439088
+vt 0.114297 -0.045011
+vt 0.140593 1.808834
+vt -0.015118 0.940452
+vt 0.156405 -1.071134
+vt 0.164119 -0.998223
+vt 0.040336 -1.068281
+vt 0.104459 -1.162571
+vt -0.165787 1.882802
+vt -0.014821 1.660811
+vt -0.287852 0.283965
+vt -0.293374 0.366508
+vt -0.289630 0.900550
+vt 0.035337 -0.191272
+vt 0.247348 0.172213
+vt 0.253300 1.021193
+vt -0.283166 0.952313
+vt -0.283398 0.919286
+vt 0.039792 0.444050
+vt 0.314806 -0.339851
+vt 0.112962 -0.334889
+vt -0.288056 0.254793
+vt -0.023788 -0.973990
+vt -0.155922 -0.359599
+vt 0.220528 -1.165425
+vt 0.108710 -0.748730
+vt -0.286364 1.918670
+vt -0.291973 1.118678
+vt -0.119962 0.896379
+vt -0.123707 0.362337
+vt 0.162891 -0.598569
+vt 0.467532 -0.853353
+vt 0.201549 -1.053262
+vt 0.161663 -0.198915
+vt 0.267667 -0.752638
+vt 0.278705 -0.371021
+vt 0.526390 -0.542053
+vt 0.483821 -0.479457
+vt 0.488162 -0.883689
+vt 0.500110 -0.105561
+vt 0.564618 -0.200418
+vt -0.110331 2.127229
+vt 0.040636 1.905238
+vt -0.010786 1.578087
+vt 0.104092 1.876168
+vt 0.255058 1.654176
+vt -0.054992 2.087323
+vt 0.203048 1.901245
+vt 0.052081 2.123235
+vt 0.042658 1.943733
+vt -0.056437 1.881175
+vt 0.147710 1.941151
+vt 0.050060 2.084741
+vt 0.146264 1.735002
+vt 0.041212 1.737584
+vt 0.048615 1.878591
+vt 0.663065 1.872485
+vt 0.786311 1.691257
+vt 0.507355 1.004102
+vt 0.630601 0.822874
+vt 0.955144 1.689498
+vt 0.860727 1.828333
+vt 0.725565 1.074543
+vt 0.819981 0.935708
+vt 0.674594 1.805657
+vt 0.539432 1.051867
+vt 0.646413 0.894554
+vt 0.781576 1.648344
+vt 0.240127 -0.712141
+vn 0.994400 0.000000 0.105700
+vn 0.000000 1.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.984700 0.000000 0.174100
+vn 0.211800 0.976600 0.037500
+vn -0.103300 0.000000 -0.994600
+vn 0.103300 -0.000000 0.994600
+vn 0.911400 0.378700 0.161200
+vn -0.157300 -0.987200 -0.027800
+vn 0.113700 -0.993300 0.020100
+vn 0.030600 -0.000000 0.999500
+vn -0.061100 0.998100 -0.010800
+vn -0.030600 0.000000 -0.999500
+vn -0.000000 -0.000000 1.000000
+vn 0.000000 0.000000 -1.000000
+vn -0.755400 0.655300 0.000000
+vn 0.000000 -1.000000 0.000000
+vn -0.000000 -0.180000 0.983700
+vn 0.000000 -0.395500 -0.918500
+vn -0.000000 0.688500 0.725200
+vn 0.000000 -0.585700 -0.810500
+vn -0.000000 0.974900 0.222500
+vn -0.000000 -1.000000 0.002800
+vn -1.000000 0.000000 -0.000000
+vn -0.000000 0.935500 0.353200
+vn 0.755400 0.655300 0.000000
+vn 0.000000 0.935500 -0.353200
+vn 0.673800 0.724900 0.143400
+vn 0.872300 -0.000000 0.489100
+vn -0.872300 0.000000 -0.489100
+vn -0.518300 -0.853500 -0.054200
+vn -0.975500 0.000000 -0.219900
+vn 0.975500 0.000000 -0.219900
+vn -0.913200 0.000000 -0.407500
+vn -0.436900 0.896200 -0.077300
+vn -0.995300 -0.000000 0.096600
+vn -0.297300 -0.953400 -0.052600
+vn 0.473900 -0.876600 0.083800
+vn 0.913200 0.000000 0.407500
+vn 0.342200 0.937700 0.060500
+vn 0.995300 -0.000000 -0.096600
+vn -0.519200 -0.853000 -0.054300
+vn 0.722400 0.676400 0.143800
+vn -0.994400 0.000000 0.105700
+vn -0.984700 0.000000 0.174100
+vn -0.211800 0.976600 0.037500
+vn 0.103300 0.000000 -0.994600
+vn -0.103300 -0.000000 0.994600
+vn -0.911400 0.378700 0.161200
+vn 0.157300 -0.987200 -0.027800
+vn -0.113700 -0.993300 0.020100
+vn -0.030600 -0.000000 0.999500
+vn 0.061100 0.998100 -0.010800
+vn 0.030600 0.000000 -0.999500
+vn -0.691900 0.713200 0.112500
+vn -0.872300 -0.000000 0.489100
+vn 0.872300 0.000000 -0.489100
+vn 0.518300 -0.853500 -0.054200
+vn 0.913200 0.000000 -0.407500
+vn 0.436900 0.896200 -0.077300
+vn 0.995300 0.000000 0.096600
+vn 0.297300 -0.953300 -0.052600
+vn -0.473900 -0.876600 0.083800
+vn -0.913200 -0.000000 0.407500
+vn -0.342200 0.937700 0.060500
+vn -0.995300 -0.000000 -0.096600
+vn 0.519200 -0.853000 -0.054300
+vn -0.714800 0.690100 0.113700
+vn 0.974400 0.089700 0.206200
+vn 0.870400 0.288400 0.399100
+vn 0.691900 0.713200 0.112500
+vn -0.518000 -0.853700 -0.053400
+vn -0.519700 -0.852700 -0.053600
+vn 0.714800 0.690100 0.113700
+vn -0.974400 0.089700 0.206200
+vn -0.870400 0.288400 0.399100
+vn -0.673800 0.724900 0.143400
+vn 0.518000 -0.853700 -0.053400
+vn 0.297300 -0.953400 -0.052600
+vn 0.519700 -0.852700 -0.053600
+vn -0.722400 0.676400 0.143800
+vn -0.000000 0.962300 0.272000
+usemtl Material.001
+s off
+f 103/1/1 102/2/1 6/3/1
+f 20/4/2 5/5/2 4/6/2
+f 20/4/2 3/7/2 52/8/2
+f 36/9/3 22/10/3 11/11/3
+f 39/12/2 51/13/2 4/6/2
+f 4/6/4 54/14/4 53/15/4
+f 14/16/5 13/17/5 12/18/5
+f 18/19/6 14/16/6 10/20/6
+f 20/4/3 16/21/3 31/22/3
+f 17/23/7 8/24/7 12/18/7
+f 25/25/4 32/26/4 29/27/4
+f 10/20/4 12/18/4 8/24/4
+f 1/28/8 18/19/8 17/23/8
+f 19/29/4 17/23/4 13/17/4
+f 25/25/4 14/16/4 18/19/4
+f 18/19/9 7/30/9 8/24/9
+f 92/31/10 27/32/10 28/33/10
+f 16/21/3 22/10/3 36/9/3
+f 31/22/3 36/9/3 21/34/3
+f 90/35/11 89/36/11 28/33/11
+f 91/37/12 90/35/12 24/38/12
+f 33/39/4 13/17/4 14/16/4
+f 23/40/4 24/38/4 28/33/4
+f 33/39/3 31/22/3 15/41/3
+f 21/34/3 36/9/3 30/42/3
+f 5/5/4 35/43/4 32/26/4
+f 5/5/4 20/4/4 34/44/4
+f 33/39/4 29/27/4 34/44/4
+f 91/37/13 23/40/13 27/32/13
+f 103/1/1 26/45/1 63/46/1
+f 26/45/14 50/47/14 38/48/14
+f 39/12/15 71/49/15 72/50/15
+f 48/51/16 60/52/16 59/53/16
+f 15/41/17 21/34/17 47/54/17
+f 19/29/17 46/55/17 37/56/17
+f 39/12/2 45/57/2 52/8/2
+f 20/4/2 45/57/2 44/58/2
+f 19/29/18 15/41/18 43/59/18
+f 9/60/19 42/61/19 47/54/19
+f 22/10/20 48/51/20 41/62/20
+f 25/25/21 1/28/21 37/56/21
+f 6/3/14 40/63/14 50/47/14
+f 104/64/22 40/63/22 6/3/22
+f 2/65/23 38/48/23 105/66/23
+f 55/67/2 56/68/2 53/15/2
+f 3/7/14 53/15/14 56/68/14
+f 51/13/15 55/67/15 54/14/15
+f 52/8/24 56/68/24 55/67/24
+f 57/69/2 59/53/2 60/52/2
+f 48/51/25 22/10/25 58/70/25
+f 16/21/26 57/69/26 58/70/26
+f 16/21/27 44/58/27 59/53/27
+f 107/71/28 63/46/28 67/72/28
+f 26/45/1 2/65/1 61/73/1
+f 9/60/1 30/42/1 64/74/1
+f 101/75/1 9/60/1 62/76/1
+f 108/77/1 109/78/1 67/72/1
+f 61/73/29 65/79/29 67/72/29
+f 62/76/30 64/74/30 68/80/30
+f 62/76/31 66/81/31 108/77/31
+f 71/49/32 75/82/32 76/83/32
+f 25/25/15 49/84/15 72/50/15
+f 5/5/15 69/85/15 71/49/15
+f 25/25/15 70/86/15 69/85/15
+f 76/83/15 75/82/15 79/87/15
+f 72/50/17 76/83/17 74/88/17
+f 71/49/2 69/85/2 73/89/2
+f 70/86/33 74/88/33 73/89/33
+f 80/90/3 79/87/3 83/91/3
+f 76/83/15 80/90/15 78/92/15
+f 75/82/15 73/89/15 77/93/15
+f 74/88/15 78/92/15 77/93/15
+f 82/94/15 84/95/15 83/91/15
+f 80/90/2 84/95/2 82/94/2
+f 77/93/17 81/96/17 83/91/17
+f 77/93/24 78/92/24 82/94/24
+f 35/43/13 87/97/13 88/98/13
+f 35/43/12 34/44/12 86/99/12
+f 34/44/11 29/27/11 85/100/11
+f 32/26/10 88/98/10 85/100/10
+f 92/31/34 100/101/34 99/102/34
+f 90/35/35 91/37/35 99/102/35
+f 89/36/36 90/35/36 98/103/36
+f 89/36/37 97/104/37 100/101/37
+f 95/105/13 99/102/13 100/101/13
+f 95/105/12 94/106/12 98/103/12
+f 94/106/11 93/107/11 97/104/11
+f 96/108/10 100/101/10 97/104/10
+f 88/98/38 96/108/38 93/107/38
+f 86/99/39 85/100/39 93/107/39
+f 87/97/40 86/99/40 94/106/40
+f 87/97/41 95/105/41 96/108/41
+f 106/109/42 108/77/42 65/79/42
+f 66/81/1 68/80/1 109/78/1
+f 101/75/1 106/109/1 61/73/1
+f 64/74/43 107/71/43 109/78/43
+f 101/75/23 105/66/23 42/61/23
+f 103/1/1 107/71/1 64/74/1
+f 30/42/1 11/11/1 102/2/1
+f 212/1/44 135/45/44 115/3/44
+f 129/4/2 112/7/2 113/6/2
+f 161/8/2 112/7/2 129/4/2
+f 145/9/24 139/42/24 120/11/24
+f 113/6/2 160/13/2 148/12/2
+f 162/15/45 163/14/45 113/6/45
+f 123/16/46 119/20/46 121/18/46
+f 127/19/47 116/30/47 119/20/47
+f 140/22/24 125/21/24 129/4/24
+f 121/18/48 117/24/48 126/23/48
+f 138/27/45 141/26/45 134/25/45
+f 117/24/45 121/18/45 119/20/45
+f 126/23/49 127/19/49 110/28/49
+f 122/17/45 126/23/45 128/29/45
+f 127/19/45 123/16/45 134/25/45
+f 117/24/50 116/30/50 127/19/50
+f 137/33/51 136/32/51 201/31/51
+f 145/9/24 131/10/24 125/21/24
+f 130/34/24 145/9/24 140/22/24
+f 199/35/52 133/38/52 137/33/52
+f 200/37/53 132/40/53 133/38/53
+f 123/16/45 122/17/45 142/39/45
+f 137/33/45 133/38/45 132/40/45
+f 124/41/24 140/22/24 142/39/24
+f 130/34/24 118/60/24 139/42/24
+f 141/26/45 144/43/45 114/5/45
+f 114/5/45 144/43/45 143/44/45
+f 143/44/45 138/27/45 142/39/45
+f 136/32/54 132/40/54 200/37/54
+f 212/1/44 216/71/44 172/46/44
+f 147/48/14 159/47/14 135/45/14
+f 181/50/15 180/49/15 148/12/15
+f 168/53/26 169/52/26 157/51/26
+f 124/41/17 152/59/17 156/54/17
+f 146/56/17 155/55/17 128/29/17
+f 148/12/2 160/13/2 161/8/2
+f 129/4/2 125/21/2 153/58/2
+f 155/55/18 152/59/18 124/41/18
+f 130/34/19 156/54/19 151/61/19
+f 131/10/20 120/11/20 150/62/20
+f 134/25/21 158/84/21 146/56/21
+f 159/47/14 149/63/14 115/3/14
+f 115/3/22 149/63/22 213/64/22
+f 214/66/23 147/48/23 111/65/23
+f 162/15/2 165/68/2 164/67/2
+f 165/68/14 162/15/14 112/7/14
+f 163/14/15 164/67/15 160/13/15
+f 164/67/3 165/68/3 161/8/3
+f 166/69/2 167/70/2 169/52/2
+f 157/51/25 169/52/25 167/70/25
+f 167/70/16 166/69/16 125/21/16
+f 125/21/27 166/69/27 168/53/27
+f 216/71/55 218/78/55 176/72/55
+f 135/45/44 172/46/44 170/73/44
+f 118/60/44 171/76/44 173/74/44
+f 210/75/44 215/109/44 171/76/44
+f 217/77/44 174/79/44 176/72/44
+f 176/72/56 174/79/56 170/73/56
+f 171/76/57 175/81/57 177/80/57
+f 217/77/58 175/81/58 171/76/58
+f 185/83/33 184/82/33 180/49/33
+f 134/25/15 179/86/15 181/50/15
+f 180/49/15 178/85/15 114/5/15
+f 178/85/15 179/86/15 134/25/15
+f 189/90/15 188/87/15 184/82/15
+f 183/88/17 185/83/17 181/50/17
+f 180/49/2 184/82/2 182/89/2
+f 182/89/32 183/88/32 179/86/32
+f 189/90/24 193/95/24 192/91/24
+f 187/92/15 189/90/15 185/83/15
+f 184/82/15 188/87/15 186/93/15
+f 186/93/15 187/92/15 183/88/15
+f 192/91/15 193/95/15 191/94/15
+f 191/94/2 193/95/2 189/90/2
+f 192/91/17 190/96/17 186/93/17
+f 186/93/3 190/96/3 191/94/3
+f 197/98/54 196/97/54 144/43/54
+f 144/43/53 196/97/53 195/99/53
+f 143/44/52 195/99/52 194/100/52
+f 194/100/51 197/98/51 141/26/51
+f 208/102/59 209/101/59 201/31/59
+f 199/35/60 207/103/60 208/102/60
+f 198/36/61 206/104/61 207/103/61
+f 209/101/62 206/104/62 198/36/62
+f 209/101/54 208/102/54 204/105/54
+f 204/105/53 208/102/53 207/103/53
+f 203/106/52 207/103/52 206/104/52
+f 206/104/51 209/101/51 205/108/51
+f 202/107/63 205/108/63 197/98/63
+f 195/99/64 203/106/64 202/107/64
+f 196/97/65 204/105/65 203/106/65
+f 205/108/66 204/105/66 196/97/66
+f 174/79/67 217/77/67 215/109/67
+f 175/81/44 217/77/44 218/78/44
+f 170/73/44 215/109/44 210/75/44
+f 173/74/68 177/80/68 218/78/68
+f 151/61/23 214/66/23 210/75/23
+f 173/74/44 216/71/44 212/1/44
+f 139/42/44 212/1/44 211/2/44
+f 26/45/1 103/1/1 6/3/1
+f 3/7/2 20/4/2 4/6/2
+f 45/57/2 20/4/2 52/8/2
+f 30/42/3 36/9/3 11/11/3
+f 5/5/2 39/12/2 4/6/2
+f 3/7/4 4/6/4 53/15/4
+f 10/20/5 14/16/5 12/18/5
+f 7/30/6 18/19/6 10/20/6
+f 33/39/3 20/4/3 31/22/3
+f 13/17/7 17/23/7 12/18/7
+f 33/39/4 25/25/4 29/27/4
+f 7/30/4 10/20/4 8/24/4
+f 19/29/69 1/28/69 17/23/69
+f 33/39/4 19/29/4 13/17/4
+f 1/28/70 25/25/70 18/19/70
+f 17/23/9 18/19/9 8/24/9
+f 89/36/10 92/31/10 28/33/10
+f 31/22/3 16/21/3 36/9/3
+f 15/41/3 31/22/3 21/34/3
+f 24/38/11 90/35/11 28/33/11
+f 23/40/12 91/37/12 24/38/12
+f 25/25/4 33/39/4 14/16/4
+f 27/32/4 23/40/4 28/33/4
+f 19/29/3 33/39/3 15/41/3
+f 9/60/3 21/34/3 30/42/3
+f 25/25/4 5/5/4 32/26/4
+f 35/43/4 5/5/4 34/44/4
+f 20/4/4 33/39/4 34/44/4
+f 92/31/13 91/37/13 27/32/13
+f 107/71/1 103/1/1 63/46/1
+f 2/65/14 26/45/14 38/48/14
+f 49/84/15 39/12/15 72/50/15
+f 44/58/16 48/51/16 59/53/16
+f 43/59/17 15/41/17 47/54/17
+f 1/28/17 19/29/17 37/56/17
+f 51/13/2 39/12/2 52/8/2
+f 16/21/2 20/4/2 44/58/2
+f 46/55/18 19/29/18 43/59/18
+f 21/34/19 9/60/19 47/54/19
+f 11/11/20 22/10/20 41/62/20
+f 49/84/21 25/25/21 37/56/21
+f 26/45/14 6/3/14 50/47/14
+f 102/2/22 104/64/22 6/3/22
+f 101/75/23 2/65/23 105/66/23
+f 54/14/2 55/67/2 53/15/2
+f 52/8/14 3/7/14 56/68/14
+f 4/6/15 51/13/15 54/14/15
+f 51/13/24 52/8/24 55/67/24
+f 58/70/2 57/69/2 60/52/2
+f 60/52/25 48/51/25 58/70/25
+f 22/10/26 16/21/26 58/70/26
+f 57/69/27 16/21/27 59/53/27
+f 109/78/71 107/71/71 67/72/71
+f 63/46/1 26/45/1 61/73/1
+f 62/76/1 9/60/1 64/74/1
+f 106/109/1 101/75/1 62/76/1
+f 65/79/1 108/77/1 67/72/1
+f 63/46/29 61/73/29 67/72/29
+f 66/81/30 62/76/30 68/80/30
+f 106/109/72 62/76/72 108/77/72
+f 72/50/32 71/49/32 76/83/32
+f 70/86/15 25/25/15 72/50/15
+f 39/12/15 5/5/15 71/49/15
+f 5/5/15 25/25/15 69/85/15
+f 80/90/15 76/83/15 79/87/15
+f 70/86/17 72/50/17 74/88/17
+f 75/82/2 71/49/2 73/89/2
+f 69/85/33 70/86/33 73/89/33
+f 84/95/3 80/90/3 83/91/3
+f 74/88/15 76/83/15 78/92/15
+f 79/87/15 75/82/15 77/93/15
+f 73/89/15 74/88/15 77/93/15
+f 81/96/15 82/94/15 83/91/15
+f 78/92/2 80/90/2 82/94/2
+f 79/87/17 77/93/17 83/91/17
+f 81/96/24 77/93/24 82/94/24
+f 32/26/13 35/43/13 88/98/13
+f 87/97/12 35/43/12 86/99/12
+f 86/99/11 34/44/11 85/100/11
+f 29/27/10 32/26/10 85/100/10
+f 91/37/34 92/31/34 99/102/34
+f 98/103/35 90/35/35 99/102/35
+f 97/104/36 89/36/36 98/103/36
+f 92/31/37 89/36/37 100/101/37
+f 96/108/13 95/105/13 100/101/13
+f 99/102/12 95/105/12 98/103/12
+f 98/103/11 94/106/11 97/104/11
+f 93/107/10 96/108/10 97/104/10
+f 85/100/38 88/98/38 93/107/38
+f 94/106/39 86/99/39 93/107/39
+f 95/105/40 87/97/40 94/106/40
+f 88/98/41 87/97/41 96/108/41
+f 61/73/73 106/109/73 65/79/73
+f 108/77/1 66/81/1 109/78/1
+f 2/65/1 101/75/1 61/73/1
+f 68/80/74 64/74/74 109/78/74
+f 9/60/23 101/75/23 42/61/23
+f 30/42/1 103/1/1 64/74/1
+f 103/1/1 30/42/1 102/2/1
+f 211/2/44 212/1/44 115/3/44
+f 114/5/2 129/4/2 113/6/2
+f 154/57/2 161/8/2 129/4/2
+f 131/10/24 145/9/24 120/11/24
+f 114/5/2 113/6/2 148/12/2
+f 112/7/45 162/15/45 113/6/45
+f 122/17/46 123/16/46 121/18/46
+f 123/16/47 127/19/47 119/20/47
+f 142/39/24 140/22/24 129/4/24
+f 122/17/48 121/18/48 126/23/48
+f 142/39/45 138/27/45 134/25/45
+f 116/30/45 117/24/45 119/20/45
+f 128/29/75 126/23/75 110/28/75
+f 142/39/45 122/17/45 128/29/45
+f 110/28/76 127/19/76 134/25/76
+f 126/23/50 117/24/50 127/19/50
+f 198/36/51 137/33/51 201/31/51
+f 140/22/24 145/9/24 125/21/24
+f 124/41/24 130/34/24 140/22/24
+f 198/36/52 199/35/52 137/33/52
+f 199/35/53 200/37/53 133/38/53
+f 134/25/45 123/16/45 142/39/45
+f 136/32/45 137/33/45 132/40/45
+f 128/29/24 124/41/24 142/39/24
+f 145/9/24 130/34/24 139/42/24
+f 134/25/45 141/26/45 114/5/45
+f 129/4/45 114/5/45 143/44/45
+f 129/4/45 143/44/45 142/39/45
+f 201/31/54 136/32/54 200/37/54
+f 135/45/44 212/1/44 172/46/44
+f 111/65/14 147/48/14 135/45/14
+f 158/84/15 181/50/15 148/12/15
+f 153/58/26 168/53/26 157/51/26
+f 130/34/17 124/41/17 156/54/17
+f 110/28/17 146/56/17 128/29/17
+f 154/57/2 148/12/2 161/8/2
+f 154/57/2 129/4/2 153/58/2
+f 128/29/18 155/55/18 124/41/18
+f 118/60/19 130/34/19 151/61/19
+f 157/51/20 131/10/20 150/62/20
+f 110/28/21 134/25/21 146/56/21
+f 135/45/14 159/47/14 115/3/14
+f 211/2/22 115/3/22 213/64/22
+f 210/75/23 214/66/23 111/65/23
+f 163/14/2 162/15/2 164/67/2
+f 161/8/14 165/68/14 112/7/14
+f 113/6/15 163/14/15 160/13/15
+f 160/13/3 164/67/3 161/8/3
+f 168/53/2 166/69/2 169/52/2
+f 131/10/25 157/51/25 167/70/25
+f 131/10/16 167/70/16 125/21/16
+f 153/58/27 125/21/27 168/53/27
+f 172/46/77 216/71/77 176/72/77
+f 111/65/44 135/45/44 170/73/44
+f 139/42/44 118/60/44 173/74/44
+f 118/60/44 210/75/44 171/76/44
+f 218/78/44 217/77/44 176/72/44
+f 172/46/56 176/72/56 170/73/56
+f 173/74/57 171/76/57 177/80/57
+f 215/109/78 217/77/78 171/76/78
+f 181/50/33 185/83/33 180/49/33
+f 158/84/15 134/25/15 181/50/15
+f 148/12/15 180/49/15 114/5/15
+f 114/5/15 178/85/15 134/25/15
+f 185/83/15 189/90/15 184/82/15
+f 179/86/17 183/88/17 181/50/17
+f 178/85/2 180/49/2 182/89/2
+f 178/85/32 182/89/32 179/86/32
+f 188/87/24 189/90/24 192/91/24
+f 183/88/15 187/92/15 185/83/15
+f 182/89/15 184/82/15 186/93/15
+f 182/89/15 186/93/15 183/88/15
+f 190/96/15 192/91/15 191/94/15
+f 187/92/2 191/94/2 189/90/2
+f 188/87/17 192/91/17 186/93/17
+f 187/92/3 186/93/3 191/94/3
+f 141/26/54 197/98/54 144/43/54
+f 143/44/53 144/43/53 195/99/53
+f 138/27/52 143/44/52 194/100/52
+f 138/27/51 194/100/51 141/26/51
+f 200/37/59 208/102/59 201/31/59
+f 200/37/60 199/35/60 208/102/60
+f 199/35/61 198/36/61 207/103/61
+f 201/31/79 209/101/79 198/36/79
+f 205/108/54 209/101/54 204/105/54
+f 203/106/53 204/105/53 207/103/53
+f 202/107/52 203/106/52 206/104/52
+f 202/107/51 206/104/51 205/108/51
+f 194/100/63 202/107/63 197/98/63
+f 194/100/64 195/99/64 202/107/64
+f 195/99/65 196/97/65 203/106/65
+f 197/98/66 205/108/66 196/97/66
+f 170/73/80 174/79/80 215/109/80
+f 177/80/44 175/81/44 218/78/44
+f 111/65/44 170/73/44 210/75/44
+f 216/71/81 173/74/81 218/78/81
+f 118/60/23 151/61/23 210/75/23
+f 139/42/44 173/74/44 212/1/44
+f 120/11/44 139/42/44 211/2/44
+usemtl Material.002
+f 41/62/82 104/64/82 102/2/82
+f 211/2/82 213/64/82 150/62/82
+f 11/11/82 41/62/82 102/2/82
+f 120/11/82 211/2/82 150/62/82
diff --git a/models/usemtl-issue-104.obj b/models/usemtl-issue-104.obj
new file mode 100644
index 0000000..5183676
--- /dev/null
+++ b/models/usemtl-issue-104.obj
@@ -0,0 +1,30 @@
+# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project.
+# original cornell box data
+ # comment
+
+# empty line including some space
+
+
+mtllib cornell_box.mtl
+
+o floor
+v 552.8 0.0 0.0
+v 0.0 0.0 0.0
+v 0.0 0.0 559.2
+v 549.6 0.0 559.2
+
+v 130.0 0.0 65.0
+v 82.0 0.0 225.0
+v 240.0 0.0 272.0
+v 290.0 0.0 114.0
+
+v 423.0 0.0 247.0
+v 265.0 0.0 296.0
+v 314.0 0.0 456.0
+v 472.0 0.0 406.0
+
+f 1 2 3 4
+f 8 7 6 5
+f 12 11 10 9
+
+usemtl white
diff --git a/models/usemtl-issue-68.mtl b/models/usemtl-issue-68.mtl
new file mode 100644
index 0000000..24a791e
--- /dev/null
+++ b/models/usemtl-issue-68.mtl
@@ -0,0 +1,9 @@
+newmtl Material.001
+Ka 0 0 0
+Kd 0 0 0
+Ks 0 0 0
+
+newmtl Material.003
+Ka 0 0 0
+Kd 1 1 1
+Ks 0 0 0
diff --git a/models/usemtl-issue-68.obj b/models/usemtl-issue-68.obj
new file mode 100644
index 0000000..dddc900
--- /dev/null
+++ b/models/usemtl-issue-68.obj
@@ -0,0 +1,817 @@
+# https://github.com/syoyo/tinyobjloader/issues/68
+# Blender v2.73 (sub 0) OBJ File: 'enemy.blend'
+# www.blender.org
+mtllib usemtl-issue-68.mtl
+o Cube
+v 1.864151 -1.219172 -5.532511
+v 0.575869 -0.666304 5.896140
+v 0.940448 1.000000 -1.971128
+v 1.620345 1.000000 -5.815706
+v 1.864152 1.000000 -6.334323
+v 0.575869 -0.129842 5.896143
+v 5.440438 -1.462153 -5.818601
+v 4.896782 -1.462153 -2.744413
+v 1.000825 -0.677484 1.899605
+v 5.440438 -1.246362 -5.818600
+v 1.000825 0.852342 1.899608
+v 4.896782 -1.246362 -2.744412
+v 1.160660 -0.450871 -2.356325
+v 1.704316 -0.450871 -5.430513
+v 1.000825 -0.351920 -1.293797
+v 1.000825 1.000000 -1.293794
+v 1.160660 -0.877888 -2.356326
+v 1.704316 -0.877888 -5.430514
+v 1.000825 -1.219172 -1.452514
+v 1.000825 1.000000 -1.452511
+v 1.000825 -0.351920 1.759410
+v 1.000825 1.000000 1.759413
+v 9.097919 1.221145 -6.212147
+v 8.356775 1.221145 -2.021231
+v 1.864151 -0.109586 -6.334325
+v 0.575869 -0.398073 5.896141
+v 9.097919 0.943958 -6.212148
+v 8.356775 0.943958 -2.021233
+v 1.061916 0.113661 -1.797961
+v 1.000825 0.161258 1.899606
+v 1.000825 0.324040 -1.293795
+v 1.803060 0.113661 -5.988876
+v 1.000825 -0.109586 -1.452513
+v 1.061916 0.776753 -1.797960
+v 1.803061 0.776753 -5.988875
+v 1.000825 0.324040 1.759412
+v 0.000825 -1.219172 -5.532512
+v 0.000825 -0.666304 5.896139
+v 0.000826 1.000000 -6.334325
+v 0.000825 -0.129842 5.896140
+v 0.000825 0.852342 1.899606
+v 0.000825 -0.677484 1.899604
+v 0.000825 -0.351920 -1.293797
+v 0.000825 1.000000 -1.293796
+v 0.000825 1.000000 -1.452513
+v 0.000825 -1.219172 -1.452515
+v 0.000825 -0.351920 1.759409
+v 0.000825 1.000000 1.759411
+v 0.000826 -0.109586 -6.334326
+v 0.000825 -0.398073 5.896140
+v 0.152918 1.000000 -5.815708
+v 0.152917 1.000000 -1.971130
+v 0.940448 1.168419 -1.971128
+v 1.620345 1.168419 -5.815706
+v 0.152918 1.168419 -5.815708
+v 0.152917 1.168419 -1.971130
+v 0.921118 1.091883 -1.050430
+v 0.921118 1.091883 1.516050
+v 0.080533 1.091883 -1.050432
+v 0.080533 1.091883 1.516048
+v 0.613003 -0.553430 5.546911
+v 0.963691 -0.559956 2.248834
+v 0.613003 -0.396857 5.546912
+v 0.963691 -0.070362 2.248835
+v 1.499370 -0.994317 3.966028
+v 1.850058 -0.997914 0.667950
+v 1.499370 -0.908021 3.966029
+v 1.850058 -0.728071 0.667951
+v 1.601022 0.760960 -6.334324
+v 1.601021 0.129454 -6.334325
+v 0.263955 0.760960 -6.334325
+v 0.263955 0.129454 -6.334325
+v 1.334809 0.760960 -7.515329
+v 1.334809 0.129455 -7.515330
+v 0.530168 0.760960 -7.515330
+v 0.530168 0.129455 -7.515330
+v 1.192720 0.649445 -7.515329
+v 1.192720 0.240971 -7.515330
+v 0.672258 0.649445 -7.515330
+v 0.672258 0.240971 -7.515330
+v 1.192719 0.649444 -6.524630
+v 1.192719 0.240970 -6.524631
+v 0.672257 0.649444 -6.524631
+v 0.672257 0.240970 -6.524631
+v 3.851026 0.431116 -1.883326
+v 3.851026 0.946662 -1.883325
+v 4.592170 0.946662 -6.074241
+v 4.592169 0.431116 -6.074242
+v 4.995714 0.561404 -1.918362
+v 4.995714 1.016394 -1.918360
+v 5.736857 1.016394 -6.109276
+v 5.736857 0.561404 -6.109277
+v 3.975454 0.471731 -2.162156
+v 3.975454 0.919244 -2.162155
+v 4.618796 0.919244 -5.800034
+v 4.618795 0.471730 -5.800035
+v 4.969088 0.584825 -2.192568
+v 4.969088 0.979775 -2.192567
+v 5.612430 0.979775 -5.830446
+v 5.612429 0.584825 -5.830447
+v 0.864214 -0.673890 3.184381
+v 0.864213 0.489129 3.184384
+v 0.864213 -0.018552 3.184383
+v 0.000825 0.489129 3.184382
+v 0.000825 -0.673890 3.184381
+v 0.850955 -0.557858 3.309075
+v 0.850955 -0.175321 3.309076
+v 1.737321 -0.996758 1.728192
+v 1.737321 -0.785920 1.728193
+v -1.864151 -1.219172 -5.532511
+v -0.575869 -0.666304 5.896140
+v -0.940448 1.000000 -1.971128
+v -1.620345 1.000000 -5.815706
+v -1.864152 1.000000 -6.334323
+v -0.575869 -0.129842 5.896143
+v -5.440438 -1.462153 -5.818601
+v -4.896782 -1.462153 -2.744413
+v -1.000825 -0.677484 1.899605
+v -5.440438 -1.246362 -5.818600
+v -1.000825 0.852342 1.899608
+v -4.896782 -1.246362 -2.744412
+v -1.160660 -0.450871 -2.356325
+v -1.704316 -0.450871 -5.430513
+v -1.000825 -0.351920 -1.293797
+v -1.000825 1.000000 -1.293794
+v -1.160660 -0.877888 -2.356326
+v -1.704316 -0.877888 -5.430514
+v -1.000825 -1.219172 -1.452514
+v -1.000825 1.000000 -1.452511
+v -1.000825 -0.351920 1.759410
+v -1.000825 1.000000 1.759413
+v -9.097919 1.221145 -6.212147
+v -8.356775 1.221145 -2.021231
+v -1.864151 -0.109586 -6.334325
+v -0.575869 -0.398073 5.896141
+v -9.097919 0.943958 -6.212148
+v -8.356775 0.943958 -2.021233
+v -1.061916 0.113661 -1.797961
+v -1.000825 0.161258 1.899606
+v -1.000825 0.324040 -1.293795
+v -1.803060 0.113661 -5.988876
+v -1.000825 -0.109586 -1.452513
+v -1.061916 0.776753 -1.797960
+v -1.803061 0.776753 -5.988875
+v -1.000825 0.324040 1.759412
+v -0.000825 -1.219172 -5.532512
+v -0.000825 -0.666304 5.896139
+v -0.000826 1.000000 -6.334325
+v -0.000825 -0.129842 5.896140
+v -0.000825 0.852342 1.899606
+v -0.000825 -0.677484 1.899604
+v -0.000825 -0.351920 -1.293797
+v -0.000825 1.000000 -1.293796
+v -0.000825 1.000000 -1.452513
+v -0.000825 -1.219172 -1.452515
+v -0.000825 -0.351920 1.759409
+v -0.000825 1.000000 1.759411
+v -0.000826 -0.109586 -6.334326
+v -0.000825 -0.398073 5.896140
+v -0.152918 1.000000 -5.815708
+v -0.152917 1.000000 -1.971130
+v -0.940448 1.168419 -1.971128
+v -1.620345 1.168419 -5.815706
+v -0.152918 1.168419 -5.815708
+v -0.152917 1.168419 -1.971130
+v -0.921118 1.091883 -1.050430
+v -0.921118 1.091883 1.516050
+v -0.080533 1.091883 -1.050432
+v -0.080533 1.091883 1.516048
+v -0.613003 -0.553430 5.546911
+v -0.963691 -0.559956 2.248834
+v -0.613003 -0.396857 5.546912
+v -0.963691 -0.070362 2.248835
+v -1.499370 -0.994317 3.966028
+v -1.850058 -0.997914 0.667950
+v -1.499370 -0.908021 3.966029
+v -1.850058 -0.728071 0.667951
+v -1.601022 0.760960 -6.334324
+v -1.601021 0.129454 -6.334325
+v -0.263955 0.760960 -6.334325
+v -0.263955 0.129454 -6.334325
+v -1.334809 0.760960 -7.515329
+v -1.334809 0.129455 -7.515330
+v -0.530168 0.760960 -7.515330
+v -0.530168 0.129455 -7.515330
+v -1.192720 0.649445 -7.515329
+v -1.192720 0.240971 -7.515330
+v -0.672258 0.649445 -7.515330
+v -0.672258 0.240971 -7.515330
+v -1.192719 0.649444 -6.524630
+v -1.192719 0.240970 -6.524631
+v -0.672257 0.649444 -6.524631
+v -0.672257 0.240970 -6.524631
+v -3.851026 0.431116 -1.883326
+v -3.851026 0.946662 -1.883325
+v -4.592170 0.946662 -6.074241
+v -4.592169 0.431116 -6.074242
+v -4.995714 0.561404 -1.918362
+v -4.995714 1.016394 -1.918360
+v -5.736857 1.016394 -6.109276
+v -5.736857 0.561404 -6.109277
+v -3.975454 0.471731 -2.162156
+v -3.975454 0.919244 -2.162155
+v -4.618796 0.919244 -5.800034
+v -4.618795 0.471730 -5.800035
+v -4.969088 0.584825 -2.192568
+v -4.969088 0.979775 -2.192567
+v -5.612430 0.979775 -5.830446
+v -5.612429 0.584825 -5.830447
+v -0.864214 -0.673890 3.184381
+v -0.864213 0.489129 3.184384
+v -0.864213 -0.018552 3.184383
+v -0.000825 0.489129 3.184382
+v -0.000825 -0.673890 3.184381
+v -0.850955 -0.557858 3.309075
+v -0.850955 -0.175321 3.309076
+v -1.737321 -0.996758 1.728192
+v -1.737321 -0.785920 1.728193
+vt 0.135351 -0.558072
+vt 0.003035 -0.363507
+vt 0.092282 -0.976844
+vt -0.081322 0.947351
+vt 0.100058 1.958891
+vt 0.050091 1.852185
+vt -0.092752 1.055565
+vt -0.251711 1.059474
+vt 0.075587 0.041384
+vt -0.086008 0.279003
+vt -0.086212 0.249830
+vt -0.276044 1.968137
+vt -0.246101 1.859467
+vt 0.009828 1.911388
+vt -0.133014 1.114769
+vt 0.413322 1.261595
+vt 0.299103 0.624605
+vt 1.243955 0.407183
+vt 0.515404 1.111487
+vt 1.358173 1.044173
+vt -0.081553 0.914324
+vt 0.080042 0.676706
+vt 0.401185 0.474498
+vt 1.295541 0.331328
+vt 0.365315 1.568841
+vt 0.299111 1.575740
+vt 0.143401 0.707357
+vt 0.629403 1.011947
+vt 0.449192 0.167251
+vt 1.409760 0.968317
+vt 0.986264 1.738667
+vt 1.573373 1.877873
+vt 1.417663 1.009490
+vt 0.237182 -0.196235
+vt 0.721785 1.030226
+vt 0.830554 0.870285
+vt 0.877494 1.898608
+vt 1.351399 1.106930
+vt 0.183935 0.557301
+vt 1.507109 1.975312
+vt 0.241636 0.439088
+vt 0.114297 -0.045011
+vt 0.140593 1.808834
+vt -0.015118 0.940452
+vt 0.156405 -1.071134
+vt 0.164119 -0.998223
+vt 0.040336 -1.068281
+vt 0.104459 -1.162571
+vt -0.165787 1.882802
+vt -0.014821 1.660811
+vt -0.287852 0.283965
+vt -0.293374 0.366508
+vt -0.289630 0.900550
+vt 0.035337 -0.191272
+vt 0.247348 0.172213
+vt 0.253300 1.021193
+vt -0.283166 0.952313
+vt -0.283398 0.919286
+vt 0.039792 0.444050
+vt 0.314806 -0.339851
+vt 0.112962 -0.334889
+vt -0.288056 0.254793
+vt -0.023788 -0.973990
+vt -0.155922 -0.359599
+vt 0.220528 -1.165425
+vt 0.108710 -0.748730
+vt -0.286364 1.918670
+vt -0.291973 1.118678
+vt -0.119962 0.896379
+vt -0.123707 0.362337
+vt 0.162891 -0.598569
+vt 0.467532 -0.853353
+vt 0.201549 -1.053262
+vt 0.161663 -0.198915
+vt 0.267667 -0.752638
+vt 0.278705 -0.371021
+vt 0.526390 -0.542053
+vt 0.483821 -0.479457
+vt 0.488162 -0.883689
+vt 0.500110 -0.105561
+vt 0.564618 -0.200418
+vt -0.110331 2.127229
+vt 0.040636 1.905238
+vt -0.010786 1.578087
+vt 0.104092 1.876168
+vt 0.255058 1.654176
+vt -0.054992 2.087323
+vt 0.203048 1.901245
+vt 0.052081 2.123235
+vt 0.042658 1.943733
+vt -0.056437 1.881175
+vt 0.147710 1.941151
+vt 0.050060 2.084741
+vt 0.146264 1.735002
+vt 0.041212 1.737584
+vt 0.048615 1.878591
+vt 0.663065 1.872485
+vt 0.786311 1.691257
+vt 0.507355 1.004102
+vt 0.630601 0.822874
+vt 0.955144 1.689498
+vt 0.860727 1.828333
+vt 0.725565 1.074543
+vt 0.819981 0.935708
+vt 0.674594 1.805657
+vt 0.539432 1.051867
+vt 0.646413 0.894554
+vt 0.781576 1.648344
+vt 0.240127 -0.712141
+vn 0.994400 0.000000 0.105700
+vn 0.000000 1.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.984700 0.000000 0.174100
+vn 0.211800 0.976600 0.037500
+vn -0.103300 0.000000 -0.994600
+vn 0.103300 -0.000000 0.994600
+vn 0.911400 0.378700 0.161200
+vn -0.157300 -0.987200 -0.027800
+vn 0.113700 -0.993300 0.020100
+vn 0.030600 -0.000000 0.999500
+vn -0.061100 0.998100 -0.010800
+vn -0.030600 0.000000 -0.999500
+vn -0.000000 -0.000000 1.000000
+vn 0.000000 0.000000 -1.000000
+vn -0.755400 0.655300 0.000000
+vn 0.000000 -1.000000 0.000000
+vn -0.000000 -0.180000 0.983700
+vn 0.000000 -0.395500 -0.918500
+vn -0.000000 0.688500 0.725200
+vn 0.000000 -0.585700 -0.810500
+vn -0.000000 0.974900 0.222500
+vn -0.000000 -1.000000 0.002800
+vn -1.000000 0.000000 -0.000000
+vn -0.000000 0.935500 0.353200
+vn 0.755400 0.655300 0.000000
+vn 0.000000 0.935500 -0.353200
+vn 0.673800 0.724900 0.143400
+vn 0.872300 -0.000000 0.489100
+vn -0.872300 0.000000 -0.489100
+vn -0.518300 -0.853500 -0.054200
+vn -0.975500 0.000000 -0.219900
+vn 0.975500 0.000000 -0.219900
+vn -0.913200 0.000000 -0.407500
+vn -0.436900 0.896200 -0.077300
+vn -0.995300 -0.000000 0.096600
+vn -0.297300 -0.953400 -0.052600
+vn 0.473900 -0.876600 0.083800
+vn 0.913200 0.000000 0.407500
+vn 0.342200 0.937700 0.060500
+vn 0.995300 -0.000000 -0.096600
+vn -0.519200 -0.853000 -0.054300
+vn 0.722400 0.676400 0.143800
+vn -0.994400 0.000000 0.105700
+vn -0.984700 0.000000 0.174100
+vn -0.211800 0.976600 0.037500
+vn 0.103300 0.000000 -0.994600
+vn -0.103300 -0.000000 0.994600
+vn -0.911400 0.378700 0.161200
+vn 0.157300 -0.987200 -0.027800
+vn -0.113700 -0.993300 0.020100
+vn -0.030600 -0.000000 0.999500
+vn 0.061100 0.998100 -0.010800
+vn 0.030600 0.000000 -0.999500
+vn -0.691900 0.713200 0.112500
+vn -0.872300 -0.000000 0.489100
+vn 0.872300 0.000000 -0.489100
+vn 0.518300 -0.853500 -0.054200
+vn 0.913200 0.000000 -0.407500
+vn 0.436900 0.896200 -0.077300
+vn 0.995300 0.000000 0.096600
+vn 0.297300 -0.953300 -0.052600
+vn -0.473900 -0.876600 0.083800
+vn -0.913200 -0.000000 0.407500
+vn -0.342200 0.937700 0.060500
+vn -0.995300 -0.000000 -0.096600
+vn 0.519200 -0.853000 -0.054300
+vn -0.714800 0.690100 0.113700
+vn 0.974400 0.089700 0.206200
+vn 0.870400 0.288400 0.399100
+vn 0.691900 0.713200 0.112500
+vn -0.518000 -0.853700 -0.053400
+vn -0.519700 -0.852700 -0.053600
+vn 0.714800 0.690100 0.113700
+vn -0.974400 0.089700 0.206200
+vn -0.870400 0.288400 0.399100
+vn -0.673800 0.724900 0.143400
+vn 0.518000 -0.853700 -0.053400
+vn 0.297300 -0.953400 -0.052600
+vn 0.519700 -0.852700 -0.053600
+vn -0.722400 0.676400 0.143800
+vn -0.000000 0.962300 0.272000
+usemtl Material.001
+s off
+f 103/1/1 102/2/1 6/3/1
+f 20/4/2 5/5/2 4/6/2
+f 20/4/2 3/7/2 52/8/2
+f 36/9/3 22/10/3 11/11/3
+f 39/12/2 51/13/2 4/6/2
+f 4/6/4 54/14/4 53/15/4
+f 14/16/5 13/17/5 12/18/5
+f 18/19/6 14/16/6 10/20/6
+f 20/4/3 16/21/3 31/22/3
+f 17/23/7 8/24/7 12/18/7
+f 25/25/4 32/26/4 29/27/4
+f 10/20/4 12/18/4 8/24/4
+f 1/28/8 18/19/8 17/23/8
+f 19/29/4 17/23/4 13/17/4
+f 25/25/4 14/16/4 18/19/4
+f 18/19/9 7/30/9 8/24/9
+f 92/31/10 27/32/10 28/33/10
+f 16/21/3 22/10/3 36/9/3
+f 31/22/3 36/9/3 21/34/3
+f 90/35/11 89/36/11 28/33/11
+f 91/37/12 90/35/12 24/38/12
+f 33/39/4 13/17/4 14/16/4
+f 23/40/4 24/38/4 28/33/4
+f 33/39/3 31/22/3 15/41/3
+f 21/34/3 36/9/3 30/42/3
+f 5/5/4 35/43/4 32/26/4
+f 5/5/4 20/4/4 34/44/4
+f 33/39/4 29/27/4 34/44/4
+f 91/37/13 23/40/13 27/32/13
+f 103/1/1 26/45/1 63/46/1
+f 26/45/14 50/47/14 38/48/14
+f 39/12/15 71/49/15 72/50/15
+f 48/51/16 60/52/16 59/53/16
+f 15/41/17 21/34/17 47/54/17
+f 19/29/17 46/55/17 37/56/17
+f 39/12/2 45/57/2 52/8/2
+f 20/4/2 45/57/2 44/58/2
+f 19/29/18 15/41/18 43/59/18
+f 9/60/19 42/61/19 47/54/19
+f 22/10/20 48/51/20 41/62/20
+f 25/25/21 1/28/21 37/56/21
+f 6/3/14 40/63/14 50/47/14
+f 104/64/22 40/63/22 6/3/22
+f 2/65/23 38/48/23 105/66/23
+f 55/67/2 56/68/2 53/15/2
+f 3/7/14 53/15/14 56/68/14
+f 51/13/15 55/67/15 54/14/15
+f 52/8/24 56/68/24 55/67/24
+f 57/69/2 59/53/2 60/52/2
+f 48/51/25 22/10/25 58/70/25
+f 16/21/26 57/69/26 58/70/26
+f 16/21/27 44/58/27 59/53/27
+f 107/71/28 63/46/28 67/72/28
+f 26/45/1 2/65/1 61/73/1
+f 9/60/1 30/42/1 64/74/1
+f 101/75/1 9/60/1 62/76/1
+f 108/77/1 109/78/1 67/72/1
+f 61/73/29 65/79/29 67/72/29
+f 62/76/30 64/74/30 68/80/30
+f 62/76/31 66/81/31 108/77/31
+f 71/49/32 75/82/32 76/83/32
+f 25/25/15 49/84/15 72/50/15
+f 5/5/15 69/85/15 71/49/15
+f 25/25/15 70/86/15 69/85/15
+f 76/83/15 75/82/15 79/87/15
+f 72/50/17 76/83/17 74/88/17
+f 71/49/2 69/85/2 73/89/2
+f 70/86/33 74/88/33 73/89/33
+f 80/90/3 79/87/3 83/91/3
+f 76/83/15 80/90/15 78/92/15
+f 75/82/15 73/89/15 77/93/15
+f 74/88/15 78/92/15 77/93/15
+f 82/94/15 84/95/15 83/91/15
+f 80/90/2 84/95/2 82/94/2
+f 77/93/17 81/96/17 83/91/17
+f 77/93/24 78/92/24 82/94/24
+f 35/43/13 87/97/13 88/98/13
+f 35/43/12 34/44/12 86/99/12
+f 34/44/11 29/27/11 85/100/11
+f 32/26/10 88/98/10 85/100/10
+f 92/31/34 100/101/34 99/102/34
+f 90/35/35 91/37/35 99/102/35
+f 89/36/36 90/35/36 98/103/36
+f 89/36/37 97/104/37 100/101/37
+f 95/105/13 99/102/13 100/101/13
+f 95/105/12 94/106/12 98/103/12
+f 94/106/11 93/107/11 97/104/11
+f 96/108/10 100/101/10 97/104/10
+f 88/98/38 96/108/38 93/107/38
+f 86/99/39 85/100/39 93/107/39
+f 87/97/40 86/99/40 94/106/40
+f 87/97/41 95/105/41 96/108/41
+f 106/109/42 108/77/42 65/79/42
+f 66/81/1 68/80/1 109/78/1
+f 101/75/1 106/109/1 61/73/1
+f 64/74/43 107/71/43 109/78/43
+f 101/75/23 105/66/23 42/61/23
+f 103/1/1 107/71/1 64/74/1
+f 30/42/1 11/11/1 102/2/1
+f 212/1/44 135/45/44 115/3/44
+f 129/4/2 112/7/2 113/6/2
+f 161/8/2 112/7/2 129/4/2
+f 145/9/24 139/42/24 120/11/24
+f 113/6/2 160/13/2 148/12/2
+f 162/15/45 163/14/45 113/6/45
+f 123/16/46 119/20/46 121/18/46
+f 127/19/47 116/30/47 119/20/47
+f 140/22/24 125/21/24 129/4/24
+f 121/18/48 117/24/48 126/23/48
+f 138/27/45 141/26/45 134/25/45
+f 117/24/45 121/18/45 119/20/45
+f 126/23/49 127/19/49 110/28/49
+f 122/17/45 126/23/45 128/29/45
+f 127/19/45 123/16/45 134/25/45
+f 117/24/50 116/30/50 127/19/50
+f 137/33/51 136/32/51 201/31/51
+f 145/9/24 131/10/24 125/21/24
+f 130/34/24 145/9/24 140/22/24
+f 199/35/52 133/38/52 137/33/52
+f 200/37/53 132/40/53 133/38/53
+f 123/16/45 122/17/45 142/39/45
+f 137/33/45 133/38/45 132/40/45
+f 124/41/24 140/22/24 142/39/24
+f 130/34/24 118/60/24 139/42/24
+f 141/26/45 144/43/45 114/5/45
+f 114/5/45 144/43/45 143/44/45
+f 143/44/45 138/27/45 142/39/45
+f 136/32/54 132/40/54 200/37/54
+f 212/1/44 216/71/44 172/46/44
+f 147/48/14 159/47/14 135/45/14
+f 181/50/15 180/49/15 148/12/15
+f 168/53/26 169/52/26 157/51/26
+f 124/41/17 152/59/17 156/54/17
+f 146/56/17 155/55/17 128/29/17
+f 148/12/2 160/13/2 161/8/2
+f 129/4/2 125/21/2 153/58/2
+f 155/55/18 152/59/18 124/41/18
+f 130/34/19 156/54/19 151/61/19
+f 131/10/20 120/11/20 150/62/20
+f 134/25/21 158/84/21 146/56/21
+f 159/47/14 149/63/14 115/3/14
+f 115/3/22 149/63/22 213/64/22
+f 214/66/23 147/48/23 111/65/23
+f 162/15/2 165/68/2 164/67/2
+f 165/68/14 162/15/14 112/7/14
+f 163/14/15 164/67/15 160/13/15
+f 164/67/3 165/68/3 161/8/3
+f 166/69/2 167/70/2 169/52/2
+f 157/51/25 169/52/25 167/70/25
+f 167/70/16 166/69/16 125/21/16
+f 125/21/27 166/69/27 168/53/27
+f 216/71/55 218/78/55 176/72/55
+f 135/45/44 172/46/44 170/73/44
+f 118/60/44 171/76/44 173/74/44
+f 210/75/44 215/109/44 171/76/44
+f 217/77/44 174/79/44 176/72/44
+f 176/72/56 174/79/56 170/73/56
+f 171/76/57 175/81/57 177/80/57
+f 217/77/58 175/81/58 171/76/58
+f 185/83/33 184/82/33 180/49/33
+f 134/25/15 179/86/15 181/50/15
+f 180/49/15 178/85/15 114/5/15
+f 178/85/15 179/86/15 134/25/15
+f 189/90/15 188/87/15 184/82/15
+f 183/88/17 185/83/17 181/50/17
+f 180/49/2 184/82/2 182/89/2
+f 182/89/32 183/88/32 179/86/32
+f 189/90/24 193/95/24 192/91/24
+f 187/92/15 189/90/15 185/83/15
+f 184/82/15 188/87/15 186/93/15
+f 186/93/15 187/92/15 183/88/15
+f 192/91/15 193/95/15 191/94/15
+f 191/94/2 193/95/2 189/90/2
+f 192/91/17 190/96/17 186/93/17
+f 186/93/3 190/96/3 191/94/3
+f 197/98/54 196/97/54 144/43/54
+f 144/43/53 196/97/53 195/99/53
+f 143/44/52 195/99/52 194/100/52
+f 194/100/51 197/98/51 141/26/51
+f 208/102/59 209/101/59 201/31/59
+f 199/35/60 207/103/60 208/102/60
+f 198/36/61 206/104/61 207/103/61
+f 209/101/62 206/104/62 198/36/62
+f 209/101/54 208/102/54 204/105/54
+f 204/105/53 208/102/53 207/103/53
+f 203/106/52 207/103/52 206/104/52
+f 206/104/51 209/101/51 205/108/51
+f 202/107/63 205/108/63 197/98/63
+f 195/99/64 203/106/64 202/107/64
+f 196/97/65 204/105/65 203/106/65
+f 205/108/66 204/105/66 196/97/66
+f 174/79/67 217/77/67 215/109/67
+f 175/81/44 217/77/44 218/78/44
+f 170/73/44 215/109/44 210/75/44
+f 173/74/68 177/80/68 218/78/68
+f 151/61/23 214/66/23 210/75/23
+f 173/74/44 216/71/44 212/1/44
+f 139/42/44 212/1/44 211/2/44
+f 26/45/1 103/1/1 6/3/1
+f 3/7/2 20/4/2 4/6/2
+f 45/57/2 20/4/2 52/8/2
+f 30/42/3 36/9/3 11/11/3
+f 5/5/2 39/12/2 4/6/2
+f 3/7/4 4/6/4 53/15/4
+f 10/20/5 14/16/5 12/18/5
+f 7/30/6 18/19/6 10/20/6
+f 33/39/3 20/4/3 31/22/3
+f 13/17/7 17/23/7 12/18/7
+f 33/39/4 25/25/4 29/27/4
+f 7/30/4 10/20/4 8/24/4
+f 19/29/69 1/28/69 17/23/69
+f 33/39/4 19/29/4 13/17/4
+f 1/28/70 25/25/70 18/19/70
+f 17/23/9 18/19/9 8/24/9
+f 89/36/10 92/31/10 28/33/10
+f 31/22/3 16/21/3 36/9/3
+f 15/41/3 31/22/3 21/34/3
+f 24/38/11 90/35/11 28/33/11
+f 23/40/12 91/37/12 24/38/12
+f 25/25/4 33/39/4 14/16/4
+f 27/32/4 23/40/4 28/33/4
+f 19/29/3 33/39/3 15/41/3
+f 9/60/3 21/34/3 30/42/3
+f 25/25/4 5/5/4 32/26/4
+f 35/43/4 5/5/4 34/44/4
+f 20/4/4 33/39/4 34/44/4
+f 92/31/13 91/37/13 27/32/13
+f 107/71/1 103/1/1 63/46/1
+f 2/65/14 26/45/14 38/48/14
+f 49/84/15 39/12/15 72/50/15
+f 44/58/16 48/51/16 59/53/16
+f 43/59/17 15/41/17 47/54/17
+f 1/28/17 19/29/17 37/56/17
+f 51/13/2 39/12/2 52/8/2
+f 16/21/2 20/4/2 44/58/2
+f 46/55/18 19/29/18 43/59/18
+f 21/34/19 9/60/19 47/54/19
+f 11/11/20 22/10/20 41/62/20
+f 49/84/21 25/25/21 37/56/21
+f 26/45/14 6/3/14 50/47/14
+f 102/2/22 104/64/22 6/3/22
+f 101/75/23 2/65/23 105/66/23
+f 54/14/2 55/67/2 53/15/2
+f 52/8/14 3/7/14 56/68/14
+f 4/6/15 51/13/15 54/14/15
+f 51/13/24 52/8/24 55/67/24
+f 58/70/2 57/69/2 60/52/2
+f 60/52/25 48/51/25 58/70/25
+f 22/10/26 16/21/26 58/70/26
+f 57/69/27 16/21/27 59/53/27
+f 109/78/71 107/71/71 67/72/71
+f 63/46/1 26/45/1 61/73/1
+f 62/76/1 9/60/1 64/74/1
+f 106/109/1 101/75/1 62/76/1
+f 65/79/1 108/77/1 67/72/1
+f 63/46/29 61/73/29 67/72/29
+f 66/81/30 62/76/30 68/80/30
+f 106/109/72 62/76/72 108/77/72
+f 72/50/32 71/49/32 76/83/32
+f 70/86/15 25/25/15 72/50/15
+f 39/12/15 5/5/15 71/49/15
+f 5/5/15 25/25/15 69/85/15
+f 80/90/15 76/83/15 79/87/15
+f 70/86/17 72/50/17 74/88/17
+f 75/82/2 71/49/2 73/89/2
+f 69/85/33 70/86/33 73/89/33
+f 84/95/3 80/90/3 83/91/3
+f 74/88/15 76/83/15 78/92/15
+f 79/87/15 75/82/15 77/93/15
+f 73/89/15 74/88/15 77/93/15
+f 81/96/15 82/94/15 83/91/15
+f 78/92/2 80/90/2 82/94/2
+f 79/87/17 77/93/17 83/91/17
+f 81/96/24 77/93/24 82/94/24
+f 32/26/13 35/43/13 88/98/13
+f 87/97/12 35/43/12 86/99/12
+f 86/99/11 34/44/11 85/100/11
+f 29/27/10 32/26/10 85/100/10
+f 91/37/34 92/31/34 99/102/34
+f 98/103/35 90/35/35 99/102/35
+f 97/104/36 89/36/36 98/103/36
+f 92/31/37 89/36/37 100/101/37
+f 96/108/13 95/105/13 100/101/13
+f 99/102/12 95/105/12 98/103/12
+f 98/103/11 94/106/11 97/104/11
+f 93/107/10 96/108/10 97/104/10
+f 85/100/38 88/98/38 93/107/38
+f 94/106/39 86/99/39 93/107/39
+f 95/105/40 87/97/40 94/106/40
+f 88/98/41 87/97/41 96/108/41
+f 61/73/73 106/109/73 65/79/73
+f 108/77/1 66/81/1 109/78/1
+f 2/65/1 101/75/1 61/73/1
+f 68/80/74 64/74/74 109/78/74
+f 9/60/23 101/75/23 42/61/23
+f 30/42/1 103/1/1 64/74/1
+f 103/1/1 30/42/1 102/2/1
+f 211/2/44 212/1/44 115/3/44
+f 114/5/2 129/4/2 113/6/2
+f 154/57/2 161/8/2 129/4/2
+f 131/10/24 145/9/24 120/11/24
+f 114/5/2 113/6/2 148/12/2
+f 112/7/45 162/15/45 113/6/45
+f 122/17/46 123/16/46 121/18/46
+f 123/16/47 127/19/47 119/20/47
+f 142/39/24 140/22/24 129/4/24
+f 122/17/48 121/18/48 126/23/48
+f 142/39/45 138/27/45 134/25/45
+f 116/30/45 117/24/45 119/20/45
+f 128/29/75 126/23/75 110/28/75
+f 142/39/45 122/17/45 128/29/45
+f 110/28/76 127/19/76 134/25/76
+f 126/23/50 117/24/50 127/19/50
+f 198/36/51 137/33/51 201/31/51
+f 140/22/24 145/9/24 125/21/24
+f 124/41/24 130/34/24 140/22/24
+f 198/36/52 199/35/52 137/33/52
+f 199/35/53 200/37/53 133/38/53
+f 134/25/45 123/16/45 142/39/45
+f 136/32/45 137/33/45 132/40/45
+f 128/29/24 124/41/24 142/39/24
+f 145/9/24 130/34/24 139/42/24
+f 134/25/45 141/26/45 114/5/45
+f 129/4/45 114/5/45 143/44/45
+f 129/4/45 143/44/45 142/39/45
+f 201/31/54 136/32/54 200/37/54
+f 135/45/44 212/1/44 172/46/44
+f 111/65/14 147/48/14 135/45/14
+f 158/84/15 181/50/15 148/12/15
+f 153/58/26 168/53/26 157/51/26
+f 130/34/17 124/41/17 156/54/17
+f 110/28/17 146/56/17 128/29/17
+f 154/57/2 148/12/2 161/8/2
+f 154/57/2 129/4/2 153/58/2
+f 128/29/18 155/55/18 124/41/18
+f 118/60/19 130/34/19 151/61/19
+f 157/51/20 131/10/20 150/62/20
+f 110/28/21 134/25/21 146/56/21
+f 135/45/14 159/47/14 115/3/14
+f 211/2/22 115/3/22 213/64/22
+f 210/75/23 214/66/23 111/65/23
+f 163/14/2 162/15/2 164/67/2
+f 161/8/14 165/68/14 112/7/14
+f 113/6/15 163/14/15 160/13/15
+f 160/13/3 164/67/3 161/8/3
+f 168/53/2 166/69/2 169/52/2
+f 131/10/25 157/51/25 167/70/25
+f 131/10/16 167/70/16 125/21/16
+f 153/58/27 125/21/27 168/53/27
+f 172/46/77 216/71/77 176/72/77
+f 111/65/44 135/45/44 170/73/44
+f 139/42/44 118/60/44 173/74/44
+f 118/60/44 210/75/44 171/76/44
+f 218/78/44 217/77/44 176/72/44
+f 172/46/56 176/72/56 170/73/56
+f 173/74/57 171/76/57 177/80/57
+f 215/109/78 217/77/78 171/76/78
+f 181/50/33 185/83/33 180/49/33
+f 158/84/15 134/25/15 181/50/15
+f 148/12/15 180/49/15 114/5/15
+f 114/5/15 178/85/15 134/25/15
+f 185/83/15 189/90/15 184/82/15
+f 179/86/17 183/88/17 181/50/17
+f 178/85/2 180/49/2 182/89/2
+f 178/85/32 182/89/32 179/86/32
+f 188/87/24 189/90/24 192/91/24
+f 183/88/15 187/92/15 185/83/15
+f 182/89/15 184/82/15 186/93/15
+f 182/89/15 186/93/15 183/88/15
+f 190/96/15 192/91/15 191/94/15
+f 187/92/2 191/94/2 189/90/2
+f 188/87/17 192/91/17 186/93/17
+f 187/92/3 186/93/3 191/94/3
+f 141/26/54 197/98/54 144/43/54
+f 143/44/53 144/43/53 195/99/53
+f 138/27/52 143/44/52 194/100/52
+f 138/27/51 194/100/51 141/26/51
+f 200/37/59 208/102/59 201/31/59
+f 200/37/60 199/35/60 208/102/60
+f 199/35/61 198/36/61 207/103/61
+f 201/31/79 209/101/79 198/36/79
+f 205/108/54 209/101/54 204/105/54
+f 203/106/53 204/105/53 207/103/53
+f 202/107/52 203/106/52 206/104/52
+f 202/107/51 206/104/51 205/108/51
+f 194/100/63 202/107/63 197/98/63
+f 194/100/64 195/99/64 202/107/64
+f 195/99/65 196/97/65 203/106/65
+f 197/98/66 205/108/66 196/97/66
+f 170/73/80 174/79/80 215/109/80
+f 177/80/44 175/81/44 218/78/44
+f 111/65/44 170/73/44 210/75/44
+f 216/71/81 173/74/81 218/78/81
+f 118/60/23 151/61/23 210/75/23
+f 139/42/44 173/74/44 212/1/44
+f 120/11/44 139/42/44 211/2/44
+usemtl Material.003
+f 41/62/82 104/64/82 102/2/82
+f 211/2/82 213/64/82 150/62/82
+f 11/11/82 41/62/82 102/2/82
+f 120/11/82 211/2/82 150/62/82
diff --git a/premake4.lua b/premake4.lua
new file mode 100644
index 0000000..cd5b295
--- /dev/null
+++ b/premake4.lua
@@ -0,0 +1,29 @@
+sources = {
+ "loader_example.cc",
+ }
+
+-- premake4.lua
+solution "TinyObjLoaderSolution"
+ configurations { "Release", "Debug" }
+
+ if (os.is("windows")) then
+ platforms { "x32", "x64" }
+ else
+ platforms { "native", "x32", "x64" }
+ end
+
+ -- A project defines one build target
+ project "tinyobjloader"
+ kind "ConsoleApp"
+ language "C++"
+ files { sources }
+
+ configuration "Debug"
+ defines { "DEBUG" } -- -DDEBUG
+ flags { "Symbols" }
+ targetname "loader_example_debug"
+
+ configuration "Release"
+ -- defines { "NDEBUG" } -- -NDEBUG
+ flags { "Symbols", "Optimize" }
+ targetname "loader_example"
diff --git a/python/TODO.md b/python/TODO.md
new file mode 100644
index 0000000..621e79f
--- /dev/null
+++ b/python/TODO.md
@@ -0,0 +1,2 @@
+* PBR material
+* Define index_t struct
diff --git a/python/cornell_box_multimaterial_output.json b/python/cornell_box_multimaterial_output.json
new file mode 100644
index 0000000..ac2de10
--- /dev/null
+++ b/python/cornell_box_multimaterial_output.json
@@ -0,0 +1,581 @@
+{
+ "shapes": {
+ "back_wall": {
+ "material_ids": [
+ 0.0,
+ 0.0
+ ],
+ "normals": [],
+ "positions": [
+ 549.5999755859375,
+ 0.0,
+ 559.2000122070312,
+ 0.0,
+ 0.0,
+ 559.2000122070312,
+ 0.0,
+ 548.7999877929688,
+ 559.2000122070312,
+ 556.0,
+ 548.7999877929688,
+ 559.2000122070312
+ ],
+ "indicies": [
+ 0.0,
+ 1.0,
+ 2.0,
+ 0.0,
+ 2.0,
+ 3.0
+ ],
+ "texcoords": []
+ },
+ "floor": {
+ "material_ids": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "normals": [],
+ "positions": [
+ 552.7999877929688,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 559.2000122070312,
+ 549.5999755859375,
+ 0.0,
+ 559.2000122070312,
+ 290.0,
+ 0.0,
+ 114.0,
+ 240.0,
+ 0.0,
+ 272.0,
+ 82.0,
+ 0.0,
+ 225.0,
+ 130.0,
+ 0.0,
+ 65.0,
+ 472.0,
+ 0.0,
+ 406.0,
+ 314.0,
+ 0.0,
+ 456.0,
+ 265.0,
+ 0.0,
+ 296.0,
+ 423.0,
+ 0.0,
+ 247.0
+ ],
+ "indicies": [
+ 0.0,
+ 1.0,
+ 2.0,
+ 0.0,
+ 2.0,
+ 3.0,
+ 4.0,
+ 5.0,
+ 6.0,
+ 4.0,
+ 6.0,
+ 7.0,
+ 8.0,
+ 9.0,
+ 10.0,
+ 8.0,
+ 10.0,
+ 11.0
+ ],
+ "texcoords": []
+ },
+ "red_wall": {
+ "material_ids": [
+ 1.0,
+ 1.0
+ ],
+ "normals": [],
+ "positions": [
+ 552.7999877929688,
+ 0.0,
+ 0.0,
+ 549.5999755859375,
+ 0.0,
+ 559.2000122070312,
+ 556.0,
+ 548.7999877929688,
+ 559.2000122070312,
+ 556.0,
+ 548.7999877929688,
+ 0.0
+ ],
+ "indicies": [
+ 0.0,
+ 1.0,
+ 2.0,
+ 0.0,
+ 2.0,
+ 3.0
+ ],
+ "texcoords": []
+ },
+ "light": {
+ "material_ids": [
+ 4.0,
+ 4.0
+ ],
+ "normals": [],
+ "positions": [
+ 343.0,
+ 548.0,
+ 227.0,
+ 343.0,
+ 548.0,
+ 332.0,
+ 213.0,
+ 548.0,
+ 332.0,
+ 213.0,
+ 548.0,
+ 227.0
+ ],
+ "indicies": [
+ 0.0,
+ 1.0,
+ 2.0,
+ 0.0,
+ 2.0,
+ 3.0
+ ],
+ "texcoords": []
+ },
+ "tall_block": {
+ "material_ids": [
+ 2.0,
+ 2.0,
+ 2.0,
+ 2.0
+ ],
+ "normals": [],
+ "positions": [
+ 314.0,
+ 0.0,
+ 456.0,
+ 314.0,
+ 330.0,
+ 456.0,
+ 265.0,
+ 330.0,
+ 296.0,
+ 265.0,
+ 0.0,
+ 296.0,
+ 265.0,
+ 0.0,
+ 296.0,
+ 265.0,
+ 330.0,
+ 296.0,
+ 423.0,
+ 330.0,
+ 247.0,
+ 423.0,
+ 0.0,
+ 247.0
+ ],
+ "indicies": [
+ 0.0,
+ 1.0,
+ 2.0,
+ 0.0,
+ 2.0,
+ 3.0,
+ 4.0,
+ 5.0,
+ 6.0,
+ 4.0,
+ 6.0,
+ 7.0
+ ],
+ "texcoords": []
+ },
+ "short_block": {
+ "material_ids": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "normals": [],
+ "positions": [
+ 130.0,
+ 165.0,
+ 65.0,
+ 82.0,
+ 165.0,
+ 225.0,
+ 240.0,
+ 165.0,
+ 272.0,
+ 290.0,
+ 165.0,
+ 114.0,
+ 290.0,
+ 0.0,
+ 114.0,
+ 290.0,
+ 165.0,
+ 114.0,
+ 240.0,
+ 165.0,
+ 272.0,
+ 240.0,
+ 0.0,
+ 272.0,
+ 130.0,
+ 0.0,
+ 65.0,
+ 130.0,
+ 165.0,
+ 65.0,
+ 290.0,
+ 165.0,
+ 114.0,
+ 290.0,
+ 0.0,
+ 114.0,
+ 82.0,
+ 0.0,
+ 225.0,
+ 82.0,
+ 165.0,
+ 225.0,
+ 130.0,
+ 165.0,
+ 65.0,
+ 130.0,
+ 0.0,
+ 65.0,
+ 240.0,
+ 0.0,
+ 272.0,
+ 240.0,
+ 165.0,
+ 272.0,
+ 82.0,
+ 165.0,
+ 225.0,
+ 82.0,
+ 0.0,
+ 225.0
+ ],
+ "indicies": [
+ 0.0,
+ 1.0,
+ 2.0,
+ 0.0,
+ 2.0,
+ 3.0,
+ 4.0,
+ 5.0,
+ 6.0,
+ 4.0,
+ 6.0,
+ 7.0,
+ 8.0,
+ 9.0,
+ 10.0,
+ 8.0,
+ 10.0,
+ 11.0,
+ 12.0,
+ 13.0,
+ 14.0,
+ 12.0,
+ 14.0,
+ 15.0,
+ 16.0,
+ 17.0,
+ 18.0,
+ 16.0,
+ 18.0,
+ 19.0
+ ],
+ "texcoords": []
+ },
+ "green_wall": {
+ "material_ids": [
+ 2.0,
+ 2.0
+ ],
+ "normals": [],
+ "positions": [
+ 0.0,
+ 0.0,
+ 559.2000122070312,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 548.7999877929688,
+ 0.0,
+ 0.0,
+ 548.7999877929688,
+ 559.2000122070312
+ ],
+ "indicies": [
+ 0.0,
+ 1.0,
+ 2.0,
+ 0.0,
+ 2.0,
+ 3.0
+ ],
+ "texcoords": []
+ },
+ "ceiling": {
+ "material_ids": [
+ 0.0,
+ 0.0
+ ],
+ "normals": [],
+ "positions": [
+ 556.0,
+ 548.7999877929688,
+ 0.0,
+ 556.0,
+ 548.7999877929688,
+ 559.2000122070312,
+ 0.0,
+ 548.7999877929688,
+ 559.2000122070312,
+ 0.0,
+ 548.7999877929688,
+ 0.0
+ ],
+ "indicies": [
+ 0.0,
+ 1.0,
+ 2.0,
+ 0.0,
+ 2.0,
+ 3.0
+ ],
+ "texcoords": []
+ }
+ },
+ "materials": {
+ "blue": {
+ "diffuse_texname": "",
+ "ambient_texname": "",
+ "illum": 0,
+ "displacement_texname": "",
+ "alpha_texname": "",
+ "emission": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "transmittance": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "ambient": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "bump_texname": "",
+ "diffuse": [
+ 0.0,
+ 0.0,
+ 1.0
+ ],
+ "shininess": 1.0,
+ "specular_highlight_texname": "",
+ "unknown_parameter": {},
+ "ior": 1.0,
+ "dissolve": 1.0,
+ "specular": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "specular_texname": ""
+ },
+ "white": {
+ "diffuse_texname": "",
+ "ambient_texname": "",
+ "illum": 0,
+ "displacement_texname": "",
+ "alpha_texname": "",
+ "emission": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "transmittance": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "ambient": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "bump_texname": "",
+ "diffuse": [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "shininess": 1.0,
+ "specular_highlight_texname": "",
+ "unknown_parameter": {},
+ "ior": 1.0,
+ "dissolve": 1.0,
+ "specular": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "specular_texname": ""
+ },
+ "red": {
+ "diffuse_texname": "",
+ "ambient_texname": "",
+ "illum": 0,
+ "displacement_texname": "",
+ "alpha_texname": "",
+ "emission": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "transmittance": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "ambient": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "bump_texname": "",
+ "diffuse": [
+ 1.0,
+ 0.0,
+ 0.0
+ ],
+ "shininess": 1.0,
+ "specular_highlight_texname": "",
+ "unknown_parameter": {},
+ "ior": 1.0,
+ "dissolve": 1.0,
+ "specular": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "specular_texname": ""
+ },
+ "light": {
+ "diffuse_texname": "",
+ "ambient_texname": "",
+ "illum": 0,
+ "displacement_texname": "",
+ "alpha_texname": "",
+ "emission": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "transmittance": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "ambient": [
+ 20.0,
+ 20.0,
+ 20.0
+ ],
+ "bump_texname": "",
+ "diffuse": [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "shininess": 1.0,
+ "specular_highlight_texname": "",
+ "unknown_parameter": {},
+ "ior": 1.0,
+ "dissolve": 1.0,
+ "specular": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "specular_texname": ""
+ },
+ "green": {
+ "diffuse_texname": "",
+ "ambient_texname": "",
+ "illum": 0,
+ "displacement_texname": "",
+ "alpha_texname": "",
+ "emission": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "transmittance": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "ambient": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "bump_texname": "",
+ "diffuse": [
+ 0.0,
+ 1.0,
+ 0.0
+ ],
+ "shininess": 1.0,
+ "specular_highlight_texname": "",
+ "unknown_parameter": {},
+ "ior": 1.0,
+ "dissolve": 1.0,
+ "specular": [
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "specular_texname": ""
+ }
+ }
+}
diff --git a/python/howto.py b/python/howto.py
new file mode 100644
index 0000000..a4c6e04
--- /dev/null
+++ b/python/howto.py
@@ -0,0 +1,9 @@
+import tinyobjloader as tol
+import json
+
+model = tol.LoadObj("cornell_box_multimaterial.obj")
+
+#print(model["shapes"], model["materials"])
+print( json.dumps(model, indent=4) )
+
+#see cornell_box_output.json
diff --git a/python/main.cpp b/python/main.cpp
new file mode 100644
index 0000000..4f1d0e0
--- /dev/null
+++ b/python/main.cpp
@@ -0,0 +1,206 @@
+// python2/3 module for tinyobjloader
+//
+// usage:
+// import tinyobjloader as tol
+// model = tol.LoadObj(name)
+// print(model["shapes"])
+// print(model["materials"]
+// note:
+// `shape.mesh.index_t` is represented as flattened array: (vertex_index, normal_index, texcoord_index) * num_faces
+
+#include <Python.h>
+#include <vector>
+#include "../tiny_obj_loader.h"
+
+typedef std::vector<double> vectd;
+typedef std::vector<int> vecti;
+
+PyObject* pyTupleFromfloat3(float array[3]) {
+ int i;
+ PyObject* tuple = PyTuple_New(3);
+
+ for (i = 0; i <= 2; i++) {
+ PyTuple_SetItem(tuple, i, PyFloat_FromDouble(array[i]));
+ }
+
+ return tuple;
+}
+
+extern "C" {
+
+static PyObject* pyLoadObj(PyObject* self, PyObject* args) {
+ PyObject *rtndict, *pyshapes, *pymaterials, *pymaterial_indices, *attribobj, *current, *meshobj;
+
+ char const* current_name;
+ char const* filename;
+ vectd vect;
+ std::vector<tinyobj::index_t> indices;
+ std::vector<unsigned char> face_verts;
+
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ if (!PyArg_ParseTuple(args, "s", &filename)) return NULL;
+
+ std::string err;
+ tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename);
+
+ pyshapes = PyDict_New();
+ pymaterials = PyDict_New();
+ pymaterial_indices = PyList_New(0);
+ rtndict = PyDict_New();
+
+ attribobj = PyDict_New();
+
+ for (int i = 0; i <= 2; i++) {
+ current = PyList_New(0);
+
+ switch (i) {
+ case 0:
+ current_name = "vertices";
+ vect = vectd(attrib.vertices.begin(), attrib.vertices.end());
+ break;
+ case 1:
+ current_name = "normals";
+ vect = vectd(attrib.normals.begin(), attrib.normals.end());
+ break;
+ case 2:
+ current_name = "texcoords";
+ vect = vectd(attrib.texcoords.begin(), attrib.texcoords.end());
+ break;
+ }
+
+ for (vectd::iterator it = vect.begin(); it != vect.end(); it++) {
+ PyList_Insert(current, it - vect.begin(), PyFloat_FromDouble(*it));
+ }
+
+ PyDict_SetItemString(attribobj, current_name, current);
+ }
+
+ for (std::vector<tinyobj::shape_t>::iterator shape = shapes.begin();
+ shape != shapes.end(); shape++) {
+ meshobj = PyDict_New();
+ tinyobj::mesh_t cm = (*shape).mesh;
+
+ {
+ current = PyList_New(0);
+
+ for (size_t i = 0; i < cm.indices.size(); i++) {
+ // Flatten index array: v_idx, vn_idx, vt_idx, v_idx, vn_idx, vt_idx,
+ // ...
+ PyList_Insert(current, 3 * i + 0,
+ PyLong_FromLong(cm.indices[i].vertex_index));
+ PyList_Insert(current, 3 * i + 1,
+ PyLong_FromLong(cm.indices[i].normal_index));
+ PyList_Insert(current, 3 * i + 2,
+ PyLong_FromLong(cm.indices[i].texcoord_index));
+ }
+
+ PyDict_SetItemString(meshobj, "indices", current);
+ }
+
+ {
+ current = PyList_New(0);
+
+ for (size_t i = 0; i < cm.num_face_vertices.size(); i++) {
+ // Widen data type to long.
+ PyList_Insert(current, i, PyLong_FromLong(cm.num_face_vertices[i]));
+ }
+
+ PyDict_SetItemString(meshobj, "num_face_vertices", current);
+ }
+
+ {
+ current = PyList_New(0);
+
+ for (size_t i = 0; i < cm.material_ids.size(); i++) {
+ PyList_Insert(current, i, PyLong_FromLong(cm.material_ids[i]));
+ }
+
+ PyDict_SetItemString(meshobj, "material_ids", current);
+ }
+
+ PyDict_SetItemString(pyshapes, (*shape).name.c_str(), meshobj);
+ }
+
+ for (std::vector<tinyobj::material_t>::iterator mat = materials.begin();
+ mat != materials.end(); mat++) {
+ PyObject* matobj = PyDict_New();
+ PyObject* unknown_parameter = PyDict_New();
+
+ for (std::map<std::string, std::string>::iterator p =
+ mat->unknown_parameter.begin();
+ p != mat->unknown_parameter.end(); ++p) {
+ PyDict_SetItemString(unknown_parameter, p->first.c_str(),
+ PyUnicode_FromString(p->second.c_str()));
+ }
+
+ PyDict_SetItemString(matobj, "shininess",
+ PyFloat_FromDouble(mat->shininess));
+ PyDict_SetItemString(matobj, "ior", PyFloat_FromDouble(mat->ior));
+ PyDict_SetItemString(matobj, "dissolve",
+ PyFloat_FromDouble(mat->dissolve));
+ PyDict_SetItemString(matobj, "illum", PyLong_FromLong(mat->illum));
+ PyDict_SetItemString(matobj, "ambient_texname",
+ PyUnicode_FromString(mat->ambient_texname.c_str()));
+ PyDict_SetItemString(matobj, "diffuse_texname",
+ PyUnicode_FromString(mat->diffuse_texname.c_str()));
+ PyDict_SetItemString(matobj, "specular_texname",
+ PyUnicode_FromString(mat->specular_texname.c_str()));
+ PyDict_SetItemString(
+ matobj, "specular_highlight_texname",
+ PyUnicode_FromString(mat->specular_highlight_texname.c_str()));
+ PyDict_SetItemString(matobj, "bump_texname",
+ PyUnicode_FromString(mat->bump_texname.c_str()));
+ PyDict_SetItemString(
+ matobj, "displacement_texname",
+ PyUnicode_FromString(mat->displacement_texname.c_str()));
+ PyDict_SetItemString(matobj, "alpha_texname",
+ PyUnicode_FromString(mat->alpha_texname.c_str()));
+ PyDict_SetItemString(matobj, "ambient", pyTupleFromfloat3(mat->ambient));
+ PyDict_SetItemString(matobj, "diffuse", pyTupleFromfloat3(mat->diffuse));
+ PyDict_SetItemString(matobj, "specular",
+ pyTupleFromfloat3(mat->specular));
+ PyDict_SetItemString(matobj, "transmittance",
+ pyTupleFromfloat3(mat->transmittance));
+ PyDict_SetItemString(matobj, "emission",
+ pyTupleFromfloat3(mat->emission));
+ PyDict_SetItemString(matobj, "unknown_parameter", unknown_parameter);
+
+ PyDict_SetItemString(pymaterials, mat->name.c_str(), matobj);
+ PyList_Append(pymaterial_indices, PyUnicode_FromString(mat->name.c_str()));
+ }
+
+ PyDict_SetItemString(rtndict, "shapes", pyshapes);
+ PyDict_SetItemString(rtndict, "materials", pymaterials);
+ PyDict_SetItemString(rtndict, "material_indices", pymaterial_indices);
+ PyDict_SetItemString(rtndict, "attribs", attribobj);
+
+ return rtndict;
+}
+
+static PyMethodDef mMethods[] = {
+
+ {"LoadObj", pyLoadObj, METH_VARARGS}, {NULL, NULL, 0, NULL}
+
+};
+
+#if PY_MAJOR_VERSION >= 3
+
+static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, "tinyobjloader",
+ NULL, -1, mMethods};
+
+PyMODINIT_FUNC PyInit_tinyobjloader(void) {
+ return PyModule_Create(&moduledef);
+}
+
+#else
+
+PyMODINIT_FUNC inittinyobjloader(void) {
+ Py_InitModule3("tinyobjloader", mMethods, NULL);
+}
+
+#endif // PY_MAJOR_VERSION >= 3
+
+}
diff --git a/python/setup.py b/python/setup.py
new file mode 100644
index 0000000..de7b976
--- /dev/null
+++ b/python/setup.py
@@ -0,0 +1,13 @@
+from distutils.core import setup, Extension
+
+
+m = Extension('tinyobjloader',
+ sources = ['main.cpp', '../tiny_obj_loader.cc'])
+
+
+setup (name = 'tinyobjloader',
+ version = '0.1',
+ description = 'Python module for tinyobjloader',
+ ext_modules = [m])
+
+
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000..4a18c71
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,16 @@
+.PHONY: clean
+
+CXX ?= g++
+CXXFLAGS ?= -g -O2
+
+tester: tester.cc
+ $(CXX) $(CXXFLAGS) -o tester tester.cc
+
+all: tester
+
+check: tester
+ ./tester
+
+clean:
+ rm -rf tester
+
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..1b0b43d
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,37 @@
+# Build&Test
+
+## Use makefile
+
+ $ make check
+
+## Use ninja + kuroga
+
+Assume
+
+* ninja 1.4+
+* python 2.6+
+
+Are installed.
+
+### Linux/MacOSX
+
+ $ python kuroga.py config-posix.py
+ $ ninja
+
+### Windows
+
+Visual Studio 2013 is required to build tester.
+
+On Windows console.
+
+ > python kuroga.py config-msvc.py
+ > vcbuild.bat
+
+
+Or on msys2 bash,
+
+ $ python kuroga.py config-msvc.py
+ $ cmd //c vcbuild.bat
+
+
+
diff --git a/tests/catch.hpp b/tests/catch.hpp
new file mode 100644
index 0000000..2a7146a
--- /dev/null
+++ b/tests/catch.hpp
@@ -0,0 +1,10445 @@
+/*
+ * Catch v1.4.0
+ * Generated: 2016-03-15 07:23:12.623111
+ * ----------------------------------------------------------
+ * This file has been merged from multiple headers. Please don't edit it directly
+ * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved.
+ *
+ * Distributed under the Boost Software License, Version 1.0. (See accompanying
+ * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ */
+#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+
+#define TWOBLUECUBES_CATCH_HPP_INCLUDED
+
+#ifdef __clang__
+# pragma clang system_header
+#elif defined __GNUC__
+# pragma GCC system_header
+#endif
+
+// #included from: internal/catch_suppress_warnings.h
+
+#ifdef __clang__
+# ifdef __ICC // icpc defines the __clang__ macro
+# pragma warning(push)
+# pragma warning(disable: 161 1682)
+# else // __ICC
+# pragma clang diagnostic ignored "-Wglobal-constructors"
+# pragma clang diagnostic ignored "-Wvariadic-macros"
+# pragma clang diagnostic ignored "-Wc99-extensions"
+# pragma clang diagnostic ignored "-Wunused-variable"
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wpadded"
+# pragma clang diagnostic ignored "-Wc++98-compat"
+# pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+# pragma clang diagnostic ignored "-Wswitch-enum"
+# pragma clang diagnostic ignored "-Wcovered-switch-default"
+# endif
+#elif defined __GNUC__
+# pragma GCC diagnostic ignored "-Wvariadic-macros"
+# pragma GCC diagnostic ignored "-Wunused-variable"
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wpadded"
+#endif
+#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)
+# define CATCH_IMPL
+#endif
+
+#ifdef CATCH_IMPL
+# ifndef CLARA_CONFIG_MAIN
+# define CLARA_CONFIG_MAIN_NOT_DEFINED
+# define CLARA_CONFIG_MAIN
+# endif
+#endif
+
+// #included from: internal/catch_notimplemented_exception.h
+#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED
+
+// #included from: catch_common.h
+#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED
+
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
+#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )
+#ifdef CATCH_CONFIG_COUNTER
+# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )
+#else
+# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )
+#endif
+
+#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr
+#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr )
+
+#include <sstream>
+#include <stdexcept>
+#include <algorithm>
+
+// #included from: catch_compiler_capabilities.h
+#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED
+
+// Detect a number of compiler features - mostly C++11/14 conformance - by compiler
+// The following features are defined:
+//
+// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported?
+// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported?
+// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods
+// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported?
+// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported
+// CATCH_CONFIG_CPP11_LONG_LONG : is long long supported?
+// CATCH_CONFIG_CPP11_OVERRIDE : is override supported?
+// CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr)
+
+// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported?
+
+// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported?
+// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported?
+// ****************
+// Note to maintainers: if new toggles are added please document them
+// in configuration.md, too
+// ****************
+
+// In general each macro has a _NO_<feature name> form
+// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature.
+// Many features, at point of detection, define an _INTERNAL_ macro, so they
+// can be combined, en-mass, with the _NO_ forms later.
+
+// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11
+
+#if defined(__cplusplus) && __cplusplus >= 201103L
+# define CATCH_CPP11_OR_GREATER
+#endif
+
+#ifdef __clang__
+
+# if __has_feature(cxx_nullptr)
+# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+# endif
+
+# if __has_feature(cxx_noexcept)
+# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+# endif
+
+# if defined(CATCH_CPP11_OR_GREATER)
+# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "clang diagnostic ignored \"-Wparentheses\"" )
+# endif
+
+#endif // __clang__
+
+////////////////////////////////////////////////////////////////////////////////
+// Borland
+#ifdef __BORLANDC__
+
+#endif // __BORLANDC__
+
+////////////////////////////////////////////////////////////////////////////////
+// EDG
+#ifdef __EDG_VERSION__
+
+#endif // __EDG_VERSION__
+
+////////////////////////////////////////////////////////////////////////////////
+// Digital Mars
+#ifdef __DMC__
+
+#endif // __DMC__
+
+////////////////////////////////////////////////////////////////////////////////
+// GCC
+#ifdef __GNUC__
+
+# if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__)
+# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+# endif
+
+# if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) && defined(CATCH_CPP11_OR_GREATER)
+# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" )
+# endif
+
+// - otherwise more recent versions define __cplusplus >= 201103L
+// and will get picked up below
+
+#endif // __GNUC__
+
+////////////////////////////////////////////////////////////////////////////////
+// Visual C++
+#ifdef _MSC_VER
+
+#if (_MSC_VER >= 1600)
+# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
+#endif
+
+#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015))
+#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#endif
+
+#endif // _MSC_VER
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Use variadic macros if the compiler supports them
+#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \
+ ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \
+ ( defined __GNUC__ && __GNUC__ >= 3 ) || \
+ ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L )
+
+#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
+
+#endif
+
+// Use __COUNTER__ if the compiler supports it
+#if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \
+ ( defined __GNUC__ && __GNUC__ >= 4 && __GNUC_MINOR__ >= 3 ) || \
+ ( defined __clang__ && __clang_major__ >= 3 )
+
+#define CATCH_INTERNAL_CONFIG_COUNTER
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// C++ language feature support
+
+// catch all support for C++11
+#if defined(CATCH_CPP11_OR_GREATER)
+
+# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR)
+# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR
+# endif
+
+# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT
+# endif
+
+# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+# endif
+
+# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM
+# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM
+# endif
+
+# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE
+# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE
+# endif
+
+# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
+# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS
+# endif
+
+# if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG)
+# define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG
+# endif
+
+# if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE)
+# define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE
+# endif
+# if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR)
+# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
+# endif
+
+#endif // __cplusplus >= 201103L
+
+// Now set the actual defines based on the above + anything the user has configured
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11)
+# define CATCH_CONFIG_CPP11_NULLPTR
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11)
+# define CATCH_CONFIG_CPP11_NOEXCEPT
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11)
+# define CATCH_CONFIG_CPP11_GENERATED_METHODS
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11)
+# define CATCH_CONFIG_CPP11_IS_ENUM
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11)
+# define CATCH_CONFIG_CPP11_TUPLE
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS)
+# define CATCH_CONFIG_VARIADIC_MACROS
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11)
+# define CATCH_CONFIG_CPP11_LONG_LONG
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11)
+# define CATCH_CONFIG_CPP11_OVERRIDE
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11)
+# define CATCH_CONFIG_CPP11_UNIQUE_PTR
+#endif
+#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER)
+# define CATCH_CONFIG_COUNTER
+#endif
+
+#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)
+# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
+#endif
+
+// noexcept support:
+#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT)
+# define CATCH_NOEXCEPT noexcept
+# define CATCH_NOEXCEPT_IS(x) noexcept(x)
+#else
+# define CATCH_NOEXCEPT throw()
+# define CATCH_NOEXCEPT_IS(x)
+#endif
+
+// nullptr support
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+# define CATCH_NULL nullptr
+#else
+# define CATCH_NULL NULL
+#endif
+
+// override support
+#ifdef CATCH_CONFIG_CPP11_OVERRIDE
+# define CATCH_OVERRIDE override
+#else
+# define CATCH_OVERRIDE
+#endif
+
+// unique_ptr support
+#ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR
+# define CATCH_AUTO_PTR( T ) std::unique_ptr<T>
+#else
+# define CATCH_AUTO_PTR( T ) std::auto_ptr<T>
+#endif
+
+namespace Catch {
+
+ struct IConfig;
+
+ struct CaseSensitive { enum Choice {
+ Yes,
+ No
+ }; };
+
+ class NonCopyable {
+#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ NonCopyable( NonCopyable const& ) = delete;
+ NonCopyable( NonCopyable && ) = delete;
+ NonCopyable& operator = ( NonCopyable const& ) = delete;
+ NonCopyable& operator = ( NonCopyable && ) = delete;
+#else
+ NonCopyable( NonCopyable const& info );
+ NonCopyable& operator = ( NonCopyable const& );
+#endif
+
+ protected:
+ NonCopyable() {}
+ virtual ~NonCopyable();
+ };
+
+ class SafeBool {
+ public:
+ typedef void (SafeBool::*type)() const;
+
+ static type makeSafe( bool value ) {
+ return value ? &SafeBool::trueValue : 0;
+ }
+ private:
+ void trueValue() const {}
+ };
+
+ template<typename ContainerT>
+ inline void deleteAll( ContainerT& container ) {
+ typename ContainerT::const_iterator it = container.begin();
+ typename ContainerT::const_iterator itEnd = container.end();
+ for(; it != itEnd; ++it )
+ delete *it;
+ }
+ template<typename AssociativeContainerT>
+ inline void deleteAllValues( AssociativeContainerT& container ) {
+ typename AssociativeContainerT::const_iterator it = container.begin();
+ typename AssociativeContainerT::const_iterator itEnd = container.end();
+ for(; it != itEnd; ++it )
+ delete it->second;
+ }
+
+ bool startsWith( std::string const& s, std::string const& prefix );
+ bool endsWith( std::string const& s, std::string const& suffix );
+ bool contains( std::string const& s, std::string const& infix );
+ void toLowerInPlace( std::string& s );
+ std::string toLower( std::string const& s );
+ std::string trim( std::string const& str );
+ bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );
+
+ struct pluralise {
+ pluralise( std::size_t count, std::string const& label );
+
+ friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser );
+
+ std::size_t m_count;
+ std::string m_label;
+ };
+
+ struct SourceLineInfo {
+
+ SourceLineInfo();
+ SourceLineInfo( char const* _file, std::size_t _line );
+ SourceLineInfo( SourceLineInfo const& other );
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ SourceLineInfo( SourceLineInfo && ) = default;
+ SourceLineInfo& operator = ( SourceLineInfo const& ) = default;
+ SourceLineInfo& operator = ( SourceLineInfo && ) = default;
+# endif
+ bool empty() const;
+ bool operator == ( SourceLineInfo const& other ) const;
+ bool operator < ( SourceLineInfo const& other ) const;
+
+ std::string file;
+ std::size_t line;
+ };
+
+ std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info );
+
+ // This is just here to avoid compiler warnings with macro constants and boolean literals
+ inline bool isTrue( bool value ){ return value; }
+ inline bool alwaysTrue() { return true; }
+ inline bool alwaysFalse() { return false; }
+
+ void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo );
+
+ void seedRng( IConfig const& config );
+ unsigned int rngSeed();
+
+ // Use this in variadic streaming macros to allow
+ // >> +StreamEndStop
+ // as well as
+ // >> stuff +StreamEndStop
+ struct StreamEndStop {
+ std::string operator+() {
+ return std::string();
+ }
+ };
+ template<typename T>
+ T const& operator + ( T const& value, StreamEndStop ) {
+ return value;
+ }
+}
+
+#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) )
+#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO );
+
+#include <ostream>
+
+namespace Catch {
+
+ class NotImplementedException : public std::exception
+ {
+ public:
+ NotImplementedException( SourceLineInfo const& lineInfo );
+ NotImplementedException( NotImplementedException const& ) {}
+
+ virtual ~NotImplementedException() CATCH_NOEXCEPT {}
+
+ virtual const char* what() const CATCH_NOEXCEPT;
+
+ private:
+ std::string m_what;
+ SourceLineInfo m_lineInfo;
+ };
+
+} // end namespace Catch
+
+///////////////////////////////////////////////////////////////////////////////
+#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO )
+
+// #included from: internal/catch_context.h
+#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED
+
+// #included from: catch_interfaces_generators.h
+#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ struct IGeneratorInfo {
+ virtual ~IGeneratorInfo();
+ virtual bool moveNext() = 0;
+ virtual std::size_t getCurrentIndex() const = 0;
+ };
+
+ struct IGeneratorsForTest {
+ virtual ~IGeneratorsForTest();
+
+ virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0;
+ virtual bool moveNext() = 0;
+ };
+
+ IGeneratorsForTest* createGeneratorsForTest();
+
+} // end namespace Catch
+
+// #included from: catch_ptr.hpp
+#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+namespace Catch {
+
+ // An intrusive reference counting smart pointer.
+ // T must implement addRef() and release() methods
+ // typically implementing the IShared interface
+ template<typename T>
+ class Ptr {
+ public:
+ Ptr() : m_p( CATCH_NULL ){}
+ Ptr( T* p ) : m_p( p ){
+ if( m_p )
+ m_p->addRef();
+ }
+ Ptr( Ptr const& other ) : m_p( other.m_p ){
+ if( m_p )
+ m_p->addRef();
+ }
+ ~Ptr(){
+ if( m_p )
+ m_p->release();
+ }
+ void reset() {
+ if( m_p )
+ m_p->release();
+ m_p = CATCH_NULL;
+ }
+ Ptr& operator = ( T* p ){
+ Ptr temp( p );
+ swap( temp );
+ return *this;
+ }
+ Ptr& operator = ( Ptr const& other ){
+ Ptr temp( other );
+ swap( temp );
+ return *this;
+ }
+ void swap( Ptr& other ) { std::swap( m_p, other.m_p ); }
+ T* get() const{ return m_p; }
+ T& operator*() const { return *m_p; }
+ T* operator->() const { return m_p; }
+ bool operator !() const { return m_p == CATCH_NULL; }
+ operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); }
+
+ private:
+ T* m_p;
+ };
+
+ struct IShared : NonCopyable {
+ virtual ~IShared();
+ virtual void addRef() const = 0;
+ virtual void release() const = 0;
+ };
+
+ template<typename T = IShared>
+ struct SharedImpl : T {
+
+ SharedImpl() : m_rc( 0 ){}
+
+ virtual void addRef() const {
+ ++m_rc;
+ }
+ virtual void release() const {
+ if( --m_rc == 0 )
+ delete this;
+ }
+
+ mutable unsigned int m_rc;
+ };
+
+} // end namespace Catch
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#include <memory>
+#include <vector>
+#include <stdlib.h>
+
+namespace Catch {
+
+ class TestCase;
+ class Stream;
+ struct IResultCapture;
+ struct IRunner;
+ struct IGeneratorsForTest;
+ struct IConfig;
+
+ struct IContext
+ {
+ virtual ~IContext();
+
+ virtual IResultCapture* getResultCapture() = 0;
+ virtual IRunner* getRunner() = 0;
+ virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0;
+ virtual bool advanceGeneratorsForCurrentTest() = 0;
+ virtual Ptr<IConfig const> getConfig() const = 0;
+ };
+
+ struct IMutableContext : IContext
+ {
+ virtual ~IMutableContext();
+ virtual void setResultCapture( IResultCapture* resultCapture ) = 0;
+ virtual void setRunner( IRunner* runner ) = 0;
+ virtual void setConfig( Ptr<IConfig const> const& config ) = 0;
+ };
+
+ IContext& getCurrentContext();
+ IMutableContext& getCurrentMutableContext();
+ void cleanUpContext();
+ Stream createStream( std::string const& streamName );
+
+}
+
+// #included from: internal/catch_test_registry.hpp
+#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED
+
+// #included from: catch_interfaces_testcase.h
+#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED
+
+#include <vector>
+
+namespace Catch {
+
+ class TestSpec;
+
+ struct ITestCase : IShared {
+ virtual void invoke () const = 0;
+ protected:
+ virtual ~ITestCase();
+ };
+
+ class TestCase;
+ struct IConfig;
+
+ struct ITestCaseRegistry {
+ virtual ~ITestCaseRegistry();
+ virtual std::vector<TestCase> const& getAllTests() const = 0;
+ virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0;
+ };
+
+ bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );
+ std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );
+ std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );
+
+}
+
+namespace Catch {
+
+template<typename C>
+class MethodTestCase : public SharedImpl<ITestCase> {
+
+public:
+ MethodTestCase( void (C::*method)() ) : m_method( method ) {}
+
+ virtual void invoke() const {
+ C obj;
+ (obj.*m_method)();
+ }
+
+private:
+ virtual ~MethodTestCase() {}
+
+ void (C::*m_method)();
+};
+
+typedef void(*TestFunction)();
+
+struct NameAndDesc {
+ NameAndDesc( const char* _name = "", const char* _description= "" )
+ : name( _name ), description( _description )
+ {}
+
+ const char* name;
+ const char* description;
+};
+
+void registerTestCase
+ ( ITestCase* testCase,
+ char const* className,
+ NameAndDesc const& nameAndDesc,
+ SourceLineInfo const& lineInfo );
+
+struct AutoReg {
+
+ AutoReg
+ ( TestFunction function,
+ SourceLineInfo const& lineInfo,
+ NameAndDesc const& nameAndDesc );
+
+ template<typename C>
+ AutoReg
+ ( void (C::*method)(),
+ char const* className,
+ NameAndDesc const& nameAndDesc,
+ SourceLineInfo const& lineInfo ) {
+
+ registerTestCase
+ ( new MethodTestCase<C>( method ),
+ className,
+ nameAndDesc,
+ lineInfo );
+ }
+
+ ~AutoReg();
+
+private:
+ AutoReg( AutoReg const& );
+ void operator= ( AutoReg const& );
+};
+
+void registerTestCaseFunction
+ ( TestFunction function,
+ SourceLineInfo const& lineInfo,
+ NameAndDesc const& nameAndDesc );
+
+} // end namespace Catch
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \
+ static void TestName(); \
+ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\
+ static void TestName()
+ #define INTERNAL_CATCH_TESTCASE( ... ) \
+ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ )
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \
+ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\
+ namespace{ \
+ struct TestName : ClassName{ \
+ void test(); \
+ }; \
+ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \
+ } \
+ void TestName::test()
+ #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \
+ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ )
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \
+ Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) );
+
+#else
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \
+ static void TestName(); \
+ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\
+ static void TestName()
+ #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \
+ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc )
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \
+ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\
+ namespace{ \
+ struct TestCaseName : ClassName{ \
+ void test(); \
+ }; \
+ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \
+ } \
+ void TestCaseName::test()
+ #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\
+ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc )
+
+ ///////////////////////////////////////////////////////////////////////////////
+ #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \
+ Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) );
+#endif
+
+// #included from: internal/catch_capture.hpp
+#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED
+
+// #included from: catch_result_builder.h
+#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED
+
+// #included from: catch_result_type.h
+#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED
+
+namespace Catch {
+
+ // ResultWas::OfType enum
+ struct ResultWas { enum OfType {
+ Unknown = -1,
+ Ok = 0,
+ Info = 1,
+ Warning = 2,
+
+ FailureBit = 0x10,
+
+ ExpressionFailed = FailureBit | 1,
+ ExplicitFailure = FailureBit | 2,
+
+ Exception = 0x100 | FailureBit,
+
+ ThrewException = Exception | 1,
+ DidntThrowException = Exception | 2,
+
+ FatalErrorCondition = 0x200 | FailureBit
+
+ }; };
+
+ inline bool isOk( ResultWas::OfType resultType ) {
+ return ( resultType & ResultWas::FailureBit ) == 0;
+ }
+ inline bool isJustInfo( int flags ) {
+ return flags == ResultWas::Info;
+ }
+
+ // ResultDisposition::Flags enum
+ struct ResultDisposition { enum Flags {
+ Normal = 0x01,
+
+ ContinueOnFailure = 0x02, // Failures fail test, but execution continues
+ FalseTest = 0x04, // Prefix expression with !
+ SuppressFail = 0x08 // Failures are reported but do not fail the test
+ }; };
+
+ inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) {
+ return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) );
+ }
+
+ inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; }
+ inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; }
+ inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; }
+
+} // end namespace Catch
+
+// #included from: catch_assertionresult.h
+#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ struct AssertionInfo
+ {
+ AssertionInfo() {}
+ AssertionInfo( std::string const& _macroName,
+ SourceLineInfo const& _lineInfo,
+ std::string const& _capturedExpression,
+ ResultDisposition::Flags _resultDisposition );
+
+ std::string macroName;
+ SourceLineInfo lineInfo;
+ std::string capturedExpression;
+ ResultDisposition::Flags resultDisposition;
+ };
+
+ struct AssertionResultData
+ {
+ AssertionResultData() : resultType( ResultWas::Unknown ) {}
+
+ std::string reconstructedExpression;
+ std::string message;
+ ResultWas::OfType resultType;
+ };
+
+ class AssertionResult {
+ public:
+ AssertionResult();
+ AssertionResult( AssertionInfo const& info, AssertionResultData const& data );
+ ~AssertionResult();
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ AssertionResult( AssertionResult const& ) = default;
+ AssertionResult( AssertionResult && ) = default;
+ AssertionResult& operator = ( AssertionResult const& ) = default;
+ AssertionResult& operator = ( AssertionResult && ) = default;
+# endif
+
+ bool isOk() const;
+ bool succeeded() const;
+ ResultWas::OfType getResultType() const;
+ bool hasExpression() const;
+ bool hasMessage() const;
+ std::string getExpression() const;
+ std::string getExpressionInMacro() const;
+ bool hasExpandedExpression() const;
+ std::string getExpandedExpression() const;
+ std::string getMessage() const;
+ SourceLineInfo getSourceInfo() const;
+ std::string getTestMacroName() const;
+
+ protected:
+ AssertionInfo m_info;
+ AssertionResultData m_resultData;
+ };
+
+} // end namespace Catch
+
+// #included from: catch_matchers.hpp
+#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED
+
+namespace Catch {
+namespace Matchers {
+ namespace Impl {
+
+ namespace Generic {
+ template<typename ExpressionT> class AllOf;
+ template<typename ExpressionT> class AnyOf;
+ template<typename ExpressionT> class Not;
+ }
+
+ template<typename ExpressionT>
+ struct Matcher : SharedImpl<IShared>
+ {
+ typedef ExpressionT ExpressionType;
+
+ virtual ~Matcher() {}
+ virtual Ptr<Matcher> clone() const = 0;
+ virtual bool match( ExpressionT const& expr ) const = 0;
+ virtual std::string toString() const = 0;
+
+ Generic::AllOf<ExpressionT> operator && ( Matcher<ExpressionT> const& other ) const;
+ Generic::AnyOf<ExpressionT> operator || ( Matcher<ExpressionT> const& other ) const;
+ Generic::Not<ExpressionT> operator ! () const;
+ };
+
+ template<typename DerivedT, typename ExpressionT>
+ struct MatcherImpl : Matcher<ExpressionT> {
+
+ virtual Ptr<Matcher<ExpressionT> > clone() const {
+ return Ptr<Matcher<ExpressionT> >( new DerivedT( static_cast<DerivedT const&>( *this ) ) );
+ }
+ };
+
+ namespace Generic {
+ template<typename ExpressionT>
+ class Not : public MatcherImpl<Not<ExpressionT>, ExpressionT> {
+ public:
+ explicit Not( Matcher<ExpressionT> const& matcher ) : m_matcher(matcher.clone()) {}
+ Not( Not const& other ) : m_matcher( other.m_matcher ) {}
+
+ virtual bool match( ExpressionT const& expr ) const CATCH_OVERRIDE {
+ return !m_matcher->match( expr );
+ }
+
+ virtual std::string toString() const CATCH_OVERRIDE {
+ return "not " + m_matcher->toString();
+ }
+ private:
+ Ptr< Matcher<ExpressionT> > m_matcher;
+ };
+
+ template<typename ExpressionT>
+ class AllOf : public MatcherImpl<AllOf<ExpressionT>, ExpressionT> {
+ public:
+
+ AllOf() {}
+ AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {}
+
+ AllOf& add( Matcher<ExpressionT> const& matcher ) {
+ m_matchers.push_back( matcher.clone() );
+ return *this;
+ }
+ virtual bool match( ExpressionT const& expr ) const
+ {
+ for( std::size_t i = 0; i < m_matchers.size(); ++i )
+ if( !m_matchers[i]->match( expr ) )
+ return false;
+ return true;
+ }
+ virtual std::string toString() const {
+ std::ostringstream oss;
+ oss << "( ";
+ for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
+ if( i != 0 )
+ oss << " and ";
+ oss << m_matchers[i]->toString();
+ }
+ oss << " )";
+ return oss.str();
+ }
+
+ AllOf operator && ( Matcher<ExpressionT> const& other ) const {
+ AllOf allOfExpr( *this );
+ allOfExpr.add( other );
+ return allOfExpr;
+ }
+
+ private:
+ std::vector<Ptr<Matcher<ExpressionT> > > m_matchers;
+ };
+
+ template<typename ExpressionT>
+ class AnyOf : public MatcherImpl<AnyOf<ExpressionT>, ExpressionT> {
+ public:
+
+ AnyOf() {}
+ AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {}
+
+ AnyOf& add( Matcher<ExpressionT> const& matcher ) {
+ m_matchers.push_back( matcher.clone() );
+ return *this;
+ }
+ virtual bool match( ExpressionT const& expr ) const
+ {
+ for( std::size_t i = 0; i < m_matchers.size(); ++i )
+ if( m_matchers[i]->match( expr ) )
+ return true;
+ return false;
+ }
+ virtual std::string toString() const {
+ std::ostringstream oss;
+ oss << "( ";
+ for( std::size_t i = 0; i < m_matchers.size(); ++i ) {
+ if( i != 0 )
+ oss << " or ";
+ oss << m_matchers[i]->toString();
+ }
+ oss << " )";
+ return oss.str();
+ }
+
+ AnyOf operator || ( Matcher<ExpressionT> const& other ) const {
+ AnyOf anyOfExpr( *this );
+ anyOfExpr.add( other );
+ return anyOfExpr;
+ }
+
+ private:
+ std::vector<Ptr<Matcher<ExpressionT> > > m_matchers;
+ };
+
+ } // namespace Generic
+
+ template<typename ExpressionT>
+ Generic::AllOf<ExpressionT> Matcher<ExpressionT>::operator && ( Matcher<ExpressionT> const& other ) const {
+ Generic::AllOf<ExpressionT> allOfExpr;
+ allOfExpr.add( *this );
+ allOfExpr.add( other );
+ return allOfExpr;
+ }
+
+ template<typename ExpressionT>
+ Generic::AnyOf<ExpressionT> Matcher<ExpressionT>::operator || ( Matcher<ExpressionT> const& other ) const {
+ Generic::AnyOf<ExpressionT> anyOfExpr;
+ anyOfExpr.add( *this );
+ anyOfExpr.add( other );
+ return anyOfExpr;
+ }
+
+ template<typename ExpressionT>
+ Generic::Not<ExpressionT> Matcher<ExpressionT>::operator ! () const {
+ return Generic::Not<ExpressionT>( *this );
+ }
+
+ namespace StdString {
+
+ inline std::string makeString( std::string const& str ) { return str; }
+ inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); }
+
+ struct CasedString
+ {
+ CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity )
+ : m_caseSensitivity( caseSensitivity ),
+ m_str( adjustString( str ) )
+ {}
+ std::string adjustString( std::string const& str ) const {
+ return m_caseSensitivity == CaseSensitive::No
+ ? toLower( str )
+ : str;
+
+ }
+ std::string toStringSuffix() const
+ {
+ return m_caseSensitivity == CaseSensitive::No
+ ? " (case insensitive)"
+ : "";
+ }
+ CaseSensitive::Choice m_caseSensitivity;
+ std::string m_str;
+ };
+
+ struct Equals : MatcherImpl<Equals, std::string> {
+ Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
+ : m_data( str, caseSensitivity )
+ {}
+ Equals( Equals const& other ) : m_data( other.m_data ){}
+
+ virtual ~Equals();
+
+ virtual bool match( std::string const& expr ) const {
+ return m_data.m_str == m_data.adjustString( expr );;
+ }
+ virtual std::string toString() const {
+ return "equals: \"" + m_data.m_str + "\"" + m_data.toStringSuffix();
+ }
+
+ CasedString m_data;
+ };
+
+ struct Contains : MatcherImpl<Contains, std::string> {
+ Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
+ : m_data( substr, caseSensitivity ){}
+ Contains( Contains const& other ) : m_data( other.m_data ){}
+
+ virtual ~Contains();
+
+ virtual bool match( std::string const& expr ) const {
+ return m_data.adjustString( expr ).find( m_data.m_str ) != std::string::npos;
+ }
+ virtual std::string toString() const {
+ return "contains: \"" + m_data.m_str + "\"" + m_data.toStringSuffix();
+ }
+
+ CasedString m_data;
+ };
+
+ struct StartsWith : MatcherImpl<StartsWith, std::string> {
+ StartsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
+ : m_data( substr, caseSensitivity ){}
+
+ StartsWith( StartsWith const& other ) : m_data( other.m_data ){}
+
+ virtual ~StartsWith();
+
+ virtual bool match( std::string const& expr ) const {
+ return startsWith( m_data.adjustString( expr ), m_data.m_str );
+ }
+ virtual std::string toString() const {
+ return "starts with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix();
+ }
+
+ CasedString m_data;
+ };
+
+ struct EndsWith : MatcherImpl<EndsWith, std::string> {
+ EndsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes )
+ : m_data( substr, caseSensitivity ){}
+ EndsWith( EndsWith const& other ) : m_data( other.m_data ){}
+
+ virtual ~EndsWith();
+
+ virtual bool match( std::string const& expr ) const {
+ return endsWith( m_data.adjustString( expr ), m_data.m_str );
+ }
+ virtual std::string toString() const {
+ return "ends with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix();
+ }
+
+ CasedString m_data;
+ };
+ } // namespace StdString
+ } // namespace Impl
+
+ // The following functions create the actual matcher objects.
+ // This allows the types to be inferred
+ template<typename ExpressionT>
+ inline Impl::Generic::Not<ExpressionT> Not( Impl::Matcher<ExpressionT> const& m ) {
+ return Impl::Generic::Not<ExpressionT>( m );
+ }
+
+ template<typename ExpressionT>
+ inline Impl::Generic::AllOf<ExpressionT> AllOf( Impl::Matcher<ExpressionT> const& m1,
+ Impl::Matcher<ExpressionT> const& m2 ) {
+ return Impl::Generic::AllOf<ExpressionT>().add( m1 ).add( m2 );
+ }
+ template<typename ExpressionT>
+ inline Impl::Generic::AllOf<ExpressionT> AllOf( Impl::Matcher<ExpressionT> const& m1,
+ Impl::Matcher<ExpressionT> const& m2,
+ Impl::Matcher<ExpressionT> const& m3 ) {
+ return Impl::Generic::AllOf<ExpressionT>().add( m1 ).add( m2 ).add( m3 );
+ }
+ template<typename ExpressionT>
+ inline Impl::Generic::AnyOf<ExpressionT> AnyOf( Impl::Matcher<ExpressionT> const& m1,
+ Impl::Matcher<ExpressionT> const& m2 ) {
+ return Impl::Generic::AnyOf<ExpressionT>().add( m1 ).add( m2 );
+ }
+ template<typename ExpressionT>
+ inline Impl::Generic::AnyOf<ExpressionT> AnyOf( Impl::Matcher<ExpressionT> const& m1,
+ Impl::Matcher<ExpressionT> const& m2,
+ Impl::Matcher<ExpressionT> const& m3 ) {
+ return Impl::Generic::AnyOf<ExpressionT>().add( m1 ).add( m2 ).add( m3 );
+ }
+
+ inline Impl::StdString::Equals Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
+ return Impl::StdString::Equals( str, caseSensitivity );
+ }
+ inline Impl::StdString::Equals Equals( const char* str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
+ return Impl::StdString::Equals( Impl::StdString::makeString( str ), caseSensitivity );
+ }
+ inline Impl::StdString::Contains Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
+ return Impl::StdString::Contains( substr, caseSensitivity );
+ }
+ inline Impl::StdString::Contains Contains( const char* substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) {
+ return Impl::StdString::Contains( Impl::StdString::makeString( substr ), caseSensitivity );
+ }
+ inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) {
+ return Impl::StdString::StartsWith( substr );
+ }
+ inline Impl::StdString::StartsWith StartsWith( const char* substr ) {
+ return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) );
+ }
+ inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) {
+ return Impl::StdString::EndsWith( substr );
+ }
+ inline Impl::StdString::EndsWith EndsWith( const char* substr ) {
+ return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) );
+ }
+
+} // namespace Matchers
+
+using namespace Matchers;
+
+} // namespace Catch
+
+namespace Catch {
+
+ struct TestFailureException{};
+
+ template<typename T> class ExpressionLhs;
+
+ struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison;
+
+ struct CopyableStream {
+ CopyableStream() {}
+ CopyableStream( CopyableStream const& other ) {
+ oss << other.oss.str();
+ }
+ CopyableStream& operator=( CopyableStream const& other ) {
+ oss.str("");
+ oss << other.oss.str();
+ return *this;
+ }
+ std::ostringstream oss;
+ };
+
+ class ResultBuilder {
+ public:
+ ResultBuilder( char const* macroName,
+ SourceLineInfo const& lineInfo,
+ char const* capturedExpression,
+ ResultDisposition::Flags resultDisposition,
+ char const* secondArg = "" );
+
+ template<typename T>
+ ExpressionLhs<T const&> operator <= ( T const& operand );
+ ExpressionLhs<bool> operator <= ( bool value );
+
+ template<typename T>
+ ResultBuilder& operator << ( T const& value ) {
+ m_stream.oss << value;
+ return *this;
+ }
+
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& );
+
+ ResultBuilder& setResultType( ResultWas::OfType result );
+ ResultBuilder& setResultType( bool result );
+ ResultBuilder& setLhs( std::string const& lhs );
+ ResultBuilder& setRhs( std::string const& rhs );
+ ResultBuilder& setOp( std::string const& op );
+
+ void endExpression();
+
+ std::string reconstructExpression() const;
+ AssertionResult build() const;
+
+ void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal );
+ void captureResult( ResultWas::OfType resultType );
+ void captureExpression();
+ void captureExpectedException( std::string const& expectedMessage );
+ void captureExpectedException( Matchers::Impl::Matcher<std::string> const& matcher );
+ void handleResult( AssertionResult const& result );
+ void react();
+ bool shouldDebugBreak() const;
+ bool allowThrows() const;
+
+ private:
+ AssertionInfo m_assertionInfo;
+ AssertionResultData m_data;
+ struct ExprComponents {
+ ExprComponents() : testFalse( false ) {}
+ bool testFalse;
+ std::string lhs, rhs, op;
+ } m_exprComponents;
+ CopyableStream m_stream;
+
+ bool m_shouldDebugBreak;
+ bool m_shouldThrow;
+ };
+
+} // namespace Catch
+
+// Include after due to circular dependency:
+// #included from: catch_expression_lhs.hpp
+#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED
+
+// #included from: catch_evaluate.hpp
+#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4389) // '==' : signed/unsigned mismatch
+#endif
+
+#include <cstddef>
+
+namespace Catch {
+namespace Internal {
+
+ enum Operator {
+ IsEqualTo,
+ IsNotEqualTo,
+ IsLessThan,
+ IsGreaterThan,
+ IsLessThanOrEqualTo,
+ IsGreaterThanOrEqualTo
+ };
+
+ template<Operator Op> struct OperatorTraits { static const char* getName(){ return "*error*"; } };
+ template<> struct OperatorTraits<IsEqualTo> { static const char* getName(){ return "=="; } };
+ template<> struct OperatorTraits<IsNotEqualTo> { static const char* getName(){ return "!="; } };
+ template<> struct OperatorTraits<IsLessThan> { static const char* getName(){ return "<"; } };
+ template<> struct OperatorTraits<IsGreaterThan> { static const char* getName(){ return ">"; } };
+ template<> struct OperatorTraits<IsLessThanOrEqualTo> { static const char* getName(){ return "<="; } };
+ template<> struct OperatorTraits<IsGreaterThanOrEqualTo>{ static const char* getName(){ return ">="; } };
+
+ template<typename T>
+ inline T& opCast(T const& t) { return const_cast<T&>(t); }
+
+// nullptr_t support based on pull request #154 from Konstantin Baumann
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+ inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; }
+#endif // CATCH_CONFIG_CPP11_NULLPTR
+
+ // So the compare overloads can be operator agnostic we convey the operator as a template
+ // enum, which is used to specialise an Evaluator for doing the comparison.
+ template<typename T1, typename T2, Operator Op>
+ class Evaluator{};
+
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsEqualTo> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs) {
+ return bool( opCast( lhs ) == opCast( rhs ) );
+ }
+ };
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsNotEqualTo> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+ return bool( opCast( lhs ) != opCast( rhs ) );
+ }
+ };
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsLessThan> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+ return bool( opCast( lhs ) < opCast( rhs ) );
+ }
+ };
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsGreaterThan> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+ return bool( opCast( lhs ) > opCast( rhs ) );
+ }
+ };
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsGreaterThanOrEqualTo> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+ return bool( opCast( lhs ) >= opCast( rhs ) );
+ }
+ };
+ template<typename T1, typename T2>
+ struct Evaluator<T1, T2, IsLessThanOrEqualTo> {
+ static bool evaluate( T1 const& lhs, T2 const& rhs ) {
+ return bool( opCast( lhs ) <= opCast( rhs ) );
+ }
+ };
+
+ template<Operator Op, typename T1, typename T2>
+ bool applyEvaluator( T1 const& lhs, T2 const& rhs ) {
+ return Evaluator<T1, T2, Op>::evaluate( lhs, rhs );
+ }
+
+ // This level of indirection allows us to specialise for integer types
+ // to avoid signed/ unsigned warnings
+
+ // "base" overload
+ template<Operator Op, typename T1, typename T2>
+ bool compare( T1 const& lhs, T2 const& rhs ) {
+ return Evaluator<T1, T2, Op>::evaluate( lhs, rhs );
+ }
+
+ // unsigned X to int
+ template<Operator Op> bool compare( unsigned int lhs, int rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
+ }
+ template<Operator Op> bool compare( unsigned long lhs, int rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
+ }
+ template<Operator Op> bool compare( unsigned char lhs, int rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) );
+ }
+
+ // unsigned X to long
+ template<Operator Op> bool compare( unsigned int lhs, long rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
+ }
+ template<Operator Op> bool compare( unsigned long lhs, long rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
+ }
+ template<Operator Op> bool compare( unsigned char lhs, long rhs ) {
+ return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) );
+ }
+
+ // int to unsigned X
+ template<Operator Op> bool compare( int lhs, unsigned int rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( int lhs, unsigned long rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( int lhs, unsigned char rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs );
+ }
+
+ // long to unsigned X
+ template<Operator Op> bool compare( long lhs, unsigned int rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( long lhs, unsigned long rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( long lhs, unsigned char rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+ }
+
+ // pointer to long (when comparing against NULL)
+ template<Operator Op, typename T> bool compare( long lhs, T* rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs );
+ }
+ template<Operator Op, typename T> bool compare( T* lhs, long rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) );
+ }
+
+ // pointer to int (when comparing against NULL)
+ template<Operator Op, typename T> bool compare( int lhs, T* rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs );
+ }
+ template<Operator Op, typename T> bool compare( T* lhs, int rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) );
+ }
+
+#ifdef CATCH_CONFIG_CPP11_LONG_LONG
+ // long long to unsigned X
+ template<Operator Op> bool compare( long long lhs, unsigned int rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( long long lhs, unsigned long rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( long long lhs, unsigned long long rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( long long lhs, unsigned char rhs ) {
+ return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs );
+ }
+
+ // unsigned long long to X
+ template<Operator Op> bool compare( unsigned long long lhs, int rhs ) {
+ return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( unsigned long long lhs, long rhs ) {
+ return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( unsigned long long lhs, long long rhs ) {
+ return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
+ }
+ template<Operator Op> bool compare( unsigned long long lhs, char rhs ) {
+ return applyEvaluator<Op>( static_cast<long>( lhs ), rhs );
+ }
+
+ // pointer to long long (when comparing against NULL)
+ template<Operator Op, typename T> bool compare( long long lhs, T* rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs );
+ }
+ template<Operator Op, typename T> bool compare( T* lhs, long long rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) );
+ }
+#endif // CATCH_CONFIG_CPP11_LONG_LONG
+
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+ // pointer to nullptr_t (when comparing against nullptr)
+ template<Operator Op, typename T> bool compare( std::nullptr_t, T* rhs ) {
+ return Evaluator<T*, T*, Op>::evaluate( nullptr, rhs );
+ }
+ template<Operator Op, typename T> bool compare( T* lhs, std::nullptr_t ) {
+ return Evaluator<T*, T*, Op>::evaluate( lhs, nullptr );
+ }
+#endif // CATCH_CONFIG_CPP11_NULLPTR
+
+} // end of namespace Internal
+} // end of namespace Catch
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+// #included from: catch_tostring.h
+#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED
+
+#include <sstream>
+#include <iomanip>
+#include <limits>
+#include <vector>
+#include <cstddef>
+
+#ifdef __OBJC__
+// #included from: catch_objc_arc.hpp
+#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED
+
+#import <Foundation/Foundation.h>
+
+#ifdef __has_feature
+#define CATCH_ARC_ENABLED __has_feature(objc_arc)
+#else
+#define CATCH_ARC_ENABLED 0
+#endif
+
+void arcSafeRelease( NSObject* obj );
+id performOptionalSelector( id obj, SEL sel );
+
+#if !CATCH_ARC_ENABLED
+inline void arcSafeRelease( NSObject* obj ) {
+ [obj release];
+}
+inline id performOptionalSelector( id obj, SEL sel ) {
+ if( [obj respondsToSelector: sel] )
+ return [obj performSelector: sel];
+ return nil;
+}
+#define CATCH_UNSAFE_UNRETAINED
+#define CATCH_ARC_STRONG
+#else
+inline void arcSafeRelease( NSObject* ){}
+inline id performOptionalSelector( id obj, SEL sel ) {
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+#endif
+ if( [obj respondsToSelector: sel] )
+ return [obj performSelector: sel];
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+ return nil;
+}
+#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained
+#define CATCH_ARC_STRONG __strong
+#endif
+
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_TUPLE
+#include <tuple>
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_IS_ENUM
+#include <type_traits>
+#endif
+
+namespace Catch {
+
+// Why we're here.
+template<typename T>
+std::string toString( T const& value );
+
+// Built in overloads
+
+std::string toString( std::string const& value );
+std::string toString( std::wstring const& value );
+std::string toString( const char* const value );
+std::string toString( char* const value );
+std::string toString( const wchar_t* const value );
+std::string toString( wchar_t* const value );
+std::string toString( int value );
+std::string toString( unsigned long value );
+std::string toString( unsigned int value );
+std::string toString( const double value );
+std::string toString( const float value );
+std::string toString( bool value );
+std::string toString( char value );
+std::string toString( signed char value );
+std::string toString( unsigned char value );
+
+#ifdef CATCH_CONFIG_CPP11_LONG_LONG
+std::string toString( long long value );
+std::string toString( unsigned long long value );
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+std::string toString( std::nullptr_t );
+#endif
+
+#ifdef __OBJC__
+ std::string toString( NSString const * const& nsstring );
+ std::string toString( NSString * CATCH_ARC_STRONG const& nsstring );
+ std::string toString( NSObject* const& nsObject );
+#endif
+
+namespace Detail {
+
+ extern const std::string unprintableString;
+
+ struct BorgType {
+ template<typename T> BorgType( T const& );
+ };
+
+ struct TrueType { char sizer[1]; };
+ struct FalseType { char sizer[2]; };
+
+ TrueType& testStreamable( std::ostream& );
+ FalseType testStreamable( FalseType );
+
+ FalseType operator<<( std::ostream const&, BorgType const& );
+
+ template<typename T>
+ struct IsStreamInsertable {
+ static std::ostream &s;
+ static T const&t;
+ enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) };
+ };
+
+#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
+ template<typename T,
+ bool IsEnum = std::is_enum<T>::value
+ >
+ struct EnumStringMaker
+ {
+ static std::string convert( T const& ) { return unprintableString; }
+ };
+
+ template<typename T>
+ struct EnumStringMaker<T,true>
+ {
+ static std::string convert( T const& v )
+ {
+ return ::Catch::toString(
+ static_cast<typename std::underlying_type<T>::type>(v)
+ );
+ }
+ };
+#endif
+ template<bool C>
+ struct StringMakerBase {
+#if defined(CATCH_CONFIG_CPP11_IS_ENUM)
+ template<typename T>
+ static std::string convert( T const& v )
+ {
+ return EnumStringMaker<T>::convert( v );
+ }
+#else
+ template<typename T>
+ static std::string convert( T const& ) { return unprintableString; }
+#endif
+ };
+
+ template<>
+ struct StringMakerBase<true> {
+ template<typename T>
+ static std::string convert( T const& _value ) {
+ std::ostringstream oss;
+ oss << _value;
+ return oss.str();
+ }
+ };
+
+ std::string rawMemoryToString( const void *object, std::size_t size );
+
+ template<typename T>
+ inline std::string rawMemoryToString( const T& object ) {
+ return rawMemoryToString( &object, sizeof(object) );
+ }
+
+} // end namespace Detail
+
+template<typename T>
+struct StringMaker :
+ Detail::StringMakerBase<Detail::IsStreamInsertable<T>::value> {};
+
+template<typename T>
+struct StringMaker<T*> {
+ template<typename U>
+ static std::string convert( U* p ) {
+ if( !p )
+ return "NULL";
+ else
+ return Detail::rawMemoryToString( p );
+ }
+};
+
+template<typename R, typename C>
+struct StringMaker<R C::*> {
+ static std::string convert( R C::* p ) {
+ if( !p )
+ return "NULL";
+ else
+ return Detail::rawMemoryToString( p );
+ }
+};
+
+namespace Detail {
+ template<typename InputIterator>
+ std::string rangeToString( InputIterator first, InputIterator last );
+}
+
+//template<typename T, typename Allocator>
+//struct StringMaker<std::vector<T, Allocator> > {
+// static std::string convert( std::vector<T,Allocator> const& v ) {
+// return Detail::rangeToString( v.begin(), v.end() );
+// }
+//};
+
+template<typename T, typename Allocator>
+std::string toString( std::vector<T,Allocator> const& v ) {
+ return Detail::rangeToString( v.begin(), v.end() );
+}
+
+#ifdef CATCH_CONFIG_CPP11_TUPLE
+
+// toString for tuples
+namespace TupleDetail {
+ template<
+ typename Tuple,
+ std::size_t N = 0,
+ bool = (N < std::tuple_size<Tuple>::value)
+ >
+ struct ElementPrinter {
+ static void print( const Tuple& tuple, std::ostream& os )
+ {
+ os << ( N ? ", " : " " )
+ << Catch::toString(std::get<N>(tuple));
+ ElementPrinter<Tuple,N+1>::print(tuple,os);
+ }
+ };
+
+ template<
+ typename Tuple,
+ std::size_t N
+ >
+ struct ElementPrinter<Tuple,N,false> {
+ static void print( const Tuple&, std::ostream& ) {}
+ };
+
+}
+
+template<typename ...Types>
+struct StringMaker<std::tuple<Types...>> {
+
+ static std::string convert( const std::tuple<Types...>& tuple )
+ {
+ std::ostringstream os;
+ os << '{';
+ TupleDetail::ElementPrinter<std::tuple<Types...>>::print( tuple, os );
+ os << " }";
+ return os.str();
+ }
+};
+#endif // CATCH_CONFIG_CPP11_TUPLE
+
+namespace Detail {
+ template<typename T>
+ std::string makeString( T const& value ) {
+ return StringMaker<T>::convert( value );
+ }
+} // end namespace Detail
+
+/// \brief converts any type to a string
+///
+/// The default template forwards on to ostringstream - except when an
+/// ostringstream overload does not exist - in which case it attempts to detect
+/// that and writes {?}.
+/// Overload (not specialise) this template for custom typs that you don't want
+/// to provide an ostream overload for.
+template<typename T>
+std::string toString( T const& value ) {
+ return StringMaker<T>::convert( value );
+}
+
+ namespace Detail {
+ template<typename InputIterator>
+ std::string rangeToString( InputIterator first, InputIterator last ) {
+ std::ostringstream oss;
+ oss << "{ ";
+ if( first != last ) {
+ oss << Catch::toString( *first );
+ for( ++first ; first != last ; ++first )
+ oss << ", " << Catch::toString( *first );
+ }
+ oss << " }";
+ return oss.str();
+ }
+}
+
+} // end namespace Catch
+
+namespace Catch {
+
+// Wraps the LHS of an expression and captures the operator and RHS (if any) -
+// wrapping them all in a ResultBuilder object
+template<typename T>
+class ExpressionLhs {
+ ExpressionLhs& operator = ( ExpressionLhs const& );
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ ExpressionLhs& operator = ( ExpressionLhs && ) = delete;
+# endif
+
+public:
+ ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {}
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ ExpressionLhs( ExpressionLhs const& ) = default;
+ ExpressionLhs( ExpressionLhs && ) = default;
+# endif
+
+ template<typename RhsT>
+ ResultBuilder& operator == ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsEqualTo>( rhs );
+ }
+
+ template<typename RhsT>
+ ResultBuilder& operator != ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsNotEqualTo>( rhs );
+ }
+
+ template<typename RhsT>
+ ResultBuilder& operator < ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsLessThan>( rhs );
+ }
+
+ template<typename RhsT>
+ ResultBuilder& operator > ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsGreaterThan>( rhs );
+ }
+
+ template<typename RhsT>
+ ResultBuilder& operator <= ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsLessThanOrEqualTo>( rhs );
+ }
+
+ template<typename RhsT>
+ ResultBuilder& operator >= ( RhsT const& rhs ) {
+ return captureExpression<Internal::IsGreaterThanOrEqualTo>( rhs );
+ }
+
+ ResultBuilder& operator == ( bool rhs ) {
+ return captureExpression<Internal::IsEqualTo>( rhs );
+ }
+
+ ResultBuilder& operator != ( bool rhs ) {
+ return captureExpression<Internal::IsNotEqualTo>( rhs );
+ }
+
+ void endExpression() {
+ bool value = m_lhs ? true : false;
+ m_rb
+ .setLhs( Catch::toString( value ) )
+ .setResultType( value )
+ .endExpression();
+ }
+
+ // Only simple binary expressions are allowed on the LHS.
+ // If more complex compositions are required then place the sub expression in parentheses
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& );
+ template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& );
+
+private:
+ template<Internal::Operator Op, typename RhsT>
+ ResultBuilder& captureExpression( RhsT const& rhs ) {
+ return m_rb
+ .setResultType( Internal::compare<Op>( m_lhs, rhs ) )
+ .setLhs( Catch::toString( m_lhs ) )
+ .setRhs( Catch::toString( rhs ) )
+ .setOp( Internal::OperatorTraits<Op>::getName() );
+ }
+
+private:
+ ResultBuilder& m_rb;
+ T m_lhs;
+};
+
+} // end namespace Catch
+
+
+namespace Catch {
+
+ template<typename T>
+ inline ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) {
+ return ExpressionLhs<T const&>( *this, operand );
+ }
+
+ inline ExpressionLhs<bool> ResultBuilder::operator <= ( bool value ) {
+ return ExpressionLhs<bool>( *this, value );
+ }
+
+} // namespace Catch
+
+// #included from: catch_message.h
+#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ struct MessageInfo {
+ MessageInfo( std::string const& _macroName,
+ SourceLineInfo const& _lineInfo,
+ ResultWas::OfType _type );
+
+ std::string macroName;
+ SourceLineInfo lineInfo;
+ ResultWas::OfType type;
+ std::string message;
+ unsigned int sequence;
+
+ bool operator == ( MessageInfo const& other ) const {
+ return sequence == other.sequence;
+ }
+ bool operator < ( MessageInfo const& other ) const {
+ return sequence < other.sequence;
+ }
+ private:
+ static unsigned int globalCount;
+ };
+
+ struct MessageBuilder {
+ MessageBuilder( std::string const& macroName,
+ SourceLineInfo const& lineInfo,
+ ResultWas::OfType type )
+ : m_info( macroName, lineInfo, type )
+ {}
+
+ template<typename T>
+ MessageBuilder& operator << ( T const& value ) {
+ m_stream << value;
+ return *this;
+ }
+
+ MessageInfo m_info;
+ std::ostringstream m_stream;
+ };
+
+ class ScopedMessage {
+ public:
+ ScopedMessage( MessageBuilder const& builder );
+ ScopedMessage( ScopedMessage const& other );
+ ~ScopedMessage();
+
+ MessageInfo m_info;
+ };
+
+} // end namespace Catch
+
+// #included from: catch_interfaces_capture.h
+#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ class TestCase;
+ class AssertionResult;
+ struct AssertionInfo;
+ struct SectionInfo;
+ struct SectionEndInfo;
+ struct MessageInfo;
+ class ScopedMessageBuilder;
+ struct Counts;
+
+ struct IResultCapture {
+
+ virtual ~IResultCapture();
+
+ virtual void assertionEnded( AssertionResult const& result ) = 0;
+ virtual bool sectionStarted( SectionInfo const& sectionInfo,
+ Counts& assertions ) = 0;
+ virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0;
+ virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0;
+ virtual void pushScopedMessage( MessageInfo const& message ) = 0;
+ virtual void popScopedMessage( MessageInfo const& message ) = 0;
+
+ virtual std::string getCurrentTestName() const = 0;
+ virtual const AssertionResult* getLastResult() const = 0;
+
+ virtual void handleFatalErrorCondition( std::string const& message ) = 0;
+ };
+
+ IResultCapture& getResultCapture();
+}
+
+// #included from: catch_debugger.h
+#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED
+
+// #included from: catch_platform.h
+#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED
+
+#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
+#define CATCH_PLATFORM_MAC
+#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
+#define CATCH_PLATFORM_IPHONE
+#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
+#define CATCH_PLATFORM_WINDOWS
+#endif
+
+#include <string>
+
+namespace Catch{
+
+ bool isDebuggerActive();
+ void writeToDebugConsole( std::string const& text );
+}
+
+#ifdef CATCH_PLATFORM_MAC
+
+ // The following code snippet based on:
+ // http://cocoawithlove.com/2008/03/break-into-debugger.html
+ #ifdef DEBUG
+ #if defined(__ppc64__) || defined(__ppc__)
+ #define CATCH_BREAK_INTO_DEBUGGER() \
+ if( Catch::isDebuggerActive() ) { \
+ __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \
+ : : : "memory","r0","r3","r4" ); \
+ }
+ #else
+ #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );}
+ #endif
+ #endif
+
+#elif defined(_MSC_VER)
+ #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); }
+#elif defined(__MINGW32__)
+ extern "C" __declspec(dllimport) void __stdcall DebugBreak();
+ #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); }
+#endif
+
+#ifndef CATCH_BREAK_INTO_DEBUGGER
+#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue();
+#endif
+
+// #included from: catch_interfaces_runner.h
+#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED
+
+namespace Catch {
+ class TestCase;
+
+ struct IRunner {
+ virtual ~IRunner();
+ virtual bool aborting() const = 0;
+ };
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// In the event of a failure works out if the debugger needs to be invoked
+// and/or an exception thrown and takes appropriate action.
+// This needs to be done as a macro so the debugger will stop in the user
+// source code rather than in Catch library code
+#define INTERNAL_CATCH_REACT( resultBuilder ) \
+ if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \
+ resultBuilder.react();
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+ try { \
+ CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
+ ( __catchResult <= expr ).endExpression(); \
+ } \
+ catch( ... ) { \
+ __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \
+ } \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::isTrue( false && static_cast<bool>(expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \
+ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \
+ if( Catch::getResultCapture().getLastResult()->succeeded() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \
+ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \
+ if( !Catch::getResultCapture().getLastResult()->succeeded() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+ try { \
+ expr; \
+ __catchResult.captureResult( Catch::ResultWas::Ok ); \
+ } \
+ catch( ... ) { \
+ __catchResult.useActiveException( resultDisposition ); \
+ } \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS( expr, resultDisposition, matcher, macroName ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \
+ if( __catchResult.allowThrows() ) \
+ try { \
+ expr; \
+ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
+ } \
+ catch( ... ) { \
+ __catchResult.captureExpectedException( matcher ); \
+ } \
+ else \
+ __catchResult.captureResult( Catch::ResultWas::Ok ); \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \
+ if( __catchResult.allowThrows() ) \
+ try { \
+ expr; \
+ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \
+ } \
+ catch( exceptionType ) { \
+ __catchResult.captureResult( Catch::ResultWas::Ok ); \
+ } \
+ catch( ... ) { \
+ __catchResult.useActiveException( resultDisposition ); \
+ } \
+ else \
+ __catchResult.captureResult( Catch::ResultWas::Ok ); \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+
+///////////////////////////////////////////////////////////////////////////////
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+ #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
+ __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \
+ __catchResult.captureResult( messageType ); \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+#else
+ #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \
+ __catchResult << log + ::Catch::StreamEndStop(); \
+ __catchResult.captureResult( messageType ); \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_INFO( log, macroName ) \
+ Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log;
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \
+ do { \
+ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg ", " #matcher, resultDisposition ); \
+ try { \
+ std::string matcherAsString = (matcher).toString(); \
+ __catchResult \
+ .setLhs( Catch::toString( arg ) ) \
+ .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \
+ .setOp( "matches" ) \
+ .setResultType( (matcher).match( arg ) ); \
+ __catchResult.captureExpression(); \
+ } catch( ... ) { \
+ __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \
+ } \
+ INTERNAL_CATCH_REACT( __catchResult ) \
+ } while( Catch::alwaysFalse() )
+
+// #included from: internal/catch_section.h
+#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED
+
+// #included from: catch_section_info.h
+#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED
+
+// #included from: catch_totals.hpp
+#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED
+
+#include <cstddef>
+
+namespace Catch {
+
+ struct Counts {
+ Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {}
+
+ Counts operator - ( Counts const& other ) const {
+ Counts diff;
+ diff.passed = passed - other.passed;
+ diff.failed = failed - other.failed;
+ diff.failedButOk = failedButOk - other.failedButOk;
+ return diff;
+ }
+ Counts& operator += ( Counts const& other ) {
+ passed += other.passed;
+ failed += other.failed;
+ failedButOk += other.failedButOk;
+ return *this;
+ }
+
+ std::size_t total() const {
+ return passed + failed + failedButOk;
+ }
+ bool allPassed() const {
+ return failed == 0 && failedButOk == 0;
+ }
+ bool allOk() const {
+ return failed == 0;
+ }
+
+ std::size_t passed;
+ std::size_t failed;
+ std::size_t failedButOk;
+ };
+
+ struct Totals {
+
+ Totals operator - ( Totals const& other ) const {
+ Totals diff;
+ diff.assertions = assertions - other.assertions;
+ diff.testCases = testCases - other.testCases;
+ return diff;
+ }
+
+ Totals delta( Totals const& prevTotals ) const {
+ Totals diff = *this - prevTotals;
+ if( diff.assertions.failed > 0 )
+ ++diff.testCases.failed;
+ else if( diff.assertions.failedButOk > 0 )
+ ++diff.testCases.failedButOk;
+ else
+ ++diff.testCases.passed;
+ return diff;
+ }
+
+ Totals& operator += ( Totals const& other ) {
+ assertions += other.assertions;
+ testCases += other.testCases;
+ return *this;
+ }
+
+ Counts assertions;
+ Counts testCases;
+ };
+}
+
+namespace Catch {
+
+ struct SectionInfo {
+ SectionInfo
+ ( SourceLineInfo const& _lineInfo,
+ std::string const& _name,
+ std::string const& _description = std::string() );
+
+ std::string name;
+ std::string description;
+ SourceLineInfo lineInfo;
+ };
+
+ struct SectionEndInfo {
+ SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds )
+ : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds )
+ {}
+
+ SectionInfo sectionInfo;
+ Counts prevAssertions;
+ double durationInSeconds;
+ };
+
+} // end namespace Catch
+
+// #included from: catch_timer.h
+#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED
+
+#ifdef CATCH_PLATFORM_WINDOWS
+typedef unsigned long long uint64_t;
+#else
+#include <stdint.h>
+#endif
+
+namespace Catch {
+
+ class Timer {
+ public:
+ Timer() : m_ticks( 0 ) {}
+ void start();
+ unsigned int getElapsedMicroseconds() const;
+ unsigned int getElapsedMilliseconds() const;
+ double getElapsedSeconds() const;
+
+ private:
+ uint64_t m_ticks;
+ };
+
+} // namespace Catch
+
+#include <string>
+
+namespace Catch {
+
+ class Section : NonCopyable {
+ public:
+ Section( SectionInfo const& info );
+ ~Section();
+
+ // This indicates whether the section should be executed or not
+ operator bool() const;
+
+ private:
+ SectionInfo m_info;
+
+ std::string m_name;
+ Counts m_assertions;
+ bool m_sectionIncluded;
+ Timer m_timer;
+ };
+
+} // end namespace Catch
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+ #define INTERNAL_CATCH_SECTION( ... ) \
+ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) )
+#else
+ #define INTERNAL_CATCH_SECTION( name, desc ) \
+ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) )
+#endif
+
+// #included from: internal/catch_generators.hpp
+#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED
+
+#include <iterator>
+#include <vector>
+#include <string>
+#include <stdlib.h>
+
+namespace Catch {
+
+template<typename T>
+struct IGenerator {
+ virtual ~IGenerator() {}
+ virtual T getValue( std::size_t index ) const = 0;
+ virtual std::size_t size () const = 0;
+};
+
+template<typename T>
+class BetweenGenerator : public IGenerator<T> {
+public:
+ BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){}
+
+ virtual T getValue( std::size_t index ) const {
+ return m_from+static_cast<int>( index );
+ }
+
+ virtual std::size_t size() const {
+ return static_cast<std::size_t>( 1+m_to-m_from );
+ }
+
+private:
+
+ T m_from;
+ T m_to;
+};
+
+template<typename T>
+class ValuesGenerator : public IGenerator<T> {
+public:
+ ValuesGenerator(){}
+
+ void add( T value ) {
+ m_values.push_back( value );
+ }
+
+ virtual T getValue( std::size_t index ) const {
+ return m_values[index];
+ }
+
+ virtual std::size_t size() const {
+ return m_values.size();
+ }
+
+private:
+ std::vector<T> m_values;
+};
+
+template<typename T>
+class CompositeGenerator {
+public:
+ CompositeGenerator() : m_totalSize( 0 ) {}
+
+ // *** Move semantics, similar to auto_ptr ***
+ CompositeGenerator( CompositeGenerator& other )
+ : m_fileInfo( other.m_fileInfo ),
+ m_totalSize( 0 )
+ {
+ move( other );
+ }
+
+ CompositeGenerator& setFileInfo( const char* fileInfo ) {
+ m_fileInfo = fileInfo;
+ return *this;
+ }
+
+ ~CompositeGenerator() {
+ deleteAll( m_composed );
+ }
+
+ operator T () const {
+ size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize );
+
+ typename std::vector<const IGenerator<T>*>::const_iterator it = m_composed.begin();
+ typename std::vector<const IGenerator<T>*>::const_iterator itEnd = m_composed.end();
+ for( size_t index = 0; it != itEnd; ++it )
+ {
+ const IGenerator<T>* generator = *it;
+ if( overallIndex >= index && overallIndex < index + generator->size() )
+ {
+ return generator->getValue( overallIndex-index );
+ }
+ index += generator->size();
+ }
+ CATCH_INTERNAL_ERROR( "Indexed past end of generated range" );
+ return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so
+ }
+
+ void add( const IGenerator<T>* generator ) {
+ m_totalSize += generator->size();
+ m_composed.push_back( generator );
+ }
+
+ CompositeGenerator& then( CompositeGenerator& other ) {
+ move( other );
+ return *this;
+ }
+
+ CompositeGenerator& then( T value ) {
+ ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+ valuesGen->add( value );
+ add( valuesGen );
+ return *this;
+ }
+
+private:
+
+ void move( CompositeGenerator& other ) {
+ std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) );
+ m_totalSize += other.m_totalSize;
+ other.m_composed.clear();
+ }
+
+ std::vector<const IGenerator<T>*> m_composed;
+ std::string m_fileInfo;
+ size_t m_totalSize;
+};
+
+namespace Generators
+{
+ template<typename T>
+ CompositeGenerator<T> between( T from, T to ) {
+ CompositeGenerator<T> generators;
+ generators.add( new BetweenGenerator<T>( from, to ) );
+ return generators;
+ }
+
+ template<typename T>
+ CompositeGenerator<T> values( T val1, T val2 ) {
+ CompositeGenerator<T> generators;
+ ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+ valuesGen->add( val1 );
+ valuesGen->add( val2 );
+ generators.add( valuesGen );
+ return generators;
+ }
+
+ template<typename T>
+ CompositeGenerator<T> values( T val1, T val2, T val3 ){
+ CompositeGenerator<T> generators;
+ ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+ valuesGen->add( val1 );
+ valuesGen->add( val2 );
+ valuesGen->add( val3 );
+ generators.add( valuesGen );
+ return generators;
+ }
+
+ template<typename T>
+ CompositeGenerator<T> values( T val1, T val2, T val3, T val4 ) {
+ CompositeGenerator<T> generators;
+ ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>();
+ valuesGen->add( val1 );
+ valuesGen->add( val2 );
+ valuesGen->add( val3 );
+ valuesGen->add( val4 );
+ generators.add( valuesGen );
+ return generators;
+ }
+
+} // end namespace Generators
+
+using namespace Generators;
+
+} // end namespace Catch
+
+#define INTERNAL_CATCH_LINESTR2( line ) #line
+#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line )
+
+#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" )
+
+// #included from: internal/catch_interfaces_exception.h
+#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED
+
+#include <string>
+#include <vector>
+
+// #included from: catch_interfaces_registry_hub.h
+#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ class TestCase;
+ struct ITestCaseRegistry;
+ struct IExceptionTranslatorRegistry;
+ struct IExceptionTranslator;
+ struct IReporterRegistry;
+ struct IReporterFactory;
+
+ struct IRegistryHub {
+ virtual ~IRegistryHub();
+
+ virtual IReporterRegistry const& getReporterRegistry() const = 0;
+ virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0;
+ virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0;
+ };
+
+ struct IMutableRegistryHub {
+ virtual ~IMutableRegistryHub();
+ virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) = 0;
+ virtual void registerListener( Ptr<IReporterFactory> const& factory ) = 0;
+ virtual void registerTest( TestCase const& testInfo ) = 0;
+ virtual void registerTranslator( const IExceptionTranslator* translator ) = 0;
+ };
+
+ IRegistryHub& getRegistryHub();
+ IMutableRegistryHub& getMutableRegistryHub();
+ void cleanUp();
+ std::string translateActiveException();
+
+}
+
+namespace Catch {
+
+ typedef std::string(*exceptionTranslateFunction)();
+
+ struct IExceptionTranslator;
+ typedef std::vector<const IExceptionTranslator*> ExceptionTranslators;
+
+ struct IExceptionTranslator {
+ virtual ~IExceptionTranslator();
+ virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0;
+ };
+
+ struct IExceptionTranslatorRegistry {
+ virtual ~IExceptionTranslatorRegistry();
+
+ virtual std::string translateActiveException() const = 0;
+ };
+
+ class ExceptionTranslatorRegistrar {
+ template<typename T>
+ class ExceptionTranslator : public IExceptionTranslator {
+ public:
+
+ ExceptionTranslator( std::string(*translateFunction)( T& ) )
+ : m_translateFunction( translateFunction )
+ {}
+
+ virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE {
+ try {
+ if( it == itEnd )
+ throw;
+ else
+ return (*it)->translate( it+1, itEnd );
+ }
+ catch( T& ex ) {
+ return m_translateFunction( ex );
+ }
+ }
+
+ protected:
+ std::string(*m_translateFunction)( T& );
+ };
+
+ public:
+ template<typename T>
+ ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) {
+ getMutableRegistryHub().registerTranslator
+ ( new ExceptionTranslator<T>( translateFunction ) );
+ }
+ };
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \
+ static std::string translatorName( signature ); \
+ namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\
+ static std::string translatorName( signature )
+
+#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )
+
+// #included from: internal/catch_approx.hpp
+#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED
+
+#include <cmath>
+#include <limits>
+
+namespace Catch {
+namespace Detail {
+
+ class Approx {
+ public:
+ explicit Approx ( double value )
+ : m_epsilon( std::numeric_limits<float>::epsilon()*100 ),
+ m_scale( 1.0 ),
+ m_value( value )
+ {}
+
+ Approx( Approx const& other )
+ : m_epsilon( other.m_epsilon ),
+ m_scale( other.m_scale ),
+ m_value( other.m_value )
+ {}
+
+ static Approx custom() {
+ return Approx( 0 );
+ }
+
+ Approx operator()( double value ) {
+ Approx approx( value );
+ approx.epsilon( m_epsilon );
+ approx.scale( m_scale );
+ return approx;
+ }
+
+ friend bool operator == ( double lhs, Approx const& rhs ) {
+ // Thanks to Richard Harris for his help refining this formula
+ return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) );
+ }
+
+ friend bool operator == ( Approx const& lhs, double rhs ) {
+ return operator==( rhs, lhs );
+ }
+
+ friend bool operator != ( double lhs, Approx const& rhs ) {
+ return !operator==( lhs, rhs );
+ }
+
+ friend bool operator != ( Approx const& lhs, double rhs ) {
+ return !operator==( rhs, lhs );
+ }
+
+ Approx& epsilon( double newEpsilon ) {
+ m_epsilon = newEpsilon;
+ return *this;
+ }
+
+ Approx& scale( double newScale ) {
+ m_scale = newScale;
+ return *this;
+ }
+
+ std::string toString() const {
+ std::ostringstream oss;
+ oss << "Approx( " << Catch::toString( m_value ) << " )";
+ return oss.str();
+ }
+
+ private:
+ double m_epsilon;
+ double m_scale;
+ double m_value;
+ };
+}
+
+template<>
+inline std::string toString<Detail::Approx>( Detail::Approx const& value ) {
+ return value.toString();
+}
+
+} // end namespace Catch
+
+// #included from: internal/catch_interfaces_tag_alias_registry.h
+#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED
+
+// #included from: catch_tag_alias.h
+#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED
+
+#include <string>
+
+namespace Catch {
+
+ struct TagAlias {
+ TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {}
+
+ std::string tag;
+ SourceLineInfo lineInfo;
+ };
+
+ struct RegistrarForTagAliases {
+ RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
+ };
+
+} // end namespace Catch
+
+#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); }
+// #included from: catch_option.hpp
+#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED
+
+namespace Catch {
+
+ // An optional type
+ template<typename T>
+ class Option {
+ public:
+ Option() : nullableValue( CATCH_NULL ) {}
+ Option( T const& _value )
+ : nullableValue( new( storage ) T( _value ) )
+ {}
+ Option( Option const& _other )
+ : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL )
+ {}
+
+ ~Option() {
+ reset();
+ }
+
+ Option& operator= ( Option const& _other ) {
+ if( &_other != this ) {
+ reset();
+ if( _other )
+ nullableValue = new( storage ) T( *_other );
+ }
+ return *this;
+ }
+ Option& operator = ( T const& _value ) {
+ reset();
+ nullableValue = new( storage ) T( _value );
+ return *this;
+ }
+
+ void reset() {
+ if( nullableValue )
+ nullableValue->~T();
+ nullableValue = CATCH_NULL;
+ }
+
+ T& operator*() { return *nullableValue; }
+ T const& operator*() const { return *nullableValue; }
+ T* operator->() { return nullableValue; }
+ const T* operator->() const { return nullableValue; }
+
+ T valueOr( T const& defaultValue ) const {
+ return nullableValue ? *nullableValue : defaultValue;
+ }
+
+ bool some() const { return nullableValue != CATCH_NULL; }
+ bool none() const { return nullableValue == CATCH_NULL; }
+
+ bool operator !() const { return nullableValue == CATCH_NULL; }
+ operator SafeBool::type() const {
+ return SafeBool::makeSafe( some() );
+ }
+
+ private:
+ T* nullableValue;
+ char storage[sizeof(T)];
+ };
+
+} // end namespace Catch
+
+namespace Catch {
+
+ struct ITagAliasRegistry {
+ virtual ~ITagAliasRegistry();
+ virtual Option<TagAlias> find( std::string const& alias ) const = 0;
+ virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0;
+
+ static ITagAliasRegistry const& get();
+ };
+
+} // end namespace Catch
+
+// These files are included here so the single_include script doesn't put them
+// in the conditionally compiled sections
+// #included from: internal/catch_test_case_info.h
+#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED
+
+#include <string>
+#include <set>
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+namespace Catch {
+
+ struct ITestCase;
+
+ struct TestCaseInfo {
+ enum SpecialProperties{
+ None = 0,
+ IsHidden = 1 << 1,
+ ShouldFail = 1 << 2,
+ MayFail = 1 << 3,
+ Throws = 1 << 4
+ };
+
+ TestCaseInfo( std::string const& _name,
+ std::string const& _className,
+ std::string const& _description,
+ std::set<std::string> const& _tags,
+ SourceLineInfo const& _lineInfo );
+
+ TestCaseInfo( TestCaseInfo const& other );
+
+ friend void setTags( TestCaseInfo& testCaseInfo, std::set<std::string> const& tags );
+
+ bool isHidden() const;
+ bool throws() const;
+ bool okToFail() const;
+ bool expectedToFail() const;
+
+ std::string name;
+ std::string className;
+ std::string description;
+ std::set<std::string> tags;
+ std::set<std::string> lcaseTags;
+ std::string tagsAsString;
+ SourceLineInfo lineInfo;
+ SpecialProperties properties;
+ };
+
+ class TestCase : public TestCaseInfo {
+ public:
+
+ TestCase( ITestCase* testCase, TestCaseInfo const& info );
+ TestCase( TestCase const& other );
+
+ TestCase withName( std::string const& _newName ) const;
+
+ void invoke() const;
+
+ TestCaseInfo const& getTestCaseInfo() const;
+
+ void swap( TestCase& other );
+ bool operator == ( TestCase const& other ) const;
+ bool operator < ( TestCase const& other ) const;
+ TestCase& operator = ( TestCase const& other );
+
+ private:
+ Ptr<ITestCase> test;
+ };
+
+ TestCase makeTestCase( ITestCase* testCase,
+ std::string const& className,
+ std::string const& name,
+ std::string const& description,
+ SourceLineInfo const& lineInfo );
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+
+#ifdef __OBJC__
+// #included from: internal/catch_objc.hpp
+#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED
+
+#import <objc/runtime.h>
+
+#include <string>
+
+// NB. Any general catch headers included here must be included
+// in catch.hpp first to make sure they are included by the single
+// header for non obj-usage
+
+///////////////////////////////////////////////////////////////////////////////
+// This protocol is really only here for (self) documenting purposes, since
+// all its methods are optional.
+@protocol OcFixture
+
+@optional
+
+-(void) setUp;
+-(void) tearDown;
+
+@end
+
+namespace Catch {
+
+ class OcMethod : public SharedImpl<ITestCase> {
+
+ public:
+ OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {}
+
+ virtual void invoke() const {
+ id obj = [[m_cls alloc] init];
+
+ performOptionalSelector( obj, @selector(setUp) );
+ performOptionalSelector( obj, m_sel );
+ performOptionalSelector( obj, @selector(tearDown) );
+
+ arcSafeRelease( obj );
+ }
+ private:
+ virtual ~OcMethod() {}
+
+ Class m_cls;
+ SEL m_sel;
+ };
+
+ namespace Detail{
+
+ inline std::string getAnnotation( Class cls,
+ std::string const& annotationName,
+ std::string const& testCaseName ) {
+ NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()];
+ SEL sel = NSSelectorFromString( selStr );
+ arcSafeRelease( selStr );
+ id value = performOptionalSelector( cls, sel );
+ if( value )
+ return [(NSString*)value UTF8String];
+ return "";
+ }
+ }
+
+ inline size_t registerTestMethods() {
+ size_t noTestMethods = 0;
+ int noClasses = objc_getClassList( CATCH_NULL, 0 );
+
+ Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses);
+ objc_getClassList( classes, noClasses );
+
+ for( int c = 0; c < noClasses; c++ ) {
+ Class cls = classes[c];
+ {
+ u_int count;
+ Method* methods = class_copyMethodList( cls, &count );
+ for( u_int m = 0; m < count ; m++ ) {
+ SEL selector = method_getName(methods[m]);
+ std::string methodName = sel_getName(selector);
+ if( startsWith( methodName, "Catch_TestCase_" ) ) {
+ std::string testCaseName = methodName.substr( 15 );
+ std::string name = Detail::getAnnotation( cls, "Name", testCaseName );
+ std::string desc = Detail::getAnnotation( cls, "Description", testCaseName );
+ const char* className = class_getName( cls );
+
+ getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) );
+ noTestMethods++;
+ }
+ }
+ free(methods);
+ }
+ }
+ return noTestMethods;
+ }
+
+ namespace Matchers {
+ namespace Impl {
+ namespace NSStringMatchers {
+
+ template<typename MatcherT>
+ struct StringHolder : MatcherImpl<MatcherT, NSString*>{
+ StringHolder( NSString* substr ) : m_substr( [substr copy] ){}
+ StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){}
+ StringHolder() {
+ arcSafeRelease( m_substr );
+ }
+
+ NSString* m_substr;
+ };
+
+ struct Equals : StringHolder<Equals> {
+ Equals( NSString* substr ) : StringHolder( substr ){}
+
+ virtual bool match( ExpressionType const& str ) const {
+ return (str != nil || m_substr == nil ) &&
+ [str isEqualToString:m_substr];
+ }
+
+ virtual std::string toString() const {
+ return "equals string: " + Catch::toString( m_substr );
+ }
+ };
+
+ struct Contains : StringHolder<Contains> {
+ Contains( NSString* substr ) : StringHolder( substr ){}
+
+ virtual bool match( ExpressionType const& str ) const {
+ return (str != nil || m_substr == nil ) &&
+ [str rangeOfString:m_substr].location != NSNotFound;
+ }
+
+ virtual std::string toString() const {
+ return "contains string: " + Catch::toString( m_substr );
+ }
+ };
+
+ struct StartsWith : StringHolder<StartsWith> {
+ StartsWith( NSString* substr ) : StringHolder( substr ){}
+
+ virtual bool match( ExpressionType const& str ) const {
+ return (str != nil || m_substr == nil ) &&
+ [str rangeOfString:m_substr].location == 0;
+ }
+
+ virtual std::string toString() const {
+ return "starts with: " + Catch::toString( m_substr );
+ }
+ };
+ struct EndsWith : StringHolder<EndsWith> {
+ EndsWith( NSString* substr ) : StringHolder( substr ){}
+
+ virtual bool match( ExpressionType const& str ) const {
+ return (str != nil || m_substr == nil ) &&
+ [str rangeOfString:m_substr].location == [str length] - [m_substr length];
+ }
+
+ virtual std::string toString() const {
+ return "ends with: " + Catch::toString( m_substr );
+ }
+ };
+
+ } // namespace NSStringMatchers
+ } // namespace Impl
+
+ inline Impl::NSStringMatchers::Equals
+ Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); }
+
+ inline Impl::NSStringMatchers::Contains
+ Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); }
+
+ inline Impl::NSStringMatchers::StartsWith
+ StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); }
+
+ inline Impl::NSStringMatchers::EndsWith
+ EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); }
+
+ } // namespace Matchers
+
+ using namespace Matchers;
+
+} // namespace Catch
+
+///////////////////////////////////////////////////////////////////////////////
+#define OC_TEST_CASE( name, desc )\
++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \
+{\
+return @ name; \
+}\
++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \
+{ \
+return @ desc; \
+} \
+-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test )
+
+#endif
+
+#ifdef CATCH_IMPL
+// #included from: internal/catch_impl.hpp
+#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED
+
+// Collect all the implementation files together here
+// These are the equivalent of what would usually be cpp files
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+// #included from: ../catch_session.hpp
+#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED
+
+// #included from: internal/catch_commandline.hpp
+#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED
+
+// #included from: catch_config.hpp
+#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED
+
+// #included from: catch_test_spec_parser.hpp
+#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+// #included from: catch_test_spec.hpp
+#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+// #included from: catch_wildcard_pattern.hpp
+#define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED
+
+namespace Catch
+{
+ class WildcardPattern {
+ enum WildcardPosition {
+ NoWildcard = 0,
+ WildcardAtStart = 1,
+ WildcardAtEnd = 2,
+ WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd
+ };
+
+ public:
+
+ WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity )
+ : m_caseSensitivity( caseSensitivity ),
+ m_wildcard( NoWildcard ),
+ m_pattern( adjustCase( pattern ) )
+ {
+ if( startsWith( m_pattern, "*" ) ) {
+ m_pattern = m_pattern.substr( 1 );
+ m_wildcard = WildcardAtStart;
+ }
+ if( endsWith( m_pattern, "*" ) ) {
+ m_pattern = m_pattern.substr( 0, m_pattern.size()-1 );
+ m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd );
+ }
+ }
+ virtual ~WildcardPattern();
+ virtual bool matches( std::string const& str ) const {
+ switch( m_wildcard ) {
+ case NoWildcard:
+ return m_pattern == adjustCase( str );
+ case WildcardAtStart:
+ return endsWith( adjustCase( str ), m_pattern );
+ case WildcardAtEnd:
+ return startsWith( adjustCase( str ), m_pattern );
+ case WildcardAtBothEnds:
+ return contains( adjustCase( str ), m_pattern );
+ }
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunreachable-code"
+#endif
+ throw std::logic_error( "Unknown enum" );
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+ }
+ private:
+ std::string adjustCase( std::string const& str ) const {
+ return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str;
+ }
+ CaseSensitive::Choice m_caseSensitivity;
+ WildcardPosition m_wildcard;
+ std::string m_pattern;
+ };
+}
+
+#include <string>
+#include <vector>
+
+namespace Catch {
+
+ class TestSpec {
+ struct Pattern : SharedImpl<> {
+ virtual ~Pattern();
+ virtual bool matches( TestCaseInfo const& testCase ) const = 0;
+ };
+ class NamePattern : public Pattern {
+ public:
+ NamePattern( std::string const& name )
+ : m_wildcardPattern( toLower( name ), CaseSensitive::No )
+ {}
+ virtual ~NamePattern();
+ virtual bool matches( TestCaseInfo const& testCase ) const {
+ return m_wildcardPattern.matches( toLower( testCase.name ) );
+ }
+ private:
+ WildcardPattern m_wildcardPattern;
+ };
+
+ class TagPattern : public Pattern {
+ public:
+ TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {}
+ virtual ~TagPattern();
+ virtual bool matches( TestCaseInfo const& testCase ) const {
+ return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end();
+ }
+ private:
+ std::string m_tag;
+ };
+
+ class ExcludedPattern : public Pattern {
+ public:
+ ExcludedPattern( Ptr<Pattern> const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {}
+ virtual ~ExcludedPattern();
+ virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); }
+ private:
+ Ptr<Pattern> m_underlyingPattern;
+ };
+
+ struct Filter {
+ std::vector<Ptr<Pattern> > m_patterns;
+
+ bool matches( TestCaseInfo const& testCase ) const {
+ // All patterns in a filter must match for the filter to be a match
+ for( std::vector<Ptr<Pattern> >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it )
+ if( !(*it)->matches( testCase ) )
+ return false;
+ return true;
+ }
+ };
+
+ public:
+ bool hasFilters() const {
+ return !m_filters.empty();
+ }
+ bool matches( TestCaseInfo const& testCase ) const {
+ // A TestSpec matches if any filter matches
+ for( std::vector<Filter>::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it )
+ if( it->matches( testCase ) )
+ return true;
+ return false;
+ }
+
+ private:
+ std::vector<Filter> m_filters;
+
+ friend class TestSpecParser;
+ };
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+namespace Catch {
+
+ class TestSpecParser {
+ enum Mode{ None, Name, QuotedName, Tag };
+ Mode m_mode;
+ bool m_exclusion;
+ std::size_t m_start, m_pos;
+ std::string m_arg;
+ TestSpec::Filter m_currentFilter;
+ TestSpec m_testSpec;
+ ITagAliasRegistry const* m_tagAliases;
+
+ public:
+ TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}
+
+ TestSpecParser& parse( std::string const& arg ) {
+ m_mode = None;
+ m_exclusion = false;
+ m_start = std::string::npos;
+ m_arg = m_tagAliases->expandAliases( arg );
+ for( m_pos = 0; m_pos < m_arg.size(); ++m_pos )
+ visitChar( m_arg[m_pos] );
+ if( m_mode == Name )
+ addPattern<TestSpec::NamePattern>();
+ return *this;
+ }
+ TestSpec testSpec() {
+ addFilter();
+ return m_testSpec;
+ }
+ private:
+ void visitChar( char c ) {
+ if( m_mode == None ) {
+ switch( c ) {
+ case ' ': return;
+ case '~': m_exclusion = true; return;
+ case '[': return startNewMode( Tag, ++m_pos );
+ case '"': return startNewMode( QuotedName, ++m_pos );
+ default: startNewMode( Name, m_pos ); break;
+ }
+ }
+ if( m_mode == Name ) {
+ if( c == ',' ) {
+ addPattern<TestSpec::NamePattern>();
+ addFilter();
+ }
+ else if( c == '[' ) {
+ if( subString() == "exclude:" )
+ m_exclusion = true;
+ else
+ addPattern<TestSpec::NamePattern>();
+ startNewMode( Tag, ++m_pos );
+ }
+ }
+ else if( m_mode == QuotedName && c == '"' )
+ addPattern<TestSpec::NamePattern>();
+ else if( m_mode == Tag && c == ']' )
+ addPattern<TestSpec::TagPattern>();
+ }
+ void startNewMode( Mode mode, std::size_t start ) {
+ m_mode = mode;
+ m_start = start;
+ }
+ std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); }
+ template<typename T>
+ void addPattern() {
+ std::string token = subString();
+ if( startsWith( token, "exclude:" ) ) {
+ m_exclusion = true;
+ token = token.substr( 8 );
+ }
+ if( !token.empty() ) {
+ Ptr<TestSpec::Pattern> pattern = new T( token );
+ if( m_exclusion )
+ pattern = new TestSpec::ExcludedPattern( pattern );
+ m_currentFilter.m_patterns.push_back( pattern );
+ }
+ m_exclusion = false;
+ m_mode = None;
+ }
+ void addFilter() {
+ if( !m_currentFilter.m_patterns.empty() ) {
+ m_testSpec.m_filters.push_back( m_currentFilter );
+ m_currentFilter = TestSpec::Filter();
+ }
+ }
+ };
+ inline TestSpec parseTestSpec( std::string const& arg ) {
+ return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec();
+ }
+
+} // namespace Catch
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// #included from: catch_interfaces_config.h
+#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace Catch {
+
+ struct Verbosity { enum Level {
+ NoOutput = 0,
+ Quiet,
+ Normal
+ }; };
+
+ struct WarnAbout { enum What {
+ Nothing = 0x00,
+ NoAssertions = 0x01
+ }; };
+
+ struct ShowDurations { enum OrNot {
+ DefaultForReporter,
+ Always,
+ Never
+ }; };
+ struct RunTests { enum InWhatOrder {
+ InDeclarationOrder,
+ InLexicographicalOrder,
+ InRandomOrder
+ }; };
+ struct UseColour { enum YesOrNo {
+ Auto,
+ Yes,
+ No
+ }; };
+
+ class TestSpec;
+
+ struct IConfig : IShared {
+
+ virtual ~IConfig();
+
+ virtual bool allowThrows() const = 0;
+ virtual std::ostream& stream() const = 0;
+ virtual std::string name() const = 0;
+ virtual bool includeSuccessfulResults() const = 0;
+ virtual bool shouldDebugBreak() const = 0;
+ virtual bool warnAboutMissingAssertions() const = 0;
+ virtual int abortAfter() const = 0;
+ virtual bool showInvisibles() const = 0;
+ virtual ShowDurations::OrNot showDurations() const = 0;
+ virtual TestSpec const& testSpec() const = 0;
+ virtual RunTests::InWhatOrder runOrder() const = 0;
+ virtual unsigned int rngSeed() const = 0;
+ virtual UseColour::YesOrNo useColour() const = 0;
+ };
+}
+
+// #included from: catch_stream.h
+#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED
+
+// #included from: catch_streambuf.h
+#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED
+
+#include <streambuf>
+
+namespace Catch {
+
+ class StreamBufBase : public std::streambuf {
+ public:
+ virtual ~StreamBufBase() CATCH_NOEXCEPT;
+ };
+}
+
+#include <streambuf>
+#include <ostream>
+#include <fstream>
+
+namespace Catch {
+
+ std::ostream& cout();
+ std::ostream& cerr();
+
+ struct IStream {
+ virtual ~IStream() CATCH_NOEXCEPT;
+ virtual std::ostream& stream() const = 0;
+ };
+
+ class FileStream : public IStream {
+ mutable std::ofstream m_ofs;
+ public:
+ FileStream( std::string const& filename );
+ virtual ~FileStream() CATCH_NOEXCEPT;
+ public: // IStream
+ virtual std::ostream& stream() const CATCH_OVERRIDE;
+ };
+
+ class CoutStream : public IStream {
+ mutable std::ostream m_os;
+ public:
+ CoutStream();
+ virtual ~CoutStream() CATCH_NOEXCEPT;
+
+ public: // IStream
+ virtual std::ostream& stream() const CATCH_OVERRIDE;
+ };
+
+ class DebugOutStream : public IStream {
+ std::auto_ptr<StreamBufBase> m_streamBuf;
+ mutable std::ostream m_os;
+ public:
+ DebugOutStream();
+ virtual ~DebugOutStream() CATCH_NOEXCEPT;
+
+ public: // IStream
+ virtual std::ostream& stream() const CATCH_OVERRIDE;
+ };
+}
+
+#include <memory>
+#include <vector>
+#include <string>
+#include <iostream>
+#include <ctime>
+
+#ifndef CATCH_CONFIG_CONSOLE_WIDTH
+#define CATCH_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+namespace Catch {
+
+ struct ConfigData {
+
+ ConfigData()
+ : listTests( false ),
+ listTags( false ),
+ listReporters( false ),
+ listTestNamesOnly( false ),
+ showSuccessfulTests( false ),
+ shouldDebugBreak( false ),
+ noThrow( false ),
+ showHelp( false ),
+ showInvisibles( false ),
+ filenamesAsTags( false ),
+ abortAfter( -1 ),
+ rngSeed( 0 ),
+ verbosity( Verbosity::Normal ),
+ warnings( WarnAbout::Nothing ),
+ showDurations( ShowDurations::DefaultForReporter ),
+ runOrder( RunTests::InDeclarationOrder ),
+ useColour( UseColour::Auto )
+ {}
+
+ bool listTests;
+ bool listTags;
+ bool listReporters;
+ bool listTestNamesOnly;
+
+ bool showSuccessfulTests;
+ bool shouldDebugBreak;
+ bool noThrow;
+ bool showHelp;
+ bool showInvisibles;
+ bool filenamesAsTags;
+
+ int abortAfter;
+ unsigned int rngSeed;
+
+ Verbosity::Level verbosity;
+ WarnAbout::What warnings;
+ ShowDurations::OrNot showDurations;
+ RunTests::InWhatOrder runOrder;
+ UseColour::YesOrNo useColour;
+
+ std::string outputFilename;
+ std::string name;
+ std::string processName;
+
+ std::vector<std::string> reporterNames;
+ std::vector<std::string> testsOrTags;
+ };
+
+ class Config : public SharedImpl<IConfig> {
+ private:
+ Config( Config const& other );
+ Config& operator = ( Config const& other );
+ virtual void dummy();
+ public:
+
+ Config()
+ {}
+
+ Config( ConfigData const& data )
+ : m_data( data ),
+ m_stream( openStream() )
+ {
+ if( !data.testsOrTags.empty() ) {
+ TestSpecParser parser( ITagAliasRegistry::get() );
+ for( std::size_t i = 0; i < data.testsOrTags.size(); ++i )
+ parser.parse( data.testsOrTags[i] );
+ m_testSpec = parser.testSpec();
+ }
+ }
+
+ virtual ~Config() {
+ }
+
+ std::string const& getFilename() const {
+ return m_data.outputFilename ;
+ }
+
+ bool listTests() const { return m_data.listTests; }
+ bool listTestNamesOnly() const { return m_data.listTestNamesOnly; }
+ bool listTags() const { return m_data.listTags; }
+ bool listReporters() const { return m_data.listReporters; }
+
+ std::string getProcessName() const { return m_data.processName; }
+
+ bool shouldDebugBreak() const { return m_data.shouldDebugBreak; }
+
+ std::vector<std::string> getReporterNames() const { return m_data.reporterNames; }
+
+ int abortAfter() const { return m_data.abortAfter; }
+
+ TestSpec const& testSpec() const { return m_testSpec; }
+
+ bool showHelp() const { return m_data.showHelp; }
+ bool showInvisibles() const { return m_data.showInvisibles; }
+
+ // IConfig interface
+ virtual bool allowThrows() const { return !m_data.noThrow; }
+ virtual std::ostream& stream() const { return m_stream->stream(); }
+ virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; }
+ virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; }
+ virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; }
+ virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; }
+ virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; }
+ virtual unsigned int rngSeed() const { return m_data.rngSeed; }
+ virtual UseColour::YesOrNo useColour() const { return m_data.useColour; }
+
+ private:
+
+ IStream const* openStream() {
+ if( m_data.outputFilename.empty() )
+ return new CoutStream();
+ else if( m_data.outputFilename[0] == '%' ) {
+ if( m_data.outputFilename == "%debug" )
+ return new DebugOutStream();
+ else
+ throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename );
+ }
+ else
+ return new FileStream( m_data.outputFilename );
+ }
+ ConfigData m_data;
+
+ std::auto_ptr<IStream const> m_stream;
+ TestSpec m_testSpec;
+ };
+
+} // end namespace Catch
+
+// #included from: catch_clara.h
+#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED
+
+// Use Catch's value for console width (store Clara's off to the side, if present)
+#ifdef CLARA_CONFIG_CONSOLE_WIDTH
+#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH
+#undef CLARA_CONFIG_CONSOLE_WIDTH
+#endif
+#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH
+
+// Declare Clara inside the Catch namespace
+#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch {
+// #included from: ../external/clara.h
+
+// Version 0.0.1.1
+
+// Only use header guard if we are not using an outer namespace
+#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE)
+
+#ifndef STITCH_CLARA_OPEN_NAMESPACE
+#define TWOBLUECUBES_CLARA_H_INCLUDED
+#define STITCH_CLARA_OPEN_NAMESPACE
+#define STITCH_CLARA_CLOSE_NAMESPACE
+#else
+#define STITCH_CLARA_CLOSE_NAMESPACE }
+#endif
+
+#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE
+
+// ----------- #included from tbc_text_format.h -----------
+
+// Only use header guard if we are not using an outer namespace
+#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE)
+#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+#define TBC_TEXT_FORMAT_H_INCLUDED
+#endif
+
+#include <string>
+#include <vector>
+#include <sstream>
+#include <algorithm>
+
+// Use optional outer namespace
+#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE {
+#endif
+
+namespace Tbc {
+
+#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH
+ const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH;
+#else
+ const unsigned int consoleWidth = 80;
+#endif
+
+ struct TextAttributes {
+ TextAttributes()
+ : initialIndent( std::string::npos ),
+ indent( 0 ),
+ width( consoleWidth-1 ),
+ tabChar( '\t' )
+ {}
+
+ TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; }
+ TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; }
+ TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; }
+ TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; }
+
+ std::size_t initialIndent; // indent of first line, or npos
+ std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos
+ std::size_t width; // maximum width of text, including indent. Longer text will wrap
+ char tabChar; // If this char is seen the indent is changed to current pos
+ };
+
+ class Text {
+ public:
+ Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() )
+ : attr( _attr )
+ {
+ std::string wrappableChars = " [({.,/|\\-";
+ std::size_t indent = _attr.initialIndent != std::string::npos
+ ? _attr.initialIndent
+ : _attr.indent;
+ std::string remainder = _str;
+
+ while( !remainder.empty() ) {
+ if( lines.size() >= 1000 ) {
+ lines.push_back( "... message truncated due to excessive size" );
+ return;
+ }
+ std::size_t tabPos = std::string::npos;
+ std::size_t width = (std::min)( remainder.size(), _attr.width - indent );
+ std::size_t pos = remainder.find_first_of( '\n' );
+ if( pos <= width ) {
+ width = pos;
+ }
+ pos = remainder.find_last_of( _attr.tabChar, width );
+ if( pos != std::string::npos ) {
+ tabPos = pos;
+ if( remainder[width] == '\n' )
+ width--;
+ remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 );
+ }
+
+ if( width == remainder.size() ) {
+ spliceLine( indent, remainder, width );
+ }
+ else if( remainder[width] == '\n' ) {
+ spliceLine( indent, remainder, width );
+ if( width <= 1 || remainder.size() != 1 )
+ remainder = remainder.substr( 1 );
+ indent = _attr.indent;
+ }
+ else {
+ pos = remainder.find_last_of( wrappableChars, width );
+ if( pos != std::string::npos && pos > 0 ) {
+ spliceLine( indent, remainder, pos );
+ if( remainder[0] == ' ' )
+ remainder = remainder.substr( 1 );
+ }
+ else {
+ spliceLine( indent, remainder, width-1 );
+ lines.back() += "-";
+ }
+ if( lines.size() == 1 )
+ indent = _attr.indent;
+ if( tabPos != std::string::npos )
+ indent += tabPos;
+ }
+ }
+ }
+
+ void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) {
+ lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) );
+ _remainder = _remainder.substr( _pos );
+ }
+
+ typedef std::vector<std::string>::const_iterator const_iterator;
+
+ const_iterator begin() const { return lines.begin(); }
+ const_iterator end() const { return lines.end(); }
+ std::string const& last() const { return lines.back(); }
+ std::size_t size() const { return lines.size(); }
+ std::string const& operator[]( std::size_t _index ) const { return lines[_index]; }
+ std::string toString() const {
+ std::ostringstream oss;
+ oss << *this;
+ return oss.str();
+ }
+
+ inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
+ for( Text::const_iterator it = _text.begin(), itEnd = _text.end();
+ it != itEnd; ++it ) {
+ if( it != _text.begin() )
+ _stream << "\n";
+ _stream << *it;
+ }
+ return _stream;
+ }
+
+ private:
+ std::string str;
+ TextAttributes attr;
+ std::vector<std::string> lines;
+ };
+
+} // end namespace Tbc
+
+#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+} // end outer namespace
+#endif
+
+#endif // TBC_TEXT_FORMAT_H_INCLUDED
+
+// ----------- end of #include from tbc_text_format.h -----------
+// ........... back in clara.h
+
+#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE
+
+// ----------- #included from clara_compilers.h -----------
+
+#ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED
+#define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED
+
+// Detect a number of compiler features - mostly C++11/14 conformance - by compiler
+// The following features are defined:
+//
+// CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported?
+// CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported?
+// CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods
+// CLARA_CONFIG_CPP11_OVERRIDE : is override supported?
+// CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr)
+
+// CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported?
+
+// CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported?
+
+// In general each macro has a _NO_<feature name> form
+// (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature.
+// Many features, at point of detection, define an _INTERNAL_ macro, so they
+// can be combined, en-mass, with the _NO_ forms later.
+
+// All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11
+
+#ifdef __clang__
+
+#if __has_feature(cxx_nullptr)
+#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
+#endif
+
+#if __has_feature(cxx_noexcept)
+#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#endif
+
+#endif // __clang__
+
+////////////////////////////////////////////////////////////////////////////////
+// GCC
+#ifdef __GNUC__
+
+#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__)
+#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
+#endif
+
+// - otherwise more recent versions define __cplusplus >= 201103L
+// and will get picked up below
+
+#endif // __GNUC__
+
+////////////////////////////////////////////////////////////////////////////////
+// Visual C++
+#ifdef _MSC_VER
+
+#if (_MSC_VER >= 1600)
+#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
+#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
+#endif
+
+#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015))
+#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#endif
+
+#endif // _MSC_VER
+
+////////////////////////////////////////////////////////////////////////////////
+// C++ language feature support
+
+// catch all support for C++11
+#if defined(__cplusplus) && __cplusplus >= 201103L
+
+#define CLARA_CPP11_OR_GREATER
+
+#if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR)
+#define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR
+#endif
+
+#ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT
+#endif
+
+#ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS
+#endif
+
+#if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE)
+#define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE
+#endif
+#if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR)
+#define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR
+#endif
+
+#endif // __cplusplus >= 201103L
+
+// Now set the actual defines based on the above + anything the user has configured
+#if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11)
+#define CLARA_CONFIG_CPP11_NULLPTR
+#endif
+#if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11)
+#define CLARA_CONFIG_CPP11_NOEXCEPT
+#endif
+#if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11)
+#define CLARA_CONFIG_CPP11_GENERATED_METHODS
+#endif
+#if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11)
+#define CLARA_CONFIG_CPP11_OVERRIDE
+#endif
+#if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11)
+#define CLARA_CONFIG_CPP11_UNIQUE_PTR
+#endif
+
+// noexcept support:
+#if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT)
+#define CLARA_NOEXCEPT noexcept
+# define CLARA_NOEXCEPT_IS(x) noexcept(x)
+#else
+#define CLARA_NOEXCEPT throw()
+# define CLARA_NOEXCEPT_IS(x)
+#endif
+
+// nullptr support
+#ifdef CLARA_CONFIG_CPP11_NULLPTR
+#define CLARA_NULL nullptr
+#else
+#define CLARA_NULL NULL
+#endif
+
+// override support
+#ifdef CLARA_CONFIG_CPP11_OVERRIDE
+#define CLARA_OVERRIDE override
+#else
+#define CLARA_OVERRIDE
+#endif
+
+// unique_ptr support
+#ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR
+# define CLARA_AUTO_PTR( T ) std::unique_ptr<T>
+#else
+# define CLARA_AUTO_PTR( T ) std::auto_ptr<T>
+#endif
+
+#endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED
+
+// ----------- end of #include from clara_compilers.h -----------
+// ........... back in clara.h
+
+#include <map>
+#include <stdexcept>
+#include <memory>
+
+// Use optional outer namespace
+#ifdef STITCH_CLARA_OPEN_NAMESPACE
+STITCH_CLARA_OPEN_NAMESPACE
+#endif
+
+namespace Clara {
+
+ struct UnpositionalTag {};
+
+ extern UnpositionalTag _;
+
+#ifdef CLARA_CONFIG_MAIN
+ UnpositionalTag _;
+#endif
+
+ namespace Detail {
+
+#ifdef CLARA_CONSOLE_WIDTH
+ const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH;
+#else
+ const unsigned int consoleWidth = 80;
+#endif
+
+ // Use this to try and stop compiler from warning about unreachable code
+ inline bool isTrue( bool value ) { return value; }
+
+ using namespace Tbc;
+
+ inline bool startsWith( std::string const& str, std::string const& prefix ) {
+ return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix;
+ }
+
+ template<typename T> struct RemoveConstRef{ typedef T type; };
+ template<typename T> struct RemoveConstRef<T&>{ typedef T type; };
+ template<typename T> struct RemoveConstRef<T const&>{ typedef T type; };
+ template<typename T> struct RemoveConstRef<T const>{ typedef T type; };
+
+ template<typename T> struct IsBool { static const bool value = false; };
+ template<> struct IsBool<bool> { static const bool value = true; };
+
+ template<typename T>
+ void convertInto( std::string const& _source, T& _dest ) {
+ std::stringstream ss;
+ ss << _source;
+ ss >> _dest;
+ if( ss.fail() )
+ throw std::runtime_error( "Unable to convert " + _source + " to destination type" );
+ }
+ inline void convertInto( std::string const& _source, std::string& _dest ) {
+ _dest = _source;
+ }
+ inline void convertInto( std::string const& _source, bool& _dest ) {
+ std::string sourceLC = _source;
+ std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower );
+ if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" )
+ _dest = true;
+ else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" )
+ _dest = false;
+ else
+ throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" );
+ }
+ inline void convertInto( bool _source, bool& _dest ) {
+ _dest = _source;
+ }
+ template<typename T>
+ inline void convertInto( bool, T& ) {
+ if( isTrue( true ) )
+ throw std::runtime_error( "Invalid conversion" );
+ }
+
+ template<typename ConfigT>
+ struct IArgFunction {
+ virtual ~IArgFunction() {}
+#ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS
+ IArgFunction() = default;
+ IArgFunction( IArgFunction const& ) = default;
+#endif
+ virtual void set( ConfigT& config, std::string const& value ) const = 0;
+ virtual void setFlag( ConfigT& config ) const = 0;
+ virtual bool takesArg() const = 0;
+ virtual IArgFunction* clone() const = 0;
+ };
+
+ template<typename ConfigT>
+ class BoundArgFunction {
+ public:
+ BoundArgFunction() : functionObj( CLARA_NULL ) {}
+ BoundArgFunction( IArgFunction<ConfigT>* _functionObj ) : functionObj( _functionObj ) {}
+ BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {}
+ BoundArgFunction& operator = ( BoundArgFunction const& other ) {
+ IArgFunction<ConfigT>* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL;
+ delete functionObj;
+ functionObj = newFunctionObj;
+ return *this;
+ }
+ ~BoundArgFunction() { delete functionObj; }
+
+ void set( ConfigT& config, std::string const& value ) const {
+ functionObj->set( config, value );
+ }
+ void setFlag( ConfigT& config ) const {
+ functionObj->setFlag( config );
+ }
+ bool takesArg() const { return functionObj->takesArg(); }
+
+ bool isSet() const {
+ return functionObj != CLARA_NULL;
+ }
+ private:
+ IArgFunction<ConfigT>* functionObj;
+ };
+
+ template<typename C>
+ struct NullBinder : IArgFunction<C>{
+ virtual void set( C&, std::string const& ) const {}
+ virtual void setFlag( C& ) const {}
+ virtual bool takesArg() const { return true; }
+ virtual IArgFunction<C>* clone() const { return new NullBinder( *this ); }
+ };
+
+ template<typename C, typename M>
+ struct BoundDataMember : IArgFunction<C>{
+ BoundDataMember( M C::* _member ) : member( _member ) {}
+ virtual void set( C& p, std::string const& stringValue ) const {
+ convertInto( stringValue, p.*member );
+ }
+ virtual void setFlag( C& p ) const {
+ convertInto( true, p.*member );
+ }
+ virtual bool takesArg() const { return !IsBool<M>::value; }
+ virtual IArgFunction<C>* clone() const { return new BoundDataMember( *this ); }
+ M C::* member;
+ };
+ template<typename C, typename M>
+ struct BoundUnaryMethod : IArgFunction<C>{
+ BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {}
+ virtual void set( C& p, std::string const& stringValue ) const {
+ typename RemoveConstRef<M>::type value;
+ convertInto( stringValue, value );
+ (p.*member)( value );
+ }
+ virtual void setFlag( C& p ) const {
+ typename RemoveConstRef<M>::type value;
+ convertInto( true, value );
+ (p.*member)( value );
+ }
+ virtual bool takesArg() const { return !IsBool<M>::value; }
+ virtual IArgFunction<C>* clone() const { return new BoundUnaryMethod( *this ); }
+ void (C::*member)( M );
+ };
+ template<typename C>
+ struct BoundNullaryMethod : IArgFunction<C>{
+ BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {}
+ virtual void set( C& p, std::string const& stringValue ) const {
+ bool value;
+ convertInto( stringValue, value );
+ if( value )
+ (p.*member)();
+ }
+ virtual void setFlag( C& p ) const {
+ (p.*member)();
+ }
+ virtual bool takesArg() const { return false; }
+ virtual IArgFunction<C>* clone() const { return new BoundNullaryMethod( *this ); }
+ void (C::*member)();
+ };
+
+ template<typename C>
+ struct BoundUnaryFunction : IArgFunction<C>{
+ BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {}
+ virtual void set( C& obj, std::string const& stringValue ) const {
+ bool value;
+ convertInto( stringValue, value );
+ if( value )
+ function( obj );
+ }
+ virtual void setFlag( C& p ) const {
+ function( p );
+ }
+ virtual bool takesArg() const { return false; }
+ virtual IArgFunction<C>* clone() const { return new BoundUnaryFunction( *this ); }
+ void (*function)( C& );
+ };
+
+ template<typename C, typename T>
+ struct BoundBinaryFunction : IArgFunction<C>{
+ BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {}
+ virtual void set( C& obj, std::string const& stringValue ) const {
+ typename RemoveConstRef<T>::type value;
+ convertInto( stringValue, value );
+ function( obj, value );
+ }
+ virtual void setFlag( C& obj ) const {
+ typename RemoveConstRef<T>::type value;
+ convertInto( true, value );
+ function( obj, value );
+ }
+ virtual bool takesArg() const { return !IsBool<T>::value; }
+ virtual IArgFunction<C>* clone() const { return new BoundBinaryFunction( *this ); }
+ void (*function)( C&, T );
+ };
+
+ } // namespace Detail
+
+ struct Parser {
+ Parser() : separators( " \t=:" ) {}
+
+ struct Token {
+ enum Type { Positional, ShortOpt, LongOpt };
+ Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {}
+ Type type;
+ std::string data;
+ };
+
+ void parseIntoTokens( int argc, char const* const argv[], std::vector<Parser::Token>& tokens ) const {
+ const std::string doubleDash = "--";
+ for( int i = 1; i < argc && argv[i] != doubleDash; ++i )
+ parseIntoTokens( argv[i] , tokens);
+ }
+ void parseIntoTokens( std::string arg, std::vector<Parser::Token>& tokens ) const {
+ while( !arg.empty() ) {
+ Parser::Token token( Parser::Token::Positional, arg );
+ arg = "";
+ if( token.data[0] == '-' ) {
+ if( token.data.size() > 1 && token.data[1] == '-' ) {
+ token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) );
+ }
+ else {
+ token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) );
+ if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) {
+ arg = "-" + token.data.substr( 1 );
+ token.data = token.data.substr( 0, 1 );
+ }
+ }
+ }
+ if( token.type != Parser::Token::Positional ) {
+ std::size_t pos = token.data.find_first_of( separators );
+ if( pos != std::string::npos ) {
+ arg = token.data.substr( pos+1 );
+ token.data = token.data.substr( 0, pos );
+ }
+ }
+ tokens.push_back( token );
+ }
+ }
+ std::string separators;
+ };
+
+ template<typename ConfigT>
+ struct CommonArgProperties {
+ CommonArgProperties() {}
+ CommonArgProperties( Detail::BoundArgFunction<ConfigT> const& _boundField ) : boundField( _boundField ) {}
+
+ Detail::BoundArgFunction<ConfigT> boundField;
+ std::string description;
+ std::string detail;
+ std::string placeholder; // Only value if boundField takes an arg
+
+ bool takesArg() const {
+ return !placeholder.empty();
+ }
+ void validate() const {
+ if( !boundField.isSet() )
+ throw std::logic_error( "option not bound" );
+ }
+ };
+ struct OptionArgProperties {
+ std::vector<std::string> shortNames;
+ std::string longName;
+
+ bool hasShortName( std::string const& shortName ) const {
+ return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end();
+ }
+ bool hasLongName( std::string const& _longName ) const {
+ return _longName == longName;
+ }
+ };
+ struct PositionalArgProperties {
+ PositionalArgProperties() : position( -1 ) {}
+ int position; // -1 means non-positional (floating)
+
+ bool isFixedPositional() const {
+ return position != -1;
+ }
+ };
+
+ template<typename ConfigT>
+ class CommandLine {
+
+ struct Arg : CommonArgProperties<ConfigT>, OptionArgProperties, PositionalArgProperties {
+ Arg() {}
+ Arg( Detail::BoundArgFunction<ConfigT> const& _boundField ) : CommonArgProperties<ConfigT>( _boundField ) {}
+
+ using CommonArgProperties<ConfigT>::placeholder; // !TBD
+
+ std::string dbgName() const {
+ if( !longName.empty() )
+ return "--" + longName;
+ if( !shortNames.empty() )
+ return "-" + shortNames[0];
+ return "positional args";
+ }
+ std::string commands() const {
+ std::ostringstream oss;
+ bool first = true;
+ std::vector<std::string>::const_iterator it = shortNames.begin(), itEnd = shortNames.end();
+ for(; it != itEnd; ++it ) {
+ if( first )
+ first = false;
+ else
+ oss << ", ";
+ oss << "-" << *it;
+ }
+ if( !longName.empty() ) {
+ if( !first )
+ oss << ", ";
+ oss << "--" << longName;
+ }
+ if( !placeholder.empty() )
+ oss << " <" << placeholder << ">";
+ return oss.str();
+ }
+ };
+
+ typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr;
+
+ friend void addOptName( Arg& arg, std::string const& optName )
+ {
+ if( optName.empty() )
+ return;
+ if( Detail::startsWith( optName, "--" ) ) {
+ if( !arg.longName.empty() )
+ throw std::logic_error( "Only one long opt may be specified. '"
+ + arg.longName
+ + "' already specified, now attempting to add '"
+ + optName + "'" );
+ arg.longName = optName.substr( 2 );
+ }
+ else if( Detail::startsWith( optName, "-" ) )
+ arg.shortNames.push_back( optName.substr( 1 ) );
+ else
+ throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" );
+ }
+ friend void setPositionalArg( Arg& arg, int position )
+ {
+ arg.position = position;
+ }
+
+ class ArgBuilder {
+ public:
+ ArgBuilder( Arg* arg ) : m_arg( arg ) {}
+
+ // Bind a non-boolean data member (requires placeholder string)
+ template<typename C, typename M>
+ void bind( M C::* field, std::string const& placeholder ) {
+ m_arg->boundField = new Detail::BoundDataMember<C,M>( field );
+ m_arg->placeholder = placeholder;
+ }
+ // Bind a boolean data member (no placeholder required)
+ template<typename C>
+ void bind( bool C::* field ) {
+ m_arg->boundField = new Detail::BoundDataMember<C,bool>( field );
+ }
+
+ // Bind a method taking a single, non-boolean argument (requires a placeholder string)
+ template<typename C, typename M>
+ void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) {
+ m_arg->boundField = new Detail::BoundUnaryMethod<C,M>( unaryMethod );
+ m_arg->placeholder = placeholder;
+ }
+
+ // Bind a method taking a single, boolean argument (no placeholder string required)
+ template<typename C>
+ void bind( void (C::* unaryMethod)( bool ) ) {
+ m_arg->boundField = new Detail::BoundUnaryMethod<C,bool>( unaryMethod );
+ }
+
+ // Bind a method that takes no arguments (will be called if opt is present)
+ template<typename C>
+ void bind( void (C::* nullaryMethod)() ) {
+ m_arg->boundField = new Detail::BoundNullaryMethod<C>( nullaryMethod );
+ }
+
+ // Bind a free function taking a single argument - the object to operate on (no placeholder string required)
+ template<typename C>
+ void bind( void (* unaryFunction)( C& ) ) {
+ m_arg->boundField = new Detail::BoundUnaryFunction<C>( unaryFunction );
+ }
+
+ // Bind a free function taking a single argument - the object to operate on (requires a placeholder string)
+ template<typename C, typename T>
+ void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) {
+ m_arg->boundField = new Detail::BoundBinaryFunction<C, T>( binaryFunction );
+ m_arg->placeholder = placeholder;
+ }
+
+ ArgBuilder& describe( std::string const& description ) {
+ m_arg->description = description;
+ return *this;
+ }
+ ArgBuilder& detail( std::string const& detail ) {
+ m_arg->detail = detail;
+ return *this;
+ }
+
+ protected:
+ Arg* m_arg;
+ };
+
+ class OptBuilder : public ArgBuilder {
+ public:
+ OptBuilder( Arg* arg ) : ArgBuilder( arg ) {}
+ OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {}
+
+ OptBuilder& operator[]( std::string const& optName ) {
+ addOptName( *ArgBuilder::m_arg, optName );
+ return *this;
+ }
+ };
+
+ public:
+
+ CommandLine()
+ : m_boundProcessName( new Detail::NullBinder<ConfigT>() ),
+ m_highestSpecifiedArgPosition( 0 ),
+ m_throwOnUnrecognisedTokens( false )
+ {}
+ CommandLine( CommandLine const& other )
+ : m_boundProcessName( other.m_boundProcessName ),
+ m_options ( other.m_options ),
+ m_positionalArgs( other.m_positionalArgs ),
+ m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ),
+ m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens )
+ {
+ if( other.m_floatingArg.get() )
+ m_floatingArg.reset( new Arg( *other.m_floatingArg ) );
+ }
+
+ CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) {
+ m_throwOnUnrecognisedTokens = shouldThrow;
+ return *this;
+ }
+
+ OptBuilder operator[]( std::string const& optName ) {
+ m_options.push_back( Arg() );
+ addOptName( m_options.back(), optName );
+ OptBuilder builder( &m_options.back() );
+ return builder;
+ }
+
+ ArgBuilder operator[]( int position ) {
+ m_positionalArgs.insert( std::make_pair( position, Arg() ) );
+ if( position > m_highestSpecifiedArgPosition )
+ m_highestSpecifiedArgPosition = position;
+ setPositionalArg( m_positionalArgs[position], position );
+ ArgBuilder builder( &m_positionalArgs[position] );
+ return builder;
+ }
+
+ // Invoke this with the _ instance
+ ArgBuilder operator[]( UnpositionalTag ) {
+ if( m_floatingArg.get() )
+ throw std::logic_error( "Only one unpositional argument can be added" );
+ m_floatingArg.reset( new Arg() );
+ ArgBuilder builder( m_floatingArg.get() );
+ return builder;
+ }
+
+ template<typename C, typename M>
+ void bindProcessName( M C::* field ) {
+ m_boundProcessName = new Detail::BoundDataMember<C,M>( field );
+ }
+ template<typename C, typename M>
+ void bindProcessName( void (C::*_unaryMethod)( M ) ) {
+ m_boundProcessName = new Detail::BoundUnaryMethod<C,M>( _unaryMethod );
+ }
+
+ void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const {
+ typename std::vector<Arg>::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it;
+ std::size_t maxWidth = 0;
+ for( it = itBegin; it != itEnd; ++it )
+ maxWidth = (std::max)( maxWidth, it->commands().size() );
+
+ for( it = itBegin; it != itEnd; ++it ) {
+ Detail::Text usage( it->commands(), Detail::TextAttributes()
+ .setWidth( maxWidth+indent )
+ .setIndent( indent ) );
+ Detail::Text desc( it->description, Detail::TextAttributes()
+ .setWidth( width - maxWidth - 3 ) );
+
+ for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) {
+ std::string usageCol = i < usage.size() ? usage[i] : "";
+ os << usageCol;
+
+ if( i < desc.size() && !desc[i].empty() )
+ os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' )
+ << desc[i];
+ os << "\n";
+ }
+ }
+ }
+ std::string optUsage() const {
+ std::ostringstream oss;
+ optUsage( oss );
+ return oss.str();
+ }
+
+ void argSynopsis( std::ostream& os ) const {
+ for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) {
+ if( i > 1 )
+ os << " ";
+ typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( i );
+ if( it != m_positionalArgs.end() )
+ os << "<" << it->second.placeholder << ">";
+ else if( m_floatingArg.get() )
+ os << "<" << m_floatingArg->placeholder << ">";
+ else
+ throw std::logic_error( "non consecutive positional arguments with no floating args" );
+ }
+ // !TBD No indication of mandatory args
+ if( m_floatingArg.get() ) {
+ if( m_highestSpecifiedArgPosition > 1 )
+ os << " ";
+ os << "[<" << m_floatingArg->placeholder << "> ...]";
+ }
+ }
+ std::string argSynopsis() const {
+ std::ostringstream oss;
+ argSynopsis( oss );
+ return oss.str();
+ }
+
+ void usage( std::ostream& os, std::string const& procName ) const {
+ validate();
+ os << "usage:\n " << procName << " ";
+ argSynopsis( os );
+ if( !m_options.empty() ) {
+ os << " [options]\n\nwhere options are: \n";
+ optUsage( os, 2 );
+ }
+ os << "\n";
+ }
+ std::string usage( std::string const& procName ) const {
+ std::ostringstream oss;
+ usage( oss, procName );
+ return oss.str();
+ }
+
+ ConfigT parse( int argc, char const* const argv[] ) const {
+ ConfigT config;
+ parseInto( argc, argv, config );
+ return config;
+ }
+
+ std::vector<Parser::Token> parseInto( int argc, char const* argv[], ConfigT& config ) const {
+ std::string processName = argv[0];
+ std::size_t lastSlash = processName.find_last_of( "/\\" );
+ if( lastSlash != std::string::npos )
+ processName = processName.substr( lastSlash+1 );
+ m_boundProcessName.set( config, processName );
+ std::vector<Parser::Token> tokens;
+ Parser parser;
+ parser.parseIntoTokens( argc, argv, tokens );
+ return populate( tokens, config );
+ }
+
+ std::vector<Parser::Token> populate( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+ validate();
+ std::vector<Parser::Token> unusedTokens = populateOptions( tokens, config );
+ unusedTokens = populateFixedArgs( unusedTokens, config );
+ unusedTokens = populateFloatingArgs( unusedTokens, config );
+ return unusedTokens;
+ }
+
+ std::vector<Parser::Token> populateOptions( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+ std::vector<Parser::Token> unusedTokens;
+ std::vector<std::string> errors;
+ for( std::size_t i = 0; i < tokens.size(); ++i ) {
+ Parser::Token const& token = tokens[i];
+ typename std::vector<Arg>::const_iterator it = m_options.begin(), itEnd = m_options.end();
+ for(; it != itEnd; ++it ) {
+ Arg const& arg = *it;
+
+ try {
+ if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) ||
+ ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) {
+ if( arg.takesArg() ) {
+ if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional )
+ errors.push_back( "Expected argument to option: " + token.data );
+ else
+ arg.boundField.set( config, tokens[++i].data );
+ }
+ else {
+ arg.boundField.setFlag( config );
+ }
+ break;
+ }
+ }
+ catch( std::exception& ex ) {
+ errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" );
+ }
+ }
+ if( it == itEnd ) {
+ if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens )
+ unusedTokens.push_back( token );
+ else if( errors.empty() && m_throwOnUnrecognisedTokens )
+ errors.push_back( "unrecognised option: " + token.data );
+ }
+ }
+ if( !errors.empty() ) {
+ std::ostringstream oss;
+ for( std::vector<std::string>::const_iterator it = errors.begin(), itEnd = errors.end();
+ it != itEnd;
+ ++it ) {
+ if( it != errors.begin() )
+ oss << "\n";
+ oss << *it;
+ }
+ throw std::runtime_error( oss.str() );
+ }
+ return unusedTokens;
+ }
+ std::vector<Parser::Token> populateFixedArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+ std::vector<Parser::Token> unusedTokens;
+ int position = 1;
+ for( std::size_t i = 0; i < tokens.size(); ++i ) {
+ Parser::Token const& token = tokens[i];
+ typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( position );
+ if( it != m_positionalArgs.end() )
+ it->second.boundField.set( config, token.data );
+ else
+ unusedTokens.push_back( token );
+ if( token.type == Parser::Token::Positional )
+ position++;
+ }
+ return unusedTokens;
+ }
+ std::vector<Parser::Token> populateFloatingArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const {
+ if( !m_floatingArg.get() )
+ return tokens;
+ std::vector<Parser::Token> unusedTokens;
+ for( std::size_t i = 0; i < tokens.size(); ++i ) {
+ Parser::Token const& token = tokens[i];
+ if( token.type == Parser::Token::Positional )
+ m_floatingArg->boundField.set( config, token.data );
+ else
+ unusedTokens.push_back( token );
+ }
+ return unusedTokens;
+ }
+
+ void validate() const
+ {
+ if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() )
+ throw std::logic_error( "No options or arguments specified" );
+
+ for( typename std::vector<Arg>::const_iterator it = m_options.begin(),
+ itEnd = m_options.end();
+ it != itEnd; ++it )
+ it->validate();
+ }
+
+ private:
+ Detail::BoundArgFunction<ConfigT> m_boundProcessName;
+ std::vector<Arg> m_options;
+ std::map<int, Arg> m_positionalArgs;
+ ArgAutoPtr m_floatingArg;
+ int m_highestSpecifiedArgPosition;
+ bool m_throwOnUnrecognisedTokens;
+ };
+
+} // end namespace Clara
+
+STITCH_CLARA_CLOSE_NAMESPACE
+#undef STITCH_CLARA_OPEN_NAMESPACE
+#undef STITCH_CLARA_CLOSE_NAMESPACE
+
+#endif // TWOBLUECUBES_CLARA_H_INCLUDED
+#undef STITCH_CLARA_OPEN_NAMESPACE
+
+// Restore Clara's value for console width, if present
+#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH
+#endif
+
+#include <fstream>
+
+namespace Catch {
+
+ inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; }
+ inline void abortAfterX( ConfigData& config, int x ) {
+ if( x < 1 )
+ throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" );
+ config.abortAfter = x;
+ }
+ inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); }
+ inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); }
+
+ inline void addWarning( ConfigData& config, std::string const& _warning ) {
+ if( _warning == "NoAssertions" )
+ config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions );
+ else
+ throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" );
+ }
+ inline void setOrder( ConfigData& config, std::string const& order ) {
+ if( startsWith( "declared", order ) )
+ config.runOrder = RunTests::InDeclarationOrder;
+ else if( startsWith( "lexical", order ) )
+ config.runOrder = RunTests::InLexicographicalOrder;
+ else if( startsWith( "random", order ) )
+ config.runOrder = RunTests::InRandomOrder;
+ else
+ throw std::runtime_error( "Unrecognised ordering: '" + order + "'" );
+ }
+ inline void setRngSeed( ConfigData& config, std::string const& seed ) {
+ if( seed == "time" ) {
+ config.rngSeed = static_cast<unsigned int>( std::time(0) );
+ }
+ else {
+ std::stringstream ss;
+ ss << seed;
+ ss >> config.rngSeed;
+ if( ss.fail() )
+ throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" );
+ }
+ }
+ inline void setVerbosity( ConfigData& config, int level ) {
+ // !TBD: accept strings?
+ config.verbosity = static_cast<Verbosity::Level>( level );
+ }
+ inline void setShowDurations( ConfigData& config, bool _showDurations ) {
+ config.showDurations = _showDurations
+ ? ShowDurations::Always
+ : ShowDurations::Never;
+ }
+ inline void setUseColour( ConfigData& config, std::string const& value ) {
+ std::string mode = toLower( value );
+
+ if( mode == "yes" )
+ config.useColour = UseColour::Yes;
+ else if( mode == "no" )
+ config.useColour = UseColour::No;
+ else if( mode == "auto" )
+ config.useColour = UseColour::Auto;
+ else
+ throw std::runtime_error( "colour mode must be one of: auto, yes or no" );
+ }
+ inline void forceColour( ConfigData& config ) {
+ config.useColour = UseColour::Yes;
+ }
+ inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) {
+ std::ifstream f( _filename.c_str() );
+ if( !f.is_open() )
+ throw std::domain_error( "Unable to load input file: " + _filename );
+
+ std::string line;
+ while( std::getline( f, line ) ) {
+ line = trim(line);
+ if( !line.empty() && !startsWith( line, "#" ) )
+ addTestOrTags( config, "\"" + line + "\"," );
+ }
+ }
+
+ inline Clara::CommandLine<ConfigData> makeCommandLineParser() {
+
+ using namespace Clara;
+ CommandLine<ConfigData> cli;
+
+ cli.bindProcessName( &ConfigData::processName );
+
+ cli["-?"]["-h"]["--help"]
+ .describe( "display usage information" )
+ .bind( &ConfigData::showHelp );
+
+ cli["-l"]["--list-tests"]
+ .describe( "list all/matching test cases" )
+ .bind( &ConfigData::listTests );
+
+ cli["-t"]["--list-tags"]
+ .describe( "list all/matching tags" )
+ .bind( &ConfigData::listTags );
+
+ cli["-s"]["--success"]
+ .describe( "include successful tests in output" )
+ .bind( &ConfigData::showSuccessfulTests );
+
+ cli["-b"]["--break"]
+ .describe( "break into debugger on failure" )
+ .bind( &ConfigData::shouldDebugBreak );
+
+ cli["-e"]["--nothrow"]
+ .describe( "skip exception tests" )
+ .bind( &ConfigData::noThrow );
+
+ cli["-i"]["--invisibles"]
+ .describe( "show invisibles (tabs, newlines)" )
+ .bind( &ConfigData::showInvisibles );
+
+ cli["-o"]["--out"]
+ .describe( "output filename" )
+ .bind( &ConfigData::outputFilename, "filename" );
+
+ cli["-r"]["--reporter"]
+// .placeholder( "name[:filename]" )
+ .describe( "reporter to use (defaults to console)" )
+ .bind( &addReporterName, "name" );
+
+ cli["-n"]["--name"]
+ .describe( "suite name" )
+ .bind( &ConfigData::name, "name" );
+
+ cli["-a"]["--abort"]
+ .describe( "abort at first failure" )
+ .bind( &abortAfterFirst );
+
+ cli["-x"]["--abortx"]
+ .describe( "abort after x failures" )
+ .bind( &abortAfterX, "no. failures" );
+
+ cli["-w"]["--warn"]
+ .describe( "enable warnings" )
+ .bind( &addWarning, "warning name" );
+
+// - needs updating if reinstated
+// cli.into( &setVerbosity )
+// .describe( "level of verbosity (0=no output)" )
+// .shortOpt( "v")
+// .longOpt( "verbosity" )
+// .placeholder( "level" );
+
+ cli[_]
+ .describe( "which test or tests to use" )
+ .bind( &addTestOrTags, "test name, pattern or tags" );
+
+ cli["-d"]["--durations"]
+ .describe( "show test durations" )
+ .bind( &setShowDurations, "yes|no" );
+
+ cli["-f"]["--input-file"]
+ .describe( "load test names to run from a file" )
+ .bind( &loadTestNamesFromFile, "filename" );
+
+ cli["-#"]["--filenames-as-tags"]
+ .describe( "adds a tag for the filename" )
+ .bind( &ConfigData::filenamesAsTags );
+
+ // Less common commands which don't have a short form
+ cli["--list-test-names-only"]
+ .describe( "list all/matching test cases names only" )
+ .bind( &ConfigData::listTestNamesOnly );
+
+ cli["--list-reporters"]
+ .describe( "list all reporters" )
+ .bind( &ConfigData::listReporters );
+
+ cli["--order"]
+ .describe( "test case order (defaults to decl)" )
+ .bind( &setOrder, "decl|lex|rand" );
+
+ cli["--rng-seed"]
+ .describe( "set a specific seed for random numbers" )
+ .bind( &setRngSeed, "'time'|number" );
+
+ cli["--force-colour"]
+ .describe( "force colourised output (deprecated)" )
+ .bind( &forceColour );
+
+ cli["--use-colour"]
+ .describe( "should output be colourised" )
+ .bind( &setUseColour, "yes|no" );
+
+ return cli;
+ }
+
+} // end namespace Catch
+
+// #included from: internal/catch_list.hpp
+#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED
+
+// #included from: catch_text.h
+#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED
+
+#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH
+
+#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch
+// #included from: ../external/tbc_text_format.h
+// Only use header guard if we are not using an outer namespace
+#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED
+# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+# endif
+# else
+# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED
+# endif
+#endif
+#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+#include <string>
+#include <vector>
+#include <sstream>
+
+// Use optional outer namespace
+#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE {
+#endif
+
+namespace Tbc {
+
+#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH
+ const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH;
+#else
+ const unsigned int consoleWidth = 80;
+#endif
+
+ struct TextAttributes {
+ TextAttributes()
+ : initialIndent( std::string::npos ),
+ indent( 0 ),
+ width( consoleWidth-1 ),
+ tabChar( '\t' )
+ {}
+
+ TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; }
+ TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; }
+ TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; }
+ TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; }
+
+ std::size_t initialIndent; // indent of first line, or npos
+ std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos
+ std::size_t width; // maximum width of text, including indent. Longer text will wrap
+ char tabChar; // If this char is seen the indent is changed to current pos
+ };
+
+ class Text {
+ public:
+ Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() )
+ : attr( _attr )
+ {
+ std::string wrappableChars = " [({.,/|\\-";
+ std::size_t indent = _attr.initialIndent != std::string::npos
+ ? _attr.initialIndent
+ : _attr.indent;
+ std::string remainder = _str;
+
+ while( !remainder.empty() ) {
+ if( lines.size() >= 1000 ) {
+ lines.push_back( "... message truncated due to excessive size" );
+ return;
+ }
+ std::size_t tabPos = std::string::npos;
+ std::size_t width = (std::min)( remainder.size(), _attr.width - indent );
+ std::size_t pos = remainder.find_first_of( '\n' );
+ if( pos <= width ) {
+ width = pos;
+ }
+ pos = remainder.find_last_of( _attr.tabChar, width );
+ if( pos != std::string::npos ) {
+ tabPos = pos;
+ if( remainder[width] == '\n' )
+ width--;
+ remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 );
+ }
+
+ if( width == remainder.size() ) {
+ spliceLine( indent, remainder, width );
+ }
+ else if( remainder[width] == '\n' ) {
+ spliceLine( indent, remainder, width );
+ if( width <= 1 || remainder.size() != 1 )
+ remainder = remainder.substr( 1 );
+ indent = _attr.indent;
+ }
+ else {
+ pos = remainder.find_last_of( wrappableChars, width );
+ if( pos != std::string::npos && pos > 0 ) {
+ spliceLine( indent, remainder, pos );
+ if( remainder[0] == ' ' )
+ remainder = remainder.substr( 1 );
+ }
+ else {
+ spliceLine( indent, remainder, width-1 );
+ lines.back() += "-";
+ }
+ if( lines.size() == 1 )
+ indent = _attr.indent;
+ if( tabPos != std::string::npos )
+ indent += tabPos;
+ }
+ }
+ }
+
+ void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) {
+ lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) );
+ _remainder = _remainder.substr( _pos );
+ }
+
+ typedef std::vector<std::string>::const_iterator const_iterator;
+
+ const_iterator begin() const { return lines.begin(); }
+ const_iterator end() const { return lines.end(); }
+ std::string const& last() const { return lines.back(); }
+ std::size_t size() const { return lines.size(); }
+ std::string const& operator[]( std::size_t _index ) const { return lines[_index]; }
+ std::string toString() const {
+ std::ostringstream oss;
+ oss << *this;
+ return oss.str();
+ }
+
+ inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) {
+ for( Text::const_iterator it = _text.begin(), itEnd = _text.end();
+ it != itEnd; ++it ) {
+ if( it != _text.begin() )
+ _stream << "\n";
+ _stream << *it;
+ }
+ return _stream;
+ }
+
+ private:
+ std::string str;
+ TextAttributes attr;
+ std::vector<std::string> lines;
+ };
+
+} // end namespace Tbc
+
+#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+} // end outer namespace
+#endif
+
+#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED
+#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE
+
+namespace Catch {
+ using Tbc::Text;
+ using Tbc::TextAttributes;
+}
+
+// #included from: catch_console_colour.hpp
+#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED
+
+namespace Catch {
+
+ struct Colour {
+ enum Code {
+ None = 0,
+
+ White,
+ Red,
+ Green,
+ Blue,
+ Cyan,
+ Yellow,
+ Grey,
+
+ Bright = 0x10,
+
+ BrightRed = Bright | Red,
+ BrightGreen = Bright | Green,
+ LightGrey = Bright | Grey,
+ BrightWhite = Bright | White,
+
+ // By intention
+ FileName = LightGrey,
+ Warning = Yellow,
+ ResultError = BrightRed,
+ ResultSuccess = BrightGreen,
+ ResultExpectedFailure = Warning,
+
+ Error = BrightRed,
+ Success = Green,
+
+ OriginalExpression = Cyan,
+ ReconstructedExpression = Yellow,
+
+ SecondaryText = LightGrey,
+ Headers = White
+ };
+
+ // Use constructed object for RAII guard
+ Colour( Code _colourCode );
+ Colour( Colour const& other );
+ ~Colour();
+
+ // Use static method for one-shot changes
+ static void use( Code _colourCode );
+
+ private:
+ bool m_moved;
+ };
+
+ inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; }
+
+} // end namespace Catch
+
+// #included from: catch_interfaces_reporter.h
+#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED
+
+#include <string>
+#include <ostream>
+#include <map>
+#include <assert.h>
+
+namespace Catch
+{
+ struct ReporterConfig {
+ explicit ReporterConfig( Ptr<IConfig const> const& _fullConfig )
+ : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {}
+
+ ReporterConfig( Ptr<IConfig const> const& _fullConfig, std::ostream& _stream )
+ : m_stream( &_stream ), m_fullConfig( _fullConfig ) {}
+
+ std::ostream& stream() const { return *m_stream; }
+ Ptr<IConfig const> fullConfig() const { return m_fullConfig; }
+
+ private:
+ std::ostream* m_stream;
+ Ptr<IConfig const> m_fullConfig;
+ };
+
+ struct ReporterPreferences {
+ ReporterPreferences()
+ : shouldRedirectStdOut( false )
+ {}
+
+ bool shouldRedirectStdOut;
+ };
+
+ template<typename T>
+ struct LazyStat : Option<T> {
+ LazyStat() : used( false ) {}
+ LazyStat& operator=( T const& _value ) {
+ Option<T>::operator=( _value );
+ used = false;
+ return *this;
+ }
+ void reset() {
+ Option<T>::reset();
+ used = false;
+ }
+ bool used;
+ };
+
+ struct TestRunInfo {
+ TestRunInfo( std::string const& _name ) : name( _name ) {}
+ std::string name;
+ };
+ struct GroupInfo {
+ GroupInfo( std::string const& _name,
+ std::size_t _groupIndex,
+ std::size_t _groupsCount )
+ : name( _name ),
+ groupIndex( _groupIndex ),
+ groupsCounts( _groupsCount )
+ {}
+
+ std::string name;
+ std::size_t groupIndex;
+ std::size_t groupsCounts;
+ };
+
+ struct AssertionStats {
+ AssertionStats( AssertionResult const& _assertionResult,
+ std::vector<MessageInfo> const& _infoMessages,
+ Totals const& _totals )
+ : assertionResult( _assertionResult ),
+ infoMessages( _infoMessages ),
+ totals( _totals )
+ {
+ if( assertionResult.hasMessage() ) {
+ // Copy message into messages list.
+ // !TBD This should have been done earlier, somewhere
+ MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() );
+ builder << assertionResult.getMessage();
+ builder.m_info.message = builder.m_stream.str();
+
+ infoMessages.push_back( builder.m_info );
+ }
+ }
+ virtual ~AssertionStats();
+
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ AssertionStats( AssertionStats const& ) = default;
+ AssertionStats( AssertionStats && ) = default;
+ AssertionStats& operator = ( AssertionStats const& ) = default;
+ AssertionStats& operator = ( AssertionStats && ) = default;
+# endif
+
+ AssertionResult assertionResult;
+ std::vector<MessageInfo> infoMessages;
+ Totals totals;
+ };
+
+ struct SectionStats {
+ SectionStats( SectionInfo const& _sectionInfo,
+ Counts const& _assertions,
+ double _durationInSeconds,
+ bool _missingAssertions )
+ : sectionInfo( _sectionInfo ),
+ assertions( _assertions ),
+ durationInSeconds( _durationInSeconds ),
+ missingAssertions( _missingAssertions )
+ {}
+ virtual ~SectionStats();
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ SectionStats( SectionStats const& ) = default;
+ SectionStats( SectionStats && ) = default;
+ SectionStats& operator = ( SectionStats const& ) = default;
+ SectionStats& operator = ( SectionStats && ) = default;
+# endif
+
+ SectionInfo sectionInfo;
+ Counts assertions;
+ double durationInSeconds;
+ bool missingAssertions;
+ };
+
+ struct TestCaseStats {
+ TestCaseStats( TestCaseInfo const& _testInfo,
+ Totals const& _totals,
+ std::string const& _stdOut,
+ std::string const& _stdErr,
+ bool _aborting )
+ : testInfo( _testInfo ),
+ totals( _totals ),
+ stdOut( _stdOut ),
+ stdErr( _stdErr ),
+ aborting( _aborting )
+ {}
+ virtual ~TestCaseStats();
+
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ TestCaseStats( TestCaseStats const& ) = default;
+ TestCaseStats( TestCaseStats && ) = default;
+ TestCaseStats& operator = ( TestCaseStats const& ) = default;
+ TestCaseStats& operator = ( TestCaseStats && ) = default;
+# endif
+
+ TestCaseInfo testInfo;
+ Totals totals;
+ std::string stdOut;
+ std::string stdErr;
+ bool aborting;
+ };
+
+ struct TestGroupStats {
+ TestGroupStats( GroupInfo const& _groupInfo,
+ Totals const& _totals,
+ bool _aborting )
+ : groupInfo( _groupInfo ),
+ totals( _totals ),
+ aborting( _aborting )
+ {}
+ TestGroupStats( GroupInfo const& _groupInfo )
+ : groupInfo( _groupInfo ),
+ aborting( false )
+ {}
+ virtual ~TestGroupStats();
+
+# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ TestGroupStats( TestGroupStats const& ) = default;
+ TestGroupStats( TestGroupStats && ) = default;
+ TestGroupStats& operator = ( TestGroupStats const& ) = default;
+ TestGroupStats& operator = ( TestGroupStats && ) = default;
+# endif
+
+ GroupInfo groupInfo;
+ Totals totals;
+ bool aborting;
+ };
+
+ struct TestRunStats {
+ TestRunStats( TestRunInfo const& _runInfo,
+ Totals const& _totals,
+ bool _aborting )
+ : runInfo( _runInfo ),
+ totals( _totals ),
+ aborting( _aborting )
+ {}
+ virtual ~TestRunStats();
+
+# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS
+ TestRunStats( TestRunStats const& _other )
+ : runInfo( _other.runInfo ),
+ totals( _other.totals ),
+ aborting( _other.aborting )
+ {}
+# else
+ TestRunStats( TestRunStats const& ) = default;
+ TestRunStats( TestRunStats && ) = default;
+ TestRunStats& operator = ( TestRunStats const& ) = default;
+ TestRunStats& operator = ( TestRunStats && ) = default;
+# endif
+
+ TestRunInfo runInfo;
+ Totals totals;
+ bool aborting;
+ };
+
+ struct IStreamingReporter : IShared {
+ virtual ~IStreamingReporter();
+
+ // Implementing class must also provide the following static method:
+ // static std::string getDescription();
+
+ virtual ReporterPreferences getPreferences() const = 0;
+
+ virtual void noMatchingTestCases( std::string const& spec ) = 0;
+
+ virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0;
+ virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0;
+
+ virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0;
+ virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0;
+
+ virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0;
+
+ // The return value indicates if the messages buffer should be cleared:
+ virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0;
+
+ virtual void sectionEnded( SectionStats const& sectionStats ) = 0;
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0;
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0;
+ virtual void testRunEnded( TestRunStats const& testRunStats ) = 0;
+
+ virtual void skipTest( TestCaseInfo const& testInfo ) = 0;
+ };
+
+ struct IReporterFactory : IShared {
+ virtual ~IReporterFactory();
+ virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0;
+ virtual std::string getDescription() const = 0;
+ };
+
+ struct IReporterRegistry {
+ typedef std::map<std::string, Ptr<IReporterFactory> > FactoryMap;
+ typedef std::vector<Ptr<IReporterFactory> > Listeners;
+
+ virtual ~IReporterRegistry();
+ virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig const> const& config ) const = 0;
+ virtual FactoryMap const& getFactories() const = 0;
+ virtual Listeners const& getListeners() const = 0;
+ };
+
+ Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingReporter, Ptr<IStreamingReporter> const& additionalReporter );
+
+}
+
+#include <limits>
+#include <algorithm>
+
+namespace Catch {
+
+ inline std::size_t listTests( Config const& config ) {
+
+ TestSpec testSpec = config.testSpec();
+ if( config.testSpec().hasFilters() )
+ Catch::cout() << "Matching test cases:\n";
+ else {
+ Catch::cout() << "All available test cases:\n";
+ testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
+ }
+
+ std::size_t matchedTests = 0;
+ TextAttributes nameAttr, tagsAttr;
+ nameAttr.setInitialIndent( 2 ).setIndent( 4 );
+ tagsAttr.setIndent( 6 );
+
+ std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+ for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
+ it != itEnd;
+ ++it ) {
+ matchedTests++;
+ TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
+ Colour::Code colour = testCaseInfo.isHidden()
+ ? Colour::SecondaryText
+ : Colour::None;
+ Colour colourGuard( colour );
+
+ Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl;
+ if( !testCaseInfo.tags.empty() )
+ Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl;
+ }
+
+ if( !config.testSpec().hasFilters() )
+ Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl;
+ else
+ Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl;
+ return matchedTests;
+ }
+
+ inline std::size_t listTestsNamesOnly( Config const& config ) {
+ TestSpec testSpec = config.testSpec();
+ if( !config.testSpec().hasFilters() )
+ testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
+ std::size_t matchedTests = 0;
+ std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+ for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
+ it != itEnd;
+ ++it ) {
+ matchedTests++;
+ TestCaseInfo const& testCaseInfo = it->getTestCaseInfo();
+ Catch::cout() << testCaseInfo.name << std::endl;
+ }
+ return matchedTests;
+ }
+
+ struct TagInfo {
+ TagInfo() : count ( 0 ) {}
+ void add( std::string const& spelling ) {
+ ++count;
+ spellings.insert( spelling );
+ }
+ std::string all() const {
+ std::string out;
+ for( std::set<std::string>::const_iterator it = spellings.begin(), itEnd = spellings.end();
+ it != itEnd;
+ ++it )
+ out += "[" + *it + "]";
+ return out;
+ }
+ std::set<std::string> spellings;
+ std::size_t count;
+ };
+
+ inline std::size_t listTags( Config const& config ) {
+ TestSpec testSpec = config.testSpec();
+ if( config.testSpec().hasFilters() )
+ Catch::cout() << "Tags for matching test cases:\n";
+ else {
+ Catch::cout() << "All available tags:\n";
+ testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec();
+ }
+
+ std::map<std::string, TagInfo> tagCounts;
+
+ std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config );
+ for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end();
+ it != itEnd;
+ ++it ) {
+ for( std::set<std::string>::const_iterator tagIt = it->getTestCaseInfo().tags.begin(),
+ tagItEnd = it->getTestCaseInfo().tags.end();
+ tagIt != tagItEnd;
+ ++tagIt ) {
+ std::string tagName = *tagIt;
+ std::string lcaseTagName = toLower( tagName );
+ std::map<std::string, TagInfo>::iterator countIt = tagCounts.find( lcaseTagName );
+ if( countIt == tagCounts.end() )
+ countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first;
+ countIt->second.add( tagName );
+ }
+ }
+
+ for( std::map<std::string, TagInfo>::const_iterator countIt = tagCounts.begin(),
+ countItEnd = tagCounts.end();
+ countIt != countItEnd;
+ ++countIt ) {
+ std::ostringstream oss;
+ oss << " " << std::setw(2) << countIt->second.count << " ";
+ Text wrapper( countIt->second.all(), TextAttributes()
+ .setInitialIndent( 0 )
+ .setIndent( oss.str().size() )
+ .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) );
+ Catch::cout() << oss.str() << wrapper << "\n";
+ }
+ Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl;
+ return tagCounts.size();
+ }
+
+ inline std::size_t listReporters( Config const& /*config*/ ) {
+ Catch::cout() << "Available reporters:\n";
+ IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories();
+ IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it;
+ std::size_t maxNameLen = 0;
+ for(it = itBegin; it != itEnd; ++it )
+ maxNameLen = (std::max)( maxNameLen, it->first.size() );
+
+ for(it = itBegin; it != itEnd; ++it ) {
+ Text wrapper( it->second->getDescription(), TextAttributes()
+ .setInitialIndent( 0 )
+ .setIndent( 7+maxNameLen )
+ .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) );
+ Catch::cout() << " "
+ << it->first
+ << ":"
+ << std::string( maxNameLen - it->first.size() + 2, ' ' )
+ << wrapper << "\n";
+ }
+ Catch::cout() << std::endl;
+ return factories.size();
+ }
+
+ inline Option<std::size_t> list( Config const& config ) {
+ Option<std::size_t> listedCount;
+ if( config.listTests() )
+ listedCount = listedCount.valueOr(0) + listTests( config );
+ if( config.listTestNamesOnly() )
+ listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config );
+ if( config.listTags() )
+ listedCount = listedCount.valueOr(0) + listTags( config );
+ if( config.listReporters() )
+ listedCount = listedCount.valueOr(0) + listReporters( config );
+ return listedCount;
+ }
+
+} // end namespace Catch
+
+// #included from: internal/catch_run_context.hpp
+#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED
+
+// #included from: catch_test_case_tracker.hpp
+#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED
+
+#include <map>
+#include <string>
+#include <assert.h>
+#include <vector>
+
+namespace Catch {
+namespace TestCaseTracking {
+
+ struct ITracker : SharedImpl<> {
+ virtual ~ITracker();
+
+ // static queries
+ virtual std::string name() const = 0;
+
+ // dynamic queries
+ virtual bool isComplete() const = 0; // Successfully completed or failed
+ virtual bool isSuccessfullyCompleted() const = 0;
+ virtual bool isOpen() const = 0; // Started but not complete
+ virtual bool hasChildren() const = 0;
+
+ virtual ITracker& parent() = 0;
+
+ // actions
+ virtual void close() = 0; // Successfully complete
+ virtual void fail() = 0;
+ virtual void markAsNeedingAnotherRun() = 0;
+
+ virtual void addChild( Ptr<ITracker> const& child ) = 0;
+ virtual ITracker* findChild( std::string const& name ) = 0;
+ virtual void openChild() = 0;
+ };
+
+ class TrackerContext {
+
+ enum RunState {
+ NotStarted,
+ Executing,
+ CompletedCycle
+ };
+
+ Ptr<ITracker> m_rootTracker;
+ ITracker* m_currentTracker;
+ RunState m_runState;
+
+ public:
+
+ static TrackerContext& instance() {
+ static TrackerContext s_instance;
+ return s_instance;
+ }
+
+ TrackerContext()
+ : m_currentTracker( CATCH_NULL ),
+ m_runState( NotStarted )
+ {}
+
+ ITracker& startRun();
+
+ void endRun() {
+ m_rootTracker.reset();
+ m_currentTracker = CATCH_NULL;
+ m_runState = NotStarted;
+ }
+
+ void startCycle() {
+ m_currentTracker = m_rootTracker.get();
+ m_runState = Executing;
+ }
+ void completeCycle() {
+ m_runState = CompletedCycle;
+ }
+
+ bool completedCycle() const {
+ return m_runState == CompletedCycle;
+ }
+ ITracker& currentTracker() {
+ return *m_currentTracker;
+ }
+ void setCurrentTracker( ITracker* tracker ) {
+ m_currentTracker = tracker;
+ }
+ };
+
+ class TrackerBase : public ITracker {
+ protected:
+ enum CycleState {
+ NotStarted,
+ Executing,
+ ExecutingChildren,
+ NeedsAnotherRun,
+ CompletedSuccessfully,
+ Failed
+ };
+ class TrackerHasName {
+ std::string m_name;
+ public:
+ TrackerHasName( std::string const& name ) : m_name( name ) {}
+ bool operator ()( Ptr<ITracker> const& tracker ) {
+ return tracker->name() == m_name;
+ }
+ };
+ typedef std::vector<Ptr<ITracker> > Children;
+ std::string m_name;
+ TrackerContext& m_ctx;
+ ITracker* m_parent;
+ Children m_children;
+ CycleState m_runState;
+ public:
+ TrackerBase( std::string const& name, TrackerContext& ctx, ITracker* parent )
+ : m_name( name ),
+ m_ctx( ctx ),
+ m_parent( parent ),
+ m_runState( NotStarted )
+ {}
+ virtual ~TrackerBase();
+
+ virtual std::string name() const CATCH_OVERRIDE {
+ return m_name;
+ }
+ virtual bool isComplete() const CATCH_OVERRIDE {
+ return m_runState == CompletedSuccessfully || m_runState == Failed;
+ }
+ virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE {
+ return m_runState == CompletedSuccessfully;
+ }
+ virtual bool isOpen() const CATCH_OVERRIDE {
+ return m_runState != NotStarted && !isComplete();
+ }
+ virtual bool hasChildren() const CATCH_OVERRIDE {
+ return !m_children.empty();
+ }
+
+ virtual void addChild( Ptr<ITracker> const& child ) CATCH_OVERRIDE {
+ m_children.push_back( child );
+ }
+
+ virtual ITracker* findChild( std::string const& name ) CATCH_OVERRIDE {
+ Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( name ) );
+ return( it != m_children.end() )
+ ? it->get()
+ : CATCH_NULL;
+ }
+ virtual ITracker& parent() CATCH_OVERRIDE {
+ assert( m_parent ); // Should always be non-null except for root
+ return *m_parent;
+ }
+
+ virtual void openChild() CATCH_OVERRIDE {
+ if( m_runState != ExecutingChildren ) {
+ m_runState = ExecutingChildren;
+ if( m_parent )
+ m_parent->openChild();
+ }
+ }
+ void open() {
+ m_runState = Executing;
+ moveToThis();
+ if( m_parent )
+ m_parent->openChild();
+ }
+
+ virtual void close() CATCH_OVERRIDE {
+
+ // Close any still open children (e.g. generators)
+ while( &m_ctx.currentTracker() != this )
+ m_ctx.currentTracker().close();
+
+ switch( m_runState ) {
+ case NotStarted:
+ case CompletedSuccessfully:
+ case Failed:
+ throw std::logic_error( "Illogical state" );
+
+ case NeedsAnotherRun:
+ break;;
+
+ case Executing:
+ m_runState = CompletedSuccessfully;
+ break;
+ case ExecutingChildren:
+ if( m_children.empty() || m_children.back()->isComplete() )
+ m_runState = CompletedSuccessfully;
+ break;
+
+ default:
+ throw std::logic_error( "Unexpected state" );
+ }
+ moveToParent();
+ m_ctx.completeCycle();
+ }
+ virtual void fail() CATCH_OVERRIDE {
+ m_runState = Failed;
+ if( m_parent )
+ m_parent->markAsNeedingAnotherRun();
+ moveToParent();
+ m_ctx.completeCycle();
+ }
+ virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE {
+ m_runState = NeedsAnotherRun;
+ }
+ private:
+ void moveToParent() {
+ assert( m_parent );
+ m_ctx.setCurrentTracker( m_parent );
+ }
+ void moveToThis() {
+ m_ctx.setCurrentTracker( this );
+ }
+ };
+
+ class SectionTracker : public TrackerBase {
+ public:
+ SectionTracker( std::string const& name, TrackerContext& ctx, ITracker* parent )
+ : TrackerBase( name, ctx, parent )
+ {}
+ virtual ~SectionTracker();
+
+ static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) {
+ SectionTracker* section = CATCH_NULL;
+
+ ITracker& currentTracker = ctx.currentTracker();
+ if( ITracker* childTracker = currentTracker.findChild( name ) ) {
+ section = dynamic_cast<SectionTracker*>( childTracker );
+ assert( section );
+ }
+ else {
+ section = new SectionTracker( name, ctx, &currentTracker );
+ currentTracker.addChild( section );
+ }
+ if( !ctx.completedCycle() && !section->isComplete() ) {
+
+ section->open();
+ }
+ return *section;
+ }
+ };
+
+ class IndexTracker : public TrackerBase {
+ int m_size;
+ int m_index;
+ public:
+ IndexTracker( std::string const& name, TrackerContext& ctx, ITracker* parent, int size )
+ : TrackerBase( name, ctx, parent ),
+ m_size( size ),
+ m_index( -1 )
+ {}
+ virtual ~IndexTracker();
+
+ static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) {
+ IndexTracker* tracker = CATCH_NULL;
+
+ ITracker& currentTracker = ctx.currentTracker();
+ if( ITracker* childTracker = currentTracker.findChild( name ) ) {
+ tracker = dynamic_cast<IndexTracker*>( childTracker );
+ assert( tracker );
+ }
+ else {
+ tracker = new IndexTracker( name, ctx, &currentTracker, size );
+ currentTracker.addChild( tracker );
+ }
+
+ if( !ctx.completedCycle() && !tracker->isComplete() ) {
+ if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun )
+ tracker->moveNext();
+ tracker->open();
+ }
+
+ return *tracker;
+ }
+
+ int index() const { return m_index; }
+
+ void moveNext() {
+ m_index++;
+ m_children.clear();
+ }
+
+ virtual void close() CATCH_OVERRIDE {
+ TrackerBase::close();
+ if( m_runState == CompletedSuccessfully && m_index < m_size-1 )
+ m_runState = Executing;
+ }
+ };
+
+ inline ITracker& TrackerContext::startRun() {
+ m_rootTracker = new SectionTracker( "{root}", *this, CATCH_NULL );
+ m_currentTracker = CATCH_NULL;
+ m_runState = Executing;
+ return *m_rootTracker;
+ }
+
+} // namespace TestCaseTracking
+
+using TestCaseTracking::ITracker;
+using TestCaseTracking::TrackerContext;
+using TestCaseTracking::SectionTracker;
+using TestCaseTracking::IndexTracker;
+
+} // namespace Catch
+
+// #included from: catch_fatal_condition.hpp
+#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED
+
+namespace Catch {
+
+ // Report the error condition then exit the process
+ inline void fatal( std::string const& message, int exitCode ) {
+ IContext& context = Catch::getCurrentContext();
+ IResultCapture* resultCapture = context.getResultCapture();
+ resultCapture->handleFatalErrorCondition( message );
+
+ if( Catch::alwaysTrue() ) // avoids "no return" warnings
+ exit( exitCode );
+ }
+
+} // namespace Catch
+
+#if defined ( CATCH_PLATFORM_WINDOWS ) /////////////////////////////////////////
+
+namespace Catch {
+
+ struct FatalConditionHandler {
+ void reset() {}
+ };
+
+} // namespace Catch
+
+#else // Not Windows - assumed to be POSIX compatible //////////////////////////
+
+#include <signal.h>
+
+namespace Catch {
+
+ struct SignalDefs { int id; const char* name; };
+ extern SignalDefs signalDefs[];
+ SignalDefs signalDefs[] = {
+ { SIGINT, "SIGINT - Terminal interrupt signal" },
+ { SIGILL, "SIGILL - Illegal instruction signal" },
+ { SIGFPE, "SIGFPE - Floating point error signal" },
+ { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
+ { SIGTERM, "SIGTERM - Termination request signal" },
+ { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
+ };
+
+ struct FatalConditionHandler {
+
+ static void handleSignal( int sig ) {
+ for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+ if( sig == signalDefs[i].id )
+ fatal( signalDefs[i].name, -sig );
+ fatal( "<unknown signal>", -sig );
+ }
+
+ FatalConditionHandler() : m_isSet( true ) {
+ for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+ signal( signalDefs[i].id, handleSignal );
+ }
+ ~FatalConditionHandler() {
+ reset();
+ }
+ void reset() {
+ if( m_isSet ) {
+ for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i )
+ signal( signalDefs[i].id, SIG_DFL );
+ m_isSet = false;
+ }
+ }
+
+ bool m_isSet;
+ };
+
+} // namespace Catch
+
+#endif // not Windows
+
+#include <set>
+#include <string>
+
+namespace Catch {
+
+ class StreamRedirect {
+
+ public:
+ StreamRedirect( std::ostream& stream, std::string& targetString )
+ : m_stream( stream ),
+ m_prevBuf( stream.rdbuf() ),
+ m_targetString( targetString )
+ {
+ stream.rdbuf( m_oss.rdbuf() );
+ }
+
+ ~StreamRedirect() {
+ m_targetString += m_oss.str();
+ m_stream.rdbuf( m_prevBuf );
+ }
+
+ private:
+ std::ostream& m_stream;
+ std::streambuf* m_prevBuf;
+ std::ostringstream m_oss;
+ std::string& m_targetString;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ class RunContext : public IResultCapture, public IRunner {
+
+ RunContext( RunContext const& );
+ void operator =( RunContext const& );
+
+ public:
+
+ explicit RunContext( Ptr<IConfig const> const& _config, Ptr<IStreamingReporter> const& reporter )
+ : m_runInfo( _config->name() ),
+ m_context( getCurrentMutableContext() ),
+ m_activeTestCase( CATCH_NULL ),
+ m_config( _config ),
+ m_reporter( reporter )
+ {
+ m_context.setRunner( this );
+ m_context.setConfig( m_config );
+ m_context.setResultCapture( this );
+ m_reporter->testRunStarting( m_runInfo );
+ }
+
+ virtual ~RunContext() {
+ m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) );
+ }
+
+ void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) {
+ m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) );
+ }
+ void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) {
+ m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) );
+ }
+
+ Totals runTest( TestCase const& testCase ) {
+ Totals prevTotals = m_totals;
+
+ std::string redirectedCout;
+ std::string redirectedCerr;
+
+ TestCaseInfo testInfo = testCase.getTestCaseInfo();
+
+ m_reporter->testCaseStarting( testInfo );
+
+ m_activeTestCase = &testCase;
+
+ do {
+ m_trackerContext.startRun();
+ do {
+ m_trackerContext.startCycle();
+ m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, testInfo.name );
+ runCurrentTest( redirectedCout, redirectedCerr );
+ }
+ while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() );
+ }
+ // !TBD: deprecated - this will be replaced by indexed trackers
+ while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() );
+
+ Totals deltaTotals = m_totals.delta( prevTotals );
+ if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) {
+ deltaTotals.assertions.failed++;
+ deltaTotals.testCases.passed--;
+ deltaTotals.testCases.failed++;
+ }
+ m_totals.testCases += deltaTotals.testCases;
+ m_reporter->testCaseEnded( TestCaseStats( testInfo,
+ deltaTotals,
+ redirectedCout,
+ redirectedCerr,
+ aborting() ) );
+
+ m_activeTestCase = CATCH_NULL;
+ m_testCaseTracker = CATCH_NULL;
+
+ return deltaTotals;
+ }
+
+ Ptr<IConfig const> config() const {
+ return m_config;
+ }
+
+ private: // IResultCapture
+
+ virtual void assertionEnded( AssertionResult const& result ) {
+ if( result.getResultType() == ResultWas::Ok ) {
+ m_totals.assertions.passed++;
+ }
+ else if( !result.isOk() ) {
+ m_totals.assertions.failed++;
+ }
+
+ if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) )
+ m_messages.clear();
+
+ // Reset working state
+ m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition );
+ m_lastResult = result;
+ }
+
+ virtual bool sectionStarted (
+ SectionInfo const& sectionInfo,
+ Counts& assertions
+ )
+ {
+ std::ostringstream oss;
+ oss << sectionInfo.name << "@" << sectionInfo.lineInfo;
+
+ ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, oss.str() );
+ if( !sectionTracker.isOpen() )
+ return false;
+ m_activeSections.push_back( &sectionTracker );
+
+ m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo;
+
+ m_reporter->sectionStarting( sectionInfo );
+
+ assertions = m_totals.assertions;
+
+ return true;
+ }
+ bool testForMissingAssertions( Counts& assertions ) {
+ if( assertions.total() != 0 )
+ return false;
+ if( !m_config->warnAboutMissingAssertions() )
+ return false;
+ if( m_trackerContext.currentTracker().hasChildren() )
+ return false;
+ m_totals.assertions.failed++;
+ assertions.failed++;
+ return true;
+ }
+
+ virtual void sectionEnded( SectionEndInfo const& endInfo ) {
+ Counts assertions = m_totals.assertions - endInfo.prevAssertions;
+ bool missingAssertions = testForMissingAssertions( assertions );
+
+ if( !m_activeSections.empty() ) {
+ m_activeSections.back()->close();
+ m_activeSections.pop_back();
+ }
+
+ m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) );
+ m_messages.clear();
+ }
+
+ virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) {
+ if( m_unfinishedSections.empty() )
+ m_activeSections.back()->fail();
+ else
+ m_activeSections.back()->close();
+ m_activeSections.pop_back();
+
+ m_unfinishedSections.push_back( endInfo );
+ }
+
+ virtual void pushScopedMessage( MessageInfo const& message ) {
+ m_messages.push_back( message );
+ }
+
+ virtual void popScopedMessage( MessageInfo const& message ) {
+ m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() );
+ }
+
+ virtual std::string getCurrentTestName() const {
+ return m_activeTestCase
+ ? m_activeTestCase->getTestCaseInfo().name
+ : "";
+ }
+
+ virtual const AssertionResult* getLastResult() const {
+ return &m_lastResult;
+ }
+
+ virtual void handleFatalErrorCondition( std::string const& message ) {
+ ResultBuilder resultBuilder = makeUnexpectedResultBuilder();
+ resultBuilder.setResultType( ResultWas::FatalErrorCondition );
+ resultBuilder << message;
+ resultBuilder.captureExpression();
+
+ handleUnfinishedSections();
+
+ // Recreate section for test case (as we will lose the one that was in scope)
+ TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+ SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description );
+
+ Counts assertions;
+ assertions.failed = 1;
+ SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false );
+ m_reporter->sectionEnded( testCaseSectionStats );
+
+ TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo();
+
+ Totals deltaTotals;
+ deltaTotals.testCases.failed = 1;
+ m_reporter->testCaseEnded( TestCaseStats( testInfo,
+ deltaTotals,
+ "",
+ "",
+ false ) );
+ m_totals.testCases.failed++;
+ testGroupEnded( "", m_totals, 1, 1 );
+ m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) );
+ }
+
+ public:
+ // !TBD We need to do this another way!
+ bool aborting() const {
+ return m_totals.assertions.failed == static_cast<std::size_t>( m_config->abortAfter() );
+ }
+
+ private:
+
+ void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) {
+ TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo();
+ SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description );
+ m_reporter->sectionStarting( testCaseSection );
+ Counts prevAssertions = m_totals.assertions;
+ double duration = 0;
+ try {
+ m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal );
+
+ seedRng( *m_config );
+
+ Timer timer;
+ timer.start();
+ if( m_reporter->getPreferences().shouldRedirectStdOut ) {
+ StreamRedirect coutRedir( Catch::cout(), redirectedCout );
+ StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr );
+ invokeActiveTestCase();
+ }
+ else {
+ invokeActiveTestCase();
+ }
+ duration = timer.getElapsedSeconds();
+ }
+ catch( TestFailureException& ) {
+ // This just means the test was aborted due to failure
+ }
+ catch(...) {
+ makeUnexpectedResultBuilder().useActiveException();
+ }
+ m_testCaseTracker->close();
+ handleUnfinishedSections();
+ m_messages.clear();
+
+ Counts assertions = m_totals.assertions - prevAssertions;
+ bool missingAssertions = testForMissingAssertions( assertions );
+
+ if( testCaseInfo.okToFail() ) {
+ std::swap( assertions.failedButOk, assertions.failed );
+ m_totals.assertions.failed -= assertions.failedButOk;
+ m_totals.assertions.failedButOk += assertions.failedButOk;
+ }
+
+ SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions );
+ m_reporter->sectionEnded( testCaseSectionStats );
+ }
+
+ void invokeActiveTestCase() {
+ FatalConditionHandler fatalConditionHandler; // Handle signals
+ m_activeTestCase->invoke();
+ fatalConditionHandler.reset();
+ }
+
+ private:
+
+ ResultBuilder makeUnexpectedResultBuilder() const {
+ return ResultBuilder( m_lastAssertionInfo.macroName.c_str(),
+ m_lastAssertionInfo.lineInfo,
+ m_lastAssertionInfo.capturedExpression.c_str(),
+ m_lastAssertionInfo.resultDisposition );
+ }
+
+ void handleUnfinishedSections() {
+ // If sections ended prematurely due to an exception we stored their
+ // infos here so we can tear them down outside the unwind process.
+ for( std::vector<SectionEndInfo>::const_reverse_iterator it = m_unfinishedSections.rbegin(),
+ itEnd = m_unfinishedSections.rend();
+ it != itEnd;
+ ++it )
+ sectionEnded( *it );
+ m_unfinishedSections.clear();
+ }
+
+ TestRunInfo m_runInfo;
+ IMutableContext& m_context;
+ TestCase const* m_activeTestCase;
+ ITracker* m_testCaseTracker;
+ ITracker* m_currentSectionTracker;
+ AssertionResult m_lastResult;
+
+ Ptr<IConfig const> m_config;
+ Totals m_totals;
+ Ptr<IStreamingReporter> m_reporter;
+ std::vector<MessageInfo> m_messages;
+ AssertionInfo m_lastAssertionInfo;
+ std::vector<SectionEndInfo> m_unfinishedSections;
+ std::vector<ITracker*> m_activeSections;
+ TrackerContext m_trackerContext;
+ };
+
+ IResultCapture& getResultCapture() {
+ if( IResultCapture* capture = getCurrentContext().getResultCapture() )
+ return *capture;
+ else
+ throw std::logic_error( "No result capture instance" );
+ }
+
+} // end namespace Catch
+
+// #included from: internal/catch_version.h
+#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED
+
+namespace Catch {
+
+ // Versioning information
+ struct Version {
+ Version( unsigned int _majorVersion,
+ unsigned int _minorVersion,
+ unsigned int _patchNumber,
+ std::string const& _branchName,
+ unsigned int _buildNumber );
+
+ unsigned int const majorVersion;
+ unsigned int const minorVersion;
+ unsigned int const patchNumber;
+
+ // buildNumber is only used if branchName is not null
+ std::string const branchName;
+ unsigned int const buildNumber;
+
+ friend std::ostream& operator << ( std::ostream& os, Version const& version );
+
+ private:
+ void operator=( Version const& );
+ };
+
+ extern Version libraryVersion;
+}
+
+#include <fstream>
+#include <stdlib.h>
+#include <limits>
+
+namespace Catch {
+
+ Ptr<IStreamingReporter> createReporter( std::string const& reporterName, Ptr<Config> const& config ) {
+ Ptr<IStreamingReporter> reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() );
+ if( !reporter ) {
+ std::ostringstream oss;
+ oss << "No reporter registered with name: '" << reporterName << "'";
+ throw std::domain_error( oss.str() );
+ }
+ return reporter;
+ }
+
+ Ptr<IStreamingReporter> makeReporter( Ptr<Config> const& config ) {
+ std::vector<std::string> reporters = config->getReporterNames();
+ if( reporters.empty() )
+ reporters.push_back( "console" );
+
+ Ptr<IStreamingReporter> reporter;
+ for( std::vector<std::string>::const_iterator it = reporters.begin(), itEnd = reporters.end();
+ it != itEnd;
+ ++it )
+ reporter = addReporter( reporter, createReporter( *it, config ) );
+ return reporter;
+ }
+ Ptr<IStreamingReporter> addListeners( Ptr<IConfig const> const& config, Ptr<IStreamingReporter> reporters ) {
+ IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners();
+ for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end();
+ it != itEnd;
+ ++it )
+ reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) );
+ return reporters;
+ }
+
+ Totals runTests( Ptr<Config> const& config ) {
+
+ Ptr<IConfig const> iconfig = config.get();
+
+ Ptr<IStreamingReporter> reporter = makeReporter( config );
+ reporter = addListeners( iconfig, reporter );
+
+ RunContext context( iconfig, reporter );
+
+ Totals totals;
+
+ context.testGroupStarting( config->name(), 1, 1 );
+
+ TestSpec testSpec = config->testSpec();
+ if( !testSpec.hasFilters() )
+ testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests
+
+ std::vector<TestCase> const& allTestCases = getAllTestCasesSorted( *iconfig );
+ for( std::vector<TestCase>::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end();
+ it != itEnd;
+ ++it ) {
+ if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) )
+ totals += context.runTest( *it );
+ else
+ reporter->skipTest( *it );
+ }
+
+ context.testGroupEnded( iconfig->name(), totals, 1, 1 );
+ return totals;
+ }
+
+ void applyFilenamesAsTags( IConfig const& config ) {
+ std::vector<TestCase> const& tests = getAllTestCasesSorted( config );
+ for(std::size_t i = 0; i < tests.size(); ++i ) {
+ TestCase& test = const_cast<TestCase&>( tests[i] );
+ std::set<std::string> tags = test.tags;
+
+ std::string filename = test.lineInfo.file;
+ std::string::size_type lastSlash = filename.find_last_of( "\\/" );
+ if( lastSlash != std::string::npos )
+ filename = filename.substr( lastSlash+1 );
+
+ std::string::size_type lastDot = filename.find_last_of( "." );
+ if( lastDot != std::string::npos )
+ filename = filename.substr( 0, lastDot );
+
+ tags.insert( "#" + filename );
+ setTags( test, tags );
+ }
+ }
+
+ class Session : NonCopyable {
+ static bool alreadyInstantiated;
+
+ public:
+
+ struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; };
+
+ Session()
+ : m_cli( makeCommandLineParser() ) {
+ if( alreadyInstantiated ) {
+ std::string msg = "Only one instance of Catch::Session can ever be used";
+ Catch::cerr() << msg << std::endl;
+ throw std::logic_error( msg );
+ }
+ alreadyInstantiated = true;
+ }
+ ~Session() {
+ Catch::cleanUp();
+ }
+
+ void showHelp( std::string const& processName ) {
+ Catch::cout() << "\nCatch v" << libraryVersion << "\n";
+
+ m_cli.usage( Catch::cout(), processName );
+ Catch::cout() << "For more detail usage please see the project docs\n" << std::endl;
+ }
+
+ int applyCommandLine( int argc, char const* argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) {
+ try {
+ m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail );
+ m_unusedTokens = m_cli.parseInto( argc, argv, m_configData );
+ if( m_configData.showHelp )
+ showHelp( m_configData.processName );
+ m_config.reset();
+ }
+ catch( std::exception& ex ) {
+ {
+ Colour colourGuard( Colour::Red );
+ Catch::cerr()
+ << "\nError(s) in input:\n"
+ << Text( ex.what(), TextAttributes().setIndent(2) )
+ << "\n\n";
+ }
+ m_cli.usage( Catch::cout(), m_configData.processName );
+ return (std::numeric_limits<int>::max)();
+ }
+ return 0;
+ }
+
+ void useConfigData( ConfigData const& _configData ) {
+ m_configData = _configData;
+ m_config.reset();
+ }
+
+ int run( int argc, char const* argv[] ) {
+
+ int returnCode = applyCommandLine( argc, argv );
+ if( returnCode == 0 )
+ returnCode = run();
+ return returnCode;
+ }
+ int run( int argc, char* argv[] ) {
+ return run( argc, const_cast<char const**>( argv ) );
+ }
+
+ int run() {
+ if( m_configData.showHelp )
+ return 0;
+
+ try
+ {
+ config(); // Force config to be constructed
+
+ seedRng( *m_config );
+
+ if( m_configData.filenamesAsTags )
+ applyFilenamesAsTags( *m_config );
+
+ // Handle list request
+ if( Option<std::size_t> listed = list( config() ) )
+ return static_cast<int>( *listed );
+
+ return static_cast<int>( runTests( m_config ).assertions.failed );
+ }
+ catch( std::exception& ex ) {
+ Catch::cerr() << ex.what() << std::endl;
+ return (std::numeric_limits<int>::max)();
+ }
+ }
+
+ Clara::CommandLine<ConfigData> const& cli() const {
+ return m_cli;
+ }
+ std::vector<Clara::Parser::Token> const& unusedTokens() const {
+ return m_unusedTokens;
+ }
+ ConfigData& configData() {
+ return m_configData;
+ }
+ Config& config() {
+ if( !m_config )
+ m_config = new Config( m_configData );
+ return *m_config;
+ }
+ private:
+ Clara::CommandLine<ConfigData> m_cli;
+ std::vector<Clara::Parser::Token> m_unusedTokens;
+ ConfigData m_configData;
+ Ptr<Config> m_config;
+ };
+
+ bool Session::alreadyInstantiated = false;
+
+} // end namespace Catch
+
+// #included from: catch_registry_hub.hpp
+#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED
+
+// #included from: catch_test_case_registry_impl.hpp
+#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED
+
+#include <vector>
+#include <set>
+#include <sstream>
+#include <iostream>
+#include <algorithm>
+
+namespace Catch {
+
+ struct LexSort {
+ bool operator() (TestCase i,TestCase j) const { return (i<j);}
+ };
+ struct RandomNumberGenerator {
+ int operator()( int n ) const { return std::rand() % n; }
+ };
+
+ inline std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) {
+
+ std::vector<TestCase> sorted = unsortedTestCases;
+
+ switch( config.runOrder() ) {
+ case RunTests::InLexicographicalOrder:
+ std::sort( sorted.begin(), sorted.end(), LexSort() );
+ break;
+ case RunTests::InRandomOrder:
+ {
+ seedRng( config );
+
+ RandomNumberGenerator rng;
+ std::random_shuffle( sorted.begin(), sorted.end(), rng );
+ }
+ break;
+ case RunTests::InDeclarationOrder:
+ // already in declaration order
+ break;
+ }
+ return sorted;
+ }
+ bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) {
+ return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() );
+ }
+
+ void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) {
+ std::set<TestCase> seenFunctions;
+ for( std::vector<TestCase>::const_iterator it = functions.begin(), itEnd = functions.end();
+ it != itEnd;
+ ++it ) {
+ std::pair<std::set<TestCase>::const_iterator, bool> prev = seenFunctions.insert( *it );
+ if( !prev.second ){
+ Catch::cerr()
+ << Colour( Colour::Red )
+ << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n"
+ << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n"
+ << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl;
+ exit(1);
+ }
+ }
+ }
+
+ std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) {
+ std::vector<TestCase> filtered;
+ filtered.reserve( testCases.size() );
+ for( std::vector<TestCase>::const_iterator it = testCases.begin(), itEnd = testCases.end();
+ it != itEnd;
+ ++it )
+ if( matchTest( *it, testSpec, config ) )
+ filtered.push_back( *it );
+ return filtered;
+ }
+ std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) {
+ return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config );
+ }
+
+ class TestRegistry : public ITestCaseRegistry {
+ public:
+ TestRegistry()
+ : m_currentSortOrder( RunTests::InDeclarationOrder ),
+ m_unnamedCount( 0 )
+ {}
+ virtual ~TestRegistry();
+
+ virtual void registerTest( TestCase const& testCase ) {
+ std::string name = testCase.getTestCaseInfo().name;
+ if( name == "" ) {
+ std::ostringstream oss;
+ oss << "Anonymous test case " << ++m_unnamedCount;
+ return registerTest( testCase.withName( oss.str() ) );
+ }
+ m_functions.push_back( testCase );
+ }
+
+ virtual std::vector<TestCase> const& getAllTests() const {
+ return m_functions;
+ }
+ virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const {
+ if( m_sortedFunctions.empty() )
+ enforceNoDuplicateTestCases( m_functions );
+
+ if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) {
+ m_sortedFunctions = sortTests( config, m_functions );
+ m_currentSortOrder = config.runOrder();
+ }
+ return m_sortedFunctions;
+ }
+
+ private:
+ std::vector<TestCase> m_functions;
+ mutable RunTests::InWhatOrder m_currentSortOrder;
+ mutable std::vector<TestCase> m_sortedFunctions;
+ size_t m_unnamedCount;
+ std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ class FreeFunctionTestCase : public SharedImpl<ITestCase> {
+ public:
+
+ FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {}
+
+ virtual void invoke() const {
+ m_fun();
+ }
+
+ private:
+ virtual ~FreeFunctionTestCase();
+
+ TestFunction m_fun;
+ };
+
+ inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) {
+ std::string className = classOrQualifiedMethodName;
+ if( startsWith( className, "&" ) )
+ {
+ std::size_t lastColons = className.rfind( "::" );
+ std::size_t penultimateColons = className.rfind( "::", lastColons-1 );
+ if( penultimateColons == std::string::npos )
+ penultimateColons = 1;
+ className = className.substr( penultimateColons, lastColons-penultimateColons );
+ }
+ return className;
+ }
+
+ void registerTestCase
+ ( ITestCase* testCase,
+ char const* classOrQualifiedMethodName,
+ NameAndDesc const& nameAndDesc,
+ SourceLineInfo const& lineInfo ) {
+
+ getMutableRegistryHub().registerTest
+ ( makeTestCase
+ ( testCase,
+ extractClassName( classOrQualifiedMethodName ),
+ nameAndDesc.name,
+ nameAndDesc.description,
+ lineInfo ) );
+ }
+ void registerTestCaseFunction
+ ( TestFunction function,
+ SourceLineInfo const& lineInfo,
+ NameAndDesc const& nameAndDesc ) {
+ registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo );
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ AutoReg::AutoReg
+ ( TestFunction function,
+ SourceLineInfo const& lineInfo,
+ NameAndDesc const& nameAndDesc ) {
+ registerTestCaseFunction( function, lineInfo, nameAndDesc );
+ }
+
+ AutoReg::~AutoReg() {}
+
+} // end namespace Catch
+
+// #included from: catch_reporter_registry.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED
+
+#include <map>
+
+namespace Catch {
+
+ class ReporterRegistry : public IReporterRegistry {
+
+ public:
+
+ virtual ~ReporterRegistry() CATCH_OVERRIDE {}
+
+ virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig const> const& config ) const CATCH_OVERRIDE {
+ FactoryMap::const_iterator it = m_factories.find( name );
+ if( it == m_factories.end() )
+ return CATCH_NULL;
+ return it->second->create( ReporterConfig( config ) );
+ }
+
+ void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) {
+ m_factories.insert( std::make_pair( name, factory ) );
+ }
+ void registerListener( Ptr<IReporterFactory> const& factory ) {
+ m_listeners.push_back( factory );
+ }
+
+ virtual FactoryMap const& getFactories() const CATCH_OVERRIDE {
+ return m_factories;
+ }
+ virtual Listeners const& getListeners() const CATCH_OVERRIDE {
+ return m_listeners;
+ }
+
+ private:
+ FactoryMap m_factories;
+ Listeners m_listeners;
+ };
+}
+
+// #included from: catch_exception_translator_registry.hpp
+#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED
+
+#ifdef __OBJC__
+#import "Foundation/Foundation.h"
+#endif
+
+namespace Catch {
+
+ class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry {
+ public:
+ ~ExceptionTranslatorRegistry() {
+ deleteAll( m_translators );
+ }
+
+ virtual void registerTranslator( const IExceptionTranslator* translator ) {
+ m_translators.push_back( translator );
+ }
+
+ virtual std::string translateActiveException() const {
+ try {
+#ifdef __OBJC__
+ // In Objective-C try objective-c exceptions first
+ @try {
+ return tryTranslators();
+ }
+ @catch (NSException *exception) {
+ return Catch::toString( [exception description] );
+ }
+#else
+ return tryTranslators();
+#endif
+ }
+ catch( TestFailureException& ) {
+ throw;
+ }
+ catch( std::exception& ex ) {
+ return ex.what();
+ }
+ catch( std::string& msg ) {
+ return msg;
+ }
+ catch( const char* msg ) {
+ return msg;
+ }
+ catch(...) {
+ return "Unknown exception";
+ }
+ }
+
+ std::string tryTranslators() const {
+ if( m_translators.empty() )
+ throw;
+ else
+ return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() );
+ }
+
+ private:
+ std::vector<const IExceptionTranslator*> m_translators;
+ };
+}
+
+namespace Catch {
+
+ namespace {
+
+ class RegistryHub : public IRegistryHub, public IMutableRegistryHub {
+
+ RegistryHub( RegistryHub const& );
+ void operator=( RegistryHub const& );
+
+ public: // IRegistryHub
+ RegistryHub() {
+ }
+ virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE {
+ return m_reporterRegistry;
+ }
+ virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE {
+ return m_testCaseRegistry;
+ }
+ virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE {
+ return m_exceptionTranslatorRegistry;
+ }
+
+ public: // IMutableRegistryHub
+ virtual void registerReporter( std::string const& name, Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE {
+ m_reporterRegistry.registerReporter( name, factory );
+ }
+ virtual void registerListener( Ptr<IReporterFactory> const& factory ) CATCH_OVERRIDE {
+ m_reporterRegistry.registerListener( factory );
+ }
+ virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE {
+ m_testCaseRegistry.registerTest( testInfo );
+ }
+ virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE {
+ m_exceptionTranslatorRegistry.registerTranslator( translator );
+ }
+
+ private:
+ TestRegistry m_testCaseRegistry;
+ ReporterRegistry m_reporterRegistry;
+ ExceptionTranslatorRegistry m_exceptionTranslatorRegistry;
+ };
+
+ // Single, global, instance
+ inline RegistryHub*& getTheRegistryHub() {
+ static RegistryHub* theRegistryHub = CATCH_NULL;
+ if( !theRegistryHub )
+ theRegistryHub = new RegistryHub();
+ return theRegistryHub;
+ }
+ }
+
+ IRegistryHub& getRegistryHub() {
+ return *getTheRegistryHub();
+ }
+ IMutableRegistryHub& getMutableRegistryHub() {
+ return *getTheRegistryHub();
+ }
+ void cleanUp() {
+ delete getTheRegistryHub();
+ getTheRegistryHub() = CATCH_NULL;
+ cleanUpContext();
+ }
+ std::string translateActiveException() {
+ return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException();
+ }
+
+} // end namespace Catch
+
+// #included from: catch_notimplemented_exception.hpp
+#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED
+
+#include <ostream>
+
+namespace Catch {
+
+ NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo )
+ : m_lineInfo( lineInfo ) {
+ std::ostringstream oss;
+ oss << lineInfo << ": function ";
+ oss << "not implemented";
+ m_what = oss.str();
+ }
+
+ const char* NotImplementedException::what() const CATCH_NOEXCEPT {
+ return m_what.c_str();
+ }
+
+} // end namespace Catch
+
+// #included from: catch_context_impl.hpp
+#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED
+
+// #included from: catch_stream.hpp
+#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED
+
+#include <stdexcept>
+#include <cstdio>
+#include <iostream>
+
+namespace Catch {
+
+ template<typename WriterF, size_t bufferSize=256>
+ class StreamBufImpl : public StreamBufBase {
+ char data[bufferSize];
+ WriterF m_writer;
+
+ public:
+ StreamBufImpl() {
+ setp( data, data + sizeof(data) );
+ }
+
+ ~StreamBufImpl() CATCH_NOEXCEPT {
+ sync();
+ }
+
+ private:
+ int overflow( int c ) {
+ sync();
+
+ if( c != EOF ) {
+ if( pbase() == epptr() )
+ m_writer( std::string( 1, static_cast<char>( c ) ) );
+ else
+ sputc( static_cast<char>( c ) );
+ }
+ return 0;
+ }
+
+ int sync() {
+ if( pbase() != pptr() ) {
+ m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) );
+ setp( pbase(), epptr() );
+ }
+ return 0;
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ FileStream::FileStream( std::string const& filename ) {
+ m_ofs.open( filename.c_str() );
+ if( m_ofs.fail() ) {
+ std::ostringstream oss;
+ oss << "Unable to open file: '" << filename << "'";
+ throw std::domain_error( oss.str() );
+ }
+ }
+
+ std::ostream& FileStream::stream() const {
+ return m_ofs;
+ }
+
+ struct OutputDebugWriter {
+
+ void operator()( std::string const&str ) {
+ writeToDebugConsole( str );
+ }
+ };
+
+ DebugOutStream::DebugOutStream()
+ : m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ),
+ m_os( m_streamBuf.get() )
+ {}
+
+ std::ostream& DebugOutStream::stream() const {
+ return m_os;
+ }
+
+ // Store the streambuf from cout up-front because
+ // cout may get redirected when running tests
+ CoutStream::CoutStream()
+ : m_os( Catch::cout().rdbuf() )
+ {}
+
+ std::ostream& CoutStream::stream() const {
+ return m_os;
+ }
+
+#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions
+ std::ostream& cout() {
+ return std::cout;
+ }
+ std::ostream& cerr() {
+ return std::cerr;
+ }
+#endif
+}
+
+namespace Catch {
+
+ class Context : public IMutableContext {
+
+ Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {}
+ Context( Context const& );
+ void operator=( Context const& );
+
+ public: // IContext
+ virtual IResultCapture* getResultCapture() {
+ return m_resultCapture;
+ }
+ virtual IRunner* getRunner() {
+ return m_runner;
+ }
+ virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) {
+ return getGeneratorsForCurrentTest()
+ .getGeneratorInfo( fileInfo, totalSize )
+ .getCurrentIndex();
+ }
+ virtual bool advanceGeneratorsForCurrentTest() {
+ IGeneratorsForTest* generators = findGeneratorsForCurrentTest();
+ return generators && generators->moveNext();
+ }
+
+ virtual Ptr<IConfig const> getConfig() const {
+ return m_config;
+ }
+
+ public: // IMutableContext
+ virtual void setResultCapture( IResultCapture* resultCapture ) {
+ m_resultCapture = resultCapture;
+ }
+ virtual void setRunner( IRunner* runner ) {
+ m_runner = runner;
+ }
+ virtual void setConfig( Ptr<IConfig const> const& config ) {
+ m_config = config;
+ }
+
+ friend IMutableContext& getCurrentMutableContext();
+
+ private:
+ IGeneratorsForTest* findGeneratorsForCurrentTest() {
+ std::string testName = getResultCapture()->getCurrentTestName();
+
+ std::map<std::string, IGeneratorsForTest*>::const_iterator it =
+ m_generatorsByTestName.find( testName );
+ return it != m_generatorsByTestName.end()
+ ? it->second
+ : CATCH_NULL;
+ }
+
+ IGeneratorsForTest& getGeneratorsForCurrentTest() {
+ IGeneratorsForTest* generators = findGeneratorsForCurrentTest();
+ if( !generators ) {
+ std::string testName = getResultCapture()->getCurrentTestName();
+ generators = createGeneratorsForTest();
+ m_generatorsByTestName.insert( std::make_pair( testName, generators ) );
+ }
+ return *generators;
+ }
+
+ private:
+ Ptr<IConfig const> m_config;
+ IRunner* m_runner;
+ IResultCapture* m_resultCapture;
+ std::map<std::string, IGeneratorsForTest*> m_generatorsByTestName;
+ };
+
+ namespace {
+ Context* currentContext = CATCH_NULL;
+ }
+ IMutableContext& getCurrentMutableContext() {
+ if( !currentContext )
+ currentContext = new Context();
+ return *currentContext;
+ }
+ IContext& getCurrentContext() {
+ return getCurrentMutableContext();
+ }
+
+ void cleanUpContext() {
+ delete currentContext;
+ currentContext = CATCH_NULL;
+ }
+}
+
+// #included from: catch_console_colour_impl.hpp
+#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED
+
+namespace Catch {
+ namespace {
+
+ struct IColourImpl {
+ virtual ~IColourImpl() {}
+ virtual void use( Colour::Code _colourCode ) = 0;
+ };
+
+ struct NoColourImpl : IColourImpl {
+ void use( Colour::Code ) {}
+
+ static IColourImpl* instance() {
+ static NoColourImpl s_instance;
+ return &s_instance;
+ }
+ };
+
+ } // anon namespace
+} // namespace Catch
+
+#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI )
+# ifdef CATCH_PLATFORM_WINDOWS
+# define CATCH_CONFIG_COLOUR_WINDOWS
+# else
+# define CATCH_CONFIG_COLOUR_ANSI
+# endif
+#endif
+
+#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#ifdef __AFXDLL
+#include <AfxWin.h>
+#else
+#include <windows.h>
+#endif
+
+namespace Catch {
+namespace {
+
+ class Win32ColourImpl : public IColourImpl {
+ public:
+ Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) )
+ {
+ CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+ GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo );
+ originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY );
+ originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY );
+ }
+
+ virtual void use( Colour::Code _colourCode ) {
+ switch( _colourCode ) {
+ case Colour::None: return setTextAttribute( originalForegroundAttributes );
+ case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
+ case Colour::Red: return setTextAttribute( FOREGROUND_RED );
+ case Colour::Green: return setTextAttribute( FOREGROUND_GREEN );
+ case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE );
+ case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN );
+ case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN );
+ case Colour::Grey: return setTextAttribute( 0 );
+
+ case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY );
+ case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED );
+ case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN );
+ case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );
+
+ case Colour::Bright: throw std::logic_error( "not a colour" );
+ }
+ }
+
+ private:
+ void setTextAttribute( WORD _textAttribute ) {
+ SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes );
+ }
+ HANDLE stdoutHandle;
+ WORD originalForegroundAttributes;
+ WORD originalBackgroundAttributes;
+ };
+
+ IColourImpl* platformColourInstance() {
+ static Win32ColourImpl s_instance;
+
+ Ptr<IConfig const> config = getCurrentContext().getConfig();
+ UseColour::YesOrNo colourMode = config
+ ? config->useColour()
+ : UseColour::Auto;
+ if( colourMode == UseColour::Auto )
+ colourMode = !isDebuggerActive()
+ ? UseColour::Yes
+ : UseColour::No;
+ return colourMode == UseColour::Yes
+ ? &s_instance
+ : NoColourImpl::instance();
+ }
+
+} // end anon namespace
+} // end namespace Catch
+
+#elif defined( CATCH_CONFIG_COLOUR_ANSI ) //////////////////////////////////////
+
+#include <unistd.h>
+
+namespace Catch {
+namespace {
+
+ // use POSIX/ ANSI console terminal codes
+ // Thanks to Adam Strzelecki for original contribution
+ // (http://github.com/nanoant)
+ // https://github.com/philsquared/Catch/pull/131
+ class PosixColourImpl : public IColourImpl {
+ public:
+ virtual void use( Colour::Code _colourCode ) {
+ switch( _colourCode ) {
+ case Colour::None:
+ case Colour::White: return setColour( "[0m" );
+ case Colour::Red: return setColour( "[0;31m" );
+ case Colour::Green: return setColour( "[0;32m" );
+ case Colour::Blue: return setColour( "[0:34m" );
+ case Colour::Cyan: return setColour( "[0;36m" );
+ case Colour::Yellow: return setColour( "[0;33m" );
+ case Colour::Grey: return setColour( "[1;30m" );
+
+ case Colour::LightGrey: return setColour( "[0;37m" );
+ case Colour::BrightRed: return setColour( "[1;31m" );
+ case Colour::BrightGreen: return setColour( "[1;32m" );
+ case Colour::BrightWhite: return setColour( "[1;37m" );
+
+ case Colour::Bright: throw std::logic_error( "not a colour" );
+ }
+ }
+ static IColourImpl* instance() {
+ static PosixColourImpl s_instance;
+ return &s_instance;
+ }
+
+ private:
+ void setColour( const char* _escapeCode ) {
+ Catch::cout() << '\033' << _escapeCode;
+ }
+ };
+
+ IColourImpl* platformColourInstance() {
+ Ptr<IConfig const> config = getCurrentContext().getConfig();
+ UseColour::YesOrNo colourMode = config
+ ? config->useColour()
+ : UseColour::Auto;
+ if( colourMode == UseColour::Auto )
+ colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) )
+ ? UseColour::Yes
+ : UseColour::No;
+ return colourMode == UseColour::Yes
+ ? PosixColourImpl::instance()
+ : NoColourImpl::instance();
+ }
+
+} // end anon namespace
+} // end namespace Catch
+
+#else // not Windows or ANSI ///////////////////////////////////////////////
+
+namespace Catch {
+
+ static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); }
+
+} // end namespace Catch
+
+#endif // Windows/ ANSI/ None
+
+namespace Catch {
+
+ Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); }
+ Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast<Colour&>( _other ).m_moved = true; }
+ Colour::~Colour(){ if( !m_moved ) use( None ); }
+
+ void Colour::use( Code _colourCode ) {
+ static IColourImpl* impl = platformColourInstance();
+ impl->use( _colourCode );
+ }
+
+} // end namespace Catch
+
+// #included from: catch_generators_impl.hpp
+#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED
+
+#include <vector>
+#include <string>
+#include <map>
+
+namespace Catch {
+
+ struct GeneratorInfo : IGeneratorInfo {
+
+ GeneratorInfo( std::size_t size )
+ : m_size( size ),
+ m_currentIndex( 0 )
+ {}
+
+ bool moveNext() {
+ if( ++m_currentIndex == m_size ) {
+ m_currentIndex = 0;
+ return false;
+ }
+ return true;
+ }
+
+ std::size_t getCurrentIndex() const {
+ return m_currentIndex;
+ }
+
+ std::size_t m_size;
+ std::size_t m_currentIndex;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ class GeneratorsForTest : public IGeneratorsForTest {
+
+ public:
+ ~GeneratorsForTest() {
+ deleteAll( m_generatorsInOrder );
+ }
+
+ IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) {
+ std::map<std::string, IGeneratorInfo*>::const_iterator it = m_generatorsByName.find( fileInfo );
+ if( it == m_generatorsByName.end() ) {
+ IGeneratorInfo* info = new GeneratorInfo( size );
+ m_generatorsByName.insert( std::make_pair( fileInfo, info ) );
+ m_generatorsInOrder.push_back( info );
+ return *info;
+ }
+ return *it->second;
+ }
+
+ bool moveNext() {
+ std::vector<IGeneratorInfo*>::const_iterator it = m_generatorsInOrder.begin();
+ std::vector<IGeneratorInfo*>::const_iterator itEnd = m_generatorsInOrder.end();
+ for(; it != itEnd; ++it ) {
+ if( (*it)->moveNext() )
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ std::map<std::string, IGeneratorInfo*> m_generatorsByName;
+ std::vector<IGeneratorInfo*> m_generatorsInOrder;
+ };
+
+ IGeneratorsForTest* createGeneratorsForTest()
+ {
+ return new GeneratorsForTest();
+ }
+
+} // end namespace Catch
+
+// #included from: catch_assertionresult.hpp
+#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED
+
+namespace Catch {
+
+ AssertionInfo::AssertionInfo( std::string const& _macroName,
+ SourceLineInfo const& _lineInfo,
+ std::string const& _capturedExpression,
+ ResultDisposition::Flags _resultDisposition )
+ : macroName( _macroName ),
+ lineInfo( _lineInfo ),
+ capturedExpression( _capturedExpression ),
+ resultDisposition( _resultDisposition )
+ {}
+
+ AssertionResult::AssertionResult() {}
+
+ AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data )
+ : m_info( info ),
+ m_resultData( data )
+ {}
+
+ AssertionResult::~AssertionResult() {}
+
+ // Result was a success
+ bool AssertionResult::succeeded() const {
+ return Catch::isOk( m_resultData.resultType );
+ }
+
+ // Result was a success, or failure is suppressed
+ bool AssertionResult::isOk() const {
+ return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition );
+ }
+
+ ResultWas::OfType AssertionResult::getResultType() const {
+ return m_resultData.resultType;
+ }
+
+ bool AssertionResult::hasExpression() const {
+ return !m_info.capturedExpression.empty();
+ }
+
+ bool AssertionResult::hasMessage() const {
+ return !m_resultData.message.empty();
+ }
+
+ std::string AssertionResult::getExpression() const {
+ if( isFalseTest( m_info.resultDisposition ) )
+ return "!" + m_info.capturedExpression;
+ else
+ return m_info.capturedExpression;
+ }
+ std::string AssertionResult::getExpressionInMacro() const {
+ if( m_info.macroName.empty() )
+ return m_info.capturedExpression;
+ else
+ return m_info.macroName + "( " + m_info.capturedExpression + " )";
+ }
+
+ bool AssertionResult::hasExpandedExpression() const {
+ return hasExpression() && getExpandedExpression() != getExpression();
+ }
+
+ std::string AssertionResult::getExpandedExpression() const {
+ return m_resultData.reconstructedExpression;
+ }
+
+ std::string AssertionResult::getMessage() const {
+ return m_resultData.message;
+ }
+ SourceLineInfo AssertionResult::getSourceInfo() const {
+ return m_info.lineInfo;
+ }
+
+ std::string AssertionResult::getTestMacroName() const {
+ return m_info.macroName;
+ }
+
+} // end namespace Catch
+
+// #included from: catch_test_case_info.hpp
+#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED
+
+namespace Catch {
+
+ inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {
+ if( startsWith( tag, "." ) ||
+ tag == "hide" ||
+ tag == "!hide" )
+ return TestCaseInfo::IsHidden;
+ else if( tag == "!throws" )
+ return TestCaseInfo::Throws;
+ else if( tag == "!shouldfail" )
+ return TestCaseInfo::ShouldFail;
+ else if( tag == "!mayfail" )
+ return TestCaseInfo::MayFail;
+ else
+ return TestCaseInfo::None;
+ }
+ inline bool isReservedTag( std::string const& tag ) {
+ return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] );
+ }
+ inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {
+ if( isReservedTag( tag ) ) {
+ {
+ Colour colourGuard( Colour::Red );
+ Catch::cerr()
+ << "Tag name [" << tag << "] not allowed.\n"
+ << "Tag names starting with non alpha-numeric characters are reserved\n";
+ }
+ {
+ Colour colourGuard( Colour::FileName );
+ Catch::cerr() << _lineInfo << std::endl;
+ }
+ exit(1);
+ }
+ }
+
+ TestCase makeTestCase( ITestCase* _testCase,
+ std::string const& _className,
+ std::string const& _name,
+ std::string const& _descOrTags,
+ SourceLineInfo const& _lineInfo )
+ {
+ bool isHidden( startsWith( _name, "./" ) ); // Legacy support
+
+ // Parse out tags
+ std::set<std::string> tags;
+ std::string desc, tag;
+ bool inTag = false;
+ for( std::size_t i = 0; i < _descOrTags.size(); ++i ) {
+ char c = _descOrTags[i];
+ if( !inTag ) {
+ if( c == '[' )
+ inTag = true;
+ else
+ desc += c;
+ }
+ else {
+ if( c == ']' ) {
+ TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag );
+ if( prop == TestCaseInfo::IsHidden )
+ isHidden = true;
+ else if( prop == TestCaseInfo::None )
+ enforceNotReservedTag( tag, _lineInfo );
+
+ tags.insert( tag );
+ tag.clear();
+ inTag = false;
+ }
+ else
+ tag += c;
+ }
+ }
+ if( isHidden ) {
+ tags.insert( "hide" );
+ tags.insert( "." );
+ }
+
+ TestCaseInfo info( _name, _className, desc, tags, _lineInfo );
+ return TestCase( _testCase, info );
+ }
+
+ void setTags( TestCaseInfo& testCaseInfo, std::set<std::string> const& tags )
+ {
+ testCaseInfo.tags = tags;
+ testCaseInfo.lcaseTags.clear();
+
+ std::ostringstream oss;
+ for( std::set<std::string>::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) {
+ oss << "[" << *it << "]";
+ std::string lcaseTag = toLower( *it );
+ testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) );
+ testCaseInfo.lcaseTags.insert( lcaseTag );
+ }
+ testCaseInfo.tagsAsString = oss.str();
+ }
+
+ TestCaseInfo::TestCaseInfo( std::string const& _name,
+ std::string const& _className,
+ std::string const& _description,
+ std::set<std::string> const& _tags,
+ SourceLineInfo const& _lineInfo )
+ : name( _name ),
+ className( _className ),
+ description( _description ),
+ lineInfo( _lineInfo ),
+ properties( None )
+ {
+ setTags( *this, _tags );
+ }
+
+ TestCaseInfo::TestCaseInfo( TestCaseInfo const& other )
+ : name( other.name ),
+ className( other.className ),
+ description( other.description ),
+ tags( other.tags ),
+ lcaseTags( other.lcaseTags ),
+ tagsAsString( other.tagsAsString ),
+ lineInfo( other.lineInfo ),
+ properties( other.properties )
+ {}
+
+ bool TestCaseInfo::isHidden() const {
+ return ( properties & IsHidden ) != 0;
+ }
+ bool TestCaseInfo::throws() const {
+ return ( properties & Throws ) != 0;
+ }
+ bool TestCaseInfo::okToFail() const {
+ return ( properties & (ShouldFail | MayFail ) ) != 0;
+ }
+ bool TestCaseInfo::expectedToFail() const {
+ return ( properties & (ShouldFail ) ) != 0;
+ }
+
+ TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {}
+
+ TestCase::TestCase( TestCase const& other )
+ : TestCaseInfo( other ),
+ test( other.test )
+ {}
+
+ TestCase TestCase::withName( std::string const& _newName ) const {
+ TestCase other( *this );
+ other.name = _newName;
+ return other;
+ }
+
+ void TestCase::swap( TestCase& other ) {
+ test.swap( other.test );
+ name.swap( other.name );
+ className.swap( other.className );
+ description.swap( other.description );
+ tags.swap( other.tags );
+ lcaseTags.swap( other.lcaseTags );
+ tagsAsString.swap( other.tagsAsString );
+ std::swap( TestCaseInfo::properties, static_cast<TestCaseInfo&>( other ).properties );
+ std::swap( lineInfo, other.lineInfo );
+ }
+
+ void TestCase::invoke() const {
+ test->invoke();
+ }
+
+ bool TestCase::operator == ( TestCase const& other ) const {
+ return test.get() == other.test.get() &&
+ name == other.name &&
+ className == other.className;
+ }
+
+ bool TestCase::operator < ( TestCase const& other ) const {
+ return name < other.name;
+ }
+ TestCase& TestCase::operator = ( TestCase const& other ) {
+ TestCase temp( other );
+ swap( temp );
+ return *this;
+ }
+
+ TestCaseInfo const& TestCase::getTestCaseInfo() const
+ {
+ return *this;
+ }
+
+} // end namespace Catch
+
+// #included from: catch_version.hpp
+#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED
+
+namespace Catch {
+
+ Version::Version
+ ( unsigned int _majorVersion,
+ unsigned int _minorVersion,
+ unsigned int _patchNumber,
+ std::string const& _branchName,
+ unsigned int _buildNumber )
+ : majorVersion( _majorVersion ),
+ minorVersion( _minorVersion ),
+ patchNumber( _patchNumber ),
+ branchName( _branchName ),
+ buildNumber( _buildNumber )
+ {}
+
+ std::ostream& operator << ( std::ostream& os, Version const& version ) {
+ os << version.majorVersion << "."
+ << version.minorVersion << "."
+ << version.patchNumber;
+
+ if( !version.branchName.empty() ) {
+ os << "-" << version.branchName
+ << "." << version.buildNumber;
+ }
+ return os;
+ }
+
+ Version libraryVersion( 1, 4, 0, "", 0 );
+
+}
+
+// #included from: catch_message.hpp
+#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED
+
+namespace Catch {
+
+ MessageInfo::MessageInfo( std::string const& _macroName,
+ SourceLineInfo const& _lineInfo,
+ ResultWas::OfType _type )
+ : macroName( _macroName ),
+ lineInfo( _lineInfo ),
+ type( _type ),
+ sequence( ++globalCount )
+ {}
+
+ // This may need protecting if threading support is added
+ unsigned int MessageInfo::globalCount = 0;
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ ScopedMessage::ScopedMessage( MessageBuilder const& builder )
+ : m_info( builder.m_info )
+ {
+ m_info.message = builder.m_stream.str();
+ getResultCapture().pushScopedMessage( m_info );
+ }
+ ScopedMessage::ScopedMessage( ScopedMessage const& other )
+ : m_info( other.m_info )
+ {}
+
+ ScopedMessage::~ScopedMessage() {
+ getResultCapture().popScopedMessage( m_info );
+ }
+
+} // end namespace Catch
+
+// #included from: catch_legacy_reporter_adapter.hpp
+#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED
+
+// #included from: catch_legacy_reporter_adapter.h
+#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED
+
+namespace Catch
+{
+ // Deprecated
+ struct IReporter : IShared {
+ virtual ~IReporter();
+
+ virtual bool shouldRedirectStdout() const = 0;
+
+ virtual void StartTesting() = 0;
+ virtual void EndTesting( Totals const& totals ) = 0;
+ virtual void StartGroup( std::string const& groupName ) = 0;
+ virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0;
+ virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0;
+ virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0;
+ virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0;
+ virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0;
+ virtual void NoAssertionsInSection( std::string const& sectionName ) = 0;
+ virtual void NoAssertionsInTestCase( std::string const& testName ) = 0;
+ virtual void Aborted() = 0;
+ virtual void Result( AssertionResult const& result ) = 0;
+ };
+
+ class LegacyReporterAdapter : public SharedImpl<IStreamingReporter>
+ {
+ public:
+ LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter );
+ virtual ~LegacyReporterAdapter();
+
+ virtual ReporterPreferences getPreferences() const;
+ virtual void noMatchingTestCases( std::string const& );
+ virtual void testRunStarting( TestRunInfo const& );
+ virtual void testGroupStarting( GroupInfo const& groupInfo );
+ virtual void testCaseStarting( TestCaseInfo const& testInfo );
+ virtual void sectionStarting( SectionInfo const& sectionInfo );
+ virtual void assertionStarting( AssertionInfo const& );
+ virtual bool assertionEnded( AssertionStats const& assertionStats );
+ virtual void sectionEnded( SectionStats const& sectionStats );
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats );
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats );
+ virtual void testRunEnded( TestRunStats const& testRunStats );
+ virtual void skipTest( TestCaseInfo const& );
+
+ private:
+ Ptr<IReporter> m_legacyReporter;
+ };
+}
+
+namespace Catch
+{
+ LegacyReporterAdapter::LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter )
+ : m_legacyReporter( legacyReporter )
+ {}
+ LegacyReporterAdapter::~LegacyReporterAdapter() {}
+
+ ReporterPreferences LegacyReporterAdapter::getPreferences() const {
+ ReporterPreferences prefs;
+ prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout();
+ return prefs;
+ }
+
+ void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {}
+ void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) {
+ m_legacyReporter->StartTesting();
+ }
+ void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) {
+ m_legacyReporter->StartGroup( groupInfo.name );
+ }
+ void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) {
+ m_legacyReporter->StartTestCase( testInfo );
+ }
+ void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) {
+ m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description );
+ }
+ void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) {
+ // Not on legacy interface
+ }
+
+ bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) {
+ if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
+ for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
+ it != itEnd;
+ ++it ) {
+ if( it->type == ResultWas::Info ) {
+ ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal );
+ rb << it->message;
+ rb.setResultType( ResultWas::Info );
+ AssertionResult result = rb.build();
+ m_legacyReporter->Result( result );
+ }
+ }
+ }
+ m_legacyReporter->Result( assertionStats.assertionResult );
+ return true;
+ }
+ void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) {
+ if( sectionStats.missingAssertions )
+ m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name );
+ m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions );
+ }
+ void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) {
+ m_legacyReporter->EndTestCase
+ ( testCaseStats.testInfo,
+ testCaseStats.totals,
+ testCaseStats.stdOut,
+ testCaseStats.stdErr );
+ }
+ void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) {
+ if( testGroupStats.aborting )
+ m_legacyReporter->Aborted();
+ m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals );
+ }
+ void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) {
+ m_legacyReporter->EndTesting( testRunStats.totals );
+ }
+ void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) {
+ }
+}
+
+// #included from: catch_timer.hpp
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++11-long-long"
+#endif
+
+#ifdef CATCH_PLATFORM_WINDOWS
+#include <windows.h>
+#else
+#include <sys/time.h>
+#endif
+
+namespace Catch {
+
+ namespace {
+#ifdef CATCH_PLATFORM_WINDOWS
+ uint64_t getCurrentTicks() {
+ static uint64_t hz=0, hzo=0;
+ if (!hz) {
+ QueryPerformanceFrequency( reinterpret_cast<LARGE_INTEGER*>( &hz ) );
+ QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &hzo ) );
+ }
+ uint64_t t;
+ QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &t ) );
+ return ((t-hzo)*1000000)/hz;
+ }
+#else
+ uint64_t getCurrentTicks() {
+ timeval t;
+ gettimeofday(&t,CATCH_NULL);
+ return static_cast<uint64_t>( t.tv_sec ) * 1000000ull + static_cast<uint64_t>( t.tv_usec );
+ }
+#endif
+ }
+
+ void Timer::start() {
+ m_ticks = getCurrentTicks();
+ }
+ unsigned int Timer::getElapsedMicroseconds() const {
+ return static_cast<unsigned int>(getCurrentTicks() - m_ticks);
+ }
+ unsigned int Timer::getElapsedMilliseconds() const {
+ return static_cast<unsigned int>(getElapsedMicroseconds()/1000);
+ }
+ double Timer::getElapsedSeconds() const {
+ return getElapsedMicroseconds()/1000000.0;
+ }
+
+} // namespace Catch
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+// #included from: catch_common.hpp
+#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED
+
+namespace Catch {
+
+ bool startsWith( std::string const& s, std::string const& prefix ) {
+ return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix;
+ }
+ bool endsWith( std::string const& s, std::string const& suffix ) {
+ return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix;
+ }
+ bool contains( std::string const& s, std::string const& infix ) {
+ return s.find( infix ) != std::string::npos;
+ }
+ void toLowerInPlace( std::string& s ) {
+ std::transform( s.begin(), s.end(), s.begin(), ::tolower );
+ }
+ std::string toLower( std::string const& s ) {
+ std::string lc = s;
+ toLowerInPlace( lc );
+ return lc;
+ }
+ std::string trim( std::string const& str ) {
+ static char const* whitespaceChars = "\n\r\t ";
+ std::string::size_type start = str.find_first_not_of( whitespaceChars );
+ std::string::size_type end = str.find_last_not_of( whitespaceChars );
+
+ return start != std::string::npos ? str.substr( start, 1+end-start ) : "";
+ }
+
+ bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) {
+ bool replaced = false;
+ std::size_t i = str.find( replaceThis );
+ while( i != std::string::npos ) {
+ replaced = true;
+ str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() );
+ if( i < str.size()-withThis.size() )
+ i = str.find( replaceThis, i+withThis.size() );
+ else
+ i = std::string::npos;
+ }
+ return replaced;
+ }
+
+ pluralise::pluralise( std::size_t count, std::string const& label )
+ : m_count( count ),
+ m_label( label )
+ {}
+
+ std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) {
+ os << pluraliser.m_count << " " << pluraliser.m_label;
+ if( pluraliser.m_count != 1 )
+ os << "s";
+ return os;
+ }
+
+ SourceLineInfo::SourceLineInfo() : line( 0 ){}
+ SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line )
+ : file( _file ),
+ line( _line )
+ {}
+ SourceLineInfo::SourceLineInfo( SourceLineInfo const& other )
+ : file( other.file ),
+ line( other.line )
+ {}
+ bool SourceLineInfo::empty() const {
+ return file.empty();
+ }
+ bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const {
+ return line == other.line && file == other.file;
+ }
+ bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const {
+ return line < other.line || ( line == other.line && file < other.file );
+ }
+
+ void seedRng( IConfig const& config ) {
+ if( config.rngSeed() != 0 )
+ std::srand( config.rngSeed() );
+ }
+ unsigned int rngSeed() {
+ return getCurrentContext().getConfig()->rngSeed();
+ }
+
+ std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) {
+#ifndef __GNUG__
+ os << info.file << "(" << info.line << ")";
+#else
+ os << info.file << ":" << info.line;
+#endif
+ return os;
+ }
+
+ void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) {
+ std::ostringstream oss;
+ oss << locationInfo << ": Internal Catch error: '" << message << "'";
+ if( alwaysTrue() )
+ throw std::logic_error( oss.str() );
+ }
+}
+
+// #included from: catch_section.hpp
+#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED
+
+namespace Catch {
+
+ SectionInfo::SectionInfo
+ ( SourceLineInfo const& _lineInfo,
+ std::string const& _name,
+ std::string const& _description )
+ : name( _name ),
+ description( _description ),
+ lineInfo( _lineInfo )
+ {}
+
+ Section::Section( SectionInfo const& info )
+ : m_info( info ),
+ m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) )
+ {
+ m_timer.start();
+ }
+
+ Section::~Section() {
+ if( m_sectionIncluded ) {
+ SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() );
+ if( std::uncaught_exception() )
+ getResultCapture().sectionEndedEarly( endInfo );
+ else
+ getResultCapture().sectionEnded( endInfo );
+ }
+ }
+
+ // This indicates whether the section should be executed or not
+ Section::operator bool() const {
+ return m_sectionIncluded;
+ }
+
+} // end namespace Catch
+
+// #included from: catch_debugger.hpp
+#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED
+
+#include <iostream>
+
+#ifdef CATCH_PLATFORM_MAC
+
+ #include <assert.h>
+ #include <stdbool.h>
+ #include <sys/types.h>
+ #include <unistd.h>
+ #include <sys/sysctl.h>
+
+ namespace Catch{
+
+ // The following function is taken directly from the following technical note:
+ // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html
+
+ // Returns true if the current process is being debugged (either
+ // running under the debugger or has a debugger attached post facto).
+ bool isDebuggerActive(){
+
+ int mib[4];
+ struct kinfo_proc info;
+ size_t size;
+
+ // Initialize the flags so that, if sysctl fails for some bizarre
+ // reason, we get a predictable result.
+
+ info.kp_proc.p_flag = 0;
+
+ // Initialize mib, which tells sysctl the info we want, in this case
+ // we're looking for information about a specific process ID.
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = getpid();
+
+ // Call sysctl.
+
+ size = sizeof(info);
+ if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) {
+ Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl;
+ return false;
+ }
+
+ // We're being debugged if the P_TRACED flag is set.
+
+ return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
+ }
+ } // namespace Catch
+
+#elif defined(_MSC_VER)
+ extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
+ namespace Catch {
+ bool isDebuggerActive() {
+ return IsDebuggerPresent() != 0;
+ }
+ }
+#elif defined(__MINGW32__)
+ extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
+ namespace Catch {
+ bool isDebuggerActive() {
+ return IsDebuggerPresent() != 0;
+ }
+ }
+#else
+ namespace Catch {
+ inline bool isDebuggerActive() { return false; }
+ }
+#endif // Platform
+
+#ifdef CATCH_PLATFORM_WINDOWS
+ extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* );
+ namespace Catch {
+ void writeToDebugConsole( std::string const& text ) {
+ ::OutputDebugStringA( text.c_str() );
+ }
+ }
+#else
+ namespace Catch {
+ void writeToDebugConsole( std::string const& text ) {
+ // !TBD: Need a version for Mac/ XCode and other IDEs
+ Catch::cout() << text;
+ }
+ }
+#endif // Platform
+
+// #included from: catch_tostring.hpp
+#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED
+
+namespace Catch {
+
+namespace Detail {
+
+ const std::string unprintableString = "{?}";
+
+ namespace {
+ const int hexThreshold = 255;
+
+ struct Endianness {
+ enum Arch { Big, Little };
+
+ static Arch which() {
+ union _{
+ int asInt;
+ char asChar[sizeof (int)];
+ } u;
+
+ u.asInt = 1;
+ return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little;
+ }
+ };
+ }
+
+ std::string rawMemoryToString( const void *object, std::size_t size )
+ {
+ // Reverse order for little endian architectures
+ int i = 0, end = static_cast<int>( size ), inc = 1;
+ if( Endianness::which() == Endianness::Little ) {
+ i = end-1;
+ end = inc = -1;
+ }
+
+ unsigned char const *bytes = static_cast<unsigned char const *>(object);
+ std::ostringstream os;
+ os << "0x" << std::setfill('0') << std::hex;
+ for( ; i != end; i += inc )
+ os << std::setw(2) << static_cast<unsigned>(bytes[i]);
+ return os.str();
+ }
+}
+
+std::string toString( std::string const& value ) {
+ std::string s = value;
+ if( getCurrentContext().getConfig()->showInvisibles() ) {
+ for(size_t i = 0; i < s.size(); ++i ) {
+ std::string subs;
+ switch( s[i] ) {
+ case '\n': subs = "\\n"; break;
+ case '\t': subs = "\\t"; break;
+ default: break;
+ }
+ if( !subs.empty() ) {
+ s = s.substr( 0, i ) + subs + s.substr( i+1 );
+ ++i;
+ }
+ }
+ }
+ return "\"" + s + "\"";
+}
+std::string toString( std::wstring const& value ) {
+
+ std::string s;
+ s.reserve( value.size() );
+ for(size_t i = 0; i < value.size(); ++i )
+ s += value[i] <= 0xff ? static_cast<char>( value[i] ) : '?';
+ return Catch::toString( s );
+}
+
+std::string toString( const char* const value ) {
+ return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" );
+}
+
+std::string toString( char* const value ) {
+ return Catch::toString( static_cast<const char*>( value ) );
+}
+
+std::string toString( const wchar_t* const value )
+{
+ return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" );
+}
+
+std::string toString( wchar_t* const value )
+{
+ return Catch::toString( static_cast<const wchar_t*>( value ) );
+}
+
+std::string toString( int value ) {
+ std::ostringstream oss;
+ oss << value;
+ if( value > Detail::hexThreshold )
+ oss << " (0x" << std::hex << value << ")";
+ return oss.str();
+}
+
+std::string toString( unsigned long value ) {
+ std::ostringstream oss;
+ oss << value;
+ if( value > Detail::hexThreshold )
+ oss << " (0x" << std::hex << value << ")";
+ return oss.str();
+}
+
+std::string toString( unsigned int value ) {
+ return Catch::toString( static_cast<unsigned long>( value ) );
+}
+
+template<typename T>
+std::string fpToString( T value, int precision ) {
+ std::ostringstream oss;
+ oss << std::setprecision( precision )
+ << std::fixed
+ << value;
+ std::string d = oss.str();
+ std::size_t i = d.find_last_not_of( '0' );
+ if( i != std::string::npos && i != d.size()-1 ) {
+ if( d[i] == '.' )
+ i++;
+ d = d.substr( 0, i+1 );
+ }
+ return d;
+}
+
+std::string toString( const double value ) {
+ return fpToString( value, 10 );
+}
+std::string toString( const float value ) {
+ return fpToString( value, 5 ) + "f";
+}
+
+std::string toString( bool value ) {
+ return value ? "true" : "false";
+}
+
+std::string toString( char value ) {
+ return value < ' '
+ ? toString( static_cast<unsigned int>( value ) )
+ : Detail::makeString( value );
+}
+
+std::string toString( signed char value ) {
+ return toString( static_cast<char>( value ) );
+}
+
+std::string toString( unsigned char value ) {
+ return toString( static_cast<char>( value ) );
+}
+
+#ifdef CATCH_CONFIG_CPP11_LONG_LONG
+std::string toString( long long value ) {
+ std::ostringstream oss;
+ oss << value;
+ if( value > Detail::hexThreshold )
+ oss << " (0x" << std::hex << value << ")";
+ return oss.str();
+}
+std::string toString( unsigned long long value ) {
+ std::ostringstream oss;
+ oss << value;
+ if( value > Detail::hexThreshold )
+ oss << " (0x" << std::hex << value << ")";
+ return oss.str();
+}
+#endif
+
+#ifdef CATCH_CONFIG_CPP11_NULLPTR
+std::string toString( std::nullptr_t ) {
+ return "nullptr";
+}
+#endif
+
+#ifdef __OBJC__
+ std::string toString( NSString const * const& nsstring ) {
+ if( !nsstring )
+ return "nil";
+ return "@" + toString([nsstring UTF8String]);
+ }
+ std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) {
+ if( !nsstring )
+ return "nil";
+ return "@" + toString([nsstring UTF8String]);
+ }
+ std::string toString( NSObject* const& nsObject ) {
+ return toString( [nsObject description] );
+ }
+#endif
+
+} // end namespace Catch
+
+// #included from: catch_result_builder.hpp
+#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED
+
+namespace Catch {
+
+ std::string capturedExpressionWithSecondArgument( std::string const& capturedExpression, std::string const& secondArg ) {
+ return secondArg.empty() || secondArg == "\"\""
+ ? capturedExpression
+ : capturedExpression + ", " + secondArg;
+ }
+ ResultBuilder::ResultBuilder( char const* macroName,
+ SourceLineInfo const& lineInfo,
+ char const* capturedExpression,
+ ResultDisposition::Flags resultDisposition,
+ char const* secondArg )
+ : m_assertionInfo( macroName, lineInfo, capturedExpressionWithSecondArgument( capturedExpression, secondArg ), resultDisposition ),
+ m_shouldDebugBreak( false ),
+ m_shouldThrow( false )
+ {}
+
+ ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) {
+ m_data.resultType = result;
+ return *this;
+ }
+ ResultBuilder& ResultBuilder::setResultType( bool result ) {
+ m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed;
+ return *this;
+ }
+ ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) {
+ m_exprComponents.lhs = lhs;
+ return *this;
+ }
+ ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) {
+ m_exprComponents.rhs = rhs;
+ return *this;
+ }
+ ResultBuilder& ResultBuilder::setOp( std::string const& op ) {
+ m_exprComponents.op = op;
+ return *this;
+ }
+
+ void ResultBuilder::endExpression() {
+ m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition );
+ captureExpression();
+ }
+
+ void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) {
+ m_assertionInfo.resultDisposition = resultDisposition;
+ m_stream.oss << Catch::translateActiveException();
+ captureResult( ResultWas::ThrewException );
+ }
+
+ void ResultBuilder::captureResult( ResultWas::OfType resultType ) {
+ setResultType( resultType );
+ captureExpression();
+ }
+ void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) {
+ if( expectedMessage.empty() )
+ captureExpectedException( Matchers::Impl::Generic::AllOf<std::string>() );
+ else
+ captureExpectedException( Matchers::Equals( expectedMessage ) );
+ }
+
+ void ResultBuilder::captureExpectedException( Matchers::Impl::Matcher<std::string> const& matcher ) {
+
+ assert( m_exprComponents.testFalse == false );
+ AssertionResultData data = m_data;
+ data.resultType = ResultWas::Ok;
+ data.reconstructedExpression = m_assertionInfo.capturedExpression;
+
+ std::string actualMessage = Catch::translateActiveException();
+ if( !matcher.match( actualMessage ) ) {
+ data.resultType = ResultWas::ExpressionFailed;
+ data.reconstructedExpression = actualMessage;
+ }
+ AssertionResult result( m_assertionInfo, data );
+ handleResult( result );
+ }
+
+ void ResultBuilder::captureExpression() {
+ AssertionResult result = build();
+ handleResult( result );
+ }
+ void ResultBuilder::handleResult( AssertionResult const& result )
+ {
+ getResultCapture().assertionEnded( result );
+
+ if( !result.isOk() ) {
+ if( getCurrentContext().getConfig()->shouldDebugBreak() )
+ m_shouldDebugBreak = true;
+ if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) )
+ m_shouldThrow = true;
+ }
+ }
+ void ResultBuilder::react() {
+ if( m_shouldThrow )
+ throw Catch::TestFailureException();
+ }
+
+ bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; }
+ bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); }
+
+ AssertionResult ResultBuilder::build() const
+ {
+ assert( m_data.resultType != ResultWas::Unknown );
+
+ AssertionResultData data = m_data;
+
+ // Flip bool results if testFalse is set
+ if( m_exprComponents.testFalse ) {
+ if( data.resultType == ResultWas::Ok )
+ data.resultType = ResultWas::ExpressionFailed;
+ else if( data.resultType == ResultWas::ExpressionFailed )
+ data.resultType = ResultWas::Ok;
+ }
+
+ data.message = m_stream.oss.str();
+ data.reconstructedExpression = reconstructExpression();
+ if( m_exprComponents.testFalse ) {
+ if( m_exprComponents.op == "" )
+ data.reconstructedExpression = "!" + data.reconstructedExpression;
+ else
+ data.reconstructedExpression = "!(" + data.reconstructedExpression + ")";
+ }
+ return AssertionResult( m_assertionInfo, data );
+ }
+ std::string ResultBuilder::reconstructExpression() const {
+ if( m_exprComponents.op == "" )
+ return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs;
+ else if( m_exprComponents.op == "matches" )
+ return m_exprComponents.lhs + " " + m_exprComponents.rhs;
+ else if( m_exprComponents.op != "!" ) {
+ if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 &&
+ m_exprComponents.lhs.find("\n") == std::string::npos &&
+ m_exprComponents.rhs.find("\n") == std::string::npos )
+ return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs;
+ else
+ return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs;
+ }
+ else
+ return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}";
+ }
+
+} // end namespace Catch
+
+// #included from: catch_tag_alias_registry.hpp
+#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED
+
+// #included from: catch_tag_alias_registry.h
+#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED
+
+#include <map>
+
+namespace Catch {
+
+ class TagAliasRegistry : public ITagAliasRegistry {
+ public:
+ virtual ~TagAliasRegistry();
+ virtual Option<TagAlias> find( std::string const& alias ) const;
+ virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const;
+ void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
+ static TagAliasRegistry& get();
+
+ private:
+ std::map<std::string, TagAlias> m_registry;
+ };
+
+} // end namespace Catch
+
+#include <map>
+#include <iostream>
+
+namespace Catch {
+
+ TagAliasRegistry::~TagAliasRegistry() {}
+
+ Option<TagAlias> TagAliasRegistry::find( std::string const& alias ) const {
+ std::map<std::string, TagAlias>::const_iterator it = m_registry.find( alias );
+ if( it != m_registry.end() )
+ return it->second;
+ else
+ return Option<TagAlias>();
+ }
+
+ std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const {
+ std::string expandedTestSpec = unexpandedTestSpec;
+ for( std::map<std::string, TagAlias>::const_iterator it = m_registry.begin(), itEnd = m_registry.end();
+ it != itEnd;
+ ++it ) {
+ std::size_t pos = expandedTestSpec.find( it->first );
+ if( pos != std::string::npos ) {
+ expandedTestSpec = expandedTestSpec.substr( 0, pos ) +
+ it->second.tag +
+ expandedTestSpec.substr( pos + it->first.size() );
+ }
+ }
+ return expandedTestSpec;
+ }
+
+ void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
+
+ if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) {
+ std::ostringstream oss;
+ oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo;
+ throw std::domain_error( oss.str().c_str() );
+ }
+ if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) {
+ std::ostringstream oss;
+ oss << "error: tag alias, \"" << alias << "\" already registered.\n"
+ << "\tFirst seen at " << find(alias)->lineInfo << "\n"
+ << "\tRedefined at " << lineInfo;
+ throw std::domain_error( oss.str().c_str() );
+ }
+ }
+
+ TagAliasRegistry& TagAliasRegistry::get() {
+ static TagAliasRegistry instance;
+ return instance;
+
+ }
+
+ ITagAliasRegistry::~ITagAliasRegistry() {}
+ ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); }
+
+ RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) {
+ try {
+ TagAliasRegistry::get().add( alias, tag, lineInfo );
+ }
+ catch( std::exception& ex ) {
+ Colour colourGuard( Colour::Red );
+ Catch::cerr() << ex.what() << std::endl;
+ exit(1);
+ }
+ }
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_multi.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED
+
+namespace Catch {
+
+class MultipleReporters : public SharedImpl<IStreamingReporter> {
+ typedef std::vector<Ptr<IStreamingReporter> > Reporters;
+ Reporters m_reporters;
+
+public:
+ void add( Ptr<IStreamingReporter> const& reporter ) {
+ m_reporters.push_back( reporter );
+ }
+
+public: // IStreamingReporter
+
+ virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE {
+ return m_reporters[0]->getPreferences();
+ }
+
+ virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE {
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ (*it)->noMatchingTestCases( spec );
+ }
+
+ virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE {
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ (*it)->testRunStarting( testRunInfo );
+ }
+
+ virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE {
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ (*it)->testGroupStarting( groupInfo );
+ }
+
+ virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE {
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ (*it)->testCaseStarting( testInfo );
+ }
+
+ virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE {
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ (*it)->sectionStarting( sectionInfo );
+ }
+
+ virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE {
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ (*it)->assertionStarting( assertionInfo );
+ }
+
+ // The return value indicates if the messages buffer should be cleared:
+ virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
+ bool clearBuffer = false;
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ clearBuffer |= (*it)->assertionEnded( assertionStats );
+ return clearBuffer;
+ }
+
+ virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE {
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ (*it)->sectionEnded( sectionStats );
+ }
+
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ (*it)->testCaseEnded( testCaseStats );
+ }
+
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ (*it)->testGroupEnded( testGroupStats );
+ }
+
+ virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE {
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ (*it)->testRunEnded( testRunStats );
+ }
+
+ virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE {
+ for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end();
+ it != itEnd;
+ ++it )
+ (*it)->skipTest( testInfo );
+ }
+};
+
+Ptr<IStreamingReporter> addReporter( Ptr<IStreamingReporter> const& existingReporter, Ptr<IStreamingReporter> const& additionalReporter ) {
+ Ptr<IStreamingReporter> resultingReporter;
+
+ if( existingReporter ) {
+ MultipleReporters* multi = dynamic_cast<MultipleReporters*>( existingReporter.get() );
+ if( !multi ) {
+ multi = new MultipleReporters;
+ resultingReporter = Ptr<IStreamingReporter>( multi );
+ if( existingReporter )
+ multi->add( existingReporter );
+ }
+ else
+ resultingReporter = existingReporter;
+ multi->add( additionalReporter );
+ }
+ else
+ resultingReporter = additionalReporter;
+
+ return resultingReporter;
+}
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_xml.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED
+
+// #included from: catch_reporter_bases.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED
+
+#include <cstring>
+
+namespace Catch {
+
+ struct StreamingReporterBase : SharedImpl<IStreamingReporter> {
+
+ StreamingReporterBase( ReporterConfig const& _config )
+ : m_config( _config.fullConfig() ),
+ stream( _config.stream() )
+ {
+ m_reporterPrefs.shouldRedirectStdOut = false;
+ }
+
+ virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE {
+ return m_reporterPrefs;
+ }
+
+ virtual ~StreamingReporterBase() CATCH_OVERRIDE;
+
+ virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {}
+
+ virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE {
+ currentTestRunInfo = _testRunInfo;
+ }
+ virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE {
+ currentGroupInfo = _groupInfo;
+ }
+
+ virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE {
+ currentTestCaseInfo = _testInfo;
+ }
+ virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE {
+ m_sectionStack.push_back( _sectionInfo );
+ }
+
+ virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE {
+ m_sectionStack.pop_back();
+ }
+ virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE {
+ currentTestCaseInfo.reset();
+ }
+ virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE {
+ currentGroupInfo.reset();
+ }
+ virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE {
+ currentTestCaseInfo.reset();
+ currentGroupInfo.reset();
+ currentTestRunInfo.reset();
+ }
+
+ virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {
+ // Don't do anything with this by default.
+ // It can optionally be overridden in the derived class.
+ }
+
+ Ptr<IConfig const> m_config;
+ std::ostream& stream;
+
+ LazyStat<TestRunInfo> currentTestRunInfo;
+ LazyStat<GroupInfo> currentGroupInfo;
+ LazyStat<TestCaseInfo> currentTestCaseInfo;
+
+ std::vector<SectionInfo> m_sectionStack;
+ ReporterPreferences m_reporterPrefs;
+ };
+
+ struct CumulativeReporterBase : SharedImpl<IStreamingReporter> {
+ template<typename T, typename ChildNodeT>
+ struct Node : SharedImpl<> {
+ explicit Node( T const& _value ) : value( _value ) {}
+ virtual ~Node() {}
+
+ typedef std::vector<Ptr<ChildNodeT> > ChildNodes;
+ T value;
+ ChildNodes children;
+ };
+ struct SectionNode : SharedImpl<> {
+ explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {}
+ virtual ~SectionNode();
+
+ bool operator == ( SectionNode const& other ) const {
+ return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo;
+ }
+ bool operator == ( Ptr<SectionNode> const& other ) const {
+ return operator==( *other );
+ }
+
+ SectionStats stats;
+ typedef std::vector<Ptr<SectionNode> > ChildSections;
+ typedef std::vector<AssertionStats> Assertions;
+ ChildSections childSections;
+ Assertions assertions;
+ std::string stdOut;
+ std::string stdErr;
+ };
+
+ struct BySectionInfo {
+ BySectionInfo( SectionInfo const& other ) : m_other( other ) {}
+ BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {}
+ bool operator() ( Ptr<SectionNode> const& node ) const {
+ return node->stats.sectionInfo.lineInfo == m_other.lineInfo;
+ }
+ private:
+ void operator=( BySectionInfo const& );
+ SectionInfo const& m_other;
+ };
+
+ typedef Node<TestCaseStats, SectionNode> TestCaseNode;
+ typedef Node<TestGroupStats, TestCaseNode> TestGroupNode;
+ typedef Node<TestRunStats, TestGroupNode> TestRunNode;
+
+ CumulativeReporterBase( ReporterConfig const& _config )
+ : m_config( _config.fullConfig() ),
+ stream( _config.stream() )
+ {
+ m_reporterPrefs.shouldRedirectStdOut = false;
+ }
+ ~CumulativeReporterBase();
+
+ virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE {
+ return m_reporterPrefs;
+ }
+
+ virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {}
+ virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {}
+
+ virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {}
+
+ virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE {
+ SectionStats incompleteStats( sectionInfo, Counts(), 0, false );
+ Ptr<SectionNode> node;
+ if( m_sectionStack.empty() ) {
+ if( !m_rootSection )
+ m_rootSection = new SectionNode( incompleteStats );
+ node = m_rootSection;
+ }
+ else {
+ SectionNode& parentNode = *m_sectionStack.back();
+ SectionNode::ChildSections::const_iterator it =
+ std::find_if( parentNode.childSections.begin(),
+ parentNode.childSections.end(),
+ BySectionInfo( sectionInfo ) );
+ if( it == parentNode.childSections.end() ) {
+ node = new SectionNode( incompleteStats );
+ parentNode.childSections.push_back( node );
+ }
+ else
+ node = *it;
+ }
+ m_sectionStack.push_back( node );
+ m_deepestSection = node;
+ }
+
+ virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {}
+
+ virtual bool assertionEnded( AssertionStats const& assertionStats ) {
+ assert( !m_sectionStack.empty() );
+ SectionNode& sectionNode = *m_sectionStack.back();
+ sectionNode.assertions.push_back( assertionStats );
+ return true;
+ }
+ virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE {
+ assert( !m_sectionStack.empty() );
+ SectionNode& node = *m_sectionStack.back();
+ node.stats = sectionStats;
+ m_sectionStack.pop_back();
+ }
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
+ Ptr<TestCaseNode> node = new TestCaseNode( testCaseStats );
+ assert( m_sectionStack.size() == 0 );
+ node->children.push_back( m_rootSection );
+ m_testCases.push_back( node );
+ m_rootSection.reset();
+
+ assert( m_deepestSection );
+ m_deepestSection->stdOut = testCaseStats.stdOut;
+ m_deepestSection->stdErr = testCaseStats.stdErr;
+ }
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
+ Ptr<TestGroupNode> node = new TestGroupNode( testGroupStats );
+ node->children.swap( m_testCases );
+ m_testGroups.push_back( node );
+ }
+ virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE {
+ Ptr<TestRunNode> node = new TestRunNode( testRunStats );
+ node->children.swap( m_testGroups );
+ m_testRuns.push_back( node );
+ testRunEndedCumulative();
+ }
+ virtual void testRunEndedCumulative() = 0;
+
+ virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {}
+
+ Ptr<IConfig const> m_config;
+ std::ostream& stream;
+ std::vector<AssertionStats> m_assertions;
+ std::vector<std::vector<Ptr<SectionNode> > > m_sections;
+ std::vector<Ptr<TestCaseNode> > m_testCases;
+ std::vector<Ptr<TestGroupNode> > m_testGroups;
+
+ std::vector<Ptr<TestRunNode> > m_testRuns;
+
+ Ptr<SectionNode> m_rootSection;
+ Ptr<SectionNode> m_deepestSection;
+ std::vector<Ptr<SectionNode> > m_sectionStack;
+ ReporterPreferences m_reporterPrefs;
+
+ };
+
+ template<char C>
+ char const* getLineOfChars() {
+ static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0};
+ if( !*line ) {
+ memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 );
+ line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0;
+ }
+ return line;
+ }
+
+ struct TestEventListenerBase : StreamingReporterBase {
+ TestEventListenerBase( ReporterConfig const& _config )
+ : StreamingReporterBase( _config )
+ {}
+
+ virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {}
+ virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE {
+ return false;
+ }
+ };
+
+} // end namespace Catch
+
+// #included from: ../internal/catch_reporter_registrars.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED
+
+namespace Catch {
+
+ template<typename T>
+ class LegacyReporterRegistrar {
+
+ class ReporterFactory : public IReporterFactory {
+ virtual IStreamingReporter* create( ReporterConfig const& config ) const {
+ return new LegacyReporterAdapter( new T( config ) );
+ }
+
+ virtual std::string getDescription() const {
+ return T::getDescription();
+ }
+ };
+
+ public:
+
+ LegacyReporterRegistrar( std::string const& name ) {
+ getMutableRegistryHub().registerReporter( name, new ReporterFactory() );
+ }
+ };
+
+ template<typename T>
+ class ReporterRegistrar {
+
+ class ReporterFactory : public SharedImpl<IReporterFactory> {
+
+ // *** Please Note ***:
+ // - If you end up here looking at a compiler error because it's trying to register
+ // your custom reporter class be aware that the native reporter interface has changed
+ // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via
+ // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter.
+ // However please consider updating to the new interface as the old one is now
+ // deprecated and will probably be removed quite soon!
+ // Please contact me via github if you have any questions at all about this.
+ // In fact, ideally, please contact me anyway to let me know you've hit this - as I have
+ // no idea who is actually using custom reporters at all (possibly no-one!).
+ // The new interface is designed to minimise exposure to interface changes in the future.
+ virtual IStreamingReporter* create( ReporterConfig const& config ) const {
+ return new T( config );
+ }
+
+ virtual std::string getDescription() const {
+ return T::getDescription();
+ }
+ };
+
+ public:
+
+ ReporterRegistrar( std::string const& name ) {
+ getMutableRegistryHub().registerReporter( name, new ReporterFactory() );
+ }
+ };
+
+ template<typename T>
+ class ListenerRegistrar {
+
+ class ListenerFactory : public SharedImpl<IReporterFactory> {
+
+ virtual IStreamingReporter* create( ReporterConfig const& config ) const {
+ return new T( config );
+ }
+ virtual std::string getDescription() const {
+ return "";
+ }
+ };
+
+ public:
+
+ ListenerRegistrar() {
+ getMutableRegistryHub().registerListener( new ListenerFactory() );
+ }
+ };
+}
+
+#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \
+ namespace{ Catch::LegacyReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); }
+
+#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \
+ namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); }
+
+#define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \
+ namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; }
+
+// #included from: ../internal/catch_xmlwriter.hpp
+#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
+
+#include <sstream>
+#include <string>
+#include <vector>
+#include <iomanip>
+
+namespace Catch {
+
+ class XmlEncode {
+ public:
+ enum ForWhat { ForTextNodes, ForAttributes };
+
+ XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes )
+ : m_str( str ),
+ m_forWhat( forWhat )
+ {}
+
+ void encodeTo( std::ostream& os ) const {
+
+ // Apostrophe escaping not necessary if we always use " to write attributes
+ // (see: http://www.w3.org/TR/xml/#syntax)
+
+ for( std::size_t i = 0; i < m_str.size(); ++ i ) {
+ char c = m_str[i];
+ switch( c ) {
+ case '<': os << "&lt;"; break;
+ case '&': os << "&amp;"; break;
+
+ case '>':
+ // See: http://www.w3.org/TR/xml/#syntax
+ if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' )
+ os << "&gt;";
+ else
+ os << c;
+ break;
+
+ case '\"':
+ if( m_forWhat == ForAttributes )
+ os << "&quot;";
+ else
+ os << c;
+ break;
+
+ default:
+ // Escape control chars - based on contribution by @espenalb in PR #465
+ if ( ( c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' )
+ os << "&#x" << std::uppercase << std::hex << static_cast<int>( c );
+ else
+ os << c;
+ }
+ }
+ }
+
+ friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
+ xmlEncode.encodeTo( os );
+ return os;
+ }
+
+ private:
+ std::string m_str;
+ ForWhat m_forWhat;
+ };
+
+ class XmlWriter {
+ public:
+
+ class ScopedElement {
+ public:
+ ScopedElement( XmlWriter* writer )
+ : m_writer( writer )
+ {}
+
+ ScopedElement( ScopedElement const& other )
+ : m_writer( other.m_writer ){
+ other.m_writer = CATCH_NULL;
+ }
+
+ ~ScopedElement() {
+ if( m_writer )
+ m_writer->endElement();
+ }
+
+ ScopedElement& writeText( std::string const& text, bool indent = true ) {
+ m_writer->writeText( text, indent );
+ return *this;
+ }
+
+ template<typename T>
+ ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
+ m_writer->writeAttribute( name, attribute );
+ return *this;
+ }
+
+ private:
+ mutable XmlWriter* m_writer;
+ };
+
+ XmlWriter()
+ : m_tagIsOpen( false ),
+ m_needsNewline( false ),
+ m_os( &Catch::cout() )
+ {}
+
+ XmlWriter( std::ostream& os )
+ : m_tagIsOpen( false ),
+ m_needsNewline( false ),
+ m_os( &os )
+ {}
+
+ ~XmlWriter() {
+ while( !m_tags.empty() )
+ endElement();
+ }
+
+ XmlWriter& startElement( std::string const& name ) {
+ ensureTagClosed();
+ newlineIfNecessary();
+ stream() << m_indent << "<" << name;
+ m_tags.push_back( name );
+ m_indent += " ";
+ m_tagIsOpen = true;
+ return *this;
+ }
+
+ ScopedElement scopedElement( std::string const& name ) {
+ ScopedElement scoped( this );
+ startElement( name );
+ return scoped;
+ }
+
+ XmlWriter& endElement() {
+ newlineIfNecessary();
+ m_indent = m_indent.substr( 0, m_indent.size()-2 );
+ if( m_tagIsOpen ) {
+ stream() << "/>\n";
+ m_tagIsOpen = false;
+ }
+ else {
+ stream() << m_indent << "</" << m_tags.back() << ">\n";
+ }
+ m_tags.pop_back();
+ return *this;
+ }
+
+ XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) {
+ if( !name.empty() && !attribute.empty() )
+ stream() << " " << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << "\"";
+ return *this;
+ }
+
+ XmlWriter& writeAttribute( std::string const& name, bool attribute ) {
+ stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\"";
+ return *this;
+ }
+
+ template<typename T>
+ XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
+ std::ostringstream oss;
+ oss << attribute;
+ return writeAttribute( name, oss.str() );
+ }
+
+ XmlWriter& writeText( std::string const& text, bool indent = true ) {
+ if( !text.empty() ){
+ bool tagWasOpen = m_tagIsOpen;
+ ensureTagClosed();
+ if( tagWasOpen && indent )
+ stream() << m_indent;
+ stream() << XmlEncode( text );
+ m_needsNewline = true;
+ }
+ return *this;
+ }
+
+ XmlWriter& writeComment( std::string const& text ) {
+ ensureTagClosed();
+ stream() << m_indent << "<!--" << text << "-->";
+ m_needsNewline = true;
+ return *this;
+ }
+
+ XmlWriter& writeBlankLine() {
+ ensureTagClosed();
+ stream() << "\n";
+ return *this;
+ }
+
+ void setStream( std::ostream& os ) {
+ m_os = &os;
+ }
+
+ private:
+ XmlWriter( XmlWriter const& );
+ void operator=( XmlWriter const& );
+
+ std::ostream& stream() {
+ return *m_os;
+ }
+
+ void ensureTagClosed() {
+ if( m_tagIsOpen ) {
+ stream() << ">\n";
+ m_tagIsOpen = false;
+ }
+ }
+
+ void newlineIfNecessary() {
+ if( m_needsNewline ) {
+ stream() << "\n";
+ m_needsNewline = false;
+ }
+ }
+
+ bool m_tagIsOpen;
+ bool m_needsNewline;
+ std::vector<std::string> m_tags;
+ std::string m_indent;
+ std::ostream* m_os;
+ };
+
+}
+// #included from: catch_reenable_warnings.h
+
+#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED
+
+#ifdef __clang__
+# ifdef __ICC // icpc defines the __clang__ macro
+# pragma warning(pop)
+# else
+# pragma clang diagnostic pop
+# endif
+#elif defined __GNUC__
+# pragma GCC diagnostic pop
+#endif
+
+
+namespace Catch {
+ class XmlReporter : public StreamingReporterBase {
+ public:
+ XmlReporter( ReporterConfig const& _config )
+ : StreamingReporterBase( _config ),
+ m_sectionDepth( 0 )
+ {
+ m_reporterPrefs.shouldRedirectStdOut = true;
+ }
+
+ virtual ~XmlReporter() CATCH_OVERRIDE;
+
+ static std::string getDescription() {
+ return "Reports test results as an XML document";
+ }
+
+ public: // StreamingReporterBase
+
+ virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE {
+ StreamingReporterBase::noMatchingTestCases( s );
+ }
+
+ virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE {
+ StreamingReporterBase::testRunStarting( testInfo );
+ m_xml.setStream( stream );
+ m_xml.startElement( "Catch" );
+ if( !m_config->name().empty() )
+ m_xml.writeAttribute( "name", m_config->name() );
+ }
+
+ virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE {
+ StreamingReporterBase::testGroupStarting( groupInfo );
+ m_xml.startElement( "Group" )
+ .writeAttribute( "name", groupInfo.name );
+ }
+
+ virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE {
+ StreamingReporterBase::testCaseStarting(testInfo);
+ m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) );
+
+ if ( m_config->showDurations() == ShowDurations::Always )
+ m_testCaseTimer.start();
+ }
+
+ virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE {
+ StreamingReporterBase::sectionStarting( sectionInfo );
+ if( m_sectionDepth++ > 0 ) {
+ m_xml.startElement( "Section" )
+ .writeAttribute( "name", trim( sectionInfo.name ) )
+ .writeAttribute( "description", sectionInfo.description );
+ }
+ }
+
+ virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { }
+
+ virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
+ const AssertionResult& assertionResult = assertionStats.assertionResult;
+
+ // Print any info messages in <Info> tags.
+ if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) {
+ for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end();
+ it != itEnd;
+ ++it ) {
+ if( it->type == ResultWas::Info ) {
+ m_xml.scopedElement( "Info" )
+ .writeText( it->message );
+ } else if ( it->type == ResultWas::Warning ) {
+ m_xml.scopedElement( "Warning" )
+ .writeText( it->message );
+ }
+ }
+ }
+
+ // Drop out if result was successful but we're not printing them.
+ if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) )
+ return true;
+
+ // Print the expression if there is one.
+ if( assertionResult.hasExpression() ) {
+ m_xml.startElement( "Expression" )
+ .writeAttribute( "success", assertionResult.succeeded() )
+ .writeAttribute( "type", assertionResult.getTestMacroName() )
+ .writeAttribute( "filename", assertionResult.getSourceInfo().file )
+ .writeAttribute( "line", assertionResult.getSourceInfo().line );
+
+ m_xml.scopedElement( "Original" )
+ .writeText( assertionResult.getExpression() );
+ m_xml.scopedElement( "Expanded" )
+ .writeText( assertionResult.getExpandedExpression() );
+ }
+
+ // And... Print a result applicable to each result type.
+ switch( assertionResult.getResultType() ) {
+ case ResultWas::ThrewException:
+ m_xml.scopedElement( "Exception" )
+ .writeAttribute( "filename", assertionResult.getSourceInfo().file )
+ .writeAttribute( "line", assertionResult.getSourceInfo().line )
+ .writeText( assertionResult.getMessage() );
+ break;
+ case ResultWas::FatalErrorCondition:
+ m_xml.scopedElement( "Fatal Error Condition" )
+ .writeAttribute( "filename", assertionResult.getSourceInfo().file )
+ .writeAttribute( "line", assertionResult.getSourceInfo().line )
+ .writeText( assertionResult.getMessage() );
+ break;
+ case ResultWas::Info:
+ m_xml.scopedElement( "Info" )
+ .writeText( assertionResult.getMessage() );
+ break;
+ case ResultWas::Warning:
+ // Warning will already have been written
+ break;
+ case ResultWas::ExplicitFailure:
+ m_xml.scopedElement( "Failure" )
+ .writeText( assertionResult.getMessage() );
+ break;
+ default:
+ break;
+ }
+
+ if( assertionResult.hasExpression() )
+ m_xml.endElement();
+
+ return true;
+ }
+
+ virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE {
+ StreamingReporterBase::sectionEnded( sectionStats );
+ if( --m_sectionDepth > 0 ) {
+ XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" );
+ e.writeAttribute( "successes", sectionStats.assertions.passed );
+ e.writeAttribute( "failures", sectionStats.assertions.failed );
+ e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk );
+
+ if ( m_config->showDurations() == ShowDurations::Always )
+ e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds );
+
+ m_xml.endElement();
+ }
+ }
+
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
+ StreamingReporterBase::testCaseEnded( testCaseStats );
+ XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" );
+ e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() );
+
+ if ( m_config->showDurations() == ShowDurations::Always )
+ e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() );
+
+ m_xml.endElement();
+ }
+
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
+ StreamingReporterBase::testGroupEnded( testGroupStats );
+ // TODO: Check testGroupStats.aborting and act accordingly.
+ m_xml.scopedElement( "OverallResults" )
+ .writeAttribute( "successes", testGroupStats.totals.assertions.passed )
+ .writeAttribute( "failures", testGroupStats.totals.assertions.failed )
+ .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk );
+ m_xml.endElement();
+ }
+
+ virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE {
+ StreamingReporterBase::testRunEnded( testRunStats );
+ m_xml.scopedElement( "OverallResults" )
+ .writeAttribute( "successes", testRunStats.totals.assertions.passed )
+ .writeAttribute( "failures", testRunStats.totals.assertions.failed )
+ .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk );
+ m_xml.endElement();
+ }
+
+ private:
+ Timer m_testCaseTimer;
+ XmlWriter m_xml;
+ int m_sectionDepth;
+ };
+
+ INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter )
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_junit.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED
+
+#include <assert.h>
+
+namespace Catch {
+
+ class JunitReporter : public CumulativeReporterBase {
+ public:
+ JunitReporter( ReporterConfig const& _config )
+ : CumulativeReporterBase( _config ),
+ xml( _config.stream() )
+ {
+ m_reporterPrefs.shouldRedirectStdOut = true;
+ }
+
+ virtual ~JunitReporter() CATCH_OVERRIDE;
+
+ static std::string getDescription() {
+ return "Reports test results in an XML format that looks like Ant's junitreport target";
+ }
+
+ virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {}
+
+ virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE {
+ CumulativeReporterBase::testRunStarting( runInfo );
+ xml.startElement( "testsuites" );
+ }
+
+ virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE {
+ suiteTimer.start();
+ stdOutForSuite.str("");
+ stdErrForSuite.str("");
+ unexpectedExceptions = 0;
+ CumulativeReporterBase::testGroupStarting( groupInfo );
+ }
+
+ virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE {
+ if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException )
+ unexpectedExceptions++;
+ return CumulativeReporterBase::assertionEnded( assertionStats );
+ }
+
+ virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE {
+ stdOutForSuite << testCaseStats.stdOut;
+ stdErrForSuite << testCaseStats.stdErr;
+ CumulativeReporterBase::testCaseEnded( testCaseStats );
+ }
+
+ virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE {
+ double suiteTime = suiteTimer.getElapsedSeconds();
+ CumulativeReporterBase::testGroupEnded( testGroupStats );
+ writeGroup( *m_testGroups.back(), suiteTime );
+ }
+
+ virtual void testRunEndedCumulative() CATCH_OVERRIDE {
+ xml.endElement();
+ }
+
+ void writeGroup( TestGroupNode const& groupNode, double suiteTime ) {
+ XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
+ TestGroupStats const& stats = groupNode.value;
+ xml.writeAttribute( "name", stats.groupInfo.name );
+ xml.writeAttribute( "errors", unexpectedExceptions );
+ xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions );
+ xml.writeAttribute( "tests", stats.totals.assertions.total() );
+ xml.writeAttribute( "hostname", "tbd" ); // !TBD
+ if( m_config->showDurations() == ShowDurations::Never )
+ xml.writeAttribute( "time", "" );
+ else
+ xml.writeAttribute( "time", suiteTime );
+ xml.writeAttribute( "timestamp", "tbd" ); // !TBD
+
+ // Write test cases
+ for( TestGroupNode::ChildNodes::const_iterator
+ it = groupNode.children.begin(), itEnd = groupNode.children.end();
+ it != itEnd;
+ ++it )
+ writeTestCase( **it );
+
+ xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false );
+ xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false );
+ }
+
+ void writeTestCase( TestCaseNode const& testCaseNode ) {
+ TestCaseStats const& stats = testCaseNode.value;
+
+ // All test cases have exactly one section - which represents the
+ // test case itself. That section may have 0-n nested sections
+ assert( testCaseNode.children.size() == 1 );
+ SectionNode const& rootSection = *testCaseNode.children.front();
+
+ std::string className = stats.testInfo.className;
+
+ if( className.empty() ) {
+ if( rootSection.childSections.empty() )
+ className = "global";
+ }
+ writeSection( className, "", rootSection );
+ }
+
+ void writeSection( std::string const& className,
+ std::string const& rootName,
+ SectionNode const& sectionNode ) {
+ std::string name = trim( sectionNode.stats.sectionInfo.name );
+ if( !rootName.empty() )
+ name = rootName + "/" + name;
+
+ if( !sectionNode.assertions.empty() ||
+ !sectionNode.stdOut.empty() ||
+ !sectionNode.stdErr.empty() ) {
+ XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
+ if( className.empty() ) {
+ xml.writeAttribute( "classname", name );
+ xml.writeAttribute( "name", "root" );
+ }
+ else {
+ xml.writeAttribute( "classname", className );
+ xml.writeAttribute( "name", name );
+ }
+ xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) );
+
+ writeAssertions( sectionNode );
+
+ if( !sectionNode.stdOut.empty() )
+ xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false );
+ if( !sectionNode.stdErr.empty() )
+ xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false );
+ }
+ for( SectionNode::ChildSections::const_iterator
+ it = sectionNode.childSections.begin(),
+ itEnd = sectionNode.childSections.end();
+ it != itEnd;
+ ++it )
+ if( className.empty() )
+ writeSection( name, "", **it );
+ else
+ writeSection( className, name, **it );
+ }
+
+ void writeAssertions( SectionNode const& sectionNode ) {
+ for( SectionNode::Assertions::const_iterator
+ it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end();
+ it != itEnd;
+ ++it )
+ writeAssertion( *it );
+ }
+ void writeAssertion( AssertionStats const& stats ) {
+ AssertionResult const& result = stats.assertionResult;
+ if( !result.isOk() ) {
+ std::string elementName;
+ switch( result.getResultType() ) {
+ case ResultWas::ThrewException:
+ case ResultWas::FatalErrorCondition:
+ elementName = "error";
+ break;
+ case ResultWas::ExplicitFailure:
+ elementName = "failure";
+ break;
+ case ResultWas::ExpressionFailed:
+ elementName = "failure";
+ break;
+ case ResultWas::DidntThrowException:
+ elementName = "failure";
+ break;
+
+ // We should never see these here:
+ case ResultWas::Info:
+ case ResultWas::Warning:
+ case ResultWas::Ok:
+ case ResultWas::Unknown:
+ case ResultWas::FailureBit:
+ case ResultWas::Exception:
+ elementName = "internalError";
+ break;
+ }
+
+ XmlWriter::ScopedElement e = xml.scopedElement( elementName );
+
+ xml.writeAttribute( "message", result.getExpandedExpression() );
+ xml.writeAttribute( "type", result.getTestMacroName() );
+
+ std::ostringstream oss;
+ if( !result.getMessage().empty() )
+ oss << result.getMessage() << "\n";
+ for( std::vector<MessageInfo>::const_iterator
+ it = stats.infoMessages.begin(),
+ itEnd = stats.infoMessages.end();
+ it != itEnd;
+ ++it )
+ if( it->type == ResultWas::Info )
+ oss << it->message << "\n";
+
+ oss << "at " << result.getSourceInfo();
+ xml.writeText( oss.str(), false );
+ }
+ }
+
+ XmlWriter xml;
+ Timer suiteTimer;
+ std::ostringstream stdOutForSuite;
+ std::ostringstream stdErrForSuite;
+ unsigned int unexpectedExceptions;
+ };
+
+ INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter )
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_console.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED
+
+namespace Catch {
+
+ struct ConsoleReporter : StreamingReporterBase {
+ ConsoleReporter( ReporterConfig const& _config )
+ : StreamingReporterBase( _config ),
+ m_headerPrinted( false )
+ {}
+
+ virtual ~ConsoleReporter() CATCH_OVERRIDE;
+ static std::string getDescription() {
+ return "Reports test results as plain lines of text";
+ }
+
+ virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE {
+ stream << "No test cases matched '" << spec << "'" << std::endl;
+ }
+
+ virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {
+ }
+
+ virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE {
+ AssertionResult const& result = _assertionStats.assertionResult;
+
+ bool printInfoMessages = true;
+
+ // Drop out if result was successful and we're not printing those
+ if( !m_config->includeSuccessfulResults() && result.isOk() ) {
+ if( result.getResultType() != ResultWas::Warning )
+ return false;
+ printInfoMessages = false;
+ }
+
+ lazyPrint();
+
+ AssertionPrinter printer( stream, _assertionStats, printInfoMessages );
+ printer.print();
+ stream << std::endl;
+ return true;
+ }
+
+ virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE {
+ m_headerPrinted = false;
+ StreamingReporterBase::sectionStarting( _sectionInfo );
+ }
+ virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE {
+ if( _sectionStats.missingAssertions ) {
+ lazyPrint();
+ Colour colour( Colour::ResultError );
+ if( m_sectionStack.size() > 1 )
+ stream << "\nNo assertions in section";
+ else
+ stream << "\nNo assertions in test case";
+ stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl;
+ }
+ if( m_headerPrinted ) {
+ if( m_config->showDurations() == ShowDurations::Always )
+ stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl;
+ m_headerPrinted = false;
+ }
+ else {
+ if( m_config->showDurations() == ShowDurations::Always )
+ stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl;
+ }
+ StreamingReporterBase::sectionEnded( _sectionStats );
+ }
+
+ virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE {
+ StreamingReporterBase::testCaseEnded( _testCaseStats );
+ m_headerPrinted = false;
+ }
+ virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE {
+ if( currentGroupInfo.used ) {
+ printSummaryDivider();
+ stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n";
+ printTotals( _testGroupStats.totals );
+ stream << "\n" << std::endl;
+ }
+ StreamingReporterBase::testGroupEnded( _testGroupStats );
+ }
+ virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE {
+ printTotalsDivider( _testRunStats.totals );
+ printTotals( _testRunStats.totals );
+ stream << std::endl;
+ StreamingReporterBase::testRunEnded( _testRunStats );
+ }
+
+ private:
+
+ class AssertionPrinter {
+ void operator= ( AssertionPrinter const& );
+ public:
+ AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages )
+ : stream( _stream ),
+ stats( _stats ),
+ result( _stats.assertionResult ),
+ colour( Colour::None ),
+ message( result.getMessage() ),
+ messages( _stats.infoMessages ),
+ printInfoMessages( _printInfoMessages )
+ {
+ switch( result.getResultType() ) {
+ case ResultWas::Ok:
+ colour = Colour::Success;
+ passOrFail = "PASSED";
+ //if( result.hasMessage() )
+ if( _stats.infoMessages.size() == 1 )
+ messageLabel = "with message";
+ if( _stats.infoMessages.size() > 1 )
+ messageLabel = "with messages";
+ break;
+ case ResultWas::ExpressionFailed:
+ if( result.isOk() ) {
+ colour = Colour::Success;
+ passOrFail = "FAILED - but was ok";
+ }
+ else {
+ colour = Colour::Error;
+ passOrFail = "FAILED";
+ }
+ if( _stats.infoMessages.size() == 1 )
+ messageLabel = "with message";
+ if( _stats.infoMessages.size() > 1 )
+ messageLabel = "with messages";
+ break;
+ case ResultWas::ThrewException:
+ colour = Colour::Error;
+ passOrFail = "FAILED";
+ messageLabel = "due to unexpected exception with message";
+ break;
+ case ResultWas::FatalErrorCondition:
+ colour = Colour::Error;
+ passOrFail = "FAILED";
+ messageLabel = "due to a fatal error condition";
+ break;
+ case ResultWas::DidntThrowException:
+ colour = Colour::Error;
+ passOrFail = "FAILED";
+ messageLabel = "because no exception was thrown where one was expected";
+ break;
+ case ResultWas::Info:
+ messageLabel = "info";
+ break;
+ case ResultWas::Warning:
+ messageLabel = "warning";
+ break;
+ case ResultWas::ExplicitFailure:
+ passOrFail = "FAILED";
+ colour = Colour::Error;
+ if( _stats.infoMessages.size() == 1 )
+ messageLabel = "explicitly with message";
+ if( _stats.infoMessages.size() > 1 )
+ messageLabel = "explicitly with messages";
+ break;
+ // These cases are here to prevent compiler warnings
+ case ResultWas::Unknown:
+ case ResultWas::FailureBit:
+ case ResultWas::Exception:
+ passOrFail = "** internal error **";
+ colour = Colour::Error;
+ break;
+ }
+ }
+
+ void print() const {
+ printSourceInfo();
+ if( stats.totals.assertions.total() > 0 ) {
+ if( result.isOk() )
+ stream << "\n";
+ printResultType();
+ printOriginalExpression();
+ printReconstructedExpression();
+ }
+ else {
+ stream << "\n";
+ }
+ printMessage();
+ }
+
+ private:
+ void printResultType() const {
+ if( !passOrFail.empty() ) {
+ Colour colourGuard( colour );
+ stream << passOrFail << ":\n";
+ }
+ }
+ void printOriginalExpression() const {
+ if( result.hasExpression() ) {
+ Colour colourGuard( Colour::OriginalExpression );
+ stream << " ";
+ stream << result.getExpressionInMacro();
+ stream << "\n";
+ }
+ }
+ void printReconstructedExpression() const {
+ if( result.hasExpandedExpression() ) {
+ stream << "with expansion:\n";
+ Colour colourGuard( Colour::ReconstructedExpression );
+ stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n";
+ }
+ }
+ void printMessage() const {
+ if( !messageLabel.empty() )
+ stream << messageLabel << ":" << "\n";
+ for( std::vector<MessageInfo>::const_iterator it = messages.begin(), itEnd = messages.end();
+ it != itEnd;
+ ++it ) {
+ // If this assertion is a warning ignore any INFO messages
+ if( printInfoMessages || it->type != ResultWas::Info )
+ stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n";
+ }
+ }
+ void printSourceInfo() const {
+ Colour colourGuard( Colour::FileName );
+ stream << result.getSourceInfo() << ": ";
+ }
+
+ std::ostream& stream;
+ AssertionStats const& stats;
+ AssertionResult const& result;
+ Colour::Code colour;
+ std::string passOrFail;
+ std::string messageLabel;
+ std::string message;
+ std::vector<MessageInfo> messages;
+ bool printInfoMessages;
+ };
+
+ void lazyPrint() {
+
+ if( !currentTestRunInfo.used )
+ lazyPrintRunInfo();
+ if( !currentGroupInfo.used )
+ lazyPrintGroupInfo();
+
+ if( !m_headerPrinted ) {
+ printTestCaseAndSectionHeader();
+ m_headerPrinted = true;
+ }
+ }
+ void lazyPrintRunInfo() {
+ stream << "\n" << getLineOfChars<'~'>() << "\n";
+ Colour colour( Colour::SecondaryText );
+ stream << currentTestRunInfo->name
+ << " is a Catch v" << libraryVersion << " host application.\n"
+ << "Run with -? for options\n\n";
+
+ if( m_config->rngSeed() != 0 )
+ stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n";
+
+ currentTestRunInfo.used = true;
+ }
+ void lazyPrintGroupInfo() {
+ if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) {
+ printClosedHeader( "Group: " + currentGroupInfo->name );
+ currentGroupInfo.used = true;
+ }
+ }
+ void printTestCaseAndSectionHeader() {
+ assert( !m_sectionStack.empty() );
+ printOpenHeader( currentTestCaseInfo->name );
+
+ if( m_sectionStack.size() > 1 ) {
+ Colour colourGuard( Colour::Headers );
+
+ std::vector<SectionInfo>::const_iterator
+ it = m_sectionStack.begin()+1, // Skip first section (test case)
+ itEnd = m_sectionStack.end();
+ for( ; it != itEnd; ++it )
+ printHeaderString( it->name, 2 );
+ }
+
+ SourceLineInfo lineInfo = m_sectionStack.front().lineInfo;
+
+ if( !lineInfo.empty() ){
+ stream << getLineOfChars<'-'>() << "\n";
+ Colour colourGuard( Colour::FileName );
+ stream << lineInfo << "\n";
+ }
+ stream << getLineOfChars<'.'>() << "\n" << std::endl;
+ }
+
+ void printClosedHeader( std::string const& _name ) {
+ printOpenHeader( _name );
+ stream << getLineOfChars<'.'>() << "\n";
+ }
+ void printOpenHeader( std::string const& _name ) {
+ stream << getLineOfChars<'-'>() << "\n";
+ {
+ Colour colourGuard( Colour::Headers );
+ printHeaderString( _name );
+ }
+ }
+
+ // if string has a : in first line will set indent to follow it on
+ // subsequent lines
+ void printHeaderString( std::string const& _string, std::size_t indent = 0 ) {
+ std::size_t i = _string.find( ": " );
+ if( i != std::string::npos )
+ i+=2;
+ else
+ i = 0;
+ stream << Text( _string, TextAttributes()
+ .setIndent( indent+i)
+ .setInitialIndent( indent ) ) << "\n";
+ }
+
+ struct SummaryColumn {
+
+ SummaryColumn( std::string const& _label, Colour::Code _colour )
+ : label( _label ),
+ colour( _colour )
+ {}
+ SummaryColumn addRow( std::size_t count ) {
+ std::ostringstream oss;
+ oss << count;
+ std::string row = oss.str();
+ for( std::vector<std::string>::iterator it = rows.begin(); it != rows.end(); ++it ) {
+ while( it->size() < row.size() )
+ *it = " " + *it;
+ while( it->size() > row.size() )
+ row = " " + row;
+ }
+ rows.push_back( row );
+ return *this;
+ }
+
+ std::string label;
+ Colour::Code colour;
+ std::vector<std::string> rows;
+
+ };
+
+ void printTotals( Totals const& totals ) {
+ if( totals.testCases.total() == 0 ) {
+ stream << Colour( Colour::Warning ) << "No tests ran\n";
+ }
+ else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) {
+ stream << Colour( Colour::ResultSuccess ) << "All tests passed";
+ stream << " ("
+ << pluralise( totals.assertions.passed, "assertion" ) << " in "
+ << pluralise( totals.testCases.passed, "test case" ) << ")"
+ << "\n";
+ }
+ else {
+
+ std::vector<SummaryColumn> columns;
+ columns.push_back( SummaryColumn( "", Colour::None )
+ .addRow( totals.testCases.total() )
+ .addRow( totals.assertions.total() ) );
+ columns.push_back( SummaryColumn( "passed", Colour::Success )
+ .addRow( totals.testCases.passed )
+ .addRow( totals.assertions.passed ) );
+ columns.push_back( SummaryColumn( "failed", Colour::ResultError )
+ .addRow( totals.testCases.failed )
+ .addRow( totals.assertions.failed ) );
+ columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure )
+ .addRow( totals.testCases.failedButOk )
+ .addRow( totals.assertions.failedButOk ) );
+
+ printSummaryRow( "test cases", columns, 0 );
+ printSummaryRow( "assertions", columns, 1 );
+ }
+ }
+ void printSummaryRow( std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row ) {
+ for( std::vector<SummaryColumn>::const_iterator it = cols.begin(); it != cols.end(); ++it ) {
+ std::string value = it->rows[row];
+ if( it->label.empty() ) {
+ stream << label << ": ";
+ if( value != "0" )
+ stream << value;
+ else
+ stream << Colour( Colour::Warning ) << "- none -";
+ }
+ else if( value != "0" ) {
+ stream << Colour( Colour::LightGrey ) << " | ";
+ stream << Colour( it->colour )
+ << value << " " << it->label;
+ }
+ }
+ stream << "\n";
+ }
+
+ static std::size_t makeRatio( std::size_t number, std::size_t total ) {
+ std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0;
+ return ( ratio == 0 && number > 0 ) ? 1 : ratio;
+ }
+ static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) {
+ if( i > j && i > k )
+ return i;
+ else if( j > k )
+ return j;
+ else
+ return k;
+ }
+
+ void printTotalsDivider( Totals const& totals ) {
+ if( totals.testCases.total() > 0 ) {
+ std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() );
+ std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() );
+ std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() );
+ while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 )
+ findMax( failedRatio, failedButOkRatio, passedRatio )++;
+ while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 )
+ findMax( failedRatio, failedButOkRatio, passedRatio )--;
+
+ stream << Colour( Colour::Error ) << std::string( failedRatio, '=' );
+ stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' );
+ if( totals.testCases.allPassed() )
+ stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' );
+ else
+ stream << Colour( Colour::Success ) << std::string( passedRatio, '=' );
+ }
+ else {
+ stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' );
+ }
+ stream << "\n";
+ }
+ void printSummaryDivider() {
+ stream << getLineOfChars<'-'>() << "\n";
+ }
+
+ private:
+ bool m_headerPrinted;
+ };
+
+ INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter )
+
+} // end namespace Catch
+
+// #included from: ../reporters/catch_reporter_compact.hpp
+#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED
+
+namespace Catch {
+
+ struct CompactReporter : StreamingReporterBase {
+
+ CompactReporter( ReporterConfig const& _config )
+ : StreamingReporterBase( _config )
+ {}
+
+ virtual ~CompactReporter();
+
+ static std::string getDescription() {
+ return "Reports test results on a single line, suitable for IDEs";
+ }
+
+ virtual ReporterPreferences getPreferences() const {
+ ReporterPreferences prefs;
+ prefs.shouldRedirectStdOut = false;
+ return prefs;
+ }
+
+ virtual void noMatchingTestCases( std::string const& spec ) {
+ stream << "No test cases matched '" << spec << "'" << std::endl;
+ }
+
+ virtual void assertionStarting( AssertionInfo const& ) {
+ }
+
+ virtual bool assertionEnded( AssertionStats const& _assertionStats ) {
+ AssertionResult const& result = _assertionStats.assertionResult;
+
+ bool printInfoMessages = true;
+
+ // Drop out if result was successful and we're not printing those
+ if( !m_config->includeSuccessfulResults() && result.isOk() ) {
+ if( result.getResultType() != ResultWas::Warning )
+ return false;
+ printInfoMessages = false;
+ }
+
+ AssertionPrinter printer( stream, _assertionStats, printInfoMessages );
+ printer.print();
+
+ stream << std::endl;
+ return true;
+ }
+
+ virtual void testRunEnded( TestRunStats const& _testRunStats ) {
+ printTotals( _testRunStats.totals );
+ stream << "\n" << std::endl;
+ StreamingReporterBase::testRunEnded( _testRunStats );
+ }
+
+ private:
+ class AssertionPrinter {
+ void operator= ( AssertionPrinter const& );
+ public:
+ AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages )
+ : stream( _stream )
+ , stats( _stats )
+ , result( _stats.assertionResult )
+ , messages( _stats.infoMessages )
+ , itMessage( _stats.infoMessages.begin() )
+ , printInfoMessages( _printInfoMessages )
+ {}
+
+ void print() {
+ printSourceInfo();
+
+ itMessage = messages.begin();
+
+ switch( result.getResultType() ) {
+ case ResultWas::Ok:
+ printResultType( Colour::ResultSuccess, passedString() );
+ printOriginalExpression();
+ printReconstructedExpression();
+ if ( ! result.hasExpression() )
+ printRemainingMessages( Colour::None );
+ else
+ printRemainingMessages();
+ break;
+ case ResultWas::ExpressionFailed:
+ if( result.isOk() )
+ printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) );
+ else
+ printResultType( Colour::Error, failedString() );
+ printOriginalExpression();
+ printReconstructedExpression();
+ printRemainingMessages();
+ break;
+ case ResultWas::ThrewException:
+ printResultType( Colour::Error, failedString() );
+ printIssue( "unexpected exception with message:" );
+ printMessage();
+ printExpressionWas();
+ printRemainingMessages();
+ break;
+ case ResultWas::FatalErrorCondition:
+ printResultType( Colour::Error, failedString() );
+ printIssue( "fatal error condition with message:" );
+ printMessage();
+ printExpressionWas();
+ printRemainingMessages();
+ break;
+ case ResultWas::DidntThrowException:
+ printResultType( Colour::Error, failedString() );
+ printIssue( "expected exception, got none" );
+ printExpressionWas();
+ printRemainingMessages();
+ break;
+ case ResultWas::Info:
+ printResultType( Colour::None, "info" );
+ printMessage();
+ printRemainingMessages();
+ break;
+ case ResultWas::Warning:
+ printResultType( Colour::None, "warning" );
+ printMessage();
+ printRemainingMessages();
+ break;
+ case ResultWas::ExplicitFailure:
+ printResultType( Colour::Error, failedString() );
+ printIssue( "explicitly" );
+ printRemainingMessages( Colour::None );
+ break;
+ // These cases are here to prevent compiler warnings
+ case ResultWas::Unknown:
+ case ResultWas::FailureBit:
+ case ResultWas::Exception:
+ printResultType( Colour::Error, "** internal error **" );
+ break;
+ }
+ }
+
+ private:
+ // Colour::LightGrey
+
+ static Colour::Code dimColour() { return Colour::FileName; }
+
+#ifdef CATCH_PLATFORM_MAC
+ static const char* failedString() { return "FAILED"; }
+ static const char* passedString() { return "PASSED"; }
+#else
+ static const char* failedString() { return "failed"; }
+ static const char* passedString() { return "passed"; }
+#endif
+
+ void printSourceInfo() const {
+ Colour colourGuard( Colour::FileName );
+ stream << result.getSourceInfo() << ":";
+ }
+
+ void printResultType( Colour::Code colour, std::string passOrFail ) const {
+ if( !passOrFail.empty() ) {
+ {
+ Colour colourGuard( colour );
+ stream << " " << passOrFail;
+ }
+ stream << ":";
+ }
+ }
+
+ void printIssue( std::string issue ) const {
+ stream << " " << issue;
+ }
+
+ void printExpressionWas() {
+ if( result.hasExpression() ) {
+ stream << ";";
+ {
+ Colour colour( dimColour() );
+ stream << " expression was:";
+ }
+ printOriginalExpression();
+ }
+ }
+
+ void printOriginalExpression() const {
+ if( result.hasExpression() ) {
+ stream << " " << result.getExpression();
+ }
+ }
+
+ void printReconstructedExpression() const {
+ if( result.hasExpandedExpression() ) {
+ {
+ Colour colour( dimColour() );
+ stream << " for: ";
+ }
+ stream << result.getExpandedExpression();
+ }
+ }
+
+ void printMessage() {
+ if ( itMessage != messages.end() ) {
+ stream << " '" << itMessage->message << "'";
+ ++itMessage;
+ }
+ }
+
+ void printRemainingMessages( Colour::Code colour = dimColour() ) {
+ if ( itMessage == messages.end() )
+ return;
+
+ // using messages.end() directly yields compilation error:
+ std::vector<MessageInfo>::const_iterator itEnd = messages.end();
+ const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) );
+
+ {
+ Colour colourGuard( colour );
+ stream << " with " << pluralise( N, "message" ) << ":";
+ }
+
+ for(; itMessage != itEnd; ) {
+ // If this assertion is a warning ignore any INFO messages
+ if( printInfoMessages || itMessage->type != ResultWas::Info ) {
+ stream << " '" << itMessage->message << "'";
+ if ( ++itMessage != itEnd ) {
+ Colour colourGuard( dimColour() );
+ stream << " and";
+ }
+ }
+ }
+ }
+
+ private:
+ std::ostream& stream;
+ AssertionStats const& stats;
+ AssertionResult const& result;
+ std::vector<MessageInfo> messages;
+ std::vector<MessageInfo>::const_iterator itMessage;
+ bool printInfoMessages;
+ };
+
+ // Colour, message variants:
+ // - white: No tests ran.
+ // - red: Failed [both/all] N test cases, failed [both/all] M assertions.
+ // - white: Passed [both/all] N test cases (no assertions).
+ // - red: Failed N tests cases, failed M assertions.
+ // - green: Passed [both/all] N tests cases with M assertions.
+
+ std::string bothOrAll( std::size_t count ) const {
+ return count == 1 ? "" : count == 2 ? "both " : "all " ;
+ }
+
+ void printTotals( const Totals& totals ) const {
+ if( totals.testCases.total() == 0 ) {
+ stream << "No tests ran.";
+ }
+ else if( totals.testCases.failed == totals.testCases.total() ) {
+ Colour colour( Colour::ResultError );
+ const std::string qualify_assertions_failed =
+ totals.assertions.failed == totals.assertions.total() ?
+ bothOrAll( totals.assertions.failed ) : "";
+ stream <<
+ "Failed " << bothOrAll( totals.testCases.failed )
+ << pluralise( totals.testCases.failed, "test case" ) << ", "
+ "failed " << qualify_assertions_failed <<
+ pluralise( totals.assertions.failed, "assertion" ) << ".";
+ }
+ else if( totals.assertions.total() == 0 ) {
+ stream <<
+ "Passed " << bothOrAll( totals.testCases.total() )
+ << pluralise( totals.testCases.total(), "test case" )
+ << " (no assertions).";
+ }
+ else if( totals.assertions.failed ) {
+ Colour colour( Colour::ResultError );
+ stream <<
+ "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", "
+ "failed " << pluralise( totals.assertions.failed, "assertion" ) << ".";
+ }
+ else {
+ Colour colour( Colour::ResultSuccess );
+ stream <<
+ "Passed " << bothOrAll( totals.testCases.passed )
+ << pluralise( totals.testCases.passed, "test case" ) <<
+ " with " << pluralise( totals.assertions.passed, "assertion" ) << ".";
+ }
+ }
+ };
+
+ INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter )
+
+} // end namespace Catch
+
+namespace Catch {
+ // These are all here to avoid warnings about not having any out of line
+ // virtual methods
+ NonCopyable::~NonCopyable() {}
+ IShared::~IShared() {}
+ IStream::~IStream() CATCH_NOEXCEPT {}
+ FileStream::~FileStream() CATCH_NOEXCEPT {}
+ CoutStream::~CoutStream() CATCH_NOEXCEPT {}
+ DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {}
+ StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {}
+ IContext::~IContext() {}
+ IResultCapture::~IResultCapture() {}
+ ITestCase::~ITestCase() {}
+ ITestCaseRegistry::~ITestCaseRegistry() {}
+ IRegistryHub::~IRegistryHub() {}
+ IMutableRegistryHub::~IMutableRegistryHub() {}
+ IExceptionTranslator::~IExceptionTranslator() {}
+ IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {}
+ IReporter::~IReporter() {}
+ IReporterFactory::~IReporterFactory() {}
+ IReporterRegistry::~IReporterRegistry() {}
+ IStreamingReporter::~IStreamingReporter() {}
+ AssertionStats::~AssertionStats() {}
+ SectionStats::~SectionStats() {}
+ TestCaseStats::~TestCaseStats() {}
+ TestGroupStats::~TestGroupStats() {}
+ TestRunStats::~TestRunStats() {}
+ CumulativeReporterBase::SectionNode::~SectionNode() {}
+ CumulativeReporterBase::~CumulativeReporterBase() {}
+
+ StreamingReporterBase::~StreamingReporterBase() {}
+ ConsoleReporter::~ConsoleReporter() {}
+ CompactReporter::~CompactReporter() {}
+ IRunner::~IRunner() {}
+ IMutableContext::~IMutableContext() {}
+ IConfig::~IConfig() {}
+ XmlReporter::~XmlReporter() {}
+ JunitReporter::~JunitReporter() {}
+ TestRegistry::~TestRegistry() {}
+ FreeFunctionTestCase::~FreeFunctionTestCase() {}
+ IGeneratorInfo::~IGeneratorInfo() {}
+ IGeneratorsForTest::~IGeneratorsForTest() {}
+ WildcardPattern::~WildcardPattern() {}
+ TestSpec::Pattern::~Pattern() {}
+ TestSpec::NamePattern::~NamePattern() {}
+ TestSpec::TagPattern::~TagPattern() {}
+ TestSpec::ExcludedPattern::~ExcludedPattern() {}
+
+ Matchers::Impl::StdString::Equals::~Equals() {}
+ Matchers::Impl::StdString::Contains::~Contains() {}
+ Matchers::Impl::StdString::StartsWith::~StartsWith() {}
+ Matchers::Impl::StdString::EndsWith::~EndsWith() {}
+
+ void Config::dummy() {}
+
+ namespace TestCaseTracking {
+ ITracker::~ITracker() {}
+ TrackerBase::~TrackerBase() {}
+ SectionTracker::~SectionTracker() {}
+ IndexTracker::~IndexTracker() {}
+ }
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#endif
+
+#ifdef CATCH_CONFIG_MAIN
+// #included from: internal/catch_default_main.hpp
+#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED
+
+#ifndef __OBJC__
+
+// Standard C/C++ main entry point
+int main (int argc, char * argv[]) {
+ return Catch::Session().run( argc, argv );
+}
+
+#else // __OBJC__
+
+// Objective-C entry point
+int main (int argc, char * const argv[]) {
+#if !CATCH_ARC_ENABLED
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+#endif
+
+ Catch::registerTestMethods();
+ int result = Catch::Session().run( argc, (char* const*)argv );
+
+#if !CATCH_ARC_ENABLED
+ [pool drain];
+#endif
+
+ return result;
+}
+
+#endif // __OBJC__
+
+#endif
+
+#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED
+# undef CLARA_CONFIG_MAIN
+#endif
+
+//////
+
+// If this config identifier is defined then all CATCH macros are prefixed with CATCH_
+#ifdef CATCH_CONFIG_PREFIX_ALL
+
+#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" )
+#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" )
+
+#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "CATCH_REQUIRE_THROWS" )
+#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" )
+#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "CATCH_REQUIRE_THROWS_WITH" )
+#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" )
+
+#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" )
+#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" )
+#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" )
+#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" )
+#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" )
+
+#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" )
+#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" )
+#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CATCH_CHECK_THROWS_WITH" )
+#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" )
+
+#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" )
+#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" )
+
+#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" )
+#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg )
+#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" )
+#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" )
+#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" )
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+ #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+ #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+ #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+ #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
+ #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+ #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ )
+ #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ )
+#else
+ #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
+ #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
+ #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
+ #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description )
+ #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
+ #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg )
+ #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg )
+#endif
+#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
+
+#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType )
+#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType )
+
+#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr )
+
+// "BDD-style" convenience wrappers
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ )
+#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
+#else
+#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags )
+#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags )
+#endif
+#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" )
+#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" )
+#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" )
+#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" )
+#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" )
+
+// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required
+#else
+
+#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" )
+#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" )
+
+#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "REQUIRE_THROWS" )
+#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" )
+#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "REQUIRE_THROWS_WITH" )
+#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" )
+
+#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" )
+#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" )
+#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" )
+#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" )
+#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" )
+
+#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CHECK_THROWS" )
+#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" )
+#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CHECK_THROWS_WITH" )
+#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" )
+
+#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" )
+#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" )
+
+#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" )
+#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg )
+#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" )
+#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" )
+#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" )
+
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+ #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ )
+ #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )
+ #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )
+ #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ )
+ #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ )
+ #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ )
+ #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ )
+#else
+ #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description )
+ #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description )
+ #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description )
+ #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description )
+ #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description )
+ #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg )
+ #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg )
+#endif
+#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" )
+
+#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType )
+#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType )
+
+#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr )
+
+#endif
+
+#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature )
+
+// "BDD-style" convenience wrappers
+#ifdef CATCH_CONFIG_VARIADIC_MACROS
+#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ )
+#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
+#else
+#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags )
+#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags )
+#endif
+#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" )
+#define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" )
+#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" )
+#define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" )
+#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" )
+
+using Catch::Detail::Approx;
+
+#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
+
diff --git a/tests/config-msvc.py b/tests/config-msvc.py
new file mode 100644
index 0000000..a7771de
--- /dev/null
+++ b/tests/config-msvc.py
@@ -0,0 +1,52 @@
+exe = "tester.exe"
+
+toolchain = "msvc"
+
+# optional
+link_pool_depth = 1
+
+# optional
+builddir = {
+ "gnu" : "build"
+ , "msvc" : "build"
+ , "clang" : "build"
+ }
+
+includes = {
+ "gnu" : [ "-I." ]
+ , "msvc" : [ "/I." ]
+ , "clang" : [ "-I." ]
+ }
+
+defines = {
+ "gnu" : [ "-DEXAMPLE=1" ]
+ , "msvc" : [ "/DEXAMPLE=1" ]
+ , "clang" : [ "-DEXAMPLE=1" ]
+ }
+
+cflags = {
+ "gnu" : [ "-O2", "-g" ]
+ , "msvc" : [ "/O2" ]
+ , "clang" : [ "-O2", "-g" ]
+ }
+
+cxxflags = {
+ "gnu" : [ "-O2", "-g" ]
+ , "msvc" : [ "/O2", "/W4", "/EHsc"]
+ , "clang" : [ "-O2", "-g", "-fsanitize=address" ]
+ }
+
+ldflags = {
+ "gnu" : [ ]
+ , "msvc" : [ ]
+ , "clang" : [ "-fsanitize=address" ]
+ }
+
+# optionsl
+cxx_files = [ "tester.cc" ]
+c_files = [ ]
+
+# You can register your own toolchain through register_toolchain function
+def register_toolchain(ninja):
+ pass
+
diff --git a/tests/config-posix.py b/tests/config-posix.py
new file mode 100644
index 0000000..29cc4d5
--- /dev/null
+++ b/tests/config-posix.py
@@ -0,0 +1,53 @@
+exe = "tester"
+
+# "gnu" or "clang"
+toolchain = "gnu"
+
+# optional
+link_pool_depth = 1
+
+# optional
+builddir = {
+ "gnu" : "build"
+ , "msvc" : "build"
+ , "clang" : "build"
+ }
+
+includes = {
+ "gnu" : [ "-I." ]
+ , "msvc" : [ "/I." ]
+ , "clang" : [ "-I." ]
+ }
+
+defines = {
+ "gnu" : [ ]
+ , "msvc" : [ ]
+ , "clang" : [ ]
+ }
+
+cflags = {
+ "gnu" : [ "-O2", "-g" ]
+ , "msvc" : [ "/O2" ]
+ , "clang" : [ "-O2", "-g" ]
+ }
+
+# Warn as much as possible: http://qiita.com/MitsutakaTakeda/items/6b9966f890cc9b944d75
+cxxflags = {
+ "gnu" : [ "-O2", "-g", "-pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wmissing-declarations -Wmissing-include-dirs -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror -Wno-unused", "-fsanitize=address" ]
+ , "msvc" : [ "/O2", "/W4" ]
+ , "clang" : [ "-O2", "-g", "-Werror -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic", "-fsanitize=address" ]
+ }
+
+ldflags = {
+ "gnu" : [ "-fsanitize=address" ]
+ , "msvc" : [ ]
+ , "clang" : [ "-fsanitize=address" ]
+ }
+
+cxx_files = [ "tester.cc" ]
+c_files = [ ]
+
+# You can register your own toolchain through register_toolchain function
+def register_toolchain(ninja):
+ pass
+
diff --git a/tests/kuroga.py b/tests/kuroga.py
new file mode 100755
index 0000000..56d3f86
--- /dev/null
+++ b/tests/kuroga.py
@@ -0,0 +1,312 @@
+#!/usr/bin/env python
+
+#
+# Kuroga, single python file meta-build system for ninja
+# https://github.com/lighttransport/kuroga
+#
+# Requirements: python 2.6 or 2.7
+#
+# Usage: $ python kuroga.py input.py
+#
+
+import imp
+import re
+import textwrap
+import glob
+import os
+import sys
+
+# gcc preset
+def add_gnu_rule(ninja):
+ ninja.rule('gnucxx', description='CXX $out',
+ command='$gnucxx -MMD -MF $out.d $gnudefines $gnuincludes $gnucxxflags -c $in -o $out',
+ depfile='$out.d', deps='gcc')
+ ninja.rule('gnucc', description='CC $out',
+ command='$gnucc -MMD -MF $out.d $gnudefines $gnuincludes $gnucflags -c $in -o $out',
+ depfile='$out.d', deps='gcc')
+ ninja.rule('gnulink', description='LINK $out', pool='link_pool',
+ command='$gnuld -o $out $in $libs $gnuldflags')
+ ninja.rule('gnuar', description='AR $out', pool='link_pool',
+ command='$gnuar rsc $out $in')
+ ninja.rule('gnustamp', description='STAMP $out', command='touch $out')
+ ninja.newline()
+
+ ninja.variable('gnucxx', 'g++')
+ ninja.variable('gnucc', 'gcc')
+ ninja.variable('gnuld', '$gnucxx')
+ ninja.variable('gnuar', 'ar')
+ ninja.newline()
+
+# clang preset
+def add_clang_rule(ninja):
+ ninja.rule('clangcxx', description='CXX $out',
+ command='$clangcxx -MMD -MF $out.d $clangdefines $clangincludes $clangcxxflags -c $in -o $out',
+ depfile='$out.d', deps='gcc')
+ ninja.rule('clangcc', description='CC $out',
+ command='$clangcc -MMD -MF $out.d $clangdefines $clangincludes $clangcflags -c $in -o $out',
+ depfile='$out.d', deps='gcc')
+ ninja.rule('clanglink', description='LINK $out', pool='link_pool',
+ command='$clangld -o $out $in $libs $clangldflags')
+ ninja.rule('clangar', description='AR $out', pool='link_pool',
+ command='$clangar rsc $out $in')
+ ninja.rule('clangstamp', description='STAMP $out', command='touch $out')
+ ninja.newline()
+
+ ninja.variable('clangcxx', 'clang++')
+ ninja.variable('clangcc', 'clang')
+ ninja.variable('clangld', '$clangcxx')
+ ninja.variable('clangar', 'ar')
+ ninja.newline()
+
+# msvc preset
+def add_msvc_rule(ninja):
+ ninja.rule('msvccxx', description='CXX $out',
+ command='$msvccxx /TP /showIncludes $msvcdefines $msvcincludes $msvccxxflags -c $in /Fo$out',
+ depfile='$out.d', deps='msvc')
+ ninja.rule('msvccc', description='CC $out',
+ command='$msvccc /TC /showIncludes $msvcdefines $msvcincludes $msvccflags -c $in /Fo$out',
+ depfile='$out.d', deps='msvc')
+ ninja.rule('msvclink', description='LINK $out', pool='link_pool',
+ command='$msvcld $msvcldflags $in $libs /OUT:$out')
+ ninja.rule('msvcar', description='AR $out', pool='link_pool',
+ command='$msvcar $in /OUT:$out')
+ #ninja.rule('msvcstamp', description='STAMP $out', command='touch $out')
+ ninja.newline()
+
+ ninja.variable('msvccxx', 'cl.exe')
+ ninja.variable('msvccc', 'cl.exe')
+ ninja.variable('msvcld', 'link.exe')
+ ninja.variable('msvcar', 'lib.exe')
+ ninja.newline()
+
+# -- from ninja_syntax.py --
+def escape_path(word):
+ return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:')
+
+class Writer(object):
+ def __init__(self, output, width=78):
+ self.output = output
+ self.width = width
+
+ def newline(self):
+ self.output.write('\n')
+
+ def comment(self, text, has_path=False):
+ for line in textwrap.wrap(text, self.width - 2, break_long_words=False,
+ break_on_hyphens=False):
+ self.output.write('# ' + line + '\n')
+
+ def variable(self, key, value, indent=0):
+ if value is None:
+ return
+ if isinstance(value, list):
+ value = ' '.join(filter(None, value)) # Filter out empty strings.
+ self._line('%s = %s' % (key, value), indent)
+
+ def pool(self, name, depth):
+ self._line('pool %s' % name)
+ self.variable('depth', depth, indent=1)
+
+ def rule(self, name, command, description=None, depfile=None,
+ generator=False, pool=None, restat=False, rspfile=None,
+ rspfile_content=None, deps=None):
+ self._line('rule %s' % name)
+ self.variable('command', command, indent=1)
+ if description:
+ self.variable('description', description, indent=1)
+ if depfile:
+ self.variable('depfile', depfile, indent=1)
+ if generator:
+ self.variable('generator', '1', indent=1)
+ if pool:
+ self.variable('pool', pool, indent=1)
+ if restat:
+ self.variable('restat', '1', indent=1)
+ if rspfile:
+ self.variable('rspfile', rspfile, indent=1)
+ if rspfile_content:
+ self.variable('rspfile_content', rspfile_content, indent=1)
+ if deps:
+ self.variable('deps', deps, indent=1)
+
+ def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
+ variables=None):
+ outputs = as_list(outputs)
+ out_outputs = [escape_path(x) for x in outputs]
+ all_inputs = [escape_path(x) for x in as_list(inputs)]
+
+ if implicit:
+ implicit = [escape_path(x) for x in as_list(implicit)]
+ all_inputs.append('|')
+ all_inputs.extend(implicit)
+ if order_only:
+ order_only = [escape_path(x) for x in as_list(order_only)]
+ all_inputs.append('||')
+ all_inputs.extend(order_only)
+
+ self._line('build %s: %s' % (' '.join(out_outputs),
+ ' '.join([rule] + all_inputs)))
+
+ if variables:
+ if isinstance(variables, dict):
+ iterator = iter(variables.items())
+ else:
+ iterator = iter(variables)
+
+ for key, val in iterator:
+ self.variable(key, val, indent=1)
+
+ return outputs
+
+ def include(self, path):
+ self._line('include %s' % path)
+
+ def subninja(self, path):
+ self._line('subninja %s' % path)
+
+ def default(self, paths):
+ self._line('default %s' % ' '.join(as_list(paths)))
+
+ def _count_dollars_before_index(self, s, i):
+ """Returns the number of '$' characters right in front of s[i]."""
+ dollar_count = 0
+ dollar_index = i - 1
+ while dollar_index > 0 and s[dollar_index] == '$':
+ dollar_count += 1
+ dollar_index -= 1
+ return dollar_count
+
+ def _line(self, text, indent=0):
+ """Write 'text' word-wrapped at self.width characters."""
+ leading_space = ' ' * indent
+ while len(leading_space) + len(text) > self.width:
+ # The text is too wide; wrap if possible.
+
+ # Find the rightmost space that would obey our width constraint and
+ # that's not an escaped space.
+ available_space = self.width - len(leading_space) - len(' $')
+ space = available_space
+ while True:
+ space = text.rfind(' ', 0, space)
+ if (space < 0 or
+ self._count_dollars_before_index(text, space) % 2 == 0):
+ break
+
+ if space < 0:
+ # No such space; just use the first unescaped space we can find.
+ space = available_space - 1
+ while True:
+ space = text.find(' ', space + 1)
+ if (space < 0 or
+ self._count_dollars_before_index(text, space) % 2 == 0):
+ break
+ if space < 0:
+ # Give up on breaking.
+ break
+
+ self.output.write(leading_space + text[0:space] + ' $\n')
+ text = text[space+1:]
+
+ # Subsequent lines are continuations, so indent them.
+ leading_space = ' ' * (indent+2)
+
+ self.output.write(leading_space + text + '\n')
+
+ def close(self):
+ self.output.close()
+
+
+def as_list(input):
+ if input is None:
+ return []
+ if isinstance(input, list):
+ return input
+ return [input]
+
+# -- end from ninja_syntax.py --
+
+def gen(ninja, toolchain, config):
+
+ ninja.variable('ninja_required_version', '1.4')
+ ninja.newline()
+
+ if hasattr(config, "builddir"):
+ builddir = config.builddir[toolchain]
+ ninja.variable(toolchain + 'builddir', builddir)
+ else:
+ builddir = ''
+
+ ninja.variable(toolchain + 'defines', config.defines[toolchain] or [])
+ ninja.variable(toolchain + 'includes', config.includes[toolchain] or [])
+ ninja.variable(toolchain + 'cflags', config.cflags[toolchain] or [])
+ ninja.variable(toolchain + 'cxxflags', config.cxxflags[toolchain] or [])
+ ninja.variable(toolchain + 'ldflags', config.ldflags[toolchain] or [])
+ ninja.newline()
+
+ if hasattr(config, "link_pool_depth"):
+ ninja.pool('link_pool', depth=config.link_pool_depth)
+ else:
+ ninja.pool('link_pool', depth=4)
+ ninja.newline()
+
+ # Add default toolchain(gnu, clang and msvc)
+ add_gnu_rule(ninja)
+ add_clang_rule(ninja)
+ add_msvc_rule(ninja)
+
+ obj_files = []
+
+ cc = toolchain + 'cc'
+ cxx = toolchain + 'cxx'
+ link = toolchain + 'link'
+ ar = toolchain + 'ar'
+
+ if hasattr(config, "cxx_files"):
+ for src in config.cxx_files:
+ srcfile = src
+ obj = os.path.splitext(srcfile)[0] + '.o'
+ obj = os.path.join(builddir, obj);
+ obj_files.append(obj)
+ ninja.build(obj, cxx, srcfile)
+ ninja.newline()
+
+ if hasattr(config, "c_files"):
+ for src in config.c_files:
+ srcfile = src
+ obj = os.path.splitext(srcfile)[0] + '.o'
+ obj = os.path.join(builddir, obj);
+ obj_files.append(obj)
+ ninja.build(obj, cc, srcfile)
+ ninja.newline()
+
+ targetlist = []
+ if hasattr(config, "exe"):
+ ninja.build(config.exe, link, obj_files)
+ targetlist.append(config.exe)
+
+ if hasattr(config, "staticlib"):
+ ninja.build(config.staticlib, ar, obj_files)
+ targetlist.append(config.staticlib)
+
+ ninja.build('all', 'phony', targetlist)
+ ninja.newline()
+
+ ninja.default('all')
+
+def main():
+ if len(sys.argv) < 2:
+ print("Usage: python kuroga.py config.py")
+ sys.exit(1)
+
+ config = imp.load_source("config", sys.argv[1])
+
+ f = open('build.ninja', 'w')
+ ninja = Writer(f)
+
+ if hasattr(config, "register_toolchain"):
+ config.register_toolchain(ninja)
+
+ gen(ninja, config.toolchain, config)
+ f.close()
+
+main()
diff --git a/tests/tester.cc b/tests/tester.cc
new file mode 100644
index 0000000..cd972f8
--- /dev/null
+++ b/tests/tester.cc
@@ -0,0 +1,741 @@
+#define TINYOBJLOADER_IMPLEMENTATION
+#include "../tiny_obj_loader.h"
+
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
+#include "catch.hpp"
+
+#include <cstdio>
+#include <cstdlib>
+#include <cassert>
+#include <iostream>
+#include <sstream>
+#include <fstream>
+
+static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector<tinyobj::shape_t>& shapes, const std::vector<tinyobj::material_t>& materials, bool triangulate = true)
+{
+ std::cout << "# of vertices : " << (attrib.vertices.size() / 3) << std::endl;
+ std::cout << "# of normals : " << (attrib.normals.size() / 3) << std::endl;
+ std::cout << "# of texcoords : " << (attrib.texcoords.size() / 2) << std::endl;
+
+ std::cout << "# of shapes : " << shapes.size() << std::endl;
+ std::cout << "# of materials : " << materials.size() << std::endl;
+
+ for (size_t v = 0; v < attrib.vertices.size() / 3; v++) {
+ printf(" v[%ld] = (%f, %f, %f)\n", v,
+ static_cast<const double>(attrib.vertices[3*v+0]),
+ static_cast<const double>(attrib.vertices[3*v+1]),
+ static_cast<const double>(attrib.vertices[3*v+2]));
+ }
+
+ for (size_t v = 0; v < attrib.normals.size() / 3; v++) {
+ printf(" n[%ld] = (%f, %f, %f)\n", v,
+ static_cast<const double>(attrib.normals[3*v+0]),
+ static_cast<const double>(attrib.normals[3*v+1]),
+ static_cast<const double>(attrib.normals[3*v+2]));
+ }
+
+ for (size_t v = 0; v < attrib.texcoords.size() / 2; v++) {
+ printf(" uv[%ld] = (%f, %f)\n", v,
+ static_cast<const double>(attrib.texcoords[2*v+0]),
+ static_cast<const double>(attrib.texcoords[2*v+1]));
+ }
+
+ for (size_t i = 0; i < shapes.size(); i++) {
+ printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str());
+ printf("Size of shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size());
+
+ if (triangulate)
+ {
+ printf("Size of shape[%ld].material_ids: %ld\n", i, shapes[i].mesh.material_ids.size());
+ assert((shapes[i].mesh.indices.size() % 3) == 0);
+ for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) {
+ tinyobj::index_t i0 = shapes[i].mesh.indices[3*f+0];
+ tinyobj::index_t i1 = shapes[i].mesh.indices[3*f+1];
+ tinyobj::index_t i2 = shapes[i].mesh.indices[3*f+2];
+ printf(" idx[%ld] = %d/%d/%d, %d/%d/%d, %d/%d/%d. mat_id = %d\n", f,
+ i0.vertex_index, i0.normal_index, i0.texcoord_index,
+ i1.vertex_index, i1.normal_index, i1.texcoord_index,
+ i2.vertex_index, i2.normal_index, i2.texcoord_index,
+ shapes[i].mesh.material_ids[f]);
+ }
+ } else {
+ for (size_t f = 0; f < shapes[i].mesh.indices.size(); f++) {
+ tinyobj::index_t idx = shapes[i].mesh.indices[f];
+ printf(" idx[%ld] = %d/%d/%d\n", f, idx.vertex_index, idx.normal_index, idx.texcoord_index);
+ }
+
+ printf("Size of shape[%ld].material_ids: %ld\n", i, shapes[i].mesh.material_ids.size());
+ assert(shapes[i].mesh.material_ids.size() == shapes[i].mesh.num_face_vertices.size());
+ for (size_t m = 0; m < shapes[i].mesh.material_ids.size(); m++) {
+ printf(" material_id[%ld] = %d\n", m,
+ shapes[i].mesh.material_ids[m]);
+ }
+
+ }
+
+ printf("shape[%ld].num_faces: %ld\n", i, shapes[i].mesh.num_face_vertices.size());
+ for (size_t v = 0; v < shapes[i].mesh.num_face_vertices.size(); v++) {
+ printf(" num_vertices[%ld] = %ld\n", v,
+ static_cast<long>(shapes[i].mesh.num_face_vertices[v]));
+ }
+
+ //printf("shape[%ld].vertices: %ld\n", i, shapes[i].mesh.positions.size());
+ //assert((shapes[i].mesh.positions.size() % 3) == 0);
+ //for (size_t v = 0; v < shapes[i].mesh.positions.size() / 3; v++) {
+ // printf(" v[%ld] = (%f, %f, %f)\n", v,
+ // static_cast<const double>(shapes[i].mesh.positions[3*v+0]),
+ // static_cast<const double>(shapes[i].mesh.positions[3*v+1]),
+ // static_cast<const double>(shapes[i].mesh.positions[3*v+2]));
+ //}
+
+ printf("shape[%ld].num_tags: %ld\n", i, shapes[i].mesh.tags.size());
+ for (size_t t = 0; t < shapes[i].mesh.tags.size(); t++) {
+ printf(" tag[%ld] = %s ", t, shapes[i].mesh.tags[t].name.c_str());
+ printf(" ints: [");
+ for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j)
+ {
+ printf("%ld", static_cast<long>(shapes[i].mesh.tags[t].intValues[j]));
+ if (j < (shapes[i].mesh.tags[t].intValues.size()-1))
+ {
+ printf(", ");
+ }
+ }
+ printf("]");
+
+ printf(" floats: [");
+ for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j)
+ {
+ printf("%f", static_cast<const double>(shapes[i].mesh.tags[t].floatValues[j]));
+ if (j < (shapes[i].mesh.tags[t].floatValues.size()-1))
+ {
+ printf(", ");
+ }
+ }
+ printf("]");
+
+ printf(" strings: [");
+ for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j)
+ {
+ printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str());
+ if (j < (shapes[i].mesh.tags[t].stringValues.size()-1))
+ {
+ printf(", ");
+ }
+ }
+ printf("]");
+ printf("\n");
+ }
+ }
+
+ for (size_t i = 0; i < materials.size(); i++) {
+ printf("material[%ld].name = %s\n", i, materials[i].name.c_str());
+ printf(" material.Ka = (%f, %f ,%f)\n", static_cast<const double>(materials[i].ambient[0]), static_cast<const double>(materials[i].ambient[1]), static_cast<const double>(materials[i].ambient[2]));
+ printf(" material.Kd = (%f, %f ,%f)\n", static_cast<const double>(materials[i].diffuse[0]), static_cast<const double>(materials[i].diffuse[1]), static_cast<const double>(materials[i].diffuse[2]));
+ printf(" material.Ks = (%f, %f ,%f)\n", static_cast<const double>(materials[i].specular[0]), static_cast<const double>(materials[i].specular[1]), static_cast<const double>(materials[i].specular[2]));
+ printf(" material.Tr = (%f, %f ,%f)\n", static_cast<const double>(materials[i].transmittance[0]), static_cast<const double>(materials[i].transmittance[1]), static_cast<const double>(materials[i].transmittance[2]));
+ printf(" material.Ke = (%f, %f ,%f)\n", static_cast<const double>(materials[i].emission[0]), static_cast<const double>(materials[i].emission[1]), static_cast<const double>(materials[i].emission[2]));
+ printf(" material.Ns = %f\n", static_cast<const double>(materials[i].shininess));
+ printf(" material.Ni = %f\n", static_cast<const double>(materials[i].ior));
+ printf(" material.dissolve = %f\n", static_cast<const double>(materials[i].dissolve));
+ printf(" material.illum = %d\n", materials[i].illum);
+ printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str());
+ printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str());
+ printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str());
+ printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str());
+ printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str());
+ printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str());
+ printf(" material.disp = %s\n", materials[i].displacement_texname.c_str());
+ printf(" material.refl = %s\n", materials[i].reflection_texname.c_str());
+ std::map<std::string, std::string>::const_iterator it(materials[i].unknown_parameter.begin());
+ std::map<std::string, std::string>::const_iterator itEnd(materials[i].unknown_parameter.end());
+
+ for (; it != itEnd; it++) {
+ printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str());
+ }
+ printf("\n");
+ }
+}
+
+static bool
+TestLoadObj(
+ const char* filename,
+ const char* basepath = NULL,
+ bool triangulate = true)
+{
+ std::cout << "Loading " << filename << std::endl;
+
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, basepath, triangulate);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ if (!ret) {
+ printf("Failed to load/parse .obj.\n");
+ return false;
+ }
+
+ PrintInfo(attrib, shapes, materials, triangulate);
+
+ return true;
+}
+
+static bool
+TestLoadObjFromPreopenedFile(
+ const char* filename,
+ const char* basepath = NULL,
+ bool readMaterials = true,
+ bool triangulate = true)
+{
+ std::string fullFilename = std::string(basepath) + filename;
+ std::cout << "Loading " << fullFilename << std::endl;
+
+ std::ifstream fileStream(fullFilename.c_str());
+
+ if (!fileStream) {
+ std::cerr << "Could not find specified file: " << fullFilename << std::endl;
+ return false;
+ }
+
+ tinyobj::MaterialStreamReader materialStreamReader(fileStream);
+ tinyobj::MaterialStreamReader* materialReader = readMaterials
+ ? &materialStreamReader
+ : NULL;
+
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &fileStream, materialReader);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ if (!ret) {
+ printf("Failed to load/parse .obj.\n");
+ return false;
+ }
+
+ std::cout << "Loaded material count: " << materials.size() << "\n";
+
+ return true;
+}
+
+static bool
+TestStreamLoadObj()
+{
+ std::cout << "Stream Loading " << std::endl;
+
+ std::stringstream objStream;
+ objStream
+ << "mtllib cube.mtl\n"
+ "\n"
+ "v 0.000000 2.000000 2.000000\n"
+ "v 0.000000 0.000000 2.000000\n"
+ "v 2.000000 0.000000 2.000000\n"
+ "v 2.000000 2.000000 2.000000\n"
+ "v 0.000000 2.000000 0.000000\n"
+ "v 0.000000 0.000000 0.000000\n"
+ "v 2.000000 0.000000 0.000000\n"
+ "v 2.000000 2.000000 0.000000\n"
+ "# 8 vertices\n"
+ "\n"
+ "g front cube\n"
+ "usemtl white\n"
+ "f 1 2 3 4\n"
+ "g back cube\n"
+ "# expects white material\n"
+ "f 8 7 6 5\n"
+ "g right cube\n"
+ "usemtl red\n"
+ "f 4 3 7 8\n"
+ "g top cube\n"
+ "usemtl white\n"
+ "f 5 1 4 8\n"
+ "g left cube\n"
+ "usemtl green\n"
+ "f 5 6 2 1\n"
+ "g bottom cube\n"
+ "usemtl white\n"
+ "f 2 6 7 3\n"
+ "# 6 elements";
+
+std::string matStream(
+ "newmtl white\n"
+ "Ka 0 0 0\n"
+ "Kd 1 1 1\n"
+ "Ks 0 0 0\n"
+ "\n"
+ "newmtl red\n"
+ "Ka 0 0 0\n"
+ "Kd 1 0 0\n"
+ "Ks 0 0 0\n"
+ "\n"
+ "newmtl green\n"
+ "Ka 0 0 0\n"
+ "Kd 0 1 0\n"
+ "Ks 0 0 0\n"
+ "\n"
+ "newmtl blue\n"
+ "Ka 0 0 0\n"
+ "Kd 0 0 1\n"
+ "Ks 0 0 0\n"
+ "\n"
+ "newmtl light\n"
+ "Ka 20 20 20\n"
+ "Kd 1 1 1\n"
+ "Ks 0 0 0");
+
+ using namespace tinyobj;
+ class MaterialStringStreamReader:
+ public MaterialReader
+ {
+ public:
+ MaterialStringStreamReader(const std::string& matSStream): m_matSStream(matSStream) {}
+ virtual ~MaterialStringStreamReader() {}
+ virtual bool operator() (
+ const std::string& matId,
+ std::vector<material_t>* materials,
+ std::map<std::string, int>* matMap,
+ std::string* err)
+ {
+ (void)matId;
+ (void)err;
+ std::string warning;
+ LoadMtl(matMap, materials, &m_matSStream, &warning);
+ return true;
+ }
+
+ private:
+ std::stringstream m_matSStream;
+ };
+
+ MaterialStringStreamReader matSSReader(matStream);
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream, &matSSReader);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ if (!ret) {
+ return false;
+ }
+
+ PrintInfo(attrib, shapes, materials);
+
+ return true;
+}
+
+const char* gMtlBasePath = "../models/";
+
+TEST_CASE("cornell_box", "[Loader]") {
+
+ REQUIRE(true == TestLoadObj("../models/cornell_box.obj", gMtlBasePath));
+}
+
+TEST_CASE("catmark_torus_creases0", "[Loader]") {
+
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/catmark_torus_creases0.obj", gMtlBasePath, /*triangulate*/false);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ REQUIRE(true == ret);
+
+ REQUIRE(1 == shapes.size());
+ REQUIRE(8 == shapes[0].mesh.tags.size());
+}
+
+TEST_CASE("pbr", "[Loader]") {
+
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/pbr-mat-ext.obj", gMtlBasePath, /*triangulate*/false);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+ REQUIRE(true == ret);
+ REQUIRE(1 == materials.size());
+ REQUIRE(0.2 == Approx(materials[0].roughness));
+ REQUIRE(0.3 == Approx(materials[0].metallic));
+ REQUIRE(0.4 == Approx(materials[0].sheen));
+ REQUIRE(0.5 == Approx(materials[0].clearcoat_thickness));
+ REQUIRE(0.6 == Approx(materials[0].clearcoat_roughness));
+ REQUIRE(0.7 == Approx(materials[0].anisotropy));
+ REQUIRE(0.8 == Approx(materials[0].anisotropy_rotation));
+ REQUIRE(0 == materials[0].roughness_texname.compare("roughness.tex"));
+ REQUIRE(0 == materials[0].metallic_texname.compare("metallic.tex"));
+ REQUIRE(0 == materials[0].sheen_texname.compare("sheen.tex"));
+ REQUIRE(0 == materials[0].emissive_texname.compare("emissive.tex"));
+ REQUIRE(0 == materials[0].normal_texname.compare("normalmap.tex"));
+}
+
+TEST_CASE("stream_load", "[Stream]") {
+ REQUIRE(true == TestStreamLoadObj());
+}
+
+TEST_CASE("stream_load_from_file_skipping_materials", "[Stream]") {
+ REQUIRE(true == TestLoadObjFromPreopenedFile(
+ "../models/pbr-mat-ext.obj", gMtlBasePath, /*readMaterials*/false, /*triangulate*/false));
+}
+
+TEST_CASE("stream_load_from_file_with_materials", "[Stream]") {
+ REQUIRE(true == TestLoadObjFromPreopenedFile(
+ "../models/pbr-mat-ext.obj", gMtlBasePath, /*readMaterials*/true, /*triangulate*/false));
+}
+
+TEST_CASE("trailing_whitespace_in_mtl", "[Issue92]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-92.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+ REQUIRE(true == ret);
+ REQUIRE(1 == materials.size());
+ REQUIRE(0 == materials[0].diffuse_texname.compare("tmp.png"));
+}
+
+TEST_CASE("transmittance_filter", "[Issue95]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-95.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+ REQUIRE(true == ret);
+ REQUIRE(1 == materials.size());
+ REQUIRE(0.1 == Approx(materials[0].transmittance[0]));
+ REQUIRE(0.2 == Approx(materials[0].transmittance[1]));
+ REQUIRE(0.3 == Approx(materials[0].transmittance[2]));
+}
+
+TEST_CASE("transmittance_filter_Tf", "[Issue95-Tf]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-95-2.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+ REQUIRE(true == ret);
+ REQUIRE(1 == materials.size());
+ REQUIRE(0.1 == Approx(materials[0].transmittance[0]));
+ REQUIRE(0.2 == Approx(materials[0].transmittance[1]));
+ REQUIRE(0.3 == Approx(materials[0].transmittance[2]));
+}
+
+TEST_CASE("transmittance_filter_Kt", "[Issue95-Kt]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-95.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+ REQUIRE(true == ret);
+ REQUIRE(1 == materials.size());
+ REQUIRE(0.1 == Approx(materials[0].transmittance[0]));
+ REQUIRE(0.2 == Approx(materials[0].transmittance[1]));
+ REQUIRE(0.3 == Approx(materials[0].transmittance[2]));
+}
+
+TEST_CASE("usemtl_at_last_line", "[Issue104]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/usemtl-issue-104.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+ REQUIRE(true == ret);
+ REQUIRE(1 == shapes.size());
+}
+
+TEST_CASE("texture_opts", "[Issue85]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/texture-options-issue-85.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+ REQUIRE(true == ret);
+ REQUIRE(1 == shapes.size());
+ REQUIRE(3 == materials.size());
+ REQUIRE(0 == materials[0].name.compare("default"));
+ REQUIRE(0 == materials[1].name.compare("bm2"));
+ REQUIRE(0 == materials[2].name.compare("bm3"));
+ REQUIRE(true == materials[0].ambient_texopt.clamp);
+ REQUIRE(0.1 == Approx(materials[0].diffuse_texopt.origin_offset[0]));
+ REQUIRE(0.0 == Approx(materials[0].diffuse_texopt.origin_offset[1]));
+ REQUIRE(0.0 == Approx(materials[0].diffuse_texopt.origin_offset[2]));
+ REQUIRE(0.1 == Approx(materials[0].specular_texopt.scale[0]));
+ REQUIRE(0.2 == Approx(materials[0].specular_texopt.scale[1]));
+ REQUIRE(1.0 == Approx(materials[0].specular_texopt.scale[2]));
+ REQUIRE(0.1 == Approx(materials[0].specular_highlight_texopt.turbulence[0]));
+ REQUIRE(0.2 == Approx(materials[0].specular_highlight_texopt.turbulence[1]));
+ REQUIRE(0.3 == Approx(materials[0].specular_highlight_texopt.turbulence[2]));
+ REQUIRE(3.0 == Approx(materials[0].bump_texopt.bump_multiplier));
+
+ REQUIRE(0.1 == Approx(materials[1].specular_highlight_texopt.brightness));
+ REQUIRE(0.3 == Approx(materials[1].specular_highlight_texopt.contrast));
+ REQUIRE('r' == materials[1].bump_texopt.imfchan);
+
+ REQUIRE(tinyobj::TEXTURE_TYPE_SPHERE == materials[2].diffuse_texopt.type);
+ REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_TOP == materials[2].specular_texopt.type);
+ REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_BOTTOM == materials[2].specular_highlight_texopt.type);
+ REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_LEFT == materials[2].ambient_texopt.type);
+ REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_RIGHT == materials[2].alpha_texopt.type);
+ REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_FRONT == materials[2].bump_texopt.type);
+ REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_BACK == materials[2].displacement_texopt.type);
+}
+
+TEST_CASE("mtllib_multiple_filenames", "[Issue112]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/mtllib-multiple-files-issue-112.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+ REQUIRE(true == ret);
+ REQUIRE(1 == materials.size());
+}
+
+TEST_CASE("tr_and_d", "[Issue43]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/tr-and-d-issue-43.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+ REQUIRE(true == ret);
+ REQUIRE(2 == materials.size());
+
+ REQUIRE(0.75 == Approx(materials[0].dissolve));
+ REQUIRE(0.75 == Approx(materials[1].dissolve));
+}
+
+TEST_CASE("refl", "[refl]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/refl.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ PrintInfo(attrib, shapes, materials);
+
+ REQUIRE(true == ret);
+ REQUIRE(5 == materials.size());
+
+ REQUIRE(materials[0].reflection_texname.compare("reflection.tga") == 0);
+}
+
+TEST_CASE("map_Bump", "[bump]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/map-bump.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ PrintInfo(attrib, shapes, materials);
+
+ REQUIRE(true == ret);
+ REQUIRE(2 == materials.size());
+
+ REQUIRE(materials[0].bump_texname.compare("bump.jpg") == 0);
+}
+
+TEST_CASE("g_ignored", "[Issue138]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-138.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ PrintInfo(attrib, shapes, materials);
+
+ REQUIRE(true == ret);
+ REQUIRE(2 == shapes.size());
+ REQUIRE(2 == materials.size());
+
+}
+
+TEST_CASE("vertex-col-ext", "[Issue144]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/cube-vertexcol.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ //PrintInfo(attrib, shapes, materials);
+
+ REQUIRE(true == ret);
+ REQUIRE((8 * 3) == attrib.colors.size());
+
+ REQUIRE(0 == Approx(attrib.colors[3 * 0 + 0]));
+ REQUIRE(0 == Approx(attrib.colors[3 * 0 + 1]));
+ REQUIRE(0 == Approx(attrib.colors[3 * 0 + 2]));
+
+ REQUIRE(0 == Approx(attrib.colors[3 * 1 + 0]));
+ REQUIRE(0 == Approx(attrib.colors[3 * 1 + 1]));
+ REQUIRE(1 == Approx(attrib.colors[3 * 1 + 2]));
+
+ REQUIRE(1 == Approx(attrib.colors[3 * 4 + 0]));
+
+ REQUIRE(1 == Approx(attrib.colors[3 * 7 + 0]));
+ REQUIRE(1 == Approx(attrib.colors[3 * 7 + 1]));
+ REQUIRE(1 == Approx(attrib.colors[3 * 7 + 2]));
+}
+
+TEST_CASE("norm_texopts", "[norm]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/norm-texopt.obj", gMtlBasePath);
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+
+ REQUIRE(true == ret);
+ REQUIRE(1 == shapes.size());
+ REQUIRE(1 == materials.size());
+ REQUIRE(3.0 == Approx(materials[0].normal_texopt.bump_multiplier));
+
+}
+
+TEST_CASE("zero-face-idx-value", "[Issue140]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-140-zero-face-idx.obj", gMtlBasePath);
+
+
+ if (!err.empty()) {
+ std::cerr << err << std::endl;
+ }
+ REQUIRE(false == ret);
+ REQUIRE(!err.empty());
+
+}
+
+TEST_CASE("texture-name-whitespace", "[Issue145]") {
+ tinyobj::attrib_t attrib;
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> materials;
+
+ std::string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/texture-filename-with-whitespace.obj", gMtlBasePath);
+
+
+ if (!err.empty()) {
+ std::cerr << "[Issue145] " << err << std::endl;
+ }
+
+ REQUIRE(true == ret);
+ REQUIRE(err.empty());
+ REQUIRE(2 < materials.size());
+
+ REQUIRE(0 == materials[0].diffuse_texname.compare("texture 01.png"));
+ REQUIRE(0 == materials[1].bump_texname.compare("bump 01.png"));
+ REQUIRE(2 == Approx(materials[1].bump_texopt.bump_multiplier));
+
+}
+
+#if 0
+int
+main(
+ int argc,
+ char **argv)
+{
+ if (argc > 1) {
+ const char* basepath = NULL;
+ if (argc > 2) {
+ basepath = argv[2];
+ }
+ assert(true == TestLoadObj(argv[1], basepath));
+ } else {
+ //assert(true == TestLoadObj("cornell_box.obj"));
+ //assert(true == TestLoadObj("cube.obj"));
+ assert(true == TestStreamLoadObj());
+ assert(true == TestLoadObj("catmark_torus_creases0.obj", NULL, false));
+ }
+
+ return 0;
+}
+#endif
diff --git a/tests/vcbuild.bat b/tests/vcbuild.bat
new file mode 100644
index 0000000..e864673
--- /dev/null
+++ b/tests/vcbuild.bat
@@ -0,0 +1,4 @@
+chcp 437
+python kuroga.py config-msvc.py
+call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" x86_amd64
+ninja
diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc
new file mode 100644
index 0000000..e57d044
--- /dev/null
+++ b/tiny_obj_loader.cc
@@ -0,0 +1,2 @@
+#define TINYOBJLOADER_IMPLEMENTATION
+#include "tiny_obj_loader.h"
diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h
new file mode 100644
index 0000000..65a9d62
--- /dev/null
+++ b/tiny_obj_loader.h
@@ -0,0 +1,2105 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2012-2017 Syoyo Fujita and many contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+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.
+*/
+
+//
+// version 1.1.0 : Support parsing vertex color(#144)
+// version 1.0.8 : Fix parsing `g` tag just after `usemtl`(#138)
+// version 1.0.7 : Support multiple tex options(#126)
+// version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124)
+// version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43)
+// version 1.0.4 : Support multiple filenames for 'mtllib'(#112)
+// version 1.0.3 : Support parsing texture options(#85)
+// version 1.0.2 : Improve parsing speed by about a factor of 2 for large
+// files(#105)
+// version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104)
+// version 1.0.0 : Change data structure. Change license from BSD to MIT.
+//
+
+//
+// Use this in *one* .cc
+// #define TINYOBJLOADER_IMPLEMENTATION
+// #include "tiny_obj_loader.h"
+//
+
+#ifndef TINY_OBJ_LOADER_H_
+#define TINY_OBJ_LOADER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace tinyobj {
+
+// https://en.wikipedia.org/wiki/Wavefront_.obj_file says ...
+//
+// -blendu on | off # set horizontal texture blending
+// (default on)
+// -blendv on | off # set vertical texture blending
+// (default on)
+// -boost real_value # boost mip-map sharpness
+// -mm base_value gain_value # modify texture map values (default
+// 0 1)
+// # base_value = brightness,
+// gain_value = contrast
+// -o u [v [w]] # Origin offset (default
+// 0 0 0)
+// -s u [v [w]] # Scale (default
+// 1 1 1)
+// -t u [v [w]] # Turbulence (default
+// 0 0 0)
+// -texres resolution # texture resolution to create
+// -clamp on | off # only render texels in the clamped
+// 0-1 range (default off)
+// # When unclamped, textures are
+// repeated across a surface,
+// # when clamped, only texels which
+// fall within the 0-1
+// # range are rendered.
+// -bm mult_value # bump multiplier (for bump maps
+// only)
+//
+// -imfchan r | g | b | m | l | z # specifies which channel of the file
+// is used to
+// # create a scalar or bump texture.
+// r:red, g:green,
+// # b:blue, m:matte, l:luminance,
+// z:z-depth..
+// # (the default for bump is 'l' and
+// for decal is 'm')
+// bump -imfchan r bumpmap.tga # says to use the red channel of
+// bumpmap.tga as the bumpmap
+//
+// For reflection maps...
+//
+// -type sphere # specifies a sphere for a "refl"
+// reflection map
+// -type cube_top | cube_bottom | # when using a cube map, the texture
+// file for each
+// cube_front | cube_back | # side of the cube is specified
+// separately
+// cube_left | cube_right
+
+#ifdef TINYOBJLOADER_USE_DOUBLE
+//#pragma message "using double"
+typedef double real_t;
+#else
+//#pragma message "using float"
+typedef float real_t;
+#endif
+
+typedef enum {
+ TEXTURE_TYPE_NONE, // default
+ TEXTURE_TYPE_SPHERE,
+ TEXTURE_TYPE_CUBE_TOP,
+ TEXTURE_TYPE_CUBE_BOTTOM,
+ TEXTURE_TYPE_CUBE_FRONT,
+ TEXTURE_TYPE_CUBE_BACK,
+ TEXTURE_TYPE_CUBE_LEFT,
+ TEXTURE_TYPE_CUBE_RIGHT
+} texture_type_t;
+
+typedef struct {
+ texture_type_t type; // -type (default TEXTURE_TYPE_NONE)
+ real_t sharpness; // -boost (default 1.0?)
+ real_t brightness; // base_value in -mm option (default 0)
+ real_t contrast; // gain_value in -mm option (default 1)
+ real_t origin_offset[3]; // -o u [v [w]] (default 0 0 0)
+ real_t scale[3]; // -s u [v [w]] (default 1 1 1)
+ real_t turbulence[3]; // -t u [v [w]] (default 0 0 0)
+ // int texture_resolution; // -texres resolution (default = ?) TODO
+ bool clamp; // -clamp (default false)
+ char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm')
+ bool blendu; // -blendu (default on)
+ bool blendv; // -blendv (default on)
+ real_t bump_multiplier; // -bm (for bump maps only, default 1.0)
+} texture_option_t;
+
+typedef struct {
+ std::string name;
+
+ real_t ambient[3];
+ real_t diffuse[3];
+ real_t specular[3];
+ real_t transmittance[3];
+ real_t emission[3];
+ real_t shininess;
+ real_t ior; // index of refraction
+ real_t dissolve; // 1 == opaque; 0 == fully transparent
+ // illumination model (see http://www.fileformat.info/format/material/)
+ int illum;
+
+ int dummy; // Suppress padding warning.
+
+ std::string ambient_texname; // map_Ka
+ std::string diffuse_texname; // map_Kd
+ std::string specular_texname; // map_Ks
+ std::string specular_highlight_texname; // map_Ns
+ std::string bump_texname; // map_bump, map_Bump, bump
+ std::string displacement_texname; // disp
+ std::string alpha_texname; // map_d
+ std::string reflection_texname; // refl
+
+ texture_option_t ambient_texopt;
+ texture_option_t diffuse_texopt;
+ texture_option_t specular_texopt;
+ texture_option_t specular_highlight_texopt;
+ texture_option_t bump_texopt;
+ texture_option_t displacement_texopt;
+ texture_option_t alpha_texopt;
+ texture_option_t reflection_texopt;
+
+ // PBR extension
+ // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
+ real_t roughness; // [0, 1] default 0
+ real_t metallic; // [0, 1] default 0
+ real_t sheen; // [0, 1] default 0
+ real_t clearcoat_thickness; // [0, 1] default 0
+ real_t clearcoat_roughness; // [0, 1] default 0
+ real_t anisotropy; // aniso. [0, 1] default 0
+ real_t anisotropy_rotation; // anisor. [0, 1] default 0
+ real_t pad0;
+ std::string roughness_texname; // map_Pr
+ std::string metallic_texname; // map_Pm
+ std::string sheen_texname; // map_Ps
+ std::string emissive_texname; // map_Ke
+ std::string normal_texname; // norm. For normal mapping.
+
+ texture_option_t roughness_texopt;
+ texture_option_t metallic_texopt;
+ texture_option_t sheen_texopt;
+ texture_option_t emissive_texopt;
+ texture_option_t normal_texopt;
+
+ int pad2;
+
+ std::map<std::string, std::string> unknown_parameter;
+} material_t;
+
+typedef struct {
+ std::string name;
+
+ std::vector<int> intValues;
+ std::vector<real_t> floatValues;
+ std::vector<std::string> stringValues;
+} tag_t;
+
+// Index struct to support different indices for vtx/normal/texcoord.
+// -1 means not used.
+typedef struct {
+ int vertex_index;
+ int normal_index;
+ int texcoord_index;
+} index_t;
+
+typedef struct {
+ std::vector<index_t> indices;
+ std::vector<unsigned char> num_face_vertices; // The number of vertices per
+ // face. 3 = polygon, 4 = quad,
+ // ... Up to 255.
+ std::vector<int> material_ids; // per-face material ID
+ std::vector<tag_t> tags; // SubD tag
+} mesh_t;
+
+typedef struct {
+ std::string name;
+ mesh_t mesh;
+} shape_t;
+
+// Vertex attributes
+typedef struct {
+ std::vector<real_t> vertices; // 'v'
+ std::vector<real_t> normals; // 'vn'
+ std::vector<real_t> texcoords; // 'vt'
+ std::vector<real_t> colors; // extension: vertex colors
+} attrib_t;
+
+typedef struct callback_t_ {
+ // W is optional and set to 1 if there is no `w` item in `v` line
+ void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w);
+ void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z);
+
+ // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in
+ // `vt` line.
+ void (*texcoord_cb)(void *user_data, real_t x, real_t y, real_t z);
+
+ // called per 'f' line. num_indices is the number of face indices(e.g. 3 for
+ // triangle, 4 for quad)
+ // 0 will be passed for undefined index in index_t members.
+ void (*index_cb)(void *user_data, index_t *indices, int num_indices);
+ // `name` material name, `material_id` = the array index of material_t[]. -1
+ // if
+ // a material not found in .mtl
+ void (*usemtl_cb)(void *user_data, const char *name, int material_id);
+ // `materials` = parsed material data.
+ void (*mtllib_cb)(void *user_data, const material_t *materials,
+ int num_materials);
+ // There may be multiple group names
+ void (*group_cb)(void *user_data, const char **names, int num_names);
+ void (*object_cb)(void *user_data, const char *name);
+
+ callback_t_()
+ : vertex_cb(NULL),
+ normal_cb(NULL),
+ texcoord_cb(NULL),
+ index_cb(NULL),
+ usemtl_cb(NULL),
+ mtllib_cb(NULL),
+ group_cb(NULL),
+ object_cb(NULL) {}
+} callback_t;
+
+class MaterialReader {
+ public:
+ MaterialReader() {}
+ virtual ~MaterialReader();
+
+ virtual bool operator()(const std::string &matId,
+ std::vector<material_t> *materials,
+ std::map<std::string, int> *matMap,
+ std::string *err) = 0;
+};
+
+class MaterialFileReader : public MaterialReader {
+ public:
+ explicit MaterialFileReader(const std::string &mtl_basedir)
+ : m_mtlBaseDir(mtl_basedir) {}
+ virtual ~MaterialFileReader() {}
+ virtual bool operator()(const std::string &matId,
+ std::vector<material_t> *materials,
+ std::map<std::string, int> *matMap, std::string *err);
+
+ private:
+ std::string m_mtlBaseDir;
+};
+
+class MaterialStreamReader : public MaterialReader {
+ public:
+ explicit MaterialStreamReader(std::istream &inStream)
+ : m_inStream(inStream) {}
+ virtual ~MaterialStreamReader() {}
+ virtual bool operator()(const std::string &matId,
+ std::vector<material_t> *materials,
+ std::map<std::string, int> *matMap, std::string *err);
+
+ private:
+ std::istream &m_inStream;
+};
+
+/// Loads .obj from a file.
+/// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data
+/// 'shapes' will be filled with parsed shape data
+/// Returns true when loading .obj become success.
+/// Returns warning and error message into `err`
+/// 'mtl_basedir' is optional, and used for base directory for .mtl file.
+/// In default(`NULL'), .mtl file is searched from an application's working
+/// directory.
+/// 'triangulate' is optional, and used whether triangulate polygon face in .obj
+/// or not.
+bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, std::string *err,
+ const char *filename, const char *mtl_basedir = NULL,
+ bool triangulate = true);
+
+/// Loads .obj from a file with custom user callback.
+/// .mtl is loaded as usual and parsed material_t data will be passed to
+/// `callback.mtllib_cb`.
+/// Returns true when loading .obj/.mtl become success.
+/// Returns warning and error message into `err`
+/// See `examples/callback_api/` for how to use this function.
+bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
+ void *user_data = NULL,
+ MaterialReader *readMatFn = NULL,
+ std::string *err = NULL);
+
+/// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve
+/// std::istream for materials.
+/// Returns true when loading .obj become success.
+/// Returns warning and error message into `err`
+bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, std::string *err,
+ std::istream *inStream, MaterialReader *readMatFn = NULL,
+ bool triangulate = true);
+
+/// Loads materials into std::map
+void LoadMtl(std::map<std::string, int> *material_map,
+ std::vector<material_t> *materials, std::istream *inStream,
+ std::string *warning);
+
+} // namespace tinyobj
+
+#endif // TINY_OBJ_LOADER_H_
+
+#ifdef TINYOBJLOADER_IMPLEMENTATION
+#include <cassert>
+#include <cctype>
+#include <cmath>
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+#include <utility>
+
+#include <fstream>
+#include <sstream>
+
+namespace tinyobj {
+
+MaterialReader::~MaterialReader() {}
+
+struct vertex_index {
+ int v_idx, vt_idx, vn_idx;
+ vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {}
+ explicit vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {}
+ vertex_index(int vidx, int vtidx, int vnidx)
+ : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {}
+};
+
+struct tag_sizes {
+ tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {}
+ int num_ints;
+ int num_reals;
+ int num_strings;
+};
+
+struct obj_shape {
+ std::vector<real_t> v;
+ std::vector<real_t> vn;
+ std::vector<real_t> vt;
+};
+
+// See
+// http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf
+static std::istream &safeGetline(std::istream &is, std::string &t) {
+ t.clear();
+
+ // The characters in the stream are read one-by-one using a std::streambuf.
+ // That is faster than reading them one-by-one using the std::istream.
+ // Code that uses streambuf this way must be guarded by a sentry object.
+ // The sentry object performs various tasks,
+ // such as thread synchronization and updating the stream state.
+
+ std::istream::sentry se(is, true);
+ std::streambuf *sb = is.rdbuf();
+
+ if (se) {
+ for (;;) {
+ int c = sb->sbumpc();
+ switch (c) {
+ case '\n':
+ return is;
+ case '\r':
+ if (sb->sgetc() == '\n') sb->sbumpc();
+ return is;
+ case EOF:
+ // Also handle the case when the last line has no line ending
+ if (t.empty()) is.setstate(std::ios::eofbit);
+ return is;
+ default:
+ t += static_cast<char>(c);
+ }
+ }
+ }
+
+ return is;
+}
+
+#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t'))
+#define IS_DIGIT(x) \
+ (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10))
+#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0'))
+
+// Make index zero-base, and also support relative index.
+static inline bool fixIndex(int idx, int n, int *ret) {
+ if (!ret) {
+ return false;
+ }
+
+ if (idx > 0) {
+ (*ret) = idx - 1;
+ return true;
+ }
+
+ if (idx == 0) {
+ // zero is not allowed according to the spec.
+ return false;
+ }
+
+ if (idx < 0) {
+ (*ret) = n + idx; // negative value = relative
+ return true;
+ }
+
+ return false; // never reach here.
+}
+
+static inline std::string parseString(const char **token) {
+ std::string s;
+ (*token) += strspn((*token), " \t");
+ size_t e = strcspn((*token), " \t\r");
+ s = std::string((*token), &(*token)[e]);
+ (*token) += e;
+ return s;
+}
+
+static inline int parseInt(const char **token) {
+ (*token) += strspn((*token), " \t");
+ int i = atoi((*token));
+ (*token) += strcspn((*token), " \t\r");
+ return i;
+}
+
+// Tries to parse a floating point number located at s.
+//
+// s_end should be a location in the string where reading should absolutely
+// stop. For example at the end of the string, to prevent buffer overflows.
+//
+// Parses the following EBNF grammar:
+// sign = "+" | "-" ;
+// END = ? anything not in digit ?
+// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
+// integer = [sign] , digit , {digit} ;
+// decimal = integer , ["." , integer] ;
+// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ;
+//
+// Valid strings are for example:
+// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2
+//
+// If the parsing is a success, result is set to the parsed value and true
+// is returned.
+//
+// The function is greedy and will parse until any of the following happens:
+// - a non-conforming character is encountered.
+// - s_end is reached.
+//
+// The following situations triggers a failure:
+// - s >= s_end.
+// - parse failure.
+//
+static bool tryParseDouble(const char *s, const char *s_end, double *result) {
+ if (s >= s_end) {
+ return false;
+ }
+
+ double mantissa = 0.0;
+ // This exponent is base 2 rather than 10.
+ // However the exponent we parse is supposed to be one of ten,
+ // thus we must take care to convert the exponent/and or the
+ // mantissa to a * 2^E, where a is the mantissa and E is the
+ // exponent.
+ // To get the final double we will use ldexp, it requires the
+ // exponent to be in base 2.
+ int exponent = 0;
+
+ // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED
+ // TO JUMP OVER DEFINITIONS.
+ char sign = '+';
+ char exp_sign = '+';
+ char const *curr = s;
+
+ // How many characters were read in a loop.
+ int read = 0;
+ // Tells whether a loop terminated due to reaching s_end.
+ bool end_not_reached = false;
+
+ /*
+ BEGIN PARSING.
+ */
+
+ // Find out what sign we've got.
+ if (*curr == '+' || *curr == '-') {
+ sign = *curr;
+ curr++;
+ } else if (IS_DIGIT(*curr)) { /* Pass through. */
+ } else {
+ goto fail;
+ }
+
+ // Read the integer part.
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ mantissa *= 10;
+ mantissa += static_cast<int>(*curr - 0x30);
+ curr++;
+ read++;
+ end_not_reached = (curr != s_end);
+ }
+
+ // We must make sure we actually got something.
+ if (read == 0) goto fail;
+ // We allow numbers of form "#", "###" etc.
+ if (!end_not_reached) goto assemble;
+
+ // Read the decimal part.
+ if (*curr == '.') {
+ curr++;
+ read = 1;
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ static const double pow_lut[] = {
+ 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001,
+ };
+ const int lut_entries = sizeof pow_lut / sizeof pow_lut[0];
+
+ // NOTE: Don't use powf here, it will absolutely murder precision.
+ mantissa += static_cast<int>(*curr - 0x30) *
+ (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read));
+ read++;
+ curr++;
+ end_not_reached = (curr != s_end);
+ }
+ } else if (*curr == 'e' || *curr == 'E') {
+ } else {
+ goto assemble;
+ }
+
+ if (!end_not_reached) goto assemble;
+
+ // Read the exponent part.
+ if (*curr == 'e' || *curr == 'E') {
+ curr++;
+ // Figure out if a sign is present and if it is.
+ end_not_reached = (curr != s_end);
+ if (end_not_reached && (*curr == '+' || *curr == '-')) {
+ exp_sign = *curr;
+ curr++;
+ } else if (IS_DIGIT(*curr)) { /* Pass through. */
+ } else {
+ // Empty E is not allowed.
+ goto fail;
+ }
+
+ read = 0;
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ exponent *= 10;
+ exponent += static_cast<int>(*curr - 0x30);
+ curr++;
+ read++;
+ end_not_reached = (curr != s_end);
+ }
+ exponent *= (exp_sign == '+' ? 1 : -1);
+ if (read == 0) goto fail;
+ }
+
+assemble:
+ *result = (sign == '+' ? 1 : -1) *
+ (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent)
+ : mantissa);
+ return true;
+fail:
+ return false;
+}
+
+static inline real_t parseReal(const char **token, double default_value = 0.0) {
+ (*token) += strspn((*token), " \t");
+ const char *end = (*token) + strcspn((*token), " \t\r");
+ double val = default_value;
+ tryParseDouble((*token), end, &val);
+ real_t f = static_cast<real_t>(val);
+ (*token) = end;
+ return f;
+}
+
+static inline bool parseReal(const char **token, real_t *out) {
+ (*token) += strspn((*token), " \t");
+ const char *end = (*token) + strcspn((*token), " \t\r");
+ double val;
+ bool ret = tryParseDouble((*token), end, &val);
+ if (ret) {
+ real_t f = static_cast<real_t>(val);
+ (*out) = f;
+ }
+ (*token) = end;
+ return ret;
+}
+
+static inline void parseReal2(real_t *x, real_t *y, const char **token,
+ const double default_x = 0.0,
+ const double default_y = 0.0) {
+ (*x) = parseReal(token, default_x);
+ (*y) = parseReal(token, default_y);
+}
+
+static inline void parseReal3(real_t *x, real_t *y, real_t *z,
+ const char **token, const double default_x = 0.0,
+ const double default_y = 0.0,
+ const double default_z = 0.0) {
+ (*x) = parseReal(token, default_x);
+ (*y) = parseReal(token, default_y);
+ (*z) = parseReal(token, default_z);
+}
+
+static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w,
+ const char **token, const double default_x = 0.0,
+ const double default_y = 0.0,
+ const double default_z = 0.0,
+ const double default_w = 1.0) {
+ (*x) = parseReal(token, default_x);
+ (*y) = parseReal(token, default_y);
+ (*z) = parseReal(token, default_z);
+ (*w) = parseReal(token, default_w);
+}
+
+// Extension: parse vertex with colors(6 items)
+static inline bool parseVertexWithColor(real_t *x, real_t *y, real_t *z, real_t *r,
+ real_t *g, real_t *b,
+ const char **token, const double default_x = 0.0,
+ const double default_y = 0.0,
+ const double default_z = 0.0) {
+ (*x) = parseReal(token, default_x);
+ (*y) = parseReal(token, default_y);
+ (*z) = parseReal(token, default_z);
+
+ (*r) = parseReal(token, 1.0);
+ (*g) = parseReal(token, 1.0);
+ (*b) = parseReal(token, 1.0);
+
+ return true;
+}
+
+static inline bool parseOnOff(const char **token, bool default_value = true) {
+ (*token) += strspn((*token), " \t");
+ const char *end = (*token) + strcspn((*token), " \t\r");
+
+ bool ret = default_value;
+ if ((0 == strncmp((*token), "on", 2))) {
+ ret = true;
+ } else if ((0 == strncmp((*token), "off", 3))) {
+ ret = false;
+ }
+
+ (*token) = end;
+ return ret;
+}
+
+static inline texture_type_t parseTextureType(
+ const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) {
+ (*token) += strspn((*token), " \t");
+ const char *end = (*token) + strcspn((*token), " \t\r");
+ texture_type_t ty = default_value;
+
+ if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) {
+ ty = TEXTURE_TYPE_CUBE_TOP;
+ } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) {
+ ty = TEXTURE_TYPE_CUBE_BOTTOM;
+ } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) {
+ ty = TEXTURE_TYPE_CUBE_LEFT;
+ } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) {
+ ty = TEXTURE_TYPE_CUBE_RIGHT;
+ } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) {
+ ty = TEXTURE_TYPE_CUBE_FRONT;
+ } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) {
+ ty = TEXTURE_TYPE_CUBE_BACK;
+ } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) {
+ ty = TEXTURE_TYPE_SPHERE;
+ }
+
+ (*token) = end;
+ return ty;
+}
+
+static tag_sizes parseTagTriple(const char **token) {
+ tag_sizes ts;
+
+ (*token) += strspn((*token), " \t");
+ ts.num_ints = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ return ts;
+ }
+
+ (*token)++; // Skip '/'
+
+ (*token) += strspn((*token), " \t");
+ ts.num_reals = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ return ts;
+ }
+ (*token)++; // Skip '/'
+
+ ts.num_strings = parseInt(token);
+
+ return ts;
+}
+
+// Parse triples with index offsets: i, i/j/k, i//k, i/j
+static bool parseTriple(const char **token, int vsize, int vnsize, int vtsize,
+ vertex_index *ret) {
+ if (!ret) {
+ return false;
+ }
+
+ vertex_index vi(-1);
+
+ if (!fixIndex(atoi((*token)), vsize, &(vi.v_idx))) {
+ return false;
+ }
+
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ (*ret) = vi;
+ return true;
+ }
+ (*token)++;
+
+ // i//k
+ if ((*token)[0] == '/') {
+ (*token)++;
+ if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) {
+ return false;
+ }
+ (*token) += strcspn((*token), "/ \t\r");
+ (*ret) = vi;
+ return true;
+ }
+
+ // i/j/k or i/j
+ if (!fixIndex(atoi((*token)), vtsize, &(vi.vt_idx))) {
+ return false;
+ }
+
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ (*ret) = vi;
+ return true;
+ }
+
+ // i/j/k
+ (*token)++; // skip '/'
+ if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) {
+ return false;
+ }
+ (*token) += strcspn((*token), "/ \t\r");
+
+ (*ret) = vi;
+
+ return true;
+}
+
+// Parse raw triples: i, i/j/k, i//k, i/j
+static vertex_index parseRawTriple(const char **token) {
+ vertex_index vi(static_cast<int>(0)); // 0 is an invalid index in OBJ
+
+ vi.v_idx = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ return vi;
+ }
+ (*token)++;
+
+ // i//k
+ if ((*token)[0] == '/') {
+ (*token)++;
+ vi.vn_idx = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ return vi;
+ }
+
+ // i/j/k or i/j
+ vi.vt_idx = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ if ((*token)[0] != '/') {
+ return vi;
+ }
+
+ // i/j/k
+ (*token)++; // skip '/'
+ vi.vn_idx = atoi((*token));
+ (*token) += strcspn((*token), "/ \t\r");
+ return vi;
+}
+
+static bool ParseTextureNameAndOption(std::string *texname,
+ texture_option_t *texopt,
+ const char *linebuf, const bool is_bump) {
+ // @todo { write more robust lexer and parser. }
+ bool found_texname = false;
+ std::string texture_name;
+
+ // Fill with default value for texopt.
+ if (is_bump) {
+ texopt->imfchan = 'l';
+ } else {
+ texopt->imfchan = 'm';
+ }
+ texopt->bump_multiplier = 1.0f;
+ texopt->clamp = false;
+ texopt->blendu = true;
+ texopt->blendv = true;
+ texopt->sharpness = 1.0f;
+ texopt->brightness = 0.0f;
+ texopt->contrast = 1.0f;
+ texopt->origin_offset[0] = 0.0f;
+ texopt->origin_offset[1] = 0.0f;
+ texopt->origin_offset[2] = 0.0f;
+ texopt->scale[0] = 1.0f;
+ texopt->scale[1] = 1.0f;
+ texopt->scale[2] = 1.0f;
+ texopt->turbulence[0] = 0.0f;
+ texopt->turbulence[1] = 0.0f;
+ texopt->turbulence[2] = 0.0f;
+ texopt->type = TEXTURE_TYPE_NONE;
+
+ const char *token = linebuf; // Assume line ends with NULL
+
+ while (!IS_NEW_LINE((*token))) {
+ token += strspn(token, " \t"); // skip space
+ if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) {
+ token += 8;
+ texopt->blendu = parseOnOff(&token, /* default */ true);
+ } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) {
+ token += 8;
+ texopt->blendv = parseOnOff(&token, /* default */ true);
+ } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) {
+ token += 7;
+ texopt->clamp = parseOnOff(&token, /* default */ true);
+ } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) {
+ token += 7;
+ texopt->sharpness = parseReal(&token, 1.0);
+ } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) {
+ token += 4;
+ texopt->bump_multiplier = parseReal(&token, 1.0);
+ } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) {
+ token += 3;
+ parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]),
+ &(texopt->origin_offset[2]), &token);
+ } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) {
+ token += 3;
+ parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]),
+ &token, 1.0, 1.0, 1.0);
+ } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) {
+ token += 3;
+ parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]),
+ &(texopt->turbulence[2]), &token);
+ } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) {
+ token += 5;
+ texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE);
+ } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) {
+ token += 9;
+ token += strspn(token, " \t");
+ const char *end = token + strcspn(token, " \t\r");
+ if ((end - token) == 1) { // Assume one char for -imfchan
+ texopt->imfchan = (*token);
+ }
+ token = end;
+ } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) {
+ token += 4;
+ parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0);
+ } else {
+ // Assume texture filename
+#if 0
+ size_t len = strcspn(token, " \t\r"); // untile next space
+ texture_name = std::string(token, token + len);
+ token += len;
+
+ token += strspn(token, " \t"); // skip space
+#else
+ // Read filename until line end to parse filename containing whitespace
+ // TODO(syoyo): Support parsing texture option flag after the filename.
+ texture_name = std::string(token);
+ token += texture_name.length();
+#endif
+
+ found_texname = true;
+ }
+ }
+
+ if (found_texname) {
+ (*texname) = texture_name;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static void InitMaterial(material_t *material) {
+ material->name = "";
+ material->ambient_texname = "";
+ material->diffuse_texname = "";
+ material->specular_texname = "";
+ material->specular_highlight_texname = "";
+ material->bump_texname = "";
+ material->displacement_texname = "";
+ material->reflection_texname = "";
+ material->alpha_texname = "";
+ for (int i = 0; i < 3; i++) {
+ material->ambient[i] = 0.f;
+ material->diffuse[i] = 0.f;
+ material->specular[i] = 0.f;
+ material->transmittance[i] = 0.f;
+ material->emission[i] = 0.f;
+ }
+ material->illum = 0;
+ material->dissolve = 1.f;
+ material->shininess = 1.f;
+ material->ior = 1.f;
+
+ material->roughness = 0.f;
+ material->metallic = 0.f;
+ material->sheen = 0.f;
+ material->clearcoat_thickness = 0.f;
+ material->clearcoat_roughness = 0.f;
+ material->anisotropy_rotation = 0.f;
+ material->anisotropy = 0.f;
+ material->roughness_texname = "";
+ material->metallic_texname = "";
+ material->sheen_texname = "";
+ material->emissive_texname = "";
+ material->normal_texname = "";
+
+ material->unknown_parameter.clear();
+}
+
+static bool exportFaceGroupToShape(
+ shape_t *shape, const std::vector<std::vector<vertex_index> > &faceGroup,
+ const std::vector<tag_t> &tags, const int material_id,
+ const std::string &name, bool triangulate) {
+ if (faceGroup.empty()) {
+ return false;
+ }
+
+ // Flatten vertices and indices
+ for (size_t i = 0; i < faceGroup.size(); i++) {
+ const std::vector<vertex_index> &face = faceGroup[i];
+
+ vertex_index i0 = face[0];
+ vertex_index i1(-1);
+ vertex_index i2 = face[1];
+
+ size_t npolys = face.size();
+
+ if (triangulate) {
+ // Polygon -> triangle fan conversion
+ for (size_t k = 2; k < npolys; k++) {
+ i1 = i2;
+ i2 = face[k];
+
+ index_t idx0, idx1, idx2;
+ idx0.vertex_index = i0.v_idx;
+ idx0.normal_index = i0.vn_idx;
+ idx0.texcoord_index = i0.vt_idx;
+ idx1.vertex_index = i1.v_idx;
+ idx1.normal_index = i1.vn_idx;
+ idx1.texcoord_index = i1.vt_idx;
+ idx2.vertex_index = i2.v_idx;
+ idx2.normal_index = i2.vn_idx;
+ idx2.texcoord_index = i2.vt_idx;
+
+ shape->mesh.indices.push_back(idx0);
+ shape->mesh.indices.push_back(idx1);
+ shape->mesh.indices.push_back(idx2);
+
+ shape->mesh.num_face_vertices.push_back(3);
+ shape->mesh.material_ids.push_back(material_id);
+ }
+ } else {
+ for (size_t k = 0; k < npolys; k++) {
+ index_t idx;
+ idx.vertex_index = face[k].v_idx;
+ idx.normal_index = face[k].vn_idx;
+ idx.texcoord_index = face[k].vt_idx;
+ shape->mesh.indices.push_back(idx);
+ }
+
+ shape->mesh.num_face_vertices.push_back(
+ static_cast<unsigned char>(npolys));
+ shape->mesh.material_ids.push_back(material_id); // per face
+ }
+ }
+
+ shape->name = name;
+ shape->mesh.tags = tags;
+
+ return true;
+}
+
+// Split a string with specified delimiter character.
+// http://stackoverflow.com/questions/236129/split-a-string-in-c
+static void SplitString(const std::string &s, char delim,
+ std::vector<std::string> &elems) {
+ std::stringstream ss;
+ ss.str(s);
+ std::string item;
+ while (std::getline(ss, item, delim)) {
+ elems.push_back(item);
+ }
+}
+
+void LoadMtl(std::map<std::string, int> *material_map,
+ std::vector<material_t> *materials, std::istream *inStream,
+ std::string *warning) {
+ // Create a default material anyway.
+ material_t material;
+ InitMaterial(&material);
+
+ // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification.
+ bool has_d = false;
+ bool has_tr = false;
+
+ std::stringstream ss;
+
+ std::string linebuf;
+ while (inStream->peek() != -1) {
+ safeGetline(*inStream, linebuf);
+
+ // Trim trailing whitespace.
+ if (linebuf.size() > 0) {
+ linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1);
+ }
+
+ // Trim newline '\r\n' or '\n'
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\n')
+ linebuf.erase(linebuf.size() - 1);
+ }
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\r')
+ linebuf.erase(linebuf.size() - 1);
+ }
+
+ // Skip if empty line.
+ if (linebuf.empty()) {
+ continue;
+ }
+
+ // Skip leading space.
+ const char *token = linebuf.c_str();
+ token += strspn(token, " \t");
+
+ assert(token);
+ if (token[0] == '\0') continue; // empty line
+
+ if (token[0] == '#') continue; // comment line
+
+ // new mtl
+ if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) {
+ // flush previous material.
+ if (!material.name.empty()) {
+ material_map->insert(std::pair<std::string, int>(
+ material.name, static_cast<int>(materials->size())));
+ materials->push_back(material);
+ }
+
+ // initial temporary material
+ InitMaterial(&material);
+
+ has_d = false;
+ has_tr = false;
+
+ // set new mtl name
+ token += 7;
+ {
+ std::stringstream sstr;
+ sstr << token;
+ material.name = sstr.str();
+ }
+ continue;
+ }
+
+ // ambient
+ if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) {
+ token += 2;
+ real_t r, g, b;
+ parseReal3(&r, &g, &b, &token);
+ material.ambient[0] = r;
+ material.ambient[1] = g;
+ material.ambient[2] = b;
+ continue;
+ }
+
+ // diffuse
+ if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) {
+ token += 2;
+ real_t r, g, b;
+ parseReal3(&r, &g, &b, &token);
+ material.diffuse[0] = r;
+ material.diffuse[1] = g;
+ material.diffuse[2] = b;
+ continue;
+ }
+
+ // specular
+ if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) {
+ token += 2;
+ real_t r, g, b;
+ parseReal3(&r, &g, &b, &token);
+ material.specular[0] = r;
+ material.specular[1] = g;
+ material.specular[2] = b;
+ continue;
+ }
+
+ // transmittance
+ if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) ||
+ (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) {
+ token += 2;
+ real_t r, g, b;
+ parseReal3(&r, &g, &b, &token);
+ material.transmittance[0] = r;
+ material.transmittance[1] = g;
+ material.transmittance[2] = b;
+ continue;
+ }
+
+ // ior(index of refraction)
+ if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) {
+ token += 2;
+ material.ior = parseReal(&token);
+ continue;
+ }
+
+ // emission
+ if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) {
+ token += 2;
+ real_t r, g, b;
+ parseReal3(&r, &g, &b, &token);
+ material.emission[0] = r;
+ material.emission[1] = g;
+ material.emission[2] = b;
+ continue;
+ }
+
+ // shininess
+ if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) {
+ token += 2;
+ material.shininess = parseReal(&token);
+ continue;
+ }
+
+ // illum model
+ if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) {
+ token += 6;
+ material.illum = parseInt(&token);
+ continue;
+ }
+
+ // dissolve
+ if ((token[0] == 'd' && IS_SPACE(token[1]))) {
+ token += 1;
+ material.dissolve = parseReal(&token);
+
+ if (has_tr) {
+ ss << "WARN: Both `d` and `Tr` parameters defined for \""
+ << material.name << "\". Use the value of `d` for dissolve."
+ << std::endl;
+ }
+ has_d = true;
+ continue;
+ }
+ if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) {
+ token += 2;
+ if (has_d) {
+ // `d` wins. Ignore `Tr` value.
+ ss << "WARN: Both `d` and `Tr` parameters defined for \""
+ << material.name << "\". Use the value of `d` for dissolve."
+ << std::endl;
+ } else {
+ // We invert value of Tr(assume Tr is in range [0, 1])
+ // NOTE: Interpretation of Tr is application(exporter) dependent. For
+ // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43)
+ material.dissolve = 1.0f - parseReal(&token);
+ }
+ has_tr = true;
+ continue;
+ }
+
+ // PBR: roughness
+ if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) {
+ token += 2;
+ material.roughness = parseReal(&token);
+ continue;
+ }
+
+ // PBR: metallic
+ if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) {
+ token += 2;
+ material.metallic = parseReal(&token);
+ continue;
+ }
+
+ // PBR: sheen
+ if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) {
+ token += 2;
+ material.sheen = parseReal(&token);
+ continue;
+ }
+
+ // PBR: clearcoat thickness
+ if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) {
+ token += 2;
+ material.clearcoat_thickness = parseReal(&token);
+ continue;
+ }
+
+ // PBR: clearcoat roughness
+ if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) {
+ token += 4;
+ material.clearcoat_roughness = parseReal(&token);
+ continue;
+ }
+
+ // PBR: anisotropy
+ if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) {
+ token += 6;
+ material.anisotropy = parseReal(&token);
+ continue;
+ }
+
+ // PBR: anisotropy rotation
+ if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.anisotropy_rotation = parseReal(&token);
+ continue;
+ }
+
+ // ambient texture
+ if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.ambient_texname),
+ &(material.ambient_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // diffuse texture
+ if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.diffuse_texname),
+ &(material.diffuse_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // specular texture
+ if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.specular_texname),
+ &(material.specular_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // specular highlight texture
+ if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.specular_highlight_texname),
+ &(material.specular_highlight_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // bump texture
+ if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) {
+ token += 9;
+ ParseTextureNameAndOption(&(material.bump_texname),
+ &(material.bump_texopt), token,
+ /* is_bump */ true);
+ continue;
+ }
+
+ // bump texture
+ if ((0 == strncmp(token, "map_Bump", 8)) && IS_SPACE(token[8])) {
+ token += 9;
+ ParseTextureNameAndOption(&(material.bump_texname),
+ &(material.bump_texopt), token,
+ /* is_bump */ true);
+ continue;
+ }
+
+ // bump texture
+ if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ ParseTextureNameAndOption(&(material.bump_texname),
+ &(material.bump_texopt), token,
+ /* is_bump */ true);
+ continue;
+ }
+
+ // alpha texture
+ if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) {
+ token += 6;
+ material.alpha_texname = token;
+ ParseTextureNameAndOption(&(material.alpha_texname),
+ &(material.alpha_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // displacement texture
+ if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ ParseTextureNameAndOption(&(material.displacement_texname),
+ &(material.displacement_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // reflection map
+ if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ ParseTextureNameAndOption(&(material.reflection_texname),
+ &(material.reflection_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // PBR: roughness texture
+ if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.roughness_texname),
+ &(material.roughness_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // PBR: metallic texture
+ if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.metallic_texname),
+ &(material.metallic_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // PBR: sheen texture
+ if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.sheen_texname),
+ &(material.sheen_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // PBR: emissive texture
+ if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ ParseTextureNameAndOption(&(material.emissive_texname),
+ &(material.emissive_texopt), token,
+ /* is_bump */ false);
+ continue;
+ }
+
+ // PBR: normal map texture
+ if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ ParseTextureNameAndOption(
+ &(material.normal_texname), &(material.normal_texopt), token,
+ /* is_bump */ false); // @fixme { is_bump will be true? }
+ continue;
+ }
+
+ // unknown parameter
+ const char *_space = strchr(token, ' ');
+ if (!_space) {
+ _space = strchr(token, '\t');
+ }
+ if (_space) {
+ std::ptrdiff_t len = _space - token;
+ std::string key(token, static_cast<size_t>(len));
+ std::string value = _space + 1;
+ material.unknown_parameter.insert(
+ std::pair<std::string, std::string>(key, value));
+ }
+ }
+ // flush last material.
+ material_map->insert(std::pair<std::string, int>(
+ material.name, static_cast<int>(materials->size())));
+ materials->push_back(material);
+
+ if (warning) {
+ (*warning) = ss.str();
+ }
+}
+
+bool MaterialFileReader::operator()(const std::string &matId,
+ std::vector<material_t> *materials,
+ std::map<std::string, int> *matMap,
+ std::string *err) {
+ std::string filepath;
+
+ if (!m_mtlBaseDir.empty()) {
+ filepath = std::string(m_mtlBaseDir) + matId;
+ } else {
+ filepath = matId;
+ }
+
+ std::ifstream matIStream(filepath.c_str());
+ if (!matIStream) {
+ std::stringstream ss;
+ ss << "WARN: Material file [ " << filepath << " ] not found." << std::endl;
+ if (err) {
+ (*err) += ss.str();
+ }
+ return false;
+ }
+
+ std::string warning;
+ LoadMtl(matMap, materials, &matIStream, &warning);
+
+ if (!warning.empty()) {
+ if (err) {
+ (*err) += warning;
+ }
+ }
+
+ return true;
+}
+
+bool MaterialStreamReader::operator()(const std::string &matId,
+ std::vector<material_t> *materials,
+ std::map<std::string, int> *matMap,
+ std::string *err) {
+ (void)matId;
+ if (!m_inStream) {
+ std::stringstream ss;
+ ss << "WARN: Material stream in error state. " << std::endl;
+ if (err) {
+ (*err) += ss.str();
+ }
+ return false;
+ }
+
+ std::string warning;
+ LoadMtl(matMap, materials, &m_inStream, &warning);
+
+ if (!warning.empty()) {
+ if (err) {
+ (*err) += warning;
+ }
+ }
+
+ return true;
+}
+
+bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, std::string *err,
+ const char *filename, const char *mtl_basedir, bool trianglulate) {
+ attrib->vertices.clear();
+ attrib->normals.clear();
+ attrib->texcoords.clear();
+ attrib->colors.clear();
+ shapes->clear();
+
+ std::stringstream errss;
+
+ std::ifstream ifs(filename);
+ if (!ifs) {
+ errss << "Cannot open file [" << filename << "]" << std::endl;
+ if (err) {
+ (*err) = errss.str();
+ }
+ return false;
+ }
+
+ std::string baseDir;
+ if (mtl_basedir) {
+ baseDir = mtl_basedir;
+ }
+ MaterialFileReader matFileReader(baseDir);
+
+ return LoadObj(attrib, shapes, materials, err, &ifs, &matFileReader,
+ trianglulate);
+}
+
+bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, std::string *err,
+ std::istream *inStream, MaterialReader *readMatFn /*= NULL*/,
+ bool triangulate) {
+ std::stringstream errss;
+
+ std::vector<real_t> v;
+ std::vector<real_t> vn;
+ std::vector<real_t> vt;
+ std::vector<real_t> vc;
+ std::vector<tag_t> tags;
+ std::vector<std::vector<vertex_index> > faceGroup;
+ std::string name;
+
+ // material
+ std::map<std::string, int> material_map;
+ int material = -1;
+
+ shape_t shape;
+
+ std::string linebuf;
+ while (inStream->peek() != -1) {
+ safeGetline(*inStream, linebuf);
+
+ // Trim newline '\r\n' or '\n'
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\n')
+ linebuf.erase(linebuf.size() - 1);
+ }
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\r')
+ linebuf.erase(linebuf.size() - 1);
+ }
+
+ // Skip if empty line.
+ if (linebuf.empty()) {
+ continue;
+ }
+
+ // Skip leading space.
+ const char *token = linebuf.c_str();
+ token += strspn(token, " \t");
+
+ assert(token);
+ if (token[0] == '\0') continue; // empty line
+
+ if (token[0] == '#') continue; // comment line
+
+ // vertex
+ if (token[0] == 'v' && IS_SPACE((token[1]))) {
+ token += 2;
+ real_t x, y, z;
+ real_t r, g, b;
+ parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token);
+ v.push_back(x);
+ v.push_back(y);
+ v.push_back(z);
+
+ vc.push_back(r);
+ vc.push_back(g);
+ vc.push_back(b);
+ continue;
+ }
+
+ // normal
+ if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
+ token += 3;
+ real_t x, y, z;
+ parseReal3(&x, &y, &z, &token);
+ vn.push_back(x);
+ vn.push_back(y);
+ vn.push_back(z);
+ continue;
+ }
+
+ // texcoord
+ if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
+ token += 3;
+ real_t x, y;
+ parseReal2(&x, &y, &token);
+ vt.push_back(x);
+ vt.push_back(y);
+ continue;
+ }
+
+ // face
+ if (token[0] == 'f' && IS_SPACE((token[1]))) {
+ token += 2;
+ token += strspn(token, " \t");
+
+ std::vector<vertex_index> face;
+ face.reserve(3);
+
+ while (!IS_NEW_LINE(token[0])) {
+ vertex_index vi;
+ if (!parseTriple(&token, static_cast<int>(v.size() / 3),
+ static_cast<int>(vn.size() / 3),
+ static_cast<int>(vt.size() / 2), &vi)) {
+ if (err) {
+ (*err) = "Failed parse `f' line(e.g. zero value for face index).\n";
+ }
+ return false;
+ }
+
+ face.push_back(vi);
+ size_t n = strspn(token, " \t\r");
+ token += n;
+ }
+
+ // replace with emplace_back + std::move on C++11
+ faceGroup.push_back(std::vector<vertex_index>());
+ faceGroup[faceGroup.size() - 1].swap(face);
+
+ continue;
+ }
+
+ // use mtl
+ if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) {
+ token += 7;
+ std::stringstream ss;
+ ss << token;
+ std::string namebuf = ss.str();
+
+ int newMaterialId = -1;
+ if (material_map.find(namebuf) != material_map.end()) {
+ newMaterialId = material_map[namebuf];
+ } else {
+ // { error!! material not found }
+ }
+
+ if (newMaterialId != material) {
+ // Create per-face material. Thus we don't add `shape` to `shapes` at
+ // this time.
+ // just clear `faceGroup` after `exportFaceGroupToShape()` call.
+ exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
+ triangulate);
+ faceGroup.clear();
+ material = newMaterialId;
+ }
+
+ continue;
+ }
+
+ // load mtl
+ if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
+ if (readMatFn) {
+ token += 7;
+
+ std::vector<std::string> filenames;
+ SplitString(std::string(token), ' ', filenames);
+
+ if (filenames.empty()) {
+ if (err) {
+ (*err) +=
+ "WARN: Looks like empty filename for mtllib. Use default "
+ "material. \n";
+ }
+ } else {
+ bool found = false;
+ for (size_t s = 0; s < filenames.size(); s++) {
+ std::string err_mtl;
+ bool ok = (*readMatFn)(filenames[s].c_str(), materials,
+ &material_map, &err_mtl);
+ if (err && (!err_mtl.empty())) {
+ (*err) += err_mtl; // This should be warn message.
+ }
+
+ if (ok) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (err) {
+ (*err) +=
+ "WARN: Failed to load material file(s). Use default "
+ "material.\n";
+ }
+ }
+ }
+ }
+
+ continue;
+ }
+
+ // group name
+ if (token[0] == 'g' && IS_SPACE((token[1]))) {
+ // flush previous face group.
+ bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
+ triangulate);
+ (void)ret; // return value not used.
+
+ if (shape.mesh.indices.size() > 0) {
+ shapes->push_back(shape);
+ }
+
+ shape = shape_t();
+
+ // material = -1;
+ faceGroup.clear();
+
+ std::vector<std::string> names;
+ names.reserve(2);
+
+ while (!IS_NEW_LINE(token[0])) {
+ std::string str = parseString(&token);
+ names.push_back(str);
+ token += strspn(token, " \t\r"); // skip tag
+ }
+
+ assert(names.size() > 0);
+
+ // names[0] must be 'g', so skip the 0th element.
+ if (names.size() > 1) {
+ name = names[1];
+ } else {
+ name = "";
+ }
+
+ continue;
+ }
+
+ // object name
+ if (token[0] == 'o' && IS_SPACE((token[1]))) {
+ // flush previous face group.
+ bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
+ triangulate);
+ if (ret) {
+ shapes->push_back(shape);
+ }
+
+ // material = -1;
+ faceGroup.clear();
+ shape = shape_t();
+
+ // @todo { multiple object name? }
+ token += 2;
+ std::stringstream ss;
+ ss << token;
+ name = ss.str();
+
+ continue;
+ }
+
+ if (token[0] == 't' && IS_SPACE(token[1])) {
+ tag_t tag;
+
+ token += 2;
+
+ tag.name = parseString(&token);
+
+ tag_sizes ts = parseTagTriple(&token);
+
+ tag.intValues.resize(static_cast<size_t>(ts.num_ints));
+
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) {
+ tag.intValues[i] = parseInt(&token);
+ }
+
+ tag.floatValues.resize(static_cast<size_t>(ts.num_reals));
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) {
+ tag.floatValues[i] = parseReal(&token);
+ }
+
+ tag.stringValues.resize(static_cast<size_t>(ts.num_strings));
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) {
+ tag.stringValues[i] = parseString(&token);
+ }
+
+ tags.push_back(tag);
+ }
+
+ // Ignore unknown command.
+ }
+
+ bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
+ triangulate);
+ // exportFaceGroupToShape return false when `usemtl` is called in the last
+ // line.
+ // we also add `shape` to `shapes` when `shape.mesh` has already some
+ // faces(indices)
+ if (ret || shape.mesh.indices.size()) {
+ shapes->push_back(shape);
+ }
+ faceGroup.clear(); // for safety
+
+ if (err) {
+ (*err) += errss.str();
+ }
+
+ attrib->vertices.swap(v);
+ attrib->normals.swap(vn);
+ attrib->texcoords.swap(vt);
+ attrib->colors.swap(vc);
+
+ return true;
+}
+
+bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
+ void *user_data /*= NULL*/,
+ MaterialReader *readMatFn /*= NULL*/,
+ std::string *err /*= NULL*/) {
+ std::stringstream errss;
+
+ // material
+ std::map<std::string, int> material_map;
+ int material_id = -1; // -1 = invalid
+
+ std::vector<index_t> indices;
+ std::vector<material_t> materials;
+ std::vector<std::string> names;
+ names.reserve(2);
+ std::string name;
+ std::vector<const char *> names_out;
+
+ std::string linebuf;
+ while (inStream.peek() != -1) {
+ safeGetline(inStream, linebuf);
+
+ // Trim newline '\r\n' or '\n'
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\n')
+ linebuf.erase(linebuf.size() - 1);
+ }
+ if (linebuf.size() > 0) {
+ if (linebuf[linebuf.size() - 1] == '\r')
+ linebuf.erase(linebuf.size() - 1);
+ }
+
+ // Skip if empty line.
+ if (linebuf.empty()) {
+ continue;
+ }
+
+ // Skip leading space.
+ const char *token = linebuf.c_str();
+ token += strspn(token, " \t");
+
+ assert(token);
+ if (token[0] == '\0') continue; // empty line
+
+ if (token[0] == '#') continue; // comment line
+
+ // vertex
+ if (token[0] == 'v' && IS_SPACE((token[1]))) {
+ token += 2;
+ // TODO(syoyo): Support parsing vertex color extension.
+ real_t x, y, z, w; // w is optional. default = 1.0
+ parseV(&x, &y, &z, &w, &token);
+ if (callback.vertex_cb) {
+ callback.vertex_cb(user_data, x, y, z, w);
+ }
+ continue;
+ }
+
+ // normal
+ if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
+ token += 3;
+ real_t x, y, z;
+ parseReal3(&x, &y, &z, &token);
+ if (callback.normal_cb) {
+ callback.normal_cb(user_data, x, y, z);
+ }
+ continue;
+ }
+
+ // texcoord
+ if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
+ token += 3;
+ real_t x, y, z; // y and z are optional. default = 0.0
+ parseReal3(&x, &y, &z, &token);
+ if (callback.texcoord_cb) {
+ callback.texcoord_cb(user_data, x, y, z);
+ }
+ continue;
+ }
+
+ // face
+ if (token[0] == 'f' && IS_SPACE((token[1]))) {
+ token += 2;
+ token += strspn(token, " \t");
+
+ indices.clear();
+ while (!IS_NEW_LINE(token[0])) {
+ vertex_index vi = parseRawTriple(&token);
+
+ index_t idx;
+ idx.vertex_index = vi.v_idx;
+ idx.normal_index = vi.vn_idx;
+ idx.texcoord_index = vi.vt_idx;
+
+ indices.push_back(idx);
+ size_t n = strspn(token, " \t\r");
+ token += n;
+ }
+
+ if (callback.index_cb && indices.size() > 0) {
+ callback.index_cb(user_data, &indices.at(0),
+ static_cast<int>(indices.size()));
+ }
+
+ continue;
+ }
+
+ // use mtl
+ if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) {
+ token += 7;
+ std::stringstream ss;
+ ss << token;
+ std::string namebuf = ss.str();
+
+ int newMaterialId = -1;
+ if (material_map.find(namebuf) != material_map.end()) {
+ newMaterialId = material_map[namebuf];
+ } else {
+ // { error!! material not found }
+ }
+
+ if (newMaterialId != material_id) {
+ material_id = newMaterialId;
+ }
+
+ if (callback.usemtl_cb) {
+ callback.usemtl_cb(user_data, namebuf.c_str(), material_id);
+ }
+
+ continue;
+ }
+
+ // load mtl
+ if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
+ if (readMatFn) {
+ token += 7;
+
+ std::vector<std::string> filenames;
+ SplitString(std::string(token), ' ', filenames);
+
+ if (filenames.empty()) {
+ if (err) {
+ (*err) +=
+ "WARN: Looks like empty filename for mtllib. Use default "
+ "material. \n";
+ }
+ } else {
+ bool found = false;
+ for (size_t s = 0; s < filenames.size(); s++) {
+ std::string err_mtl;
+ bool ok = (*readMatFn)(filenames[s].c_str(), &materials,
+ &material_map, &err_mtl);
+ if (err && (!err_mtl.empty())) {
+ (*err) += err_mtl; // This should be warn message.
+ }
+
+ if (ok) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (err) {
+ (*err) +=
+ "WARN: Failed to load material file(s). Use default "
+ "material.\n";
+ }
+ } else {
+ if (callback.mtllib_cb) {
+ callback.mtllib_cb(user_data, &materials.at(0),
+ static_cast<int>(materials.size()));
+ }
+ }
+ }
+ }
+
+ continue;
+ }
+
+ // group name
+ if (token[0] == 'g' && IS_SPACE((token[1]))) {
+ names.clear();
+
+ while (!IS_NEW_LINE(token[0])) {
+ std::string str = parseString(&token);
+ names.push_back(str);
+ token += strspn(token, " \t\r"); // skip tag
+ }
+
+ assert(names.size() > 0);
+
+ // names[0] must be 'g', so skip the 0th element.
+ if (names.size() > 1) {
+ name = names[1];
+ } else {
+ name.clear();
+ }
+
+ if (callback.group_cb) {
+ if (names.size() > 1) {
+ // create const char* array.
+ names_out.resize(names.size() - 1);
+ for (size_t j = 0; j < names_out.size(); j++) {
+ names_out[j] = names[j + 1].c_str();
+ }
+ callback.group_cb(user_data, &names_out.at(0),
+ static_cast<int>(names_out.size()));
+
+ } else {
+ callback.group_cb(user_data, NULL, 0);
+ }
+ }
+
+ continue;
+ }
+
+ // object name
+ if (token[0] == 'o' && IS_SPACE((token[1]))) {
+ // @todo { multiple object name? }
+ token += 2;
+
+ std::stringstream ss;
+ ss << token;
+ std::string object_name = ss.str();
+
+ if (callback.object_cb) {
+ callback.object_cb(user_data, object_name.c_str());
+ }
+
+ continue;
+ }
+
+#if 0 // @todo
+ if (token[0] == 't' && IS_SPACE(token[1])) {
+ tag_t tag;
+
+ token += 2;
+ std::stringstream ss;
+ ss << token;
+ tag.name = ss.str();
+
+ token += tag.name.size() + 1;
+
+ tag_sizes ts = parseTagTriple(&token);
+
+ tag.intValues.resize(static_cast<size_t>(ts.num_ints));
+
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_ints); ++i) {
+ tag.intValues[i] = atoi(token);
+ token += strcspn(token, "/ \t\r") + 1;
+ }
+
+ tag.floatValues.resize(static_cast<size_t>(ts.num_reals));
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_reals); ++i) {
+ tag.floatValues[i] = parseReal(&token);
+ token += strcspn(token, "/ \t\r") + 1;
+ }
+
+ tag.stringValues.resize(static_cast<size_t>(ts.num_strings));
+ for (size_t i = 0; i < static_cast<size_t>(ts.num_strings); ++i) {
+ std::stringstream ss;
+ ss << token;
+ tag.stringValues[i] = ss.str();
+ token += tag.stringValues[i].size() + 1;
+ }
+
+ tags.push_back(tag);
+ }
+#endif
+
+ // Ignore unknown command.
+ }
+
+ if (err) {
+ (*err) += errss.str();
+ }
+
+ return true;
+}
+} // namespace tinyobj
+
+#endif
diff --git a/tinyobjloader-config.cmake.in b/tinyobjloader-config.cmake.in
new file mode 100644
index 0000000..91f01b0
--- /dev/null
+++ b/tinyobjloader-config.cmake.in
@@ -0,0 +1,9 @@
+@PACKAGE_INIT@
+
+set(TINYOBJLOADER_VERSION "@TINYOBJLOADER_VERSION@")
+
+set_and_check(TINYOBJLOADER_INCLUDE_DIRS "@PACKAGE_TINYOBJLOADER_INCLUDE_DIR@")
+set_and_check(TINYOBJLOADER_LIBRARY_DIRS "@PACKAGE_TINYOBJLOADER_LIBRARY_DIR@")
+set(TINYOBJLOADER_LIBRARIES @LIBRARY_NAME@)
+
+include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake")
diff --git a/tinyobjloader.pc.in b/tinyobjloader.pc.in
new file mode 100644
index 0000000..048a287
--- /dev/null
+++ b/tinyobjloader.pc.in
@@ -0,0 +1,15 @@
+# Generated by CMake @CMAKE_VERSION@ for @PROJECT_NAME@. Any changes to this
+# file will be overwritten by the next CMake run. The input file was
+# tinyobjloader.pc.in.
+
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@TINYOBJLOADER_LIBRARY_DIR@
+includedir=${prefix}/@TINYOBJLOADER_INCLUDE_DIR@
+
+Name: @PROJECT_NAME@
+Description: Tiny but powerful single file wavefront obj loader
+URL: https://syoyo.github.io/tinyobjloader/
+Version: @TINYOBJLOADER_VERSION@
+Libs: -L${libdir} -l@LIBRARY_NAME@
+Cflags: -I${includedir}
diff --git a/tools/travis_postbuild.sh b/tools/travis_postbuild.sh
new file mode 100755
index 0000000..00c5d49
--- /dev/null
+++ b/tools/travis_postbuild.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+DATEVAL=`date +%Y-%m-%d`
+VERSIONVAL=master
+
+# Use tag as version
+if [ $TRAVIS_TAG ]; then
+ VERSIONVAL=$TRAVIS_TAG
+fi
+
+sed -e s%@DATE@%${DATEVAL}% .bintray.in > .bintray.tmp
+sed -e s%@VERSION@%${VERSIONVAL}% .bintray.tmp > .bintray.json
diff --git a/tools/windows/premake5.exe b/tools/windows/premake5.exe
new file mode 100644
index 0000000..51c05a8
--- /dev/null
+++ b/tools/windows/premake5.exe
Binary files differ
diff --git a/vcsetup.bat b/vcsetup.bat
new file mode 100644
index 0000000..921c1e9
--- /dev/null
+++ b/vcsetup.bat
@@ -0,0 +1 @@
+.\\tools\\windows\\premake5.exe vs2013
diff --git a/wercker.yml b/wercker.yml
new file mode 100644
index 0000000..1d1aa26
--- /dev/null
+++ b/wercker.yml
@@ -0,0 +1,10 @@
+box: rioki/gcc-cpp@0.0.1
+build:
+ steps:
+ # Execute a custom script step.
+ - script:
+ name: build
+ code: |
+ git clone https://github.com/syoyo/orebuildenv.git
+ cd tests
+ make check