diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp index d79f59448e277439f175361414ff46dc50b77149..4796d65ea4cefc207c9af4952ea136aa52c62f4f 100644 --- a/src/libnrtype/Layout-TNG-Output.cpp +++ b/src/libnrtype/Layout-TNG-Output.cpp @@ -848,6 +848,38 @@ Geom::PathVector Layout::convertToCurves(iterator const &from_glyph, iterator co return curve; } +Geom::PathVector Layout::convertToSVG(iterator const &from_glyph, iterator const &to_glyph, + std::vector> &svgOut) const +{ + Geom::PathVector curve; + + for (int glyph_index = from_glyph._glyph_index; glyph_index < to_glyph._glyph_index; glyph_index++) { + Span const &span = _glyphs[glyph_index].span(this); + Geom::Affine glyph_matrix = _glyphs[glyph_index].transform(*this); + auto glyph_id = _glyphs[glyph_index].glyph; + auto &font = span.font; + auto glyph = font->LoadGlyph(glyph_id); + if (!glyph) { + continue; + } + if (font->FontHasSVG()) { + auto svg = font->GlyphSvg(glyph_id); + if (!svg.empty()) { + auto scale = 1.0 / font->GetDesignUnits(); + svgOut.push_back({std::move(svg), Geom::Scale(scale, -scale) * glyph_matrix}); + } + continue; + } + Geom::PathVector const *pathv = span.font->PathVector(glyph_id); + if (pathv) { + Geom::PathVector pathv_trans = *pathv * glyph_matrix; + pathvector_append(curve, std::move(pathv_trans)); + } + } + + return curve; +} + void Layout::transform(Geom::Affine const &transform) { // this is all massively oversimplified diff --git a/src/libnrtype/Layout-TNG.h b/src/libnrtype/Layout-TNG.h index 4ac69eb4ecb236e30e1fa00439dd0bfe74776b52..b7b025c8ee8bc8a2ba1549cbb50095d74e6bedbd 100644 --- a/src/libnrtype/Layout-TNG.h +++ b/src/libnrtype/Layout-TNG.h @@ -415,6 +415,8 @@ public: outlines. */ Geom::PathVector convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const; + Geom::PathVector convertToSVG(iterator const &from_glyph, iterator const &to_glyph, + std::vector> &svgOut) const; Geom::PathVector convertToCurves() const; /** Apply the given transform to all the output presently stored in diff --git a/src/libnrtype/font-instance.cpp b/src/libnrtype/font-instance.cpp index 58235e59eadaac5064cdd1f3de7705a15cc6a7aa..18232ad607e5f6810c5f57955c21bb0cfd31e14d 100644 --- a/src/libnrtype/font-instance.cpp +++ b/src/libnrtype/font-instance.cpp @@ -718,6 +718,15 @@ Inkscape::Pixbuf const *FontInstance::PixBuf(unsigned int glyph_id) return pixbuf; } +std::string FontInstance::GlyphSvg(unsigned int glyph_id) +{ + auto glyph_iter = data->openTypeSVGGlyphs.find(glyph_id); + if (glyph_iter == data->openTypeSVGGlyphs.end()) { + return ""; + } + return data->openTypeSVGData[glyph_iter->second.entry_index]; +} + double FontInstance::Advance(unsigned int glyph_id, bool vertical) { auto g = LoadGlyph(glyph_id); diff --git a/src/libnrtype/font-instance.h b/src/libnrtype/font-instance.h index 3fbc601a1e3feba7856c74cccf759bd4098c11be..a09b791675f9e8a4c8126606d9ff09ddfe1d839a 100644 --- a/src/libnrtype/font-instance.h +++ b/src/libnrtype/font-instance.h @@ -83,6 +83,8 @@ public: // are lazy-loaded but immutable once loaded. They are guaranteed to be in Cairo pixel format. Inkscape::Pixbuf const *PixBuf(unsigned int glyph_id); + std::string GlyphSvg(unsigned int glyph_id); + // Horizontal advance if 'vertical' is false, vertical advance if true. double Advance(unsigned int glyph_id, bool vertical); diff --git a/src/path-chemistry.cpp b/src/path-chemistry.cpp index bc2760627a9e23f8cb088af5f1a3a48f54388a84..f3732e8cc593965d008685c89764ffd12cb929e8 100644 --- a/src/path-chemistry.cpp +++ b/src/path-chemistry.cpp @@ -518,101 +518,130 @@ void Inkscape::convert_text_to_curves(SPDocument *doc) sp_item_list_to_curves(items, selected, to_select); } -Inkscape::XML::Node * -sp_selected_item_to_curved_repr(SPItem *item, guint32 /*text_grouping_policy*/) +Inkscape::XML::Node *sp_text_to_curve_repr(SPItem *item) { - if (!item) - return nullptr; - Inkscape::XML::Document *xml_doc = item->getRepr()->document(); + auto target_doc = item->document; + + // Special treatment for text: convert each glyph to separate path, then group the paths + auto layout = te_get_layout(item); + + // Save original text for accessibility. + Glib::ustring original_text = sp_te_get_string_multiline(item, layout->begin(), layout->end()); + + SPObject *prev_parent = nullptr; + std::vector> curves; + + Inkscape::XML::Node *result = xml_doc->createElement("svg:g"); + // temporary add the group to doc, some of the transformation logic expects object to be within document tree + item->parent->addChild(result); + SPItem *tmpParent = cast(target_doc->getObjectByRepr(result)); + tmpParent->set_i2d_affine(item->i2dt_affine()); + + bool need_group = false; + Inkscape::Text::Layout::iterator iter = layout->begin(); + while (iter != layout->end()) { + Inkscape::Text::Layout::iterator iter_next = iter; + iter_next.nextGlyph(); // iter_next is one glyph ahead from iter + if (iter == iter_next) + break; + + /* This glyph's style */ + SPObject *pos_obj = nullptr; + layout->getSourceOfCharacter(iter, &pos_obj); + if (!pos_obj) // no source for glyph, abort + break; + while (is(pos_obj) && pos_obj->parent) { + pos_obj = pos_obj->parent; // SPStrings don't have style + } - if (is(item) || is(item)) { + // get path from iter to iter_next: + std::vector> svgSnippets; + auto curve = layout->convertToSVG(iter, iter_next, svgSnippets); + iter = iter_next; // shift to next glyph - // Special treatment for text: convert each glyph to separate path, then group the paths - auto layout = te_get_layout(item); - - // Save original text for accessibility. - Glib::ustring original_text = sp_te_get_string_multiline(item, layout->begin(), layout->end()); - - SPObject *prev_parent = nullptr; - std::vector> curves; - - Inkscape::Text::Layout::iterator iter = layout->begin(); - do { - Inkscape::Text::Layout::iterator iter_next = iter; - iter_next.nextGlyph(); // iter_next is one glyph ahead from iter - if (iter == iter_next) - break; - - /* This glyph's style */ - SPObject *pos_obj = nullptr; - layout->getSourceOfCharacter(iter, &pos_obj); - if (!pos_obj) // no source for glyph, abort - break; - while (is(pos_obj) && pos_obj->parent) { - pos_obj = pos_obj->parent; // SPStrings don't have style - } + if (curve.empty() && svgSnippets.empty()) { // whitespace glyph? + continue; + } - // get path from iter to iter_next: - auto curve = layout->convertToCurves(iter, iter_next); - iter = iter_next; // shift to next glyph - if (curve.empty()) { // whitespace glyph? + for (auto &svgSnippet : svgSnippets) { + auto doc = SPDocument::createNewDocFromMem(svgSnippet.first, false); + if (!doc) { continue; } + auto transform = svgSnippet.second * item->i2doc_affine(); + target_doc->import(*doc, result, nullptr, transform, nullptr, SPDocument::ImportRoot::UngroupSingle, + SPDocument::ImportLayersMode::ToGroup); + need_group = true; + } - // Create a new path for each span to group glyphs into - // which preserves styles such as paint-order - if (!prev_parent || prev_parent != pos_obj) { - // Record the style for the dying tspan tree (see sp_style_merge_from_dying_parent in style.cpp) - auto style = pos_obj->style; - for (auto sp = pos_obj->parent; sp && sp != item; sp = sp->parent) { - style->merge(sp->style); - } - curves.emplace_back(curve, style); - } else { - for (auto &path : curve) { - curves.back().first.push_back(path); - } - } + if (curve.empty()) { + continue; + } - prev_parent = pos_obj; - if (iter == layout->end()) - break; - - } while (true); - - if (curves.empty()) - return nullptr; - - Inkscape::XML::Node *result = curves.size() > 1 ? xml_doc->createElement("svg:g") : nullptr; - SPStyle *result_style = new SPStyle(item->document); - - for (auto &[pathv, style] : curves) { - Glib::ustring glyph_style = style->writeIfDiff(item->style); - auto new_path = xml_doc->createElement("svg:path"); - new_path->setAttributeOrRemoveIfEmpty("style", glyph_style); - new_path->setAttribute("d", sp_svg_write_path(pathv)); - if (curves.size() == 1) { - result = new_path; - result_style->merge(style); - } else { - result->appendChild(new_path); - Inkscape::GC::release(new_path); + // Create a new path for each span to group glyphs into + // which preserves styles such as paint-order + if (!prev_parent || prev_parent != pos_obj) { + // Record the style for the dying tspan tree (see sp_style_merge_from_dying_parent in style.cpp) + auto style = pos_obj->style; + for (auto sp = pos_obj->parent; sp && sp != item; sp = sp->parent) { + style->merge(sp->style); + } + curves.emplace_back(curve, style); + } else { + for (auto &path : curve) { + curves.back().first.push_back(path); } } - result_style->merge(item->style); - Glib::ustring css = result_style->writeIfDiff(item->parent ? item->parent->style : nullptr); - delete result_style; + prev_parent = pos_obj; + } + result->parent()->removeChild(result); + + if (curves.empty() && !need_group) { + Inkscape::GC::release(result); + return nullptr; + } - Inkscape::copy_object_properties(result, item->getRepr()); - result->setAttributeOrRemoveIfEmpty("style", css); - result->setAttributeOrRemoveIfEmpty("transform", item->getRepr()->attribute("transform")); + auto result_style = std::make_unique(item->document); - if (!original_text.empty()) { - result->setAttribute("aria-label", original_text); + for (auto &[pathv, style] : curves) { + Glib::ustring glyph_style = style->writeIfDiff(item->style); + auto new_path = xml_doc->createElement("svg:path"); + new_path->setAttributeOrRemoveIfEmpty("style", glyph_style); + new_path->setAttribute("d", sp_svg_write_path(pathv)); + if (curves.size() == 1 && !need_group) { + Inkscape::GC::release(result); + result = new_path; + result_style->merge(style); + } else { + result->appendChild(new_path); + Inkscape::GC::release(new_path); } - return result; + } + + result_style->merge(item->style); + Glib::ustring css = result_style->writeIfDiff(item->parent ? item->parent->style : nullptr); + + Inkscape::copy_object_properties(result, item->getRepr()); + result->setAttributeOrRemoveIfEmpty("style", css); + result->setAttributeOrRemoveIfEmpty("transform", item->getRepr()->attribute("transform")); + + if (!original_text.empty()) { + result->setAttribute("aria-label", original_text); + } + return result; +} + +Inkscape::XML::Node *sp_selected_item_to_curved_repr(SPItem *item, guint32 /*text_grouping_policy*/) +{ + if (!item) + return nullptr; + + Inkscape::XML::Document *xml_doc = item->getRepr()->document(); + + if (is(item) || is(item)) { + return sp_text_to_curve_repr(item); } Geom::PathVector curve; @@ -646,7 +675,6 @@ sp_selected_item_to_curved_repr(SPItem *item, guint32 /*text_grouping_policy*/) return repr; } - void ObjectSet::pathReverse() { diff --git a/testfiles/cli_tests/CMakeLists.txt b/testfiles/cli_tests/CMakeLists.txt index 2ec3ba88773e262cf8aa672af68def56c352a556..f653f249b6c8d88efc9a75b8ca346371d2b6e6b9 100644 --- a/testfiles/cli_tests/CMakeLists.txt +++ b/testfiles/cli_tests/CMakeLists.txt @@ -1011,7 +1011,15 @@ add_cli_test(action_test_multiline_anchoring PARAMETERS --actions=select-by-id:grouped_text$transform-translate:10,0$transform-translate:-10,0$export-filename:multiline-anchoring_out.svg$export-plain-svg$export-do EXPECTED_FILES multiline-anchoring_out.svg TEST_SCRIPT match_regex_fail.sh multiline-anchoring_out.svg "(x=\"[3-9][0-9]{2}\")|(x=\"[12][0-9]{3}\")|(y=\"[0-9]{2}\")|(y=\"1[0-9]{2}\")") - +# test text to path with SVG in OTF fonts, at the end of test erase all text elements to make sure it doesn't pass by +# rendering the nonconverted text. +add_cli_test(actions-svginot-text-topath + INPUT_FILENAME ../../rendering_tests/text-gzipped-svg-glyph.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-gzipped-svg-glyph.png + FUZZ_PERCENTAGE 3 +) # The export area type should be the last set value add_cli_test(actions-export-area-drawing-then-page diff --git a/testfiles/cli_tests/check_output.sh b/testfiles/cli_tests/check_output.sh index baae1ffc41524529ead178d2221187a5b3f393d7..4dce1d7e4c27b3c03f08dfa9a952ff053203ab24 100755 --- a/testfiles/cli_tests/check_output.sh +++ b/testfiles/cli_tests/check_output.sh @@ -59,7 +59,7 @@ if [ -n "${REFERENCE_FILENAME}" ]; then fi # compare files - if ! compare ${FUZZ} -metric AE ${PNG_FILENAME} ${PNG_REFERENCE} ${PNG_COMPARE}; then + if ! compare ${FUZZ} -metric AE ${PNG_REFERENCE} ${PNG_FILENAME} ${PNG_COMPARE}; then echo && echo "Error: Comparison failed." exit 1 fi