diff options
-rw-r--r-- | autobuild.xml | 34 | ||||
-rw-r--r-- | indra/cmake/ViewerMiscLibs.cmake | 1 | ||||
-rw-r--r-- | indra/llrender/CMakeLists.txt | 2 | ||||
-rw-r--r-- | indra/llrender/llfontfreetype.cpp | 13 | ||||
-rw-r--r-- | indra/llrender/llfontfreetypesvg.cpp | 205 | ||||
-rw-r--r-- | indra/llrender/llfontfreetypesvg.h | 54 |
6 files changed, 307 insertions, 2 deletions
diff --git a/autobuild.xml b/autobuild.xml index babc7d73bc..6fe7620112 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -958,9 +958,9 @@ <key>archive</key> <map> <key>hash</key> - <string>f3e63d80bd80da03aa7f87f7bd7c5f8b</string> + <string>2fee8d8320ab6bd180f0acf4d2b24bff</string> <key>url</key> - <string>freetype-2.10.1.0-windows64-222392239.tar.bz2</string> + <string>freetype-2.12.1.0-windows64-223091759.tar.bz2</string> </map> <key>name</key> <string>windows64</string> @@ -2424,6 +2424,36 @@ <key>version</key> <string>7.11.1.297294</string> </map> + <key>nanosvg</key> + <map> + <key>copyright</key> + <string>Copyright (c) 2013-14 Mikko Mononen</string> + <key>description</key> + <string>NanoSVG is a simple single-header-file SVG parser and rasterizer</string> + <key>license</key> + <string>Zlib</string> + <key>license_file</key> + <string>LICENSES/nanosvg.txt</string> + <key>name</key> + <string>nanosvg</string> + <key>platforms</key> + <map> + <key>common</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>aa73d8b6c45168b74e2cc4640ec01f95</string> + <key>url</key> + <string>nanosvg-2022.09.27-common-223101446.tar.bz2</string> + </map> + <key>name</key> + <string>common</string> + </map> + </map> + <key>version</key> + <string>2022.09.27</string> + </map> <key>nghttp2</key> <map> <key>copyright</key> diff --git a/indra/cmake/ViewerMiscLibs.cmake b/indra/cmake/ViewerMiscLibs.cmake index f213de100c..aa204a7406 100644 --- a/indra/cmake/ViewerMiscLibs.cmake +++ b/indra/cmake/ViewerMiscLibs.cmake @@ -11,3 +11,4 @@ if (NOT USESYSTEMLIBS) endif(NOT USESYSTEMLIBS) use_prebuilt_binary(fonts) +use_prebuilt_binary(nanosvg)
\ No newline at end of file diff --git a/indra/llrender/CMakeLists.txt b/indra/llrender/CMakeLists.txt index 47e7ad915b..282a5c70ad 100644 --- a/indra/llrender/CMakeLists.txt +++ b/indra/llrender/CMakeLists.txt @@ -35,6 +35,7 @@ set(llrender_SOURCE_FILES llcubemap.cpp llfontbitmapcache.cpp llfontfreetype.cpp + llfontfreetypesvg.cpp llfontgl.cpp llfontregistry.cpp llgl.cpp @@ -62,6 +63,7 @@ set(llrender_HEADER_FILES llcubemap.h llfontgl.h llfontfreetype.h + llfontfreetypesvg.h llfontbitmapcache.h llfontregistry.h llgl.h diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp index 25740a5f87..d3a49339da 100644 --- a/indra/llrender/llfontfreetype.cpp +++ b/indra/llrender/llfontfreetype.cpp @@ -34,6 +34,7 @@ #ifdef LL_WINDOWS #include <freetype2\freetype\ftsystem.h> #endif +#include "llfontfreetypesvg.h" // For some reason, this won't work if it's not wrapped in the ifdef #ifdef FT_FREETYPE_H @@ -51,6 +52,8 @@ #include "llfontbitmapcache.h" #include "llgl.h" +#define ENABLE_OT_SVG_SUPPORT + FT_Render_Mode gFontRenderMode = FT_RENDER_MODE_NORMAL; LLFontManager *gFontManagerp = NULL; @@ -83,6 +86,16 @@ LLFontManager::LLFontManager() LL_ERRS() << "Freetype initialization failure!" << LL_ENDL; FT_Done_FreeType(gFTLibrary); } + +#ifdef ENABLE_OT_SVG_SUPPORT + SVG_RendererHooks hooks = { + LLFontFreeTypeSvgRenderer::OnInit, + LLFontFreeTypeSvgRenderer::OnFree, + LLFontFreeTypeSvgRenderer::OnRender, + LLFontFreeTypeSvgRenderer::OnPresetGlypthSlot, + }; + FT_Property_Set(gFTLibrary, "ot-svg", "svg-hooks", &hooks); +#endif } LLFontManager::~LLFontManager() diff --git a/indra/llrender/llfontfreetypesvg.cpp b/indra/llrender/llfontfreetypesvg.cpp new file mode 100644 index 0000000000..19d327a4c9 --- /dev/null +++ b/indra/llrender/llfontfreetypesvg.cpp @@ -0,0 +1,205 @@ +/** + * @file llfontfreetypesvg.cpp + * @brief Freetype font library SVG glyph rendering + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llfontfreetypesvg.h" + +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable : 4702) +#endif + +#define NANOSVG_IMPLEMENTATION +#include <nanosvg/nanosvg.h> +#define NANOSVGRAST_IMPLEMENTATION +#include <nanosvg/nanosvgrast.h> + +#if LL_WINDOWS +#pragma warning (pop) +#endif + +struct LLSvgRenderData +{ + FT_UInt GlyphIndex = 0; + FT_Error Error = FT_Err_Ok; // FreeType currently (@2.12.1) ignores the error value returned by the preset glyph slot callback so we return it at render time + // (See https://github.com/freetype/freetype/blob/5faa1df8b93ebecf0f8fd5fe8fda7b9082eddced/src/base/ftobjs.c#L1170) + NSVGimage* pNSvgImage = nullptr; + float Scale = 0.f; +}; + +// static +FT_Error LLFontFreeTypeSvgRenderer::OnInit(FT_Pointer* state) +{ + // The SVG driver hook state is shared across all callback invocations; since our state is lightweight + // we store it in the glyph instead. + *state = nullptr; + + return FT_Err_Ok; +} + +// static +void LLFontFreeTypeSvgRenderer::OnFree(FT_Pointer* state) +{ +} + +// static +void LLFontFreeTypeSvgRenderer::OnDataFinalizer(void* objectp) +{ + FT_GlyphSlot glyph_slot = static_cast<FT_GlyphSlot>(objectp); + + LLSvgRenderData* pData = static_cast<LLSvgRenderData*>(glyph_slot->generic.data); + glyph_slot->generic.data = nullptr; + glyph_slot->generic.finalizer = nullptr; + delete(pData); +} + +//static +FT_Error LLFontFreeTypeSvgRenderer::OnPresetGlypthSlot(FT_GlyphSlot glyph_slot, FT_Bool cache, FT_Pointer*) +{ + FT_SVG_Document document = static_cast<FT_SVG_Document>(glyph_slot->other); + + llassert(!glyph_slot->generic.data || !cache || glyph_slot->glyph_index == ((LLSvgRenderData*)glyph_slot->generic.data)->GlyphIndex); + if (!glyph_slot->generic.data) + { + glyph_slot->generic.data = new LLSvgRenderData(); + glyph_slot->generic.finalizer = LLFontFreeTypeSvgRenderer::OnDataFinalizer; + } + LLSvgRenderData* datap = static_cast<LLSvgRenderData*>(glyph_slot->generic.data); + if (!cache) + { + datap->GlyphIndex = glyph_slot->glyph_index; + datap->Error = FT_Err_Ok; + } + + // NOTE: nsvgParse modifies the input string so we need a temporary copy + llassert(!datap->pNSvgImage || cache); + if (!datap->pNSvgImage) + { + char* document_buffer = new char[document->svg_document_length + 1]; + memcpy(document_buffer, document->svg_document, document->svg_document_length); + document_buffer[document->svg_document_length] = '\0'; + + datap->pNSvgImage = nsvgParse(document_buffer, "px", 0.); + + delete[] document_buffer; + } + + if (!datap->pNSvgImage) + { + datap->Error = FT_Err_Invalid_SVG_Document; + return FT_Err_Invalid_SVG_Document; + } + + // We don't (currently) support transformations so test for an identity rotation matrix + zero translation + if (document->transform.xx != 1 << 16 || document->transform.yx != 0 || + document->transform.xy != 0 || document->transform.yy != 1 << 16 || + document->delta.x > 0 || document->delta.y > 0) + { + datap->Error = FT_Err_Unimplemented_Feature; + return FT_Err_Unimplemented_Feature; + } + + float svg_width = datap->pNSvgImage->width; + float svg_height = datap->pNSvgImage->height; + if (svg_width == 0.f || svg_height == 0.f) + { + svg_width = document->units_per_EM; + svg_height = document->units_per_EM; + } + + float svg_x_scale = (float)document->metrics.x_ppem / floorf(svg_width); + float svg_y_scale = (float)document->metrics.y_ppem / floorf(svg_height); + float svg_scale = llmin(svg_x_scale, svg_y_scale); + datap->Scale = svg_scale; + + glyph_slot->bitmap.width = floorf(svg_width) * svg_scale; + glyph_slot->bitmap.rows = floorf(svg_height) * svg_scale; + glyph_slot->bitmap_left = (document->metrics.x_ppem - glyph_slot->bitmap.width) / 2; + glyph_slot->bitmap_top = glyph_slot->face->size->metrics.ascender / 64.f; + glyph_slot->bitmap.pitch = glyph_slot->bitmap.width * 4; + glyph_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + + /* Copied as-is from fcft (MIT license) */ + + // Compute all the bearings and set them correctly. The outline is scaled already, we just need to use the bounding box. + float horiBearingX = 0.; + float horiBearingY = -glyph_slot->bitmap_top; + + // XXX parentheses correct? + float vertBearingX = glyph_slot->metrics.horiBearingX / 64.0f - glyph_slot->metrics.horiAdvance / 64.0f / 2; + float vertBearingY = (glyph_slot->metrics.vertAdvance / 64.0f - glyph_slot->metrics.height / 64.0f) / 2; + + // Do conversion in two steps to avoid 'bad function cast' warning + glyph_slot->metrics.width = glyph_slot->bitmap.width * 64; + glyph_slot->metrics.height = glyph_slot->bitmap.rows * 64; + glyph_slot->metrics.horiBearingX = horiBearingX * 64; + glyph_slot->metrics.horiBearingY = horiBearingY * 64; + glyph_slot->metrics.vertBearingX = vertBearingX * 64; + glyph_slot->metrics.vertBearingY = vertBearingY * 64; + if (glyph_slot->metrics.vertAdvance == 0) + { + glyph_slot->metrics.vertAdvance = glyph_slot->bitmap.rows * 1.2f * 64; + } + + return FT_Err_Ok; +} + +// static +FT_Error LLFontFreeTypeSvgRenderer::OnRender(FT_GlyphSlot glyph_slot, FT_Pointer*) +{ + LLSvgRenderData* datap = static_cast<LLSvgRenderData*>(glyph_slot->generic.data); + llassert(FT_Err_Ok == datap->Error); + if (FT_Err_Ok != datap->Error) + { + return datap->Error; + } + + // Render to glyph bitmap + NSVGrasterizer* nsvgRasterizer = nsvgCreateRasterizer(); + nsvgRasterize(nsvgRasterizer, datap->pNSvgImage, 0, 0, datap->Scale, glyph_slot->bitmap.buffer, glyph_slot->bitmap.width, glyph_slot->bitmap.rows, glyph_slot->bitmap.pitch); + nsvgDeleteRasterizer(nsvgRasterizer); + nsvgDelete(datap->pNSvgImage); + datap->pNSvgImage = nullptr; + + // Convert from RGBA to BGRA + U32* pixel_buffer = (U32*)glyph_slot->bitmap.buffer; U8* byte_buffer = glyph_slot->bitmap.buffer; + for (size_t y = 0, h = glyph_slot->bitmap.rows; y < h; y++) + { + for (size_t x = 0, w = glyph_slot->bitmap.pitch / 4; x < w; x++) + { + size_t pixel_idx = y * w + x; + size_t byte_idx = pixel_idx * 4; + U8 alpha = byte_buffer[byte_idx + 3]; + // Store as ARGB (*TODO - do we still have to care about endianness?) + pixel_buffer[y * w + x] = alpha << 24 | (byte_buffer[byte_idx] * alpha / 0xFF) << 16 | (byte_buffer[byte_idx + 1] * alpha / 0xFF) << 8 | (byte_buffer[byte_idx + 2] * alpha / 0xFF); + } + } + + glyph_slot->format = FT_GLYPH_FORMAT_BITMAP; + glyph_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + return FT_Err_Ok; +} diff --git a/indra/llrender/llfontfreetypesvg.h b/indra/llrender/llfontfreetypesvg.h new file mode 100644 index 0000000000..b5f541991a --- /dev/null +++ b/indra/llrender/llfontfreetypesvg.h @@ -0,0 +1,54 @@ +/** + * @file llfontfreetypesvg.h + * @brief Freetype font library SVG glyph rendering + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include <ft2build.h> +#include FT_TYPES_H +#include FT_MODULE_H +#include FT_OTSVG_H + + // See https://freetype.org/freetype2/docs/reference/ft2-svg_fonts.html +class LLFontFreeTypeSvgRenderer +{ +public: + // Called when the very first OT-SVG glyph is rendered (across the entire lifetime of our FT_Library object) + static FT_Error OnInit(FT_Pointer* state); + + // Called when the ot-svg module is being freed (but only called if the init hook was called previously) + static void OnFree(FT_Pointer* state); + + // Called to preset the glyph slot, twice per glyph: + // - when FT_Load_Glyph needs to preset the glyph slot (with cache == false) + // - right before the svg module calls the render callback hook. (with cache == true) + static FT_Error OnPresetGlypthSlot(FT_GlyphSlot glyph_slot, FT_Bool cache, FT_Pointer* state); + + // Called to render an OT-SVG glyph (right after the preset hook OnPresetGlypthSlot was called with cache set to TRUE) + static FT_Error OnRender(FT_GlyphSlot glyph_slot, FT_Pointer* state); + + // Called to deallocate our per glyph slot data + static void OnDataFinalizer(void* objectp); +}; |