From 4ae9b55f95ba02c68c9b15a0a0e90d77f34b5d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C4=81rlis=20Se=C5=86ko?= Date: Mon, 15 Sep 2025 21:47:41 +0300 Subject: [PATCH] Read SVG OTF font drawing box from glyph description. Create a custom test font for checkign specific SVG/OTF font edge cases. Script for updating font file needs to be run manually. --- src/display/drawing-text.cpp | 2 +- src/libnrtype/font-instance.cpp | 66 +++++++------- testfiles/cli_tests/CMakeLists.txt | 7 ++ testfiles/rendering_tests/CMakeLists.txt | 2 + .../text-svg-glyph-custom.png | Bin 0 -> 5602 bytes testfiles/rendering_tests/svginotf/build.py | 83 ++++++++++++++++++ .../rendering_tests/svginotf/glyphs/a.svg | 23 +++++ .../rendering_tests/svginotf/glyphs/b.svg | 23 +++++ .../rendering_tests/svginotf/glyphs/c.svg | 25 ++++++ .../rendering_tests/svginotf/glyphs/d.svg | 26 ++++++ .../svginotf/glyphs/reference.svg | 57 ++++++++++++ .../svginotf/svginotf_testfont1.otf | Bin 0 -> 3232 bytes .../rendering_tests/text-svg-glyph-custom.svg | 61 +++++++++++++ 13 files changed, 344 insertions(+), 31 deletions(-) create mode 100644 testfiles/rendering_tests/expected_rendering/text-svg-glyph-custom.png create mode 100644 testfiles/rendering_tests/svginotf/build.py create mode 100644 testfiles/rendering_tests/svginotf/glyphs/a.svg create mode 100644 testfiles/rendering_tests/svginotf/glyphs/b.svg create mode 100644 testfiles/rendering_tests/svginotf/glyphs/c.svg create mode 100644 testfiles/rendering_tests/svginotf/glyphs/d.svg create mode 100644 testfiles/rendering_tests/svginotf/glyphs/reference.svg create mode 100644 testfiles/rendering_tests/svginotf/svginotf_testfont1.otf create mode 100644 testfiles/rendering_tests/text-svg-glyph-custom.svg diff --git a/src/display/drawing-text.cpp b/src/display/drawing-text.cpp index 7a19a29cfa..c1dbabc445 100644 --- a/src/display/drawing-text.cpp +++ b/src/display/drawing-text.cpp @@ -620,7 +620,7 @@ unsigned DrawingText::_renderItem(DrawingContext &dc, RenderContext &rc, Geom::I double scale = g->design_units; if (scale <= 0) scale = 1000; Inkscape::DrawingContext::Save save(dc); - dc.translate(0, 1); + dc.translate(g->bbox_draw.corner(3)); dc.scale(1.0 / scale, -1.0 / scale); dc.setSource(g->pixbuf->getSurfaceRaw(), 0, 0); dc.paint(1); diff --git a/src/libnrtype/font-instance.cpp b/src/libnrtype/font-instance.cpp index 18232ad607..935504269b 100644 --- a/src/libnrtype/font-instance.cpp +++ b/src/libnrtype/font-instance.cpp @@ -14,8 +14,9 @@ # include "config.h" // only include where actually required! #endif -#include #include +#include +#include #ifndef PANGO_ENABLE_ENGINE #define PANGO_ENABLE_ENGINE @@ -29,20 +30,19 @@ #include FT_GLYPH_H #include FT_MULTIPLE_MASTERS_H -#include -#include +#include +#include #include #include - -#include - -#include <2geom/pathvector.h> +#include +#include #include <2geom/path-sink.h> +#include <2geom/pathvector.h> + +#include "display/cairo-utils.h" // Inkscape::Pixbuf #include "libnrtype/font-glyph.h" #include "libnrtype/font-instance.h" -#include "display/cairo-utils.h" // Inkscape::Pixbuf - /* * Outline extraction */ @@ -619,14 +619,18 @@ Inkscape::Pixbuf const *FontInstance::PixBuf(unsigned int glyph_id) Glib::ustring svg = data->openTypeSVGData[glyph_iter->second.entry_index]; + auto glyphBox = BBoxDraw(glyph_id) * Geom::Scale(_design_units); + // Don't use Rect.roundOutwards/Inwards, most dimensions in font description are in design_units which are integers. + // Multiplying by design_units should give close to original integers and should use traditional rounding. + Geom::IntRect box(std::lround(glyphBox.left()), std::lround(glyphBox.top()), std::lround(glyphBox.right()), + std::lround(glyphBox.bottom())); + // Create new viewbox which determines pixbuf size. - Glib::ustring viewbox("viewBox=\"0 "); - viewbox += std::to_string(-_design_units); - viewbox += " "; - viewbox += std::to_string(_design_units*2); // Noto emoji leaks outside of em-box. - viewbox += " "; - viewbox += std::to_string(_design_units*2); - viewbox += "\""; + std::ostringstream svg_stream; + svg_stream.imbue(std::locale::classic()); + svg_stream << "viewBox=\"" << box.min().x() << " " << -box.max().y() << " " << box.width() << " " << box.height() + << "\""; + Glib::ustring viewbox = svg_stream.str(); // Search for existing viewbox static auto regex = Glib::Regex::create("viewBox=\"\\s*(\\d*\\.?\\d+)\\s*,?\\s*(\\d*\\.?\\d+)\\s*,?\\s*(\\d+\\.?\\d+)\\s*,?\\s*(\\d+\\.?\\d+)\\s*\"", Glib::Regex::CompileFlags::OPTIMIZE); @@ -640,10 +644,11 @@ Inkscape::Pixbuf const *FontInstance::PixBuf(unsigned int glyph_id) svg = regex->replace_literal(svg, 0, viewbox, static_cast(0)); // Insert group with required transform to map glyph to new viewbox. - double x = std::stod(matchInfo.fetch(1).raw()); - double y = std::stod(matchInfo.fetch(2).raw()); - double w = std::stod(matchInfo.fetch(3).raw()); - double h = std::stod(matchInfo.fetch(4).raw()); + + double x = Glib::Ascii::strtod(matchInfo.fetch(1)); + double y = Glib::Ascii::strtod(matchInfo.fetch(2)); + double w = Glib::Ascii::strtod(matchInfo.fetch(3)); + double h = Glib::Ascii::strtod(matchInfo.fetch(4)); // std::cout << " x: " << x // << " y: " << y // << " w: " << w @@ -658,18 +663,15 @@ Inkscape::Pixbuf const *FontInstance::PixBuf(unsigned int glyph_id) double xtrans = _design_units/w * x; double ytrans = _design_units/h * y; - if (xscale != 1.0 || yscale != 1.0) { - Glib::ustring group = ""; + if (xscale != 1.0 || yscale != 1.0 || xtrans != 0 || ytrans != 0) { + svg_stream.str(""); + svg_stream << ""; + Glib::ustring group = svg_stream.str(); // Insert start group tag after initial - Glib::RefPtr regex = Glib::Regex::create("<\\s*svg.*?>"); + Glib::RefPtr regex = + Glib::Regex::create("<\\s*svg.*?>", Glib::Regex::CompileFlags::DOTALL); regex->match(svg, matchInfo); if (matchInfo.matches()) { int start = -1; @@ -708,6 +710,10 @@ Inkscape::Pixbuf const *FontInstance::PixBuf(unsigned int glyph_id) // Finally create pixbuf! auto pixbuf = Inkscape::Pixbuf::create_from_buffer(svg.raw()); + if (!pixbuf) { + std::cerr << "Bad svg data for glyph " << glyph_id << "\n"; + pixbuf = new Pixbuf(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1)); + } // Ensure exists in cairo format before locking it down. (Rendering code requires cairo format.) pixbuf->ensurePixelFormat(Inkscape::Pixbuf::PF_CAIRO); diff --git a/testfiles/cli_tests/CMakeLists.txt b/testfiles/cli_tests/CMakeLists.txt index f653f249b6..58c0d49c8f 100644 --- a/testfiles/cli_tests/CMakeLists.txt +++ b/testfiles/cli_tests/CMakeLists.txt @@ -1020,6 +1020,13 @@ add_cli_test(actions-svginot-text-topath FUZZYREF_FILENAME ../../rendering_tests/expected_rendering/text-gzipped-svg-glyph.png FUZZ_PERCENTAGE 3 ) +add_cli_test(actions-svginot-text-topath2 + INPUT_FILENAME ../../rendering_tests/text-svg-glyph-custom.svg + PARAMETERS --actions=select-all$object-to-path$select-clear$select-by-element:text$delete + OUTPUT_FILENAME actions-svginot-text-topath-output.png + FUZZYREF_FILENAME ../../rendering_tests/expected_rendering/text-svg-glyph-custom.png + FUZZ_PERCENTAGE 1 +) # The export area type should be the last set value add_cli_test(actions-export-area-drawing-then-page diff --git a/testfiles/rendering_tests/CMakeLists.txt b/testfiles/rendering_tests/CMakeLists.txt index 62024d1dd8..942d009e77 100644 --- a/testfiles/rendering_tests/CMakeLists.txt +++ b/testfiles/rendering_tests/CMakeLists.txt @@ -75,3 +75,5 @@ if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) # .otf font with compressed SVG glyphs add_rendering_test(text-gzipped-svg-glyph FUZZ 0.03) endif() +# custom .otf font with SVG glyphs for testing various edge cases +add_rendering_test(text-svg-glyph-custom FUZZ 0.3) \ No newline at end of file diff --git a/testfiles/rendering_tests/expected_rendering/text-svg-glyph-custom.png b/testfiles/rendering_tests/expected_rendering/text-svg-glyph-custom.png new file mode 100644 index 0000000000000000000000000000000000000000..bea70949249630606a9002a4929ad7de075fb4ef GIT binary patch literal 5602 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 literal 0 HcmV?d00001 diff --git a/testfiles/rendering_tests/svginotf/build.py b/testfiles/rendering_tests/svginotf/build.py new file mode 100644 index 0000000000..39fac88833 --- /dev/null +++ b/testfiles/rendering_tests/svginotf/build.py @@ -0,0 +1,83 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# Requirements and build instructions +# setup venv (consult with python docs for non Linux OS): +# +# python -m venv venv +# source ./venv/bin/activate +# pip install fonttools defcon ufo2ft ufoLib2 + +# glyph indexes/order in config and ids in SVG files are important + +import defcon +import fontTools +import ufo2ft +import fontTools.svgLib.path +from fontTools.ttLib import newTable +from pathlib import Path +from fontTools.ttLib.tables.S_V_G_ import SVGDocument + +OUTPUT = "svginotf_testfont1.otf" +EM_SIZE = 1000 + +ufo_font = defcon.Font() +ufo_font.info.unitPerEm = EM_SIZE +ufo_font.info.familyName = "SVGinOTF testfont1" +ufo_font.info.xHeight = 500 +ufo_font.info.capHeight = 800 +ufo_font.info.ascender = 1000 +ufo_font.info.descender = -200 + +DEFAULT_WIDTH = 500 + +FALLBACK_BOX = 'M 0,0 L 0,800 800,800 800,0 z' +glyphs = [ + # somewhat normal + {'unicode': 0x61, 'name': 'a', 'svg': 'glyphs/a.svg'}, + # tall + {'unicode': 0x62, 'name': 'b', + 'fallback_shape': 'M 0,-1400 L 0,2331 500,2331 500,-1400 z', + 'width': 502, + 'svg': 'glyphs/b.svg'}, + # wide + {'unicode': 0x63, 'name': 'c', + 'fallback_shape': 'M -1276,0 L -1276,920 1750,920 500,0 z', + 'svg': 'glyphs/c.svg'}, + # viewport offset + {'unicode': 0x64, 'name': 'd', + 'fallback_shape': 'M 0,0 L 0,800 500,800 500,0 z', + 'svg': 'glyphs/d.svg'}, +] + +for info in glyphs: + glyph = ufo_font.newGlyph(info['name']) + glyph.unicodes = [info['unicode']] + glyph.width = info.get('width', DEFAULT_WIDTH) + pen = glyph.getPen() + non_color_shape = info.get('fallback_shape', FALLBACK_BOX) + fontTools.svgLib.parse_path(non_color_shape, pen) + +otf = ufo2ft.compileOTF(ufo_font) +svg_table = newTable("SVG ") +svg_table.docList = [] +glyph_order = otf.getGlyphOrder() +print(glyph_order) +index_map = {name: i for i, name in enumerate(glyph_order)} +for info in glyphs: + svg_file_path = info.get('svg', None) + if not svg_file_path: + continue + group_range = info.get('group', None) + svg_text = Path(svg_file_path).read_text() + gid = index_map[info['name']] + gid_min = gid + gid_max = gid + + if group_range: + gid_min = group_range[0] + gid_max = group_range[1] + + svg_table.docList.append(SVGDocument(svg_text, gid_min, gid_max, info.get('compressed', True))) +svg_table.docList.sort(key=lambda x: x.startGlyphID) +otf[svg_table.tableTag] = svg_table +otf.save(OUTPUT) diff --git a/testfiles/rendering_tests/svginotf/glyphs/a.svg b/testfiles/rendering_tests/svginotf/glyphs/a.svg new file mode 100644 index 0000000000..6aacb3534b --- /dev/null +++ b/testfiles/rendering_tests/svginotf/glyphs/a.svg @@ -0,0 +1,23 @@ + + + + + + diff --git a/testfiles/rendering_tests/svginotf/glyphs/b.svg b/testfiles/rendering_tests/svginotf/glyphs/b.svg new file mode 100644 index 0000000000..39c3535351 --- /dev/null +++ b/testfiles/rendering_tests/svginotf/glyphs/b.svg @@ -0,0 +1,23 @@ + + + + + + diff --git a/testfiles/rendering_tests/svginotf/glyphs/c.svg b/testfiles/rendering_tests/svginotf/glyphs/c.svg new file mode 100644 index 0000000000..33455fe749 --- /dev/null +++ b/testfiles/rendering_tests/svginotf/glyphs/c.svg @@ -0,0 +1,25 @@ + + + + + + + diff --git a/testfiles/rendering_tests/svginotf/glyphs/d.svg b/testfiles/rendering_tests/svginotf/glyphs/d.svg new file mode 100644 index 0000000000..b363c02bdf --- /dev/null +++ b/testfiles/rendering_tests/svginotf/glyphs/d.svg @@ -0,0 +1,26 @@ + + + + + diff --git a/testfiles/rendering_tests/svginotf/glyphs/reference.svg b/testfiles/rendering_tests/svginotf/glyphs/reference.svg new file mode 100644 index 0000000000..bc1b49c36e --- /dev/null +++ b/testfiles/rendering_tests/svginotf/glyphs/reference.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + diff --git a/testfiles/rendering_tests/svginotf/svginotf_testfont1.otf b/testfiles/rendering_tests/svginotf/svginotf_testfont1.otf new file mode 100644 index 0000000000000000000000000000000000000000..8a626dd99a0fb195bee809aff3960ffab3d56603 GIT binary patch literal 3232 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 literal 0 HcmV?d00001 diff --git a/testfiles/rendering_tests/text-svg-glyph-custom.svg b/testfiles/rendering_tests/text-svg-glyph-custom.svg new file mode 100644 index 0000000000..0f14e1a089 --- /dev/null +++ b/testfiles/rendering_tests/text-svg-glyph-custom.svg @@ -0,0 +1,61 @@ + + + + + + + + + abcd + + -- GitLab