From b3a208901a089b0930ff1d265a024da8832e7df0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 8 Jan 2015 23:35:01 +0000 Subject: [PATCH 01/41] Loading TTF file with stb_truetype. Broke setup API slightly. Font baked, packed with space for custom data. Embeds compressed ProggyClean. --- .../directx11_example.vcxproj | 2 - .../directx11_example.vcxproj.filters | 6 - examples/directx11_example/main.cpp | 37 +- .../directx9_example/directx9_example.vcxproj | 1 - .../directx9_example.vcxproj.filters | 3 - examples/directx9_example/main.cpp | 33 +- examples/opengl3_example/main.cpp | 53 +- .../opengl3_example/opengl3_example.vcxproj | 1 - .../opengl3_example.vcxproj.filters | 3 - examples/opengl_example/ProggyClean.ttf | Bin 0 -> 41208 bytes examples/opengl_example/main.cpp | 48 +- .../opengl_example/opengl_example.vcxproj | 2 - .../opengl_example.vcxproj.filters | 6 - examples/shared/README.txt | 3 - examples/shared/stb_image.h | 4744 ----------------- imgui.cpp | 939 ++-- imgui.h | 129 +- stb_rect_pack.h | 546 ++ stb_truetype.h | 2646 +++++++++ 19 files changed, 3953 insertions(+), 5249 deletions(-) create mode 100644 examples/opengl_example/ProggyClean.ttf delete mode 100644 examples/shared/README.txt delete mode 100644 examples/shared/stb_image.h create mode 100644 stb_rect_pack.h create mode 100644 stb_truetype.h diff --git a/examples/directx11_example/directx11_example.vcxproj b/examples/directx11_example/directx11_example.vcxproj index 061142023..5f731ac7d 100644 --- a/examples/directx11_example/directx11_example.vcxproj +++ b/examples/directx11_example/directx11_example.vcxproj @@ -68,8 +68,6 @@ - - diff --git a/examples/directx11_example/directx11_example.vcxproj.filters b/examples/directx11_example/directx11_example.vcxproj.filters index 8fc7b4d5a..ab9c654e1 100644 --- a/examples/directx11_example/directx11_example.vcxproj.filters +++ b/examples/directx11_example/directx11_example.vcxproj.filters @@ -15,12 +15,6 @@ imgui - - imgui - - - sources - diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index ec3e20aaf..e718d14c7 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -1,9 +1,6 @@ // ImGui - standalone example application for DirectX 11 #include -#define STB_IMAGE_STATIC -#define STB_IMAGE_IMPLEMENTATION -#include "../shared/stb_image.h" // for .png loading #include "../../imgui.h" // DirectX 11 @@ -290,8 +287,9 @@ HRESULT InitDeviceD3D(HWND hWnd) \ float4 main(PS_INPUT input) : SV_Target\ {\ - float4 out_col = texture0.Sample(sampler0, input.uv);\ - return input.col * out_col;\ + float4 out_col = input.col; \ + out_col.w *= texture0.Sample(sampler0, input.uv).w; \ + return out_col; \ }"; D3DCompile(pixelShader, strlen(pixelShader), NULL, NULL, NULL, "main", "ps_5_0", 0, 0, &g_pPixelShaderBlob, NULL); @@ -427,23 +425,22 @@ void InitImGui() } } - // Load font texture - // Default font (embedded in code) - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - int tex_x, tex_y, tex_comp; - void* tex_data = stbi_load_from_memory((const unsigned char*)png_data, (int)png_size, &tex_x, &tex_y, &tex_comp, 0); - IM_ASSERT(tex_data != NULL); + // Load font + io.Font = new ImFont(); + io.Font->LoadDefault(); + //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); + //io.Font->DisplayOffset.y += 0.0f; + IM_ASSERT(io.Font->IsLoaded()); + // Copy font texture { D3D11_TEXTURE2D_DESC desc; ZeroMemory(&desc, sizeof(desc)); - desc.Width = tex_x; - desc.Height = tex_y; + desc.Width = io.Font->TexWidth; + desc.Height = io.Font->TexHeight; desc.MipLevels = 1; desc.ArraySize = 1; - desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.Format = DXGI_FORMAT_A8_UNORM; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; @@ -451,15 +448,15 @@ void InitImGui() ID3D11Texture2D *pTexture = NULL; D3D11_SUBRESOURCE_DATA subResource; - subResource.pSysMem = tex_data; - subResource.SysMemPitch = tex_x * 4; + subResource.pSysMem = io.Font->TexPixels; + subResource.SysMemPitch = desc.Width * 1; subResource.SysMemSlicePitch = 0; g_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); // Create texture view D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; ZeroMemory(&srvDesc, sizeof(srvDesc)); - srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.Format = DXGI_FORMAT_A8_UNORM; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = desc.MipLevels; srvDesc.Texture2D.MostDetailedMip = 0; @@ -471,7 +468,7 @@ void InitImGui() { D3D11_SAMPLER_DESC desc; ZeroMemory(&desc, sizeof(desc)); - desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + desc.Filter = D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR; desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; diff --git a/examples/directx9_example/directx9_example.vcxproj b/examples/directx9_example/directx9_example.vcxproj index 5609b7569..a6bea6b8b 100644 --- a/examples/directx9_example/directx9_example.vcxproj +++ b/examples/directx9_example/directx9_example.vcxproj @@ -76,7 +76,6 @@ - diff --git a/examples/directx9_example/directx9_example.vcxproj.filters b/examples/directx9_example/directx9_example.vcxproj.filters index f39e15892..06afcb03f 100644 --- a/examples/directx9_example/directx9_example.vcxproj.filters +++ b/examples/directx9_example/directx9_example.vcxproj.filters @@ -24,8 +24,5 @@ imgui - - imgui - \ No newline at end of file diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index fe4f15d71..a35206db8 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -73,12 +73,13 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c // Setup texture g_pd3dDevice->SetTexture( 0, g_pTexture ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_DIFFUSE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); + g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); + g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); // Setup orthographic projection matrix D3DXMATRIXA16 mat; @@ -213,15 +214,21 @@ void InitImGui() return; } - // Load font texture - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) - { - IM_ASSERT(0); - return; - } + // Load font + io.Font = new ImFont(); + io.Font->LoadDefault(); + //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); + io.Font->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 20.0f, ImFont::GetGlyphRangesDefault()); + //io.Font->DisplayOffset.y += 0.0f; + IM_ASSERT(io.Font->IsLoaded()); + + // Copy font texture + if (D3DXCreateTexture(g_pd3dDevice, io.Font->TexWidth, io.Font->TexHeight, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8, D3DPOOL_DEFAULT, &g_pTexture) < 0) { IM_ASSERT(0); return; } + D3DLOCKED_RECT tex_locked_rect; + if (g_pTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK) { IM_ASSERT(0); return; } + for (int y = 0; y < io.Font->TexHeight; y++) + memcpy((unsigned char *)tex_locked_rect.pBits + tex_locked_rect.Pitch * y, io.Font->TexPixels + io.Font->TexWidth * y, io.Font->TexWidth); + g_pTexture->UnlockRect(0); } INT64 ticks_per_second = 0; @@ -330,7 +337,7 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) // 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowTestWindow() if (show_test_window) { - ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiSetCondition_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiSetCondition_FirstUseEver); ImGui::ShowTestWindow(&show_test_window); } diff --git a/examples/opengl3_example/main.cpp b/examples/opengl3_example/main.cpp index 91dcd1497..ff8122f9c 100644 --- a/examples/opengl3_example/main.cpp +++ b/examples/opengl3_example/main.cpp @@ -8,9 +8,7 @@ #endif #include "../../imgui.h" -#define STB_IMAGE_STATIC -#define STB_IMAGE_IMPLEMENTATION -#include "../shared/stb_image.h" // stb_image.h for PNG loading +#include // Glfw/Glew #define GLEW_STATIC @@ -24,7 +22,7 @@ static bool mousePressed[2] = { false, false }; // Shader variables static int shader_handle, vert_handle, frag_handle; -static int texture_location, ortho_location; +static int texture_location, proj_mtx_location; static int position_location, uv_location, colour_location; static size_t vbo_max_size = 20000; static unsigned int vbo_handle, vao_handle; @@ -62,7 +60,7 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c }; glUseProgram(shader_handle); glUniform1i(texture_location, 0); - glUniformMatrix4fv(ortho_location, 1, GL_FALSE, &ortho_projection[0][0]); + glUniformMatrix4fv(proj_mtx_location, 1, GL_FALSE, &ortho_projection[0][0]); // Grow our buffer according to what we need size_t total_vtx_count = 0; @@ -184,28 +182,29 @@ void InitGL() const GLchar *vertex_shader = "#version 330\n" - "uniform mat4 ortho;\n" + "uniform mat4 ProjMtx;\n" "in vec2 Position;\n" "in vec2 UV;\n" - "in vec4 Colour;\n" + "in vec4 Color;\n" "out vec2 Frag_UV;\n" - "out vec4 Frag_Colour;\n" + "out vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" - " Frag_Colour = Colour;\n" - " gl_Position = ortho*vec4(Position.xy,0,1);\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* fragment_shader = "#version 330\n" "uniform sampler2D Texture;\n" "in vec2 Frag_UV;\n" - "in vec4 Frag_Colour;\n" - "out vec4 FragColor;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" "void main()\n" "{\n" - " FragColor = Frag_Colour * texture( Texture, Frag_UV.st);\n" + " Out_Color = Frag_Color;\n" + " Out_Color.w *= texture( Texture, Frag_UV.st).x;\n" "}\n"; shader_handle = glCreateProgram(); @@ -220,10 +219,10 @@ void InitGL() glLinkProgram(shader_handle); texture_location = glGetUniformLocation(shader_handle, "Texture"); - ortho_location = glGetUniformLocation(shader_handle, "ortho"); + proj_mtx_location = glGetUniformLocation(shader_handle, "ProjMtx"); position_location = glGetAttribLocation(shader_handle, "Position"); uv_location = glGetAttribLocation(shader_handle, "UV"); - colour_location = glGetAttribLocation(shader_handle, "Colour"); + colour_location = glGetAttribLocation(shader_handle, "Color"); glGenBuffers(1, &vbo_handle); glBindBuffer(GL_ARRAY_BUFFER, vbo_handle); @@ -270,18 +269,20 @@ void InitImGui() io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; - // Load font texture + // Load font + io.Font = new ImFont(); + io.Font->LoadDefault(); + //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); + //io.Font->DisplayOffset.y += 0.0f; + IM_ASSERT(io.Font->IsLoaded()); + + // Copy font texture glGenTextures(1, &fontTex); glBindTexture(GL_TEXTURE_2D, fontTex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - int tex_x, tex_y, tex_comp; - void* tex_data = stbi_load_from_memory((const unsigned char*)png_data, (int)png_size, &tex_x, &tex_y, &tex_comp, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_x, tex_y, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_data); - stbi_image_free(tex_data); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + IM_ASSERT(io.Font->IsLoaded()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, io.Font->TexWidth, io.Font->TexHeight, 0, GL_RED, GL_UNSIGNED_BYTE, io.Font->TexPixels); } void UpdateImGui() @@ -364,7 +365,7 @@ int main(int argc, char** argv) // 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowTestWindow() if (show_test_window) { - ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiSetCondition_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiSetCondition_FirstUseEver); ImGui::ShowTestWindow(&show_test_window); } diff --git a/examples/opengl3_example/opengl3_example.vcxproj b/examples/opengl3_example/opengl3_example.vcxproj index 657b9e1bd..a5c110473 100644 --- a/examples/opengl3_example/opengl3_example.vcxproj +++ b/examples/opengl3_example/opengl3_example.vcxproj @@ -74,7 +74,6 @@ - diff --git a/examples/opengl3_example/opengl3_example.vcxproj.filters b/examples/opengl3_example/opengl3_example.vcxproj.filters index f5668ce5d..c961b9986 100644 --- a/examples/opengl3_example/opengl3_example.vcxproj.filters +++ b/examples/opengl3_example/opengl3_example.vcxproj.filters @@ -24,8 +24,5 @@ imgui - - imgui - \ No newline at end of file diff --git a/examples/opengl_example/ProggyClean.ttf b/examples/opengl_example/ProggyClean.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0270cdfe3caa811e1b252cdcc5b179989c1b8ad6 GIT binary patch literal 41208 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 // Glfw/Glew #define GLEW_STATIC @@ -180,42 +178,20 @@ void InitImGui() io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; - // Load font texture - glGenTextures(1, &fontTex); - glBindTexture(GL_TEXTURE_2D, fontTex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - -#if 1 - // Default font (embedded in code) - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - int tex_x, tex_y, tex_comp; - void* tex_data = stbi_load_from_memory((const unsigned char*)png_data, (int)png_size, &tex_x, &tex_y, &tex_comp, 0); - IM_ASSERT(tex_data != NULL); -#else - // Custom font from filesystem + // Load font io.Font = new ImFont(); - io.Font->LoadFromFile("../../extra_fonts/mplus-2m-medium_18.fnt"); + io.Font->LoadDefault(); + //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); + //io.Font->DisplayOffset.y += 0.0f; IM_ASSERT(io.Font->IsLoaded()); - int tex_x, tex_y, tex_comp; - void* tex_data = stbi_load("../../extra_fonts/mplus-2m-medium_18.png", &tex_x, &tex_y, &tex_comp, 0); - IM_ASSERT(tex_data != NULL); - - // Automatically find white pixel from the texture we just loaded - // (io.Font->TexUvForWhite needs to contains UV coordinates pointing to a white pixel in order to render solid objects) - for (int tex_data_off = 0; tex_data_off < tex_x*tex_y; tex_data_off++) - if (((unsigned int*)tex_data)[tex_data_off] == 0xffffffff) - { - io.Font->TexUvForWhite = ImVec2((float)(tex_data_off % tex_x)/(tex_x), (float)(tex_data_off / tex_x)/(tex_y)); - break; - } -#endif - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_x, tex_y, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_data); - stbi_image_free(tex_data); + // Copy font texture + glGenTextures(1, &fontTex); + glBindTexture(GL_TEXTURE_2D, fontTex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + IM_ASSERT(io.Font->IsLoaded()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, io.Font->TexWidth, io.Font->TexHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, io.Font->TexPixels); } void UpdateImGui() diff --git a/examples/opengl_example/opengl_example.vcxproj b/examples/opengl_example/opengl_example.vcxproj index 8c1898d64..d453e2f51 100644 --- a/examples/opengl_example/opengl_example.vcxproj +++ b/examples/opengl_example/opengl_example.vcxproj @@ -74,8 +74,6 @@ - - diff --git a/examples/opengl_example/opengl_example.vcxproj.filters b/examples/opengl_example/opengl_example.vcxproj.filters index 69a481e2f..cf1ba2414 100644 --- a/examples/opengl_example/opengl_example.vcxproj.filters +++ b/examples/opengl_example/opengl_example.vcxproj.filters @@ -24,11 +24,5 @@ imgui - - imgui - - - sources - \ No newline at end of file diff --git a/examples/shared/README.txt b/examples/shared/README.txt deleted file mode 100644 index b2b431a02..000000000 --- a/examples/shared/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -stb_image.h is used to load the PNG texture data by - opengl_example - directx11_example diff --git a/examples/shared/stb_image.h b/examples/shared/stb_image.h deleted file mode 100644 index 5ab9c58ec..000000000 --- a/examples/shared/stb_image.h +++ /dev/null @@ -1,4744 +0,0 @@ -/* stb_image - v1.46 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c - when you control the images you're loading - no warranty implied; use at your own risk - - Do this: - #define STB_IMAGE_IMPLEMENTATION - before you include this file in *one* C or C++ file to create the implementation. - - #define STBI_ASSERT(x) to avoid using assert.h. - - QUICK NOTES: - Primarily of interest to game developers and other people who can - avoid problematic images and only need the trivial interface - - JPEG baseline (no JPEG progressive) - PNG 1/2/4/8-bit-per-channel (16 bpc not supported) - - TGA (not sure what subset, if a subset) - BMP non-1bpp, non-RLE - PSD (composited view only, no extra channels) - - GIF (*comp always reports as 4-channel) - HDR (radiance rgbE format) - PIC (Softimage PIC) - - - decode from memory or through FILE (define STBI_NO_STDIO to remove code) - - decode from arbitrary I/O callbacks - - overridable dequantizing-IDCT, YCbCr-to-RGB conversion (define STBI_SIMD) - - Latest revisions: - 1.xx (2014-09-26) 1/2/4-bit PNG support (both grayscale and paletted) - 1.46 (2014-08-26) fix broken tRNS chunk in non-paletted PNG - 1.45 (2014-08-16) workaround MSVC-ARM internal compiler error by wrapping malloc - 1.44 (2014-08-07) warnings - 1.43 (2014-07-15) fix MSVC-only bug in 1.42 - 1.42 (2014-07-09) no _CRT_SECURE_NO_WARNINGS; error-path fixes; STBI_ASSERT - 1.41 (2014-06-25) fix search&replace that messed up comments/error messages - 1.40 (2014-06-22) gcc warning - 1.39 (2014-06-15) TGA optimization bugfix, multiple BMP fixes - 1.38 (2014-06-06) suppress MSVC run-time warnings, fix accidental rename of 'skip' - 1.37 (2014-06-04) remove duplicate typedef - 1.36 (2014-06-03) converted to header file, allow reading incorrect iphoned-images without iphone flag - 1.35 (2014-05-27) warnings, bugfixes, TGA optimization, etc - - See end of file for full revision history. - - TODO: - stbi_info support for BMP,PSD,HDR,PIC - - - ============================ Contributors ========================= - - Image formats Bug fixes & warning fixes - Sean Barrett (jpeg, png, bmp) Marc LeBlanc - Nicolas Schulz (hdr, psd) Christpher Lloyd - Jonathan Dummer (tga) Dave Moore - Jean-Marc Lienher (gif) Won Chun - Tom Seddon (pic) the Horde3D community - Thatcher Ulrich (psd) Janez Zemva - Jonathan Blow - Laurent Gomila - Extensions, features Aruelien Pocheville - Jetro Lauha (stbi_info) Ryamond Barbiero - James "moose2000" Brown (iPhone PNG) David Woo - Ben "Disch" Wenger (io callbacks) Roy Eltham - Martin "SpartanJ" Golini Luke Graham - Omar Cornut (1/2/4-bit png) Thomas Ruf - John Bartholomew - Optimizations & bugfixes Ken Hamada - Fabian "ryg" Giesen Cort Stratton - Arseny Kapoulkine Blazej Dariusz Roszkowski - Thibault Reuille - Paul Du Bois - Guillaume George - Jerry Jansson - If your name should be here but Hayaki Saito - isn't, let Sean know. Johan Duparc - Ronny Chevalier - Michal Cichon -*/ - -#ifndef STBI_INCLUDE_STB_IMAGE_H -#define STBI_INCLUDE_STB_IMAGE_H - -// Limitations: -// - no jpeg progressive support -// - non-HDR formats support 8-bit samples only (jpeg, png) -// - no delayed line count (jpeg) -- IJG doesn't support either -// - no 1-bit BMP -// - GIF always returns *comp=4 -// -// Basic usage (see HDR discussion below): -// int x,y,n; -// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); -// // ... process data if not NULL ... -// // ... x = width, y = height, n = # 8-bit components per pixel ... -// // ... replace '0' with '1'..'4' to force that many components per pixel -// // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) -// -// Standard parameters: -// int *x -- outputs image width in pixels -// int *y -- outputs image height in pixels -// int *comp -- outputs # of image components in image file -// int req_comp -- if non-zero, # of image components requested in result -// -// The return value from an image loader is an 'unsigned char *' which points -// to the pixel data. The pixel data consists of *y scanlines of *x pixels, -// with each pixel consisting of N interleaved 8-bit components; the first -// pixel pointed to is top-left-most in the image. There is no padding between -// image scanlines or between pixels, regardless of format. The number of -// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. -// If req_comp is non-zero, *comp has the number of components that _would_ -// have been output otherwise. E.g. if you set req_comp to 4, you will always -// get RGBA output, but you can check *comp to easily see if it's opaque. -// -// An output image with N components has the following components interleaved -// in this order in each pixel: -// -// N=#comp components -// 1 grey -// 2 grey, alpha -// 3 red, green, blue -// 4 red, green, blue, alpha -// -// If image loading fails for any reason, the return value will be NULL, -// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() -// can be queried for an extremely brief, end-user unfriendly explanation -// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid -// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly -// more user-friendly ones. -// -// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. -// -// =========================================================================== -// -// iPhone PNG support: -// -// By default we convert iphone-formatted PNGs back to RGB; nominally they -// would silently load as BGR, except the existing code should have just -// failed on such iPhone PNGs. But you can disable this conversion by -// by calling stbi_convert_iphone_png_to_rgb(0), in which case -// you will always just get the native iphone "format" through. -// -// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per -// pixel to remove any premultiplied alpha *only* if the image file explicitly -// says there's premultiplied data (currently only happens in iPhone images, -// and only if iPhone convert-to-rgb processing is on). -// -// =========================================================================== -// -// HDR image support (disable by defining STBI_NO_HDR) -// -// stb_image now supports loading HDR images in general, and currently -// the Radiance .HDR file format, although the support is provided -// generically. You can still load any file through the existing interface; -// if you attempt to load an HDR file, it will be automatically remapped to -// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; -// both of these constants can be reconfigured through this interface: -// -// stbi_hdr_to_ldr_gamma(2.2f); -// stbi_hdr_to_ldr_scale(1.0f); -// -// (note, do not use _inverse_ constants; stbi_image will invert them -// appropriately). -// -// Additionally, there is a new, parallel interface for loading files as -// (linear) floats to preserve the full dynamic range: -// -// float *data = stbi_loadf(filename, &x, &y, &n, 0); -// -// If you load LDR images through this interface, those images will -// be promoted to floating point values, run through the inverse of -// constants corresponding to the above: -// -// stbi_ldr_to_hdr_scale(1.0f); -// stbi_ldr_to_hdr_gamma(2.2f); -// -// Finally, given a filename (or an open file or memory block--see header -// file for details) containing image data, you can query for the "most -// appropriate" interface to use (that is, whether the image is HDR or -// not), using: -// -// stbi_is_hdr(char *filename); -// -// =========================================================================== -// -// I/O callbacks -// -// I/O callbacks allow you to read from arbitrary sources, like packaged -// files or some other source. Data read from callbacks are processed -// through a small internal buffer (currently 128 bytes) to try to reduce -// overhead. -// -// The three functions you must define are "read" (reads some bytes of data), -// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). - - -#ifndef STBI_NO_STDIO -#include -#endif // STBI_NO_STDIO - -#define STBI_VERSION 1 - -enum -{ - STBI_default = 0, // only used for req_comp - - STBI_grey = 1, - STBI_grey_alpha = 2, - STBI_rgb = 3, - STBI_rgb_alpha = 4 -}; - -typedef unsigned char stbi_uc; - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef STB_IMAGE_STATIC -#define STBIDEF static -#else -#define STBIDEF extern -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// PRIMARY API - works on images of any type -// - -// -// load image by filename, open file, or memory buffer -// - -STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); -STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -// for stbi_load_from_file, file pointer is left pointing immediately after image -#endif - -typedef struct -{ - int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read - void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative - int (*eof) (void *user); // returns nonzero if we are at end of file/data -} stbi_io_callbacks; - -STBIDEF stbi_uc *stbi_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); - -#ifndef STBI_NO_HDR - STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); - - #ifndef STBI_NO_STDIO - STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); - STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); - #endif - - STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); - - STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); - STBIDEF void stbi_hdr_to_ldr_scale(float scale); - - STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); - STBIDEF void stbi_ldr_to_hdr_scale(float scale); -#endif // STBI_NO_HDR - -// stbi_is_hdr is always defined -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename); -STBIDEF int stbi_is_hdr_from_file(FILE *f); -#endif // STBI_NO_STDIO - - -// get a VERY brief reason for failure -// NOT THREADSAFE -STBIDEF const char *stbi_failure_reason (void); - -// free the loaded image -- this is just free() -STBIDEF void stbi_image_free (void *retval_from_stbi_load); - -// get image dimensions & components without fully decoding -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); - -#endif - - - -// for image formats that explicitly notate that they have premultiplied alpha, -// we just return the colors as stored in the file. set this flag to force -// unpremultiplication. results are undefined if the unpremultiply overflow. -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); - -// indicate whether we should process iphone images back to canonical format, -// or just pass them through "as-is" -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); - - -// ZLIB client - used by PNG, available for other purposes - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); -STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - -STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - - -// define faster low-level operations (typically SIMD support) -#ifdef STBI_SIMD -typedef void (*stbi_idct_8x8)(stbi_uc *out, int out_stride, short data[64], unsigned short *dequantize); -// compute an integer IDCT on "input" -// input[x] = data[x] * dequantize[x] -// write results to 'out': 64 samples, each run of 8 spaced by 'out_stride' -// CLAMP results to 0..255 -typedef void (*stbi_YCbCr_to_RGB_run)(stbi_uc *output, stbi_uc const *y, stbi_uc const *cb, stbi_uc const *cr, int count, int step); -// compute a conversion from YCbCr to RGB -// 'count' pixels -// write pixels to 'output'; each pixel is 'step' bytes (either 3 or 4; if 4, write '255' as 4th), order R,G,B -// y: Y input channel -// cb: Cb input channel; scale/biased to be 0..255 -// cr: Cr input channel; scale/biased to be 0..255 - -STBIDEF void stbi_install_idct(stbi_idct_8x8 func); -STBIDEF void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func); -#endif // STBI_SIMD - - -#ifdef __cplusplus -} -#endif - -// -// -//// end header file ///////////////////////////////////////////////////// -#endif // STBI_INCLUDE_STB_IMAGE_H - -#ifdef STB_IMAGE_IMPLEMENTATION - -#ifndef STBI_NO_HDR -#include // ldexp -#include // strcmp, strtok -#endif - -#ifndef STBI_NO_STDIO -#include -#endif -#include -#include -#ifndef STBI_ASSERT -#include -#define STBI_ASSERT(x) assert(x) -#endif -#include -#include // ptrdiff_t on osx - -#ifndef _MSC_VER - #ifdef __cplusplus - #define stbi_inline inline - #else - #define stbi_inline - #endif -#else - #define stbi_inline __forceinline -#endif - - -#ifdef _MSC_VER -typedef unsigned short stbi__uint16; -typedef signed short stbi__int16; -typedef unsigned int stbi__uint32; -typedef signed int stbi__int32; -#else -#include -typedef uint16_t stbi__uint16; -typedef int16_t stbi__int16; -typedef uint32_t stbi__uint32; -typedef int32_t stbi__int32; -#endif - -// should produce compiler error if size is wrong -typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; - -#ifdef _MSC_VER -#define STBI_NOTUSED(v) (void)(v) -#else -#define STBI_NOTUSED(v) (void)sizeof(v) -#endif - -#ifdef _MSC_VER -#define STBI_HAS_LROTL -#endif - -#ifdef STBI_HAS_LROTL - #define stbi_lrot(x,y) _lrotl(x,y) -#else - #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) -#endif - -/////////////////////////////////////////////// -// -// stbi__context struct and start_xxx functions - -// stbi__context structure is our basic context used by all images, so it -// contains all the IO context, plus some basic image information -typedef struct -{ - stbi__uint32 img_x, img_y; - int img_n, img_out_n; - - stbi_io_callbacks io; - void *io_user_data; - - int read_from_callbacks; - int buflen; - stbi_uc buffer_start[128]; - - stbi_uc *img_buffer, *img_buffer_end; - stbi_uc *img_buffer_original; -} stbi__context; - - -static void stbi__refill_buffer(stbi__context *s); - -// initialize a memory-decode context -static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) -{ - s->io.read = NULL; - s->read_from_callbacks = 0; - s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; - s->img_buffer_end = (stbi_uc *) buffer+len; -} - -// initialize a callback-based context -static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) -{ - s->io = *c; - s->io_user_data = user; - s->buflen = sizeof(s->buffer_start); - s->read_from_callbacks = 1; - s->img_buffer_original = s->buffer_start; - stbi__refill_buffer(s); -} - -#ifndef STBI_NO_STDIO - -static int stbi__stdio_read(void *user, char *data, int size) -{ - return (int) fread(data,1,size,(FILE*) user); -} - -static void stbi__stdio_skip(void *user, int n) -{ - fseek((FILE*) user, n, SEEK_CUR); -} - -static int stbi__stdio_eof(void *user) -{ - return feof((FILE*) user); -} - -static stbi_io_callbacks stbi__stdio_callbacks = -{ - stbi__stdio_read, - stbi__stdio_skip, - stbi__stdio_eof, -}; - -static void stbi__start_file(stbi__context *s, FILE *f) -{ - stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); -} - -//static void stop_file(stbi__context *s) { } - -#endif // !STBI_NO_STDIO - -static void stbi__rewind(stbi__context *s) -{ - // conceptually rewind SHOULD rewind to the beginning of the stream, - // but we just rewind to the beginning of the initial buffer, because - // we only use it after doing 'test', which only ever looks at at most 92 bytes - s->img_buffer = s->img_buffer_original; -} - -static int stbi__jpeg_test(stbi__context *s); -static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__png_test(stbi__context *s); -static stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__bmp_test(stbi__context *s); -static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__tga_test(stbi__context *s); -static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__psd_test(stbi__context *s); -static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -#ifndef STBI_NO_HDR -static int stbi__hdr_test(stbi__context *s); -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -#endif -static int stbi__pic_test(stbi__context *s); -static stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__gif_test(stbi__context *s); -static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); - - -// this is not threadsafe -static const char *stbi__g_failure_reason; - -STBIDEF const char *stbi_failure_reason(void) -{ - return stbi__g_failure_reason; -} - -static int stbi__err(const char *str) -{ - stbi__g_failure_reason = str; - return 0; -} - -static void *stbi__malloc(size_t size) -{ - return malloc(size); -} - -// stbi__err - error -// stbi__errpf - error returning pointer to float -// stbi__errpuc - error returning pointer to unsigned char - -#ifdef STBI_NO_FAILURE_STRINGS - #define stbi__err(x,y) 0 -#elif defined(STBI_FAILURE_USERMSG) - #define stbi__err(x,y) stbi__err(y) -#else - #define stbi__err(x,y) stbi__err(x) -#endif - -#define stbi__errpf(x,y) ((float *) (stbi__err(x,y)?NULL:NULL)) -#define stbi__errpuc(x,y) ((unsigned char *) (stbi__err(x,y)?NULL:NULL)) - -STBIDEF void stbi_image_free(void *retval_from_stbi_load) -{ - free(retval_from_stbi_load); -} - -#ifndef STBI_NO_HDR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); -#endif - -static unsigned char *stbi_load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); - if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp); - if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp); - if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp); - if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp); - if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp); - - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - float *hdr = stbi__hdr_load(s, x,y,comp,req_comp); - return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); - } - #endif - - // test tga last because it's a crappy test! - if (stbi__tga_test(s)) - return stbi__tga_load(s,x,y,comp,req_comp); - return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); -} - -#ifndef STBI_NO_STDIO - -FILE *stbi__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != fopen_s(&f, filename, mode)) - f=0; -#else - f = fopen(filename, mode); -#endif - return f; -} - - -STBIDEF unsigned char *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - unsigned char *result; - if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi_load_main(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} -#endif //!STBI_NO_STDIO - -STBIDEF unsigned char *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi_load_main(&s,x,y,comp,req_comp); -} - -unsigned char *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi_load_main(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_HDR - -float *stbi_loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *data; - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) - return stbi__hdr_load(s,x,y,comp,req_comp); - #endif - data = stbi_load_main(s, x, y, comp, req_comp); - if (data) - return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); - return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); -} - -float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi_loadf_main(&s,x,y,comp,req_comp); -} - -float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi_loadf_main(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_STDIO -float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - float *result; - FILE *f = stbi__fopen(filename, "rb"); - if (!f) return stbi__errpf("can't fopen", "Unable to open file"); - result = stbi_loadf_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi_loadf_main(&s,x,y,comp,req_comp); -} -#endif // !STBI_NO_STDIO - -#endif // !STBI_NO_HDR - -// these is-hdr-or-not is defined independent of whether STBI_NO_HDR is -// defined, for API simplicity; if STBI_NO_HDR is defined, it always -// reports false! - -int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(buffer); - STBI_NOTUSED(len); - return 0; - #endif -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result=0; - if (f) { - result = stbi_is_hdr_from_file(f); - fclose(f); - } - return result; -} - -STBIDEF int stbi_is_hdr_from_file(FILE *f) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_file(&s,f); - return stbi__hdr_test(&s); - #else - return 0; - #endif -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__hdr_test(&s); - #else - return 0; - #endif -} - -#ifndef STBI_NO_HDR -static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; -static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; - -void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } -void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } - -void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } -void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } -#endif - - -////////////////////////////////////////////////////////////////////////////// -// -// Common code used by all image loaders -// - -enum -{ - SCAN_load=0, - SCAN_type, - SCAN_header -}; - -static void stbi__refill_buffer(stbi__context *s) -{ - int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); - if (n == 0) { - // at end of file, treat same as if from memory, but need to handle case - // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file - s->read_from_callbacks = 0; - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start+1; - *s->img_buffer = 0; - } else { - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start + n; - } -} - -stbi_inline static stbi_uc stbi__get8(stbi__context *s) -{ - if (s->img_buffer < s->img_buffer_end) - return *s->img_buffer++; - if (s->read_from_callbacks) { - stbi__refill_buffer(s); - return *s->img_buffer++; - } - return 0; -} - -stbi_inline static int stbi__at_eof(stbi__context *s) -{ - if (s->io.read) { - if (!(s->io.eof)(s->io_user_data)) return 0; - // if feof() is true, check if buffer = end - // special case: we've only got the special 0 character at the end - if (s->read_from_callbacks == 0) return 1; - } - - return s->img_buffer >= s->img_buffer_end; -} - -static void stbi__skip(stbi__context *s, int n) -{ - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - s->img_buffer = s->img_buffer_end; - (s->io.skip)(s->io_user_data, n - blen); - return; - } - } - s->img_buffer += n; -} - -static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) -{ - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - int res, count; - - memcpy(buffer, s->img_buffer, blen); - - count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); - res = (count == (n-blen)); - s->img_buffer = s->img_buffer_end; - return res; - } - } - - if (s->img_buffer+n <= s->img_buffer_end) { - memcpy(buffer, s->img_buffer, n); - s->img_buffer += n; - return 1; - } else - return 0; -} - -static int stbi__get16be(stbi__context *s) -{ - int z = stbi__get8(s); - return (z << 8) + stbi__get8(s); -} - -static stbi__uint32 stbi__get32be(stbi__context *s) -{ - stbi__uint32 z = stbi__get16be(s); - return (z << 16) + stbi__get16be(s); -} - -static int stbi__get16le(stbi__context *s) -{ - int z = stbi__get8(s); - return z + (stbi__get8(s) << 8); -} - -static stbi__uint32 stbi__get32le(stbi__context *s) -{ - stbi__uint32 z = stbi__get16le(s); - return z + (stbi__get16le(s) << 16); -} - -////////////////////////////////////////////////////////////////////////////// -// -// generic converter from built-in img_n to req_comp -// individual types do this automatically as much as possible (e.g. jpeg -// does all cases internally since it needs to colorspace convert anyway, -// and it never has alpha, so very few cases ). png can automatically -// interleave an alpha=255 channel, but falls back to this for other cases -// -// assume data buffer is malloced, so malloc a new one and free that one -// only failure mode is malloc failing - -static stbi_uc stbi__compute_y(int r, int g, int b) -{ - return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); -} - -static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - unsigned char *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (unsigned char *) stbi__malloc(req_comp * x * y); - if (good == NULL) { - free(data); - return stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - unsigned char *src = data + j * x * img_n ; - unsigned char *dest = good + j * x * req_comp; - - #define COMBO(a,b) ((a)*8+(b)) - #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (COMBO(img_n, req_comp)) { - CASE(1,2) dest[0]=src[0], dest[1]=255; break; - CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; - CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; - CASE(2,1) dest[0]=src[0]; break; - CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; - CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; - CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; - CASE(3,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; - CASE(3,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; break; - CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; - CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; - CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; - default: STBI_ASSERT(0); - } - #undef CASE - } - - free(data); - return good; -} - -#ifndef STBI_NO_HDR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) -{ - int i,k,n; - float *output = (float *) stbi__malloc(x * y * comp * sizeof(float)); - if (output == NULL) { free(data); return stbi__errpf("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); - } - if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; - } - free(data); - return output; -} - -#define stbi__float2int(x) ((int) (x)) -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) -{ - int i,k,n; - stbi_uc *output = (stbi_uc *) stbi__malloc(x * y * comp); - if (output == NULL) { free(data); return stbi__errpuc("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - if (k < comp) { - float z = data[i*comp+k] * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - } - free(data); - return output; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// "baseline" JPEG/JFIF decoder (not actually fully baseline implementation) -// -// simple implementation -// - channel subsampling of at most 2 in each dimension -// - doesn't support delayed output of y-dimension -// - simple interface (only one output format: 8-bit interleaved RGB) -// - doesn't try to recover corrupt jpegs -// - doesn't allow partial loading, loading multiple at once -// - still fast on x86 (copying globals into locals doesn't help x86) -// - allocates lots of intermediate memory (full size of all components) -// - non-interleaved case requires this anyway -// - allows good upsampling (see next) -// high-quality -// - upsampled channels are bilinearly interpolated, even across blocks -// - quality integer IDCT derived from IJG's 'slow' -// performance -// - fast huffman; reasonable integer IDCT -// - uses a lot of intermediate memory, could cache poorly -// - load http://nothings.org/remote/anemones.jpg 3 times on 2.8Ghz P4 -// stb_jpeg: 1.34 seconds (MSVC6, default release build) -// stb_jpeg: 1.06 seconds (MSVC6, processor = Pentium Pro) -// IJL11.dll: 1.08 seconds (compiled by intel) -// IJG 1998: 0.98 seconds (MSVC6, makefile provided by IJG) -// IJG 1998: 0.95 seconds (MSVC6, makefile + proc=PPro) - -// huffman decoding acceleration -#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache - -typedef struct -{ - stbi_uc fast[1 << FAST_BITS]; - // weirdly, repacking this into AoS is a 10% speed loss, instead of a win - stbi__uint16 code[256]; - stbi_uc values[256]; - stbi_uc size[257]; - unsigned int maxcode[18]; - int delta[17]; // old 'firstsymbol' - old 'firstcode' -} stbi__huffman; - -typedef struct -{ - #ifdef STBI_SIMD - unsigned short dequant2[4][64]; - #endif - stbi__context *s; - stbi__huffman huff_dc[4]; - stbi__huffman huff_ac[4]; - stbi_uc dequant[4][64]; - -// sizes for components, interleaved MCUs - int img_h_max, img_v_max; - int img_mcu_x, img_mcu_y; - int img_mcu_w, img_mcu_h; - -// definition of jpeg image component - struct - { - int id; - int h,v; - int tq; - int hd,ha; - int dc_pred; - - int x,y,w2,h2; - stbi_uc *data; - void *raw_data; - stbi_uc *linebuf; - } img_comp[4]; - - stbi__uint32 code_buffer; // jpeg entropy-coded buffer - int code_bits; // number of valid bits - unsigned char marker; // marker seen while filling entropy buffer - int nomore; // flag if we saw a marker so must stop - - int scan_n, order[4]; - int restart_interval, todo; -} stbi__jpeg; - -static int stbi__build_huffman(stbi__huffman *h, int *count) -{ - int i,j,k=0,code; - // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) - h->size[k++] = (stbi_uc) (i+1); - h->size[k] = 0; - - // compute actual symbols (from jpeg spec) - code = 0; - k = 0; - for(j=1; j <= 16; ++j) { - // compute delta to add to code to compute symbol id - h->delta[j] = k - code; - if (h->size[k] == j) { - while (h->size[k] == j) - h->code[k++] = (stbi__uint16) (code++); - if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); - } - // compute largest code + 1 for this size, preshifted as needed later - h->maxcode[j] = code << (16-j); - code <<= 1; - } - h->maxcode[j] = 0xffffffff; - - // build non-spec acceleration table; 255 is flag for not-accelerated - memset(h->fast, 255, 1 << FAST_BITS); - for (i=0; i < k; ++i) { - int s = h->size[i]; - if (s <= FAST_BITS) { - int c = h->code[i] << (FAST_BITS-s); - int m = 1 << (FAST_BITS-s); - for (j=0; j < m; ++j) { - h->fast[c+j] = (stbi_uc) i; - } - } - } - return 1; -} - -static void stbi__grow_buffer_unsafe(stbi__jpeg *j) -{ - do { - int b = j->nomore ? 0 : stbi__get8(j->s); - if (b == 0xff) { - int c = stbi__get8(j->s); - if (c != 0) { - j->marker = (unsigned char) c; - j->nomore = 1; - return; - } - } - j->code_buffer |= b << (24 - j->code_bits); - j->code_bits += 8; - } while (j->code_bits <= 24); -} - -// (1 << n) - 1 -static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; - -// decode a jpeg huffman value from the bitstream -stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) -{ - unsigned int temp; - int c,k; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - // look at the top FAST_BITS and determine what symbol ID it is, - // if the code is <= FAST_BITS - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - k = h->fast[c]; - if (k < 255) { - int s = h->size[k]; - if (s > j->code_bits) - return -1; - j->code_buffer <<= s; - j->code_bits -= s; - return h->values[k]; - } - - // naive test is to shift the code_buffer down so k bits are - // valid, then test against maxcode. To speed this up, we've - // preshifted maxcode left so that it has (16-k) 0s at the - // end; in other words, regardless of the number of bits, it - // wants to be compared against something shifted to have 16; - // that way we don't need to shift inside the loop. - temp = j->code_buffer >> 16; - for (k=FAST_BITS+1 ; ; ++k) - if (temp < h->maxcode[k]) - break; - if (k == 17) { - // error! code not found - j->code_bits -= 16; - return -1; - } - - if (k > j->code_bits) - return -1; - - // convert the huffman code to the symbol id - c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; - STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); - - // convert the id to a symbol - j->code_bits -= k; - j->code_buffer <<= k; - return h->values[c]; -} - -// combined JPEG 'receive' and JPEG 'extend', since baseline -// always extends everything it receives. -stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n) -{ - unsigned int m = 1 << (n-1); - unsigned int k; - if (j->code_bits < n) stbi__grow_buffer_unsafe(j); - - #if 1 - k = stbi_lrot(j->code_buffer, n); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - #else - k = (j->code_buffer >> (32 - n)) & stbi__bmask[n]; - j->code_bits -= n; - j->code_buffer <<= n; - #endif - // the following test is probably a random branch that won't - // predict well. I tried to table accelerate it but failed. - // maybe it's compiling as a conditional move? - if (k < m) - return (-1 << n) + k + 1; - else - return k; -} - -// given a value that's at position X in the zigzag stream, -// where does it appear in the 8x8 matrix coded as row-major? -static stbi_uc stbi__jpeg_dezigzag[64+15] = -{ - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, - // let corrupt input sample past end - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63 -}; - -// decode one 64-entry block-- -static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, int b) -{ - int diff,dc,k; - int t = stbi__jpeg_huff_decode(j, hdc); - if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - - // 0 all the ac values now so we can do it 32-bits at a time - memset(data,0,64*sizeof(data[0])); - - diff = t ? stbi__extend_receive(j, t) : 0; - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) dc; - - // decode AC components, see JPEG spec - k = 1; - do { - int r,s; - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (rs != 0xf0) break; // end block - k += 16; - } else { - k += r; - // decode into unzigzag'd location - data[stbi__jpeg_dezigzag[k++]] = (short) stbi__extend_receive(j,s); - } - } while (k < 64); - return 1; -} - -// take a -128..127 value and stbi__clamp it and convert to 0..255 -stbi_inline static stbi_uc stbi__clamp(int x) -{ - // trick to use a single test to catch both cases - if ((unsigned int) x > 255) { - if (x < 0) return 0; - if (x > 255) return 255; - } - return (stbi_uc) x; -} - -#define stbi__f2f(x) (int) (((x) * 4096 + 0.5)) -#define stbi__fsh(x) ((x) << 12) - -// derived from jidctint -- DCT_ISLOW -#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ - int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ - p2 = s2; \ - p3 = s6; \ - p1 = (p2+p3) * stbi__f2f(0.5411961f); \ - t2 = p1 + p3*stbi__f2f(-1.847759065f); \ - t3 = p1 + p2*stbi__f2f( 0.765366865f); \ - p2 = s0; \ - p3 = s4; \ - t0 = stbi__fsh(p2+p3); \ - t1 = stbi__fsh(p2-p3); \ - x0 = t0+t3; \ - x3 = t0-t3; \ - x1 = t1+t2; \ - x2 = t1-t2; \ - t0 = s7; \ - t1 = s5; \ - t2 = s3; \ - t3 = s1; \ - p3 = t0+t2; \ - p4 = t1+t3; \ - p1 = t0+t3; \ - p2 = t1+t2; \ - p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ - t0 = t0*stbi__f2f( 0.298631336f); \ - t1 = t1*stbi__f2f( 2.053119869f); \ - t2 = t2*stbi__f2f( 3.072711026f); \ - t3 = t3*stbi__f2f( 1.501321110f); \ - p1 = p5 + p1*stbi__f2f(-0.899976223f); \ - p2 = p5 + p2*stbi__f2f(-2.562915447f); \ - p3 = p3*stbi__f2f(-1.961570560f); \ - p4 = p4*stbi__f2f(-0.390180644f); \ - t3 += p1+p4; \ - t2 += p2+p3; \ - t1 += p2+p4; \ - t0 += p1+p3; - -#ifdef STBI_SIMD -typedef unsigned short stbi_dequantize_t; -#else -typedef stbi_uc stbi_dequantize_t; -#endif - -// .344 seconds on 3*anemones.jpg -static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64], stbi_dequantize_t *dequantize) -{ - int i,val[64],*v=val; - stbi_dequantize_t *dq = dequantize; - stbi_uc *o; - short *d = data; - - // columns - for (i=0; i < 8; ++i,++d,++dq, ++v) { - // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing - if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 - && d[40]==0 && d[48]==0 && d[56]==0) { - // no shortcut 0 seconds - // (1|2|3|4|5|6|7)==0 0 seconds - // all separate -0.047 seconds - // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0] * dq[0] << 2; - v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; - } else { - STBI__IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24], - d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[56]) - // constants scaled things up by 1<<12; let's bring them back - // down, but keep 2 extra bits of precision - x0 += 512; x1 += 512; x2 += 512; x3 += 512; - v[ 0] = (x0+t3) >> 10; - v[56] = (x0-t3) >> 10; - v[ 8] = (x1+t2) >> 10; - v[48] = (x1-t2) >> 10; - v[16] = (x2+t1) >> 10; - v[40] = (x2-t1) >> 10; - v[24] = (x3+t0) >> 10; - v[32] = (x3-t0) >> 10; - } - } - - for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { - // no fast case since the first 1D IDCT spread components out - STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) - // constants scaled things up by 1<<12, plus we had 1<<2 from first - // loop, plus horizontal and vertical each scale by sqrt(8) so together - // we've got an extra 1<<3, so 1<<17 total we need to remove. - // so we want to round that, which means adding 0.5 * 1<<17, - // aka 65536. Also, we'll end up with -128 to 127 that we want - // to encode as 0..255 by adding 128, so we'll add that before the shift - x0 += 65536 + (128<<17); - x1 += 65536 + (128<<17); - x2 += 65536 + (128<<17); - x3 += 65536 + (128<<17); - // tried computing the shifts into temps, or'ing the temps to see - // if any were out of range, but that was slower - o[0] = stbi__clamp((x0+t3) >> 17); - o[7] = stbi__clamp((x0-t3) >> 17); - o[1] = stbi__clamp((x1+t2) >> 17); - o[6] = stbi__clamp((x1-t2) >> 17); - o[2] = stbi__clamp((x2+t1) >> 17); - o[5] = stbi__clamp((x2-t1) >> 17); - o[3] = stbi__clamp((x3+t0) >> 17); - o[4] = stbi__clamp((x3-t0) >> 17); - } -} - -#ifdef STBI_SIMD -static stbi_idct_8x8 stbi__idct_installed = stbi__idct_block; - -STBIDEF void stbi_install_idct(stbi_idct_8x8 func) -{ - stbi__idct_installed = func; -} -#endif - -#define STBI__MARKER_none 0xff -// if there's a pending marker from the entropy stream, return that -// otherwise, fetch from the stream and get a marker. if there's no -// marker, return 0xff, which is never a valid marker value -static stbi_uc stbi__get_marker(stbi__jpeg *j) -{ - stbi_uc x; - if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } - x = stbi__get8(j->s); - if (x != 0xff) return STBI__MARKER_none; - while (x == 0xff) - x = stbi__get8(j->s); - return x; -} - -// in each scan, we'll have scan_n components, and the order -// of the components is specified by order[] -#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) - -// after a restart interval, stbi__jpeg_reset the entropy decoder and -// the dc prediction -static void stbi__jpeg_reset(stbi__jpeg *j) -{ - j->code_bits = 0; - j->code_buffer = 0; - j->nomore = 0; - j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; - j->marker = STBI__MARKER_none; - j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; - // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, - // since we don't even allow 1<<30 pixels -} - -static int stbi__parse_entropy_coded_data(stbi__jpeg *z) -{ - stbi__jpeg_reset(z); - if (z->scan_n == 1) { - int i,j; - #ifdef STBI_SIMD - __declspec(align(16)) - #endif - short data[64]; - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; - #ifdef STBI_SIMD - stbi__idct_installed(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); - #else - stbi__idct_block(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); - #endif - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - } else { // interleaved! - int i,j,k,x,y; - short data[64]; - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x)*8; - int y2 = (j*z->img_comp[n].v + y)*8; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; - #ifdef STBI_SIMD - stbi__idct_installed(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); - #else - stbi__idct_block(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); - #endif - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - } - return 1; -} - -static int stbi__process_marker(stbi__jpeg *z, int m) -{ - int L; - switch (m) { - case STBI__MARKER_none: // no marker found - return stbi__err("expected marker","Corrupt JPEG"); - - case 0xC2: // stbi__SOF - progressive - return stbi__err("progressive jpeg","JPEG format not supported (progressive)"); - - case 0xDD: // DRI - specify restart interval - if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); - z->restart_interval = stbi__get16be(z->s); - return 1; - - case 0xDB: // DQT - define quantization table - L = stbi__get16be(z->s)-2; - while (L > 0) { - int q = stbi__get8(z->s); - int p = q >> 4; - int t = q & 15,i; - if (p != 0) return stbi__err("bad DQT type","Corrupt JPEG"); - if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); - for (i=0; i < 64; ++i) - z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s); - #ifdef STBI_SIMD - for (i=0; i < 64; ++i) - z->dequant2[t][i] = z->dequant[t][i]; - #endif - L -= 65; - } - return L==0; - - case 0xC4: // DHT - define huffman table - L = stbi__get16be(z->s)-2; - while (L > 0) { - stbi_uc *v; - int sizes[16],i,n=0; - int q = stbi__get8(z->s); - int tc = q >> 4; - int th = q & 15; - if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); - for (i=0; i < 16; ++i) { - sizes[i] = stbi__get8(z->s); - n += sizes[i]; - } - L -= 17; - if (tc == 0) { - if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; - v = z->huff_dc[th].values; - } else { - if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; - v = z->huff_ac[th].values; - } - for (i=0; i < n; ++i) - v[i] = stbi__get8(z->s); - L -= n; - } - return L==0; - } - // check for comment block or APP blocks - if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { - stbi__skip(z->s, stbi__get16be(z->s)-2); - return 1; - } - return 0; -} - -// after we see stbi__SOS -static int stbi__process_scan_header(stbi__jpeg *z) -{ - int i; - int Ls = stbi__get16be(z->s); - z->scan_n = stbi__get8(z->s); - if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad stbi__SOS component count","Corrupt JPEG"); - if (Ls != 6+2*z->scan_n) return stbi__err("bad stbi__SOS len","Corrupt JPEG"); - for (i=0; i < z->scan_n; ++i) { - int id = stbi__get8(z->s), which; - int q = stbi__get8(z->s); - for (which = 0; which < z->s->img_n; ++which) - if (z->img_comp[which].id == id) - break; - if (which == z->s->img_n) return 0; - z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); - z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); - z->order[i] = which; - } - if (stbi__get8(z->s) != 0) return stbi__err("bad stbi__SOS","Corrupt JPEG"); - stbi__get8(z->s); // should be 63, but might be 0 - if (stbi__get8(z->s) != 0) return stbi__err("bad stbi__SOS","Corrupt JPEG"); - - return 1; -} - -static int stbi__process_frame_header(stbi__jpeg *z, int scan) -{ - stbi__context *s = z->s; - int Lf,p,i,q, h_max=1,v_max=1,c; - Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad stbi__SOF len","Corrupt JPEG"); // JPEG - p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline - s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG - s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires - c = stbi__get8(s); - if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires - s->img_n = c; - for (i=0; i < c; ++i) { - z->img_comp[i].data = NULL; - z->img_comp[i].linebuf = NULL; - } - - if (Lf != 8+3*s->img_n) return stbi__err("bad stbi__SOF len","Corrupt JPEG"); - - for (i=0; i < s->img_n; ++i) { - z->img_comp[i].id = stbi__get8(s); - if (z->img_comp[i].id != i+1) // JFIF requires - if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! - return stbi__err("bad component ID","Corrupt JPEG"); - q = stbi__get8(s); - z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); - z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); - z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); - } - - if (scan != SCAN_load) return 1; - - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - - for (i=0; i < s->img_n; ++i) { - if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; - if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; - } - - // compute interleaved mcu info - z->img_h_max = h_max; - z->img_v_max = v_max; - z->img_mcu_w = h_max * 8; - z->img_mcu_h = v_max * 8; - z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; - z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; - - for (i=0; i < s->img_n; ++i) { - // number of effective pixels (e.g. for non-interleaved MCU) - z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; - z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; - // to simplify generation, we'll allocate enough memory to decode - // the bogus oversized data from using interleaved MCUs and their - // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't - // discard the extra data until colorspace conversion - z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; - z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; - z->img_comp[i].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); - if (z->img_comp[i].raw_data == NULL) { - for(--i; i >= 0; --i) { - free(z->img_comp[i].raw_data); - z->img_comp[i].data = NULL; - } - return stbi__err("outofmem", "Out of memory"); - } - // align blocks for installable-idct using mmx/sse - z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); - z->img_comp[i].linebuf = NULL; - } - - return 1; -} - -// use comparisons since in some cases we handle more than one case (e.g. stbi__SOF) -#define stbi__DNL(x) ((x) == 0xdc) -#define stbi__SOI(x) ((x) == 0xd8) -#define stbi__EOI(x) ((x) == 0xd9) -#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1) -#define stbi__SOS(x) ((x) == 0xda) - -static int decode_jpeg_header(stbi__jpeg *z, int scan) -{ - int m; - z->marker = STBI__MARKER_none; // initialize cached marker to empty - m = stbi__get_marker(z); - if (!stbi__SOI(m)) return stbi__err("no stbi__SOI","Corrupt JPEG"); - if (scan == SCAN_type) return 1; - m = stbi__get_marker(z); - while (!stbi__SOF(m)) { - if (!stbi__process_marker(z,m)) return 0; - m = stbi__get_marker(z); - while (m == STBI__MARKER_none) { - // some files have extra padding after their blocks, so ok, we'll scan - if (stbi__at_eof(z->s)) return stbi__err("no stbi__SOF", "Corrupt JPEG"); - m = stbi__get_marker(z); - } - } - if (!stbi__process_frame_header(z, scan)) return 0; - return 1; -} - -static int decode_jpeg_image(stbi__jpeg *j) -{ - int m; - j->restart_interval = 0; - if (!decode_jpeg_header(j, SCAN_load)) return 0; - m = stbi__get_marker(j); - while (!stbi__EOI(m)) { - if (stbi__SOS(m)) { - if (!stbi__process_scan_header(j)) return 0; - if (!stbi__parse_entropy_coded_data(j)) return 0; - if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } else if (x != 0) { - return 0; - } - } - // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 - } - } else { - if (!stbi__process_marker(j, m)) return 0; - } - m = stbi__get_marker(j); - } - return 1; -} - -// static jfif-centered resampling (across block boundaries) - -typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, - int w, int hs); - -#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) - -static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - STBI_NOTUSED(out); - STBI_NOTUSED(in_far); - STBI_NOTUSED(w); - STBI_NOTUSED(hs); - return in_near; -} - -static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples vertically for every one in input - int i; - STBI_NOTUSED(hs); - for (i=0; i < w; ++i) - out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); - return out; -} - -static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples horizontally for every one in input - int i; - stbi_uc *input = in_near; - - if (w == 1) { - // if only one sample, can't do any interpolation - out[0] = out[1] = input[0]; - return out; - } - - out[0] = input[0]; - out[1] = stbi__div4(input[0]*3 + input[1] + 2); - for (i=1; i < w-1; ++i) { - int n = 3*input[i]+2; - out[i*2+0] = stbi__div4(n+input[i-1]); - out[i*2+1] = stbi__div4(n+input[i+1]); - } - out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); - out[i*2+1] = input[w-1]; - - STBI_NOTUSED(in_far); - STBI_NOTUSED(hs); - - return out; -} - -#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) - -static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i,t0,t1; - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - out[0] = stbi__div4(t1+2); - for (i=1; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} - -static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // resample with nearest-neighbor - int i,j; - STBI_NOTUSED(in_far); - for (i=0; i < w; ++i) - for (j=0; j < hs; ++j) - out[i*hs+j] = in_near[i]; - return out; -} - -#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) - -// 0.38 seconds on 3*anemones.jpg (0.25 with processor = Pro) -// VC6 without processor=Pro is generating multiple LEAs per multiply! -static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) -{ - int i; - for (i=0; i < count; ++i) { - int y_fixed = (y[i] << 16) + 32768; // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr*float2fixed(1.40200f); - g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); - b = y_fixed + cb*float2fixed(1.77200f); - r >>= 16; - g >>= 16; - b >>= 16; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} - -#ifdef STBI_SIMD -static stbi_YCbCr_to_RGB_run stbi__YCbCr_installed = stbi__YCbCr_to_RGB_row; - -STBIDEF void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func) -{ - stbi__YCbCr_installed = func; -} -#endif - - -// clean up the temporary component buffers -static void stbi__cleanup_jpeg(stbi__jpeg *j) -{ - int i; - for (i=0; i < j->s->img_n; ++i) { - if (j->img_comp[i].raw_data) { - free(j->img_comp[i].raw_data); - j->img_comp[i].raw_data = NULL; - j->img_comp[i].data = NULL; - } - if (j->img_comp[i].linebuf) { - free(j->img_comp[i].linebuf); - j->img_comp[i].linebuf = NULL; - } - } -} - -typedef struct -{ - resample_row_func resample; - stbi_uc *line0,*line1; - int hs,vs; // expansion factor in each axis - int w_lores; // horizontal pixels pre-expansion - int ystep; // how far through vertical expansion we are - int ypos; // which pre-expansion row we're on -} stbi__resample; - -static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) -{ - int n, decode_n; - z->s->img_n = 0; // make stbi__cleanup_jpeg safe - - // validate req_comp - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - - // load a jpeg image from whichever source - if (!decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } - - // determine actual number of components to generate - n = req_comp ? req_comp : z->s->img_n; - - if (z->s->img_n == 3 && n < 3) - decode_n = 1; - else - decode_n = z->s->img_n; - - // resample and color-convert - { - int k; - unsigned int i,j; - stbi_uc *output; - stbi_uc *coutput[4]; - - stbi__resample res_comp[4]; - - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - - // allocate line buffer big enough for upsampling off the edges - // with upsample factor of 4 - z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); - if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - r->hs = z->img_h_max / z->img_comp[k].h; - r->vs = z->img_v_max / z->img_comp[k].v; - r->ystep = r->vs >> 1; - r->w_lores = (z->s->img_x + r->hs-1) / r->hs; - r->ypos = 0; - r->line0 = r->line1 = z->img_comp[k].data; - - if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; - else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; - else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; - else if (r->hs == 2 && r->vs == 2) r->resample = stbi__resample_row_hv_2; - else r->resample = stbi__resample_row_generic; - } - - // can't error after this so, this is safe - output = (stbi_uc *) stbi__malloc(n * z->s->img_x * z->s->img_y + 1); - if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - // now go ahead and resample - for (j=0; j < z->s->img_y; ++j) { - stbi_uc *out = output + n * z->s->img_x * j; - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - int y_bot = r->ystep >= (r->vs >> 1); - coutput[k] = r->resample(z->img_comp[k].linebuf, - y_bot ? r->line1 : r->line0, - y_bot ? r->line0 : r->line1, - r->w_lores, r->hs); - if (++r->ystep >= r->vs) { - r->ystep = 0; - r->line0 = r->line1; - if (++r->ypos < z->img_comp[k].y) - r->line1 += z->img_comp[k].w2; - } - } - if (n >= 3) { - stbi_uc *y = coutput[0]; - if (z->s->img_n == 3) { - #ifdef STBI_SIMD - stbi__YCbCr_installed(out, y, coutput[1], coutput[2], z->s->img_x, n); - #else - stbi__YCbCr_to_RGB_row(out, y, coutput[1], coutput[2], z->s->img_x, n); - #endif - } else - for (i=0; i < z->s->img_x; ++i) { - out[0] = out[1] = out[2] = y[i]; - out[3] = 255; // not used if n==3 - out += n; - } - } else { - stbi_uc *y = coutput[0]; - if (n == 1) - for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; - else - for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; - } - } - stbi__cleanup_jpeg(z); - *out_x = z->s->img_x; - *out_y = z->s->img_y; - if (comp) *comp = z->s->img_n; // report original components, not output - return output; - } -} - -static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__jpeg j; - j.s = s; - return load_jpeg_image(&j, x,y,comp,req_comp); -} - -static int stbi__jpeg_test(stbi__context *s) -{ - int r; - stbi__jpeg j; - j.s = s; - r = decode_jpeg_header(&j, SCAN_type); - stbi__rewind(s); - return r; -} - -static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) -{ - if (!decode_jpeg_header(j, SCAN_header)) { - stbi__rewind( j->s ); - return 0; - } - if (x) *x = j->s->img_x; - if (y) *y = j->s->img_y; - if (comp) *comp = j->s->img_n; - return 1; -} - -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__jpeg j; - j.s = s; - return stbi__jpeg_info_raw(&j, x, y, comp); -} - -// public domain zlib decode v0.2 Sean Barrett 2006-11-18 -// simple implementation -// - all input must be provided in an upfront buffer -// - all output is written to a single output buffer (can malloc/realloc) -// performance -// - fast huffman - -// fast-way is faster to check than jpeg huffman, but slow way is slower -#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables -#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) - -// zlib-style huffman encoding -// (jpegs packs from left, zlib from right, so can't share code) -typedef struct -{ - stbi__uint16 fast[1 << STBI__ZFAST_BITS]; - stbi__uint16 firstcode[16]; - int maxcode[17]; - stbi__uint16 firstsymbol[16]; - stbi_uc size[288]; - stbi__uint16 value[288]; -} stbi__zhuffman; - -stbi_inline static int stbi__bitreverse16(int n) -{ - n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); - n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); - n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); - n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); - return n; -} - -stbi_inline static int stbi__bit_reverse(int v, int bits) -{ - STBI_ASSERT(bits <= 16); - // to bit reverse n bits, reverse 16 and shift - // e.g. 11 bits, bit reverse and shift away 5 - return stbi__bitreverse16(v) >> (16-bits); -} - -static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) -{ - int i,k=0; - int code, next_code[16], sizes[17]; - - // DEFLATE spec for generating codes - memset(sizes, 0, sizeof(sizes)); - memset(z->fast, 255, sizeof(z->fast)); - for (i=0; i < num; ++i) - ++sizes[sizelist[i]]; - sizes[0] = 0; - for (i=1; i < 16; ++i) - STBI_ASSERT(sizes[i] <= (1 << i)); - code = 0; - for (i=1; i < 16; ++i) { - next_code[i] = code; - z->firstcode[i] = (stbi__uint16) code; - z->firstsymbol[i] = (stbi__uint16) k; - code = (code + sizes[i]); - if (sizes[i]) - if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt JPEG"); - z->maxcode[i] = code << (16-i); // preshift for inner loop - code <<= 1; - k += sizes[i]; - } - z->maxcode[16] = 0x10000; // sentinel - for (i=0; i < num; ++i) { - int s = sizelist[i]; - if (s) { - int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; - z->size [c] = (stbi_uc ) s; - z->value[c] = (stbi__uint16) i; - if (s <= STBI__ZFAST_BITS) { - int k = stbi__bit_reverse(next_code[s],s); - while (k < (1 << STBI__ZFAST_BITS)) { - z->fast[k] = (stbi__uint16) c; - k += (1 << s); - } - } - ++next_code[s]; - } - } - return 1; -} - -// zlib-from-memory implementation for PNG reading -// because PNG allows splitting the zlib stream arbitrarily, -// and it's annoying structurally to have PNG call ZLIB call PNG, -// we require PNG read all the IDATs and combine them into a single -// memory buffer - -typedef struct -{ - stbi_uc *zbuffer, *zbuffer_end; - int num_bits; - stbi__uint32 code_buffer; - - char *zout; - char *zout_start; - char *zout_end; - int z_expandable; - - stbi__zhuffman z_length, z_distance; -} stbi__zbuf; - -stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) -{ - if (z->zbuffer >= z->zbuffer_end) return 0; - return *z->zbuffer++; -} - -static void stbi__fill_bits(stbi__zbuf *z) -{ - do { - STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); - z->code_buffer |= stbi__zget8(z) << z->num_bits; - z->num_bits += 8; - } while (z->num_bits <= 24); -} - -stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) -{ - unsigned int k; - if (z->num_bits < n) stbi__fill_bits(z); - k = z->code_buffer & ((1 << n) - 1); - z->code_buffer >>= n; - z->num_bits -= n; - return k; -} - -stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s,k; - if (a->num_bits < 16) stbi__fill_bits(a); - b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; - if (b < 0xffff) { - s = z->size[b]; - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; - } - - // not resolved by fast table, so compute it the slow way - // use jpeg approach, which requires MSbits at top - k = stbi__bit_reverse(a->code_buffer, 16); - for (s=STBI__ZFAST_BITS+1; ; ++s) - if (k < z->maxcode[s]) - break; - if (s == 16) return -1; // invalid code! - // code size is s, so: - b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - STBI_ASSERT(z->size[b] == s); - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; -} - -static int stbi__zexpand(stbi__zbuf *z, int n) // need to make room for n bytes -{ - char *q; - int cur, limit; - if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); - cur = (int) (z->zout - z->zout_start); - limit = (int) (z->zout_end - z->zout_start); - while (cur + n > limit) - limit *= 2; - q = (char *) realloc(z->zout_start, limit); - if (q == NULL) return stbi__err("outofmem", "Out of memory"); - z->zout_start = q; - z->zout = q + cur; - z->zout_end = q + limit; - return 1; -} - -static int stbi__zlength_base[31] = { - 3,4,5,6,7,8,9,10,11,13, - 15,17,19,23,27,31,35,43,51,59, - 67,83,99,115,131,163,195,227,258,0,0 }; - -static int stbi__zlength_extra[31]= -{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; - -static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, -257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; - -static int stbi__zdist_extra[32] = -{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; - -static int stbi__parse_huffman_block(stbi__zbuf *a) -{ - for(;;) { - int z = stbi__zhuffman_decode(a, &a->z_length); - if (z < 256) { - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes - if (a->zout >= a->zout_end) if (!stbi__zexpand(a, 1)) return 0; - *a->zout++ = (char) z; - } else { - stbi_uc *p; - int len,dist; - if (z == 256) return 1; - z -= 257; - len = stbi__zlength_base[z]; - if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); - z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); - dist = stbi__zdist_base[z]; - if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); - if (a->zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (a->zout + len > a->zout_end) if (!stbi__zexpand(a, len)) return 0; - p = (stbi_uc *) (a->zout - dist); - while (len--) - *a->zout++ = *p++; - } - } -} - -static int stbi__compute_huffman_codes(stbi__zbuf *a) -{ - static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; - stbi__zhuffman z_codelength; - stbi_uc lencodes[286+32+137];//padding for maximum single op - stbi_uc codelength_sizes[19]; - int i,n; - - int hlit = stbi__zreceive(a,5) + 257; - int hdist = stbi__zreceive(a,5) + 1; - int hclen = stbi__zreceive(a,4) + 4; - - memset(codelength_sizes, 0, sizeof(codelength_sizes)); - for (i=0; i < hclen; ++i) { - int s = stbi__zreceive(a,3); - codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; - } - if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; - - n = 0; - while (n < hlit + hdist) { - int c = stbi__zhuffman_decode(a, &z_codelength); - STBI_ASSERT(c >= 0 && c < 19); - if (c < 16) - lencodes[n++] = (stbi_uc) c; - else if (c == 16) { - c = stbi__zreceive(a,2)+3; - memset(lencodes+n, lencodes[n-1], c); - n += c; - } else if (c == 17) { - c = stbi__zreceive(a,3)+3; - memset(lencodes+n, 0, c); - n += c; - } else { - STBI_ASSERT(c == 18); - c = stbi__zreceive(a,7)+11; - memset(lencodes+n, 0, c); - n += c; - } - } - if (n != hlit+hdist) return stbi__err("bad codelengths","Corrupt PNG"); - if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; - return 1; -} - -static int stbi__parse_uncomperssed_block(stbi__zbuf *a) -{ - stbi_uc header[4]; - int len,nlen,k; - if (a->num_bits & 7) - stbi__zreceive(a, a->num_bits & 7); // discard - // drain the bit-packed data into header - k = 0; - while (a->num_bits > 0) { - header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check - a->code_buffer >>= 8; - a->num_bits -= 8; - } - STBI_ASSERT(a->num_bits == 0); - // now fill header the normal way - while (k < 4) - header[k++] = stbi__zget8(a); - len = header[1] * 256 + header[0]; - nlen = header[3] * 256 + header[2]; - if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); - if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); - if (a->zout + len > a->zout_end) - if (!stbi__zexpand(a, len)) return 0; - memcpy(a->zout, a->zbuffer, len); - a->zbuffer += len; - a->zout += len; - return 1; -} - -static int stbi__parse_zlib_header(stbi__zbuf *a) -{ - int cmf = stbi__zget8(a); - int cm = cmf & 15; - /* int cinfo = cmf >> 4; */ - int flg = stbi__zget8(a); - if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec - if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png - if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png - // window = 1 << (8 + cinfo)... but who cares, we fully buffer output - return 1; -} - -// @TODO: should statically initialize these for optimal thread safety -static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32]; -static void stbi__init_zdefaults(void) -{ - int i; // use <= to match clearly with spec - for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; - for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; - for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; - for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; - - for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; -} - -static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) -{ - int final, type; - if (parse_header) - if (!stbi__parse_zlib_header(a)) return 0; - a->num_bits = 0; - a->code_buffer = 0; - do { - final = stbi__zreceive(a,1); - type = stbi__zreceive(a,2); - if (type == 0) { - if (!stbi__parse_uncomperssed_block(a)) return 0; - } else if (type == 3) { - return 0; - } else { - if (type == 1) { - // use fixed code lengths - if (!stbi__zdefault_distance[31]) stbi__init_zdefaults(); - if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; - } else { - if (!stbi__compute_huffman_codes(a)) return 0; - } - if (!stbi__parse_huffman_block(a)) return 0; - } - } while (!final); - return 1; -} - -static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) -{ - a->zout_start = obuf; - a->zout = obuf; - a->zout_end = obuf + olen; - a->z_expandable = exp; - - return stbi__parse_zlib(a, parse_header); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - free(a.zout_start); - return NULL; - } -} - -STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) -{ - return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - free(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) - return (int) (a.zout - a.zout_start); - else - return -1; -} - -STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(16384); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer+len; - if (stbi__do_zlib(&a, p, 16384, 1, 0)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - free(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) - return (int) (a.zout - a.zout_start); - else - return -1; -} - -// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 -// simple implementation -// - only 8-bit samples -// - no CRC checking -// - allocates lots of intermediate memory -// - avoids problem of streaming data between subsystems -// - avoids explicit window management -// performance -// - uses stb_zlib, a PD zlib implementation with fast huffman decoding - - -typedef struct -{ - stbi__uint32 length; - stbi__uint32 type; -} stbi__pngchunk; - -#define PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) - -static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) -{ - stbi__pngchunk c; - c.length = stbi__get32be(s); - c.type = stbi__get32be(s); - return c; -} - -static int stbi__check_png_header(stbi__context *s) -{ - static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; - int i; - for (i=0; i < 8; ++i) - if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); - return 1; -} - -typedef struct -{ - stbi__context *s; - stbi_uc *idata, *expanded, *out; -} stbi__png; - - -enum { - STBI__F_none=0, STBI__F_sub=1, STBI__F_up=2, STBI__F_avg=3, STBI__F_paeth=4, - STBI__F_avg_first, STBI__F_paeth_first -}; - -static stbi_uc first_row_filter[5] = -{ - STBI__F_none, STBI__F_sub, STBI__F_none, STBI__F_avg_first, STBI__F_paeth_first -}; - -static int stbi__paeth(int a, int b, int c) -{ - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; -} - -#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings - -// create the png data from post-deflated data -static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) -{ - stbi__context *s = a->s; - stbi__uint32 i,j,stride = x*out_n; - stbi__uint32 img_len; - int k; - int img_n = s->img_n; // copy it into a local for later - stbi_uc* line8 = NULL; // point into raw when depth==8 else temporary local buffer - - STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); - a->out = (stbi_uc *) stbi__malloc(x * y * out_n); - if (!a->out) return stbi__err("outofmem", "Out of memory"); - - img_len = ((((img_n * x * depth) + 7) >> 3) + 1) * y; - if (s->img_x == x && s->img_y == y) { - if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); - } else { // interlaced: - if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); - } - - if (depth != 8) { - line8 = (stbi_uc *) stbi__malloc((x+7) * out_n); // allocate buffer for one scanline - if (!line8) return stbi__err("outofmem", "Out of memory"); - } - - for (j=0; j < y; ++j) { - stbi_uc *in; - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior = cur - stride; - int filter = *raw++; - if (filter > 4) { - if (depth != 8) free(line8); - return stbi__err("invalid filter","Corrupt PNG"); - } - - if (depth == 8) { - in = raw; - raw += x*img_n; - } - else { - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop - in = line8; - stbi_uc* decode_out = line8; - stbi_uc scale = (color == 0) ? 0xFF/((1<= 1; k-=2, raw++) { - *decode_out++ = scale * ((*raw >> 4) ); - *decode_out++ = scale * ((*raw ) & 0x0f); - } - } else if (depth == 2) { - for (k=x*img_n; k >= 1; k-=4, raw++) { - *decode_out++ = scale * ((*raw >> 6) ); - *decode_out++ = scale * ((*raw >> 4) & 0x03); - *decode_out++ = scale * ((*raw >> 2) & 0x03); - *decode_out++ = scale * ((*raw ) & 0x03); - } - } else if (depth == 1) { - for (k=x*img_n; k >= 1; k-=8, raw++) { - *decode_out++ = scale * ((*raw >> 7) ); - *decode_out++ = scale * ((*raw >> 6) & 0x01); - *decode_out++ = scale * ((*raw >> 5) & 0x01); - *decode_out++ = scale * ((*raw >> 4) & 0x01); - *decode_out++ = scale * ((*raw >> 3) & 0x01); - *decode_out++ = scale * ((*raw >> 2) & 0x01); - *decode_out++ = scale * ((*raw >> 1) & 0x01); - *decode_out++ = scale * ((*raw ) & 0x01); - } - } - } - - // if first row, use special filter that doesn't sample previous row - if (j == 0) filter = first_row_filter[filter]; - - // handle first pixel explicitly - for (k=0; k < img_n; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = in[k]; break; - case STBI__F_sub : cur[k] = in[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(in[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(in[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(in[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = in[k]; break; - case STBI__F_paeth_first: cur[k] = in[k]; break; - } - } - if (img_n != out_n) cur[img_n] = 255; - in += img_n; - cur += out_n; - prior += out_n; - // this is a little gross, so that we don't switch per-pixel or per-component - if (img_n == out_n) { - #define CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, in+=img_n,cur+=img_n,prior+=img_n) \ - for (k=0; k < img_n; ++k) - switch (filter) { - CASE(STBI__F_none) cur[k] = in[k]; break; - CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(in[k] + cur[k-img_n]); break; - CASE(STBI__F_up) cur[k] = STBI__BYTECAST(in[k] + prior[k]); break; - CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(in[k] + ((prior[k] + cur[k-img_n])>>1)); break; - CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(in[k] + stbi__paeth(cur[k-img_n],prior[k],prior[k-img_n])); break; - CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(in[k] + (cur[k-img_n] >> 1)); break; - CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(in[k] + stbi__paeth(cur[k-img_n],0,0)); break; - } - #undef CASE - } else { - STBI_ASSERT(img_n+1 == out_n); - #define CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[img_n]=255,in+=img_n,cur+=out_n,prior+=out_n) \ - for (k=0; k < img_n; ++k) - switch (filter) { - CASE(STBI__F_none) cur[k] = in[k]; break; - CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(in[k] + cur[k-out_n]); break; - CASE(STBI__F_up) cur[k] = STBI__BYTECAST(in[k] + prior[k]); break; - CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(in[k] + ((prior[k] + cur[k-out_n])>>1)); break; - CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(in[k] + stbi__paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; - CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(in[k] + (cur[k-out_n] >> 1)); break; - CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(in[k] + stbi__paeth(cur[k-out_n],0,0)); break; - } - #undef CASE - } - } - - if (depth != 8) free(line8); - return 1; -} - -static int stbi__create_png_image(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, int depth, int color, int interlaced) -{ - stbi_uc *final; - int p; - if (!interlaced) - return stbi__create_png_image_raw(a, raw, raw_len, out_n, a->s->img_x, a->s->img_y, depth, color); - - // de-interlacing - final = (stbi_uc *) stbi__malloc(a->s->img_x * a->s->img_y * out_n); - for (p=0; p < 7; ++p) { - int xorig[] = { 0,4,0,2,0,1,0 }; - int yorig[] = { 0,0,4,0,2,0,1 }; - int xspc[] = { 8,8,4,4,2,2,1 }; - int yspc[] = { 8,8,8,4,4,2,2 }; - int i,j,x,y; - // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 - x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; - y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; - if (x && y) { - stbi__uint32 img_len = ((((out_n * x * depth) + 7) >> 3) + 1) * y; - if (!stbi__create_png_image_raw(a, raw, raw_len, out_n, x, y, depth, color)) { - free(final); - return 0; - } - for (j=0; j < y; ++j) - for (i=0; i < x; ++i) - memcpy(final + (j*yspc[p]+yorig[p])*a->s->img_x*out_n + (i*xspc[p]+xorig[p])*out_n, - a->out + (j*x+i)*out_n, out_n); - free(a->out); - raw += img_len; - raw_len -= img_len; - } - } - a->out = final; - - return 1; -} - -static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - // compute color-based transparency, assuming we've - // already got 255 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i=0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 255); - p += 2; - } - } else { - for (i=0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) -{ - stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; - stbi_uc *p, *temp_out, *orig = a->out; - - p = (stbi_uc *) stbi__malloc(pixel_count * pal_img_n); - if (p == NULL) return stbi__err("outofmem", "Out of memory"); - - // between here and free(out) below, exitting would leak - temp_out = p; - - if (pal_img_n == 3) { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p += 3; - } - } else { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p[3] = palette[n+3]; - p += 4; - } - } - free(a->out); - a->out = temp_out; - - STBI_NOTUSED(len); - - return 1; -} - -static int stbi__unpremultiply_on_load = 0; -static int stbi__de_iphone_flag = 0; - -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) -{ - stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; -} - -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) -{ - stbi__de_iphone_flag = flag_true_if_should_convert; -} - -static void stbi__de_iphone(stbi__png *z) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - if (s->img_out_n == 3) { // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 3; - } - } else { - STBI_ASSERT(s->img_out_n == 4); - if (stbi__unpremultiply_on_load) { - // convert bgr to rgb and unpremultiply - for (i=0; i < pixel_count; ++i) { - stbi_uc a = p[3]; - stbi_uc t = p[0]; - if (a) { - p[0] = p[2] * 255 / a; - p[1] = p[1] * 255 / a; - p[2] = t * 255 / a; - } else { - p[0] = p[2]; - p[2] = t; - } - p += 4; - } - } else { - // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 4; - } - } - } -} - -static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) -{ - stbi_uc palette[1024], pal_img_n=0; - stbi_uc has_trans=0, tc[3]; - stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; - int first=1,k,interlace=0, color=0, depth=0, is_iphone=0; - stbi__context *s = z->s; - - z->expanded = NULL; - z->idata = NULL; - z->out = NULL; - - if (!stbi__check_png_header(s)) return 0; - - if (scan == SCAN_type) return 1; - - for (;;) { - stbi__pngchunk c = stbi__get_chunk_header(s); - switch (c.type) { - case PNG_TYPE('C','g','B','I'): - is_iphone = 1; - stbi__skip(s, c.length); - break; - case PNG_TYPE('I','H','D','R'): { - int comp,filter; - if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); - first = 0; - if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); - s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - depth = stbi__get8(s); if (depth != 1 && depth != 2 && depth != 4 && depth != 8) return stbi__err("1/2/4/8-bit only","PNG not supported: 1/2/4/8-bit only"); - color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); - comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); - filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); - interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); - if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); - if (!pal_img_n) { - s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == SCAN_header) return 1; - } else { - // if paletted, then pal_n is our final components, and - // img_n is # components to decompress/filter. - s->img_n = 1; - if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS - } - break; - } - - case PNG_TYPE('P','L','T','E'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); - pal_len = c.length / 3; - if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); - for (i=0; i < pal_len; ++i) { - palette[i*4+0] = stbi__get8(s); - palette[i*4+1] = stbi__get8(s); - palette[i*4+2] = stbi__get8(s); - palette[i*4+3] = 255; - } - break; - } - - case PNG_TYPE('t','R','N','S'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); - if (pal_img_n) { - if (scan == SCAN_header) { s->img_n = 4; return 1; } - if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); - if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); - pal_img_n = 4; - for (i=0; i < c.length; ++i) - palette[i*4+3] = stbi__get8(s); - } else { - if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); - if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); - has_trans = 1; - for (k=0; k < s->img_n; ++k) - tc[k] = (stbi_uc) (stbi__get16be(s) & 255); // non 8-bit images will be larger - } - break; - } - - case PNG_TYPE('I','D','A','T'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == SCAN_header) { s->img_n = pal_img_n; return 1; } - if (ioff + c.length > idata_limit) { - stbi_uc *p; - if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; - while (ioff + c.length > idata_limit) - idata_limit *= 2; - p = (stbi_uc *) realloc(z->idata, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); - z->idata = p; - } - if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); - ioff += c.length; - break; - } - - case PNG_TYPE('I','E','N','D'): { - stbi__uint32 raw_len; - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (scan != SCAN_load) return 1; - if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); - z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, 16384, (int *) &raw_len, !is_iphone); - if (z->expanded == NULL) return 0; // zlib should set error - free(z->idata); z->idata = NULL; - if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) - s->img_out_n = s->img_n+1; - else - s->img_out_n = s->img_n; - if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, depth, color, interlace)) return 0; - if (has_trans) - if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; - if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) - stbi__de_iphone(z); - if (pal_img_n) { - // pal_img_n == 3 or 4 - s->img_n = pal_img_n; // record the actual colors we had - s->img_out_n = pal_img_n; - if (req_comp >= 3) s->img_out_n = req_comp; - if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) - return 0; - } - free(z->expanded); z->expanded = NULL; - return 1; - } - - default: - // if critical, fail - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if ((c.type & (1 << 29)) == 0) { - #ifndef STBI_NO_FAILURE_STRINGS - // not threadsafe - static char invalid_chunk[] = "XXXX PNG chunk not known"; - invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); - invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); - invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); - invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); - #endif - return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); - } - stbi__skip(s, c.length); - break; - } - // end of PNG chunk, read and skip CRC - stbi__get32be(s); - } -} - -static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp) -{ - unsigned char *result=NULL; - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - if (stbi__parse_png_file(p, SCAN_load, req_comp)) { - result = p->out; - p->out = NULL; - if (req_comp && req_comp != p->s->img_out_n) { - result = stbi__convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - p->s->img_out_n = req_comp; - if (result == NULL) return result; - } - *x = p->s->img_x; - *y = p->s->img_y; - if (n) *n = p->s->img_out_n; - } - free(p->out); p->out = NULL; - free(p->expanded); p->expanded = NULL; - free(p->idata); p->idata = NULL; - - return result; -} - -static unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__png p; - p.s = s; - return stbi__do_png(&p, x,y,comp,req_comp); -} - -static int stbi__png_test(stbi__context *s) -{ - int r; - r = stbi__check_png_header(s); - stbi__rewind(s); - return r; -} - -static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) -{ - if (!stbi__parse_png_file(p, SCAN_header, 0)) { - stbi__rewind( p->s ); - return 0; - } - if (x) *x = p->s->img_x; - if (y) *y = p->s->img_y; - if (comp) *comp = p->s->img_n; - return 1; -} - -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__png p; - p.s = s; - return stbi__png_info_raw(&p, x, y, comp); -} - -// Microsoft/Windows BMP image -static int stbi__bmp_test_raw(stbi__context *s) -{ - int r; - int sz; - if (stbi__get8(s) != 'B') return 0; - if (stbi__get8(s) != 'M') return 0; - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - stbi__get32le(s); // discard data offset - sz = stbi__get32le(s); - r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); - return r; -} - -static int stbi__bmp_test(stbi__context *s) -{ - int r = stbi__bmp_test_raw(s); - stbi__rewind(s); - return r; -} - - -// returns 0..31 for the highest set bit -static int stbi__high_bit(unsigned int z) -{ - int n=0; - if (z == 0) return -1; - if (z >= 0x10000) n += 16, z >>= 16; - if (z >= 0x00100) n += 8, z >>= 8; - if (z >= 0x00010) n += 4, z >>= 4; - if (z >= 0x00004) n += 2, z >>= 2; - if (z >= 0x00002) n += 1, z >>= 1; - return n; -} - -static int stbi__bitcount(unsigned int a) -{ - a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 - a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 - a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits - a = (a + (a >> 8)); // max 16 per 8 bits - a = (a + (a >> 16)); // max 32 per 8 bits - return a & 0xff; -} - -static int stbi__shiftsigned(int v, int shift, int bits) -{ - int result; - int z=0; - - if (shift < 0) v <<= -shift; - else v >>= shift; - result = v; - - z = bits; - while (z < 8) { - result += v >> z; - z += bits; - } - return result; -} - -static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *out; - unsigned int mr=0,mg=0,mb=0,ma=0, fake_a=0; - stbi_uc pal[256][4]; - int psize=0,i,j,compress=0,width; - int bpp, flip_vertically, pad, target, offset, hsz; - if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - offset = stbi__get32le(s); - hsz = stbi__get32le(s); - if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); - if (hsz == 12) { - s->img_x = stbi__get16le(s); - s->img_y = stbi__get16le(s); - } else { - s->img_x = stbi__get32le(s); - s->img_y = stbi__get32le(s); - } - if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); - bpp = stbi__get16le(s); - if (bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); - flip_vertically = ((int) s->img_y) > 0; - s->img_y = abs((int) s->img_y); - if (hsz == 12) { - if (bpp < 24) - psize = (offset - 14 - 24) / 3; - } else { - compress = stbi__get32le(s); - if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); - stbi__get32le(s); // discard sizeof - stbi__get32le(s); // discard hres - stbi__get32le(s); // discard vres - stbi__get32le(s); // discard colorsused - stbi__get32le(s); // discard max important - if (hsz == 40 || hsz == 56) { - if (hsz == 56) { - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - } - if (bpp == 16 || bpp == 32) { - mr = mg = mb = 0; - if (compress == 0) { - if (bpp == 32) { - mr = 0xffu << 16; - mg = 0xffu << 8; - mb = 0xffu << 0; - ma = 0xffu << 24; - fake_a = 1; // @TODO: check for cases like alpha value is all 0 and switch it to 255 - STBI_NOTUSED(fake_a); - } else { - mr = 31u << 10; - mg = 31u << 5; - mb = 31u << 0; - } - } else if (compress == 3) { - mr = stbi__get32le(s); - mg = stbi__get32le(s); - mb = stbi__get32le(s); - // not documented, but generated by photoshop and handled by mspaint - if (mr == mg && mg == mb) { - // ?!?!? - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else { - STBI_ASSERT(hsz == 108 || hsz == 124); - mr = stbi__get32le(s); - mg = stbi__get32le(s); - mb = stbi__get32le(s); - ma = stbi__get32le(s); - stbi__get32le(s); // discard color space - for (i=0; i < 12; ++i) - stbi__get32le(s); // discard color space parameters - if (hsz == 124) { - stbi__get32le(s); // discard rendering intent - stbi__get32le(s); // discard offset of profile data - stbi__get32le(s); // discard size of profile data - stbi__get32le(s); // discard reserved - } - } - if (bpp < 16) - psize = (offset - 14 - hsz) >> 2; - } - s->img_n = ma ? 4 : 3; - if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 - target = req_comp; - else - target = s->img_n; // if they want monochrome, we'll post-convert - out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - if (bpp < 16) { - int z=0; - if (psize == 0 || psize > 256) { free(out); return stbi__errpuc("invalid", "Corrupt BMP"); } - for (i=0; i < psize; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - if (hsz != 12) stbi__get8(s); - pal[i][3] = 255; - } - stbi__skip(s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4)); - if (bpp == 4) width = (s->img_x + 1) >> 1; - else if (bpp == 8) width = s->img_x; - else { free(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } - pad = (-width)&3; - for (j=0; j < (int) s->img_y; ++j) { - for (i=0; i < (int) s->img_x; i += 2) { - int v=stbi__get8(s),v2=0; - if (bpp == 4) { - v2 = v & 15; - v >>= 4; - } - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - v = (bpp == 8) ? stbi__get8(s) : v2; - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - } - stbi__skip(s, pad); - } - } else { - int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; - int z = 0; - int easy=0; - stbi__skip(s, offset - 14 - hsz); - if (bpp == 24) width = 3 * s->img_x; - else if (bpp == 16) width = 2*s->img_x; - else /* bpp = 32 and pad = 0 */ width=0; - pad = (-width) & 3; - if (bpp == 24) { - easy = 1; - } else if (bpp == 32) { - if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) - easy = 2; - } - if (!easy) { - if (!mr || !mg || !mb) { free(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } - // right shift amt to put high bit in position #7 - rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); - gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); - bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); - ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); - } - for (j=0; j < (int) s->img_y; ++j) { - if (easy) { - for (i=0; i < (int) s->img_x; ++i) { - unsigned char a; - out[z+2] = stbi__get8(s); - out[z+1] = stbi__get8(s); - out[z+0] = stbi__get8(s); - z += 3; - a = (easy == 2 ? stbi__get8(s) : 255); - if (target == 4) out[z++] = a; - } - } else { - for (i=0; i < (int) s->img_x; ++i) { - stbi__uint32 v = (stbi__uint32) (bpp == 16 ? stbi__get16le(s) : stbi__get32le(s)); - int a; - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); - a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); - if (target == 4) out[z++] = STBI__BYTECAST(a); - } - } - stbi__skip(s, pad); - } - } - if (flip_vertically) { - stbi_uc t; - for (j=0; j < (int) s->img_y>>1; ++j) { - stbi_uc *p1 = out + j *s->img_x*target; - stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; - for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i], p1[i] = p2[i], p2[i] = t; - } - } - } - - if (req_comp && req_comp != target) { - out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - return out; -} - -// Targa Truevision - TGA -// by Jonathan Dummer - -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) -{ - int tga_w, tga_h, tga_comp; - int sz; - stbi__get8(s); // discard Offset - sz = stbi__get8(s); // color type - if( sz > 1 ) { - stbi__rewind(s); - return 0; // only RGB or indexed allowed - } - sz = stbi__get8(s); // image type - // only RGB or grey allowed, +/- RLE - if ((sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11)) return 0; - stbi__skip(s,9); - tga_w = stbi__get16le(s); - if( tga_w < 1 ) { - stbi__rewind(s); - return 0; // test width - } - tga_h = stbi__get16le(s); - if( tga_h < 1 ) { - stbi__rewind(s); - return 0; // test height - } - sz = stbi__get8(s); // bits per pixel - // only RGB or RGBA or grey allowed - if ((sz != 8) && (sz != 16) && (sz != 24) && (sz != 32)) { - stbi__rewind(s); - return 0; - } - tga_comp = sz; - if (x) *x = tga_w; - if (y) *y = tga_h; - if (comp) *comp = tga_comp / 8; - return 1; // seems to have passed everything -} - -static int stbi__tga_test(stbi__context *s) -{ - int res; - int sz; - stbi__get8(s); // discard Offset - sz = stbi__get8(s); // color type - if ( sz > 1 ) return 0; // only RGB or indexed allowed - sz = stbi__get8(s); // image type - if ( (sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11) ) return 0; // only RGB or grey allowed, +/- RLE - stbi__get16be(s); // discard palette start - stbi__get16be(s); // discard palette length - stbi__get8(s); // discard bits per palette color entry - stbi__get16be(s); // discard x origin - stbi__get16be(s); // discard y origin - if ( stbi__get16be(s) < 1 ) return 0; // test width - if ( stbi__get16be(s) < 1 ) return 0; // test height - sz = stbi__get8(s); // bits per pixel - if ( (sz != 8) && (sz != 16) && (sz != 24) && (sz != 32) ) - res = 0; - else - res = 1; - stbi__rewind(s); - return res; -} - -static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - // read in the TGA header stuff - int tga_offset = stbi__get8(s); - int tga_indexed = stbi__get8(s); - int tga_image_type = stbi__get8(s); - int tga_is_RLE = 0; - int tga_palette_start = stbi__get16le(s); - int tga_palette_len = stbi__get16le(s); - int tga_palette_bits = stbi__get8(s); - int tga_x_origin = stbi__get16le(s); - int tga_y_origin = stbi__get16le(s); - int tga_width = stbi__get16le(s); - int tga_height = stbi__get16le(s); - int tga_bits_per_pixel = stbi__get8(s); - int tga_comp = tga_bits_per_pixel / 8; - int tga_inverted = stbi__get8(s); - // image data - unsigned char *tga_data; - unsigned char *tga_palette = NULL; - int i, j; - unsigned char raw_data[4]; - int RLE_count = 0; - int RLE_repeating = 0; - int read_next_pixel = 1; - - // do a tiny bit of precessing - if ( tga_image_type >= 8 ) - { - tga_image_type -= 8; - tga_is_RLE = 1; - } - /* int tga_alpha_bits = tga_inverted & 15; */ - tga_inverted = 1 - ((tga_inverted >> 5) & 1); - - // error check - if ( //(tga_indexed) || - (tga_width < 1) || (tga_height < 1) || - (tga_image_type < 1) || (tga_image_type > 3) || - ((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) && - (tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32)) - ) - { - return NULL; // we don't report this as a bad TGA because we don't even know if it's TGA - } - - // If I'm paletted, then I'll use the number of bits from the palette - if ( tga_indexed ) - { - tga_comp = tga_palette_bits / 8; - } - - // tga info - *x = tga_width; - *y = tga_height; - if (comp) *comp = tga_comp; - - tga_data = (unsigned char*)stbi__malloc( tga_width * tga_height * tga_comp ); - if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); - - // skip to the data's starting position (offset usually = 0) - stbi__skip(s, tga_offset ); - - if ( !tga_indexed && !tga_is_RLE) { - for (i=0; i < tga_height; ++i) { - int y = tga_inverted ? tga_height -i - 1 : i; - stbi_uc *tga_row = tga_data + y*tga_width*tga_comp; - stbi__getn(s, tga_row, tga_width * tga_comp); - } - } else { - // do I need to load a palette? - if ( tga_indexed) - { - // any data to skip? (offset usually = 0) - stbi__skip(s, tga_palette_start ); - // load the palette - tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_palette_bits / 8 ); - if (!tga_palette) { - free(tga_data); - return stbi__errpuc("outofmem", "Out of memory"); - } - if (!stbi__getn(s, tga_palette, tga_palette_len * tga_palette_bits / 8 )) { - free(tga_data); - free(tga_palette); - return stbi__errpuc("bad palette", "Corrupt TGA"); - } - } - // load the data - for (i=0; i < tga_width * tga_height; ++i) - { - // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? - if ( tga_is_RLE ) - { - if ( RLE_count == 0 ) - { - // yep, get the next byte as a RLE command - int RLE_cmd = stbi__get8(s); - RLE_count = 1 + (RLE_cmd & 127); - RLE_repeating = RLE_cmd >> 7; - read_next_pixel = 1; - } else if ( !RLE_repeating ) - { - read_next_pixel = 1; - } - } else - { - read_next_pixel = 1; - } - // OK, if I need to read a pixel, do it now - if ( read_next_pixel ) - { - // load however much data we did have - if ( tga_indexed ) - { - // read in 1 byte, then perform the lookup - int pal_idx = stbi__get8(s); - if ( pal_idx >= tga_palette_len ) - { - // invalid index - pal_idx = 0; - } - pal_idx *= tga_bits_per_pixel / 8; - for (j = 0; j*8 < tga_bits_per_pixel; ++j) - { - raw_data[j] = tga_palette[pal_idx+j]; - } - } else - { - // read in the data raw - for (j = 0; j*8 < tga_bits_per_pixel; ++j) - { - raw_data[j] = stbi__get8(s); - } - } - // clear the reading flag for the next pixel - read_next_pixel = 0; - } // end of reading a pixel - - // copy data - for (j = 0; j < tga_comp; ++j) - tga_data[i*tga_comp+j] = raw_data[j]; - - // in case we're in RLE mode, keep counting down - --RLE_count; - } - // do I need to invert the image? - if ( tga_inverted ) - { - for (j = 0; j*2 < tga_height; ++j) - { - int index1 = j * tga_width * tga_comp; - int index2 = (tga_height - 1 - j) * tga_width * tga_comp; - for (i = tga_width * tga_comp; i > 0; --i) - { - unsigned char temp = tga_data[index1]; - tga_data[index1] = tga_data[index2]; - tga_data[index2] = temp; - ++index1; - ++index2; - } - } - } - // clear my palette, if I had one - if ( tga_palette != NULL ) - { - free( tga_palette ); - } - } - - // swap RGB - if (tga_comp >= 3) - { - unsigned char* tga_pixel = tga_data; - for (i=0; i < tga_width * tga_height; ++i) - { - unsigned char temp = tga_pixel[0]; - tga_pixel[0] = tga_pixel[2]; - tga_pixel[2] = temp; - tga_pixel += tga_comp; - } - } - - // convert to target component count - if (req_comp && req_comp != tga_comp) - tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); - - // the things I do to get rid of an error message, and yet keep - // Microsoft's C compilers happy... [8^( - tga_palette_start = tga_palette_len = tga_palette_bits = - tga_x_origin = tga_y_origin = 0; - // OK, done - return tga_data; -} - -// ************************************************************************************************* -// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB - -static int stbi__psd_test(stbi__context *s) -{ - int r = (stbi__get32be(s) == 0x38425053); - stbi__rewind(s); - return r; -} - -static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - int pixelCount; - int channelCount, compression; - int channel, i, count, len; - int w,h; - stbi_uc *out; - - // Check identifier - if (stbi__get32be(s) != 0x38425053) // "8BPS" - return stbi__errpuc("not PSD", "Corrupt PSD image"); - - // Check file type version. - if (stbi__get16be(s) != 1) - return stbi__errpuc("wrong version", "Unsupported version of PSD image"); - - // Skip 6 reserved bytes. - stbi__skip(s, 6 ); - - // Read the number of channels (R, G, B, A, etc). - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) - return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); - - // Read the rows and columns of the image. - h = stbi__get32be(s); - w = stbi__get32be(s); - - // Make sure the depth is 8 bits. - if (stbi__get16be(s) != 8) - return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 bit"); - - // Make sure the color mode is RGB. - // Valid options are: - // 0: Bitmap - // 1: Grayscale - // 2: Indexed color - // 3: RGB color - // 4: CMYK color - // 7: Multichannel - // 8: Duotone - // 9: Lab color - if (stbi__get16be(s) != 3) - return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); - - // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) - stbi__skip(s,stbi__get32be(s) ); - - // Skip the image resources. (resolution, pen tool paths, etc) - stbi__skip(s, stbi__get32be(s) ); - - // Skip the reserved data. - stbi__skip(s, stbi__get32be(s) ); - - // Find out if the data is compressed. - // Known values: - // 0: no compression - // 1: RLE compressed - compression = stbi__get16be(s); - if (compression > 1) - return stbi__errpuc("bad compression", "PSD has an unknown compression format"); - - // Create the destination image. - out = (stbi_uc *) stbi__malloc(4 * w*h); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - pixelCount = w*h; - - // Initialize the data to zero. - //memset( out, 0, pixelCount * 4 ); - - // Finally, the image data. - if (compression) { - // RLE as used by .PSD and .TIFF - // Loop until you get the number of unpacked bytes you are expecting: - // Read the next source byte into n. - // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. - // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. - // Else if n is 128, noop. - // Endloop - - // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, - // which we're going to just skip. - stbi__skip(s, h * channelCount * 2 ); - - // Read the RLE data by channel. - for (channel = 0; channel < 4; channel++) { - stbi_uc *p; - - p = out+channel; - if (channel >= channelCount) { - // Fill this channel with default data. - for (i = 0; i < pixelCount; i++) *p = (channel == 3 ? 255 : 0), p += 4; - } else { - // Read the RLE data. - count = 0; - while (count < pixelCount) { - len = stbi__get8(s); - if (len == 128) { - // No-op. - } else if (len < 128) { - // Copy next len+1 bytes literally. - len++; - count += len; - while (len) { - *p = stbi__get8(s); - p += 4; - len--; - } - } else if (len > 128) { - stbi_uc val; - // Next -len+1 bytes in the dest are replicated from next source byte. - // (Interpret len as a negative 8-bit int.) - len ^= 0x0FF; - len += 2; - val = stbi__get8(s); - count += len; - while (len) { - *p = val; - p += 4; - len--; - } - } - } - } - } - - } else { - // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) - // where each channel consists of an 8-bit value for each pixel in the image. - - // Read the data by channel. - for (channel = 0; channel < 4; channel++) { - stbi_uc *p; - - p = out + channel; - if (channel > channelCount) { - // Fill this channel with default data. - for (i = 0; i < pixelCount; i++) *p = channel == 3 ? 255 : 0, p += 4; - } else { - // Read the data. - for (i = 0; i < pixelCount; i++) - *p = stbi__get8(s), p += 4; - } - } - } - - if (req_comp && req_comp != 4) { - out = stbi__convert_format(out, 4, req_comp, w, h); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - if (comp) *comp = channelCount; - *y = h; - *x = w; - - return out; -} - -// ************************************************************************************************* -// Softimage PIC loader -// by Tom Seddon -// -// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format -// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ - -static int stbi__pic_is4(stbi__context *s,const char *str) -{ - int i; - for (i=0; i<4; ++i) - if (stbi__get8(s) != (stbi_uc)str[i]) - return 0; - - return 1; -} - -static int stbi__pic_test_core(stbi__context *s) -{ - int i; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) - return 0; - - for(i=0;i<84;++i) - stbi__get8(s); - - if (!stbi__pic_is4(s,"PICT")) - return 0; - - return 1; -} - -typedef struct -{ - stbi_uc size,type,channel; -} stbi__pic_packet; - -static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) -{ - int mask=0x80, i; - - for (i=0; i<4; ++i, mask>>=1) { - if (channel & mask) { - if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); - dest[i]=stbi__get8(s); - } - } - - return dest; -} - -static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) -{ - int mask=0x80,i; - - for (i=0;i<4; ++i, mask>>=1) - if (channel&mask) - dest[i]=src[i]; -} - -static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) -{ - int act_comp=0,num_packets=0,y,chained; - stbi__pic_packet packets[10]; - - // this will (should...) cater for even some bizarre stuff like having data - // for the same channel in multiple packets. - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return stbi__errpuc("bad format","too many packets"); - - packet = &packets[num_packets++]; - - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - - act_comp |= packet->channel; - - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); - if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? - - for(y=0; ytype) { - default: - return stbi__errpuc("bad format","packet has bad compression type"); - - case 0: {//uncompressed - int x; - - for(x=0;xchannel,dest)) - return 0; - break; - } - - case 1://Pure RLE - { - int left=width, i; - - while (left>0) { - stbi_uc count,value[4]; - - count=stbi__get8(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); - - if (count > left) - count = (stbi_uc) left; - - if (!stbi__readval(s,packet->channel,value)) return 0; - - for(i=0; ichannel,dest,value); - left -= count; - } - } - break; - - case 2: {//Mixed RLE - int left=width; - while (left>0) { - int count = stbi__get8(s), i; - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); - - if (count >= 128) { // Repeated - stbi_uc value[4]; - int i; - - if (count==128) - count = stbi__get16be(s); - else - count -= 127; - if (count > left) - return stbi__errpuc("bad file","scanline overrun"); - - if (!stbi__readval(s,packet->channel,value)) - return 0; - - for(i=0;ichannel,dest,value); - } else { // Raw - ++count; - if (count>left) return stbi__errpuc("bad file","scanline overrun"); - - for(i=0;ichannel,dest)) - return 0; - } - left-=count; - } - break; - } - } - } - } - - return result; -} - -static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp) -{ - stbi_uc *result; - int i, x,y; - - for (i=0; i<92; ++i) - stbi__get8(s); - - x = stbi__get16be(s); - y = stbi__get16be(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); - if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to decode"); - - stbi__get32be(s); //skip `ratio' - stbi__get16be(s); //skip `fields' - stbi__get16be(s); //skip `pad' - - // intermediate buffer is RGBA - result = (stbi_uc *) stbi__malloc(x*y*4); - memset(result, 0xff, x*y*4); - - if (!stbi__pic_load_core(s,x,y,comp, result)) { - free(result); - result=0; - } - *px = x; - *py = y; - if (req_comp == 0) req_comp = *comp; - result=stbi__convert_format(result,4,req_comp,x,y); - - return result; -} - -static int stbi__pic_test(stbi__context *s) -{ - int r = stbi__pic_test_core(s); - stbi__rewind(s); - return r; -} - -// ************************************************************************************************* -// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb -typedef struct -{ - stbi__int16 prefix; - stbi_uc first; - stbi_uc suffix; -} stbi__gif_lzw; - -typedef struct -{ - int w,h; - stbi_uc *out; // output buffer (always 4 components) - int flags, bgindex, ratio, transparent, eflags; - stbi_uc pal[256][4]; - stbi_uc lpal[256][4]; - stbi__gif_lzw codes[4096]; - stbi_uc *color_table; - int parse, step; - int lflags; - int start_x, start_y; - int max_x, max_y; - int cur_x, cur_y; - int line_size; -} stbi__gif; - -static int stbi__gif_test_raw(stbi__context *s) -{ - int sz; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; - sz = stbi__get8(s); - if (sz != '9' && sz != '7') return 0; - if (stbi__get8(s) != 'a') return 0; - return 1; -} - -static int stbi__gif_test(stbi__context *s) -{ - int r = stbi__gif_test_raw(s); - stbi__rewind(s); - return r; -} - -static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) -{ - int i; - for (i=0; i < num_entries; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - pal[i][3] = transp ? 0 : 255; - } -} - -static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) -{ - stbi_uc version; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') - return stbi__err("not GIF", "Corrupt GIF"); - - version = stbi__get8(s); - if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); - if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); - - stbi__g_failure_reason = ""; - g->w = stbi__get16le(s); - g->h = stbi__get16le(s); - g->flags = stbi__get8(s); - g->bgindex = stbi__get8(s); - g->ratio = stbi__get8(s); - g->transparent = -1; - - if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments - - if (is_info) return 1; - - if (g->flags & 0x80) - stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); - - return 1; -} - -static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__gif g; - if (!stbi__gif_header(s, &g, comp, 1)) { - stbi__rewind( s ); - return 0; - } - if (x) *x = g.w; - if (y) *y = g.h; - return 1; -} - -static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) -{ - stbi_uc *p, *c; - - // recurse to decode the prefixes, since the linked-list is backwards, - // and working backwards through an interleaved image would be nasty - if (g->codes[code].prefix >= 0) - stbi__out_gif_code(g, g->codes[code].prefix); - - if (g->cur_y >= g->max_y) return; - - p = &g->out[g->cur_x + g->cur_y]; - c = &g->color_table[g->codes[code].suffix * 4]; - - if (c[3] >= 128) { - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = c[3]; - } - g->cur_x += 4; - - if (g->cur_x >= g->max_x) { - g->cur_x = g->start_x; - g->cur_y += g->step; - - while (g->cur_y >= g->max_y && g->parse > 0) { - g->step = (1 << g->parse) * g->line_size; - g->cur_y = g->start_y + (g->step >> 1); - --g->parse; - } - } -} - -static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) -{ - stbi_uc lzw_cs; - stbi__int32 len, code; - stbi__uint32 first; - stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; - stbi__gif_lzw *p; - - lzw_cs = stbi__get8(s); - clear = 1 << lzw_cs; - first = 1; - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - bits = 0; - valid_bits = 0; - for (code = 0; code < clear; code++) { - g->codes[code].prefix = -1; - g->codes[code].first = (stbi_uc) code; - g->codes[code].suffix = (stbi_uc) code; - } - - // support no starting clear code - avail = clear+2; - oldcode = -1; - - len = 0; - for(;;) { - if (valid_bits < codesize) { - if (len == 0) { - len = stbi__get8(s); // start new block - if (len == 0) - return g->out; - } - --len; - bits |= (stbi__int32) stbi__get8(s) << valid_bits; - valid_bits += 8; - } else { - stbi__int32 code = bits & codemask; - bits >>= codesize; - valid_bits -= codesize; - // @OPTIMIZE: is there some way we can accelerate the non-clear path? - if (code == clear) { // clear code - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - avail = clear + 2; - oldcode = -1; - first = 0; - } else if (code == clear + 1) { // end of stream code - stbi__skip(s, len); - while ((len = stbi__get8(s)) > 0) - stbi__skip(s,len); - return g->out; - } else if (code <= avail) { - if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); - - if (oldcode >= 0) { - p = &g->codes[avail++]; - if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); - p->prefix = (stbi__int16) oldcode; - p->first = g->codes[oldcode].first; - p->suffix = (code == avail) ? p->first : g->codes[code].first; - } else if (code == avail) - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - - stbi__out_gif_code(g, (stbi__uint16) code); - - if ((avail & codemask) == 0 && avail <= 0x0FFF) { - codesize++; - codemask = (1 << codesize) - 1; - } - - oldcode = code; - } else { - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - } - } - } -} - -static void stbi__fill_gif_background(stbi__gif *g) -{ - int i; - stbi_uc *c = g->pal[g->bgindex]; - // @OPTIMIZE: write a dword at a time - for (i = 0; i < g->w * g->h * 4; i += 4) { - stbi_uc *p = &g->out[i]; - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = c[3]; - } -} - -// this function is designed to support animated gifs, although stb_image doesn't support it -static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) -{ - int i; - stbi_uc *old_out = 0; - - if (g->out == 0) { - if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header - g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); - if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); - stbi__fill_gif_background(g); - } else { - // animated-gif-only path - if (((g->eflags & 0x1C) >> 2) == 3) { - old_out = g->out; - g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); - if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); - memcpy(g->out, old_out, g->w*g->h*4); - } - } - - for (;;) { - switch (stbi__get8(s)) { - case 0x2C: /* Image Descriptor */ - { - stbi__int32 x, y, w, h; - stbi_uc *o; - - x = stbi__get16le(s); - y = stbi__get16le(s); - w = stbi__get16le(s); - h = stbi__get16le(s); - if (((x + w) > (g->w)) || ((y + h) > (g->h))) - return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); - - g->line_size = g->w * 4; - g->start_x = x * 4; - g->start_y = y * g->line_size; - g->max_x = g->start_x + w * 4; - g->max_y = g->start_y + h * g->line_size; - g->cur_x = g->start_x; - g->cur_y = g->start_y; - - g->lflags = stbi__get8(s); - - if (g->lflags & 0x40) { - g->step = 8 * g->line_size; // first interlaced spacing - g->parse = 3; - } else { - g->step = g->line_size; - g->parse = 0; - } - - if (g->lflags & 0x80) { - stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); - g->color_table = (stbi_uc *) g->lpal; - } else if (g->flags & 0x80) { - for (i=0; i < 256; ++i) // @OPTIMIZE: stbi__jpeg_reset only the previous transparent - g->pal[i][3] = 255; - if (g->transparent >= 0 && (g->eflags & 0x01)) - g->pal[g->transparent][3] = 0; - g->color_table = (stbi_uc *) g->pal; - } else - return stbi__errpuc("missing color table", "Corrupt GIF"); - - o = stbi__process_gif_raster(s, g); - if (o == NULL) return NULL; - - if (req_comp && req_comp != 4) - o = stbi__convert_format(o, 4, req_comp, g->w, g->h); - return o; - } - - case 0x21: // Comment Extension. - { - int len; - if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. - len = stbi__get8(s); - if (len == 4) { - g->eflags = stbi__get8(s); - stbi__get16le(s); // delay - g->transparent = stbi__get8(s); - } else { - stbi__skip(s, len); - break; - } - } - while ((len = stbi__get8(s)) != 0) - stbi__skip(s, len); - break; - } - - case 0x3B: // gif stream termination code - return (stbi_uc *) s; // using '1' causes warning on some compilers - - default: - return stbi__errpuc("unknown code", "Corrupt GIF"); - } - } -} - -static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi_uc *u = 0; - stbi__gif g; - memset(&g, 0, sizeof(g)); - - u = stbi__gif_load_next(s, &g, comp, req_comp); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - if (u) { - *x = g.w; - *y = g.h; - } - - return u; -} - -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) -{ - return stbi__gif_info_raw(s,x,y,comp); -} - - -// ************************************************************************************************* -// Radiance RGBE HDR loader -// originally by Nicolas Schulz -#ifndef STBI_NO_HDR -static int stbi__hdr_test_core(stbi__context *s) -{ - const char *signature = "#?RADIANCE\n"; - int i; - for (i=0; signature[i]; ++i) - if (stbi__get8(s) != signature[i]) - return 0; - return 1; -} - -static int stbi__hdr_test(stbi__context* s) -{ - int r = stbi__hdr_test_core(s); - stbi__rewind(s); - return r; -} - -#define STBI__HDR_BUFLEN 1024 -static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) -{ - int len=0; - char c = '\0'; - - c = (char) stbi__get8(z); - - while (!stbi__at_eof(z) && c != '\n') { - buffer[len++] = c; - if (len == STBI__HDR_BUFLEN-1) { - // flush to end of line - while (!stbi__at_eof(z) && stbi__get8(z) != '\n') - ; - break; - } - c = (char) stbi__get8(z); - } - - buffer[len] = 0; - return buffer; -} - -static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) -{ - if ( input[3] != 0 ) { - float f1; - // Exponent - f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); - if (req_comp <= 2) - output[0] = (input[0] + input[1] + input[2]) * f1 / 3; - else { - output[0] = input[0] * f1; - output[1] = input[1] * f1; - output[2] = input[2] * f1; - } - if (req_comp == 2) output[1] = 1; - if (req_comp == 4) output[3] = 1; - } else { - switch (req_comp) { - case 4: output[3] = 1; /* fallthrough */ - case 3: output[0] = output[1] = output[2] = 0; - break; - case 2: output[1] = 1; /* fallthrough */ - case 1: output[0] = 0; - break; - } - } -} - -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int width, height; - stbi_uc *scanline; - float *hdr_data; - int len; - unsigned char count, value; - int i, j, k, c1,c2, z; - - - // Check identifier - if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) - return stbi__errpf("not HDR", "Corrupt HDR image"); - - // Parse header - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); - - // Parse width and height - // can't use sscanf() if we're not using stdio! - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - height = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - width = (int) strtol(token, NULL, 10); - - *x = width; - *y = height; - - if (comp) *comp = 3; - if (req_comp == 0) req_comp = 3; - - // Read data - hdr_data = (float *) stbi__malloc(height * width * req_comp * sizeof(float)); - - // Load image data - // image data is stored as some number of sca - if ( width < 8 || width >= 32768) { - // Read flat data - for (j=0; j < height; ++j) { - for (i=0; i < width; ++i) { - stbi_uc rgbe[4]; - main_decode_loop: - stbi__getn(s, rgbe, 4); - stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); - } - } - } else { - // Read RLE-encoded data - scanline = NULL; - - for (j = 0; j < height; ++j) { - c1 = stbi__get8(s); - c2 = stbi__get8(s); - len = stbi__get8(s); - if (c1 != 2 || c2 != 2 || (len & 0x80)) { - // not run-length encoded, so we have to actually use THIS data as a decoded - // pixel (note this can't be a valid pixel--one of RGB must be >= 128) - stbi_uc rgbe[4]; - rgbe[0] = (stbi_uc) c1; - rgbe[1] = (stbi_uc) c2; - rgbe[2] = (stbi_uc) len; - rgbe[3] = (stbi_uc) stbi__get8(s); - stbi__hdr_convert(hdr_data, rgbe, req_comp); - i = 1; - j = 0; - free(scanline); - goto main_decode_loop; // yes, this makes no sense - } - len <<= 8; - len |= stbi__get8(s); - if (len != width) { free(hdr_data); free(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } - if (scanline == NULL) scanline = (stbi_uc *) stbi__malloc(width * 4); - - for (k = 0; k < 4; ++k) { - i = 0; - while (i < width) { - count = stbi__get8(s); - if (count > 128) { - // Run - value = stbi__get8(s); - count -= 128; - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = value; - } else { - // Dump - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = stbi__get8(s); - } - } - } - for (i=0; i < width; ++i) - stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); - } - free(scanline); - } - - return hdr_data; -} - -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - - if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) { - stbi__rewind( s ); - return 0; - } - - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) { - stbi__rewind( s ); - return 0; - } - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *y = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *x = (int) strtol(token, NULL, 10); - *comp = 3; - return 1; -} -#endif // STBI_NO_HDR - -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) -{ - int hsz; - if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') { - stbi__rewind( s ); - return 0; - } - stbi__skip(s,12); - hsz = stbi__get32le(s); - if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) { - stbi__rewind( s ); - return 0; - } - if (hsz == 12) { - *x = stbi__get16le(s); - *y = stbi__get16le(s); - } else { - *x = stbi__get32le(s); - *y = stbi__get32le(s); - } - if (stbi__get16le(s) != 1) { - stbi__rewind( s ); - return 0; - } - *comp = stbi__get16le(s) / 8; - return 1; -} - -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) -{ - int channelCount; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - *y = stbi__get32be(s); - *x = stbi__get32be(s); - if (stbi__get16be(s) != 8) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 3) { - stbi__rewind( s ); - return 0; - } - *comp = 4; - return 1; -} - -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) -{ - int act_comp=0,num_packets=0,chained; - stbi__pic_packet packets[10]; - - stbi__skip(s, 92); - - *x = stbi__get16be(s); - *y = stbi__get16be(s); - if (stbi__at_eof(s)) return 0; - if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { - stbi__rewind( s ); - return 0; - } - - stbi__skip(s, 8); - - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return 0; - - packet = &packets[num_packets++]; - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - act_comp |= packet->channel; - - if (stbi__at_eof(s)) { - stbi__rewind( s ); - return 0; - } - if (packet->size != 8) { - stbi__rewind( s ); - return 0; - } - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); - - return 1; -} - -static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) -{ - if (stbi__jpeg_info(s, x, y, comp)) - return 1; - if (stbi__png_info(s, x, y, comp)) - return 1; - if (stbi__gif_info(s, x, y, comp)) - return 1; - if (stbi__bmp_info(s, x, y, comp)) - return 1; - if (stbi__psd_info(s, x, y, comp)) - return 1; - if (stbi__pic_info(s, x, y, comp)) - return 1; - #ifndef STBI_NO_HDR - if (stbi__hdr_info(s, x, y, comp)) - return 1; - #endif - // test tga last because it's a crappy test! - if (stbi__tga_info(s, x, y, comp)) - return 1; - return stbi__err("unknown image type", "Image not of any known type, or corrupt"); -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_info_from_file(f, x, y, comp); - fclose(f); - return result; -} - -STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__info_main(&s,x,y,comp); - fseek(f,pos,SEEK_SET); - return r; -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__info_main(&s,x,y,comp); -} - -#endif // STB_IMAGE_IMPLEMENTATION - -/* - revision history: - 1.46 (2014-08-26) - fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG - 1.45 (2014-08-16) - fix MSVC-ARM internal compiler error by wrapping malloc - 1.44 (2014-08-07) - various warning fixes from Ronny Chevalier - 1.43 (2014-07-15) - fix MSVC-only compiler problem in code changed in 1.42 - 1.42 (2014-07-09) - don't define _CRT_SECURE_NO_WARNINGS (affects user code) - fixes to stbi__cleanup_jpeg path - added STBI_ASSERT to avoid requiring assert.h - 1.41 (2014-06-25) - fix search&replace from 1.36 that messed up comments/error messages - 1.40 (2014-06-22) - fix gcc struct-initialization warning - 1.39 (2014-06-15) - fix to TGA optimization when req_comp != number of components in TGA; - fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) - add support for BMP version 5 (more ignored fields) - 1.38 (2014-06-06) - suppress MSVC warnings on integer casts truncating values - fix accidental rename of 'skip' field of I/O - 1.37 (2014-06-04) - remove duplicate typedef - 1.36 (2014-06-03) - convert to header file single-file library - if de-iphone isn't set, load iphone images color-swapped instead of returning NULL - 1.35 (2014-05-27) - various warnings - fix broken STBI_SIMD path - fix bug where stbi_load_from_file no longer left file pointer in correct place - fix broken non-easy path for 32-bit BMP (possibly never used) - TGA optimization by Arseny Kapoulkine - 1.34 (unknown) - use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case - 1.33 (2011-07-14) - make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements - 1.32 (2011-07-13) - support for "info" function for all supported filetypes (SpartanJ) - 1.31 (2011-06-20) - a few more leak fixes, bug in PNG handling (SpartanJ) - 1.30 (2011-06-11) - added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) - removed deprecated format-specific test/load functions - removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway - error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) - fix inefficiency in decoding 32-bit BMP (David Woo) - 1.29 (2010-08-16) - various warning fixes from Aurelien Pocheville - 1.28 (2010-08-01) - fix bug in GIF palette transparency (SpartanJ) - 1.27 (2010-08-01) - cast-to-stbi_uc to fix warnings - 1.26 (2010-07-24) - fix bug in file buffering for PNG reported by SpartanJ - 1.25 (2010-07-17) - refix trans_data warning (Won Chun) - 1.24 (2010-07-12) - perf improvements reading from files on platforms with lock-heavy fgetc() - minor perf improvements for jpeg - deprecated type-specific functions so we'll get feedback if they're needed - attempt to fix trans_data warning (Won Chun) - 1.23 fixed bug in iPhone support - 1.22 (2010-07-10) - removed image *writing* support - stbi_info support from Jetro Lauha - GIF support from Jean-Marc Lienher - iPhone PNG-extensions from James Brown - warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) - 1.21 fix use of 'stbi_uc' in header (reported by jon blow) - 1.20 added support for Softimage PIC, by Tom Seddon - 1.19 bug in interlaced PNG corruption check (found by ryg) - 1.18 2008-08-02 - fix a threading bug (local mutable static) - 1.17 support interlaced PNG - 1.16 major bugfix - stbi__convert_format converted one too many pixels - 1.15 initialize some fields for thread safety - 1.14 fix threadsafe conversion bug - header-file-only version (#define STBI_HEADER_FILE_ONLY before including) - 1.13 threadsafe - 1.12 const qualifiers in the API - 1.11 Support installable IDCT, colorspace conversion routines - 1.10 Fixes for 64-bit (don't use "unsigned long") - optimized upsampling by Fabian "ryg" Giesen - 1.09 Fix format-conversion for PSD code (bad global variables!) - 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz - 1.07 attempt to fix C++ warning/errors again - 1.06 attempt to fix C++ warning/errors again - 1.05 fix TGA loading to return correct *comp and use good luminance calc - 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free - 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR - 1.02 support for (subset of) HDR files, float interface for preferred access to them - 1.01 fix bug: possible bug in handling right-side up bmps... not sure - fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all - 1.00 interface to zlib that skips zlib header - 0.99 correct handling of alpha in palette - 0.98 TGA loader by lonesock; dynamically add loaders (untested) - 0.97 jpeg errors on too large a file; also catch another malloc failure - 0.96 fix detection of invalid v value - particleman@mollyrocket forum - 0.95 during header scan, seek to markers in case of padding - 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same - 0.93 handle jpegtran output; verbose errors - 0.92 read 4,8,16,24,32-bit BMP files of several formats - 0.91 output 24-bit Windows 3.0 BMP files - 0.90 fix a few more warnings; bump version number to approach 1.0 - 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd - 0.60 fix compiling as c++ - 0.59 fix warnings: merge Dave Moore's -Wall fixes - 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian - 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available - 0.56 fix bug: zlib uncompressed mode len vs. nlen - 0.55 fix bug: restart_interval not initialized to 0 - 0.54 allow NULL for 'int *comp' - 0.53 fix bug in png 3->4; speedup png decoding - 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments - 0.51 obey req_comp requests, 1-component jpegs return as 1-component, - on 'test' only check type, not whether we support this variant - 0.50 first released version -*/ diff --git a/imgui.cpp b/imgui.cpp index 32c0ca0bd..eac091f87 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1,4 +1,4 @@ -// ImGui library v1.20 +// ImGui library v1.30 wip // See ImGui::ShowTestWindow() for sample code. // Read 'Programmer guide' below for notes on how to setup ImGui in your codebase. // Get latest version at https://github.com/ocornut/imgui @@ -251,7 +251,6 @@ - optimization/render: use indexed rendering to reduce vertex data cost (for remote/networked imgui) - optimization/render: move clip-rect to vertex data? would allow merging all commands - optimization/render: merge command-lists with same clip-rect into one even if they aren't sequential? (as long as in-between clip rectangle don't overlap)? - - optimization/render: font exported by bmfont is not tight fit on vertical axis, incur unneeded pixel-shading cost. - optimization: turn some the various stack vectors into statically-sized arrays - optimization: better clipping for multi-component widgets - optimization: specialize for height based clipping first (assume widgets never go up + height tests before width tests?) @@ -281,6 +280,24 @@ #pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. #endif +//------------------------------------------------------------------------- +// STB libraries implementation +//------------------------------------------------------------------------- + +#define STBRP_STATIC +#define STB_RECT_PACK_IMPLEMENTATION +#include "stb_rect_pack.h" + +#define STB_TRUETYPE_IMPLEMENTATION +#define STBTT_malloc(x,u) ((void)(u), ImGui::MemAlloc(x)) +#define STBTT_free(x,u) ((void)(u), ImGui::MemFree(x)) +#include "stb_truetype.h" + +struct ImGuiTextEditState; +#define STB_TEXTEDIT_STRING ImGuiTextEditState +#define STB_TEXTEDIT_CHARTYPE ImWchar +#include "stb_textedit.h" + //------------------------------------------------------------------------- // Forward Declarations //------------------------------------------------------------------------- @@ -787,11 +804,6 @@ struct ImGuiDrawContext } }; -struct ImGuiTextEditState; -#define STB_TEXTEDIT_STRING ImGuiTextEditState -#define STB_TEXTEDIT_CHARTYPE ImWchar -#include "stb_textedit.h" - // Internal state of the currently focused/edited text input box struct ImGuiTextEditState { @@ -841,7 +853,7 @@ struct ImGuiState ImGuiIO IO; ImGuiStyle Style; float FontSize; // == IO.FontGlobalScale * IO.Font->Scale * IO.Font->Info->FontSize. Vertical distance between two lines of text, aka == CalcTextSize(" ").y - ImVec2 FontTexUvForWhite; // == IO.Font->FontTexUvForWhite (cached copy) + ImVec2 FontTexUvWhitePixel; // == IO.Font->TexUvForWhite (cached copy) float Time; int FrameCount; @@ -1472,10 +1484,12 @@ void ImGui::NewFrame() { ImGuiState& g = GImGui; - // Check user inputs + // Check user data IM_ASSERT(g.IO.DeltaTime > 0.0f); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f); IM_ASSERT(g.IO.RenderDrawListsFn != NULL); // Must be implemented + IM_ASSERT(g.IO.Font); // Font not created + IM_ASSERT(g.IO.Font->IsLoaded()); // Font not loaded if (!g.Initialized) { @@ -1485,26 +1499,13 @@ void ImGui::NewFrame() IM_ASSERT(g.Settings.empty()); LoadSettings(); - if (!g.IO.Font) - { - // Default font - const void* fnt_data; - unsigned int fnt_size; - ImGui::GetDefaultFontData(&fnt_data, &fnt_size, NULL, NULL); - g.IO.Font = (ImFont*)ImGui::MemAlloc(sizeof(ImFont)); - new(g.IO.Font) ImFont(); - g.IO.Font->LoadFromMemory(fnt_data, fnt_size); - IM_ASSERT(g.IO.Font->IsLoaded()); // Font failed to load - g.IO.Font->DisplayOffset = ImVec2(0.0f, +1.0f); - } g.Initialized = true; } - IM_ASSERT(g.IO.Font && g.IO.Font->IsLoaded()); // Font not loaded IM_ASSERT(g.IO.Font->Scale > 0.0f); - g.FontSize = g.IO.FontGlobalScale * (float)g.IO.Font->Info->FontSize * g.IO.Font->Scale; - g.FontTexUvForWhite = g.IO.Font->TexUvForWhite; - g.IO.Font->FallbackGlyph = NULL; // Because subsequent FindGlyph may return the fallback itself. + g.FontSize = g.IO.FontGlobalScale * g.IO.Font->FontSize * g.IO.Font->Scale; + g.FontTexUvWhitePixel = g.IO.Font->TexUvWhitePixel; + g.IO.Font->FallbackGlyph = NULL; g.IO.Font->FallbackGlyph = g.IO.Font->FindGlyph(g.IO.Font->FallbackChar); g.Time += g.IO.DeltaTime; @@ -5865,7 +5866,7 @@ void ImDrawList::AddVtx(const ImVec2& pos, ImU32 col) { vtx_write->pos = pos; vtx_write->col = col; - vtx_write->uv = GImGui.FontTexUvForWhite; + vtx_write->uv = GImGui.FontTexUvWhitePixel; vtx_write++; } @@ -6099,121 +6100,295 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 ImFont::ImFont() { Scale = 1.0f; - DisplayOffset = ImVec2(0.0f,0.0f); - TexUvForWhite = ImVec2(0.0f,0.0f); + DisplayOffset = ImVec2(0.5f, 0.5f); FallbackChar = (ImWchar)'?'; - Data = NULL; - DataSize = 0; - DataOwned = false; - Info = NULL; - Common = NULL; - Glyphs = NULL; - GlyphsCount = 0; - Kerning = NULL; - KerningCount = 0; - FallbackGlyph = NULL; + TexPixels = NULL; + Clear(); +} + +ImFont::~ImFont() +{ + Clear(); } void ImFont::Clear() { - if (Data && DataOwned) - ImGui::MemFree(Data); - Data = NULL; - DataOwned = false; - Info = NULL; - Common = NULL; - Glyphs = NULL; - GlyphsCount = 0; - Filenames.clear(); + if (TexPixels) + ImGui::MemFree(TexPixels); + + DisplayOffset = ImVec2(0.5f, 0.5f); + FontSize = 0.0f; + Glyphs.clear(); IndexLookup.clear(); FallbackGlyph = NULL; + TexPixels = NULL; + TexWidth = TexHeight = 0; + TexExtraDataPos = ImVec2(0, 0); } -bool ImFont::LoadFromFile(const char* filename) +// Retrieve list of range (2 int per range, values are inclusive) +const ImWchar* ImFont::GetGlyphRangesDefault() { - IM_ASSERT(!IsLoaded()); // Call Clear() - - if (!ImLoadFileToMemory(filename, "rb", (void**)&Data, &DataSize)) - return false; - DataOwned = true; - return LoadFromMemory(Data, DataSize); -} - -bool ImFont::LoadFromMemory(const void* data, size_t data_size) -{ - IM_ASSERT(!IsLoaded()); // Call Clear() - - Data = (unsigned char*)data; - DataSize = data_size; - - // Parse data - if (DataSize < 4 || Data[0] != 'B' || Data[1] != 'M' || Data[2] != 'F' || Data[3] != 0x03) - return false; - for (const unsigned char* p = Data+4; p < Data + DataSize; ) + static const ImWchar ranges[] = { - const unsigned char block_type = *(unsigned char*)p; - p += sizeof(unsigned char); - ImU32 block_size; // use memcpy to read 4-byte because they may be unaligned. This seems to break when compiling for Emscripten. - memcpy(&block_size, p, sizeof(ImU32)); - p += sizeof(ImU32); + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0, + }; + return &ranges[0]; +} - switch (block_type) +const ImWchar* ImFont::GetGlyphRangesChinese() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3040, 0x309F, // Hiragana, Katakana + 0xFF00, 0xFFEF, // Half-width characters + 0x4e00, 0x9FAF, // CJK Ideograms + 0, + }; + return &ranges[0]; +} + +const ImWchar* ImFont::GetGlyphRangesJapanese() +{ + // Store the 1946 ideograms code points as successive offsets from the initial unicode codepoint 0x4E00. Each offset has an implicit +1. + // This encoding helps us reduce the source code size. + static const short offsets_from_0x4E00[] = + { + -1,0,1,3,0,0,0,0,1,0,5,1,1,0,7,4,6,10,0,1,9,9,7,1,3,19,1,10,7,1,0,1,0,5,1,0,6,4,2,6,0,0,12,6,8,0,3,5,0,1,0,9,0,0,8,1,1,3,4,5,13,0,0,8,2,17, + 4,3,1,1,9,6,0,0,0,2,1,3,2,22,1,9,11,1,13,1,3,12,0,5,9,2,0,6,12,5,3,12,4,1,2,16,1,1,4,6,5,3,0,6,13,15,5,12,8,14,0,0,6,15,3,6,0,18,8,1,6,14,1, + 5,4,12,24,3,13,12,10,24,0,0,0,1,0,1,1,2,9,10,2,2,0,0,3,3,1,0,3,8,0,3,2,4,4,1,6,11,10,14,6,15,3,4,15,1,0,0,5,2,2,0,0,1,6,5,5,6,0,3,6,5,0,0,1,0, + 11,2,2,8,4,7,0,10,0,1,2,17,19,3,0,2,5,0,6,2,4,4,6,1,1,11,2,0,3,1,2,1,2,10,7,6,3,16,0,8,24,0,0,3,1,1,3,0,1,6,0,0,0,2,0,1,5,15,0,1,0,0,2,11,19, + 1,4,19,7,6,5,1,0,0,0,0,5,1,0,1,9,0,0,5,0,2,0,1,0,3,0,11,3,0,2,0,0,0,0,0,9,3,6,4,12,0,14,0,0,29,10,8,0,14,37,13,0,31,16,19,0,8,30,1,20,8,3,48, + 21,1,0,12,0,10,44,34,42,54,11,18,82,0,2,1,2,12,1,0,6,2,17,2,12,7,0,7,17,4,2,6,24,23,8,23,39,2,16,23,1,0,5,1,2,15,14,5,6,2,11,0,8,6,2,2,2,14, + 20,4,15,3,4,11,10,10,2,5,2,1,30,2,1,0,0,22,5,5,0,3,1,5,4,1,0,0,2,2,21,1,5,1,2,16,2,1,3,4,0,8,4,0,0,5,14,11,2,16,1,13,1,7,0,22,15,3,1,22,7,14, + 22,19,11,24,18,46,10,20,64,45,3,2,0,4,5,0,1,4,25,1,0,0,2,10,0,0,0,1,0,1,2,0,0,9,1,2,0,0,0,2,5,2,1,1,5,5,8,1,1,1,5,1,4,9,1,3,0,1,0,1,1,2,0,0, + 2,0,1,8,22,8,1,0,0,0,0,4,2,1,0,9,8,5,0,9,1,30,24,2,6,4,39,0,14,5,16,6,26,179,0,2,1,1,0,0,0,5,2,9,6,0,2,5,16,7,5,1,1,0,2,4,4,7,15,13,14,0,0, + 3,0,1,0,0,0,2,1,6,4,5,1,4,9,0,3,1,8,0,0,10,5,0,43,0,2,6,8,4,0,2,0,0,9,6,0,9,3,1,6,20,14,6,1,4,0,7,2,3,0,2,0,5,0,3,1,0,3,9,7,0,3,4,0,4,9,1,6,0, + 9,0,0,2,3,10,9,28,3,6,2,4,1,2,32,4,1,18,2,0,3,1,5,30,10,0,2,2,2,0,7,9,8,11,10,11,7,2,13,7,5,10,0,3,40,2,0,1,6,12,0,4,5,1,5,11,11,21,4,8,3,7, + 8,8,33,5,23,0,0,19,8,8,2,3,0,6,1,1,1,5,1,27,4,2,5,0,3,5,6,3,1,0,3,1,12,5,3,3,2,0,7,7,2,1,0,4,0,1,1,2,0,10,10,6,2,5,9,7,5,15,15,21,6,11,5,20, + 4,3,5,5,2,5,0,2,1,0,1,7,28,0,9,0,5,12,5,5,18,30,0,12,3,3,21,16,25,32,9,3,14,11,24,5,66,9,1,2,0,5,9,1,5,1,8,0,8,3,3,0,1,15,1,4,8,1,2,7,0,7,2, + 8,3,7,5,3,7,10,2,1,0,0,2,25,0,6,4,0,10,0,4,2,4,1,12,5,38,4,0,4,1,10,5,9,4,0,14,4,2,5,18,20,21,1,3,0,5,0,7,0,3,7,1,3,1,1,8,1,0,0,0,3,2,5,2,11, + 6,0,13,1,3,9,1,12,0,16,6,2,1,0,2,1,12,6,13,11,2,0,28,1,7,8,14,13,8,13,0,2,0,5,4,8,10,2,37,42,19,6,6,7,4,14,11,18,14,80,7,6,0,4,72,12,36,27, + 7,7,0,14,17,19,164,27,0,5,10,7,3,13,6,14,0,2,2,5,3,0,6,13,0,0,10,29,0,4,0,3,13,0,3,1,6,51,1,5,28,2,0,8,0,20,2,4,0,25,2,10,13,10,0,16,4,0,1,0, + 2,1,7,0,1,8,11,0,0,1,2,7,2,23,11,6,6,4,16,2,2,2,0,22,9,3,3,5,2,0,15,16,21,2,9,20,15,15,5,3,9,1,0,0,1,7,7,5,4,2,2,2,38,24,14,0,0,15,5,6,24,14, + 5,5,11,0,21,12,0,3,8,4,11,1,8,0,11,27,7,2,4,9,21,59,0,1,39,3,60,62,3,0,12,11,0,3,30,11,0,13,88,4,15,5,28,13,1,4,48,17,17,4,28,32,46,0,16,0, + 18,11,1,8,6,38,11,2,6,11,38,2,0,45,3,11,2,7,8,4,30,14,17,2,1,1,65,18,12,16,4,2,45,123,12,56,33,1,4,3,4,7,0,0,0,3,2,0,16,4,2,4,2,0,7,4,5,2,26, + 2,25,6,11,6,1,16,2,6,17,77,15,3,35,0,1,0,5,1,0,38,16,6,3,12,3,3,3,0,9,3,1,3,5,2,9,0,18,0,25,1,3,32,1,72,46,6,2,7,1,3,14,17,0,28,1,40,13,0,20, + 15,40,6,38,24,12,43,1,1,9,0,12,6,0,6,2,4,19,3,7,1,48,0,9,5,0,5,6,9,6,10,15,2,11,19,3,9,2,0,1,10,1,27,8,1,3,6,1,14,0,26,0,27,16,3,4,9,6,2,23, + 9,10,5,25,2,1,6,1,1,48,15,9,15,14,3,4,26,60,29,13,37,21,1,6,4,0,2,11,22,23,16,16,2,2,1,3,0,5,1,6,4,0,0,4,0,0,8,3,0,2,5,0,7,1,7,3,13,2,4,10, + 3,0,2,31,0,18,3,0,12,10,4,1,0,7,5,7,0,5,4,12,2,22,10,4,2,15,2,8,9,0,23,2,197,51,3,1,1,4,13,4,3,21,4,19,3,10,5,40,0,4,1,1,10,4,1,27,34,7,21, + 2,17,2,9,6,4,2,3,0,4,2,7,8,2,5,1,15,21,3,4,4,2,2,17,22,1,5,22,4,26,7,0,32,1,11,42,15,4,1,2,5,0,19,3,1,8,6,0,10,1,9,2,13,30,8,2,24,17,19,1,4, + 4,25,13,0,10,16,11,39,18,8,5,30,82,1,6,8,18,77,11,13,20,75,11,112,78,33,3,0,0,60,17,84,9,1,1,12,30,10,49,5,32,158,178,5,5,6,3,3,1,3,1,4,7,6, + 19,31,21,0,2,9,5,6,27,4,9,8,1,76,18,12,1,4,0,3,3,6,3,12,2,8,30,16,2,25,1,5,5,4,3,0,6,10,2,3,1,0,5,1,19,3,0,8,1,5,2,6,0,0,0,19,1,2,0,5,1,2,5, + 1,3,7,0,4,12,7,3,10,22,0,9,5,1,0,2,20,1,1,3,23,30,3,9,9,1,4,191,14,3,15,6,8,50,0,1,0,0,4,0,0,1,0,2,4,2,0,2,3,0,2,0,2,2,8,7,0,1,1,1,3,3,17,11, + 91,1,9,3,2,13,4,24,15,41,3,13,3,1,20,4,125,29,30,1,0,4,12,2,21,4,5,5,19,11,0,13,11,86,2,18,0,7,1,8,8,2,2,22,1,2,6,5,2,0,1,2,8,0,2,0,5,2,1,0, + 2,10,2,0,5,9,2,1,2,0,1,0,4,0,0,10,2,5,3,0,6,1,0,1,4,4,33,3,13,17,3,18,6,4,7,1,5,78,0,4,1,13,7,1,8,1,0,35,27,15,3,0,0,0,1,11,5,41,38,15,22,6, + 14,14,2,1,11,6,20,63,5,8,27,7,11,2,2,40,58,23,50,54,56,293,8,8,1,5,1,14,0,1,12,37,89,8,8,8,2,10,6,0,0,0,4,5,2,1,0,1,1,2,7,0,3,3,0,4,6,0,3,2, + 19,3,8,0,0,0,4,4,16,0,4,1,5,1,3,0,3,4,6,2,17,10,10,31,6,4,3,6,10,126,7,3,2,2,0,9,0,0,5,20,13,0,15,0,6,0,2,5,8,64,50,3,2,12,2,9,0,0,11,8,20, + 109,2,18,23,0,0,9,61,3,0,28,41,77,27,19,17,81,5,2,14,5,83,57,252,14,154,263,14,20,8,13,6,57,39,38, + }; + static int ranges_unpacked = false; + static ImWchar ranges[6 + 1 + IM_ARRAYSIZE(offsets_from_0x4E00)*2] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x3040, 0x309F, // Hiragana, Katakana + 0xFF00, 0xFFEF, // Half-width characters + 0, + }; + if (!ranges_unpacked) + { + // Unpack + int codepoint = 0x4e00; + ImWchar* dst = &ranges[6]; + for (int n = 0; n < IM_ARRAYSIZE(offsets_from_0x4E00); n++, dst += 2) + dst[0] = dst[1] = (ImWchar)(codepoint += (offsets_from_0x4E00[n] + 1)); + dst[0] = 0; + ranges_unpacked = true; + } + return &ranges[0]; +} + +extern const unsigned int proggy_clean_ttf_compressed_size; +extern const unsigned int proggy_clean_ttf_compressed_data[9584/4]; +extern unsigned int stb_decompress_length(unsigned char *input); +extern unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length); + +// Load embedded ProggyClean.ttf at size 13 +bool ImFont::LoadDefault() +{ + unsigned int buf_compressed_size = (int)proggy_clean_ttf_compressed_size; + unsigned char* buf_compressed = (unsigned char*)proggy_clean_ttf_compressed_data; + const size_t buf_decompressed_size = stb_decompress_length(buf_compressed); + unsigned char* buf_decompressed = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size); + stb_decompress(buf_decompressed, buf_compressed, buf_compressed_size); + const bool ret = LoadFromMemoryTTF((void *)buf_decompressed, buf_decompressed_size, 13.0f, ImFont::GetGlyphRangesDefault(), 0); + ImGui::MemFree(buf_decompressed); + DisplayOffset.y += 1; + return ret; +} + +bool ImFont::LoadFromFileTTF(const char* filename, float size_pixels, const ImWchar* glyph_ranges, int font_no) +{ + void* data = NULL; + size_t data_size = 0; + if (!ImLoadFileToMemory(filename, "rb", (void**)&data, &data_size)) + return false; + const bool ret = LoadFromMemoryTTF(data, data_size, size_pixels, glyph_ranges, font_no); + ImGui::MemFree(data); + return ret; +} + +static int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } + +bool ImFont::LoadFromMemoryTTF(const void* data, size_t data_size, float size_pixels, const ImWchar* glyph_ranges, int font_no) +{ + Clear(); + if (!glyph_ranges) + glyph_ranges = GetGlyphRangesDefault(); + + // Initialize font information + stbtt_fontinfo ttf_info; + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)data, font_no); + IM_ASSERT(font_offset >= 0); + if (!stbtt_InitFont(&ttf_info, (unsigned char*)data, font_offset)) + return false; + + // Setup ranges + int glyph_count = 0; + int glyph_ranges_count = 0; + for (const ImWchar* p = glyph_ranges; p[0] && p[1]; p += 2) + { + glyph_count += p[1]; + glyph_ranges_count++; + } + + ImVector ranges; + ranges.resize(glyph_ranges_count); + for (size_t i = 0; i < ranges.size(); i++) + { + stbtt_pack_range& range = ranges[i]; + range.font_size = size_pixels; + range.first_unicode_char_in_range = glyph_ranges[i*2]; + range.num_chars_in_range = (glyph_ranges[i*2+1] - range.first_unicode_char_in_range) + 1; + range.chardata_for_range = (stbtt_packedchar*)ImGui::MemAlloc(range.num_chars_in_range * sizeof(stbtt_packedchar)); + } + + { + TexWidth = 512; + TexHeight = 0; + TexPixels = NULL; + const int max_tex_height = 1024*16; + stbtt_pack_context spc; + int ret = stbtt_PackBegin(&spc, NULL, TexWidth, max_tex_height, 0, 1, NULL); + IM_ASSERT(ret); + stbtt_PackSetOversampling(&spc, 1, 1); + + // Flag all characters as not packed + for (size_t i = 0; i < ranges.size(); ++i) + for (int j = 0; j < ranges[i].num_chars_in_range; ++j) + ranges[i].chardata_for_range[j].x0 = ranges[i].chardata_for_range[j].y0 = ranges[i].chardata_for_range[j].x1 = ranges[i].chardata_for_range[j].y1 = 0; + + // Pack our extra data rectangle first, so it will be on the upper-left corner of our texture (UV will have small values). + stbrp_rect extra_rect; + extra_rect.w = 16; + extra_rect.h = 16; + stbrp_pack_rects((stbrp_context*)spc.pack_info, &extra_rect, 1); + TexExtraDataPos = ImVec2(extra_rect.x, extra_rect.y); + + // Pack + stbrp_rect* rects = (stbrp_rect*)ImGui::MemAlloc(sizeof(*rects) * glyph_count); + IM_ASSERT(rects); + const int n = stbtt_PackFontRangesGatherRects(&spc, &ttf_info, ranges.begin(), ranges.size(), rects); + stbrp_pack_rects((stbrp_context*)spc.pack_info, rects, n); + + // Create texture + int tex_h = 0; + for (int i = 0; i < n; i++) + if (rects[i].was_packed) + tex_h = ImMax(tex_h, rects[i].y + rects[i].h); + TexHeight = ImUpperPowerOfTwo(tex_h); + TexPixels = (unsigned char*)ImGui::MemRealloc(TexPixels, TexWidth * TexHeight); + memset(TexPixels, 0, TexWidth * TexHeight); + + // Render characters + spc.pixels = TexPixels; + spc.height = TexHeight; + ret = stbtt_PackFontRangesRenderIntoRects(&spc, &ttf_info, ranges.begin(), ranges.size(), rects); + stbtt_PackEnd(&spc); + ImGui::MemFree(rects); + } + + // Setup glyphs for runtime + FontSize = size_pixels; + + const float font_scale = stbtt_ScaleForPixelHeight(&ttf_info, size_pixels); + int font_ascent, font_descent, font_line_gap; + stbtt_GetFontVMetrics(&ttf_info, &font_ascent, &font_descent, &font_line_gap); + + const float uv_scale_x = 1.0f / TexWidth; + const float uv_scale_y = 1.0f / TexHeight; + const int character_spacing_x = 1; + for (size_t i = 0; i < ranges.size(); i++) + { + stbtt_pack_range& range = ranges[i]; + for (int char_idx = 0; char_idx < range.num_chars_in_range; char_idx += 1) { - case 1: - IM_ASSERT(Info == NULL); - Info = (FntInfo*)p; - break; - case 2: - IM_ASSERT(Common == NULL); - Common = (FntCommon*)p; - break; - case 3: - for (const unsigned char* s = p; s < p+block_size && s < Data+DataSize; s = s + strlen((const char*)s) + 1) - Filenames.push_back((const char*)s); - break; - case 4: - IM_ASSERT(Glyphs == NULL && GlyphsCount == 0); - Glyphs = (FntGlyph*)p; - GlyphsCount = block_size / sizeof(FntGlyph); - break; - case 5: - IM_ASSERT(Kerning == NULL && KerningCount == 0); - Kerning = (FntKerning*)p; - KerningCount = block_size / sizeof(FntKerning); - break; - default: - break; + const int codepoint = range.first_unicode_char_in_range + char_idx; + const stbtt_packedchar& pc = range.chardata_for_range[char_idx]; + if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1) + continue; + + Glyphs.resize(Glyphs.size() + 1); + ImFont::Glyph& glyph = Glyphs.back(); + glyph.Codepoint = (ImWchar)codepoint; + glyph.Width = pc.x1 - pc.x0 + 1; + glyph.Height = pc.y1 - pc.y0 + 1; + glyph.XOffset = (signed short)(pc.xoff); + glyph.YOffset = (signed short)(pc.yoff) + (int)(font_ascent * font_scale); + glyph.XAdvance = (signed short)(pc.xadvance + character_spacing_x); // Bake spacing into XAdvance + glyph.U0 = ((float)pc.x0 - 0.5f) * uv_scale_x; + glyph.V0 = ((float)pc.y0 - 0.5f) * uv_scale_y; + glyph.U1 = ((float)pc.x0 - 0.5f + glyph.Width) * uv_scale_x; + glyph.V1 = ((float)pc.y0 - 0.5f + glyph.Height) * uv_scale_y; } - p += block_size; } BuildLookupTable(); + + // Cleanup temporary + for (size_t i = 0; i < ranges.size(); i++) + ImGui::MemFree(ranges[i].chardata_for_range); + + // Draw white pixel and make UV points to it + TexPixels[0] = TexPixels[1] = TexPixels[TexWidth+0] = TexPixels[TexWidth+1] = 0xFF; + TexUvWhitePixel = ImVec2(TexExtraDataPos.x + 0.5f / TexWidth, TexExtraDataPos.y + 0.5f / TexHeight); + return true; } void ImFont::BuildLookupTable() { - ImU32 max_c = 0; - for (size_t i = 0; i != GlyphsCount; i++) - if (max_c < Glyphs[i].Id) - max_c = Glyphs[i].Id; + int max_codepoint = 0; + for (size_t i = 0; i != Glyphs.size(); i++) + max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); IndexLookup.clear(); - IndexLookup.resize(max_c + 1); + IndexLookup.resize((size_t)max_codepoint + 1); for (size_t i = 0; i < IndexLookup.size(); i++) IndexLookup[i] = -1; - for (size_t i = 0; i < GlyphsCount; i++) - IndexLookup[Glyphs[i].Id] = (int)i; + for (size_t i = 0; i < Glyphs.size(); i++) + IndexLookup[(int)Glyphs[i].Codepoint] = (int)i; } -const ImFont::FntGlyph* ImFont::FindGlyph(unsigned short c) const +const ImFont::Glyph* ImFont::FindGlyph(unsigned short c) const { if (c < (int)IndexLookup.size()) { const int i = IndexLookup[c]; - if (i >= 0 && i < (int)GlyphsCount) - return &Glyphs[i]; + return &Glyphs[i]; } return FallbackGlyph; } @@ -6388,17 +6563,10 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c // List of hardcoded separators: .,;!?'" // Skip extra blanks after a line returns (that includes not counting them in width computation) - // e.g. "Hello world" - // --> - // "Hello" - // "world" + // e.g. "Hello world" --> "Hello" "World" // Cut words that cannot possibly fit within one line. - // e.g.: "The tropical fish" with ~5 characters worth of width - // --> - // "The tr" - // "opical" - // "fish" + // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" float line_width = 0.0f; float word_width = 0.0f; @@ -6426,13 +6594,13 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c float char_width = 0.0f; if (c == '\t') { - if (const FntGlyph* glyph = FindGlyph((unsigned short)' ')) - char_width = (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale; + if (const Glyph* glyph = FindGlyph((unsigned short)' ')) + char_width = (glyph->XAdvance * 4) * scale; } else { - if (const FntGlyph* glyph = FindGlyph((unsigned short)c)) - char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale; + if (const Glyph* glyph = FindGlyph((unsigned short)c)) + char_width = glyph->XAdvance * scale; } if (c == ' ' || c == '\t') @@ -6483,8 +6651,8 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons if (!text_end) text_end = text_begin + strlen(text_begin); // FIXME-OPT - const float scale = size / (float)Info->FontSize; - const float line_height = (float)Info->FontSize * scale; + const float scale = size / FontSize; + const float line_height = FontSize * scale; ImVec2 text_size = ImVec2(0,0); float line_width = 0.0f; @@ -6541,12 +6709,12 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons if (c == '\t') { // FIXME: Better TAB handling - if (const FntGlyph* glyph = FindGlyph((unsigned short)' ')) - char_width = (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale; + if (const Glyph* glyph = FindGlyph((unsigned short)' ')) + char_width = (glyph->XAdvance * 4) * scale; } - else if (const FntGlyph* glyph = FindGlyph((unsigned short)c)) + else if (const Glyph* glyph = FindGlyph((unsigned short)c)) { - char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale; + char_width = glyph->XAdvance * scale; } if (line_width + char_width >= max_width) @@ -6573,8 +6741,8 @@ ImVec2 ImFont::CalcTextSizeW(float size, float max_width, const ImWchar* text_be if (!text_end) text_end = text_begin + ImStrlenW(text_begin); - const float scale = size / (float)Info->FontSize; - const float line_height = (float)Info->FontSize * scale; + const float scale = size / FontSize; + const float line_height = FontSize * scale; ImVec2 text_size = ImVec2(0,0); float line_width = 0.0f; @@ -6597,13 +6765,13 @@ ImVec2 ImFont::CalcTextSizeW(float size, float max_width, const ImWchar* text_be if (c == '\t') { // FIXME: Better TAB handling - if (const FntGlyph* glyph = FindGlyph((unsigned short)' ')) - char_width = (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale; + if (const Glyph* glyph = FindGlyph((unsigned short)' ')) + char_width = (glyph->XAdvance * 4) * scale; } else { - if (const FntGlyph* glyph = FindGlyph((unsigned short)c)) - char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale; + if (const Glyph* glyph = FindGlyph((unsigned short)c)) + char_width = glyph->XAdvance * scale; } if (line_width + char_width >= max_width) @@ -6630,11 +6798,8 @@ void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_re if (!text_end) text_end = text_begin + strlen(text_begin); - const float scale = size / (float)Info->FontSize; - const float line_height = (float)Info->FontSize * scale; - const float tex_scale_x = 1.0f / (float)Common->ScaleW; - const float tex_scale_y = 1.0f / (float)Common->ScaleH; - const float outline = (float)Info->Outline; + const float scale = size / FontSize; + const float line_height = FontSize * scale; // Align to be pixel perfect pos.x = (float)(int)pos.x + DisplayOffset.x; @@ -6692,46 +6857,46 @@ void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_re if (c == '\t') { // FIXME: Better TAB handling - if (const FntGlyph* glyph = FindGlyph((unsigned short)' ')) - char_width += (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale; + if (const Glyph* glyph = FindGlyph((unsigned short)' ')) + char_width += (glyph->XAdvance * 4) * scale; } - else if (const FntGlyph* glyph = FindGlyph((unsigned short)c)) + else if (const Glyph* glyph = FindGlyph((unsigned short)c)) { - char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale; + char_width = glyph->XAdvance * scale; if (c != ' ') { // Clipping on Y is more likely - const float y1 = (float)(y + (glyph->YOffset + outline*2) * scale); + const float y1 = (float)(y + glyph->YOffset * scale); const float y2 = (float)(y1 + glyph->Height * scale); if (y1 <= clip_rect.w && y2 >= clip_rect.y) { - const float x1 = (float)(x + (glyph->XOffset + outline) * scale); + const float x1 = (float)(x + glyph->XOffset * scale); const float x2 = (float)(x1 + glyph->Width * scale); if (x1 <= clip_rect.z && x2 >= clip_rect.x) { // Render a character - const float s1 = (glyph->X) * tex_scale_x; - const float t1 = (glyph->Y) * tex_scale_y; - const float s2 = (glyph->X + glyph->Width) * tex_scale_x; - const float t2 = (glyph->Y + glyph->Height) * tex_scale_y; + const float u0 = glyph->U0; + const float v0 = glyph->V0; + const float u1 = glyph->U1; + const float v1 = glyph->V1; out_vertices[0].pos = ImVec2(x1, y1); - out_vertices[0].uv = ImVec2(s1, t1); + out_vertices[0].uv = ImVec2(u0, v0); out_vertices[0].col = col; out_vertices[1].pos = ImVec2(x2, y1); - out_vertices[1].uv = ImVec2(s2, t1); + out_vertices[1].uv = ImVec2(u1, v0); out_vertices[1].col = col; out_vertices[2].pos = ImVec2(x2, y2); - out_vertices[2].uv = ImVec2(s2, t2); + out_vertices[2].uv = ImVec2(u1, v1); out_vertices[2].col = col; out_vertices[3] = out_vertices[0]; out_vertices[4] = out_vertices[2]; out_vertices[5].pos = ImVec2(x1, y2); - out_vertices[5].uv = ImVec2(s1, t2); + out_vertices[5].uv = ImVec2(u0, v1); out_vertices[5].col = col; out_vertices += 6; @@ -6932,19 +7097,19 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::TreePop(); } - /* // Font scaling options // Note that those are not actually part of the style. if (ImGui::TreeNode("Font")) { static float window_scale = 1.0f; + ImFont* font = ImGui::GetIO().Font; + ImGui::Text("Font Size: %.2f", font->FontSize); ImGui::SliderFloat("window scale", &window_scale, 0.3f, 2.0f, "%.1f"); // scale only this window - ImGui::SliderFloat("font scale", &ImGui::GetIO().Font->Scale, 0.3f, 2.0f, "%.1f"); // scale only this font + ImGui::SliderFloat("font scale", &font->Scale, 0.3f, 2.0f, "%.1f"); // scale only this font ImGui::SliderFloat("global scale", &ImGui::GetIO().FontGlobalScale, 0.3f, 2.0f, "%.1f"); // scale everything ImGui::SetWindowFontScale(window_scale); ImGui::TreePop(); } - */ ImGui::PopItemWidth(); } @@ -7843,156 +8008,332 @@ int main(int argc, char** argv) return 1; } */ + //----------------------------------------------------------------------------- -static const unsigned int proggy_clean_13_png_size = 1557; -static const unsigned int proggy_clean_13_png_data[1560/4] = -{ - 0x474e5089, 0x0a1a0a0d, 0x0d000000, 0x52444849, 0x00010000, 0x80000000, 0x00000308, 0x476bd300, 0x00000038, 0x544c5006, 0x00000045, 0xa5ffffff, - 0x00dd9fd9, 0x74010000, 0x00534e52, 0x66d8e640, 0xbd050000, 0x54414449, 0x9bed5e78, 0x30e36e51, 0xeef5440c, 0x31fde97f, 0x584ec0f0, 0x681ace39, - 0xca120e6b, 0x1c5a28a6, 0xc5d98a89, 0x1a3d602e, 0x323c0043, 0xf6bc9e68, 0xbe3ad62c, 0x3d60260f, 0x82d60096, 0xe0bfc707, 0xfb9bf1d1, 0xbf0267ac, - 0x1600260f, 0x061229c0, 0x0000c183, 0x37162c58, 0xdfa088fc, 0xde7d5704, 0x77fcbb80, 0x48e5c3f1, 0x73d8b8f8, 0xc4af7802, 0x1ca111ad, 0x0001ed7a, - 0x76eda3ef, 0xb78d3e00, 0x801c7203, 0x0215c0b1, 0x0410b044, 0xa85100d4, 0x07627ec7, 0x0cf83fa8, 0x94001a22, 0xf87347f1, 0xdcb5cfc1, 0x1c3880cc, - 0xd4e034ca, 0xfa928d9d, 0xb0167e31, 0x325cc570, 0x4bbd584b, 0xbd4e6574, 0x70bae084, 0xf0c0008a, 0x3f601ddb, 0x0bba506a, 0xa58a0082, 0x5b46946e, - 0x720a4ccd, 0xdfaaed39, 0x25dc8042, 0x7ee403f4, 0x2ad69cc9, 0x6c4b3009, 0x429037ed, 0x0293f875, 0x1a69dced, 0xab120198, 0x61c01d88, 0xcf2e43dc, - 0xfc3c00ef, 0xc049a270, 0xdbbea582, 0x0d592601, 0xc3c9a8dd, 0x5013d143, 0x19a47bbb, 0xf89253dd, 0x0a9901dc, 0x38900ecd, 0xb2dec9d7, 0xc2b91230, - 0xb8e0106f, 0x976404cb, 0x5d83c3f3, 0x6e8086fd, 0x5c9ab007, 0xf50354f6, 0xe7e72002, 0x4bc870ca, 0xab49736f, 0xc137c6e0, 0xa9aa6ff3, 0xbff84f2f, - 0x673e6e20, 0xf6e3c7e0, 0x618fe05a, 0x39ca2a00, 0x93ca03b4, 0x3a9d2728, 0xbbebba41, 0xce0e3681, 0x6e29ec05, 0x111eca83, 0xfdfe7ec1, 0xa7c8a75b, - 0xac6bc3ab, 0x72a5bc25, 0x9f612c1c, 0x378ec05e, 0x7202b157, 0x789e5a82, 0x5256bc0e, 0xcb900996, 0x10721105, 0x00823ce0, 0x69ab59fb, 0x39c72084, - 0xf5e37b25, 0xd1794700, 0x538d0637, 0x9a2bff4f, 0xce0d43a4, 0xa6da7ed2, 0xd7095132, 0xf5ad6232, 0x9aaa8e9c, 0xd8d1d3ed, 0x058940a1, 0x21f00d64, - 0x89a5c9de, 0x021b3f24, 0x77a97aac, 0x714be65a, 0x5e2d57ae, 0x27e3610f, 0x28809288, 0x36b9559f, 0xd00e347a, 0x0094e385, 0x565d034d, 0x7f52d5f2, - 0x9aea81de, 0x5e804909, 0x010d7f0a, 0x8f0d3fb1, 0xbbce23bc, 0x375e85ac, 0x01fa03b9, 0xc0526c3a, 0xf7866870, 0x9d46d804, 0x158ebf64, 0x7bd534c5, - 0xd80cf202, 0x410ee80f, 0x79419915, 0x74a844ae, 0x94119881, 0xcbbcc0fc, 0xa263d471, 0x013d0269, 0x67f6a0f8, 0x3e4474d0, 0xd1e50cb5, 0x56fd0e60, - 0xc4c0fd4c, 0x940629ff, 0xe18a7a16, 0xcca0330f, 0xb8ed50b7, 0x6935778b, 0x3735c791, 0x3909eb94, 0x0be36620, 0x0ac0d7aa, 0xefe942c9, 0xf0092727, - 0x5c020ee2, 0x0246da53, 0xa24be8bc, 0xa891ab94, 0xd012c7e2, 0x9c115954, 0xde0dac8e, 0x555dc022, 0x59e84f77, 0xbed2cf80, 0xe9af2cda, 0x4b600716, - 0x8955bd80, 0x7098c3f3, 0x25a8466a, 0x4ddbf26a, 0x5f554753, 0xf4890f28, 0x886a27ab, 0x54a00413, 0x0a157ca9, 0x52909a80, 0x7122a312, 0x0024a75c, - 0xe6d72935, 0xecde29cf, 0x025de009, 0x7995a6aa, 0x4a180491, 0x013df0d8, 0xe009edba, 0xd40019dc, 0x45b36b2a, 0x0122eb0d, 0x6e80a79f, 0x746590f5, - 0xd1a6dd49, 0xc05954b6, 0x83d4b957, 0xa00fe5b1, 0x59695ad7, 0xcff8433d, 0x44a0f340, 0xdd226c73, 0x5537f08c, 0xe1e89c32, 0x431056af, 0x233eb000, - 0x60773f40, 0xed7e490a, 0xc160091f, 0x12829db5, 0x43fbe6cf, 0x0a6b26c2, 0xd5f0f35a, 0xfc09fda8, 0x73525f8c, 0x2ea38cf9, 0x32bc410b, 0x94a60a22, - 0x1f62a42b, 0x5f290034, 0x07beaa91, 0x1e8ccb40, 0x17d6b0f9, 0xa2a017c9, 0x4c79a610, 0xa1de6525, 0xe975029f, 0xe063585f, 0x6246cfbb, 0x04acad44, - 0xe6a05138, 0xd03d8434, 0xc9950013, 0x5d4c809e, 0xfd26932d, 0x739213ac, 0xe260d8ef, 0xe4164617, 0x16fc60aa, 0x1d0b21e7, 0x445004b4, 0x13fd1b59, - 0x56b0f804, 0xaa936a3a, 0x335459c1, 0xb37f8caa, 0x06b68e03, 0x14d5eb01, 0x8300c78c, 0x9674792a, 0x20ba791b, 0x4d88024d, 0xef747354, 0x451e673e, - 0xc4dafc9a, 0xe53b9cd1, 0x32b4011a, 0x3d702c0f, 0x09bc0b40, 0x220d277d, 0x47eb7809, 0x8a946500, 0x7a28c4bd, 0x96e00f99, 0xc04365da, 0x05edcf46, - 0x7dee2c27, 0xe6020b7f, 0x159ecedf, 0xcbdb00ff, 0x516bb9e3, 0xd0716161, 0xeba75956, 0xf17fc22b, 0x5c578beb, 0xfe474a09, 0xc1750a87, 0xe384c189, - 0x5df54e26, 0xa6f76b79, 0xd4b172be, 0x3e8d5ceb, 0x832d90ec, 0x180368e7, 0x354c724d, 0x1a8b1412, 0x8de07be9, 0xaf009efe, 0x4616c621, 0x2860eb01, - 0x244f1404, 0xc3de724b, 0x6497a802, 0xab2f4419, 0x4e02910d, 0xe3ecf410, 0x7a6404a8, 0x8c72b112, 0xde5bc706, 0xd4f8ffe9, 0x50176344, 0x7b49fe7d, - 0x02c1d88c, 0x25634a40, 0x194804f7, 0x03b76d84, 0x392bde58, 0xdeebad27, 0xc160c021, 0xa97a72db, 0xa8040b83, 0x78804f3e, 0x046b9433, 0x178cc824, - 0x62800897, 0x7010370b, 0x21cfe7e4, 0x8053ec40, 0xf9d60526, 0xae9d353f, 0x069b40c7, 0x80496f14, 0x57e682b3, 0x6e0273e0, 0x974e2e28, 0x60ab7c3d, - 0x2025ba33, 0x507b3a8c, 0x12b70173, 0xd095c400, 0xee012d96, 0x6e194c9a, 0xe5933f89, 0x43b70102, 0xf30306aa, 0xc5802189, 0x53c077c3, 0x86029009, - 0xa0c1e780, 0xa4c04c1f, 0x93dbd580, 0xf8149809, 0x06021893, 0x3060c183, 0x83060c18, 0x183060c1, 0xc183060c, 0x0c183060, 0x60c18306, 0xfe0c1830, - 0x0cb69501, 0x7a40d9df, 0x000000dd, 0x4e454900, 0x6042ae44, 0x00000082, -}; +// Decompressor from stb.h (public domain) by Sean Barrett +// https://github.com/nothings/stb/blob/master/stb.h -static const unsigned int proggy_clean_13_fnt_size = 4647; -static const unsigned int proggy_clean_13_fnt_data[4648/4] = +static unsigned int stb_decompress_length(unsigned char *input) { - 0x03464d42, 0x00001a01, 0x40000d00, 0x01006400, 0x00000000, 0x50000101, 0x67676f72, 0x656c4379, 0x02006e61, 0x0000000f, 0x000a000d, 0x00800100, - 0x01000001, 0x03000000, 0x00000016, 0x676f7270, 0x635f7967, 0x6e61656c, 0x5f33315f, 0x6e702e30, 0xd0040067, 0x00000011, 0x2e000000, 0x07000e00, - 0x00000d00, 0x07000000, 0x010f0000, 0x36000000, 0x05003800, 0x01000d00, 0x07000000, 0x020f0000, 0x86000000, 0x07000e00, 0x00000d00, 0x07000000, - 0x030f0000, 0x07000000, 0x06001c00, 0x01000d00, 0x07000000, 0x040f0000, 0x15000000, 0x06001c00, 0x01000d00, 0x07000000, 0x050f0000, 0x23000000, - 0x06001c00, 0x01000d00, 0x07000000, 0x060f0000, 0x31000000, 0x06001c00, 0x01000d00, 0x07000000, 0x070f0000, 0xfc000000, 0x03003800, 0x02000d00, - 0x07000000, 0x080f0000, 0x54000000, 0x05003800, 0x01000d00, 0x07000000, 0x090f0000, 0x4d000000, 0x06001c00, 0x01000d00, 0x07000000, 0x0a0f0000, - 0xa8000000, 0x06001c00, 0x01000d00, 0x07000000, 0x0b0f0000, 0x6a000000, 0x04004600, 0x00000d00, 0x07000000, 0x0c0f0000, 0x74000000, 0x04004600, - 0x00000d00, 0x07000000, 0x0d0f0000, 0x88000000, 0x04004600, 0x03000d00, 0x07000000, 0x0e0f0000, 0x65000000, 0x04004600, 0x03000d00, 0x07000000, - 0x0f0f0000, 0x36000000, 0x07000e00, 0x00000d00, 0x07000000, 0x100f0000, 0x5a000000, 0x05003800, 0x00000d00, 0x07000000, 0x110f0000, 0x60000000, - 0x05003800, 0x00000d00, 0x07000000, 0x120f0000, 0xe4000000, 0x03004600, 0x01000d00, 0x07000000, 0x130f0000, 0xe0000000, 0x03004600, 0x01000d00, - 0x07000000, 0x140f0000, 0x66000000, 0x05003800, 0x00000d00, 0x07000000, 0x150f0000, 0x6c000000, 0x05003800, 0x00000d00, 0x07000000, 0x160f0000, - 0x72000000, 0x05003800, 0x00000d00, 0x07000000, 0x170f0000, 0xd8000000, 0x03004600, 0x00000d00, 0x07000000, 0x180f0000, 0xcc000000, 0x03004600, - 0x01000d00, 0x07000000, 0x190f0000, 0xc8000000, 0x03004600, 0x02000d00, 0x07000000, 0x1a0f0000, 0x78000000, 0x05003800, 0x00000d00, 0x07000000, - 0x1b0f0000, 0x84000000, 0x05003800, 0x00000d00, 0x07000000, 0x1c0f0000, 0x00000000, 0x15000000, 0xf9000d00, 0x070000ff, 0x1d0f0000, 0xb0000000, - 0x15000000, 0xf9000d00, 0x070000ff, 0x1e0f0000, 0x2c000000, 0x15000000, 0xf9000d00, 0x070000ff, 0x200f0000, 0x9a000000, 0x15000000, 0xf9000d00, - 0x070000ff, 0x210f0000, 0x0c000000, 0x01005400, 0x03000d00, 0x07000000, 0x220f0000, 0xbc000000, 0x03004600, 0x02000d00, 0x07000000, 0x230f0000, - 0x4e000000, 0x07000e00, 0x00000d00, 0x07000000, 0x240f0000, 0x8a000000, 0x05003800, 0x01000d00, 0x07000000, 0x250f0000, 0xa6000000, 0x07000e00, - 0x00000d00, 0x07000000, 0x260f0000, 0xf4000000, 0x06000e00, 0x01000d00, 0x07000000, 0x270f0000, 0x06000000, 0x01005400, 0x03000d00, 0x07000000, - 0x280f0000, 0xb8000000, 0x03004600, 0x02000d00, 0x07000000, 0x290f0000, 0xb4000000, 0x03004600, 0x02000d00, 0x07000000, 0x2a0f0000, 0x90000000, - 0x05003800, 0x01000d00, 0x07000000, 0x2b0f0000, 0x96000000, 0x05003800, 0x01000d00, 0x07000000, 0x2c0f0000, 0xe8000000, 0x02004600, 0x01000d00, - 0x07000000, 0x2d0f0000, 0x9c000000, 0x05003800, 0x01000d00, 0x07000000, 0x2e0f0000, 0x04000000, 0x01005400, 0x02000d00, 0x07000000, 0x2f0f0000, - 0xa2000000, 0x05003800, 0x01000d00, 0x07000000, 0x300f0000, 0xae000000, 0x05003800, 0x01000d00, 0x07000000, 0x310f0000, 0xd8000000, 0x05003800, - 0x01000d00, 0x07000000, 0x320f0000, 0xfa000000, 0x05000000, 0x01000d00, 0x07000000, 0x330f0000, 0x31000000, 0x05002a00, 0x01000d00, 0x07000000, - 0x340f0000, 0x3f000000, 0x06001c00, 0x01000d00, 0x07000000, 0x350f0000, 0x37000000, 0x05002a00, 0x01000d00, 0x07000000, 0x360f0000, 0x3d000000, - 0x05002a00, 0x01000d00, 0x07000000, 0x370f0000, 0x43000000, 0x05002a00, 0x01000d00, 0x07000000, 0x380f0000, 0x49000000, 0x05002a00, 0x01000d00, - 0x07000000, 0x390f0000, 0x4f000000, 0x05002a00, 0x01000d00, 0x07000000, 0x3a0f0000, 0x02000000, 0x01005400, 0x03000d00, 0x07000000, 0x3b0f0000, - 0xfa000000, 0x02004600, 0x01000d00, 0x07000000, 0x3c0f0000, 0x77000000, 0x06001c00, 0x00000d00, 0x07000000, 0x3d0f0000, 0x7e000000, 0x06001c00, - 0x01000d00, 0x07000000, 0x3e0f0000, 0x85000000, 0x06001c00, 0x01000d00, 0x07000000, 0x3f0f0000, 0x55000000, 0x05002a00, 0x01000d00, 0x07000000, - 0x400f0000, 0xae000000, 0x07000e00, 0x00000d00, 0x07000000, 0x410f0000, 0xe0000000, 0x06001c00, 0x01000d00, 0x07000000, 0x420f0000, 0xa1000000, - 0x06001c00, 0x01000d00, 0x07000000, 0x430f0000, 0x5b000000, 0x05002a00, 0x01000d00, 0x07000000, 0x440f0000, 0xaf000000, 0x06001c00, 0x01000d00, - 0x07000000, 0x450f0000, 0x61000000, 0x05002a00, 0x01000d00, 0x07000000, 0x460f0000, 0x67000000, 0x05002a00, 0x01000d00, 0x07000000, 0x470f0000, - 0x38000000, 0x06001c00, 0x01000d00, 0x07000000, 0x480f0000, 0x8c000000, 0x06001c00, 0x01000d00, 0x07000000, 0x490f0000, 0xa0000000, 0x03004600, - 0x02000d00, 0x07000000, 0x4a0f0000, 0x97000000, 0x04004600, 0x01000d00, 0x07000000, 0x4b0f0000, 0xb6000000, 0x06001c00, 0x01000d00, 0x07000000, - 0x4c0f0000, 0x6d000000, 0x05002a00, 0x01000d00, 0x07000000, 0x4d0f0000, 0x1e000000, 0x07000e00, 0x00000d00, 0x07000000, 0x4e0f0000, 0x23000000, - 0x06002a00, 0x01000d00, 0x07000000, 0x4f0f0000, 0xed000000, 0x06000e00, 0x01000d00, 0x07000000, 0x500f0000, 0x73000000, 0x05002a00, 0x01000d00, - 0x07000000, 0x510f0000, 0x00000000, 0x06001c00, 0x01000d00, 0x07000000, 0x520f0000, 0x0e000000, 0x06001c00, 0x01000d00, 0x07000000, 0x530f0000, - 0x1c000000, 0x06001c00, 0x01000d00, 0x07000000, 0x540f0000, 0x66000000, 0x07000e00, 0x00000d00, 0x07000000, 0x550f0000, 0x2a000000, 0x06001c00, - 0x01000d00, 0x07000000, 0x560f0000, 0x6e000000, 0x07000e00, 0x00000d00, 0x07000000, 0x570f0000, 0x76000000, 0x07000e00, 0x00000d00, 0x07000000, - 0x580f0000, 0x46000000, 0x06001c00, 0x01000d00, 0x07000000, 0x590f0000, 0x7e000000, 0x07000e00, 0x00000d00, 0x07000000, 0x5a0f0000, 0x54000000, - 0x06001c00, 0x01000d00, 0x07000000, 0x5b0f0000, 0x9c000000, 0x03004600, 0x02000d00, 0x07000000, 0x5c0f0000, 0x79000000, 0x05002a00, 0x01000d00, - 0x07000000, 0x5d0f0000, 0xdc000000, 0x03004600, 0x02000d00, 0x07000000, 0x5e0f0000, 0x7f000000, 0x05002a00, 0x01000d00, 0x07000000, 0x5f0f0000, - 0xc6000000, 0x07000e00, 0x00000d00, 0x07000000, 0x600f0000, 0xfd000000, 0x02004600, 0x02000d00, 0x07000000, 0x610f0000, 0x85000000, 0x05002a00, - 0x01000d00, 0x07000000, 0x620f0000, 0x8b000000, 0x05002a00, 0x01000d00, 0x07000000, 0x630f0000, 0x91000000, 0x05002a00, 0x01000d00, 0x07000000, - 0x640f0000, 0x97000000, 0x05002a00, 0x01000d00, 0x07000000, 0x650f0000, 0x9d000000, 0x05002a00, 0x01000d00, 0x07000000, 0x660f0000, 0xa3000000, - 0x05002a00, 0x01000d00, 0x07000000, 0x670f0000, 0xa9000000, 0x05002a00, 0x01000d00, 0x07000000, 0x680f0000, 0xaf000000, 0x05002a00, 0x01000d00, - 0x07000000, 0x690f0000, 0xee000000, 0x02004600, 0x02000d00, 0x07000000, 0x6a0f0000, 0x92000000, 0x04004600, 0x01000d00, 0x07000000, 0x6b0f0000, - 0xb5000000, 0x05002a00, 0x01000d00, 0x07000000, 0x6c0f0000, 0xfd000000, 0x02002a00, 0x02000d00, 0x07000000, 0x6d0f0000, 0x8e000000, 0x07000e00, - 0x00000d00, 0x07000000, 0x6e0f0000, 0xbb000000, 0x05002a00, 0x01000d00, 0x07000000, 0x6f0f0000, 0xc1000000, 0x05002a00, 0x01000d00, 0x07000000, - 0x700f0000, 0xc7000000, 0x05002a00, 0x01000d00, 0x07000000, 0x710f0000, 0xcd000000, 0x05002a00, 0x01000d00, 0x07000000, 0x720f0000, 0xd3000000, - 0x05002a00, 0x01000d00, 0x07000000, 0x730f0000, 0xd9000000, 0x05002a00, 0x01000d00, 0x07000000, 0x740f0000, 0x7e000000, 0x04004600, 0x02000d00, - 0x07000000, 0x750f0000, 0xdf000000, 0x05002a00, 0x01000d00, 0x07000000, 0x760f0000, 0xe5000000, 0x05002a00, 0x01000d00, 0x07000000, 0x770f0000, - 0xbe000000, 0x07000e00, 0x00000d00, 0x07000000, 0x780f0000, 0xeb000000, 0x05002a00, 0x01000d00, 0x07000000, 0x790f0000, 0xf1000000, 0x05002a00, - 0x01000d00, 0x07000000, 0x7a0f0000, 0xf7000000, 0x05002a00, 0x01000d00, 0x07000000, 0x7b0f0000, 0x00000000, 0x05003800, 0x01000d00, 0x07000000, - 0x7c0f0000, 0x00000000, 0x01005400, 0x03000d00, 0x07000000, 0x7d0f0000, 0x06000000, 0x05003800, 0x01000d00, 0x07000000, 0x7e0f0000, 0x16000000, - 0x07000e00, 0x00000d00, 0x07000000, 0x7f0f0000, 0x58000000, 0x15000000, 0xf9000d00, 0x070000ff, 0x810f0000, 0x16000000, 0x15000000, 0xf9000d00, - 0x070000ff, 0x8d0f0000, 0x00000000, 0x15000e00, 0xf9000d00, 0x070000ff, 0x8f0f0000, 0xc6000000, 0x15000000, 0xf9000d00, 0x070000ff, 0x900f0000, - 0x6e000000, 0x15000000, 0xf9000d00, 0x070000ff, 0x9d0f0000, 0x84000000, 0x15000000, 0xf9000d00, 0x070000ff, 0xa00f0000, 0xdc000000, 0x15000000, - 0xf9000d00, 0x070000ff, 0xa10f0000, 0x0a000000, 0x01005400, 0x03000d00, 0x07000000, 0xa20f0000, 0x0c000000, 0x05003800, 0x01000d00, 0x07000000, - 0xa30f0000, 0x12000000, 0x05003800, 0x01000d00, 0x07000000, 0xa40f0000, 0x96000000, 0x07000e00, 0x00000d00, 0x07000000, 0xa50f0000, 0x5e000000, - 0x07000e00, 0x00000d00, 0x07000000, 0xa60f0000, 0x08000000, 0x01005400, 0x03000d00, 0x07000000, 0xa70f0000, 0x18000000, 0x05003800, 0x01000d00, - 0x07000000, 0xa80f0000, 0xac000000, 0x03004600, 0x02000d00, 0x07000000, 0xa90f0000, 0x56000000, 0x07000e00, 0x00000d00, 0x07000000, 0xaa0f0000, - 0x8d000000, 0x04004600, 0x01000d00, 0x07000000, 0xab0f0000, 0x1e000000, 0x05003800, 0x01000d00, 0x07000000, 0xac0f0000, 0xfb000000, 0x04000e00, - 0x01000d00, 0x07000000, 0xad0f0000, 0x42000000, 0x15000000, 0xf9000d00, 0x070000ff, 0xae0f0000, 0x3e000000, 0x07000e00, 0x00000d00, 0x07000000, - 0xaf0f0000, 0x26000000, 0x07000e00, 0x00000d00, 0x07000000, 0xb00f0000, 0x6f000000, 0x04004600, 0x01000d00, 0x07000000, 0xb10f0000, 0x24000000, - 0x05003800, 0x01000d00, 0x07000000, 0xb20f0000, 0x79000000, 0x04004600, 0x01000d00, 0x07000000, 0xb30f0000, 0x83000000, 0x04004600, 0x01000d00, - 0x07000000, 0xb40f0000, 0xeb000000, 0x02004600, 0x03000d00, 0x07000000, 0xb50f0000, 0x46000000, 0x07000e00, 0x00000d00, 0x07000000, 0xb60f0000, - 0xe6000000, 0x06000e00, 0x01000d00, 0x07000000, 0xb70f0000, 0xc0000000, 0x03004600, 0x02000d00, 0x07000000, 0xb80f0000, 0xf7000000, 0x02004600, - 0x03000d00, 0x07000000, 0xb90f0000, 0xc4000000, 0x03004600, 0x01000d00, 0x07000000, 0xba0f0000, 0x60000000, 0x04004600, 0x01000d00, 0x07000000, - 0xbb0f0000, 0x2a000000, 0x05003800, 0x01000d00, 0x07000000, 0xbc0f0000, 0x1c000000, 0x06002a00, 0x01000d00, 0x07000000, 0xbd0f0000, 0xc4000000, - 0x06001c00, 0x01000d00, 0x07000000, 0xbe0f0000, 0x9e000000, 0x07000e00, 0x00000d00, 0x07000000, 0xbf0f0000, 0x30000000, 0x05003800, 0x01000d00, - 0x07000000, 0xc00f0000, 0x9a000000, 0x06001c00, 0x01000d00, 0x07000000, 0xc10f0000, 0x93000000, 0x06001c00, 0x01000d00, 0x07000000, 0xc20f0000, - 0x70000000, 0x06001c00, 0x01000d00, 0x07000000, 0xc30f0000, 0x69000000, 0x06001c00, 0x01000d00, 0x07000000, 0xc40f0000, 0x62000000, 0x06001c00, - 0x01000d00, 0x07000000, 0xc50f0000, 0x5b000000, 0x06001c00, 0x01000d00, 0x07000000, 0xc60f0000, 0xf2000000, 0x07000000, 0x00000d00, 0x07000000, - 0xc70f0000, 0xbd000000, 0x06001c00, 0x01000d00, 0x07000000, 0xc80f0000, 0x3c000000, 0x05003800, 0x01000d00, 0x07000000, 0xc90f0000, 0x42000000, - 0x05003800, 0x01000d00, 0x07000000, 0xca0f0000, 0x48000000, 0x05003800, 0x01000d00, 0x07000000, 0xcb0f0000, 0x4e000000, 0x05003800, 0x01000d00, - 0x07000000, 0xcc0f0000, 0xa4000000, 0x03004600, 0x02000d00, 0x07000000, 0xcd0f0000, 0xb0000000, 0x03004600, 0x02000d00, 0x07000000, 0xce0f0000, - 0xa8000000, 0x03004600, 0x02000d00, 0x07000000, 0xcf0f0000, 0xfc000000, 0x03001c00, 0x02000d00, 0x07000000, 0xd00f0000, 0xce000000, 0x07000e00, - 0x00000d00, 0x07000000, 0xd10f0000, 0xcb000000, 0x06001c00, 0x01000d00, 0x07000000, 0xd20f0000, 0xd2000000, 0x06001c00, 0x01000d00, 0x07000000, - 0xd30f0000, 0xd9000000, 0x06001c00, 0x01000d00, 0x07000000, 0xd40f0000, 0x2a000000, 0x06002a00, 0x01000d00, 0x07000000, 0xd50f0000, 0xe7000000, - 0x06001c00, 0x01000d00, 0x07000000, 0xd60f0000, 0xee000000, 0x06001c00, 0x01000d00, 0x07000000, 0xd70f0000, 0x7e000000, 0x05003800, 0x01000d00, - 0x07000000, 0xd80f0000, 0xf5000000, 0x06001c00, 0x01000d00, 0x07000000, 0xd90f0000, 0x00000000, 0x06002a00, 0x01000d00, 0x07000000, 0xda0f0000, - 0x07000000, 0x06002a00, 0x01000d00, 0x07000000, 0xdb0f0000, 0x0e000000, 0x06002a00, 0x01000d00, 0x07000000, 0xdc0f0000, 0x15000000, 0x06002a00, - 0x01000d00, 0x07000000, 0xdd0f0000, 0xd6000000, 0x07000e00, 0x00000d00, 0x07000000, 0xde0f0000, 0xa8000000, 0x05003800, 0x01000d00, 0x07000000, - 0xdf0f0000, 0xde000000, 0x07000e00, 0x00000d00, 0x07000000, 0xe00f0000, 0xb4000000, 0x05003800, 0x01000d00, 0x07000000, 0xe10f0000, 0xba000000, - 0x05003800, 0x01000d00, 0x07000000, 0xe20f0000, 0xc0000000, 0x05003800, 0x01000d00, 0x07000000, 0xe30f0000, 0xc6000000, 0x05003800, 0x01000d00, - 0x07000000, 0xe40f0000, 0xcc000000, 0x05003800, 0x01000d00, 0x07000000, 0xe50f0000, 0xd2000000, 0x05003800, 0x01000d00, 0x07000000, 0xe60f0000, - 0xb6000000, 0x07000e00, 0x00000d00, 0x07000000, 0xe70f0000, 0xde000000, 0x05003800, 0x01000d00, 0x07000000, 0xe80f0000, 0xe4000000, 0x05003800, - 0x01000d00, 0x07000000, 0xe90f0000, 0xea000000, 0x05003800, 0x01000d00, 0x07000000, 0xea0f0000, 0xf0000000, 0x05003800, 0x01000d00, 0x07000000, - 0xeb0f0000, 0xf6000000, 0x05003800, 0x01000d00, 0x07000000, 0xec0f0000, 0xf1000000, 0x02004600, 0x02000d00, 0x07000000, 0xed0f0000, 0xf4000000, - 0x02004600, 0x02000d00, 0x07000000, 0xee0f0000, 0xd0000000, 0x03004600, 0x02000d00, 0x07000000, 0xef0f0000, 0xd4000000, 0x03004600, 0x02000d00, - 0x07000000, 0xf00f0000, 0x00000000, 0x05004600, 0x01000d00, 0x07000000, 0xf10f0000, 0x06000000, 0x05004600, 0x01000d00, 0x07000000, 0xf20f0000, - 0x0c000000, 0x05004600, 0x01000d00, 0x07000000, 0xf30f0000, 0x12000000, 0x05004600, 0x01000d00, 0x07000000, 0xf40f0000, 0x18000000, 0x05004600, - 0x01000d00, 0x07000000, 0xf50f0000, 0x1e000000, 0x05004600, 0x01000d00, 0x07000000, 0xf60f0000, 0x24000000, 0x05004600, 0x01000d00, 0x07000000, - 0xf70f0000, 0x2a000000, 0x05004600, 0x01000d00, 0x07000000, 0xf80f0000, 0x30000000, 0x05004600, 0x01000d00, 0x07000000, 0xf90f0000, 0x36000000, - 0x05004600, 0x01000d00, 0x07000000, 0xfa0f0000, 0x3c000000, 0x05004600, 0x01000d00, 0x07000000, 0xfb0f0000, 0x42000000, 0x05004600, 0x01000d00, - 0x07000000, 0xfc0f0000, 0x48000000, 0x05004600, 0x01000d00, 0x07000000, 0xfd0f0000, 0x4e000000, 0x05004600, 0x01000d00, 0x07000000, 0xfe0f0000, - 0x54000000, 0x05004600, 0x01000d00, 0x07000000, 0xff0f0000, 0x5a000000, 0x05004600, 0x01000d00, 0x07000000, 0x000f0000, -}; - -void ImGui::GetDefaultFontData(const void** fnt_data, unsigned int* fnt_size, const void** png_data, unsigned int* png_size) -{ - if (fnt_data) *fnt_data = (const void*)proggy_clean_13_fnt_data; - if (fnt_size) *fnt_size = proggy_clean_13_fnt_size; - if (png_data) *png_data = (const void*)proggy_clean_13_png_data; - if (png_size) *png_size = proggy_clean_13_png_size; + return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]; } +static unsigned char *stb__barrier; +static unsigned char *stb__barrier2; +static unsigned char *stb__barrier3; +static unsigned char *stb__barrier4; + +static unsigned char *stb__dout; +static void stb__match(unsigned char *data, unsigned int length) +{ + // INVERSE of memmove... write each byte before copying the next... + assert (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier4) { stb__dout = stb__barrier+1; return; } + while (length--) *stb__dout++ = *data++; +} + +static void stb__lit(unsigned char *data, unsigned int length) +{ + assert (stb__dout + length <= stb__barrier); + if (stb__dout + length > stb__barrier) { stb__dout += length; return; } + if (data < stb__barrier2) { stb__dout = stb__barrier+1; return; } + memcpy(stb__dout, data, length); + stb__dout += length; +} + +#define stb__in2(x) ((i[x] << 8) + i[(x)+1]) +#define stb__in3(x) ((i[x] << 16) + stb__in2((x)+1)) +#define stb__in4(x) ((i[x] << 24) + stb__in3((x)+1)) + +static unsigned char *stb_decompress_token(unsigned char *i) +{ + if (*i >= 0x20) { // use fewer if's for cases that expand small + if (*i >= 0x80) stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2; + else if (*i >= 0x40) stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3; + else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1); + } else { // more ifs for cases that expand large, since overhead is amortized + if (*i >= 0x18) stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4; + else if (*i >= 0x10) stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5; + else if (*i >= 0x08) stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1); + else if (*i == 0x07) stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1); + else if (*i == 0x06) stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5; + else if (*i == 0x04) stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6; + } + return i; +} + +static unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen) +{ + const unsigned long ADLER_MOD = 65521; + unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; + unsigned long blocklen, i; + + blocklen = buflen % 5552; + while (buflen) { + for (i=0; i + 7 < blocklen; i += 8) { + s1 += buffer[0], s2 += s1; + s1 += buffer[1], s2 += s1; + s1 += buffer[2], s2 += s1; + s1 += buffer[3], s2 += s1; + s1 += buffer[4], s2 += s1; + s1 += buffer[5], s2 += s1; + s1 += buffer[6], s2 += s1; + s1 += buffer[7], s2 += s1; + + buffer += 8; + } + + for (; i < blocklen; ++i) + s1 += *buffer++, s2 += s1; + + s1 %= ADLER_MOD, s2 %= ADLER_MOD; + buflen -= blocklen; + blocklen = 5552; + } + return (s2 << 16) + s1; +} + +static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length) +{ + unsigned int olen; + if (stb__in4(0) != 0x57bC0000) return 0; + if (stb__in4(4) != 0) return 0; // error! stream is > 4GB + olen = stb_decompress_length(i); + stb__barrier2 = i; + stb__barrier3 = i+length; + stb__barrier = output + olen; + stb__barrier4 = output; + i += 16; + + stb__dout = output; + while (1) { + unsigned char *old_i = i; + i = stb_decompress_token(i); + if (i == old_i) { + if (*i == 0x05 && i[1] == 0xfa) { + assert(stb__dout == output + olen); + if (stb__dout != output + olen) return 0; + if (stb_adler32(1, output, olen) != (unsigned int) stb__in4(2)) + return 0; + return olen; + } else { + assert(0); /* NOTREACHED */ + return 0; + } + } + assert(stb__dout <= output + olen); + if (stb__dout > output + olen) + return 0; + } +} + +static const unsigned int proggy_clean_ttf_compressed_size = 9583; +static const unsigned int proggy_clean_ttf_compressed_data[9584/4] = +{ + 0x0000bc57, 0x00000000, 0xf8a00000, 0x00000400, 0x00010037, 0x000c0000, 0x00030080, 0x2f534f40, 0x74eb8832, 0x01000090, 0x2c158248, 0x616d634e, + 0x23120270, 0x03000075, 0x241382a0, 0x74766352, 0x82178220, 0xfc042102, 0x02380482, 0x66796c67, 0x5689af12, 0x04070000, 0x80920000, 0x64616568, + 0xd36691d7, 0xcc201b82, 0x36210382, 0x27108268, 0xc3014208, 0x04010000, 0x243b0f82, 0x78746d68, 0x807e008a, 0x98010000, 0x06020000, 0x61636f6c, + 0xd8b0738c, 0x82050000, 0x0402291e, 0x7078616d, 0xda00ae01, 0x28201f82, 0x202c1082, 0x656d616e, 0x96bb5925, 0x84990000, 0x9e2c1382, 0x74736f70, + 0xef83aca6, 0x249b0000, 0xd22c3382, 0x70657270, 0x12010269, 0xf4040000, 0x08202f82, 0x012ecb84, 0x553c0000, 0x0f5fd5e9, 0x0300f53c, 0x00830008, + 0x7767b722, 0x002b3f82, 0xa692bd00, 0xfe0000d7, 0x83800380, 0x21f1826f, 0x00850002, 0x41820120, 0x40fec026, 0x80030000, 0x05821083, 0x07830120, + 0x0221038a, 0x24118200, 0x90000101, 0x82798200, 0x00022617, 0x00400008, 0x2009820a, 0x82098276, 0x82002006, 0x9001213b, 0x0223c883, 0x828a02bc, + 0x858f2010, 0xc5012507, 0x00023200, 0x04210083, 0x91058309, 0x6c412b03, 0x40007374, 0xac200000, 0x00830008, 0x01000523, 0x834d8380, 0x80032103, + 0x012101bf, 0x23b88280, 0x00800000, 0x0b830382, 0x07820120, 0x83800021, 0x88012001, 0x84002009, 0x2005870f, 0x870d8301, 0x2023901b, 0x83199501, + 0x82002015, 0x84802000, 0x84238267, 0x88002027, 0x8561882d, 0x21058211, 0x13880000, 0x01800022, 0x05850d85, 0x0f828020, 0x03208384, 0x03200582, + 0x47901b84, 0x1b850020, 0x1f821d82, 0x3f831d88, 0x3f410383, 0x84058405, 0x210982cd, 0x09830000, 0x03207789, 0xf38a1384, 0x01203782, 0x13872384, + 0x0b88c983, 0x0d898f84, 0x00202982, 0x23900383, 0x87008021, 0x83df8301, 0x86118d03, 0x863f880d, 0x8f35880f, 0x2160820f, 0x04830300, 0x1c220382, + 0x05820100, 0x4c000022, 0x09831182, 0x04001c24, 0x11823000, 0x0800082e, 0x00000200, 0xff007f00, 0xffffac20, 0x00220982, 0x09848100, 0xdf216682, + 0x843586d5, 0x06012116, 0x04400684, 0xa58120d7, 0x00b127d8, 0x01b88d01, 0x2d8685ff, 0xc100c621, 0xf4be0801, 0x9e011c01, 0x88021402, 0x1403fc02, + 0x9c035803, 0x1404de03, 0x50043204, 0xa2046204, 0x66051605, 0x1206bc05, 0xd6067406, 0x7e073807, 0x4e08ec07, 0x96086c08, 0x1009d008, 0x88094a09, + 0x800a160a, 0x560b040b, 0x2e0cc80b, 0xea0c820c, 0xa40d5e0d, 0x500eea0d, 0x280f960e, 0x1210b00f, 0xe0107410, 0xb6115211, 0x6e120412, 0x4c13c412, + 0xf613ac13, 0xae145814, 0x4015ea14, 0xa6158015, 0x1216b815, 0xc6167e16, 0x8e173417, 0x5618e017, 0xee18ba18, 0x96193619, 0x481ad419, 0xf01a9c1a, + 0xc81b5c1b, 0x4c1c041c, 0xea1c961c, 0x921d2a1d, 0x401ed21d, 0xe01e8e1e, 0x761f241f, 0xa61fa61f, 0x01821020, 0x8a202e34, 0xc820b220, 0x74211421, + 0xee219821, 0x86226222, 0x01820c23, 0x83238021, 0x23983c01, 0x24d823b0, 0x244a2400, 0x24902468, 0x250625ae, 0x25822560, 0x26f825f8, 0x82aa2658, + 0xd8be0801, 0x9a274027, 0x68280a28, 0x0e29a828, 0xb8292029, 0x362af829, 0x602a602a, 0x2a2b022b, 0xac2b5e2b, 0x202ce62b, 0x9a2c342c, 0x5c2d282d, + 0xaa2d782d, 0x262ee82d, 0x262fa62e, 0xf42fb62f, 0xc8305e30, 0xb4313e31, 0x9e321e32, 0x82331e33, 0x5c34ee33, 0x3a35ce34, 0xd4358635, 0x72362636, + 0x7637e636, 0x3a38d837, 0x1239a638, 0xae397439, 0x9a3a2e3a, 0x7c3b063b, 0x3a3ce83b, 0x223d963c, 0xec3d863d, 0xc63e563e, 0x9a3f2a3f, 0x6a401240, + 0x3641d040, 0x0842a241, 0x7a424042, 0xf042b842, 0xcc436243, 0x8a442a44, 0x5845ee44, 0xe245b645, 0xb4465446, 0x7a471447, 0x5448da47, 0x4049c648, + 0x15462400, 0x034d0808, 0x0b000700, 0x13000f00, 0x1b001700, 0x23001f00, 0x2b002700, 0x33002f00, 0x3b003700, 0x43003f00, 0x4b004700, 0x53004f00, + 0x5b005700, 0x63005f00, 0x6b006700, 0x73006f00, 0x7b007700, 0x83007f00, 0x8b008700, 0x00008f00, 0x15333511, 0x20039631, 0x20178205, 0xd3038221, + 0x20739707, 0x25008580, 0x028080fc, 0x05be8080, 0x04204a85, 0x05ce0685, 0x0107002a, 0x02000080, 0x00000400, 0x250d8b41, 0x33350100, 0x03920715, + 0x13820320, 0x858d0120, 0x0e8d0320, 0xff260d83, 0x00808000, 0x54820106, 0x04800223, 0x845b8c80, 0x41332059, 0x078b068f, 0x82000121, 0x82fe2039, + 0x84802003, 0x83042004, 0x23598a0e, 0x00180000, 0x03210082, 0x42ab9080, 0x73942137, 0x2013bb41, 0x8f978205, 0x2027a39b, 0x20b68801, 0x84b286fd, + 0x91c88407, 0x41032011, 0x11a51130, 0x15000027, 0x80ff8000, 0x11af4103, 0x841b0341, 0x8bd983fd, 0x9be99bc9, 0x8343831b, 0x21f1821f, 0xb58300ff, + 0x0f84e889, 0xf78a0484, 0x8000ff22, 0x0020eeb3, 0x14200082, 0x2130ef41, 0xeb431300, 0x4133200a, 0xd7410ecb, 0x9a07200b, 0x2027871b, 0x21238221, + 0xe7828080, 0xe784fd20, 0xe8848020, 0xfe808022, 0x08880d85, 0xba41fd20, 0x82248205, 0x85eab02a, 0x008022e7, 0x2cd74200, 0x44010021, 0xd34406eb, + 0x44312013, 0xcf8b0eef, 0x0d422f8b, 0x82332007, 0x0001212f, 0x8023cf82, 0x83000180, 0x820583de, 0x830682d4, 0x820020d4, 0x82dc850a, 0x20e282e9, + 0xb2ff85fe, 0x010327e9, 0x02000380, 0x0f440400, 0x0c634407, 0x68825982, 0x85048021, 0x260a825d, 0x010b0000, 0x4400ff00, 0x2746103f, 0x08d74209, + 0x4d440720, 0x0eaf4406, 0xc3441d20, 0x23078406, 0xff800002, 0x04845b83, 0x8d05b241, 0x1781436f, 0x6b8c87a5, 0x1521878e, 0x06474505, 0x01210783, + 0x84688c00, 0x8904828e, 0x441e8cf7, 0x0b270cff, 0x80008000, 0x45030003, 0xfb430fab, 0x080f4107, 0x410bf942, 0xd34307e5, 0x070d4207, 0x80800123, + 0x205d85fe, 0x849183fe, 0x20128404, 0x82809702, 0x00002217, 0x41839a09, 0x6b4408cf, 0x0733440f, 0x3b460720, 0x82798707, 0x97802052, 0x0000296f, + 0xff800004, 0x01800100, 0x0021ef89, 0x0a914625, 0x410a4d41, 0x00250ed4, 0x00050000, 0x056d4280, 0x210a7b46, 0x21481300, 0x46ed8512, 0x00210bd1, + 0x89718202, 0x21738877, 0x2b850001, 0x00220582, 0x87450a00, 0x0ddb4606, 0x41079b42, 0x9d420c09, 0x0b09420b, 0x8d820720, 0x9742fc84, 0x42098909, + 0x00241e0f, 0x00800014, 0x0b47da82, 0x0833442a, 0x49078d41, 0x2f450f13, 0x42278f17, 0x01200751, 0x22063742, 0x44808001, 0x20450519, 0x88068906, + 0x83fe2019, 0x4203202a, 0x1a941a58, 0x00820020, 0xe7a40e20, 0x420ce146, 0x854307e9, 0x0fcb4713, 0xff20a182, 0xfe209b82, 0x0c867f8b, 0x0021aea4, + 0x219fa40f, 0x7d41003b, 0x07194214, 0xbf440520, 0x071d4206, 0x6941a590, 0x80802309, 0x028900ff, 0xa9a4b685, 0xc5808021, 0x449b82ab, 0x152007eb, + 0x42134d46, 0x61440a15, 0x051e4208, 0x222b0442, 0x47001100, 0xfd412913, 0x17194714, 0x410f5b41, 0x02220773, 0x09428080, 0x21a98208, 0xd4420001, + 0x481c840d, 0x00232bc9, 0x42120000, 0xe74c261b, 0x149d4405, 0x07209d87, 0x410db944, 0x14421c81, 0x42fd2005, 0x80410bd2, 0x203d8531, 0x06874100, + 0x48256f4a, 0xcb420c95, 0x13934113, 0x44075d44, 0x044c0855, 0x00ff2105, 0xfe228185, 0x45448000, 0x22c5b508, 0x410c0000, 0x7b412087, 0x1bb74514, + 0x32429c85, 0x0a574805, 0x21208943, 0x8ba01300, 0x440dfb4e, 0x77431437, 0x245b4113, 0x200fb145, 0x41108ffe, 0x80203562, 0x00200082, 0x46362b42, + 0x1742178d, 0x4527830f, 0x0f830b2f, 0x4a138146, 0x802409a1, 0xfe8000ff, 0x94419982, 0x09294320, 0x04000022, 0x49050f4f, 0xcb470a63, 0x48032008, + 0x2b48067b, 0x85022008, 0x82638338, 0x00002209, 0x05af4806, 0x900e9f49, 0x84c5873f, 0x214285bd, 0x064900ff, 0x0c894607, 0x00000023, 0x4903820a, + 0x714319f3, 0x0749410c, 0x8a07a145, 0x02152507, 0xfe808000, 0x74490386, 0x8080211b, 0x0c276f82, 0x00018000, 0x48028003, 0x2b2315db, 0x43002f00, + 0x6f82142f, 0x44011521, 0x93510da7, 0x20e68508, 0x06494d80, 0x8e838020, 0x06821286, 0x124bff20, 0x25f3830c, 0x03800080, 0xe74a0380, 0x207b8715, + 0x876b861d, 0x4a152007, 0x07870775, 0xf6876086, 0x8417674a, 0x0a0021f2, 0x431c9743, 0x8d421485, 0x200b830b, 0x06474d03, 0x71828020, 0x04510120, + 0x42da8606, 0x1f831882, 0x001a0022, 0xff4d0082, 0x0b0f532c, 0x0d449b94, 0x4e312007, 0x074f12e7, 0x0bf3490b, 0xbb412120, 0x413f820a, 0xef490857, + 0x80002313, 0xe2830001, 0x6441fc20, 0x8b802006, 0x00012108, 0xfd201582, 0x492c9b48, 0x802014ff, 0x51084347, 0x0f4327f3, 0x17bf4a14, 0x201b7944, + 0x06964201, 0x134ffe20, 0x20d6830b, 0x25d78280, 0xfd800002, 0x05888000, 0x9318dc41, 0x21d282d4, 0xdb481800, 0x0dff542a, 0x45107743, 0xe14813f5, + 0x0f034113, 0x83135d45, 0x47b28437, 0xe4510e73, 0x21f58e06, 0x2b8400fd, 0x1041fcac, 0x08db4b0b, 0x421fdb41, 0xdf4b18df, 0x011d210a, 0x420af350, + 0x6e8308af, 0xac85cb86, 0x1e461082, 0x82b7a407, 0x411420a3, 0xa34130ab, 0x178f4124, 0x41139741, 0x86410d93, 0x82118511, 0x057243d8, 0x8941d9a4, + 0x3093480c, 0x4a13474f, 0xfb5016a9, 0x07ad4108, 0x4a0f9d42, 0xfe200fad, 0x4708aa41, 0x83482dba, 0x288f4d06, 0xb398c3bb, 0x44267b41, 0xb34439d7, + 0x0755410f, 0x200ebb45, 0x0f5f4215, 0x20191343, 0x06df5301, 0xf04c0220, 0x2ba64d07, 0x82050841, 0x430020ce, 0xa78f3627, 0x5213ff42, 0x2f970bc1, + 0x4305ab55, 0xa084111b, 0x450bac45, 0x5f4238b8, 0x010c2106, 0x0220ed82, 0x441bb344, 0x875010af, 0x0737480f, 0x490c5747, 0x0c840c03, 0x4c204b42, + 0x8ba905d7, 0x8b948793, 0x510c0c51, 0xfb4b24b9, 0x1b174107, 0x5709d74c, 0xd1410ca5, 0x079d480f, 0x201ff541, 0x06804780, 0x7d520120, 0x80002205, + 0x20a983fe, 0x47bb83fe, 0x1b8409b4, 0x81580220, 0x4e00202c, 0x4f41282f, 0x0eab4f17, 0x57471520, 0x0e0f4808, 0x8221e041, 0x3e1b4a8b, 0x4407175d, + 0x1b4b071f, 0x4a0f8b07, 0x174a0703, 0x0ba5411b, 0x430fb141, 0x0120057b, 0xfc20dd82, 0x4a056047, 0xf4850c0c, 0x01221982, 0x02828000, 0x1a5d088b, + 0x20094108, 0x8c0e3941, 0x4900200e, 0x7744434f, 0x200b870b, 0x0e4b5a33, 0x2b41f78b, 0x8b138307, 0x0b9f450b, 0x2406f741, 0xfd808001, 0x09475a00, + 0x84000121, 0x5980200e, 0x85450e5d, 0x832c8206, 0x4106831e, 0x00213814, 0x28b34810, 0x410c2f4b, 0x5f4a13d7, 0x0b2b4113, 0x6e43a883, 0x11174b05, + 0x4b066a45, 0xcc470541, 0x5000202b, 0xcb472f4b, 0x44b59f0f, 0xc5430b5b, 0x0d654907, 0x21065544, 0xd6828080, 0xfe201982, 0x8230ec4a, 0x120025c2, + 0x80ff8000, 0x4128d74d, 0x3320408b, 0x410a9f50, 0xdb822793, 0x822bd454, 0x61134b2e, 0x410b214a, 0xad4117c9, 0x0001211f, 0x4206854f, 0x4b430596, + 0x06bb5530, 0x2025cf46, 0x0ddd5747, 0x500ea349, 0x0f840fa7, 0x5213c153, 0x634e08d1, 0x0bbe4809, 0x59316e4d, 0x5b50053f, 0x203f6323, 0x5117eb46, + 0x94450a63, 0x246e410a, 0x63410020, 0x0bdb5f2f, 0x4233ab44, 0x39480757, 0x112d4a07, 0x7241118f, 0x000e2132, 0x9f286f41, 0x0f8762c3, 0x33350723, + 0x094e6415, 0x2010925f, 0x067252fe, 0xd0438020, 0x63a68225, 0x11203a4f, 0x480e6360, 0x5748131f, 0x079b521f, 0x200e2f43, 0x864b8315, 0x113348e7, + 0x85084e48, 0x06855008, 0x5880fd21, 0x7c420925, 0x0c414824, 0x37470c86, 0x1b8b422b, 0x5b0a8755, 0x23410c21, 0x0b83420b, 0x5a082047, 0xf482067f, + 0xa80b4c47, 0x0c0021cf, 0x20207b42, 0x0fb74100, 0x420b8744, 0xeb43076f, 0x0f6f420b, 0x4261fe20, 0x439aa00c, 0x215034e3, 0x0ff9570f, 0x4b1f2d5d, + 0x2d5d0c6f, 0x09634d0b, 0x1f51b8a0, 0x620f200c, 0xaf681e87, 0x24f94d07, 0x4e0f4945, 0xfe200c05, 0x22139742, 0x57048080, 0x23950c20, 0x97601585, + 0x4813201f, 0xad620523, 0x200f8f0f, 0x9e638f15, 0x00002181, 0x41342341, 0x0f930f0b, 0x210b4b62, 0x978f0001, 0xfe200f84, 0x8425c863, 0x2704822b, + 0x80000a00, 0x00038001, 0x610e9768, 0x834514bb, 0x0bc3430f, 0x2107e357, 0x80848080, 0x4400fe21, 0x2e410983, 0x00002a1a, 0x00000700, 0x800380ff, + 0x0fdf5800, 0x59150021, 0xd142163d, 0x0c02410c, 0x01020025, 0x65800300, 0x00240853, 0x1d333501, 0x15220382, 0x35420001, 0x44002008, 0x376406d7, + 0x096f6b19, 0x480bc142, 0x8f4908a7, 0x211f8b1f, 0x9e830001, 0x0584fe20, 0x4180fd21, 0x11850910, 0x8d198259, 0x000021d4, 0x5a08275d, 0x275d1983, + 0x06d9420e, 0x9f08b36a, 0x0f7d47b5, 0x8d8a2f8b, 0x4c0e0b57, 0xe7410e17, 0x42d18c1a, 0xb351087a, 0x1ac36505, 0x4b4a2f20, 0x0b9f450d, 0x430beb53, + 0xa7881015, 0xa5826a83, 0x80200f82, 0x86185a65, 0x4100208e, 0x176c3367, 0x0fe7650b, 0x4a17ad4b, 0x0f4217ed, 0x112e4206, 0x41113a42, 0xf7423169, + 0x0cb34737, 0x560f8b46, 0xa75407e5, 0x5f01200f, 0x31590c48, 0x80802106, 0x42268841, 0x0020091e, 0x4207ef64, 0x69461df7, 0x138d4114, 0x820f5145, + 0x53802090, 0xff200529, 0xb944b183, 0x417e8505, 0x00202561, 0x15210082, 0x42378200, 0x9b431cc3, 0x004f220d, 0x0dd54253, 0x4213f149, 0x7d41133b, + 0x42c9870b, 0x802010f9, 0x420b2c42, 0x8f441138, 0x267c4408, 0x600cb743, 0x8f4109d3, 0x05ab701d, 0x83440020, 0x3521223f, 0x0b794733, 0xfb62fe20, + 0x4afd2010, 0xaf410ae7, 0x25ce8525, 0x01080000, 0x7b6b0000, 0x0973710b, 0x82010021, 0x49038375, 0x33420767, 0x052c4212, 0x58464b85, 0x41fe2005, + 0x50440c27, 0x000c2209, 0x1cb36b80, 0x9b06df44, 0x0f93566f, 0x52830220, 0xfe216e8d, 0x200f8200, 0x0fb86704, 0xb057238d, 0x050b5305, 0x7217eb47, + 0xbd410b6b, 0x0f214610, 0x871f9956, 0x1e91567e, 0x2029b741, 0x20008200, 0x18b7410a, 0x27002322, 0x41095543, 0x0f8f0fb3, 0x41000121, 0x889d111c, + 0x14207b82, 0x00200382, 0x73188761, 0x475013a7, 0x6e33200c, 0x234e0ea3, 0x9b138313, 0x08e54d17, 0x9711094e, 0x2ee74311, 0x4908875e, 0xd75d1f1f, + 0x19ab5238, 0xa2084d48, 0x63a7a9b3, 0x55450b83, 0x0fd74213, 0x440d814c, 0x4f481673, 0x05714323, 0x13000022, 0x412e1f46, 0xdf493459, 0x21c7550f, + 0x8408215f, 0x201d49cb, 0xb1103043, 0x0f0d65d7, 0x452b8d41, 0x594b0f8d, 0x0b004605, 0xb215eb46, 0x000a24d7, 0x47000080, 0x002118cf, 0x06436413, + 0x420bd750, 0x2b500743, 0x076a470c, 0x4105c050, 0xd942053f, 0x0d00211a, 0x5f44779c, 0x0ce94805, 0x51558186, 0x14a54c0b, 0x49082b41, 0x0a4b0888, + 0x8080261f, 0x0d000000, 0x20048201, 0x1deb6a03, 0x420cb372, 0x07201783, 0x4306854d, 0x8b830c59, 0x59093c74, 0x0020250f, 0x67070f4a, 0x2341160b, + 0x00372105, 0x431c515d, 0x554e17ef, 0x0e5d6b05, 0x41115442, 0xb74a1ac1, 0x2243420a, 0x5b4f878f, 0x7507200f, 0x384b086f, 0x09d45409, 0x0020869a, + 0x12200082, 0xab460382, 0x10075329, 0x54138346, 0xaf540fbf, 0x1ea75413, 0x9a0c9e54, 0x0f6b44c1, 0x41000021, 0x47412a4f, 0x07374907, 0x5310bf76, + 0xff2009b4, 0x9a09a64c, 0x8200208d, 0x34c34500, 0x970fe141, 0x1fd74b0f, 0x440a3850, 0x206411f0, 0x27934609, 0x470c5d41, 0x555c2947, 0x1787540f, + 0x6e0f234e, 0x7d540a1b, 0x1d736b08, 0x0026a088, 0x80000e00, 0x9b5200ff, 0x08ef4318, 0x450bff77, 0x1d4d0b83, 0x081f7006, 0xcb691b86, 0x4b022008, + 0xc34b0b33, 0x1d0d4a0c, 0x8025a188, 0x0b000000, 0x52a38201, 0xbf7d0873, 0x0c234511, 0x8f0f894a, 0x4101200f, 0x0c880c9d, 0x2b418ea1, 0x06c74128, + 0x66181341, 0x7b4c0bb9, 0x0c06630b, 0xfe200c87, 0x9ba10882, 0x27091765, 0x01000008, 0x02800380, 0x48113f4e, 0x29430cf5, 0x09a75a0b, 0x31618020, + 0x6d802009, 0x61840e33, 0x8208bf51, 0x0c637d61, 0x7f092379, 0x4f470f4b, 0x1797510c, 0x46076157, 0xf5500fdf, 0x0f616910, 0x1171fe20, 0x82802006, + 0x08696908, 0x41127a4c, 0x3f4a15f3, 0x01042607, 0x0200ff00, 0x1cf77700, 0xff204185, 0x00235b8d, 0x43100000, 0x3b22243f, 0x3b4d3f00, 0x0b937709, + 0xad42f18f, 0x0b1f420f, 0x51084b43, 0x8020104a, 0xb557ff83, 0x052b7f2a, 0x0280ff22, 0x250beb78, 0x00170013, 0xbf6d2500, 0x07db760e, 0x410e2b7f, + 0x00230e4f, 0x49030000, 0x0582055b, 0x07000326, 0x00000b00, 0x580bcd46, 0x00200cdd, 0x57078749, 0x8749160f, 0x0f994f0a, 0x41134761, 0x01200b31, + 0xeb796883, 0x0b41500b, 0x0e90b38e, 0x202e7b51, 0x05d95801, 0x41080570, 0x1d530fc9, 0x0b937a0f, 0xaf8eb387, 0xf743b98f, 0x07c74227, 0x80000523, + 0x0fcb4503, 0x430ca37b, 0x7782077f, 0x8d0a9947, 0x08af4666, 0xeb798020, 0x6459881e, 0xc3740bbf, 0x0feb6f0b, 0x20072748, 0x052b6102, 0x435e0584, + 0x7d088308, 0x03200afd, 0x92109e41, 0x28aa8210, 0x80001500, 0x80030000, 0x0fdb5805, 0x209f4018, 0xa7418d87, 0x0aa3440f, 0x20314961, 0x073a52ff, + 0x6108505d, 0x43181051, 0x00223457, 0xe7820500, 0x50028021, 0x81410d33, 0x063d7108, 0xdb41af84, 0x4d888205, 0x00201198, 0x463d835f, 0x152106d7, + 0x0a355a33, 0x6917614e, 0x75411f4d, 0x184b8b07, 0x1809c344, 0x21091640, 0x0b828000, 0x42808021, 0x26790519, 0x86058605, 0x2428422d, 0x22123b42, + 0x42000080, 0xf587513b, 0x7813677b, 0xaf4d139f, 0x00ff210c, 0x5e0a1d57, 0x3b421546, 0x01032736, 0x02000380, 0x41180480, 0x2f420f07, 0x0c624807, + 0x00000025, 0x18000103, 0x83153741, 0x430120c3, 0x042106b2, 0x088d4d00, 0x2f830620, 0x1810434a, 0x18140345, 0x8507fb41, 0x5ee582ea, 0x0023116c, + 0x8d000600, 0x053b56af, 0xa6554fa2, 0x0d704608, 0x40180d20, 0x47181a43, 0xd37b07ff, 0x0b79500c, 0x420fd745, 0x47450bd9, 0x8471830a, 0x095a777e, + 0x84137542, 0x82002013, 0x2f401800, 0x0007213b, 0x4405e349, 0x0d550ff3, 0x16254c0c, 0x820ffe4a, 0x0400218a, 0x89066f41, 0x106b414f, 0xc84d0120, + 0x80802206, 0x0c9a4b03, 0x00100025, 0x68000200, 0x9d8c2473, 0x44134344, 0xf36a0f33, 0x4678860f, 0x1b440a25, 0x41988c0a, 0x80201879, 0x43079b5e, + 0x4a18080b, 0x0341190b, 0x1259530c, 0x43251552, 0x908205c8, 0x0cac4018, 0x86000421, 0x0e504aa2, 0x0020b891, 0xfb450082, 0x51132014, 0x8f5205f3, + 0x35052108, 0x8505cb59, 0x0f6d4f70, 0x82150021, 0x29af5047, 0x4f004b24, 0x75795300, 0x1b595709, 0x460b6742, 0xbf4b0f0d, 0x5743870b, 0xcb6d1461, + 0x08f64505, 0x4e05ab6c, 0x334126c3, 0x0bcb6b0d, 0x1811034d, 0x4111ef4b, 0x814f1ce5, 0x20af8227, 0x07fd7b80, 0x41188e84, 0xef410f33, 0x80802429, + 0x410d0000, 0xa34205ab, 0x76b7881c, 0xff500b89, 0x0741430f, 0x20086f4a, 0x209d8200, 0x234c18fd, 0x05d4670a, 0x4509af51, 0x9642078d, 0x189e831d, + 0x7c1cc74b, 0xcd4c07b9, 0x0e7c440f, 0x8b7b0320, 0x21108210, 0xc76c8080, 0x03002106, 0x6b23bf41, 0xc549060b, 0x7946180b, 0x0ff7530f, 0x17ad4618, + 0x200ecd45, 0x208c83fd, 0x5e0488fe, 0x032009c6, 0x420d044e, 0x0d8f0d7f, 0x00820020, 0x18001021, 0x6d273b45, 0xfd4c0c93, 0xcf451813, 0x0fe5450f, + 0x5a47c382, 0x820a8b0a, 0x282b4998, 0x410a8b5b, 0x4b232583, 0x54004f00, 0x978f0ce3, 0x500f1944, 0xa95f1709, 0x0280220b, 0x05ba7080, 0xa1530682, + 0x06324c13, 0x91412582, 0x05536e2c, 0x63431020, 0x0f434706, 0x8c11374c, 0x176143d7, 0x4d0f454c, 0xd3680bed, 0x0bee4d17, 0x212b9a41, 0x0f530a00, + 0x140d531c, 0x43139143, 0x95610e8d, 0x0f094415, 0x4205fb56, 0x1b4205cf, 0x17015225, 0x5e0c477f, 0xaf6e0aeb, 0x0ff36218, 0x04849a84, 0x0a454218, + 0x9c430420, 0x23c6822b, 0x04000102, 0x45091b4b, 0xf05f0955, 0x82802007, 0x421c2023, 0x5218282b, 0x7b53173f, 0x0fe7480c, 0x74173b7f, 0x47751317, + 0x634d1807, 0x0f6f430f, 0x24086547, 0xfc808002, 0x0b3c7f80, 0x10840120, 0x188d1282, 0x20096b43, 0x0fc24403, 0x00260faf, 0x0180000b, 0x3f500280, + 0x18002019, 0x450b4941, 0xf3530fb9, 0x18002010, 0x8208a551, 0x06234d56, 0xcb58a39b, 0xc3421805, 0x1313461e, 0x0f855018, 0xd34b0120, 0x6cfe2008, + 0x574f0885, 0x09204114, 0x07000029, 0x00008000, 0x44028002, 0x01420f57, 0x10c95c10, 0x11184c18, 0x80221185, 0x7f421e00, 0x00732240, 0x09cd4977, + 0x6d0b2b42, 0x4f180f8f, 0x8f5a0bcb, 0x9b0f830f, 0x0fb9411f, 0x230b5756, 0x00fd8080, 0x82060745, 0x000121d5, 0x8e0fb277, 0x4a8d4211, 0x24061c53, + 0x04000007, 0x12275280, 0x430c954c, 0x80201545, 0x200f764f, 0x20008200, 0x20ce8308, 0x09534f02, 0x660edf64, 0x73731771, 0xe7411807, 0x20a2820c, + 0x13b64404, 0x8f5d6682, 0x1d6b4508, 0x0cff4d18, 0x3348c58f, 0x0fc34c07, 0x31558b84, 0x8398820f, 0x17514712, 0x240b0e46, 0x80000a00, 0x093b4502, + 0x420f9759, 0xa54c0bf1, 0x0f2b470c, 0x410d314b, 0x2584170c, 0x73b30020, 0xb55fe782, 0x204d8410, 0x08e043fe, 0x4f147e41, 0x022008ab, 0x4b055159, + 0x2950068f, 0x00022208, 0x48511880, 0x82002009, 0x00112300, 0x634dff00, 0x24415f27, 0x180f6d43, 0x4d0b5d45, 0x4d5f05ef, 0x01802317, 0x56188000, + 0xa7840807, 0xc6450220, 0x21ca8229, 0x4b781a00, 0x3359182c, 0x0cf3470f, 0x180bef46, 0x420b0354, 0xff470b07, 0x4515200a, 0x9758239b, 0x4a80200c, + 0xd2410a26, 0x05fb4a08, 0x4b05e241, 0x03200dc9, 0x92290941, 0x00002829, 0x00010900, 0x5b020001, 0x23201363, 0x460d776a, 0xef530fdb, 0x209a890c, + 0x13fc4302, 0x00008024, 0xc4820104, 0x08820220, 0x20086b5b, 0x18518700, 0x8408d349, 0x0da449a1, 0x00080024, 0x7b690280, 0x4c438b1a, 0x01220f63, + 0x4c878000, 0x5c149c53, 0xfb430868, 0x2f56181e, 0x0ccf7b1b, 0x0f075618, 0x2008e347, 0x14144104, 0x00207f83, 0x00207b82, 0x201adf47, 0x16c35a13, + 0x540fdf47, 0x802006c8, 0x5418f185, 0x29430995, 0x00002419, 0x58001600, 0x5720316f, 0x4d051542, 0x4b7b1b03, 0x138f4707, 0xb747b787, 0x4aab8213, + 0x058305fc, 0x20115759, 0x82128401, 0x0a0b44e8, 0x46800121, 0xe64210d0, 0x82129312, 0x4bffdffe, 0x3b41171b, 0x9b27870f, 0x808022ff, 0x085c68fe, + 0x41800021, 0x01410b20, 0x001a213a, 0x47480082, 0x11374e12, 0x56130b4c, 0xdf4b0c65, 0x0b0f590b, 0x0f574c18, 0x830feb4b, 0x075f480f, 0x480b4755, + 0x40490b73, 0x80012206, 0x09d74280, 0x80fe8022, 0x80210e86, 0x056643ff, 0x10820020, 0x420b2646, 0x0b58391a, 0xd74c1808, 0x078b4e22, 0x2007f55f, + 0x4b491807, 0x83802017, 0x65aa82a7, 0x3152099e, 0x068b7616, 0x9b431220, 0x09bb742c, 0x500e376c, 0x8342179b, 0x0a4d5d0f, 0x8020a883, 0x180cd349, + 0x2016bb4b, 0x14476004, 0x84136c43, 0x08cf7813, 0x4f4c0520, 0x156f420f, 0x20085f42, 0x6fd3be03, 0xd4d30803, 0xa7411420, 0x004b222c, 0x0d3b614f, + 0x3f702120, 0x1393410a, 0x8f132745, 0x47421827, 0x41e08209, 0xb05e2bb9, 0x18b7410c, 0x18082647, 0x4107a748, 0xeb8826bf, 0x0ca76018, 0x733ecb41, + 0xd0410d83, 0x43ebaf2a, 0x0420067f, 0x721dab4c, 0x472005bb, 0x4105d341, 0x334844cb, 0x20dba408, 0x47d6ac00, 0x034e3aef, 0x0f8f421b, 0x930f134d, + 0x3521231f, 0xb7421533, 0x42f5ad0a, 0x1e961eaa, 0x17000022, 0x4c367b50, 0x7d491001, 0x0bf5520f, 0x4c18fda7, 0xb8460c55, 0x83fe2005, 0x00fe25b9, + 0x80000180, 0x9e751085, 0x261b5c12, 0x82110341, 0x001123fb, 0x4518fe80, 0xf38c2753, 0x6d134979, 0x295107a7, 0xaf5f180f, 0x0fe3660c, 0x180b6079, + 0x2007bd5f, 0x9aab9103, 0x2f4d1811, 0x05002109, 0x44254746, 0x1d200787, 0x450bab75, 0x4f180f57, 0x4f181361, 0x3b831795, 0xeb4b0120, 0x0b734805, + 0x84078f48, 0x2e1b47bc, 0x00203383, 0xaf065f45, 0x831520d7, 0x130f51a7, 0x1797bf97, 0x2b47d783, 0x18fe2005, 0x4a18a44f, 0xa64d086d, 0x1ab0410d, + 0x6205a258, 0xdbab069f, 0x4f06f778, 0xa963081d, 0x133b670a, 0x8323d141, 0x13195b23, 0x530f5e70, 0xe5ad0824, 0x58001421, 0x1f472b4b, 0x47bf410c, + 0x82000121, 0x83fe20cb, 0x07424404, 0x68068243, 0xd7ad0d3d, 0x00010d26, 0x80020000, 0x4a1c6f43, 0x23681081, 0x10a14f13, 0x8a070e57, 0x430a848f, + 0x7372243e, 0x4397a205, 0xb56c1021, 0x43978f0f, 0x64180505, 0x99aa0ff2, 0x0e000022, 0x20223341, 0x094b4f37, 0x074a3320, 0x2639410a, 0xfe208e84, + 0x8b0e0048, 0x508020a3, 0x9e4308fe, 0x073f4115, 0xe3480420, 0x0c9b5f1b, 0x7c137743, 0x9a95185b, 0x6122b148, 0x979b08df, 0x0fe36c18, 0x48109358, + 0x23441375, 0x0ffd5c0b, 0x180fc746, 0x2011d157, 0x07e95702, 0x58180120, 0x18770ac3, 0x51032008, 0x7d4118e3, 0x80802315, 0x3b4c1900, 0xbb5a1830, + 0x0ceb6109, 0x5b0b3d42, 0x4f181369, 0x4f180b8d, 0x4f180f75, 0x355a1b81, 0x200d820d, 0x18e483fd, 0x4528854f, 0x89420846, 0x1321411f, 0x44086b60, + 0x07421d77, 0x107d4405, 0x4113fd41, 0x5a181bf1, 0x4f180db3, 0x8021128f, 0x20f68280, 0x44a882fe, 0x334d249a, 0x052f6109, 0x1520c3a7, 0xef4eb783, + 0x4ec39b1b, 0xc4c90ee7, 0x20060b4d, 0x256f4905, 0x4d0cf761, 0xcf9b1f13, 0xa213d74e, 0x0e1145d4, 0x50135b42, 0xcb4e398f, 0x20d79f27, 0x08865d80, + 0x186d5018, 0xa90f7142, 0x067342d7, 0x3f450420, 0x65002021, 0xe3560771, 0x24d38f23, 0x15333531, 0x0eb94d01, 0x451c9f41, 0x384322fb, 0x00092108, + 0x19af6b18, 0x6e0c6f5a, 0xbd770bfb, 0x22bb7718, 0x20090f57, 0x25e74204, 0x4207275a, 0xdb5408ef, 0x1769450f, 0x1b1b5518, 0x210b1f57, 0x5e4c8001, + 0x55012006, 0x802107f1, 0x0a306a80, 0x45808021, 0x0d850b88, 0x31744f18, 0x1808ec54, 0x2009575b, 0x45ffa505, 0x1b420c73, 0x180f9f0f, 0x4a0cf748, + 0x501805b2, 0x00210f40, 0x4d118f80, 0xd6823359, 0x072b5118, 0x314ad7aa, 0x8fc79f08, 0x45d78b1f, 0xfe20058f, 0x23325118, 0x7b54d9b5, 0x9fc38f46, + 0x10bb410f, 0x41077b42, 0xc1410faf, 0x27cf441d, 0x46051b4f, 0x04200683, 0x2121d344, 0x8f530043, 0x8fcf9f0e, 0x21df8c1f, 0x50188000, 0x5d180e52, + 0xfd201710, 0x4405c341, 0xd68528e3, 0x20071f6b, 0x1b734305, 0x6b080957, 0x7d422b1f, 0x67002006, 0x7f8317b1, 0x2024cb48, 0x08676e00, 0x8749a39b, + 0x18132006, 0x410a6370, 0x8f490b47, 0x7e1f8f13, 0x551805c3, 0x4c180915, 0xfe200e2f, 0x244d5d18, 0x270bcf44, 0xff000019, 0x04800380, 0x5f253342, + 0xff520df7, 0x13274c18, 0x5542dd93, 0x0776181b, 0xf94a1808, 0x084a4c0c, 0x4308ea5b, 0xde831150, 0x7900fd21, 0x00492c1e, 0x060f4510, 0x17410020, + 0x0ce74526, 0x6206b341, 0x1f561083, 0x9d6c181b, 0x08a0500e, 0x112e4118, 0x60000421, 0xbf901202, 0x4408e241, 0xc7ab0513, 0xb40f0950, 0x055943c7, + 0x4f18ff20, 0xc9ae1cad, 0x32b34f18, 0x7a180120, 0x3d520a05, 0x53d1b40a, 0x80200813, 0x1b815018, 0x832bf86f, 0x67731847, 0x297f4308, 0x6418d54e, + 0x734213f7, 0x056b4b27, 0xdba5fe20, 0x1828aa4e, 0x2031a370, 0x06cb6101, 0x2040ad41, 0x07365300, 0x2558d985, 0x83fe200c, 0x0380211c, 0x542c4743, + 0x052006b7, 0x6021df45, 0x897b0707, 0x18d3c010, 0x20090e70, 0x1d5843ff, 0x540a0e44, 0x002126c5, 0x322f7416, 0x636a5720, 0x0f317409, 0x610fe159, + 0x294617e7, 0x08555213, 0x2006a75d, 0x6cec84fd, 0xfb5907be, 0x3a317405, 0x83808021, 0x180f20ea, 0x4626434a, 0x531818e3, 0xdb59172d, 0x0cbb460c, + 0x2013d859, 0x18b94502, 0x8f46188d, 0x77521842, 0x0a184e38, 0x9585fd20, 0x6a180684, 0xc64507e9, 0x51cbb230, 0xd3440cf3, 0x17ff6a0f, 0x450f5b42, + 0x276407c1, 0x4853180a, 0x21ccb010, 0xcf580013, 0x0c15442d, 0x410a1144, 0x1144359d, 0x5cfe2006, 0xa1410a43, 0x2bb64519, 0x2f5b7618, 0xb512b745, + 0x0cfd6fd1, 0x42089f59, 0xb8450c70, 0x0000232d, 0x50180900, 0xb9491ae3, 0x0fc37610, 0x01210f83, 0x0f3b4100, 0xa01b2742, 0x0ccd426f, 0x6e8f6f94, + 0x9c808021, 0xc7511870, 0x17c74b08, 0x9b147542, 0x44fe2079, 0xd5480c7e, 0x95ef861d, 0x101b597b, 0xf5417594, 0x9f471808, 0x86868d0e, 0x3733491c, + 0x690f4d6d, 0x43440b83, 0x1ba94c0b, 0x660cd16b, 0x802008ae, 0x74126448, 0xcb4f38a3, 0x2cb74b0b, 0x47137755, 0xe3971777, 0x1b5d0120, 0x057a4108, + 0x6e08664d, 0x17421478, 0x11af4208, 0x850c3f42, 0x08234f0c, 0x4321eb4a, 0xf3451095, 0x0f394e0f, 0x4310eb45, 0xc09707b1, 0x54431782, 0xaec08d1d, + 0x0f434dbb, 0x9f0c0b45, 0x0a3b4dbb, 0x4618bdc7, 0x536032eb, 0x17354213, 0x4d134169, 0xc7a30c2f, 0x4e254342, 0x174332cf, 0x43cdae17, 0x6b4706e4, + 0x0e16430d, 0x530b5542, 0x2f7c26bb, 0x13075f31, 0x43175342, 0x60181317, 0x6550114e, 0x28624710, 0x58070021, 0x59181683, 0x2d540cf5, 0x05d5660c, + 0x20090c7b, 0x0e157e02, 0x8000ff2b, 0x14000080, 0x80ff8000, 0x27137e03, 0x336a4b20, 0x0f817107, 0x13876e18, 0x730f2f7e, 0x2f450b75, 0x6d02200b, + 0x6d66094c, 0x4b802009, 0x15820a02, 0x2f45fe20, 0x5e032006, 0x00202fd9, 0x450af741, 0xeb412e0f, 0x0ff3411f, 0x420a8b65, 0xf7410eae, 0x1c664810, + 0x540e1145, 0xbfa509f3, 0x42302f58, 0x80200c35, 0xcb066c47, 0x4b1120c1, 0x41492abb, 0x34854110, 0xa7097b72, 0x251545c7, 0x4b2c7f56, 0xc5b40bab, + 0x940cd54e, 0x2e6151c8, 0x09f35f18, 0x4b420420, 0x09677121, 0x8f24f357, 0x1b5418e1, 0x08915a1f, 0x3143d894, 0x22541805, 0x1b9b4b0e, 0x8c0d3443, + 0x1400240d, 0x18ff8000, 0x582e6387, 0xf99b2b3b, 0x8807a550, 0x17a14790, 0x2184fd20, 0x5758fe20, 0x2354882c, 0x15000080, 0x5e056751, 0x334c2c2f, + 0x97c58f0c, 0x1fd7410f, 0x0d4d4018, 0x4114dc41, 0x04470ed6, 0x0dd54128, 0x00820020, 0x02011523, 0x22008700, 0x86480024, 0x0001240a, 0x8682001a, + 0x0002240b, 0x866c000e, 0x8a03200b, 0x8a042017, 0x0005220b, 0x22218614, 0x84060000, 0x86012017, 0x8212200f, 0x250b8519, 0x000d0001, 0x0b850031, + 0x07000224, 0x0b862600, 0x11000324, 0x0b862d00, 0x238a0420, 0x0a000524, 0x17863e00, 0x17840620, 0x01000324, 0x57820904, 0x0b85a783, 0x0b85a785, + 0x0b85a785, 0x22000325, 0x85007a00, 0x85a7850b, 0x85a7850b, 0x22a7850b, 0x82300032, 0x00342201, 0x0805862f, 0x35003131, 0x54207962, 0x74736972, + 0x47206e61, 0x6d6d6972, 0x65527265, 0x616c7567, 0x58545472, 0x6f725020, 0x43796767, 0x6e61656c, 0x30325454, 0x822f3430, 0x35313502, 0x79006200, + 0x54002000, 0x69007200, 0x74007300, 0x6e006100, 0x47200f82, 0x6d240f84, 0x65006d00, 0x52200982, 0x67240582, 0x6c007500, 0x72201d82, 0x54222b82, + 0x23825800, 0x19825020, 0x67006f22, 0x79220182, 0x1b824300, 0x3b846520, 0x1f825420, 0x41000021, 0x1422099b, 0x0b410000, 0x87088206, 0x01012102, + 0x78080982, 0x01020101, 0x01040103, 0x01060105, 0x01080107, 0x010a0109, 0x010c010b, 0x010e010d, 0x0110010f, 0x01120111, 0x01140113, 0x01160115, + 0x01180117, 0x011a0119, 0x011c011b, 0x011e011d, 0x0020011f, 0x00040003, 0x00060005, 0x00080007, 0x000a0009, 0x000c000b, 0x000e000d, 0x0010000f, + 0x00120011, 0x00140013, 0x00160015, 0x00180017, 0x001a0019, 0x001c001b, 0x001e001d, 0x08bb821f, 0x22002142, 0x24002300, 0x26002500, 0x28002700, + 0x2a002900, 0x2c002b00, 0x2e002d00, 0x30002f00, 0x32003100, 0x34003300, 0x36003500, 0x38003700, 0x3a003900, 0x3c003b00, 0x3e003d00, 0x40003f00, + 0x42004100, 0x4b09f382, 0x00450044, 0x00470046, 0x00490048, 0x004b004a, 0x004d004c, 0x004f004e, 0x00510050, 0x00530052, 0x00550054, 0x00570056, + 0x00590058, 0x005b005a, 0x005d005c, 0x005f005e, 0x01610060, 0x01220121, 0x01240123, 0x01260125, 0x01280127, 0x012a0129, 0x012c012b, 0x012e012d, + 0x0130012f, 0x01320131, 0x01340133, 0x01360135, 0x01380137, 0x013a0139, 0x013c013b, 0x013e013d, 0x0140013f, 0x00ac0041, 0x008400a3, 0x00bd0085, + 0x00e80096, 0x008e0086, 0x009d008b, 0x00a400a9, 0x008a00ef, 0x008300da, 0x00f20093, 0x008d00f3, 0x00880097, 0x00de00c3, 0x009e00f1, 0x00f500aa, + 0x00f600f4, 0x00ad00a2, 0x00c700c9, 0x006200ae, 0x00900063, 0x00cb0064, 0x00c80065, 0x00cf00ca, 0x00cd00cc, 0x00e900ce, 0x00d30066, 0x00d100d0, + 0x006700af, 0x009100f0, 0x00d400d6, 0x006800d5, 0x00ed00eb, 0x006a0089, 0x006b0069, 0x006c006d, 0x00a0006e, 0x0071006f, 0x00720070, 0x00750073, + 0x00760074, 0x00ea0077, 0x007a0078, 0x007b0079, 0x007c007d, 0x00a100b8, 0x007e007f, 0x00810080, 0x00ee00ec, 0x6e750eba, 0x646f6369, 0x78302365, + 0x31303030, 0x32200e8d, 0x33200e8d, 0x34200e8d, 0x35200e8d, 0x36200e8d, 0x37200e8d, 0x38200e8d, 0x39200e8d, 0x61200e8d, 0x62200e8d, 0x63200e8d, + 0x64200e8d, 0x65200e8d, 0x66200e8d, 0x31210e8c, 0x8d0e8d30, 0x8d3120ef, 0x8d3120ef, 0x8d3120ef, 0x8d3120ef, 0x8d3120ef, 0x8d3120ef, 0x8d3120ef, + 0x8d3120ef, 0x8d3120ef, 0x8d3120ef, 0x8d3120ef, 0x8d3120ef, 0x8d3120ef, 0x66312def, 0x6c656406, 0x04657465, 0x6f727545, 0x3820ec8c, 0x3820ec8d, + 0x3820ec8d, 0x3820ec8d, 0x3820ec8d, 0x3820ec8d, 0x3820ec8d, 0x3820ec8d, 0x3820ec8d, 0x3820ec8d, 0x3820ec8d, 0x3820ec8d, 0x3820ec8d, 0x3820ec8d, + 0x3820ec8d, 0x200ddc41, 0x0ddc4139, 0xef8d3920, 0xef8d3920, 0xef8d3920, 0xef8d3920, 0xef8d3920, 0xef8d3920, 0xef8d3920, 0xef8d3920, 0xef8d3920, + 0xef8d3920, 0xef8d3920, 0xef8d3920, 0xef8d3920, 0xef8d3920, 0x00663923, 0x48fa0500, 0x00f762f9, +}; + //----------------------------------------------------------------------------- //---- Include imgui_user.inl at the end of imgui.cpp diff --git a/imgui.h b/imgui.h index 9248ae448..b4cb11c4f 100644 --- a/imgui.h +++ b/imgui.h @@ -1,4 +1,4 @@ -// ImGui library v1.20 +// ImGui library v1.30 wip // See .cpp file for commentary. // See ImGui::ShowTestWindow() for sample code. // Read 'Programmer guide' in .cpp for notes on how to setup ImGui in your codebase. @@ -129,7 +129,7 @@ public: // - struct ImGuiTextBuffer // Text buffer for logging/accumulating text // - struct ImGuiStorage // Custom key value storage (if you need to alter open/close states manually) // - struct ImDrawList // Draw command list -// - struct ImFont // Bitmap font loader +// - struct ImFont // TTF font loader, bake glyphs into bitmap // ImGui End-user API // In a namespace so that user can add extra functions (e.g. Value() helpers for your vector or common types) @@ -309,7 +309,6 @@ namespace ImGui IMGUI_API float GetTime(); IMGUI_API int GetFrameCount(); IMGUI_API const char* GetStyleColName(ImGuiCol idx); - IMGUI_API void GetDefaultFontData(const void** fnt_data, unsigned int* fnt_size, const void** png_data, unsigned int* png_size); IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); } // namespace ImGui @@ -729,97 +728,59 @@ struct ImDrawList IMGUI_API void AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f); }; -// Bitmap font data loader & renderer into vertices -// Using the .fnt format exported by BMFont -// - tool: http://www.angelcode.com/products/bmfont -// - file-format: http://www.angelcode.com/products/bmfont/doc/file_format.html -// Assume valid file data (won't handle invalid/malicious data) -// Handle a subset of the options, namely: -// - kerning pair are not supported (because some ImGui code does per-character CalcTextSize calls, need to turn it into something more state-ful to allow for kerning) +// TTF font loading and rendering +// NB: kerning pair are not supported (because some ImGui code does per-character CalcTextSize calls, need to turn it into something more state-ful to allow for kerning) struct ImFont { - struct FntInfo; - struct FntCommon; - struct FntGlyph; - struct FntKerning; - // Settings - float Scale; // = 1.0f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() - ImVec2 DisplayOffset; // = (0.0f,0.0f) // Offset font rendering by xx pixels - ImVec2 TexUvForWhite; // = (0.0f,0.0f) // Font texture must have a white pixel at this UV coordinate. Adjust if you are using custom texture. - ImWchar FallbackChar; // = '?' // Replacement glyph is one isn't found. + float Scale; // = 1.0f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() + ImVec2 DisplayOffset; // = (0.0f,0.0f) // Offset font rendering by xx pixels + ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. - // Data - unsigned char* Data; // Raw data, content of .fnt file - size_t DataSize; // - bool DataOwned; // - const FntInfo* Info; // (point into raw data) - const FntCommon* Common; // (point into raw data) - const FntGlyph* Glyphs; // (point into raw data) - size_t GlyphsCount; // - const FntKerning* Kerning; // (point into raw data) - NB: kerning is unsupported - size_t KerningCount; // - ImVector Filenames; // (point into raw data) - ImVector IndexLookup; // (built) - const FntGlyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) + // Texture data + unsigned char* TexPixels; // 1 byte, 1 component per pixel. Total byte size of TexWidth * TexHeight + int TexWidth; + int TexHeight; + ImVec2 TexExtraDataPos; // Position of our rectangle where we draw non-font graphics + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel (part of the TexExtraData block) + struct Glyph + { + ImWchar Codepoint; + signed short XAdvance; + signed short Width, Height; + signed short XOffset, YOffset; + float U0, V0, U1, V1; // Texture coordinates + }; + + // Runtime data + float FontSize; // Height of characters + ImVector Glyphs; + ImVector IndexLookup; + const Glyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) + + // Methods IMGUI_API ImFont(); - IMGUI_API ~ImFont() { Clear(); } + IMGUI_API ~ImFont(); + IMGUI_API bool LoadDefault(); + IMGUI_API bool LoadFromFileTTF(const char* filename, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); + IMGUI_API bool LoadFromMemoryTTF(const void* data, size_t data_size, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); + IMGUI_API void Clear(); + IMGUI_API void BuildLookupTable(); + IMGUI_API const Glyph* FindGlyph(unsigned short c) const; + IMGUI_API bool IsLoaded() const { return TexPixels != NULL && !Glyphs.empty(); } - IMGUI_API bool LoadFromMemory(const void* data, size_t data_size); - IMGUI_API bool LoadFromFile(const char* filename); - IMGUI_API void Clear(); - IMGUI_API void BuildLookupTable(); - IMGUI_API const FntGlyph* FindGlyph(unsigned short c) const; - IMGUI_API bool IsLoaded() const { return Info != NULL && Common != NULL && Glyphs != NULL; } + // Retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) + static IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + a few more + static IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + static IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Japanese + full set of about 21000 CJK Unified Ideographs // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. - IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 - IMGUI_API ImVec2 CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL) const; // wchar - IMGUI_API void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawVert*& out_vertices, float wrap_width = 0.0f) const; - - IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; - -#pragma pack(push, 1) - struct FntInfo - { - signed short FontSize; - unsigned char BitField; // bit 0: smooth, bit 1: unicode, bit 2: italic, bit 3: bold, bit 4: fixedHeight, bits 5-7: reserved - unsigned char CharSet; - unsigned short StretchH; - unsigned char AA; - unsigned char PaddingUp, PaddingRight, PaddingDown, PaddingLeft; - unsigned char SpacingHoriz, SpacingVert, Outline; - //char FontName[]; - }; - - struct FntCommon - { - unsigned short LineHeight, Base; - unsigned short ScaleW, ScaleH; - unsigned short Pages; - unsigned char BitField; - unsigned char Channels[4]; - }; - - struct FntGlyph - { - unsigned int Id; - unsigned short X, Y, Width, Height; - signed short XOffset, YOffset; - signed short XAdvance; - unsigned char Page; - unsigned char Channel; - }; - - struct FntKerning - { - unsigned int IdFirst; - unsigned int IdSecond; - signed short Amount; - }; -#pragma pack(pop) + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 + IMGUI_API ImVec2 CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL) const; // wchar + IMGUI_API void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawVert*& out_vertices, float wrap_width = 0.0f) const; + IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; }; //---- Include imgui_user.h at the end of imgui.h diff --git a/stb_rect_pack.h b/stb_rect_pack.h new file mode 100644 index 000000000..dcc9d8877 --- /dev/null +++ b/stb_rect_pack.h @@ -0,0 +1,546 @@ +// stb_rect_pack.h - v0.05 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Version history: +// +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF void stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight, +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#include + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +enum +{ + STBRP__INIT_skyline = 1, +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height < c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + stbrp_node *L1 = NULL, *L2 = NULL; + int count=0; + cur = context->active_head; + while (cur) { + L1 = cur; + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + L2 = cur; + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int rect_height_compare(const void *a, const void *b) +{ + stbrp_rect *p = (stbrp_rect *) a; + stbrp_rect *q = (stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int rect_width_compare(const void *a, const void *b) +{ + stbrp_rect *p = (stbrp_rect *) a; + stbrp_rect *q = (stbrp_rect *) b; + if (p->w > q->w) + return -1; + if (p->w < q->w) + return 1; + return (p->h > q->h) ? -1 : (p->h < q->h); +} + +static int rect_original_order(const void *a, const void *b) +{ + stbrp_rect *p = (stbrp_rect *) a; + stbrp_rect *q = (stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF void stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + #ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); + #endif + } + + // sort according to heuristic + qsort(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + + // unsort + qsort(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags + for (i=0; i < num_rects; ++i) + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); +} +#endif diff --git a/stb_truetype.h b/stb_truetype.h new file mode 100644 index 000000000..2143164ab --- /dev/null +++ b/stb_truetype.h @@ -0,0 +1,2646 @@ +// stb_truetype.h - v1.02 - public domain +// authored from 2009-2014 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// David Gow +// David Given +// Ivan-Assen Ivanov +// Anthony Pesch +// Johan Duparc +// Hou Qiming +// Fabian "ryg" Giesen +// +// Misc other: +// Ryan Gordon +// +// VERSION HISTORY +// +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// +// LICENSE +// +// This software is in the public domain. Where that dedication is not +// recognized, you are granted a perpetual, irrevokable license to copy +// and modify this file as you see fit. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversample() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- use for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLstbtt_uint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(data,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_sort() to override this to avoid qsort + #ifndef STBTT_sort + #include + #define STBTT_sort(data,num_items,item_size,compare_func) qsort(data,num_items,item_size,compare_func) + #endif + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +extern void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; + +extern int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is weight x height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is // the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +extern void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +extern int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_char_in_range; + int num_chars_in_range; + stbtt_packedchar *chardata_for_range; // output +} stbtt_pack_range; + +extern int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. + +extern int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +extern int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Those functions are called by stbtt_PackFontRanges(). If you want to +// pack multiple fonts or custom data into a same texture, you may copy +// the contents of stbtt_PackFontRanges() and create a custom version +// using those functions. + +extern void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s). The default (no oversampling) is achieved by +// h_oversample=1, v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts + +extern void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +extern int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. You can just skip +// this step if you know it's that kind of font. + + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +typedef struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph +} stbtt_fontinfo; + +extern int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +extern float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +extern float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +extern void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +extern void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +extern void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +extern int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +extern int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +extern void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +extern int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +extern int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy; + unsigned char type,padding; + } stbtt_vertex; +#endif + +extern int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +extern int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +extern int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +extern void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +extern void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +extern unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +extern unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +extern void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +extern void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +extern void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +extern void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +extern unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +extern unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +extern void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +extern void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +extern void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +extern void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +extern void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata); + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +extern int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +extern int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +extern const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE) + + #define ttUSHORT(p) (* (stbtt_uint16 *) (p)) + #define ttSHORT(p) (* (stbtt_int16 *) (p)) + #define ttULONG(p) (* (stbtt_uint32 *) (p)) + #define ttLONG(p) (* (stbtt_int32 *) (p)) + +#else + + stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#endif + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(const stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*14); + } + } + return -1; +} + +int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart) +{ + stbtt_uint8 *data = (stbtt_uint8 *) data2; + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx) + return 0; + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + stbtt_uint16 item, offset, start, end; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + searchRange >>= 1; + start = ttUSHORT(data + search + searchRange*2 + segcount*2 + 2); + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + end = ttUSHORT(data + index_map + 14 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + return 1; +} + +int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0,y0,x1,y1; + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + +typedef struct stbtt__active_edge +{ + int x,dx; + float ey; + struct stbtt__active_edge *next; + int valid; +} stbtt__active_edge; + +#define FIXSHIFT 10 +#define FIX (1 << FIXSHIFT) +#define FIXMASK (FIX-1) + +static stbtt__active_edge *new_active(stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) STBTT_malloc(sizeof(*z), userdata); // @TODO: make a pool of these!!! + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(e->y0 <= start_point); + if (!z) return z; + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = -STBTT_ifloor(FIX * -dxdy); + else + z->dx = STBTT_ifloor(FIX * dxdy); + z->x = STBTT_ifloor(FIX * (e->x0 + dxdy * (start_point - e->y0))); + z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->valid = e->invert ? 1 : -1; + return z; +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->valid; + } else { + int x1 = e->x; w += e->valid; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> FIXSHIFT; + int j = x1 >> FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((FIX - (x0 & FIXMASK)) * max_weight) >> FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & FIXMASK) * max_weight) >> FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->valid); + z->valid = 0; + STBTT_free(z, userdata); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = new_active(e, off_x, scan_y, userdata); + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + while (active) { + stbtt__active_edge *z = active; + active = active->next; + STBTT_free(z, userdata); + } + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +static int stbtt__edge_compare(const void *p, const void *q) +{ + stbtt__edge *a = (stbtt__edge *) p; + stbtt__edge *b = (stbtt__edge *) q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; + int vsubsample = result->h < 8 ? 15 : 5; + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +// returns number of contours +stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +typedef struct +{ + stbrp_coord x,y; + int id,w,h,was_packed; +} stbrp_rect; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + for (j=0; j < h; ++j) { + int i; + unsigned int total; + memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + for (j=0; j < w; ++j) { + int i; + unsigned int total; + memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + for (j=0; j < ranges[i].num_chars_in_range; ++j) { + int x0,y0,x1,y1; + int glyph = stbtt_FindGlyphIndex(info,ranges[i].first_unicode_char_in_range + j); + if (glyph) { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + } else { + rects[k].was_packed = false; + } + ++k; + } + } + + return k; +} + +// rects array must be big enough to accommodate all characters in the given ranges +int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + float recip_h = 1.0f / spc->h_oversample; + float recip_v = 1.0f / spc->v_oversample; + float sub_x = stbtt__oversample_shift(spc->h_oversample); + float sub_y = stbtt__oversample_shift(spc->v_oversample); + int i,j,k, return_value = 1; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + for (j=0; j < ranges[i].num_chars_in_range; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int glyph = stbtt_FindGlyphIndex(info, ranges[i].first_unicode_char_in_range + j); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + return return_value; +} + +int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars_in_range; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars_in_range; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbrp_pack_rects(context, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + return return_value; +} + +int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_char_in_range = first_unicode_char_in_range; + range.num_chars_in_range = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#endif // STB_TRUETYPE_IMPLEMENTATION From f77490cb2d9c522bfd4c58c139e782c64b59d2e1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 8 Jan 2015 23:49:17 +0000 Subject: [PATCH 02/41] Cleanup extra_fonts/ folder --- examples/directx9_example/main.cpp | 1 - extra_fonts/Karla-Regular.ttf | Bin 0 -> 16848 bytes extra_fonts/ProggyClean.ttf | Bin 0 -> 41208 bytes extra_fonts/ProggyClean.zip | Bin 4825 -> 0 bytes extra_fonts/ProggySmall.zip | Bin 4238 -> 0 bytes extra_fonts/ProggyTiny.ttf | Bin 0 -> 35656 bytes extra_fonts/README.txt | 112 ++++++----------------------- extra_fonts/courier_new_16.fnt | Bin 3906 -> 0 bytes extra_fonts/courier_new_16.png | Bin 1393 -> 0 bytes extra_fonts/courier_new_18.fnt | Bin 3906 -> 0 bytes extra_fonts/courier_new_18.png | Bin 2655 -> 0 bytes extra_fonts/mplus-2m-medium_18.fnt | Bin 119711 -> 0 bytes extra_fonts/mplus-2m-medium_18.png | Bin 291077 -> 0 bytes extra_fonts/proggy_clean_13.fnt | Bin 4647 -> 0 bytes extra_fonts/proggy_clean_13.png | Bin 1557 -> 0 bytes extra_fonts/proggy_small_12.fnt | Bin 4647 -> 0 bytes extra_fonts/proggy_small_12.png | Bin 949 -> 0 bytes extra_fonts/proggy_small_14.fnt | Bin 4647 -> 0 bytes extra_fonts/proggy_small_14.png | Bin 949 -> 0 bytes imgui.h | 2 +- 20 files changed, 22 insertions(+), 93 deletions(-) create mode 100644 extra_fonts/Karla-Regular.ttf create mode 100644 extra_fonts/ProggyClean.ttf delete mode 100644 extra_fonts/ProggyClean.zip delete mode 100644 extra_fonts/ProggySmall.zip create mode 100644 extra_fonts/ProggyTiny.ttf delete mode 100644 extra_fonts/courier_new_16.fnt delete mode 100644 extra_fonts/courier_new_16.png delete mode 100644 extra_fonts/courier_new_18.fnt delete mode 100644 extra_fonts/courier_new_18.png delete mode 100644 extra_fonts/mplus-2m-medium_18.fnt delete mode 100644 extra_fonts/mplus-2m-medium_18.png delete mode 100644 extra_fonts/proggy_clean_13.fnt delete mode 100644 extra_fonts/proggy_clean_13.png delete mode 100644 extra_fonts/proggy_small_12.fnt delete mode 100644 extra_fonts/proggy_small_12.png delete mode 100644 extra_fonts/proggy_small_14.fnt delete mode 100644 extra_fonts/proggy_small_14.png diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index a35206db8..f826d8c9b 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -218,7 +218,6 @@ void InitImGui() io.Font = new ImFont(); io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); - io.Font->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 20.0f, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; IM_ASSERT(io.Font->IsLoaded()); diff --git a/extra_fonts/Karla-Regular.ttf b/extra_fonts/Karla-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..81b3de6eb12d7da48b307dfcb754d6595cd09a3f GIT binary patch literal 16848 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 D@s=Uh diff --git a/extra_fonts/ProggySmall.zip b/extra_fonts/ProggySmall.zip deleted file mode 100644 index f7e267184590d49cdafbe8be688c27b310def6d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4238 zcmZ{o2QVC5yT=g;qIZ%NHPMCWEeY03qW6}K=-p~7qP|gATOlEOw1_O0C5tEDiAVv8;(tnRx%uBB zBGRWLBBH%M)%O8AIlVFRuyJ=6a|C3(YV2|LQ!d&;d9H{Cp;c^WJ_z)xJ9;jRN`1w-xPCl}PJ(ND%dhZgUyd7`^JWTCN~hv{EoLK z`!~;*HTEwQHj902w%$)`4PhfnQ(E{rr;$1)+n@C=aAdcU;mNHRghE>(BCR!)2dqbnD)`9+@@XXFg%{hy0+^XZqO8{~ zCvW19`-14!*LZf>!SD54pt`$a<5+H`aJGoa#2aiK5>oqjbQh%NI)mzwJ>IQ-$hC}Pp9>(iU7^_Hf+N_us7 z^>C1Y9jUaG7oY;G(`!q!i@p80Z32Jq5KnE&I>6af$9Xv!NvD?=sVoP{VeQkHsue4Z%RFJg>iB`xoaq)rcvx? z%lK!5M!v=%zXux@6`eFXx->d^#>azYKXyt+W+3iwg+g75#Pk*%4fOS8-GtAcV$q-JQ z#sixW6*5nT+-3MzJC@BRrm{+OnSjSeeVVg(n^-af)0(??YX=O~`-ZdA8x`d9Ew}1R zEX1sHb8<3W=|J^Tl9I|HFIE^NHMX6;r@Otgs(z!iBL#6}IVjCQS^D)JPOwY$-Igxs zI`jkCxw`tB?y^dIlpVTB@eCwr9&c9Ndh7O_%ckqWD>8G2$zm^F*y}AABN~pVK9Z=6 zGf1`$u`##v9rptz>#3u7o#Ty5H(5W!bxnAA1S%q6V{;iVf*g_4z{dPzAe`V%F;NiOZ{#7rE4qdijAPYMhLFD#L+ zQwqQbP6Z!sYLC^%fM~6<8=+Tao5$}CC?r0!7y(!0Is4Z#93jOe^dT^#7xjA2tr~EJ zXjr|8`ex}yEoxN*xLw-tP5XX^f&$y8qM2cd6IKySaL_Lw7xgpcZyH)>-<()T4}v~_ z5k;e?8>@%h)S7C}n!jVyOJs6ZJ~}=9F$&O>2x{;!Iaty(-5`Bt>$46y}= z^wGJ79YHbqqo%PKwaAYof)oP;x6z#R#mRVC!NA|r-2WbGN7}*yBWUomAH@ZGFE?DR zsXuzTZQ34H1=UDaAC5C`ZiE$<@)Kw_BZkKhKcBl-StVya?CUR8Vx0ga69v7;2N zu>6kqOA6cayKdD77Pgh$#c~iiSw?z!k4x|T*AsJ=St1xOYVXKVZvZzpy*CuVKpz8z zYC<(XXhuRcHG#&OFSvQUvqR=jbEZJMAI?j!HXu*7t9J*S<}yOSpdZJthxj7Jz1CoK z{83_P`~^cHWa_J_1Zpn}qbmamIW)Hc@8&7%Z_XS2Le7ir1Y)0pT_*CH(5EOv!Ycx< z>e3Z#jU<$}q{g#~>a9)gwMLwArxLH^oVh8fJU|P?b6?FGNIx1`EjuGtTeej`r$_1o zMRSB?M(SzyIQ>8CXH&SaZdltkRYG+(nT||#@ zO8VoR*3%7z;3TRI+i81&K)jRV2ux z%O@{{06M?O!5HP@EA}|hm;w9^N%Or|y5{C_E9MlmZbMOh>n&RXcI3QlZS;VSHMuC? zI;b$7Sk{ZumiVlYI;piqXc<*0=rTS-RxxaEL*b*UjS#!z90-s+P@?m z9J9MGdDTqCBC>R4Czxh;U#U!;Ri==vqA-Cw;T4sJ7t<#!Q~x3lVu5U?Sri&mM{sT{ z^s2o5rc5oeT%drKnF>qyxSc<}ovD9;1l=M1JJRJhMPbxMlD`s6w=6p8qd~L(wmOAL zW4u9dBt>xadpv_R_7MVWHq$QB+c6X7xA3gaJc8(Mhz*00i+<>J=Nj;4 z`@ErT9#XYF1mr(n;4IzIvh7^ZOY_CS^tUz63a&!en>cLS%_G7Mz zsv`x`f-Lcf0!&LP{E~cZXsR}HReWF1clyyyd!)O3-2!u&U$Sd?#c$`*TDY@-G5f6> z8QrV}DSSrH43k4Z8F~EnIYV=d1M6Lq#-&rZNXg>ghZ2mKT1d_sruKM}JpvePov0~P z537aS(5x$s3WGpm!oc{TagQO$n23t&OtosowC7JW(8%bE-4w3grYjpLB4iDhhS{Nh z9ciZU@s6mjv6gN_m>A!v1oKfWqlVlb7*q$C`ZsYzi}VKfLB?O;4zb$#^WyG@@jl1`acA)57#XL>7z9?Bo1XTZzhTMj4}2WgU`s z5+&l|s*r(?D@@G{24FqS@17{vFol@#9v+V7*nr?i(7ZI4nlNCL>N(1GVmO4{uODb4 zQz`UAS?O3jDwR3^@y|f%q5$)CLkdBmgYb=wSGqYOKEN1IW@m{Hiq&Tio0h z&p@<&zxmKAuz#lh92zugp{v~Mll!B=5liQ(VK&A3dSFfFM1K|}e6c=mmY6Nm^omU# zG;QL?9XH+|)H*3VP21k&bGBv>v-QUfLf(8BC&e4w*Jf%AzrDScN}wg1Bg>dZq-`k2 z7vO#6iAM2+)~9_ujg6a5<2)?Sc|Fzd#9G?Ro0?*V#?nH_##CJ3XYft;M{dd46`8qa z7GgDz??HY@6cY2~9Q?BC$H5uG)?k2P)RDrq zniPdRJRLZ#Wm>Tbu9y!@Bq(0~jX}&m5+r+&ST#=)A|i28A|lFb25Gz4Ie6MRi1`Kh zfefv|zr^XnDn@s`mcFJIATnz{Hf6N`iFyZw31TJqX!eNQi8#M{_7?4@V6dK-giYldk?zHUwibYb28I33OA8(- zl+Vl}X~FU+CFd$X;QB-X@Nfz|ACnm0Xyi=fWLT#%*c~9w%FQg#C6AIvo0Gjv;+0@K z;;2S9#7h?W6huW&#}(ilDlr4kywoqJbOgTg7FX9*mEf&v-zrGWO?!W1jT`^xhntTxlM_blWoq^&T;o{UO1429pj0s5x!?tg4ug7WWd72D@u7EsV)B5 zn2={>a7dcYBrR&styS~|!c&mIcMZkVC{yZBV&S~TVAm_$eXNKEBD49LOu1pi`dY*! zOqBnB{eJy7uLsw^*8fw@|2_CWIjjGU`YX8bFTeG_t$@GztiNmgr_A_EgZN(;;HUma Yp6F}cycQ)Qx^X@0uXprc?FOR%0&Dsy{r~^~ diff --git a/extra_fonts/ProggyTiny.ttf b/extra_fonts/ProggyTiny.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1c4312c3dbaa81babf38bac992e3722e15eedb73 GIT binary patch literal 35656 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 aWGe4uD(_?}?_?_PWGe4uD(~cOqW=eI(CQrk literal 0 HcmV?d00001 diff --git a/extra_fonts/README.txt b/extra_fonts/README.txt index 523996285..3ff694cf7 100644 --- a/extra_fonts/README.txt +++ b/extra_fonts/README.txt @@ -1,100 +1,30 @@ -Extra fonts for ImGui. -THOSE FONTS ARE OPTIONAL. +--------------------------------- + EXTRA FONTS FOR IMGUI +--------------------------------- -ImGui embeds a copy of 'proggy_clean' that you can use without any external files. -Export your own font with bmfont (www.angelcode.com/products/bmfont). +ImGui embeds a copy of 'ProggyClean.ttf' that you can use without any external files. -bmfont reads fonts (.ttf, .fon, etc.) and output a .fnt file and a texture file, e.g: +Load .TTF file with: - proggy_clean.fon --> [bmfont] ---> proggy_clean_13.fnt - proggy_clean_13.png + ImGuiIO& io = ImGui::GetIO(); + io.Font = new ImFont(); + io.Font->LoadFromFileTTF("myfontfile.ttf", size_pixels); + +Add a third parameter to bake specific font ranges: -If you need a free font that supports chinese/japanese characters, you can use the M+ fonts. -TTF and sources are availables at http://mplus-fonts.sourceforge.jp/mplus-outline-fonts. -This directory include some of the M+ fonts converted by bmfont. + io.Font->LoadFromFileTTF("myfontfile.ttf", size_pixels, ImFont::GetGlyphRangesDefault()); // Basic Latin, Extended Latin + io.Font->LoadFromFileTTF("myfontfile.ttf", size_pixels, ImFont::GetGlyphRangesJapanese()); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + io.Font->LoadFromFileTTF("myfontfile.ttf", size_pixels, ImFont::GetGlyphRangesChinese()); // Japanese + full set of about 21000 CJK Unified Ideographs -//----------------------------------------------------------------------------- +Offset font by altering the io.Font->DisplayOffset value: -Configure bmfont: + io.Font->DisplayOffset.y += 1; // Render 1 pixel down - - Export .fnt as Binary - - Output .png, 32-bits (or whatever is suitable for your loader/renderer) - - Tip: uncheck "Render from TrueType outline" and "Font Smoothing" for best result with non-anti-aliased type fonts. - But you can experiment with other settings if you want anti-aliased fonts. - - Tip: use pngout.exe (http://advsys.net/ken/utils.htm) to further reduce the file size of .png files - All files in this folder have been optimised with pngout.exe +----------------------------------- + RECOMMENDED SIZES +----------------------------------- ------------------------------------------------------------------------------ - -(A) Use font data embedded in ImGui - - // Access embedded font data - const void* fnt_data; // pointer to FNT data - unsigned fnt_size; // size of FNT data - const void* png_data; // pointer to PNG data - unsigned int png_size; // size of PNG data - ImGui::GetDefaultFontData(&fnt_data, &fnt_size, &png_data, &png_size); - - 1. Load the .FNT data from 'fnt_data' (NB: this is done for you by default if you don't do anything) - - ImGuiIO& io = ImGui::GetIO(); - io.Font = new ImFont(); - io.Font->LoadFromMemory(fnt_data, fnt_size); - - 2. Load the .PNG data from 'png_data' into a texture - -//----------------------------------------------------------------------------- - -(B) Use fonts from external files - - You need to set io.Font->TexUvForWhite to UV coordinates pointing to a white pixel in the texture. - You can either locate a white pixel manually or use code at runtime to find or write one. - The OpenGL example include sample code to find a white pixel given an uncompressed 32-bits texture: - - // Automatically find white pixel from the texture we just loaded - // (io.Font->TexUvForWhite needs to contains UV coordinates pointing to a white pixel in order to render solid objects) - for (int tex_data_off = 0; tex_data_off < tex_x*tex_y; tex_data_off++) - if (((unsigned int*)tex_data)[tex_data_off] == 0xffffffff) - { - io.Font->TexUvForWhite = ImVec2((float)(tex_data_off % tex_x)/(tex_x), (float)(tex_data_off / tex_x)/(tex_y)); - break; - } - - 1. Load the .FNT data, e.g. - - ImGuiIO& io = ImGui::GetIO(); - - // proggy_clean_13 [default] - io.Font = new ImFont(); - io.Font->LoadFromFile("proggy_clean_13.fnt"); - IM_ASSERT(io.Font->IsLoaded()); - io.Font->TexUvForWhite = ImVec2(0.0f/256.0f,0.0f/128); - io.Font->DisplayOffset = ImVec2(0.0f, +1.0f); - - // proggy_small_12 - io.Font = new ImFont(); - io.Font->LoadFromFile("proggy_small_12.fnt"); - IM_ASSERT(io.Font->IsLoaded()); - io.Font->TexUvForWhite = ImVec2(84.0f/256.0f,20.0f/64); - io.Font->DisplayOffset = ImVec2(0.0f, +2.0f); - - // proggy_small_14 - io.Font = new ImFont(); - io.Font->LoadFromFile("proggy_small_14.fnt"); - IM_ASSERT(io.Font->IsLoaded()); - io.Font->TexUvForWhite = ImVec2(84.0f/256.0f,20.0f/64); - io.Font->DisplayOffset = ImVec2(0.0f, +3.0f); - - // courier_new_16 - io.Font->LoadFromFile("courier_new_16.fnt"); - io.Font->TexUvForWhite = ImVec2(1.0f/256.0f,4.0f/128); - - // courier_new_18 - io.Font->LoadFromFile("courier_new_18.fnt"); - io.Font->TexUvForWhite = ImVec2(4.0f/256.0f,5.0f/256); - - - 2. Load the matching .PNG data into a texture - -//----------------------------------------------------------------------------- + ProggyTiny.ttf Size: 10.0f Offset: Y: +1 + ProggyClean.ttf Size: 13.0f Offset: Y: +1 + diff --git a/extra_fonts/courier_new_16.fnt b/extra_fonts/courier_new_16.fnt deleted file mode 100644 index bd5c7fba35632c2c4dc049ecd0d6592a7ce8f293..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3906 zcmZA4eN5F=9LMo<0g)O5a&u$V8mSdg*(HjoED4S10iwbptsoJFG9XH!2bq6ZYt@>o z%~fkn?Lm6*AxpHv(lj%ve5%B(pe(Uev{ceG>3x2j-#Yi>_Vf4qzV7#&^F8OD-?>^) zTo@lY)fkgz^35a@;mJfIW=#FG#@gz}jFRdm6PFr%oXiI^ZGz2-AIe`<14$Ki)lC)I zxfRio4RupY!rl~PGWc}A8EfL{w!hz)Bx6#~#8S*Snx~lro~;ezK7_nMSe&!r+$WkG zlZe@&Y~Bd&3&~6H^3K8%UE474Y;3;tcTUj9z_{_jvq{b5ew4g8uay{TuEAb?=M!%w&(1e&djVD}^B9TsxiP|8F2p9gF~WI7vFD|2HdZOMT!f96wmDdt z*u~gd>F*M(Lo637kufgC4w+#w-$x#{McQ75Z5O*7J4kz8f=zWA@;P`wOEmi zTZEO0U56#hJg&!{lm2eNYNYLr*o%@^j5SMM31mPfA`XHdp$)8GAwU zZVB>Y*Ys9wfwV2dmPp%i*b1@RFuOK>eYX>@m%MUplh_?tr`ULGj;!eftjF!Ya8KTe zt;6j367H9~u$?Y1w7aocuD{SKu&G>r&6ULQoOS-bzXwZaUHo%nBDTua%wurYRdHV= z>o*B&jbo#66+Yv&V> zkXl~FjGVErA%n$U$MR%uZ(zw%^8zeI+O}fHsM-JReiO6jw!hAA1$j9!d2eGwrN4Ku z*{&8IgLh^j_tPbB5tbW#{{LCGnArYX!XI}DaXahc*Y|Fa6ML>L#a2t+d)QL3_c0^y z{<6RZ`m>Ko{F|nYdm8KF@4w~P$!<*txx5wJcQA(CFX7(!0P7O_5L+&_e1r{`b!o?{ zxcoI(No>zb-##X`XTEQr5YJ`||95p2wp;ebYOGuAQ*5T#8mwGwEtVnn8I~@#4m*v@ zukUl>ESc|mEL-dgEKh6$Hd?F$D-`<@%W`WFo|9i;QL(SFT(OPVD6vggv5c`98^Yz! zZ42>FT>gI9N^HmR?HgjdHokpJY`;(6?3rfY5#P2Ew^E<~E_7n{UH9|06VDO*p7;r| z9mFkSKM-fizW)(R60>tHlXdCBCWv)oM%w<29FhI<3wA(kC)O(VE7lH3i5iseat|6uuI$FL%?<5;QK XzgW5039Ql?k3lW{-0k=7zYG5XSjZd2 diff --git a/extra_fonts/courier_new_16.png b/extra_fonts/courier_new_16.png deleted file mode 100644 index 3e9f99e8e647fd97d7625c73f5d9b0b050bce951..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1393 zcmV-%1&;cOP)OKp0x zuFce|O(P_35qPExx zwTK{%=#4FcqrMui#$7k;3?F`T2F{{3JcDW`hYfO(HYNgV zQD4Tg<0ed?gwJWi@n_89f9E`)Y|UnpF(CBS$;qv{fyxmu(g^fc)d9xv82gqJ zl`=P(Ou|RIPixvw8ZhR17k$_F{bW*S6qt-L2nazd_OqoO#)T>90RwP>?a_7pitgJ& z&!(;YdI1GtG0`Viv=sk zL~1vqBA26}+b|4~(;Zx}Bw+Y~08i##P;?C)q4aR4@Pv0v)Kb9Ap60;Yp>U1{U0y$KwEW?BgnX%q->) z!ti?;)6>kA$`8k`jb-NUKypD)e+dv1fIz^shc%Hd@U9qS5F4rqOP{bTapeN>`}#-Z z%@LV*Ntb74+TMBZei9}eeZq0XUa@3={UkTn$z1&Oa-4mXbn=_HjT}3a%ZZBAx}^zm zZbjDIU`M%SpLK0Gtbry3^b{MB6KXteMiiL>#dalNlr@afHQ7P*f&QgK#^aqM4NHMD z%qhqk4cV;Ww-MHtFVBu$-8gvtz|Ffejk&pZlN3;b3NRB@7#@-FP{qKLhN&DYQrRw! z+QI-gsaDxVR2;UEl*&N;;BN~LKUhtHIh7hynEnNq#*VcEq7U1`42Z@BU6rr~5@I%p zTx?1yPz22THo*1n$yD?3D>!uT=GoG*cI)2F6BIl<|NpBn$2+;kjm-6PiR~JWL>ii@ z9f~xFvBngaH|#gPuz%^pb#){OYZK#ED0C7MDk+51u1rF4I*>83%+%1~3 z8~Nr0FCgS0^!52xbV?t)&@obIa#1aV3byfimxBL@o)N5U zq*fr;3_h(D$h8903e;Lb z4k(+3B}v>!>^SX42YtB$D|UVM<4woLIP-lOg{8Xsyp6`vrHw1GOtCRon$&j{mMw7^ zSe~?THTIktJ75knvGb+AvDjjn2o|o#c&v!|3)lQwtVHZOtWqosds^Df z#`0wx*JJiCp1JRTnuHu)rpX*kz^bK<8?Z%UH)1bJ*<5V7*iG13l#L7aP#(5a>YIqQ zO4*ySI*GdlYY>}+HHl5e;xXH<{j+;~3eR<<&~7E)DrNJreXcKluN7b&5_cQcDRw(H zPS)cN?0}5zPApNr^SiJjQn*G_$&Z$COv8-W-N<;cLaapkau0UU?Y~&w?Y!K}GsVUE zZ}2{Bi;MGfFdaK8ox~7^f^519zv$dyv)T;kiu`Zq%S|P?88_)DcozNRW*fH4~PhiHBbKfNzzbBtWcDp!l)z~vq-*Rl1i}QEw3T(f$QHxE-!aH;& zwm|G z95K81P8Vw?znc{9gU`tqFy8QN+KjD}dESC;6#D|(Dz+8dDfT6{M{FC`F7_36NX(uO zonqTDgN1$Gft)9M{A;XM_RvnOMC==^P;3{LC-yCtCH5VbBkS9WWk{Tz_hhl}F?)-K zR4Qmnm8EX}5$J)ex!8*kDW1V8Z zV)pC^_w#S$<76!lU_+VH@LoNL&5&_)V6(*zVP#^6vH4<0u&2a+#}>@TcQ>^Qbw>~Czd*a>XASQnNoefbAV6Z;p-5bMUW#CotivH!4q R*?%XoLW%3e>|BRu&i{iEEwlgt diff --git a/extra_fonts/courier_new_18.png b/extra_fonts/courier_new_18.png deleted file mode 100644 index d23c96cd8d48dc52f4e4ad25e49c76df7a75d79f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2655 zcmc&#dpHw*7yr)onrllpUBvozk>nOXiEIev6+@EisD6ZUiEgwTy8K=uCHGrOM$O1= z&25CpWaOvCgvuH*lgpN^ZLj`+|9{UP=bYy}=Q+=LKIb`~&wt!pkE^R}R{;R1JDsri z006RLZcS6C2{TbHx}5k6aMBpDq^%g))wixKLE( zB@M8z{xCzKPx%&$oU5bl71|&^MJb3_=1JfPch6HGkps2rauqrrM7gc>;LNR{oWO&i|u{w&mHLPxJ!lw^h$t<9Oei1EJJMph)5llonU3%`$S1Y#RqFcq;|+0*=!H7j)^ z@FJ=VAQENR<7nh5dM0F&B&4irw@oj78=LFp`#solV7dK+05T;pVhv&6{nWMzWj>w# zuCfpJMyL0v5j(Bori8HN*7RMid;7(2SHc!%Ri)pXPq6A9wyr%~G?W~ggu)J=>2GSU zGKf8uecSRoY6I*QRz}-7VKg$AO4Ygqm*=g>Q1f|iXWXwXt&CYQ9`I$&$#YdVXIc61 zuA0SjSw0l?{fsA;-k8WBDw*LXu8IlKpFLvYZO(i0YTXMV)!QJx<&i1Rzq_Umvx;n} zBO7H@Qg|d#efa8@^N`8-irQA#Zay8G{3vAjior4~I=sBDr_5z2^TGytL`Bh95$dYf z<(*lo_<^hvB$qWJ3~JH9zkL)Sfj{HtLy#VoCH+!&D9{@E)5jGk1@{5Fe2l*7-Zj!_ z5`7$NC){H33yy!_Wc_L#PYC{ar_T48sn|EmWdSd<>T+<_@vmJd!MqK`xTpOc*!g@FbdZpZ0hTL6m-@`s^{Qw?>v7U=%Q5m!pv@7 z=9Kb%Y6(ZP>CE~RXGM=_7lWa{W>zn0uqu7p-n)F3JZl|-+Az$#Ko0%r;;rCjX=)bX z&Lf=(NAf$&_u`f0QOqDwvA$U#7*}qUw7)@^j>HVmTf2F zB*_>E4ly|}rP*qHY+Yx{Li5ndeL&iqDb~eEY*8pFbPZ~7;?(I6BLYU9)l$Y^WQTuQ z3DPh>@0`OCPIXh-C;n+cM~uq)Fb>IIf|w4xflZV-E%>wA}gTY*P& zYVGR~Aiz4VeW+(CkB|Q(6-NKMD!;_hrlw6VVJG5Mhje4Ar(iNiYj?CiLsozZ}7i9rJmZCZ00$mPeA; z$Vi81n8BTe$Nk3kx21dRN7E_r#k4pjgil<3Gxv!eZO9S zsmq&+1o?f{)Yq|JTC=7Sn=3yC$`8;MMO<`X9&U$4*|lfqi<_r3U69NK3!_X|=yVt< z>{LlT-mJa_>g?W& zn}JxfkdRE&->|FGwU}tai*MYsmqsvuK-1)vsDZf2Qc{7A2trrc4$a%vvRJeay#Otp(HS%Ht=hV5T3>4wdX}1 zbmL?`7N?HE>u(X(J!jDSFto%*-YgP>}C1a zIb?J=c!eDW(F;zTDrLaQKnr0cg0NZ6yil;d6GoESO0s(Ul+3$0%t8psm|Jv?FUx5* w2k9Eyf8W)HN`IOFfD$16Z}+?B|7Hbz^g?Lzs+TEB_In2>2UmNlT~OkG0nR)6X#fBK diff --git a/extra_fonts/mplus-2m-medium_18.fnt b/extra_fonts/mplus-2m-medium_18.fnt deleted file mode 100644 index 24ef69ea028c10a01169288edffb94f4ab137446..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 119711 zcmZ794}4YgAII_gr&g_6!^+B3hRGVO3^SQbX0b9^!=$oCE5oFQQHEhMlTevqG0fCx z)ePe|HDNMjSS(hUgqaLe(eHKdx%ar=_wVuW_|4Pj`{#Vm_k6$S&-a|0G&R}nJj&s4 z9PVgzWILSv<#0M3Q^$^;aNX$ZuFjsn_&SGci1)vTI2?T(eLN9;`t)%R=l`$E%Uyia zNfWL+$qJl3K7MxW$$8h$bM%?~snaoiZa29eI&_#L$l(}r3^v^PAQtR!k)6jP zvFF{7x%)c$66yBZvDo>}JKaIVd5ZPK9mikixR1dG@s|~MJb%q`=ezp_#6@8#Vkcl> zj%O*iFXdWk$6)b}Sa%=tbNb3S5la$_#&)>}4wY?})k;I)&nFPu+N7dL9fjl+I)Eg-*v{AF!#EPqXPn{6;28$$c_ zp(KBuCt!(=<&^QCor0Y$Y2&b=Vy9w1J4%hTr(q!ycRJQjEFPQZD5B2)?V~fWhaGnt zmVga%Ji+$%XM3%-Iui@Fx5KiD*n85ZXJNNH){tKx{_5{-zeN7}NaD`MHcPpau)pj! z?dwf@4p!_k`%4mboy%8Dp<6FAQM1|0h{ zv2MG+Slf0bc9x6>v#_h3p7Ed`e_3&}`D-=Dc@Re>N2s^2&Ec=_7}u=vz#4O|!d|k| z_VdPNVP8vI&Bewr=JcfuC;xcHzijMdiMtwWl>Dy2-jic`9u{G@i(4Vujj8`sq+okZ(@1a=l1@! z(%y)@C$|3#$v7NFVw_xoOcPsXk)UN)nD{dB1(t+%ha_{*A8D)?)(%(ZK7Rt zhwbvuTQ6b_t`hpjfBT~v+hC9FR$X4gez5CeZO2;d0gkO8_M?AZSjS&8WUj5j4oVp> zV^`R5RvDglUu&O(ShgO!*xp{Nt!l9i)TIx7*}okduzInL*hlspZ{@cMyTH!R+KxJ` zT-xeY>?=F1wSVie*X;HS@s0{F~>tQG4P`waU_>~qZFSm=$r; zx&sT5v|nOF#lFHK*j{UYSY@|gu*z#5-Bv7?=#*6)o zrOCP8FW5}6F09b5i!~1YiWP`;V@sus->?#~eOQ^;@7Nl#9;{025A0>JKe0Nozp%IM zG0d8Kda>7Sv)c4;>`h6#ANxS;0M;dT5W7*@{vT|--T$n%`WO4#ooe3ghvirT&20>V(T^B3A zeps0FgZ|jN5;p*Am3|P6wMg7R>`RFogncG)gRu^Y3&D;d&YEYe`VGPE6br@Pv$xIK z-ovnu#SX_t+vhJ@7RaN*jT-OZy#-eQTQ)Hxk=xo7H}!uuPZP|3+iKN!&45uWeR-k=Qc& zZxAE5f8IY9d)PK>TssasWT&;tJsu0P%e5>D3zhlz1neEz-Z5C6-F{Zu6S2|KPouFI z*}r44PU%-MSf%8564onmCu6JaII9iDVL#jZ$g)^$vu#%W#$zXQ-V}HZHUWEIj>}W9 z!FIb?`Nd&jwpsa|iY=15oQ8R>K?3*3>6qtQAkgBml`krCuN+4{VVO4h)t6Hdp6c0ag(rLWuKpe{VA4&{UdfR7AbX}jPT-|Jm0NR{PzAEw|@sYkw@lhBJQz&ijk8)pnefHXng_*Laaiz z?N00!v3s$nB)|KydWl$km%drQ=9>$vOZL`{R1=b+;2zHq4w@0xT zByKgzZXJx^di%YDozvB#y1r!a@z|EzxXH1?g8QGtz-Icg2|7{_40 zfafzWWAPIA3ihPLt;a5txD8k@$4uaJrj1y*Jr-N_+k`EaZL7nk%X!4B*r0&>b=;{K z@;_{}-S@C+`y9kd`#x47`?49^Bl&%RZIQTb*e_xqVsG2$T-Nd1g8d}@;3KS8 z&fz}BGGq?;1iMzojqTWM$?sF_0lO|%xvkhDiTeyY&2D>ZjQt#2DC77S*!2?EhGj_H z4s4FZeTn@jabIB=m)!u{;?&d$2;gE>>Ur z11q=9D)%q!V*B{DtQY&rjL_Ib5D+9py%wIxV^%^~b`+ z24G=g!C0!)c_5Z1HVAt~`p{r(qgV)5FE#{g5DUec#16yWbN#or!qZO=$F_+bfqg6% zhP8?f#oEM%VeMi^VjW`P*mUo)_1`^(;n-HWh8TfOmbjy^6ubXf=iU+6MYdUe=xFR- zIi^Qq8Frl2|3+a~ibY}HNu5u?_K1zaeil0s>lTZ~dc?+Jy<#!gL9vrChxDtHu|8ts zu>NAP*jaY_S!4Ei>_ZugCtzE}PQhC3cCp&@bS%tXgJF%6XJOaNv+^lesMIA5E0#Is zV(gIEbS%jG?5Q6$x5~H#+bCmGI+iTwNHegLI5+Fd^FjZ0Xa+WvZ3|p~a4D7|W7B2W zEUC-oSgu$m_Om=6yaEf8I?u#zlC)Q1-$~k8*gClupN%cI&l#*Xn1gMyuO+Q}+*e_% zBrXekP0F2%{oyY3orhWZWn<4txmRPi+wE+{U4yN*&FTm9uy17k&B5M~xcS(dw2SB2 zr^~l57hscQzg>&>t^-Jk0Zi#yTIjw(Ul& z*S*-Vh1ewf`ooI53G0@)Mc6~q&Wo{Hv3%??smsmSn_{+Lzz+KyYXO3AMP8)nzf zin|Rv+`fNc)%kX8g?;U3*%EBE*ivk+?4vudSw8b0M}=6L)a6cWigOkH>c3<4F04|@ zxEot2`>hDORqP%tj%Q-`|ZAFZCf!`YL8*oyl_AEptSQc>?A4U0cum)+rN3cQmIg%Ck zD7H?@eGGeEY$f)z*eWbbuEADg%Ovh`tVQ;D8I~m5_5@aFZ=2Ny<=CZ?_DSr1>Elmf zH|sS!mO$GF-bb&%-jVTd4YtEhYnA&9_Ji27*ez1OO6*3d%X8RZnJb>hw%Pm0>Q^sd zk4xGr>`A+RR@{r&CK)HIv5)LA+p6y&_KB47GUk-} zy@I`HAJbO3>#^lxwb(!I2iz|2+Dj{L0~T(tg|+6MjaaYyQO0)9+EvRoVI2L2)nR?b zUd6iXb#7MLdhC$5E}j)}mc53Jw#&8dQM`_=u-EZe_6GKjV-4l@A+1&C1}xNB!8Jr5 zhyR}2o7f=d8pGbg9JX2c{SON#ttXv-T~#AiCT;aL_O#e$Pg=KU&hu=Cb=|lH>mkmw z$NbiWZKTe9y>+(AeFuA8Y%8{abv%6o)&*lL$% z>|1-SkYyiW8|}41mTkl0X?rWbLDbwaG?X7*j9gDE{xn-YXXWDhKtQEVAGOTv7);xWNr8=KB>~pNbUZ-WP2mS)P z%5E1cZ5x(t=V!IS4(tca)1Ll5^doV4k8*g{GBJ+{DUu50eZ=8FA*%@o^%%@F$$ znwAT1y)ujh}nY#4x9+%dB`vW_h{uhM#uU-Dc;u%W< z?Jq3P-VQ6jUThL|@obg9ulBiznaj7n^C0$(9cT6Nf3Vx7 z+<&oru|wG1Y_GM3-fBP3CjINahkeEk8|!~VoL;-zFqhZvG>ie4w09WR$7}J1_4V2k z!-BkaylmTOpluPrcafY)*i3-;PIh7I(Z=UtZnwr!BtvJ4ySwdsb1crDGaAzqti zSg6-h4Li(h7Z`T9*OCo8!fT0!g?a5b!-jcnv|&ejEyA#HuMIP7xYu4X>?p6jXjp{T zUNG!vuRU$pNUuF<*eI_(ZrEtAtupKwuRUg1q}LuX>{zcoY}j#Ld&sckz4oACQC@q% zuoJv?zhPs%cAsGLk42$vFB*RYf+C;-n_F95rd4riT%Bw%sg^OyhL13DAyanGWDp7lANw-+5lCSsF(_48aKc`OlI?%n5} z{cD|HosEsK`+;??G6{>c&5Ao8JKa&>9^`wc(34*Z*1(l_aDYw0cKXb7PUDHY06W9J zr)6!&C0MjwhIRguhgI6!Yqi0R*k;V_y~k+TBJ56yTa4W+mXF;ob~BbEb_@2H*sa*j zVg*>EeV%BY+unv<$98zGB>nf&ZpT_BZVA@kzCN5f&nL54PXCy`F1WtBiZG6D00F>* z>_>astbP6f_L8^#JfpjHOqXEKaeVag9&f?k^ZN&}zqxN=wUt%BQY_SNKg-r&7uma1e#wOVP)Y=~}Ve{o)%UW!##J!BIk+@f|dG>uhE5G$vsFYiaJtlSD zfc2NSP1q?C$Dqr$na};4*bZO+V}+#m7<6GxzH!oTZfvcygt2Y_{m=8}qhm-Ptlb&H zTQNLAr2ReC7n{Sg{9yjg^FFr6g0L``Ip*}k3f&)&k_ca1f9xsWy~ut(8-T5_?@wC! z1!FHe=F;;9b0kUTJH&}nY(5m>ia7!sWyu^cHk96Lwy z8;)g5ej~6_i8~5=OyVN2JjXN46`p%(-lt|mj>e|@t`YrhIua}MnZM4XuxIF3);+C3 z-ZqWJE~Z}%rvG@Zi9KyP9_#0O=f^V_dTat#&b}N#yZG%CY!&^=Gt2ttia6{7Utjav zsaU-u%iA|xM0)Z&6Z^xrzQmv3M66u)$645^JkuOR$^Pv<2YbqyPrvtkbHM5^Nmz)T&eREubK6`0E?CSU5Gv5{?z5!6aIaaicJ-piXA6ur(xf_%(NF_Z`k|8 z+HYxCp37{Pi?KOU=jm9h^B1Eomte<98>C~K+#N>T3~ZaLpV9Ui*c6u;cPX~Rxqxx# zzc#%Ls~5W*i*uU!Wny`(+6f$+uE54f+)Qk##9fJ{$o9^{$|P{+xt6cky z?Y#=Cmp0A9mPy=P>`M0V0Pnev)&H_Rank>;#-_9WbP(<0?+4dlvCi4l<-c>nd04A+ zx-ZT;H_O3NWgpGQilu%Fu-nD1#g3CQaZAO2&85=5TZ^6RYM}yf%o)s9L zvFTQ9FZ0o0-*+%PR)AI7Wmw17ZP*^i9Ah8dj`f!^mS6{*W*=IL^*YVt@(ygQ-4Cqo zD8#xX?VVVMq`eDk6}uZ-LthIH7}tuh0%@0fu*ou>-iy5_`Q3+A%Q#t##rej2f8V_y zJ6ZbVGVD)x9yRgI6;}H_fVKFJegAb&305ifdl2hOex7l|A6JU)k+Jw8Y?rUC{O2#r zv2;21AI3hG{k8&oMYi`5tV!Y?#ad;5Jchk%Uo%;A%1SJhwzAI6tbX-4R_>dB{q-xu z_SoloR=Yfb)ks`9)+}*PVoknhpPujFc>3v6Se3mmtv>WLwpzBW0$a?y-;Z>jYco&U zHP{HL^D~~bY}>$qYs6=<<0P&UE0(r;4qIl|#j4-)Sc%l-1?& z*dodAC9FjHCHxINlx(9Aj&+*Cp;{Y`^r=SFm*ww;r1;b*aTFByIziYHx?t zE*r5&ByJOyL%D(VtHWGwb6k5Bn<;Vi*sYS^Yo7dA)e*QaU&m%k+#6W5?_Pm_JZ->Y zq}(^L66qUnVbdkQ|6ym#{%FMFCGKtP7DukJy_>PWSWO&wj9 zcd&<8u@{(jD;Ddx-iUh_JLEFYecrhdF2EM@G){&Y__u2X)(ra5L9=Tbjot9<$SuV;S2 z7D_+u!oHSy^;axg%I(G`iT#ExmHhT$ze>5kW4}wk>cQe1yNzS@4{V>*4zxz6v6^S{5b*X{Ew>-gA@og!%uVDI|w@AjqtdgjK1SRd|ld9Gai_mKa=?xBx+ z-bwP$zyD%8INtg*U;4+0L)Zde|MQ=}bdUFpFXle~4NH=KwGTU4?00M+wc`H7dWiFUqsMc1&6D;o>^6z(#Zs7)g9ENz{>Gx5H`CYtyYAkP zwaWfDfYpf|#M-6Yf3TIr1)iV&i!JeOhrfP@ux80`)cKw@(aG%J|L$|q>3O%_^Zdi} zOvKu6POMz!6gO7kGW$k9>_?6x&-I6Y&g+l0%Qe#gY@_5Cj5SMs1F^mKxMq!=gRx$* z5UfYi9)<XlInDbo7h=1mjH%dkU!4D(f0`#hiMt5%tX>Vg zzmb6@y3&n4ekm3rahG8cjvgcJ<=8=~^Oe|Lj?auSb{4kBz13^}`pw4TCBH0>$^D@l zv1Ez62}>7SibcB2d(wqimc-qIh4{V|;r8{Rd$In`YklW5)^SvfrOH0PA1jgk9>B`P zO0Y$8&RU9PN`4PxVN&i2EJo~6EJxBlhIKK+1@6n$SiZzPj-@d-2Aaa$!*Rfp3FxvFLd#Z0>e=^4h-g{`oS|sh;*t@@cicYy?&<7J=1^9gS@j8;89veLNOx zmAE);x7cY|w^%$z>pKI-x&&;Ulra(GE0{*ySr{wd3_BOAkhIAdtDB6t^D$N@8a5S6 z7Mq5pi(Q0eiKStAVi#je#V)~0#AaY+VwYl7VwYoeVwqSIE8GLy?+R?Y*i5WL>`JUl z>;{Z)p&9w*VZqKr#+-5!7B03JixSJn;>2#llEiMu(!}n-B3^_x7fTWAo93B&%(?_&*Ap=661Jp0s@mmL+MYV0mH}VN1nkU?pOi zSee*NtV--^tWInp*5nv$wACVPtkiiiwq4@#u@12%SeIBKc2MjdEZ8~7DEC1uTi zN^CWj$iLJxZ#A>lwVjHkrv5i=ZGr}l$6ILK`uVTewZ(%FNHe;1y z?_jlJ?_-T(A7U+HA7SlcpJ02%KEry&KF5Mw=KlB+3lsYmixm3-ixum_62<<&QpFBn znPQHMJ@b}X7Z;W<)(0yR8-T44I~=PJ3&U!}hG7k2Bd}(%QCOSUF<7TqG}a?F0jpz# z2^=HN#zy*%H~+n~N!U939=CPBX zjk-+7>crBq7k%c>FB2=5xS1Gt%#HTD8q1UC-8tAosq=j7QHfiCl}g;TScBZV$;H~l zuEPqYE;nNJlHX!%t5`nPDt0rrTkICBTkLj>+0ob^cVUa9F85$LV)tTer45#0J+f^N zVhL{ZzV|~|n&kH|Hd$;17BBV)7R}24z-z0OST^^=0@qKj!s_H$cpOWSx>R5pVwD)J zW!yh~4$GFf=dnVuDy&}Gd?k22S ztPbmvw69_Z#hS2S=WwHpcd&4=tysI$d_MC&7A0{XU~yvGuq3e$u{5#Iu^4wcW7~hv zfWE?JNnAUYE4C9W5bMC|rCoMmtzw;6rKJ4f6?P7mm4p~e0CpJ;m zGWBB1#s0>s#P(yO<@opq+bD6K4?i`Exv&J^x^=hjzPuaTCUJeRbxw1AVi1<;GW$zE zEJv(A_K|O0seddE#x{!`hE<6ji9I29G*&8h9CoMJ@z^4<6R~+>(OACJITp*5xYMwy zVy9zkq@TuPMUvkc*eQ}W0UIuMCN@ayT!gfl zF_+ss24BQN#j3H9VlQDaV(YO4v5i=+)bBMcMdIGVGQ{4-vcV=195k zW2F-JAyzK79jg}m467Gw!%F13aR;_l;&x)KVqass#lFG1#dc%d4Kn7{@30WDAFv2k zy$7Dt?8BnP{>JW=ey|_Q75f*PEp`YyPt19V=l+ISt_vG0aecAF#DcIs&RxdX)F0d8 zTkqpPW`<&S${Ms`ShAFRB$h5V9Lo|LflZcooQ}pE(qBend*nTWW3Z3KqOp3hcq~oI zI0Kt0mVo7morzUQerIEsiA};%#m>P>9Ak~^!E>=PX@g{}O6+{BPAmm$;?3E>esux1 zU2G~=DBF>Stroi&D{w{|^_z|zl>9EiY9%fm3wA~sb-5G^7rPAWm9&{yl*G-%{$#%N zJpc8dzs$iVNt<4SB}v-(SOtC3vzo}C_697Tce)qZhzbzSh2+2g>}g@>mqEooZGI(y2Z+{2*(Ai zA^2}?)YDkJV~SxFSQaCGVBB-qQnBZ;Ua4OdRw8jPVr63Muqv?)SftDBFLhX&!(6xd z29_vso3T`}Em)@5dsv;6`#yH7v}rT;mdjj6_5rp+^4o?rNq!$-l(b)AJ!0Qr%O&kD>?V08vK#Y!PcQJk)VElxj0fLgp>A^@eUBaT zy({DIyFX$bl6Eilv)E7A2lAfH&sZN>liG!~`P$UK_VQP(%W=NZPrI>fso!tdTT;e8 ztW~a~e#f@>)|vaa<4~GvR3&lQ=wf2W$9Wr(v zg@rrKeKZPNCTT}wOT?nEC`o%F)+%i<7TYZrgUyxvPQo(8;;}=rkIulRNZgs&M6rq3 zII**^p<;;m)tpAv~7|W8l z*;t-f4)&YNTu-o3SKmm)o#9$?taT1>f36 ze|}4_CR#PHtqQSnSr=J^ZI`%vvDvcb^8qYZtOV=*mCTk*b3|v+1_WcG}+!t z>~$&kIc$=ot-_YedbpRcS(4vcEXjF_(Jn7zX=1h5337jYgEv1jZ9SGNX&bOG89U#^ z3MB3=EXsF(!@u7ev0{mP8~aYqGyr zO4?nRXT?C^^N&ufQPS?gs^ooxAF+M%9?LISuI!^OtX=ZkhwT;HkM)ZEgUyq6=`+J~ zKTPh|VPP(FU-rXFeQ}KFo-@rML$Fwh3&o=3o^KfTgp@H1E0Ap)hrQvuN9NxjtwOn<8nC z^rp==-V+JO`b*r2SiIbu8;3B`(gJpP4ouOP07KEL|)WJ4cS+ zE3gh~6L@`;jb%yN94t?40X9O8t!pvvju^+=A}mUdw_C8Kl3xKM8#G3e(@W6faHrBx&32dt^SeMur*fzN~{Ss>w`wAN^ z?`wRGHORGO2bL~rzr`ksbz`^4xzs*SoY?PJxU9F>kM(y(8uP+G-u#X=%yB8dm&H67 z_orxuhO~TT|lCW7~=VQ5I7hnZq)39Q(i?EeqX;`J$ z#aOM_Ram1~Hr66`4c0C;AKNRo0P}pGKJb{i1M8KzyRaaa+4lEiVPX$pkz$Wvv0@ci zqS!N7s@StwrdTDGBUX*&i@ky+%R0*qSdqkS!B&WUfK`aKU^QZ`Sc6zQ)-3i7)+W}0 z709~2Z?R5^>%n@&{=yn1?cbQoZSJG}Sg6=RY@`@JaLBotnbw8%$h%s7u^^XuZV`kf z%d>=FEK=eIVi{7#P%K+)IJQviXsl4|IIL7G8f$QwbHF&PT;gJ}YPT6T9@{Mz=Z!Pl zQYi(PU^*V##8& zuynDjuq?6pSf1Fm*ix~@Sc%wDtW4}~tV-+=tWInd*2I@x0^6k=+b;Gr)*)7fb%|}j z4vN)d!Op2h8LwmEVsBwlVsB${V((%}V$E2Z*fwmI*r!;o*k@RQSQ}O>){gbaHQm?P zN{Raps}%bQYm@VrpRq(arn|64N&73-BG!$yi~WY}72AjPiv5KJxy=3XHx?$gABz+_ zfW?aagC&Z&_|fY@a-BB-TP62YgRwlZAy}x~D;Kfn z5_dY*EOGHzn^+>&DK;7F5j!7qxy}BPf`y7r!LE?^sHR~fCGH|DMl20W5W5&l5lhE1 z#4g3M#pYlO#j>zMv8%CCv3XdzSPoV#wg9UayB^yrb`#brwg}rTb_>=mwgls=g~r%< zCl(@h7xs`mcfK2o;7e42V|x+S&98U_-Va-bMN8ZxSiIOuELrSvEM4qbEKBS;tlVK< zTUB9s61NsxDpre?h`omGmGiYXuri5z3#$^_iq(m|k7da@*9TY=Jvy-cKE}3-eS&p} zeTsF7wPFXwzQlr^nMN7ySh(2tSd>^N7ALj`OA_0QrHTE7%@X?)%N09-6^I?e=E?mH zXQpSqG`GWztrQ!8Rf-*k)ry5nUEK=gm!D7Xdu|%;cSgKeimMNBn<%nH_aZk{w%K|K4;_kqT#8zM{#LBP=u_v$^ zvA3}XvCUYs*!x(U*fy+F>~pL~>Of9Y7M#Ld8RB<@nITH-Fl>cuj# ztzt8=R=K0mDz%el!3lX~pi{MM|fpPP(Xt8Utc(FVzS!^+uE_Mr+ zC02mti4|g7`4+$DYxquw^?k>?v5j&bb`Mq}Y45|z#EP*hvHP()u?Mgwz8D$U1|`@e zIWK+?E0%M(hp!tZQj%0faOZsjaY%$CahSj4%;i=xO@#; zDRKY9cKYJ{*BYC#N{MU2YQ^5c8sr*cE7mA+?_w=t+pvk!cei8h689FP0;A2+J39 z&h*TU=61NS6=Hp`3bDRejaU%YAl46S7VD3-i4DLy#e%UOv4NP&ZPs}(7Ah8kjT9S# z#fTk-C5Rn?rHBo~GQ>uB;$+SnjaAC?)kv&dz9D%WcBibjI0?&^GR9*I#p1C?oaS22 zGccaB8_#AFuu1YA;WM!n5;qaMSDuNSg;hvgB32`IHr60E$&;Vd?;NaI;?BeN`rdE! zKZ{AhJ{G$GTO{i|F2tsaO~u-zjA@vQZ3|rcdl42Yb}`oKdk*U#!!E(n`IW=Kd20qX zf}RlgJ;qsBoA2I@KkXbWPtsn6Jt*I_%f>c}U5)Xptj2o@H(*O8zZ31fNn#yXn$vureV5nFy6nMbN!qDm@CHGZ^Vl5Ik3~LuV z5=)nHG9240al^4*u@P91%iNAgEKKY~>?7ZKqQ7rkh($_VDi$j?4NDYD!&1ejV<~dX z%*Qe%?glJJ>@F-{>^`iEtAoI2qz_<4689vwLhKo=LToKoBUXboh`o;e#0V2u?wi=( zV%xBxav$?U>{zkw*m$w8u-oMw@|{?`%nM&*S4h9wg>_1uzr}jQzQ;b6d(EBLba~I~ z2kZiVSPX@A15k+?1_#%=b$->};y?LI6|?04)t`Nnh)79nweVGE^<16ZNh zKiFMTzkjh?WG#eimS^rc!`Q!W>aZkaB zJ00WP$FO*;O2)9WuvW=$61H3HJgi&ne5_FJ@uXrL{l=U(4GR%V$08he8*wwR47WL! zT#79en~g);n+%cW1=ggqs;2+NcF7GrBAE*~qAxLdGd`R%w{ zv0AYL>@fN5hTE_*$?tZoN^A*MC$!kL$2p ziQ9w~h}B_RWDa;0E0(x=Y^B)SSf$t&tXAxOtWm5P%XgW_%%@npY{yqvi=_Pqt9F~` zi5=dw=6>6a?Ul6OV!dL&V+k@I^kDD$uFL%IyZniTxy&;D!Xm|bu~@PFSfbbgES(jF zfpg~%RV|HX17?IA2*%sJb0{$kdz5B8qqHvp@UxL~YCY#`PkHVA7L8;rGy4Z%9a zLa`pP!!VcItn(4rdoJ^L8N;w}`*%#NHNr!&IWjLCiIvE?d^omQo)sO1#YnlMuuZ-= z|NMJAmLPFaSc=#QScce%Shm@Hc~J|1hA^R-j4LP>i%Rw@>ceJ=ZVBIa<8Gx}8` zRxfE&uqe6Sx&YfMaj96V*fean*d^G{()Jlxx5QnFIUElgW9;SFd^wN33hR+}$-*M| zx=rA`a1B;2W7vEwTH+R9@nY9v$zr)!y4ZDCmRKHEDbIgz#PTHWChSl8Rp7VAZ^o8N z+^txN*zH)E*d17vSRqy?b|=>4c*tn0yRhwIcViu5MOc^EJ=j69`>$WyR$ysjk6^RJR$;kftFZ#Hr?6tN3T&m=8mv;R z3ab@+5o;8y##+SIVeMiy*j}*>Sg+VdEXZZ7Q`awP2+SiV>rRwT9qTOrntRfz4xYQ#FQ2C?t3X0e~J zHnDE3Q|vdaN9+&GC7Yh~p2OB9CI>$2~nQ0Hl62yjLgMIH5`sb)&Sc=3A$1=o@ z!m`C8u!UkrWAU=zMq{NCcMMi8c05)sb^=x}HU`@&b|Tg)7LDx|8;f;|jl(>@030|k zjK@O6lCTKJlSbURShQF&7B6-ImMnH5mM%6G%Mwe&^2DZNOT}hjC1P1vnb=&cO6+Q^ zPAms&;un+y+hsnsU2Fl?A$Bd+C6lFJF>k<1K zbGglZv>yuwp;9ctXnJv;|(ceyt)tz5lh7)9MwkLG%Q-|5-eV9 z1~yXeDP&@;a^E5gOO~{Av2?L{Se950mM3;Swp1(+D-pX9D-*j3s}fs`)rsX}O^z3h zcDWhbF18fw5W5STDA)0&*g=U~js-ibj4~d^!o?oNqQuIuJbC}O5{r|#7qBF;7qK+4 zm#|r4Yq4Ch8mvI97AqF3!&ZvDj#Y}ih1H5}#Tv!l#ahJP$J)i3vAtp+W4&UXSdhzX z`+ZoLSPvE{_7@f_){7;I?Z;BZ{=qWE{>5V4=I$_3MvK^W6jRe{)~}mM?L^ zSdrKuY=zihtU@dVs}T#u8pIC6n#B&s+Qh=J9Jv=X4C|D*5m=AdD6C!XJsg7-i$!5B zx7k+FSg6>^*hsN)Sd3UK7BBB##$gE(7mxLKnfLI{#44QT+L^Pl6PS?#*K|z67D|5S zU=PXKl=HE{tg#79n}U@~+9_DIZPvZR3$c3JtZ!JQV!f<~3%u5yifxs+X;`b+CD?AU z8CbX2Wf&zJu@@a>P2Ye6d|vk=W1J3b9|X3b8J%M(j7NLF{+zO{dvk{=k|g z?oX^u>@TcS>|d-$>=3qF&SRZ(J#&h=KU`R-SU+r}*Z?d>Y!H?pb{Li-HXO?k8---K zOzdtfPM#ANV^tD&Kh`SejwKjtW{rD6rC6P$eF$rEY%$tz1-4ymCDtLf8tW2!0y`-7 zG#2dKY?M)fg^R7hqQsuT;>4cAlEhxX(!{E;gY3<~=PoZ|vm~w>%N2VGyVPZlIcu>3 ziL1qm#WrFq#ooXw#ooed#Tv0ju`O7OSQFMRwiVke_CD4t_7N83GWW;FSeV%7SftpO zSghDrSfbc2ELH4VEK}@9EJv&hdql?j9xPwt{=|yJ_G2r=24{Qb9&>w#U^QY#U=3o! zux7C%u{N;?tWzuo>k&H%bGgmBoQ8#pC14}P60sPuBrHK}8kQn<8I~b756c#tkJZaO zcmu||dgD0C#}-Q3Td+d00<2W*ZY)glE5^ztZUt5?_6SxlR)%dAdkSk6tHgGTJ%@FR zy@)Xa8spVVScup_Idksq#YrxXQ-p1-=Tzd!0lDK!V zBKe-ohghD(ZO4|1wPGb=yRkB{Z?P(|POMJs2dq-o(*2Az@k;}NbN8>mBRxFl+trWWms}#E!s}-ApHHu|mEn=5q?P8Z< zd&Mrtdd0G^AeXt{=3-%D*IJtQ2byTaGo0J&d)9t-=P$H|QS6Iwh_g>k)ez`%R98HQ1A4mDm^Z zUF_$u_3}Q`3s|qLt6GblDfz9#zL)RVyo|ZrX5V-P3l)0}8!6U+#fZI$C5XL^70Eu@ zf~82@J6ML;2Uxb)N7zEKFR((f9ayQ@S6I1N2lkPCA9y#mRPJegkB#7W_6G2!MVG_6 zPuPjIN`60Jaq|cJH9sZ3&!Hb24Rb&uZ4I_?&S`_(k1N?*o#t^Ff2>rBCtHMQCNkn z^&9Om`9ACMSc#-P0lQn)#*e{vN!pXJ*PZ6Kt;bbFmkF_p|-^O~y7!+=W<`)bAp!L&}|w?UMD%S7KcfHw!x` zHX93ewj0}#g@ub{W2>b9U4!Mx_bqa;Nxtt|`PV4T#~zX2rM(`DlQM3=lEm_`G_gh4 zd9r`=v5E5Bxd3}ezQuJXHcRp=!g9s#!@8uu+>Z^ke?P!Frc1GZ z)o~Ty>G`? z^E~^*Tu1B1D#ZF@$N9cN?630xtVr@3h%NCQ=l*xY2Yd4~_t6lnM$(31V}0)d`O6rJ zHAvhrtXb?xY@hu0&r#S$nOBd-u9NrBMqzi#yc&hINf{?#onm9K9EEXyj zg9W+F=hG))BPDJ;mL~IR92O&S@mPXb0+u3n7M3A)HkK`R4z^G%87mZ4;jqqgGO#4EnOKD52P1A47A-a#Yjb^X#LdAv#je6! zZZj?m8!0vy%Mi=Ps>QCxmhxso;C3v)>cpq_kCD|{9g3^Si0B)Se94`mM69xTPpS#w%swpIJa1Zb%>Q= z#qxW{<=9HGC$UQTowy3DR%{K{DE182BKADiF7`6ES8O9TRmPV(EJ%JM?NuyHtRBk| zdkw1)dmC#LYr-<*_o%jF3&onTda)L4tJp`F!*QCiKR(8)AAIPbf1JlkvL=f3D^p;IILXW8;i%P#m>M| zUFLfe6R}LOv#>&WXW$&HUDi$~VJl>gAe!Di$Hvrqi%0u{3PET!USV zbx1p3f{m1Yl#Z2(U5?etIrU5|Roe7QY=xZX%*GPM=3%j7`PfP+;|{Dq>`p97>>h7^ z<~h`RXuu!q>*baGC*^0Hf%eThYgeS?KKZZ_t!4$L983+tA#vkPk# z`xUDe+lQ5k{f$-0`r`fAQu+3xW4`BjGrw8w#8SojV8Qa+9DT74`Tdms*mkjjSdPq> zgRn%g!C0hN2v+4VzmGHo3zE3Qv6V8%567a!Mqt5WM`2}hZV`bk6^q2u#g50K#l~PE za{dyFb<25999AfH2KI|Qw>k?;k+`$51hEUS7_m#Rkz(nXOKb*KBju)T7P_%@a%wgn3odl%a-*QOs}O=8=yD!JDE z5X%#5!J@@J##-fix((YZwgW2_`x?s@`vFT4`w451djP*+HDU*_6=MHjMPi4rd@c#fD>%Vn<x4E`r6c!|L$6&oqv(Cq2d&N$~ z+Qp)=7O}Bdqu8lft=MT8--a}Ymt#?4v$1fotFU0PYp{ckPU9ZWLaa;d4y;405Zf;HAl4*Siq*+IvgKHn z*ea|{tQ_O_gpGE71}l-cwb)Xz8Z1w21C}NB29_?i8A}%1f(1)mKE>iC?kgLu=QtXk}7tXym~79rm%J_ak5 zxMQ(Gu@kU;@~mhAwou{{uxzojune(8EJZ98OAwoejTB46a^)Lfmtdh1mxcAX%r>2i zE#iB9fzMsCu}+D*25S?Wk2Q-ezz)jw^bJ^~Jh!?DYml^yuwa+@-HKbWaIxF4D6vAU zM)E7dD#Y%^R*2n?6^Sjw^2Hv&a>O3PGR0P6sbWuIiDEBdx$^ApWh_?W)?<-kwOE+g z25h?1e9pcRn=Q5p3zD?2V!iS_;B{=T*c({8*#EFN*&mHqi^RQyHHvM;hRU<;53pK0 z&U)7SA+}Pi1&fz&K5oY%#9FapJFWHnqYX=vI)8=bO5D%bEU{m)G_l{YII$tQo_WhW z-VVdkq}(I0gN_}>cp8Rvi5-b`h#iY<7mLH1#7@P+9OK=dUqI;Ndj@nmR_FNAC^sIf z5=+4He9s5{?|3F+t@2!a5|$%tE6%}|NZKTDDcg~UB}>|c81JbY>soHY;w7#C zixyjgMTjlMrb%7y#6lcj8T;!3dHVvk_0Vk@z&Vvl3>VrAGM*_Ugu zTxt7fur{ap{_0Cug1k>sgMB3Vt;edR+znW{SUpxM_8L|p-LrNZueV;?Z9@6 z?ZR5c_F!AZ_G0y7KVj8kKV#)$UD!v|FmOEh4J(zn-?2imKd?T|R%1N)3k!0a*I<8R z3nlFVEL*Ja^`807Y?ppmidcUvL2LjPBNmL^By-t7Y^1~;frW|<#jcUG!!eh{jlgkm~ZDPk@&0@!44PwV*HDXa%h1iK$n6&*^Y=y+dU~l>EJ^07*ldvL*8;9kK z#bY^Q30S6BqSwr|jTd045;qk~6q|;{ie+GtVwYiIVpm{|@{aLrEJ)(!VZBaszg>&% z6}ujbls3HqYnQmCSc}*lSgfQi#L}dGcVUf^wgjscdkCu(TaK+1TZ#3Rb(ZB=vBW)z z6^K2J<%&In%@TVKOB1WblEmt;II-8URB4yZSd_#yVc}x$V8LSVV+S4oGse>our9G} zScljr*mkkcuqLscSe>KMDC29aRGydpiWQ3O!>S~04^}4DizP_fLs*H#IdAaHjb>e3 zSe{rAmL)a-OBWl0C5s(~#fu$*MT>=D5n@BJ5Xaj_TaCsXVv$(4d%Y2NEVf&0EY>O( zgKZTXkJXFCV@=dBaDF=*tCqO)uyV0+wg6I+Efi><*L#453^axGqs)r+mi zY9#FjtU~NHY=u}ORwVW|mM^vi%Msg(Ws0?8sbXJYiDDgCtk^Eh<;*ks_;*;C#QlT? ziT#T8I?es@8@5;MU#wkhV4i0_GSd#h8pRI7YQ=_Pm15!8O0khxvDhfAKr9N&6*~c& zB^HCFi6vr5VyRf1*kxD_?~(-`=X0?riMtlNU7p$GVzb?k@eHFc?}&N6BIX!!0~Ri6 z^RWK%e#I?Vu*BVt9dy(h`+NzOF7L72k9A4hGOR?2us{Sj6oaXYZ3Vmq-M z8UOyq@+9sbEKYtma=?uo=aM!I%aXLiv2?K!ShCnLSiD#i_Azh62i^xc4cjR;5sQ|z zQ?LlJR4jy64Q%^qn5U%!Z3cFi+>^c>>vmTfaoN~zv8%CGv3b~FW~9J(Bj;mVB`z1M z7rP#-7F&Xqixpv|V#}~Xv4^mQVvl0kVvl3XCvVI##_ zuu!p&F_+kOtjA^Uk5;Tx>@%!Q>%=O=e!*6V9l(mj4q^FXK?^>{@<9Avj<>Js)e7xD>2bEETI1n~tp%yBsT$@#+e!SmLh43dCk(kuG!X z$5mLa#ARVY5;qT^k60-OFWU(Kyc(I?cXt7_g2(iDg5XTEf8}wohvA?ly z_bMaqAhuiVAFNd@_$JSMWTqX4)r%d8Rf`>km5UvXm5PnR3dN4c7K)vSWs5~)8De9x z6tR=B1hH{gj945tQtUJ=RP1!jC6#I?;+nAriTe=ib(;IK1=}n3HP$ZHfwhS3#Tvza!D_{Nv1X~u zzgU}?dy!{;GwatETPYTV6^jkP3dAC@PATJfELY+tV6((d!P3OyFwR1Z-!M8A3lTdF z+al-G30RWkcQzJho3*BB1{NXtWnxiwoOKO$1r{zg6AKobg&lOPHpa<0SeMvbtV1jt z+b(tk)+CmP)j1wF%D4%u63fTR#Fk zW>`43TWkcDF6+%Au&oj|3d@o25FCqDOWg5TsaOpM9oddwu4=|8hCDQ{t|`8YI7&SdG|~SdM%PWDXW1+na?Y zN?bO!Lh`#ByH&oua}Bn|_njSooflyFlJ;6GN9;N*Q!Ecl6}t&b6kGKFuI@N4&*}f; z_;ug+*0yZPgf_{9kPt!|A(LipGA+}{miT51>1%C^wU8}a3(eY=5c-l>CiKmO_@>de zSmGONmNv0i`c8<2-|Oza?|%1rKlWG8ea>~xb*}3==en=!oO59jVma6p*|#N_kHjs- z25si?Sb_D5t;9OTR$;ATtFcD0HCUE>+$C6?+p) z6nhJc6>GpE#oon2#XiJ*#kOHX)*7R|+KKgvwP9Ui?O2=G=U9_i2UbT9;@wVuiB*a1 z#>&LHu_CbpSf1DbmL>KF79!s)o{;10kIb^1f+dN?VsTq+{fG5nu%)U9>oQmo&x+(?^?b&K7HwTmsshIo_h{X01ii?S^;o|hD0 zi={nSiB-$?R$=>Pdxh9MNn4D~5_=2_mG7Ob!>*9HQY=!e3_DG%2AeG3c-VqPOPSxp zs^psf4^}7EjD<_ukFa2|7R+C4CuSG>FE(H|kK1mnLu@bBBxTu$wMg84tU>HMtVXO4 zi<18G5Y{2bZ2)VOXZnNKZW;gb7nUP=*p@i^1#{oVV6E=7o<3zP_P*FS>{fZ7as(D5 z+Z&HXh#ij&^1f$0SA!>yV605?IMEqrGs`yxJ4c=&pMqT?b}H5@Y0tn~#V*1c#iFo$ zDN8g~C~=oy(021}4U9>=nz4xYw_Xx+X0ihp63N!nMiE{WTWwTW%Pn#5km>Z}z; z9(7oi*jrec*xOiv$aqGm=B(58)lIwCG7AJAvVyC+6#WSAkJ1kJ_ z0On`q8~b|@``%%`pYFrH68j0;A@(!&jr$(mGq!sO^R=1hqQX`7AR?t z!Jd=2DcBQYr(!F`Lb0V{r(rjVorld5i^MJ$i^f8we3xP8N!(S~DPlKc$BHe&#)@TP zk+x{#n!X#070br{u$#y2ek@hu9>9K-xK-HKVnx`eVr#Jv#EP*u#2&|95L<^mAmf$S zV;d!I1GZYM47*?KdF(E+7qR(bFJYNd2i4eFQkFNd*^+iEHcjk(>_V{*vD3wxvB_fF zvGHP`V%ueXm0j3hGDf5w>lgb9>lXVCYZv<-dsy=L5nCem6V@zg2e5jvKe1{t+cKW5 zi;cmG#r&{zv58o+*d#1oEDQ@5I}-~Q3&;G$qOmt+?dYjkE$@ZKa@XMbCi*2lGqJcn%K=) zmRKs5Cw2?gDdS}3W8b>_X-|8fh80QL+psdR3~W&DMeoF_ByI^-C$<7>;!e!_ULYT9 z6MF#b5-Y&^#2&_m#8zXzwgW~UYq3DFVk}hbQ7lsI1uRyq8uOEHFxFs+61N3Q6?+}a z6nhIRko$wJSh&>lJ6NuyeHSYfdk-rWYs4zW-p6XiwquQAyRcTVHmpq+01J@q^S0q3d91j60u2Gh1g`wA$?vDRwHpIVhv(buokhiu@15GupY5XumQ14F}uU8tIIHd zvCFYwu~;lz>`E+JEDnnon~5ch&BD^fuEny&5{Ki=-!nI0`4X3g6^q@9m5VLHs>QOf zda--4d>O-+gEdRsGVFW1Io2;18!u}vFUQ&??Fy`0>;bG_>>=!O8N*kAea_s1BfNfh z7h?BHTnT1bzZ>o8I?PY(8LU{+R%1b2{od_;4fdqOy^4iN+*T|~>_1qhJa1~k;w0`P zEL484ZpV@&?h`Cc>@zG&>~kzn>`Saj>?^EHtP`se+k@4K?Zuj`-;BEI!osAV`37r~ zxNfXVY#-Jq_ANFf)`R)lel_y##e!|-w-yd!ffDx<7An?{MT-53#ftroC5rulrHcKH zWs3RS=j;c~GLOXy#g4>E#g4)%#U^33V#i~RVpFhIu~V@FGXFdj>y)@~tXJ$}Y!6qL z_dC+K;W+c1S3EW-X{Td8cC!vb~c$aStRwrv6Jb@KR+Vxn8*aoaZ>?y29 z>=~><>{+ZutQxD6@@>XCB_IF_>>(^p z>=7(UYz>wswie40dko7HE5VAy)?;O28?Y*|jaZ%7lUNfYJiY7X8LZG2XFMZ&7HgBZ ze_>r>FJgUSRoIZ&X3W>-F!HUz0>x^vP_fssNU^uDSh07oM6q|VRIx@ZQ|tpQS=J8x z5X+Ug7OYUL6)P2M!z#tTz-q-hutu@3v2>ZYxEE`cxGt0o5$l2 z79ci=g^2xuMTi~7V#NN!62z?K&c4UYmse5rJ*JJqa>Pbs1!6~FC1Mk>3Ne4IMl1kp z5IY8I5jzg+5DUb5#3o?_Vv{ku!`!#yF@LcsSg_bhSh!dS7A5mqOoeR%dmQ}Sgcv>N~~RM8rCfqhxLoiz%1)%qs>Xc z{KRHqLDobgE)fe8OU9zaQn5I(1z3_;8kQz@E0!g8JC-Mwffb3}gO!P8V^v~{u{yCF ztjRjc*xzMXo7i%!OY9M>Pi!4FB(@3jwfP%)lwpBl%z)qfA?c468AlpA$9=E5&ID<5c>%$ z5j%uchz(#hVu!H?F0R$6xJc;i}i?2#0JEU$LtPsd%>8$SO^v@b}|+&7K%lS zor%SZosA`nor|T5orh(MU4Z3_U4#{jU5u5BMPt=smtyr|F<7(M6V0pUg!zfh!Gf$4jkshiOzcK1N-PD76T2Bp5=+I>#1>-taxL76Wl3BXmM69h zOP93wVMP+R94ixBfmMk;h}Fm#*GI5Ai7UpMtYD+A9>vmwCC_N*V}X)(Ar>mO2&cyrn&@+mAGYCp{!Z+5Edx*Fcu`wZVIt*`E9ToOO!l{u~e}# zEK}?$ELZGltWc~HD;2B4D#c#HYQyw7XL1|@A579-2-~aVIn>&n!NZKK+jU(*+ zEREqr^tEQbHY`TWj`>-+{ATy%0&vZrabO7&HyTS3^T9I2#$)sOnxyw!UwV&`MEvbNlXSfkiftVYsaiZzJEVJ%|UU>#z!u^zEx>;w0B22cIX#ReoU z1+zQMj&B+QnYOy2Wa-ez7+&=V&YMdz!6S zq`X)A4;Covp)_Hqh&5w=vb`Nx5ThKt^JvAw#J<3y#JOJnEe6gy^oo8 zG&UsWgZbLRj6B9*+zS{s77G>g#UjNfW3gh#W2}*9Z0`&#RqPxrQ|w$USL}R@`xj$- z7h|PjF<7P846Igc7Is+H0Z+slCGI+`RqT4KQ!EMV6`O+%irt3!*vSZaGAhrxk5zEDxn`We4f#rzhV+CS`Sc%wbjPG6>+j|t_p5L(bSh+m+ zF2mk*e_z}){<$1$khD)>d~e&>UIo@6_B_@jR)r0SZNcmgb9=92{$g)p*;3|rFy<&4 zWqB71m$>(a&5Uctq9yIeSiIOyELm(9#_uvC-_NjYu?~#ien#9^Sh3h%jOQgrTo+a? zwh!AcYqNaoOe^1K{~miutQV`4^{)`B_#k5ECmY{y9J9BOUGixZpRYEGO$#!MOdcTU0ARDUbq{}mAGu|eEBV&gB42L zQp{i4h-KK7j#AG1(VT~_=L~DGQc1fOs}w82YQ;8Tjbcw>tz!SeI>lbVdc`(lgJQ2@ zK6dlCy@mydy^e*5)ngH2Z(*HMe{W+4#NNSTB<+V-g4ixBMeH-oE_L6IWk}qAu^h48 zSb^AHtVHZvtU{~@s}cJSYY_VZYZ3b&)*&{4^@#m}McB=?qz+>P5@&zNSqJ8E8-@9c zjmCn-#$n-Neps~Faagac2Ni_HOWf&Lve=ney4ZzSwpbLFFLp6jEOt3oE_M}GEjAs? zlrmp~)l1y9ShLt{>=^g&OHbc37i*WeC0MuEeOSNP{g`E4ZM2DxV18n&u^=nXh~u_Un;EKO_&RxIlUwPINk_ZgNa_61fX)`^vg?Zv9Z4q$a+ zKVVJPRYtx1gyl>5`mr{N`ybXN<|uGp3+9?8qp&`S8;=c%`C-1cX+~L&#sbBrV4-4X zV3A_sSghE&SfbdaSgP1`EK_V2W|w`t0n3%Rd03&?0<2W*R;*I&cC10Z|CWhG*dI3P z?kZp(jJW!h#iNOh)u>S#6qzev9mCK?;H7EfHg?mrC5vD6HU0s zIyNA2*I;&sc`RpP{$e*^E%KZ{0}Gb8JFsxEg;=!MomjkB9+of9PxG;4i7UX;#a3Z8 z^89o)Rv`8SmLs+S8OqQ~?; zmhWRtVmq;FN&5*_FSZA(k}>)Fv1W-ofVGSDVr7!{H!M%=cPvZnFxD;Gv#)gCGnnU} z4@OUIw5MaRG})dnmLxVA3lcjE^ONn(9FDu*$YT~3W|@|VJ?4JT>3N=$ggqe7c;;YH zk~Re^mbja+C&f~+I7xd8mL#?a`@o&nb8of`OOv?!uq?6FSf1E=tWDPVsKh#DuIfv} zc5wJvy(iz7u_D>tt5})X7OYCF4yzMu!kU-`?fv__8EX^UhINU3jP;3a$8u$k>`p9F zp69e;Lz4DO%-43EQI_3UpxD<~sMuaCQfxmKD|Qe|6zjtlOZk4nQYCH>%M|+^%N6?z zD-^Rwum-W`uokhcScll#SdUl}cDEeMW^6#>wqX+_?o-U}FpozU<}daW7A$rM+h#Y< z$=|Wt#13QOlJ+kwT5NQo^M1oD%LFW0>=-Ov>{u*YECf4I%5pN6FLCE$#bQ&j_vzuh z`}51Oa*2z>s>Kqpda>)VX0aQvcCj0=Zn1e-zu3)~Wi2q;zXh0|*sWL)t6q4w+3DCz zvaUe}7AA3vuqd%CEKY0*mL#?uOB2h(vcy(kd14P>MPd(PWnzU`mDn1rPHZjKWZh!a zK?&9-R*H3rZNU1(Heo|z&tbl{R3ndSghE4SfbebSgP0uSf<#2 zFy2EL=W`R5D{&uUg<_vzrDE+^rP$|Ktym}4D7FV{72At-ihYCiihYj_iXFmy>}K8n zj0K1dVj*HfScKT0Sd7?TSc2H7N1T0%neP}ZL(CV;5gU*3KFT;A{#b#;O~gvXPQWU} z!mt{#D6Bzj8rC9~fblNSC`&TdA#t~0J!0wDfY|Mr-C@>CHs&vOFBUAeWH_x^mK9iy zto59a-RLOd9mZJNLDzbR4`2oI&Tu6bE_tlNcwcMm?;0#q(msksOWIN_UTg!FELMg+ zC1WpN#CC~QW9gFibu3$KE0!f=CEKu0`Cjx_SiYp~#EQlCVC7=_uxha$j4_KwyY?N{ zEM~2yjg_&DEE#I8w=Z;zVYI2ka*g3*X;%6?&-UEo`mnF1UdCc4%DhkC;k4#>!tq$Q zY|jsCmNBIMSii&_ja~1~!}GiGSS(o59)}IcbCp2Mxx$pIUv=%r37DVQiCBF&cGwuZ}PwW*eO>8rkB(?>M6RX9d#NNWf#2PR^vG*`~3u8Y%!20Dq zaT8W7whc=c+l~dwd&C`Bk#)6^$4)Fy>=UfX$~4-hHY`cvzQE}9jO~4eb<2C4PRw8I zYphN3=)zLu{Y*F3C2{+)KCvDwK+=AP`H201^~(F1gIK3nAJ!`N8`dZ`h!u+cfu)N5 zg~f`otToq``99BvF|Ng^Q#;lw?aL@^XrvBY*O4FA$C>4Q4IYQRD8KC|VQa)DV@Jtv z;^Q&K@fh1X0b{(6VN)>1I~sNp#+XXOLa`>U2JiBnj+IHja3+=~7LMh}u{y$S3 z7OYh4b}UtF5!NN`V%)?YxiMYAkeQ-(2V4V_28O zt;5>HO0g_yOEzI?Viy%L4o~Jd#bGgW&mWIPN?ESKV#N}$M6o$oh^!f$f~88_0xVN( zA(kt)2rCp@iv7YGy3X&v*}3B#&l>E@uu_Rzja7;jVYOmwu|~0CtX1qOY_7GynA`F+ zmLzk7DzHvT`z+Qg_8c}S_9EtE_gsUneXGI(#9qRRB^VhLhf zuoSV^u?(>{upF^EtU&DF*nWGn@$RG^E0MUju?n#UtVZk|tU>HOtVOI5>kxY%>k<0^ z8xZ>sW_Os!qY3jD+lB>;wP4|5JF#f7RxDm@7ZxIO5I)0_C2lvCF4l=VJ%{-u@14dSdZA_*nrp*nB8I4{U*#` z>{%>Z>hEPNSmIv6!o^<2qQ$mg@nWxG$ztzf>0-@Twphz>TC?5VjzvqEcVOXSJF)%N zG2Ayfzklv}&d`Pp$nTH6SdUmY)*&hy@^ zgV9)o*f=a->dzM&l;5V~u~M<4u|Tl^j5!xZpL`6)91g=KVazcx?0AfMEQXzcapVj; z5n~RKVWAlFj|@8lWA2h+VHk6(3_Ay7UY23!W6bF?EDB@JnPF2g=DHbn8CEQHIt^p) zoDmm?G4IZ>=@|3#44a8DZ_%(>7;_#Cy9r~yq+z#U%(XOZ0hTB44breMv2@H&>`9EP z#@OD|Sid|Ue+KIodmby7df76(y|m%)$9v|azlKH0cNA-}N?DJh4y%^!ZN<{$x5nF8 zy~H(Oc@p;?)+})!U}X~5gtbfDhgi2*E7mWz3!_&y?jzc;$Y!EEww(I}sZan}Ye;%<HSg6>^Sftq5SghE& zSfbc@SgKeQmML}#mMeAzRwx#Wm5N=3Rf=7W)rwt%HHyu^TE%8!{tojTnuT>r-1S(m zSQ0iUHV5;OvHLe-0b(~{A!75f1iSg&;RRTX#N9d^XCB|%uoQ{A1IrM*6U!013o8&? zjFpHj!Lqm`n!r0!Pn)kuozdc;;?17fQ&yTh!P$FX_x z4dmysE37%gZM)}Q>IKYS(pF=>wyTZ(eGLnixOyyH?0qa+tObh~YsHeqc4O&c-(Y>V zvyHNRk7Y~TK`dYF7pz$9H>_OjPpn$Z{gf)tth1E;i^M=iQ2R%elwc4LKN zomi>Z9;{MqA66^YgEfkMhs|@ZIq!Kc`YYBdaYI}Slfo-^8} zLCjC=4=l)f)`%Oz!o+;mIr|wi?no?7%pa?feG9~rByKX6CN>4j5<3~o6AQ(P#7@V` z#LmR3#KN&Uv9qxzt{U$?`5dfG>^!VXY%10#HVqpRn}PY-o-y*6i3N%!VWDElScTN- zJS4=3|LsnOLgWVk}eaek@lk4=WTa!b-)Sz$(Q`v0AYWSff}O)++WQ)+zQ1 z)+@Fd8x*U-eC%f3Z@~h@KEgu8c3}}>|HWd&_FxHO-(V?X`>_l$#}m%>-Ynl3tU$~k zD-jF8D#QY@8nH=OgV@BVvk@u#MWRP zBhTes?>-;J`o$i{hQ!ukV{M*i`mVH3U;$#K*cAD0+D0r&>^Uq^?0M`au}UmM>|a== z*o#<$SS|LW*xT4BSx5L?%wKF979`e!oi4TmyIgE1mLT>8Hdm|zOBV|&bzZCH@6Z#l z$&Q5%AD(55;@^12zntWZlW!-5VHJ)z<5|Jk*gCOuu*H)0JnTWSso0}pmtj$|z00we zBrX>F)O|1NnKwHP+as2Moo2b7U3;Ey%*1|^xNET}_n2eP_7btll6D@pPU3FD^5xpP z87me`!?sD&F!rg{XOwRxHp*sNA$F9k z+qfF5m-4N_n#I;)mi2{^M=_RReQwyJSi7Ws0((i~)?=Sq?MB)S*ir6vLp|r(Myy-5 zw+TB_;-19%C9WJ>Cvg=RBixO9v*$5Cu@|u*dRFiI#A@sk`)5X(-@w8ot`19(w&dSf zl*GM-O_KR8Z)0&1*Wfgn*U^YYNFMKFNh4`p=W`R5DdRvt#C#-eGnO`z)^(n4!*V6x zkFi>rBe4T372AoWinU^~V!NU zlqJo%J?j+X-Z2B4E@Ra1z<$v=HCU|VaSxU#b}yDHmWzES+bh5_C2l2_E4BtJ6kCgx ziamz0?xk^zHe!_$SB}++RbY)`&tt7(FJPTwRamdsW^7RGb~AbZ%(juWUSwRe9cz&>sSfNL>AOc`Jrd`GWk?=l zF}u9K9FOHloIe&UaYtd{ViU0fNqaO_B6b{BAr^?$h)u?_C692dLE_HBiX|=rYmvA} ztV8S~tVb*gYnJUz#Reqq63p%}&#Ox@f3Y~svaT`Se%6Vy|NPVy|Jv zVog}N*lw&#_M;Q4mbkrGy;v9aE9(@Eqc`)+Rp`c=CGH2TU91o57W)b77dwPG&r0um z!U4=rYzPao-ZtX?#KOe>!V+XojboGZKG94&8jBMfhb4*mVrgRIu`DrvEKlrctVk>n zD-#RGs>Dvh>cmdRnyjrx9bARAiA~44#AaZ9VzaR!u^TX7+gnB+rC6ZYCM;B}3JZ|B ze+64+n{ABes=*>9?dw>q*c(`)*!x(jSQC~h_950I?~a?X0kMy;TuJ*WRw&knm5P0i zO}73V{_WzqhwH#9C2kK^D|QfT6zjuU#r}tNiXFmw#SUYGVt->kcJo-;%ACi@G(Rjv zY$A3@);&KOi;%cUn4isDAN_PJM&cr{1hE(_MeIr}PR3hYg=LJyx!Tj2Sk8#K+Riyx zf!GaLiC79&AvO=|mHBjOSdGM`V+~?=U@c;cunw^*>_=I1z8>q5xJGP1tO>I_%;VOM z`HOX6!D3%x;bMESXt7=_UhE*2Eavm1v)?kyayphRb`Dk`&u!1e@+Iy(tWwr9zXS`m z{b4*8xB@Gdw6R#Z*p*nd*fi`a+1^!Hy~NGLn#E>e?PAwr-D1~a{bEU&WqoY4rzzM< zcUe5`#@(2o#I3?w<(w?Wf~=2>?LCf#iIrecV(YLtu~IBaYy*}i_6(LKR)OV-RbfS9 z)mWL>%UG4zW~@%E25VvkG4K0>W~@!@W2{ST7uF}%h7E~*f%)2+jXZW^fnxixP_gf^ zNU?q_R_qrnQOu{@*_WC5j>R&?#$mZ)zF48y1guodAFC8Q8mkoxz#7F)!dk^n!8*mx z!g|Hd!3M=H#(eDN{zhW~VpFjYu^22uEDnniyBbRni^o#LW@8y*H()tpDOiEne5_Wk z%YR@c61Nnq5c?LZ5j%i2i2aO3+p_pQ%^MQ;JmCTCTf5mN{)%Nw+?c0mW5vc{3mxY6 z{IK!vZy9)=4Iht@+Q|2GEXZ16*qK}I}?VgX`hScurGSc2FaSc=#|tU&A#RwDL0Rw4EW)+6>O z7Vj|29Q?GipEhkNRw(D`GAvVU1(q&pOR#LQby&XG2CP`@8LV9F1*~4|C9GNOO)QW_ zp}fzHx3EgFtyqBFjC&gk5ql3S5NpCJ#CBpWVy#$*SR1zAW`3XZORPuYc4KyjS-wsz zTI^43v3w`f?-}RuHParAC5uhLvc*oq%EhK)fpW~R!RjS$2G%Y%3mf7J_I`#u8|xFh zWjL+5E<_rZV>c}mE0ncp)?zU-{@(VtWu0VO>u^}U)@aLh&u_;VkK}Ov?Rj_Xz=CW` z9OHV-Cpb=GO|kK`oUY#nqp^J6Ivhcq=iRIi)`t0dr5%F>*eb|()JVQ#u`cd@ zMpFx8+}j(6wY$yJcKTuq?M0Mj9A$B>H+KZ)BQ_ozVn3XD*eIj(9ry&y-~Kdd?fe@% zY<^fa=0H4gM`H6?FKry}&prF=k4=<1I0{?Il{uPy^Q@~j5u0nzp{I0eOWIShE{9pqq1a&tzNE8i$=uEbr8<%mUNjZ&7W*iJ{9<-@h_ z)Sie)621gvx^&q!*fi@NuCxgw?ZFIeP|_w~XGz+b*gYe4<;r&!Ho>;hsDo><4`n|R zF>B=7a;3cv3ly7;J>{;eQSQ3B-f6PEBy1AvhWgOHc-oCQ*r`&MWb7ER8?Z}8zR&I| z%Uo=<%{-PjVo_4Q6zp>;%RKB1TNR>Zrp^u$Dy8vsUJ$1Ij7JZEm=V=<&!Pu?Q{Oyc$+O610?(tmC zHCCOLjvaEJ&z`imVS8=PzRcO1jUA4=9Xov_?Wke<2X;PnKbpFCe&61?y$tMPu{*HI z)?>r{o6Vi?LTt})+}M${i?C5PS3l^9%fzmd?cIs(leBkXr%Bx1*kxi_*cDvUKI|E- z)Nq;a!OoF=%f>Dg`zO}Vy~^+x_S|)PFBa!`0CTlr&N^6({U&iam}Q-7q+NoI5nGC# zDruKt;bOViR?6o(zOLhOA9l0E-H*+X{aB98lQQRFizMv|Y__D$$KI2;2e2C@?m=w6 z*hAP|Vg=Y6nD>4>j6E-Tti-aU%&V})vb{pATGBp(?Uy`OWA{tiHQ3h@SA;zzaci-) zoL6Hwe>~SwG4?He=6J~tl4R!iE)u*bz7$2N+UV9$uH!(I@30^2OM9&3`iD#dov z4?4$kI{RAZdAb2>7u$%nu|4nOu?gEuoO9h@PuenUhr~UJ?UwD8W8Gp;VTauJyUzQ2 zXIY-cO5MK~oUNtPp27Bwv`sGS!VbFY+0)*CgJq1g&#wNh8=E`Qp1SVQ_hEyyYtFlI fPusa4`-|TO4zD)*+hJR3+ynPu&e34r_bUGf00pV~ diff --git a/extra_fonts/mplus-2m-medium_18.png b/extra_fonts/mplus-2m-medium_18.png deleted file mode 100644 index 8c2c2bc6dfb20750dc76ab4f19b122e193ed4404..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 291077 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

!v}n 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 z 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

1C&==`<4V_Z3=0Z8`wD7yuyA4QLb9#eraX42%Vw zarJy2CuYzK(z{!l2EwKlAZ)e6Lv6tEG9gjg>s`6b{N7bmw|nc+NHtV>jo6j zmtJ^sSQ>~P4Ia!P?P?P*H&%33wmcZa2msV5ikny8Qq7vCcyXjSjJbwcg%gi|h?Pp& zKZW&UG^6h{52Fr>CJNwp=6ed+2lN+mF)Wnoqgfq2;NYeo3@j>X(xL6FvfKm$WeY(IPm#MmI6C!>e|=v71+ zlzga1eBCP-fi0LbaRsrR5M>Tra$_aCFw!Ynd=$%#Wn6F-1pp>9J3zi?&QAaU(gBd4 z>Z%0*O_f)&+qNnX?okoh#7Dq2&=5_X<+~oU8ye7Z1JIVa8zd;rA|>q9e`rcrY>o^7 zl)O_L-cM#;UyF%x>kDS~k=)uEpWml;Z0%By8xMS`TDRUM0IywWRYIF~l%99tC*~v>!v01_sf$xTK8FZZItK_V4 zJf{wTw>xg?SLs;PGET#&C)UX#9ROBMSV-I)HtyI4?V3i@k@?*;07OA>yo6P&UQf7& zVzHv7I;WbxuF>x1UiwD3Cg;5k)TsmDyPELjR+yCdJErgI8wj}p>KXL)a25c7HgViH z(xNbpi~b5WWNpJBsOElRQqVYn#I6sn7-osAi1QZpVD01itRv{QP~&%iB0_%o-OIRx z`O~hS=UvX6$z+_OMHgYYZHh?x9e^~l&R=?*vFkavm+t^9IER`w?mvkJ()EOHDG|tL zG)0lu44XLMU?Uv+$Zf;xSbOzavWWqp#11e3paS3z4?6byyR}eIh>#~Hi+tYH44KN^ z-^2jO&Hmi-2WDy#>zyeJYCuD<>-rI+oo!;NYO{+4W~m(>m9o2W;*i3vIAwT)Hn@PX zQlWJx9RQ?ZJ{9WK-4YN0AU30%YFs~rU_8L!5+#ST`i@o;MX_YxA<_Y05&!@vEF^%$ z{vPIN9x_aS1prfe22p9hS)H7+qmm61(PF8n= zVJ^GJFuMndxq&KhCQbB5>n{ccS1u}KCG#OE-(j?~8P-P189+-C&OOx2-@vYh1pq+w z6PXvFm-N5B0GXC8h8s3Av|Kg*a1EB*&+ha2hymc-Muz&5DPc1fU;TeZ&9Z@Ypgf;# zwZh7CMXEZjT8QdD;380-I%YvX*0J@n_o4S-gtTVf>Sa)x71u~b>W`zw4Th;b&{VR> z@eC|$G($iaAy520z9R<7w@G*wB@+VxZp}2wVFLi#@Zun{&W3eWzDh)61H_v?%K_LB zCHu1zY7R5Dw9X*=ZZ7{A01O)_#7*B(7@h=xS2a5LDe{Mj6S*Yj{a!H}In~y_zXyPe z;DQy#HO+c&0suwxH0A#QfE9*r27vD#svP=BK{Sa1SdWMeczK-7lMKc2A4Z*Wy2Aej z0Lp;D!vLrdTwz!TKq$ss?)mKVAXaCSt=klX>-%gM)FSXACR&nofSdMD06;$i1OR5~ zQ3sA5z@u*|j&u0HZ>%3$ave0IIkmea0f1vig9euo+qR26F+bT2F012wJ*7Y zbkpAn0L%%V!E(6IZoNzK+1pN4*8y-Fk!3;jKYDSc$($e$-C z05HBH=vfP{s?^;eX8}Wzfv@e7LqXH{dU_dJr`YG#e<>0)OvU~D zTKZc6u=>L8JlD{<|;%0AdH= z`SinZUbd7T*G^o)r*P6YmC{E*VR(A0RED)nbTPntBS&^3?RUu|IITuksD^u8#IwM& zi*(9ZvZOU31v0psXaWHFCGjE@lMWe&$S^=u<3Pl9v*Z$Y*S#dj2#D~lW+Y-lRPYw% zVGtReNd^GZNScDCh5RW4fN>mOUu&ewX0JxRR;7ubd z$p{iQ2sP+s&ci$U6;UhlP^%M(*HGa`ZiNA0p6)p*&&mJ*6+Tq6fme8YsQowqbm>Ao zAy00*kBh~pQZgcBT%nV`s#0+Tj9==uK(G1WrGQ~I?hu3Bl#DX~7OupQA^Wh1RcBrF zC&qzkc<4Wb5ceDYyGQ`Q2CPNV{^0OMeLgx`ybKZf|(xIEyN%H>U^E7&@Y> zKx&R@jzXdu=8(tox(R*Jk%Q%q*(eF=CLxan9x~Cz75ws->7I>x4IB)B&Tw@m^O^XC zFhn#KLGK)dB0UJw6G8MMcU&j|mq}cHCg#|Kl6epD9cz&E>giTE?$JamvtTp{F4Kc? zo1q$<0&cR08^;Gk54?9oU=%u|5T8--*1(FQQ{ux!k~mK$r#>?C=EL{% zL+hbBqI%-}Mm&m0bRe;p{oqdw@P4mw;_@B~umhQTfBSd+p~)4gua`+}Bqz#D{!K6P zdA*1+r~^0fxAiib(Id7GH8IlIW)G3=3sDH+=(Vi##EGVd%-b-i26qWX+bsu~)$S569p|rjXB^BM>Ehh%jnir~%2=Dq0<@g^sQ9VPp3a9g; zF*mC7IR(PeOY}quL`Td6$3s8qo+!D|?{XZ*ELE&J12~)|O;qO!ARPAyL=SkhMmZT8 zn5cK*5+WIMoA^^FdeB8NiRco~9V$I2MZFmOejNb!h{!F};EAkfbyJCOw>?_gRMHfn zVRqWaL9tUM4QR|+TO7dwFMiZmk^!=6gnIo9fCi3*gCXJYuOSPJE#l~5pJz@CFlvaE z)cfU0J@g;-=xW0b>Sf5KG0N*Dqelb|rMh8o91n}hbTNa}CF@E_QAD>BI-5q9*Y9G; z4eHY)#dl*u2HSVDPHIiAKA#eT!$Y5PY{6ST^q$sllr|wT%@t}pG{;u>HL!fT08cj} z06<;GG}{2_?*ilLR>(-UC`y zvmZUkkBrVno>t`O3=(DZ(5oC)w$dN1pDlT1de4g?rZEaGO{AG4Jf9p73q-g#$i#Hs zDg<9x$s%Y~eOv^sS^%>(8K9CN=i}!U9_6K6H~O=TR}d+9LLqX@3X6^1sOgTR!@O&Q zI|z;p;R>yI0|;C;45OqTb)ej&4XR{}ir$UG2ZXXB+{LhMx8lSzs)oz8jXORdy)o3I zhLJpwBL+JZs2bM;r~BQ$Tp^;lg`Ja$AnfBD)&1QoWN`cr)9M&G3qm-DA!yt_9GB{m z$A|&pxJMv7S&+&^I8KgU4~e-;eK&#tVzbjdpy^%?yl!wrn>{7a9`O-?L;--T&xU-6 z+=AX;y?vDQk;#BdwNi}=8XV`#Bth_PM}@5O1=W;0MT0ONak{2%@_6vnbwLjcLa z@vLY#t+&KR0*GrqXx;d0p*n0YmSKd+kCG_~5ZL6-#A(mjNYFUUf-CeWb^uef0#-7V zHG+6e3KGTUtAxe6?$U%5Ul9NR*9#7LCas7d@LnrhLb9Smg<1}2&T8r!_W?j^*YIfw zr;yhulRGVwK!vIcM5H$clD+c&b3u@!SAf&PI98DOj_;bqp?AI{f^ol zs1T8yUO8<@py*nIwkc7&=B75!6$yS6ZOr32hc?9%rMqpL9Z4nKcrO~lFQfO0>XJHQ zpB~BiVta99pLbJ^eV9eBz$jdi10`3p6Dy-ONYnrvO1OEEu2lqKQ5q73q5=5z$G~YK zXqDlzUq;ZX=bnNK%*TUW!%I+4RYt~>u!+r%?t>_al%!Jz02F=IEz#=N0sB_}?yPT?uVR{^u9Rcu9o|9Ztcs7ypOs+h62*<8xkOo_|Jcp0D#|9?5M}1@XXiqSR{#CIS5wm_iP)xZ;|haFa}m)g(@0H6o}WYE>6bzp~J{i6o}=n?<`$+}O#*gPzVCDEv&0>>-@ zr^ts7WCV1Wo{~6Tf~GM5F0B$r&Wf-N0KhOrNX@V7>$*_i^uIe?T8}$Gp)R?)aV!!p zDwUz_kh)|jJ#t_2*dgeCJ|27lLdH1^fI^MhhH&*v4xYrL8CN6P;wrjPt7%L@tuidm z-9@o?T!ZkaaM{;3>{D+P06=5I=k6bddSnKlQC}ae#hMD-9FDUK%|+o-4pDPs6l?HZ zB;LWWz45YlsBaZ2oT!(^h$<-NkuZ4vb{eA=agV+(L@_8*;%t$EH-~-wV`zG~2{=G2 z0su(lnTPtv3H8sP9Ya@cbEAS;^Ag<{K6*eqOfO!=kz2Tx()wof@YJSfiT*t0!}&`W ztABQk)>qWY6lZleiim(@osKBKtWM9BAFe^un0dPcq!`ddbqUCpD?N7Y@B#FoIZ=}9 zf$&78)x!=7l+i^206+(0VT=mhWVx1u&{yCb)J)*m(gMVWdYH%@5P;fHQ)H**0sttf zyo-`3pie7KM4leek=4Nfz(m?kMh7vBv{*`yC?)^^F4u)JEW73%eR%OUhnb6=7$Xo{ zrDuvt*0ph#F+tgA3WJ#A5tfI;@<2)-0jTF$M&cukfRXDNIMO;e_AX!)0ARm33S;;@9|g+zhGF1puJJvD#&e zK=iOTLfwuEhfeib!I=|VI53LG%dsBCUJOnza@pnQPe96Q?G0`go{e~Mor$G*r!qP? zato>>`qmmk#Ez>%KGi7cHy%f-aTi`>Oz62yJjqy8Wm9FXT{U>hq#=Hk;>BSH@=5_s z5CH(gT)URLfsA4YK;m&eoQRg|J3t@G(;k5AG6MiB63_uk_>nM!1?RI?0!R+abgNpZ zv8@#nD+pBcp`D; zWmHoW03cmCqXa(&fN*xTLnx$=2mlNo^*CJQeO+78%`-nN0N_1%c}E5S`VoM70vOc@hBmDI&De_Ws-w`? znOPcdCI`r90ssJhN9V8Zhq#%8R`E>89JK1mZf%g^U)Y?@i_c(B13cGk9uUFC0LtyU-sUssaFFdyL@|D1v#b z5dgTZfdNqdVLy7{fIuK0wX9qLY1ft@BFGxGNPJfv`pZt;bDXVzV%hiO`4GP54fK#+ zpJ#VHe4T1c4jWu6U>Gv3vEf727*>|uvm45pKX0!4mHMRXjihfG>?O`smtW%EB?$l! zzhsc;t@h4$nTCfA_9OVMR{rT19zfIO!i3~<>-G~@ktx|^m|jQ5y7%p-3k0Zidl_O2 zYDad;#$t}#f;QhUyoFZyo=h|WfUsePU+(fV0Q5<2fExGx`gomem@#|(i_I!0PFeN0 z0O)^ZZ1`f(7>)^5f_@HBW zSIPons)>%`TC2f@kLdkJmx1MnWvYOZZViX8fm=lpCw1gXy0KU`jzAYyNT!HvM$i=F zh}2HgfqD_DD2qCEQa9%Za2l6&aV5cXp1WmdepJ2?(f1_06hL@Quj)NPn{ z0~w330QsHmAO?U|J3E*aFxo}r77Q>CV)wrRz*M_=5ds$}e+7Vh4Y1h6SkJoXFzS7a zS`Pdz04}sH8OT>N02s*_7L80Z*gJ#9v6#A|mW+*_W+%hngFo)r#{d`>p|4m&HFs!Y zna0pI7h#X80Ch%}W$_6BEL~JexvZXD0gSvQ44{S&Q1J~P0{}@^r2+6i_xHafa~2pi zb^hKi*?4c~6>k`VX?WmtYq^gy3sa~l0~4IqsuyA8eN35YOl=33CGqT9TKf&~7ZEOm%R^ zZXN`eoJd4Uo(j$!;NEZec7DJG4&t)wLDD8qCJ3%TbRCgV_(0}|S3wGC#>Y*bOsK{s zC#{Au?(+!WMoxy{N!KE4FrzuL1c}?_LGbuwE^3xP4uCKfQT~66pSVfYTW65TaXc@` zHwyH(R;hx#3x!A`KsCBvq^Pad7bB9~uqRNdMi;Kv>v^O8TR%!ykkS>1YT%j#dC`EA zrIL52bj8g~QJpSUu+|~gm|!3DMNg(uOeENCfH)kEGNN@HIXytdOoCMG+{zO7W%E}r zz}9t2?;)bg?Z;A4{*VN}Ql0fgeNuc2^493}dadV@_%%yuZxHzn+wn!R8elicU*Q~Y6z_EDIsnBlkssk zr^SC8IJZgk9;G|8fLnuZ4plW%f`ux=miUyD8CwdA4?mW#|GT*Y3J>Y3&AjgPLb+a_ zGY|ps3FRVAFEw3+$u~veFSOH1E}}2Yt92;;Mt>!}q}1|za_VY^LCgn;XFXd(@4tYF zl<9$+rXX|(C7VOL5|~dN01rs)ro=LKH>7=3IPAam944!owGGItDQzku+E*dsnvdGq z{=*VMp!w1OZPX2tD)ube?ZUz3J!xaH^m_g$fsY>AdPAYM47Skxq2M)2bl9Rg2uP82>mP7cgyi0_$xjIQ=7YV@9;&4V@s%!3Kbz)!X_3z=E+7WtZ2k}BFNi`#p5es5PNYx4D zVhs-;Mf9;172O~`N_4*>G>##CeWs#PqKs&dW?&1C2KJ0dv{k}2UDwVKOKfhm!Xam!hd+~M^loSQLEZ*2yVfWJ0M#p2^(~Q2aWvx% zWL;=_7kUuU=5#sh%?7D~C~tctz8T%ZSxyP^wn4lTg_kGX&7#zrXsd$DA2{7Q(q)tL zcRc*N<``L%l5NgI=mByVIb#X=JmkA`67Z}Bmpd)8ZvBJmGnyB?uC`qXs~cwkhBoI8 z+o?10$Og+Wu;w_n=P$#}R>9Pf*_AB!d=VB~td3?C6@9YH&31Wy26|`>>eR971A{>#i$~ zb)Z6AC{pyh3Uo9gzN@3L&ljSc9<0IHD8d+F6MKl?)ZE$LC2d$>WnmG3*6v-^cLm(! z-nhN(5Gt?*0TSDq>K=lnj&qMcCL1&nB|mFiJW8%h03g@Xr2Y6&%6Wsg3>fo7;#=yN z!yhUvneBm}EO%3{+bQSp7d8C>RM=EatcQt7e##zhTjF-8=&u|A2VnqMV?u*kI9lGn zZcy%UoaT^~q3smP~DJBuhM1TfBQ9t$4JKl=AyE zMRui9>!I)w05FZ|R%qcEb5JFaWDDkPt&^Z}v7dq zsIc*YJ`Db9E=g@+wb`&Wg@{kGKQ$yW_?EZdAY>U=xUtbuQOGlk_{Lwt4*2KLb%Lgr{PsQm zp5?mAZdtgbyg{P=Iby5n_pDy*ZV2>yQg=6OB!6@aiBPgc@Ip{hb8GI&lHzD&A`?cf z8y1Px!d#T|+|ysWbGe+8fJsE7D`)6U&zm0Vgs1}lkTGIwsmAu@n*J zI9p1e3*avBCD5?E(z9WhjER`1-rH>$`gMnVj7$*qto3C8kl6t+hMAK`0F>drjiC=! z=#v2`Qh4exS(?63rj$JDiK$9h>;MXHyY(PXO!3_SQo;{2Xpak3qvPIJr6Ov(Iy9Jc z>kD%#g}fp((?z>j$;-qJ03xn$%fg{UzGWbyqnpEr%!eExz9?S$pRZhk9ajBwnR9KV zj&2?rlHw9odQ!#UI$em**i1K%l%|GK!JBmeeD`t%P7AryAvpnn${C)WSeQ2pG_kKk zuY`B11K`hB{&T7G9s2WBF{D510H}0}JNlpP3h#}7o>uGeJgDBRJl*Ok)3WS0ZgAC1)^USURm@_2l!ZN?T7AGA#z0n~~`8$bmZ*x!IG7~%AQ zo3R!^HC%V$+#`rkQX3%X^_AGlHj6g#QL*0#E1>iakTIluoR5GtDpGYhbK4ZQ#|kVD zpNa35=%S{vyVQ$iSFJmAD!5z-n;7B-yBkEf9u|wQo-R%S08qisKZOW@k3-|QY*ajb z|LZeZmvp23_)J8p{jR705-;84l)pQUWmD?jYLcqiPiWwafaOB$VxWN=9R9zROfL75oH$T}Cib!@+kddkT& z5WHWx?#nReGCJ;*ib7;86Gxn3hKIcu8SO8WHgUvEiJ#WQOQ9A-Y*>aFF|PyLUPU;D z3!-7R_m+}(Wuq9ijOyn+{9*rsPt5-WN^&A5#KS(c5It_^u6l6JH|LJZMjZeKcVly+ zMK)abZd2JznyJ|s0N0Ky3;>hw)DW)91%af^WhA0~20IH7Y78zjIM_NF)tS2fL^nc2 zb!r4q4a+Le{cr=7yNMiE5nQTXpfyDFMXPJV61!9U3J9iAk?}^%^Esnx9QtJ50f2kc zWr^rU9ROH^aL0#r#tRw{kMUyodUCXKiURfQ%bv~GLVJ{0?)TH5?GbiV35}xz8pp3k zR^!6y>fG#*60DqwjI6msmrF_@nxO*DyN0&^^wwTXdA_6MEWbR+s6h(uW8trTq5ljN zBBdeh09;Q1z}?6%$l%%F&K@U|R?P0#O!_i4jb69s>>vQJ`?0qjI40|UwSpBQx;P-T zGH+;r40zzTS@m{k-Q{KgU`n(`A>%aP`lJsPcwW$L+tDlcP6D7rU`re%6990ASq@;B z;~cuwM8?>Z@Q(Sgn)beWS1+rnR(9D@5=Vi;!69Stp=EGsQygwG%$~9ivE(uU(02gC zEZmo>$)bBHBuxN-9v_2DmN#Y&gGJff40aIfuz@-AI1`KJdvD=VHRB&nD;)RL@D9L7 zfQbRX=Q{195L-H#b_T(&GB%_5&8u#Fzz1?=)tx?6{CDebi3syKEICWDeZsJJHmfhg zl36thkYOhLu53GmzZpqjUlc{64HbKAaz=;X*u?-a>HshR0J$4<7qd>60j91o9~N{& z*yvQTmLg8pLPbjx2w)SvdwgUQ0FZmIWeBqCtl@bK0I%#@1BJvMo2wK~D?5DQP#OTL zp|77E^BPuM_s-f1R0#cARCsRNrB8rdoVWcHF7?+QzI6h?+pH6 z2gh%#N%~de-Ho3N4+a2$R6GO#y!P3)PXPdYG^5+*`F8KX1OPzlLAVvtHwoz6_z1w; zR{qpj+ie{BS+pbyRCEPiJqL4-{X6bms{@nq}z0gtoHH#bLCsnpHGu31O@SVtUt#d4Bat3TmL&3b=LBoAs)#R@>yfrAZtr&$kk48{3jOCT z=RQY-$y(y^-iY5vPKQ{ZhVv5k87=H^D*(Wobz9i72(#u87spt##~`L(?#nvpR)!|cV{9a(*M+kk0gt?_{c zzyVdUQfK6TYsq~;jB0b`!CcSg(}wh&0|1x_XBR_QZicDVMpU=H$rY0ORSVQO;lQ%4 zQ8L&q`{-!E;C}F5t;SX0GRezf&nn? z%NUYdujktRA4I6IL$#b6Ft{BJF(_x;YrX*Yc?^K(n;8K71|R23;B#uu6v}FOkV9gb zr82Uyk<;`HA?<=nFHAU;8^6a|@;RV>%fOo-F#vj}FaUn;xuKvt>k1;eoeZ3EfegPQ zvRt4XCd(;zDN)+gu-xPI0NZ|-&~2|!ix&n20I)))Jho-U14yj+rx%(DYR{F1{VMN` zb^X|O+43}CY-z1jLX!Y!DuKokXL%I^;9~zLc?XzcZ5jGcD1)}D!n>G8SqaRw{ZKAQ z{Jfd1O#b)pe~dsAOSByNX*+l(Y9{p%h!LD9 z{@00?k0t(^Kr$t*>Q|8`x<^USyrABLYf{~WAx+f*a4D7A2YoW%MgQAS766%{KW}?R zW%UP@BjS~Z-;Qukof!63Ed%%Mai^-6>9Q!^-Hq)q}FZew?jS3{wRF40a(?OOp=o#-F$bRj5E3MK~hTbq znzil3(qxF>5INyiWZRFP3=JxB)`8ILNbKA)RoD#cpUoJS)(8NMRb23u@FX8f`p9%)6&V=)oiktC?CQlKq~E=k+i2Z4r}5NxF?60PmuYk zY9=#FBYF&|M1Z2YBWvprCJ?i$N5x&K+okIs?M%|wSGRZ8ywdH2;Q=+`jY z9lcrrz(F9MrsA;1&T}efXyi?`HpSwuMpH0>sCg^@t+ypnk*9SjOhMlDzyF?XeP*dn> zcQgh7&EQrqL_WhSo+_YXlAJ|g3|Q107m<>R#G$3 z!@Ezl2A7Vqj=5WZcZ*+tkQ7}0%cA_q-GF}iM1ki-{5^J_0M~d z-lp^pP+)fp0Jt|tG@g0;#{d8*N&rA%66lJ!@`eF`H;KND9bf?WAIrt~6=aSMe((M+ z*&I!cMVzuJ@x(f>S1ydilcRphf|LRn7yvV)nODohd9DGKCzL6U;l-h`AL4c2+- z?#vy)ra8O<0G_Z8fRbrU8W8k>b%P55fc%6pjCutLtrEd9o*RpL#^Q)`;$0Q)N9c^{ zp$&tSHPkV+wuJ)%^}bg`St(6Rl~Y@9qvWg$+!Uq)Jc{DS9}J+RgpkI&3<3alg8+b3 z1H$pXoR<|cx}!4yfO#LR`Jw{gI3k=#Zt9Q$@TdAmiNp~Q2DhNGHE}<+*3}*YXRCAs zuqQv+9hwqWg68BsTqLKwyc>xCz_no;ig;!q5l=fM+(r--`M}|@xS8{o*V{G0EpHk_ zNwLGF<$@CcFiQ|Hvs+Gy0bm`oE%EN9bRlbO;hop)z|!!hymL7e6$5}0Vsa>wQ%w~r z?V^~72vyFZbu1AZ6dN$xxOj?fe~$~#XG8)3=F~}>)=reVZ04hbiaa0YOt=ZR;zCk} z3J|UW02X}*P&J{z{?`5np1oEy=JFzu4_85?%|sPjrnHt17!%RlFN|b@E`C%t?lwsB zBmg8n31&k84sg_#Dkn9{iMI`72e2hlk*e+L<6kX~PkUU2I>tYvN=E369XsQc8|jZN z+Xr0>n{-g_n5ISWCV)%Z3^Ilv%W)~xR);@`#1Pgnv=lQqUjQJL5&&3Te)Y=_V%v4k z8BxUh1OUui%42N+0F(zK0szR7UrYS7h=`8><2Gy(&3V&<6@jXCo|_o}^Vit{0OOro zzqAb%LU&z>MAM#q+mLATY%$t-lBMey0436u8@${**Hc2Fs+GuFzl#At{)(U7H4k@y z8N;pvq?}wl>*Zq<+ZIliHbU0ckv?3#?4}EV2 z9SLt~vV{@##LMmw+s2d8JBJyzWaFS9?yjIh=_4RN1+B&?RDIc8!0GBSr%XkNfrZ|z@mb0V;7u5s&QvsYvmK_sWi#LBIp0Cxol6$C{hP|Ic z+mJ`5O;418w7Anh^WP>Ft1D zO$P%YYsfy`R|9fYhwKKqFKi5R+%6GgE9GYZ6s~A)-|{IUvkrh7-@d%?DYzL9l(}$4LLaD@!z&|BR0GS0Aynx6b0^S-{wTq#fSHkLc|xPB<}#ct5|#s z--0IHGScyUk3tAI` zfNS?Jd9quPp$`7`1|^u?clEsOezLu~v9xJ-z~q4~v__A&!2jxg)+q1}kc4oqv7);H z%0=7(@&`;2phN@bZz_(fhhyu0(U#4js7NDr{guZ<&vAi4XSHUI!h^n0eOZ=r>; zH=&886Te#bdf|!zS|NNN(jFRYAbMo8R#PhX5k& zr7d;AiNGk)R#!tWkpKV^t?SE-7$NqV0kTMC8l4dUFbM@K0kzg~2emr84XfwR2=I@5 zxKZSv4SRC~DbtR7H>JH%xi^8l7?* z4Ia*)%lD?WwQXt}(evw_Ai&CBkofiat_o7rtW7`eL!#+&^g(Z`J6DDIAK52TE?o@e%3;3EV*!5MAi`Y5?FBT}3%FI-hY**aJ2Y(GK`N&$oBU9bXD37l&lU z_sEtlx2j6zK7*??*zuq1j$W~rI{vW!CC9_TQ%|ft+XBUyJ8#R&F*>@>q#=rG!QQr2 z(5Ncks2cBfx|Fj`yUIWiICQqt^>jSGixSltK~QYUkwY<8CIrxURe$OIG!YoZFlXD7 zE*$_!^hgM_V<`>(b6=2%+O8?v!2s}E6&aV*<6fdCR!$cVO6~Jq$7{*by2s?(O3&;) zvf_hU%9ErbQz%ILMkC(}1-ty4wjhznAMmUt>xwH(RhMZ&>OUT=DkW?AXOAr!ffd}L zrm7O^eQ-rzRX>`3ZbwxyJsvZ~kZ3*Jm_nju{x!`YhdW&xk@$o?cAalfUZBXe;HvPE z7xGN=xAvpW!ofv5e3+`|XYV*ZOqPnfj8 zK$h6j)uIVgF2eTZJ=Ohn-J_Su0R`b2kV1`$Krq8$vS`@>;o+=o$NP5?9g|5W%&w3F zbo{Ho+B~LGd%M?gon@K*O!4rI>dlxGqE$g_T!>d8~{@~X)&M0vd6o0<#ikEkE>7hS}(PixXAq#7cB^i=sVBQ^Fa$#Fu|J$PeFp(jeq1s zqN_-n!tU%kr5y{q!k!2dVZ~w$|lfglqWVN?m#fD8Qk*KWsJ|~)` z|FpL{2nt6|e4fr=xfAXFCf6fH=DbSj^jrh3a)~FpQT@gK*IegdbA=vuxe$mX@{mJjM-XQJ`vBDe$lm?>NT-8sYHj@tsS4GoI7hIV6RaWUd5;KwVzMEp- zr1ifvzuQe@eatz4*#!XpQC&fPPevcYocRmYEP*~>*dxTrBDGo+K)AH-3EdGDT3m&k zN_t*K=^#Yci!vPjF>RnglSCA;`uLBiJzZU=`PTEM9`ufc%#cKluQGX^o)%)$KZpt@ zS6XrNd8YrKR!sd?uXXZcik_;8Zkf*;V1^siTM1C6BV84Bea2i!e2oc4F3(fZR_X^LOMwVtMi^GEePhEe? z3*qF59up<1+Z(#mDu{2a*r$Q;yxdn$#&w1N=?Dc9A^F5R?)>~t##(M1_w@<+_XEUZF^0X6$@*_SxY?sZOe)aihT%RqO}J1ps!0iiywuAiDuR~N znGq<10teldx@r8`CTD8W$FOcMA%dBL)6Jo@Jm%8Qc8Tf*0HSGI`Y*?vsA-M!;v{PG z2PRF4q6CjYY~39ep49?vsGaemr$NdF_HX_y$U4LgR0TjNYd}vO_@<6#qpb5mX3R3p z0AWZG1>21(r-HkFQmIJ;-va46DbRccp7666R65*dTZyAyPW$^a+7J4dNR;6i03I3Q z;@-AkOa2uKbxxM=u~>QH#SL5`RU~6fdjJ1!!td7GI>*vc)S={ zNJ5_Zp|;ykdjpr;FU2sHr?ux++Y~ts{&~?cPp^X@l4n;3Pir3Un;!L8@|h{VdmwLD z1+w!l(>sJmsnZ7`1*eP$b?JL1Vo_!+vB#sH_4ITyt4V4MDE=9jvo@sT7oo9UMadb^ z)yghji0yO;3|URY?v1H*j{&16JE%oZIhRdk(envXP z%K^8d2j12=QjJtsFTsFjRFamvcn4s|Hf!qYYR{2P@s@il`Ra=1f~^U@;IZU7dq%Xn zH4$)_G}iR|j)ozt{yC-`?EZP4YWM@Q>8c&Y(@H#~$24ir%{3v9Q<)Ye3u z3rTHn&hid)$hp$p0027jrfeOVrSNEE?A@yzERqKR@U*SYT9Cw%S_y-XODuE71sWD& zc!nu(-Gw4hZj7E!OW#dff^_1(iJ)o;=MM8Yik!ejR#p_Z-IAmQJK!&H7v%fz`jrk6 z^EoqmHGX3FuXYf1!Ny6~KSp+;D-Jy~O0Z$UkaPfKvA)4$KLrGxb$B9JC~Zqh2#cuB zxxR_4di)tPCN;{Nc9bNR-4GZw>eHSK5Y%h>6tub7FeS%Dw-8k}^x2(bWN_R*=S>L! z5N0Blm{-6UM?ey_lj6M2OD^#|RTW4BAn!x}`%@NW007<2?a*DB5*(3=1NRIbk5*eE z!~n32*?xa_m@E<9m^q;z0qzF2pEoB573=_c!-`D+pycXxQV_!JjblzZ^C}sAQZQ?D z-Q%O{$TZZJiaXhQG|}MVM)x7P_EQVNNrOcc`>{M&34UBXuX`Cdk+AB zy$)g7=C9oXWAU5r^GTU-ejsZonjf$p(-;5+8*~5|k83ZYfB--MT7x+ppCxqO8b_9W z3B50q!BuA7;07lw1@lt9m)yKJO4Cy3$;{^HJyej0d6mIntFzt#0Hn7%0KfnMK(})z z)>3{7Jv4I2p;6P23$5&eb9Hm7L$0evo1HFFpyJ_}NB}?+n-z4m4%yfs_^K|W+upDj zJq*2f&?5lQ72HQavb$#ux;+@KO!9{vFRpwKiIGG%qMjtY4{!7biH~g=&o{Q&@4z@) z*|ab}A;c^BshSoXhIvDMS$p|~c&_y=#ACKr(=bL|bi>k@RkEBKV(WNu!OBFxF{cQD zDbPb>c{c-q8w(KsYsUcHio!~Wuf^k0{!B0C_p_4u;UeFDg@wprO_5Jrs9wbYXr$?- z0i4j9t&aT^dOZ;p=rS+>Ofh9iy+^h#9=|zy!vp}}YCo!nEs%(^Wr|M5qsmW}IN-qy zN(Tts$lAPiVa;Ax0@Tm&d+J$IO@ z`aK>1fEKRy`2)NIqyT{2#CT14hnzF?fQyO<8lrAsGAGp#!Rj%j`sB81a5d1}K)B84 zXJ!Ym>Z8t06bk_Oe-MfO9KG+n5cgtdcCz``A z0GV*V9VG<)m@&0K>Kud=y`RrL03hd+VKQ}Ho40)g-8RHyD(|n;v~eOg#Wxh8m4j36 zx;Ab6fk8wi#ewRZ;v9fK@#uM2`Q}8{wzOphvxgcx)(^^hbUZY+Y6D(yZd{ld8SCye zJ6EZCUd9JNcB(X*w|gKb)pG4N2EchG{iC~WfaUvjN{32x!Y`GFcR;DMQNzsVjW?*8 z&?0DIV9nYzLaU$)C1Kq&0D^CH7>TDII(tf zAV(4PV8WECwq9xmS;CfHxz+hUBtHY7!O!2xq7?w(iW;dTqAJnNpEdeD zKmSwXN#~ALM9ZGR5g_z1@lmpQz3sM2*O73tq^V6lGP-Bj_z*Nib2wnm!dSJ2jyvLw z^26hhRbOBf9vN+s$(!B*GSECS-YWp$T^qk}ciLb^X2+=Wh@LAi3(UJIczAu|n(v+s zK>n@ODx_$=K%Q*u#eTbv=xk2)3PQKAD_=*0;~Y_%0bm|-k-=L&u=_l^-5=%m(E0{B zH_%h1wd4CWwDK}=sFFs)dsG{Z@ z75+NwdTMt^F$HQG0BLX>9>s?TtMG(1d=EEs;v%g1yB&jg_oznzfJ1D2udtJ-IvoJ4 zSaRn$yXls2ylqDr(Y_qs0iNu$d{bMfwxtF;x++~z__Km^4o2*UVA0*l0drZ>72Vjt(1u9e8Y;tbsmT=c)2)EVHP*B`py1D`0 z4=(N}5Z@!4iDo%INJYHl4!e~zicDuracM8n9_y~Xgl>&t^W5MsG#vmx)+nV?t}>_E zIe>0iZx+n2@sl?|olSoVd+lhY*BO_BIsh<;Wbdu+?#Gfl{D+lUxsCZ8*c-oQ)0v}wy1Lqw!(2_(tv1px9@{Sv5}TJ2bTP!wI@89W>v z!~vH9Kzfy@Z?w?dkOWqYCj$;o)WDX*Gqr<#L!QhbZiLx=ZwD417(N0n>Eh*CEL>0j z3NV~^TXDOF3nwRzfWl!IDWzDhoZ5C9ed&x+;lCI#@#^vN3;IeNBT2^yiDug|C{xt- ze$Wc8LkMAX0CcRs`1-leW~^~c;c-^(btpdw=d48U#(lB9LY`gl5Yb@$3^U(`h8*Ae z6aw9t?Nk9w&I+wULpzVfJcJlM=9c@pTb^Xs*ENNWfcmy-*}GR#m+hKI5}zhw0CdSY zt>udnd&x4MWykcG!*p&9Q5pcIkr9Kx*G}Vq+OupBdBW_Ov~Qhj*oYx{iIfZgEKyq8 z1>IG$X`pMHcvQnA05U~PER!$fg$n@quf0r;Oza~{Hl~zQfcAGhU-~S4ho@%*GjI5$ z_S7~2pf0u679paCjl0H)7yvLydKC>p{Vk3ty2G60OV9p0w9xz z30Zn@Gt>;Go$>RTM5E(#TOSS)?*PZxG|V{8*3kM6kXODliZWdRz`@IwWr}Su=pAxI z;9-lD4}Dc5lsDS)W{ZdOj92grYK_f@ZYKi(TxLHe2wH)pG)LZ>KT0J&R5R8%!v`)Z zDfyH?b$RW>Dru$1!~THzA_M?2_H*M-a8#k=b9aA2s4-_qY%fu);T+l@hjf7kU)k-d z;BNB}PzBlr#dlDmf1%Nx-`W}cvT}WCV0{n(NZ#B}%J1hkR$m+_*r1eeUk6CP_F0%) zr0)QxWU@5nJWt~#&+R*h9=t3tC(ZxchOPku08F8YZ5}46r5;pw7$06IXM&^Yc@sqn zQX2zg(ru5F+PW|NSCUL@9VMAv?n>d1BtPX6 z08rGf1_1!WT6-aGn3);W)>N>Uutm<))XcAd3F4LiPXK6alImp|?>`wXkuv}&djHC) zY~N&^=4C%`!cK_v9ia2g8A09w=%Z9>KLcQ$p>^OG(Oi@ud@C~4Lze;r)FyMD`Sg%V z(c4w}a&Q6wZ%{@MLqzX}=Ao;WuV{`&@uD`L(X-Bmln9wb!gN01WYo z2)WU;!Jfkq$PZ+KqjBD3EZrzsA9+v!K#5WNS3k$XdlQ?7>mSc6y8T3GhVNd8ewu!P zna7t4ZO|j;J>)i2ZzaY`IzQ`4f}Y47A7sNM4<>Sqai2 zaNK*QNT$C-lcqvETy#HaA)%LNYS(LYe@qc_43DXz*^}uamY_a}NmC+bOg!l0z8fa% z(XT%KBIvJrFnYQ_mdE<3Oe}bA=9={2za`SRLOc0H(<-&AAch@kXWbR7I{p!6{=elf zqoE?wUxHwMlAc~xR0$P`lTT=ao!kT>BnCT4ST*r0LW^yMMMf6$AJ3%;-9FE0ip# z*POSPjUlAn+)CcAnNe`M{w6BCB6dn$JTh_!MNy~JDmBJ2TpjNY%iPkqP-uygRVbq3 zs^M;8?h9{ePl9-b0>(`oHK((zByO~$LNDK@VTw^4hd!)qX7n+ZKV|phAVGgVO#v}u ze3MN-8i7_v)Z=j)>xwCJ3B!=0jVW2gg1--3prQikh?;wyQE&&{%^o6!BOQTz_@%nq zz~9Z4SSUc@b2@vRIr=(HXexbrx;g>C8B{o7VF$SCVXE30U6c4_^%Hu0R@IC^Tf7oa zQ5cs3XE^b@tf+#Cj=}*#qeq|`2*4VLr8Vt;=xub$q%0THqj={*u>p={1p!@SLyA|i z+HC*;|FKO^WKKCs%J*ik;;e#DP)3QZFO{gx-cfH~mQ2YW-W(M(W zQXuZ{b)t9ns?7Z{02Kd;?zSLhiuh;Ehw4l5o!A}wN+7~mwOt`>=WyUM7a6u%-v#84O_g`L`d zX7Ku*Dh7Nb1{HWT9w?7Qe^n>~HLnr-q(9~GfctRXnsL<LX+%P01l;QS4k_pu>EIM%mZ$4?J*}5FrltNL$EW^bBaWNcrsGZl!uE5!zrxl zwXnufM2K}V&mD!M1?0KA6hqL^I=zKqlwAD1!tCkoq zQ<);B2(|^N5}dX5$oneSgI&bQ$OZz4y{B9ZfZd13RO)YQUF`z^Qi&+fb@@!Fu!Nk9 z6A`+A6Q#o(ImY%u8fgkEMp=u}GYJ58F$+#;#|*2ZQLPp|Jg!~BOP)VACoVJY>?8>d zScwN53>f@unG=_IfV;XvA=W{~AhBgVbZm*IMot6Z0M|e$ztOR=u-ldaK*2Unrbi<} z(n^%u0}j)v$OBLY!Te_naEND&Vhw(yCm*>A03h@GU7S?pbPy*LISCG)YC-@2Jm$Yc zD^MiFG77}=_edVn9tuBSG^yg)NTB=3SyFkiYd$>E4>M-i*m$-sou^0OAP1U0>H!vZOU5yu*h1Agfs;BIZjP z0EJ2GKcVO(08AQ7jwD5llxxCJZOR}6z`OHJA8*i&vs_+iG-95E{4GWeKU0p_VO68L zNV5Rqm3MA6ehwwy-C^D`fNl)2Y}FvuYq84=fc$+nIipC4-*WfD-ypFH)d}79JTMoW z>GI&9TJVX~XdZ!^FSI}@#9Hg&0T#plFFVz!17L~w;I+__)X(zy!4(e2pi4^p7(#5y z9=avMsIZCXJNSlP-vNZk+8~Yq4&YGccENjymRrvYEQ{+QSXlp(2tfSv8Cos?&?>Rh z`NW7p906VNntcoaw`WBD#+pQQMOcAw?dLjYlq&5M7 zrM_BlCH~NBnG2#~d*o$kmh!&}6>1*u0IN|XJ5}RS#1SCu_F9VcBVb8~ACtt}nTxOr zhKEP77ZcwzTv(AXq67d~2d`#?xX`w`8{ive@u+Ypr<~fBa~|kwTN|lQ0|521HfRe6 zCIP_p468%RNrs$mS=P9nkAM@OGxv%CCuTq7rM56~Acc*^R|A|2V|8#_Bd2vVX!z8V zynUWU#99F0YJX2H8G2oa8pZ&4@-?z*9pL`J%yq1^u70dUWy+G^7t zb-=9%JidoWDwZU%1Mn3B{grjr@unZadN~qn!mz>R@Zc9lonjENzA$;Y0z3QdXt-x) z^fW1en%x0CQ|cDz&O0`a{i+cF*u84z)S5KhsjXhxENzsPO3!^-8>&BBaZnk6A(IXO z|GT+i2Eds}UuIolJBW)rK<^LuUTRa)JHY#Pg?$c)Lrp37C;|ZW7_Op}^*vYyiN0f} zrvVi>=#Hv80%id0iWK90N-pJF*bgwd4^pnbk^|i+xWWQrY@4M30GQpl1^}erQ*L7B z_Ffx~d-g&~CLjMHQFCwUk_qPAD}rd?=J9hxSUtWl;|Z8_P;ScWii{F<@<1~?fWeNr zcT@TfU{1|q02E4+J_T;q8ZXV%0Wddul#hU!QH24Z>u4ANIlG8=Uw{K#|CA?IQnov# z^=HX^Da-DcD~Iek@4%)i;b?K8H&Nu#uwfq_%&A`jU&W^#0bO90*#TaO zwoA$q3~IYVKAZeCT!tfJGBRElc!60RTk+z~t!ih3`d$ zgV1-XrYK(mk*KJKQuvVy69YiRBcV)3>s}maoGZQ2e+Fmw_vDhE6d63KG05DYS{ZjLi&3 z(XHDa`YZ-tbB%FVKw1C#{5Zac6v6ILPl4ZUnW!U{!j)S-5>8xZWs`Mhd@pN8bJw{i zj(aNN2z5vtc5m&IPdL%rJ3%&btlmRdJ>_GwNx^XXmeDUFb!Rnm+Su;nN5F?Q=urUR zQH2!rH!%g3E)pspG7ZW?0u;#rAg!ZOYG>|ze}}3_olSgst^MJa?u)@T)x5d0QC1{H z_+Ov*VBPa@W;Hrl zccCo#Qk@gxY^_{GTa=9UvAQ5>?2l2Us0nufuE70K*~V{&yJYkY0HC^pI{+t=!6h6; zCTmcV??!5-qb0wHX2n!xj`tORlMEb-BcKenN4YLVaI477KVZ*rM5XV71dum0L>B`ziUx+`)d80F$^9T z;vsN9*z^P&jhHynky5*U)O|=LD9Srr#9Li5dz@@`3wNkI+}L1H9)O_b`p8*YIZjj1oNrDMboLB1OSZnfO0vEc|t!A zmP^9(O@+?-mXf%)T*E*dtIulg4I#QV3UZGZ@2fOLj?lb_}$rKp% z5osb10MO88x<7JsJ2#kB6G?g=ONRh}Y5{<&f#?`YihAjeO!Q-!-j~}gtMjI01=0Y( z6U`nlqa(L<%1|&30CXG9OMtl7sL$h}ZB^|F)nwjER34Zp;wGThBziy*$BOSfI$%Sn zANE;i26n%eB)_zsFJ?2cT=w`iBazS_6*iI3G zAf5@vQo9Nb9PhN$Nu9xoDKN02>VNf(`#-MhyBh0_X;wFLG7^~Rnhmx|_!n^e zUxJVzR|}%oyPg)&WFA@4d1kvH7FVXR$?8^>zQrV<*CYyZE5H4i^EVmll}rWBL-%QJ zl6+02r6AlClh*}-_}s@FE&4an?V;+7@en6g>4`4g+1x)*j!kO&-xr&loO?ZGuWdNvIo3fum>mZDka2fkX@ zi_OrVkVl};E)z}v^}=Ql9&q_Rxl4-)SZWn>ZET*y3HKO*u()y#{eYcrGbScQBu@5 z;s0svCv`f?r^1N{UV5{KuS`|^gqh93VN8J@ORi`ti3APO5N434$DVY53;?m~R1~+W zIso9x2E?GxgfngF)griKzK4A(wIQNxH>LCNW+3cgx6(Rra1PbzRxP^&BIeze0KR8S z@{>gK*`|jaiYx%ose4GX$wEPCY?z^DHZ*j!e9=h^uxj;!DIk}sk2Ay;Ts0?#Jlr0= z;Pl=v3uC3u#(U1Ba3E&ptT<8vfjMG2$2c0_xh5$doCJV(6$1c$Do|5(v4njB08`w! zn%l13+ke`_c$T~(mhSbwRSW=rFbfNmuLs9LMfe#?H$_lVgxwNt+GL}KA^?Cu#pI>| zO{K09gWV1;8uW6|;;Du=MZIrp&(yg( z%iIm~qEK{hagO!Ly7p66rk8GgDH=SM^g^ltMm`~)bb9gq(^P*j+JtZ08M`vOM+w`>8`c5v!SGF0x{C0M=Z6j8{0_V)MQ!~wrc`?q6%3$|D_wCNQUB6HC# zSC>gb;_T?$QB*$;^H9tgt-%`brXZ=h5qa@>tNq9;mHG>SI}!3MujRo@TSkxwBh~_# zf8}h*U5S$J5|0y!-@2T?l1TiN0C({&^JXY0i>_Pq0XyAd02s%LV$-8^c)xxG{9^59 z002dnEeoOd4u~UpMesPy-X%u=Y~*oJ5rA0iIEN#=18{{i*emoS09-=s024}P#ciu? z0@0Q4?r&>@0D!W5b7&dFEsb8K5_#UbEp;Hukb->$AM!70AjLC(239!|J6O}_HnLrw zBHdFv9RRSU0T8J#-Ypl#K5A?0fPGU1o-pIovAzCk3jmNm^X-|#aqe?b0NJ0m@XH4 zJm)Y@j|fke>JR`JKcL+Yl0E{i8Ib@SZ0v5gGWlTa0V4wd5!G|LO0f9)EYZXrU{2fj zhAI2OlfDm%QBR;EV?`tYu%sRYAF@cSlq~fMg-_%!EF<+d)~yc!08+Uh+}81XOS(;- zSq<2hFuUF&g#l2}u_OQur@&8fYP9*U0C446Ie*9)`VEVM-{B`rTSAe83daZvm?gN8 z$b(UD`if>B=a-iTKw%hr%p?HX2KIqa+MjNbm>$Xt51^j$q2c}XqB_p z>u4W8uh>q+k5O3_uNOGr!P~rRGAFHBxxQL55(A)ZBgnAvi`fAHpxDPy(fu@t zZzzHyXz%6>0CG}JmarSOjj{U}0IS^|0ATH99RMX;upR-impuw9^or1TUa7T?e|?{g zgCi}U!H>(v&i*$s__=kp@u2CI=B&ss)4(B`R5H5_Qkf2b*{qW)_wo)fuerY)iEZKt zP`pGjOZkzxBw&{WY8O_AizuJGz z4iKB~vO%S2ZUiq~;D=W^tI$!sgk9vB!nX?mgqtw)J(LN7UuJ8&oLOfR#{b@wG6HgF zp0-#805AvO#1_@wcTZW}X>1am(lkbzgQsWVSPM(ob=?lyP2miYZA*f0*b)H1aXn7G z!mm>Mwto z0np}O;S~U=!$~xHvhF~Ktc=z06HWr41BPm+wXQ3&?L?CR(DLLA0H&Yq5df&v0pPW? zbmIt+A8iHz#{Y1cdWj+{yOwdiJ*XNGKZT|dC12iPnA&4XN;v@W#7@a?8+kA3M?i=C z2RG$!DUO$JMeo9<$I8_)mPBgWLiU-{AxpqPtZfx}3Dw2zdYnJ_L-i~>9|4!!bpR}> z81)2bTz84A)%&|Xh5q#2H~pd2c(DDos~~h^)@9VvJ3t#_@@u>Pl>BJF3eKlVD7nV& z=nT$`hFHQ*6v7pfjGsx?V7DziBsNduWj6rFP;uA*fN0wX5h=Zyf!niou#Z2iXYqMf zDxw*-^q@`b0D~E84EA~X7bD{rB@C+bpY_pdl9$|MByD^ z0st&lP>n5)s{UpGV5nXI;9SD5004C*_09pM^g{gS41nd0YfFNB30&fruhq|J>ciGXd_M`sYGywpffE-t0qL?r4&+MuT0swib3jhE}+}zSbUrRmu0EGzg z@z`P26Y-P#voTo_J870o(=QH{R~sBXj}MI+hlmOjZy3V{1nE0ZBG1hLFb{?JK;SKd z#U_bnw&_V9PDdInj}KizPp3li30OxAt#yzWy+ckh8;RW_=zaWL55}7}^eg~?A$6Zx zL&$rb7v|$b!`J%XbeV_v4v-nrNvcq3w#f*iz! z5u|r*q&BdC12QJ~fS+5-0O%NM0|2}i9qZs;d%6@aVgP(+`#fmmZz*@I@ypnt2S4f~ zOVqaHz6k&vOod0mOBux{?e_Z z&e=`D*M35llM*fX>H4q3Vb!78Go&vM-1;3PmIuUGLbnN} zgZC}|VUn?{1?QQ%ihlp{eUV*tGL7zF347Yix%r?C9LMbf>3Hc!x1h+QOBev5tfiNSGXWWuB?j-8f1@g)cgN}hfEClH zUYygdtSO2e=mIL9Twgjsilf}|PKaoHpY2QMhyVaQVcPEg*&uAT&*4GW1n97^b`tNt zkQJT(LXsZn&cy&=c^xVcz5Z)DDiiZ;AD}y|qSHiK!JENrl=0Y)JSt@JJ@2F?_jDxY#WSh`qAyFU8H%S0MB?drd5Gt9d@c@zaWR$XIn8X{@0bp(x+9&J3?ImquEctg? zTM{Y>2RXIa$|YS1TDv(Kg_^Z&=4es@cae>=6scz6!Y);j3brrClouB-4z~@YL$WGU zvl?qw1eM}F;RXPp?4H#%OOQBsEQ;#VH&(2_UnqQx2_j8}^J^BWlJfwizvL$R9lsR% z-@pGc0@w3x_&wjQ!?9hxKN|J2F7)#1-O$7${_7C(?o(`?NTk`Rhro6BZ!iaS0D!Z6 zZ)H|@JNd#t!B%Ig-Ma>f#-Zug?GCn6?N6!v9~y;>C_XalH}YM~e*GBEWGlO`=RDmV zT9C{ECg3s--k8e=N6g57QzYZTY7w_UCN%%8w4&(miFMlF6I9A@lNt=8O6}?1<%v9# zYx2YT|9cOAZzjaU{Ck@oy-2#PLVL!~S|)FjSWLR@khxkfGf8wgJTfQp&g&#N5%^`& zOTQ&W6{mF#`wMCjlDt$qJE3)H02wqwfe6&!mj|<0Mnkz~h@Z?gcmDTf@k570*N>9s!Az zE9^3iUJ(@}o!2QykV1&E(GAYYoSA=>Po*@;SB_xRnY{EbcA_o;fPH3(IZb#v`#U2& z^rmY~EfWgazf(10P5D5Kfs8_S(qhjLu-Y>k&P-q`Q~25OdE3dB8f}E+952KG$%={R zOv72oPbx$zKzhpmnivJi$V7Dx-R4)Vz9f_WqwrYlWsWXO{#2L& zFaruzY;?0uuR?V*<~@R+1{%=d%v{kj7B)ImCOX!BSq|Ykilh1pwp+6gi#OJ|_;MRForXzUS7dw?odm`sKOPS5c0? zd_nL6wVxYZwi~jAillqjAxe9@sfxMTbKShr>qJ0dvJQMVtyT_TCbRaLcdTHG@+yp2 z71kmdC6+v~G%AWyO(41T;L^?+^Cc1I91?<}=+($$lbOk-QNsE-+uW&*{Yz9xDS4Ti zVl!hi836NU-Z=#?@9W+lc-6->$L9KeDoh|ky+l~$haC^gP~*S^3Yrie1jwen1?vd_ zhf}gmtF!JKNw$ye947iww~d)8dv4 z2Sw3uny#XD34ab1S}h?0++!MM0Jux*r)osXQ=^QS2;6P6O~hEwaV7IDGXV!|?HvZ- zHOh!4412A7Lpyp*GYk-A9k^~kxWe`Q4Jwhf*}NB=VP6tO(qnmECuXlh)f73MM+-*T z>f^Q3CEg`xyPw!T^t)h zf@23i8rj62>kdbnb%#NDiDT{7001&fPgtQ39KgVUy;maOfNJ8UF@rFJY<^*@d#$+kton5FtanMDoCd(- zNwLu)h?8^uFa}lDRO}^#=i!PiMU?IE^ujk5)Hnza1WkzMa4R6Sapxuk1CSheD~I>% zI{**Ug}fvp9)tA8`f6E`!iFe@h*O}q)Xg2GzlN}Ig{w2f3?i03Q__oI)bBchYyp5e z6~;rX1Ta2iZt(*EuG|^2MkM9D=|N%?L3g55I6NzrsN*-9Oz-`s3;=X3E#ST@l}a=d z9f3SENT#|UBtvi(-BJxnDAB3azS3?o?p_;T-;7h@-O9Io^kn$3O^%XPS&C>P$oo_B zQZsxaEb+{YsK$mSR>ph{>!}gjiL^7`1OQx>Ozw3Mc@Gyu#r(BXrz4)ZL31Cb{*x-1 zFOx!K-sWb{b@}$sFaSjKvDT%SR;>h*%z*OmUzor8DQFIRafV>(fDI0^){lX!*|mw1 z$XX=cBuW1PwN*kA3#9_vHo+jOq^XuYh`ktQ0L+QaYDj(`SmjsQ1*>0*t;ygKVxLoUHen6h^G-O3T!7WE(WGyKF3V6^k{26-7_oXLP^@3NE8 z5#bNlZR_j2>p72Si?0H4ARUSnp7}ya904S>KWQW3%x|uICS@5XQMM%jfIq{T!uxNY zGhIId-e9+FTr&6E%wMI@77(jxEM+zX_fYz4?fgW?@1qL$H1>A@u;Nvz{L~Vh&`*yx`h2Uv z2>^&4poR)0DYTL+@Vm^!B{GF|09+TJ6FETGT-sb+!HjrYk0}ooNaL!w3Ic z@pxogX0mb;>ut>pfQ~kms8z!3MzDPWKk+~zDc4DItoV)QB*alaD)@JGr|tH$~V#@}hcP3dCf zvko#V)*S~m%33D?V3KqI0Cf8R1N;5#xz6^w<|)A(anu72$2KpfE;|(@df_jxu3F95 zmf~CZix~hH)8jqYwqSrH>;QIvItl=QD0+-N<5o(kfuDUhaTu05EGGdd3v8BHEV=Pl6WqrVKv2hM?elEVO+S^TvukJLA z1qO$UcNbkCA}Pzxo**hC5SXV)z78W(+eu+z(>-_0btH3jC1jbx8Kh-yqQjopZ0!A0 z0Gz3rmxVq2YUytQV0ks===(I>Wmu<~-@vx8blQ*mL{ z0iWx_?78m5?(_N)V5_TBY8x@-&mX8fK-L|nI;ZUi=k~-h7jUe>U$<@ANw`GlSHh^| z;XFWh#smO7kt-8#tDTLh-GiIY100-4p8h2`n!)Ki?Bi{th%2m3pZfF1$aLk;Z~DRK zSf}aX#A^8RS@eKpVc)Rcgkj8m7C zI@eMmm%I*VIaPT75N4 z&1G{`%GzUTCStFpNO?6WXj!3DV3~B8UP0G zOMS=uWJ&^17rS$o?s1;)!Yq~HTBL_i_v0k0{9G(d?G^Wwt@o%C006Ga@Sgm_+VYm@ zlJUWy`RvvAS^V8;0Az-78SbI|lvZMy##hCp8p_&xYWhE|C8H()P?)j_OS);E{&>bi zcjlU49`dp~&(JNq!pa^B)eMEt?#Sd7z>qVhQMy8?BoV$;OGk(5!sTnpq5~j(1Y`^K zALWI0DacA~TJYuQ62HQMU!WKM;|#?v6JFOHxwiGyXZ*DQsw zgst~dqzBX1uc49KF1`#Ln8R7y90nKpA6+?!R`Pb3_t6BQ74vJZB6`s0t6VveI1#|F zjsdWOq9>{|0HEVX9zGN#lkd`QI3OePRvL z$j1i)qWq}fxE?IAAp!^*8_*_;dY*}~l=xIJd#ULWs;vm7OI~mOcI?h5&GmgLS`?jd4CfZX?rfd2 z*=c2tE*YZ=xR+xm@Bzb=K6HdT0Q##MUL56a{mN1wRote~u^I+I7w-;H0VDHHVgM+? zQ+9G9KEaCn`yNo~*r~(-A60(2Xo2gAVcKtwsOBx*1jD~Hv7Wn&N`FeUlza=$XA+^u zNFvM+cAToJFcoa~C237&x3@kr`sMh)lIfBC`8y7gdBuI!^|uoFfmI@F<*3KI4Y3RA zzq9p^zy|w$M9I^R)!qBH98XY!R4kxZzyQ+eYxRF;8cqXDB#zV~>x0^ysJzJ-HVXOu?+5AmgidOod-D=CJ%gd!+P8PSMLx3C0wZMHAzS2o(tRBKx40Y_~MKoXAN zM}dsdn(0^0hw?KeCyK0d415%zsBVbjZWY%z!yGPBpy~j~0^=eB(1~3vUHz{K&vdH; z0Jz=k+{PM=)QE1jpb%AB?Y#ignF^$8$_uu9mhv-W$+|}+^K{Au`PzCa_!Lp$9b~?b#4aXDZ+f;O zFGw%0$2j;qZXE}H%V(^)W!WND`SzRmyuBn}yeKn{ftSk;VI zZ1Yx?4hJY-Uzo-Rh$@0F_WsrCG2BmsiFAd3&@$+&!Z%tE{gSz~u|byjmz6A&%8N;I z4U@E+II>Q4f#dT%LoBSH5gbkXcs;H#$(gDI`9YfV-2h_wxa&E}v}sKb!g`2_u{#qi zXtFU3n)4>dc?={o_-}Cn#`AMg(p3-?9DS^5v)TSdBr>PxeM{s`n=B%pc7k&mja365 z^KnNqF}FB4DYeIzmC3w*QX6E_jXTJyf=;Tc1pxkA{xF|GL44x<0#78!j0uN!*09B` z6p|TN*sv25f3n z&}@9`G6%jgdt%(CO|NGHNprI0^1Lm5ZxW5rZpjW}W=|%151ET)*4X=K4B~9zP6|!e zrLIDCBcgArB3KY7>T!rhU~IB0KqI1Q8tIf;;>Z(1aclUA(HOUq&mQQk5S02)%O<^^ zH4Giz6>nL^Pe|AgJhQWA(ad1vxWxbM#q>?#wq%2B8Ha zI#^Sj;xIeW4gkOk+5MILx$3;ezQ0ih{k^V`XDQ8DC#TM;nnX#H>#s`wo%S=6h!ZNH zGSSVI_^1IvtVom6@@NNZibt9@JY}Mg#(ZGzSYhld6Sb$jvj7myoxd10f*K zrP)B<|M@@l@`FK)3C??LI0RBNeU%4fNo+Hd4?`gz_ZM?FgKK2qfCJLd=AYd6`5=k= zyrkR-Qpy6)c~O`WalKZ_Eg^CJ?z)9~6r^Ri_qQDi6F8Ng?k1rut!K@Wh_oG>bzo67 znHCL)SQ-vnrM{IA@1FgyK}G)f4CXVpkMxCN@%_*V!jJ;7e&B9lF6j6s5xzQ8kT{`)@zP$-b7K+F;ho`SRt+Qy0Dz3`rS-wXyPd)X#T4JoBg92a8du;oL7fj3d$;$g zhj&I`R?dmF@j4XIkjLChQW=O{Q;Z;W4*ZZuM*sl#5RvNWaRxw4{m!UTEJaadY~?XU z)|+MmHA6aB5c1}JCVmx&+krjw@GftC+=(Kdf4ckt5@EA8#N)g&R)@sY%mxotZoUxT z&Ape_)j2^NO|w>^_XwbZA{Tf=25E%6>+tR^`}6rRm+XkXjUvvXh|`7ML$jdrXhE2$ z#fU@nhUaQG5lN;naR3({Age2VNGwe9ri;+6BbZpqK7X%(%Dm5P;aEc#I64B)V0y}l z_Xl{$KOoso01-c7{LtILsOU*yl2&1+oY_dw6Q?eh5FLQrT`tUNUc(- zp!X05CeOd})B8O;DSt{76~L{7OXh*B8mBYm#9=(tkNbUAdta(>MtuX!0FKRfOtRU z{~!n=gunf-B3KX-`|IMY5ETH(FY>bOTD~Nj&Z>F6-+KdLoHt_!DCk{ruCA7lm|RnX z2U`{jDd1A9ECq4QOrZ!X9r&Sl&f1P~O1n!p1+y-cE{!F@Ij6>)uB;30T}SFE-(R=e zSsP# zxd?br zEthn^o*evuvfcdvhn($pFZErIB2&@IglKm2!{w>(Q_hhEbE69P*Es8fEBhr#nbX07 zvXHoQ(M%3k&j3JU3=WVbEWOQs_00Ov{DJJ3`ikL1B=>OTE*p=!9*V6%?vab z5|#bx`!HnXr(No+0Xes0-tK|yS6|*-?m&@WEKe-~`BF3zL!0hhrS(BWsIIi{MioBb zTnqriC*KNl92b>z16JcO#}WiRKzY2(t|yvR>Ez(i$10lf>hcph0I)E(l64$p|2|S= z=n5GC#?=e}v0h?65DvKja`y&DN05=;wwKHsG%pd)C$PoKhELi~20%QhyR!mtXSp7m zg()a2o4p`l=s}dU8p@s#_O071+D76yjlLe6$c;T^zs=|)M4qQV=ft{PZ+Qcu}mT>Bs2z8LHn z^gq8!K`V7zXAY^Tt~;OXV{3MuBb#y~hfuLy8q`Puc_fNfItHgM+_V|b$9o%fbv<#1 zcZu9FcnErHwM#h)0HC__k!Tx+RLPglE~UL`*8L#g?3I3kyn_G$$QE{;+R?>`2_Uf^ ziMEhZ;ti>Yg2VuLzb^hd0FaZqm9M<`gDQyzqeSr_b6<-RJ(?QrtRAOAr;tbI#Orkc z*4JXP=8&y=gpUB*?NcxSe$CKSQC9;jo#Du5s4t@Q4P+d{ zHJ;O2W4s^mrqs5pEd_F-<OClvp%i|Dm_wo;t%x8vT`e7u?NV0@TVPVQFOEjVSj2zIBe=P-){ z%I+!vK<#QumCV`+;~{A&08sTRtj^8y!m%=mT~6!Y=R6zj1%;s0mw=e~*%~tMhw}1M z^hlFTgW4H?hyjq@v&sjLblE-ek02I@u8^%&a^DjNJeJh2sT`BFbY9e_+IpA!8uA8g zmv9FF);_smmd}e7IW-~Gp~z|d2xw%zlMz}Vu{7FV@x7y_C#!4chKNVPgq#eM0`eg= zRb-qMg(8J4YyYiGG4rSLmV1b-n}3vQU}OAcIoFJkX-BI;>yoW~i&&&<^?tBAlHYv3RAhbWep)U(Xc$FiOBlt};)>mnl_8{?C<$X%GwXc(R0L!|;!TFozDd z9tAc;cy@LUt6Db?wn}}nb9g) zyqKkdcdX+~3Sxp&#%Cc93Y7eTj@{Ko-#BMvi+C0IrV!Niim(X_{hY!jP%A(uipNo9f@++fK9gStmhrT z6iFzHN66ALpuvTlb!cFt*TCeF0+}l?0H|@zE5qpM!R4F&2}4TUI^w}Q9vTDy(3pS< z{pwr`dO#vL6ftdzp9iUrF&q-R=dZ6u-X9@F=jU5jaUP^=VDm#{N|b7)d+}gTExmjf z66?y#6&a7hCM0<~8zB^DvcT&_;(G1U8Wee77fpl|#fC(ucBz;OVU+ocXb%TyqzM2J z17I>nM^vZjG&dCj0N8fw0kOi7+iDcQjJe*mffSj#pdM~eSXU@fPQV&s0Prkj<0(M8 zyebnij|0sB0A5$y`C%%@ihRi-=CevDyhC^T)&Y5NF13buebSwd%w*9yaRgBP9sq#n zlY2}9;GP3-$;L+-WfYl-BDp_XFn|~+na1pA>Ob40=KicIb}WQ?#dcFj&h3eW z>WhQfSN>ZJF66R5`TK3&`L66w{$=-I|qYVI5TEJe?K*(pNRB-P# zRLDd66}>O1j}jjN;D`Z`?-(G{n}kFeA@?7(a0nF)@FxHOxF39qeiZz&E=5Pv*O+<- zX41i#l&!Yr2Q|q&aF9+qx8O1p8&b$D6@0ATV|p+v1zS~jqtyOm11A$`Zw zR(7rka$?}4z)aJ1)?Zhs41m-qnWX_sV;r1#1!PR^)d5g54aD4d1(>4_XrZ3RR7+P0 z0GtXaphU}S831&lmZ$={LTkx~emQ&PpK*J@Ed6lTV=*k7<%moSa_H;<0=CN&-ts`! zHvb>yM4=%6Oo>#-^C^z5#Va;${s~zRI?H%z{H(+?Cnk$IbJB@{e0>H@ENH{KXD6!!MlorW^Vb69|Mw80&rfn34&~|z^Bp6&8JSTF zf$AY7>SPfwHnvN$B2yHZY|SK8UzW6)(V{~tN?XY6GP~$+WbAN9r*o69M|6A5zk>;y zRNXE~W|FCu=b9|ZgPY_yiAx?6nk6hu_|60gimlU8wRWKG+glrpD$w5sby8oFs~7N?H|Q!9i7CM*xJs zMrcW}S;{HsZ!+Do?;?rV$pkxg*0oL@()7zLu(}Fj!TMn4-Uea?sYox@0qsW0xQs&7 zZ&`bX%%|-(S1WtdD8=1gS1@tq6>HVZ`{Z+B@*6S|4%SF7`S;48ubT!;@?Qb}s|J_G-f@m#jhs zWukYFXKqO!O3saP)0Kx>4~Y=w90pleRv5fF$e&- z0NvoX-SZy#&rSef`Toy@WL;9u7xrnswd&&%Vv7{%@t!p*XmU=0$1*y7u)=XIWdKl4 z8h^ypqT8np;zvt$#&QKidOi6UwvKadxgWZngXO3J|H1Pu|- z0PtFusx6~q$)mTN455PiPuYX=$0Ll1*{v{c%W;vCdY!9yQx~LEoLcd`VWP)(Imb&O ztsa^c_ePMXV=c-+4yGsa|44MzRQen245x^>meZW=QRpZ_{O;KVz`y{gA=`~Zl7#y- zmt316&#*J0J2DRO4Z9>LH%amOv@ZM_Tr_ST#YB32y?iauXU~6K_>Rlh9dRFW$;pHY})07 zUA9FB!1G&n-23(#fBI^p-)HLxa|AmyAmVlcE zg&O-<+lQAv{hx%Fbqfr>7JTu_=;&W?M-Cmq5RbJ9M+g+HVDL1bVPDK2>dB1PP0Ckqcc{@uxL?j6whwWr4 zcuV_*iRj#aUH8IyGPb2!JsXM03w;(9?zUHcZUbbL0Kjz_+lJOC)=!jjOZ$%8vMu8a z`Rj4`Z(Oa?lOJlAR4UxQChM*Plz-m^{PagN=xah*k*^M0c!m1l+(qUiz#>A&VMrQq z?!SQ?ziLjT2!$y`_$E9-EJyDAicmOCnvkcCC3B zdn42p8ZB^N8Ig_zvl?FA^}$x6TjJ7#9O%o*igJUCzJ8`RGrQpE4JI7};Dqj{fE8rFpH7jj$lyrec1^~IQZ+h+1l!;bA#_^l`KysQrljeN77vqVK zH>S1u1FMJ>8l*jBzIdPM+yj)gYHE|AiDhF4V>2ji+t9(MVw7^`3|m{|3;@Jl+?|i6Bu!(rdI<1wI>&Q01ioFPcbxpB+JeYtGpc1-Z~=uU*%MTD5^Ev6B_7Yu0kipc#-4XTf*-WAniI zYq8a24Y_AFFaR>fj29r>(K32O)3~y>kS#*mHXP~3X&Pelz=ie2LqB+Vz}R%|%uCJ4 zi#y7k9Ow&SX<(RAH}oHW$W*tgM2@d!w(E2c?c(c%%ym{@3>lGC!Dl+U7yw*-0sv&{ zOZ}2Ep^}WLM0^SG+lWRc%x$R`qUWB8fY@0F3U{d7NZ)^j=>>K>n+RsjOEvgkV6#=p zlg!vQWQ`tVYrwoA91_q$)nXr6o$(=`-ZvjlVP}h}m>$S}h<9O$#7W>px9h`-tN{)? zL_7_foLwEwJP`n}(GCDCoj*h4OCYw-nA}>r;!U_l<2&RWtgmxFC$WdTHe@O0+bQ|?Tptt=7)3{HjoJ36{+pa~1 z>gwtOISCgTM>4Mt`l1=o5ViYf94zsuIf=S?5YXqw>wi`jk~0cf%%=c=^_XvQ0J}g$ zzvQbBTE8*2&_(9aHzIp~!x)_d094kO#%iU&3q0;!!NSo{R7wI*RjXkkX7Lo0{`WBx zC@2Mm>pNd4b<#)Jbg-3YHUiyfj?aV`5*HrXY&$rf-T^YR+nL&NV`DrM7h62NN`sB4 ztz{gHq*XQP9l+FxKoR%RQ*FO_T&fm!y)e0YCebV1%Hwh@bZmodLxTsl4HSv+4W#We zSkcaaj*3iF?yt2V0?>YSUK@6{M{u$9^WtT>yE)uF=Ost)6fS;5l^=1e0!RAfd~V@ir|K~bvl9xJ&RykPYdal;Y zQ)zHta~J9#J&j#OQf;3vbNyLjG@-is90*!Kpwpfz9Gf4$425A(_`tLcFzj~-=trpt zOu?AG$k-EDmbb!&&_O2OsUa+{SHqXyn!lQ6hQ%Ggqx~qd@^oegfWC+UU`p*ZrdA?v zmUX*>?g+c3%?yA}Mu`;k##R_yQ6AI~YYH^;mM`w0uWRwrrn3RlqKC7$#d;Pvm1}do?u^2?NSZGuY32O+8MEX@0k)~x9>!=rclP!=-eoFvO0RvvrMJ-%n<;&q5!}>jfmw%g*!|d zUffL`Isn#EG<)TQ=Y*zDGXU~-@rnExlSZsv0z9S*&HA z_powZ=^w^ap2)@Fj4P58)zg3c;UG@axZ?{%G`cu7bcphjzNC4SsD1c2pN6j{s}eoB z{b(3_g>Tj4&1CzYUpcv;8z4LMAfw<~pP%7s_$CHX@DSaj;IQ?szk+ox#{HbP@XnBo zrT@gr>Fi&N-1H(!b{uu(_5KRxq#sE{pR&$c)HTZ+X=P%DfTVih*=wB~y5Dzw;_GF#s@%*tnLzKG41y zZ?Hk7Sb0&*qyA7a?{#OpNRwDVkCH#NV1RtULs)>C7YzOjnM;{Tg9j2j5>M0=ZKqb} zl8fz;^Vi>5q|MVN3=ef*#mCq|uA*CEhb90(R|)`7LF#^@F8Lm5o}|{6)J6^(KetXLiZxyH=TbFm0MN~ZA#|CP!CfXCXuHk~NyYD~Q>XWki}NMAcaH_s3Y=ougexXZk4syuIH1|v^10769i4nS0KXz|(%a4}-W zyU7htiT9C3tWe@mPRK1J%wHjre9V1CwfvBW_`iSub3~uBr?pt!t`!+QVy7L(WrpDU z_5p*vkPJiLsHQFQlLJ$zv6{g-OhoUWcRco3f&qX}Y`|BtG$UP|$bdya`D`IA@)f zZfw!tmSn3FUxu5ka2@?S6Vk_NK2P9GKA8#8G(rAk?Ga61M-v7@GlXFg4_yWK-NJxr zU{dR2l#xGSEmKXiK@O3X^*t^sbbZ|1B0n(UosM%Y+%%}DK*U6UgP*5weqM08)ZPlB zBGKG4Su!(c?@ycZ#lqA7JxdWY!NZW#fKzWMOh06BBG04S`#4PQ(kKY;8BB7&SPf#q zCoDu$O!$QXlHb_(V-Wf%hx2Sga^RrBV;U0M`GXLH9knIonc2@#hMQvKl7`i|O4fp- z>@7i>$jdK|iwaU>0vwA&CtA^%qjbHA>ZW(9xW7);=Ad3_QyDm0YW$x-%t#dDM3e(` zw9p?wD=I9L=Qoe9Y9;PE} zDWjhvt(EmYRzyUKp-4j>LKmT)9;eU&p_qN*4N7!3%8SI5?(*L0B9=h#s15%|H*u~> z1EFIR7@YwCruCNSu^s=P_O#VdcTDGIos?z8Mig=zRvi6T=s z&D@PdenSCWoXv@IM?I>}A@8(Zk!gbKS!>=t$^8_tKx5LqBivE1Yl=9e@h1Q(_a`N$t1n*pMtt5V6fo-`Rp1anY*=I- zwtarv8nQZJsxpmCNfi%-BK`G{TOEr9iG-k^m7b?fLP&*>|!1y3aoYC66zJ z{dzw8U@IfDb5=v&tx)*XiI6|Lu0)g!4#Z+&ZueD;CQih3bS8Qo9xoE7VbeyDt5_C> zzi+^|NHDX7n8G3|36Lii!zr`CXq4JG9;yEHReld#_sD4uxSa9vrDJ+Pdme<1c|f*&V(N(jMT#&g6vt?t|3|?}$CTCDuQEC))ceX7SK5OZDma9= zg*gvb-zl{KxfKvQfD^z`q@n*@si@%UcRJ<5lON23L(9sjR3ER?9f@6?7Ez(dG)yw; zH8jnQ%9TOLwOd!-Uv0P8LlV^-uSuy1Lpj)Z2Hdco01+wJdz+CRJJ(Yu1OQDxp-%T3wEzH?hQOf%)*tSSV}eRY zG_u<%<5jw%HaRQeY;>FyX6+bMrR2cO%K$*r7Ks#aka9BsG{(W`=9xp?iX*~pVg^@g zchJQ9y*uaPw+H}O9Sg~*a0cJ0;-#gtw!xN81)hWpbQI?0RUJ406;Hu zMI2KZgNRhcP{iShAkT4+=itnsHP#B*ylP6=*szGGVeoN#Miyp?Dc5Th0OUDXx4rb{>v+l&bpcuz6r#&A zGWtF3ehI9AX60I9{I=1?SKJfQQ=*P{fIUiS0Gq^~ zn5tsTd%Jg8%?xC96gaoCZz;mD_#;5cZ?q)O|{Mw<2LlDVe+%n(EG-RxoNY z*KjY9x=$0Zb-H*ESfHvX86g3H*+?An`Pv310pONymr1Tr-v9YIwVNv_Fd)hEd z8Bs`T&{nCSNSBMEAXs{}loqKLrdoLi$QlxXJ2JY+At#879mbS?NSVZxgg@**vBU)i zKvcXzY4kL#P$vLz1>Xgg%!ZZ3!!rQb4^$hg{`tC*H!O|-AWw?2!U;ok1C`4pY5jn4 zs_->r^ZOOQmyDyp=s31{gPkP-Zl_bM9zK&906<$tgfj^MJ_4+Ocg!Xt5WDGP01!_I zt}DoES6FX2h7ryy0K#Dk^ri0t#Ks3N;X${o_SFsW4v^#>Aa+Eo>&Eqo-K6UqxfKs$ z!cFV|MUGy0QwzfgLIx)-3;AWEa03c%bNS02Mw8xy6vdE?cIB`aaELlQe6gHc9G&qo z0N_?41wb4gi2zT;WM?AYpcIpac$|rd1QKy@;=~Rh^y)++BEA=stSr_-l$8)W0FdViXTH<| zD!)2UyPk}^<3&k?w6~El>50|7J_&&7YStd&y-`e^#(i{3ySb0QJ_1OxT-}zSb|+>B z;E>qeGXr3C2xtZXI{s4y0MH_@Ys7Nt0RT=508F_l!mIAe)bd)wGc`%Ov1ntP;@scJ zPN|U}8^PrUOg{c2BuZt#SeeIt`Z}J89zFsXMX3n@G{DDa18*mFNSxRKoG^7kp8p5l zDgXe-;Ch$AdrP!I%lH%p3VD4_0st>22+mF#zk=(IbDpE#UoInYi?favMqlSU`r()` zD>`upd5b*{j%`!xs7>Ul30RVB_N__YuG1U+eqF{xE z*_h-|aiorN6Hx|0R-)0vKYsWlB8jq7BAlNA0FoF0ZHh8TMt$p<0Dyy51o_Rtlqhoq z;Ov6_PaHv;y_q9`J`d&0H5s>N006`Y;s~($Ft#1 zE*O>hNGFK^fV;!(K+Moi>pq^yoLw1Rq(EVfn+1CvGj}4d#L2_G0z1I<>1}=z0J~$> z9cXM=O4Na?!)LAx0CK7f02y;405CjYD3YWiiD#FuESVz!^-Ahwa7~hyxuh@v`YsM& zOKg4)IvUNmRD&KSYSRIbeH(rTc8a$fd__|Z82$8$=B&rGLU98x14O5wN1aM;hvL(0 z^31UFW6qjSYYXZ<5%~!i3N{z0PeQ@ea7mrR9uM3YOX~blM|Gv71E54JC=F2M^k{hB zDYY`8zw44{RKRuu0NgevxzQAc9&x;mkARMbx(`IKtMx;?$;9eQB4uQFQwPA{%Szip z%DUpU1P*v(Q)yF)06_j;VgS5jJ-`4!9~u4Mpo#46YPH97#+hU=;cMKr_iZq8fr6|gVJ(eeRK zj7mBH?tVs{zot=E@R+yqi$zJvt$5P_0C(pL>wD1HWnZinTtf2r`3NYCoTvFQr#rRF zmGjb-Jh(R#J9jj1L6k z>iMhZO%DJ7g9A1`pA#qCd;UN%gM@@txiTHJ>FuY>@10b9BTAPB#m@jG^L!K7B z@=s&Ux_q za#xq{cd^)d7Xx5N4X>HgB_0f-iJb?6YhB|HDyh})C^L|^X2iP?k8;&L6%mjveHT}# zq^(&T?ixq~z_7D{ZD-A;`1Wink*46-1o{1*&QV}Q_~ty}5WiLk07yE8-bD$?PhGrj zyy91Atk%3;2f!i?gy`EKQH4Xtl4AuGY<7{u$a787(F3$zCdbob8zaD$k&gh9p5F46 z9VlrK07!p1ibjOX}ikUxr$^Q=7zArd4 zhaz7n0pu~F0=1J2(*m#RMQTCj2A-C>JL$|%=}{23N}aM%hHUd$t|y0*v~c=$ z&#<0}z73n1KtZRjS;Doxb2l07kL9dKr#u0G!!CC6T#>1$`Q2mh(8BZR@giypL1u-yKhXbMdUepcN6GGPQ85!G}$pN-Aog8)As~uJ8f_U@_lGYoU z3j!qy z38uOKA7qy(`S%rq23=nAcN|wpyL(eo*LmKF&;(%}49H6?IHl;TIXzu4ypifqfpgq6 z@ewm^q+b%!V5{>HWGC+9ejnv+o&N8TadUECkI=a}k~)cI2&8?dquVI^Cl8QvfmKm6 zOJaN@&}BZedo=DnJWiB0)4%{do|+c9vk|9uiqfODi9?iZDOXM52*zm{W7U7ugbJuv zK6_g^h#mtVA|Cqah4-abdx^Thg#UiBW-y)@eKU+I2rDfo+!fBqkjY3^%ze`Zk`<)K z)rkefYh@C*TvS-<{qS8MyjcsGkD=_2In&V?&hX#8j^8yU zXj6r|x3h5fW(wUnJr*P;QIK;iBL#;Dy^zes2EVr9EVMGGH(@Mk0CaAVU>Xu+yA6K; zdCz)pXMIt`$;!lCyVha%Zd+f;{k!jwr5c@~QEwm2I5VT3=U`2bY5wf7IS~kSpJU@` zQ|x`=*j;fq$0B6%3&&H(YO+>cwTH4dRZ6ezCprhm)#t?wT0ibPGn)3pSQYV31%di9 zqH?TcMsxHS<52fZC{x!-h@QB@6vk1(N?Hz*bBz!$bzrR!?nXZCWM?pUe1B^EKxUG4 z`9X#=q!alf$$F(!s_AO1sx25WCQE9B~||!)$9ff5sAp)<^>I+ z+X;C9fClEGkGaWkU2RLn_sQTCxHq^RBCp9q7xogZ64g5shrfS$AIxdK$+DX?0McKu8T)D_C+lcVDz6?;^E?uvnyt5C=mCW8 z;M$3xm~ts70N_z4$;{}_RqOf#FfdWm()U&t2&MplIS-~90f0ocM3`Myu1h5Vpwf<8 z_`YTN4?jfK)Nl@djalb8Skq&j@0McTAf)zhVhwaAzL0Z%fGfa;1OV`sG&PZFS>xuW z7S~NXd!-{VxM9bK-D1iO`jp%B2R+SB@;c-GGtPIZHGXiZb}hKyjLjTXkLix#;F+*W z17cQ4TNqZc=fy&~`BRIcNIU{q`(>&jGjE>@4r%u*vN`eDSmLuh$?2GxCz;sA-On)9 zj$LIE0AVg=m>p$3lVKW+FjkS_3RY93cL3FJ#juid9`gR$r~L$S>CN>Y5^GF+Yq0pz zbG>wx8vvMf_nsytXqYkolgS%^iuyXKnGENoI~!4C&hGAW`+>AphHFm$kA%B71SyZ* z0Ky+00MQHpaK3q~*Ahj{fBMEY=wS{^K<{540a??dvzM))!lYQHFGT7E0Q{SWD=xE4 ze>lBiZu?sTRIpW@vg#W4X-x@(!`+}lHDp=*8`jY5*A$S1EL{)s-n*6qAOZju?^spK zXXpfvx)|zi2MhXhQnO*$My7<*5tGO=Hi#c^TmfN;Km-7kNbR|;aBqp|-R^jx{pXme zB`UF&7y?2}av%HFUXVa@erOJ7eROHATAsN2^7wXI5NX&6UzX4~t~OUgHT3$t`+Q^( zaofSw{Rwbj=d8OW%4^7yNs-&N55z&XZb9U}MwyCl87}@Y8K(-(001zo1Aw2}z>2?o zGD`Z?O@Uj_*_fr4RJd$nK8rK~0DCDk>SQR|GN?e~ckM&R{OI<&UEWKpi9P7RRAFKV zsBdNfTs;&#kpx$|DN+wmA#Vm5ZZA1KUP*?!#2ckULDa)vFzxQmmhvDzJBU~R`d(QZ zX%rk1&8c7oVY43sy4SE>FVG*<0ieUdLiq{ue#WO7SGrMyY!8!s5s@Xno{VJz@WyZ+$^#C-A11`1-JDztIEZg1 z0f3oN0ASOx#bKP+&~os)ZVcbXN5C|=pco)SOQrLk!+ZpYleQ85VnAk#nHK=K4ghd8 zFaXxW=&@zMP!9lX;V@%$e5{msoTGEe&@-=WjYBouf<#9~X=OEd@OsILIn_XH$`3R@ zSqumUjtyjp!RX3~U~11(``joiGBS27GXxD5^yImW^F)KeHF?Vdd4F~H<0Kfql z3Ht>y2yLG2^~0>`v7PL6X{>dU7y#L+?zUJbDJN~b11LU$^T8XY@19;KfBniz>A$9` z=Kd9roc|;dYl$H=Vy%~u5J#NLmzTIIBTIerKdHJSu2Vxj$YYr|&{a188#(ALS(lf~ zo{=;pE@3rt8UT5Fi-;7;`rJ079ZKaD!?Bh&+2AN&H;aQjidA6TmVMp|QpPe_03a1j z^-zI5t8oiQbmljMwd;XI6022eF6e!hEuFVoRbox0uwhh60mcX^C7-=4Dd1ccJ6xnV+bTa( zMAWMT!1gJY{MgplN1DrJ+xi;*aOKG8Sx2lyUKs!Fdf0U+PT|2&sTY^Piu#gg?jMvh zW>kk(j7Ihf09?5`+52|@aHgblvO97i8QsZJ{Gylyjia{D_&J<5ep#GyvYQBrk&=WI zFu@joEnf^CVgOK`tKd8UFn@=(-k(up7w7=Uw|5M*sRT|O0jI;JVUX$-U2ce%uZ#X> zf4-VC1L`V`vaj*A%($L%r0|o6II#a2bzwDS>-RmIf0+qKK#r$@XE0~xo&M%wvgrV@ zz0XGga8S~ekSI4K)MU2P7kgvNRa=eZu#KSPjQJcdjxPpLo{@Nfu)iN|h#>aikZ>=a zAFA^pgNU#g)vxDuP>0n8lSe0lqvJG_RxNM&urp^wpp>7#b zL_|9zTZPMQRgZQ$F7Jb82{)hQ4`@zwK?8K)kBs_nL&(v}4i=@-bMA6?gG7(@I^V7D zmq`*Z0b`50InF(iWmTR>)?v?Bfwczw2R!4M!?FY0|4mSR{mrFT7!O!Gy?!6 zKAD)%z)?qy0Klmk7_zLiwG9s=aT(>@(t}AB05I6kQMMf%0{}CKJAgs9x%b*!o50Gsd9(%6mEA^T?}Wa=8+>c3D%9qv*`_6F)|D=B-RnK~*n07!1RjVy_0 z`6RF;bahOGrLj2hI4~wzqlF(0sZ_{&f{Umt@Nod-_AqB=m#Gq&7QT*?3uGx1{1uMp z1emscr5g>;03o9`hY6YH49@R9QTM@Me}l{9pjA(HlSQi?8k;MV%>%_h4u-~ngf48Z z6)L~kb%wS5RLDq45`~_M!Onx%5X9MBSbLkV^a7i(i5#+{0#B8TbO4Z41Y1qHFebtr z79L;W&t542kkZfjD}1;tS0>63ISIs>a*WFE1Yi|Hkutbp2f8R{C(%q+>U-I9!WQhC zNL(AKCd%JH=z`AX1}H?d?-w1iA}d>*i6#JG%jfdqS+cEL03g@3A1{EwDKa0G;jcs8 z)wUpLbG@Lp!APMZPHnJv^$iBv7HW&Gd5pZUF2b%@k+rhfR6}iFUrVtNojP1RBep~JLis8c(W%PfvK1T2TbTdr zmc8V)o(uSwiapudu};?Rn28;LqE%aZIhLH)qPWPqlP5o8%xqn+f|yg5;G9{c4iT^A^NkPjr*x$jZEj(UC?;xO^L|`Wz(e zrUkbjtIg{+4bDt7rc{%6Q)yyFx5-7mMI&S!rAKBd1J(t%#5RtpCdD%|krDtf)qu1{ zg6mO1{BSqDCoX}eRi%|XLUI;g=wjFbKINr#Dq*fydqXFA%?lhPTx&nrOO^4ySJp;* z+>!VBYFRmUfg>=m@@|Yk7yx{8DiJcLg-gR5d0rU+Sk33Dm-6>H&yfPBq&LMr?3az( z-2J6Tf^Di91~)YLO5B;Mr?!Kv?o!)sN{l84<$Mwg>p7!{-1njdn#mpX++jfG0=)>u zVQL#a_x>;Gspa${o(0{$IMaNgF%^7~2zER4+#(zPp%<~~C}Z^E0=2WVnO=O&Md!f1 z0mHbXYC5FJil&03hqLf)F=;ry$mi8DgHHP7)Ne5S0@-RtZl{<2_UtmksNz zLKL!2_C^Wh1CK8|(D~rflcrAwiCxC@;7nvk&y>IZ)i>oz)v)gUB{QBJU;rpF0O0K) z8G@Y;Vn$GXkFEhb;4uGG64p)7mYD}y%P0>Vru;qS#z5uRj8ivFVHsjY>bEKjBx69O zCVpFx_AKo3koSIU{tgg5z?1$CD7xPiejz~XhyieQNd5zVc~1qH z6zZIr3K{1{wXmD>GKr=D6N^k=K;kjTAvtkTyUHYB9bjVSBHwyeZqdg6K9=E57oUj|GX)P(;9~ZGlgKP#bTX=R_5 zUR8EDfC{x7%09!bFNvH5NpsfJJwYCP%ROJM2H4ZaH3VG%2qXV*;u~_JMj7n}cJNYI zbY^@RMGG~(h|x|=3@#?e;M5rO;)?UkDJ;$=g@se#te1KI`#nz4t#3uTdN^G&!ra+4 z#EvC=+BW_LPG^Ih$VVDwb6eo@r)VI4$W|{7FY)|etOc3ez$AZ$3<7y3 zIDpCI0%6$diJ+|hGzT)1%*3M4?$w_c+`n<+5ur_{Bb^vGxR7{?75WUG`@qejpS^8D zJ2xyMeHQF3-h&J3(e3$rvOf*Z82I%~AiYq_f!rKj@D>QH_Uk#ZUa1Wf>@{qFe7Inb z#Pj*YT{|OfAy>g(ukcE?w*vbRN=ocF%CJ3l>3dD|#HXW@zqY5~fCxyP=AQpd$;H0f zo-_vp{3u2Q^6DdY$kA`+SVN}R<5ACgzG^|Mm~qZw*e&UERw=PvQd2Q=h|Hs@6Q94( z*y94ku{HtQ*q~6v3DG)Gf0uQA0M%hV6D8}zUdNaddHGzS{o(QpzIVaZCd5UghQnv2 zzx}XAzPr)c%lE~1qJ|-fZ;ms4;}p%UOgcE(vipj0C!MQmdHo{MX5j4 z3@*14nyJ>O-wN9l*Aw6JN>V+VZUI4F35O~=dhCfW?S?qJFt#@CbshZ|Mhv*fW^iBr zx?@oCx>^epyTJ+Nr*4ng5fEQBNsW-3J9n7JX?}ba*14SV*&_+;&riJ_^{{1P&cpwT z93SGm`X;`7OH~9U0+}&ybqy^{1v-4u>iMZAj=r>D2kc39$@AkY9i-8d)ho;oxl!^I zI+Y8zbzHi754i6SPa_ZqhtK*>s!t{^((>ksV9y#BnqGiFo2$`mS~g7{ls+vJ1YL!F{!*2c-7jusZ~)Yrm~( z8&Ov+1{k2OAaDTATPrmYI2-{Wu_q98gSy&|Ub&L!VsWHRaZ#63I6OPm;^8*C>V+F^ z<;%OLaDP(qkPS^Eaz|)_>r1U4!XQrl{+Hk^!6=`rrb8M@-zTc`aUey@*rF580E8xC znWE|aJp+KsvHQXxSPnO?2a#Ivm>B?6CbpmyNVU=KX0qHAt*%T&a;p_;M_dzMm0B5n zsDU)HWbJ*@1-TrHyA(MmcA+p~r^=!Cu$3dR2QLd;-4Y?QyI+!iKG0d(#1eKA0iuyt zQeQfxD!eIc;2IgCdCy0bwo!DuS(s@IRbSd2L|(nv0odsnto_tu$(Pr8POIcF%x)&i z{qy+;_Om#2Nitw2jP|KVEL9KR4WRKs7%3qiLj ztf-w9(j*0Q&EUYahJ0b3vr0C3peM)0lq>RJLQEtVG2^G88vv-a@#bbu&K-hYB3l#S zumJ{sXGRspW!<5xagvI_ z!43yo09*IBa2#$kLZH@lAW`F0V2*m`fHNmObNX#$9=-U8zXKA>C@XSsh~|ylLzdJO z5lc5khlc`)C4SXL03hwHl7L020f~Eb02u5Rl`TN6J1A4+mB>{LfLCGx2EdPEO?yoV z*DGVQZCX1K7qO%In`54sOc%eo=J-ZbIOB>cLCcd-Q%g6!GRDU?$Ws0|_se4~41k%V zGLe1neI9C6_;>+q@lk}000M+Qe>O_&(IWP0ISjo^4d1+MIROrczRjVTlYub1Z8Z#K^Xm-%*Y3hO7BAkZe|1 zV_Mg%HmC}oEMBbriAdoc0Ezl)tyO&3=ILGo8$D6o1~IMsAKBgCpjvkzikU`jn!-Ck zwq{$`R0eL&*vT6QPH9dND@HTirX8UZ8qYk)5o1#sNH0`N!4ul?8s+3f-tfaa*W&^? zZHm^RxR8gX#Z5B~PJ|us8HAF9kE}-G$d)bVmLW0DtDx<&yJ6unM6iyQS5%#-ouqTS$~P=WQ7{9lgFyMYcr_h&i~~@!|D!I{IG%0A)3H+C$XW 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 Cq> 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%0AcwyfTi-DeD28Y4r{2c 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 diff --git a/extra_fonts/proggy_clean_13.fnt b/extra_fonts/proggy_clean_13.fnt deleted file mode 100644 index 126ff7f01d681e29958d2844fbc73e2c70e0919b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4647 zcmZA43s6*59LMp)Log#K3Zo*X!mPF;$U-Y$VG&lM6q1oqtNbK{=ysR8DfQoTS9k z;zASJZ~?#ZNi>lroObOe+?Xg^FU9mRT`+rZEtu=e5*OnAEzX2FaiLg@3A8R)f>;=q zEJhyFLEaGBv-xb^u3R&vFXv!Gq%MOtm15^&<4l?fb;cHfO%$^uf5m5=V=_^ZSiYE@ z-V~XG^RX;n|G4$`*-mS&STxo`dv%XMqhi}dixT~ zwO(>uf^GJhf1d5mnj-C8hD{TT!|W%9ahy8and5TG%f;d;-yn7c{Va?{BwFWwnXB3V#hIepI(E7N#1L*m6G>5tVYa(&6K>?W3$BU z_zzMy*je)%vAGhLh=uz4;$M%Ou!RzrgjI2rTtchk`Lh4+#b#snx#r*Z z_hGeC_kL`=h+&de}ug0%MlW+d-}NTk?9-l)L$LMB!qeHo5< zr0xi;SmGYWCW$?QrAggKu?mUH!ZIZ;8=EWh{1{d)<9!_4BXJ|KsS=liZIryZ*cs~D z=Yjv6=V8;O?kMavvC-IDVq>t`V)@uSu>x$7SRuAl>cZ3ASFW6x$^B6t-2Y4BH_#0oyG$5v%Zx!@nL+V|yiT61Gol zGPX{}F$Fs;apl-Cv1hP0v1c*+L~x(k&v`BI-S|8fCUF&5cj?Pi%$^7LJ^i9LF3@IR z_O!ZjZz3D{x;B%tT_@M(QJy9?pYk5D1(dtV^L`-~A>&wtMT;%QX35yz!$yiN!MaM0 z_pw-MuM%6vn!A5vF2zPjdmmskWRHJ{#Y=n3u+!9a&%#GoDqnZ5swnrD9MxD)vE^8a z?N@p>@a4}UnnZ!21^zD7E2e~f@O%+V~b^twqnc0wqccG z-(el}&t2aJY@5vU_t+L0$98PH*bZ#B*iOtN>-z(CK;m{`tzti76*AsN?1aSa#%vaM z5B-FMi~Wp6igiA#0_*-l+%B1eCahBWvKMQTxMr+I;(o_041>@1cg_CK=fRpbBw diff --git a/extra_fonts/proggy_clean_13.png b/extra_fonts/proggy_clean_13.png deleted file mode 100644 index 19922df338881788d0fcbbb9e7054c2c7ac8c81e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1557 zcmV+w2I~2VP)HRVAz)n~>&KhWI z4id_yC|VqeirK|3U_BZ`06a2ip1k%f);hiqCSX050M>#B$G_mw@tgasX9B+uCIA+| zDG~;Q!2keQEEYHXh@jsDSAE`qyZm?Y!{tc$xY%<7c(25*5uqGM|1eW&$ZmlfH)k=G~m>ojgtB?eipEB z#auE=SiMVhWlp_>;JR>%0Ko9u9bi9dP`V3(0E(q%(d+-Vw^0TQc-9l&ASLoUzn06hF~qDjDlrM}w%CRq*LsL8`a z(GyU+d!!lNQp&;Px!weZa!z=$K&=|;E!PdD#|&u1Im*qC!IP$y6d}v zHV)1O>?v-8${rEHe*XPir^u(P!)vT1yrps+EMcEsz>YUpu>x{}TAp|gyjD_{36RSL z5podVJc0oGS*vM;Ajdf+d*k&0M|sgV28~ls|0|lLLk-T-e%huoQ3=;FVy*R@j;fmN z)6v+WK#2up4e%k}$)$-TKN|w9dZ~9>=1Xy|S1n!-VdE!=l7J|mRk=2LG!D>(b#KMjw(BhI_5g@nL&B3M5uIum=Toxz`Va4*gb4 z{lLWkDF&1ldWzu>GoZ}3Q0=&jcQt8|$2B*U>j^m^X5$O0*T4$NLh0`(CkgQ44gy?L z+C~Dr=u4uMtC6VU#}d#~SrMF$tPS2Gz+F{$Pv}{I&(glyEU)Pn2VhHpy;X_x!QM^#@a4~g`vCu)cj1fW!@d=(0SnvhZwqatx!rz8M1Dc9!DDcb-1Q<%#@I3*#?Fr!A835EOYqLcS>mmW4r+{wtkY#j9-KNpDR9V1RxzvNP^YGQE{R#YxUs7}VjH50KLA){|3Z|4Rq+%a504ZOQs=fz6%ZwiRu+|sJ7oegL zrg=;yW!|Bm0(I$MSYzP3&qiWIt*it%QK05Dggwv`0F}v}fJ|L2lP3MF6Owc9*kIxp zMi%6%VEh*6AqyR}1W-g-8~qam_^?(wYLlwLSyVHsjDNEOj(vyD#{h#Wd32T= zdAcA?0*Fmib9C=MXC6hG{My9PoIB+j0kkp?EO0$Q3%m(^Ck-MAc#Ebe`O3j*ff&Yl(j0Ncysxoc5jVR6t_S*PnO!hiAWi&tC;N=N>O z3U$GW!Gz-`PW4@RYxkzUatHAZ6i*~ea^AxNsF!3JL@%oikpfN-^z7rP1Y~*=v2u(C$6Ma%|M=8IV;4|;{z-d` z*ueroN@FGW1V|Z#ZMOqh-YYpLt?S+)z+l1Ka(bzQ3k0Y>Pk?wclxqYe$cz`42!LV> zHxO{-=g%QP>{Eay1=jgLHJz@Z%Kf&g63D?a{_KCE>4#{e5+tHx+NftI(txa z0k;wW#FfyNEdlPDOc`#8Ka=GG0k=b{1_SenA%MlhcfeB#kOGE)=fR*KOu(do)!UN^ zm=ySv7y<@^!C){L3T00000NkvXX Hu0mjfG#kbD diff --git a/extra_fonts/proggy_small_12.fnt b/extra_fonts/proggy_small_12.fnt deleted file mode 100644 index f645aabc9eaa67305fd0686cfdf9d9a5687ef17d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4647 zcmZA33s6->9LMqHsS$-46qYfTR77e>rdAY4MwE{zu?#KHNTd)6EU}axV^h=6L_^~= zF*Q(U3-JfAQc4h$JH!k7@-F4cr`9yI)|HDAu6`E%p^xQ-X|=Q>p^fa`3rK(33# z$YV;yn2gyZb_&<^Vs@mhVyAN5BNoiHEs{Hr(>yV*osLCIoGov>*eGnhi7)|9t!!Ce zFtNrDv$eGK8_lsy>`d&Wi88)UTqtIU^LP3SL#ic*UDg`0v#|v7>*k~abB}KYn0?Y7xNbzi`ntGwK|XMRQh)<0?QT~hZTvPk9Cs6j?eBVJH87z z*2(zBW6hrVdTSbqwTfMc<;mW?2y4Lt$zeY>$Hg2wBySY9S@K?jRf*aApk4A_igk)j z!1mJCuCKSw6R|Fdn}i+ljKv#w8P+XvF<6JpV=}f)#utlqN!;aFkJuFKnAjEAaj~h` zfY>z5Fg~}Y(~&^2E3sg)tFQx}`Fhv~kqb zECHJ;HUom?@DHb7?g^d?mhDC|l_x64n^SxLeX5UfXd+h|_R$=qS3b8b? z)mXaNLs+KR!`KeSVt+Gu_r@A*h?R5Sjcc(j5@&x$ zY{Uksvzx>Imb7<`JKxul7p1={Y^3CU1Iw0mdlQS3xXoC!w?f8CTd`!Z8m!CH)}QkcJVxH-m@D(B#h#M3?_v8e_xH>D*iq@P4r`aV z53o?_??bFW@@~W0h_rjx`z)!)ilkNzSfuo~9V?XnKEftQ+{ajq*bZ!pSR*!F>=SIQ z;!-AbK@Y_8V*W^hE&0U+i>t5Kq zYYk0?kPKf`*=*W%(iFj1R@jTVHlCK&SJi=ZRNb>SBw5T@Qyl9_)asBqeKF~~!7M4o zC{p?d`eFmYXu+^TZ3lxy*Lpseq+WvC@?v8f``&TK^W2B$_uP*=`1fNi2TcD2_&-En z4j9V;^Irx=_&Z}w#xjqEl2FT!Xdrm+~17I`rZvtbt(Rw;@koLa=J{~>=4d@W*GZpqa zI&>yGgvUnU><^jmD>E0;Km8Jw>M#L=Nw?Zd8o)FN&&M!pyQG9v1k~urfdPP_CbW(Wf|kkxNa-Q` z5-3xY&A4_UV_n*6^_+dI_|S(TnlP`RP2gk zRg|ZSnr8K~!dlwt*kNil2Of++mF0d}J};}ORWB>2XWyvspNAV*1@DhLWSN|C4UiIE znvHOcIp7-y0!mhJ=7HXx2VdoZtby${Rfj6sNP}|%v=n!T!U6^>cqryokSf+X_>bfT X42d-$NI#9e00000NkvXXu0mjfsrtHe diff --git a/extra_fonts/proggy_small_14.fnt b/extra_fonts/proggy_small_14.fnt deleted file mode 100644 index 546f8fb97575069b2d2481a39cdf84387243ac13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4647 zcmZA34Nz4@7{>8MJ~SdRgTgY#l8Q(TiPVY`$%yhJN-RSQG!iL90!u6#G4G`!{J2gL^XEECEP(4m zG4hxaF(zX+i=D=GotPbIi`eO0cZ&saZHwg2;|x!XYa_8JiL>RME;b6=V8V^RQ!886 z7fg)t#cVBY{YG;v6*~(%Wg^WmCoTjt#Q8b>g(6jw!!B#J*g06aCkLlPdBZpkvX(yd zXV=hAIwkH3tXnJ=J1%x5c0z0l z)-N^{GmOuz=`?$ls>}u?wXTIJwzXogd)XKZQaag#ty%y`i?EU7AyAF$zIQv|R z7K_KGh|R#_#1gOsvFovHv6_#kIED2jGb`!Qu*7s(tmpHcuv#XJ?_2F z7i0d?b_o_Jb`KUTmVt$c-HVM8TZ)B?Wn$ySmSK@%_Pu>T#(W=^i`jRS_a3|-n<x!6>(2e3G?JZy$oK9(r95(|ver zY&Di9_Ar(q_6W96Z+xM{jnEU(Xee9U@SBtet z+y_{Q^!Fi_FL}3Ptwh?r>wT8gVTDqwdMrZv+kq8Ge;;8JB<^D@T5Km4E7pKb6Z-^P zD>-&ykBfbZJt_7XRxJHBVzb3|W65HlV<}>LumxgYV2i|>uynC6u?(?hY?;_zEL&_J zRwQ$4!B$G#eyl+3D{PHeE4EJTYizw(8@AiCH#i;IyWenZ5IcZ9C*%7T+aT7CJuf*r zuu_RTh@IrmeQq4$x=i8@V-;dYuu8E`tV--CRxNf6s}=hWs~7toYY^+g>ZC5+Sfj-K zfHjHzh_#6QgtdwFU}L3zKVy3^_Ze{<+laZ(uwSr9Y5OZyBXPZ0hvfYY>lFJP>k>PG z^@#m}^@{ajePVxNrSe&PBAFC7_z-q<*!Rp2AK5G#B7b}ulox&O=ZV)Sx G=j4A6c5hw) diff --git a/extra_fonts/proggy_small_14.png b/extra_fonts/proggy_small_14.png deleted file mode 100644 index 7954a6ec6c5106803d1528293685115d25fd4331..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 949 zcmV;m14{gfP)8V*W^hE&0U+i>t5Kq zYYk0?kPKf`*=*W%(iFj1R@jTVHlCK&SJi=ZRNb>SBw5T@Qyl9_)asBqeKF~~!7M4o zC{p?d`eFmYXu+^TZ3lxy*Lpseq+WvC@?v8f``&TK^W2B$_uP*=`1fNi2TcD2_&-En z4j9V;^Irx=_&Z}w#xjqEl2FT!Xdrm+~17I`rZvtbt(Rw;@koLa=J{~>=4d@W*GZpqa zI&>yGgvUnU><^jmD>E0;Km8Jw>M#L=Nw?Zd8o)FN&&M!pyQG9v1k~urfdPP_CbW(Wf|kkxNa-Q` z5-3xY&A4_UV_n*6^_+dI_|S(TnlP`RP2gk zRg|ZSnr8K~!dlwt*kNil2Of++mF0d}J};}ORWB>2XWyvspNAV*1@DhLWSN|C4UiIE znvHOcIp7-y0!mhJ=7HXx2VdoZtby${Rfj6sNP}|%v=n!T!U6^>cqryokSf+X_>bfT X42d-$NI#9e00000NkvXXu0mjfsrtHe diff --git a/imgui.h b/imgui.h index b4cb11c4f..98855a51a 100644 --- a/imgui.h +++ b/imgui.h @@ -771,7 +771,7 @@ struct ImFont IMGUI_API bool IsLoaded() const { return TexPixels != NULL && !Glyphs.empty(); } // Retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) - static IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + a few more + static IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin static IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs static IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Japanese + full set of about 21000 CJK Unified Ideographs From 2757e3573a1942a019014426aea565ed9a3b36e7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 8 Jan 2015 23:53:07 +0000 Subject: [PATCH 03/41] Comments --- imgui.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index eac091f87..5937b87f7 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -118,6 +118,7 @@ Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix. Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. + - 2015/01/08 (1.30) XXXXXXXX - 2014/12/10 (1.18) removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) - 2014/11/28 (1.17) moved IO.Font*** options to inside the IO.Font-> structure. - 2014/11/26 (1.17) reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility @@ -156,12 +157,13 @@ e.g. "##Foobar" display an empty label and uses "##Foobar" as ID - read articles about the imgui principles (see web links) to understand the requirement and use of ID. - If you want to use a different font than the default: - - read extra_fonts/README.txt for instructions. Examples fonts are also provided. - - if you can only see text but no solid shapes or lines, make sure io.Font->TexUvForWhite is set to the texture coordinates of a pure white pixel in your texture! + If you want to use a different font than the default embedded copy of ProggyClean.ttf (size 13): + ImGuiIO& io = ImGui::GetIO(); + io.Font = new Font(); + io.Font->LoadFromFileTTF("myfontfile.ttf", size_pixels); If you want to input Japanese/Chinese/Korean in the text input widget: - - make sure you are using a font that can display the glyphs you want (see above paragraph about fonts) + - when loading the font, pass a range that contains the characters you need, e.g.: ImFont::GetGlyphRangesJapanese() - to have the Microsoft IME cursor appears at the right location in the screen, setup a handler for the io.ImeSetInputScreenPosFn function: #include @@ -1489,7 +1491,7 @@ void ImGui::NewFrame() IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f); IM_ASSERT(g.IO.RenderDrawListsFn != NULL); // Must be implemented IM_ASSERT(g.IO.Font); // Font not created - IM_ASSERT(g.IO.Font->IsLoaded()); // Font not loaded + IM_ASSERT(g.IO.Font->IsLoaded()); // Font not loaded if (!g.Initialized) { From 5dff478dc04847d0c2b9deefddd721e1f4e26738 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 9 Jan 2015 09:00:53 +0000 Subject: [PATCH 04/41] Build fix for GCC/Clang, cannot foward declare a static array? --- imgui.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 5937b87f7..410cb15b5 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6213,19 +6213,24 @@ const ImWchar* ImFont::GetGlyphRangesJapanese() return &ranges[0]; } -extern const unsigned int proggy_clean_ttf_compressed_size; -extern const unsigned int proggy_clean_ttf_compressed_data[9584/4]; -extern unsigned int stb_decompress_length(unsigned char *input); -extern unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length); +static void GetDefaultCompressedFontDataTTF(const void** ttf_compressed_data, unsigned int* ttf_compressed_size); +static unsigned int stb_decompress_length(unsigned char *input); +static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length); // Load embedded ProggyClean.ttf at size 13 bool ImFont::LoadDefault() { - unsigned int buf_compressed_size = (int)proggy_clean_ttf_compressed_size; - unsigned char* buf_compressed = (unsigned char*)proggy_clean_ttf_compressed_data; - const size_t buf_decompressed_size = stb_decompress_length(buf_compressed); + // Get compressed data + unsigned int ttf_compressed_size; + const void* ttf_compressed; + GetDefaultCompressedFontDataTTF(&ttf_compressed, &ttf_compressed_size); + + // Decompress + const size_t buf_decompressed_size = stb_decompress_length((unsigned char*)ttf_compressed); unsigned char* buf_decompressed = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size); - stb_decompress(buf_decompressed, buf_compressed, buf_compressed_size); + stb_decompress(buf_decompressed, (unsigned char*)ttf_compressed, ttf_compressed_size); + + // Load TTF const bool ret = LoadFromMemoryTTF((void *)buf_decompressed, buf_decompressed_size, 13.0f, ImFont::GetGlyphRangesDefault(), 0); ImGui::MemFree(buf_decompressed); DisplayOffset.y += 1; @@ -8336,6 +8341,12 @@ static const unsigned int proggy_clean_ttf_compressed_data[9584/4] = 0xef8d3920, 0xef8d3920, 0xef8d3920, 0xef8d3920, 0xef8d3920, 0x00663923, 0x48fa0500, 0x00f762f9, }; +static void GetDefaultCompressedFontDataTTF(const void** ttf_compressed_data, unsigned int* ttf_compressed_size) +{ + *ttf_compressed_data = proggy_clean_ttf_compressed_data; + *ttf_compressed_size = proggy_clean_ttf_compressed_size; +} + //----------------------------------------------------------------------------- //---- Include imgui_user.inl at the end of imgui.cpp From 1a6e7f25e0dbb6f315ed947735ab140a7df5258b Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 16:25:57 +0000 Subject: [PATCH 05/41] ImVector: less hoops in back() / front(), more friendly for debugger and unoptimized builds + fixed typos --- imgui.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/imgui.h b/imgui.h index 98855a51a..46b6afaef 100644 --- a/imgui.h +++ b/imgui.h @@ -31,8 +31,8 @@ struct ImGuiWindow; #endif typedef unsigned int ImU32; -typedef unsigned short ImWchar; // hold a character for display -typedef ImU32 ImGuiID; // hold widget unique ID +typedef unsigned short ImWchar; // character for display +typedef ImU32 ImGuiID; // unique ID used by widgets (typically hashed from a stack of string) typedef int ImGuiCol; // enum ImGuiCol_ typedef int ImGuiStyleVar; // enum ImGuiStyleVar_ typedef int ImGuiKey; // enum ImGuiKey_ @@ -40,7 +40,7 @@ typedef int ImGuiColorEditMode; // enum ImGuiColorEditMode_ typedef int ImGuiWindowFlags; // enum ImGuiWindowFlags_ typedef int ImGuiSetCondition; // enum ImGuiSetCondition_ typedef int ImGuiInputTextFlags; // enum ImGuiInputTextFlags_ -struct ImGuiTextEditCallbackData; +struct ImGuiTextEditCallbackData; // for advanced uses of InputText() struct ImVec2 { @@ -106,10 +106,10 @@ public: inline const_iterator begin() const { return Data; } inline iterator end() { return Data + Size; } inline const_iterator end() const { return Data + Size; } - inline value_type& front() { return at(0); } - inline const value_type& front() const { return at(0); } - inline value_type& back() { IM_ASSERT(Size > 0); return at(Size-1); } - inline const value_type& back() const { IM_ASSERT(Size > 0); return at(Size-1); } + inline value_type& front() { IM_ASSERT(Size > 0); return Data[0]; } + inline const value_type& front() const { IM_ASSERT(Size > 0); return Data[0]; } + inline value_type& back() { IM_ASSERT(Size > 0); return Data[Size-1]; } + inline const value_type& back() const { IM_ASSERT(Size > 0); return Data[Size-1]; } inline void swap(ImVector& rhs) { const size_t rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; const size_t rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; value_type* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; } inline void reserve(size_t new_capacity) { Data = (value_type*)ImGui::MemRealloc(Data, new_capacity * sizeof(value_type)); Capacity = new_capacity; } @@ -668,7 +668,7 @@ struct ImGuiTextEditCallbackData //----------------------------------------------------------------------------- // Draw List -// Hold a series of drawing commands. The user provide a renderer for ImDrawList +// Hold a series of drawing commands. The user provides a renderer for ImDrawList //----------------------------------------------------------------------------- struct ImDrawCmd From cc79b85c2841de499d638b1e2518f2a453c896a3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 16:30:39 +0000 Subject: [PATCH 06/41] Added first-pass of Image() based on #73 + demo --- imgui.cpp | 141 ++++++++++++++++++++++++++++++++++++++++++++++++------ imgui.h | 17 ++++++- 2 files changed, 142 insertions(+), 16 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 410cb15b5..c99da17f4 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2332,6 +2332,7 @@ bool ImGui::Begin(const char* name, bool* p_opened, ImVec2 size, float fill_alph if (first_begin_of_the_frame) { window->DrawList->Clear(); + window->DrawList->PushTextureID(g.IO.Font->TexID); window->Visible = true; // New windows appears in front @@ -2652,6 +2653,9 @@ bool ImGui::Begin(const char* name, bool* p_opened, ImVec2 size, float fill_alph } else { + // Short path when we do multiple Begin in the same frame. + window->DrawList->PushTextureID(g.IO.Font->TexID); + // Outer clipping rectangle if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ComboBox)) { @@ -2705,6 +2709,7 @@ void ImGui::End() ImGui::Columns(1, "#CloseColumns"); PopClipRect(); // inner window clip rectangle PopClipRect(); // outer window clip rectangle + window->DrawList->PopTextureID(); // Select window for move/focus when we're done with all our widgets (we only consider non-childs windows here) const ImGuiAabb bb(window->Pos, window->Pos+window->Size); @@ -3402,7 +3407,7 @@ static bool ButtonBehaviour(const ImGuiAabb& bb, const ImGuiID& id, bool* out_ho return pressed; } -bool ImGui::Button(const char* label, ImVec2 size, bool repeat_when_held) +bool ImGui::Button(const char* label, const ImVec2& size_arg, bool repeat_when_held) { ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); @@ -3413,10 +3418,7 @@ bool ImGui::Button(const char* label, ImVec2 size, bool repeat_when_held) const ImGuiID id = window->GetID(label); const ImVec2 text_size = CalcTextSize(label, NULL, true); - if (size.x == 0.0f) - size.x = text_size.x; - if (size.y == 0.0f) - size.y = text_size.y; + const ImVec2 size(size_arg.x != 0.0f ? size_arg.x : text_size.x, size_arg.y != 0.0f ? size_arg.y : text_size.y); const ImGuiAabb bb(window->DC.CursorPos, window->DC.CursorPos + size + style.FramePadding*2.0f); ItemSize(bb); @@ -3500,6 +3502,31 @@ static bool CloseWindowButton(bool* p_opened) return pressed; } +void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, ImU32 tint_col, ImU32 border_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiAabb bb(window->DC.CursorPos, window->DC.CursorPos + size); + if (border_col != 0) + bb.Max += ImVec2(2,2); + ItemSize(bb.GetSize(), &bb.Min); + + if (ClipAdvance(bb)) + return; + + if (border_col != 0) + { + window->DrawList->AddRect(bb.Min, bb.Max, border_col, 0.0f); + window->DrawList->AddImage(user_texture_id, bb.Min+ImVec2(1,1), bb.Max-ImVec2(1,1), uv0, uv1, tint_col); + } + else + { + window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, tint_col); + } +} + // Start logging ImGui output to TTY void ImGui::LogToTTY(int max_depth) { @@ -5810,19 +5837,24 @@ void ImGui::Color(const char* prefix, unsigned int v) // ImDrawList //----------------------------------------------------------------------------- +static ImVec4 GNullClipRect(-9999.0f,-9999.0f, +9999.0f, +9999.0f); +static ImTextureID GNullTextureID = NULL; + void ImDrawList::Clear() { commands.resize(0); vtx_buffer.resize(0); vtx_write = NULL; clip_rect_stack.resize(0); + texture_id_stack.resize(0); } -void ImDrawList::PushClipRect(const ImVec4& clip_rect) +void ImDrawList::SetClipRect(const ImVec4& clip_rect) { if (!commands.empty() && commands.back().vtx_count == 0) { - // Reuse empty command because high-level clipping may have discarded the other vertices already + // Reuse existing command (high-level clipping may have discarded vertices submitted earlier) + // FIXME-OPT: Possibly even reuse previous command. commands.back().clip_rect = clip_rect; } else @@ -5830,29 +5862,55 @@ void ImDrawList::PushClipRect(const ImVec4& clip_rect) ImDrawCmd draw_cmd; draw_cmd.vtx_count = 0; draw_cmd.clip_rect = clip_rect; + draw_cmd.texture_id = texture_id_stack.back(); commands.push_back(draw_cmd); } +} + +void ImDrawList::PushClipRect(const ImVec4& clip_rect) +{ + SetClipRect(clip_rect); clip_rect_stack.push_back(clip_rect); } void ImDrawList::PopClipRect() { clip_rect_stack.pop_back(); - const ImVec4 clip_rect = clip_rect_stack.empty() ? ImVec4(-9999.0f,-9999.0f, +9999.0f, +9999.0f) : clip_rect_stack.back(); + const ImVec4 clip_rect = clip_rect_stack.empty() ? GNullClipRect : clip_rect_stack.back(); + SetClipRect(clip_rect); +} + +void ImDrawList::SetTextureID(const ImTextureID& texture_id) +{ if (!commands.empty() && commands.back().vtx_count == 0) { - // Reuse empty command because high-level clipping may have discarded the other vertices already - commands.back().clip_rect = clip_rect; + // Reuse existing command (high-level clipping may have discarded vertices submitted earlier) + // FIXME-OPT: Possibly even reuse previous command. + commands.back().texture_id = texture_id; } else { ImDrawCmd draw_cmd; draw_cmd.vtx_count = 0; - draw_cmd.clip_rect = clip_rect; + draw_cmd.clip_rect = clip_rect_stack.empty() ? GNullClipRect: clip_rect_stack.back(); + draw_cmd.texture_id = texture_id; commands.push_back(draw_cmd); } } +void ImDrawList::PushTextureID(const ImTextureID& texture_id) +{ + SetTextureID(texture_id); + texture_id_stack.push_back(texture_id); +} + +void ImDrawList::PopTextureID() +{ + texture_id_stack.pop_back(); + const ImTextureID texture_id = texture_id_stack.empty() ? NULL : texture_id_stack.back(); + SetTextureID(texture_id); +} + void ImDrawList::ReserveVertices(unsigned int vtx_count) { if (vtx_count > 0) @@ -5872,6 +5930,14 @@ void ImDrawList::AddVtx(const ImVec2& pos, ImU32 col) vtx_write++; } +void ImDrawList::AddVtxUV(const ImVec2& pos, ImU32 col, const ImVec2& uv) +{ + vtx_write->pos = pos; + vtx_write->col = col; + vtx_write->uv = uv; + vtx_write++; +} + void ImDrawList::AddVtxLine(const ImVec2& a, const ImVec2& b, ImU32 col) { const float offset = GImGui.IO.PixelCenterOffset; @@ -6095,6 +6161,27 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 vtx_write -= (vtx_count_max - vtx_count); } +void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv0, const ImVec2& uv1, ImU32 col) +{ + if ((col >> 24) == 0) + return; + + const bool push_texture_id = user_texture_id != texture_id_stack.back(); + if (push_texture_id) + PushTextureID(user_texture_id); + + ReserveVertices(6); + AddVtxUV(ImVec2(a.x,a.y), col, uv0); + AddVtxUV(ImVec2(b.x,a.y), col, ImVec2(uv1.x,uv0.y)); + AddVtxUV(ImVec2(b.x,b.y), col, uv1); + AddVtxUV(ImVec2(a.x,a.y), col, ImVec2(uv0.x,uv0.y)); + AddVtxUV(ImVec2(b.x,b.y), col, uv1); + AddVtxUV(ImVec2(a.x,b.y), col, ImVec2(uv0.x,uv1.y)); + + if (push_texture_id) + PopTextureID(); +} + //----------------------------------------------------------------------------- // ImBitmapFont //----------------------------------------------------------------------------- @@ -6120,13 +6207,16 @@ void ImFont::Clear() ImGui::MemFree(TexPixels); DisplayOffset = ImVec2(0.5f, 0.5f); + + TexID = NULL; + TexPixels = NULL; + TexWidth = TexHeight = 0; + TexExtraDataPos = TexUvWhitePixel = ImVec2(0, 0); + FontSize = 0.0f; Glyphs.clear(); IndexLookup.clear(); FallbackGlyph = NULL; - TexPixels = NULL; - TexWidth = TexHeight = 0; - TexExtraDataPos = ImVec2(0, 0); } // Retrieve list of range (2 int per range, values are inclusive) @@ -7259,6 +7349,29 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::TreePop(); } + if (ImGui::TreeNode("Images")) + { + ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data.\nHover the texture for a zoomed view."); + ImVec2 tex_screen_pos = ImGui::GetCursorScreenPos(); + float tex_w = (float)ImGui::GetIO().Font->TexWidth; + float tex_h = (float)ImGui::GetIO().Font->TexHeight; + ImGui::Image(ImGui::GetIO().Font->TexID, ImVec2(tex_w, tex_h), ImVec2(0,0), ImVec2(1,1), 0xFFFFFFFF, 0x999999FF); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + float focus_sz = 32.0f; + float focus_x = ImClamp(ImGui::GetMousePos().x - tex_screen_pos.x - focus_sz * 0.5f, 0.0f, tex_w - focus_sz); + float focus_y = ImClamp(ImGui::GetMousePos().y - tex_screen_pos.y - focus_sz * 0.5f, 0.0f, tex_h - focus_sz); + ImGui::Text("Min: (%.2f, %.2f)", focus_x, focus_y); + ImGui::Text("Max: (%.2f, %.2f)", focus_x + focus_sz, focus_y + focus_sz); + ImVec2 uv0 = ImVec2((focus_x) / tex_w, (focus_y) / tex_h); + ImVec2 uv1 = ImVec2((focus_x + focus_sz) / tex_w, (focus_y + focus_sz) / tex_h); + ImGui::Image(ImGui::GetIO().Font->TexID, ImVec2(128,128), uv0, uv1, 0xFFFFFFFF, 0x999999FF); + ImGui::EndTooltip(); + } + ImGui::TreePop(); + } + static bool check = true; ImGui::Checkbox("checkbox", &check); diff --git a/imgui.h b/imgui.h index 46b6afaef..e325a2995 100644 --- a/imgui.h +++ b/imgui.h @@ -32,6 +32,7 @@ struct ImGuiWindow; typedef unsigned int ImU32; typedef unsigned short ImWchar; // character for display +typedef void* ImTextureID; // user data to refer to a texture (e.g. store your texture handle/id) typedef ImU32 ImGuiID; // unique ID used by widgets (typically hashed from a stack of string) typedef int ImGuiCol; // enum ImGuiCol_ typedef int ImGuiStyleVar; // enum ImGuiStyleVar_ @@ -233,8 +234,9 @@ namespace ImGui IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args); IMGUI_API void BulletText(const char* fmt, ...); IMGUI_API void BulletTextV(const char* fmt, va_list args); - IMGUI_API bool Button(const char* label, ImVec2 size = ImVec2(0,0), bool repeat_when_held = false); + IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0,0), bool repeat_when_held = false); IMGUI_API bool SmallButton(const char* label); + IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), ImU32 tint_col = 0xFFFFFFFF, ImU32 border_col = 0x00000000); IMGUI_API bool CollapsingHeader(const char* label, const char* str_id = NULL, const bool display_frame = true, const bool default_open = false); IMGUI_API bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); // adjust display_format to decorate the value with a prefix or a suffix. Use power!=1.0 for logarithmic sliders. IMGUI_API bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); @@ -675,6 +677,7 @@ struct ImDrawCmd { unsigned int vtx_count; ImVec4 clip_rect; + ImTextureID texture_id; // Copy of user-provided 'TexID' from ImFont or passed to Image*() functions. Ignore if not using images or multiple fonts. }; #ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT @@ -705,16 +708,22 @@ struct ImDrawList ImVector vtx_buffer; // each command consume ImDrawCmd::vtx_count of those // [Internal to ImGui] - ImVector clip_rect_stack; // [internal] clip rect stack while building the command-list (so text command can perform clipping early on) + ImVector clip_rect_stack; // [internal] + ImVector texture_id_stack; // [internal] ImDrawVert* vtx_write; // [internal] point within vtx_buffer after each add command (to avoid using the ImVector<> operators too much) ImDrawList() { Clear(); } IMGUI_API void Clear(); + IMGUI_API void SetClipRect(const ImVec4& clip_rect); IMGUI_API void PushClipRect(const ImVec4& clip_rect); IMGUI_API void PopClipRect(); + IMGUI_API void SetTextureID(const ImTextureID& texture_id); + IMGUI_API void PushTextureID(const ImTextureID& texture_id); + IMGUI_API void PopTextureID(); IMGUI_API void ReserveVertices(unsigned int vtx_count); IMGUI_API void AddVtx(const ImVec2& pos, ImU32 col); + IMGUI_API void AddVtxUV(const ImVec2& pos, ImU32 col, const ImVec2& uv); IMGUI_API void AddVtxLine(const ImVec2& a, const ImVec2& b, ImU32 col); // Primitives @@ -726,6 +735,7 @@ struct ImDrawList IMGUI_API void AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12); IMGUI_API void AddArc(const ImVec2& center, float rad, ImU32 col, int a_min, int a_max, bool tris = false, const ImVec2& third_point_offset = ImVec2(0,0)); IMGUI_API void AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f); + IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv0, const ImVec2& uv1, ImU32 col); }; // TTF font loading and rendering @@ -736,8 +746,11 @@ struct ImFont float Scale; // = 1.0f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() ImVec2 DisplayOffset; // = (0.0f,0.0f) // Offset font rendering by xx pixels ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. + ImTextureID TexID; // = NULL // User reference to texture used by the font (ignore if you aren't using multiple fonts/textures) // Texture data + // User is in charge of copying the pixels into a GPU texture. + // You can set 'TexID' to uniquely identify your texture. TexId is copied to the ImDrawCmd structure which you receive during rendering. unsigned char* TexPixels; // 1 byte, 1 component per pixel. Total byte size of TexWidth * TexHeight int TexWidth; int TexHeight; From 40f7b67ef18dd7971b1a761b99c82f7c3c9910f1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 16:46:39 +0000 Subject: [PATCH 07/41] ImDrawList::AddText() allows changing font --- imgui.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index c99da17f4..9747f6042 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6144,7 +6144,11 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 return; if (text_end == NULL) - text_end = text_begin + strlen(text_begin); // FIXME-OPT + text_end = text_begin + strlen(text_begin); + + const bool push_texture_id = font->TexID != texture_id_stack.back(); + if (push_texture_id) + PushTextureID(font->TexID); // reserve vertices for worse case const unsigned int char_count = (unsigned int)(text_end - text_begin); @@ -6159,6 +6163,9 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 const size_t vtx_count = vtx_buffer.size() - vtx_begin; commands.back().vtx_count -= (unsigned int)(vtx_count_max - vtx_count); vtx_write -= (vtx_count_max - vtx_count); + + if (push_texture_id) + PopTextureID(); } void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv0, const ImVec2& uv1, ImU32 col) @@ -6461,7 +6468,7 @@ bool ImFont::LoadFromMemoryTTF(const void* data, size_t data_size, float size // Draw white pixel and make UV points to it TexPixels[0] = TexPixels[1] = TexPixels[TexWidth+0] = TexPixels[TexWidth+1] = 0xFF; - TexUvWhitePixel = ImVec2(TexExtraDataPos.x + 0.5f / TexWidth, TexExtraDataPos.y + 0.5f / TexHeight); + TexUvWhitePixel = ImVec2((TexExtraDataPos.x + 0.5f) / TexWidth, (TexExtraDataPos.y + 0.5f) / TexHeight); return true; } From 20bb6270bca6ac82f768c2b95ac46cd773ad6231 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 17:17:43 +0000 Subject: [PATCH 08/41] Examples: all supports TextureID in renderer, added LoadFontTexture() function. --- examples/directx11_example/main.cpp | 84 ++++++++++++++++------------- examples/directx9_example/main.cpp | 62 +++++++++++++-------- examples/opengl3_example/main.cpp | 33 +++++++----- examples/opengl_example/main.cpp | 33 +++++++----- 4 files changed, 129 insertions(+), 83 deletions(-) diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index e718d14c7..3e3a26548 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -26,7 +26,6 @@ static ID3D11Buffer* g_pVertexConstantBuffer = NULL; static ID3D10Blob * g_pPixelShaderBlob = NULL; static ID3D11PixelShader* g_pPixelShader = NULL; -static ID3D11ShaderResourceView*g_pFontTextureView = NULL; static ID3D11SamplerState* g_pFontSampler = NULL; static ID3D11BlendState* g_blendState = NULL; @@ -122,7 +121,6 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c g_pd3dDeviceImmediateContext->VSSetConstantBuffers(0, 1, &g_pVertexConstantBuffer); g_pd3dDeviceImmediateContext->PSSetShader(g_pPixelShader, NULL, 0); - g_pd3dDeviceImmediateContext->PSSetShaderResources(0, 1, &g_pFontTextureView); g_pd3dDeviceImmediateContext->PSSetSamplers(0, 1, &g_pFontSampler); // Setup render state @@ -139,6 +137,7 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c { const ImDrawCmd* pcmd = &cmd_list->commands[cmd_i]; const D3D11_RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; + g_pd3dDeviceImmediateContext->PSSetShaderResources(0, 1, (ID3D11ShaderResourceView**)&pcmd->texture_id); g_pd3dDeviceImmediateContext->RSSetScissorRects(1, &r); g_pd3dDeviceImmediateContext->Draw(pcmd->vtx_count, vtx_offset); vtx_offset += pcmd->vtx_count; @@ -324,7 +323,8 @@ void CleanupDevice() // InitImGui if (g_pFontSampler) g_pFontSampler->Release(); - if (g_pFontTextureView) g_pFontTextureView->Release(); + if (ID3D11ShaderResourceView* font_texture_view = (ID3D11ShaderResourceView*)ImGui::GetIO().Font->TexID) + font_texture_view->Release(); if (g_pVB) g_pVB->Release(); // InitDeviceD3D @@ -379,6 +379,45 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) return DefWindowProc(hWnd, msg, wParam, lParam); } +void LoadFontTexture(ImFont* font) +{ + IM_ASSERT(font && font->IsLoaded()); + + // Create texture + D3D11_TEXTURE2D_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.Width = font->TexWidth; + desc.Height = font->TexHeight; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_A8_UNORM; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = 0; + + ID3D11Texture2D *pTexture = NULL; + D3D11_SUBRESOURCE_DATA subResource; + subResource.pSysMem = font->TexPixels; + subResource.SysMemPitch = desc.Width * 1; + subResource.SysMemSlicePitch = 0; + g_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); + + // Create texture view + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; + ZeroMemory(&srvDesc, sizeof(srvDesc)); + srvDesc.Format = DXGI_FORMAT_A8_UNORM; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = 0; + ID3D11ShaderResourceView* font_texture_view = NULL; + g_pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &font_texture_view); + pTexture->Release(); + + // Store ID + font->TexID = (void *)font_texture_view; +} + void InitImGui() { RECT rect; @@ -430,39 +469,7 @@ void InitImGui() io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; - IM_ASSERT(io.Font->IsLoaded()); - - // Copy font texture - { - D3D11_TEXTURE2D_DESC desc; - ZeroMemory(&desc, sizeof(desc)); - desc.Width = io.Font->TexWidth; - desc.Height = io.Font->TexHeight; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.Format = DXGI_FORMAT_A8_UNORM; - desc.SampleDesc.Count = 1; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - desc.CPUAccessFlags = 0; - - ID3D11Texture2D *pTexture = NULL; - D3D11_SUBRESOURCE_DATA subResource; - subResource.pSysMem = io.Font->TexPixels; - subResource.SysMemPitch = desc.Width * 1; - subResource.SysMemSlicePitch = 0; - g_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); - - // Create texture view - D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; - ZeroMemory(&srvDesc, sizeof(srvDesc)); - srvDesc.Format = DXGI_FORMAT_A8_UNORM; - srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = desc.MipLevels; - srvDesc.Texture2D.MostDetailedMip = 0; - g_pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &g_pFontTextureView); - pTexture->Release(); - } + LoadFontTexture(io.Font); // Create texture sampler { @@ -563,6 +570,11 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); + static ImFont* font2 = NULL; + if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } + ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (FLOAT)font2->TexHeight)); + //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); + // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; static int ms_per_frame_idx = 0; diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index f826d8c9b..afec7ae10 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -14,7 +14,6 @@ static HWND hWnd; static LPDIRECT3D9 g_pD3D = NULL; // Used to create the D3DDevice static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices -static LPDIRECT3DTEXTURE9 g_pTexture = NULL; // Our texture struct CUSTOMVERTEX { D3DXVECTOR3 pos; @@ -70,16 +69,13 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c g_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); g_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); g_pd3dDevice->SetRenderState( D3DRS_SCISSORTESTENABLE, true ); - - // Setup texture - g_pd3dDevice->SetTexture( 0, g_pTexture ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_DIFFUSE ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); - g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_DIFFUSE ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); + g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_POINT ); + g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_POINT ); // Setup orthographic projection matrix D3DXMATRIXA16 mat; @@ -99,6 +95,7 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c { const ImDrawCmd* pcmd = &cmd_list->commands[cmd_i]; const RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; + g_pd3dDevice->SetTexture( 0, (LPDIRECT3DTEXTURE9)pcmd->texture_id ); g_pd3dDevice->SetScissorRect(&r); g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_offset, pcmd->vtx_count/3); vtx_offset += pcmd->vtx_count; @@ -133,7 +130,8 @@ void CleanupDevice() if (g_pVB) g_pVB->Release(); // InitDeviceD3D - if (g_pTexture) g_pTexture->Release(); + if (LPDIRECT3DTEXTURE9 tex = (LPDIRECT3DTEXTURE9)ImGui::GetIO().Font->TexID) + tex->Release(); if (g_pd3dDevice) g_pd3dDevice->Release(); if (g_pD3D) g_pD3D->Release(); } @@ -176,6 +174,31 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) return DefWindowProc(hWnd, msg, wParam, lParam); } +void LoadFontTexture(ImFont* font) +{ + IM_ASSERT(font && font->IsLoaded()); + + LPDIRECT3DTEXTURE9 pTexture = NULL; + if (D3DXCreateTexture(g_pd3dDevice, font->TexWidth, font->TexHeight, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8, D3DPOOL_DEFAULT, &pTexture) < 0) + { + IM_ASSERT(0); + return; + } + + // Copy pixels + D3DLOCKED_RECT tex_locked_rect; + if (pTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK) + { + IM_ASSERT(0); + return; + } + for (int y = 0; y < font->TexHeight; y++) + memcpy((unsigned char *)tex_locked_rect.pBits + tex_locked_rect.Pitch * y, font->TexPixels + font->TexWidth * y, font->TexWidth); + pTexture->UnlockRect(0); + + font->TexID = (void *)pTexture; +} + void InitImGui() { RECT rect; @@ -219,15 +242,7 @@ void InitImGui() io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; - IM_ASSERT(io.Font->IsLoaded()); - - // Copy font texture - if (D3DXCreateTexture(g_pd3dDevice, io.Font->TexWidth, io.Font->TexHeight, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8, D3DPOOL_DEFAULT, &g_pTexture) < 0) { IM_ASSERT(0); return; } - D3DLOCKED_RECT tex_locked_rect; - if (g_pTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK) { IM_ASSERT(0); return; } - for (int y = 0; y < io.Font->TexHeight; y++) - memcpy((unsigned char *)tex_locked_rect.pBits + tex_locked_rect.Pitch * y, io.Font->TexPixels + io.Font->TexWidth * y, io.Font->TexWidth); - g_pTexture->UnlockRect(0); + LoadFontTexture(io.Font); } INT64 ticks_per_second = 0; @@ -313,6 +328,11 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); + static ImFont* font2 = NULL; + if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } + ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (FLOAT)font2->TexHeight)); + //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); + // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; static int ms_per_frame_idx = 0; diff --git a/examples/opengl3_example/main.cpp b/examples/opengl3_example/main.cpp index ff8122f9c..ecf31fa90 100644 --- a/examples/opengl3_example/main.cpp +++ b/examples/opengl3_example/main.cpp @@ -17,7 +17,6 @@ #define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT)) static GLFWwindow* window; -static GLuint fontTex; static bool mousePressed[2] = { false, false }; // Shader variables @@ -43,10 +42,7 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glEnable(GL_SCISSOR_TEST); - - // Setup texture glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, fontTex); // Setup orthographic projection matrix const float width = ImGui::GetIO().DisplaySize.x; @@ -96,6 +92,7 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c const ImDrawCmd* pcmd_end = cmd_list->commands.end(); for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) { + glBindTexture(GL_TEXTURE_2D, (GLuint)pcmd->texture_id); glScissor((int)pcmd->clip_rect.x, (int)(height - pcmd->clip_rect.w), (int)(pcmd->clip_rect.z - pcmd->clip_rect.x), (int)(pcmd->clip_rect.w - pcmd->clip_rect.y)); glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count); vtx_offset += pcmd->vtx_count; @@ -242,6 +239,19 @@ void InitGL() glBindBuffer(GL_ARRAY_BUFFER, 0); } +void LoadFontTexture(ImFont* font) +{ + IM_ASSERT(font && font->IsLoaded()); + + GLuint tex_id; + glGenTextures(1, &tex_id); + glBindTexture(GL_TEXTURE_2D, tex_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, font->TexWidth, font->TexHeight, 0, GL_RED, GL_UNSIGNED_BYTE, font->TexPixels); + font->TexID = (void *)tex_id; +} + void InitImGui() { ImGuiIO& io = ImGui::GetIO(); @@ -274,15 +284,7 @@ void InitImGui() io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; - IM_ASSERT(io.Font->IsLoaded()); - - // Copy font texture - glGenTextures(1, &fontTex); - glBindTexture(GL_TEXTURE_2D, fontTex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - IM_ASSERT(io.Font->IsLoaded()); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, io.Font->TexWidth, io.Font->TexHeight, 0, GL_RED, GL_UNSIGNED_BYTE, io.Font->TexPixels); + LoadFontTexture(io.Font); } void UpdateImGui() @@ -342,6 +344,11 @@ int main(int argc, char** argv) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); + static ImFont* font2 = NULL; + if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } + ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (float)font2->TexHeight)); + //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); + // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; static int ms_per_frame_idx = 0; diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 2ceb6aa11..2edb865c7 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -17,7 +17,6 @@ #define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT)) static GLFWwindow* window; -static GLuint fontTex; static bool mousePressed[2] = { false, false }; // This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure) @@ -41,9 +40,6 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); - - // Setup texture - glBindTexture(GL_TEXTURE_2D, fontTex); glEnable(GL_TEXTURE_2D); // Setup orthographic projection matrix @@ -70,6 +66,7 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c for (size_t cmd_i = 0; cmd_i < cmd_list->commands.size(); cmd_i++) { const ImDrawCmd* pcmd = &cmd_list->commands[cmd_i]; + glBindTexture(GL_TEXTURE_2D, (GLuint)pcmd->texture_id); glScissor((int)pcmd->clip_rect.x, (int)(height - pcmd->clip_rect.w), (int)(pcmd->clip_rect.z - pcmd->clip_rect.x), (int)(pcmd->clip_rect.w - pcmd->clip_rect.y)); glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count); vtx_offset += pcmd->vtx_count; @@ -151,6 +148,19 @@ void InitGL() glewInit(); } +void LoadFontTexture(ImFont* font) +{ + IM_ASSERT(font && font->IsLoaded()); + + GLuint tex_id; + glGenTextures(1, &tex_id); + glBindTexture(GL_TEXTURE_2D, tex_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, font->TexWidth, font->TexHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, font->TexPixels); + font->TexID = (void *)tex_id; +} + void InitImGui() { ImGuiIO& io = ImGui::GetIO(); @@ -183,15 +193,7 @@ void InitImGui() io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; - IM_ASSERT(io.Font->IsLoaded()); - - // Copy font texture - glGenTextures(1, &fontTex); - glBindTexture(GL_TEXTURE_2D, fontTex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - IM_ASSERT(io.Font->IsLoaded()); - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, io.Font->TexWidth, io.Font->TexHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, io.Font->TexPixels); + LoadFontTexture(io.Font); } void UpdateImGui() @@ -250,6 +252,11 @@ int main(int argc, char** argv) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); + static ImFont* font2 = NULL; + if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } + ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (float)font2->TexHeight)); + //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); + // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; static int ms_per_frame_idx = 0; From 5ca563b5a5f19ae08461acd037068107783764e5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 17:22:04 +0000 Subject: [PATCH 09/41] Speculative fix for warnings for GCC/Clang --- examples/opengl3_example/main.cpp | 4 ++-- examples/opengl_example/main.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/opengl3_example/main.cpp b/examples/opengl3_example/main.cpp index ecf31fa90..f124d1030 100644 --- a/examples/opengl3_example/main.cpp +++ b/examples/opengl3_example/main.cpp @@ -92,7 +92,7 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c const ImDrawCmd* pcmd_end = cmd_list->commands.end(); for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) { - glBindTexture(GL_TEXTURE_2D, (GLuint)pcmd->texture_id); + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->texture_id); glScissor((int)pcmd->clip_rect.x, (int)(height - pcmd->clip_rect.w), (int)(pcmd->clip_rect.z - pcmd->clip_rect.x), (int)(pcmd->clip_rect.w - pcmd->clip_rect.y)); glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count); vtx_offset += pcmd->vtx_count; @@ -249,7 +249,7 @@ void LoadFontTexture(ImFont* font) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, font->TexWidth, font->TexHeight, 0, GL_RED, GL_UNSIGNED_BYTE, font->TexPixels); - font->TexID = (void *)tex_id; + font->TexID = (void *)(intptr_t)tex_id; } void InitImGui() diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 2edb865c7..005dfa51a 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -66,7 +66,7 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c for (size_t cmd_i = 0; cmd_i < cmd_list->commands.size(); cmd_i++) { const ImDrawCmd* pcmd = &cmd_list->commands[cmd_i]; - glBindTexture(GL_TEXTURE_2D, (GLuint)pcmd->texture_id); + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->texture_id); glScissor((int)pcmd->clip_rect.x, (int)(height - pcmd->clip_rect.w), (int)(pcmd->clip_rect.z - pcmd->clip_rect.x), (int)(pcmd->clip_rect.w - pcmd->clip_rect.y)); glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count); vtx_offset += pcmd->vtx_count; @@ -158,7 +158,7 @@ void LoadFontTexture(ImFont* font) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, font->TexWidth, font->TexHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, font->TexPixels); - font->TexID = (void *)tex_id; + font->TexID = (void *)(intptr_t)tex_id; } void InitImGui() From a09f426b891550362ed99731a5d00af6e665988d Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 17:56:43 +0000 Subject: [PATCH 10/41] TAB to spaces --- examples/directx11_example/main.cpp | 78 ++++++++++++++--------------- examples/directx9_example/main.cpp | 62 +++++++++++------------ examples/opengl3_example/main.cpp | 28 +++++------ examples/opengl_example/main.cpp | 8 +-- imgui.cpp | 2 +- 5 files changed, 89 insertions(+), 89 deletions(-) diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index 3e3a26548..4f6c4b72b 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -137,7 +137,7 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c { const ImDrawCmd* pcmd = &cmd_list->commands[cmd_i]; const D3D11_RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; - g_pd3dDeviceImmediateContext->PSSetShaderResources(0, 1, (ID3D11ShaderResourceView**)&pcmd->texture_id); + g_pd3dDeviceImmediateContext->PSSetShaderResources(0, 1, (ID3D11ShaderResourceView**)&pcmd->texture_id); g_pd3dDeviceImmediateContext->RSSetScissorRects(1, &r); g_pd3dDeviceImmediateContext->Draw(pcmd->vtx_count, vtx_offset); vtx_offset += pcmd->vtx_count; @@ -323,8 +323,8 @@ void CleanupDevice() // InitImGui if (g_pFontSampler) g_pFontSampler->Release(); - if (ID3D11ShaderResourceView* font_texture_view = (ID3D11ShaderResourceView*)ImGui::GetIO().Font->TexID) - font_texture_view->Release(); + if (ID3D11ShaderResourceView* font_texture_view = (ID3D11ShaderResourceView*)ImGui::GetIO().Font->TexID) + font_texture_view->Release(); if (g_pVB) g_pVB->Release(); // InitDeviceD3D @@ -381,41 +381,41 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) void LoadFontTexture(ImFont* font) { - IM_ASSERT(font && font->IsLoaded()); + IM_ASSERT(font && font->IsLoaded()); - // Create texture - D3D11_TEXTURE2D_DESC desc; - ZeroMemory(&desc, sizeof(desc)); - desc.Width = font->TexWidth; - desc.Height = font->TexHeight; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.Format = DXGI_FORMAT_A8_UNORM; - desc.SampleDesc.Count = 1; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - desc.CPUAccessFlags = 0; + // Create texture + D3D11_TEXTURE2D_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.Width = font->TexWidth; + desc.Height = font->TexHeight; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_A8_UNORM; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = 0; - ID3D11Texture2D *pTexture = NULL; - D3D11_SUBRESOURCE_DATA subResource; - subResource.pSysMem = font->TexPixels; - subResource.SysMemPitch = desc.Width * 1; - subResource.SysMemSlicePitch = 0; - g_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); + ID3D11Texture2D *pTexture = NULL; + D3D11_SUBRESOURCE_DATA subResource; + subResource.pSysMem = font->TexPixels; + subResource.SysMemPitch = desc.Width * 1; + subResource.SysMemSlicePitch = 0; + g_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); - // Create texture view - D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; - ZeroMemory(&srvDesc, sizeof(srvDesc)); - srvDesc.Format = DXGI_FORMAT_A8_UNORM; - srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = desc.MipLevels; - srvDesc.Texture2D.MostDetailedMip = 0; - ID3D11ShaderResourceView* font_texture_view = NULL; - g_pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &font_texture_view); - pTexture->Release(); + // Create texture view + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; + ZeroMemory(&srvDesc, sizeof(srvDesc)); + srvDesc.Format = DXGI_FORMAT_A8_UNORM; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = 0; + ID3D11ShaderResourceView* font_texture_view = NULL; + g_pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &font_texture_view); + pTexture->Release(); - // Store ID - font->TexID = (void *)font_texture_view; + // Store ID + font->TexID = (void *)font_texture_view; } void InitImGui() @@ -469,7 +469,7 @@ void InitImGui() io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; - LoadFontTexture(io.Font); + LoadFontTexture(io.Font); // Create texture sampler { @@ -570,10 +570,10 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); - static ImFont* font2 = NULL; - if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } - ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (FLOAT)font2->TexHeight)); - //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); + static ImFont* font2 = NULL; + if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } + ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (FLOAT)font2->TexHeight)); + //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index afec7ae10..d1939ad7f 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -69,11 +69,11 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c g_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); g_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); g_pd3dDevice->SetRenderState( D3DRS_SCISSORTESTENABLE, true ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_DIFFUSE ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); - g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_DIFFUSE ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); + g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_POINT ); g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_POINT ); @@ -95,7 +95,7 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c { const ImDrawCmd* pcmd = &cmd_list->commands[cmd_i]; const RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; - g_pd3dDevice->SetTexture( 0, (LPDIRECT3DTEXTURE9)pcmd->texture_id ); + g_pd3dDevice->SetTexture( 0, (LPDIRECT3DTEXTURE9)pcmd->texture_id ); g_pd3dDevice->SetScissorRect(&r); g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_offset, pcmd->vtx_count/3); vtx_offset += pcmd->vtx_count; @@ -130,8 +130,8 @@ void CleanupDevice() if (g_pVB) g_pVB->Release(); // InitDeviceD3D - if (LPDIRECT3DTEXTURE9 tex = (LPDIRECT3DTEXTURE9)ImGui::GetIO().Font->TexID) - tex->Release(); + if (LPDIRECT3DTEXTURE9 tex = (LPDIRECT3DTEXTURE9)ImGui::GetIO().Font->TexID) + tex->Release(); if (g_pd3dDevice) g_pd3dDevice->Release(); if (g_pD3D) g_pD3D->Release(); } @@ -176,27 +176,27 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) void LoadFontTexture(ImFont* font) { - IM_ASSERT(font && font->IsLoaded()); + IM_ASSERT(font && font->IsLoaded()); - LPDIRECT3DTEXTURE9 pTexture = NULL; - if (D3DXCreateTexture(g_pd3dDevice, font->TexWidth, font->TexHeight, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8, D3DPOOL_DEFAULT, &pTexture) < 0) - { - IM_ASSERT(0); - return; - } + LPDIRECT3DTEXTURE9 pTexture = NULL; + if (D3DXCreateTexture(g_pd3dDevice, font->TexWidth, font->TexHeight, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8, D3DPOOL_DEFAULT, &pTexture) < 0) + { + IM_ASSERT(0); + return; + } - // Copy pixels - D3DLOCKED_RECT tex_locked_rect; - if (pTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK) - { - IM_ASSERT(0); - return; - } - for (int y = 0; y < font->TexHeight; y++) - memcpy((unsigned char *)tex_locked_rect.pBits + tex_locked_rect.Pitch * y, font->TexPixels + font->TexWidth * y, font->TexWidth); - pTexture->UnlockRect(0); + // Copy pixels + D3DLOCKED_RECT tex_locked_rect; + if (pTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK) + { + IM_ASSERT(0); + return; + } + for (int y = 0; y < font->TexHeight; y++) + memcpy((unsigned char *)tex_locked_rect.pBits + tex_locked_rect.Pitch * y, font->TexPixels + font->TexWidth * y, font->TexWidth); + pTexture->UnlockRect(0); - font->TexID = (void *)pTexture; + font->TexID = (void *)pTexture; } void InitImGui() @@ -242,7 +242,7 @@ void InitImGui() io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; - LoadFontTexture(io.Font); + LoadFontTexture(io.Font); } INT64 ticks_per_second = 0; @@ -328,10 +328,10 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); - static ImFont* font2 = NULL; - if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } - ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (FLOAT)font2->TexHeight)); - //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); + static ImFont* font2 = NULL; + if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } + ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (FLOAT)font2->TexHeight)); + //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; diff --git a/examples/opengl3_example/main.cpp b/examples/opengl3_example/main.cpp index f124d1030..c8fe320de 100644 --- a/examples/opengl3_example/main.cpp +++ b/examples/opengl3_example/main.cpp @@ -92,7 +92,7 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c const ImDrawCmd* pcmd_end = cmd_list->commands.end(); for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) { - glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->texture_id); + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->texture_id); glScissor((int)pcmd->clip_rect.x, (int)(height - pcmd->clip_rect.w), (int)(pcmd->clip_rect.z - pcmd->clip_rect.x), (int)(pcmd->clip_rect.w - pcmd->clip_rect.y)); glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count); vtx_offset += pcmd->vtx_count; @@ -241,15 +241,15 @@ void InitGL() void LoadFontTexture(ImFont* font) { - IM_ASSERT(font && font->IsLoaded()); + IM_ASSERT(font && font->IsLoaded()); - GLuint tex_id; - glGenTextures(1, &tex_id); - glBindTexture(GL_TEXTURE_2D, tex_id); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, font->TexWidth, font->TexHeight, 0, GL_RED, GL_UNSIGNED_BYTE, font->TexPixels); - font->TexID = (void *)(intptr_t)tex_id; + GLuint tex_id; + glGenTextures(1, &tex_id); + glBindTexture(GL_TEXTURE_2D, tex_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, font->TexWidth, font->TexHeight, 0, GL_RED, GL_UNSIGNED_BYTE, font->TexPixels); + font->TexID = (void *)(intptr_t)tex_id; } void InitImGui() @@ -284,7 +284,7 @@ void InitImGui() io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; - LoadFontTexture(io.Font); + LoadFontTexture(io.Font); } void UpdateImGui() @@ -344,10 +344,10 @@ int main(int argc, char** argv) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); - static ImFont* font2 = NULL; - if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } - ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (float)font2->TexHeight)); - //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); + static ImFont* font2 = NULL; + if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } + ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (float)font2->TexHeight)); + //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 005dfa51a..cc3fae34a 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -252,10 +252,10 @@ int main(int argc, char** argv) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); - static ImFont* font2 = NULL; - if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } - ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (float)font2->TexHeight)); - //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); + static ImFont* font2 = NULL; + if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } + ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (float)font2->TexHeight)); + //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; diff --git a/imgui.cpp b/imgui.cpp index 9747f6042..5b5577006 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7358,7 +7358,7 @@ void ImGui::ShowTestWindow(bool* opened) if (ImGui::TreeNode("Images")) { - ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data.\nHover the texture for a zoomed view."); + ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. Hover the texture for a zoomed view!"); ImVec2 tex_screen_pos = ImGui::GetCursorScreenPos(); float tex_w = (float)ImGui::GetIO().Font->TexWidth; float tex_h = (float)ImGui::GetIO().Font->TexHeight; From 51df5874a631a15fc2fa1f8cab129fcdcd4e4577 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 18:03:40 +0000 Subject: [PATCH 11/41] Examples' Says "OpenGL2" vs "OpenGL3" in title bar. --- examples/opengl3_example/main.cpp | 4 +--- examples/opengl_example/main.cpp | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/opengl3_example/main.cpp b/examples/opengl3_example/main.cpp index c8fe320de..9f86ebee4 100644 --- a/examples/opengl3_example/main.cpp +++ b/examples/opengl3_example/main.cpp @@ -152,11 +152,9 @@ static void glfw_char_callback(GLFWwindow* window, unsigned int c) ImGui::GetIO().AddInputCharacter((unsigned short)c); } -// OpenGL code based on http://open.gl tutorials void InitGL() { glfwSetErrorCallback(glfw_error_callback); - if (!glfwInit()) exit(1); @@ -164,7 +162,7 @@ void InitGL() glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - window = glfwCreateWindow(1280, 720, "ImGui OpenGL example", NULL, NULL); + window = glfwCreateWindow(1280, 720, "ImGui OpenGL3 example", NULL, NULL); glfwMakeContextCurrent(window); glfwSetKeyCallback(window, glfw_key_callback); glfwSetMouseButtonCallback(window, glfw_mouse_button_callback); diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index cc3fae34a..018616fdc 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -130,15 +130,13 @@ static void glfw_char_callback(GLFWwindow* window, unsigned int c) ImGui::GetIO().AddInputCharacter((unsigned short)c); } -// OpenGL code based on http://open.gl tutorials void InitGL() { glfwSetErrorCallback(glfw_error_callback); - if (!glfwInit()) exit(1); - window = glfwCreateWindow(1280, 720, "ImGui OpenGL example", NULL, NULL); + window = glfwCreateWindow(1280, 720, "ImGui OpenGL2 example", NULL, NULL); glfwMakeContextCurrent(window); glfwSetKeyCallback(window, glfw_key_callback); glfwSetMouseButtonCallback(window, glfw_mouse_button_callback); From 241e8086fa9f92542314ff3e39cd6e707c7e7a39 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 18:27:05 +0000 Subject: [PATCH 12/41] Mde it optional to new() io.Font - however it stills needs to be loaded. --- examples/directx11_example/main.cpp | 1 - examples/directx9_example/main.cpp | 1 - examples/opengl3_example/main.cpp | 1 - examples/opengl_example/main.cpp | 1 - imgui.cpp | 25 +++++++++++++++++-------- imgui.h | 7 ++++--- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index 4f6c4b72b..bee3602e9 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -465,7 +465,6 @@ void InitImGui() } // Load font - io.Font = new ImFont(); io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index d1939ad7f..c6cc01254 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -238,7 +238,6 @@ void InitImGui() } // Load font - io.Font = new ImFont(); io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; diff --git a/examples/opengl3_example/main.cpp b/examples/opengl3_example/main.cpp index 9f86ebee4..5a4543840 100644 --- a/examples/opengl3_example/main.cpp +++ b/examples/opengl3_example/main.cpp @@ -278,7 +278,6 @@ void InitImGui() io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; // Load font - io.Font = new ImFont(); io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 018616fdc..40f26870a 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -187,7 +187,6 @@ void InitImGui() io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; // Load font - io.Font = new ImFont(); io.Font->LoadDefault(); //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); //io.Font->DisplayOffset.y += 0.0f; diff --git a/imgui.cpp b/imgui.cpp index 5b5577006..7cc2c7494 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -417,6 +417,12 @@ ImGuiStyle::ImGuiStyle() Colors[ImGuiCol_TooltipBg] = ImVec4(0.05f, 0.05f, 0.10f, 0.90f); } +// We statically allocate a default font storage for the user. +// This allows the user to avoid newing the default font, while keeping IO.Font a pointer which is easy to swap if needed. +// We cannot new() the font because user may override MemAllocFn after the ImGuiIO() constructor is called. +// For the same reason we cannot call LoadDefault() on the font. +static ImFont GDefaultStaticFont; + ImGuiIO::ImGuiIO() { memset(this, 0, sizeof(*this)); @@ -425,7 +431,7 @@ ImGuiIO::ImGuiIO() IniSavingRate = 5.0f; IniFilename = "imgui.ini"; LogFilename = "imgui_log.txt"; - Font = NULL; + Font = &GDefaultStaticFont; FontGlobalScale = 1.0f; FontAllowUserScaling = false; PixelCenterOffset = 0.0f; @@ -1663,8 +1669,11 @@ void ImGui::Shutdown() } if (g.IO.Font) { - g.IO.Font->~ImFont(); - ImGui::MemFree(g.IO.Font); + if (g.IO.Font != &GDefaultStaticFont) + { + g.IO.Font->~ImFont(); + ImGui::MemFree(g.IO.Font); + } g.IO.Font = NULL; } @@ -6146,9 +6155,9 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 if (text_end == NULL) text_end = text_begin + strlen(text_begin); - const bool push_texture_id = font->TexID != texture_id_stack.back(); - if (push_texture_id) - PushTextureID(font->TexID); + const bool push_texture_id = font->TexID != texture_id_stack.back(); + if (push_texture_id) + PushTextureID(font->TexID); // reserve vertices for worse case const unsigned int char_count = (unsigned int)(text_end - text_begin); @@ -6164,8 +6173,8 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 commands.back().vtx_count -= (unsigned int)(vtx_count_max - vtx_count); vtx_write -= (vtx_count_max - vtx_count); - if (push_texture_id) - PopTextureID(); + if (push_texture_id) + PopTextureID(); } void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv0, const ImVec2& uv1, ImU32 col) diff --git a/imgui.h b/imgui.h index e325a2995..3723eacb8 100644 --- a/imgui.h +++ b/imgui.h @@ -746,14 +746,15 @@ struct ImFont float Scale; // = 1.0f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() ImVec2 DisplayOffset; // = (0.0f,0.0f) // Offset font rendering by xx pixels ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. - ImTextureID TexID; // = NULL // User reference to texture used by the font (ignore if you aren't using multiple fonts/textures) - // Texture data - // User is in charge of copying the pixels into a GPU texture. + // Texture data: user is in charge of copying the pixels into a GPU texture. // You can set 'TexID' to uniquely identify your texture. TexId is copied to the ImDrawCmd structure which you receive during rendering. + ImTextureID TexID; // User reference to texture used by the font (ignore if you aren't using multiple fonts/textures) unsigned char* TexPixels; // 1 byte, 1 component per pixel. Total byte size of TexWidth * TexHeight int TexWidth; int TexHeight; + + // [Internal] ImVec2 TexExtraDataPos; // Position of our rectangle where we draw non-font graphics ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel (part of the TexExtraData block) From 0f4d74d6147ee66c6bef2aa97ce48d139b58f7e0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 21:06:57 +0000 Subject: [PATCH 13/41] ImFont::GetTextureData API allow to retrieve 8/32 bits data + lazily load defaults font Examples: OpenGL3 and DirectX11 back to using 32-bits texture solely for ease of integration. --- examples/directx11_example/main.cpp | 29 ++++++------- examples/directx9_example/main.cpp | 21 ++++----- examples/opengl3_example/main.cpp | 20 ++++----- examples/opengl_example/main.cpp | 17 +++----- imgui.cpp | 60 +++++++++++++++++++++----- imgui.h | 66 +++++++++++++++++------------ 6 files changed, 125 insertions(+), 88 deletions(-) diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index bee3602e9..bf15c858c 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -286,8 +286,7 @@ HRESULT InitDeviceD3D(HWND hWnd) \ float4 main(PS_INPUT input) : SV_Target\ {\ - float4 out_col = input.col; \ - out_col.w *= texture0.Sample(sampler0, input.uv).w; \ + float4 out_col = input.col * texture0.Sample(sampler0, input.uv); \ return out_col; \ }"; @@ -381,16 +380,18 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) void LoadFontTexture(ImFont* font) { - IM_ASSERT(font && font->IsLoaded()); + unsigned char* pixels; + int width, height; + font->GetTextureDataRGBA32(&pixels, &width, &height); // Create texture D3D11_TEXTURE2D_DESC desc; ZeroMemory(&desc, sizeof(desc)); - desc.Width = font->TexWidth; - desc.Height = font->TexHeight; + desc.Width = width; + desc.Height = height; desc.MipLevels = 1; desc.ArraySize = 1; - desc.Format = DXGI_FORMAT_A8_UNORM; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; @@ -398,15 +399,15 @@ void LoadFontTexture(ImFont* font) ID3D11Texture2D *pTexture = NULL; D3D11_SUBRESOURCE_DATA subResource; - subResource.pSysMem = font->TexPixels; - subResource.SysMemPitch = desc.Width * 1; + subResource.pSysMem = pixels; + subResource.SysMemPitch = desc.Width * 4; subResource.SysMemSlicePitch = 0; g_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); // Create texture view D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; ZeroMemory(&srvDesc, sizeof(srvDesc)); - srvDesc.Format = DXGI_FORMAT_A8_UNORM; + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = desc.MipLevels; srvDesc.Texture2D.MostDetailedMip = 0; @@ -464,10 +465,9 @@ void InitImGui() } } - // Load font - io.Font->LoadDefault(); + // Load font (optionally load a custom TTF font) //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); - //io.Font->DisplayOffset.y += 0.0f; + //io.Font->DisplayOffset.y += 1.0f; LoadFontTexture(io.Font); // Create texture sampler @@ -569,11 +569,6 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); - static ImFont* font2 = NULL; - if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } - ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (FLOAT)font2->TexHeight)); - //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); - // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; static int ms_per_frame_idx = 0; diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index c6cc01254..0e8464040 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -176,10 +176,13 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) void LoadFontTexture(ImFont* font) { - IM_ASSERT(font && font->IsLoaded()); + unsigned char* pixels; + int width, height; + int bytes_per_pixel; + font->GetTextureDataAlpha8(&pixels, &width, &height, &bytes_per_pixel); LPDIRECT3DTEXTURE9 pTexture = NULL; - if (D3DXCreateTexture(g_pd3dDevice, font->TexWidth, font->TexHeight, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8, D3DPOOL_DEFAULT, &pTexture) < 0) + if (D3DXCreateTexture(g_pd3dDevice, width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8, D3DPOOL_DEFAULT, &pTexture) < 0) { IM_ASSERT(0); return; @@ -192,8 +195,8 @@ void LoadFontTexture(ImFont* font) IM_ASSERT(0); return; } - for (int y = 0; y < font->TexHeight; y++) - memcpy((unsigned char *)tex_locked_rect.pBits + tex_locked_rect.Pitch * y, font->TexPixels + font->TexWidth * y, font->TexWidth); + for (int y = 0; y < height; y++) + memcpy((unsigned char *)tex_locked_rect.pBits + tex_locked_rect.Pitch * y, pixels + (width * bytes_per_pixel) * y, (width * bytes_per_pixel)); pTexture->UnlockRect(0); font->TexID = (void *)pTexture; @@ -237,10 +240,9 @@ void InitImGui() return; } - // Load font - io.Font->LoadDefault(); + // Load font (optionally load a custom TTF font) //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); - //io.Font->DisplayOffset.y += 0.0f; + //io.Font->DisplayOffset.y += 1.0f; LoadFontTexture(io.Font); } @@ -327,11 +329,6 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); - static ImFont* font2 = NULL; - if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } - ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (FLOAT)font2->TexHeight)); - //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); - // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; static int ms_per_frame_idx = 0; diff --git a/examples/opengl3_example/main.cpp b/examples/opengl3_example/main.cpp index 5a4543840..0f0763b0f 100644 --- a/examples/opengl3_example/main.cpp +++ b/examples/opengl3_example/main.cpp @@ -198,8 +198,7 @@ void InitGL() "out vec4 Out_Color;\n" "void main()\n" "{\n" - " Out_Color = Frag_Color;\n" - " Out_Color.w *= texture( Texture, Frag_UV.st).x;\n" + " Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n" "}\n"; shader_handle = glCreateProgram(); @@ -239,14 +238,17 @@ void InitGL() void LoadFontTexture(ImFont* font) { - IM_ASSERT(font && font->IsLoaded()); + unsigned char* pixels; + int width, height; + font->GetTextureDataRGBA32(&pixels, &width, &height); GLuint tex_id; glGenTextures(1, &tex_id); glBindTexture(GL_TEXTURE_2D, tex_id); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, font->TexWidth, font->TexHeight, 0, GL_RED, GL_UNSIGNED_BYTE, font->TexPixels); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + font->TexID = (void *)(intptr_t)tex_id; } @@ -277,10 +279,9 @@ void InitImGui() io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; - // Load font - io.Font->LoadDefault(); + // Load font (optionally load a custom TTF font) //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); - //io.Font->DisplayOffset.y += 0.0f; + //io.Font->DisplayOffset.y += 1.0f; LoadFontTexture(io.Font); } @@ -341,11 +342,6 @@ int main(int argc, char** argv) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); - static ImFont* font2 = NULL; - if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } - ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (float)font2->TexHeight)); - //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); - // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; static int ms_per_frame_idx = 0; diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 40f26870a..18ffee38c 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -148,14 +148,17 @@ void InitGL() void LoadFontTexture(ImFont* font) { - IM_ASSERT(font && font->IsLoaded()); + unsigned char* pixels; + int width, height; + font->GetTextureDataAlpha8(&pixels, &width, &height); GLuint tex_id; glGenTextures(1, &tex_id); glBindTexture(GL_TEXTURE_2D, tex_id); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, font->TexWidth, font->TexHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, font->TexPixels); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels); + font->TexID = (void *)(intptr_t)tex_id; } @@ -186,10 +189,9 @@ void InitImGui() io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; - // Load font - io.Font->LoadDefault(); + // Load font (optionally load a custom TTF font) //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); - //io.Font->DisplayOffset.y += 0.0f; + //io.Font->DisplayOffset.y += 1.0f; LoadFontTexture(io.Font); } @@ -249,11 +251,6 @@ int main(int argc, char** argv) show_test_window ^= ImGui::Button("Test Window"); show_another_window ^= ImGui::Button("Another Window"); - static ImFont* font2 = NULL; - if (!font2) { font2 = new ImFont(); font2->LoadFromFileTTF("../../extra_fonts/ArialUni.ttf", 30.0f); LoadFontTexture(font2); } - ImGui::Image(font2->TexID, ImVec2((float)font2->TexWidth, (float)font2->TexHeight)); - //ImGui::GetWindowDrawList()->AddText(font2, 30.0f, ImGui::GetCursorScreenPos(), 0xFFFFFFFF, "Another font"); - // Calculate and show frame rate static float ms_per_frame[120] = { 0 }; static int ms_per_frame_idx = 0; diff --git a/imgui.cpp b/imgui.cpp index 7cc2c7494..7f8aed8f1 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6208,7 +6208,8 @@ ImFont::ImFont() DisplayOffset = ImVec2(0.5f, 0.5f); FallbackChar = (ImWchar)'?'; - TexPixels = NULL; + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; Clear(); } @@ -6217,15 +6218,55 @@ ImFont::~ImFont() Clear(); } +void ImFont::GetTextureDataAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Lazily load default font + if (!IsLoaded()) + LoadDefault(); + + *out_pixels = TexPixelsAlpha8; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; +} + +void ImFont::GetTextureDataRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Lazily convert to RGBA32 format + // Although it is likely to be the most commonly used format, our font rendering is 8 bpp + if (!TexPixelsRGBA32) + { + unsigned char* pixels; + GetTextureDataAlpha8(&pixels, NULL, NULL); + TexPixelsRGBA32 = (unsigned int*)ImGui::MemAlloc(TexWidth * TexHeight * 4); + const unsigned char* src = pixels; + unsigned int* dst = TexPixelsRGBA32; + for (int n = TexWidth * TexHeight; n > 0; n--) + *dst++ = ((*src++) << 24) | 0x00FFFFFF; + } + + *out_pixels = (unsigned char*)TexPixelsRGBA32; + if (out_width) *out_width = TexWidth; + if (out_height) *out_height = TexHeight; + if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; +} + +void ImFont::ClearTextureData() +{ + if (TexPixelsAlpha8) + ImGui::MemFree(TexPixelsAlpha8); + if (TexPixelsRGBA32) + ImGui::MemFree(TexPixelsRGBA32); + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; +} + void ImFont::Clear() { - if (TexPixels) - ImGui::MemFree(TexPixels); - DisplayOffset = ImVec2(0.5f, 0.5f); + ClearTextureData(); TexID = NULL; - TexPixels = NULL; TexWidth = TexHeight = 0; TexExtraDataPos = TexUvWhitePixel = ImVec2(0, 0); @@ -6392,7 +6433,6 @@ bool ImFont::LoadFromMemoryTTF(const void* data, size_t data_size, float size { TexWidth = 512; TexHeight = 0; - TexPixels = NULL; const int max_tex_height = 1024*16; stbtt_pack_context spc; int ret = stbtt_PackBegin(&spc, NULL, TexWidth, max_tex_height, 0, 1, NULL); @@ -6423,11 +6463,11 @@ bool ImFont::LoadFromMemoryTTF(const void* data, size_t data_size, float size if (rects[i].was_packed) tex_h = ImMax(tex_h, rects[i].y + rects[i].h); TexHeight = ImUpperPowerOfTwo(tex_h); - TexPixels = (unsigned char*)ImGui::MemRealloc(TexPixels, TexWidth * TexHeight); - memset(TexPixels, 0, TexWidth * TexHeight); + TexPixelsAlpha8 = (unsigned char*)ImGui::MemRealloc(TexPixelsAlpha8, TexWidth * TexHeight); + memset(TexPixelsAlpha8, 0, TexWidth * TexHeight); // Render characters - spc.pixels = TexPixels; + spc.pixels = TexPixelsAlpha8; spc.height = TexHeight; ret = stbtt_PackFontRangesRenderIntoRects(&spc, &ttf_info, ranges.begin(), ranges.size(), rects); stbtt_PackEnd(&spc); @@ -6476,7 +6516,7 @@ bool ImFont::LoadFromMemoryTTF(const void* data, size_t data_size, float size ImGui::MemFree(ranges[i].chardata_for_range); // Draw white pixel and make UV points to it - TexPixels[0] = TexPixels[1] = TexPixels[TexWidth+0] = TexPixels[TexWidth+1] = 0xFF; + TexPixelsAlpha8[0] = TexPixelsAlpha8[1] = TexPixelsAlpha8[TexWidth+0] = TexPixelsAlpha8[TexWidth+1] = 0xFF; TexUvWhitePixel = ImVec2((TexExtraDataPos.x + 0.5f) / TexWidth, (TexExtraDataPos.y + 0.5f) / TexHeight); return true; diff --git a/imgui.h b/imgui.h index 3723eacb8..495591a2c 100644 --- a/imgui.h +++ b/imgui.h @@ -739,39 +739,26 @@ struct ImDrawList }; // TTF font loading and rendering -// NB: kerning pair are not supported (because some ImGui code does per-character CalcTextSize calls, need to turn it into something more state-ful to allow for kerning) +// - ImGui automatically loads a default embedded font for you +// - Call GetTextureData() to retrieve pixels data so you can upload the texture to your graphics system. +// - Store your texture handle in 'TexID'. It will be passed back to you when rendering ('texture_id' field in ImDrawCmd) +// (NB: kerning isn't supported. At the moment some ImGui code does per-character CalcTextSize calls, need something more state-ful) struct ImFont { // Settings float Scale; // = 1.0f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() ImVec2 DisplayOffset; // = (0.0f,0.0f) // Offset font rendering by xx pixels ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. + ImTextureID TexID; // = 0 // After loading texture, store your texture handle here (ignore if you aren't using multiple fonts/textures) - // Texture data: user is in charge of copying the pixels into a GPU texture. - // You can set 'TexID' to uniquely identify your texture. TexId is copied to the ImDrawCmd structure which you receive during rendering. - ImTextureID TexID; // User reference to texture used by the font (ignore if you aren't using multiple fonts/textures) - unsigned char* TexPixels; // 1 byte, 1 component per pixel. Total byte size of TexWidth * TexHeight - int TexWidth; - int TexHeight; - - // [Internal] - ImVec2 TexExtraDataPos; // Position of our rectangle where we draw non-font graphics - ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel (part of the TexExtraData block) - - struct Glyph - { - ImWchar Codepoint; - signed short XAdvance; - signed short Width, Height; - signed short XOffset, YOffset; - float U0, V0, U1, V1; // Texture coordinates - }; - - // Runtime data - float FontSize; // Height of characters - ImVector Glyphs; - ImVector IndexLookup; - const Glyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) + // Retrieve texture data + // User is in charge of copying the pixels into graphics memory, then set 'TexID'. + // RGBA32 format is provided for convenience and high compatibility, but note that all RGB pixels are white. + // If you intend to use large font it may be pref + // NB: the data is invalidated as soon as you call a Load* function. + IMGUI_API void GetTextureDataRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + IMGUI_API void GetTextureDataAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void ClearTextureData(); // Save RAM once the texture has been copied to graphics memory. // Methods IMGUI_API ImFont(); @@ -781,8 +768,9 @@ struct ImFont IMGUI_API bool LoadFromMemoryTTF(const void* data, size_t data_size, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); IMGUI_API void Clear(); IMGUI_API void BuildLookupTable(); + struct Glyph; IMGUI_API const Glyph* FindGlyph(unsigned short c) const; - IMGUI_API bool IsLoaded() const { return TexPixels != NULL && !Glyphs.empty(); } + IMGUI_API bool IsLoaded() const { return !Glyphs.empty(); } // Retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) static IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin @@ -795,6 +783,30 @@ struct ImFont IMGUI_API ImVec2 CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL) const; // wchar IMGUI_API void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawVert*& out_vertices, float wrap_width = 0.0f) const; IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; + + // Texture data + // Access via GetTextureData() which will load the font if not loaded + unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight + unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + int TexWidth; + int TexHeight; + ImVec2 TexExtraDataPos; // Position of our rectangle where we draw non-font graphics + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel (part of the TexExtraData block) + + struct Glyph + { + ImWchar Codepoint; + signed short XAdvance; + signed short Width, Height; + signed short XOffset, YOffset; + float U0, V0, U1, V1; // Texture coordinates + }; + + // Runtime data + float FontSize; // Height of characters + ImVector Glyphs; + ImVector IndexLookup; + const Glyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) }; //---- Include imgui_user.h at the end of imgui.h From d27b295f4cb4be3fe5a55774964907dab5c96e3f Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 21:24:12 +0000 Subject: [PATCH 14/41] Documentation on new font / texture get api --- imgui.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 7f8aed8f1..d09204f27 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -80,12 +80,18 @@ - a typical application skeleton may be: // Application init - // TODO: Fill all settings fields of the io structure ImGuiIO& io = ImGui::GetIO(); io.DisplaySize.x = 1920.0f; io.DisplaySize.y = 1280.0f; io.DeltaTime = 1.0f/60.0f; io.IniFilename = "imgui.ini"; + // TODO: Fill others settings of the io structure + + // Load texture + unsigned char* pixels; + int width, height; + io.Font->GetTextureData(&pixels, &width, &height); + // TODO: copy texture to graphics memory. Store texture identifier for your engine in io.Font->TexID // Application main loop while (true) @@ -118,7 +124,9 @@ Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix. Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. - - 2015/01/08 (1.30) XXXXXXXX + - 2015/01/11 (1.30) big font/image API change. now loads TTF file. allow for multiple fonts. no need for a PNG loader. + removed GetDefaultFontData(). uses io.Font->GetTextureData*() API to retrieve uncompressed pixels. + added texture identifier in ImDrawCmd passed to your render function. - 2014/12/10 (1.18) removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) - 2014/11/28 (1.17) moved IO.Font*** options to inside the IO.Font-> structure. - 2014/11/26 (1.17) reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility @@ -157,9 +165,9 @@ e.g. "##Foobar" display an empty label and uses "##Foobar" as ID - read articles about the imgui principles (see web links) to understand the requirement and use of ID. - If you want to use a different font than the default embedded copy of ProggyClean.ttf (size 13): + If you want to use a different font than the default embedded copy of ProggyClean.ttf (size 13), before calling io.Font->GetTextureData(): + ImGuiIO& io = ImGui::GetIO(); - io.Font = new Font(); io.Font->LoadFromFileTTF("myfontfile.ttf", size_pixels); If you want to input Japanese/Chinese/Korean in the text input widget: @@ -420,7 +428,7 @@ ImGuiStyle::ImGuiStyle() // We statically allocate a default font storage for the user. // This allows the user to avoid newing the default font, while keeping IO.Font a pointer which is easy to swap if needed. // We cannot new() the font because user may override MemAllocFn after the ImGuiIO() constructor is called. -// For the same reason we cannot call LoadDefault() on the font. +// For the same reason we cannot call LoadDefault() on the font by default, but it is called by GetTextureData*() API. static ImFont GDefaultStaticFont; ImGuiIO::ImGuiIO() From e5d0d8334ffe1406d4a264bb151b322291fa215a Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 21:55:21 +0000 Subject: [PATCH 15/41] Tweak default texture width for large amount of characters. --- imgui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d09204f27..9a48e19d1 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6439,9 +6439,9 @@ bool ImFont::LoadFromMemoryTTF(const void* data, size_t data_size, float size } { - TexWidth = 512; + TexWidth = (glyph_count > 1000) ? 1024 : 512; // Width doesn't really matters. TexHeight = 0; - const int max_tex_height = 1024*16; + const int max_tex_height = 1024*32; stbtt_pack_context spc; int ret = stbtt_PackBegin(&spc, NULL, TexWidth, max_tex_height, 0, 1, NULL); IM_ASSERT(ret); From 0f89e0615259dbf925af7d75234abe7f9b50e1d6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 22:03:03 +0000 Subject: [PATCH 16/41] Fixed temporary glyph rectangle allocation using size much too big. --- imgui.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 9a48e19d1..51cbb9675 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6421,9 +6421,9 @@ bool ImFont::LoadFromMemoryTTF(const void* data, size_t data_size, float size // Setup ranges int glyph_count = 0; int glyph_ranges_count = 0; - for (const ImWchar* p = glyph_ranges; p[0] && p[1]; p += 2) + for (const ImWchar* in_range = glyph_ranges; in_range[0] && in_range[1]; in_range += 2) { - glyph_count += p[1]; + glyph_count += (in_range[1] - in_range[0]) + 1; glyph_ranges_count++; } @@ -6433,8 +6433,9 @@ bool ImFont::LoadFromMemoryTTF(const void* data, size_t data_size, float size { stbtt_pack_range& range = ranges[i]; range.font_size = size_pixels; - range.first_unicode_char_in_range = glyph_ranges[i*2]; - range.num_chars_in_range = (glyph_ranges[i*2+1] - range.first_unicode_char_in_range) + 1; + const ImWchar* in_range = &glyph_ranges[i * 2]; + range.first_unicode_char_in_range = in_range[0]; + range.num_chars_in_range = (in_range[1] - in_range[0]) + 1; range.chardata_for_range = (stbtt_packedchar*)ImGui::MemAlloc(range.num_chars_in_range * sizeof(stbtt_packedchar)); } From f061884dea12060f844e73f2f9cdb9d6983b39cf Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 11 Jan 2015 22:22:46 +0000 Subject: [PATCH 17/41] Typos and comments --- imgui.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 51cbb9675..03c3cfcb6 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7377,7 +7377,7 @@ void ImGui::ShowTestWindow(bool* opened) if (ImGui::TreeNode("Word Wrapping")) { // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. - ImGui::TextWrapped("This is a long paragraph. The text should automatically wrap on the edge of the window. The current implementation follows simple rules that works for English and possibly other languages."); + ImGui::TextWrapped("This text should automatically wrap on the edge of the window. The current implementation for text wrapping follows simple rules that works for English and possibly other languages."); ImGui::Spacing(); static float wrap_width = 200.0f; @@ -7402,11 +7402,12 @@ void ImGui::ShowTestWindow(bool* opened) if (ImGui::TreeNode("UTF-8 Text")) { - // UTF-8 test (need a suitable font, try extra_fonts/mplus* files for example) + // UTF-8 test with Japanese characters + // (needs a suitable font, try Arial Unicode or M+ fonts http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/index-en.html) // Most compiler appears to support UTF-8 in source code (with Visual Studio you need to save your file as 'UTF-8 without signature') // However for the sake for maximum portability here we are *not* including raw UTF-8 character in this source file, instead we encode the string with hexadecimal constants. - // In your own application please be reasonable and use UTF-8 in the source or get the data from external files! :) - ImGui::TextWrapped("(CJK text will only appears if the font supports it. Please check in the extra_fonts/ folder if you intend to use non-ASCII characters. Note that characters values are preserved even if the font cannot be displayed, so you can safely copy & paste garbled characters.)"); + // In your own application be reasonable and use UTF-8 in source or retrieve the data from file system! + ImGui::TextWrapped("CJK text will only appears if the font was loaded with the appropriate CJK character ranges. Call io.Font->LoadFromFileTTF() manually to specify extra character ranges. Note that characters values are preserved even if the font cannot be displayed, so you can safely copy & paste garbled characters into another application."); ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; @@ -7902,7 +7903,7 @@ struct ExampleAppConsole return; } - ImGui::TextWrapped("This example implement a console with basic coloring, completion and history. A more elaborate implementation may want to store entries along with extra data such as timestamp, emitter, etc."); + ImGui::TextWrapped("This example implements a console with basic coloring, completion and history. A more elaborate implementation may want to store entries along with extra data such as timestamp, emitter, etc."); ImGui::TextWrapped("Enter 'HELP' for help, press TAB to use text completion."); // TODO: display from bottom From 6c6d1746d449917f86068a2eca70663f8601c86f Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 13 Jan 2015 08:56:02 +0000 Subject: [PATCH 18/41] Fix missing glyph handling in modified stb_truetype.h --- stb_truetype.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stb_truetype.h b/stb_truetype.h index 2143164ab..2374e83c6 100644 --- a/stb_truetype.h +++ b/stb_truetype.h @@ -2330,8 +2330,8 @@ int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *inf rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); } else { - rects[k].was_packed = false; - } + rects[k].w = rects[k].h = 0; + } ++k; } } From 188165a06338a3b9998d0016454c79c04bdf9e86 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 14 Jan 2015 21:59:39 +0000 Subject: [PATCH 19/41] Fix stupid crash on fallback glyph handling (ttf branch) --- imgui.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 03c3cfcb6..caef89e94 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6550,7 +6550,8 @@ const ImFont::Glyph* ImFont::FindGlyph(unsigned short c) const if (c < (int)IndexLookup.size()) { const int i = IndexLookup[c]; - return &Glyphs[i]; + if (i != -1) + return &Glyphs[i]; } return FallbackGlyph; } From 4f6643cc0ce61caa3bd8959ce681c9c4e1678c5e Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 14 Jan 2015 22:05:13 +0000 Subject: [PATCH 20/41] Fix missing glyph handling in modified stb_truetype.h (ttf branch) --- stb_truetype.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stb_truetype.h b/stb_truetype.h index 2374e83c6..8d101accb 100644 --- a/stb_truetype.h +++ b/stb_truetype.h @@ -2330,7 +2330,7 @@ int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *inf rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); } else { - rects[k].w = rects[k].h = 0; + rects[k].w = rects[k].h = 1; } ++k; } From 7ebd7ef9ac3e74ac147481c868912f0960b43451 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 13:40:56 +0000 Subject: [PATCH 21/41] Added PushFont/PopFont API --- imgui.cpp | 58 +++++++++++++++++++++++++++++++++++++---------------- imgui.h | 60 ++++++++++++++++++++++++++++--------------------------- 2 files changed, 72 insertions(+), 46 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 6a569c7f9..3d18a0e4d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -323,6 +323,7 @@ static void RenderText(ImVec2 pos, const char* text, const char* text_en static void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); static void RenderCollapseTriangle(ImVec2 p_min, bool opened, float scale = 1.0f, bool shadow = false); +static void SetFont(ImFont* font); static void ItemSize(ImVec2 size, ImVec2* adjust_start_offset = NULL); static void ItemSize(const ImGuiAabb& aabb, ImVec2* adjust_start_offset = NULL); static void PushColumnClipRect(int column_index = -1); @@ -345,9 +346,10 @@ static const char* ImStristr(const char* haystack, const char* needle, const ch static size_t ImFormatString(char* buf, size_t buf_size, const char* fmt, ...); static size_t ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args); -// Helpers: Data +// Helpers: Misc static ImU32 ImCrc32(const void* data, size_t data_size, ImU32 seed); static bool ImLoadFileToMemory(const char* filename, const char* file_open_mode, void** out_file_data, size_t* out_file_size, size_t padding_bytes = 0); +static int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } // Helpers: Color Conversion static ImU32 ImConvertColorFloat4ToU32(const ImVec4& in); @@ -873,8 +875,9 @@ struct ImGuiState bool Initialized; ImGuiIO IO; ImGuiStyle Style; - float FontSize; // == IO.FontGlobalScale * IO.Font->Scale * IO.Font->Info->FontSize. Vertical distance between two lines of text, aka == CalcTextSize(" ").y - ImVec2 FontTexUvWhitePixel; // == IO.Font->TexUvForWhite (cached copy) + ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() + float FontSize; // (Shortcut) == IO.FontGlobalScale * (Font->Scale * Font->FontSize). Vertical distance between two lines of text, aka == CalcTextSize(" ").y + ImVec2 FontTexUvWhitePixel; // (Shortcut) == Font->TexUvForWhite float Time; int FrameCount; @@ -893,6 +896,7 @@ struct ImGuiState ImVector Settings; ImVector ColorModifiers; ImVector StyleModifiers; + ImVector FontStack; ImVec2 SetNextWindowPosVal; ImGuiSetCondition SetNextWindowPosCond; ImVec2 SetNextWindowSizeVal; @@ -1003,7 +1007,7 @@ public: void FocusItemUnregister(); ImGuiAabb Aabb() const { return ImGuiAabb(Pos, Pos+Size); } - ImFont* Font() const { return GImGui.IO.Font; } + ImFont* Font() const { return GImGui.Font; } float FontSize() const { return GImGui.FontSize * FontWindowScale; } ImVec2 CursorPos() const { return DC.CursorPos; } float TitleBarHeight() const { return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0 : FontSize() + GImGui.Style.FramePadding.y * 2.0f; } @@ -1537,11 +1541,7 @@ void ImGui::NewFrame() g.Initialized = true; } - IM_ASSERT(g.IO.Font->Scale > 0.0f); - g.FontSize = g.IO.FontGlobalScale * g.IO.Font->FontSize * g.IO.Font->Scale; - g.FontTexUvWhitePixel = g.IO.Font->TexUvWhitePixel; - g.IO.Font->FallbackGlyph = NULL; - g.IO.Font->FallbackGlyph = g.IO.Font->FindGlyph(g.IO.Font->FallbackChar); + SetFont(g.IO.Font); g.Time += g.IO.DeltaTime; g.FrameCount += 1; @@ -2795,6 +2795,34 @@ float ImGui::GetItemWidth() return window->DC.ItemWidth.back(); } +static void SetFont(ImFont* font) +{ + ImGuiState& g = GImGui; + + IM_ASSERT(font->Scale > 0.0f); + g.Font = font; + g.FontSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale; + g.FontTexUvWhitePixel = g.Font->TexUvWhitePixel; + g.Font->FallbackGlyph = NULL; + g.Font->FallbackGlyph = g.Font->FindGlyph(g.Font->FallbackChar); +} + +void ImGui::PushFont(ImFont* font) +{ + ImGuiState& g = GImGui; + + g.FontStack.push_back(font); + SetFont(font); +} + +void ImGui::PopFont() +{ + ImGuiState& g = GImGui; + + g.FontStack.pop_back(); + SetFont(g.FontStack.empty() ? g.IO.Font : g.FontStack.back()); +} + void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) { ImGuiWindow* window = GetCurrentWindow(); @@ -6432,8 +6460,6 @@ bool ImFont::LoadFromFileTTF(const char* filename, float size_pixels, const I return ret; } -static int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } - bool ImFont::LoadFromMemoryTTF(const void* data, size_t data_size, float size_pixels, const ImWchar* glyph_ranges, int font_no) { Clear(); @@ -6841,7 +6867,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const { if (!text_end) - text_end = text_begin + strlen(text_begin); // FIXME-OPT + text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. const float scale = size / FontSize; const float line_height = FontSize * scale; @@ -6890,8 +6916,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons if (c == '\n') { - if (text_size.x < line_width) - text_size.x = line_width; + text_size.x = ImMax(text_size.x, line_width); text_size.y += line_height; line_width = 0.0f; continue; @@ -6946,8 +6971,7 @@ ImVec2 ImFont::CalcTextSizeW(float size, float max_width, const ImWchar* text_be if (c == '\n') { - if (text_size.x < line_width) - text_size.x = line_width; + text_size.x = ImMax(text_size.x, line_width); text_size.y += line_height; line_width = 0.0f; continue; @@ -7435,7 +7459,7 @@ void ImGui::ShowTestWindow(bool* opened) if (ImGui::TreeNode("UTF-8 Text")) { // UTF-8 test with Japanese characters - // (needs a suitable font, try Arial Unicode or M+ fonts http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/index-en.html) + // (needs a suitable font, try Arial Unicode or M+ fonts http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/index-en.html) // Most compiler appears to support UTF-8 in source code (with Visual Studio you need to save your file as 'UTF-8 without signature') // However for the sake for maximum portability here we are *not* including raw UTF-8 character in this source file, instead we encode the string with hexadecimal constants. // In your own application be reasonable and use UTF-8 in source or retrieve the data from file system! diff --git a/imgui.h b/imgui.h index 368609b7d..3e1df04c7 100644 --- a/imgui.h +++ b/imgui.h @@ -178,6 +178,8 @@ namespace ImGui IMGUI_API void PushItemWidth(float item_width); // width of items for the common item+label case. default to ~2/3 of windows width. IMGUI_API void PopItemWidth(); IMGUI_API float GetItemWidth(); + IMGUI_API void PushFont(ImFont* font); + IMGUI_API void PopFont(); IMGUI_API void PushAllowKeyboardFocus(bool v); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets. IMGUI_API void PopAllowKeyboardFocus(); IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); @@ -753,16 +755,16 @@ struct ImFont float Scale; // = 1.0f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() ImVec2 DisplayOffset; // = (0.0f,0.0f) // Offset font rendering by xx pixels ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. - ImTextureID TexID; // = 0 // After loading texture, store your texture handle here (ignore if you aren't using multiple fonts/textures) + ImTextureID TexID; // = 0 // After loading texture, store your texture handle here (ignore if you aren't using multiple fonts/textures) - // Retrieve texture data + // Retrieve texture data // User is in charge of copying the pixels into graphics memory, then set 'TexID'. - // RGBA32 format is provided for convenience and high compatibility, but note that all RGB pixels are white. - // If you intend to use large font it may be pref - // NB: the data is invalidated as soon as you call a Load* function. - IMGUI_API void GetTextureDataRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - IMGUI_API void GetTextureDataAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel - IMGUI_API void ClearTextureData(); // Save RAM once the texture has been copied to graphics memory. + // RGBA32 format is provided for convenience and high compatibility, but note that all RGB pixels are white. + // If you intend to use large font it may be pref + // NB: the data is invalidated as soon as you call a Load* function. + IMGUI_API void GetTextureDataRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + IMGUI_API void GetTextureDataAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void ClearTextureData(); // Save RAM once the texture has been copied to graphics memory. // Methods IMGUI_API ImFont(); @@ -772,7 +774,7 @@ struct ImFont IMGUI_API bool LoadFromMemoryTTF(const void* data, size_t data_size, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); IMGUI_API void Clear(); IMGUI_API void BuildLookupTable(); - struct Glyph; + struct Glyph; IMGUI_API const Glyph* FindGlyph(unsigned short c) const; IMGUI_API bool IsLoaded() const { return !Glyphs.empty(); } @@ -788,29 +790,29 @@ struct ImFont IMGUI_API void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawVert*& out_vertices, float wrap_width = 0.0f) const; IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; - // Texture data - // Access via GetTextureData() which will load the font if not loaded - unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight - unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 - int TexWidth; - int TexHeight; - ImVec2 TexExtraDataPos; // Position of our rectangle where we draw non-font graphics - ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel (part of the TexExtraData block) + // Texture data + // Access via GetTextureData() which will load the font if not loaded + unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight + unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + int TexWidth; + int TexHeight; + ImVec2 TexExtraDataPos; // Position of our rectangle where we draw non-font graphics + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel (part of the TexExtraData block) - struct Glyph - { - ImWchar Codepoint; + struct Glyph + { + ImWchar Codepoint; signed short XAdvance; - signed short Width, Height; - signed short XOffset, YOffset; - float U0, V0, U1, V1; // Texture coordinates - }; + signed short Width, Height; + signed short XOffset, YOffset; + float U0, V0, U1, V1; // Texture coordinates + }; - // Runtime data - float FontSize; // Height of characters - ImVector Glyphs; - ImVector IndexLookup; - const Glyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) + // Runtime data + float FontSize; // Height of characters + ImVector Glyphs; + ImVector IndexLookup; + const Glyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) }; //---- Include imgui_user.h at the end of imgui.h From 3e30ad3802f08629229ceff30943608bf9e5125d Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 14:00:43 +0000 Subject: [PATCH 22/41] PushFont/PopFont changes texture at high-level in current draw list - faster --- imgui.cpp | 20 +++++++++----------- imgui.h | 15 +++++++++------ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 3d18a0e4d..b60ef2bf6 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -90,7 +90,7 @@ // Load texture unsigned char* pixels; int width, height; - io.Font->GetTextureData(&pixels, &width, &height); + io.Font->GetTextureDataAlpha8(&pixels, &width, &height); // or use GetTextureDataRGBA32() // TODO: copy texture to graphics memory. Store texture identifier for your engine in io.Font->TexID // Application main loop @@ -1688,6 +1688,7 @@ void ImGui::Shutdown() g.Settings.clear(); g.ColorModifiers.clear(); g.StyleModifiers.clear(); + g.FontStack.clear(); g.ColorEditModeStorage.Clear(); if (g.LogFile && g.LogFile != stdout) { @@ -2373,7 +2374,7 @@ bool ImGui::Begin(const char* name, bool* p_opened, ImVec2 size, float fill_alph if (first_begin_of_the_frame) { window->DrawList->Clear(); - window->DrawList->PushTextureID(g.IO.Font->TexID); + window->DrawList->PushTextureID(g.Font->TexID); window->Visible = true; // New windows appears in front @@ -2686,7 +2687,7 @@ bool ImGui::Begin(const char* name, bool* p_opened, ImVec2 size, float fill_alph else { // Short path when we do multiple Begin in the same frame. - window->DrawList->PushTextureID(g.IO.Font->TexID); + window->DrawList->PushTextureID(g.Font->TexID); // Outer clipping rectangle if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ComboBox)) @@ -2811,14 +2812,16 @@ void ImGui::PushFont(ImFont* font) { ImGuiState& g = GImGui; - g.FontStack.push_back(font); - SetFont(font); + SetFont(font); + g.FontStack.push_back(font); + g.CurrentWindow->DrawList->PushTextureID(font->TexID); } void ImGui::PopFont() { ImGuiState& g = GImGui; + g.CurrentWindow->DrawList->PopTextureID(); g.FontStack.pop_back(); SetFont(g.FontStack.empty() ? g.IO.Font : g.FontStack.back()); } @@ -6220,9 +6223,7 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 if (text_end == NULL) text_end = text_begin + strlen(text_begin); - const bool push_texture_id = font->TexID != texture_id_stack.back(); - if (push_texture_id) - PushTextureID(font->TexID); + IM_ASSERT(font->TexID == texture_id_stack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. // reserve vertices for worse case const unsigned int char_count = (unsigned int)(text_end - text_begin); @@ -6237,9 +6238,6 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 const size_t vtx_count = vtx_buffer.size() - vtx_begin; commands.back().vtx_count -= (unsigned int)(vtx_count_max - vtx_count); vtx_write -= (vtx_count_max - vtx_count); - - if (push_texture_id) - PopTextureID(); } void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv0, const ImVec2& uv1, ImU32 col) diff --git a/imgui.h b/imgui.h index 3e1df04c7..a2512df16 100644 --- a/imgui.h +++ b/imgui.h @@ -175,18 +175,21 @@ namespace ImGui IMGUI_API void SetStateStorage(ImGuiStorage* tree); // replace tree state storage with our own (if you want to manipulate it yourself, typically clear subsection of it). IMGUI_API ImGuiStorage* GetStateStorage(); - IMGUI_API void PushItemWidth(float item_width); // width of items for the common item+label case. default to ~2/3 of windows width. - IMGUI_API void PopItemWidth(); - IMGUI_API float GetItemWidth(); + // Parameters stacks (shared) IMGUI_API void PushFont(ImFont* font); IMGUI_API void PopFont(); - IMGUI_API void PushAllowKeyboardFocus(bool v); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets. - IMGUI_API void PopAllowKeyboardFocus(); IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); IMGUI_API void PopStyleColor(int count = 1); IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); IMGUI_API void PopStyleVar(int count = 1); + + // Parameters stacks (current window) + IMGUI_API void PushItemWidth(float item_width); // width of items for the common item+label case. default to ~2/3 of windows width. + IMGUI_API void PopItemWidth(); + IMGUI_API float GetItemWidth(); + IMGUI_API void PushAllowKeyboardFocus(bool v); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets. + IMGUI_API void PopAllowKeyboardFocus(); IMGUI_API void PushTextWrapPos(float wrap_pos_x = 0.0f); // word-wrapping for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space. IMGUI_API void PopTextWrapPos(); @@ -802,7 +805,7 @@ struct ImFont struct Glyph { ImWchar Codepoint; - signed short XAdvance; + signed short XAdvance; signed short Width, Height; signed short XOffset, YOffset; float U0, V0, U1, V1; // Texture coordinates From edee014ab85b02025087e7f9f3b2092e511b31e5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 14:13:08 +0000 Subject: [PATCH 23/41] ImDrawList merging commands with same texture --- imgui.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b60ef2bf6..b33e6a18f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1688,7 +1688,7 @@ void ImGui::Shutdown() g.Settings.clear(); g.ColorModifiers.clear(); g.StyleModifiers.clear(); - g.FontStack.clear(); + g.FontStack.clear(); g.ColorEditModeStorage.Clear(); if (g.LogFile && g.LogFile != stdout) { @@ -2812,16 +2812,16 @@ void ImGui::PushFont(ImFont* font) { ImGuiState& g = GImGui; - SetFont(font); - g.FontStack.push_back(font); - g.CurrentWindow->DrawList->PushTextureID(font->TexID); + SetFont(font); + g.FontStack.push_back(font); + g.CurrentWindow->DrawList->PushTextureID(font->TexID); } void ImGui::PopFont() { ImGuiState& g = GImGui; - g.CurrentWindow->DrawList->PopTextureID(); + g.CurrentWindow->DrawList->PopTextureID(); g.FontStack.pop_back(); SetFont(g.FontStack.empty() ? g.IO.Font : g.FontStack.back()); } @@ -5928,11 +5928,12 @@ void ImDrawList::Clear() void ImDrawList::SetClipRect(const ImVec4& clip_rect) { - if (!commands.empty() && commands.back().vtx_count == 0) + ImDrawCmd* current_cmd = commands.empty() ? NULL : &commands.back(); + if (current_cmd && current_cmd->vtx_count == 0) { // Reuse existing command (high-level clipping may have discarded vertices submitted earlier) // FIXME-OPT: Possibly even reuse previous command. - commands.back().clip_rect = clip_rect; + current_cmd->clip_rect = clip_rect; } else { @@ -5959,11 +5960,12 @@ void ImDrawList::PopClipRect() void ImDrawList::SetTextureID(const ImTextureID& texture_id) { - if (!commands.empty() && commands.back().vtx_count == 0) + ImDrawCmd* current_cmd = commands.empty() ? NULL : &commands.back(); + if (current_cmd && (current_cmd->vtx_count == 0 || current_cmd->texture_id == texture_id)) { - // Reuse existing command (high-level clipping may have discarded vertices submitted earlier) + // Reuse existing command // FIXME-OPT: Possibly even reuse previous command. - commands.back().texture_id = texture_id; + current_cmd->texture_id = texture_id; } else { From 014f88b1fcf8275bd92dba87477d28c4deba10bc Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 15:44:25 +0000 Subject: [PATCH 24/41] Font fixes for horizontal centering within frames --- imgui.cpp | 21 +++++++++++++++------ imgui.h | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b33e6a18f..db449086f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -876,7 +876,7 @@ struct ImGuiState ImGuiIO IO; ImGuiStyle Style; ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() - float FontSize; // (Shortcut) == IO.FontGlobalScale * (Font->Scale * Font->FontSize). Vertical distance between two lines of text, aka == CalcTextSize(" ").y + float FontSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Size of characters. ImVec2 FontTexUvWhitePixel; // (Shortcut) == Font->TexUvForWhite float Time; @@ -2025,7 +2025,7 @@ static ImGuiWindow* FindHoveredWindow(ImVec2 pos, bool excluding_childs) continue; if (excluding_childs && (window->Flags & ImGuiWindowFlags_ChildWindow) != 0) continue; - ImGuiAabb bb(window->Pos - g.Style.TouchExtraPadding, window->Pos+window->Size + g.Style.TouchExtraPadding); + ImGuiAabb bb(window->Pos - g.Style.TouchExtraPadding, window->Pos + window->Size + g.Style.TouchExtraPadding); if (bb.Contains(pos)) return window; } @@ -6270,7 +6270,6 @@ void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const Im ImFont::ImFont() { Scale = 1.0f; - DisplayOffset = ImVec2(0.5f, 0.5f); FallbackChar = (ImWchar)'?'; TexPixelsAlpha8 = NULL; @@ -6328,14 +6327,14 @@ void ImFont::ClearTextureData() void ImFont::Clear() { - DisplayOffset = ImVec2(0.5f, 0.5f); + FontSize = 0.0f; + DisplayOffset = ImVec2(-0.5f, 0.5f); ClearTextureData(); TexID = NULL; TexWidth = TexHeight = 0; TexExtraDataPos = TexUvWhitePixel = ImVec2(0, 0); - FontSize = 0.0f; Glyphs.clear(); IndexLookup.clear(); FallbackGlyph = NULL; @@ -6947,6 +6946,11 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons text_size.y += line_height; } + // Cancel out character spacing for the last character of a line (it is baked into glyph->XAdvance field) + const float character_spacing_x = 1.0f * scale; + if (text_size.x > 0.0f) + text_size.x -= character_spacing_x; + if (remaining) *remaining = s; @@ -7003,6 +7007,11 @@ ImVec2 ImFont::CalcTextSizeW(float size, float max_width, const ImWchar* text_be text_size.y += line_height; } + // Cancel out character spacing for the last character of a line (it is baked into glyph->XAdvance field) + const float character_spacing_x = 1.0f * scale; + if (text_size.x > 0.0f) + text_size.x -= character_spacing_x; + if (remaining) *remaining = s; @@ -7321,7 +7330,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) { static float window_scale = 1.0f; ImFont* font = ImGui::GetIO().Font; - ImGui::Text("Font Size: %.2f", font->FontSize); + ImGui::Text("Base Font Size: %.2f", font->FontSize); ImGui::SliderFloat("window scale", &window_scale, 0.3f, 2.0f, "%.1f"); // scale only this window ImGui::SliderFloat("font scale", &font->Scale, 0.3f, 2.0f, "%.1f"); // scale only this font ImGui::SliderFloat("global scale", &ImGui::GetIO().FontGlobalScale, 0.3f, 2.0f, "%.1f"); // scale everything diff --git a/imgui.h b/imgui.h index a2512df16..216d5a42d 100644 --- a/imgui.h +++ b/imgui.h @@ -755,6 +755,7 @@ struct ImDrawList struct ImFont { // Settings + float FontSize; // // Height of characters, set during loading (don't change after loading) float Scale; // = 1.0f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() ImVec2 DisplayOffset; // = (0.0f,0.0f) // Offset font rendering by xx pixels ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. @@ -812,7 +813,6 @@ struct ImFont }; // Runtime data - float FontSize; // Height of characters ImVector Glyphs; ImVector IndexLookup; const Glyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) From 43c8b5e0d214fb6cbd72f6d64ae600dbea57b0ab Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 16:52:21 +0000 Subject: [PATCH 25/41] Examples: DirectX11: use linear sampler to be in sync with other examples. --- examples/directx11_example/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index a682c04ba..5f18917a8 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -474,7 +474,7 @@ void InitImGui() { D3D11_SAMPLER_DESC desc; ZeroMemory(&desc, sizeof(desc)); - desc.Filter = D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR; + desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; From ad92018bc0ceee4e39617af5657006a09c894edc Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 17:03:22 +0000 Subject: [PATCH 26/41] Fixed lower-right rounded triangle rendering precision. PixelCenterOffset is handled very inconsistently, needs to be fixed. --- imgui.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index db449086f..6f56113dc 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -214,6 +214,7 @@ - main: IsItemHovered() returns true even if mouse is active on another widget (e.g. dragging outside of sliders). Maybe not a sensible default? Add parameter or alternate function? - main: IsItemHovered() make it more consistent for various type of widgets, widgets with multiple components, etc. also effectively IsHovered() region sometimes differs from hot region, e.g tree nodes - main: IsItemHovered() info stored in a stack? so that 'if TreeNode() { Text; TreePop; } if IsHovered' return the hover state of the TreeNode? +!- drawlist: handling of PixelCenterOffset is badly inconsistent (sometimes in callee, sometimes in caller). - scrollbar: use relative mouse movement when first-clicking inside of scroll grab box. - scrollbar: make the grab visible and a minimum size for long scroll regions !- input number: very large int not reliably supported because of int<>float conversions. @@ -2629,10 +2630,11 @@ bool ImGui::Begin(const char* name, bool* p_opened, ImVec2 size, float fill_alph } else { - // FIXME: We should draw 4 triangles and decide on a size that's not dependant on the rounding size (previously used 18) + // FIXME: We should draw 4 triangles and decide on a size that's not dependent on the rounding size (previously used 18) + const ImVec2 offset(GImGui.IO.PixelCenterOffset,GImGui.IO.PixelCenterOffset); window->DrawList->AddArc(br - ImVec2(r,r), r, resize_col, 6, 9, true); - window->DrawList->AddTriangleFilled(br+ImVec2(0,-2*r),br+ImVec2(0,-r),br+ImVec2(-r,-r), resize_col); - window->DrawList->AddTriangleFilled(br+ImVec2(-r,-r), br+ImVec2(-r,0),br+ImVec2(-2*r,0), resize_col); + window->DrawList->AddTriangleFilled(br-offset+ImVec2(0,-2*r),br-offset+ImVec2(0,-r),br-offset+ImVec2(-r,-r), resize_col); + window->DrawList->AddTriangleFilled(br-offset+ImVec2(-r,-r), br-offset+ImVec2(-r,0),br-offset+ImVec2(-2*r,0), resize_col); } } } From cb9a3235bec264809089f1016e31424b21b251d3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 17:17:18 +0000 Subject: [PATCH 27/41] Removed PixelCenterOffset, uncesssary --- examples/directx11_example/main.cpp | 2 -- examples/directx9_example/main.cpp | 2 -- examples/opengl3_example/main.cpp | 2 -- examples/opengl_example/main.cpp | 2 -- imgui.cpp | 43 +++++++++++------------------ imgui.h | 1 - 6 files changed, 16 insertions(+), 36 deletions(-) diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index 5f18917a8..32ceb293d 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -44,7 +44,6 @@ struct VERTEX_CONSTANT_BUFFER // This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure) // If text or lines are blurry when integrating ImGui in your engine: // - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f) -// - try adjusting ImGui::GetIO().PixelCenterOffset to 0.5f or 0.375f static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { size_t total_vtx_count = 0; @@ -429,7 +428,6 @@ void InitImGui() ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2((float)display_w, (float)display_h); // Display size, in pixels. For clamping windows positions. io.DeltaTime = 1.0f/60.0f; // Time elapsed since last frame, in seconds (in this sample app we'll override this every frame because our time step is variable) - io.PixelCenterOffset = 0.0f; // Align Direct3D Texels io.KeyMap[ImGuiKey_Tab] = VK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index 3752bafc2..1f312538b 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -25,7 +25,6 @@ struct CUSTOMVERTEX // This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure) // If text or lines are blurry when integrating ImGui in your engine: // - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f) -// - try adjusting ImGui::GetIO().PixelCenterOffset to 0.5f or 0.375f static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { size_t total_vtx_count = 0; @@ -212,7 +211,6 @@ void InitImGui() ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2((float)display_w, (float)display_h); // Display size, in pixels. For clamping windows positions. io.DeltaTime = 1.0f/60.0f; // Time elapsed since last frame, in seconds (in this sample app we'll override this every frame because our time step is variable) - io.PixelCenterOffset = 0.0f; // Align Direct3D Texels io.KeyMap[ImGuiKey_Tab] = VK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; diff --git a/examples/opengl3_example/main.cpp b/examples/opengl3_example/main.cpp index 9934bc887..0c0dfab63 100644 --- a/examples/opengl3_example/main.cpp +++ b/examples/opengl3_example/main.cpp @@ -28,7 +28,6 @@ static unsigned int vbo_handle, vao_handle; // This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure) // If text or lines are blurry when integrating ImGui in your engine: -// - try adjusting ImGui::GetIO().PixelCenterOffset to 0.0f or 0.5f // - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f) static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { @@ -256,7 +255,6 @@ void InitImGui() { ImGuiIO& io = ImGui::GetIO(); io.DeltaTime = 1.0f / 60.0f; // Time elapsed since last frame, in seconds (in this sample app we'll override this every frame because our timestep is variable) - io.PixelCenterOffset = 0.5f; // Align OpenGL texels io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 0119e7891..29e1c97b4 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -22,7 +22,6 @@ static bool mousePressed[2] = { false, false }; // This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure) // If text or lines are blurry when integrating ImGui in your engine: // - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f) -// - try adjusting ImGui::GetIO().PixelCenterOffset to 0.5f or 0.375f static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { if (cmd_lists_count == 0) @@ -166,7 +165,6 @@ void InitImGui() { ImGuiIO& io = ImGui::GetIO(); io.DeltaTime = 1.0f/60.0f; // Time elapsed since last frame, in seconds (in this sample app we'll override this every frame because our time step is variable) - io.PixelCenterOffset = 0.0f; // Align OpenGL texels io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; diff --git a/imgui.cpp b/imgui.cpp index 6f56113dc..f9840bb8a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -127,6 +127,7 @@ - 2015/01/11 (1.30) big font/image API change. now loads TTF file. allow for multiple fonts. no need for a PNG loader. removed GetDefaultFontData(). uses io.Font->GetTextureData*() API to retrieve uncompressed pixels. added texture identifier in ImDrawCmd passed to your render function. + removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) - 2014/12/10 (1.18) removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) - 2014/11/28 (1.17) moved IO.Font*** options to inside the IO.Font-> structure. - 2014/11/26 (1.17) reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility @@ -145,7 +146,6 @@ If text or lines are blurry when integrating ImGui in your engine: - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f) - - try adjusting ImGui::GetIO().PixelCenterOffset to 0.5f or 0.375f If you are confused about the meaning or use of ID in ImGui: - some widgets requires state to be carried over multiple frames (most typically ImGui often wants remember what is the "active" widget). @@ -214,7 +214,6 @@ - main: IsItemHovered() returns true even if mouse is active on another widget (e.g. dragging outside of sliders). Maybe not a sensible default? Add parameter or alternate function? - main: IsItemHovered() make it more consistent for various type of widgets, widgets with multiple components, etc. also effectively IsHovered() region sometimes differs from hot region, e.g tree nodes - main: IsItemHovered() info stored in a stack? so that 'if TreeNode() { Text; TreePop; } if IsHovered' return the hover state of the TreeNode? -!- drawlist: handling of PixelCenterOffset is badly inconsistent (sometimes in callee, sometimes in caller). - scrollbar: use relative mouse movement when first-clicking inside of scroll grab box. - scrollbar: make the grab visible and a minimum size for long scroll regions !- input number: very large int not reliably supported because of int<>float conversions. @@ -450,7 +449,6 @@ ImGuiIO::ImGuiIO() Font = &GDefaultStaticFont; FontGlobalScale = 1.0f; FontAllowUserScaling = false; - PixelCenterOffset = 0.0f; MousePos = ImVec2(-1,-1); MousePosPrev = ImVec2(-1,-1); MouseDoubleClickTime = 0.30f; @@ -1963,10 +1961,9 @@ static void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); if (border && (window->Flags & ImGuiWindowFlags_ShowBorders)) { - // FIXME: This is the best I've found that works on multiple renderer/back ends. Rather dodgy. - const float offset = GImGui.IO.PixelCenterOffset; - window->DrawList->AddRect(p_min+ImVec2(1.5f-offset,1.5f-offset), p_max+ImVec2(1.0f-offset*2,1.0f-offset*2), window->Color(ImGuiCol_BorderShadow), rounding); - window->DrawList->AddRect(p_min+ImVec2(0.5f-offset,0.5f-offset), p_max+ImVec2(0.0f-offset*2,0.0f-offset*2), window->Color(ImGuiCol_Border), rounding); + // FIXME: This is the best I've found that works on multiple renderer/back ends. Bit dodgy. + window->DrawList->AddRect(p_min+ImVec2(1.5f,1.5f), p_max+ImVec2(1,1), window->Color(ImGuiCol_BorderShadow), rounding); + window->DrawList->AddRect(p_min+ImVec2(0.5f,0.5f), p_max+ImVec2(0,0), window->Color(ImGuiCol_Border), rounding); } } @@ -2631,10 +2628,9 @@ bool ImGui::Begin(const char* name, bool* p_opened, ImVec2 size, float fill_alph else { // FIXME: We should draw 4 triangles and decide on a size that's not dependent on the rounding size (previously used 18) - const ImVec2 offset(GImGui.IO.PixelCenterOffset,GImGui.IO.PixelCenterOffset); window->DrawList->AddArc(br - ImVec2(r,r), r, resize_col, 6, 9, true); - window->DrawList->AddTriangleFilled(br-offset+ImVec2(0,-2*r),br-offset+ImVec2(0,-r),br-offset+ImVec2(-r,-r), resize_col); - window->DrawList->AddTriangleFilled(br-offset+ImVec2(-r,-r), br-offset+ImVec2(-r,0),br-offset+ImVec2(-2*r,0), resize_col); + window->DrawList->AddTriangleFilled(br+ImVec2(0,-2*r),br+ImVec2(0,-r),br+ImVec2(-r,-r), resize_col); + window->DrawList->AddTriangleFilled(br+ImVec2(-r,-r), br+ImVec2(-r,0),br+ImVec2(-2*r,0), resize_col); } } } @@ -6021,11 +6017,10 @@ void ImDrawList::AddVtxUV(const ImVec2& pos, ImU32 col, const ImVec2& uv) void ImDrawList::AddVtxLine(const ImVec2& a, const ImVec2& b, ImU32 col) { - const float offset = GImGui.IO.PixelCenterOffset; const float length = sqrtf(ImLengthSqr(b - a)); - const ImVec2 hn = (b - a) * (0.50f / length); // half normal - const ImVec2 hp0 = ImVec2(offset + hn.y, offset - hn.x); // half perpendiculars + user offset - const ImVec2 hp1 = ImVec2(offset - hn.y, offset + hn.x); + const ImVec2 hn = (b - a) * (0.50f / length); // half normal + const ImVec2 hp0 = ImVec2(+hn.y, -hn.x); // half perpendiculars + user offset + const ImVec2 hp1 = ImVec2(-hn.y, +hn.x); // Two triangles makes up one line. Using triangles allows us to reduce amount of draw calls. AddVtx(a + hp0, col); @@ -6173,12 +6168,10 @@ void ImDrawList::AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec if ((col >> 24) == 0) return; - const ImVec2 offset(GImGui.IO.PixelCenterOffset,GImGui.IO.PixelCenterOffset); - ReserveVertices(3); - AddVtx(a + offset, col); - AddVtx(b + offset, col); - AddVtx(c + offset, col); + AddVtx(a, col); + AddVtx(b, col); + AddVtx(c, col); } void ImDrawList::AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments) @@ -6186,15 +6179,13 @@ void ImDrawList::AddCircle(const ImVec2& centre, float radius, ImU32 col, int nu if ((col >> 24) == 0) return; - const ImVec2 offset(GImGui.IO.PixelCenterOffset,GImGui.IO.PixelCenterOffset); - ReserveVertices((unsigned int)num_segments*6); const float a_step = 2*PI/(float)num_segments; float a0 = 0.0f; for (int i = 0; i < num_segments; i++) { const float a1 = (i + 1) == num_segments ? 0.0f : a0 + a_step; - AddVtxLine(centre + offset + ImVec2(cosf(a0), sinf(a0))*radius, centre + ImVec2(cosf(a1), sinf(a1))*radius, col); + AddVtxLine(centre + ImVec2(cosf(a0), sinf(a0))*radius, centre + ImVec2(cosf(a1), sinf(a1))*radius, col); a0 = a1; } } @@ -6204,17 +6195,15 @@ void ImDrawList::AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, if ((col >> 24) == 0) return; - const ImVec2 offset(GImGui.IO.PixelCenterOffset,GImGui.IO.PixelCenterOffset); - ReserveVertices((unsigned int)num_segments*3); const float a_step = 2*PI/(float)num_segments; float a0 = 0.0f; for (int i = 0; i < num_segments; i++) { const float a1 = (i + 1) == num_segments ? 0.0f : a0 + a_step; - AddVtx(centre + offset + ImVec2(cosf(a0), sinf(a0))*radius, col); - AddVtx(centre + offset + ImVec2(cosf(a1), sinf(a1))*radius, col); - AddVtx(centre + offset, col); + AddVtx(centre + ImVec2(cosf(a0), sinf(a0))*radius, col); + AddVtx(centre + ImVec2(cosf(a1), sinf(a1))*radius, col); + AddVtx(centre, col); a0 = a1; } } diff --git a/imgui.h b/imgui.h index 216d5a42d..4353c5d2f 100644 --- a/imgui.h +++ b/imgui.h @@ -494,7 +494,6 @@ struct ImGuiIO ImFont* Font; // // Font (also see 'Settings' fields inside ImFont structure for details) float FontGlobalScale; // = 1.0f // Global scale all fonts bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. - float PixelCenterOffset; // = 0.0f // Try to set to 0.5f or 0.375f if rendering is blurry void* UserData; // = NULL // Store your own data for retrieval by callbacks. From 2c31599bcc31dd7a8418f70e362b8f4f58e725d6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 22:15:29 +0000 Subject: [PATCH 28/41] ImFontAtlas allows loading multiple fonts into same texture. Revamped new init API for 1.30 --- examples/directx11_example/main.cpp | 22 +- examples/directx9_example/main.cpp | 30 +- examples/opengl3_example/main.cpp | 20 +- examples/opengl_example/main.cpp | 20 +- imgui.cpp | 583 +++++++++++++++++----------- imgui.h | 148 ++++--- 6 files changed, 488 insertions(+), 335 deletions(-) diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index 32ceb293d..8d258abae 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -321,7 +321,7 @@ void CleanupDevice() // InitImGui if (g_pFontSampler) g_pFontSampler->Release(); - if (ID3D11ShaderResourceView* font_texture_view = (ID3D11ShaderResourceView*)ImGui::GetIO().Font->TexID) + if (ID3D11ShaderResourceView* font_texture_view = (ID3D11ShaderResourceView*)ImGui::GetIO().FontAtlas->TexID) font_texture_view->Release(); if (g_pVB) g_pVB->Release(); @@ -377,11 +377,17 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) return DefWindowProc(hWnd, msg, wParam, lParam); } -void LoadFontTexture(ImFont* font) +void LoadFontTexture() { + // Load one or more font + ImGuiIO& io = ImGui::GetIO(); + //ImFont* my_font = io.FontAtlas->AddFontDefault(); + //ImFont* my_font2 = io.FontAtlas->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, ImFontAtlas::GetGlyphRangesJapanese()); + + // Build unsigned char* pixels; int width, height; - font->GetTextureDataRGBA32(&pixels, &width, &height); + io.FontAtlas->GetTexDataAsRGBA32(&pixels, &width, &height); // Create texture D3D11_TEXTURE2D_DESC desc; @@ -414,8 +420,8 @@ void LoadFontTexture(ImFont* font) g_pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &font_texture_view); pTexture->Release(); - // Store ID - font->TexID = (void *)font_texture_view; + // Store our identifier + io.FontAtlas->TexID = (void *)font_texture_view; } void InitImGui() @@ -463,10 +469,8 @@ void InitImGui() } } - // Load font (optionally load a custom TTF font) - //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); - //io.Font->DisplayOffset.y += 1.0f; - LoadFontTexture(io.Font); + // Load fonts + LoadFontTexture(); // Create texture sampler { diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index 1f312538b..6e689cfeb 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -73,8 +73,8 @@ static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_c g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_POINT ); - g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_POINT ); + g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); + g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); // Setup orthographic projection matrix D3DXMATRIXA16 mat; @@ -129,7 +129,7 @@ void CleanupDevice() if (g_pVB) g_pVB->Release(); // InitDeviceD3D - if (LPDIRECT3DTEXTURE9 tex = (LPDIRECT3DTEXTURE9)ImGui::GetIO().Font->TexID) + if (LPDIRECT3DTEXTURE9 tex = (LPDIRECT3DTEXTURE9)ImGui::GetIO().FontAtlas->TexID) tex->Release(); if (g_pd3dDevice) g_pd3dDevice->Release(); if (g_pD3D) g_pD3D->Release(); @@ -173,13 +173,19 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) return DefWindowProc(hWnd, msg, wParam, lParam); } -void LoadFontTexture(ImFont* font) +void LoadFontTexture() { - unsigned char* pixels; - int width, height; - int bytes_per_pixel; - font->GetTextureDataAlpha8(&pixels, &width, &height, &bytes_per_pixel); + // Load one or more font + ImGuiIO& io = ImGui::GetIO(); + //ImFont* my_font = io.FontAtlas->AddFontDefault(); + //ImFont* my_font2 = io.FontAtlas->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, ImFontAtlas::GetGlyphRangesJapanese()); + // Build + unsigned char* pixels; + int width, height, bytes_per_pixel; + io.FontAtlas->GetTexDataAsAlpha8(&pixels, &width, &height, &bytes_per_pixel); + + // Create texture LPDIRECT3DTEXTURE9 pTexture = NULL; if (D3DXCreateTexture(g_pd3dDevice, width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8, D3DPOOL_DEFAULT, &pTexture) < 0) { @@ -198,7 +204,8 @@ void LoadFontTexture(ImFont* font) memcpy((unsigned char *)tex_locked_rect.pBits + tex_locked_rect.Pitch * y, pixels + (width * bytes_per_pixel) * y, (width * bytes_per_pixel)); pTexture->UnlockRect(0); - font->TexID = (void *)pTexture; + // Store our identifier + io.FontAtlas->TexID = (void *)pTexture; } void InitImGui() @@ -238,10 +245,7 @@ void InitImGui() return; } - // Load font (optionally load a custom TTF font) - //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); - //io.Font->DisplayOffset.y += 1.0f; - LoadFontTexture(io.Font); + LoadFontTexture(); } INT64 ticks_per_second = 0; diff --git a/examples/opengl3_example/main.cpp b/examples/opengl3_example/main.cpp index 0c0dfab63..8ed935ed1 100644 --- a/examples/opengl3_example/main.cpp +++ b/examples/opengl3_example/main.cpp @@ -235,20 +235,25 @@ void InitGL() glBindBuffer(GL_ARRAY_BUFFER, 0); } -void LoadFontTexture(ImFont* font) +void LoadFontTexture() { + ImGuiIO& io = ImGui::GetIO(); + //ImFont* my_font = io.FontAtlas->AddFontDefault(); + //ImFont* my_font2 = io.FontAtlas->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, ImFontAtlas::GetGlyphRangesJapanese()); + unsigned char* pixels; int width, height; - font->GetTextureDataRGBA32(&pixels, &width, &height); + io.FontAtlas->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits for OpenGL3 demo because it is more likely to be compatible with user's existing shader. GLuint tex_id; glGenTextures(1, &tex_id); glBindTexture(GL_TEXTURE_2D, tex_id); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - font->TexID = (void *)(intptr_t)tex_id; + // Store our identifier + io.FontAtlas->TexID = (void *)(intptr_t)tex_id; } void InitImGui() @@ -277,10 +282,7 @@ void InitImGui() io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; - // Load font (optionally load a custom TTF font) - //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); - //io.Font->DisplayOffset.y += 1.0f; - LoadFontTexture(io.Font); + LoadFontTexture(); } void UpdateImGui() diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 29e1c97b4..02077e3ac 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -145,20 +145,25 @@ void InitGL() glewInit(); } -void LoadFontTexture(ImFont* font) +void LoadFontTexture() { + ImGuiIO& io = ImGui::GetIO(); + //ImFont* my_font = io.FontAtlas->AddFontDefault(); + //ImFont* my_font2 = io.FontAtlas->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, ImFontAtlas::GetGlyphRangesJapanese()); + unsigned char* pixels; int width, height; - font->GetTextureDataAlpha8(&pixels, &width, &height); + io.FontAtlas->GetTexDataAsAlpha8(&pixels, &width, &height); GLuint tex_id; glGenTextures(1, &tex_id); glBindTexture(GL_TEXTURE_2D, tex_id); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels); - font->TexID = (void *)(intptr_t)tex_id; + // Store our identifier + io.FontAtlas->TexID = (void *)(intptr_t)tex_id; } void InitImGui() @@ -186,11 +191,6 @@ void InitImGui() io.RenderDrawListsFn = ImImpl_RenderDrawLists; io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; - - // Load font (optionally load a custom TTF font) - //io.Font->LoadFromFileTTF("myfont.ttf", font_size_px, ImFont::GetGlyphRangesDefault()); - //io.Font->DisplayOffset.y += 1.0f; - LoadFontTexture(io.Font); } void UpdateImGui() diff --git a/imgui.cpp b/imgui.cpp index f9840bb8a..4488751cd 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -432,11 +432,9 @@ ImGuiStyle::ImGuiStyle() Colors[ImGuiCol_TooltipBg] = ImVec4(0.05f, 0.05f, 0.10f, 0.90f); } -// We statically allocate a default font storage for the user. -// This allows the user to avoid newing the default font, while keeping IO.Font a pointer which is easy to swap if needed. -// We cannot new() the font because user may override MemAllocFn after the ImGuiIO() constructor is called. -// For the same reason we cannot call LoadDefault() on the font by default, but it is called by GetTextureData*() API. -static ImFont GDefaultStaticFont; +// Statically allocated font atlas. This is merely a maneuver to keep its definition at the bottom of the .H file. +// Because we cannot new() at this point (before users may define IO.MemAllocFn) +static ImFontAtlas GDefaultFontAtlas; ImGuiIO::ImGuiIO() { @@ -446,7 +444,7 @@ ImGuiIO::ImGuiIO() IniSavingRate = 5.0f; IniFilename = "imgui.ini"; LogFilename = "imgui_log.txt"; - Font = &GDefaultStaticFont; + FontAtlas = &GDefaultFontAtlas; FontGlobalScale = 1.0f; FontAllowUserScaling = false; MousePos = ImVec2(-1,-1); @@ -1525,9 +1523,9 @@ void ImGui::NewFrame() // Check user data IM_ASSERT(g.IO.DeltaTime > 0.0f); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f); - IM_ASSERT(g.IO.RenderDrawListsFn != NULL); // Must be implemented - IM_ASSERT(g.IO.Font); // Font not created - IM_ASSERT(g.IO.Font->IsLoaded()); // Font not loaded + IM_ASSERT(g.IO.RenderDrawListsFn != NULL); // Must be implemented + IM_ASSERT(g.IO.FontAtlas->IsBuilt()); // Font not created. Did you call io.FontAtlas->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? + IM_ASSERT(g.IO.FontAtlas->Fonts[0]->IsLoaded()); // Font not created. Did you call io.FontAtlas->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? if (!g.Initialized) { @@ -1540,7 +1538,7 @@ void ImGui::NewFrame() g.Initialized = true; } - SetFont(g.IO.Font); + SetFont(g.IO.FontAtlas->Fonts[0]); g.Time += g.IO.DeltaTime; g.FrameCount += 1; @@ -1694,15 +1692,7 @@ void ImGui::Shutdown() fclose(g.LogFile); g.LogFile = NULL; } - if (g.IO.Font) - { - if (g.IO.Font != &GDefaultStaticFont) - { - g.IO.Font->~ImFont(); - ImGui::MemFree(g.IO.Font); - } - g.IO.Font = NULL; - } + g.IO.FontAtlas->Clear(); if (g.PrivateClipboard) { @@ -2372,7 +2362,7 @@ bool ImGui::Begin(const char* name, bool* p_opened, ImVec2 size, float fill_alph if (first_begin_of_the_frame) { window->DrawList->Clear(); - window->DrawList->PushTextureID(g.Font->TexID); + window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); window->Visible = true; // New windows appears in front @@ -2685,7 +2675,7 @@ bool ImGui::Begin(const char* name, bool* p_opened, ImVec2 size, float fill_alph else { // Short path when we do multiple Begin in the same frame. - window->DrawList->PushTextureID(g.Font->TexID); + window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); // Outer clipping rectangle if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ComboBox)) @@ -2798,10 +2788,11 @@ static void SetFont(ImFont* font) { ImGuiState& g = GImGui; + IM_ASSERT(font && font->IsLoaded()); IM_ASSERT(font->Scale > 0.0f); g.Font = font; g.FontSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale; - g.FontTexUvWhitePixel = g.Font->TexUvWhitePixel; + g.FontTexUvWhitePixel = g.Font->ContainerAtlas->TexUvWhitePixel; g.Font->FallbackGlyph = NULL; g.Font->FallbackGlyph = g.Font->FindGlyph(g.Font->FallbackChar); } @@ -2810,9 +2801,12 @@ void ImGui::PushFont(ImFont* font) { ImGuiState& g = GImGui; + if (!font) + font = g.IO.FontAtlas->Fonts[0]; + SetFont(font); g.FontStack.push_back(font); - g.CurrentWindow->DrawList->PushTextureID(font->TexID); + g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); } void ImGui::PopFont() @@ -2821,7 +2815,7 @@ void ImGui::PopFont() g.CurrentWindow->DrawList->PopTextureID(); g.FontStack.pop_back(); - SetFont(g.FontStack.empty() ? g.IO.Font : g.FontStack.back()); + SetFont(g.FontStack.empty() ? g.IO.FontAtlas->Fonts[0] : g.FontStack.back()); } void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) @@ -6216,7 +6210,7 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 if (text_end == NULL) text_end = text_begin + strlen(text_begin); - IM_ASSERT(font->TexID == texture_id_stack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. + IM_ASSERT(font->ContainerAtlas->TexID == texture_id_stack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. // reserve vertices for worse case const unsigned int char_count = (unsigned int)(text_end - text_begin); @@ -6255,29 +6249,80 @@ void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const Im } //----------------------------------------------------------------------------- -// ImBitmapFont +// ImFontAtlias //----------------------------------------------------------------------------- -ImFont::ImFont() +struct ImFontAtlas::ImFontAtlasData { - Scale = 1.0f; - FallbackChar = (ImWchar)'?'; + // Input + ImFont* OutFont; // Load into this font + void* TTFData; // TTF data, we own the memory + size_t TTFDataSize; // TTF data size, in bytes + float SizePixels; // Desired output size, in pixels + const ImWchar* GlyphRanges; // List of Unicode range (2 value per range, values are inclusive, zero-terminated list) + int FontNo; // Index of font within .TTF file (0) + // Temporary Build Data + stbtt_fontinfo FontInfo; + stbrp_rect* Rects; + ImVector Ranges; +}; + +ImFontAtlas::ImFontAtlas() +{ + TexID = NULL; TexPixelsAlpha8 = NULL; TexPixelsRGBA32 = NULL; - Clear(); + TexWidth = TexHeight = 0; + TexExtraDataPos = TexUvWhitePixel = ImVec2(0, 0); } -ImFont::~ImFont() +ImFontAtlas::~ImFontAtlas() { - Clear(); + ClearInputData(); + ClearTexData(); } -void ImFont::GetTextureDataAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +void ImFontAtlas::ClearInputData() { - // Lazily load default font - if (!IsLoaded()) - LoadDefault(); + for (size_t i = 0; i < InputData.size(); i++) + { + if (InputData[i]->TTFData) + ImGui::MemFree(InputData[i]->TTFData); + ImGui::MemFree(InputData[i]); + } + InputData.clear(); +} + +void ImFontAtlas::ClearTexData() +{ + if (TexPixelsAlpha8) + ImGui::MemFree(TexPixelsAlpha8); + if (TexPixelsRGBA32) + ImGui::MemFree(TexPixelsRGBA32); + TexPixelsAlpha8 = NULL; + TexPixelsRGBA32 = NULL; +} + +void ImFontAtlas::ClearFonts() +{ + for (size_t i = 0; i < Fonts.size(); i++) + { + Fonts[i]->~ImFont(); + ImGui::MemFree(Fonts[i]); + } + Fonts.clear(); +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + // Lazily build + if (TexPixelsAlpha8 == NULL) + { + if (InputData.empty()) + AddFontDefault(); + Build(); + } *out_pixels = TexPixelsAlpha8; if (out_width) *out_width = TexWidth; @@ -6285,14 +6330,14 @@ void ImFont::GetTextureDataAlpha8(unsigned char** out_pixels, int* out_width, if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; } -void ImFont::GetTextureDataRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) { // Lazily convert to RGBA32 format // Although it is likely to be the most commonly used format, our font rendering is 8 bpp if (!TexPixelsRGBA32) { unsigned char* pixels; - GetTextureDataAlpha8(&pixels, NULL, NULL); + GetTexDataAsAlpha8(&pixels, NULL, NULL); TexPixelsRGBA32 = (unsigned int*)ImGui::MemAlloc(TexWidth * TexHeight * 4); const unsigned char* src = pixels; unsigned int* dst = TexPixelsRGBA32; @@ -6306,33 +6351,251 @@ void ImFont::GetTextureDataRGBA32(unsigned char** out_pixels, int* out_width, if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; } -void ImFont::ClearTextureData() +static void GetDefaultCompressedFontDataTTF(const void** ttf_compressed_data, unsigned int* ttf_compressed_size); +static unsigned int stb_decompress_length(unsigned char *input); +static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length); + +// Load embedded ProggyClean.ttf at size 13 +ImFont* ImFontAtlas::AddFontDefault() { - if (TexPixelsAlpha8) - ImGui::MemFree(TexPixelsAlpha8); - if (TexPixelsRGBA32) - ImGui::MemFree(TexPixelsRGBA32); - TexPixelsAlpha8 = NULL; - TexPixelsRGBA32 = NULL; + // Get compressed data + unsigned int ttf_compressed_size; + const void* ttf_compressed; + GetDefaultCompressedFontDataTTF(&ttf_compressed, &ttf_compressed_size); + + // Decompress + const size_t buf_decompressed_size = stb_decompress_length((unsigned char*)ttf_compressed); + unsigned char* buf_decompressed = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size); + stb_decompress(buf_decompressed, (unsigned char*)ttf_compressed, ttf_compressed_size); + + // Add + ImFont* font = AddFontFromMemoryTTF(buf_decompressed, buf_decompressed_size, 13.0f, ImFontAtlas::GetGlyphRangesDefault(), 0); + font->DisplayOffset.y += 1; + return font; +} + +ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImWchar* glyph_ranges, int font_no) +{ + void* data = NULL; + size_t data_size = 0; + if (!ImLoadFileToMemory(filename, "rb", (void**)&data, &data_size)) + return NULL; + + // Add + ImFont* font = AddFontFromMemoryTTF(data, data_size, size_pixels, glyph_ranges, font_no); + return font; +} + +// NB: ownership of 'data' is given to ImFontAtlas which will clear it. +ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* in_ttf_data, size_t in_ttf_data_size, float size_pixels, const ImWchar* glyph_ranges, int font_no) +{ + // Create new font + ImFont* font = (ImFont*)ImGui::MemAlloc(sizeof(ImFont)); + new (font) ImFont(); + Fonts.push_back(font); + + // Add to build list + ImFontAtlasData* data = (ImFontAtlasData*)ImGui::MemAlloc(sizeof(ImFontAtlasData)); + memset(data, 0, sizeof(ImFontAtlasData)); + data->OutFont = font; + data->TTFData = in_ttf_data; + data->TTFDataSize = in_ttf_data_size; + data->SizePixels = size_pixels; + data->GlyphRanges = glyph_ranges; + data->FontNo = 0; + InputData.push_back(data); + + // Invalidate texture + ClearTexData(); + + return font; +} + +bool ImFontAtlas::Build() +{ + IM_ASSERT(InputData.size() > 0); + + TexID = NULL; + TexWidth = TexHeight = 0; + TexExtraDataPos = TexUvWhitePixel = ImVec2(0, 0); + ClearTexData(); + + // Initialize font information early (so we can error without any cleanup) + count glyphs + int total_glyph_count = 0; + for (size_t input_i = 0; input_i < InputData.size(); input_i++) + { + ImFontAtlasData& data = *InputData[input_i]; + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)data.TTFData, data.FontNo); + IM_ASSERT(font_offset >= 0); + if (!stbtt_InitFont(&data.FontInfo, (unsigned char*)data.TTFData, font_offset)) + return false; + for (const ImWchar* in_range = InputData[input_i]->GlyphRanges; in_range[0] && in_range[1]; in_range += 2) + total_glyph_count += (in_range[1] - in_range[0]) + 1; + } + + // Start packing + TexWidth = (total_glyph_count > 3000) ? 2048 : (total_glyph_count > 1000) ? 1024 : 512; // Width doesn't actually matters. + TexHeight = 0; + const int max_tex_height = 1024*32; + stbtt_pack_context spc; + int ret = stbtt_PackBegin(&spc, NULL, TexWidth, max_tex_height, 0, 1, NULL); + IM_ASSERT(ret); + stbtt_PackSetOversampling(&spc, 1, 1); + + // Pack our extra data rectangle first, so it will be on the upper-left corner of our texture (UV will have small values). + stbrp_rect extra_rect; + extra_rect.w = 16; + extra_rect.h = 16; + stbrp_pack_rects((stbrp_context*)spc.pack_info, &extra_rect, 1); + TexExtraDataPos = ImVec2(extra_rect.x, extra_rect.y); + + // First pass: pack all glyphs (no rendering at this point, we are working with glyph sizes only) + int tex_height = extra_rect.y + extra_rect.h; + for (size_t input_i = 0; input_i < InputData.size(); input_i++) + { + ImFontAtlasData& data = *InputData[input_i]; + if (!data.GlyphRanges) + data.GlyphRanges = ImFontAtlas::GetGlyphRangesDefault(); + + // Setup ranges + int glyph_count = 0; + int glyph_ranges_count = 0; + for (const ImWchar* in_range = data.GlyphRanges; in_range[0] && in_range[1]; in_range += 2) + { + glyph_count += (in_range[1] - in_range[0]) + 1; + glyph_ranges_count++; + } + data.Ranges.resize(glyph_ranges_count); + for (size_t i = 0; i < data.Ranges.size(); i++) + { + stbtt_pack_range& range = data.Ranges[i]; + range.font_size = data.SizePixels; + const ImWchar* in_range = &data.GlyphRanges[i * 2]; + range.first_unicode_char_in_range = in_range[0]; + range.num_chars_in_range = (in_range[1] - in_range[0]) + 1; + + // Allocate characters and flag as not packed + // FIXME-OPT: Loose ranges will incur lots of allocations. Allocate all contiguous in loop above. + range.chardata_for_range = (stbtt_packedchar*)ImGui::MemAlloc(range.num_chars_in_range * sizeof(stbtt_packedchar)); + for (int j = 0; j < range.num_chars_in_range; ++j) + range.chardata_for_range[j].x0 = range.chardata_for_range[j].y0 = range.chardata_for_range[j].x1 = range.chardata_for_range[j].y1 = 0; + } + + // Pack + data.Rects = (stbrp_rect*)ImGui::MemAlloc(sizeof(stbrp_rect) * glyph_count); + IM_ASSERT(data.Rects); + const int n = stbtt_PackFontRangesGatherRects(&spc, &data.FontInfo, data.Ranges.begin(), data.Ranges.size(), data.Rects); + stbrp_pack_rects((stbrp_context*)spc.pack_info, data.Rects, n); + + // Extend texture height + for (int i = 0; i < n; i++) + if (data.Rects[i].was_packed) + tex_height = ImMax(tex_height, data.Rects[i].y + data.Rects[i].h); + } + + // Create texture + TexHeight = ImUpperPowerOfTwo(tex_height); + TexPixelsAlpha8 = (unsigned char*)ImGui::MemRealloc(TexPixelsAlpha8, TexWidth * TexHeight); + memset(TexPixelsAlpha8, 0, TexWidth * TexHeight); + spc.pixels = TexPixelsAlpha8; + spc.height = TexHeight; + + // Second pass: render characters + for (size_t input_i = 0; input_i < InputData.size(); input_i++) + { + ImFontAtlasData& data = *InputData[input_i]; + ret = stbtt_PackFontRangesRenderIntoRects(&spc, &data.FontInfo, data.Ranges.begin(), data.Ranges.size(), data.Rects); + ImGui::MemFree(data.Rects); + data.Rects = NULL; + } + + // End packing + stbtt_PackEnd(&spc); + + // Third pass: setup ImFont and glyphs for runtime + for (size_t input_i = 0; input_i < InputData.size(); input_i++) + { + ImFontAtlasData& data = *InputData[input_i]; + data.OutFont->ContainerAtlas = this; + data.OutFont->FontSize = data.SizePixels; + + const float font_scale = stbtt_ScaleForPixelHeight(&data.FontInfo, data.SizePixels); + int font_ascent, font_descent, font_line_gap; + stbtt_GetFontVMetrics(&data.FontInfo, &font_ascent, &font_descent, &font_line_gap); + + const float uv_scale_x = 1.0f / TexWidth; + const float uv_scale_y = 1.0f / TexHeight; + const int character_spacing_x = 1; + for (size_t i = 0; i < data.Ranges.size(); i++) + { + stbtt_pack_range& range = data.Ranges[i]; + for (int char_idx = 0; char_idx < range.num_chars_in_range; char_idx += 1) + { + const int codepoint = range.first_unicode_char_in_range + char_idx; + const stbtt_packedchar& pc = range.chardata_for_range[char_idx]; + if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1) + continue; + + data.OutFont->Glyphs.resize(data.OutFont->Glyphs.size() + 1); + ImFont::Glyph& glyph = data.OutFont->Glyphs.back(); + glyph.Codepoint = (ImWchar)codepoint; + glyph.Width = pc.x1 - pc.x0 + 1; + glyph.Height = pc.y1 - pc.y0 + 1; + glyph.XOffset = (signed short)(pc.xoff); + glyph.YOffset = (signed short)(pc.yoff) + (int)(font_ascent * font_scale); + glyph.XAdvance = (signed short)(pc.xadvance + character_spacing_x); // Bake spacing into XAdvance + glyph.U0 = ((float)pc.x0 - 0.5f) * uv_scale_x; + glyph.V0 = ((float)pc.y0 - 0.5f) * uv_scale_y; + glyph.U1 = ((float)pc.x0 - 0.5f + glyph.Width) * uv_scale_x; + glyph.V1 = ((float)pc.y0 - 0.5f + glyph.Height) * uv_scale_y; + } + } + + data.OutFont->BuildLookupTable(); + + // Cleanup temporary + for (size_t i = 0; i < data.Ranges.size(); i++) + ImGui::MemFree(data.Ranges[i].chardata_for_range); + data.Ranges.clear(); + } + + // Draw white pixel into texture and make UV points to it + TexPixelsAlpha8[0] = TexPixelsAlpha8[1] = TexPixelsAlpha8[TexWidth+0] = TexPixelsAlpha8[TexWidth+1] = 0xFF; + TexUvWhitePixel = ImVec2((TexExtraDataPos.x + 0.5f) / TexWidth, (TexExtraDataPos.y + 0.5f) / TexHeight); + + ClearInputData(); + + return true; +} + +//----------------------------------------------------------------------------- +// ImFont +//----------------------------------------------------------------------------- + +ImFont::ImFont() +{ + Scale = 1.0f; + FallbackChar = (ImWchar)'?'; + Clear(); +} + +ImFont::~ImFont() +{ + Clear(); } void ImFont::Clear() { FontSize = 0.0f; DisplayOffset = ImVec2(-0.5f, 0.5f); - - ClearTextureData(); - TexID = NULL; - TexWidth = TexHeight = 0; - TexExtraDataPos = TexUvWhitePixel = ImVec2(0, 0); - + ContainerAtlas = NULL; Glyphs.clear(); IndexLookup.clear(); FallbackGlyph = NULL; } // Retrieve list of range (2 int per range, values are inclusive) -const ImWchar* ImFont::GetGlyphRangesDefault() +const ImWchar* ImFontAtlas::GetGlyphRangesDefault() { static const ImWchar ranges[] = { @@ -6342,7 +6605,7 @@ const ImWchar* ImFont::GetGlyphRangesDefault() return &ranges[0]; } -const ImWchar* ImFont::GetGlyphRangesChinese() +const ImWchar* ImFontAtlas::GetGlyphRangesChinese() { static const ImWchar ranges[] = { @@ -6355,7 +6618,7 @@ const ImWchar* ImFont::GetGlyphRangesChinese() return &ranges[0]; } -const ImWchar* ImFont::GetGlyphRangesJapanese() +const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() { // Store the 1946 ideograms code points as successive offsets from the initial unicode codepoint 0x4E00. Each offset has an implicit +1. // This encoding helps us reduce the source code size. @@ -6415,167 +6678,6 @@ const ImWchar* ImFont::GetGlyphRangesJapanese() return &ranges[0]; } -static void GetDefaultCompressedFontDataTTF(const void** ttf_compressed_data, unsigned int* ttf_compressed_size); -static unsigned int stb_decompress_length(unsigned char *input); -static unsigned int stb_decompress(unsigned char *output, unsigned char *i, unsigned int length); - -// Load embedded ProggyClean.ttf at size 13 -bool ImFont::LoadDefault() -{ - // Get compressed data - unsigned int ttf_compressed_size; - const void* ttf_compressed; - GetDefaultCompressedFontDataTTF(&ttf_compressed, &ttf_compressed_size); - - // Decompress - const size_t buf_decompressed_size = stb_decompress_length((unsigned char*)ttf_compressed); - unsigned char* buf_decompressed = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size); - stb_decompress(buf_decompressed, (unsigned char*)ttf_compressed, ttf_compressed_size); - - // Load TTF - const bool ret = LoadFromMemoryTTF((void *)buf_decompressed, buf_decompressed_size, 13.0f, ImFont::GetGlyphRangesDefault(), 0); - ImGui::MemFree(buf_decompressed); - DisplayOffset.y += 1; - return ret; -} - -bool ImFont::LoadFromFileTTF(const char* filename, float size_pixels, const ImWchar* glyph_ranges, int font_no) -{ - void* data = NULL; - size_t data_size = 0; - if (!ImLoadFileToMemory(filename, "rb", (void**)&data, &data_size)) - return false; - const bool ret = LoadFromMemoryTTF(data, data_size, size_pixels, glyph_ranges, font_no); - ImGui::MemFree(data); - return ret; -} - -bool ImFont::LoadFromMemoryTTF(const void* data, size_t data_size, float size_pixels, const ImWchar* glyph_ranges, int font_no) -{ - Clear(); - if (!glyph_ranges) - glyph_ranges = GetGlyphRangesDefault(); - - // Initialize font information - stbtt_fontinfo ttf_info; - const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)data, font_no); - IM_ASSERT(font_offset >= 0); - if (!stbtt_InitFont(&ttf_info, (unsigned char*)data, font_offset)) - return false; - - // Setup ranges - int glyph_count = 0; - int glyph_ranges_count = 0; - for (const ImWchar* in_range = glyph_ranges; in_range[0] && in_range[1]; in_range += 2) - { - glyph_count += (in_range[1] - in_range[0]) + 1; - glyph_ranges_count++; - } - - ImVector ranges; - ranges.resize(glyph_ranges_count); - for (size_t i = 0; i < ranges.size(); i++) - { - stbtt_pack_range& range = ranges[i]; - range.font_size = size_pixels; - const ImWchar* in_range = &glyph_ranges[i * 2]; - range.first_unicode_char_in_range = in_range[0]; - range.num_chars_in_range = (in_range[1] - in_range[0]) + 1; - range.chardata_for_range = (stbtt_packedchar*)ImGui::MemAlloc(range.num_chars_in_range * sizeof(stbtt_packedchar)); - } - - { - TexWidth = (glyph_count > 1000) ? 1024 : 512; // Width doesn't really matters. - TexHeight = 0; - const int max_tex_height = 1024*32; - stbtt_pack_context spc; - int ret = stbtt_PackBegin(&spc, NULL, TexWidth, max_tex_height, 0, 1, NULL); - IM_ASSERT(ret); - stbtt_PackSetOversampling(&spc, 1, 1); - - // Flag all characters as not packed - for (size_t i = 0; i < ranges.size(); ++i) - for (int j = 0; j < ranges[i].num_chars_in_range; ++j) - ranges[i].chardata_for_range[j].x0 = ranges[i].chardata_for_range[j].y0 = ranges[i].chardata_for_range[j].x1 = ranges[i].chardata_for_range[j].y1 = 0; - - // Pack our extra data rectangle first, so it will be on the upper-left corner of our texture (UV will have small values). - stbrp_rect extra_rect; - extra_rect.w = 16; - extra_rect.h = 16; - stbrp_pack_rects((stbrp_context*)spc.pack_info, &extra_rect, 1); - TexExtraDataPos = ImVec2(extra_rect.x, extra_rect.y); - - // Pack - stbrp_rect* rects = (stbrp_rect*)ImGui::MemAlloc(sizeof(*rects) * glyph_count); - IM_ASSERT(rects); - const int n = stbtt_PackFontRangesGatherRects(&spc, &ttf_info, ranges.begin(), ranges.size(), rects); - stbrp_pack_rects((stbrp_context*)spc.pack_info, rects, n); - - // Create texture - int tex_h = 0; - for (int i = 0; i < n; i++) - if (rects[i].was_packed) - tex_h = ImMax(tex_h, rects[i].y + rects[i].h); - TexHeight = ImUpperPowerOfTwo(tex_h); - TexPixelsAlpha8 = (unsigned char*)ImGui::MemRealloc(TexPixelsAlpha8, TexWidth * TexHeight); - memset(TexPixelsAlpha8, 0, TexWidth * TexHeight); - - // Render characters - spc.pixels = TexPixelsAlpha8; - spc.height = TexHeight; - ret = stbtt_PackFontRangesRenderIntoRects(&spc, &ttf_info, ranges.begin(), ranges.size(), rects); - stbtt_PackEnd(&spc); - ImGui::MemFree(rects); - } - - // Setup glyphs for runtime - FontSize = size_pixels; - - const float font_scale = stbtt_ScaleForPixelHeight(&ttf_info, size_pixels); - int font_ascent, font_descent, font_line_gap; - stbtt_GetFontVMetrics(&ttf_info, &font_ascent, &font_descent, &font_line_gap); - - const float uv_scale_x = 1.0f / TexWidth; - const float uv_scale_y = 1.0f / TexHeight; - const int character_spacing_x = 1; - for (size_t i = 0; i < ranges.size(); i++) - { - stbtt_pack_range& range = ranges[i]; - for (int char_idx = 0; char_idx < range.num_chars_in_range; char_idx += 1) - { - const int codepoint = range.first_unicode_char_in_range + char_idx; - const stbtt_packedchar& pc = range.chardata_for_range[char_idx]; - if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1) - continue; - - Glyphs.resize(Glyphs.size() + 1); - ImFont::Glyph& glyph = Glyphs.back(); - glyph.Codepoint = (ImWchar)codepoint; - glyph.Width = pc.x1 - pc.x0 + 1; - glyph.Height = pc.y1 - pc.y0 + 1; - glyph.XOffset = (signed short)(pc.xoff); - glyph.YOffset = (signed short)(pc.yoff) + (int)(font_ascent * font_scale); - glyph.XAdvance = (signed short)(pc.xadvance + character_spacing_x); // Bake spacing into XAdvance - glyph.U0 = ((float)pc.x0 - 0.5f) * uv_scale_x; - glyph.V0 = ((float)pc.y0 - 0.5f) * uv_scale_y; - glyph.U1 = ((float)pc.x0 - 0.5f + glyph.Width) * uv_scale_x; - glyph.V1 = ((float)pc.y0 - 0.5f + glyph.Height) * uv_scale_y; - } - } - - BuildLookupTable(); - - // Cleanup temporary - for (size_t i = 0; i < ranges.size(); i++) - ImGui::MemFree(ranges[i].chardata_for_range); - - // Draw white pixel and make UV points to it - TexPixelsAlpha8[0] = TexPixelsAlpha8[1] = TexPixelsAlpha8[TexWidth+0] = TexPixelsAlpha8[TexWidth+1] = 0xFF; - TexUvWhitePixel = ImVec2((TexExtraDataPos.x + 0.5f) / TexWidth, (TexExtraDataPos.y + 0.5f) / TexHeight); - - return true; -} - void ImFont::BuildLookupTable() { int max_codepoint = 0; @@ -7315,20 +7417,6 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::TreePop(); } - // Font scaling options - // Note that those are not actually part of the style. - if (ImGui::TreeNode("Font")) - { - static float window_scale = 1.0f; - ImFont* font = ImGui::GetIO().Font; - ImGui::Text("Base Font Size: %.2f", font->FontSize); - ImGui::SliderFloat("window scale", &window_scale, 0.3f, 2.0f, "%.1f"); // scale only this window - ImGui::SliderFloat("font scale", &font->Scale, 0.3f, 2.0f, "%.1f"); // scale only this font - ImGui::SliderFloat("global scale", &ImGui::GetIO().FontGlobalScale, 0.3f, 2.0f, "%.1f"); // scale everything - ImGui::SetWindowFontScale(window_scale); - ImGui::TreePop(); - } - ImGui::PopItemWidth(); } @@ -7375,12 +7463,38 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::Checkbox("no scrollbar", &no_scrollbar); ImGui::SliderFloat("fill alpha", &fill_alpha, 0.0f, 1.0f); - if (ImGui::TreeNode("Style Editor")) + if (ImGui::TreeNode("Style")) { ImGui::ShowStyleEditor(); ImGui::TreePop(); } + if (ImGui::TreeNode("Fonts")) + { + ImGui::TextWrapped("Tip: Load fonts with GetIO().FontAtlas->AddFontFromFileTTF()."); + for (size_t i = 0; i < ImGui::GetIO().FontAtlas->Fonts.size(); i++) + { + ImFont* font = ImGui::GetIO().FontAtlas->Fonts[i]; + ImGui::BulletText("Font %d: %.2f pixels, %d glyphs", i, font->FontSize, font->Glyphs.size()); + ImGui::TreePush((void*)i); + ImGui::PushFont(font); + ImGui::Text("The quick brown fox jumps over the lazy dog"); + ImGui::PopFont(); + if (i > 0 && ImGui::Button("Set as default")) + { + ImGui::GetIO().FontAtlas->Fonts[i] = ImGui::GetIO().FontAtlas->Fonts[0]; + ImGui::GetIO().FontAtlas->Fonts[0] = font; + } + ImGui::SliderFloat("font scale", &font->Scale, 0.3f, 2.0f, "%.1f"); // scale only this font + ImGui::TreePop(); + } + static float window_scale = 1.0f; + ImGui::SliderFloat("this window scale", &window_scale, 0.3f, 2.0f, "%.1f"); // scale only this window + ImGui::SliderFloat("global scale", &ImGui::GetIO().FontGlobalScale, 0.3f, 2.0f, "%.1f"); // scale everything + ImGui::SetWindowFontScale(window_scale); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Logging")) { ImGui::LogButtons(); @@ -7475,9 +7589,10 @@ void ImGui::ShowTestWindow(bool* opened) { ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. Hover the texture for a zoomed view!"); ImVec2 tex_screen_pos = ImGui::GetCursorScreenPos(); - float tex_w = (float)ImGui::GetIO().Font->TexWidth; - float tex_h = (float)ImGui::GetIO().Font->TexHeight; - ImGui::Image(ImGui::GetIO().Font->TexID, ImVec2(tex_w, tex_h), ImVec2(0,0), ImVec2(1,1), 0xFFFFFFFF, 0x999999FF); + float tex_w = (float)ImGui::GetIO().FontAtlas->TexWidth; + float tex_h = (float)ImGui::GetIO().FontAtlas->TexHeight; + ImTextureID tex_id = ImGui::GetIO().FontAtlas->TexID; + ImGui::Image(tex_id, ImVec2(tex_w, tex_h), ImVec2(0,0), ImVec2(1,1), 0xFFFFFFFF, 0x999999FF); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); @@ -7488,7 +7603,7 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::Text("Max: (%.2f, %.2f)", focus_x + focus_sz, focus_y + focus_sz); ImVec2 uv0 = ImVec2((focus_x) / tex_w, (focus_y) / tex_h); ImVec2 uv1 = ImVec2((focus_x + focus_sz) / tex_w, (focus_y + focus_sz) / tex_h); - ImGui::Image(ImGui::GetIO().Font->TexID, ImVec2(128,128), uv0, uv1, 0xFFFFFFFF, 0x999999FF); + ImGui::Image(tex_id, ImVec2(128,128), uv0, uv1, 0xFFFFFFFF, 0x999999FF); ImGui::EndTooltip(); } ImGui::TreePop(); diff --git a/imgui.h b/imgui.h index 4353c5d2f..4eb775858 100644 --- a/imgui.h +++ b/imgui.h @@ -8,6 +8,7 @@ struct ImDrawList; struct ImFont; +struct ImFontAtlas; struct ImGuiAabb; struct ImGuiIO; struct ImGuiStorage; @@ -176,7 +177,7 @@ namespace ImGui IMGUI_API ImGuiStorage* GetStateStorage(); // Parameters stacks (shared) - IMGUI_API void PushFont(ImFont* font); + IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font IMGUI_API void PopFont(); IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); IMGUI_API void PopStyleColor(int count = 1); @@ -483,19 +484,19 @@ struct ImGuiIO // Settings (fill once) // Default value: //------------------------------------------------------------------ - ImVec2 DisplaySize; // // Display size, in pixels. For clamping windows positions. - float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. - float IniSavingRate; // = 5.0f // Maximum time between saving .ini file, in seconds. - const char* IniFilename; // = "imgui.ini" // Path to .ini file. NULL to disable .ini saving. - const char* LogFilename; // = "imgui_log.txt" // Path to .log file (default parameter to ImGui::LogToFile when no file is specified). - float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. - float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. - int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array - ImFont* Font; // // Font (also see 'Settings' fields inside ImFont structure for details) - float FontGlobalScale; // = 1.0f // Global scale all fonts - bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. + ImVec2 DisplaySize; // // Display size, in pixels. For clamping windows positions. + float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. + float IniSavingRate; // = 5.0f // Maximum time between saving .ini file, in seconds. + const char* IniFilename; // = "imgui.ini" // Path to .ini file. NULL to disable .ini saving. + const char* LogFilename; // = "imgui_log.txt" // Path to .log file (default parameter to ImGui::LogToFile when no file is specified). + float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. + float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. + int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array + void* UserData; // = NULL // Store your own data for retrieval by callbacks. - void* UserData; // = NULL // Store your own data for retrieval by callbacks. + ImFontAtlas* FontAtlas; // // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array. + float FontGlobalScale; // = 1.0f // Global scale all fonts + bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. //------------------------------------------------------------------ // User Functions @@ -553,7 +554,7 @@ struct ImGuiIO float MouseDownTime[5]; float KeysDownTime[512]; - IMGUI_API ImGuiIO(); + IMGUI_API ImGuiIO(); }; //----------------------------------------------------------------------------- @@ -746,6 +747,57 @@ struct ImDrawList IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv0, const ImVec2& uv1, ImU32 col); }; +// Load and rasterize multiple TTF fonts into a same texture. +// We also add custom graphic data into the texture that serves for ImGui. +// Sharing a texture for multiple fonts allows us to reduce the number of draw calls during rendering. +// The simple use case, if you don't intent to load custom or multiple fonts, is: +// 1. GetTexDataAsRGBA32() or GetTexDataAsAlpha8() // to obtain pixels +// 2. +// 3. SetTexID(my_engine_id); // use the pointer/id to your texture in your engine format +// 4. ClearPixelsData() // to save memory +struct ImFontAtlas +{ + // Methods + IMGUI_API ImFontAtlas(); + IMGUI_API ~ImFontAtlas(); + IMGUI_API ImFont* AddFontDefault(); + IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); + IMGUI_API ImFont* AddFontFromMemoryTTF(void* in_ttf_data, size_t in_ttf_data_size, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); // Pass ownership of 'in_ttf_data' memory. + IMGUI_API bool Build(); + IMGUI_API void ClearInputData(); + IMGUI_API void ClearFonts(); + IMGUI_API void ClearTexData(); // Saves RAM once the texture has been copied to graphics memory. + IMGUI_API void Clear() { ClearInputData(); ClearTexData(); ClearFonts(); } + IMGUI_API bool IsBuilt() const { return !Fonts.empty(); } + + // Methods: Retrieve texture data + // User is in charge of copying the pixels into graphics memory, then call SetTextureUserID() + // After loading the texture into your graphic system, store your texture handle in 'TexID' (ignore if you aren't using multiple fonts nor images) + // RGBA32 format is provided for convenience and high compatibility, but note that all RGB pixels are white, so 75% of the memory is wasted. + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void SetTexID(void* id) { TexID = id; } + + // Static helper: Retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) + static IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + static IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + static IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Japanese + full set of about 21000 CJK Unified Ideographs + + // Members + // Access texture data via GetTextureData*() calls which will setup a default font for you. + void* TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It ia passed back to you during rendering. + unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight + unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + int TexWidth; + int TexHeight; + ImVec2 TexExtraDataPos; // Position of our rectangle where we draw non-font graphics + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel (part of the TexExtraData block) + ImVector Fonts; + + struct ImFontAtlasData; + ImVector InputData; // Internal data +}; + // TTF font loading and rendering // - ImGui automatically loads a default embedded font for you // - Call GetTextureData() to retrieve pixels data so you can upload the texture to your graphics system. @@ -753,55 +805,13 @@ struct ImDrawList // (NB: kerning isn't supported. At the moment some ImGui code does per-character CalcTextSize calls, need something more state-ful) struct ImFont { - // Settings + // Members: Settings float FontSize; // // Height of characters, set during loading (don't change after loading) float Scale; // = 1.0f // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale() ImVec2 DisplayOffset; // = (0.0f,0.0f) // Offset font rendering by xx pixels ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. - ImTextureID TexID; // = 0 // After loading texture, store your texture handle here (ignore if you aren't using multiple fonts/textures) - - // Retrieve texture data - // User is in charge of copying the pixels into graphics memory, then set 'TexID'. - // RGBA32 format is provided for convenience and high compatibility, but note that all RGB pixels are white. - // If you intend to use large font it may be pref - // NB: the data is invalidated as soon as you call a Load* function. - IMGUI_API void GetTextureDataRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - IMGUI_API void GetTextureDataAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel - IMGUI_API void ClearTextureData(); // Save RAM once the texture has been copied to graphics memory. - - // Methods - IMGUI_API ImFont(); - IMGUI_API ~ImFont(); - IMGUI_API bool LoadDefault(); - IMGUI_API bool LoadFromFileTTF(const char* filename, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); - IMGUI_API bool LoadFromMemoryTTF(const void* data, size_t data_size, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); - IMGUI_API void Clear(); - IMGUI_API void BuildLookupTable(); - struct Glyph; - IMGUI_API const Glyph* FindGlyph(unsigned short c) const; - IMGUI_API bool IsLoaded() const { return !Glyphs.empty(); } - - // Retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) - static IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin - static IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs - static IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Japanese + full set of about 21000 CJK Unified Ideographs - - // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. - // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. - IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 - IMGUI_API ImVec2 CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL) const; // wchar - IMGUI_API void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawVert*& out_vertices, float wrap_width = 0.0f) const; - IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; - - // Texture data - // Access via GetTextureData() which will load the font if not loaded - unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight - unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 - int TexWidth; - int TexHeight; - ImVec2 TexExtraDataPos; // Position of our rectangle where we draw non-font graphics - ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel (part of the TexExtraData block) + // Members: Runtime data struct Glyph { ImWchar Codepoint; @@ -810,11 +820,29 @@ struct ImFont signed short XOffset, YOffset; float U0, V0, U1, V1; // Texture coordinates }; - - // Runtime data + ImFontAtlas* ContainerAtlas; // What we has been loaded into ImVector Glyphs; ImVector IndexLookup; const Glyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) + + // Methods + IMGUI_API ImFont(); + IMGUI_API ~ImFont(); + IMGUI_API void Clear(); + IMGUI_API void BuildLookupTable(); + IMGUI_API const Glyph* FindGlyph(unsigned short c) const; + + IMGUI_API bool IsLoaded() const { return ContainerAtlas != NULL; } + IMGUI_API ImTextureID GetTexID() const { IM_ASSERT(ContainerAtlas != NULL); return ContainerAtlas->TexID; } + IMGUI_API int GetTexWidth() const { IM_ASSERT(ContainerAtlas != NULL); return ContainerAtlas->TexWidth; } + IMGUI_API int GetTexHeight() const { IM_ASSERT(ContainerAtlas != NULL); return ContainerAtlas->TexHeight; } + + // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. + // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 + IMGUI_API ImVec2 CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL) const; // wchar + IMGUI_API void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawVert*& out_vertices, float wrap_width = 0.0f) const; + IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; }; //---- Include imgui_user.h at the end of imgui.h From 5ad9a2f119595c615de7794a25135bc89359d90f Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 22:38:10 +0000 Subject: [PATCH 29/41] Comments on new API --- imgui.cpp | 82 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 4488751cd..885032e4c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -64,7 +64,7 @@ ================ - your code creates the UI, if your code doesn't run the UI is gone! == dynamic UI, no construction step, less data retention on your side, no state duplication, less sync, less errors. - - see ImGui::ShowTestWindow() for user-side sample code + - call and read ImGui::ShowTestWindow() for user-side sample code - see examples/ folder for standalone sample applications. - customization: use the style editor to tweak the look of the interface (e.g. if you want a more compact UI or a different color scheme), and report values in your code. @@ -90,31 +90,35 @@ // Load texture unsigned char* pixels; int width, height; - io.Font->GetTextureDataAlpha8(&pixels, &width, &height); // or use GetTextureDataRGBA32() - // TODO: copy texture to graphics memory. Store texture identifier for your engine in io.Font->TexID + io.FontAtlas->GetTexDataAsRGBA32(pixels, &width, &height); + // TODO: copy texture to graphics memory. + // TODO: store your texture pointer/identifier in 'io.FontAtlas->TexID' // Application main loop while (true) { - // 1/ get low-level input + // 1) get low-level input // e.g. on Win32, GetKeyboardState(), or poll your events, etc. - // 2/ TODO: Fill all 'Input' fields of io structure and call NewFrame + // 2) TODO: fill all fields of IO structure and call NewFrame ImGuiIO& io = ImGui::GetIO(); io.MousePos = ... io.KeysDown[i] = ... ImGui::NewFrame(); - // 3/ most of your application code here - you can use any of ImGui::* functions between NewFrame() and Render() calls + // 3) most of your application code here - you can use any of ImGui::* functions at any point in the frame + ImGui::Begin("My window"); + ImGui::Text("Hello, world."); + ImGui::End(); GameUpdate(); GameRender(); - // 4/ render & swap video buffers + // 4) render & swap video buffers ImGui::Render(); // swap video buffer, etc. } - - after calling ImGui::NewFrame() you can read back 'ImGui::GetIO().WantCaptureMouse' and 'ImGui::GetIO().WantCaptureKeyboard' to tell + - after calling ImGui::NewFrame() you can read back 'io.WantCaptureMouse' and 'io.WantCaptureKeyboard' to tell if ImGui wants to use your inputs. so typically can hide the mouse inputs from the rest of your application if ImGui is using it. @@ -124,21 +128,33 @@ Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix. Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. - - 2015/01/11 (1.30) big font/image API change. now loads TTF file. allow for multiple fonts. no need for a PNG loader. - removed GetDefaultFontData(). uses io.Font->GetTextureData*() API to retrieve uncompressed pixels. - added texture identifier in ImDrawCmd passed to your render function. - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) - - 2014/12/10 (1.18) removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) - - 2014/11/28 (1.17) moved IO.Font*** options to inside the IO.Font-> structure. - - 2014/11/26 (1.17) reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility - - 2014/11/07 (1.15) renamed IsHovered() to IsItemHovered() - - 2014/10/02 (1.14) renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly) - - 2014/09/25 (1.13) removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity) - - 2014/09/24 (1.12) renamed SetFontScale() to SetWindowFontScale() - - 2014/09/24 (1.12) moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn - - 2014/08/30 (1.09) removed IO.FontHeight (now computed automatically) - - 2014/08/30 (1.09) moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite - - 2014/08/28 (1.09) changed the behavior of IO.PixelCenterOffset following various rendering fixes + - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader. + (1.30) - removed GetDefaultFontData(). uses io.FontAtlas->GetTextureData*() API to retrieve uncompressed pixels. + this sequence: + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + // + became: + unsigned char* pixels; + int width, height; + io.FontAtlas->GetTexDataAsRGBA32(&pixels, &width, &height); + // + io.FontAtlas->TexID = (your_texture_identifier); + but we now have much more flexibility to load multiple TTF fonts and manage the texture buffer with the FontAtlas. + (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.FontAtlas->TexID. + (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) + - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) + - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure. + - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility + - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered() + - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly) + - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity) + - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale() + - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn + - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically) + - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite + - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes TROUBLESHOOTING & FREQUENTLY ASKED QUESTIONS @@ -165,12 +181,24 @@ e.g. "##Foobar" display an empty label and uses "##Foobar" as ID - read articles about the imgui principles (see web links) to understand the requirement and use of ID. - If you want to use a different font than the default embedded copy of ProggyClean.ttf (size 13), before calling io.Font->GetTextureData(): + If you want to load a different font than the default (ProggyClean.ttf, size 13) - ImGuiIO& io = ImGui::GetIO(); - io.Font->LoadFromFileTTF("myfontfile.ttf", size_pixels); + io.FontAtlas.AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + io.FontAtlas.GetTexDataAs****() + + If you want to load multiple fonts, use the FontAtlas to pack them into a single texture! + + ImFont* font0 = io.FontAtlas.AddFontDefault(); + ImFont* font1 = io.FontAtlas.AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + io.FontAtlas.GetTexDataAs****() + + If you want to display Chinese, Japanese, Korean characters, pass custom Unicode ranges when loading a font: + + io.FontAtlas.AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, ImFontAtlas::GetGlyphRangesJapanese()); // Load Japanese characters + io.FontAtlas.GetTexDataAs****() If you want to input Japanese/Chinese/Korean in the text input widget: + - when loading the font, pass a range that contains the characters you need, e.g.: ImFont::GetGlyphRangesJapanese() - to have the Microsoft IME cursor appears at the right location in the screen, setup a handler for the io.ImeSetInputScreenPosFn function: @@ -196,7 +224,7 @@ - tip: the construct 'IMGUI_ONCE_UPON_A_FRAME { ... }' will evaluate to a block of code only once a frame. You can use it to quickly add custom UI in the middle of a deep nested inner loop in your code. - tip: you can call Render() multiple times (e.g for VR renders), up to you to communicate the extra state to your RenderDrawListFn function. - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug" - - tip: read the ShowTestWindow() code for more example of how to use ImGui! + - tip: call and read the ShowTestWindow() code for more example of how to use ImGui! ISSUES & TODO-LIST From e666920784e81c0878e0228bb9264560c702e282 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 22:53:54 +0000 Subject: [PATCH 30/41] ImFont: small optimization to our (incorrect) handling of TAB TAB is still handled as 4-spaces width (which is incorrect) But CalcTextSize is simplified. --- imgui.cpp | 57 +++++++++++++++++++------------------------------------ 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 885032e4c..eba2ab18f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6718,6 +6718,18 @@ void ImFont::BuildLookupTable() IndexLookup[i] = -1; for (size_t i = 0; i < Glyphs.size(); i++) IndexLookup[(int)Glyphs[i].Codepoint] = (int)i; + + // Create a glyph to handle TAB + // FIXME: Needs proper TAB handling but it needs to be contextualized (can arbitrary say that each string starts at "column 0" + if (const ImFont::Glyph* space_glyph = FindGlyph((unsigned short)' ')) + { + Glyphs.resize(Glyphs.size() + 1); + ImFont::Glyph& tab_glyph = Glyphs.back(); + tab_glyph = *space_glyph; + tab_glyph.Codepoint = '\t'; + tab_glyph.XAdvance *= 4; + IndexLookup[(int)tab_glyph.Codepoint] = (int)(Glyphs.size()-1); + } } const ImFont::Glyph* ImFont::FindGlyph(unsigned short c) const @@ -6930,16 +6942,8 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c } float char_width = 0.0f; - if (c == '\t') - { - if (const Glyph* glyph = FindGlyph((unsigned short)' ')) - char_width = (glyph->XAdvance * 4) * scale; - } - else - { - if (const Glyph* glyph = FindGlyph((unsigned short)c)) - char_width = glyph->XAdvance * scale; - } + if (const Glyph* glyph = FindGlyph((unsigned short)c)) + char_width = glyph->XAdvance * scale; if (c == ' ' || c == '\t') { @@ -7043,16 +7047,8 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons } float char_width = 0.0f; - if (c == '\t') - { - // FIXME: Better TAB handling - if (const Glyph* glyph = FindGlyph((unsigned short)' ')) - char_width = (glyph->XAdvance * 4) * scale; - } - else if (const Glyph* glyph = FindGlyph((unsigned short)c)) - { + if (const Glyph* glyph = FindGlyph((unsigned short)c)) char_width = glyph->XAdvance * scale; - } if (line_width + char_width >= max_width) break; @@ -7103,17 +7099,8 @@ ImVec2 ImFont::CalcTextSizeW(float size, float max_width, const ImWchar* text_be } float char_width = 0.0f; - if (c == '\t') - { - // FIXME: Better TAB handling - if (const Glyph* glyph = FindGlyph((unsigned short)' ')) - char_width = (glyph->XAdvance * 4) * scale; - } - else - { - if (const Glyph* glyph = FindGlyph((unsigned short)c)) - char_width = glyph->XAdvance * scale; - } + if (const Glyph* glyph = FindGlyph((unsigned short)c)) + char_width = glyph->XAdvance * scale; if (line_width + char_width >= max_width) break; @@ -7200,16 +7187,10 @@ void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_re } float char_width = 0.0f; - if (c == '\t') - { - // FIXME: Better TAB handling - if (const Glyph* glyph = FindGlyph((unsigned short)' ')) - char_width += (glyph->XAdvance * 4) * scale; - } - else if (const Glyph* glyph = FindGlyph((unsigned short)c)) + if (const Glyph* glyph = FindGlyph((unsigned short)c)) { char_width = glyph->XAdvance * scale; - if (c != ' ') + if (c != ' ' && c != '\t') { // Clipping on Y is more likely const float y1 = (float)(y + glyph->YOffset * scale); From 37dcf58e2f3898ab58f75f6e18a447c72bad6877 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 23:13:54 +0000 Subject: [PATCH 31/41] Fixed clang warnings --- imgui.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 7db7fdfa9..014781aea 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -329,7 +329,14 @@ #define STBRP_STATIC #define STB_RECT_PACK_IMPLEMENTATION +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif #include "stb_rect_pack.h" +#ifdef __clang__ +#pragma clang diagnostic pop +#endif #define STB_TRUETYPE_IMPLEMENTATION #define STBTT_malloc(x,u) ((void)(u), ImGui::MemAlloc(x)) @@ -5939,7 +5946,6 @@ void ImGui::Color(const char* prefix, unsigned int v) //----------------------------------------------------------------------------- static ImVec4 GNullClipRect(-9999.0f,-9999.0f, +9999.0f, +9999.0f); -static ImTextureID GNullTextureID = NULL; void ImDrawList::Clear() { From 1f8d209202e6eb797f75f8d58ee204698c03e17f Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 17 Jan 2015 23:21:06 +0000 Subject: [PATCH 32/41] Fixed more clang warnings + AddFontFromMemoryTTF() not honoring font_no parameter --- imgui.cpp | 28 ++++++++++++++++------------ imgui.h | 4 ++-- stb_rect_pack.h | 5 +++-- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 014781aea..b3df48192 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -321,22 +321,22 @@ #pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. #pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. #pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion chanjges signedness // #endif //------------------------------------------------------------------------- // STB libraries implementation //------------------------------------------------------------------------- -#define STBRP_STATIC -#define STB_RECT_PACK_IMPLEMENTATION #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wmissing-prototypes" #endif + +#define STBRP_STATIC +#define STB_RECT_PACK_IMPLEMENTATION #include "stb_rect_pack.h" -#ifdef __clang__ -#pragma clang diagnostic pop -#endif #define STB_TRUETYPE_IMPLEMENTATION #define STBTT_malloc(x,u) ((void)(u), ImGui::MemAlloc(x)) @@ -348,6 +348,10 @@ struct ImGuiTextEditState; #define STB_TEXTEDIT_CHARTYPE ImWchar #include "stb_textedit.h" +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + //------------------------------------------------------------------------- // Forward Declarations //------------------------------------------------------------------------- @@ -6376,11 +6380,11 @@ void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_wid { unsigned char* pixels; GetTexDataAsAlpha8(&pixels, NULL, NULL); - TexPixelsRGBA32 = (unsigned int*)ImGui::MemAlloc(TexWidth * TexHeight * 4); + TexPixelsRGBA32 = (unsigned int*)ImGui::MemAlloc((size_t)(TexWidth * TexHeight * 4)); const unsigned char* src = pixels; unsigned int* dst = TexPixelsRGBA32; for (int n = TexWidth * TexHeight; n > 0; n--) - *dst++ = ((*src++) << 24) | 0x00FFFFFF; + *dst++ = ((unsigned int)(*src++) << 24) | 0x00FFFFFF; } *out_pixels = (unsigned char*)TexPixelsRGBA32; @@ -6440,7 +6444,7 @@ ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* in_ttf_data, size_t in_ttf_data_ data->TTFDataSize = in_ttf_data_size; data->SizePixels = size_pixels; data->GlyphRanges = glyph_ranges; - data->FontNo = 0; + data->FontNo = font_no; InputData.push_back(data); // Invalidate texture @@ -6497,7 +6501,7 @@ bool ImFontAtlas::Build() // Setup ranges int glyph_count = 0; - int glyph_ranges_count = 0; + size_t glyph_ranges_count = 0; for (const ImWchar* in_range = data.GlyphRanges; in_range[0] && in_range[1]; in_range += 2) { glyph_count += (in_range[1] - in_range[0]) + 1; @@ -6577,10 +6581,10 @@ bool ImFontAtlas::Build() data.OutFont->Glyphs.resize(data.OutFont->Glyphs.size() + 1); ImFont::Glyph& glyph = data.OutFont->Glyphs.back(); glyph.Codepoint = (ImWchar)codepoint; - glyph.Width = pc.x1 - pc.x0 + 1; - glyph.Height = pc.y1 - pc.y0 + 1; + glyph.Width = (signed short)pc.x1 - pc.x0 + 1; + glyph.Height = (signed short)pc.y1 - pc.y0 + 1; glyph.XOffset = (signed short)(pc.xoff); - glyph.YOffset = (signed short)(pc.yoff) + (int)(font_ascent * font_scale); + glyph.YOffset = (signed short)(pc.yoff + (int)(font_ascent * font_scale)); glyph.XAdvance = (signed short)(pc.xadvance + character_spacing_x); // Bake spacing into XAdvance glyph.U0 = ((float)pc.x0 - 0.5f) * uv_scale_x; glyph.V0 = ((float)pc.y0 - 0.5f) * uv_scale_y; diff --git a/imgui.h b/imgui.h index 4eb775858..2b5e7e953 100644 --- a/imgui.h +++ b/imgui.h @@ -433,7 +433,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_FrameRounding, // float ImGuiStyleVar_ItemSpacing, // ImVec2 ImGuiStyleVar_ItemInnerSpacing, // ImVec2 - ImGuiStyleVar_TreeNodeSpacing, // float + ImGuiStyleVar_TreeNodeSpacing // float }; // Enumeration for ColorEditMode() @@ -452,7 +452,7 @@ enum ImGuiSetCondition_ { ImGuiSetCondition_Always = 1 << 0, // Set the variable ImGuiSetCondition_FirstUseThisSession = 1 << 1, // Only set the variable on the first call for this window (once per session) - ImGuiSetCondition_FirstUseEver = 1 << 2, // Only set the variable if the window doesn't exist in the .ini file + ImGuiSetCondition_FirstUseEver = 1 << 2 // Only set the variable if the window doesn't exist in the .ini file }; struct ImGuiStyle diff --git a/stb_rect_pack.h b/stb_rect_pack.h index dcc9d8877..eb0ef2f26 100644 --- a/stb_rect_pack.h +++ b/stb_rect_pack.h @@ -129,7 +129,7 @@ enum { STBRP_HEURISTIC_Skyline_default=0, STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, - STBRP_HEURISTIC_Skyline_BF_sortHeight, + STBRP_HEURISTIC_Skyline_BF_sortHeight }; @@ -178,7 +178,7 @@ struct stbrp_context enum { - STBRP__INIT_skyline = 1, + STBRP__INIT_skyline = 1 }; STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) @@ -248,6 +248,7 @@ STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, // find minimum y position if it starts at x1 static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) { + (void)c; stbrp_node *node = first; int x1 = x0 + width; int min_y, visited_width, waste_area; From 1916a0c78c44fe6b55c6610613d977193b16f231 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 18 Jan 2015 10:46:49 +0000 Subject: [PATCH 33/41] Renamed FontAtlas to Fonts. Further cleanup/comments. --- examples/directx11_example/main.cpp | 14 ++--- examples/directx9_example/main.cpp | 14 ++--- examples/opengl3_example/main.cpp | 12 ++--- examples/opengl_example/main.cpp | 12 +++-- imgui.cpp | 79 +++++++++++++-------------- imgui.h | 83 +++++++++++++---------------- 6 files changed, 103 insertions(+), 111 deletions(-) diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index 8d258abae..301b91f17 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -321,7 +321,7 @@ void CleanupDevice() // InitImGui if (g_pFontSampler) g_pFontSampler->Release(); - if (ID3D11ShaderResourceView* font_texture_view = (ID3D11ShaderResourceView*)ImGui::GetIO().FontAtlas->TexID) + if (ID3D11ShaderResourceView* font_texture_view = (ID3D11ShaderResourceView*)ImGui::GetIO().Fonts->TexID) font_texture_view->Release(); if (g_pVB) g_pVB->Release(); @@ -377,17 +377,17 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) return DefWindowProc(hWnd, msg, wParam, lParam); } -void LoadFontTexture() +void LoadFontsTexture() { // Load one or more font ImGuiIO& io = ImGui::GetIO(); - //ImFont* my_font = io.FontAtlas->AddFontDefault(); - //ImFont* my_font2 = io.FontAtlas->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, ImFontAtlas::GetGlyphRangesJapanese()); + //ImFont* my_font = io.Fonts->AddFontDefault(); + //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); // Build unsigned char* pixels; int width, height; - io.FontAtlas->GetTexDataAsRGBA32(&pixels, &width, &height); + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Create texture D3D11_TEXTURE2D_DESC desc; @@ -421,7 +421,7 @@ void LoadFontTexture() pTexture->Release(); // Store our identifier - io.FontAtlas->TexID = (void *)font_texture_view; + io.Fonts->TexID = (void *)font_texture_view; } void InitImGui() @@ -470,7 +470,7 @@ void InitImGui() } // Load fonts - LoadFontTexture(); + LoadFontsTexture(); // Create texture sampler { diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index 6e689cfeb..a6e665245 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -129,7 +129,7 @@ void CleanupDevice() if (g_pVB) g_pVB->Release(); // InitDeviceD3D - if (LPDIRECT3DTEXTURE9 tex = (LPDIRECT3DTEXTURE9)ImGui::GetIO().FontAtlas->TexID) + if (LPDIRECT3DTEXTURE9 tex = (LPDIRECT3DTEXTURE9)ImGui::GetIO().Fonts->TexID) tex->Release(); if (g_pd3dDevice) g_pd3dDevice->Release(); if (g_pD3D) g_pD3D->Release(); @@ -173,17 +173,17 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) return DefWindowProc(hWnd, msg, wParam, lParam); } -void LoadFontTexture() +void LoadFontsTexture() { // Load one or more font ImGuiIO& io = ImGui::GetIO(); - //ImFont* my_font = io.FontAtlas->AddFontDefault(); - //ImFont* my_font2 = io.FontAtlas->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, ImFontAtlas::GetGlyphRangesJapanese()); + //ImFont* my_font = io.Fonts->AddFontDefault(); + //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); // Build unsigned char* pixels; int width, height, bytes_per_pixel; - io.FontAtlas->GetTexDataAsAlpha8(&pixels, &width, &height, &bytes_per_pixel); + io.Fonts->GetTexDataAsAlpha8(&pixels, &width, &height, &bytes_per_pixel); // Create texture LPDIRECT3DTEXTURE9 pTexture = NULL; @@ -205,7 +205,7 @@ void LoadFontTexture() pTexture->UnlockRect(0); // Store our identifier - io.FontAtlas->TexID = (void *)pTexture; + io.Fonts->TexID = (void *)pTexture; } void InitImGui() @@ -245,7 +245,7 @@ void InitImGui() return; } - LoadFontTexture(); + LoadFontsTexture(); } INT64 ticks_per_second = 0; diff --git a/examples/opengl3_example/main.cpp b/examples/opengl3_example/main.cpp index 8ed935ed1..bd370ea3b 100644 --- a/examples/opengl3_example/main.cpp +++ b/examples/opengl3_example/main.cpp @@ -235,15 +235,15 @@ void InitGL() glBindBuffer(GL_ARRAY_BUFFER, 0); } -void LoadFontTexture() +void LoadFontsTexture() { ImGuiIO& io = ImGui::GetIO(); - //ImFont* my_font = io.FontAtlas->AddFontDefault(); - //ImFont* my_font2 = io.FontAtlas->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, ImFontAtlas::GetGlyphRangesJapanese()); + //ImFont* my_font = io.Fonts->AddFontDefault(); + //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); unsigned char* pixels; int width, height; - io.FontAtlas->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits for OpenGL3 demo because it is more likely to be compatible with user's existing shader. + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits for OpenGL3 demo because it is more likely to be compatible with user's existing shader. GLuint tex_id; glGenTextures(1, &tex_id); @@ -253,7 +253,7 @@ void LoadFontTexture() glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Store our identifier - io.FontAtlas->TexID = (void *)(intptr_t)tex_id; + io.Fonts->TexID = (void *)(intptr_t)tex_id; } void InitImGui() @@ -282,7 +282,7 @@ void InitImGui() io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; - LoadFontTexture(); + LoadFontsTexture(); } void UpdateImGui() diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 02077e3ac..70ea2e124 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -145,15 +145,15 @@ void InitGL() glewInit(); } -void LoadFontTexture() +void LoadFontsTexture() { ImGuiIO& io = ImGui::GetIO(); - //ImFont* my_font = io.FontAtlas->AddFontDefault(); - //ImFont* my_font2 = io.FontAtlas->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, ImFontAtlas::GetGlyphRangesJapanese()); + //ImFont* my_font = io.Fonts->AddFontDefault(); + //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); unsigned char* pixels; int width, height; - io.FontAtlas->GetTexDataAsAlpha8(&pixels, &width, &height); + io.Fonts->GetTexDataAsAlpha8(&pixels, &width, &height); GLuint tex_id; glGenTextures(1, &tex_id); @@ -163,7 +163,7 @@ void LoadFontTexture() glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels); // Store our identifier - io.FontAtlas->TexID = (void *)(intptr_t)tex_id; + io.Fonts->TexID = (void *)(intptr_t)tex_id; } void InitImGui() @@ -191,6 +191,8 @@ void InitImGui() io.RenderDrawListsFn = ImImpl_RenderDrawLists; io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; + + LoadFontsTexture(); } void UpdateImGui() diff --git a/imgui.cpp b/imgui.cpp index b3df48192..662e6d5f1 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -90,9 +90,9 @@ // Load texture unsigned char* pixels; int width, height; - io.FontAtlas->GetTexDataAsRGBA32(pixels, &width, &height); + io.Fonts->GetTexDataAsRGBA32(pixels, &width, &height); // TODO: copy texture to graphics memory. - // TODO: store your texture pointer/identifier in 'io.FontAtlas->TexID' + // TODO: store your texture pointer/identifier in 'io.Fonts->TexID' // Application main loop while (true) @@ -129,7 +129,7 @@ Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader. - (1.30) - removed GetDefaultFontData(). uses io.FontAtlas->GetTextureData*() API to retrieve uncompressed pixels. + (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels. this sequence: const void* png_data; unsigned int png_size; @@ -138,11 +138,11 @@ became: unsigned char* pixels; int width, height; - io.FontAtlas->GetTexDataAsRGBA32(&pixels, &width, &height); + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // - io.FontAtlas->TexID = (your_texture_identifier); - but we now have much more flexibility to load multiple TTF fonts and manage the texture buffer with the FontAtlas. - (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.FontAtlas->TexID. + io.Fonts->TexID = (your_texture_identifier); + but we now have much more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. + (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID. (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure. @@ -183,23 +183,23 @@ If you want to load a different font than the default (ProggyClean.ttf, size 13) - io.FontAtlas.AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); - io.FontAtlas.GetTexDataAs****() + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + io.Fonts->GetTexDataAs****() - If you want to load multiple fonts, use the FontAtlas to pack them into a single texture! + If you want to load multiple fonts, use the font atlas to pack them into a single texture! - ImFont* font0 = io.FontAtlas.AddFontDefault(); - ImFont* font1 = io.FontAtlas.AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); - io.FontAtlas.GetTexDataAs****() + ImFont* font0 = io.Fonts->AddFontDefault(); + ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); + io.Fonts->GetTexDataAs****() If you want to display Chinese, Japanese, Korean characters, pass custom Unicode ranges when loading a font: - io.FontAtlas.AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, ImFontAtlas::GetGlyphRangesJapanese()); // Load Japanese characters - io.FontAtlas.GetTexDataAs****() + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, io.Fonts->GetGlyphRangesJapanese()); // Load Japanese characters + io.Fonts->GetTexDataAs****() If you want to input Japanese/Chinese/Korean in the text input widget: - - when loading the font, pass a range that contains the characters you need, e.g.: ImFont::GetGlyphRangesJapanese() + - when loading the font, pass a range that contains the characters you need, e.g.: io.Fonts->GetGlyphRangesJapanese() - to have the Microsoft IME cursor appears at the right location in the screen, setup a handler for the io.ImeSetInputScreenPosFn function: #include @@ -484,7 +484,7 @@ ImGuiIO::ImGuiIO() IniSavingRate = 5.0f; IniFilename = "imgui.ini"; LogFilename = "imgui_log.txt"; - FontAtlas = &GDefaultFontAtlas; + Fonts = &GDefaultFontAtlas; FontGlobalScale = 1.0f; FontAllowUserScaling = false; MousePos = ImVec2(-1,-1); @@ -1564,8 +1564,8 @@ void ImGui::NewFrame() IM_ASSERT(g.IO.DeltaTime > 0.0f); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f); IM_ASSERT(g.IO.RenderDrawListsFn != NULL); // Must be implemented - IM_ASSERT(g.IO.FontAtlas->IsBuilt()); // Font not created. Did you call io.FontAtlas->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? - IM_ASSERT(g.IO.FontAtlas->Fonts[0]->IsLoaded()); // Font not created. Did you call io.FontAtlas->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? + IM_ASSERT(g.IO.Fonts->Fonts.size() > 0); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? + IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? if (!g.Initialized) { @@ -1578,7 +1578,7 @@ void ImGui::NewFrame() g.Initialized = true; } - SetFont(g.IO.FontAtlas->Fonts[0]); + SetFont(g.IO.Fonts->Fonts[0]); g.Time += g.IO.DeltaTime; g.FrameCount += 1; @@ -1732,7 +1732,7 @@ void ImGui::Shutdown() fclose(g.LogFile); g.LogFile = NULL; } - g.IO.FontAtlas->Clear(); + g.IO.Fonts->Clear(); if (g.PrivateClipboard) { @@ -2841,7 +2841,7 @@ void ImGui::PushFont(ImFont* font) ImGuiState& g = GImGui; if (!font) - font = g.IO.FontAtlas->Fonts[0]; + font = g.IO.Fonts->Fonts[0]; SetFont(font); g.FontStack.push_back(font); @@ -2854,7 +2854,7 @@ void ImGui::PopFont() g.CurrentWindow->DrawList->PopTextureID(); g.FontStack.pop_back(); - SetFont(g.FontStack.empty() ? g.IO.FontAtlas->Fonts[0] : g.FontStack.back()); + SetFont(g.FontStack.empty() ? g.IO.Fonts->Fonts[0] : g.FontStack.back()); } void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) @@ -6321,8 +6321,7 @@ ImFontAtlas::ImFontAtlas() ImFontAtlas::~ImFontAtlas() { - ClearInputData(); - ClearTexData(); + Clear(); } void ImFontAtlas::ClearInputData() @@ -6346,8 +6345,10 @@ void ImFontAtlas::ClearTexData() TexPixelsRGBA32 = NULL; } -void ImFontAtlas::ClearFonts() +void ImFontAtlas::Clear() { + ClearInputData(); + ClearTexData(); for (size_t i = 0; i < Fonts.size(); i++) { Fonts[i]->~ImFont(); @@ -6411,7 +6412,7 @@ ImFont* ImFontAtlas::AddFontDefault() stb_decompress(buf_decompressed, (unsigned char*)ttf_compressed, ttf_compressed_size); // Add - ImFont* font = AddFontFromMemoryTTF(buf_decompressed, buf_decompressed_size, 13.0f, ImFontAtlas::GetGlyphRangesDefault(), 0); + ImFont* font = AddFontFromMemoryTTF(buf_decompressed, buf_decompressed_size, 13.0f, GetGlyphRangesDefault(), 0); font->DisplayOffset.y += 1; return font; } @@ -6467,6 +6468,7 @@ bool ImFontAtlas::Build() for (size_t input_i = 0; input_i < InputData.size(); input_i++) { ImFontAtlasData& data = *InputData[input_i]; + IM_ASSERT(data.OutFont && !data.OutFont->IsLoaded()); const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)data.TTFData, data.FontNo); IM_ASSERT(font_offset >= 0); if (!stbtt_InitFont(&data.FontInfo, (unsigned char*)data.TTFData, font_offset)) @@ -6497,7 +6499,7 @@ bool ImFontAtlas::Build() { ImFontAtlasData& data = *InputData[input_i]; if (!data.GlyphRanges) - data.GlyphRanges = ImFontAtlas::GetGlyphRangesDefault(); + data.GlyphRanges = GetGlyphRangesDefault(); // Setup ranges int glyph_count = 0; @@ -6621,11 +6623,6 @@ ImFont::ImFont() Clear(); } -ImFont::~ImFont() -{ - Clear(); -} - void ImFont::Clear() { FontSize = 0.0f; @@ -7494,10 +7491,10 @@ void ImGui::ShowTestWindow(bool* opened) if (ImGui::TreeNode("Fonts")) { - ImGui::TextWrapped("Tip: Load fonts with GetIO().FontAtlas->AddFontFromFileTTF()."); - for (size_t i = 0; i < ImGui::GetIO().FontAtlas->Fonts.size(); i++) + ImGui::TextWrapped("Tip: Load fonts with GetIO().Fonts->AddFontFromFileTTF()."); + for (size_t i = 0; i < ImGui::GetIO().Fonts->Fonts.size(); i++) { - ImFont* font = ImGui::GetIO().FontAtlas->Fonts[i]; + ImFont* font = ImGui::GetIO().Fonts->Fonts[i]; ImGui::BulletText("Font %d: %.2f pixels, %d glyphs", i, font->FontSize, font->Glyphs.size()); ImGui::TreePush((void*)i); ImGui::PushFont(font); @@ -7505,8 +7502,8 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::PopFont(); if (i > 0 && ImGui::Button("Set as default")) { - ImGui::GetIO().FontAtlas->Fonts[i] = ImGui::GetIO().FontAtlas->Fonts[0]; - ImGui::GetIO().FontAtlas->Fonts[0] = font; + ImGui::GetIO().Fonts->Fonts[i] = ImGui::GetIO().Fonts->Fonts[0]; + ImGui::GetIO().Fonts->Fonts[0] = font; } ImGui::SliderFloat("font scale", &font->Scale, 0.3f, 2.0f, "%.1f"); // scale only this font ImGui::TreePop(); @@ -7612,9 +7609,9 @@ void ImGui::ShowTestWindow(bool* opened) { ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. Hover the texture for a zoomed view!"); ImVec2 tex_screen_pos = ImGui::GetCursorScreenPos(); - float tex_w = (float)ImGui::GetIO().FontAtlas->TexWidth; - float tex_h = (float)ImGui::GetIO().FontAtlas->TexHeight; - ImTextureID tex_id = ImGui::GetIO().FontAtlas->TexID; + float tex_w = (float)ImGui::GetIO().Fonts->TexWidth; + float tex_h = (float)ImGui::GetIO().Fonts->TexHeight; + ImTextureID tex_id = ImGui::GetIO().Fonts->TexID; ImGui::Image(tex_id, ImVec2(tex_w, tex_h), ImVec2(0,0), ImVec2(1,1), 0xFFFFFFFF, 0x999999FF); if (ImGui::IsItemHovered()) { diff --git a/imgui.h b/imgui.h index 2b5e7e953..cd442b8f9 100644 --- a/imgui.h +++ b/imgui.h @@ -494,7 +494,7 @@ struct ImGuiIO int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array void* UserData; // = NULL // Store your own data for retrieval by callbacks. - ImFontAtlas* FontAtlas; // // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array. + ImFontAtlas* Fonts; // // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array. float FontGlobalScale; // = 1.0f // Global scale all fonts bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. @@ -748,61 +748,58 @@ struct ImDrawList }; // Load and rasterize multiple TTF fonts into a same texture. -// We also add custom graphic data into the texture that serves for ImGui. // Sharing a texture for multiple fonts allows us to reduce the number of draw calls during rendering. -// The simple use case, if you don't intent to load custom or multiple fonts, is: -// 1. GetTexDataAsRGBA32() or GetTexDataAsAlpha8() // to obtain pixels -// 2. -// 3. SetTexID(my_engine_id); // use the pointer/id to your texture in your engine format -// 4. ClearPixelsData() // to save memory +// We also add custom graphic data into the texture that serves for ImGui. +// 1. (Optional) Call AddFont*** functions. If you don't call any, the default font will be loaded for you. +// 2. Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. +// 3. Upload the pixels data into a texture within your graphics system. +// 4. Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture. This value will be passed back to you during rendering to identify the texture. +// 5. Call ClearPixelsData() to free textures memory on the heap. struct ImFontAtlas { - // Methods IMGUI_API ImFontAtlas(); IMGUI_API ~ImFontAtlas(); - IMGUI_API ImFont* AddFontDefault(); - IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); - IMGUI_API ImFont* AddFontFromMemoryTTF(void* in_ttf_data, size_t in_ttf_data_size, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); // Pass ownership of 'in_ttf_data' memory. - IMGUI_API bool Build(); - IMGUI_API void ClearInputData(); - IMGUI_API void ClearFonts(); - IMGUI_API void ClearTexData(); // Saves RAM once the texture has been copied to graphics memory. - IMGUI_API void Clear() { ClearInputData(); ClearTexData(); ClearFonts(); } - IMGUI_API bool IsBuilt() const { return !Fonts.empty(); } + IMGUI_API ImFont* AddFontDefault(); + IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); + IMGUI_API ImFont* AddFontFromMemoryTTF(void* in_ttf_data, size_t in_ttf_data_size, float size_pixels, const ImWchar* glyph_ranges = NULL, int font_no = 0); // Pass ownership of 'in_ttf_data' memory. + IMGUI_API void ClearTexData(); // Saves RAM once the texture has been copied to graphics memory. + IMGUI_API void Clear(); - // Methods: Retrieve texture data + // Retrieve texture data // User is in charge of copying the pixels into graphics memory, then call SetTextureUserID() // After loading the texture into your graphic system, store your texture handle in 'TexID' (ignore if you aren't using multiple fonts nor images) // RGBA32 format is provided for convenience and high compatibility, but note that all RGB pixels are white, so 75% of the memory is wasted. - IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel - IMGUI_API void SetTexID(void* id) { TexID = id; } + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + IMGUI_API void SetTexID(void* id) { TexID = id; } - // Static helper: Retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) - static IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin - static IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs - static IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Japanese + full set of about 21000 CJK Unified Ideographs + // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) + // (Those functions could be static, aren't so simple use case doesn't have to refer to the ImFontAtlas:: type ever if in their code) + IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + IMGUI_API const ImWchar* GetGlyphRangesChinese(); // Japanese + full set of about 21000 CJK Unified Ideographs // Members - // Access texture data via GetTextureData*() calls which will setup a default font for you. - void* TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It ia passed back to you during rendering. - unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight - unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 - int TexWidth; - int TexHeight; - ImVec2 TexExtraDataPos; // Position of our rectangle where we draw non-font graphics - ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel (part of the TexExtraData block) - ImVector Fonts; + // (Access texture data via GetTexData*() calls which will setup a default font for you.) + void* TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It ia passed back to you during rendering. + unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight + unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + int TexWidth; + int TexHeight; + ImVec2 TexExtraDataPos; // Position of our rectangle where we draw non-font graphics + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel (part of the TexExtraData block) + ImVector Fonts; + // Private struct ImFontAtlasData; - ImVector InputData; // Internal data + ImVector InputData; // Internal data + IMGUI_API bool Build(); // Build pixels data. This is automatically for you by the GetTexData*** functions. + IMGUI_API void ClearInputData(); // Clear the input TTF data. }; // TTF font loading and rendering -// - ImGui automatically loads a default embedded font for you -// - Call GetTextureData() to retrieve pixels data so you can upload the texture to your graphics system. -// - Store your texture handle in 'TexID'. It will be passed back to you when rendering ('texture_id' field in ImDrawCmd) -// (NB: kerning isn't supported. At the moment some ImGui code does per-character CalcTextSize calls, need something more state-ful) +// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). +// Kerning isn't supported. At the moment some ImGui code does per-character CalcTextSize calls, need something more state-ful. struct ImFont { // Members: Settings @@ -822,20 +819,16 @@ struct ImFont }; ImFontAtlas* ContainerAtlas; // What we has been loaded into ImVector Glyphs; - ImVector IndexLookup; + ImVector IndexLookup; // Index glyphs by Unicode code-point const Glyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) // Methods IMGUI_API ImFont(); - IMGUI_API ~ImFont(); + IMGUI_API ~ImFont() { Clear(); } IMGUI_API void Clear(); IMGUI_API void BuildLookupTable(); IMGUI_API const Glyph* FindGlyph(unsigned short c) const; - IMGUI_API bool IsLoaded() const { return ContainerAtlas != NULL; } - IMGUI_API ImTextureID GetTexID() const { IM_ASSERT(ContainerAtlas != NULL); return ContainerAtlas->TexID; } - IMGUI_API int GetTexWidth() const { IM_ASSERT(ContainerAtlas != NULL); return ContainerAtlas->TexWidth; } - IMGUI_API int GetTexHeight() const { IM_ASSERT(ContainerAtlas != NULL); return ContainerAtlas->TexHeight; } // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. From ca81fd3a34361f8e1e399521b7b887a98f33d42b Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 18 Jan 2015 11:19:11 +0000 Subject: [PATCH 34/41] Removing the binary_to_c() comment, link to license for ProggyClean --- imgui.cpp | 67 ++++++++----------------------------------------------- 1 file changed, 9 insertions(+), 58 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 662e6d5f1..9a7133323 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8331,75 +8331,26 @@ static void ShowExampleAppLongText(bool* opened) // End of Sample code //----------------------------------------------------------------------------- -// Font data -// Bitmap exported from proggy_clean.fon (c) by Tristan Grimmer http://upperbounds.net/ -// Also available on unofficial ProggyFonts mirror http://www.proggyfonts.net +// FONT DATA //----------------------------------------------------------------------------- -/* + +//----------------------------------------------------------------------------- +// ProggyClean.ttf // Copyright (c) 2004, 2005 Tristan Grimmer - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -*/ +// MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip) +// Download and more information at http://upperbounds.net //----------------------------------------------------------------------------- -// Fonts exported with BMFont http://www.angelcode.com/products/bmfont -// We are using bmfont format and you can load your own font from a file by setting up ImGui::GetIO().Font -// PNG further compressed with pngout.exe http://advsys.net/ken/utils.htm -// Manually converted to C++ array using the following program: -/* -#include -static void binary_to_c(const char* name_in, const char* symbol) -{ - FILE* fi = fopen(name_in, "rb"); fseek(fi, 0, SEEK_END); long sz = ftell(fi); fseek(fi, 0, SEEK_SET); - fprintf(stdout, "static const unsigned int %s_size = %d;\n", symbol, sz); - fprintf(stdout, "static const unsigned int %s_data[%d/4] =\n{", symbol, ((sz+3)/4)*4); - int column = 0; - for (unsigned int data = 0; fread(&data, 1, 4, fi); data = 0) - if ((column++ % 12) == 0) - fprintf(stdout, "\n 0x%08x, ", data); - else - fprintf(stdout, "0x%08x, ", data); - fprintf(stdout, "\n};\n\n"); - fclose(fi); -} - -int main(int argc, char** argv) -{ - binary_to_c("proggy_clean_13.png", "proggy_clean_13_png"); - binary_to_c("proggy_clean_13.fnt", "proggy_clean_13_fnt"); - return 1; -} -*/ - -//----------------------------------------------------------------------------- - +// Compressed with stb_compress() then converted to a C array. // Decompressor from stb.h (public domain) by Sean Barrett // https://github.com/nothings/stb/blob/master/stb.h +//----------------------------------------------------------------------------- static unsigned int stb_decompress_length(unsigned char *input) { return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]; } -static unsigned char *stb__barrier; -static unsigned char *stb__barrier2; -static unsigned char *stb__barrier3; -static unsigned char *stb__barrier4; +static unsigned char *stb__barrier, *stb__barrier2, *stb__barrier3, *stb__barrier4; static unsigned char *stb__dout; static void stb__match(unsigned char *data, unsigned int length) From ed23598e4930df9cc8df5c4f14da49fe137e5118 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 18 Jan 2015 11:24:06 +0000 Subject: [PATCH 35/41] Font documentation --- README.md | 5 ++--- extra_fonts/README.txt | 26 +++++++++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a11711813..9f5cf6e73 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,8 @@ Credits Developed by [Omar Cornut](http://www.miracleworld.net) and every direct or indirect contributors to the GitHub. The early version of this library was developed with the support of [Media Molecule](http://www.mediamolecule.com) and first used internally on the game [Tearaway](http://tearaway.mediamolecule.com). -Embeds [proggy_clean](http://upperbounds.net) font by Tristan Grimmer (MIT license). -Embeds [M+ fonts](http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/index-en.html) font by Coji Morishita (free software license). -Embeds [stb_textedit.h](https://github.com/nothings/stb/) by Sean Barrett (public domain). +Embeds [ProggyClean.ttf](http://upperbounds.net) font by Tristan Grimmer (MIT license). +Embeds [stb_textedit.h, stb_truetype.h, stb_rectpack.h](https://github.com/nothings/stb/) by Sean Barrett (public domain). Inspiration, feedback, and testing for early versions: Casey Muratori, Atman Binstock, Mikko Mononen, Emmanuel Briney, Stefan Kamoda, Anton Mikhailov, Matt Willis. And everybody posting feedback, questions and patches on the GitHub. diff --git a/extra_fonts/README.txt b/extra_fonts/README.txt index 3ff694cf7..1e69d08ee 100644 --- a/extra_fonts/README.txt +++ b/extra_fonts/README.txt @@ -3,23 +3,35 @@ EXTRA FONTS FOR IMGUI --------------------------------- -ImGui embeds a copy of 'ProggyClean.ttf' that you can use without any external files. + ProggyClean.ttf + Copyright (c) 2004, 2005 Tristan Grimmer + MIT License + + ProggyTiny.ttf + Copyright (c) 2004, 2005 Tristan Grimmer + MIT License + + Karla-Regular + Copyright (c) 2012, Jonathan Pinhorn + SIL OPEN FONT LICENSE Version 1.1 + +imgui.cpp embeds a copy of 'ProggyClean.ttf' that you can use without any external files. Load .TTF file with: ImGuiIO& io = ImGui::GetIO(); - io.Font = new ImFont(); - io.Font->LoadFromFileTTF("myfontfile.ttf", size_pixels); + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_pixels); Add a third parameter to bake specific font ranges: - io.Font->LoadFromFileTTF("myfontfile.ttf", size_pixels, ImFont::GetGlyphRangesDefault()); // Basic Latin, Extended Latin - io.Font->LoadFromFileTTF("myfontfile.ttf", size_pixels, ImFont::GetGlyphRangesJapanese()); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs - io.Font->LoadFromFileTTF("myfontfile.ttf", size_pixels, ImFont::GetGlyphRangesChinese()); // Japanese + full set of about 21000 CJK Unified Ideographs + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesDefault()); // Basic Latin, Extended Latin + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesJapanese()); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesChinese()); // Include full set of about 21000 CJK Unified Ideographs Offset font by altering the io.Font->DisplayOffset value: - io.Font->DisplayOffset.y += 1; // Render 1 pixel down + ImFont* font = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_pixels); + font->DisplayOffset.y += 1; // Render 1 pixel down ----------------------------------- RECOMMENDED SIZES From 8386e4fb7e6565b5a6c09a08120bcf939e98a05f Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 18 Jan 2015 11:36:23 +0000 Subject: [PATCH 36/41] Fix handling of NULL GlyphRanges. Asserting on AddFontFromFileTTF() failure. --- imgui.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 9a7133323..fc603d958 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6422,7 +6422,10 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, void* data = NULL; size_t data_size = 0; if (!ImLoadFileToMemory(filename, "rb", (void**)&data, &data_size)) + { + IM_ASSERT(0); // Could not load file. return NULL; + } // Add ImFont* font = AddFontFromMemoryTTF(data, data_size, size_pixels, glyph_ranges, font_no); @@ -6473,7 +6476,10 @@ bool ImFontAtlas::Build() IM_ASSERT(font_offset >= 0); if (!stbtt_InitFont(&data.FontInfo, (unsigned char*)data.TTFData, font_offset)) return false; - for (const ImWchar* in_range = InputData[input_i]->GlyphRanges; in_range[0] && in_range[1]; in_range += 2) + + if (!data.GlyphRanges) + data.GlyphRanges = GetGlyphRangesDefault(); + for (const ImWchar* in_range = data.GlyphRanges; in_range[0] && in_range[1]; in_range += 2) total_glyph_count += (in_range[1] - in_range[0]) + 1; } @@ -6498,8 +6504,6 @@ bool ImFontAtlas::Build() for (size_t input_i = 0; input_i < InputData.size(); input_i++) { ImFontAtlasData& data = *InputData[input_i]; - if (!data.GlyphRanges) - data.GlyphRanges = GetGlyphRangesDefault(); // Setup ranges int glyph_count = 0; From edcf2d3bf64ba0236385f8a657f7dadd3ae98334 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 18 Jan 2015 11:38:14 +0000 Subject: [PATCH 37/41] Examples: more comments on loading fonts. --- examples/directx11_example/main.cpp | 7 +++++-- examples/directx9_example/main.cpp | 7 +++++-- examples/opengl3_example/main.cpp | 7 +++++-- examples/opengl_example/main.cpp | 7 +++++-- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/examples/directx11_example/main.cpp b/examples/directx11_example/main.cpp index 301b91f17..8563f072f 100644 --- a/examples/directx11_example/main.cpp +++ b/examples/directx11_example/main.cpp @@ -381,8 +381,11 @@ void LoadFontsTexture() { // Load one or more font ImGuiIO& io = ImGui::GetIO(); - //ImFont* my_font = io.Fonts->AddFontDefault(); - //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* my_font1 = io.Fonts->AddFontDefault(); + //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("extra_fonts/Karla-Regular.ttf", 15.0f); + //ImFont* my_font3 = io.Fonts->AddFontFromFileTTF("extra_fonts/ProggyClean.ttf", 13.0f); my_font3->DisplayOffset.y += 1; + //ImFont* my_font4 = io.Fonts->AddFontFromFileTTF("extra_fonts/ProggyTiny.ttf", 10.0f); my_font4->DisplayOffset.y += 1; + //ImFont* my_font5 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); // Build unsigned char* pixels; diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index a6e665245..ed813780e 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -177,8 +177,11 @@ void LoadFontsTexture() { // Load one or more font ImGuiIO& io = ImGui::GetIO(); - //ImFont* my_font = io.Fonts->AddFontDefault(); - //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* my_font1 = io.Fonts->AddFontDefault(); + //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("extra_fonts/Karla-Regular.ttf", 15.0f); + //ImFont* my_font3 = io.Fonts->AddFontFromFileTTF("extra_fonts/ProggyClean.ttf", 13.0f); my_font3->DisplayOffset.y += 1; + //ImFont* my_font4 = io.Fonts->AddFontFromFileTTF("extra_fonts/ProggyTiny.ttf", 10.0f); my_font4->DisplayOffset.y += 1; + //ImFont* my_font5 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); // Build unsigned char* pixels; diff --git a/examples/opengl3_example/main.cpp b/examples/opengl3_example/main.cpp index bd370ea3b..e56f105bb 100644 --- a/examples/opengl3_example/main.cpp +++ b/examples/opengl3_example/main.cpp @@ -238,8 +238,11 @@ void InitGL() void LoadFontsTexture() { ImGuiIO& io = ImGui::GetIO(); - //ImFont* my_font = io.Fonts->AddFontDefault(); - //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* my_font1 = io.Fonts->AddFontDefault(); + //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("extra_fonts/Karla-Regular.ttf", 15.0f); + //ImFont* my_font3 = io.Fonts->AddFontFromFileTTF("extra_fonts/ProggyClean.ttf", 13.0f); my_font3->DisplayOffset.y += 1; + //ImFont* my_font4 = io.Fonts->AddFontFromFileTTF("extra_fonts/ProggyTiny.ttf", 10.0f); my_font4->DisplayOffset.y += 1; + //ImFont* my_font5 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); unsigned char* pixels; int width, height; diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 70ea2e124..fbce41c65 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -148,8 +148,11 @@ void InitGL() void LoadFontsTexture() { ImGuiIO& io = ImGui::GetIO(); - //ImFont* my_font = io.Fonts->AddFontDefault(); - //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); + //ImFont* my_font1 = io.Fonts->AddFontDefault(); + //ImFont* my_font2 = io.Fonts->AddFontFromFileTTF("extra_fonts/Karla-Regular.ttf", 15.0f); + //ImFont* my_font3 = io.Fonts->AddFontFromFileTTF("extra_fonts/ProggyClean.ttf", 13.0f); my_font3->DisplayOffset.y += 1; + //ImFont* my_font4 = io.Fonts->AddFontFromFileTTF("extra_fonts/ProggyTiny.ttf", 10.0f); my_font4->DisplayOffset.y += 1; + //ImFont* my_font5 = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 20.0f, io.Fonts->GetGlyphRangesJapanese()); unsigned char* pixels; int width, height; From e685e4978198fc7af62b204579c32751d08bf84b Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 18 Jan 2015 11:55:58 +0000 Subject: [PATCH 38/41] ImFontAtlas: reduced number of temporary allocation when building lots of input ranges --- imgui.cpp | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index fc603d958..e76abfa89 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6484,7 +6484,7 @@ bool ImFontAtlas::Build() } // Start packing - TexWidth = (total_glyph_count > 3000) ? 2048 : (total_glyph_count > 1000) ? 1024 : 512; // Width doesn't actually matters. + TexWidth = (total_glyph_count > 1000) ? 1024 : 512; // Width doesn't actually matters. TexHeight = 0; const int max_tex_height = 1024*32; stbtt_pack_context spc; @@ -6499,6 +6499,14 @@ bool ImFontAtlas::Build() stbrp_pack_rects((stbrp_context*)spc.pack_info, &extra_rect, 1); TexExtraDataPos = ImVec2(extra_rect.x, extra_rect.y); + // Allocate packing character data and flag as non-packed (x0=y0=x1=y1=0) + stbtt_packedchar* packed_chardata = (stbtt_packedchar*)ImGui::MemAlloc(total_glyph_count * sizeof(stbtt_packedchar)); + int packed_chardata_n = 0; + memset(packed_chardata, 0, total_glyph_count * sizeof(stbtt_packedchar)); + + stbrp_rect* rectdata = (stbrp_rect*)ImGui::MemAlloc(total_glyph_count * sizeof(stbrp_rect)); + int rectdata_n = 0; + // First pass: pack all glyphs (no rendering at this point, we are working with glyph sizes only) int tex_height = extra_rect.y + extra_rect.h; for (size_t input_i = 0; input_i < InputData.size(); input_i++) @@ -6521,17 +6529,13 @@ bool ImFontAtlas::Build() const ImWchar* in_range = &data.GlyphRanges[i * 2]; range.first_unicode_char_in_range = in_range[0]; range.num_chars_in_range = (in_range[1] - in_range[0]) + 1; - - // Allocate characters and flag as not packed - // FIXME-OPT: Loose ranges will incur lots of allocations. Allocate all contiguous in loop above. - range.chardata_for_range = (stbtt_packedchar*)ImGui::MemAlloc(range.num_chars_in_range * sizeof(stbtt_packedchar)); - for (int j = 0; j < range.num_chars_in_range; ++j) - range.chardata_for_range[j].x0 = range.chardata_for_range[j].y0 = range.chardata_for_range[j].x1 = range.chardata_for_range[j].y1 = 0; + range.chardata_for_range = packed_chardata + packed_chardata_n; + packed_chardata_n += range.num_chars_in_range; } // Pack - data.Rects = (stbrp_rect*)ImGui::MemAlloc(sizeof(stbrp_rect) * glyph_count); - IM_ASSERT(data.Rects); + data.Rects = rectdata + rectdata_n; + rectdata_n += glyph_count; const int n = stbtt_PackFontRangesGatherRects(&spc, &data.FontInfo, data.Ranges.begin(), data.Ranges.size(), data.Rects); stbrp_pack_rects((stbrp_context*)spc.pack_info, data.Rects, n); @@ -6540,6 +6544,7 @@ bool ImFontAtlas::Build() if (data.Rects[i].was_packed) tex_height = ImMax(tex_height, data.Rects[i].y + data.Rects[i].h); } + IM_ASSERT(packed_chardata_n == total_glyph_count); // Create texture TexHeight = ImUpperPowerOfTwo(tex_height); @@ -6553,12 +6558,13 @@ bool ImFontAtlas::Build() { ImFontAtlasData& data = *InputData[input_i]; ret = stbtt_PackFontRangesRenderIntoRects(&spc, &data.FontInfo, data.Ranges.begin(), data.Ranges.size(), data.Rects); - ImGui::MemFree(data.Rects); data.Rects = NULL; } // End packing stbtt_PackEnd(&spc); + ImGui::MemFree(rectdata); + rectdata = NULL; // Third pass: setup ImFont and glyphs for runtime for (size_t input_i = 0; input_i < InputData.size(); input_i++) @@ -6601,18 +6607,19 @@ bool ImFontAtlas::Build() data.OutFont->BuildLookupTable(); - // Cleanup temporary - for (size_t i = 0; i < data.Ranges.size(); i++) - ImGui::MemFree(data.Ranges[i].chardata_for_range); + // Cleanup temporaries data.Ranges.clear(); } + // Cleanup temporaries + ClearInputData(); + ImGui::MemFree(packed_chardata); + packed_chardata = NULL; + // Draw white pixel into texture and make UV points to it TexPixelsAlpha8[0] = TexPixelsAlpha8[1] = TexPixelsAlpha8[TexWidth+0] = TexPixelsAlpha8[TexWidth+1] = 0xFF; TexUvWhitePixel = ImVec2((TexExtraDataPos.x + 0.5f) / TexWidth, (TexExtraDataPos.y + 0.5f) / TexHeight); - ClearInputData(); - return true; } From bfe894406d449625cf1c27391628ea3ebf05914f Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 18 Jan 2015 12:12:16 +0000 Subject: [PATCH 39/41] ImFontAtlas: some more optimisations / cleanup. --- imgui.cpp | 78 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e76abfa89..9c394a4c3 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6297,17 +6297,18 @@ void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const Im struct ImFontAtlas::ImFontAtlasData { // Input - ImFont* OutFont; // Load into this font - void* TTFData; // TTF data, we own the memory - size_t TTFDataSize; // TTF data size, in bytes - float SizePixels; // Desired output size, in pixels - const ImWchar* GlyphRanges; // List of Unicode range (2 value per range, values are inclusive, zero-terminated list) - int FontNo; // Index of font within .TTF file (0) + ImFont* OutFont; // Load into this font + void* TTFData; // TTF data, we own the memory + size_t TTFDataSize; // TTF data size, in bytes + float SizePixels; // Desired output size, in pixels + const ImWchar* GlyphRanges; // List of Unicode range (2 value per range, values are inclusive, zero-terminated list) + int FontNo; // Index of font within .TTF file (0) // Temporary Build Data - stbtt_fontinfo FontInfo; - stbrp_rect* Rects; - ImVector Ranges; + stbtt_fontinfo FontInfo; + stbrp_rect* Rects; + stbtt_pack_range* Ranges; + int RangesCount; }; ImFontAtlas::ImFontAtlas() @@ -6468,6 +6469,7 @@ bool ImFontAtlas::Build() // Initialize font information early (so we can error without any cleanup) + count glyphs int total_glyph_count = 0; + int total_glyph_range_count = 0; for (size_t input_i = 0; input_i < InputData.size(); input_i++) { ImFontAtlasData& data = *InputData[input_i]; @@ -6480,7 +6482,10 @@ bool ImFontAtlas::Build() if (!data.GlyphRanges) data.GlyphRanges = GetGlyphRangesDefault(); for (const ImWchar* in_range = data.GlyphRanges; in_range[0] && in_range[1]; in_range += 2) + { total_glyph_count += (in_range[1] - in_range[0]) + 1; + total_glyph_range_count++; + } } // Start packing @@ -6499,13 +6504,13 @@ bool ImFontAtlas::Build() stbrp_pack_rects((stbrp_context*)spc.pack_info, &extra_rect, 1); TexExtraDataPos = ImVec2(extra_rect.x, extra_rect.y); - // Allocate packing character data and flag as non-packed (x0=y0=x1=y1=0) - stbtt_packedchar* packed_chardata = (stbtt_packedchar*)ImGui::MemAlloc(total_glyph_count * sizeof(stbtt_packedchar)); - int packed_chardata_n = 0; - memset(packed_chardata, 0, total_glyph_count * sizeof(stbtt_packedchar)); - - stbrp_rect* rectdata = (stbrp_rect*)ImGui::MemAlloc(total_glyph_count * sizeof(stbrp_rect)); - int rectdata_n = 0; + // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) + int buf_packedchars_n = 0, buf_rects_n = 0, buf_ranges_n = 0; + stbtt_packedchar* buf_packedchars = (stbtt_packedchar*)ImGui::MemAlloc(total_glyph_count * sizeof(stbtt_packedchar)); + stbrp_rect* buf_rects = (stbrp_rect*)ImGui::MemAlloc(total_glyph_count * sizeof(stbrp_rect)); + stbtt_pack_range* buf_ranges = (stbtt_pack_range*)ImGui::MemAlloc(total_glyph_range_count * sizeof(stbtt_pack_range)); + memset(buf_packedchars, 0, total_glyph_count * sizeof(stbtt_packedchar)); + memset(buf_ranges, 0, total_glyph_range_count * sizeof(stbtt_pack_range)); // First pass: pack all glyphs (no rendering at this point, we are working with glyph sizes only) int tex_height = extra_rect.y + extra_rect.h; @@ -6521,22 +6526,24 @@ bool ImFontAtlas::Build() glyph_count += (in_range[1] - in_range[0]) + 1; glyph_ranges_count++; } - data.Ranges.resize(glyph_ranges_count); - for (size_t i = 0; i < data.Ranges.size(); i++) + data.Ranges = buf_ranges + buf_ranges_n; + data.RangesCount = glyph_ranges_count; + buf_ranges_n += glyph_ranges_count; + for (size_t i = 0; i < glyph_ranges_count; i++) { + const ImWchar* in_range = &data.GlyphRanges[i * 2]; stbtt_pack_range& range = data.Ranges[i]; range.font_size = data.SizePixels; - const ImWchar* in_range = &data.GlyphRanges[i * 2]; range.first_unicode_char_in_range = in_range[0]; range.num_chars_in_range = (in_range[1] - in_range[0]) + 1; - range.chardata_for_range = packed_chardata + packed_chardata_n; - packed_chardata_n += range.num_chars_in_range; + range.chardata_for_range = buf_packedchars + buf_packedchars_n; + buf_packedchars_n += range.num_chars_in_range; } // Pack - data.Rects = rectdata + rectdata_n; - rectdata_n += glyph_count; - const int n = stbtt_PackFontRangesGatherRects(&spc, &data.FontInfo, data.Ranges.begin(), data.Ranges.size(), data.Rects); + data.Rects = buf_rects + buf_rects_n; + buf_rects_n += glyph_count; + const int n = stbtt_PackFontRangesGatherRects(&spc, &data.FontInfo, data.Ranges, data.RangesCount, data.Rects); stbrp_pack_rects((stbrp_context*)spc.pack_info, data.Rects, n); // Extend texture height @@ -6544,11 +6551,13 @@ bool ImFontAtlas::Build() if (data.Rects[i].was_packed) tex_height = ImMax(tex_height, data.Rects[i].y + data.Rects[i].h); } - IM_ASSERT(packed_chardata_n == total_glyph_count); + IM_ASSERT(buf_rects_n == total_glyph_count); + IM_ASSERT(buf_packedchars_n == total_glyph_count); + IM_ASSERT(buf_ranges_n == total_glyph_range_count); // Create texture TexHeight = ImUpperPowerOfTwo(tex_height); - TexPixelsAlpha8 = (unsigned char*)ImGui::MemRealloc(TexPixelsAlpha8, TexWidth * TexHeight); + TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(TexWidth * TexHeight); memset(TexPixelsAlpha8, 0, TexWidth * TexHeight); spc.pixels = TexPixelsAlpha8; spc.height = TexHeight; @@ -6557,14 +6566,14 @@ bool ImFontAtlas::Build() for (size_t input_i = 0; input_i < InputData.size(); input_i++) { ImFontAtlasData& data = *InputData[input_i]; - ret = stbtt_PackFontRangesRenderIntoRects(&spc, &data.FontInfo, data.Ranges.begin(), data.Ranges.size(), data.Rects); + ret = stbtt_PackFontRangesRenderIntoRects(&spc, &data.FontInfo, data.Ranges, data.RangesCount, data.Rects); data.Rects = NULL; } // End packing stbtt_PackEnd(&spc); - ImGui::MemFree(rectdata); - rectdata = NULL; + ImGui::MemFree(buf_rects); + buf_rects = NULL; // Third pass: setup ImFont and glyphs for runtime for (size_t input_i = 0; input_i < InputData.size(); input_i++) @@ -6580,7 +6589,7 @@ bool ImFontAtlas::Build() const float uv_scale_x = 1.0f / TexWidth; const float uv_scale_y = 1.0f / TexHeight; const int character_spacing_x = 1; - for (size_t i = 0; i < data.Ranges.size(); i++) + for (int i = 0; i < data.RangesCount; i++) { stbtt_pack_range& range = data.Ranges[i]; for (int char_idx = 0; char_idx < range.num_chars_in_range; char_idx += 1) @@ -6606,15 +6615,14 @@ bool ImFontAtlas::Build() } data.OutFont->BuildLookupTable(); - - // Cleanup temporaries - data.Ranges.clear(); } // Cleanup temporaries + ImGui::MemFree(buf_packedchars); + ImGui::MemFree(buf_ranges); + buf_packedchars = NULL; + buf_ranges = NULL; ClearInputData(); - ImGui::MemFree(packed_chardata); - packed_chardata = NULL; // Draw white pixel into texture and make UV points to it TexPixelsAlpha8[0] = TexPixelsAlpha8[1] = TexPixelsAlpha8[TexWidth+0] = TexPixelsAlpha8[TexWidth+1] = 0xFF; From 46fbff50cf367c6d5708cd66b18bf3a6c867dd4f Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 18 Jan 2015 12:19:49 +0000 Subject: [PATCH 40/41] Documentation --- extra_fonts/README.txt | 59 ++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/extra_fonts/README.txt b/extra_fonts/README.txt index 1e69d08ee..ed600ff33 100644 --- a/extra_fonts/README.txt +++ b/extra_fonts/README.txt @@ -1,42 +1,55 @@ + The code in imgui.cpp embeds a copy of 'ProggyClean.ttf' that you can use without any external files. + --------------------------------- EXTRA FONTS FOR IMGUI --------------------------------- ProggyClean.ttf - Copyright (c) 2004, 2005 Tristan Grimmer - MIT License + Copyright (c) 2004, 2005 Tristan Grimmer + MIT License + recommended loading setting in ImGui: Size = 13.0, DisplayOffset.Y = +1 ProggyTiny.ttf - Copyright (c) 2004, 2005 Tristan Grimmer - MIT License + Copyright (c) 2004, 2005 Tristan Grimmer + MIT License + recommended loading setting in ImGui: Size = 10.0, DisplayOffset.Y = +1 Karla-Regular - Copyright (c) 2012, Jonathan Pinhorn - SIL OPEN FONT LICENSE Version 1.1 + Copyright (c) 2012, Jonathan Pinhorn + SIL OPEN FONT LICENSE Version 1.1 -imgui.cpp embeds a copy of 'ProggyClean.ttf' that you can use without any external files. +--------------------------------- + OTHER FONTS +--------------------------------- -Load .TTF file with: + For Japanese: + + M+ fonts by Coji Morishita are free and include most useful Kanjis you would need. + mplus-fonts.sourceforge.jp/mplus-outline-fonts/index-en.html + + For Japanese, Chinese, Korean: + + You can use Arial Unicode or other Unicode fonts provided with Windows (not sure of their license). + Other suggestions? - ImGuiIO& io = ImGui::GetIO(); - io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_pixels); +--------------------------------- + LOADING INSTRUCTIONS +--------------------------------- + + Load .TTF file with: + + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_pixels); -Add a third parameter to bake specific font ranges: + Add a third parameter to bake specific font ranges: - io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesDefault()); // Basic Latin, Extended Latin - io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesJapanese()); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs - io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesChinese()); // Include full set of about 21000 CJK Unified Ideographs + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesDefault()); // Basic Latin, Extended Latin + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesJapanese()); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs + io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, io.Fonts->GetGlyphRangesChinese()); // Include full set of about 21000 CJK Unified Ideographs Offset font by altering the io.Font->DisplayOffset value: - ImFont* font = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_pixels); - font->DisplayOffset.y += 1; // Render 1 pixel down + ImFont* font = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_pixels); + font->DisplayOffset.y += 1; // Render 1 pixel down ------------------------------------ - RECOMMENDED SIZES ------------------------------------ - - ProggyTiny.ttf Size: 10.0f Offset: Y: +1 - ProggyClean.ttf Size: 13.0f Offset: Y: +1 - From 7e2305eb36db2d1b80890bccf76ff94db79a4023 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 18 Jan 2015 12:22:17 +0000 Subject: [PATCH 41/41] Comments --- imgui.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 9c394a4c3..d7687310c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -141,7 +141,8 @@ io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // io.Fonts->TexID = (your_texture_identifier); - but we now have much more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. + you now have much more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. + it is now recommended your sample the font texture with bilinear interpolation. (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID. (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver)