diff --git a/CMakeLists.txt b/CMakeLists.txt
old mode 100644
new mode 100755
index 6228877f582b82e89bd1c73707460a7ce8224b97..973942842d8d9245a0e058f1dc3c8eece03579cb
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,16 +18,13 @@ set(PADDLE_SERVING_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
 set(PADDLE_SERVING_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
 SET(PADDLE_SERVING_INSTALL_DIR ${CMAKE_BINARY_DIR}/output)
 SET(CMAKE_INSTALL_RPATH "\$ORIGIN" "${CMAKE_INSTALL_RPATH}")
-
 include(system)
-
 project(paddle-serving CXX C)
 message(STATUS "CXX compiler: ${CMAKE_CXX_COMPILER}, version: "
         "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
 message(STATUS "C compiler: ${CMAKE_C_COMPILER}, version: "
         "${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}")
 
-
 find_package(Git REQUIRED)
 find_package(Threads REQUIRED)
 find_package(CUDA QUIET)
@@ -40,25 +37,41 @@ if(NOT CMAKE_BUILD_TYPE)
       "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel"
       FORCE)
 endif()
+SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
+SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
 
 set(THIRD_PARTY_PATH "${CMAKE_BINARY_DIR}/third_party" CACHE STRING
   "A path setting third party libraries download & build directories.")
 
 set(THIRD_PARTY_BUILD_TYPE Release)
 
-option(WITH_AVX	    "Compile Paddle Serving with AVX intrinsics"    OFF)
-option(WITH_MKL	    "Compile Paddle Serving with MKL support."      OFF)
-option(WITH_GPU	    "Compile Paddle Serving with NVIDIA GPU"        OFF)
-option(WITH_LITE    "Compile Paddle Serving with Paddle Lite Engine"    OFF)
-option(WITH_XPU	    "Compile Paddle Serving with Baidu Kunlun"        OFF)
-option(WITH_PYTHON  "Compile Paddle Serving with Python"		    ON)
-option(CLIENT  	    "Compile Paddle Serving Client"		    OFF)
-option(SERVER	    "Compile Paddle Serving Server"		    OFF)
-option(APP          "Compile Paddle Serving App package"	    OFF)
-option(WITH_ELASTIC_CTR "Compile ELASITC-CTR solution"              OFF)
-option(PACK         "Compile for whl"                               OFF)
-option(WITH_TRT     "Compile Paddle Serving with TRT"       OFF)
-option(PADDLE_ON_INFERENCE "Compile for encryption" ON)
+option(WITH_AVX	            "Compile Paddle Serving with AVX intrinsics"        OFF)
+option(WITH_MKL	            "Compile Paddle Serving with MKL support."          OFF)
+option(WITH_GPU	            "Compile Paddle Serving with NVIDIA GPU"            OFF)
+option(WITH_LITE            "Compile Paddle Serving with Paddle Lite Engine"    OFF)
+option(WITH_XPU	            "Compile Paddle Serving with Baidu Kunlun"          OFF)
+option(WITH_PYTHON          "Compile Paddle Serving with Python"                 ON)
+option(CLIENT  	            "Compile Paddle Serving Client"		                OFF)
+option(SERVER	            "Compile Paddle Serving Server"		                OFF)
+option(APP                  "Compile Paddle Serving App package"	            OFF)
+option(WITH_ELASTIC_CTR     "Compile ELASITC-CTR solution"                      OFF)
+option(PACK                 "Compile for whl"                                   OFF)
+option(WITH_TRT             "Compile Paddle Serving with TRT"                   OFF)
+option(PADDLE_ON_INFERENCE  "Compile for encryption"                             ON)
+option(WITH_OPENCV	    "Compile Paddle Serving with OPENCV"                    OFF)
+
+if (WITH_OPENCV)
+    SET(OPENCV_DIR "" CACHE PATH "Location of libraries")
+    if(NOT DEFINED OPENCV_DIR)
+        message(FATAL_ERROR "please set OPENCV_DIR with -DOPENCV_DIR=/path/opencv")
+    endif()
+    if (WIN32)
+    find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/build/ NO_DEFAULT_PATH)
+    else ()
+    find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/share/OpenCV NO_DEFAULT_PATH)
+    endif ()
+    include_directories(${OpenCV_INCLUDE_DIRS})
+endif()
 
 if (PADDLE_ON_INFERENCE)
     add_definitions(-DPADDLE_ON_INFERENCE)
