本例摘选自《Design Pattern in Modern C++》一书的外观模式的demo。作者的源码中,Window类和TextBuffer类没有给实现。这里本人使用OpenCV自己实现了写字的Window类和TextBuffer类。 本例的绘图系统的设计思路图如下。
Text Print System Design
最大宽度 和 最大高度
取最大宽度 为宽度,取最大高度为高度
每次绘制先往下加(最大高度 + 2),接着画
Window的宽度为最大宽度
Window的高度为 行数 * (最大高度 + 2)
CPP Code Demo
struct TextBuffer {
std::vector<std::string> texts;
double max_width;
double max_height;
double total_height;
void add_string() {
// update max_width
// update_max_height
// update total_height
}
};
struct Window {
std::vector<TextBuffer> buffers;
void add_buffer() {
}
void show() {
// 获取所有buffer的最大宽,作为窗口宽
// 获取所有buffer的高之和,作为窗口高
// 从上往下显示buffer
// 第0个: 0, 0
// 第1个: 1, buffer0.total_height
// 第2个: 2, buffer0.total_height + buffer1.total_height
}
private:
int width;
int height;
int get_width() {
// std::max buffer0.max_width, buffer1.max_width, buffer2.max_width ... buffern.max_width
return result;
}
int get_height() {
// std::accumulate buffer0.total_height, buffer1.total_height, buffer2.total_height ... buffern.total_height
return result;
}
};
本例的代码结构如下, test/CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
project(facade)
set(CMAKE_CXX_STANDARD 20)
add_definitions(-g)
find_package(Boost REQUIRED COMPONENTS
system
filesystem
serialization
program_options
thread
)
find_package(OpenCV REQUIRED )
find_package(glog REQUIRED)
include_directories(${Boost_INCLUDE_DIRS} /usr/local/include /usr/local/include/opencv4 /usr/local/iODBC/include /opt/snowflake/snowflakeodbc/include/ ${CMAKE_CURRENT_SOURCE_DIR}/../../ ${CMAKE_CURRENT_SOURCE_DIR}/../include/ ${CMAKE_CURRENT_SOURCE_DIR}/../../../include/)
LINK_DIRECTORIES(/usr/local/lib /usr/local/iODBC/lib /opt/snowflake/snowflakeodbc/lib/universal)
file( GLOB APP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../impl/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../include/death_handler/impl/*.cpp)
foreach( sourcefile ${APP_SOURCES} )
file(RELATIVE_PATH filename ${CMAKE_CURRENT_SOURCE_DIR} ${sourcefile})
string(FIND "${filename}" "test.cpp" "TEMP")
if( NOT "${TEMP}" STREQUAL "-1" )
string(REPLACE ".cpp" "" file ${filename})
add_executable(${file} ${APP_SOURCES})
target_link_libraries(${file} ${Boost_LIBRARIES} ${OpenCV_LIBS})
target_link_libraries(${file} glog::glog ssl crypto libgtest.a libgmock.a iodbc iodbcinst libnanodbc.a pthread)
endif()
endforeach( sourcefile ${APP_SOURCES})
test/facade_test.cpp
#include "death_handler/death_handler.h"
#include <glog/logging.h>
#include "facade.hpp"
#include <utility>
#include <gtest/gtest.h>
int main(int argc, char** argv) {
FLAGS_log_dir = "./";
FLAGS_alsologtostderr = true;
// 日志级别 INFO, WARNING, ERROR, FATAL 的值分别为0、1、2、3
FLAGS_minloglevel = 0;
Debug::DeathHandler dh;
google::InitGoogleLogging("./logs.log");
testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
return ret;
}
GTEST_TEST(FacadeTests, Facade) {
auto window = Console::instance().multi_buffers(3);
for (std::size_t i=0; i<20; ++i){
cv::HersheyFonts font = cv::HersheyFonts::FONT_HERSHEY_DUPLEX;
if(i%2 == 0) {
font = cv::HersheyFonts::FONT_HERSHEY_TRIPLEX;
}
window->buffers[0].add_string(
std::string("This is line ") + std::to_string(i), font);
}
window->show();
window->wait_to_dispose();
}
test/include/facade.hpp
#ifndef _FREDRIC_FACADE_HPP_
#define _FREDRIC_FACADE_HPP_
#include "window.h"
#include <memory>
class Console {
public:
static Console& instance() {
static Console console;
return console;
}
std::shared_ptr<Window> single_buffers(int buffer_count) {
auto w = std::make_shared<Window>();
w->add_buffer(TextBuffer{});
return w;
}
std::shared_ptr<Window> multi_buffers(int buffer_count) {
auto w = std::make_shared<Window>();
for(int i=0; i<buffer_count; ++i) {
w->add_buffer(TextBuffer{});
}
return w;
}
};
#endif
test/include/window.h
#include "opencv2/opencv.hpp"
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
std::string const window_name = "Draw Fonts";
int const EXIT_KEY = 27;
int const line_distance = 5;
int const title_bar_height = 50;
struct Text {
std::string text;
cv::HersheyFonts font;
};
struct TextBuffer {
std::vector<Text> texts;
int max_width{0};
int max_height{0};
int total_height{0};
void add_string(std::string const& text, cv::HersheyFonts const& font_style) {
texts.emplace_back(Text{text, font_style});
int baseline;
auto size = cv::getTextSize(text, font_style, 1.0f, 1, &baseline);
if(size.width > max_width) {
max_width = size.width;
}
if(size.height > max_height) {
max_height = size.height;
}
// 加的5是行间距
total_height = texts.size() * (max_height + line_distance);
}
// 用于std::max_element,求最大行宽
bool operator<(TextBuffer const& other) const {
return this->max_width < other.max_width;
}
// 用于std::accmulate,求所有buffer的高度
operator int() {
return this->total_height;
}
};
struct Window {
Window() {
cv::namedWindow(window_name, cv::WINDOW_NORMAL);
cv::setWindowProperty(window_name, cv::WND_PROP_TOPMOST, 1);
}
void add_buffer(TextBuffer const& buffer) {
buffers.emplace_back(std::move(buffer));
}
void show() {
width = get_width();
height = get_height();
cv::resizeWindow(window_name, width, height + title_bar_height);
img_ = cv::Mat::zeros(cv::Size(width, height + title_bar_height), CV_8UC1);
img_.setTo(cv::Scalar(255));
int current_y_pos = title_bar_height;
for(auto&& buffer_: buffers) {
for(auto&& text_: buffer_.texts) {
cv::putText(img_, text_.text, cv::Point(0, current_y_pos), text_.font, 1.0, cv::Scalar(0),1);
current_y_pos += buffer_.max_height + line_distance;
}
}
cv::imshow(window_name, img_);
}
void wait_to_dispose() {
while(EXIT_KEY != cv::waitKey(1000)) {
}
}
std::vector<TextBuffer> buffers;
private:
int width;
int height;
cv::Mat img_;
int get_width() {
auto buffer = *std::max_element(buffers.begin(), buffers.end());
return buffer.max_width;
}
int get_height() {
auto result = std::accumulate(buffers.begin(), buffers.end(), 0);
return result;
}
};
程序输出如下,
|