diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp index 491373e43b9e691ae09da00533396a1f12eecab5..9e4e0816e9ec38f278e0c62076b881235eb86dc8 100644 --- a/src/libnrtype/Layout-TNG-Output.cpp +++ b/src/libnrtype/Layout-TNG-Output.cpp @@ -843,6 +843,24 @@ SPCurve Layout::convertToCurves(iterator const &from_glyph, iterator const &to_g return curve; } +bool Layout::isSvgGlyph(iterator const &iter) const +{ + Glyph const &glyph = _glyphs[iter._glyph_index]; + Span const &span = glyph.span(this); + return span.font->isSvgGlyph(glyph.glyph); +} + +std::optional Layout::convertToSvg(iterator const &iter, Geom::Affine &glyph_matrix) const +{ + auto glyph_index = iter._glyph_index; + Span const &span = _glyphs[glyph_index].span(this); + + // Replace this with glyph->transform(...) when that refactoring lands + _getGlyphTransformMatrix(glyph_index, &glyph_matrix); + glyph_matrix = Geom::Scale(span.font->GetDesignUnits()).inverse() * glyph_matrix; + return span.font->SvgGlyph(_glyphs[glyph_index].glyph); +} + 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 f90676aceb3d35c6df33f29fe457d54acc84acb2..aeb72e95fe3149fefa355b443dd00e8b091670c9 100644 --- a/src/libnrtype/Layout-TNG.h +++ b/src/libnrtype/Layout-TNG.h @@ -418,6 +418,12 @@ public: SPCurve convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const; SPCurve convertToCurves() const; + // Return true if the given glyph is an SVG glyph + bool isSvgGlyph(iterator const &glyph) const; + + // Get the glyph as an SVG document instead of a curve. + std::optional convertToSvg(iterator const &iter, Geom::Affine &glyph_matrix) const; + /** Apply the given transform to all the output presently stored in this object. This only transforms the glyph positions, The glyphs themselves will not be transformed. */ diff --git a/src/libnrtype/font-instance.cpp b/src/libnrtype/font-instance.cpp index c6e4091c28db6cf7db51ed8a7c46080909a58192..86df852281603bea6f9c6ceea6fe442dc6a8b2a9 100644 --- a/src/libnrtype/font-instance.cpp +++ b/src/libnrtype/font-instance.cpp @@ -596,17 +596,51 @@ Inkscape::Pixbuf const *FontInstance::PixBuf(unsigned int glyph_id) return nullptr; // out of range } - // Glyphs are layed out in the +x, -y quadrant (assuming viewBox origin is 0,0). - // We need to shift the viewBox by the height inorder to generate pixbuf! - // To do: glyphs must draw overflow so we actually need larger pixbuf! - // To do: Error handling. - if (glyph_iter->second.pixbuf) { return glyph_iter->second.pixbuf.get(); // already loaded } + if (auto svg = SvgGlyph(glyph_id)) { + // Finally create pixbuf! + auto pixbuf = Inkscape::Pixbuf::create_from_buffer(svg->raw()); + + // Ensure exists in cairo format before locking it down. (Rendering code requires cairo format.) + pixbuf->ensurePixelFormat(Inkscape::Pixbuf::PF_CAIRO); + + // And cache it. + glyph_iter->second.pixbuf.reset(pixbuf); + + return pixbuf; + } + return nullptr; +} + +/** + * Return true if this glyph has an SVG glyph + */ +bool FontInstance::isSvgGlyph(int glyph_id) const +{ + auto glyph_iter = data->openTypeSVGGlyphs.find(glyph_id); + return glyph_iter != data->openTypeSVGGlyphs.end(); +} + +/** + * Load the glyph's SVG data as an SVG document. + */ +std::optional FontInstance::SvgGlyph(int glyph_id) const +{ + auto glyph_iter = data->openTypeSVGGlyphs.find(glyph_id); + if (glyph_iter == data->openTypeSVGGlyphs.end()) { + return {}; // out of range + } + Glib::ustring svg = data->openTypeSVGData[glyph_iter->second.entry_index]; + // Glyphs are layed out in the +x, -y quadrant (assuming viewBox origin is 0,0). + // We need to shift the viewBox by the height inorder to generate pixbuf! + // To do: glyphs must draw overflow so we actually need larger pixbuf! + // To do: Error handling. + // Create new viewbox which determines pixbuf size. Glib::ustring viewbox("viewBox=\"0 "); viewbox += std::to_string(-_design_units); @@ -694,16 +728,7 @@ Inkscape::Pixbuf const *FontInstance::PixBuf(unsigned int glyph_id) auto regex2 = Glib::Regex::create(pattern, Glib::Regex::CompileFlags::OPTIMIZE); svg = regex2->replace(svg, 0, "\\1", static_cast(0)); - // Finally create pixbuf! - auto pixbuf = Inkscape::Pixbuf::create_from_buffer(svg.raw()); - - // Ensure exists in cairo format before locking it down. (Rendering code requires cairo format.) - pixbuf->ensurePixelFormat(Inkscape::Pixbuf::PF_CAIRO); - - // And cache it. - glyph_iter->second.pixbuf.reset(pixbuf); - - return pixbuf; + return svg; } double FontInstance::Advance(unsigned int glyph_id, bool vertical) diff --git a/src/libnrtype/font-instance.h b/src/libnrtype/font-instance.h index 95298c57ef5821c9c3f78bbb260b58ee3726b3f6..237f59d954bd1c6af4f60c092d4da8b0d817bdb0 100644 --- a/src/libnrtype/font-instance.h +++ b/src/libnrtype/font-instance.h @@ -83,6 +83,12 @@ 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); + // Return true if the glyph has SVG data. + bool isSvgGlyph(int glyph_id) const; + + // Return the SVG document of a glyph is available as an SVG glyph. + std::optional SvgGlyph(int glyph_id) const; + // 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 cbd8158cd8f33307c38f1647a664403cb08f61ab..157f2fce70b5e6f0b55c3cc39c1e966a8aef7d5b 100644 --- a/src/path-chemistry.cpp +++ b/src/path-chemistry.cpp @@ -531,7 +531,7 @@ sp_selected_item_to_curved_repr(SPItem *item, guint32 /*text_grouping_policy*/) Glib::ustring original_text = sp_te_get_string_multiline(item, layout->begin(), layout->end()); SPObject *prev_parent = nullptr; - std::vector> curves; + std::vector>, SPStyle *>> curves; Inkscape::Text::Layout::iterator iter = layout->begin(); do { @@ -549,6 +549,17 @@ sp_selected_item_to_curved_repr(SPItem *item, guint32 /*text_grouping_policy*/) pos_obj = pos_obj->parent; // SPStrings don't have style } + if (layout->isSvgGlyph(iter)) { + Geom::Affine affine; + if (auto svg = layout->convertToSvg(iter, affine)) { + auto doc = SPDocument::createNewDocFromMem(svg->raw(), false); + doc->getRoot()->set_item_transform(affine); + curves.emplace_back(std::move(doc), nullptr); + iter = iter_next; // shift to next glyph + continue; + } + } + // get path from iter to iter_next: auto curve = layout->convertToCurves(iter, iter_next); iter = iter_next; // shift to next glyph @@ -558,7 +569,7 @@ sp_selected_item_to_curved_repr(SPItem *item, guint32 /*text_grouping_policy*/) // 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) { + if (!prev_parent || prev_parent != pos_obj || !std::holds_alternative(curves.back().first)) { // 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) { @@ -567,7 +578,7 @@ sp_selected_item_to_curved_repr(SPItem *item, guint32 /*text_grouping_policy*/) curves.emplace_back(curve.get_pathvector(), style); } else { for (auto &path : curve.get_pathvector()) { - curves.back().first.push_back(path); + std::get(curves.back().first).push_back(path); } } @@ -583,17 +594,51 @@ sp_selected_item_to_curved_repr(SPItem *item, guint32 /*text_grouping_policy*/) 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); + for (auto &[data, style] : curves) { + if (std::holds_alternative(data)) { + auto &pathv = std::get(data); + 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); + } } else { - result->appendChild(new_path); - Inkscape::GC::release(new_path); + // SVG Gylph + auto &doc = std::get>(data); + auto root = doc->getRoot(); + auto tr = root->c2p * root->transform; + + // Not copying defs + + Inkscape::XML::Node *glyph_group = nullptr; + auto children = root->item_list(); + if (children.size() == 1) { + SPItem *item = *children.begin(); + glyph_group = item->getRepr()->duplicate(xml_doc); + glyph_group->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(tr * item->transform)); + } else if (children.size() > 1) { + glyph_group = xml_doc->createElement("svg:g"); + glyph_group->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(tr)); + for (auto item : children) { + Inkscape::XML::Node *obj_copy = item->getRepr()->duplicate(xml_doc); + glyph_group->appendChild(obj_copy); + Inkscape::GC::release(obj_copy); + } + } + if (glyph_group) { + if (result) { + result->appendChild(glyph_group); + Inkscape::GC::release(glyph_group); + } else { + result = glyph_group; + } + } } }