diff --git a/README.md b/README.md
index 8184a3b0dfa7271c96b9b34fd7ed1a1900529a4c..4b63ce9c8aead852a76404f4268fc8131c060729 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ We consider deploying deep learning inference service online to be a user-facing
 
 - Any model trained by [PaddlePaddle](https://github.com/paddlepaddle/paddle) can be directly used or [Model Conversion Interface](./doc/SAVE.md) for online deployment of Paddle Serving.
 - Support [Multi-model Pipeline Deployment](./doc/PIPELINE_SERVING.md), and provide the requirements of the REST interface and RPC interface itself, [Pipeline example](./python/examples/pipeline).
-- Support the model zoos from the Paddle ecosystem, such as [PaddleDetection](./python/examples/detection), [PaddleOCR](./python/examples/ocr), [PaddleRec](https://github.com/PaddlePaddle/PaddleRec/tree/master/tools/recserving/movie_recommender).
+- Support the model zoos from the Paddle ecosystem, such as [PaddleDetection](./python/examples/detection), [PaddleOCR](./python/examples/ocr), [PaddleRec](https://github.com/PaddlePaddle/PaddleRec/tree/master/recserving/movie_recommender).
 - Provide a variety of pre-processing and post-processing to facilitate users in training, deployment and other stages of related code, bridging the gap between AI developers and application developers, please refer to
 [Serving Examples](./python/examples/).
 
diff --git a/README_CN.md b/README_CN.md
index d166d7c0ffb558ae309afb1fec572ad79ab5f679..4ee2c9863dfbc6f4531d0ec00ca92aacc19e769e 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -44,7 +44,7 @@ Paddle Serving 旨在帮助深度学习开发者轻易部署在线预测服务
 
 - 任何经过[PaddlePaddle](https://github.com/paddlepaddle/paddle)训练的模型,都可以经过直接保存或是[模型转换接口](./doc/SAVE_CN.md),用于Paddle Serving在线部署。
 - 支持[多模型串联服务部署](./doc/PIPELINE_SERVING_CN.md), 同时提供Rest接口和RPC接口以满足您的需求,[Pipeline示例](./python/examples/pipeline)。
-- 支持Paddle生态的各大模型库, 例如[PaddleDetection](./python/examples/detection),[PaddleOCR](./python/examples/ocr),[PaddleRec](https://github.com/PaddlePaddle/PaddleRec/tree/master/tools/recserving/movie_recommender)。
+- 支持Paddle生态的各大模型库, 例如[PaddleDetection](./python/examples/detection),[PaddleOCR](./python/examples/ocr),[PaddleRec](https://github.com/PaddlePaddle/PaddleRec/tree/master/recserving/movie_recommender)。
 - 提供丰富多彩的前后处理,方便用户在训练、部署等各阶段复用相关代码,弥合AI开发者和应用开发者之间的鸿沟,详情参考[模型示例](./python/examples/)。
 
 
diff --git a/cmake/external/zlib.cmake b/cmake/external/zlib.cmake
old mode 100644
new mode 100755
index 6c8d79c25e6a2655711fe4450e65600c9a584015..071033e57426d7232fe5e0fc79501e804283e50f
--- a/cmake/external/zlib.cmake
+++ b/cmake/external/zlib.cmake
@@ -26,7 +26,7 @@ ExternalProject_Add(
     extern_zlib
     ${EXTERNAL_PROJECT_LOG_ARGS}
     GIT_REPOSITORY  "https://github.com/madler/zlib.git"
-    GIT_TAG         "v1.2.8"
+    GIT_TAG         "v1.2.9"
     PREFIX          ${ZLIB_SOURCES_DIR}
     UPDATE_COMMAND  ""
     CMAKE_ARGS      -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
@@ -54,7 +54,10 @@ ELSE(WIN32)
   SET(ZLIB_LIBRARIES "${ZLIB_INSTALL_DIR}/lib/libz.a" CACHE FILEPATH "zlib library." FORCE)
 ENDIF(WIN32)
 
-ADD_LIBRARY(zlib STATIC IMPORTED GLOBAL)
+IF(NOT WITH_OPENCV)
+  ADD_LIBRARY(zlib STATIC IMPORTED GLOBAL)
+ENDIF()
+
 SET_PROPERTY(TARGET zlib PROPERTY IMPORTED_LOCATION ${ZLIB_LIBRARIES})
 ADD_DEPENDENCIES(zlib extern_zlib)
 
diff --git a/core/configure/proto/multi_lang_general_model_service.proto b/core/configure/proto/multi_lang_general_model_service.proto
old mode 100644
new mode 100755
index 18fbcf760647e1694e738c0832fe45f4f7d9934f..2a4764a041d7f817aba1d516427a241498d4c2e0
--- a/core/configure/proto/multi_lang_general_model_service.proto
+++ b/core/configure/proto/multi_lang_general_model_service.proto
@@ -59,7 +59,7 @@ message SimpleResponse { required int32 err_code = 1; }
 
 message GetClientConfigRequest {}
 
-message GetClientConfigResponse { required string client_config_str = 1; }
+message GetClientConfigResponse { repeated string client_config_str_list = 1; }
 
 service MultiLangGeneralModelService {
   rpc Inference(InferenceRequest) returns (InferenceResponse) {}
diff --git a/core/configure/proto/server_configure.proto b/core/configure/proto/server_configure.proto
old mode 100644
new mode 100755
index 62537d01d3f0cea52c8117561080720695c1e2c6..24fb62806476effdcf453cb7b4047122731106ea
--- a/core/configure/proto/server_configure.proto
+++ b/core/configure/proto/server_configure.proto
@@ -55,10 +55,10 @@ message ModelToolkitConf { repeated EngineDesc engines = 1; };
 
 // reource conf
 message ResourceConf {
-  required string model_toolkit_path = 1;
-  required string model_toolkit_file = 2;
-  optional string general_model_path = 3;
-  optional string general_model_file = 4;
+  repeated string model_toolkit_path = 1;
+  repeated string model_toolkit_file = 2;
+  repeated string general_model_path = 3;
+  repeated string general_model_file = 4;
   optional string cube_config_path = 5;
   optional string cube_config_file = 6;
   optional int32 cube_quant_bits = 7; // set 0 if no quant.
diff --git a/core/general-client/include/general_model.h b/core/general-client/include/general_model.h
old mode 100644
new mode 100755
index 3ee960069fd1eb8575d39fe4797038f9d4ef9f3b..b1c4f71f5602bed4eded49822d7afe7caac6e242
--- a/core/general-client/include/general_model.h
+++ b/core/general-client/include/general_model.h
@@ -207,7 +207,7 @@ class PredictorClient {
 
   void init_gflags(std::vector argv);
 
-  int init(const std::string& client_conf);
+  int init(const std::vector &client_conf);
 
   void set_predictor_conf(const std::string& conf_path,
                           const std::string& conf_file);
@@ -227,6 +227,10 @@ class PredictorClient {
       const std::vector& int_feed_name,
       const std::vector>& int_shape,
       const std::vector>& int_lod_slot_batch,
+      const std::vector>& string_feed_batch,
+      const std::vector& string_feed_name,
+      const std::vector>& string_shape,
+      const std::vector>& string_lod_slot_batch,
       const std::vector& fetch_name,
       PredictorRes& predict_res_batch,  // NOLINT
       const int& pid,
diff --git a/core/general-client/src/general_model.cpp b/core/general-client/src/general_model.cpp
old mode 100644
new mode 100755
index c2db765a082bf2e18aa7fe88c614a6bc8bb457c8..4ae4bf35af4e697c10c2ab277ef85a570f1c9b2c
--- a/core/general-client/src/general_model.cpp
+++ b/core/general-client/src/general_model.cpp
@@ -28,7 +28,7 @@ using baidu::paddle_serving::predictor::general_model::Response;
 using baidu::paddle_serving::predictor::general_model::Tensor;
 using baidu::paddle_serving::predictor::general_model::FeedInst;
 using baidu::paddle_serving::predictor::general_model::FetchInst;
-
+enum ProtoDataType { P_INT64, P_FLOAT32, P_INT32, P_STRING };
 std::once_flag gflags_init_flag;
 namespace py = pybind11;
 
@@ -56,12 +56,12 @@ void PredictorClient::init_gflags(std::vector argv) {
   });
 }
 
-int PredictorClient::init(const std::string &conf_file) {
+int PredictorClient::init(const std::vector &conf_file) {
   try {
     GeneralModelConfig model_config;
-    if (configure::read_proto_conf(conf_file.c_str(), &model_config) != 0) {
+    if (configure::read_proto_conf(conf_file[0].c_str(), &model_config) != 0) {
       LOG(ERROR) << "Failed to load general model config"
-                 << ", file path: " << conf_file;
+                 << ", file path: " << conf_file[0];
       return -1;
     }
 
@@ -69,9 +69,7 @@ int PredictorClient::init(const std::string &conf_file) {
     _fetch_name_to_idx.clear();
     _shape.clear();
     int feed_var_num = model_config.feed_var_size();
-    int fetch_var_num = model_config.fetch_var_size();
-    VLOG(2) << "feed var num: " << feed_var_num
-            << "fetch_var_num: " << fetch_var_num;
+    VLOG(2) << "feed var num: " << feed_var_num;
     for (int i = 0; i < feed_var_num; ++i) {
       _feed_name_to_idx[model_config.feed_var(i).alias_name()] = i;
       VLOG(2) << "feed alias name: " << model_config.feed_var(i).alias_name()
@@ -90,6 +88,16 @@ int PredictorClient::init(const std::string &conf_file) {
       _shape.push_back(tmp_feed_shape);
     }
 
+    if (conf_file.size() > 1) {
+      model_config.Clear();
+      if (configure::read_proto_conf(conf_file[conf_file.size()-1].c_str(), &model_config) != 0) {
+        LOG(ERROR) << "Failed to load general model config"
+                  << ", file path: " << conf_file[conf_file.size()-1];
+        return -1;
+      }
+    }
+    int fetch_var_num = model_config.fetch_var_size();
+    VLOG(2) << "fetch_var_num: " << fetch_var_num;
     for (int i = 0; i < fetch_var_num; ++i) {
       _fetch_name_to_idx[model_config.fetch_var(i).alias_name()] = i;
       VLOG(2) << "fetch [" << i << "]"
@@ -146,11 +154,16 @@ int PredictorClient::numpy_predict(
     const std::vector &int_feed_name,
     const std::vector> &int_shape,
     const std::vector> &int_lod_slot_batch,
+    const std::vector>& string_feed_batch,
+    const std::vector& string_feed_name,
+    const std::vector>& string_shape,
+    const std::vector>& string_lod_slot_batch,
     const std::vector &fetch_name,
     PredictorRes &predict_res_batch,
     const int &pid,
     const uint64_t log_id) {
   int batch_size = std::max(float_feed_batch.size(), int_feed_batch.size());
+  batch_size = batch_size > string_feed_batch.size() ? batch_size : string_feed_batch.size();
   VLOG(2) << "batch size: " << batch_size;
   predict_res_batch.clear();
   Timer timeline;
@@ -165,6 +178,7 @@ int PredictorClient::numpy_predict(
   VLOG(2) << "fetch general model predictor done.";
   VLOG(2) << "float feed name size: " << float_feed_name.size();
   VLOG(2) << "int feed name size: " << int_feed_name.size();
+  VLOG(2) << "string feed name size: " << string_feed_name.size();
   VLOG(2) << "max body size : " << brpc::fLU64::FLAGS_max_body_size;
   Request req;
   req.set_log_id(log_id);
@@ -172,12 +186,15 @@ int PredictorClient::numpy_predict(
     req.add_fetch_var_names(name);
   }
 
+  int vec_idx = 0;
+
   for (int bi = 0; bi < batch_size; bi++) {
     VLOG(2) << "prepare batch " << bi;
     std::vector tensor_vec;
     FeedInst *inst = req.add_insts();
     std::vector> float_feed = float_feed_batch[bi];
     std::vector> int_feed = int_feed_batch[bi];
+    std::vector string_feed = string_feed_batch[bi];
     for (auto &name : float_feed_name) {
       tensor_vec.push_back(inst->add_tensor_array());
     }
@@ -186,14 +203,19 @@ int PredictorClient::numpy_predict(
       tensor_vec.push_back(inst->add_tensor_array());
     }
 
-    VLOG(2) << "batch [" << bi << "] int_feed_name and float_feed_name "
-            << "prepared";
+    for (auto &name : string_feed_name) {
+      tensor_vec.push_back(inst->add_tensor_array());
+    }
 
-    int vec_idx = 0;
-    VLOG(2) << "tensor_vec size " << tensor_vec.size() << " float shape "
-            << float_shape.size();
+    VLOG(2) << "batch [" << bi << "] " << "prepared";
+
+    vec_idx = 0;
     for (auto &name : float_feed_name) {
       int idx = _feed_name_to_idx[name];
+      if (idx >= tensor_vec.size()) {
+        LOG(ERROR) << "idx > tensor_vec.size()";
+        return -1;
+      }
       Tensor *tensor = tensor_vec[idx];
       VLOG(2) << "prepare float feed " << name << " shape size "
               << float_shape[vec_idx].size();
@@ -203,7 +225,7 @@ int PredictorClient::numpy_predict(
       for (uint32_t j = 0; j < float_lod_slot_batch[vec_idx].size(); ++j) {
         tensor->add_lod(float_lod_slot_batch[vec_idx][j]);
       }
-      tensor->set_elem_type(1);
+      tensor->set_elem_type(P_FLOAT32);
       const int float_shape_size = float_shape[vec_idx].size();
       switch (float_shape_size) {
         case 4: {
@@ -249,13 +271,17 @@ int PredictorClient::numpy_predict(
       }
       vec_idx++;
     }
-
+    
     VLOG(2) << "batch [" << bi << "] "
             << "float feed value prepared";
 
     vec_idx = 0;
     for (auto &name : int_feed_name) {
       int idx = _feed_name_to_idx[name];
+      if (idx >= tensor_vec.size()) {
+        LOG(ERROR) << "idx > tensor_vec.size()";
+        return -1;
+      }
       Tensor *tensor = tensor_vec[idx];
 
       for (uint32_t j = 0; j < int_shape[vec_idx].size(); ++j) {
@@ -266,7 +292,7 @@ int PredictorClient::numpy_predict(
       }
       tensor->set_elem_type(_type[idx]);
 
-      if (_type[idx] == 0) {
+      if (_type[idx] == P_INT64) {
         VLOG(2) << "prepare int feed " << name << " shape size "
                 << int_shape[vec_idx].size();
       } else {
@@ -282,7 +308,7 @@ int PredictorClient::numpy_predict(
             for (ssize_t j = 0; j < int_array.shape(1); j++) {
               for (ssize_t k = 0; k < int_array.shape(2); k++) {
                 for (ssize_t l = 0; k < int_array.shape(3); l++) {
-                  if (_type[idx] == 0) {
+                  if (_type[idx] == P_INT64) {
                     tensor->add_int64_data(int_array(i, j, k, l));
                   } else {
                     tensor->add_int_data(int_array(i, j, k, l));
@@ -298,7 +324,7 @@ int PredictorClient::numpy_predict(
           for (ssize_t i = 0; i < int_array.shape(0); i++) {
             for (ssize_t j = 0; j < int_array.shape(1); j++) {
               for (ssize_t k = 0; k < int_array.shape(2); k++) {
-                if (_type[idx] == 0) {
+                if (_type[idx] == P_INT64) {
                   tensor->add_int64_data(int_array(i, j, k));
                 } else {
                   tensor->add_int_data(int_array(i, j, k));
@@ -312,7 +338,7 @@ int PredictorClient::numpy_predict(
           auto int_array = int_feed[vec_idx].unchecked<2>();
           for (ssize_t i = 0; i < int_array.shape(0); i++) {
             for (ssize_t j = 0; j < int_array.shape(1); j++) {
-              if (_type[idx] == 0) {
+              if (_type[idx] == P_INT64) {
                 tensor->add_int64_data(int_array(i, j));
               } else {
                 tensor->add_int_data(int_array(i, j));
@@ -324,7 +350,7 @@ int PredictorClient::numpy_predict(
         case 1: {
           auto int_array = int_feed[vec_idx].unchecked<1>();
           for (ssize_t i = 0; i < int_array.shape(0); i++) {
-            if (_type[idx] == 0) {
+            if (_type[idx] == P_INT64) {
               tensor->add_int64_data(int_array(i));
             } else {
               tensor->add_int_data(int_array(i));
@@ -338,6 +364,42 @@ int PredictorClient::numpy_predict(
 
     VLOG(2) << "batch [" << bi << "] "
             << "int feed value prepared";
+
+    vec_idx = 0;
+    for (auto &name : string_feed_name) {
+      int idx = _feed_name_to_idx[name];
+      if (idx >= tensor_vec.size()) {
+        LOG(ERROR) << "idx > tensor_vec.size()";
+        return -1;
+      }
+      Tensor *tensor = tensor_vec[idx];
+
+      for (uint32_t j = 0; j < string_shape[vec_idx].size(); ++j) {
+        tensor->add_shape(string_shape[vec_idx][j]);
+      }
+      for (uint32_t j = 0; j < string_lod_slot_batch[vec_idx].size(); ++j) {
+        tensor->add_lod(string_lod_slot_batch[vec_idx][j]);
+      }
+      tensor->set_elem_type(P_STRING);
+
+      const int string_shape_size = string_shape[vec_idx].size();
+      //string_shape[vec_idx] = [1];cause numpy has no datatype of string.
+      //we pass string via vector >.
+      if (string_shape_size != 1) {
+        LOG(ERROR) << "string_shape_size should be 1-D, but received is : " << string_shape_size;
+        return -1;
+      }
+      switch (string_shape_size) {
+        case 1: {
+          tensor->add_data(string_feed[vec_idx]);
+          break;
+        }
+      }
+      vec_idx++;
+    }
+    
+    VLOG(2) << "batch [" << bi << "] "
+            << "string feed value prepared";
   }
 
   int64_t preprocess_end = timeline.TimeStampUS();
@@ -397,19 +459,19 @@ int PredictorClient::numpy_predict(
 
       for (auto &name : fetch_name) {
         // int idx = _fetch_name_to_idx[name];
-        if (_fetch_name_to_type[name] == 0) {
+        if (_fetch_name_to_type[name] == P_INT64) {
           VLOG(2) << "ferch var " << name << "type int64";
           int size = output.insts(0).tensor_array(idx).int64_data_size();
           model._int64_value_map[name] = std::vector(
               output.insts(0).tensor_array(idx).int64_data().begin(),
               output.insts(0).tensor_array(idx).int64_data().begin() + size);
-        } else if (_fetch_name_to_type[name] == 1) {
+        } else if (_fetch_name_to_type[name] == P_FLOAT32) {
           VLOG(2) << "fetch var " << name << "type float";
           int size = output.insts(0).tensor_array(idx).float_data_size();
           model._float_value_map[name] = std::vector(
               output.insts(0).tensor_array(idx).float_data().begin(),
               output.insts(0).tensor_array(idx).float_data().begin() + size);
-        } else if (_fetch_name_to_type[name] == 2) {
+        } else if (_fetch_name_to_type[name] == P_INT32) {
           VLOG(2) << "fetch var " << name << "type int32";
           int size = output.insts(0).tensor_array(idx).int_data_size();
           model._int32_value_map[name] = std::vector(
diff --git a/core/general-client/src/pybind_general_model.cpp b/core/general-client/src/pybind_general_model.cpp
old mode 100644
new mode 100755
index a0ac6caf2e42d9c4eee475648a371681ad30b135..499f0856ad8b7ffae5f3f037142036ac486cc035
--- a/core/general-client/src/pybind_general_model.cpp
+++ b/core/general-client/src/pybind_general_model.cpp
@@ -78,7 +78,7 @@ PYBIND11_MODULE(serving_client, m) {
              self.init_gflags(argv);
            })
       .def("init",
-           [](PredictorClient &self, const std::string &conf) {
+           [](PredictorClient &self, const std::vector &conf) {
              return self.init(conf);
            })
       .def("set_predictor_conf",
@@ -107,6 +107,10 @@ PYBIND11_MODULE(serving_client, m) {
               const std::vector &int_feed_name,
               const std::vector> &int_shape,
               const std::vector> &int_lod_slot_batch,
+              const std::vector>& string_feed_batch,
+              const std::vector& string_feed_name,
+              const std::vector>& string_shape,
+              const std::vector>& string_lod_slot_batch,
               const std::vector &fetch_name,
               PredictorRes &predict_res_batch,
               const int &pid,
@@ -119,6 +123,10 @@ PYBIND11_MODULE(serving_client, m) {
                                        int_feed_name,
                                        int_shape,
                                        int_lod_slot_batch,
+                                       string_feed_batch,
+                                       string_feed_name,
+                                       string_shape,
+                                       string_lod_slot_batch,
                                        fetch_name,
                                        predict_res_batch,
                                        pid,
diff --git a/core/general-server/CMakeLists.txt b/core/general-server/CMakeLists.txt
old mode 100644
new mode 100755
index 6ea0b5e490ff0ea8954780799fb6f1b0aa00d0bb..9319d9ee5646570dfcc8b10b4bea1f4eeb290ef3
--- a/core/general-server/CMakeLists.txt
+++ b/core/general-server/CMakeLists.txt
@@ -1,6 +1,7 @@
 include_directories(SYSTEM  ${CMAKE_CURRENT_LIST_DIR}/../../)
 include(op/CMakeLists.txt)
 include(proto/CMakeLists.txt)
+
 add_executable(serving ${serving_srcs})
 add_dependencies(serving pdcodegen paddle_inference_engine pdserving paddle_inference cube-api utils)
 
@@ -20,6 +21,9 @@ include_directories(${CUDNN_ROOT}/include/)
 target_link_libraries(serving -Wl,--whole-archive paddle_inference_engine
         -Wl,--no-whole-archive)
 
+if(WITH_OPENCV)
+    target_link_libraries(serving ${OpenCV_LIBS})
+endif()
 target_link_libraries(serving paddle_inference ${paddle_depend_libs})
 target_link_libraries(serving brpc)
 target_link_libraries(serving protobuf)
@@ -27,6 +31,7 @@ target_link_libraries(serving pdserving)
 target_link_libraries(serving cube-api)
 target_link_libraries(serving utils)
 
+
 if(WITH_GPU)
     target_link_libraries(serving ${CUDA_LIBRARIES})
 endif()
diff --git a/core/general-server/op/CMakeLists.txt b/core/general-server/op/CMakeLists.txt
old mode 100644
new mode 100755
index 137bc9b236398d238e3b11c8f99a6088883abfec..1b631fd7a749e2e6f2f4c5b347ada6a1509842cd
--- a/core/general-server/op/CMakeLists.txt
+++ b/core/general-server/op/CMakeLists.txt
@@ -1,2 +1,15 @@
 FILE(GLOB op_srcs ${CMAKE_CURRENT_LIST_DIR}/*.cpp ${CMAKE_CURRENT_LIST_DIR}/../../predictor/tools/quant.cpp)
+if(WITH_OPENCV)
+    FILE(GLOB ocrtools_srcs ${CMAKE_CURRENT_LIST_DIR}/../../predictor/tools/ocrtools/*.cpp)
+    LIST(APPEND op_srcs ${ocrtools_srcs})
+else()
+    set (EXCLUDE_DIR "general_detection_op.cpp")
+    foreach (TMP_PATH ${op_srcs})
+        string (FIND ${TMP_PATH} ${EXCLUDE_DIR} EXCLUDE_DIR_FOUND)
+        if (NOT ${EXCLUDE_DIR_FOUND} EQUAL -1)
+            list (REMOVE_ITEM op_srcs ${TMP_PATH})
+            break()
+        endif ()
+    endforeach(TMP_PATH)
+endif()
 LIST(APPEND serving_srcs ${op_srcs})
diff --git a/core/general-server/op/general_detection_op.cpp b/core/general-server/op/general_detection_op.cpp
new file mode 100755
index 0000000000000000000000000000000000000000..f02465e0a70ce5ee86f71f8c194df34e545269df
--- /dev/null
+++ b/core/general-server/op/general_detection_op.cpp
@@ -0,0 +1,353 @@
+// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "core/general-server/op/general_detection_op.h"
+#include 
+#include 
+#include 
+#include 
+#include "core/predictor/framework/infer.h"
+#include "core/predictor/framework/memory.h"
+#include "core/predictor/framework/resource.h"
+#include "core/util/include/timer.h"
+
+
+/*
+#include "opencv2/imgcodecs/legacy/constants_c.h"
+#include "opencv2/imgproc/types_c.h"
+*/
+
+namespace baidu {
+namespace paddle_serving {
+namespace serving {
+
+using baidu::paddle_serving::Timer;
+using baidu::paddle_serving::predictor::MempoolWrapper;
+using baidu::paddle_serving::predictor::general_model::Tensor;
+using baidu::paddle_serving::predictor::general_model::Response;
+using baidu::paddle_serving::predictor::general_model::Request;
+using baidu::paddle_serving::predictor::general_model::FetchInst;
+using baidu::paddle_serving::predictor::InferManager;
+using baidu::paddle_serving::predictor::PaddleGeneralModelConfig;
+
+int GeneralDetectionOp::inference() {
+  VLOG(2) << "Going to run inference";
+  const std::vector pre_node_names = pre_names();
+  if (pre_node_names.size() != 1) {
+    LOG(ERROR) << "This op(" << op_name()
+               << ") can only have one predecessor op, but received "
+               << pre_node_names.size();
+    return -1;
+  }
+  const std::string pre_name = pre_node_names[0];
+
+  const GeneralBlob *input_blob = get_depend_argument(pre_name);
+  if (!input_blob) {
+    LOG(ERROR) << "input_blob is nullptr,error";
+      return -1;
+  }
+  uint64_t log_id = input_blob->GetLogId();
+  VLOG(2) << "(logid=" << log_id << ") Get precedent op name: " << pre_name;
+
+  GeneralBlob *output_blob = mutable_data();
+  if (!output_blob) {
+    LOG(ERROR) << "output_blob is nullptr,error";
+      return -1;
+  }
+  output_blob->SetLogId(log_id);
+
+  if (!input_blob) {
+    LOG(ERROR) << "(logid=" << log_id
+               << ") Failed mutable depended argument, op:" << pre_name;
+    return -1;
+  }
+
+  const TensorVector *in = &input_blob->tensor_vector;
+  TensorVector* out = &output_blob->tensor_vector;
+
+  int batch_size = input_blob->_batch_size;
+  VLOG(2) << "(logid=" << log_id << ") input batch size: " << batch_size;
+
+  output_blob->_batch_size = batch_size;
+
+  VLOG(2) << "(logid=" << log_id << ") infer batch size: " << batch_size;
+
+  std::vector input_shape;
+  int in_num =0;
+  void* databuf_data = NULL;
+  char* databuf_char = NULL;
+  size_t databuf_size = 0;
+
+  std::string* input_ptr = static_cast(in->at(0).data.data());
+  std::string base64str = input_ptr[0];
+  float ratio_h{};
+  float ratio_w{};
+
+  
+  cv::Mat img = Base2Mat(base64str);
+  cv::Mat srcimg;
+  cv::Mat resize_img;
+  
+  cv::Mat resize_img_rec;
+  cv::Mat crop_img;
+  img.copyTo(srcimg);
+
+  this->resize_op_.Run(img, resize_img, this->max_side_len_, ratio_h, ratio_w,
+                       this->use_tensorrt_);
+
+  this->normalize_op_.Run(&resize_img, this->mean_det, this->scale_det,
+                          this->is_scale_);
+
+  std::vector input(1 * 3 * resize_img.rows * resize_img.cols, 0.0f);
+  this->permute_op_.Run(&resize_img, input.data());
+
+
+  TensorVector* real_in = new TensorVector();
+  if (!real_in) {
+    LOG(ERROR) << "real_in is nullptr,error";
+    return -1;
+  }
+
+  for (int i = 0; i < in->size(); ++i) {
+    input_shape = {1, 3, resize_img.rows, resize_img.cols};
+    in_num = std::accumulate(input_shape.begin(), input_shape.end(), 1, std::multiplies());
+    databuf_size = in_num*sizeof(float);
+    databuf_data = MempoolWrapper::instance().malloc(databuf_size);
+    if (!databuf_data) {
+        LOG(ERROR) << "Malloc failed, size: " << databuf_size;
+        return -1;
+    }
+    memcpy(databuf_data,input.data(),databuf_size);
+    databuf_char = reinterpret_cast(databuf_data);
+    paddle::PaddleBuf paddleBuf(databuf_char, databuf_size);
+    paddle::PaddleTensor tensor_in;
+    tensor_in.name = in->at(i).name;
+    tensor_in.dtype = paddle::PaddleDType::FLOAT32;
+    tensor_in.shape = {1, 3, resize_img.rows, resize_img.cols};
+    tensor_in.lod = in->at(i).lod;
+    tensor_in.data = paddleBuf;
+    real_in->push_back(tensor_in);
+  }
+
+  Timer timeline;
+  int64_t start = timeline.TimeStampUS();
+  timeline.Start();
+  
+  if (InferManager::instance().infer(
+          engine_name().c_str(), real_in, out, batch_size)) {
+    LOG(ERROR) << "(logid=" << log_id
+               << ") Failed do infer in fluid model: " << engine_name().c_str();
+    return -1;
+  }
+  std::vector output_shape;
+  int out_num =0;
+  void* databuf_data_out = NULL;
+  char* databuf_char_out = NULL;
+  size_t databuf_size_out = 0;
+  //this is special add for PaddleOCR postprecess
+  int infer_outnum =  out->size();
+  for (int k = 0;k at(k).shape[2];
+    int n3 = out->at(k).shape[3];
+    int n = n2 * n3;
+
+    float* out_data = static_cast(out->at(k).data.data());
+    std::vector pred(n, 0.0);
+    std::vector cbuf(n, ' ');
+
+    for (int i = 0; i < n; i++) {
+      pred[i] = float(out_data[i]);
+      cbuf[i] = (unsigned char)((out_data[i]) * 255);
+    }
+
+    cv::Mat cbuf_map(n2, n3, CV_8UC1, (unsigned char *)cbuf.data());
+    cv::Mat pred_map(n2, n3, CV_32F, (float *)pred.data());
+
+    const double threshold = this->det_db_thresh_ * 255;
+    const double maxvalue = 255;
+    cv::Mat bit_map;
+    cv::threshold(cbuf_map, bit_map, threshold, maxvalue, cv::THRESH_BINARY);
+    cv::Mat dilation_map;
+    cv::Mat dila_ele = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
+    cv::dilate(bit_map, dilation_map, dila_ele);
+    boxes = post_processor_.BoxesFromBitmap(pred_map, dilation_map,
+                                            this->det_db_box_thresh_,
+                                            this->det_db_unclip_ratio_);
+
+    boxes = post_processor_.FilterTagDetRes(boxes, ratio_h, ratio_w, srcimg);
+
+    for (int i = boxes.size() - 1; i >= 0; i--) {
+      crop_img = GetRotateCropImage(img, boxes[i]);
+
+      float wh_ratio = float(crop_img.cols) / float(crop_img.rows);
+
+      this->resize_op_rec.Run(crop_img, resize_img_rec, wh_ratio, this->use_tensorrt_);
+
+      this->normalize_op_.Run(&resize_img_rec, this->mean_rec, this->scale_rec,
+                              this->is_scale_);
+
+      std::vector output_rec(1 * 3 * resize_img_rec.rows * resize_img_rec.cols, 0.0f);
+
+      this->permute_op_.Run(&resize_img_rec, output_rec.data());
+
+      // Inference.
+      output_shape = {1, 3, resize_img_rec.rows, resize_img_rec.cols};
+      out_num = std::accumulate(output_shape.begin(), output_shape.end(), 1, std::multiplies());
+      databuf_size_out = out_num*sizeof(float);
+      databuf_data_out = MempoolWrapper::instance().malloc(databuf_size_out);
+      if (!databuf_data_out) {
+          LOG(ERROR) << "Malloc failed, size: " << databuf_size_out;
+          return -1;
+      }
+      memcpy(databuf_data_out,output_rec.data(),databuf_size_out);
+      databuf_char_out = reinterpret_cast(databuf_data_out);
+      paddle::PaddleBuf paddleBuf(databuf_char_out, databuf_size_out);
+      paddle::PaddleTensor tensor_out;
+      tensor_out.name = "image";
+      tensor_out.dtype = paddle::PaddleDType::FLOAT32;
+      tensor_out.shape = {1, 3, resize_img_rec.rows, resize_img_rec.cols};
+      tensor_out.data = paddleBuf;
+      out->push_back(tensor_out);
+    }
+  }
+  out->erase(out->begin(),out->begin()+infer_outnum);
+
+  
+  int64_t end = timeline.TimeStampUS();
+  CopyBlobInfo(input_blob, output_blob);
+  AddBlobInfo(output_blob, start);
+  AddBlobInfo(output_blob, end);
+  return 0;
+}
+
+cv::Mat GeneralDetectionOp::Base2Mat(std::string &base64_data)
+{
+	cv::Mat img;
+	std::string s_mat;
+	s_mat = base64Decode(base64_data.data(), base64_data.size());
+	std::vector base64_img(s_mat.begin(), s_mat.end());
+	img = cv::imdecode(base64_img, cv::IMREAD_COLOR);//CV_LOAD_IMAGE_COLOR
+	return img;
+}
+
+std::string GeneralDetectionOp::base64Decode(const char* Data, int DataByte)
+{
+
+	const char DecodeTable[] =
+	{
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+		62, // '+'
+		0, 0, 0,
+		63, // '/'
+		52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0'-'9'
+		0, 0, 0, 0, 0, 0, 0,
+		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+		13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 'A'-'Z'
+		0, 0, 0, 0, 0, 0,
+		26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+		39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 'a'-'z'
+	};
+
+	std::string strDecode;
+	int nValue;
+	int i = 0;
+	while (i < DataByte)
+	{
+		if (*Data != '\r' && *Data != '\n')
+		{
+			nValue = DecodeTable[*Data++] << 18;
+			nValue += DecodeTable[*Data++] << 12;
+			strDecode += (nValue & 0x00FF0000) >> 16;
+			if (*Data != '=')
+			{
+				nValue += DecodeTable[*Data++] << 6;
+				strDecode += (nValue & 0x0000FF00) >> 8;
+				if (*Data != '=')
+				{
+					nValue += DecodeTable[*Data++];
+					strDecode += nValue & 0x000000FF;
+				}
+			}
+			i += 4;
+		}
+		else// 回车换行,跳过
+		{
+			Data++;
+			i++;
+		}
+	}
+	return strDecode;
+}
+
+cv::Mat GeneralDetectionOp::GetRotateCropImage(const cv::Mat &srcimage,
+                                           std::vector> box) {
+  cv::Mat image;
+  srcimage.copyTo(image);
+  std::vector> points = box;
+
+  int x_collect[4] = {box[0][0], box[1][0], box[2][0], box[3][0]};
+  int y_collect[4] = {box[0][1], box[1][1], box[2][1], box[3][1]};
+  int left = int(*std::min_element(x_collect, x_collect + 4));
+  int right = int(*std::max_element(x_collect, x_collect + 4));
+  int top = int(*std::min_element(y_collect, y_collect + 4));
+  int bottom = int(*std::max_element(y_collect, y_collect + 4));
+
+  cv::Mat img_crop;
+  image(cv::Rect(left, top, right - left, bottom - top)).copyTo(img_crop);
+
+  for (int i = 0; i < points.size(); i++) {
+    points[i][0] -= left;
+    points[i][1] -= top;
+  }
+
+  int img_crop_width = int(sqrt(pow(points[0][0] - points[1][0], 2) +
+                                pow(points[0][1] - points[1][1], 2)));
+  int img_crop_height = int(sqrt(pow(points[0][0] - points[3][0], 2) +
+                                 pow(points[0][1] - points[3][1], 2)));
+
+  cv::Point2f pts_std[4];
+  pts_std[0] = cv::Point2f(0., 0.);
+  pts_std[1] = cv::Point2f(img_crop_width, 0.);
+  pts_std[2] = cv::Point2f(img_crop_width, img_crop_height);
+  pts_std[3] = cv::Point2f(0.f, img_crop_height);
+
+  cv::Point2f pointsf[4];
+  pointsf[0] = cv::Point2f(points[0][0], points[0][1]);
+  pointsf[1] = cv::Point2f(points[1][0], points[1][1]);
+  pointsf[2] = cv::Point2f(points[2][0], points[2][1]);
+  pointsf[3] = cv::Point2f(points[3][0], points[3][1]);
+
+  cv::Mat M = cv::getPerspectiveTransform(pointsf, pts_std);
+
+  cv::Mat dst_img;
+  cv::warpPerspective(img_crop, dst_img, M,
+                      cv::Size(img_crop_width, img_crop_height),
+                      cv::BORDER_REPLICATE);
+
+  if (float(dst_img.rows) >= float(dst_img.cols) * 1.5) {
+    cv::Mat srcCopy = cv::Mat(dst_img.rows, dst_img.cols, dst_img.depth());
+    cv::transpose(dst_img, srcCopy);
+    cv::flip(srcCopy, srcCopy, 0);
+    return srcCopy;
+  } else {
+    return dst_img;
+  }
+}
+
+DEFINE_OP(GeneralDetectionOp);
+
+}  // namespace serving
+}  // namespace paddle_serving
+}  // namespace baidu
\ No newline at end of file
diff --git a/core/general-server/op/general_detection_op.h b/core/general-server/op/general_detection_op.h
new file mode 100755
index 0000000000000000000000000000000000000000..272ed5ff40575d42ac3058ad1824285925fc252c
--- /dev/null
+++ b/core/general-server/op/general_detection_op.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+#include 
+#include 
+#include 
+#include "core/general-server/general_model_service.pb.h"
+#include "core/general-server/op/general_infer_helper.h"
+#include "core/predictor/tools/ocrtools/postprocess_op.h"
+#include "core/predictor/tools/ocrtools/preprocess_op.h"
+#include "paddle_inference_api.h"  // NOLINT
+
+#include "opencv2/core.hpp"
+#include "opencv2/imgcodecs.hpp"
+#include "opencv2/imgproc.hpp"
+
+
+namespace baidu {
+namespace paddle_serving {
+namespace serving {
+
+class GeneralDetectionOp
+    : public baidu::paddle_serving::predictor::OpWithChannel {
+  public:
+    typedef std::vector TensorVector;
+
+    DECLARE_OP(GeneralDetectionOp);
+
+    int inference();
+
+  private:
+    //config info
+    bool use_gpu_ = false;
+    int gpu_id_ = 0;
+    int gpu_mem_ = 4000;
+    int cpu_math_library_num_threads_ = 4;
+    bool use_mkldnn_ = false;
+    // pre-process
+    PaddleOCR::ResizeImgType0 resize_op_;
+    PaddleOCR::Normalize normalize_op_;
+    PaddleOCR::Permute permute_op_;
+    PaddleOCR::CrnnResizeImg resize_op_rec;
+
+    bool use_tensorrt_ = false;
+    bool use_fp16_ = false;
+    // post-process
+    PaddleOCR::PostProcessor post_processor_;
+
+    //det config info
+    int max_side_len_ = 960;
+
+    double det_db_thresh_ = 0.3;
+    double det_db_box_thresh_ = 0.5;
+    double det_db_unclip_ratio_ = 2.0;
+
+    std::vector mean_det = {0.485f, 0.456f, 0.406f};
+    std::vector scale_det = {1 / 0.229f, 1 / 0.224f, 1 / 0.225f};
+    bool is_scale_ = true;
+
+    //rec config info
+    std::vector label_list_;
+    std::vector mean_rec = {0.5f, 0.5f, 0.5f};
+    std::vector scale_rec = {1 / 0.5f, 1 / 0.5f, 1 / 0.5f};
+    cv::Mat GetRotateCropImage(const cv::Mat &srcimage,
+                              std::vector> box);
+    cv::Mat Base2Mat(std::string &base64_data);
+    std::string base64Decode(const char* Data, int DataByte);
+    std::vector>> boxes;
+};
+
+}  // namespace serving
+}  // namespace paddle_serving
+}  // namespace baidu
diff --git a/core/general-server/op/general_dist_kv_quant_infer_op.cpp b/core/general-server/op/general_dist_kv_quant_infer_op.cpp
old mode 100644
new mode 100755
index 7d347702768c13b997ea97291a8f9fde0ce042a2..756b83d625d04b9d2c6c6faf1ab42eecf5a19073
--- a/core/general-server/op/general_dist_kv_quant_infer_op.cpp
+++ b/core/general-server/op/general_dist_kv_quant_infer_op.cpp
@@ -117,8 +117,9 @@ int GeneralDistKVQuantInferOp::inference() {
   std::unordered_map in_out_map;
   baidu::paddle_serving::predictor::Resource &resource =
       baidu::paddle_serving::predictor::Resource::instance();
+  //TODO:Temporary addition, specific details to be studied by HexToString
   std::shared_ptr model_config =
-      resource.get_general_model_config();
+      resource.get_general_model_config()[0];
   int cube_quant_bits = resource.get_cube_quant_bits();
   size_t EMBEDDING_SIZE = 0;
   if (cube_quant_bits == 0) {
diff --git a/core/general-server/op/general_infer_op.cpp b/core/general-server/op/general_infer_op.cpp
old mode 100644
new mode 100755
index 5b9df8064d6c7f50b269fc67b157494ac53e22e2..46038e1fe20d5659d3061e3d7490af65f6d54092
--- a/core/general-server/op/general_infer_op.cpp
+++ b/core/general-server/op/general_infer_op.cpp
@@ -44,9 +44,50 @@ int GeneralInferOp::inference() {
                << pre_node_names.size();
     return -1;
   }
-  if (InferManager::instance().infer(engine_name().c_str())) {
+  const std::string pre_name = pre_node_names[0];
+
+  const GeneralBlob *input_blob = get_depend_argument(pre_name);
+  if (!input_blob) {
+    LOG(ERROR) << "input_blob is nullptr,error";
+      return -1;
+  }
+  uint64_t log_id = input_blob->GetLogId();
+  VLOG(2) << "(logid=" << log_id << ") Get precedent op name: " << pre_name;
+
+  GeneralBlob *output_blob = mutable_data();
+  if (!output_blob) {
+    LOG(ERROR) << "output_blob is nullptr,error";
+      return -1;
+  }
+  output_blob->SetLogId(log_id);
+
+  if (!input_blob) {
+    LOG(ERROR) << "(logid=" << log_id
+               << ") Failed mutable depended argument, op:" << pre_name;
+    return -1;
+  }
+
+  const TensorVector *in = &input_blob->tensor_vector;
+  TensorVector *out = &output_blob->tensor_vector;
+
+  int batch_size = input_blob->_batch_size;
+  output_blob->_batch_size = batch_size;
+  VLOG(2) << "(logid=" << log_id << ") infer batch size: " << batch_size;
+
+  Timer timeline;
+  int64_t start = timeline.TimeStampUS();
+  timeline.Start();
+
+  if (InferManager::instance().infer(
+          engine_name().c_str(), in, out, batch_size)) {
+    LOG(ERROR) << "(logid=" << log_id
+               << ") Failed do infer in fluid model: " << engine_name().c_str();
     return -1;
   }
+  int64_t end = timeline.TimeStampUS();
+  CopyBlobInfo(input_blob, output_blob);
+  AddBlobInfo(output_blob, start);
+  AddBlobInfo(output_blob, end);
   return 0;
 }
 DEFINE_OP(GeneralInferOp);
diff --git a/core/general-server/op/general_reader_op.cpp b/core/general-server/op/general_reader_op.cpp
old mode 100644
new mode 100755
index b46071a3292dfb7429b240f374163df3adc324da..e98f84d06c7bae773097296e5bbf800929ffbb6e
--- a/core/general-server/op/general_reader_op.cpp
+++ b/core/general-server/op/general_reader_op.cpp
@@ -20,7 +20,6 @@
 #include "core/general-server/op/general_infer_helper.h"
 #include "core/predictor/framework/infer.h"
 #include "core/predictor/framework/memory.h"
-#include "core/predictor/framework/resource.h"
 #include "core/util/include/timer.h"
 
 namespace baidu {
@@ -33,8 +32,7 @@ using baidu::paddle_serving::predictor::general_model::Tensor;
 using baidu::paddle_serving::predictor::general_model::Request;
 using baidu::paddle_serving::predictor::general_model::FeedInst;
 using baidu::paddle_serving::predictor::PaddleGeneralModelConfig;
-using baidu::paddle_serving::predictor::InferManager;
-
+enum ProtoDataType { P_INT64, P_FLOAT32, P_INT32, P_STRING };
 int conf_check(const Request *req,
                const std::shared_ptr &model_config) {
   int var_num = req->insts(0).tensor_array_size();
@@ -48,17 +46,18 @@ int conf_check(const Request *req,
   VLOG(2) << "fetch var num in reader op: " << req->fetch_var_names_size();
 
   for (int i = 0; i < var_num; ++i) {
+    const Tensor &tensor = req->insts(0).tensor_array(i);
     if (model_config->_feed_type[i] !=
-        req->insts(0).tensor_array(i).elem_type()) {
+        tensor.elem_type()) {
       LOG(ERROR) << "feed type not match.";
       return -1;
     }
     if (model_config->_feed_shape[i].size() ==
-        req->insts(0).tensor_array(i).shape_size()) {
+        tensor.shape_size()) {
       for (int j = 0; j < model_config->_feed_shape[i].size(); ++j) {
-        req->insts(0).tensor_array(i).shape(j);
+        tensor.shape(j);
         if (model_config->_feed_shape[i][j] !=
-            req->insts(0).tensor_array(i).shape(j)) {
+            tensor.shape(j)) {
           LOG(ERROR) << "feed shape not match.";
           return -1;
         }
@@ -72,88 +71,178 @@ int conf_check(const Request *req,
 }
 
 int GeneralReaderOp::inference() {
-  // reade request from client
-  // TODO: only support one engine here
-  std::string engine_name = "general_infer_0";
+  // read request from client
   const Request *req = dynamic_cast(get_request_message());
   uint64_t log_id = req->log_id();
   int input_var_num = 0;
   std::vector elem_type;
   std::vector elem_size;
-  std::vector capacity;
+  std::vector databuf_size;
+
+  GeneralBlob *res = mutable_data();
+  TensorVector *out = &(res->tensor_vector);
+  
+  res->SetLogId(log_id);
+  if (!res) {
+    LOG(ERROR) << "(logid=" << log_id
+               << ") Failed get op tls reader object output";
+  }
+
+  Timer timeline;
+  int64_t start = timeline.TimeStampUS();
   int var_num = req->insts(0).tensor_array_size();
+
+  VLOG(2) << "(logid=" << log_id << ") var num: " << var_num
+          << ") start to call load general model_conf op";
+
   baidu::paddle_serving::predictor::Resource &resource =
       baidu::paddle_serving::predictor::Resource::instance();
+
+  VLOG(2) << "(logid=" << log_id << ") get resource pointer done.";
+  //get the first InferOP's model_config as ReaderOp's model_config by default.
   std::shared_ptr model_config =
-      resource.get_general_model_config();
+      resource.get_general_model_config().front();
+
+  // TODO(guru4elephant): how to do conditional check?
+  /*
+  int ret = conf_check(req, model_config);
+  if (ret != 0) {
+    LOG(ERROR) << "model conf of server:";
+    resource.print_general_model_config(model_config);
+    return 0;
+  }
+  */
+  // package tensor
+
   elem_type.resize(var_num);
   elem_size.resize(var_num);
-  capacity.resize(var_num);
+  databuf_size.resize(var_num);
+  // prepare basic information for input
+  // specify the memory needed for output tensor_vector
+  // fill the data into output general_blob
+  int data_len = 0;
   for (int i = 0; i < var_num; ++i) {
-    std::string tensor_name = model_config->_feed_name[i];
-    VLOG(2) << "(logid=" << log_id << ") get tensor name: " << tensor_name;
-    auto lod_tensor = InferManager::instance().GetInputHandle(
-        engine_name.c_str(), tensor_name.c_str());
-    std::vector> lod;
-    std::vector shape;
-    // get lod info here
-    if (req->insts(0).tensor_array(i).lod_size() > 0) {
-      lod.resize(1);
-      for (int k = 0; k < req->insts(0).tensor_array(i).lod_size(); ++k) {
-        lod[0].push_back(req->insts(0).tensor_array(i).lod(k));
-      }
-      capacity[i] = 1;
-      for (int k = 0; k < req->insts(0).tensor_array(i).shape_size(); ++k) {
-        int dim = req->insts(0).tensor_array(i).shape(k);
-        VLOG(2) << "(logid=" << log_id << ") shape for var[" << i
-                << "]: " << dim;
-        capacity[i] *= dim;
-        shape.push_back(dim);
+    paddle::PaddleTensor lod_tensor;
+    const Tensor &tensor = req->insts(0).tensor_array(i);
+    data_len = 0;
+    elem_type[i] = tensor.elem_type();
+    VLOG(2) << "var[" << i << "] has elem type: " << elem_type[i];
+    if (elem_type[i] == P_INT64) {  // int64
+      elem_size[i] = sizeof(int64_t);
+      lod_tensor.dtype = paddle::PaddleDType::INT64;
+      data_len = tensor.int64_data_size();
+    } else if (elem_type[i] == P_FLOAT32) {
+      elem_size[i] = sizeof(float);
+      lod_tensor.dtype = paddle::PaddleDType::FLOAT32;
+      data_len = tensor.float_data_size();
+    } else if (elem_type[i] == P_INT32) {
+      elem_size[i] = sizeof(int32_t);
+      lod_tensor.dtype = paddle::PaddleDType::INT32;
+      data_len = tensor.int_data_size();
+    } else if (elem_type[i] == P_STRING) {
+      //use paddle::PaddleDType::UINT8 as for String.
+      elem_size[i] = sizeof(uint8_t);
+      lod_tensor.dtype = paddle::PaddleDType::UINT8;
+      //this is for vector, cause the databuf_size != vector.size()*sizeof(char);
+      for (int idx = 0; idx < tensor.data_size(); idx++) {
+        data_len += tensor.data()[idx].length();
       }
-      VLOG(2) << "(logid=" << log_id << ") var[" << i
-              << "] is tensor, capacity: " << capacity[i];
-    } else {
-      capacity[i] = 1;
-      for (int k = 0; k < req->insts(0).tensor_array(i).shape_size(); ++k) {
-        int dim = req->insts(0).tensor_array(i).shape(k);
-        VLOG(2) << "(logid=" << log_id << ") shape for var[" << i
-                << "]: " << dim;
-        capacity[i] *= dim;
-        shape.push_back(dim);
+    }
+    // implement lod tensor here
+    // only support 1-D lod
+    // TODO:support 2-D lod
+    if (tensor.lod_size() > 0) {
+      VLOG(2) << "(logid=" << log_id << ") var[" << i << "] is lod_tensor";
+      lod_tensor.lod.resize(1);
+      for (int k = 0; k < tensor.lod_size(); ++k) {
+        lod_tensor.lod[0].push_back(tensor.lod(k));
       }
-      VLOG(2) << "(logid=" << log_id << ") var[" << i
-              << "] is tensor, capacity: " << capacity[i];
     }
-    lod_tensor->SetLoD(lod);
-    lod_tensor->Reshape(shape);
-    // insert data here
-    if (req->insts(0).tensor_array(i).elem_type() == 0) {
-      // TODO: Copy twice here, can optimize
-      int elem_num = req->insts(0).tensor_array(i).int64_data_size();
-      std::vector data(elem_num);
-      int64_t *dst_ptr = data.data();
+
+    for (int k = 0; k < tensor.shape_size(); ++k) {
+      int dim = tensor.shape(k);
+      VLOG(2) << "(logid=" << log_id << ") shape for var[" << i
+              << "]: " << dim;
+      lod_tensor.shape.push_back(dim);
+    }
+    lod_tensor.name = model_config->_feed_name[i];
+    out->push_back(lod_tensor);
+
+    
+    VLOG(2) << "(logid=" << log_id << ") tensor size for var[" << i
+            << "]: " << data_len;
+    databuf_size[i] = data_len * elem_size[i];
+    out->at(i).data.Resize(data_len * elem_size[i]);
+    VLOG(2) << "(logid=" << log_id << ") var[" << i
+            << "] is lod_tensor and len=" << out->at(i).lod[0].back();
+    
+    if (elem_type[i] == P_INT64) {
+      int64_t *dst_ptr = static_cast(out->at(i).data.data());
+      VLOG(2) << "(logid=" << log_id << ") first element data in var[" << i
+              << "] is " << tensor.int64_data(0);
+      if (!dst_ptr) {
+        LOG(ERROR) << "dst_ptr is nullptr";
+            return -1;
+      }
+      memcpy(dst_ptr, tensor.int64_data().data(),databuf_size[i]);
+      /*
+      int elem_num = tensor.int64_data_size();
       for (int k = 0; k < elem_num; ++k) {
-        dst_ptr[k] = req->insts(0).tensor_array(i).int64_data(k);
+        dst_ptr[k] = tensor.int64_data(k);
+      }
+      */
+    } else if (elem_type[i] == P_FLOAT32) {
+      float *dst_ptr = static_cast(out->at(i).data.data());
+      VLOG(2) << "(logid=" << log_id << ") first element data in var[" << i
+              << "] is " << tensor.float_data(0);
+      if (!dst_ptr) {
+        LOG(ERROR) << "dst_ptr is nullptr";
+            return -1;
       }
-      lod_tensor->CopyFromCpu(dst_ptr);
-    } else if (req->insts(0).tensor_array(i).elem_type() == 1) {
-      int elem_num = req->insts(0).tensor_array(i).float_data_size();
-      std::vector data(elem_num);
-      float *dst_ptr = data.data();
+      memcpy(dst_ptr, tensor.float_data().data(),databuf_size[i]);
+      /*int elem_num = tensor.float_data_size();
       for (int k = 0; k < elem_num; ++k) {
-        dst_ptr[k] = req->insts(0).tensor_array(i).float_data(k);
+        dst_ptr[k] = tensor.float_data(k);
+      }*/
+    } else if (elem_type[i] == P_INT32) {
+      int32_t *dst_ptr = static_cast(out->at(i).data.data());
+      VLOG(2) << "(logid=" << log_id << ") first element data in var[" << i
+              << "] is " << tensor.int_data(0);
+      if (!dst_ptr) {
+        LOG(ERROR) << "dst_ptr is nullptr";
+            return -1;
       }
-      lod_tensor->CopyFromCpu(dst_ptr);
-    } else if (req->insts(0).tensor_array(i).elem_type() == 2) {
-      int elem_num = req->insts(0).tensor_array(i).int_data_size();
-      std::vector data(elem_num);
-      int32_t *dst_ptr = data.data();
+      memcpy(dst_ptr, tensor.int_data().data(),databuf_size[i]);
+      /*
+      int elem_num = tensor.int_data_size();
       for (int k = 0; k < elem_num; ++k) {
-        dst_ptr[k] = req->insts(0).tensor_array(i).int_data(k);
+        dst_ptr[k] = tensor.int_data(k);
+      }
+      */
+    } else if (elem_type[i] == P_STRING) {
+      std::string *dst_ptr = static_cast(out->at(i).data.data());
+      VLOG(2) << "(logid=" << log_id << ") first element data in var[" << i
+              << "] is " << tensor.data(0);
+      if (!dst_ptr) {
+        LOG(ERROR) << "dst_ptr is nullptr";
+            return -1;
+      }
+      int elem_num = tensor.data_size();
+      for (int k = 0; k < elem_num; ++k) {
+        dst_ptr[k] = tensor.data(k);
       }
-      lod_tensor->CopyFromCpu(dst_ptr);
     }
   }
+
+  VLOG(2) << "(logid=" << log_id << ") output size: " << out->size();
+  timeline.Pause();
+  int64_t end = timeline.TimeStampUS();
+  res->p_size = 0;
+  res->_batch_size = 1;
+  AddBlobInfo(res, start);
+  AddBlobInfo(res, end);
+
+  VLOG(2) << "(logid=" << log_id << ") read data from client success";
   return 0;
 }
 DEFINE_OP(GeneralReaderOp);
diff --git a/core/general-server/op/general_response_op.cpp b/core/general-server/op/general_response_op.cpp
old mode 100644
new mode 100755
index dbc24c4cb659e116e0d1b07b03c033ad8764e033..d8fece0f7e25a967a6a72f41a9090b0977bf252a
--- a/core/general-server/op/general_response_op.cpp
+++ b/core/general-server/op/general_response_op.cpp
@@ -40,59 +40,163 @@ using baidu::paddle_serving::predictor::InferManager;
 using baidu::paddle_serving::predictor::PaddleGeneralModelConfig;
 
 int GeneralResponseOp::inference() {
+  const std::vector pre_node_names = pre_names();
+  VLOG(2) << "pre node names size: " << pre_node_names.size();
+  const GeneralBlob *input_blob = nullptr;
+  int var_idx = 0;
+  int cap = 1;
+  uint64_t log_id =
+      get_depend_argument(pre_node_names[0])->GetLogId();
+
   const Request *req = dynamic_cast(get_request_message());
   // response inst with only fetch_var_names
   Response *res = mutable_data();
+
+  Timer timeline;
+  // double response_time = 0.0;
+  // timeline.Start();
+  int64_t start = timeline.TimeStampUS();
+
+  VLOG(2) << "(logid=" << log_id
+          << ") start to call load general model_conf op";
   baidu::paddle_serving::predictor::Resource &resource =
       baidu::paddle_serving::predictor::Resource::instance();
+
+  VLOG(2) << "(logid=" << log_id << ") get resource pointer done.";
+  //get the last InferOP's model_config as ResponseOp's model_config by default.
   std::shared_ptr model_config =
-      resource.get_general_model_config();
-  std::vector capacity(req->fetch_var_names_size(), 1);
-  std::string engine_name = "general_infer_0";
-  ModelOutput *output = res->add_outputs();
-  FetchInst *fetch_inst = output->add_insts();
-  FetchInst *fetch_p = output->mutable_insts(0);
-  std::vector outs =
-      InferManager::instance().GetOutputNames(engine_name.c_str());
+      resource.get_general_model_config().back();
+
+  VLOG(2) << "(logid=" << log_id
+          << ") max body size : " << brpc::fLU64::FLAGS_max_body_size;
+
+  std::vector fetch_index;
+  fetch_index.resize(req->fetch_var_names_size());
   for (int i = 0; i < req->fetch_var_names_size(); ++i) {
-    Tensor *tensor = fetch_inst->add_tensor_array();
-    std::string tensor_name = outs[i];
-    auto lod_tensor = InferManager::instance().GetOutputHandle(
-        engine_name.c_str(), tensor_name.c_str());
-    std::vector shape = lod_tensor->shape();
-    for (int k = 0; k < shape.size(); ++k) {
-      capacity[i] *= shape[k];
-      tensor->add_shape(shape[k]);
+    fetch_index[i] =
+        model_config->_fetch_alias_name_to_index[req->fetch_var_names(i)];
+  }
+
+  for (uint32_t pi = 0; pi < pre_node_names.size(); ++pi) {
+    const std::string &pre_name = pre_node_names[pi];
+    VLOG(2) << "(logid=" << log_id << ") pre names[" << pi << "]: " << pre_name
+            << " (" << pre_node_names.size() << ")";
+    input_blob = get_depend_argument(pre_name);
+    // fprintf(stderr, "input(%s) blob address %x\n", pre_names.c_str(),
+    // input_blob);
+    if (!input_blob) {
+      LOG(ERROR) << "(logid=" << log_id
+                 << ") Failed mutable depended argument, op: " << pre_name;
+      return -1;
+    }
+
+    const TensorVector *in = &input_blob->tensor_vector;
+
+    ModelOutput *output = res->add_outputs();
+    // To get the order of model return values
+    output->set_engine_name(pre_name);
+    FetchInst *fetch_inst = output->add_insts();
+
+    for (auto &idx : fetch_index) {
+      Tensor *tensor = fetch_inst->add_tensor_array();
+      //tensor->set_elem_type(1);
+      if (model_config->_is_lod_fetch[idx]) {
+        VLOG(2) << "(logid=" << log_id << ") out[" << idx << "] "
+                << model_config->_fetch_name[idx] << " is lod_tensor";
+        for (int k = 0; k < in->at(idx).shape.size(); ++k) {
+          VLOG(2) << "(logid=" << log_id << ") shape[" << k
+                  << "]: " << in->at(idx).shape[k];
+          tensor->add_shape(in->at(idx).shape[k]);
+        }
+      } else {
+        VLOG(2) << "(logid=" << log_id << ") out[" << idx << "] "
+                << model_config->_fetch_name[idx] << " is tensor";
+        for (int k = 0; k < in->at(idx).shape.size(); ++k) {
+          VLOG(2) << "(logid=" << log_id << ") shape[" << k
+                  << "]: " << in->at(idx).shape[k];
+          tensor->add_shape(in->at(idx).shape[k]);
+        }
+      }
     }
-    auto dtype = lod_tensor->type();
-    if (dtype == paddle::PaddleDType::INT64) {
-      std::vector datas(capacity[i]);
-      int64_t *data_ptr = datas.data();
-      lod_tensor->CopyToCpu(data_ptr);
-      google::protobuf::RepeatedField tmp_data(data_ptr,
-                                                        data_ptr + capacity[i]);
-      tensor->mutable_int64_data()->Swap(&tmp_data);
-    } else if (dtype == paddle::PaddleDType::FLOAT32) {
-      std::vector datas(capacity[i]);
-      float *data_ptr = datas.data();
-      lod_tensor->CopyToCpu(data_ptr);
-      google::protobuf::RepeatedField tmp_data(data_ptr,
-                                                      data_ptr + capacity[i]);
-      tensor->mutable_float_data()->Swap(&tmp_data);
-    } else if (dtype == paddle::PaddleDType::INT32) {
-      std::vector datas(capacity[i]);
-      int32_t *data_ptr = datas.data();
-      lod_tensor->CopyToCpu(data_ptr);
-      google::protobuf::RepeatedField tmp_data(data_ptr,
-                                                        data_ptr + capacity[i]);
-      tensor->mutable_int_data()->Swap(&tmp_data);
+
+    var_idx = 0;
+    for (auto &idx : fetch_index) {
+      cap = 1;
+      for (int j = 0; j < in->at(idx).shape.size(); ++j) {
+        cap *= in->at(idx).shape[j];
+      }
+
+      FetchInst *fetch_p = output->mutable_insts(0);
+      auto dtype = in->at(idx).dtype;
+      if (dtype == paddle::PaddleDType::INT64) {
+        VLOG(2) << "(logid=" << log_id << ") Prepare int64 var ["
+                << model_config->_fetch_name[idx] << "].";
+        int64_t *data_ptr = static_cast(in->at(idx).data.data());
+        // from
+        // https://stackoverflow.com/questions/15499641/copy-a-stdvector-to-a-repeated-field-from-protobuf-with-memcpy
+        // `Swap` method is faster than `{}` method.
+        google::protobuf::RepeatedField tmp_data(data_ptr,
+                                                          data_ptr + cap);
+        fetch_p->mutable_tensor_array(var_idx)->mutable_int64_data()->Swap(
+            &tmp_data);
+      } else if (dtype == paddle::PaddleDType::FLOAT32) {
+        VLOG(2) << "(logid=" << log_id << ") Prepare float var ["
+                << model_config->_fetch_name[idx] << "].";
+        
+        float *data_ptr = static_cast(in->at(idx).data.data());
+        google::protobuf::RepeatedField tmp_data(data_ptr,
+                                                        data_ptr + cap);
+        fetch_p->mutable_tensor_array(var_idx)->mutable_float_data()->Swap(
+            &tmp_data);
+      } else if (dtype == paddle::PaddleDType::INT32) {
+
+        VLOG(2) << "(logid=" << log_id << ")Prepare int32 var ["
+                << model_config->_fetch_name[idx] << "].";
+        int32_t *data_ptr = static_cast(in->at(idx).data.data());
+        google::protobuf::RepeatedField tmp_data(data_ptr,
+                                                          data_ptr + cap);
+        fetch_p->mutable_tensor_array(var_idx)->mutable_int_data()->Swap(
+            &tmp_data);
+      }
+
+      if (model_config->_is_lod_fetch[idx]) {
+        if (in->at(idx).lod.size() > 0) {
+          for (int j = 0; j < in->at(idx).lod[0].size(); ++j) {
+            fetch_p->mutable_tensor_array(var_idx)->add_lod(
+                in->at(idx).lod[0][j]);
+          }
+        }
+      }
+
+      VLOG(2) << "(logid=" << log_id << ") fetch var ["
+              << model_config->_fetch_name[idx] << "] ready";
+      var_idx++;
     }
-    std::vector> lod = lod_tensor->lod();
-    if (lod.size() > 0) {
-      for (int j = 0; j < lod[0].size(); ++j) {
-        tensor->add_lod(lod[0][j]);
+  }
+
+  if (req->profile_server()) {
+    int64_t end = timeline.TimeStampUS();
+    // TODO(barriery): multi-model profile_time.
+    // At present, only the response_op is multi-input, so here we get
+    // the profile_time by hard coding. It needs to be replaced with
+    // a more elegant way.
+    for (uint32_t pi = 0; pi < pre_node_names.size(); ++pi) {
+      input_blob = get_depend_argument(pre_node_names[pi]);
+      VLOG(2) << "(logid=" << log_id
+              << ") p size for input blob: " << input_blob->p_size;
+      int profile_time_idx = -1;
+      if (pi == 0) {
+        profile_time_idx = 0;
+      } else {
+        profile_time_idx = input_blob->p_size - 2;
+      }
+      for (; profile_time_idx < input_blob->p_size; ++profile_time_idx) {
+        res->add_profile_time(input_blob->time_stamp[profile_time_idx]);
       }
     }
+    // TODO(guru4elephant): find more elegant way to do this
+    res->add_profile_time(start);
+    res->add_profile_time(end);
   }
   return 0;
 }
@@ -101,4 +205,4 @@ DEFINE_OP(GeneralResponseOp);
 
 }  // namespace serving
 }  // namespace paddle_serving
-}  // namespace baidu
+}  // namespace baidu
\ No newline at end of file
diff --git a/core/general-server/op/general_text_reader_op.cpp b/core/general-server/op/general_text_reader_op.cpp
old mode 100644
new mode 100755
index 3fa433c6cc31a3dbce331013780212d50e7f643c..6c305c18c0cb56bc5dd841c9c6a09807c6dbf518
--- a/core/general-server/op/general_text_reader_op.cpp
+++ b/core/general-server/op/general_text_reader_op.cpp
@@ -73,7 +73,7 @@ int GeneralTextReaderOp::inference() {
 
   VLOG(2) << "(logid=" << log_id << ") get resource pointer done.";
   std::shared_ptr model_config =
-      resource.get_general_model_config();
+      resource.get_general_model_config()[0];
 
   VLOG(2) << "(logid=" << log_id << ") print general model config done.";
 
diff --git a/core/general-server/op/general_text_response_op.cpp b/core/general-server/op/general_text_response_op.cpp
old mode 100644
new mode 100755
index 03eea7d76c83782b661ea4553fc5fc0eee99e372..03ab08cd361ea9eb8060c4ba5372d319a34df1f6
--- a/core/general-server/op/general_text_response_op.cpp
+++ b/core/general-server/op/general_text_response_op.cpp
@@ -58,7 +58,7 @@ int GeneralTextResponseOp::inference() {
 
   VLOG(2) << "(logid=" << log_id << ") get resource pointer done.";
   std::shared_ptr model_config =
-      resource.get_general_model_config();
+      resource.get_general_model_config().back();
 
   std::vector fetch_index;
   fetch_index.resize(req->fetch_var_names_size());
diff --git a/core/predictor/common/utils.h b/core/predictor/common/utils.h
index 052f90b166f04a28d0e7aeb427884921abdcab5e..4437bb94f2535281f83440c3dbae311423edf91c 100644
--- a/core/predictor/common/utils.h
+++ b/core/predictor/common/utils.h
@@ -13,8 +13,10 @@
 // limitations under the License.
 
 #pragma once
-#include 
+#include 
+#include 
 #include 
+#include 
 #include "core/predictor/common/inner_common.h"
 #include "core/predictor/common/macros.h"
 
@@ -26,6 +28,38 @@ namespace predictor {
 namespace butil = base;
 #endif
 
+enum class Precision {
+  kUnk = -1,     // unknown type
+  kFloat32 = 0,  // fp32
+  kInt8,         // int8
+  kHalf,         // fp16
+  kBfloat16,     // bf16
+};
+
+static std::string PrecisionTypeString(const Precision data_type) {
+  switch (data_type) {
+    case Precision::kFloat32:
+      return "kFloat32";
+    case Precision::kInt8:
+      return "kInt8";
+    case Precision::kHalf:
+      return "kHalf";
+    case Precision::kBfloat16:
+      return "kBloat16";
+    default:
+      return "unUnk";
+  }
+}
+
+static std::string ToLower(const std::string& data) {
+  std::string result = data;
+  std::transform(
+      result.begin(), result.end(), result.begin(), [](unsigned char c) {
+        return tolower(c);
+      });
+  return result;
+}
+
 class TimerFlow {
  public:
   static const int MAX_SIZE = 1024;
diff --git a/core/predictor/framework/infer.h b/core/predictor/framework/infer.h
old mode 100644
new mode 100755
index fcd3038c85174fd62d2f6157b003d91f8c830f2a..f7fee8c70d5222936a4a7d2cf65fb3c008ec6973
--- a/core/predictor/framework/infer.h
+++ b/core/predictor/framework/infer.h
@@ -20,7 +20,9 @@
 #include 
 #include 
 #include 
+#include 
 #include "core/predictor/common/inner_common.h"
+#include "core/predictor/framework/bsf.h"
 #include "core/predictor/framework/factory.h"
 #include "core/predictor/framework/infer_data.h"
 #include "paddle_inference_api.h"  // NOLINT
@@ -66,7 +68,7 @@ class InferEngine {
   virtual int thrd_initialize() { return thrd_initialize_impl(); }
   virtual int thrd_clear() { return thrd_clear_impl(); }
   virtual int thrd_finalize() { return thrd_finalize_impl(); }
-  virtual int infer() { return infer_impl(); }
+  virtual int infer(const void* in, void* out, uint32_t batch_size = -1) { return infer_impl(in, out, batch_size); }
 
   virtual int reload() = 0;
 
@@ -79,13 +81,12 @@ class InferEngine {
   virtual int thrd_finalize_impl() = 0;
   virtual int thrd_clear_impl() = 0;
   virtual int proc_finalize_impl() = 0;
-  virtual std::vector GetInputNames() = 0;
-  virtual std::vector GetOutputNames() = 0;
-  virtual std::unique_ptr GetInputHandle(
-      const std::string& name) = 0;
-  virtual std::unique_ptr GetOutputHandle(
-      const std::string& name) = 0;
-  virtual int infer_impl() = 0;
+  virtual int infer_impl(const void* in,
+                          void* out,
+                          uint32_t batch_size = -1) = 0;
+  virtual int task_infer_impl(const BatchTensor& in,
+                          BatchTensor& out) = 0;  // NOLINT
+
   // end: framework inner call
 };
 
@@ -100,6 +101,7 @@ class ReloadableInferEngine : public InferEngine {
   };
 
   virtual int load(const configure::EngineDesc& conf) = 0;
+  typedef im::bsf::Task TaskT;
 
   int proc_initialize_impl(const configure::EngineDesc& conf, bool version) {
     _reload_tag_file = conf.reloadable_meta();
@@ -130,10 +132,45 @@ class ReloadableInferEngine : public InferEngine {
       LOG(ERROR) << "Failed proc initialize impl";
       return -1;
     }
+
+    // init bsf framework
+    if (_infer_thread_num <= 0) {
+      return 0;
+    }
+
+    // init bsf framework
+    im::bsf::TaskExecutor::instance()->set_thread_init_fn(
+        boost::bind(&InferEngine::thrd_initialize_impl, this));
+    im::bsf::TaskExecutor::instance()->set_thread_reset_fn(
+        boost::bind(&InferEngine::thrd_clear_impl, this));
+    im::bsf::TaskExecutor::instance()->set_thread_callback_fn(
+        boost::bind(&InferEngine::task_infer_impl, this, _1, _2));
+    im::bsf::TaskExecutor::instance()->set_batch_size(_infer_batch_size);
+    im::bsf::TaskExecutor::instance()->set_batch_align(
+        _infer_batch_align);
+    if (im::bsf::TaskExecutor::instance()->start(_infer_thread_num) !=
+        0) {
+      LOG(ERROR) << "Failed start bsf executor, threads:" << _infer_thread_num;
+      return -1;
+    }
+
+    LOG(WARNING) << "Enable batch schedule framework, thread_num:"
+                 << _infer_thread_num << ", batch_size:" << _infer_batch_size
+                 << ", enable_batch_align:" << _infer_batch_align;
     return 0;
   }
 
-  int infer() { return infer_impl(); }
+  int infer(const void* in, void* out, uint32_t batch_size = -1) {
+    if (_infer_thread_num <= 0) {
+      return infer_impl(in, out, batch_size);
+    }
+
+    im::bsf::TaskManager task_manager;
+    task_manager.schedule(*(reinterpret_cast(in)),
+                          *(reinterpret_cast(out)));
+    task_manager.wait();
+    return 0;
+  }
 
   int thrd_initialize() {
     if (_infer_thread_num > 0) {
@@ -156,6 +193,9 @@ class ReloadableInferEngine : public InferEngine {
       return -1;
     }
 
+    if (_infer_thread_num > 0) {
+      im::bsf::TaskExecutor::instance()->stop();
+    }
     return 0;
   }
 
@@ -306,6 +346,10 @@ class DBReloadableInferEngine : public ReloadableInferEngine {
 
   virtual int thrd_initialize_impl() {
     // memory pool to be inited in non-serving-threads
+    if (MempoolWrapper::instance().thread_initialize() != 0) {
+      LOG(ERROR) << "Failed thread initialize mempool";
+      return -1;
+    }
 
     ModelData* md = new (std::nothrow) ModelData;
     if (!md || load_data(md, _conf) != 0) {
@@ -315,12 +359,17 @@ class DBReloadableInferEngine : public ReloadableInferEngine {
     }
 
     THREAD_SETSPECIFIC(_skey, md);
+    im::bsf::AutoMutex lock(_mutex);
     _reload_vec.push_back(md);
     return 0;
   }
 
   int thrd_clear_impl() {
     // for non-serving-threads
+    if (MempoolWrapper::instance().thread_clear() != 0) {
+      LOG(ERROR) << "Failed thread clear mempool";
+      return -1;
+    }
     return 0;
   }
 
@@ -418,6 +467,12 @@ class CloneDBReloadableInferEngine
   }
 
   virtual int thrd_initialize_impl() {
+    // memory pool to be inited in non-serving-threads
+    if (MempoolWrapper::instance().thread_initialize() != 0) {
+      LOG(ERROR) << "Failed thread initialize mempool";
+      return -1;
+    }
+
     ModelData* md = new (std::nothrow) ModelData;
     if (!md || load_data(md, _pd->cores[_pd->current_idx]) != 0) {
       LOG(ERROR) << "Failed clone thread data, origin_core["
@@ -426,6 +481,7 @@ class CloneDBReloadableInferEngine
     }
 
     THREAD_SETSPECIFIC(DBReloadableInferEngine::_skey, md);
+    im::bsf::AutoMutex lock(DBReloadableInferEngine::_mutex);
     DBReloadableInferEngine::_reload_vec.push_back(md);
     return 0;
   }
@@ -444,58 +500,119 @@ class FluidInferEngine : public CloneDBReloadableInferEngine GetInputNames() {
-    PaddleInferenceCore* core =
-        DBReloadableInferEngine::get_core();
-    if (!core || !core->get()) {
-      LOG(ERROR) << "Failed get fluid core in GetInputHandle()";
-    }
-    return core->GetInputNames();
-  }
-
-  std::vector GetOutputNames() {
-    PaddleInferenceCore* core =
-        DBReloadableInferEngine::get_core();
-    if (!core || !core->get()) {
-      LOG(ERROR) << "Failed get fluid core in GetInputHandle()";
-    }
-    return core->GetOutputNames();
-  }
-
-  std::unique_ptr GetInputHandle(
-      const std::string& name) {
-    PaddleInferenceCore* core =
-        DBReloadableInferEngine::get_core();
-    if (!core || !core->get()) {
-      LOG(ERROR) << "Failed get fluid core in GetInputHandle()";
-    }
-    return core->GetInputHandle(name);
-  }
-
-  std::unique_ptr GetOutputHandle(
-      const std::string& name) {
-    PaddleInferenceCore* core =
-        DBReloadableInferEngine::get_core();
-    if (!core || !core->get()) {
-      LOG(ERROR) << "Failed get fluid core in GetOutputHandle()";
-    }
-    return core->GetOutputHandle(name);
-  }
-
-  int infer_impl() {
-    PaddleInferenceCore* core =
-        DBReloadableInferEngine::get_core();
+  typedef std::vector TensorVector;
+  int infer_impl(const void* in, void* out, uint32_t batch_size = -1) {
+    //First of all, get the real core acording to the template parameter 'PaddleInferenceCore'.
+    PaddleInferenceCore* core =DBReloadableInferEngine::get_core();
     if (!core || !core->get()) {
       LOG(ERROR) << "Failed get fluid core in infer_impl()";
       return -1;
     }
-
+    //We use the for loop to process the input data.
+    //Inside each for loop, use the in[i]->name as inputName and call 'core->GetInputHandle(inputName)' to get the pointer of InputData.
+    //Set the lod and shape information of InputData first. then copy data from cpu to the core.
+    const TensorVector* tensorVector_in_pointer = reinterpret_cast(in);
+    for (int i=0; i < tensorVector_in_pointer->size(); ++i) {
+      auto lod_tensor_in = core->GetInputHandle((*tensorVector_in_pointer)[i].name);
+      lod_tensor_in->SetLoD((*tensorVector_in_pointer)[i].lod);
+      lod_tensor_in->Reshape((*tensorVector_in_pointer)[i].shape);
+      void* origin_data = (*tensorVector_in_pointer)[i].data.data();
+      //Because the core needs to determine the size of memory space according to the data type passed in.
+      //The pointer type of data must be one of float *,int64_t*,int32_t* instead void*.
+      if ((*tensorVector_in_pointer)[i].dtype == paddle::PaddleDType::FLOAT32) {
+        float* data = static_cast(origin_data);
+        lod_tensor_in->CopyFromCpu(data);
+      }else if ((*tensorVector_in_pointer)[i].dtype == paddle::PaddleDType::INT64) {
+        int64_t* data = static_cast(origin_data);
+        lod_tensor_in->CopyFromCpu(data);
+      }else if ((*tensorVector_in_pointer)[i].dtype == paddle::PaddleDType::INT32) {
+        int32_t* data = static_cast(origin_data);
+        lod_tensor_in->CopyFromCpu(data);
+      }
+    }
+    //After the input data is passed in, call 'core->Run()' perform the prediction process.
     if (!core->Run()) {
-      LOG(ERROR) << "Failed run fluid family core";
+        LOG(ERROR) << "Failed run fluid family core";
+        return -1;
+    }
+    
+    //In order to get the results, first, call the 'core->GetOutputNames()' to get the name of output(which is a dict like {OutputName:pointer of OutputValue}).
+    //Then, use for-loop to get OutputValue by calling 'core->GetOutputHandle'.
+    std::vector outnames = core->GetOutputNames();
+    std::vector output_shape;
+    int out_num =0;
+    int dataType =0;
+    void* databuf_data = NULL;
+    char* databuf_char = NULL;
+    size_t databuf_size = 0;
+    TensorVector* tensorVector_out_pointer = reinterpret_cast(out);
+    if (!tensorVector_out_pointer) {
+      LOG(ERROR) << "tensorVector_out_pointer is nullptr,error";
       return -1;
     }
+    //Get the type and shape information of OutputData first. then copy data to cpu from the core.
+    //The pointer type of data_out must be one of float *,int64_t*,int32_t* instead void*.
+    for (int i=0; i < outnames.size(); ++i) {
+      auto lod_tensor_out = core->GetOutputHandle(outnames[i]);
+      output_shape = lod_tensor_out->shape();
+      out_num = std::accumulate(output_shape.begin(), output_shape.end(), 1, std::multiplies());
+      dataType = lod_tensor_out->type();
+      if (dataType == paddle::PaddleDType::FLOAT32) {
+        databuf_size = out_num*sizeof(float);
+        databuf_data = MempoolWrapper::instance().malloc(databuf_size);
+        if (!databuf_data) {
+            LOG(ERROR) << "Malloc failed, size: " << databuf_size;
+            return -1;
+        }
+        float* data_out = reinterpret_cast(databuf_data);
+        lod_tensor_out->CopyToCpu(data_out);
+        databuf_char = reinterpret_cast(data_out);
+      }else if (dataType == paddle::PaddleDType::INT64) {
+        databuf_size = out_num*sizeof(int64_t);
+        databuf_data = MempoolWrapper::instance().malloc(databuf_size);
+        if (!databuf_data) {
+            LOG(ERROR) << "Malloc failed, size: " << databuf_size;
+            return -1;
+        }
+        int64_t* data_out = reinterpret_cast(databuf_data);
+        lod_tensor_out->CopyToCpu(data_out);
+        databuf_char = reinterpret_cast(data_out);
+      }else if (dataType == paddle::PaddleDType::INT32) {
+        databuf_size = out_num*sizeof(int32_t);
+        databuf_data = MempoolWrapper::instance().malloc(databuf_size);
+        if (!databuf_data) {
+            LOG(ERROR) << "Malloc failed, size: " << databuf_size;
+            return -1;
+        }
+        int32_t* data_out = reinterpret_cast(databuf_data);
+        lod_tensor_out->CopyToCpu(data_out);
+        databuf_char = reinterpret_cast(data_out);
+      }
+      //Because task scheduling requires OPs to use 'Channel'(which is a data structure) to transfer data between OPs.
+      //We need to copy the processed data to the 'Channel' for the next OP.
+      //In this function, it means we should copy the 'databuf_char' to the pointer 'void* out'.(which is also called ‘tensorVector_out_pointer’)
+      paddle::PaddleTensor tensor_out;
+      tensor_out.name = outnames[i];
+      tensor_out.dtype = paddle::PaddleDType(dataType);
+      tensor_out.shape.assign(output_shape.begin(), output_shape.end());
+      std::vector> out_lod = lod_tensor_out->lod();
+      for (int li=0; li < out_lod.size(); ++li) {
+        std::vector lod_element;
+        lod_element.assign(out_lod[li].begin(), out_lod[li].end());
+        tensor_out.lod.push_back(lod_element);
+      }
+      paddle::PaddleBuf paddleBuf(databuf_char, databuf_size);
+      tensor_out.data = paddleBuf;
+      tensorVector_out_pointer->push_back(tensor_out);
+    }
     return 0;
   }
+
+  int task_infer_impl(const BatchTensor& in, BatchTensor& out) {  // NOLINT
+    return infer_impl(&in, &out);
+  }
+
+
 };
 
 typedef FactoryPool StaticInferFactory;
@@ -621,45 +738,13 @@ class VersionedInferEngine : public InferEngine {
     return _versions.begin()->second;
   }
 
-  int infer() {
+  int infer(const void* in, void* out, uint32_t batch_size) {
     InferEngine* engine = default_engine();
     if (!engine) {
       LOG(WARNING) << "fail to get default engine";
       return -1;
     }
-    return engine->infer();
-  }
-
-  std::vector GetInputNames() {
-    InferEngine* engine = default_engine();
-    if (!engine) {
-      LOG(WARNING) << "fail to get default engine";
-    }
-    return engine->GetInputNames();
-  }
-  std::vector GetOutputNames() {
-    InferEngine* engine = default_engine();
-    if (!engine) {
-      LOG(WARNING) << "fail to get default engine";
-    }
-    return engine->GetOutputNames();
-  }
-  std::unique_ptr GetInputHandle(
-      const std::string& name) {
-    InferEngine* engine = default_engine();
-    if (!engine) {
-      LOG(WARNING) << "fail to get default engine";
-    }
-    return engine->GetInputHandle(name);
-  }
-
-  std::unique_ptr GetOutputHandle(
-      const std::string& name) {
-    InferEngine* engine = default_engine();
-    if (!engine) {
-      LOG(WARNING) << "fail to get default engine";
-    }
-    return engine->GetOutputHandle(name);
+    return engine->infer(in, out, batch_size);
   }
 
   template 
@@ -678,47 +763,14 @@ class VersionedInferEngine : public InferEngine {
   }
 
   // versioned inference interface
-  int infer(uint64_t version) {
+  int infer(const void* in, void* out, uint32_t batch_size, uint64_t version) {
     auto iter = _versions.find(version);
     if (iter == _versions.end()) {
       LOG(ERROR) << "Not found version engine: " << version;
       return -1;
     }
 
-    return iter->second->infer();
-  }
-  std::vector GetInputNames(uint64_t version) {
-    auto iter = _versions.find(version);
-    if (iter == _versions.end()) {
-      LOG(ERROR) << "Not found version engine: " << version;
-    }
-    return iter->second->GetInputNames();
-  }
-
-  std::vector GetOutputNames(uint64_t version) {
-    auto iter = _versions.find(version);
-    if (iter == _versions.end()) {
-      LOG(ERROR) << "Not found version engine: " << version;
-    }
-    return iter->second->GetOutputNames();
-  }
-
-  std::unique_ptr GetInputHandle(
-      uint64_t version, const std::string& name) {
-    auto iter = _versions.find(version);
-    if (iter == _versions.end()) {
-      LOG(ERROR) << "Not found version engine: " << version;
-    }
-    return iter->second->GetInputHandle(name);
-  }
-
-  std::unique_ptr GetOutputHandle(
-      uint64_t version, const std::string& name) {
-    auto iter = _versions.find(version);
-    if (iter == _versions.end()) {
-      LOG(ERROR) << "Not found version engine: " << version;
-    }
-    return iter->second->GetOutputHandle(name);
+    return iter->second->infer(in, out, batch_size);
   }
 
   template 
@@ -745,7 +797,10 @@ class VersionedInferEngine : public InferEngine {
   int thrd_finalize_impl() { return -1; }
   int thrd_clear_impl() { return -1; }
   int proc_finalize_impl() { return -1; }
-  int infer_impl() { return -1; }
+  int infer_impl(const void* in, void* out, uint32_t batch_size = -1) { return -1; }
+  int task_infer_impl(const BatchTensor& in, BatchTensor& out) {  // NOLINT
+    return -1;
+  }  // NOLINT
 
  private:
   boost::unordered_map _versions;
@@ -843,44 +898,16 @@ class InferManager {
   }
 
   // Inference interface
-  int infer(const char* model_name) {
+  int infer(const char* model_name,
+            const void* in,
+            void* out,
+            uint32_t batch_size = -1) {
     auto it = _map.find(model_name);
     if (it == _map.end()) {
       LOG(WARNING) << "Cannot find engine in map, model name:" << model_name;
       return -1;
     }
-    return it->second->infer();
-  }
-
-  std::vector GetInputNames(const char* model_name) {
-    auto it = _map.find(model_name);
-    if (it == _map.end()) {
-      LOG(WARNING) << "Cannot find engine in map, model name:" << model_name;
-    }
-    return it->second->GetInputNames();
-  }
-  std::vector GetOutputNames(const char* model_name) {
-    auto it = _map.find(model_name);
-    if (it == _map.end()) {
-      LOG(WARNING) << "Cannot find engine in map, model name:" << model_name;
-    }
-    return it->second->GetOutputNames();
-  }
-  std::unique_ptr GetInputHandle(
-      const char* model_name, const std::string& name) {
-    auto it = _map.find(model_name);
-    if (it == _map.end()) {
-      LOG(WARNING) << "Cannot find engine in map, model name:" << model_name;
-    }
-    return it->second->GetInputHandle(name);
-  }
-  std::unique_ptr GetOutputHandle(
-      const char* model_name, const std::string& name) {
-    auto it = _map.find(model_name);
-    if (it == _map.end()) {
-      LOG(WARNING) << "Cannot find engine in map, model name:" << model_name;
-    }
-    return it->second->GetOutputHandle(name);
+    return it->second->infer(in, out, batch_size);
   }
 
   template 
@@ -900,48 +927,19 @@ class InferManager {
   }
 
   // Versioned inference interface
-  int infer(const char* model_name, uint64_t version) {
+  int infer(const char* model_name, 
+            const void* in,
+            void* out,
+            uint32_t batch_size,
+            uint64_t version) {
     auto it = _map.find(model_name);
     if (it == _map.end()) {
       LOG(WARNING) << "Cannot find engine in map, model name:" << model_name;
       return -1;
     }
-    return it->second->infer(version);
-  }
-  std::vector GetInputNames(const char* model_name,
-                                         uint64_t version) {
-    auto it = _map.find(model_name);
-    if (it == _map.end()) {
-      LOG(WARNING) << "Cannot find engine in map, model name:" << model_name;
-    }
-    return it->second->GetInputNames(version);
+    return it->second->infer(in, out, batch_size, version);
   }
 
-  std::vector GetOutputNames(const char* model_name,
-                                          uint64_t version) {
-    auto it = _map.find(model_name);
-    if (it == _map.end()) {
-      LOG(WARNING) << "Cannot find engine in map, model name:" << model_name;
-    }
-    return it->second->GetOutputNames(version);
-  }
-
-  std::unique_ptr GetInputHandle(
-      const char* model_name, uint64_t version, const std::string& name) {
-    auto it = _map.find(model_name);
-    if (it == _map.end()) {
-      LOG(WARNING) << "Cannot find engine in map, model name:" << model_name;
-    }
-    return it->second->GetInputHandle(version, name);
-  }
-  std::unique_ptr GetOutputHandle(
-      const char* model_name, uint64_t version, const std::string& name) {
-    auto it = _map.find(model_name);
-    if (it == _map.end()) {
-      LOG(WARNING) << "Cannot find engine in map, model name:" << model_name;
-    }
-    return it->second->GetOutputHandle(version, name);
-  }
   template 
   T* get_core(const char* model_name, uint64_t version) {
     auto it = _map.find(model_name);
diff --git a/core/predictor/framework/infer_data.h b/core/predictor/framework/infer_data.h
old mode 100644
new mode 100755
index 1ad62ce61fbe480ef6a4b3385147c02c9c1c5304..cea6508a0ff96cdc879fc00f56d21957b52b2193
--- a/core/predictor/framework/infer_data.h
+++ b/core/predictor/framework/infer_data.h
@@ -21,7 +21,7 @@ namespace baidu {
 namespace paddle_serving {
 namespace predictor {
 
-enum DataType { FLOAT32, INT64 };
+enum DataType { FLOAT32, INT64, INT32 };
 
 class DataBuf {
  public:
@@ -80,8 +80,10 @@ struct Tensor {
   size_t ele_byte() const {
     if (type == INT64) {
       return sizeof(int64_t);
-    } else {
+    } else if (type == FLOAT32) {
       return sizeof(float);
+    } else {
+      return sizeof(int32_t);
     }
   }
 
diff --git a/core/predictor/framework/resource.cpp b/core/predictor/framework/resource.cpp
old mode 100644
new mode 100755
index cdb21097fdf40ca6060d99088ed5649a08507720..4da0ef537b26ba7e765471b40b0b8d035b598dc1
--- a/core/predictor/framework/resource.cpp
+++ b/core/predictor/framework/resource.cpp
@@ -42,8 +42,8 @@ DynamicResource::~DynamicResource() {}
 
 int DynamicResource::initialize() { return 0; }
 
-std::shared_ptr Resource::get_general_model_config() {
-  return _config;
+std::vector > Resource::get_general_model_config() {
+  return _configs;
 }
 
 void Resource::print_general_model_config(
@@ -149,30 +149,23 @@ int Resource::initialize(const std::string& path, const std::string& file) {
 #endif
 
   if (FLAGS_enable_model_toolkit) {
-    int err = 0;
-    std::string model_toolkit_path = resource_conf.model_toolkit_path();
-    if (err != 0) {
-      LOG(ERROR) << "read model_toolkit_path failed, path[" << path
-                 << "], file[" << file << "]";
-      return -1;
-    }
-    std::string model_toolkit_file = resource_conf.model_toolkit_file();
-    if (err != 0) {
-      LOG(ERROR) << "read model_toolkit_file failed, path[" << path
-                 << "], file[" << file << "]";
-      return -1;
-    }
-    if (InferManager::instance().proc_initialize(
-            model_toolkit_path.c_str(), model_toolkit_file.c_str()) != 0) {
-      LOG(ERROR) << "failed proc initialize modeltoolkit, config: "
-                 << model_toolkit_path << "/" << model_toolkit_file;
-      return -1;
-    }
+    size_t model_toolkit_num = resource_conf.model_toolkit_path_size();
+    for (size_t mi=0; mi < model_toolkit_num; ++mi) {
+      std::string model_toolkit_path = resource_conf.model_toolkit_path(mi);
+      std::string model_toolkit_file = resource_conf.model_toolkit_file(mi);
+
+      if (InferManager::instance().proc_initialize(
+              model_toolkit_path.c_str(), model_toolkit_file.c_str()) != 0) {
+        LOG(ERROR) << "failed proc initialize modeltoolkit, config: "
+                  << model_toolkit_path << "/" << model_toolkit_file;
+        return -1;
+      }
 
-    if (KVManager::instance().proc_initialize(
-            model_toolkit_path.c_str(), model_toolkit_file.c_str()) != 0) {
-      LOG(ERROR) << "Failed proc initialize kvmanager, config: "
-                 << model_toolkit_path << "/" << model_toolkit_file;
+      if (KVManager::instance().proc_initialize(
+              model_toolkit_path.c_str(), model_toolkit_file.c_str()) != 0) {
+        LOG(ERROR) << "Failed proc initialize kvmanager, config: "
+                  << model_toolkit_path << "/" << model_toolkit_file;
+      }
     }
   }
 
@@ -231,80 +224,79 @@ int Resource::general_model_initialize(const std::string& path,
     LOG(ERROR) << "Failed initialize resource from: " << path << "/" << file;
     return -1;
   }
-  int err = 0;
-  std::string general_model_path = resource_conf.general_model_path();
-  std::string general_model_file = resource_conf.general_model_file();
-  if (err != 0) {
-    LOG(ERROR) << "read general_model_path failed, path[" << path << "], file["
-               << file << "]";
-    return -1;
-  }
+  size_t general_model_num = resource_conf.general_model_path_size();
+  for (size_t gi=0; gi < general_model_num; ++gi) {
 
-  GeneralModelConfig model_config;
-  if (configure::read_proto_conf(general_model_path.c_str(),
-                                 general_model_file.c_str(),
-                                 &model_config) != 0) {
-    LOG(ERROR) << "Failed initialize model config from: " << general_model_path
-               << "/" << general_model_file;
-    return -1;
-  }
 
-  _config.reset(new PaddleGeneralModelConfig());
-  int feed_var_num = model_config.feed_var_size();
-  VLOG(2) << "load general model config";
-  VLOG(2) << "feed var num: " << feed_var_num;
-  _config->_feed_name.resize(feed_var_num);
-  _config->_feed_alias_name.resize(feed_var_num);
-  _config->_feed_type.resize(feed_var_num);
-  _config->_is_lod_feed.resize(feed_var_num);
-  _config->_capacity.resize(feed_var_num);
-  _config->_feed_shape.resize(feed_var_num);
-  for (int i = 0; i < feed_var_num; ++i) {
-    _config->_feed_name[i] = model_config.feed_var(i).name();
-    _config->_feed_alias_name[i] = model_config.feed_var(i).alias_name();
-    VLOG(2) << "feed var[" << i << "]: " << _config->_feed_name[i];
-    VLOG(2) << "feed var[" << i << "]: " << _config->_feed_alias_name[i];
-    _config->_feed_type[i] = model_config.feed_var(i).feed_type();
-    VLOG(2) << "feed type[" << i << "]: " << _config->_feed_type[i];
-
-    if (model_config.feed_var(i).is_lod_tensor()) {
-      VLOG(2) << "var[" << i << "] is lod tensor";
-      _config->_feed_shape[i] = {-1};
-      _config->_is_lod_feed[i] = true;
-    } else {
-      VLOG(2) << "var[" << i << "] is tensor";
-      _config->_capacity[i] = 1;
-      _config->_is_lod_feed[i] = false;
-      for (int j = 0; j < model_config.feed_var(i).shape_size(); ++j) {
-        int32_t dim = model_config.feed_var(i).shape(j);
-        VLOG(2) << "var[" << i << "].shape[" << i << "]: " << dim;
-        _config->_feed_shape[i].push_back(dim);
-        _config->_capacity[i] *= dim;
+    std::string general_model_path = resource_conf.general_model_path(gi);
+    std::string general_model_file = resource_conf.general_model_file(gi);
+
+    GeneralModelConfig model_config;
+    if (configure::read_proto_conf(general_model_path.c_str(),
+                                  general_model_file.c_str(),
+                                  &model_config) != 0) {
+      LOG(ERROR) << "Failed initialize model config from: " << general_model_path
+                << "/" << general_model_file;
+      return -1;
+    }
+    auto _config = std::make_shared();
+    int feed_var_num = model_config.feed_var_size();
+    VLOG(2) << "load general model config";
+    VLOG(2) << "feed var num: " << feed_var_num;
+    _config->_feed_name.resize(feed_var_num);
+    _config->_feed_alias_name.resize(feed_var_num);
+    _config->_feed_type.resize(feed_var_num);
+    _config->_is_lod_feed.resize(feed_var_num);
+    _config->_capacity.resize(feed_var_num);
+    _config->_feed_shape.resize(feed_var_num);
+    for (int i=0; i < feed_var_num; ++i) {
+      _config->_feed_name[i] = model_config.feed_var(i).name();
+      _config->_feed_alias_name[i] = model_config.feed_var(i).alias_name();
+      VLOG(2) << "feed var[" << i << "]: " << _config->_feed_name[i];
+      VLOG(2) << "feed var[" << i << "]: " << _config->_feed_alias_name[i];
+      _config->_feed_type[i] = model_config.feed_var(i).feed_type();
+      VLOG(2) << "feed type[" << i << "]: " << _config->_feed_type[i];
+
+      if (model_config.feed_var(i).is_lod_tensor()) {
+        VLOG(2) << "var[" << i << "] is lod tensor";
+        _config->_feed_shape[i] = {-1};
+        _config->_is_lod_feed[i] = true;
+      } else {
+        VLOG(2) << "var[" << i << "] is tensor";
+        _config->_capacity[i] = 1;
+        _config->_is_lod_feed[i] = false;
+        for (int j=0; j < model_config.feed_var(i).shape_size(); ++j) {
+          int32_t dim = model_config.feed_var(i).shape(j);
+          VLOG(2) << "var[" << i << "].shape[" << i << "]: " << dim;
+          _config->_feed_shape[i].push_back(dim);
+          _config->_capacity[i] *= dim;
+        }
       }
     }
-  }
 
-  int fetch_var_num = model_config.fetch_var_size();
-  _config->_is_lod_fetch.resize(fetch_var_num);
-  _config->_fetch_name.resize(fetch_var_num);
-  _config->_fetch_alias_name.resize(fetch_var_num);
-  _config->_fetch_shape.resize(fetch_var_num);
-  for (int i = 0; i < fetch_var_num; ++i) {
-    _config->_fetch_name[i] = model_config.fetch_var(i).name();
-    _config->_fetch_alias_name[i] = model_config.fetch_var(i).alias_name();
-    _config->_fetch_name_to_index[_config->_fetch_name[i]] = i;
-    _config->_fetch_alias_name_to_index[_config->_fetch_alias_name[i]] = i;
-    if (model_config.fetch_var(i).is_lod_tensor()) {
-      VLOG(2) << "fetch var[" << i << "] is lod tensor";
-      _config->_fetch_shape[i] = {-1};
-      _config->_is_lod_fetch[i] = true;
-    } else {
-      _config->_is_lod_fetch[i] = false;
-      for (int j = 0; j < model_config.fetch_var(i).shape_size(); ++j) {
-        int dim = model_config.fetch_var(i).shape(j);
-        _config->_fetch_shape[i].push_back(dim);
+    int fetch_var_num = model_config.fetch_var_size();
+    _config->_is_lod_fetch.resize(fetch_var_num);
+    _config->_fetch_name.resize(fetch_var_num);
+    _config->_fetch_alias_name.resize(fetch_var_num);
+    _config->_fetch_shape.resize(fetch_var_num);
+    for (int i=0; i < fetch_var_num; ++i) {
+      _config->_fetch_name[i] = model_config.fetch_var(i).name();
+      _config->_fetch_alias_name[i] = model_config.fetch_var(i).alias_name();
+      _config->_fetch_name_to_index[_config->_fetch_name[i]] = i;
+      _config->_fetch_alias_name_to_index[_config->_fetch_alias_name[i]] = i;
+      if (model_config.fetch_var(i).is_lod_tensor()) {
+        VLOG(2) << "fetch var[" << i << "] is lod tensor";
+        _config->_fetch_shape[i] = {-1};
+        _config->_is_lod_fetch[i] = true;
+      } else {
+        _config->_is_lod_fetch[i] = false;
+        for (int j=0; j < model_config.fetch_var(i).shape_size(); ++j) {
+          int dim = model_config.fetch_var(i).shape(j);
+          _config->_fetch_shape[i].push_back(dim);
+        }
       }
     }
+    _configs.push_back(std::move(_config));
   }
   return 0;
 }
diff --git a/core/predictor/framework/resource.h b/core/predictor/framework/resource.h
old mode 100644
new mode 100755
index 56b87666892fab45c2099a4de9bfbc1296b65b11..48a578f9f75488504415aaf449f5b62fc7fc1cb9
--- a/core/predictor/framework/resource.h
+++ b/core/predictor/framework/resource.h
@@ -94,7 +94,7 @@ class Resource {
 
   int finalize();
 
-  std::shared_ptr get_general_model_config();
+  std::vector > get_general_model_config();
 
   void print_general_model_config(
       const std::shared_ptr& config);
@@ -107,7 +107,7 @@ class Resource {
 
  private:
   int thread_finalize() { return 0; }
-  std::shared_ptr _config;
+  std::vector > _configs;
   std::string cube_config_fullpath;
   int cube_quant_bits;  // 0 if no empty
 
diff --git a/core/predictor/framework/service.cpp b/core/predictor/framework/service.cpp
old mode 100644
new mode 100755
diff --git a/core/predictor/op/op.cpp b/core/predictor/op/op.cpp
old mode 100644
new mode 100755
diff --git a/core/predictor/src/pdserving.cpp b/core/predictor/src/pdserving.cpp
old mode 100644
new mode 100755
index 59ec59d9012c94c322eee2ab3f357218deeedbb4..e88d9b3b2aaa03ccbb7f903485bdffecfa6f7222
--- a/core/predictor/src/pdserving.cpp
+++ b/core/predictor/src/pdserving.cpp
@@ -126,7 +126,7 @@ int main(int argc, char** argv) {
     return 0;
   }
 
-  google::ParseCommandLineFlags(&argc, &argv, true);
+  //google::ParseCommandLineFlags(&argc, &argv, true);
 
   g_change_server_port();
 
@@ -202,6 +202,7 @@ int main(int argc, char** argv) {
   }
   VLOG(2) << "Succ call pthread worker start function";
 
+  //this is not used by any code segment,which can be cancelled.
   if (Resource::instance().general_model_initialize(FLAGS_resource_path,
                                                     FLAGS_resource_file) != 0) {
     LOG(ERROR) << "Failed to initialize general model conf: "
diff --git a/core/predictor/tools/ocrtools/clipper.cpp b/core/predictor/tools/ocrtools/clipper.cpp
new file mode 100755
index 0000000000000000000000000000000000000000..0023c6715ea62789b6b9037a37624d7b8af926ac
--- /dev/null
+++ b/core/predictor/tools/ocrtools/clipper.cpp
@@ -0,0 +1,4380 @@
+/*******************************************************************************
+*                                                                              *
+* Author    :  Angus Johnson                                                   *
+* Version   :  6.4.2                                                           *
+* Date      :  27 February 2017                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2017                                         *
+*                                                                              *
+* License:                                                                     *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt                                         *
+*                                                                              *
+* Attributions:                                                                *
+* The code in this library is an extension of Bala Vatti's clipping algorithm: *
+* "A generic solution to polygon clipping"                                     *
+* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63.             *
+* http://portal.acm.org/citation.cfm?id=129906                                 *
+*                                                                              *
+* Computer graphics and geometric modeling: implementation and algorithms      *
+* By Max K. Agoston                                                            *
+* Springer; 1 edition (January 4, 2005)                                        *
+* http://books.google.com/books?q=vatti+clipping+agoston                       *
+*                                                                              *
+* See also:                                                                    *
+* "Polygon Offsetting by Computing Winding Numbers"                            *
+* Paper no. DETC2005-85513 pp. 565-575                                         *
+* ASME 2005 International Design Engineering Technical Conferences             *
+* and Computers and Information in Engineering Conference (IDETC/CIE2005)      *
+* September 24-28, 2005 , Long Beach, California, USA                          *
+* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf              *
+*                                                                              *
+*******************************************************************************/
+
+/*******************************************************************************
+*                                                                              *
+* This is a translation of the Delphi Clipper library and the naming style     *
+* used has retained a Delphi flavour.                                          *
+*                                                                              *
+*******************************************************************************/
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "clipper.h"
+
+namespace ClipperLib {
+
+static double const pi = 3.141592653589793238;
+static double const two_pi = pi * 2;
+static double const def_arc_tolerance = 0.25;
+
+enum Direction { dRightToLeft, dLeftToRight };
+
+static int const Unassigned = -1; // edge not currently 'owning' a solution
+static int const Skip = -2;       // edge that would otherwise close a path
+
+#define HORIZONTAL (-1.0E+40)
+#define TOLERANCE (1.0e-20)
+#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE))
+
+struct TEdge {
+  IntPoint Bot;
+  IntPoint Curr; // current (updated for every new scanbeam)
+  IntPoint Top;
+  double Dx;
+  PolyType PolyTyp;
+  EdgeSide Side; // side only refers to current side of solution poly
+  int WindDelta; // 1 or -1 depending on winding direction
+  int WindCnt;
+  int WindCnt2; // winding count of the opposite polytype
+  int OutIdx;
+  TEdge *Next;
+  TEdge *Prev;
+  TEdge *NextInLML;
+  TEdge *NextInAEL;
+  TEdge *PrevInAEL;
+  TEdge *NextInSEL;
+  TEdge *PrevInSEL;
+};
+
+struct IntersectNode {
+  TEdge *Edge1;
+  TEdge *Edge2;
+  IntPoint Pt;
+};
+
+struct LocalMinimum {
+  cInt Y;
+  TEdge *LeftBound;
+  TEdge *RightBound;
+};
+
+struct OutPt;
+
+// OutRec: contains a path in the clipping solution. Edges in the AEL will
+// carry a pointer to an OutRec when they are part of the clipping solution.
+struct OutRec {
+  int Idx;
+  bool IsHole;
+  bool IsOpen;
+  OutRec *FirstLeft; // see comments in clipper.pas
+  PolyNode *PolyNd;
+  OutPt *Pts;
+  OutPt *BottomPt;
+};
+
+struct OutPt {
+  int Idx;
+  IntPoint Pt;
+  OutPt *Next;
+  OutPt *Prev;
+};
+
+struct Join {
+  OutPt *OutPt1;
+  OutPt *OutPt2;
+  IntPoint OffPt;
+};
+
+struct LocMinSorter {
+  inline bool operator()(const LocalMinimum &locMin1,
+                         const LocalMinimum &locMin2) {
+    return locMin2.Y < locMin1.Y;
+  }
+};
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+
+inline cInt Round(double val) {
+  if ((val < 0))
+    return static_cast(val - 0.5);
+  else
+    return static_cast(val + 0.5);
+}
+//------------------------------------------------------------------------------
+
+inline cInt Abs(cInt val) { return val < 0 ? -val : val; }
+
+//------------------------------------------------------------------------------
+// PolyTree methods ...
+//------------------------------------------------------------------------------
+
+void PolyTree::Clear() {
+  for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i)
+    delete AllNodes[i];
+  AllNodes.resize(0);
+  Childs.resize(0);
+}
+//------------------------------------------------------------------------------
+
+PolyNode *PolyTree::GetFirst() const {
+  if (!Childs.empty())
+    return Childs[0];
+  else
+    return 0;
+}
+//------------------------------------------------------------------------------
+
+int PolyTree::Total() const {
+  int result = (int)AllNodes.size();
+  // with negative offsets, ignore the hidden outer polygon ...
+  if (result > 0 && Childs[0] != AllNodes[0])
+    result--;
+  return result;
+}
+
+//------------------------------------------------------------------------------
+// PolyNode methods ...
+//------------------------------------------------------------------------------
+
+PolyNode::PolyNode() : Parent(0), Index(0), m_IsOpen(false) {}
+//------------------------------------------------------------------------------
+
+int PolyNode::ChildCount() const { return (int)Childs.size(); }
+//------------------------------------------------------------------------------
+
+void PolyNode::AddChild(PolyNode &child) {
+  unsigned cnt = (unsigned)Childs.size();
+  Childs.push_back(&child);
+  child.Parent = this;
+  child.Index = cnt;
+}
+//------------------------------------------------------------------------------
+
+PolyNode *PolyNode::GetNext() const {
+  if (!Childs.empty())
+    return Childs[0];
+  else
+    return GetNextSiblingUp();
+}
+//------------------------------------------------------------------------------
+
+PolyNode *PolyNode::GetNextSiblingUp() const {
+  if (!Parent) // protects against PolyTree.GetNextSiblingUp()
+    return 0;
+  else if (Index == Parent->Childs.size() - 1)
+    return Parent->GetNextSiblingUp();
+  else
+    return Parent->Childs[Index + 1];
+}
+//------------------------------------------------------------------------------
+
+bool PolyNode::IsHole() const {
+  bool result = true;
+  PolyNode *node = Parent;
+  while (node) {
+    result = !result;
+    node = node->Parent;
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+bool PolyNode::IsOpen() const { return m_IsOpen; }
+//------------------------------------------------------------------------------
+
+#ifndef use_int32
+
+//------------------------------------------------------------------------------
+// Int128 class (enables safe math on signed 64bit integers)
+// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1
+//    Int128 val2((long64)9223372036854775807);
+//    Int128 val3 = val1 * val2;
+//    val3.AsString => "85070591730234615847396907784232501249" (8.5e+37)
+//------------------------------------------------------------------------------
+
+class Int128 {
+public:
+  ulong64 lo;
+  long64 hi;
+
+  Int128(long64 _lo = 0) {
+    lo = (ulong64)_lo;
+    if (_lo < 0)
+      hi = -1;
+    else
+      hi = 0;
+  }
+
+  Int128(const Int128 &val) : lo(val.lo), hi(val.hi) {}
+
+  Int128(const long64 &_hi, const ulong64 &_lo) : lo(_lo), hi(_hi) {}
+
+  Int128 &operator=(const long64 &val) {
+    lo = (ulong64)val;
+    if (val < 0)
+      hi = -1;
+    else
+      hi = 0;
+    return *this;
+  }
+
+  bool operator==(const Int128 &val) const {
+    return (hi == val.hi && lo == val.lo);
+  }
+
+  bool operator!=(const Int128 &val) const { return !(*this == val); }
+
+  bool operator>(const Int128 &val) const {
+    if (hi != val.hi)
+      return hi > val.hi;
+    else
+      return lo > val.lo;
+  }
+
+  bool operator<(const Int128 &val) const {
+    if (hi != val.hi)
+      return hi < val.hi;
+    else
+      return lo < val.lo;
+  }
+
+  bool operator>=(const Int128 &val) const { return !(*this < val); }
+
+  bool operator<=(const Int128 &val) const { return !(*this > val); }
+
+  Int128 &operator+=(const Int128 &rhs) {
+    hi += rhs.hi;
+    lo += rhs.lo;
+    if (lo < rhs.lo)
+      hi++;
+    return *this;
+  }
+
+  Int128 operator+(const Int128 &rhs) const {
+    Int128 result(*this);
+    result += rhs;
+    return result;
+  }
+
+  Int128 &operator-=(const Int128 &rhs) {
+    *this += -rhs;
+    return *this;
+  }
+
+  Int128 operator-(const Int128 &rhs) const {
+    Int128 result(*this);
+    result -= rhs;
+    return result;
+  }
+
+  Int128 operator-() const // unary negation
+  {
+    if (lo == 0)
+      return Int128(-hi, 0);
+    else
+      return Int128(~hi, ~lo + 1);
+  }
+
+  operator double() const {
+    const double shift64 = 18446744073709551616.0; // 2^64
+    if (hi < 0) {
+      if (lo == 0)
+        return (double)hi * shift64;
+      else
+        return -(double)(~lo + ~hi * shift64);
+    } else
+      return (double)(lo + hi * shift64);
+  }
+};
+//------------------------------------------------------------------------------
+
+Int128 Int128Mul(long64 lhs, long64 rhs) {
+  bool negate = (lhs < 0) != (rhs < 0);
+
+  if (lhs < 0)
+    lhs = -lhs;
+  ulong64 int1Hi = ulong64(lhs) >> 32;
+  ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF);
+
+  if (rhs < 0)
+    rhs = -rhs;
+  ulong64 int2Hi = ulong64(rhs) >> 32;
+  ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF);
+
+  // nb: see comments in clipper.pas
+  ulong64 a = int1Hi * int2Hi;
+  ulong64 b = int1Lo * int2Lo;
+  ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi;
+
+  Int128 tmp;
+  tmp.hi = long64(a + (c >> 32));
+  tmp.lo = long64(c << 32);
+  tmp.lo += long64(b);
+  if (tmp.lo < b)
+    tmp.hi++;
+  if (negate)
+    tmp = -tmp;
+  return tmp;
+};
+#endif
+
+//------------------------------------------------------------------------------
+// Miscellaneous global functions
+//------------------------------------------------------------------------------
+
+bool Orientation(const Path &poly) { return Area(poly) >= 0; }
+//------------------------------------------------------------------------------
+
+double Area(const Path &poly) {
+  int size = (int)poly.size();
+  if (size < 3)
+    return 0;
+
+  double a = 0;
+  for (int i = 0, j = size - 1; i < size; ++i) {
+    a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y);
+    j = i;
+  }
+  return -a * 0.5;
+}
+//------------------------------------------------------------------------------
+
+double Area(const OutPt *op) {
+  const OutPt *startOp = op;
+  if (!op)
+    return 0;
+  double a = 0;
+  do {
+    a += (double)(op->Prev->Pt.X + op->Pt.X) *
+         (double)(op->Prev->Pt.Y - op->Pt.Y);
+    op = op->Next;
+  } while (op != startOp);
+  return a * 0.5;
+}
+//------------------------------------------------------------------------------
+
+double Area(const OutRec &outRec) { return Area(outRec.Pts); }
+//------------------------------------------------------------------------------
+
+bool PointIsVertex(const IntPoint &Pt, OutPt *pp) {
+  OutPt *pp2 = pp;
+  do {
+    if (pp2->Pt == Pt)
+      return true;
+    pp2 = pp2->Next;
+  } while (pp2 != pp);
+  return false;
+}
+//------------------------------------------------------------------------------
+
+// See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann &
+// Agathos
+// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
+int PointInPolygon(const IntPoint &pt, const Path &path) {
+  // returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+  int result = 0;
+  size_t cnt = path.size();
+  if (cnt < 3)
+    return 0;
+  IntPoint ip = path[0];
+  for (size_t i = 1; i <= cnt; ++i) {
+    IntPoint ipNext = (i == cnt ? path[0] : path[i]);
+    if (ipNext.Y == pt.Y) {
+      if ((ipNext.X == pt.X) ||
+          (ip.Y == pt.Y && ((ipNext.X > pt.X) == (ip.X < pt.X))))
+        return -1;
+    }
+    if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) {
+      if (ip.X >= pt.X) {
+        if (ipNext.X > pt.X)
+          result = 1 - result;
+        else {
+          double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
+                     (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
+          if (!d)
+            return -1;
+          if ((d > 0) == (ipNext.Y > ip.Y))
+            result = 1 - result;
+        }
+      } else {
+        if (ipNext.X > pt.X) {
+          double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
+                     (double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
+          if (!d)
+            return -1;
+          if ((d > 0) == (ipNext.Y > ip.Y))
+            result = 1 - result;
+        }
+      }
+    }
+    ip = ipNext;
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+int PointInPolygon(const IntPoint &pt, OutPt *op) {
+  // returns 0 if false, +1 if true, -1 if pt ON polygon boundary
+  int result = 0;
+  OutPt *startOp = op;
+  for (;;) {
+    if (op->Next->Pt.Y == pt.Y) {
+      if ((op->Next->Pt.X == pt.X) ||
+          (op->Pt.Y == pt.Y && ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X))))
+        return -1;
+    }
+    if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) {
+      if (op->Pt.X >= pt.X) {
+        if (op->Next->Pt.X > pt.X)
+          result = 1 - result;
+        else {
+          double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
+                     (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
+          if (!d)
+            return -1;
+          if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
+            result = 1 - result;
+        }
+      } else {
+        if (op->Next->Pt.X > pt.X) {
+          double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
+                     (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
+          if (!d)
+            return -1;
+          if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
+            result = 1 - result;
+        }
+      }
+    }
+    op = op->Next;
+    if (startOp == op)
+      break;
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) {
+  OutPt *op = OutPt1;
+  do {
+    // nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
+    int res = PointInPolygon(op->Pt, OutPt2);
+    if (res >= 0)
+      return res > 0;
+    op = op->Next;
+  } while (op != OutPt1);
+  return true;
+}
+//----------------------------------------------------------------------
+
+bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) {
+#ifndef use_int32
+  if (UseFullInt64Range)
+    return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) ==
+           Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y);
+  else
+#endif
+    return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) ==
+           (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y);
+}
+//------------------------------------------------------------------------------
+
+bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3,
+                 bool UseFullInt64Range) {
+#ifndef use_int32
+  if (UseFullInt64Range)
+    return Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) ==
+           Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y);
+  else
+#endif
+    return (pt1.Y - pt2.Y) * (pt2.X - pt3.X) ==
+           (pt1.X - pt2.X) * (pt2.Y - pt3.Y);
+}
+//------------------------------------------------------------------------------
+
+bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3,
+                 const IntPoint pt4, bool UseFullInt64Range) {
+#ifndef use_int32
+  if (UseFullInt64Range)
+    return Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) ==
+           Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y);
+  else
+#endif
+    return (pt1.Y - pt2.Y) * (pt3.X - pt4.X) ==
+           (pt1.X - pt2.X) * (pt3.Y - pt4.Y);
+}
+//------------------------------------------------------------------------------
+
+inline bool IsHorizontal(TEdge &e) { return e.Dx == HORIZONTAL; }
+//------------------------------------------------------------------------------
+
+inline double GetDx(const IntPoint pt1, const IntPoint pt2) {
+  return (pt1.Y == pt2.Y) ? HORIZONTAL
+                          : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y);
+}
+//---------------------------------------------------------------------------
+
+inline void SetDx(TEdge &e) {
+  cInt dy = (e.Top.Y - e.Bot.Y);
+  if (dy == 0)
+    e.Dx = HORIZONTAL;
+  else
+    e.Dx = (double)(e.Top.X - e.Bot.X) / dy;
+}
+//---------------------------------------------------------------------------
+
+inline void SwapSides(TEdge &Edge1, TEdge &Edge2) {
+  EdgeSide Side = Edge1.Side;
+  Edge1.Side = Edge2.Side;
+  Edge2.Side = Side;
+}
+//------------------------------------------------------------------------------
+
+inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) {
+  int OutIdx = Edge1.OutIdx;
+  Edge1.OutIdx = Edge2.OutIdx;
+  Edge2.OutIdx = OutIdx;
+}
+//------------------------------------------------------------------------------
+
+inline cInt TopX(TEdge &edge, const cInt currentY) {
+  return (currentY == edge.Top.Y)
+             ? edge.Top.X
+             : edge.Bot.X + Round(edge.Dx * (currentY - edge.Bot.Y));
+}
+//------------------------------------------------------------------------------
+
+void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) {
+#ifdef use_xyz
+  ip.Z = 0;
+#endif
+
+  double b1, b2;
+  if (Edge1.Dx == Edge2.Dx) {
+    ip.Y = Edge1.Curr.Y;
+    ip.X = TopX(Edge1, ip.Y);
+    return;
+  } else if (Edge1.Dx == 0) {
+    ip.X = Edge1.Bot.X;
+    if (IsHorizontal(Edge2))
+      ip.Y = Edge2.Bot.Y;
+    else {
+      b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx);
+      ip.Y = Round(ip.X / Edge2.Dx + b2);
+    }
+  } else if (Edge2.Dx == 0) {
+    ip.X = Edge2.Bot.X;
+    if (IsHorizontal(Edge1))
+      ip.Y = Edge1.Bot.Y;
+    else {
+      b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx);
+      ip.Y = Round(ip.X / Edge1.Dx + b1);
+    }
+  } else {
+    b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx;
+    b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx;
+    double q = (b2 - b1) / (Edge1.Dx - Edge2.Dx);
+    ip.Y = Round(q);
+    if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
+      ip.X = Round(Edge1.Dx * q + b1);
+    else
+      ip.X = Round(Edge2.Dx * q + b2);
+  }
+
+  if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) {
+    if (Edge1.Top.Y > Edge2.Top.Y)
+      ip.Y = Edge1.Top.Y;
+    else
+      ip.Y = Edge2.Top.Y;
+    if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
+      ip.X = TopX(Edge1, ip.Y);
+    else
+      ip.X = TopX(Edge2, ip.Y);
+  }
+  // finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ...
+  if (ip.Y > Edge1.Curr.Y) {
+    ip.Y = Edge1.Curr.Y;
+    // use the more vertical edge to derive X ...
+    if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx))
+      ip.X = TopX(Edge2, ip.Y);
+    else
+      ip.X = TopX(Edge1, ip.Y);
+  }
+}
+//------------------------------------------------------------------------------
+
+void ReversePolyPtLinks(OutPt *pp) {
+  if (!pp)
+    return;
+  OutPt *pp1, *pp2;
+  pp1 = pp;
+  do {
+    pp2 = pp1->Next;
+    pp1->Next = pp1->Prev;
+    pp1->Prev = pp2;
+    pp1 = pp2;
+  } while (pp1 != pp);
+}
+//------------------------------------------------------------------------------
+
+void DisposeOutPts(OutPt *&pp) {
+  if (pp == 0)
+    return;
+  pp->Prev->Next = 0;
+  while (pp) {
+    OutPt *tmpPp = pp;
+    pp = pp->Next;
+    delete tmpPp;
+  }
+}
+//------------------------------------------------------------------------------
+
+inline void InitEdge(TEdge *e, TEdge *eNext, TEdge *ePrev, const IntPoint &Pt) {
+  std::memset(e, 0, sizeof(TEdge));
+  e->Next = eNext;
+  e->Prev = ePrev;
+  e->Curr = Pt;
+  e->OutIdx = Unassigned;
+}
+//------------------------------------------------------------------------------
+
+void InitEdge2(TEdge &e, PolyType Pt) {
+  if (e.Curr.Y >= e.Next->Curr.Y) {
+    e.Bot = e.Curr;
+    e.Top = e.Next->Curr;
+  } else {
+    e.Top = e.Curr;
+    e.Bot = e.Next->Curr;
+  }
+  SetDx(e);
+  e.PolyTyp = Pt;
+}
+//------------------------------------------------------------------------------
+
+TEdge *RemoveEdge(TEdge *e) {
+  // removes e from double_linked_list (but without removing from memory)
+  e->Prev->Next = e->Next;
+  e->Next->Prev = e->Prev;
+  TEdge *result = e->Next;
+  e->Prev = 0; // flag as removed (see ClipperBase.Clear)
+  return result;
+}
+//------------------------------------------------------------------------------
+
+inline void ReverseHorizontal(TEdge &e) {
+  // swap horizontal edges' Top and Bottom x's so they follow the natural
+  // progression of the bounds - ie so their xbots will align with the
+  // adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
+  std::swap(e.Top.X, e.Bot.X);
+#ifdef use_xyz
+  std::swap(e.Top.Z, e.Bot.Z);
+#endif
+}
+//------------------------------------------------------------------------------
+
+void SwapPoints(IntPoint &pt1, IntPoint &pt2) {
+  IntPoint tmp = pt1;
+  pt1 = pt2;
+  pt2 = tmp;
+}
+//------------------------------------------------------------------------------
+
+bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a,
+                       IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) {
+  // precondition: segments are Collinear.
+  if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) {
+    if (pt1a.X > pt1b.X)
+      SwapPoints(pt1a, pt1b);
+    if (pt2a.X > pt2b.X)
+      SwapPoints(pt2a, pt2b);
+    if (pt1a.X > pt2a.X)
+      pt1 = pt1a;
+    else
+      pt1 = pt2a;
+    if (pt1b.X < pt2b.X)
+      pt2 = pt1b;
+    else
+      pt2 = pt2b;
+    return pt1.X < pt2.X;
+  } else {
+    if (pt1a.Y < pt1b.Y)
+      SwapPoints(pt1a, pt1b);
+    if (pt2a.Y < pt2b.Y)
+      SwapPoints(pt2a, pt2b);
+    if (pt1a.Y < pt2a.Y)
+      pt1 = pt1a;
+    else
+      pt1 = pt2a;
+    if (pt1b.Y > pt2b.Y)
+      pt2 = pt1b;
+    else
+      pt2 = pt2b;
+    return pt1.Y > pt2.Y;
+  }
+}
+//------------------------------------------------------------------------------
+
+bool FirstIsBottomPt(const OutPt *btmPt1, const OutPt *btmPt2) {
+  OutPt *p = btmPt1->Prev;
+  while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
+    p = p->Prev;
+  double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt));
+  p = btmPt1->Next;
+  while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
+    p = p->Next;
+  double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt));
+
+  p = btmPt2->Prev;
+  while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
+    p = p->Prev;
+  double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt));
+  p = btmPt2->Next;
+  while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
+    p = p->Next;
+  double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt));
+
+  if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) &&
+      std::min(dx1p, dx1n) == std::min(dx2p, dx2n))
+    return Area(btmPt1) > 0; // if otherwise identical use orientation
+  else
+    return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
+}
+//------------------------------------------------------------------------------
+
+OutPt *GetBottomPt(OutPt *pp) {
+  OutPt *dups = 0;
+  OutPt *p = pp->Next;
+  while (p != pp) {
+    if (p->Pt.Y > pp->Pt.Y) {
+      pp = p;
+      dups = 0;
+    } else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) {
+      if (p->Pt.X < pp->Pt.X) {
+        dups = 0;
+        pp = p;
+      } else {
+        if (p->Next != pp && p->Prev != pp)
+          dups = p;
+      }
+    }
+    p = p->Next;
+  }
+  if (dups) {
+    // there appears to be at least 2 vertices at BottomPt so ...
+    while (dups != p) {
+      if (!FirstIsBottomPt(p, dups))
+        pp = dups;
+      dups = dups->Next;
+      while (dups->Pt != pp->Pt)
+        dups = dups->Next;
+    }
+  }
+  return pp;
+}
+//------------------------------------------------------------------------------
+
+bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, const IntPoint pt2,
+                           const IntPoint pt3) {
+  if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2))
+    return false;
+  else if (pt1.X != pt3.X)
+    return (pt2.X > pt1.X) == (pt2.X < pt3.X);
+  else
+    return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y);
+}
+//------------------------------------------------------------------------------
+
+bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) {
+  if (seg1a > seg1b)
+    std::swap(seg1a, seg1b);
+  if (seg2a > seg2b)
+    std::swap(seg2a, seg2b);
+  return (seg1a < seg2b) && (seg2a < seg1b);
+}
+
+//------------------------------------------------------------------------------
+// ClipperBase class methods ...
+//------------------------------------------------------------------------------
+
+ClipperBase::ClipperBase() // constructor
+{
+  m_CurrentLM = m_MinimaList.begin(); // begin() == end() here
+  m_UseFullRange = false;
+}
+//------------------------------------------------------------------------------
+
+ClipperBase::~ClipperBase() // destructor
+{
+  Clear();
+}
+//------------------------------------------------------------------------------
+
+void RangeTest(const IntPoint &Pt, bool &useFullRange) {
+  if (useFullRange) {
+    if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange)
+      throw clipperException("Coordinate outside allowed range");
+  } else if (Pt.X > loRange || Pt.Y > loRange || -Pt.X > loRange ||
+             -Pt.Y > loRange) {
+    useFullRange = true;
+    RangeTest(Pt, useFullRange);
+  }
+}
+//------------------------------------------------------------------------------
+
+TEdge *FindNextLocMin(TEdge *E) {
+  for (;;) {
+    while (E->Bot != E->Prev->Bot || E->Curr == E->Top)
+      E = E->Next;
+    if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev))
+      break;
+    while (IsHorizontal(*E->Prev))
+      E = E->Prev;
+    TEdge *E2 = E;
+    while (IsHorizontal(*E))
+      E = E->Next;
+    if (E->Top.Y == E->Prev->Bot.Y)
+      continue; // ie just an intermediate horz.
+    if (E2->Prev->Bot.X < E->Bot.X)
+      E = E2;
+    break;
+  }
+  return E;
+}
+//------------------------------------------------------------------------------
+
+TEdge *ClipperBase::ProcessBound(TEdge *E, bool NextIsForward) {
+  TEdge *Result = E;
+  TEdge *Horz = 0;
+
+  if (E->OutIdx == Skip) {
+    // if edges still remain in the current bound beyond the skip edge then
+    // create another LocMin and call ProcessBound once more
+    if (NextIsForward) {
+      while (E->Top.Y == E->Next->Bot.Y)
+        E = E->Next;
+      // don't include top horizontals when parsing a bound a second time,
+      // they will be contained in the opposite bound ...
+      while (E != Result && IsHorizontal(*E))
+        E = E->Prev;
+    } else {
+      while (E->Top.Y == E->Prev->Bot.Y)
+        E = E->Prev;
+      while (E != Result && IsHorizontal(*E))
+        E = E->Next;
+    }
+
+    if (E == Result) {
+      if (NextIsForward)
+        Result = E->Next;
+      else
+        Result = E->Prev;
+    } else {
+      // there are more edges in the bound beyond result starting with E
+      if (NextIsForward)
+        E = Result->Next;
+      else
+        E = Result->Prev;
+      MinimaList::value_type locMin;
+      locMin.Y = E->Bot.Y;
+      locMin.LeftBound = 0;
+      locMin.RightBound = E;
+      E->WindDelta = 0;
+      Result = ProcessBound(E, NextIsForward);
+      m_MinimaList.push_back(locMin);
+    }
+    return Result;
+  }
+
+  TEdge *EStart;
+
+  if (IsHorizontal(*E)) {
+    // We need to be careful with open paths because this may not be a
+    // true local minima (ie E may be following a skip edge).
+    // Also, consecutive horz. edges may start heading left before going right.
+    if (NextIsForward)
+      EStart = E->Prev;
+    else
+      EStart = E->Next;
+    if (IsHorizontal(*EStart)) // ie an adjoining horizontal skip edge
+    {
+      if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X)
+        ReverseHorizontal(*E);
+    } else if (EStart->Bot.X != E->Bot.X)
+      ReverseHorizontal(*E);
+  }
+
+  EStart = E;
+  if (NextIsForward) {
+    while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip)
+      Result = Result->Next;
+    if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) {
+      // nb: at the top of a bound, horizontals are added to the bound
+      // only when the preceding edge attaches to the horizontal's left vertex
+      // unless a Skip edge is encountered when that becomes the top divide
+      Horz = Result;
+      while (IsHorizontal(*Horz->Prev))
+        Horz = Horz->Prev;
+      if (Horz->Prev->Top.X > Result->Next->Top.X)
+        Result = Horz->Prev;
+    }
+    while (E != Result) {
+      E->NextInLML = E->Next;
+      if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X)
+        ReverseHorizontal(*E);
+      E = E->Next;
+    }
+    if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X)
+      ReverseHorizontal(*E);
+    Result = Result->Next; // move to the edge just beyond current bound
+  } else {
+    while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip)
+      Result = Result->Prev;
+    if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) {
+      Horz = Result;
+      while (IsHorizontal(*Horz->Next))
+        Horz = Horz->Next;
+      if (Horz->Next->Top.X == Result->Prev->Top.X ||
+          Horz->Next->Top.X > Result->Prev->Top.X)
+        Result = Horz->Next;
+    }
+
+    while (E != Result) {
+      E->NextInLML = E->Prev;
+      if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
+        ReverseHorizontal(*E);
+      E = E->Prev;
+    }
+    if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
+      ReverseHorizontal(*E);
+    Result = Result->Prev; // move to the edge just beyond current bound
+  }
+
+  return Result;
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) {
+#ifdef use_lines
+  if (!Closed && PolyTyp == ptClip)
+    throw clipperException("AddPath: Open paths must be subject.");
+#else
+  if (!Closed)
+    throw clipperException("AddPath: Open paths have been disabled.");
+#endif
+
+  int highI = (int)pg.size() - 1;
+  if (Closed)
+    while (highI > 0 && (pg[highI] == pg[0]))
+      --highI;
+  while (highI > 0 && (pg[highI] == pg[highI - 1]))
+    --highI;
+  if ((Closed && highI < 2) || (!Closed && highI < 1))
+    return false;
+
+  // create a new edge array ...
+  TEdge *edges = new TEdge[highI + 1];
+
+  bool IsFlat = true;
+  // 1. Basic (first) edge initialization ...
+  try {
+    edges[1].Curr = pg[1];
+    RangeTest(pg[0], m_UseFullRange);
+    RangeTest(pg[highI], m_UseFullRange);
+    InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]);
+    InitEdge(&edges[highI], &edges[0], &edges[highI - 1], pg[highI]);
+    for (int i = highI - 1; i >= 1; --i) {
+      RangeTest(pg[i], m_UseFullRange);
+      InitEdge(&edges[i], &edges[i + 1], &edges[i - 1], pg[i]);
+    }
+  } catch (...) {
+    delete[] edges;
+    throw; // range test fails
+  }
+  TEdge *eStart = &edges[0];
+
+  // 2. Remove duplicate vertices, and (when closed) collinear edges ...
+  TEdge *E = eStart, *eLoopStop = eStart;
+  for (;;) {
+    // nb: allows matching start and end points when not Closed ...
+    if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) {
+      if (E == E->Next)
+        break;
+      if (E == eStart)
+        eStart = E->Next;
+      E = RemoveEdge(E);
+      eLoopStop = E;
+      continue;
+    }
+    if (E->Prev == E->Next)
+      break; // only two vertices
+    else if (Closed && SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr,
+                                   m_UseFullRange) &&
+             (!m_PreserveCollinear ||
+              !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) {
+      // Collinear edges are allowed for open paths but in closed paths
+      // the default is to merge adjacent collinear edges into a single edge.
+      // However, if the PreserveCollinear property is enabled, only overlapping
+      // collinear edges (ie spikes) will be removed from closed paths.
+      if (E == eStart)
+        eStart = E->Next;
+      E = RemoveEdge(E);
+      E = E->Prev;
+      eLoopStop = E;
+      continue;
+    }
+    E = E->Next;
+    if ((E == eLoopStop) || (!Closed && E->Next == eStart))
+      break;
+  }
+
+  if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) {
+    delete[] edges;
+    return false;
+  }
+
+  if (!Closed) {
+    m_HasOpenPaths = true;
+    eStart->Prev->OutIdx = Skip;
+  }
+
+  // 3. Do second stage of edge initialization ...
+  E = eStart;
+  do {
+    InitEdge2(*E, PolyTyp);
+    E = E->Next;
+    if (IsFlat && E->Curr.Y != eStart->Curr.Y)
+      IsFlat = false;
+  } while (E != eStart);
+
+  // 4. Finally, add edge bounds to LocalMinima list ...
+
+  // Totally flat paths must be handled differently when adding them
+  // to LocalMinima list to avoid endless loops etc ...
+  if (IsFlat) {
+    if (Closed) {
+      delete[] edges;
+      return false;
+    }
+    E->Prev->OutIdx = Skip;
+    MinimaList::value_type locMin;
+    locMin.Y = E->Bot.Y;
+    locMin.LeftBound = 0;
+    locMin.RightBound = E;
+    locMin.RightBound->Side = esRight;
+    locMin.RightBound->WindDelta = 0;
+    for (;;) {
+      if (E->Bot.X != E->Prev->Top.X)
+        ReverseHorizontal(*E);
+      if (E->Next->OutIdx == Skip)
+        break;
+      E->NextInLML = E->Next;
+      E = E->Next;
+    }
+    m_MinimaList.push_back(locMin);
+    m_edges.push_back(edges);
+    return true;
+  }
+
+  m_edges.push_back(edges);
+  bool leftBoundIsForward;
+  TEdge *EMin = 0;
+
+  // workaround to avoid an endless loop in the while loop below when
+  // open paths have matching start and end points ...
+  if (E->Prev->Bot == E->Prev->Top)
+    E = E->Next;
+
+  for (;;) {
+    E = FindNextLocMin(E);
+    if (E == EMin)
+      break;
+    else if (!EMin)
+      EMin = E;
+
+    // E and E.Prev now share a local minima (left aligned if horizontal).
+    // Compare their slopes to find which starts which bound ...
+    MinimaList::value_type locMin;
+    locMin.Y = E->Bot.Y;
+    if (E->Dx < E->Prev->Dx) {
+      locMin.LeftBound = E->Prev;
+      locMin.RightBound = E;
+      leftBoundIsForward = false; // Q.nextInLML = Q.prev
+    } else {
+      locMin.LeftBound = E;
+      locMin.RightBound = E->Prev;
+      leftBoundIsForward = true; // Q.nextInLML = Q.next
+    }
+
+    if (!Closed)
+      locMin.LeftBound->WindDelta = 0;
+    else if (locMin.LeftBound->Next == locMin.RightBound)
+      locMin.LeftBound->WindDelta = -1;
+    else
+      locMin.LeftBound->WindDelta = 1;
+    locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta;
+
+    E = ProcessBound(locMin.LeftBound, leftBoundIsForward);
+    if (E->OutIdx == Skip)
+      E = ProcessBound(E, leftBoundIsForward);
+
+    TEdge *E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward);
+    if (E2->OutIdx == Skip)
+      E2 = ProcessBound(E2, !leftBoundIsForward);
+
+    if (locMin.LeftBound->OutIdx == Skip)
+      locMin.LeftBound = 0;
+    else if (locMin.RightBound->OutIdx == Skip)
+      locMin.RightBound = 0;
+    m_MinimaList.push_back(locMin);
+    if (!leftBoundIsForward)
+      E = E2;
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) {
+  bool result = false;
+  for (Paths::size_type i = 0; i < ppg.size(); ++i)
+    if (AddPath(ppg[i], PolyTyp, Closed))
+      result = true;
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::Clear() {
+  DisposeLocalMinimaList();
+  for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) {
+    TEdge *edges = m_edges[i];
+    delete[] edges;
+  }
+  m_edges.clear();
+  m_UseFullRange = false;
+  m_HasOpenPaths = false;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::Reset() {
+  m_CurrentLM = m_MinimaList.begin();
+  if (m_CurrentLM == m_MinimaList.end())
+    return; // ie nothing to process
+  std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter());
+
+  m_Scanbeam = ScanbeamList(); // clears/resets priority_queue
+  // reset all edges ...
+  for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end();
+       ++lm) {
+    InsertScanbeam(lm->Y);
+    TEdge *e = lm->LeftBound;
+    if (e) {
+      e->Curr = e->Bot;
+      e->Side = esLeft;
+      e->OutIdx = Unassigned;
+    }
+
+    e = lm->RightBound;
+    if (e) {
+      e->Curr = e->Bot;
+      e->Side = esRight;
+      e->OutIdx = Unassigned;
+    }
+  }
+  m_ActiveEdges = 0;
+  m_CurrentLM = m_MinimaList.begin();
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::DisposeLocalMinimaList() {
+  m_MinimaList.clear();
+  m_CurrentLM = m_MinimaList.begin();
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) {
+  if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y)
+    return false;
+  locMin = &(*m_CurrentLM);
+  ++m_CurrentLM;
+  return true;
+}
+//------------------------------------------------------------------------------
+
+IntRect ClipperBase::GetBounds() {
+  IntRect result;
+  MinimaList::iterator lm = m_MinimaList.begin();
+  if (lm == m_MinimaList.end()) {
+    result.left = result.top = result.right = result.bottom = 0;
+    return result;
+  }
+  result.left = lm->LeftBound->Bot.X;
+  result.top = lm->LeftBound->Bot.Y;
+  result.right = lm->LeftBound->Bot.X;
+  result.bottom = lm->LeftBound->Bot.Y;
+  while (lm != m_MinimaList.end()) {
+    // todo - needs fixing for open paths
+    result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y);
+    TEdge *e = lm->LeftBound;
+    for (;;) {
+      TEdge *bottomE = e;
+      while (e->NextInLML) {
+        if (e->Bot.X < result.left)
+          result.left = e->Bot.X;
+        if (e->Bot.X > result.right)
+          result.right = e->Bot.X;
+        e = e->NextInLML;
+      }
+      result.left = std::min(result.left, e->Bot.X);
+      result.right = std::max(result.right, e->Bot.X);
+      result.left = std::min(result.left, e->Top.X);
+      result.right = std::max(result.right, e->Top.X);
+      result.top = std::min(result.top, e->Top.Y);
+      if (bottomE == lm->LeftBound)
+        e = lm->RightBound;
+      else
+        break;
+    }
+    ++lm;
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::InsertScanbeam(const cInt Y) { m_Scanbeam.push(Y); }
+//------------------------------------------------------------------------------
+
+bool ClipperBase::PopScanbeam(cInt &Y) {
+  if (m_Scanbeam.empty())
+    return false;
+  Y = m_Scanbeam.top();
+  m_Scanbeam.pop();
+  while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) {
+    m_Scanbeam.pop();
+  } // Pop duplicates.
+  return true;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::DisposeAllOutRecs() {
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
+    DisposeOutRec(i);
+  m_PolyOuts.clear();
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::DisposeOutRec(PolyOutList::size_type index) {
+  OutRec *outRec = m_PolyOuts[index];
+  if (outRec->Pts)
+    DisposeOutPts(outRec->Pts);
+  delete outRec;
+  m_PolyOuts[index] = 0;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::DeleteFromAEL(TEdge *e) {
+  TEdge *AelPrev = e->PrevInAEL;
+  TEdge *AelNext = e->NextInAEL;
+  if (!AelPrev && !AelNext && (e != m_ActiveEdges))
+    return; // already deleted
+  if (AelPrev)
+    AelPrev->NextInAEL = AelNext;
+  else
+    m_ActiveEdges = AelNext;
+  if (AelNext)
+    AelNext->PrevInAEL = AelPrev;
+  e->NextInAEL = 0;
+  e->PrevInAEL = 0;
+}
+//------------------------------------------------------------------------------
+
+OutRec *ClipperBase::CreateOutRec() {
+  OutRec *result = new OutRec;
+  result->IsHole = false;
+  result->IsOpen = false;
+  result->FirstLeft = 0;
+  result->Pts = 0;
+  result->BottomPt = 0;
+  result->PolyNd = 0;
+  m_PolyOuts.push_back(result);
+  result->Idx = (int)m_PolyOuts.size() - 1;
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) {
+  // check that one or other edge hasn't already been removed from AEL ...
+  if (Edge1->NextInAEL == Edge1->PrevInAEL ||
+      Edge2->NextInAEL == Edge2->PrevInAEL)
+    return;
+
+  if (Edge1->NextInAEL == Edge2) {
+    TEdge *Next = Edge2->NextInAEL;
+    if (Next)
+      Next->PrevInAEL = Edge1;
+    TEdge *Prev = Edge1->PrevInAEL;
+    if (Prev)
+      Prev->NextInAEL = Edge2;
+    Edge2->PrevInAEL = Prev;
+    Edge2->NextInAEL = Edge1;
+    Edge1->PrevInAEL = Edge2;
+    Edge1->NextInAEL = Next;
+  } else if (Edge2->NextInAEL == Edge1) {
+    TEdge *Next = Edge1->NextInAEL;
+    if (Next)
+      Next->PrevInAEL = Edge2;
+    TEdge *Prev = Edge2->PrevInAEL;
+    if (Prev)
+      Prev->NextInAEL = Edge1;
+    Edge1->PrevInAEL = Prev;
+    Edge1->NextInAEL = Edge2;
+    Edge2->PrevInAEL = Edge1;
+    Edge2->NextInAEL = Next;
+  } else {
+    TEdge *Next = Edge1->NextInAEL;
+    TEdge *Prev = Edge1->PrevInAEL;
+    Edge1->NextInAEL = Edge2->NextInAEL;
+    if (Edge1->NextInAEL)
+      Edge1->NextInAEL->PrevInAEL = Edge1;
+    Edge1->PrevInAEL = Edge2->PrevInAEL;
+    if (Edge1->PrevInAEL)
+      Edge1->PrevInAEL->NextInAEL = Edge1;
+    Edge2->NextInAEL = Next;
+    if (Edge2->NextInAEL)
+      Edge2->NextInAEL->PrevInAEL = Edge2;
+    Edge2->PrevInAEL = Prev;
+    if (Edge2->PrevInAEL)
+      Edge2->PrevInAEL->NextInAEL = Edge2;
+  }
+
+  if (!Edge1->PrevInAEL)
+    m_ActiveEdges = Edge1;
+  else if (!Edge2->PrevInAEL)
+    m_ActiveEdges = Edge2;
+}
+//------------------------------------------------------------------------------
+
+void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) {
+  if (!e->NextInLML)
+    throw clipperException("UpdateEdgeIntoAEL: invalid call");
+
+  e->NextInLML->OutIdx = e->OutIdx;
+  TEdge *AelPrev = e->PrevInAEL;
+  TEdge *AelNext = e->NextInAEL;
+  if (AelPrev)
+    AelPrev->NextInAEL = e->NextInLML;
+  else
+    m_ActiveEdges = e->NextInLML;
+  if (AelNext)
+    AelNext->PrevInAEL = e->NextInLML;
+  e->NextInLML->Side = e->Side;
+  e->NextInLML->WindDelta = e->WindDelta;
+  e->NextInLML->WindCnt = e->WindCnt;
+  e->NextInLML->WindCnt2 = e->WindCnt2;
+  e = e->NextInLML;
+  e->Curr = e->Bot;
+  e->PrevInAEL = AelPrev;
+  e->NextInAEL = AelNext;
+  if (!IsHorizontal(*e))
+    InsertScanbeam(e->Top.Y);
+}
+//------------------------------------------------------------------------------
+
+bool ClipperBase::LocalMinimaPending() {
+  return (m_CurrentLM != m_MinimaList.end());
+}
+
+//------------------------------------------------------------------------------
+// TClipper methods ...
+//------------------------------------------------------------------------------
+
+Clipper::Clipper(int initOptions)
+    : ClipperBase() // constructor
+{
+  m_ExecuteLocked = false;
+  m_UseFullRange = false;
+  m_ReverseOutput = ((initOptions & ioReverseSolution) != 0);
+  m_StrictSimple = ((initOptions & ioStrictlySimple) != 0);
+  m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0);
+  m_HasOpenPaths = false;
+#ifdef use_xyz
+  m_ZFill = 0;
+#endif
+}
+//------------------------------------------------------------------------------
+
+#ifdef use_xyz
+void Clipper::ZFillFunction(ZFillCallback zFillFunc) { m_ZFill = zFillFunc; }
+//------------------------------------------------------------------------------
+#endif
+
+bool Clipper::Execute(ClipType clipType, Paths &solution,
+                      PolyFillType fillType) {
+  return Execute(clipType, solution, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::Execute(ClipType clipType, PolyTree &polytree,
+                      PolyFillType fillType) {
+  return Execute(clipType, polytree, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::Execute(ClipType clipType, Paths &solution,
+                      PolyFillType subjFillType, PolyFillType clipFillType) {
+  if (m_ExecuteLocked)
+    return false;
+  if (m_HasOpenPaths)
+    throw clipperException(
+        "Error: PolyTree struct is needed for open path clipping.");
+  m_ExecuteLocked = true;
+  solution.resize(0);
+  m_SubjFillType = subjFillType;
+  m_ClipFillType = clipFillType;
+  m_ClipType = clipType;
+  m_UsingPolyTree = false;
+  bool succeeded = ExecuteInternal();
+  if (succeeded)
+    BuildResult(solution);
+  DisposeAllOutRecs();
+  m_ExecuteLocked = false;
+  return succeeded;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::Execute(ClipType clipType, PolyTree &polytree,
+                      PolyFillType subjFillType, PolyFillType clipFillType) {
+  if (m_ExecuteLocked)
+    return false;
+  m_ExecuteLocked = true;
+  m_SubjFillType = subjFillType;
+  m_ClipFillType = clipFillType;
+  m_ClipType = clipType;
+  m_UsingPolyTree = true;
+  bool succeeded = ExecuteInternal();
+  if (succeeded)
+    BuildResult2(polytree);
+  DisposeAllOutRecs();
+  m_ExecuteLocked = false;
+  return succeeded;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixHoleLinkage(OutRec &outrec) {
+  // skip OutRecs that (a) contain outermost polygons or
+  //(b) already have the correct owner/child linkage ...
+  if (!outrec.FirstLeft ||
+      (outrec.IsHole != outrec.FirstLeft->IsHole && outrec.FirstLeft->Pts))
+    return;
+
+  OutRec *orfl = outrec.FirstLeft;
+  while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts))
+    orfl = orfl->FirstLeft;
+  outrec.FirstLeft = orfl;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::ExecuteInternal() {
+  bool succeeded = true;
+  try {
+    Reset();
+    m_Maxima = MaximaList();
+    m_SortedEdges = 0;
+
+    succeeded = true;
+    cInt botY, topY;
+    if (!PopScanbeam(botY))
+      return false;
+    InsertLocalMinimaIntoAEL(botY);
+    while (PopScanbeam(topY) || LocalMinimaPending()) {
+      ProcessHorizontals();
+      ClearGhostJoins();
+      if (!ProcessIntersections(topY)) {
+        succeeded = false;
+        break;
+      }
+      ProcessEdgesAtTopOfScanbeam(topY);
+      botY = topY;
+      InsertLocalMinimaIntoAEL(botY);
+    }
+  } catch (...) {
+    succeeded = false;
+  }
+
+  if (succeeded) {
+    // fix orientations ...
+    for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+      OutRec *outRec = m_PolyOuts[i];
+      if (!outRec->Pts || outRec->IsOpen)
+        continue;
+      if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0))
+        ReversePolyPtLinks(outRec->Pts);
+    }
+
+    if (!m_Joins.empty())
+      JoinCommonEdges();
+
+    // unfortunately FixupOutPolygon() must be done after JoinCommonEdges()
+    for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+      OutRec *outRec = m_PolyOuts[i];
+      if (!outRec->Pts)
+        continue;
+      if (outRec->IsOpen)
+        FixupOutPolyline(*outRec);
+      else
+        FixupOutPolygon(*outRec);
+    }
+
+    if (m_StrictSimple)
+      DoSimplePolygons();
+  }
+
+  ClearJoins();
+  ClearGhostJoins();
+  return succeeded;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SetWindingCount(TEdge &edge) {
+  TEdge *e = edge.PrevInAEL;
+  // find the edge of the same polytype that immediately preceeds 'edge' in AEL
+  while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0)))
+    e = e->PrevInAEL;
+  if (!e) {
+    if (edge.WindDelta == 0) {
+      PolyFillType pft =
+          (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType);
+      edge.WindCnt = (pft == pftNegative ? -1 : 1);
+    } else
+      edge.WindCnt = edge.WindDelta;
+    edge.WindCnt2 = 0;
+    e = m_ActiveEdges; // ie get ready to calc WindCnt2
+  } else if (edge.WindDelta == 0 && m_ClipType != ctUnion) {
+    edge.WindCnt = 1;
+    edge.WindCnt2 = e->WindCnt2;
+    e = e->NextInAEL; // ie get ready to calc WindCnt2
+  } else if (IsEvenOddFillType(edge)) {
+    // EvenOdd filling ...
+    if (edge.WindDelta == 0) {
+      // are we inside a subj polygon ...
+      bool Inside = true;
+      TEdge *e2 = e->PrevInAEL;
+      while (e2) {
+        if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0)
+          Inside = !Inside;
+        e2 = e2->PrevInAEL;
+      }
+      edge.WindCnt = (Inside ? 0 : 1);
+    } else {
+      edge.WindCnt = edge.WindDelta;
+    }
+    edge.WindCnt2 = e->WindCnt2;
+    e = e->NextInAEL; // ie get ready to calc WindCnt2
+  } else {
+    // nonZero, Positive or Negative filling ...
+    if (e->WindCnt * e->WindDelta < 0) {
+      // prev edge is 'decreasing' WindCount (WC) toward zero
+      // so we're outside the previous polygon ...
+      if (Abs(e->WindCnt) > 1) {
+        // outside prev poly but still inside another.
+        // when reversing direction of prev poly use the same WC
+        if (e->WindDelta * edge.WindDelta < 0)
+          edge.WindCnt = e->WindCnt;
+        // otherwise continue to 'decrease' WC ...
+        else
+          edge.WindCnt = e->WindCnt + edge.WindDelta;
+      } else
+        // now outside all polys of same polytype so set own WC ...
+        edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta);
+    } else {
+      // prev edge is 'increasing' WindCount (WC) away from zero
+      // so we're inside the previous polygon ...
+      if (edge.WindDelta == 0)
+        edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1);
+      // if wind direction is reversing prev then use same WC
+      else if (e->WindDelta * edge.WindDelta < 0)
+        edge.WindCnt = e->WindCnt;
+      // otherwise add to WC ...
+      else
+        edge.WindCnt = e->WindCnt + edge.WindDelta;
+    }
+    edge.WindCnt2 = e->WindCnt2;
+    e = e->NextInAEL; // ie get ready to calc WindCnt2
+  }
+
+  // update WindCnt2 ...
+  if (IsEvenOddAltFillType(edge)) {
+    // EvenOdd filling ...
+    while (e != &edge) {
+      if (e->WindDelta != 0)
+        edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0);
+      e = e->NextInAEL;
+    }
+  } else {
+    // nonZero, Positive or Negative filling ...
+    while (e != &edge) {
+      edge.WindCnt2 += e->WindDelta;
+      e = e->NextInAEL;
+    }
+  }
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsEvenOddFillType(const TEdge &edge) const {
+  if (edge.PolyTyp == ptSubject)
+    return m_SubjFillType == pftEvenOdd;
+  else
+    return m_ClipFillType == pftEvenOdd;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsEvenOddAltFillType(const TEdge &edge) const {
+  if (edge.PolyTyp == ptSubject)
+    return m_ClipFillType == pftEvenOdd;
+  else
+    return m_SubjFillType == pftEvenOdd;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::IsContributing(const TEdge &edge) const {
+  PolyFillType pft, pft2;
+  if (edge.PolyTyp == ptSubject) {
+    pft = m_SubjFillType;
+    pft2 = m_ClipFillType;
+  } else {
+    pft = m_ClipFillType;
+    pft2 = m_SubjFillType;
+  }
+
+  switch (pft) {
+  case pftEvenOdd:
+    // return false if a subj line has been flagged as inside a subj polygon
+    if (edge.WindDelta == 0 && edge.WindCnt != 1)
+      return false;
+    break;
+  case pftNonZero:
+    if (Abs(edge.WindCnt) != 1)
+      return false;
+    break;
+  case pftPositive:
+    if (edge.WindCnt != 1)
+      return false;
+    break;
+  default: // pftNegative
+    if (edge.WindCnt != -1)
+      return false;
+  }
+
+  switch (m_ClipType) {
+  case ctIntersection:
+    switch (pft2) {
+    case pftEvenOdd:
+    case pftNonZero:
+      return (edge.WindCnt2 != 0);
+    case pftPositive:
+      return (edge.WindCnt2 > 0);
+    default:
+      return (edge.WindCnt2 < 0);
+    }
+    break;
+  case ctUnion:
+    switch (pft2) {
+    case pftEvenOdd:
+    case pftNonZero:
+      return (edge.WindCnt2 == 0);
+    case pftPositive:
+      return (edge.WindCnt2 <= 0);
+    default:
+      return (edge.WindCnt2 >= 0);
+    }
+    break;
+  case ctDifference:
+    if (edge.PolyTyp == ptSubject)
+      switch (pft2) {
+      case pftEvenOdd:
+      case pftNonZero:
+        return (edge.WindCnt2 == 0);
+      case pftPositive:
+        return (edge.WindCnt2 <= 0);
+      default:
+        return (edge.WindCnt2 >= 0);
+      }
+    else
+      switch (pft2) {
+      case pftEvenOdd:
+      case pftNonZero:
+        return (edge.WindCnt2 != 0);
+      case pftPositive:
+        return (edge.WindCnt2 > 0);
+      default:
+        return (edge.WindCnt2 < 0);
+      }
+    break;
+  case ctXor:
+    if (edge.WindDelta == 0) // XOr always contributing unless open
+      switch (pft2) {
+      case pftEvenOdd:
+      case pftNonZero:
+        return (edge.WindCnt2 == 0);
+      case pftPositive:
+        return (edge.WindCnt2 <= 0);
+      default:
+        return (edge.WindCnt2 >= 0);
+      }
+    else
+      return true;
+    break;
+  default:
+    return true;
+  }
+}
+//------------------------------------------------------------------------------
+
+OutPt *Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) {
+  OutPt *result;
+  TEdge *e, *prevE;
+  if (IsHorizontal(*e2) || (e1->Dx > e2->Dx)) {
+    result = AddOutPt(e1, Pt);
+    e2->OutIdx = e1->OutIdx;
+    e1->Side = esLeft;
+    e2->Side = esRight;
+    e = e1;
+    if (e->PrevInAEL == e2)
+      prevE = e2->PrevInAEL;
+    else
+      prevE = e->PrevInAEL;
+  } else {
+    result = AddOutPt(e2, Pt);
+    e1->OutIdx = e2->OutIdx;
+    e1->Side = esRight;
+    e2->Side = esLeft;
+    e = e2;
+    if (e->PrevInAEL == e1)
+      prevE = e1->PrevInAEL;
+    else
+      prevE = e->PrevInAEL;
+  }
+
+  if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y) {
+    cInt xPrev = TopX(*prevE, Pt.Y);
+    cInt xE = TopX(*e, Pt.Y);
+    if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) &&
+        SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y),
+                    e->Top, m_UseFullRange)) {
+      OutPt *outPt = AddOutPt(prevE, Pt);
+      AddJoin(result, outPt, e->Top);
+    }
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) {
+  AddOutPt(e1, Pt);
+  if (e2->WindDelta == 0)
+    AddOutPt(e2, Pt);
+  if (e1->OutIdx == e2->OutIdx) {
+    e1->OutIdx = Unassigned;
+    e2->OutIdx = Unassigned;
+  } else if (e1->OutIdx < e2->OutIdx)
+    AppendPolygon(e1, e2);
+  else
+    AppendPolygon(e2, e1);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddEdgeToSEL(TEdge *edge) {
+  // SEL pointers in PEdge are reused to build a list of horizontal edges.
+  // However, we don't need to worry about order with horizontal edge
+  // processing.
+  if (!m_SortedEdges) {
+    m_SortedEdges = edge;
+    edge->PrevInSEL = 0;
+    edge->NextInSEL = 0;
+  } else {
+    edge->NextInSEL = m_SortedEdges;
+    edge->PrevInSEL = 0;
+    m_SortedEdges->PrevInSEL = edge;
+    m_SortedEdges = edge;
+  }
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::PopEdgeFromSEL(TEdge *&edge) {
+  if (!m_SortedEdges)
+    return false;
+  edge = m_SortedEdges;
+  DeleteFromSEL(m_SortedEdges);
+  return true;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::CopyAELToSEL() {
+  TEdge *e = m_ActiveEdges;
+  m_SortedEdges = e;
+  while (e) {
+    e->PrevInSEL = e->PrevInAEL;
+    e->NextInSEL = e->NextInAEL;
+    e = e->NextInAEL;
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) {
+  Join *j = new Join;
+  j->OutPt1 = op1;
+  j->OutPt2 = op2;
+  j->OffPt = OffPt;
+  m_Joins.push_back(j);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ClearJoins() {
+  for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
+    delete m_Joins[i];
+  m_Joins.resize(0);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ClearGhostJoins() {
+  for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++)
+    delete m_GhostJoins[i];
+  m_GhostJoins.resize(0);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) {
+  Join *j = new Join;
+  j->OutPt1 = op;
+  j->OutPt2 = 0;
+  j->OffPt = OffPt;
+  m_GhostJoins.push_back(j);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) {
+  const LocalMinimum *lm;
+  while (PopLocalMinima(botY, lm)) {
+    TEdge *lb = lm->LeftBound;
+    TEdge *rb = lm->RightBound;
+
+    OutPt *Op1 = 0;
+    if (!lb) {
+      // nb: don't insert LB into either AEL or SEL
+      InsertEdgeIntoAEL(rb, 0);
+      SetWindingCount(*rb);
+      if (IsContributing(*rb))
+        Op1 = AddOutPt(rb, rb->Bot);
+    } else if (!rb) {
+      InsertEdgeIntoAEL(lb, 0);
+      SetWindingCount(*lb);
+      if (IsContributing(*lb))
+        Op1 = AddOutPt(lb, lb->Bot);
+      InsertScanbeam(lb->Top.Y);
+    } else {
+      InsertEdgeIntoAEL(lb, 0);
+      InsertEdgeIntoAEL(rb, lb);
+      SetWindingCount(*lb);
+      rb->WindCnt = lb->WindCnt;
+      rb->WindCnt2 = lb->WindCnt2;
+      if (IsContributing(*lb))
+        Op1 = AddLocalMinPoly(lb, rb, lb->Bot);
+      InsertScanbeam(lb->Top.Y);
+    }
+
+    if (rb) {
+      if (IsHorizontal(*rb)) {
+        AddEdgeToSEL(rb);
+        if (rb->NextInLML)
+          InsertScanbeam(rb->NextInLML->Top.Y);
+      } else
+        InsertScanbeam(rb->Top.Y);
+    }
+
+    if (!lb || !rb)
+      continue;
+
+    // if any output polygons share an edge, they'll need joining later ...
+    if (Op1 && IsHorizontal(*rb) && m_GhostJoins.size() > 0 &&
+        (rb->WindDelta != 0)) {
+      for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) {
+        Join *jr = m_GhostJoins[i];
+        // if the horizontal Rb and a 'ghost' horizontal overlap, then convert
+        // the 'ghost' join to a real join ready for later ...
+        if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X,
+                                rb->Top.X))
+          AddJoin(jr->OutPt1, Op1, jr->OffPt);
+      }
+    }
+
+    if (lb->OutIdx >= 0 && lb->PrevInAEL &&
+        lb->PrevInAEL->Curr.X == lb->Bot.X && lb->PrevInAEL->OutIdx >= 0 &&
+        SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top,
+                    m_UseFullRange) &&
+        (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) {
+      OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot);
+      AddJoin(Op1, Op2, lb->Top);
+    }
+
+    if (lb->NextInAEL != rb) {
+
+      if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 &&
+          SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr,
+                      rb->Top, m_UseFullRange) &&
+          (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) {
+        OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot);
+        AddJoin(Op1, Op2, rb->Top);
+      }
+
+      TEdge *e = lb->NextInAEL;
+      if (e) {
+        while (e != rb) {
+          // nb: For calculating winding counts etc, IntersectEdges() assumes
+          // that param1 will be to the Right of param2 ABOVE the intersection
+          // ...
+          IntersectEdges(rb, e, lb->Curr); // order important here
+          e = e->NextInAEL;
+        }
+      }
+    }
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DeleteFromSEL(TEdge *e) {
+  TEdge *SelPrev = e->PrevInSEL;
+  TEdge *SelNext = e->NextInSEL;
+  if (!SelPrev && !SelNext && (e != m_SortedEdges))
+    return; // already deleted
+  if (SelPrev)
+    SelPrev->NextInSEL = SelNext;
+  else
+    m_SortedEdges = SelNext;
+  if (SelNext)
+    SelNext->PrevInSEL = SelPrev;
+  e->NextInSEL = 0;
+  e->PrevInSEL = 0;
+}
+//------------------------------------------------------------------------------
+
+#ifdef use_xyz
+void Clipper::SetZ(IntPoint &pt, TEdge &e1, TEdge &e2) {
+  if (pt.Z != 0 || !m_ZFill)
+    return;
+  else if (pt == e1.Bot)
+    pt.Z = e1.Bot.Z;
+  else if (pt == e1.Top)
+    pt.Z = e1.Top.Z;
+  else if (pt == e2.Bot)
+    pt.Z = e2.Bot.Z;
+  else if (pt == e2.Top)
+    pt.Z = e2.Top.Z;
+  else
+    (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt);
+}
+//------------------------------------------------------------------------------
+#endif
+
+void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) {
+  bool e1Contributing = (e1->OutIdx >= 0);
+  bool e2Contributing = (e2->OutIdx >= 0);
+
+#ifdef use_xyz
+  SetZ(Pt, *e1, *e2);
+#endif
+
+#ifdef use_lines
+  // if either edge is on an OPEN path ...
+  if (e1->WindDelta == 0 || e2->WindDelta == 0) {
+    // ignore subject-subject open path intersections UNLESS they
+    // are both open paths, AND they are both 'contributing maximas' ...
+    if (e1->WindDelta == 0 && e2->WindDelta == 0)
+      return;
+
+    // if intersecting a subj line with a subj poly ...
+    else if (e1->PolyTyp == e2->PolyTyp && e1->WindDelta != e2->WindDelta &&
+             m_ClipType == ctUnion) {
+      if (e1->WindDelta == 0) {
+        if (e2Contributing) {
+          AddOutPt(e1, Pt);
+          if (e1Contributing)
+            e1->OutIdx = Unassigned;
+        }
+      } else {
+        if (e1Contributing) {
+          AddOutPt(e2, Pt);
+          if (e2Contributing)
+            e2->OutIdx = Unassigned;
+        }
+      }
+    } else if (e1->PolyTyp != e2->PolyTyp) {
+      // toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ...
+      if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 &&
+          (m_ClipType != ctUnion || e2->WindCnt2 == 0)) {
+        AddOutPt(e1, Pt);
+        if (e1Contributing)
+          e1->OutIdx = Unassigned;
+      } else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) &&
+                 (m_ClipType != ctUnion || e1->WindCnt2 == 0)) {
+        AddOutPt(e2, Pt);
+        if (e2Contributing)
+          e2->OutIdx = Unassigned;
+      }
+    }
+    return;
+  }
+#endif
+
+  // update winding counts...
+  // assumes that e1 will be to the Right of e2 ABOVE the intersection
+  if (e1->PolyTyp == e2->PolyTyp) {
+    if (IsEvenOddFillType(*e1)) {
+      int oldE1WindCnt = e1->WindCnt;
+      e1->WindCnt = e2->WindCnt;
+      e2->WindCnt = oldE1WindCnt;
+    } else {
+      if (e1->WindCnt + e2->WindDelta == 0)
+        e1->WindCnt = -e1->WindCnt;
+      else
+        e1->WindCnt += e2->WindDelta;
+      if (e2->WindCnt - e1->WindDelta == 0)
+        e2->WindCnt = -e2->WindCnt;
+      else
+        e2->WindCnt -= e1->WindDelta;
+    }
+  } else {
+    if (!IsEvenOddFillType(*e2))
+      e1->WindCnt2 += e2->WindDelta;
+    else
+      e1->WindCnt2 = (e1->WindCnt2 == 0) ? 1 : 0;
+    if (!IsEvenOddFillType(*e1))
+      e2->WindCnt2 -= e1->WindDelta;
+    else
+      e2->WindCnt2 = (e2->WindCnt2 == 0) ? 1 : 0;
+  }
+
+  PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2;
+  if (e1->PolyTyp == ptSubject) {
+    e1FillType = m_SubjFillType;
+    e1FillType2 = m_ClipFillType;
+  } else {
+    e1FillType = m_ClipFillType;
+    e1FillType2 = m_SubjFillType;
+  }
+  if (e2->PolyTyp == ptSubject) {
+    e2FillType = m_SubjFillType;
+    e2FillType2 = m_ClipFillType;
+  } else {
+    e2FillType = m_ClipFillType;
+    e2FillType2 = m_SubjFillType;
+  }
+
+  cInt e1Wc, e2Wc;
+  switch (e1FillType) {
+  case pftPositive:
+    e1Wc = e1->WindCnt;
+    break;
+  case pftNegative:
+    e1Wc = -e1->WindCnt;
+    break;
+  default:
+    e1Wc = Abs(e1->WindCnt);
+  }
+  switch (e2FillType) {
+  case pftPositive:
+    e2Wc = e2->WindCnt;
+    break;
+  case pftNegative:
+    e2Wc = -e2->WindCnt;
+    break;
+  default:
+    e2Wc = Abs(e2->WindCnt);
+  }
+
+  if (e1Contributing && e2Contributing) {
+    if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) ||
+        (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor)) {
+      AddLocalMaxPoly(e1, e2, Pt);
+    } else {
+      AddOutPt(e1, Pt);
+      AddOutPt(e2, Pt);
+      SwapSides(*e1, *e2);
+      SwapPolyIndexes(*e1, *e2);
+    }
+  } else if (e1Contributing) {
+    if (e2Wc == 0 || e2Wc == 1) {
+      AddOutPt(e1, Pt);
+      SwapSides(*e1, *e2);
+      SwapPolyIndexes(*e1, *e2);
+    }
+  } else if (e2Contributing) {
+    if (e1Wc == 0 || e1Wc == 1) {
+      AddOutPt(e2, Pt);
+      SwapSides(*e1, *e2);
+      SwapPolyIndexes(*e1, *e2);
+    }
+  } else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) {
+    // neither edge is currently contributing ...
+
+    cInt e1Wc2, e2Wc2;
+    switch (e1FillType2) {
+    case pftPositive:
+      e1Wc2 = e1->WindCnt2;
+      break;
+    case pftNegative:
+      e1Wc2 = -e1->WindCnt2;
+      break;
+    default:
+      e1Wc2 = Abs(e1->WindCnt2);
+    }
+    switch (e2FillType2) {
+    case pftPositive:
+      e2Wc2 = e2->WindCnt2;
+      break;
+    case pftNegative:
+      e2Wc2 = -e2->WindCnt2;
+      break;
+    default:
+      e2Wc2 = Abs(e2->WindCnt2);
+    }
+
+    if (e1->PolyTyp != e2->PolyTyp) {
+      AddLocalMinPoly(e1, e2, Pt);
+    } else if (e1Wc == 1 && e2Wc == 1)
+      switch (m_ClipType) {
+      case ctIntersection:
+        if (e1Wc2 > 0 && e2Wc2 > 0)
+          AddLocalMinPoly(e1, e2, Pt);
+        break;
+      case ctUnion:
+        if (e1Wc2 <= 0 && e2Wc2 <= 0)
+          AddLocalMinPoly(e1, e2, Pt);
+        break;
+      case ctDifference:
+        if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
+            ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
+          AddLocalMinPoly(e1, e2, Pt);
+        break;
+      case ctXor:
+        AddLocalMinPoly(e1, e2, Pt);
+      }
+    else
+      SwapSides(*e1, *e2);
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SetHoleState(TEdge *e, OutRec *outrec) {
+  TEdge *e2 = e->PrevInAEL;
+  TEdge *eTmp = 0;
+  while (e2) {
+    if (e2->OutIdx >= 0 && e2->WindDelta != 0) {
+      if (!eTmp)
+        eTmp = e2;
+      else if (eTmp->OutIdx == e2->OutIdx)
+        eTmp = 0;
+    }
+    e2 = e2->PrevInAEL;
+  }
+  if (!eTmp) {
+    outrec->FirstLeft = 0;
+    outrec->IsHole = false;
+  } else {
+    outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx];
+    outrec->IsHole = !outrec->FirstLeft->IsHole;
+  }
+}
+//------------------------------------------------------------------------------
+
+OutRec *GetLowermostRec(OutRec *outRec1, OutRec *outRec2) {
+  // work out which polygon fragment has the correct hole state ...
+  if (!outRec1->BottomPt)
+    outRec1->BottomPt = GetBottomPt(outRec1->Pts);
+  if (!outRec2->BottomPt)
+    outRec2->BottomPt = GetBottomPt(outRec2->Pts);
+  OutPt *OutPt1 = outRec1->BottomPt;
+  OutPt *OutPt2 = outRec2->BottomPt;
+  if (OutPt1->Pt.Y > OutPt2->Pt.Y)
+    return outRec1;
+  else if (OutPt1->Pt.Y < OutPt2->Pt.Y)
+    return outRec2;
+  else if (OutPt1->Pt.X < OutPt2->Pt.X)
+    return outRec1;
+  else if (OutPt1->Pt.X > OutPt2->Pt.X)
+    return outRec2;
+  else if (OutPt1->Next == OutPt1)
+    return outRec2;
+  else if (OutPt2->Next == OutPt2)
+    return outRec1;
+  else if (FirstIsBottomPt(OutPt1, OutPt2))
+    return outRec1;
+  else
+    return outRec2;
+}
+//------------------------------------------------------------------------------
+
+bool OutRec1RightOfOutRec2(OutRec *outRec1, OutRec *outRec2) {
+  do {
+    outRec1 = outRec1->FirstLeft;
+    if (outRec1 == outRec2)
+      return true;
+  } while (outRec1);
+  return false;
+}
+//------------------------------------------------------------------------------
+
+OutRec *Clipper::GetOutRec(int Idx) {
+  OutRec *outrec = m_PolyOuts[Idx];
+  while (outrec != m_PolyOuts[outrec->Idx])
+    outrec = m_PolyOuts[outrec->Idx];
+  return outrec;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) {
+  // get the start and ends of both output polygons ...
+  OutRec *outRec1 = m_PolyOuts[e1->OutIdx];
+  OutRec *outRec2 = m_PolyOuts[e2->OutIdx];
+
+  OutRec *holeStateRec;
+  if (OutRec1RightOfOutRec2(outRec1, outRec2))
+    holeStateRec = outRec2;
+  else if (OutRec1RightOfOutRec2(outRec2, outRec1))
+    holeStateRec = outRec1;
+  else
+    holeStateRec = GetLowermostRec(outRec1, outRec2);
+
+  // get the start and ends of both output polygons and
+  // join e2 poly onto e1 poly and delete pointers to e2 ...
+
+  OutPt *p1_lft = outRec1->Pts;
+  OutPt *p1_rt = p1_lft->Prev;
+  OutPt *p2_lft = outRec2->Pts;
+  OutPt *p2_rt = p2_lft->Prev;
+
+  // join e2 poly onto e1 poly and delete pointers to e2 ...
+  if (e1->Side == esLeft) {
+    if (e2->Side == esLeft) {
+      // z y x a b c
+      ReversePolyPtLinks(p2_lft);
+      p2_lft->Next = p1_lft;
+      p1_lft->Prev = p2_lft;
+      p1_rt->Next = p2_rt;
+      p2_rt->Prev = p1_rt;
+      outRec1->Pts = p2_rt;
+    } else {
+      // x y z a b c
+      p2_rt->Next = p1_lft;
+      p1_lft->Prev = p2_rt;
+      p2_lft->Prev = p1_rt;
+      p1_rt->Next = p2_lft;
+      outRec1->Pts = p2_lft;
+    }
+  } else {
+    if (e2->Side == esRight) {
+      // a b c z y x
+      ReversePolyPtLinks(p2_lft);
+      p1_rt->Next = p2_rt;
+      p2_rt->Prev = p1_rt;
+      p2_lft->Next = p1_lft;
+      p1_lft->Prev = p2_lft;
+    } else {
+      // a b c x y z
+      p1_rt->Next = p2_lft;
+      p2_lft->Prev = p1_rt;
+      p1_lft->Prev = p2_rt;
+      p2_rt->Next = p1_lft;
+    }
+  }
+
+  outRec1->BottomPt = 0;
+  if (holeStateRec == outRec2) {
+    if (outRec2->FirstLeft != outRec1)
+      outRec1->FirstLeft = outRec2->FirstLeft;
+    outRec1->IsHole = outRec2->IsHole;
+  }
+  outRec2->Pts = 0;
+  outRec2->BottomPt = 0;
+  outRec2->FirstLeft = outRec1;
+
+  int OKIdx = e1->OutIdx;
+  int ObsoleteIdx = e2->OutIdx;
+
+  e1->OutIdx =
+      Unassigned; // nb: safe because we only get here via AddLocalMaxPoly
+  e2->OutIdx = Unassigned;
+
+  TEdge *e = m_ActiveEdges;
+  while (e) {
+    if (e->OutIdx == ObsoleteIdx) {
+      e->OutIdx = OKIdx;
+      e->Side = e1->Side;
+      break;
+    }
+    e = e->NextInAEL;
+  }
+
+  outRec2->Idx = outRec1->Idx;
+}
+//------------------------------------------------------------------------------
+
+OutPt *Clipper::AddOutPt(TEdge *e, const IntPoint &pt) {
+  if (e->OutIdx < 0) {
+    OutRec *outRec = CreateOutRec();
+    outRec->IsOpen = (e->WindDelta == 0);
+    OutPt *newOp = new OutPt;
+    outRec->Pts = newOp;
+    newOp->Idx = outRec->Idx;
+    newOp->Pt = pt;
+    newOp->Next = newOp;
+    newOp->Prev = newOp;
+    if (!outRec->IsOpen)
+      SetHoleState(e, outRec);
+    e->OutIdx = outRec->Idx;
+    return newOp;
+  } else {
+    OutRec *outRec = m_PolyOuts[e->OutIdx];
+    // OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
+    OutPt *op = outRec->Pts;
+
+    bool ToFront = (e->Side == esLeft);
+    if (ToFront && (pt == op->Pt))
+      return op;
+    else if (!ToFront && (pt == op->Prev->Pt))
+      return op->Prev;
+
+    OutPt *newOp = new OutPt;
+    newOp->Idx = outRec->Idx;
+    newOp->Pt = pt;
+    newOp->Next = op;
+    newOp->Prev = op->Prev;
+    newOp->Prev->Next = newOp;
+    op->Prev = newOp;
+    if (ToFront)
+      outRec->Pts = newOp;
+    return newOp;
+  }
+}
+//------------------------------------------------------------------------------
+
+OutPt *Clipper::GetLastOutPt(TEdge *e) {
+  OutRec *outRec = m_PolyOuts[e->OutIdx];
+  if (e->Side == esLeft)
+    return outRec->Pts;
+  else
+    return outRec->Pts->Prev;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ProcessHorizontals() {
+  TEdge *horzEdge;
+  while (PopEdgeFromSEL(horzEdge))
+    ProcessHorizontal(horzEdge);
+}
+//------------------------------------------------------------------------------
+
+inline bool IsMinima(TEdge *e) {
+  return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e);
+}
+//------------------------------------------------------------------------------
+
+inline bool IsMaxima(TEdge *e, const cInt Y) {
+  return e && e->Top.Y == Y && !e->NextInLML;
+}
+//------------------------------------------------------------------------------
+
+inline bool IsIntermediate(TEdge *e, const cInt Y) {
+  return e->Top.Y == Y && e->NextInLML;
+}
+//------------------------------------------------------------------------------
+
+TEdge *GetMaximaPair(TEdge *e) {
+  if ((e->Next->Top == e->Top) && !e->Next->NextInLML)
+    return e->Next;
+  else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML)
+    return e->Prev;
+  else
+    return 0;
+}
+//------------------------------------------------------------------------------
+
+TEdge *GetMaximaPairEx(TEdge *e) {
+  // as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's
+  // horizontal)
+  TEdge *result = GetMaximaPair(e);
+  if (result &&
+      (result->OutIdx == Skip ||
+       (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result))))
+    return 0;
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) {
+  if (!(Edge1->NextInSEL) && !(Edge1->PrevInSEL))
+    return;
+  if (!(Edge2->NextInSEL) && !(Edge2->PrevInSEL))
+    return;
+
+  if (Edge1->NextInSEL == Edge2) {
+    TEdge *Next = Edge2->NextInSEL;
+    if (Next)
+      Next->PrevInSEL = Edge1;
+    TEdge *Prev = Edge1->PrevInSEL;
+    if (Prev)
+      Prev->NextInSEL = Edge2;
+    Edge2->PrevInSEL = Prev;
+    Edge2->NextInSEL = Edge1;
+    Edge1->PrevInSEL = Edge2;
+    Edge1->NextInSEL = Next;
+  } else if (Edge2->NextInSEL == Edge1) {
+    TEdge *Next = Edge1->NextInSEL;
+    if (Next)
+      Next->PrevInSEL = Edge2;
+    TEdge *Prev = Edge2->PrevInSEL;
+    if (Prev)
+      Prev->NextInSEL = Edge1;
+    Edge1->PrevInSEL = Prev;
+    Edge1->NextInSEL = Edge2;
+    Edge2->PrevInSEL = Edge1;
+    Edge2->NextInSEL = Next;
+  } else {
+    TEdge *Next = Edge1->NextInSEL;
+    TEdge *Prev = Edge1->PrevInSEL;
+    Edge1->NextInSEL = Edge2->NextInSEL;
+    if (Edge1->NextInSEL)
+      Edge1->NextInSEL->PrevInSEL = Edge1;
+    Edge1->PrevInSEL = Edge2->PrevInSEL;
+    if (Edge1->PrevInSEL)
+      Edge1->PrevInSEL->NextInSEL = Edge1;
+    Edge2->NextInSEL = Next;
+    if (Edge2->NextInSEL)
+      Edge2->NextInSEL->PrevInSEL = Edge2;
+    Edge2->PrevInSEL = Prev;
+    if (Edge2->PrevInSEL)
+      Edge2->PrevInSEL->NextInSEL = Edge2;
+  }
+
+  if (!Edge1->PrevInSEL)
+    m_SortedEdges = Edge1;
+  else if (!Edge2->PrevInSEL)
+    m_SortedEdges = Edge2;
+}
+//------------------------------------------------------------------------------
+
+TEdge *GetNextInAEL(TEdge *e, Direction dir) {
+  return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL;
+}
+//------------------------------------------------------------------------------
+
+void GetHorzDirection(TEdge &HorzEdge, Direction &Dir, cInt &Left,
+                      cInt &Right) {
+  if (HorzEdge.Bot.X < HorzEdge.Top.X) {
+    Left = HorzEdge.Bot.X;
+    Right = HorzEdge.Top.X;
+    Dir = dLeftToRight;
+  } else {
+    Left = HorzEdge.Top.X;
+    Right = HorzEdge.Bot.X;
+    Dir = dRightToLeft;
+  }
+}
+//------------------------------------------------------------------------
+
+/*******************************************************************************
+* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or    *
+* Bottom of a scanbeam) are processed as if layered. The order in which HEs    *
+* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#]    *
+* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs),      *
+* and with other non-horizontal edges [*]. Once these intersections are        *
+* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into   *
+* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs.    *
+*******************************************************************************/
+
+void Clipper::ProcessHorizontal(TEdge *horzEdge) {
+  Direction dir;
+  cInt horzLeft, horzRight;
+  bool IsOpen = (horzEdge->WindDelta == 0);
+
+  GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
+
+  TEdge *eLastHorz = horzEdge, *eMaxPair = 0;
+  while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML))
+    eLastHorz = eLastHorz->NextInLML;
+  if (!eLastHorz->NextInLML)
+    eMaxPair = GetMaximaPair(eLastHorz);
+
+  MaximaList::const_iterator maxIt;
+  MaximaList::const_reverse_iterator maxRit;
+  if (m_Maxima.size() > 0) {
+    // get the first maxima in range (X) ...
+    if (dir == dLeftToRight) {
+      maxIt = m_Maxima.begin();
+      while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X)
+        maxIt++;
+      if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X)
+        maxIt = m_Maxima.end();
+    } else {
+      maxRit = m_Maxima.rbegin();
+      while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X)
+        maxRit++;
+      if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X)
+        maxRit = m_Maxima.rend();
+    }
+  }
+
+  OutPt *op1 = 0;
+
+  for (;;) // loop through consec. horizontal edges
+  {
+
+    bool IsLastHorz = (horzEdge == eLastHorz);
+    TEdge *e = GetNextInAEL(horzEdge, dir);
+    while (e) {
+
+      // this code block inserts extra coords into horizontal edges (in output
+      // polygons) whereever maxima touch these horizontal edges. This helps
+      //'simplifying' polygons (ie if the Simplify property is set).
+      if (m_Maxima.size() > 0) {
+        if (dir == dLeftToRight) {
+          while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) {
+            if (horzEdge->OutIdx >= 0 && !IsOpen)
+              AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y));
+            maxIt++;
+          }
+        } else {
+          while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) {
+            if (horzEdge->OutIdx >= 0 && !IsOpen)
+              AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y));
+            maxRit++;
+          }
+        }
+      };
+
+      if ((dir == dLeftToRight && e->Curr.X > horzRight) ||
+          (dir == dRightToLeft && e->Curr.X < horzLeft))
+        break;
+
+      // Also break if we've got to the end of an intermediate horizontal edge
+      // ...
+      // nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
+      if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML &&
+          e->Dx < horzEdge->NextInLML->Dx)
+        break;
+
+      if (horzEdge->OutIdx >= 0 && !IsOpen) // note: may be done multiple times
+      {
+#ifdef use_xyz
+        if (dir == dLeftToRight)
+          SetZ(e->Curr, *horzEdge, *e);
+        else
+          SetZ(e->Curr, *e, *horzEdge);
+#endif
+        op1 = AddOutPt(horzEdge, e->Curr);
+        TEdge *eNextHorz = m_SortedEdges;
+        while (eNextHorz) {
+          if (eNextHorz->OutIdx >= 0 &&
+              HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X,
+                                  eNextHorz->Bot.X, eNextHorz->Top.X)) {
+            OutPt *op2 = GetLastOutPt(eNextHorz);
+            AddJoin(op2, op1, eNextHorz->Top);
+          }
+          eNextHorz = eNextHorz->NextInSEL;
+        }
+        AddGhostJoin(op1, horzEdge->Bot);
+      }
+
+      // OK, so far we're still in range of the horizontal Edge  but make sure
+      // we're at the last of consec. horizontals when matching with eMaxPair
+      if (e == eMaxPair && IsLastHorz) {
+        if (horzEdge->OutIdx >= 0)
+          AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top);
+        DeleteFromAEL(horzEdge);
+        DeleteFromAEL(eMaxPair);
+        return;
+      }
+
+      if (dir == dLeftToRight) {
+        IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
+        IntersectEdges(horzEdge, e, Pt);
+      } else {
+        IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
+        IntersectEdges(e, horzEdge, Pt);
+      }
+      TEdge *eNext = GetNextInAEL(e, dir);
+      SwapPositionsInAEL(horzEdge, e);
+      e = eNext;
+    } // end while(e)
+
+    // Break out of loop if HorzEdge.NextInLML is not also horizontal ...
+    if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML))
+      break;
+
+    UpdateEdgeIntoAEL(horzEdge);
+    if (horzEdge->OutIdx >= 0)
+      AddOutPt(horzEdge, horzEdge->Bot);
+    GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
+
+  } // end for (;;)
+
+  if (horzEdge->OutIdx >= 0 && !op1) {
+    op1 = GetLastOutPt(horzEdge);
+    TEdge *eNextHorz = m_SortedEdges;
+    while (eNextHorz) {
+      if (eNextHorz->OutIdx >= 0 &&
+          HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X,
+                              eNextHorz->Bot.X, eNextHorz->Top.X)) {
+        OutPt *op2 = GetLastOutPt(eNextHorz);
+        AddJoin(op2, op1, eNextHorz->Top);
+      }
+      eNextHorz = eNextHorz->NextInSEL;
+    }
+    AddGhostJoin(op1, horzEdge->Top);
+  }
+
+  if (horzEdge->NextInLML) {
+    if (horzEdge->OutIdx >= 0) {
+      op1 = AddOutPt(horzEdge, horzEdge->Top);
+      UpdateEdgeIntoAEL(horzEdge);
+      if (horzEdge->WindDelta == 0)
+        return;
+      // nb: HorzEdge is no longer horizontal here
+      TEdge *ePrev = horzEdge->PrevInAEL;
+      TEdge *eNext = horzEdge->NextInAEL;
+      if (ePrev && ePrev->Curr.X == horzEdge->Bot.X &&
+          ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 &&
+          (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
+           SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) {
+        OutPt *op2 = AddOutPt(ePrev, horzEdge->Bot);
+        AddJoin(op1, op2, horzEdge->Top);
+      } else if (eNext && eNext->Curr.X == horzEdge->Bot.X &&
+                 eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 &&
+                 eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y &&
+                 SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) {
+        OutPt *op2 = AddOutPt(eNext, horzEdge->Bot);
+        AddJoin(op1, op2, horzEdge->Top);
+      }
+    } else
+      UpdateEdgeIntoAEL(horzEdge);
+  } else {
+    if (horzEdge->OutIdx >= 0)
+      AddOutPt(horzEdge, horzEdge->Top);
+    DeleteFromAEL(horzEdge);
+  }
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::ProcessIntersections(const cInt topY) {
+  if (!m_ActiveEdges)
+    return true;
+  try {
+    BuildIntersectList(topY);
+    size_t IlSize = m_IntersectList.size();
+    if (IlSize == 0)
+      return true;
+    if (IlSize == 1 || FixupIntersectionOrder())
+      ProcessIntersectList();
+    else
+      return false;
+  } catch (...) {
+    m_SortedEdges = 0;
+    DisposeIntersectNodes();
+    throw clipperException("ProcessIntersections error");
+  }
+  m_SortedEdges = 0;
+  return true;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DisposeIntersectNodes() {
+  for (size_t i = 0; i < m_IntersectList.size(); ++i)
+    delete m_IntersectList[i];
+  m_IntersectList.clear();
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildIntersectList(const cInt topY) {
+  if (!m_ActiveEdges)
+    return;
+
+  // prepare for sorting ...
+  TEdge *e = m_ActiveEdges;
+  m_SortedEdges = e;
+  while (e) {
+    e->PrevInSEL = e->PrevInAEL;
+    e->NextInSEL = e->NextInAEL;
+    e->Curr.X = TopX(*e, topY);
+    e = e->NextInAEL;
+  }
+
+  // bubblesort ...
+  bool isModified;
+  do {
+    isModified = false;
+    e = m_SortedEdges;
+    while (e->NextInSEL) {
+      TEdge *eNext = e->NextInSEL;
+      IntPoint Pt;
+      if (e->Curr.X > eNext->Curr.X) {
+        IntersectPoint(*e, *eNext, Pt);
+        if (Pt.Y < topY)
+          Pt = IntPoint(TopX(*e, topY), topY);
+        IntersectNode *newNode = new IntersectNode;
+        newNode->Edge1 = e;
+        newNode->Edge2 = eNext;
+        newNode->Pt = Pt;
+        m_IntersectList.push_back(newNode);
+
+        SwapPositionsInSEL(e, eNext);
+        isModified = true;
+      } else
+        e = eNext;
+    }
+    if (e->PrevInSEL)
+      e->PrevInSEL->NextInSEL = 0;
+    else
+      break;
+  } while (isModified);
+  m_SortedEdges = 0; // important
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ProcessIntersectList() {
+  for (size_t i = 0; i < m_IntersectList.size(); ++i) {
+    IntersectNode *iNode = m_IntersectList[i];
+    {
+      IntersectEdges(iNode->Edge1, iNode->Edge2, iNode->Pt);
+      SwapPositionsInAEL(iNode->Edge1, iNode->Edge2);
+    }
+    delete iNode;
+  }
+  m_IntersectList.clear();
+}
+//------------------------------------------------------------------------------
+
+bool IntersectListSort(IntersectNode *node1, IntersectNode *node2) {
+  return node2->Pt.Y < node1->Pt.Y;
+}
+//------------------------------------------------------------------------------
+
+inline bool EdgesAdjacent(const IntersectNode &inode) {
+  return (inode.Edge1->NextInSEL == inode.Edge2) ||
+         (inode.Edge1->PrevInSEL == inode.Edge2);
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::FixupIntersectionOrder() {
+  // pre-condition: intersections are sorted Bottom-most first.
+  // Now it's crucial that intersections are made only between adjacent edges,
+  // so to ensure this the order of intersections may need adjusting ...
+  CopyAELToSEL();
+  std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort);
+  size_t cnt = m_IntersectList.size();
+  for (size_t i = 0; i < cnt; ++i) {
+    if (!EdgesAdjacent(*m_IntersectList[i])) {
+      size_t j = i + 1;
+      while (j < cnt && !EdgesAdjacent(*m_IntersectList[j]))
+        j++;
+      if (j == cnt)
+        return false;
+      std::swap(m_IntersectList[i], m_IntersectList[j]);
+    }
+    SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2);
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::DoMaxima(TEdge *e) {
+  TEdge *eMaxPair = GetMaximaPairEx(e);
+  if (!eMaxPair) {
+    if (e->OutIdx >= 0)
+      AddOutPt(e, e->Top);
+    DeleteFromAEL(e);
+    return;
+  }
+
+  TEdge *eNext = e->NextInAEL;
+  while (eNext && eNext != eMaxPair) {
+    IntersectEdges(e, eNext, e->Top);
+    SwapPositionsInAEL(e, eNext);
+    eNext = e->NextInAEL;
+  }
+
+  if (e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) {
+    DeleteFromAEL(e);
+    DeleteFromAEL(eMaxPair);
+  } else if (e->OutIdx >= 0 && eMaxPair->OutIdx >= 0) {
+    if (e->OutIdx >= 0)
+      AddLocalMaxPoly(e, eMaxPair, e->Top);
+    DeleteFromAEL(e);
+    DeleteFromAEL(eMaxPair);
+  }
+#ifdef use_lines
+  else if (e->WindDelta == 0) {
+    if (e->OutIdx >= 0) {
+      AddOutPt(e, e->Top);
+      e->OutIdx = Unassigned;
+    }
+    DeleteFromAEL(e);
+
+    if (eMaxPair->OutIdx >= 0) {
+      AddOutPt(eMaxPair, e->Top);
+      eMaxPair->OutIdx = Unassigned;
+    }
+    DeleteFromAEL(eMaxPair);
+  }
+#endif
+  else
+    throw clipperException("DoMaxima error");
+}
+//------------------------------------------------------------------------------
+
+void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) {
+  TEdge *e = m_ActiveEdges;
+  while (e) {
+    // 1. process maxima, treating them as if they're 'bent' horizontal edges,
+    //   but exclude maxima with horizontal edges. nb: e can't be a horizontal.
+    bool IsMaximaEdge = IsMaxima(e, topY);
+
+    if (IsMaximaEdge) {
+      TEdge *eMaxPair = GetMaximaPairEx(e);
+      IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair));
+    }
+
+    if (IsMaximaEdge) {
+      if (m_StrictSimple)
+        m_Maxima.push_back(e->Top.X);
+      TEdge *ePrev = e->PrevInAEL;
+      DoMaxima(e);
+      if (!ePrev)
+        e = m_ActiveEdges;
+      else
+        e = ePrev->NextInAEL;
+    } else {
+      // 2. promote horizontal edges, otherwise update Curr.X and Curr.Y ...
+      if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) {
+        UpdateEdgeIntoAEL(e);
+        if (e->OutIdx >= 0)
+          AddOutPt(e, e->Bot);
+        AddEdgeToSEL(e);
+      } else {
+        e->Curr.X = TopX(*e, topY);
+        e->Curr.Y = topY;
+#ifdef use_xyz
+        e->Curr.Z =
+            topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0);
+#endif
+      }
+
+      // When StrictlySimple and 'e' is being touched by another edge, then
+      // make sure both edges have a vertex here ...
+      if (m_StrictSimple) {
+        TEdge *ePrev = e->PrevInAEL;
+        if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev &&
+            (ePrev->OutIdx >= 0) && (ePrev->Curr.X == e->Curr.X) &&
+            (ePrev->WindDelta != 0)) {
+          IntPoint pt = e->Curr;
+#ifdef use_xyz
+          SetZ(pt, *ePrev, *e);
+#endif
+          OutPt *op = AddOutPt(ePrev, pt);
+          OutPt *op2 = AddOutPt(e, pt);
+          AddJoin(op, op2, pt); // StrictlySimple (type-3) join
+        }
+      }
+
+      e = e->NextInAEL;
+    }
+  }
+
+  // 3. Process horizontals at the Top of the scanbeam ...
+  m_Maxima.sort();
+  ProcessHorizontals();
+  m_Maxima.clear();
+
+  // 4. Promote intermediate vertices ...
+  e = m_ActiveEdges;
+  while (e) {
+    if (IsIntermediate(e, topY)) {
+      OutPt *op = 0;
+      if (e->OutIdx >= 0)
+        op = AddOutPt(e, e->Top);
+      UpdateEdgeIntoAEL(e);
+
+      // if output polygons share an edge, they'll need joining later ...
+      TEdge *ePrev = e->PrevInAEL;
+      TEdge *eNext = e->NextInAEL;
+      if (ePrev && ePrev->Curr.X == e->Bot.X && ePrev->Curr.Y == e->Bot.Y &&
+          op && ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
+          SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top,
+                      m_UseFullRange) &&
+          (e->WindDelta != 0) && (ePrev->WindDelta != 0)) {
+        OutPt *op2 = AddOutPt(ePrev, e->Bot);
+        AddJoin(op, op2, e->Top);
+      } else if (eNext && eNext->Curr.X == e->Bot.X &&
+                 eNext->Curr.Y == e->Bot.Y && op && eNext->OutIdx >= 0 &&
+                 eNext->Curr.Y > eNext->Top.Y &&
+                 SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top,
+                             m_UseFullRange) &&
+                 (e->WindDelta != 0) && (eNext->WindDelta != 0)) {
+        OutPt *op2 = AddOutPt(eNext, e->Bot);
+        AddJoin(op, op2, e->Top);
+      }
+    }
+    e = e->NextInAEL;
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixupOutPolyline(OutRec &outrec) {
+  OutPt *pp = outrec.Pts;
+  OutPt *lastPP = pp->Prev;
+  while (pp != lastPP) {
+    pp = pp->Next;
+    if (pp->Pt == pp->Prev->Pt) {
+      if (pp == lastPP)
+        lastPP = pp->Prev;
+      OutPt *tmpPP = pp->Prev;
+      tmpPP->Next = pp->Next;
+      pp->Next->Prev = tmpPP;
+      delete pp;
+      pp = tmpPP;
+    }
+  }
+
+  if (pp == pp->Prev) {
+    DisposeOutPts(pp);
+    outrec.Pts = 0;
+    return;
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixupOutPolygon(OutRec &outrec) {
+  // FixupOutPolygon() - removes duplicate points and simplifies consecutive
+  // parallel edges by removing the middle vertex.
+  OutPt *lastOK = 0;
+  outrec.BottomPt = 0;
+  OutPt *pp = outrec.Pts;
+  bool preserveCol = m_PreserveCollinear || m_StrictSimple;
+
+  for (;;) {
+    if (pp->Prev == pp || pp->Prev == pp->Next) {
+      DisposeOutPts(pp);
+      outrec.Pts = 0;
+      return;
+    }
+
+    // test for duplicate points and collinear edges ...
+    if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) ||
+        (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) &&
+         (!preserveCol ||
+          !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) {
+      lastOK = 0;
+      OutPt *tmp = pp;
+      pp->Prev->Next = pp->Next;
+      pp->Next->Prev = pp->Prev;
+      pp = pp->Prev;
+      delete tmp;
+    } else if (pp == lastOK)
+      break;
+    else {
+      if (!lastOK)
+        lastOK = pp;
+      pp = pp->Next;
+    }
+  }
+  outrec.Pts = pp;
+}
+//------------------------------------------------------------------------------
+
+int PointCount(OutPt *Pts) {
+  if (!Pts)
+    return 0;
+  int result = 0;
+  OutPt *p = Pts;
+  do {
+    result++;
+    p = p->Next;
+  } while (p != Pts);
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildResult(Paths &polys) {
+  polys.reserve(m_PolyOuts.size());
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+    if (!m_PolyOuts[i]->Pts)
+      continue;
+    Path pg;
+    OutPt *p = m_PolyOuts[i]->Pts->Prev;
+    int cnt = PointCount(p);
+    if (cnt < 2)
+      continue;
+    pg.reserve(cnt);
+    for (int i = 0; i < cnt; ++i) {
+      pg.push_back(p->Pt);
+      p = p->Prev;
+    }
+    polys.push_back(pg);
+  }
+}
+//------------------------------------------------------------------------------
+
+void Clipper::BuildResult2(PolyTree &polytree) {
+  polytree.Clear();
+  polytree.AllNodes.reserve(m_PolyOuts.size());
+  // add each output polygon/contour to polytree ...
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
+    OutRec *outRec = m_PolyOuts[i];
+    int cnt = PointCount(outRec->Pts);
+    if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3))
+      continue;
+    FixHoleLinkage(*outRec);
+    PolyNode *pn = new PolyNode();
+    // nb: polytree takes ownership of all the PolyNodes
+    polytree.AllNodes.push_back(pn);
+    outRec->PolyNd = pn;
+    pn->Parent = 0;
+    pn->Index = 0;
+    pn->Contour.reserve(cnt);
+    OutPt *op = outRec->Pts->Prev;
+    for (int j = 0; j < cnt; j++) {
+      pn->Contour.push_back(op->Pt);
+      op = op->Prev;
+    }
+  }
+
+  // fixup PolyNode links etc ...
+  polytree.Childs.reserve(m_PolyOuts.size());
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
+    OutRec *outRec = m_PolyOuts[i];
+    if (!outRec->PolyNd)
+      continue;
+    if (outRec->IsOpen) {
+      outRec->PolyNd->m_IsOpen = true;
+      polytree.AddChild(*outRec->PolyNd);
+    } else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd)
+      outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd);
+    else
+      polytree.AddChild(*outRec->PolyNd);
+  }
+}
+//------------------------------------------------------------------------------
+
+void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) {
+  // just swap the contents (because fIntersectNodes is a single-linked-list)
+  IntersectNode inode = int1; // gets a copy of Int1
+  int1.Edge1 = int2.Edge1;
+  int1.Edge2 = int2.Edge2;
+  int1.Pt = int2.Pt;
+  int2.Edge1 = inode.Edge1;
+  int2.Edge2 = inode.Edge2;
+  int2.Pt = inode.Pt;
+}
+//------------------------------------------------------------------------------
+
+inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) {
+  if (e2.Curr.X == e1.Curr.X) {
+    if (e2.Top.Y > e1.Top.Y)
+      return e2.Top.X < TopX(e1, e2.Top.Y);
+    else
+      return e1.Top.X > TopX(e2, e1.Top.Y);
+  } else
+    return e2.Curr.X < e1.Curr.X;
+}
+//------------------------------------------------------------------------------
+
+bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2,
+                cInt &Left, cInt &Right) {
+  if (a1 < a2) {
+    if (b1 < b2) {
+      Left = std::max(a1, b1);
+      Right = std::min(a2, b2);
+    } else {
+      Left = std::max(a1, b2);
+      Right = std::min(a2, b1);
+    }
+  } else {
+    if (b1 < b2) {
+      Left = std::max(a2, b1);
+      Right = std::min(a1, b2);
+    } else {
+      Left = std::max(a2, b2);
+      Right = std::min(a1, b1);
+    }
+  }
+  return Left < Right;
+}
+//------------------------------------------------------------------------------
+
+inline void UpdateOutPtIdxs(OutRec &outrec) {
+  OutPt *op = outrec.Pts;
+  do {
+    op->Idx = outrec.Idx;
+    op = op->Prev;
+  } while (op != outrec.Pts);
+}
+//------------------------------------------------------------------------------
+
+void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge) {
+  if (!m_ActiveEdges) {
+    edge->PrevInAEL = 0;
+    edge->NextInAEL = 0;
+    m_ActiveEdges = edge;
+  } else if (!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) {
+    edge->PrevInAEL = 0;
+    edge->NextInAEL = m_ActiveEdges;
+    m_ActiveEdges->PrevInAEL = edge;
+    m_ActiveEdges = edge;
+  } else {
+    if (!startEdge)
+      startEdge = m_ActiveEdges;
+    while (startEdge->NextInAEL &&
+           !E2InsertsBeforeE1(*startEdge->NextInAEL, *edge))
+      startEdge = startEdge->NextInAEL;
+    edge->NextInAEL = startEdge->NextInAEL;
+    if (startEdge->NextInAEL)
+      startEdge->NextInAEL->PrevInAEL = edge;
+    edge->PrevInAEL = startEdge;
+    startEdge->NextInAEL = edge;
+  }
+}
+//----------------------------------------------------------------------
+
+OutPt *DupOutPt(OutPt *outPt, bool InsertAfter) {
+  OutPt *result = new OutPt;
+  result->Pt = outPt->Pt;
+  result->Idx = outPt->Idx;
+  if (InsertAfter) {
+    result->Next = outPt->Next;
+    result->Prev = outPt;
+    outPt->Next->Prev = result;
+    outPt->Next = result;
+  } else {
+    result->Prev = outPt->Prev;
+    result->Next = outPt;
+    outPt->Prev->Next = result;
+    outPt->Prev = result;
+  }
+  return result;
+}
+//------------------------------------------------------------------------------
+
+bool JoinHorz(OutPt *op1, OutPt *op1b, OutPt *op2, OutPt *op2b,
+              const IntPoint Pt, bool DiscardLeft) {
+  Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight);
+  Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight);
+  if (Dir1 == Dir2)
+    return false;
+
+  // When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we
+  // want Op1b to be on the Right. (And likewise with Op2 and Op2b.)
+  // So, to facilitate this while inserting Op1b and Op2b ...
+  // when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b,
+  // otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.)
+  if (Dir1 == dLeftToRight) {
+    while (op1->Next->Pt.X <= Pt.X && op1->Next->Pt.X >= op1->Pt.X &&
+           op1->Next->Pt.Y == Pt.Y)
+      op1 = op1->Next;
+    if (DiscardLeft && (op1->Pt.X != Pt.X))
+      op1 = op1->Next;
+    op1b = DupOutPt(op1, !DiscardLeft);
+    if (op1b->Pt != Pt) {
+      op1 = op1b;
+      op1->Pt = Pt;
+      op1b = DupOutPt(op1, !DiscardLeft);
+    }
+  } else {
+    while (op1->Next->Pt.X >= Pt.X && op1->Next->Pt.X <= op1->Pt.X &&
+           op1->Next->Pt.Y == Pt.Y)
+      op1 = op1->Next;
+    if (!DiscardLeft && (op1->Pt.X != Pt.X))
+      op1 = op1->Next;
+    op1b = DupOutPt(op1, DiscardLeft);
+    if (op1b->Pt != Pt) {
+      op1 = op1b;
+      op1->Pt = Pt;
+      op1b = DupOutPt(op1, DiscardLeft);
+    }
+  }
+
+  if (Dir2 == dLeftToRight) {
+    while (op2->Next->Pt.X <= Pt.X && op2->Next->Pt.X >= op2->Pt.X &&
+           op2->Next->Pt.Y == Pt.Y)
+      op2 = op2->Next;
+    if (DiscardLeft && (op2->Pt.X != Pt.X))
+      op2 = op2->Next;
+    op2b = DupOutPt(op2, !DiscardLeft);
+    if (op2b->Pt != Pt) {
+      op2 = op2b;
+      op2->Pt = Pt;
+      op2b = DupOutPt(op2, !DiscardLeft);
+    };
+  } else {
+    while (op2->Next->Pt.X >= Pt.X && op2->Next->Pt.X <= op2->Pt.X &&
+           op2->Next->Pt.Y == Pt.Y)
+      op2 = op2->Next;
+    if (!DiscardLeft && (op2->Pt.X != Pt.X))
+      op2 = op2->Next;
+    op2b = DupOutPt(op2, DiscardLeft);
+    if (op2b->Pt != Pt) {
+      op2 = op2b;
+      op2->Pt = Pt;
+      op2b = DupOutPt(op2, DiscardLeft);
+    };
+  };
+
+  if ((Dir1 == dLeftToRight) == DiscardLeft) {
+    op1->Prev = op2;
+    op2->Next = op1;
+    op1b->Next = op2b;
+    op2b->Prev = op1b;
+  } else {
+    op1->Next = op2;
+    op2->Prev = op1;
+    op1b->Prev = op2b;
+    op2b->Next = op1b;
+  }
+  return true;
+}
+//------------------------------------------------------------------------------
+
+bool Clipper::JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2) {
+  OutPt *op1 = j->OutPt1, *op1b;
+  OutPt *op2 = j->OutPt2, *op2b;
+
+  // There are 3 kinds of joins for output polygons ...
+  // 1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere
+  // along (horizontal) collinear edges (& Join.OffPt is on the same
+  // horizontal).
+  // 2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
+  // location at the Bottom of the overlapping segment (& Join.OffPt is above).
+  // 3. StrictSimple joins where edges touch but are not collinear and where
+  // Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point.
+  bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y);
+
+  if (isHorizontal && (j->OffPt == j->OutPt1->Pt) &&
+      (j->OffPt == j->OutPt2->Pt)) {
+    // Strictly Simple join ...
+    if (outRec1 != outRec2)
+      return false;
+    op1b = j->OutPt1->Next;
+    while (op1b != op1 && (op1b->Pt == j->OffPt))
+      op1b = op1b->Next;
+    bool reverse1 = (op1b->Pt.Y > j->OffPt.Y);
+    op2b = j->OutPt2->Next;
+    while (op2b != op2 && (op2b->Pt == j->OffPt))
+      op2b = op2b->Next;
+    bool reverse2 = (op2b->Pt.Y > j->OffPt.Y);
+    if (reverse1 == reverse2)
+      return false;
+    if (reverse1) {
+      op1b = DupOutPt(op1, false);
+      op2b = DupOutPt(op2, true);
+      op1->Prev = op2;
+      op2->Next = op1;
+      op1b->Next = op2b;
+      op2b->Prev = op1b;
+      j->OutPt1 = op1;
+      j->OutPt2 = op1b;
+      return true;
+    } else {
+      op1b = DupOutPt(op1, true);
+      op2b = DupOutPt(op2, false);
+      op1->Next = op2;
+      op2->Prev = op1;
+      op1b->Prev = op2b;
+      op2b->Next = op1b;
+      j->OutPt1 = op1;
+      j->OutPt2 = op1b;
+      return true;
+    }
+  } else if (isHorizontal) {
+    // treat horizontal joins differently to non-horizontal joins since with
+    // them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt
+    // may be anywhere along the horizontal edge.
+    op1b = op1;
+    while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b &&
+           op1->Prev != op2)
+      op1 = op1->Prev;
+    while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 &&
+           op1b->Next != op2)
+      op1b = op1b->Next;
+    if (op1b->Next == op1 || op1b->Next == op2)
+      return false; // a flat 'polygon'
+
+    op2b = op2;
+    while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b &&
+           op2->Prev != op1b)
+      op2 = op2->Prev;
+    while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 &&
+           op2b->Next != op1)
+      op2b = op2b->Next;
+    if (op2b->Next == op2 || op2b->Next == op1)
+      return false; // a flat 'polygon'
+
+    cInt Left, Right;
+    // Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges
+    if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right))
+      return false;
+
+    // DiscardLeftSide: when overlapping edges are joined, a spike will created
+    // which needs to be cleaned up. However, we don't want Op1 or Op2 caught up
+    // on the discard Side as either may still be needed for other joins ...
+    IntPoint Pt;
+    bool DiscardLeftSide;
+    if (op1->Pt.X >= Left && op1->Pt.X <= Right) {
+      Pt = op1->Pt;
+      DiscardLeftSide = (op1->Pt.X > op1b->Pt.X);
+    } else if (op2->Pt.X >= Left && op2->Pt.X <= Right) {
+      Pt = op2->Pt;
+      DiscardLeftSide = (op2->Pt.X > op2b->Pt.X);
+    } else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) {
+      Pt = op1b->Pt;
+      DiscardLeftSide = op1b->Pt.X > op1->Pt.X;
+    } else {
+      Pt = op2b->Pt;
+      DiscardLeftSide = (op2b->Pt.X > op2->Pt.X);
+    }
+    j->OutPt1 = op1;
+    j->OutPt2 = op2;
+    return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide);
+  } else {
+    // nb: For non-horizontal joins ...
+    //    1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y
+    //    2. Jr.OutPt1.Pt > Jr.OffPt.Y
+
+    // make sure the polygons are correctly oriented ...
+    op1b = op1->Next;
+    while ((op1b->Pt == op1->Pt) && (op1b != op1))
+      op1b = op1b->Next;
+    bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) ||
+                     !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange));
+    if (Reverse1) {
+      op1b = op1->Prev;
+      while ((op1b->Pt == op1->Pt) && (op1b != op1))
+        op1b = op1b->Prev;
+      if ((op1b->Pt.Y > op1->Pt.Y) ||
+          !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange))
+        return false;
+    };
+    op2b = op2->Next;
+    while ((op2b->Pt == op2->Pt) && (op2b != op2))
+      op2b = op2b->Next;
+    bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) ||
+                     !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange));
+    if (Reverse2) {
+      op2b = op2->Prev;
+      while ((op2b->Pt == op2->Pt) && (op2b != op2))
+        op2b = op2b->Prev;
+      if ((op2b->Pt.Y > op2->Pt.Y) ||
+          !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange))
+        return false;
+    }
+
+    if ((op1b == op1) || (op2b == op2) || (op1b == op2b) ||
+        ((outRec1 == outRec2) && (Reverse1 == Reverse2)))
+      return false;
+
+    if (Reverse1) {
+      op1b = DupOutPt(op1, false);
+      op2b = DupOutPt(op2, true);
+      op1->Prev = op2;
+      op2->Next = op1;
+      op1b->Next = op2b;
+      op2b->Prev = op1b;
+      j->OutPt1 = op1;
+      j->OutPt2 = op1b;
+      return true;
+    } else {
+      op1b = DupOutPt(op1, true);
+      op2b = DupOutPt(op2, false);
+      op1->Next = op2;
+      op2->Prev = op1;
+      op1b->Prev = op2b;
+      op2b->Next = op1b;
+      j->OutPt1 = op1;
+      j->OutPt2 = op1b;
+      return true;
+    }
+  }
+}
+//----------------------------------------------------------------------
+
+static OutRec *ParseFirstLeft(OutRec *FirstLeft) {
+  while (FirstLeft && !FirstLeft->Pts)
+    FirstLeft = FirstLeft->FirstLeft;
+  return FirstLeft;
+}
+//------------------------------------------------------------------------------
+
+void Clipper::FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec) {
+  // tests if NewOutRec contains the polygon before reassigning FirstLeft
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+    OutRec *outRec = m_PolyOuts[i];
+    OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
+    if (outRec->Pts && firstLeft == OldOutRec) {
+      if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts))
+        outRec->FirstLeft = NewOutRec;
+    }
+  }
+}
+//----------------------------------------------------------------------
+
+void Clipper::FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec) {
+  // A polygon has split into two such that one is now the inner of the other.
+  // It's possible that these polygons now wrap around other polygons, so check
+  // every polygon that's also contained by OuterOutRec's FirstLeft container
+  //(including 0) to see if they've become inner to the new inner polygon ...
+  OutRec *orfl = OuterOutRec->FirstLeft;
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+    OutRec *outRec = m_PolyOuts[i];
+
+    if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec)
+      continue;
+    OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
+    if (firstLeft != orfl && firstLeft != InnerOutRec &&
+        firstLeft != OuterOutRec)
+      continue;
+    if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts))
+      outRec->FirstLeft = InnerOutRec;
+    else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts))
+      outRec->FirstLeft = OuterOutRec;
+    else if (outRec->FirstLeft == InnerOutRec ||
+             outRec->FirstLeft == OuterOutRec)
+      outRec->FirstLeft = orfl;
+  }
+}
+//----------------------------------------------------------------------
+void Clipper::FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec) {
+  // reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon
+  for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
+    OutRec *outRec = m_PolyOuts[i];
+    OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
+    if (outRec->Pts && firstLeft == OldOutRec)
+      outRec->FirstLeft = NewOutRec;
+  }
+}
+//----------------------------------------------------------------------
+
+void Clipper::JoinCommonEdges() {
+  for (JoinList::size_type i = 0; i < m_Joins.size(); i++) {
+    Join *join = m_Joins[i];
+
+    OutRec *outRec1 = GetOutRec(join->OutPt1->Idx);
+    OutRec *outRec2 = GetOutRec(join->OutPt2->Idx);
+
+    if (!outRec1->Pts || !outRec2->Pts)
+      continue;
+    if (outRec1->IsOpen || outRec2->IsOpen)
+      continue;
+
+    // get the polygon fragment with the correct hole state (FirstLeft)
+    // before calling JoinPoints() ...
+    OutRec *holeStateRec;
+    if (outRec1 == outRec2)
+      holeStateRec = outRec1;
+    else if (OutRec1RightOfOutRec2(outRec1, outRec2))
+      holeStateRec = outRec2;
+    else if (OutRec1RightOfOutRec2(outRec2, outRec1))
+      holeStateRec = outRec1;
+    else
+      holeStateRec = GetLowermostRec(outRec1, outRec2);
+
+    if (!JoinPoints(join, outRec1, outRec2))
+      continue;
+
+    if (outRec1 == outRec2) {
+      // instead of joining two polygons, we've just created a new one by
+      // splitting one polygon into two.
+      outRec1->Pts = join->OutPt1;
+      outRec1->BottomPt = 0;
+      outRec2 = CreateOutRec();
+      outRec2->Pts = join->OutPt2;
+
+      // update all OutRec2.Pts Idx's ...
+      UpdateOutPtIdxs(*outRec2);
+
+      if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) {
+        // outRec1 contains outRec2 ...
+        outRec2->IsHole = !outRec1->IsHole;
+        outRec2->FirstLeft = outRec1;
+
+        if (m_UsingPolyTree)
+          FixupFirstLefts2(outRec2, outRec1);
+
+        if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0))
+          ReversePolyPtLinks(outRec2->Pts);
+
+      } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) {
+        // outRec2 contains outRec1 ...
+        outRec2->IsHole = outRec1->IsHole;
+        outRec1->IsHole = !outRec2->IsHole;
+        outRec2->FirstLeft = outRec1->FirstLeft;
+        outRec1->FirstLeft = outRec2;
+
+        if (m_UsingPolyTree)
+          FixupFirstLefts2(outRec1, outRec2);
+
+        if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0))
+          ReversePolyPtLinks(outRec1->Pts);
+      } else {
+        // the 2 polygons are completely separate ...
+        outRec2->IsHole = outRec1->IsHole;
+        outRec2->FirstLeft = outRec1->FirstLeft;
+
+        // fixup FirstLeft pointers that may need reassigning to OutRec2
+        if (m_UsingPolyTree)
+          FixupFirstLefts1(outRec1, outRec2);
+      }
+
+    } else {
+      // joined 2 polygons together ...
+
+      outRec2->Pts = 0;
+      outRec2->BottomPt = 0;
+      outRec2->Idx = outRec1->Idx;
+
+      outRec1->IsHole = holeStateRec->IsHole;
+      if (holeStateRec == outRec2)
+        outRec1->FirstLeft = outRec2->FirstLeft;
+      outRec2->FirstLeft = outRec1;
+
+      if (m_UsingPolyTree)
+        FixupFirstLefts3(outRec2, outRec1);
+    }
+  }
+}
+
+//------------------------------------------------------------------------------
+// ClipperOffset support functions ...
+//------------------------------------------------------------------------------
+
+DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) {
+  if (pt2.X == pt1.X && pt2.Y == pt1.Y)
+    return DoublePoint(0, 0);
+
+  double Dx = (double)(pt2.X - pt1.X);
+  double dy = (double)(pt2.Y - pt1.Y);
+  double f = 1 * 1.0 / std::sqrt(Dx * Dx + dy * dy);
+  Dx *= f;
+  dy *= f;
+  return DoublePoint(dy, -Dx);
+}
+
+//------------------------------------------------------------------------------
+// ClipperOffset class
+//------------------------------------------------------------------------------
+
+ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) {
+  this->MiterLimit = miterLimit;
+  this->ArcTolerance = arcTolerance;
+  m_lowest.X = -1;
+}
+//------------------------------------------------------------------------------
+
+ClipperOffset::~ClipperOffset() { Clear(); }
+//------------------------------------------------------------------------------
+
+void ClipperOffset::Clear() {
+  for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
+    delete m_polyNodes.Childs[i];
+  m_polyNodes.Childs.clear();
+  m_lowest.X = -1;
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::AddPath(const Path &path, JoinType joinType,
+                            EndType endType) {
+  int highI = (int)path.size() - 1;
+  if (highI < 0)
+    return;
+  PolyNode *newNode = new PolyNode();
+  newNode->m_jointype = joinType;
+  newNode->m_endtype = endType;
+
+  // strip duplicate points from path and also get index to the lowest point ...
+  if (endType == etClosedLine || endType == etClosedPolygon)
+    while (highI > 0 && path[0] == path[highI])
+      highI--;
+  newNode->Contour.reserve(highI + 1);
+  newNode->Contour.push_back(path[0]);
+  int j = 0, k = 0;
+  for (int i = 1; i <= highI; i++)
+    if (newNode->Contour[j] != path[i]) {
+      j++;
+      newNode->Contour.push_back(path[i]);
+      if (path[i].Y > newNode->Contour[k].Y ||
+          (path[i].Y == newNode->Contour[k].Y &&
+           path[i].X < newNode->Contour[k].X))
+        k = j;
+    }
+  if (endType == etClosedPolygon && j < 2) {
+    delete newNode;
+    return;
+  }
+  m_polyNodes.AddChild(*newNode);
+
+  // if this path's lowest pt is lower than all the others then update m_lowest
+  if (endType != etClosedPolygon)
+    return;
+  if (m_lowest.X < 0)
+    m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
+  else {
+    IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y];
+    if (newNode->Contour[k].Y > ip.Y ||
+        (newNode->Contour[k].Y == ip.Y && newNode->Contour[k].X < ip.X))
+      m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
+  }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::AddPaths(const Paths &paths, JoinType joinType,
+                             EndType endType) {
+  for (Paths::size_type i = 0; i < paths.size(); ++i)
+    AddPath(paths[i], joinType, endType);
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::FixOrientations() {
+  // fixup orientations of all closed paths if the orientation of the
+  // closed path with the lowermost vertex is wrong ...
+  if (m_lowest.X >= 0 &&
+      !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) {
+    for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
+      PolyNode &node = *m_polyNodes.Childs[i];
+      if (node.m_endtype == etClosedPolygon ||
+          (node.m_endtype == etClosedLine && Orientation(node.Contour)))
+        ReversePath(node.Contour);
+    }
+  } else {
+    for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
+      PolyNode &node = *m_polyNodes.Childs[i];
+      if (node.m_endtype == etClosedLine && !Orientation(node.Contour))
+        ReversePath(node.Contour);
+    }
+  }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::Execute(Paths &solution, double delta) {
+  solution.clear();
+  FixOrientations();
+  DoOffset(delta);
+
+  // now clean up 'corners' ...
+  Clipper clpr;
+  clpr.AddPaths(m_destPolys, ptSubject, true);
+  if (delta > 0) {
+    clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
+  } else {
+    IntRect r = clpr.GetBounds();
+    Path outer(4);
+    outer[0] = IntPoint(r.left - 10, r.bottom + 10);
+    outer[1] = IntPoint(r.right + 10, r.bottom + 10);
+    outer[2] = IntPoint(r.right + 10, r.top - 10);
+    outer[3] = IntPoint(r.left - 10, r.top - 10);
+
+    clpr.AddPath(outer, ptSubject, true);
+    clpr.ReverseSolution(true);
+    clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
+    if (solution.size() > 0)
+      solution.erase(solution.begin());
+  }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::Execute(PolyTree &solution, double delta) {
+  solution.Clear();
+  FixOrientations();
+  DoOffset(delta);
+
+  // now clean up 'corners' ...
+  Clipper clpr;
+  clpr.AddPaths(m_destPolys, ptSubject, true);
+  if (delta > 0) {
+    clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
+  } else {
+    IntRect r = clpr.GetBounds();
+    Path outer(4);
+    outer[0] = IntPoint(r.left - 10, r.bottom + 10);
+    outer[1] = IntPoint(r.right + 10, r.bottom + 10);
+    outer[2] = IntPoint(r.right + 10, r.top - 10);
+    outer[3] = IntPoint(r.left - 10, r.top - 10);
+
+    clpr.AddPath(outer, ptSubject, true);
+    clpr.ReverseSolution(true);
+    clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
+    // remove the outer PolyNode rectangle ...
+    if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) {
+      PolyNode *outerNode = solution.Childs[0];
+      solution.Childs.reserve(outerNode->ChildCount());
+      solution.Childs[0] = outerNode->Childs[0];
+      solution.Childs[0]->Parent = outerNode->Parent;
+      for (int i = 1; i < outerNode->ChildCount(); ++i)
+        solution.AddChild(*outerNode->Childs[i]);
+    } else
+      solution.Clear();
+  }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoOffset(double delta) {
+  m_destPolys.clear();
+  m_delta = delta;
+
+  // if Zero offset, just copy any CLOSED polygons to m_p and return ...
+  if (NEAR_ZERO(delta)) {
+    m_destPolys.reserve(m_polyNodes.ChildCount());
+    for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
+      PolyNode &node = *m_polyNodes.Childs[i];
+      if (node.m_endtype == etClosedPolygon)
+        m_destPolys.push_back(node.Contour);
+    }
+    return;
+  }
+
+  // see offset_triginometry3.svg in the documentation folder ...
+  if (MiterLimit > 2)
+    m_miterLim = 2 / (MiterLimit * MiterLimit);
+  else
+    m_miterLim = 0.5;
+
+  double y;
+  if (ArcTolerance <= 0.0)
+    y = def_arc_tolerance;
+  else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance)
+    y = std::fabs(delta) * def_arc_tolerance;
+  else
+    y = ArcTolerance;
+  // see offset_triginometry2.svg in the documentation folder ...
+  double steps = pi / std::acos(1 - y / std::fabs(delta));
+  if (steps > std::fabs(delta) * pi)
+    steps = std::fabs(delta) * pi; // ie excessive precision check
+  m_sin = std::sin(two_pi / steps);
+  m_cos = std::cos(two_pi / steps);
+  m_StepsPerRad = steps / two_pi;
+  if (delta < 0.0)
+    m_sin = -m_sin;
+
+  m_destPolys.reserve(m_polyNodes.ChildCount() * 2);
+  for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
+    PolyNode &node = *m_polyNodes.Childs[i];
+    m_srcPoly = node.Contour;
+
+    int len = (int)m_srcPoly.size();
+    if (len == 0 ||
+        (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon)))
+      continue;
+
+    m_destPoly.clear();
+    if (len == 1) {
+      if (node.m_jointype == jtRound) {
+        double X = 1.0, Y = 0.0;
+        for (cInt j = 1; j <= steps; j++) {
+          m_destPoly.push_back(IntPoint(Round(m_srcPoly[0].X + X * delta),
+                                        Round(m_srcPoly[0].Y + Y * delta)));
+          double X2 = X;
+          X = X * m_cos - m_sin * Y;
+          Y = X2 * m_sin + Y * m_cos;
+        }
+      } else {
+        double X = -1.0, Y = -1.0;
+        for (int j = 0; j < 4; ++j) {
+          m_destPoly.push_back(IntPoint(Round(m_srcPoly[0].X + X * delta),
+                                        Round(m_srcPoly[0].Y + Y * delta)));
+          if (X < 0)
+            X = 1;
+          else if (Y < 0)
+            Y = 1;
+          else
+            X = -1;
+        }
+      }
+      m_destPolys.push_back(m_destPoly);
+      continue;
+    }
+    // build m_normals ...
+    m_normals.clear();
+    m_normals.reserve(len);
+    for (int j = 0; j < len - 1; ++j)
+      m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1]));
+    if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon)
+      m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0]));
+    else
+      m_normals.push_back(DoublePoint(m_normals[len - 2]));
+
+    if (node.m_endtype == etClosedPolygon) {
+      int k = len - 1;
+      for (int j = 0; j < len; ++j)
+        OffsetPoint(j, k, node.m_jointype);
+      m_destPolys.push_back(m_destPoly);
+    } else if (node.m_endtype == etClosedLine) {
+      int k = len - 1;
+      for (int j = 0; j < len; ++j)
+        OffsetPoint(j, k, node.m_jointype);
+      m_destPolys.push_back(m_destPoly);
+      m_destPoly.clear();
+      // re-build m_normals ...
+      DoublePoint n = m_normals[len - 1];
+      for (int j = len - 1; j > 0; j--)
+        m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
+      m_normals[0] = DoublePoint(-n.X, -n.Y);
+      k = 0;
+      for (int j = len - 1; j >= 0; j--)
+        OffsetPoint(j, k, node.m_jointype);
+      m_destPolys.push_back(m_destPoly);
+    } else {
+      int k = 0;
+      for (int j = 1; j < len - 1; ++j)
+        OffsetPoint(j, k, node.m_jointype);
+
+      IntPoint pt1;
+      if (node.m_endtype == etOpenButt) {
+        int j = len - 1;
+        pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * delta),
+                       (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta));
+        m_destPoly.push_back(pt1);
+        pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * delta),
+                       (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta));
+        m_destPoly.push_back(pt1);
+      } else {
+        int j = len - 1;
+        k = len - 2;
+        m_sinA = 0;
+        m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y);
+        if (node.m_endtype == etOpenSquare)
+          DoSquare(j, k);
+        else
+          DoRound(j, k);
+      }
+
+      // re-build m_normals ...
+      for (int j = len - 1; j > 0; j--)
+        m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
+      m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y);
+
+      k = len - 1;
+      for (int j = k - 1; j > 0; --j)
+        OffsetPoint(j, k, node.m_jointype);
+
+      if (node.m_endtype == etOpenButt) {
+        pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta),
+                       (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta));
+        m_destPoly.push_back(pt1);
+        pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta),
+                       (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta));
+        m_destPoly.push_back(pt1);
+      } else {
+        k = 1;
+        m_sinA = 0;
+        if (node.m_endtype == etOpenSquare)
+          DoSquare(0, 1);
+        else
+          DoRound(0, 1);
+      }
+      m_destPolys.push_back(m_destPoly);
+    }
+  }
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::OffsetPoint(int j, int &k, JoinType jointype) {
+  // cross product ...
+  m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y);
+  if (std::fabs(m_sinA * m_delta) < 1.0) {
+    // dot product ...
+    double cosA =
+        (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y);
+    if (cosA > 0) // angle => 0 degrees
+    {
+      m_destPoly.push_back(
+          IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
+                   Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
+      return;
+    }
+    // else angle => 180 degrees
+  } else if (m_sinA > 1.0)
+    m_sinA = 1.0;
+  else if (m_sinA < -1.0)
+    m_sinA = -1.0;
+
+  if (m_sinA * m_delta < 0) {
+    m_destPoly.push_back(
+        IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
+                 Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
+    m_destPoly.push_back(m_srcPoly[j]);
+    m_destPoly.push_back(
+        IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
+                 Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
+  } else
+    switch (jointype) {
+    case jtMiter: {
+      double r = 1 + (m_normals[j].X * m_normals[k].X +
+                      m_normals[j].Y * m_normals[k].Y);
+      if (r >= m_miterLim)
+        DoMiter(j, k, r);
+      else
+        DoSquare(j, k);
+      break;
+    }
+    case jtSquare:
+      DoSquare(j, k);
+      break;
+    case jtRound:
+      DoRound(j, k);
+      break;
+    }
+  k = j;
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoSquare(int j, int k) {
+  double dx = std::tan(std::atan2(m_sinA, m_normals[k].X * m_normals[j].X +
+                                              m_normals[k].Y * m_normals[j].Y) /
+                       4);
+  m_destPoly.push_back(IntPoint(
+      Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)),
+      Round(m_srcPoly[j].Y +
+            m_delta * (m_normals[k].Y + m_normals[k].X * dx))));
+  m_destPoly.push_back(IntPoint(
+      Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)),
+      Round(m_srcPoly[j].Y +
+            m_delta * (m_normals[j].Y - m_normals[j].X * dx))));
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoMiter(int j, int k, double r) {
+  double q = m_delta / r;
+  m_destPoly.push_back(
+      IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q),
+               Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q)));
+}
+//------------------------------------------------------------------------------
+
+void ClipperOffset::DoRound(int j, int k) {
+  double a = std::atan2(m_sinA, m_normals[k].X * m_normals[j].X +
+                                    m_normals[k].Y * m_normals[j].Y);
+  int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1);
+
+  double X = m_normals[k].X, Y = m_normals[k].Y, X2;
+  for (int i = 0; i < steps; ++i) {
+    m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + X * m_delta),
+                                  Round(m_srcPoly[j].Y + Y * m_delta)));
+    X2 = X;
+    X = X * m_cos - m_sin * Y;
+    Y = X2 * m_sin + Y * m_cos;
+  }
+  m_destPoly.push_back(
+      IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
+               Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
+}
+
+//------------------------------------------------------------------------------
+// Miscellaneous public functions
+//------------------------------------------------------------------------------
+
+void Clipper::DoSimplePolygons() {
+  PolyOutList::size_type i = 0;
+  while (i < m_PolyOuts.size()) {
+    OutRec *outrec = m_PolyOuts[i++];
+    OutPt *op = outrec->Pts;
+    if (!op || outrec->IsOpen)
+      continue;
+    do // for each Pt in Polygon until duplicate found do ...
+    {
+      OutPt *op2 = op->Next;
+      while (op2 != outrec->Pts) {
+        if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) {
+          // split the polygon into two ...
+          OutPt *op3 = op->Prev;
+          OutPt *op4 = op2->Prev;
+          op->Prev = op4;
+          op4->Next = op;
+          op2->Prev = op3;
+          op3->Next = op2;
+
+          outrec->Pts = op;
+          OutRec *outrec2 = CreateOutRec();
+          outrec2->Pts = op2;
+          UpdateOutPtIdxs(*outrec2);
+          if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) {
+            // OutRec2 is contained by OutRec1 ...
+            outrec2->IsHole = !outrec->IsHole;
+            outrec2->FirstLeft = outrec;
+            if (m_UsingPolyTree)
+              FixupFirstLefts2(outrec2, outrec);
+          } else if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) {
+            // OutRec1 is contained by OutRec2 ...
+            outrec2->IsHole = outrec->IsHole;
+            outrec->IsHole = !outrec2->IsHole;
+            outrec2->FirstLeft = outrec->FirstLeft;
+            outrec->FirstLeft = outrec2;
+            if (m_UsingPolyTree)
+              FixupFirstLefts2(outrec, outrec2);
+          } else {
+            // the 2 polygons are separate ...
+            outrec2->IsHole = outrec->IsHole;
+            outrec2->FirstLeft = outrec->FirstLeft;
+            if (m_UsingPolyTree)
+              FixupFirstLefts1(outrec, outrec2);
+          }
+          op2 = op; // ie get ready for the Next iteration
+        }
+        op2 = op2->Next;
+      }
+      op = op->Next;
+    } while (op != outrec->Pts);
+  }
+}
+//------------------------------------------------------------------------------
+
+void ReversePath(Path &p) { std::reverse(p.begin(), p.end()); }
+//------------------------------------------------------------------------------
+
+void ReversePaths(Paths &p) {
+  for (Paths::size_type i = 0; i < p.size(); ++i)
+    ReversePath(p[i]);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygon(const Path &in_poly, Paths &out_polys,
+                     PolyFillType fillType) {
+  Clipper c;
+  c.StrictlySimple(true);
+  c.AddPath(in_poly, ptSubject, true);
+  c.Execute(ctUnion, out_polys, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygons(const Paths &in_polys, Paths &out_polys,
+                      PolyFillType fillType) {
+  Clipper c;
+  c.StrictlySimple(true);
+  c.AddPaths(in_polys, ptSubject, true);
+  c.Execute(ctUnion, out_polys, fillType, fillType);
+}
+//------------------------------------------------------------------------------
+
+void SimplifyPolygons(Paths &polys, PolyFillType fillType) {
+  SimplifyPolygons(polys, polys, fillType);
+}
+//------------------------------------------------------------------------------
+
+inline double DistanceSqrd(const IntPoint &pt1, const IntPoint &pt2) {
+  double Dx = ((double)pt1.X - pt2.X);
+  double dy = ((double)pt1.Y - pt2.Y);
+  return (Dx * Dx + dy * dy);
+}
+//------------------------------------------------------------------------------
+
+double DistanceFromLineSqrd(const IntPoint &pt, const IntPoint &ln1,
+                            const IntPoint &ln2) {
+  // The equation of a line in general form (Ax + By + C = 0)
+  // given 2 points (x�,y�) & (x�,y�) is ...
+  //(y� - y�)x + (x� - x�)y + (y� - y�)x� - (x� - x�)y� = 0
+  // A = (y� - y�); B = (x� - x�); C = (y� - y�)x� - (x� - x�)y�
+  // perpendicular distance of point (x�,y�) = (Ax� + By� + C)/Sqrt(A� + B�)
+  // see http://en.wikipedia.org/wiki/Perpendicular_distance
+  double A = double(ln1.Y - ln2.Y);
+  double B = double(ln2.X - ln1.X);
+  double C = A * ln1.X + B * ln1.Y;
+  C = A * pt.X + B * pt.Y - C;
+  return (C * C) / (A * A + B * B);
+}
+//---------------------------------------------------------------------------
+
+bool SlopesNearCollinear(const IntPoint &pt1, const IntPoint &pt2,
+                         const IntPoint &pt3, double distSqrd) {
+  // this function is more accurate when the point that's geometrically
+  // between the other 2 points is the one that's tested for distance.
+  // ie makes it more likely to pick up 'spikes' ...
+  if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) {
+    if ((pt1.X > pt2.X) == (pt1.X < pt3.X))
+      return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
+    else if ((pt2.X > pt1.X) == (pt2.X < pt3.X))
+      return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
+    else
+      return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
+  } else {
+    if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y))
+      return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
+    else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y))
+      return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
+    else
+      return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
+  }
+}
+//------------------------------------------------------------------------------
+
+bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) {
+  double Dx = (double)pt1.X - pt2.X;
+  double dy = (double)pt1.Y - pt2.Y;
+  return ((Dx * Dx) + (dy * dy) <= distSqrd);
+}
+//------------------------------------------------------------------------------
+
+OutPt *ExcludeOp(OutPt *op) {
+  OutPt *result = op->Prev;
+  result->Next = op->Next;
+  op->Next->Prev = result;
+  result->Idx = 0;
+  return result;
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygon(const Path &in_poly, Path &out_poly, double distance) {
+  // distance = proximity in units/pixels below which vertices
+  // will be stripped. Default ~= sqrt(2).
+
+  size_t size = in_poly.size();
+
+  if (size == 0) {
+    out_poly.clear();
+    return;
+  }
+
+  OutPt *outPts = new OutPt[size];
+  for (size_t i = 0; i < size; ++i) {
+    outPts[i].Pt = in_poly[i];
+    outPts[i].Next = &outPts[(i + 1) % size];
+    outPts[i].Next->Prev = &outPts[i];
+    outPts[i].Idx = 0;
+  }
+
+  double distSqrd = distance * distance;
+  OutPt *op = &outPts[0];
+  while (op->Idx == 0 && op->Next != op->Prev) {
+    if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) {
+      op = ExcludeOp(op);
+      size--;
+    } else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) {
+      ExcludeOp(op->Next);
+      op = ExcludeOp(op);
+      size -= 2;
+    } else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt,
+                                   distSqrd)) {
+      op = ExcludeOp(op);
+      size--;
+    } else {
+      op->Idx = 1;
+      op = op->Next;
+    }
+  }
+
+  if (size < 3)
+    size = 0;
+  out_poly.resize(size);
+  for (size_t i = 0; i < size; ++i) {
+    out_poly[i] = op->Pt;
+    op = op->Next;
+  }
+  delete[] outPts;
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygon(Path &poly, double distance) {
+  CleanPolygon(poly, poly, distance);
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygons(const Paths &in_polys, Paths &out_polys, double distance) {
+  out_polys.resize(in_polys.size());
+  for (Paths::size_type i = 0; i < in_polys.size(); ++i)
+    CleanPolygon(in_polys[i], out_polys[i], distance);
+}
+//------------------------------------------------------------------------------
+
+void CleanPolygons(Paths &polys, double distance) {
+  CleanPolygons(polys, polys, distance);
+}
+//------------------------------------------------------------------------------
+
+void Minkowski(const Path &poly, const Path &path, Paths &solution, bool isSum,
+               bool isClosed) {
+  int delta = (isClosed ? 1 : 0);
+  size_t polyCnt = poly.size();
+  size_t pathCnt = path.size();
+  Paths pp;
+  pp.reserve(pathCnt);
+  if (isSum)
+    for (size_t i = 0; i < pathCnt; ++i) {
+      Path p;
+      p.reserve(polyCnt);
+      for (size_t j = 0; j < poly.size(); ++j)
+        p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y));
+      pp.push_back(p);
+    }
+  else
+    for (size_t i = 0; i < pathCnt; ++i) {
+      Path p;
+      p.reserve(polyCnt);
+      for (size_t j = 0; j < poly.size(); ++j)
+        p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y));
+      pp.push_back(p);
+    }
+
+  solution.clear();
+  solution.reserve((pathCnt + delta) * (polyCnt + 1));
+  for (size_t i = 0; i < pathCnt - 1 + delta; ++i)
+    for (size_t j = 0; j < polyCnt; ++j) {
+      Path quad;
+      quad.reserve(4);
+      quad.push_back(pp[i % pathCnt][j % polyCnt]);
+      quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]);
+      quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]);
+      quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]);
+      if (!Orientation(quad))
+        ReversePath(quad);
+      solution.push_back(quad);
+    }
+}
+//------------------------------------------------------------------------------
+
+void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution,
+                  bool pathIsClosed) {
+  Minkowski(pattern, path, solution, true, pathIsClosed);
+  Clipper c;
+  c.AddPaths(solution, ptSubject, true);
+  c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+}
+//------------------------------------------------------------------------------
+
+void TranslatePath(const Path &input, Path &output, const IntPoint delta) {
+  // precondition: input != output
+  output.resize(input.size());
+  for (size_t i = 0; i < input.size(); ++i)
+    output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y);
+}
+//------------------------------------------------------------------------------
+
+void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution,
+                  bool pathIsClosed) {
+  Clipper c;
+  for (size_t i = 0; i < paths.size(); ++i) {
+    Paths tmp;
+    Minkowski(pattern, paths[i], tmp, true, pathIsClosed);
+    c.AddPaths(tmp, ptSubject, true);
+    if (pathIsClosed) {
+      Path tmp2;
+      TranslatePath(paths[i], tmp2, pattern[0]);
+      c.AddPath(tmp2, ptClip, true);
+    }
+  }
+  c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+}
+//------------------------------------------------------------------------------
+
+void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution) {
+  Minkowski(poly1, poly2, solution, false, true);
+  Clipper c;
+  c.AddPaths(solution, ptSubject, true);
+  c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
+}
+//------------------------------------------------------------------------------
+
+enum NodeType { ntAny, ntOpen, ntClosed };
+
+void AddPolyNodeToPaths(const PolyNode &polynode, NodeType nodetype,
+                        Paths &paths) {
+  bool match = true;
+  if (nodetype == ntClosed)
+    match = !polynode.IsOpen();
+  else if (nodetype == ntOpen)
+    return;
+
+  if (!polynode.Contour.empty() && match)
+    paths.push_back(polynode.Contour);
+  for (int i = 0; i < polynode.ChildCount(); ++i)
+    AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
+}
+//------------------------------------------------------------------------------
+
+void PolyTreeToPaths(const PolyTree &polytree, Paths &paths) {
+  paths.resize(0);
+  paths.reserve(polytree.Total());
+  AddPolyNodeToPaths(polytree, ntAny, paths);
+}
+//------------------------------------------------------------------------------
+
+void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths) {
+  paths.resize(0);
+  paths.reserve(polytree.Total());
+  AddPolyNodeToPaths(polytree, ntClosed, paths);
+}
+//------------------------------------------------------------------------------
+
+void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths) {
+  paths.resize(0);
+  paths.reserve(polytree.Total());
+  // Open paths are top level only, so ...
+  for (int i = 0; i < polytree.ChildCount(); ++i)
+    if (polytree.Childs[i]->IsOpen())
+      paths.push_back(polytree.Childs[i]->Contour);
+}
+//------------------------------------------------------------------------------
+
+std::ostream &operator<<(std::ostream &s, const IntPoint &p) {
+  s << "(" << p.X << "," << p.Y << ")";
+  return s;
+}
+//------------------------------------------------------------------------------
+
+std::ostream &operator<<(std::ostream &s, const Path &p) {
+  if (p.empty())
+    return s;
+  Path::size_type last = p.size() - 1;
+  for (Path::size_type i = 0; i < last; i++)
+    s << "(" << p[i].X << "," << p[i].Y << "), ";
+  s << "(" << p[last].X << "," << p[last].Y << ")\n";
+  return s;
+}
+//------------------------------------------------------------------------------
+
+std::ostream &operator<<(std::ostream &s, const Paths &p) {
+  for (Paths::size_type i = 0; i < p.size(); i++)
+    s << p[i];
+  s << "\n";
+  return s;
+}
+//------------------------------------------------------------------------------
+
+} // ClipperLib namespace
diff --git a/core/predictor/tools/ocrtools/clipper.h b/core/predictor/tools/ocrtools/clipper.h
new file mode 100755
index 0000000000000000000000000000000000000000..384a6cf44c191a369906373d40fb81ffb02bb7fa
--- /dev/null
+++ b/core/predictor/tools/ocrtools/clipper.h
@@ -0,0 +1,423 @@
+/*******************************************************************************
+*                                                                              *
+* Author    :  Angus Johnson                                                   *
+* Version   :  6.4.2                                                           *
+* Date      :  27 February 2017                                                *
+* Website   :  http://www.angusj.com                                           *
+* Copyright :  Angus Johnson 2010-2017                                         *
+*                                                                              *
+* License:                                                                     *
+* Use, modification & distribution is subject to Boost Software License Ver 1. *
+* http://www.boost.org/LICENSE_1_0.txt                                         *
+*                                                                              *
+* Attributions:                                                                *
+* The code in this library is an extension of Bala Vatti's clipping algorithm: *
+* "A generic solution to polygon clipping"                                     *
+* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63.             *
+* http://portal.acm.org/citation.cfm?id=129906                                 *
+*                                                                              *
+* Computer graphics and geometric modeling: implementation and algorithms      *
+* By Max K. Agoston                                                            *
+* Springer; 1 edition (January 4, 2005)                                        *
+* http://books.google.com/books?q=vatti+clipping+agoston                       *
+*                                                                              *
+* See also:                                                                    *
+* "Polygon Offsetting by Computing Winding Numbers"                            *
+* Paper no. DETC2005-85513 pp. 565-575                                         *
+* ASME 2005 International Design Engineering Technical Conferences             *
+* and Computers and Information in Engineering Conference (IDETC/CIE2005)      *
+* September 24-28, 2005 , Long Beach, California, USA                          *
+* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf              *
+*                                                                              *
+*******************************************************************************/
+
+#ifndef clipper_hpp
+#define clipper_hpp
+
+#define CLIPPER_VERSION "6.4.2"
+
+// use_int32: When enabled 32bit ints are used instead of 64bit ints. This
+// improve performance but coordinate values are limited to the range +/- 46340
+//#define use_int32
+
+// use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance.
+//#define use_xyz
+
+// use_lines: Enables line clipping. Adds a very minor cost to performance.
+#define use_lines
+
+// use_deprecated: Enables temporary support for the obsolete functions
+//#define use_deprecated
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace ClipperLib {
+
+enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
+enum PolyType { ptSubject, ptClip };
+// By far the most widely used winding rules for polygon filling are
+// EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
+// Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
+// see http://glprogramming.com/red/chapter11.html
+enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
+
+#ifdef use_int32
+typedef int cInt;
+static cInt const loRange = 0x7FFF;
+static cInt const hiRange = 0x7FFF;
+#else
+typedef signed long long cInt;
+static cInt const loRange = 0x3FFFFFFF;
+static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
+typedef signed long long long64; // used by Int128 class
+typedef unsigned long long ulong64;
+
+#endif
+
+struct IntPoint {
+  cInt X;
+  cInt Y;
+#ifdef use_xyz
+  cInt Z;
+  IntPoint(cInt x = 0, cInt y = 0, cInt z = 0) : X(x), Y(y), Z(z){};
+#else
+  IntPoint(cInt x = 0, cInt y = 0) : X(x), Y(y){};
+#endif
+
+  friend inline bool operator==(const IntPoint &a, const IntPoint &b) {
+    return a.X == b.X && a.Y == b.Y;
+  }
+  friend inline bool operator!=(const IntPoint &a, const IntPoint &b) {
+    return a.X != b.X || a.Y != b.Y;
+  }
+};
+//------------------------------------------------------------------------------
+
+typedef std::vector Path;
+typedef std::vector Paths;
+
+inline Path &operator<<(Path &poly, const IntPoint &p) {
+  poly.push_back(p);
+  return poly;
+}
+inline Paths &operator<<(Paths &polys, const Path &p) {
+  polys.push_back(p);
+  return polys;
+}
+
+std::ostream &operator<<(std::ostream &s, const IntPoint &p);
+std::ostream &operator<<(std::ostream &s, const Path &p);
+std::ostream &operator<<(std::ostream &s, const Paths &p);
+
+struct DoublePoint {
+  double X;
+  double Y;
+  DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {}
+  DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {}
+};
+//------------------------------------------------------------------------------
+
+#ifdef use_xyz
+typedef void (*ZFillCallback)(IntPoint &e1bot, IntPoint &e1top, IntPoint &e2bot,
+                              IntPoint &e2top, IntPoint &pt);
+#endif
+
+enum InitOptions {
+  ioReverseSolution = 1,
+  ioStrictlySimple = 2,
+  ioPreserveCollinear = 4
+};
+enum JoinType { jtSquare, jtRound, jtMiter };
+enum EndType {
+  etClosedPolygon,
+  etClosedLine,
+  etOpenButt,
+  etOpenSquare,
+  etOpenRound
+};
+
+class PolyNode;
+typedef std::vector PolyNodes;
+
+class PolyNode {
+public:
+  PolyNode();
+  virtual ~PolyNode(){};
+  Path Contour;
+  PolyNodes Childs;
+  PolyNode *Parent;
+  PolyNode *GetNext() const;
+  bool IsHole() const;
+  bool IsOpen() const;
+  int ChildCount() const;
+
+private:
+  // PolyNode& operator =(PolyNode& other);
+  unsigned Index; // node index in Parent.Childs
+  bool m_IsOpen;
+  JoinType m_jointype;
+  EndType m_endtype;
+  PolyNode *GetNextSiblingUp() const;
+  void AddChild(PolyNode &child);
+  friend class Clipper; // to access Index
+  friend class ClipperOffset;
+};
+
+class PolyTree : public PolyNode {
+public:
+  ~PolyTree() { Clear(); };
+  PolyNode *GetFirst() const;
+  void Clear();
+  int Total() const;
+
+private:
+  // PolyTree& operator =(PolyTree& other);
+  PolyNodes AllNodes;
+  friend class Clipper; // to access AllNodes
+};
+
+bool Orientation(const Path &poly);
+double Area(const Path &poly);
+int PointInPolygon(const IntPoint &pt, const Path &path);
+
+void SimplifyPolygon(const Path &in_poly, Paths &out_polys,
+                     PolyFillType fillType = pftEvenOdd);
+void SimplifyPolygons(const Paths &in_polys, Paths &out_polys,
+                      PolyFillType fillType = pftEvenOdd);
+void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd);
+
+void CleanPolygon(const Path &in_poly, Path &out_poly, double distance = 1.415);
+void CleanPolygon(Path &poly, double distance = 1.415);
+void CleanPolygons(const Paths &in_polys, Paths &out_polys,
+                   double distance = 1.415);
+void CleanPolygons(Paths &polys, double distance = 1.415);
+
+void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution,
+                  bool pathIsClosed);
+void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution,
+                  bool pathIsClosed);
+void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution);
+
+void PolyTreeToPaths(const PolyTree &polytree, Paths &paths);
+void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths);
+void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths);
+
+void ReversePath(Path &p);
+void ReversePaths(Paths &p);
+
+struct IntRect {
+  cInt left;
+  cInt top;
+  cInt right;
+  cInt bottom;
+};
+
+// enums that are used internally ...
+enum EdgeSide { esLeft = 1, esRight = 2 };
+
+// forward declarations (for stuff used internally) ...
+struct TEdge;
+struct IntersectNode;
+struct LocalMinimum;
+struct OutPt;
+struct OutRec;
+struct Join;
+
+typedef std::vector PolyOutList;
+typedef std::vector EdgeList;
+typedef std::vector JoinList;
+typedef std::vector IntersectList;
+
+//------------------------------------------------------------------------------
+
+// ClipperBase is the ancestor to the Clipper class. It should not be
+// instantiated directly. This class simply abstracts the conversion of sets of
+// polygon coordinates into edge objects that are stored in a LocalMinima list.
+class ClipperBase {
+public:
+  ClipperBase();
+  virtual ~ClipperBase();
+  virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed);
+  bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed);
+  virtual void Clear();
+  IntRect GetBounds();
+  bool PreserveCollinear() { return m_PreserveCollinear; };
+  void PreserveCollinear(bool value) { m_PreserveCollinear = value; };
+
+protected:
+  void DisposeLocalMinimaList();
+  TEdge *AddBoundsToLML(TEdge *e, bool IsClosed);
+  virtual void Reset();
+  TEdge *ProcessBound(TEdge *E, bool IsClockwise);
+  void InsertScanbeam(const cInt Y);
+  bool PopScanbeam(cInt &Y);
+  bool LocalMinimaPending();
+  bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin);
+  OutRec *CreateOutRec();
+  void DisposeAllOutRecs();
+  void DisposeOutRec(PolyOutList::size_type index);
+  void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
+  void DeleteFromAEL(TEdge *e);
+  void UpdateEdgeIntoAEL(TEdge *&e);
+
+  typedef std::vector MinimaList;
+  MinimaList::iterator m_CurrentLM;
+  MinimaList m_MinimaList;
+
+  bool m_UseFullRange;
+  EdgeList m_edges;
+  bool m_PreserveCollinear;
+  bool m_HasOpenPaths;
+  PolyOutList m_PolyOuts;
+  TEdge *m_ActiveEdges;
+
+  typedef std::priority_queue ScanbeamList;
+  ScanbeamList m_Scanbeam;
+};
+//------------------------------------------------------------------------------
+
+class Clipper : public virtual ClipperBase {
+public:
+  Clipper(int initOptions = 0);
+  bool Execute(ClipType clipType, Paths &solution,
+               PolyFillType fillType = pftEvenOdd);
+  bool Execute(ClipType clipType, Paths &solution, PolyFillType subjFillType,
+               PolyFillType clipFillType);
+  bool Execute(ClipType clipType, PolyTree &polytree,
+               PolyFillType fillType = pftEvenOdd);
+  bool Execute(ClipType clipType, PolyTree &polytree, PolyFillType subjFillType,
+               PolyFillType clipFillType);
+  bool ReverseSolution() { return m_ReverseOutput; };
+  void ReverseSolution(bool value) { m_ReverseOutput = value; };
+  bool StrictlySimple() { return m_StrictSimple; };
+  void StrictlySimple(bool value) { m_StrictSimple = value; };
+// set the callback function for z value filling on intersections (otherwise Z
+// is 0)
+#ifdef use_xyz
+  void ZFillFunction(ZFillCallback zFillFunc);
+#endif
+protected:
+  virtual bool ExecuteInternal();
+
+private:
+  JoinList m_Joins;
+  JoinList m_GhostJoins;
+  IntersectList m_IntersectList;
+  ClipType m_ClipType;
+  typedef std::list MaximaList;
+  MaximaList m_Maxima;
+  TEdge *m_SortedEdges;
+  bool m_ExecuteLocked;
+  PolyFillType m_ClipFillType;
+  PolyFillType m_SubjFillType;
+  bool m_ReverseOutput;
+  bool m_UsingPolyTree;
+  bool m_StrictSimple;
+#ifdef use_xyz
+  ZFillCallback m_ZFill; // custom callback
+#endif
+  void SetWindingCount(TEdge &edge);
+  bool IsEvenOddFillType(const TEdge &edge) const;
+  bool IsEvenOddAltFillType(const TEdge &edge) const;
+  void InsertLocalMinimaIntoAEL(const cInt botY);
+  void InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge);
+  void AddEdgeToSEL(TEdge *edge);
+  bool PopEdgeFromSEL(TEdge *&edge);
+  void CopyAELToSEL();
+  void DeleteFromSEL(TEdge *e);
+  void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
+  bool IsContributing(const TEdge &edge) const;
+  bool IsTopHorz(const cInt XPos);
+  void DoMaxima(TEdge *e);
+  void ProcessHorizontals();
+  void ProcessHorizontal(TEdge *horzEdge);
+  void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
+  OutPt *AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
+  OutRec *GetOutRec(int idx);
+  void AppendPolygon(TEdge *e1, TEdge *e2);
+  void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
+  OutPt *AddOutPt(TEdge *e, const IntPoint &pt);
+  OutPt *GetLastOutPt(TEdge *e);
+  bool ProcessIntersections(const cInt topY);
+  void BuildIntersectList(const cInt topY);
+  void ProcessIntersectList();
+  void ProcessEdgesAtTopOfScanbeam(const cInt topY);
+  void BuildResult(Paths &polys);
+  void BuildResult2(PolyTree &polytree);
+  void SetHoleState(TEdge *e, OutRec *outrec);
+  void DisposeIntersectNodes();
+  bool FixupIntersectionOrder();
+  void FixupOutPolygon(OutRec &outrec);
+  void FixupOutPolyline(OutRec &outrec);
+  bool IsHole(TEdge *e);
+  bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
+  void FixHoleLinkage(OutRec &outrec);
+  void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt);
+  void ClearJoins();
+  void ClearGhostJoins();
+  void AddGhostJoin(OutPt *op, const IntPoint offPt);
+  bool JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2);
+  void JoinCommonEdges();
+  void DoSimplePolygons();
+  void FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec);
+  void FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec);
+  void FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec);
+#ifdef use_xyz
+  void SetZ(IntPoint &pt, TEdge &e1, TEdge &e2);
+#endif
+};
+//------------------------------------------------------------------------------
+
+class ClipperOffset {
+public:
+  ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25);
+  ~ClipperOffset();
+  void AddPath(const Path &path, JoinType joinType, EndType endType);
+  void AddPaths(const Paths &paths, JoinType joinType, EndType endType);
+  void Execute(Paths &solution, double delta);
+  void Execute(PolyTree &solution, double delta);
+  void Clear();
+  double MiterLimit;
+  double ArcTolerance;
+
+private:
+  Paths m_destPolys;
+  Path m_srcPoly;
+  Path m_destPoly;
+  std::vector m_normals;
+  double m_delta, m_sinA, m_sin, m_cos;
+  double m_miterLim, m_StepsPerRad;
+  IntPoint m_lowest;
+  PolyNode m_polyNodes;
+
+  void FixOrientations();
+  void DoOffset(double delta);
+  void OffsetPoint(int j, int &k, JoinType jointype);
+  void DoSquare(int j, int k);
+  void DoMiter(int j, int k, double r);
+  void DoRound(int j, int k);
+};
+//------------------------------------------------------------------------------
+
+class clipperException : public std::exception {
+public:
+  clipperException(const char *description) : m_descr(description) {}
+  virtual ~clipperException() throw() {}
+  virtual const char *what() const throw() { return m_descr.c_str(); }
+
+private:
+  std::string m_descr;
+};
+//------------------------------------------------------------------------------
+
+} // ClipperLib namespace
+
+#endif // clipper_hpp
diff --git a/core/predictor/tools/ocrtools/postprocess_op.cpp b/core/predictor/tools/ocrtools/postprocess_op.cpp
new file mode 100755
index 0000000000000000000000000000000000000000..abfd31cacb18edf638d9171d9ba82160b4d0f644
--- /dev/null
+++ b/core/predictor/tools/ocrtools/postprocess_op.cpp
@@ -0,0 +1,304 @@
+// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "postprocess_op.h"
+
+namespace PaddleOCR {
+
+void PostProcessor::GetContourArea(const std::vector> &box,
+                                   float unclip_ratio, float &distance) {
+  int pts_num = 4;
+  float area = 0.0f;
+  float dist = 0.0f;
+  for (int i = 0; i < pts_num; i++) {
+    area += box[i][0] * box[(i + 1) % pts_num][1] -
+            box[i][1] * box[(i + 1) % pts_num][0];
+    dist += sqrtf((box[i][0] - box[(i + 1) % pts_num][0]) *
+                      (box[i][0] - box[(i + 1) % pts_num][0]) +
+                  (box[i][1] - box[(i + 1) % pts_num][1]) *
+                      (box[i][1] - box[(i + 1) % pts_num][1]));
+  }
+  area = fabs(float(area / 2.0));
+
+  distance = area * unclip_ratio / dist;
+}
+
+cv::RotatedRect PostProcessor::UnClip(std::vector> box,
+                                      const float &unclip_ratio) {
+  float distance = 1.0;
+
+  GetContourArea(box, unclip_ratio, distance);
+
+  ClipperLib::ClipperOffset offset;
+  ClipperLib::Path p;
+  p << ClipperLib::IntPoint(int(box[0][0]), int(box[0][1]))
+    << ClipperLib::IntPoint(int(box[1][0]), int(box[1][1]))
+    << ClipperLib::IntPoint(int(box[2][0]), int(box[2][1]))
+    << ClipperLib::IntPoint(int(box[3][0]), int(box[3][1]));
+  offset.AddPath(p, ClipperLib::jtRound, ClipperLib::etClosedPolygon);
+
+  ClipperLib::Paths soln;
+  offset.Execute(soln, distance);
+  std::vector points;
+
+  for (int j = 0; j < soln.size(); j++) {
+    for (int i = 0; i < soln[soln.size() - 1].size(); i++) {
+      points.emplace_back(soln[j][i].X, soln[j][i].Y);
+    }
+  }
+  cv::RotatedRect res;
+  if (points.size() <= 0) {
+    res = cv::RotatedRect(cv::Point2f(0, 0), cv::Size2f(1, 1), 0);
+  } else {
+    res = cv::minAreaRect(points);
+  }
+  return res;
+}
+
+float **PostProcessor::Mat2Vec(cv::Mat mat) {
+  auto **array = new float *[mat.rows];
+  for (int i = 0; i < mat.rows; ++i)
+    array[i] = new float[mat.cols];
+  for (int i = 0; i < mat.rows; ++i) {
+    for (int j = 0; j < mat.cols; ++j) {
+      array[i][j] = mat.at(i, j);
+    }
+  }
+
+  return array;
+}
+
+std::vector>
+PostProcessor::OrderPointsClockwise(std::vector> pts) {
+  std::vector> box = pts;
+  std::sort(box.begin(), box.end(), XsortInt);
+
+  std::vector> leftmost = {box[0], box[1]};
+  std::vector> rightmost = {box[2], box[3]};
+
+  if (leftmost[0][1] > leftmost[1][1])
+    std::swap(leftmost[0], leftmost[1]);
+
+  if (rightmost[0][1] > rightmost[1][1])
+    std::swap(rightmost[0], rightmost[1]);
+
+  std::vector> rect = {leftmost[0], rightmost[0], rightmost[1],
+                                        leftmost[1]};
+  return rect;
+}
+
+std::vector> PostProcessor::Mat2Vector(cv::Mat mat) {
+  std::vector> img_vec;
+  std::vector