diff --git a/src/display/drawing-text.cpp b/src/display/drawing-text.cpp index 7a19a29cfa0bb0e3e4f8214fe5c2467df08af00c..c1dbabc445a451132c662911b9f69a228dd9c89f 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 18232ad607e5f6810c5f57955c21bb0cfd31e14d..935504269bbda6291a02af3703ea9775ee7da726 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 f653f249b6c8d88efc9a75b8ca346371d2b6e6b9..58c0d49c8f816c34824d61252e37d2b33d5006d8 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 62024d1dd88d3a42beea93542dfd7bf3abe432ae..942d009e77571f86b4b770f926e96dc83f461cf0 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 Binary files /dev/null and b/testfiles/rendering_tests/expected_rendering/text-svg-glyph-custom.png differ diff --git a/testfiles/rendering_tests/svginotf/build.py b/testfiles/rendering_tests/svginotf/build.py new file mode 100644 index 0000000000000000000000000000000000000000..39fac888336333f30774c8a3387786480a0a91b0 --- /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 0000000000000000000000000000000000000000..6aacb3534b3007513e1d00832bd19388138695a7 --- /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 0000000000000000000000000000000000000000..39c3535351ba864dfaefa06fb66e9ca74e940aaf --- /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 0000000000000000000000000000000000000000..33455fe749f03f3d4c7dceb5fd3527ac0ff8d4b9 --- /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 0000000000000000000000000000000000000000..b363c02bdfe100e99c50df7b6083dd760870fc95 --- /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 0000000000000000000000000000000000000000..bc1b49c36eb4dece0cae0d26178056d67080bebd --- /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 Binary files /dev/null and b/testfiles/rendering_tests/svginotf/svginotf_testfont1.otf differ 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 0000000000000000000000000000000000000000..0f14e1a089fb6118bfecc2e90808f6bd4d421055 --- /dev/null +++ b/testfiles/rendering_tests/text-svg-glyph-custom.svg @@ -0,0 +1,61 @@ + + + + + + + + + abcd + +