summaryrefslogtreecommitdiffstats
path: root/src/libnrtype
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnrtype')
-rw-r--r--src/libnrtype/CMakeLists.txt43
-rw-r--r--src/libnrtype/FontFactory.cpp877
-rw-r--r--src/libnrtype/FontFactory.h207
-rw-r--r--src/libnrtype/FontInstance.cpp1016
-rw-r--r--src/libnrtype/Layout-TNG-Compute.cpp2414
-rw-r--r--src/libnrtype/Layout-TNG-Input.cpp241
-rw-r--r--src/libnrtype/Layout-TNG-OutIter.cpp1175
-rw-r--r--src/libnrtype/Layout-TNG-Output.cpp910
-rw-r--r--src/libnrtype/Layout-TNG-Scanline-Maker.h186
-rw-r--r--src/libnrtype/Layout-TNG-Scanline-Makers.cpp196
-rw-r--r--src/libnrtype/Layout-TNG.cpp48
-rw-r--r--src/libnrtype/Layout-TNG.h1207
-rw-r--r--src/libnrtype/OpenTypeUtil.cpp447
-rw-r--r--src/libnrtype/OpenTypeUtil.h117
-rw-r--r--src/libnrtype/font-glyph.h36
-rw-r--r--src/libnrtype/font-instance.h158
-rw-r--r--src/libnrtype/font-lister.cpp1271
-rw-r--r--src/libnrtype/font-lister.h364
-rw-r--r--src/libnrtype/font-style.h44
19 files changed, 10957 insertions, 0 deletions
diff --git a/src/libnrtype/CMakeLists.txt b/src/libnrtype/CMakeLists.txt
new file mode 100644
index 0000000..4a46bd0
--- /dev/null
+++ b/src/libnrtype/CMakeLists.txt
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(nrtype_SRC
+ FontFactory.cpp
+ FontInstance.cpp
+ font-lister.cpp
+ Layout-TNG.cpp
+ Layout-TNG-Compute.cpp
+ Layout-TNG-Input.cpp
+ Layout-TNG-OutIter.cpp
+ Layout-TNG-Output.cpp
+ Layout-TNG-Scanline-Makers.cpp
+ OpenTypeUtil.cpp
+
+ # -------
+ # Headers
+ font-glyph.h
+ font-instance.h
+ font-lister.h
+ font-style.h
+ FontFactory.h
+ Layout-TNG-Scanline-Maker.h
+ Layout-TNG.h
+ OpenTypeUtil.cpp
+)
+
+add_inkscape_lib(nrtype_LIB "${nrtype_SRC}")
+
+# we have circular references between nrtype_LIB and inkscape_base!
+# this workaround prevents undefined references in nrtype_LIB when building static libraries (likely link order problem)
+if(NOT BUILD_SHARED_LIBS)
+ if( WIN32 )
+ target_link_libraries(nrtype_LIB PRIVATE inkscape_base -lGdi32)
+ else()
+ target_link_libraries(nrtype_LIB PRIVATE inkscape_base)
+ endif()
+else()
+ if( WIN32 )
+ target_link_libraries(nrtype_LIB PUBLIC 2Geom::2geom -lGdi32)
+ else()
+ target_link_libraries(nrtype_LIB PUBLIC 2Geom::2geom)
+ endif()
+endif()
diff --git a/src/libnrtype/FontFactory.cpp b/src/libnrtype/FontFactory.cpp
new file mode 100644
index 0000000..88c8b55
--- /dev/null
+++ b/src/libnrtype/FontFactory.cpp
@@ -0,0 +1,877 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * fred
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifndef PANGO_ENABLE_ENGINE
+#define PANGO_ENABLE_ENGINE
+#endif
+
+#include <unordered_map>
+
+#include <glibmm/i18n.h>
+
+#include <fontconfig/fontconfig.h>
+
+#include <pango/pangofc-fontmap.h>
+#include <pango/pangoft2.h>
+#include <pango/pango-ot.h>
+
+#include "io/sys.h"
+#include "io/resource.h"
+
+#include "libnrtype/FontFactory.h"
+#include "libnrtype/font-instance.h"
+#include "libnrtype/OpenTypeUtil.h"
+
+# ifdef _WIN32
+
+#include <glibmm.h>
+#include <windows.h>
+
+#endif
+
+typedef std::unordered_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> FaceMapType;
+
+// need to avoid using the size field
+size_t font_descr_hash::operator()( PangoFontDescription *const &x) const {
+ int h = 0;
+ char const *theF = sp_font_description_get_family(x);
+ h += (theF)?g_str_hash(theF):0;
+ h *= 1128467;
+ h += (int)pango_font_description_get_style(x);
+ h *= 1128467;
+ h += (int)pango_font_description_get_variant(x);
+ h *= 1128467;
+ h += (int)pango_font_description_get_weight(x);
+ h *= 1128467;
+ h += (int)pango_font_description_get_stretch(x);
+ char const *theV = pango_font_description_get_variations(x);
+ h *= 1128467;
+ h += (theV)?g_str_hash(theV):0;
+ return h;
+}
+
+bool font_descr_equal::operator()( PangoFontDescription *const&a, PangoFontDescription *const &b) const {
+ //if ( pango_font_description_equal(a,b) ) return true;
+ char const *fa = sp_font_description_get_family(a);
+ char const *fb = sp_font_description_get_family(b);
+ if ( ( fa && fb == nullptr ) || ( fb && fa == nullptr ) ) return false;
+ if ( fa && fb && strcmp(fa,fb) != 0 ) return false;
+ if ( pango_font_description_get_style(a) != pango_font_description_get_style(b) ) return false;
+ if ( pango_font_description_get_variant(a) != pango_font_description_get_variant(b) ) return false;
+ if ( pango_font_description_get_weight(a) != pango_font_description_get_weight(b) ) return false;
+ if ( pango_font_description_get_stretch(a) != pango_font_description_get_stretch(b) ) return false;
+ if ( g_strcmp0( pango_font_description_get_variations(a),
+ pango_font_description_get_variations(b) ) != 0 ) return false;
+ return true;
+}
+
+// User must free return value.
+PangoFontDescription* ink_font_description_from_style(SPStyle const *style)
+{
+ PangoFontDescription *descr = pango_font_description_new();
+
+ pango_font_description_set_family(descr, style->font_family.value());
+
+ // This duplicates Layout::EnumConversionItem... perhaps we can share code?
+ switch ( style->font_style.computed ) {
+ case SP_CSS_FONT_STYLE_ITALIC:
+ pango_font_description_set_style(descr, PANGO_STYLE_ITALIC);
+ break;
+
+ case SP_CSS_FONT_STYLE_OBLIQUE:
+ pango_font_description_set_style(descr, PANGO_STYLE_OBLIQUE);
+ break;
+
+ case SP_CSS_FONT_STYLE_NORMAL:
+ default:
+ pango_font_description_set_style(descr, PANGO_STYLE_NORMAL);
+ break;
+ }
+
+ switch( style->font_weight.computed ) {
+ case SP_CSS_FONT_WEIGHT_100:
+ pango_font_description_set_weight(descr, PANGO_WEIGHT_THIN);
+ break;
+
+ case SP_CSS_FONT_WEIGHT_200:
+ pango_font_description_set_weight(descr, PANGO_WEIGHT_ULTRALIGHT);
+ break;
+
+ case SP_CSS_FONT_WEIGHT_300:
+ pango_font_description_set_weight(descr, PANGO_WEIGHT_LIGHT);
+ break;
+
+ case SP_CSS_FONT_WEIGHT_400:
+ case SP_CSS_FONT_WEIGHT_NORMAL:
+ pango_font_description_set_weight(descr, PANGO_WEIGHT_NORMAL);
+ break;
+
+ case SP_CSS_FONT_WEIGHT_500:
+ pango_font_description_set_weight(descr, PANGO_WEIGHT_MEDIUM);
+ break;
+
+ case SP_CSS_FONT_WEIGHT_600:
+ pango_font_description_set_weight(descr, PANGO_WEIGHT_SEMIBOLD);
+ break;
+
+ case SP_CSS_FONT_WEIGHT_700:
+ case SP_CSS_FONT_WEIGHT_BOLD:
+ pango_font_description_set_weight(descr, PANGO_WEIGHT_BOLD);
+ break;
+
+ case SP_CSS_FONT_WEIGHT_800:
+ pango_font_description_set_weight(descr, PANGO_WEIGHT_ULTRABOLD);
+ break;
+
+ case SP_CSS_FONT_WEIGHT_900:
+ pango_font_description_set_weight(descr, PANGO_WEIGHT_HEAVY);
+ break;
+
+ case SP_CSS_FONT_WEIGHT_LIGHTER:
+ case SP_CSS_FONT_WEIGHT_BOLDER:
+ default:
+ g_warning("FaceFromStyle: Unrecognized font_weight.computed value");
+ pango_font_description_set_weight(descr, PANGO_WEIGHT_NORMAL);
+ break;
+ }
+ // PANGO_WIEGHT_ULTRAHEAVY not used (not CSS2)
+
+ switch (style->font_stretch.computed) {
+ case SP_CSS_FONT_STRETCH_ULTRA_CONDENSED:
+ pango_font_description_set_stretch(descr, PANGO_STRETCH_ULTRA_CONDENSED);
+ break;
+
+ case SP_CSS_FONT_STRETCH_EXTRA_CONDENSED:
+ pango_font_description_set_stretch(descr, PANGO_STRETCH_EXTRA_CONDENSED);
+ break;
+
+ case SP_CSS_FONT_STRETCH_CONDENSED:
+ pango_font_description_set_stretch(descr, PANGO_STRETCH_CONDENSED);
+ break;
+
+ case SP_CSS_FONT_STRETCH_SEMI_CONDENSED:
+ pango_font_description_set_stretch(descr, PANGO_STRETCH_SEMI_CONDENSED);
+ break;
+
+ case SP_CSS_FONT_STRETCH_NORMAL:
+ pango_font_description_set_stretch(descr, PANGO_STRETCH_NORMAL);
+ break;
+
+ case SP_CSS_FONT_STRETCH_SEMI_EXPANDED:
+ pango_font_description_set_stretch(descr, PANGO_STRETCH_SEMI_EXPANDED);
+ break;
+
+ case SP_CSS_FONT_STRETCH_EXPANDED:
+ pango_font_description_set_stretch(descr, PANGO_STRETCH_EXPANDED);
+ break;
+
+ case SP_CSS_FONT_STRETCH_EXTRA_EXPANDED:
+ pango_font_description_set_stretch(descr, PANGO_STRETCH_EXTRA_EXPANDED);
+ break;
+
+ case SP_CSS_FONT_STRETCH_ULTRA_EXPANDED:
+ pango_font_description_set_stretch(descr, PANGO_STRETCH_ULTRA_EXPANDED);
+
+ case SP_CSS_FONT_STRETCH_WIDER:
+ case SP_CSS_FONT_STRETCH_NARROWER:
+ default:
+ g_warning("FaceFromStyle: Unrecognized font_stretch.computed value");
+ pango_font_description_set_stretch(descr, PANGO_STRETCH_NORMAL);
+ break;
+ }
+
+ switch ( style->font_variant.computed ) {
+ case SP_CSS_FONT_VARIANT_SMALL_CAPS:
+ pango_font_description_set_variant(descr, PANGO_VARIANT_SMALL_CAPS);
+ break;
+
+ case SP_CSS_FONT_VARIANT_NORMAL:
+ default:
+ pango_font_description_set_variant(descr, PANGO_VARIANT_NORMAL);
+ break;
+ }
+
+ // Check if not empty as Pango will add @ to string even if empty (bug in Pango?).
+ if (!style->font_variation_settings.axes.empty()) {
+ pango_font_description_set_variations(descr, style->font_variation_settings.toString().c_str());
+ }
+
+ return descr;
+}
+
+/////////////////// helper functions
+
+static void noop(...) {}
+//#define PANGO_DEBUG g_print
+#define PANGO_DEBUG noop
+
+
+///////////////////// FontFactory
+#ifndef USE_PANGO_WIN32
+// the substitute function to tell fontconfig to enforce outline fonts
+static void FactorySubstituteFunc(FcPattern *pattern,gpointer /*data*/)
+{
+ FcPatternAddBool(pattern, "FC_OUTLINE",FcTrue);
+ //char *fam = NULL;
+ //FcPatternGetString(pattern, "FC_FAMILY",0, &fam);
+ //printf("subst_f on %s\n",fam);
+}
+#endif
+
+
+font_factory *font_factory::lUsine = nullptr;
+
+font_factory *font_factory::Default()
+{
+ if ( lUsine == nullptr ) lUsine = new font_factory;
+ return lUsine;
+}
+
+font_factory::font_factory() :
+ nbEnt(0), // Note: this "ents" cache only keeps fonts from being unreffed, does not speed up access
+ maxEnt(32),
+ ents(static_cast<font_entry*>(g_malloc(maxEnt*sizeof(font_entry)))),
+#ifdef USE_PANGO_WIN32
+ fontServer(pango_win32_font_map_for_display()),
+ pangoFontCache(pango_win32_font_map_get_font_cache(fontServer)),
+ hScreenDC(pango_win32_get_dc()),
+#else
+ fontServer(pango_ft2_font_map_new()),
+#endif
+ fontContext(pango_font_map_create_context(fontServer)),
+ fontSize(512),
+ loadedPtr(new FaceMapType())
+{
+#ifndef USE_PANGO_WIN32
+ pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(fontServer),
+ 72, 72);
+#if PANGO_VERSION_CHECK(1,48,0)
+ pango_fc_font_map_set_default_substitute(PANGO_FC_FONT_MAP(fontServer),
+#else
+ pango_ft2_font_map_set_default_substitute(PANGO_FT2_FONT_MAP(fontServer),
+#endif
+ FactorySubstituteFunc,
+ this,
+ nullptr);
+#endif
+
+}
+
+font_factory::~font_factory()
+{
+ for (int i = 0;i < nbEnt;i++) ents[i].f->Unref();
+ if ( ents ) g_free(ents);
+
+ g_object_unref(fontServer);
+#ifdef USE_PANGO_WIN32
+ pango_win32_shutdown_display();
+#else
+ //pango_ft2_shutdown_display();
+#endif
+ //g_object_unref(fontContext);
+
+ if (loadedPtr) {
+ FaceMapType* tmp = static_cast<FaceMapType*>(loadedPtr);
+ delete tmp;
+ loadedPtr = nullptr;
+ }
+}
+
+
+Glib::ustring font_factory::ConstructFontSpecification(PangoFontDescription *font)
+{
+ Glib::ustring pangoString;
+
+ g_assert(font);
+
+ if (font) {
+ // Once the format for the font specification is decided, it must be
+ // kept.. if it is absolutely necessary to change it, the attribute
+ // it is written to needs to have a new version so the legacy files
+ // can be read.
+
+ PangoFontDescription *copy = pango_font_description_copy(font);
+
+ pango_font_description_unset_fields (copy, PANGO_FONT_MASK_SIZE);
+ char * copyAsString = pango_font_description_to_string(copy);
+ pangoString = copyAsString;
+ g_free(copyAsString);
+ copyAsString = nullptr;
+
+ pango_font_description_free(copy);
+
+ }
+
+ return pangoString;
+}
+
+Glib::ustring font_factory::ConstructFontSpecification(font_instance *font)
+{
+ Glib::ustring pangoString;
+
+ g_assert(font);
+
+ if (font) {
+ pangoString = ConstructFontSpecification(font->descr);
+ }
+
+ return pangoString;
+}
+
+/*
+ * Wrap calls to pango_font_description_get_family
+ * and replace some of the pango font names with generic css names
+ * http://www.w3.org/TR/2008/REC-CSS2-20080411/fonts.html#generic-font-families
+ *
+ * This function should be called in place of pango_font_description_get_family()
+ */
+const char *sp_font_description_get_family(PangoFontDescription const *fontDescr) {
+
+ static std::map<Glib::ustring, Glib::ustring> fontNameMap;
+ std::map<Glib::ustring, Glib::ustring>::iterator it;
+
+ if (fontNameMap.empty()) {
+ fontNameMap.insert(std::make_pair("Sans", "sans-serif"));
+ fontNameMap.insert(std::make_pair("Serif", "serif"));
+ fontNameMap.insert(std::make_pair("Monospace", "monospace"));
+ }
+
+ const char *pangoFamily = pango_font_description_get_family(fontDescr);
+
+ if (pangoFamily && ((it = fontNameMap.find(pangoFamily)) != fontNameMap.end())) {
+ return (it->second).c_str();
+ }
+
+ return pangoFamily;
+}
+
+Glib::ustring font_factory::GetUIFamilyString(PangoFontDescription const *fontDescr)
+{
+ Glib::ustring family;
+
+ g_assert(fontDescr);
+
+ if (fontDescr) {
+ // For now, keep it as family name taken from pango
+ const char *pangoFamily = sp_font_description_get_family(fontDescr);
+
+ if( pangoFamily ) {
+ family = pangoFamily;
+ }
+ }
+
+ return family;
+}
+
+Glib::ustring font_factory::GetUIStyleString(PangoFontDescription const *fontDescr)
+{
+ Glib::ustring style;
+
+ g_assert(fontDescr);
+
+ if (fontDescr) {
+ PangoFontDescription *fontDescrCopy = pango_font_description_copy(fontDescr);
+
+ pango_font_description_unset_fields(fontDescrCopy, PANGO_FONT_MASK_FAMILY);
+ pango_font_description_unset_fields(fontDescrCopy, PANGO_FONT_MASK_SIZE);
+
+ // For now, keep it as style name taken from pango
+ char *fontDescrAsString = pango_font_description_to_string(fontDescrCopy);
+ style = fontDescrAsString;
+ g_free(fontDescrAsString);
+ fontDescrAsString = nullptr;
+ pango_font_description_free(fontDescrCopy);
+ }
+
+ return style;
+}
+
+
+/////
+
+// Calculate a Style "value" based on CSS values for ordering styles.
+static int StyleNameValue( const Glib::ustring &style )
+{
+
+ PangoFontDescription *pfd = pango_font_description_from_string ( style.c_str() );
+ int value =
+ pango_font_description_get_weight ( pfd ) * 1000000 +
+ pango_font_description_get_style ( pfd ) * 10000 +
+ pango_font_description_get_stretch( pfd ) * 100 +
+ pango_font_description_get_variant( pfd );
+ pango_font_description_free ( pfd );
+ return value;
+}
+
+// Determines order in which styles are presented (sorted by CSS style values)
+//static bool StyleNameCompareInternal(const StyleNames &style1, const StyleNames &style2)
+//{
+// return( StyleNameValue( style1.CssName ) < StyleNameValue( style2.CssName ) );
+//}
+
+static gint StyleNameCompareInternalGlib(gconstpointer a, gconstpointer b)
+{
+ return( StyleNameValue( ((StyleNames *)a)->CssName ) <
+ StyleNameValue( ((StyleNames *)b)->CssName ) ? -1 : 1 );
+}
+
+static bool ustringPairSort(std::pair<PangoFontFamily*, Glib::ustring> const& first, std::pair<PangoFontFamily*, Glib::ustring> const& second)
+{
+ // well, this looks weird.
+ return first.second < second.second;
+}
+
+void font_factory::GetUIFamilies(std::vector<PangoFontFamily *>& out)
+{
+ // Gather the family names as listed by Pango
+ PangoFontFamily** families = nullptr;
+ int numFamilies = 0;
+ pango_font_map_list_families(fontServer, &families, &numFamilies);
+
+ std::vector<std::pair<PangoFontFamily *, Glib::ustring> > sorted;
+
+ // not size_t
+ for (int currentFamily = 0; currentFamily < numFamilies; ++currentFamily) {
+ const char* displayName = pango_font_family_get_name(families[currentFamily]);
+
+ if (displayName == nullptr || *displayName == '\0') {
+ std::cerr << "font_factory::GetUIFamilies: Missing displayName! " << std::endl;
+ continue;
+ }
+ if (!g_utf8_validate(displayName, -1, nullptr)) {
+ // TODO: can can do anything about this or does it always indicate broken fonts that should not be used?
+ std::cerr << "font_factory::GetUIFamilies: Illegal characters in displayName. ";
+ std::cerr << "Ignoring font '" << displayName << "'" << std::endl;
+ continue;
+ }
+ sorted.emplace_back(families[currentFamily], displayName);
+ }
+
+ std::sort(sorted.begin(), sorted.end(), ustringPairSort);
+
+ for (auto & i : sorted) {
+ out.push_back(i.first);
+ }
+}
+
+GList* font_factory::GetUIStyles(PangoFontFamily * in)
+{
+ GList* ret = nullptr;
+ // Gather the styles for this family
+ PangoFontFace** faces = nullptr;
+ int numFaces = 0;
+ if (in == nullptr) {
+ std::cerr << "font_factory::GetUIStyles(): PangoFontFamily is NULL" << std::endl;
+ return ret;
+ }
+
+ pango_font_family_list_faces(in, &faces, &numFaces);
+
+ for (int currentFace = 0; currentFace < numFaces; currentFace++) {
+
+ // If the face has a name, describe it, and then use the
+ // description to get the UI family and face strings
+ const gchar* displayName = pango_font_face_get_face_name(faces[currentFace]);
+ // std::cout << "Display Name: " << displayName << std::endl;
+ if (displayName == nullptr || *displayName == '\0') {
+ std::cerr << "font_factory::GetUIStyles: Missing displayName! " << std::endl;
+ continue;
+ }
+
+ PangoFontDescription *faceDescr = pango_font_face_describe(faces[currentFace]);
+ if (faceDescr) {
+ Glib::ustring familyUIName = GetUIFamilyString(faceDescr);
+ Glib::ustring styleUIName = GetUIStyleString(faceDescr);
+ // std::cout << " " << familyUIName << " styleUIName: " << styleUIName << " displayName: " << displayName << std::endl;
+
+ // Disable synthesized (faux) font faces except for CSS generic faces
+ if (pango_font_face_is_synthesized(faces[currentFace]) ) {
+ if (familyUIName.compare( "sans-serif" ) != 0 &&
+ familyUIName.compare( "serif" ) != 0 &&
+ familyUIName.compare( "monospace" ) != 0 &&
+ familyUIName.compare( "fantasy" ) != 0 &&
+ familyUIName.compare( "cursive" ) != 0 ) {
+ continue;
+ }
+ }
+
+ // Pango breaks the 1 to 1 mapping between Pango weights and CSS weights by
+ // adding Semi-Light (as of 1.36.7), Book (as of 1.24), and Ultra-Heavy (as of
+ // 1.24). We need to map these weights to CSS weights. Book and Ultra-Heavy
+ // are rarely used. Semi-Light (350) is problematic as it is halfway between
+ // Light (300) and Normal (400) and if care is not taken it is converted to
+ // Normal, rather than Light.
+ //
+ // Note: The ultimate solution to handling various weight in the same
+ // font family is to support the @font rules from CSS.
+ //
+ // Additional notes, helpful for debugging:
+ // Pango's FC backend:
+ // Weights defined in fontconfig/fontconfig.h
+ // String equivalents in src/fcfreetype.c
+ // Weight set from os2->usWeightClass
+ // Use Fontforge: Element->Font Info...->OS/2->Misc->Weight Class to check font weight
+ size_t f = styleUIName.find( "Book" );
+ if( f != Glib::ustring::npos ) {
+ styleUIName.replace( f, 4, "Normal" );
+ }
+ f = styleUIName.find( "Semi-Light" );
+ if( f != Glib::ustring::npos ) {
+ styleUIName.replace( f, 10, "Light" );
+ }
+ f = styleUIName.find( "Ultra-Heavy" );
+ if( f != Glib::ustring::npos ) {
+ styleUIName.replace( f, 11, "Heavy" );
+ }
+
+ bool exists = false;
+ for(GList *temp = ret; temp; temp = temp->next) {
+ if( ((StyleNames*)temp->data)->CssName.compare( styleUIName ) == 0 ) {
+ exists = true;
+ std::cerr << "Warning: Font face with same CSS values already added: "
+ << familyUIName.raw() << " " << styleUIName.raw()
+ << " (" << ((StyleNames*)temp->data)->DisplayName.raw()
+ << ", " << displayName << ")" << std::endl;
+ break;
+ }
+ }
+
+ if (!exists && !familyUIName.empty() && !styleUIName.empty()) {
+ // Add the style information
+ ret = g_list_append(ret, new StyleNames(styleUIName, displayName));
+ }
+ }
+ pango_font_description_free(faceDescr);
+ }
+ g_free(faces);
+
+ // Sort the style lists
+ ret = g_list_sort( ret, StyleNameCompareInternalGlib );
+ return ret;
+}
+
+
+font_instance* font_factory::FaceFromStyle(SPStyle const *style)
+{
+ font_instance *font = nullptr;
+
+ g_assert(style);
+
+ if (style) {
+
+ // First try to use the font specification if it is set
+ char const *val;
+ if (style->font_specification.set
+ && (val = style->font_specification.value())
+ && val[0]) {
+
+ font = FaceFromFontSpecification(val);
+ }
+
+ // If that failed, try using the CSS information in the style
+ if (!font) {
+ PangoFontDescription* temp_descr =
+ ink_font_description_from_style(style);
+ font = Face(temp_descr);
+ pango_font_description_free(temp_descr);
+ }
+ }
+
+ return font;
+}
+
+font_instance *font_factory::FaceFromDescr(char const *family, char const *style)
+{
+ PangoFontDescription *temp_descr = pango_font_description_from_string(style);
+ pango_font_description_set_family(temp_descr,family);
+ font_instance *res = Face(temp_descr);
+ pango_font_description_free(temp_descr);
+ return res;
+}
+
+font_instance* font_factory::FaceFromPangoString(char const *pangoString)
+{
+ font_instance *fontInstance = nullptr;
+
+ g_assert(pangoString);
+
+ if (pangoString) {
+
+ // Create a font description from the string - this may fail or
+ // produce unexpected results if the string does not have a good format
+ PangoFontDescription *descr = pango_font_description_from_string(pangoString);
+
+ if (descr) {
+ if (sp_font_description_get_family(descr) != nullptr) {
+ fontInstance = Face(descr);
+ }
+ pango_font_description_free(descr);
+ }
+ }
+
+ return fontInstance;
+}
+
+font_instance* font_factory::FaceFromFontSpecification(char const *fontSpecification)
+{
+ font_instance *font = nullptr;
+
+ g_assert(fontSpecification);
+
+ if (fontSpecification) {
+ // How the string is used to reconstruct a font depends on how it
+ // was constructed in ConstructFontSpecification. As it stands,
+ // the font specification is a pango-created string
+ font = FaceFromPangoString(fontSpecification);
+ }
+
+ return font;
+}
+
+font_instance *font_factory::Face(PangoFontDescription *descr, bool canFail)
+{
+#ifdef USE_PANGO_WIN32
+ // damn Pango fudges the size, so we need to unfudge. See source of pango_win32_font_map_init()
+ pango_font_description_set_size(descr, (int) (fontSize*PANGO_SCALE*72/GetDeviceCaps(pango_win32_get_dc(),LOGPIXELSY))); // mandatory huge size (hinting workaround)
+#else
+ pango_font_description_set_size(descr, (int) (fontSize*PANGO_SCALE)); // mandatory huge size (hinting workaround)
+#endif
+
+ font_instance *res = nullptr;
+
+ FaceMapType& loadedFaces = *static_cast<FaceMapType*>(loadedPtr);
+ if ( loadedFaces.find(descr) == loadedFaces.end() ) {
+ // not yet loaded
+ PangoFont *nFace = nullptr;
+
+ // workaround for bug #1025565.
+ // fonts without families blow up Pango.
+ if (sp_font_description_get_family(descr) != nullptr) {
+ nFace = pango_font_map_load_font(fontServer,fontContext,descr);
+ }
+ else {
+ g_warning("%s", _("Ignoring font without family that will crash Pango"));
+ }
+
+ if ( nFace ) {
+ // duplicate FcPattern, the hard way
+ res = new font_instance();
+ // store the descr of the font we asked for, since this is the key where we intend
+ // to put the font_instance at in the unordered_map. the descr of the returned
+ // pangofont may differ from what was asked, so we don't know (at this
+ // point) whether loadedFaces[that_descr] is free or not (and overwriting
+ // an entry will bring deallocation problems)
+ res->descr = pango_font_description_copy(descr);
+ res->parent = this;
+ res->InstallFace(nFace);
+ if ( res->pFont == nullptr ) {
+ // failed to install face -> bitmap font
+ // printf("face failed\n");
+ res->parent = nullptr;
+ delete res;
+ res = nullptr;
+ if ( canFail ) {
+ char *tc = pango_font_description_to_string(descr);
+ PANGO_DEBUG("falling back from %s to 'sans-serif' because InstallFace failed\n",tc);
+ g_free(tc);
+ pango_font_description_set_family(descr,"sans-serif");
+ res = Face(descr,false);
+ }
+ } else {
+ loadedFaces[res->descr]=res;
+ res->Ref();
+ AddInCache(res);
+ }
+ } else {
+ // no match
+ if ( canFail ) {
+ PANGO_DEBUG("falling back to 'sans-serif'\n");
+ PangoFontDescription *new_descr = pango_font_description_new();
+ pango_font_description_set_family(new_descr, "sans-serif");
+ res = Face(new_descr, false);
+ pango_font_description_free(new_descr);
+ } else {
+ g_critical("Could not load any face for font '%s'.", pango_font_description_to_string(descr));
+ }
+ }
+
+ } else {
+ // already here
+ res = loadedFaces[descr];
+ res->Ref();
+ AddInCache(res);
+ }
+ if (res) {
+ res->InitTheFace();
+ }
+ return res;
+}
+
+// Not used, need to add variations if ever used.
+// font_instance *font_factory::Face(char const *family, int variant, int style, int weight, int stretch, int /*size*/, int /*spacing*/)
+// {
+// // std::cout << "font_factory::Face(family, variant, style, weight, stretch,)" << std::endl;
+// PangoFontDescription *temp_descr = pango_font_description_new();
+// pango_font_description_set_family(temp_descr,family);
+// pango_font_description_set_weight(temp_descr,(PangoWeight)weight);
+// pango_font_description_set_stretch(temp_descr,(PangoStretch)stretch);
+// pango_font_description_set_style(temp_descr,(PangoStyle)style);
+// pango_font_description_set_variant(temp_descr,(PangoVariant)variant);
+// font_instance *res = Face(temp_descr);
+// pango_font_description_free(temp_descr);
+// return res;
+// }
+
+void font_factory::UnrefFace(font_instance *who)
+{
+ if ( who ) {
+ FaceMapType& loadedFaces = *static_cast<FaceMapType*>(loadedPtr);
+
+ if ( loadedFaces.find(who->descr) == loadedFaces.end() ) {
+ // not found
+ char *tc = pango_font_description_to_string(who->descr);
+ g_warning("unrefFace %p=%s: failed\n",who,tc);
+ g_free(tc);
+ } else {
+ loadedFaces.erase(loadedFaces.find(who->descr));
+ // printf("unrefFace %p: success\n",who);
+ }
+ }
+}
+
+void font_factory::AddInCache(font_instance *who)
+{
+ if ( who == nullptr ) return;
+ for (int i = 0;i < nbEnt;i++) ents[i].age *= 0.9;
+ for (int i = 0;i < nbEnt;i++) {
+ if ( ents[i].f == who ) {
+ // printf("present\n");
+ ents[i].age += 1.0;
+ return;
+ }
+ }
+ if ( nbEnt > maxEnt ) {
+ printf("cache sur-plein?\n");
+ return;
+ }
+ who->Ref();
+ if ( nbEnt == maxEnt ) { // cache is filled, unref the oldest-accessed font in it
+ int bi = 0;
+ double ba = ents[bi].age;
+ for (int i = 1;i < nbEnt;i++) {
+ if ( ents[i].age < ba ) {
+ bi = i;
+ ba = ents[bi].age;
+ }
+ }
+ ents[bi].f->Unref();
+ ents[bi]=ents[--nbEnt];
+ }
+ ents[nbEnt].f = who;
+ ents[nbEnt].age = 1.0;
+ nbEnt++;
+}
+
+# ifdef _WIN32
+void font_factory::AddFontFilesWin32(char const * directory_path )
+{
+ std::vector<const char *> allowed_ext = {"ttf","otf" };
+ std::vector<Glib::ustring> files = {};
+ Inkscape::IO::Resource::get_filenames_from_path(files,directory_path, allowed_ext,(std::vector<const char *>) {});
+ for (auto file : files) {
+ int result = AddFontResourceExA(file.c_str(),FR_PRIVATE,0);
+ if (result != 0 ) {
+ g_info("Font File: %s added sucessfully.",file.c_str());
+ } else {
+ g_warning("Font File: %s wasn't added sucessfully",file.c_str());
+ }
+ }
+}
+# endif
+
+void font_factory::AddFontsDir(char const *utf8dir)
+{
+#ifdef USE_PANGO_WIN32
+ g_info("Adding additional font directories only supported for fontconfig backend.");
+#else
+ if (!Inkscape::IO::file_test(utf8dir, G_FILE_TEST_IS_DIR)) {
+ g_info("Fonts dir '%s' does not exist and will be ignored.", utf8dir);
+ return;
+ }
+
+ gchar *dir;
+# ifdef _WIN32
+ AddFontFilesWin32(utf8dir);
+ dir = g_win32_locale_filename_from_utf8(utf8dir);
+# else
+ dir = g_filename_from_utf8(utf8dir, -1, nullptr, nullptr, nullptr);
+# endif
+
+ FcConfig *conf = nullptr;
+ conf = pango_fc_font_map_get_config(PANGO_FC_FONT_MAP(fontServer));
+ FcBool res = FcConfigAppFontAddDir(conf, (FcChar8 const *)dir);
+ if (res == FcTrue) {
+ g_info("Fonts dir '%s' added successfully.", utf8dir);
+ pango_fc_font_map_config_changed(PANGO_FC_FONT_MAP(fontServer));
+ } else {
+ g_warning("Could not add fonts dir '%s'.", utf8dir);
+ }
+
+ g_free(dir);
+#endif
+}
+
+void font_factory::AddFontFile(char const *utf8file)
+{
+#ifdef USE_PANGO_WIN32
+ g_info("Adding additional font only supported for fontconfig backend.");
+#else
+ if (!Inkscape::IO::file_test(utf8file, G_FILE_TEST_IS_REGULAR)) {
+ g_warning("Font file '%s' does not exist and will be ignored.", utf8file);
+ return;
+ }
+
+ gchar *file;
+# ifdef _WIN32
+ file = g_win32_locale_filename_from_utf8(utf8file);
+# else
+ file = g_filename_from_utf8(utf8file, -1, nullptr, nullptr, nullptr);
+# endif
+
+ FcConfig *conf = nullptr;
+ conf = pango_fc_font_map_get_config(PANGO_FC_FONT_MAP(fontServer));
+ FcBool res = FcConfigAppFontAddFile(conf, (FcChar8 const *)file);
+ if (res == FcTrue) {
+ g_info("Font file '%s' added successfully.", utf8file);
+ pango_fc_font_map_config_changed(PANGO_FC_FONT_MAP(fontServer));
+ } else {
+ g_warning("Could not add font file '%s'.", utf8file);
+ }
+
+ g_free(file);
+#endif
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/FontFactory.h b/src/libnrtype/FontFactory.h
new file mode 100644
index 0000000..e3bf1d2
--- /dev/null
+++ b/src/libnrtype/FontFactory.h
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/*
+ * FontFactory.h
+ * testICU
+ *
+ */
+
+#ifndef my_font_factory
+#define my_font_factory
+
+#include <functional>
+#include <algorithm>
+#include <utility>
+
+#ifdef _WIN32
+//#define USE_PANGO_WIN32 // disable for Bug 165665
+#endif
+
+#include <pango/pango.h>
+#include "style.h"
+
+/* Freetype */
+#ifdef USE_PANGO_WIN32
+#include <pango/pangowin32.h>
+#else
+#include <pango/pangoft2.h>
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#endif
+
+
+class font_instance;
+
+namespace Glib
+{
+ class ustring;
+}
+
+// the font_factory keeps a hashmap of all the loaded font_instances, and uses the PangoFontDescription
+// as index (nota: since pango already does that, using the PangoFont could work too)
+struct font_descr_hash : public std::unary_function<PangoFontDescription*,size_t> {
+ size_t operator()(PangoFontDescription *const &x) const;
+};
+struct font_descr_equal : public std::binary_function<PangoFontDescription*, PangoFontDescription*, bool> {
+ bool operator()(PangoFontDescription *const &a, PangoFontDescription *const &b) const;
+};
+
+// Constructs a PangoFontDescription from SPStyle. Font size is not included.
+// User must free return value.
+PangoFontDescription* ink_font_description_from_style(SPStyle const *style);
+
+// Wraps calls to pango_font_description_get_family with some name substitution
+const char *sp_font_description_get_family(PangoFontDescription const *fontDescr);
+
+// Class for style strings: both CSS and as suggested by font.
+class StyleNames {
+
+public:
+ StyleNames() = default;;
+ StyleNames( Glib::ustring name ) :
+ CssName( name ), DisplayName( name ) {};
+ StyleNames( Glib::ustring cssname, Glib::ustring displayname ) :
+ CssName(std::move( cssname )), DisplayName(std::move( displayname )) {};
+
+public:
+ Glib::ustring CssName; // Style as Pango/CSS would write it.
+ Glib::ustring DisplayName; // Style as Font designer named it.
+};
+
+// Map type for gathering UI family and style names
+// typedef std::map<Glib::ustring, std::list<StyleNames> > FamilyToStylesMap;
+
+class font_factory {
+public:
+ static font_factory *lUsine; /**< The default font_factory; i cannot think of why we would
+ * need more than one.
+ *
+ * ("l'usine" is french for "the factory".)
+ */
+
+ /** A little cache for fonts, so that you don't loose your time looking up fonts in the font list
+ * each font in the cache is refcounted once (and deref'd when removed from the cache). */
+ struct font_entry {
+ font_instance *f;
+ double age;
+ };
+ int nbEnt; ///< Number of entries.
+ int maxEnt; ///< Cache size.
+ font_entry *ents;
+
+ // Pango data. Backend-specific structures are cast to these opaque types.
+ PangoFontMap *fontServer;
+ PangoContext *fontContext;
+#ifdef USE_PANGO_WIN32
+ PangoWin32FontCache *pangoFontCache;
+ HDC hScreenDC;
+#endif
+ double fontSize; /**< The huge fontsize used as workaround for hinting.
+ * Different between freetype and win32. */
+
+ font_factory();
+ virtual ~font_factory();
+
+ /// Returns the default font_factory.
+ static font_factory* Default();
+
+ /// Constructs a pango string for use with the fontStringMap (see below)
+ Glib::ustring ConstructFontSpecification(PangoFontDescription *font);
+ Glib::ustring ConstructFontSpecification(font_instance *font);
+
+ /// Returns strings to be used in the UI for family and face (or "style" as the column is labeled)
+ Glib::ustring GetUIFamilyString(PangoFontDescription const *fontDescr);
+ Glib::ustring GetUIStyleString(PangoFontDescription const *fontDescr);
+
+ // Helpfully inserts all font families into the provided vector
+ void GetUIFamilies(std::vector<PangoFontFamily *>& out);
+ // Retrieves style information about a family in a newly allocated GList.
+ GList* GetUIStyles(PangoFontFamily * in);
+
+ /// Retrieve a font_instance from a style object, first trying to use the font-specification, the CSS information
+ font_instance* FaceFromStyle(SPStyle const *style);
+
+ // Various functions to get a font_instance from different descriptions.
+ font_instance* FaceFromDescr(char const *family, char const *style);
+ font_instance* FaceFromUIStrings(char const *uiFamily, char const *uiStyle);
+ font_instance* FaceFromPangoString(char const *pangoString);
+ font_instance* FaceFromFontSpecification(char const *fontSpecification);
+ font_instance* Face(PangoFontDescription *descr, bool canFail=true);
+ font_instance* Face(char const *family,
+ int variant=PANGO_VARIANT_NORMAL, int style=PANGO_STYLE_NORMAL,
+ int weight=PANGO_WEIGHT_NORMAL, int stretch=PANGO_STRETCH_NORMAL,
+ int size=10, int spacing=0);
+
+ /// Semi-private: tells the font_factory that the font_instance 'who' has died and should be removed from loadedFaces
+ void UnrefFace(font_instance* who);
+
+ // internal
+ void AddInCache(font_instance *who);
+
+# ifdef _WIN32
+ void AddFontFilesWin32(char const *directory_path);
+# endif
+
+ /// Add a directory from which to include additional fonts
+ void AddFontsDir(char const *utf8dir);
+
+ /// Add a an additional font.
+ void AddFontFile(char const *utf8file);
+
+private:
+ void* loadedPtr;
+
+
+ // The following two commented out maps were an attempt to allow Inkscape to use font faces
+ // that could not be distinguished by CSS values alone. In practice, they never were that
+ // useful as PangoFontDescription, which is used throughout our code, cannot distinguish
+ // between faces anymore than raw CSS values (with the exception of two additional weight
+ // values).
+ //
+ // During various works, for example to handle font-family lists and fonts that are not
+ // installed on the system, the code has become less reliant on these maps. And in the work to
+ // catch style information to speed up start up times, the maps were not being filled.
+ // I've removed all code that used these maps as of Oct 2014 in the experimental branch.
+ // The commented out maps are left here as a reminder of the path that was attempted.
+ //
+ // One possible method to keep track of font faces would be to use the 'display name', keeping
+ // pointers to the appropriate PangoFontFace. The font_factory loadedFaces map indexing would
+ // have to be changed to incorporate 'display name' (InkscapeFontDescription?).
+
+
+ // These two maps are used for translating between what's in the UI and a pango
+ // font description. This is necessary because Pango cannot always
+ // reproduce these structures from the names it gave us in the first place.
+
+ // Key: A string produced by font_factory::ConstructFontSpecification
+ // Value: The associated PangoFontDescription
+ // typedef std::map<Glib::ustring, PangoFontDescription *> PangoStringToDescrMap;
+ // PangoStringToDescrMap fontInstanceMap;
+
+ // Key: Family name in UI + Style name in UI
+ // Value: The associated string that should be produced with font_factory::ConstructFontSpecification
+ // typedef std::map<Glib::ustring, Glib::ustring> UIStringToPangoStringMap;
+ // UIStringToPangoStringMap fontStringMap;
+};
+
+
+#endif /* my_font_factory */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/libnrtype/FontInstance.cpp b/src/libnrtype/FontInstance.cpp
new file mode 100644
index 0000000..e17d593
--- /dev/null
+++ b/src/libnrtype/FontInstance.cpp
@@ -0,0 +1,1016 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * fred
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifndef PANGO_ENABLE_ENGINE
+#define PANGO_ENABLE_ENGINE
+#endif
+
+#include <ft2build.h>
+#include FT_OUTLINE_H
+#include FT_BBOX_H
+#include FT_TRUETYPE_TAGS_H
+#include FT_TRUETYPE_TABLES_H
+#include FT_GLYPH_H
+#include FT_MULTIPLE_MASTERS_H
+
+#include <pango/pangoft2.h>
+#include <harfbuzz/hb.h>
+#include <harfbuzz/hb-ft.h>
+
+#include <glibmm/regex.h>
+
+#include <2geom/pathvector.h>
+#include <2geom/path-sink.h>
+#include "libnrtype/font-glyph.h"
+#include "libnrtype/font-instance.h"
+
+#include "display/cairo-utils.h" // Inkscape::Pixbuf
+
+#ifndef USE_PANGO_WIN32
+/*
+ * Outline extraction
+ */
+
+struct FT2GeomData {
+ FT2GeomData(Geom::PathBuilder &b, double s)
+ : builder(b)
+ , last(0, 0)
+ , scale(s)
+ {}
+ Geom::PathBuilder &builder;
+ Geom::Point last;
+ double scale;
+};
+
+// outline as returned by freetype
+static int ft2_move_to(FT_Vector const *to, void * i_user)
+{
+ FT2GeomData *user = (FT2GeomData*)i_user;
+ Geom::Point p(to->x, to->y);
+ // printf("m t=%f %f\n",p[0],p[1]);
+ user->builder.moveTo(p * user->scale);
+ user->last = p;
+ return 0;
+}
+
+static int ft2_line_to(FT_Vector const *to, void *i_user)
+{
+ FT2GeomData *user = (FT2GeomData*)i_user;
+ Geom::Point p(to->x, to->y);
+ // printf("l t=%f %f\n",p[0],p[1]);
+ user->builder.lineTo(p * user->scale);
+ user->last = p;
+ return 0;
+}
+
+static int ft2_conic_to(FT_Vector const *control, FT_Vector const *to, void *i_user)
+{
+ FT2GeomData *user = (FT2GeomData*)i_user;
+ Geom::Point p(to->x, to->y), c(control->x, control->y);
+ user->builder.quadTo(c * user->scale, p * user->scale);
+ // printf("b c=%f %f t=%f %f\n",c[0],c[1],p[0],p[1]);
+ user->last = p;
+ return 0;
+}
+
+static int ft2_cubic_to(FT_Vector const *control1, FT_Vector const *control2, FT_Vector const *to, void *i_user)
+{
+ FT2GeomData *user = (FT2GeomData*)i_user;
+ Geom::Point p(to->x, to->y);
+ Geom::Point c1(control1->x, control1->y);
+ Geom::Point c2(control2->x, control2->y);
+ // printf("c c1=%f %f c2=%f %f t=%f %f\n",c1[0],c1[1],c2[0],c2[1],p[0],p[1]);
+ //user->theP->CubicTo(p,3*(c1-user->last),3*(p-c2));
+ user->builder.curveTo(c1 * user->scale, c2 * user->scale, p * user->scale);
+ user->last = p;
+ return 0;
+}
+#endif
+
+/* *** END #if HACK *** */
+
+/*
+ *
+ */
+
+font_instance::font_instance()
+{
+ //printf("font instance born\n");
+ _ascent = _ascent_max = 0.8;
+ _descent = _descent_max = 0.2;
+ _xheight = 0.5;
+
+ // Default baseline values, alphabetic is reference
+ _baselines[ SP_CSS_BASELINE_AUTO ] = 0.0;
+ _baselines[ SP_CSS_BASELINE_ALPHABETIC ] = 0.0;
+ _baselines[ SP_CSS_BASELINE_IDEOGRAPHIC ] = -_descent;
+ _baselines[ SP_CSS_BASELINE_HANGING ] = 0.8 * _ascent;
+ _baselines[ SP_CSS_BASELINE_MATHEMATICAL ] = 0.8 * _xheight;
+ _baselines[ SP_CSS_BASELINE_CENTRAL ] = 0.5 - _descent;
+ _baselines[ SP_CSS_BASELINE_MIDDLE ] = 0.5 * _xheight;
+ _baselines[ SP_CSS_BASELINE_TEXT_BEFORE_EDGE ] = _ascent;
+ _baselines[ SP_CSS_BASELINE_TEXT_AFTER_EDGE ] = -_descent;
+}
+
+font_instance::~font_instance()
+{
+ if ( parent ) {
+ parent->UnrefFace(this);
+ parent = nullptr;
+ }
+
+ //printf("font instance death\n");
+ if ( pFont ) {
+ FreeTheFace();
+ g_object_unref(pFont);
+ pFont = nullptr;
+ }
+
+ if ( descr ) {
+ pango_font_description_free(descr);
+ descr = nullptr;
+ }
+
+ // if ( theFace ) FT_Done_Face(theFace); // owned by pFont. don't touch
+ theFace = nullptr;
+
+ for (int i=0;i<nbGlyph;i++) {
+ if ( glyphs[i].pathvector ) {
+ delete glyphs[i].pathvector;
+ }
+ }
+ if ( glyphs ) {
+ free(glyphs);
+ glyphs = nullptr;
+ }
+ nbGlyph = 0;
+ maxGlyph = 0;
+}
+
+void font_instance::Ref()
+{
+ refCount++;
+ //char *tc=pango_font_description_to_string(descr);
+ //printf("font %x %s ref'd %i\n",this,tc,refCount);
+ //free(tc);
+}
+
+void font_instance::Unref()
+{
+ refCount--;
+ //char *tc=pango_font_description_to_string(descr);
+ //printf("font %x %s unref'd %i\n",this,tc,refCount);
+ //free(tc);
+ if ( refCount <= 0 ) {
+ delete this;
+ }
+}
+
+void font_instance::InitTheFace(bool loadgsub)
+{
+ if (pFont != nullptr && (theFace == nullptr || (loadgsub && !fulloaded))) {
+ if (theFace) {
+ theFace = nullptr;
+ }
+#ifdef USE_PANGO_WIN32
+
+ LOGFONT *lf=pango_win32_font_logfont(pFont);
+ g_assert(lf != NULL);
+ theFace = pango_win32_font_cache_load(parent->pangoFontCache,lf);
+ g_free(lf);
+
+ XFORM identity = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0};
+ SetWorldTransform(parent->hScreenDC, &identity);
+ SetGraphicsMode(parent->hScreenDC, GM_COMPATIBLE);
+ SelectObject(parent->hScreenDC,theFace);
+
+#else
+
+ hb_font_t* hb_font = pango_font_get_hb_font(pFont); // Pango owns hb_font.
+
+#if HB_VERSION_ATLEAST(2,6,5)
+ // hb_font is immutable, yet we need to act on it (with set_funcs) to extract the freetype face
+ hb_font_copy = hb_font_create_sub_font(hb_font);
+
+ hb_ft_font_set_funcs(hb_font_copy);
+ theFace = hb_ft_font_lock_face(hb_font_copy);
+#else
+ theFace = pango_fc_font_lock_face(PANGO_FC_FONT(pFont));
+#endif
+
+ if ( theFace ) {
+ FT_Select_Charmap(theFace, ft_encoding_unicode);
+ FT_Select_Charmap(theFace, ft_encoding_symbol);
+ }
+
+ if (!hb_font) {
+ std::cerr << "font_instance::InitTheFace: Failed to get hb_font!" << std::endl;
+ } else {
+ if (loadgsub) {
+ readOpenTypeGsubTable (hb_font, openTypeTables);
+ fulloaded = true;
+ }
+ readOpenTypeSVGTable (hb_font, openTypeSVGGlyphs);
+ }
+ readOpenTypeFvarAxes( theFace, openTypeVarAxes );
+
+ if (openTypeSVGGlyphs.size() > 0 ) {
+ fontHasSVG = true;
+ }
+
+#if FREETYPE_MAJOR == 2 && FREETYPE_MINOR >= 8 // 2.8 does not seem to work even though it has some support.
+
+ // 'font-variation-settings' support.
+ // The font returned from pango_fc_font_lock_face does not include variation settings. We must set them.
+
+ // We need to:
+ // Extract axes with values from Pango font description.
+ // Replace default axis values with extracted values.
+
+ char const *var = pango_font_description_get_variations( descr );
+ if (var) {
+ Glib::ustring variations(var);
+
+ FT_MM_Var* mmvar = nullptr;
+ FT_Multi_Master mmtype;
+ if (FT_HAS_MULTIPLE_MASTERS( theFace ) && // Font has variables
+ FT_Get_MM_Var(theFace, &mmvar) == 0 && // We found the data
+ FT_Get_Multi_Master(theFace, &mmtype) !=0) { // It's not an Adobe MM font
+
+ // std::cout << " Multiple Masters: variables: " << mmvar->num_axis
+ // << " named styles: " << mmvar->num_namedstyles << std::endl;
+
+ // Get the required values from Pango Font Description
+ // Need to check format of values from Pango, for the moment accept any format.
+ Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("(\\w{4})=([-+]?\\d*\\.?\\d+([eE][-+]?\\d+)?)");
+ Glib::MatchInfo matchInfo;
+
+ const FT_UInt num_axis = openTypeVarAxes.size();
+ FT_Fixed w[num_axis];
+ for (int i = 0; i < num_axis; ++i) w[i] = 0;
+
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(",", variations);
+ for (auto token: tokens) {
+
+ regex->match(token, matchInfo);
+ if (matchInfo.matches()) {
+
+ float value = std::stod(matchInfo.fetch(2).raw()); // Should clamp value
+
+ // Translate the "named" axes.
+ Glib::ustring name = matchInfo.fetch(1);
+ if (name == "wdth") name = "Width" ; // 'font-stretch'
+ if (name == "wght") name = "Weight" ; // 'font-weight'
+ if (name == "opsz") name = "OpticalSize" ; // 'font-optical-sizing' (indirectly)
+ if (name == "slnt") name = "Slant" ; // 'font-style'
+ if (name == "ital") name = "Italic" ; // 'font-style'
+
+ auto it = openTypeVarAxes.find(name);
+ if (it != openTypeVarAxes.end()) {
+ it->second.set_val = value;
+ w[it->second.index] = value * 65536;
+ }
+ }
+ }
+
+ // Set design coordinates
+ FT_Error err;
+ err = FT_Set_Var_Design_Coordinates (theFace, num_axis, w);
+ if (err) {
+ std::cerr << "font_instance::InitTheFace(): Error in call to FT_Set_Var_Design_Coordinates(): " << err << std::endl;
+ }
+
+ // FT_Done_MM_Var(mmlib, mmvar);
+ }
+ }
+
+#endif // FreeType
+#endif // !USE_PANGO_WIN32
+
+ FindFontMetrics();
+ }
+
+#ifdef USE_PANGO_WIN32
+ // Someone (probably pango or cairo) sets the world transform during initialization and does not reset it.
+ // Work around this by explicitly setting it again (even if the font is already initialized)
+ XFORM identity = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0};
+ SetWorldTransform(parent->hScreenDC, &identity);
+ SetGraphicsMode(parent->hScreenDC, GM_COMPATIBLE);
+ SelectObject(parent->hScreenDC,theFace);
+#endif
+
+}
+
+void font_instance::FreeTheFace()
+{
+#ifdef USE_PANGO_WIN32
+ SelectObject(parent->hScreenDC,GetStockObject(SYSTEM_FONT));
+ pango_win32_font_cache_unload(parent->pangoFontCache,theFace);
+#else
+
+#if HB_VERSION_ATLEAST(2,6,5)
+ hb_ft_font_unlock_face(hb_font_copy);
+ hb_font_destroy(hb_font_copy);
+#else
+ pango_fc_font_unlock_face(PANGO_FC_FONT(pFont));
+#endif
+#endif
+ theFace=nullptr;
+}
+
+void font_instance::InstallFace(PangoFont* iFace)
+{
+ if ( !iFace ) {
+ return;
+ }
+ pFont=iFace;
+ iFace = nullptr;
+
+ InitTheFace();
+
+ if ( pFont && IsOutlineFont() == false ) {
+ FreeTheFace();
+ if ( pFont ) {
+ g_object_unref(pFont);
+ }
+ pFont=nullptr;
+ }
+}
+
+bool font_instance::IsOutlineFont()
+{
+ if ( pFont == nullptr ) {
+ return false;
+ }
+ InitTheFace();
+#ifdef USE_PANGO_WIN32
+ TEXTMETRIC tm;
+ return GetTextMetrics(parent->hScreenDC,&tm) && tm.tmPitchAndFamily&(TMPF_TRUETYPE|TMPF_DEVICE);
+#else
+ return FT_IS_SCALABLE(theFace);
+#endif
+}
+
+int font_instance::MapUnicodeChar(gunichar c)
+{
+ int res = 0;
+ if ( pFont ) {
+#ifdef USE_PANGO_WIN32
+ res = pango_win32_font_get_glyph_index(pFont, c);
+#else
+ if (!theFace) {
+ std::cerr << "Face not properly initialized (should not happen)" << std::endl;
+ }
+ if ( c > 0xf0000 ) {
+ res = CLAMP(c, 0xf0000, 0x1fffff) - 0xf0000;
+ } else {
+ res = FT_Get_Char_Index(theFace, c);
+ }
+#endif
+ }
+ return res;
+}
+
+
+#ifdef USE_PANGO_WIN32
+static inline Geom::Point pointfx_to_nrpoint(const POINTFX &p, double scale)
+{
+ return Geom::Point(*(long*)&p.x / 65536.0 * scale,
+ *(long*)&p.y / 65536.0 * scale);
+}
+#endif
+
+void font_instance::LoadGlyph(int glyph_id)
+{
+ if ( pFont == nullptr ) {
+ return;
+ }
+ InitTheFace();
+#ifndef USE_PANGO_WIN32
+ if ( !FT_IS_SCALABLE(theFace) ) {
+ return; // bitmap font
+ }
+#endif
+
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ Geom::PathBuilder path_builder;
+
+ if ( nbGlyph >= maxGlyph ) {
+ maxGlyph=2*nbGlyph+1;
+ glyphs=(font_glyph*)realloc(glyphs,maxGlyph*sizeof(font_glyph));
+ }
+ font_glyph n_g;
+ n_g.pathvector=nullptr;
+ n_g.bbox[0]=n_g.bbox[1]=n_g.bbox[2]=n_g.bbox[3]=0;
+ n_g.h_advance = 0;
+ n_g.v_advance = 0;
+ n_g.h_width = 0;
+ n_g.v_width = 0;
+ bool doAdd=false;
+
+#ifdef USE_PANGO_WIN32
+
+#ifndef GGO_UNHINTED // For compatibility with old SDKs.
+#define GGO_UNHINTED 0x0100
+#endif
+
+ MAT2 identity = {{0,1},{0,0},{0,0},{0,1}};
+ OUTLINETEXTMETRIC otm;
+ GetOutlineTextMetrics(parent->hScreenDC, sizeof(otm), &otm);
+ GLYPHMETRICS metrics;
+ DWORD bufferSize=GetGlyphOutline (parent->hScreenDC, glyph_id, GGO_GLYPH_INDEX | GGO_NATIVE | GGO_UNHINTED, &metrics, 0, NULL, &identity);
+ double scale=1.0/parent->fontSize;
+ n_g.h_advance = metrics.gmCellIncX * scale;
+ n_g.v_advance = otm.otmTextMetrics.tmHeight * scale;
+ n_g.h_width = metrics.gmBlackBoxX * scale;
+ n_g.v_width = metrics.gmBlackBoxY * scale;
+ if ( bufferSize == GDI_ERROR) {
+ // shit happened
+ } else if ( bufferSize == 0) {
+ // character has no visual representation, but is valid (eg whitespace)
+ doAdd=true;
+ } else {
+ char *buffer = new char[bufferSize];
+ if ( GetGlyphOutline (parent->hScreenDC, glyph_id, GGO_GLYPH_INDEX | GGO_NATIVE | GGO_UNHINTED, &metrics, bufferSize, buffer, &identity) <= 0 ) {
+ // shit happened
+ } else {
+ // Platform SDK is rubbish, read KB87115 instead
+ DWORD polyOffset=0;
+ while ( polyOffset < bufferSize ) {
+ TTPOLYGONHEADER const *polyHeader=(TTPOLYGONHEADER const *)(buffer+polyOffset);
+ if (polyOffset+polyHeader->cb > bufferSize) break;
+
+ if (polyHeader->dwType == TT_POLYGON_TYPE) {
+ path_builder.moveTo(pointfx_to_nrpoint(polyHeader->pfxStart, scale));
+ DWORD curveOffset=polyOffset+sizeof(TTPOLYGONHEADER);
+
+ while ( curveOffset < polyOffset+polyHeader->cb ) {
+ TTPOLYCURVE const *polyCurve=(TTPOLYCURVE const *)(buffer+curveOffset);
+ POINTFX const *p=polyCurve->apfx;
+ POINTFX const *endp=p+polyCurve->cpfx;
+
+ switch (polyCurve->wType) {
+ case TT_PRIM_LINE:
+ while ( p != endp )
+ path_builder.lineTo(pointfx_to_nrpoint(*p++, scale));
+ break;
+
+ case TT_PRIM_QSPLINE:
+ {
+ g_assert(polyCurve->cpfx >= 2);
+
+ // The list of points specifies one or more control points and ends with the end point.
+ // The intermediate points (on the curve) are the points between the control points.
+ Geom::Point this_control = pointfx_to_nrpoint(*p++, scale);
+ while ( p+1 != endp ) { // Process all "midpoints" (all points except the last)
+ Geom::Point new_control = pointfx_to_nrpoint(*p++, scale);
+ path_builder.quadTo(this_control, (new_control+this_control)/2);
+ this_control = new_control;
+ }
+ Geom::Point end = pointfx_to_nrpoint(*p++, scale);
+ path_builder.quadTo(this_control, end);
+ }
+ break;
+
+ case 3: // TT_PRIM_CSPLINE
+ g_assert(polyCurve->cpfx % 3 == 0);
+ while ( p != endp ) {
+ path_builder.curveTo(pointfx_to_nrpoint(p[0], scale),
+ pointfx_to_nrpoint(p[1], scale),
+ pointfx_to_nrpoint(p[2], scale));
+ p += 3;
+ }
+ break;
+ }
+ curveOffset += sizeof(TTPOLYCURVE)+sizeof(POINTFX)*(polyCurve->cpfx-1);
+ }
+ }
+ polyOffset += polyHeader->cb;
+ }
+ doAdd=true;
+ }
+ delete [] buffer;
+ }
+#else
+ if (FT_Load_Glyph (theFace, glyph_id, FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)) {
+ // shit happened
+ } else {
+ if ( FT_HAS_HORIZONTAL(theFace) ) {
+ n_g.h_advance=((double)theFace->glyph->metrics.horiAdvance)/((double)theFace->units_per_EM);
+ n_g.h_width=((double)theFace->glyph->metrics.width)/((double)theFace->units_per_EM);
+ } else {
+ n_g.h_width=n_g.h_advance=((double)(theFace->bbox.xMax-theFace->bbox.xMin))/((double)theFace->units_per_EM);
+ }
+ if ( FT_HAS_VERTICAL(theFace) ) {
+ n_g.v_advance=((double)theFace->glyph->metrics.vertAdvance)/((double)theFace->units_per_EM);
+ n_g.v_width=((double)theFace->glyph->metrics.height)/((double)theFace->units_per_EM);
+ } else {
+ // CSS3 Writing modes dictates that if vertical font metrics are missing we must
+ // synthisize them. No method is specified. The SVG 1.1 spec suggests using the em
+ // height (which is not theFace->height as that includes leading). The em height
+ // is ascender + descender (descender positive). Note: The "Requirements for
+ // Japanese Text Layout" W3C document says that Japanese kanji should be "set
+ // solid" which implies that vertical (and horizontal) advance should be 1em.
+ n_g.v_width=n_g.v_advance= 1.0;
+ }
+ if ( theFace->glyph->format == ft_glyph_format_outline ) {
+ FT_Outline_Funcs ft2_outline_funcs = {
+ ft2_move_to,
+ ft2_line_to,
+ ft2_conic_to,
+ ft2_cubic_to,
+ 0, 0
+ };
+ FT2GeomData user(path_builder, 1.0/((double)theFace->units_per_EM));
+ FT_Outline_Decompose (&theFace->glyph->outline, &ft2_outline_funcs, &user);
+ }
+ doAdd=true;
+ }
+#endif
+ path_builder.flush();
+
+ if ( doAdd ) {
+ Geom::PathVector pv = path_builder.peek();
+ // close all paths
+ for (auto & i : pv) {
+ i.close();
+ }
+ if ( !pv.empty() ) {
+ n_g.pathvector = new Geom::PathVector(pv);
+ Geom::OptRect bounds = bounds_exact(*n_g.pathvector);
+ if (bounds) {
+ n_g.bbox[0] = bounds->left();
+ n_g.bbox[1] = bounds->top();
+ n_g.bbox[2] = bounds->right();
+ n_g.bbox[3] = bounds->bottom();
+ }
+ }
+ glyphs[nbGlyph]=n_g;
+ id_to_no[glyph_id]=nbGlyph;
+ nbGlyph++;
+ }
+ } else {
+ }
+}
+
+bool font_instance::FontMetrics(double &ascent,double &descent,double &xheight)
+{
+ if ( pFont == nullptr ) {
+ return false;
+ }
+ InitTheFace();
+ if ( theFace == nullptr ) {
+ return false;
+ }
+
+ ascent = _ascent;
+ descent = _descent;
+ xheight = _xheight;
+
+ return true;
+}
+
+bool font_instance::FontDecoration( double &underline_position, double &underline_thickness,
+ double &linethrough_position, double &linethrough_thickness)
+{
+ if ( pFont == nullptr ) {
+ return false;
+ }
+ InitTheFace();
+ if ( theFace == nullptr ) {
+ return false;
+ }
+#ifdef USE_PANGO_WIN32
+ OUTLINETEXTMETRIC otm;
+ if ( !GetOutlineTextMetrics(parent->hScreenDC,sizeof(otm),&otm) ) {
+ return false;
+ }
+ double scale=1.0/parent->fontSize;
+ underline_position = fabs(otm.otmsUnderscorePosition *scale);
+ underline_thickness = fabs(otm.otmsUnderscoreSize *scale);
+ linethrough_position = fabs(otm.otmsStrikeoutPosition *scale);
+ linethrough_thickness = fabs(otm.otmsStrikeoutSize *scale);
+#else
+ if ( theFace->units_per_EM == 0 ) {
+ return false; // bitmap font
+ }
+ underline_position = fabs(((double)theFace->underline_position )/((double)theFace->units_per_EM));
+ underline_thickness = fabs(((double)theFace->underline_thickness)/((double)theFace->units_per_EM));
+ // there is no specific linethrough information, mock it up from other font fields
+ linethrough_position = fabs(((double)theFace->ascender / 3.0 )/((double)theFace->units_per_EM));
+ linethrough_thickness = fabs(((double)theFace->underline_thickness)/((double)theFace->units_per_EM));
+#endif
+ return true;
+}
+
+
+bool font_instance::FontSlope(double &run, double &rise)
+{
+ run = 0.0;
+ rise = 1.0;
+
+ if ( pFont == nullptr ) {
+ return false;
+ }
+ InitTheFace();
+ if ( theFace == nullptr ) {
+ return false;
+ }
+
+#ifdef USE_PANGO_WIN32
+ OUTLINETEXTMETRIC otm;
+ if ( !GetOutlineTextMetrics(parent->hScreenDC,sizeof(otm),&otm) ) return false;
+ run=otm.otmsCharSlopeRun;
+ rise=otm.otmsCharSlopeRise;
+#else
+ if ( !FT_IS_SCALABLE(theFace) ) {
+ return false; // bitmap font
+ }
+
+ TT_HoriHeader *hhea = (TT_HoriHeader*)FT_Get_Sfnt_Table(theFace, ft_sfnt_hhea);
+ if (hhea == nullptr) {
+ return false;
+ }
+ run = hhea->caret_Slope_Run;
+ rise = hhea->caret_Slope_Rise;
+#endif
+ return true;
+}
+
+Geom::OptRect font_instance::BBox(int glyph_id)
+{
+ int no = -1;
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ LoadGlyph(glyph_id);
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ // didn't load
+ } else {
+ no = id_to_no[glyph_id];
+ }
+ } else {
+ no = id_to_no[glyph_id];
+ }
+ if ( no < 0 ) {
+ return Geom::OptRect();
+ } else {
+ Geom::Point rmin(glyphs[no].bbox[0],glyphs[no].bbox[1]);
+ Geom::Point rmax(glyphs[no].bbox[2],glyphs[no].bbox[3]);
+ return Geom::Rect(rmin, rmax);
+ }
+}
+
+Geom::PathVector* font_instance::PathVector(int glyph_id)
+{
+ int no = -1;
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ LoadGlyph(glyph_id);
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ // didn't load
+ } else {
+ no = id_to_no[glyph_id];
+ }
+ } else {
+ no = id_to_no[glyph_id];
+ }
+ if ( no < 0 ) return nullptr;
+ return glyphs[no].pathvector;
+}
+
+Inkscape::Pixbuf* font_instance::PixBuf(int glyph_id)
+{
+ Inkscape::Pixbuf* pixbuf = nullptr;
+
+ auto glyph_iter = openTypeSVGGlyphs.find(glyph_id);
+ if (glyph_iter != openTypeSVGGlyphs.end()) {
+
+ // 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: cache pixbuf.
+ // To do: Error handling.
+
+ pixbuf = glyph_iter->second.pixbuf;
+ if (!pixbuf) {
+ Glib::ustring svg = glyph_iter->second.svg;
+
+ // std::cout << svg << std::endl;
+
+ // 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);
+ viewbox += " ";
+ viewbox += std::to_string(_design_units*2);
+ viewbox += "\"";
+
+ // Search for existing viewbox
+ Glib::RefPtr<Glib::Regex> regex =
+ Glib::Regex::create("viewBox=\"\\s*(\\d*\\.?\\d+)\\s*,?\\s*(\\d*\\.?\\d+)\\s*,?\\s*(\\d+\\.?\\d+)\\s*,?\\s*(\\d+\\.?\\d+)\\s*\"");
+ Glib::MatchInfo matchInfo;
+ regex->match(svg, matchInfo);
+
+ if (matchInfo.matches()) {
+ // We have viewBox! We must transform so viewBox corresponds to design units.
+
+ // Replace viewbox
+ svg = regex->replace_literal(svg, 0, viewbox, static_cast<Glib::RegexMatchFlags >(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());
+ // std::cout << " x: " << x
+ // << " y: " << y
+ // << " w: " << w
+ // << " h: " << h << std::endl;
+
+ if (w <= 0.0 or h <= 0.0) {
+ std::cerr << "font_instance::PixBuf: Invalid glyph width or height!" << std::endl;
+ } else {
+
+ double xscale = _design_units/w;
+ double yscale = _design_units/h;
+ double xtrans = _design_units/w * x;
+ double ytrans = _design_units/h * y;
+
+ if (xscale != 1.0 || yscale != 1.0) {
+ Glib::ustring group = "<g transform=\"matrix(";
+ group += std::to_string(xscale);
+ group += ", 0, 0, ";
+ group += std::to_string(yscale);
+ group += std::to_string(-xtrans);
+ group += ", ";
+ group += std::to_string(-ytrans);
+ group += ")\">";
+
+ // Insert start group tag after initial <svg>
+ Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("<\\s*svg.*?>");
+ regex->match(svg, matchInfo);
+ if (matchInfo.matches()) {
+ int start = -1;
+ int end = -1;
+ matchInfo.fetch_pos(0, start, end);
+ svg.insert(end, group);
+ } else {
+ std::cerr << "font_instance::PixBuf: Could not find <svg> tag!" << std::endl;
+ }
+
+ // Insert end group tag before final </svg> (To do: make sure it is final </svg>)
+ regex = Glib::Regex::create("<\\s*\\/\\s*svg.*?>");
+ regex->match(svg, matchInfo);
+ if (matchInfo.matches()) {
+ int start = -1;
+ int end = -1;
+ matchInfo.fetch_pos(0, start, end);
+ svg.insert(start, "</g>");
+ } else {
+ std::cerr << "font_instance::PixBuf: Could not find </svg> tag!" << std::endl;
+ }
+ }
+ }
+
+ } else {
+ // No viewBox! We insert one. (To do: Look at 'width' and 'height' to see if we must scale.)
+ Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("<\\s*svg");
+ viewbox.insert(0, "<svg ");
+ svg = regex->replace_literal(svg, 0, viewbox, static_cast<Glib::RegexMatchFlags >(0));
+ }
+
+ // std::cout << svg << std::endl;
+
+ // Finally create pixbuf!
+ pixbuf = Inkscape::Pixbuf::create_from_buffer(svg.raw());
+
+ // And cache it.
+ glyph_iter->second.pixbuf = pixbuf;
+ }
+ }
+
+ return pixbuf;
+}
+
+double font_instance::Advance(int glyph_id, bool vertical)
+{
+ int no = -1;
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ LoadGlyph(glyph_id);
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ // didn't load
+ } else {
+ no=id_to_no[glyph_id];
+ }
+ } else {
+ no = id_to_no[glyph_id];
+ }
+ if ( no >= 0 ) {
+ if ( vertical ) {
+ return glyphs[no].v_advance;
+ } else {
+ return glyphs[no].h_advance;
+ }
+ }
+ return 0;
+}
+
+// Internal function to find baselines
+void font_instance::FindFontMetrics() {
+
+ // CSS2 recommends using the OS/2 values sTypoAscender and sTypoDescender for the Typographic
+ // ascender and descender values:
+ // http://www.w3.org/TR/CSS2/visudet.html#sTypoAscender
+ // On Windows, the typographic ascender and descender are taken from the otmMacAscent and
+ // otmMacDescent values:
+ // http://microsoft.public.win32.programmer.gdi.narkive.com/LV6k4BDh/msdn-documentation-outlinetextmetrics-clarification
+ // The otmAscent and otmDescent values are the maximum ascent and maximum descent of all the
+ // glyphs in a font.
+ if ( theFace ) {
+
+#ifdef USE_PANGO_WIN32
+ OUTLINETEXTMETRIC otm;
+ if ( GetOutlineTextMetrics(parent->hScreenDC,sizeof(otm),&otm) ) {
+ double scale=1.0/parent->fontSize;
+ _ascent = fabs(otm.otmMacAscent * scale);
+ _descent = fabs(otm.otmMacDescent * scale);
+ _xheight = fabs(otm.otmsXHeight * scale);
+ _ascent_max = fabs(otm.otmAscent * scale);
+ _descent_max = fabs(otm.otmDescent * scale);
+ _design_units = parent->fontSize;
+
+ // In CSS em size is ascent + descent... which should be 1. If not,
+ // adjust so it is.
+ double em = _ascent + _descent;
+ if( em > 0 ) {
+ _ascent /= em;
+ _descent /= em;
+ }
+
+ // May not be necessary but if OS/2 table missing or not version 2 or higher,
+ // xheight might be zero.
+ if( _xheight == 0.0 ) {
+ _xheight = 0.5;
+ }
+
+ // Baselines defined relative to alphabetic.
+ _baselines[ SP_CSS_BASELINE_IDEOGRAPHIC ] = -_descent; // Recommendation
+ _baselines[ SP_CSS_BASELINE_HANGING ] = 0.8 * _ascent; // Guess
+ _baselines[ SP_CSS_BASELINE_MATHEMATICAL ] = 0.8 * _xheight; // Guess
+ _baselines[ SP_CSS_BASELINE_CENTRAL ] = 0.5 - _descent; // Definition
+ _baselines[ SP_CSS_BASELINE_MIDDLE ] = 0.5 * _xheight; // Definition
+ _baselines[ SP_CSS_BASELINE_TEXT_BEFORE_EDGE ] = _ascent; // Definition
+ _baselines[ SP_CSS_BASELINE_TEXT_AFTER_EDGE ] = -_descent; // Definition
+
+
+ MAT2 identity = {{0,1},{0,0},{0,0},{0,1}};
+ GLYPHMETRICS metrics;
+ int retval;
+
+ // Better math baseline:
+ // Try center of minus sign
+ retval = GetGlyphOutline (parent->hScreenDC, 0x2212, GGO_NATIVE | GGO_UNHINTED, &metrics, 0, NULL, &identity);
+ // If no minus sign, try hyphen
+ if( retval <= 0 )
+ retval = GetGlyphOutline (parent->hScreenDC, '-', GGO_NATIVE | GGO_UNHINTED, &metrics, 0, NULL, &identity);
+
+ if( retval > 0 ) {
+ double math = (metrics.gmptGlyphOrigin.y + 0.5 * metrics.gmBlackBoxY) * scale;
+ _baselines[ SP_CSS_BASELINE_MATHEMATICAL ] = math;
+ }
+
+ // Find hanging baseline... assume it is at top of 'म'.
+ retval = GetGlyphOutline (parent->hScreenDC, 0x092E, GGO_NATIVE | GGO_UNHINTED, &metrics, 0, NULL, &identity);
+ if( retval > 0 ) {
+ double hanging = metrics.gmptGlyphOrigin.y * scale;
+ _baselines[ SP_CSS_BASELINE_MATHEMATICAL ] = hanging;
+ }
+ }
+
+#else
+
+ if ( theFace->units_per_EM != 0 ) { // If zero then it's a bitmap font.
+
+ TT_OS2* os2 = (TT_OS2*)FT_Get_Sfnt_Table( theFace, ft_sfnt_os2 );
+ if( os2 ) {
+ _ascent = fabs(((double)os2->sTypoAscender) / ((double)theFace->units_per_EM));
+ _descent = fabs(((double)os2->sTypoDescender)/ ((double)theFace->units_per_EM));
+ } else {
+ _ascent = fabs(((double)theFace->ascender) / ((double)theFace->units_per_EM));
+ _descent = fabs(((double)theFace->descender) / ((double)theFace->units_per_EM));
+ }
+ _ascent_max = fabs(((double)theFace->ascender) / ((double)theFace->units_per_EM));
+ _descent_max = fabs(((double)theFace->descender) / ((double)theFace->units_per_EM));
+ _design_units = theFace->units_per_EM;
+
+ // In CSS em size is ascent + descent... which should be 1. If not,
+ // adjust so it is.
+ double em = _ascent + _descent;
+ if( em > 0 ) {
+ _ascent /= em;
+ _descent /= em;
+ }
+
+ // x-height
+ if( os2 && os2->version >= 0x0002 && os2->version != 0xffffu ) {
+ // Only os/2 version 2 and above have sxHeight, 0xffff marks "old Mac fonts" without table
+ _xheight = fabs(((double)os2->sxHeight) / ((double)theFace->units_per_EM));
+ } else {
+ // Measure 'x' height in font. Recommended option by XSL standard if no sxHeight.
+ FT_UInt index = FT_Get_Char_Index( theFace, 'x' );
+ if( index != 0 ) {
+ FT_Load_Glyph( theFace, index, FT_LOAD_NO_SCALE );
+ _xheight = (fabs)(((double)theFace->glyph->metrics.height/(double)theFace->units_per_EM));
+ } else {
+ // No 'x' in font!
+ _xheight = 0.5;
+ }
+ }
+
+ // Baselines defined relative to alphabetic.
+ _baselines[ SP_CSS_BASELINE_IDEOGRAPHIC ] = -_descent; // Recommendation
+ _baselines[ SP_CSS_BASELINE_HANGING ] = 0.8 * _ascent; // Guess
+ _baselines[ SP_CSS_BASELINE_MATHEMATICAL ] = 0.8 * _xheight; // Guess
+ _baselines[ SP_CSS_BASELINE_CENTRAL ] = 0.5 - _descent; // Definition
+ _baselines[ SP_CSS_BASELINE_MIDDLE ] = 0.5 * _xheight; // Definition
+ _baselines[ SP_CSS_BASELINE_TEXT_BEFORE_EDGE ] = _ascent; // Definition
+ _baselines[ SP_CSS_BASELINE_TEXT_AFTER_EDGE ] = -_descent; // Definition
+
+ // Better math baseline:
+ // Try center of minus sign
+ FT_UInt index = FT_Get_Char_Index( theFace, 0x2212 ); //'−'
+ // If no minus sign, try hyphen
+ if( index == 0 )
+ index = FT_Get_Char_Index( theFace, '-' );
+
+ if( index != 0 ) {
+ FT_Load_Glyph( theFace, index, FT_LOAD_NO_SCALE );
+ FT_Glyph aglyph;
+ FT_Get_Glyph( theFace->glyph, &aglyph );
+ FT_BBox acbox;
+ FT_Glyph_Get_CBox( aglyph, FT_GLYPH_BBOX_UNSCALED, &acbox );
+ double math = (acbox.yMin + acbox.yMax)/2.0/(double)theFace->units_per_EM;
+ _baselines[ SP_CSS_BASELINE_MATHEMATICAL ] = math;
+ // std::cout << "Math baseline: - bbox: y_min: " << acbox.yMin
+ // << " y_max: " << acbox.yMax
+ // << " math: " << math << std::endl;
+ FT_Done_Glyph(aglyph);
+ }
+
+ // Find hanging baseline... assume it is at top of 'म'.
+ index = FT_Get_Char_Index( theFace, 0x092E ); // 'म'
+ if( index != 0 ) {
+ FT_Load_Glyph( theFace, index, FT_LOAD_NO_SCALE );
+ FT_Glyph aglyph;
+ FT_Get_Glyph( theFace->glyph, &aglyph );
+ FT_BBox acbox;
+ FT_Glyph_Get_CBox( aglyph, FT_GLYPH_BBOX_UNSCALED, &acbox );
+ double hanging = (double)acbox.yMax/(double)theFace->units_per_EM;
+ _baselines[ SP_CSS_BASELINE_HANGING ] = hanging;
+ // std::cout << "Hanging baseline: प: " << hanging << std::endl;
+ FT_Done_Glyph(aglyph);
+ }
+ }
+#endif
+ // const gchar *family = pango_font_description_get_family(descr);
+ // std::cout << "Font: " << (family?family:"null") << std::endl;
+ // std::cout << " ascent: " << _ascent << std::endl;
+ // std::cout << " descent: " << _descent << std::endl;
+ // std::cout << " x-height: " << _xheight << std::endl;
+ // std::cout << " max ascent: " << _ascent_max << std::endl;
+ // std::cout << " max descent: " << _descent_max << std::endl;
+ // std::cout << " Baselines:" << std::endl;
+ // std::cout << " alphabetic: " << _baselines[ SP_CSS_BASELINE_ALPHABETIC ] << std::endl;
+ // std::cout << " ideographic: " << _baselines[ SP_CSS_BASELINE_IDEOGRAPHIC ] << std::endl;
+ // std::cout << " hanging: " << _baselines[ SP_CSS_BASELINE_HANGING ] << std::endl;
+ // std::cout << " math: " << _baselines[ SP_CSS_BASELINE_MATHEMATICAL ] << std::endl;
+ // std::cout << " central: " << _baselines[ SP_CSS_BASELINE_CENTRAL ] << std::endl;
+ // std::cout << " middle: " << _baselines[ SP_CSS_BASELINE_MIDDLE ] << std::endl;
+ // std::cout << " text_before: " << _baselines[ SP_CSS_BASELINE_TEXT_BEFORE_EDGE ] << std::endl;
+ // std::cout << " text_after: " << _baselines[ SP_CSS_BASELINE_TEXT_AFTER_EDGE ] << std::endl;
+ }
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+ */
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp
new file mode 100644
index 0000000..aa7992c
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Compute.cpp
@@ -0,0 +1,2414 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::Text::Layout::Calculator - text layout engine meaty bits
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <iomanip>
+
+#include "Layout-TNG.h"
+#include "style.h"
+#include "font-instance.h"
+#include "svg/svg-length.h"
+#include "object/sp-object.h"
+#include "Layout-TNG-Scanline-Maker.h"
+#include <limits>
+#include "livarot/Shape.h"
+
+namespace Inkscape {
+namespace Text {
+
+//#define DEBUG_LAYOUT_TNG_COMPUTE
+//#define DEBUG_GLYPH
+
+//#define IFTRACE(_code) _code
+#define IFTRACE(_code)
+
+#define TRACE(_args) IFTRACE(g_print _args)
+
+/** \brief private to Layout. Does the real work of text flowing.
+
+This class does a standard greedy paragraph wrapping algorithm.
+
+Very high-level overview:
+
+<pre>
+foreach(paragraph) {
+ call pango_itemize() (_buildPangoItemizationForPara())
+ break into spans, without dealing with wrapping (_buildSpansForPara())
+ foreach(line in flow shape) {
+ foreach(chunk in flow shape) { (in _buildChunksInScanRun())
+ // this inner loop in _measureUnbrokenSpan()
+ if the line height changed discard the line and start again
+ keep adding characters until we run out of space in the chunk, then back up to the last word boundary
+ (do sensible things if there is no previous word break)
+ }
+ push all the glyphs, chars, spans, chunks and line to output (not completely trivial because we must draw rtl in character order) (in _outputLine())
+ }
+ push the paragraph (in calculate())
+}
+</pre>
+
+...and all of that needs to work vertically too, and with all the little details that make life annoying
+*/
+class Layout::Calculator
+{
+ class SpanPosition;
+ friend class SpanPosition;
+ Layout &_flow;
+ ScanlineMaker *_scanline_maker;
+ unsigned _current_shape_index; /// index into Layout::_input_wrap_shapes
+ PangoContext *_pango_context;
+ Direction _block_progression;
+
+ /**
+ * For y= attributes in tspan elements et al, we do the adjustment by moving each
+ * glyph individually by this number. The spec means that this is maintained across
+ * paragraphs.
+ *
+ * To do non-flow text layout, only the first "y" attribute is normally used. If there is only one
+ * "y" attribute in a <tspan> other than the first <tspan>, it is ignored. This allows Inkscape to
+ * insert a new line anywhere. On output, the Inkscape determined "y" is written out so other SVG
+ * viewers know where to place the <tspans>.
+ */
+ double _y_offset;
+
+ /** to stop pango from hinting its output, the font factory creates all fonts very large.
+ All numbers returned from pango have to be divided by this number \em and divided by
+ PANGO_SCALE. See font_factory::font_factory(). */
+ double _font_factory_size_multiplier;
+
+ /** Temporary storage associated with each item in Layout::_input_stream. */
+ struct InputItemInfo {
+ bool in_sub_flow;
+ Layout *sub_flow; // this is only set for the first input item in a sub-flow
+
+ InputItemInfo() : in_sub_flow(false), sub_flow(nullptr) {}
+
+ /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
+ * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
+ * that does delete or Unref.
+ *
+ * I suggest using the garbage collector to manage deletion.
+ */
+ void free()
+ {
+ if (sub_flow) {
+ delete sub_flow;
+ sub_flow = nullptr;
+ }
+ }
+ };
+
+ /** Temporary storage associated with each item returned by the call to
+ pango_itemize(). */
+ struct PangoItemInfo {
+ PangoItem *item;
+ font_instance *font;
+
+ PangoItemInfo() : item(nullptr), font(nullptr) {}
+
+ /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
+ * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
+ * that does delete or Unref.
+ *
+ * I suggest using the garbage collector to manage deletion.
+ */
+ void free()
+ {
+ if (item) {
+ pango_item_free(item);
+ item = nullptr;
+ }
+ if (font) {
+ font->Unref();
+ font = nullptr;
+ }
+ }
+ };
+
+
+ /** These spans have approximately the same definition as that used for
+ * Layout::Span (constant font, direction, etc), except that they are from
+ * before we have located the line breaks, so bear no relation to chunks.
+ * They are guaranteed to be in at most one PangoItem (spans with no text in
+ * them will not have an associated PangoItem), exactly one input object and
+ * will only have one change of x, y, dx, dy or rotate attribute, which will
+ * be at the beginning. An UnbrokenSpan can cross a chunk boundary, c.f.
+ * BrokenSpan.
+ */
+ struct UnbrokenSpan {
+ PangoGlyphString *glyph_string;
+ int pango_item_index; /// index into _para.pango_items, or -1 if this is style only
+ unsigned input_index; /// index into Layout::_input_stream
+ Glib::ustring::const_iterator input_stream_first_character;
+ double font_size;
+ FontMetrics line_height; /// This is not the CSS line-height attribute!
+ double line_height_multiplier; /// calculated from the font-height css property
+ double baseline_shift; /// calculated from the baseline-shift css property
+ SPCSSTextOrientation text_orientation;
+ unsigned text_bytes;
+ unsigned char_index_in_para; /// the index of the first character in this span in the paragraph, for looking up char_attributes
+ SVGLength x, y, dx, dy, rotate; // these are reoriented copies of the <tspan> attributes. We change span when we encounter one.
+
+ UnbrokenSpan() : glyph_string(nullptr) {}
+ void free()
+ {
+ if (glyph_string)
+ pango_glyph_string_free(glyph_string);
+ glyph_string = nullptr;
+ }
+ };
+
+
+ /** Used to provide storage for anything that applies to the current
+ paragraph only. Since we're only processing one paragraph at a time,
+ there's only one instantiation of this struct, on the stack of
+ calculate(). */
+ struct ParagraphInfo {
+ Glib::ustring text;
+ unsigned first_input_index; ///< Index into Layout::_input_stream.
+ Direction direction;
+ Alignment alignment;
+ std::vector<InputItemInfo> input_items;
+ std::vector<PangoItemInfo> pango_items;
+ std::vector<PangoLogAttr> char_attributes; ///< For every character in the paragraph.
+ std::vector<UnbrokenSpan> unbroken_spans;
+
+ template<typename T> static void free_sequence(T &seq)
+ {
+ for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
+ it->free();
+ }
+ seq.clear();
+ }
+
+ void free()
+ {
+ text = "";
+ free_sequence(input_items);
+ free_sequence(pango_items);
+ free_sequence(unbroken_spans);
+ }
+ };
+
+
+ /**
+ * A useful little iterator for moving char-by-char across spans.
+ */
+ struct UnbrokenSpanPosition {
+ std::vector<UnbrokenSpan>::iterator iter_span;
+ unsigned char_byte;
+ unsigned char_index;
+
+ void increment(); ///< Step forward by one character.
+
+ inline bool operator== (UnbrokenSpanPosition const &other) const
+ {return char_byte == other.char_byte && iter_span == other.iter_span;}
+ inline bool operator!= (UnbrokenSpanPosition const &other) const
+ {return char_byte != other.char_byte || iter_span != other.iter_span;}
+ };
+
+ /**
+ * The line breaking algorithm will convert each UnbrokenSpan into one
+ * or more of these. A BrokenSpan will never cross a chunk boundary,
+ * c.f. UnbrokenSpan.
+ */
+ struct BrokenSpan {
+ UnbrokenSpanPosition start;
+ UnbrokenSpanPosition end; // the end of this will always be the same as the start of the next
+ unsigned start_glyph_index;
+ unsigned end_glyph_index;
+ double width;
+ unsigned whitespace_count;
+ bool ends_with_whitespace;
+ double each_whitespace_width;
+ double letter_spacing; // Save so we can subtract from width at end of line (for center justification)
+ double word_spacing;
+ void setZero();
+ };
+
+ /** The definition of a chunk used here is the same as that used in Layout:
+ A collection of contiguous broken spans on the same line. (One chunk per line
+ unless shape splits line into several sections... then one chunk per section. */
+ struct ChunkInfo {
+ std::vector<BrokenSpan> broken_spans;
+ double scanrun_width;
+ double text_width; ///< Total width used by the text (excluding justification).
+ double x;
+ int whitespace_count;
+ };
+
+ void _buildPangoItemizationForPara(ParagraphInfo *para) const;
+ static double _computeFontLineHeight( SPStyle const *style ); // Returns line_height_multiplier
+ unsigned _buildSpansForPara(ParagraphInfo *para) const;
+ bool _goToNextWrapShape();
+ void _createFirstScanlineMaker();
+
+ bool _findChunksForLine(ParagraphInfo const &para,
+ UnbrokenSpanPosition *start_span_pos,
+ std::vector<ChunkInfo> *chunk_info,
+ FontMetrics *line_box_height,
+ FontMetrics const *strut_height);
+
+ bool _buildChunksInScanRun(ParagraphInfo const &para,
+ UnbrokenSpanPosition const &start_span_pos,
+ ScanlineMaker::ScanRun const &scan_run,
+ std::vector<ChunkInfo> *chunk_info,
+ FontMetrics *line_height) const;
+
+ bool _measureUnbrokenSpan(ParagraphInfo const &para,
+ BrokenSpan *span,
+ BrokenSpan *last_break_span,
+ BrokenSpan *last_emergency_break_span,
+ double maximum_width) const;
+
+ double _getChunkLeftWithAlignment(ParagraphInfo const &para,
+ std::vector<ChunkInfo>::const_iterator it_chunk,
+ double *add_to_each_whitespace) const;
+
+ void _outputLine(ParagraphInfo const &para,
+ FontMetrics const &line_height,
+ std::vector<ChunkInfo> const &chunk_info,
+ bool hidden);
+
+ static inline PangoLogAttr const &_charAttributes(ParagraphInfo const &para,
+ UnbrokenSpanPosition const &span_pos)
+ {
+ return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index];
+ }
+
+#ifdef DEBUG_LAYOUT_TNG_COMPUTE
+ static void dumpPangoItemsOut(ParagraphInfo *para);
+ static void dumpUnbrokenSpans(ParagraphInfo *para);
+#endif //DEBUG_LAYOUT_TNG_COMPUTE
+
+public:
+ Calculator(Layout *text_flow)
+ : _flow(*text_flow) {}
+
+ bool calculate();
+};
+
+
+/**
+ * Computes the width of a single UnbrokenSpan (pointed to by span->start.iter_span)
+ * and outputs its vital statistics into the other fields of \a span.
+ * Measuring will stop if maximum_width is reached and in that case the
+ * function will return false. In other cases where a line break must be
+ * done immediately the function will also return false. On return
+ * \a last_break_span will contain the vital statistics for the span only
+ * up to the last line breaking change. If there are no line breaking
+ * characters in the span then \a last_break_span will not be altered.
+ * Similarly, \a last_emergency_break_span will contain the vital
+ * statistics for the span up to the last inter-character boundary,
+ * or will be unaltered if there is none.
+ *
+ * An unbroken span corresponds to at most one PangoItem
+ */
+bool Layout::Calculator::_measureUnbrokenSpan(ParagraphInfo const &para,
+ BrokenSpan *span,
+ BrokenSpan *last_break_span,
+ BrokenSpan *last_emergency_break_span,
+ double maximum_width) const
+{
+ TRACE((" start _measureUnbrokenSpan %g\n", maximum_width));
+ span->setZero();
+
+ if (span->start.iter_span->dx._set && span->start.char_byte == 0){
+ if(para.direction == RIGHT_TO_LEFT){
+ span->width -= span->start.iter_span->dx.computed;
+ } else {
+ span->width += span->start.iter_span->dx.computed;
+ }
+ }
+
+ if (span->start.iter_span->pango_item_index == -1) {
+ // if this is a style-only span there's no text in it
+ // so we don't need to do very much at all
+ span->end.iter_span++;
+ return true;
+ }
+
+ if (_flow._input_stream[span->start.iter_span->input_index]->Type() == CONTROL_CODE) {
+
+ InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[span->start.iter_span->input_index]);
+
+ if (control_code->code == SHAPE_BREAK || control_code->code == PARAGRAPH_BREAK) {
+ *last_emergency_break_span = *last_break_span = *span;
+ return false;
+ }
+
+ if (control_code->code == ARBITRARY_GAP) { // Not used!
+ if (span->width + control_code->width > maximum_width)
+ return false;
+ TRACE((" fitted control code, width = %f\n", control_code->width));
+ span->width += control_code->width;
+ span->end.increment();
+ }
+ return true;
+ }
+
+ if (_flow._input_stream[span->start.iter_span->input_index]->Type() != TEXT_SOURCE)
+ return true; // never happens
+
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[span->start.iter_span->input_index]);
+
+ if (_directions_are_orthogonal(_block_progression, text_source->styleGetBlockProgression())) {
+ // TODO: block-progression altered in the middle
+ // Measure the precomputed flow from para.input_items
+ span->end.iter_span++; // for now, skip to the next span
+ return true;
+ }
+
+ // a normal span going with a normal block-progression
+ double font_size_multiplier = span->start.iter_span->font_size / (PANGO_SCALE * _font_factory_size_multiplier);
+ double soft_hyphen_glyph_width = 0.0;
+ bool soft_hyphen_in_word = false;
+ bool is_soft_hyphen = false;
+ IFTRACE(int char_count = 0);
+
+ // if we're not at the start of the span we need to pre-init glyph_index
+ span->start_glyph_index = 0;
+ while (span->start_glyph_index < (unsigned)span->start.iter_span->glyph_string->num_glyphs
+ && span->start.iter_span->glyph_string->log_clusters[span->start_glyph_index] < (int)span->start.char_byte)
+ span->start_glyph_index++;
+ span->end_glyph_index = span->start_glyph_index;
+
+ // go char-by-char summing the width, while keeping track of the previous break point
+ do {
+ PangoLogAttr const &char_attributes = _charAttributes(para, span->end);
+
+ // guint32 c = *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte);
+ // std::cout << " char_byte: " << span->end.char_byte
+ // << " char_index: " << span->end.char_index
+ // << " c: " << c << " " << char(c==10 ? '␤' : c)
+ // << " line: " << std::boolalpha << char_attributes.is_line_break
+ // << " mandatory: " << std::boolalpha << char_attributes.is_mandatory_break // Note, break is before character!
+ // << " char: " << std::boolalpha << char_attributes.is_char_break
+ // << std::endl;
+
+ if (char_attributes.is_mandatory_break && span->end != span->start) {
+ TRACE((" is_mandatory_break ************\n"));
+ *last_emergency_break_span = *last_break_span = *span;
+ TRACE((" span %ld end of para; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
+ return false;
+ }
+
+ if (char_attributes.is_line_break) {
+ TRACE((" is_line_break ************\n"));
+ // a suitable position to break at, record where we are
+ *last_emergency_break_span = *last_break_span = *span;
+ if (soft_hyphen_in_word) {
+ // if there was a previous soft hyphen we're not going to need it any more so we can remove it
+ span->width -= soft_hyphen_glyph_width;
+ if (!is_soft_hyphen)
+ soft_hyphen_in_word = false;
+ }
+ } else if (char_attributes.is_char_break) {
+ *last_emergency_break_span = *span;
+ }
+ // todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing
+
+ // sum the glyph widths, letter spacing, word spacing, and textLength adjustment to get the character width
+ double char_width = 0.0;
+ while (span->end_glyph_index < (unsigned)span->end.iter_span->glyph_string->num_glyphs
+ && span->end.iter_span->glyph_string->log_clusters[span->end_glyph_index] <= (int)span->end.char_byte) {
+
+ PangoGlyphInfo *info = &(span->end.iter_span->glyph_string->glyphs[span->end_glyph_index]);
+ double glyph_width = font_size_multiplier * info->geometry.width;
+
+ // Advance does not include kerning but Pango gives wrong advances for vertical text
+ // with upright orientation (pre 1.44.0).
+ font_instance *font = para.pango_items[span->end.iter_span->pango_item_index].font;
+ double font_size = span->start.iter_span->font_size;
+ //double glyph_h_advance = font_size * font->Advance(info->glyph, false);
+ double glyph_v_advance = font_size * font->Advance(info->glyph, true );
+
+ if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
+ // Vertical text
+
+ if( text_source->style->text_orientation.computed == SP_CSS_TEXT_ORIENTATION_SIDEWAYS ||
+ (text_source->style->text_orientation.computed == SP_CSS_TEXT_ORIENTATION_MIXED &&
+ para.pango_items[span->end.iter_span->pango_item_index].item->analysis.gravity == PANGO_GRAVITY_SOUTH) ) {
+ // Sideways orientation
+ char_width += glyph_width;
+ } else {
+ // Upright orientation
+ guint32 c = *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte);
+ if (g_unichar_type (c) != G_UNICODE_NON_SPACING_MARK) {
+ // Non-spacing marks should not contribute to width. Fonts may not report the correct advance, especially if the 'vmtx' table is missing.
+ if (pango_version_check(1,44,0) != nullptr) {
+ // Pango >= 1.44.0
+ char_width += glyph_width;
+ } else {
+ // Pango < 1.44.0 glyph_width returned is horizontal width, not vertical.
+ char_width += glyph_v_advance;
+ }
+ }
+ }
+ } else {
+ // Horizontal text
+ char_width += glyph_width;
+ }
+ span->end_glyph_index++;
+ }
+
+ if (char_attributes.is_cursor_position)
+ char_width += text_source->style->letter_spacing.computed * _flow.getTextLengthMultiplierDue();
+ if (char_attributes.is_white)
+ char_width += text_source->style->word_spacing.computed * _flow.getTextLengthMultiplierDue();
+ char_width += _flow.getTextLengthIncrementDue();
+ span->width += char_width;
+ IFTRACE(char_count++);
+
+ if (char_attributes.is_white) {
+ span->whitespace_count++;
+ span->each_whitespace_width = char_width;
+ }
+ span->ends_with_whitespace = char_attributes.is_white;
+
+ is_soft_hyphen = (UNICODE_SOFT_HYPHEN == *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte));
+ if (is_soft_hyphen)
+ soft_hyphen_glyph_width = char_width;
+
+ // Go to next character (resets end.char_byte to zero if at end)
+ span->end.increment();
+
+ // Width should not include letter_spacing (or word_spacing) after last letter at end of line.
+ // word_spacing is attached to white space that is already removed from line end (?)
+ double test_width = span->width - text_source->style->letter_spacing.computed;
+
+ // Save letter_spacing and word_spacing for subtraction later if span is last span in line.
+ span->letter_spacing = text_source->style->letter_spacing.computed;
+ span->word_spacing = text_source->style->word_spacing.computed;
+
+ if (test_width > maximum_width && !char_attributes.is_white) { // whitespaces don't matter, we can put as many as we want at eol
+ TRACE((" span %ld exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
+ return false;
+ }
+
+ } while (span->end.char_byte != 0); // while we haven't wrapped to the next span
+
+ TRACE((" fitted span %ld width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
+ TRACE((" end _measureUnbrokenSpan %g\n", maximum_width));
+ return true;
+}
+
+/* *********************************************************************************************************/
+// Per-line functions (output)
+
+/** Uses the paragraph alignment and the chunk information to work out
+ * where the actual left of the final chunk must be. Also sets
+ * \a add_to_each_whitespace to be the amount of x to add at each
+ * whitespace character to make full justification work.
+ */
+double Layout::Calculator::_getChunkLeftWithAlignment(ParagraphInfo const &para,
+ std::vector<ChunkInfo>::const_iterator it_chunk,
+ double *add_to_each_whitespace) const
+{
+ *add_to_each_whitespace = 0.0;
+ if (_flow._input_wrap_shapes.empty()) {
+ switch (para.alignment) {
+ case FULL:
+ case LEFT:
+ default:
+ return it_chunk->x;
+ case RIGHT:
+ return it_chunk->x - it_chunk->text_width;
+ case CENTER:
+ return it_chunk->x - it_chunk->text_width/ 2;
+ }
+ }
+
+ switch (para.alignment) {
+ case FULL:
+ // Don't justify the last line chunk in the span
+ if (!it_chunk->broken_spans.empty() && it_chunk->broken_spans.back().end.iter_span != para.unbroken_spans.end()) {
+
+ // Don't justify a single word or a line that ends with a manual line break.
+ PangoLogAttr const &char_attributes = _charAttributes(para, it_chunk->broken_spans.back().end);
+ if (it_chunk->whitespace_count && !char_attributes.is_mandatory_break) {
+
+ // Set the amount of extra space between each word to a fraction
+ // of the remaining line space to justify this line.
+ *add_to_each_whitespace = (it_chunk->scanrun_width - it_chunk->text_width) / it_chunk->whitespace_count;
+ }
+ }
+ return it_chunk->x;
+ case LEFT:
+ default:
+ return it_chunk->x;
+ case RIGHT:
+ return it_chunk->x + it_chunk->scanrun_width - it_chunk->text_width;
+ case CENTER:
+ return it_chunk->x + (it_chunk->scanrun_width - it_chunk->text_width) / 2;
+ }
+}
+
+/**
+ * Once we've got here we have finished making changes to the line
+ * and are ready to output the final result to #_flow.
+ * This method takes its input parameters and does that.
+ */
+void Layout::Calculator::_outputLine(ParagraphInfo const &para,
+ FontMetrics const &line_height,
+ std::vector<ChunkInfo> const &chunk_info,
+ bool hidden)
+{
+ TRACE((" Start _outputLine: ascent %f, descent %f, top of box %f\n", line_height.ascent, line_height.descent, _scanline_maker->yCoordinate() ));
+ if (chunk_info.empty()) {
+ TRACE((" line too short to fit anything on it, go to next\n"));
+ return;
+ }
+
+ // we've finished fiddling about with ascents and descents: create the output
+ TRACE((" found line fit; creating output\n"));
+ Layout::Line new_line;
+ new_line.in_paragraph = _flow._paragraphs.size() - 1;
+ new_line.baseline_y = _scanline_maker->yCoordinate();
+ new_line.hidden = hidden;
+
+ // The y coordinate is at the beginning edge of the line box (top for horizontal text, left
+ // edge for vertical lr text, right edge for vertical rl text. We align, by default to the
+ // alphabetic baseline for horizontal text and the central baseline for vertical text.
+ if( _block_progression == RIGHT_TO_LEFT ) {
+ // Vertical text, use em box center as baseline
+ new_line.baseline_y -= 0.5 * line_height.emSize();
+ } else if ( _block_progression == LEFT_TO_RIGHT ) {
+ // Vertical text, use em box center as baseline
+ new_line.baseline_y += 0.5 * line_height.emSize();
+ } else {
+ new_line.baseline_y += line_height.getTypoAscent();
+ }
+
+
+ TRACE((" initial new_line.baseline_y: %f\n", new_line.baseline_y ));
+
+ new_line.in_shape = _current_shape_index;
+ _flow._lines.push_back(new_line);
+
+ for (std::vector<ChunkInfo>::const_iterator it_chunk = chunk_info.begin() ; it_chunk != chunk_info.end() ; it_chunk++) {
+ double add_to_each_whitespace;
+ // add the chunk to the list
+ Layout::Chunk new_chunk;
+ new_chunk.in_line = _flow._lines.size() - 1;
+
+ TRACE((" New chunk: in_line: %d\n", new_chunk.in_line));
+ if (hidden) {
+ new_chunk.left_x = it_chunk->x; // Don't align. We'll place below last shape.
+ } else {
+ new_chunk.left_x = _getChunkLeftWithAlignment(para, it_chunk, &add_to_each_whitespace);
+ }
+
+ // we may also have y move orders to deal with here (dx, dy and rotate are done per span)
+
+ // Comment updated: 23 July 2010:
+ // We must handle two cases:
+ //
+ // 1. Inkscape SVG where the first line is placed by the read-in "y" value and the
+ // rest are determined by 'font-size' and 'line-height' (and not by any
+ // y-kerning). <tspan>s in this case are marked by sodipodi:role="line". This
+ // allows new lines to be inserted in the middle of a <text> object. On output,
+ // new "y" values are calculated for each <tspan> that represents a new line. Line
+ // spacing is already handled by the calling routine.
+ //
+ // 2. Plain SVG where each <text> or <tspan> is placed by its own "x" and "y" values.
+ // Note that in this case Inkscape treats each <text> object with any included
+ // <tspan>s as a single line of text. This can be confusing in the code below.
+
+ if (!it_chunk->broken_spans.empty() // Not empty paragraph
+ && it_chunk->broken_spans.front().start.char_byte == 0 ) { // Beginning of unbroken span
+
+ // If empty or new line (sodipode:role="line")
+ if( _flow._characters.empty() ||
+ _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
+
+ // This is the Inkscape SVG case.
+ //
+ // If <tspan> "y" attribute is set, use it (initial "y" attributes in
+ // <tspans> other than the first have already been stripped for <tspans>
+ // marked with role="line", see sp-text.cpp: SPText::_buildLayoutInput).
+ // NOTE: for vertical text, "y" is the user-space "x" value.
+ if( it_chunk->broken_spans.front().start.iter_span->y._set ) {
+
+ // Use set "y" attribute for baseline
+ new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed;
+
+ TRACE((" chunk new_line.baseline_y: %f\n", new_line.baseline_y ));
+
+ // Save baseline
+ _flow._lines.back().baseline_y = new_line.baseline_y;
+
+ // Calculate new top of box... given specified baseline.
+ double top_of_line_box = new_line.baseline_y;
+ if( _block_progression == RIGHT_TO_LEFT ) {
+ // Vertical text, use em box center as baseline
+ top_of_line_box += 0.5 * line_height.emSize();
+ } else if (_block_progression == LEFT_TO_RIGHT ) {
+ // Vertical text, use em box center as baseline
+ top_of_line_box -= 0.5 * line_height.emSize();
+ } else {
+ top_of_line_box -= line_height.getTypoAscent();
+ }
+ TRACE((" y attribute set, next line top_of_line_box: %f\n", top_of_line_box ));
+ // Set the initial y coordinate of the for this line (see above).
+ _scanline_maker->setNewYCoordinate(top_of_line_box);
+ }
+
+ // Reset relative y_offset ("dy" attribute is relative but should be reset at
+ // the beginning of each line since each line will have a new "y" written out.)
+ _y_offset = 0.0;
+
+ } else {
+
+ // This is the plain SVG case
+ //
+ // "x" and "y" are used to place text, simulating lines as necessary
+ if( it_chunk->broken_spans.front().start.iter_span->y._set ) {
+ _y_offset = it_chunk->broken_spans.front().start.iter_span->y.computed - new_line.baseline_y;
+ }
+ }
+ }
+ _flow._chunks.push_back(new_chunk);
+
+ double current_x;
+ double direction_sign;
+ Direction previous_direction = para.direction;
+ double counter_directional_width_remaining = 0.0;
+ float glyph_rotate = 0.0;
+ if (para.direction == LEFT_TO_RIGHT) {
+ direction_sign = +1.0;
+ current_x = 0.0;
+ } else {
+ direction_sign = -1.0;
+ if (para.alignment == FULL && !_flow._input_wrap_shapes.empty()){
+ current_x = it_chunk->scanrun_width;
+ }
+ else {
+ current_x = it_chunk->text_width;
+ }
+ }
+
+ // Loop over broken spans; a broken span is part of no more than one PangoItem.
+ for (std::vector<BrokenSpan>::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) {
+
+ // begin adding spans to the list
+ UnbrokenSpan const &unbroken_span = *it_span->start.iter_span;
+ double x_in_span_last = 0.0; // set at the END when a new cluster starts
+ double x_in_span = 0.0; // set from the preceding at the START when a new cluster starts.
+
+ // for (int i = 0; i < unbroken_span.glyph_string->num_glyphs; ++i) {
+ // std::cout << "Unbroken span: " << unbroken_span.glyph_string->glyphs[i].glyph << std::endl;
+ // }
+
+ if (it_span->start.char_byte == 0) {
+ // Start of an unbroken span, we might have dx, dy or rotate still to process
+ // (x and y are done per chunk)
+ if (unbroken_span.dx._set) current_x += unbroken_span.dx.computed;
+ if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed;
+ if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180);
+ }
+
+ if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE
+ && unbroken_span.pango_item_index == -1) {
+ // style only, nothing to output
+ continue;
+ }
+
+ Layout::Span new_span;
+
+ new_span.in_chunk = _flow._chunks.size() - 1;
+ new_span.line_height = unbroken_span.line_height;
+ new_span.in_input_stream_item = unbroken_span.input_index;
+ new_span.baseline_shift = 0.0;
+ new_span.block_progression = _block_progression;
+ new_span.text_orientation = unbroken_span.text_orientation;
+ if ((_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) && (new_span.font = para.pango_items[unbroken_span.pango_item_index].font))
+ {
+ new_span.font->Ref();
+ new_span.font_size = unbroken_span.font_size;
+ new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
+ new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte);
+ } else { // a control code
+ new_span.font = nullptr;
+ new_span.font_size = new_span.line_height.emSize();
+ new_span.direction = para.direction;
+ }
+
+ if (new_span.direction == para.direction) {
+ current_x -= counter_directional_width_remaining;
+ counter_directional_width_remaining = 0.0;
+ } else if (new_span.direction != previous_direction) {
+ // measure width of spans we need to switch round
+ counter_directional_width_remaining = 0.0;
+ std::vector<BrokenSpan>::const_iterator it_following_span;
+ for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) {
+ if (it_following_span->start.iter_span->pango_item_index == -1) break;
+ Layout::Direction following_span_progression = static_cast<InputStreamTextSource const *>(_flow._input_stream[it_following_span->start.iter_span->input_index])->styleGetBlockProgression();
+ if (!Layout::_directions_are_orthogonal(following_span_progression, _block_progression)) {
+ if (new_span.direction != (para.pango_items[it_following_span->start.iter_span->pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT)) break;
+ }
+ counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace);
+ }
+ current_x += counter_directional_width_remaining;
+ counter_directional_width_remaining = 0.0; // we want to go increasingly negative
+ }
+ new_span.x_start = current_x;
+ new_span.y_offset = _y_offset; // Offset from baseline due to 'y' and 'dy' attributes (used to simulate multiline text).
+
+ if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
+ // the span is set up, push the glyphs and chars
+
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[unbroken_span.input_index]);
+ Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ;
+ unsigned char_index_in_unbroken_span = it_span->start.char_index;
+ double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier);
+ int log_cluster_size_glyphs = 0; // Number of glyphs in this log_cluster
+ int log_cluster_size_chars = 0; // Number of characters in this log_cluster
+ unsigned end_byte = 0;
+
+ // Get some pointers (constant for an unbroken span).
+ font_instance *font = para.pango_items[unbroken_span.pango_item_index].font;
+ PangoItem *pango_item = para.pango_items[unbroken_span.pango_item_index].item;
+
+ // Loop over glyphs in span
+ double x_offset_cluster = 0.0; // Handle wrong glyph positioning post-1.44 Pango.
+ double x_offset_center = 0.0; // Handle wrong glyph positioning in pre-1.44 Pango.
+ double x_offset_advance = 0.0; // Handle wrong advance in pre-1.44 Pango.
+
+#ifdef DEBUG_GLYPH
+ std::cerr << "\nGlyphs in span: x_start: " << new_span.x_start << " y_offset: " << new_span.y_offset
+ << " PangoItem flags: " << (int)pango_item->analysis.flags << " Gravity: " << (int)pango_item->analysis.gravity << std::endl;
+ std::cerr << " Unicode Glyph h_advance v_advance width cluster orientation new_glyph delta" << std::endl;
+ std::cerr << " (hex) No. start x y x y" << std::endl;
+ std::cerr << " -------------------------------------------------------------------------------------------------" << std::endl;
+#endif
+
+ for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) {
+
+ unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
+ bool newcluster = false;
+ if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) {
+ newcluster = true;
+ x_in_span = x_in_span_last;
+ }
+
+ if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes
+ && *iter_source_text == UNICODE_SOFT_HYPHEN
+ && glyph_index + 1 != it_span->end_glyph_index) {
+ // if we're looking at a soft hyphen and it's not the last glyph in the
+ // chunk we don't draw the glyph but we still need to add to _characters
+ Layout::Character new_character;
+ new_character.the_char = *iter_source_text;
+ new_character.in_span = _flow._spans.size(); // the span hasn't been added yet, so no -1
+ new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
+ new_character.in_glyph = -1;
+ _flow._characters.push_back(new_character);
+ iter_source_text++;
+ char_index_in_unbroken_span++;
+ while (glyph_index < (unsigned)unbroken_span.glyph_string->num_glyphs
+ && unbroken_span.glyph_string->log_clusters[glyph_index] == (int)char_byte)
+ glyph_index++;
+ glyph_index--;
+ continue;
+ }
+
+ // create the Layout::Glyph
+ PangoGlyphInfo *unbroken_span_glyph_info = &unbroken_span.glyph_string->glyphs[glyph_index];
+ double glyph_width = font_size_multiplier * unbroken_span_glyph_info->geometry.width;
+
+ Layout::Glyph new_glyph;
+ new_glyph.glyph = unbroken_span_glyph_info->glyph;
+ new_glyph.in_character = _flow._characters.size();
+ new_glyph.rotation = glyph_rotate;
+ new_glyph.orientation = ORIENTATION_UPRIGHT; // Only effects vertical text
+ new_glyph.hidden = hidden; // SVG 2 overflow
+
+ // Advance does not include kerning but Pango <= 1.43 gives wrong advances for verical upright text.
+ double glyph_h_advance = new_span.font_size * font->Advance(new_glyph.glyph, false);
+ double glyph_v_advance = new_span.font_size * font->Advance(new_glyph.glyph, true );
+
+#ifdef DEBUG_GLYPH
+
+ bool is_cluster_start = unbroken_span_glyph_info->attr.is_cluster_start;
+ std::cerr << " " << std::hex << std::setw(6) << *iter_source_text << std::dec
+ << " " << std::setw(6) << new_glyph.glyph
+ << std::fixed << std::showpoint << std::setprecision(2)
+ << " " << std::setw(6) << glyph_h_advance
+ << " " << std::setw(6) << glyph_v_advance
+ << " " << std::setw(6) << glyph_width
+ << " " << std::setw(6) << std::boolalpha << is_cluster_start; // << std::endl;
+#endif
+
+ // We may have scaled font size to fit textLength; now, if
+ // @lengthAdjust=spacingAndGlyphs, this scaling must be only horizontal,
+ // not vertical, so we unscale it back vertically during output
+ if (_flow.lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACINGANDGLYPHS)
+ new_glyph.vertical_scale = 1.0 / _flow.getTextLengthMultiplierDue();
+ else
+ new_glyph.vertical_scale = 1.0;
+
+ // Position glyph --------------------
+ new_glyph.x = current_x;
+ new_glyph.y =_y_offset;
+ new_glyph.advance = glyph_width;
+
+ if (*iter_source_text == '\n') {
+ // Line feeds should take zero space but they are given 'space' width.
+ new_glyph.advance = 0.0;
+ }
+
+ // y-coordinate is flipped between vertical and horizontal text...
+ // delta_y is common offset but applied with opposite sign
+ double delta_x = unbroken_span_glyph_info->geometry.x_offset * font_size_multiplier;
+ double delta_y = unbroken_span_glyph_info->geometry.y_offset * font_size_multiplier - unbroken_span.baseline_shift;
+ SPCSSBaseline dominant_baseline = _flow._blockBaseline();
+
+ if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
+ // Vertical text
+
+ // Default dominant baseline is determined by overall block (i.e. <text>) 'text-orientation' value.
+ if( _flow._blockTextOrientation() != SP_CSS_TEXT_ORIENTATION_SIDEWAYS ) {
+ if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_CENTRAL;
+ } else {
+ if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_ALPHABETIC;
+ }
+
+ // TODO: Should also check 'glyph_orientation_vertical' if 'text-orientation' is unset...
+ if( new_span.text_orientation == SP_CSS_TEXT_ORIENTATION_SIDEWAYS ||
+ (new_span.text_orientation == SP_CSS_TEXT_ORIENTATION_MIXED && pango_item->analysis.gravity == PANGO_GRAVITY_SOUTH) ) {
+
+ // Sideways orientation (Latin characters, CJK punctuation), 90deg rotation done at output stage.
+
+#ifdef DEBUG_GLYPH
+ std::cerr << " Sideways"
+ << " " << std::setw(6) << new_glyph.x
+ << " " << std::setw(6) << new_glyph.y
+ << " " << std::setw(6) << delta_x
+ << " " << std::setw(6) << delta_y
+ << std::endl;
+#endif
+
+ new_glyph.orientation = ORIENTATION_SIDEWAYS;
+
+ new_glyph.x += delta_x;
+ new_glyph.y -= delta_y;
+
+ // Multiplying by font-size could cause slight differences in
+ // positioning for different baselines if the font size varies within a
+ // line of text (e.g. sub-scripts and super-scripts).
+ new_glyph.y -= new_span.font_size * font->GetBaselines()[ dominant_baseline ];
+
+ } else {
+ // Upright orientation
+
+ auto hb_font = pango_font_get_hb_font(font->pFont);
+
+#ifdef DEBUG_GLYPH
+ std::cerr << " Upright"
+ << " " << std::setw(6) << new_glyph.x
+ << " " << std::setw(6) << new_glyph.y
+ << " " << std::setw(6) << delta_x
+ << " " << std::setw(6) << delta_y;
+ char glyph_name[32];
+ hb_font_get_glyph_name(hb_font, new_glyph.glyph, glyph_name, sizeof (glyph_name));
+ std::cerr << " " << (glyph_name ? glyph_name : "");
+ std::cerr << std::endl;
+#endif
+
+ if (pango_version_check(1,44,0) != nullptr) {
+ // Pango < 1.44.0 (pre HarfBuzz)
+ new_glyph.x += delta_x;
+ new_glyph.y -= delta_y;
+
+ double shift = 0;
+ double scale_factor = PANGO_SCALE * _font_factory_size_multiplier;
+ if (!FT_HAS_VERTICAL(font->theFace)) {
+
+ // If there are no vertical metrics, glyphs are vertically
+ // centered before base anchor to mark anchor distance is
+ // calculated by shaper. We must undo this!
+ PangoRectangle ink_rect;
+ PangoRectangle logical_rect;
+ pango_font_get_glyph_extents (font->pFont,
+ new_glyph.glyph,
+ &ink_rect,
+ &logical_rect);
+
+ // Shift required to move centered glyph back to proper position
+ // relative to baseline.
+ shift =
+ font->GetTypoAscent() +
+ ink_rect.y / scale_factor + // negative
+ (ink_rect.height / scale_factor / 2.0) -
+ 0.5;
+ }
+
+ // Advance is wrong (horizontal width used instead of vertical)...
+ if (g_unichar_type(*iter_source_text) != G_UNICODE_NON_SPACING_MARK) {
+
+ x_offset_advance = new_glyph.advance - glyph_v_advance;
+ new_glyph.advance = glyph_v_advance;
+
+ x_offset_center = shift;
+ } else {
+ // Is non-spacing mark!
+ if (!FT_HAS_VERTICAL(font->theFace)) {
+
+ // If font lacks vertical metrics, all glyphs have em-box advance
+ // but non-spacing marks should have zero advance.
+ new_glyph.advance = 0;
+
+ // Correct for base anchor to mark anchor shift.
+ new_glyph.x += (x_offset_center - shift) * new_span.font_size;
+ }
+
+ // Correct for advance error.
+ new_glyph.x += x_offset_advance;
+ }
+
+ // Need to shift by horizontal to vertical origin (as we don't load glyph
+ // with vertical metrics).
+ new_glyph.x += font->GetTypoAscent() * new_span.font_size;
+ new_glyph.y -= glyph_h_advance/2.0;
+
+ } else if (pango_version_check(1,48,1) != nullptr) {
+ // 1.44.0 <= Pango < 1.48.1 (minus sign error, mismatch between Cairo/Harfbuzz glyph
+ // placement)
+ new_glyph.x += (glyph_width - delta_x);
+ new_glyph.y -= delta_y;
+ } else if (pango_version_check(1,48,4) != nullptr) {
+ // 1.48.1 <= Pango < 1.48.4 (minus sign fix, partial fix for Cairo/Harfbuzz mismatch,
+ // but bad mark positioning)
+ new_glyph.x += delta_x;
+ new_glyph.y -= delta_y;
+
+ // Need to shift by horizontal to vertical origin. Recall Pango lays out vertical text
+ // as horizontal text then rotates by 90 degress so y_origin -> x, x_origin -> -y.
+ hb_position_t x_origin = 0.0;
+ hb_position_t y_origin = 0.0;
+ hb_font_get_glyph_v_origin(hb_font, new_glyph.glyph, &x_origin, &y_origin);
+ new_glyph.x += y_origin * font_size_multiplier;
+ new_glyph.y -= x_origin * font_size_multiplier;
+ } else {
+ // 1.48.4 <= Pango (good mark positioning)
+ new_glyph.x += delta_x;
+ new_glyph.y -= delta_y;
+ }
+
+ // If a font has no vertical metrics, HarfBuzz using OpenType functions
+ // (which Pango uses by default from 1.44.0) to position glyphs so that
+ // the top of their "ink rectangle" is at the top of the "em-box". This
+ // section of code moves each cluster (base glyph with marks) down to
+ // match fonts with vertical metrics.
+ hb_font_extents_t hb_font_extents_not_used;
+ if (!hb_font_get_v_extents(hb_font, &hb_font_extents_not_used)) {
+ // Font does not have vertical metrics!
+
+ if (g_unichar_type(*iter_source_text) !=
+ G_UNICODE_NON_SPACING_MARK) { // Probably should include other marks!
+ hb_glyph_extents_t glyph_extents;
+ if (hb_font_get_glyph_extents(hb_font, new_glyph.glyph, &glyph_extents)) {
+
+ // double baseline_adjust =
+ // font_instance->get_baseline(BASELINE_TEXT_BEFORE_EDGE) -
+ // font_instance->get_baseline(BASELINE_ALPHABETIC);
+ // std::cout << "baseline_adjust: " << baseline_adjust << std::endl;
+ double baseline_adjust = new_span.line_height.ascent / new_span.font_size;
+ int hb_x_scale = 0;
+ int hb_y_scale = 0;
+ hb_font_get_scale(hb_font, &hb_x_scale, &hb_y_scale);
+ x_offset_cluster =
+ ((glyph_extents.y_bearing / (double)hb_y_scale) - baseline_adjust) *
+ new_span.font_size;
+ } else {
+ x_offset_cluster = 0.0; // Failed to find extents.
+ }
+ } else {
+ // Is non-spacing mark!
+
+ // Many fonts report a non-zero vertical advance for marks, especially if the 'vmtx'
+ // table is missing.
+ new_glyph.advance = 0;
+ }
+
+ new_glyph.x -= x_offset_cluster;
+ }
+
+ }
+ } else {
+ // Horizontal text
+
+#ifdef DEBUG_GLYPH
+ std::cerr << " Horizontal"
+ << " " << std::setw(6) << new_glyph.x
+ << " " << std::setw(6) << new_glyph.y
+ << " " << std::setw(6) << delta_x
+ << " " << std::setw(6) << delta_y
+ << std::endl;
+#endif
+
+ if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_ALPHABETIC;
+
+ new_glyph.x += delta_x;
+ new_glyph.y += delta_y;
+
+ new_glyph.y += new_span.font_size * font->GetBaselines()[ dominant_baseline ];
+ }
+
+ // Correct for right to left text
+ if (new_span.direction == RIGHT_TO_LEFT) {
+
+ // The following commented out code is from 2005. Subtracting cluster width gives wrong placement if more
+ // than one glyph has a horizontal advance. See GitHub issue 469. I leave the old code here in case switching to
+ // subtracting only the glyph width causes unforseen bugs.
+
+ // // pango wanted to give us glyphs in visual order but we refused, so we need to work
+ // // out where the cluster start is ourselves
+
+ // // Add up widths of remaining glyphs in span.
+ // double cluster_width = 0.0;
+ // std::cout << " glyph_index: " << glyph_index << " end_glyph_index: " << it_span->end_glyph_index << std::endl;
+ // for (unsigned rtl_index = glyph_index; rtl_index < it_span->end_glyph_index ; rtl_index++) {
+ // if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index) {
+ // break;
+ // }
+ // cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
+ // }
+ // new_glyph.x -= cluster_width;
+
+ new_glyph.x -= font_size_multiplier * unbroken_span.glyph_string->glyphs[glyph_index].geometry.width;
+ }
+
+ // Store glyph data
+ _flow._glyphs.push_back(new_glyph);
+
+ // Create the Layout::Character(s)
+ if (newcluster) {
+ newcluster = false;
+
+ // Figure out how many glyphs are in the log_cluster.
+ log_cluster_size_glyphs = 0;
+ for (; log_cluster_size_glyphs + glyph_index < it_span->end_glyph_index; log_cluster_size_glyphs++){
+ if(unbroken_span.glyph_string->log_clusters[glyph_index ] !=
+ unbroken_span.glyph_string->log_clusters[glyph_index + log_cluster_size_glyphs]) break;
+ }
+
+ // Find where the text ends for this log_cluster.
+ end_byte = it_span->start.iter_span->text_bytes; // Upper limit
+ for(int next_glyph_index = glyph_index+1; next_glyph_index < unbroken_span.glyph_string->num_glyphs; next_glyph_index++){
+ if(unbroken_span.glyph_string->glyphs[next_glyph_index].attr.is_cluster_start){
+ end_byte = unbroken_span.glyph_string->log_clusters[next_glyph_index];
+ break;
+ }
+ }
+
+ // Figure out how many characters are in the log_cluster.
+ log_cluster_size_chars = 0;
+ Glib::ustring::const_iterator lclist = iter_source_text;
+ unsigned lcb = char_byte;
+ while (lcb < end_byte){
+ log_cluster_size_chars++;
+ lclist++;
+ lcb = lclist.base() - unbroken_span.input_stream_first_character.base();
+ }
+ }
+
+ double advance_width = new_glyph.advance;
+ while (char_byte < end_byte) {
+
+ /* Hack to survive ligatures: in log_cluster keep the number of available chars >= number of glyphs remaining.
+ When there are no ligatures these two sizes are always the same.
+ */
+ if (log_cluster_size_chars < log_cluster_size_glyphs) {
+ log_cluster_size_glyphs--;
+ break;
+ }
+
+ // Store character info
+ Layout::Character new_character;
+ new_character.the_char = *iter_source_text;
+ new_character.in_span = _flow._spans.size();
+ new_character.x = x_in_span;
+ new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
+ new_character.in_glyph = (hidden ? -1 : _flow._glyphs.size() - 1);
+ _flow._characters.push_back(new_character);
+
+ // Letter/word spacing and justification
+ if (new_character.char_attributes.is_white)
+ advance_width += text_source->style->word_spacing.computed * _flow.getTextLengthMultiplierDue() + add_to_each_whitespace; // justification
+ if (new_character.char_attributes.is_cursor_position)
+ advance_width += text_source->style->letter_spacing.computed * _flow.getTextLengthMultiplierDue();
+ advance_width += _flow.getTextLengthIncrementDue();
+
+ // Update counters
+ iter_source_text++;
+ char_index_in_unbroken_span++;
+ char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
+ log_cluster_size_chars--;
+ }
+
+ // Update x position variables
+ advance_width *= direction_sign;
+ if (new_span.direction != para.direction) {
+ counter_directional_width_remaining -= advance_width;
+ current_x -= advance_width;
+ x_in_span_last -= advance_width;
+ } else {
+ current_x += advance_width;
+ x_in_span_last += advance_width;
+ }
+ } // Loop over glyphs in span
+
+ } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) {
+ current_x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
+ }
+
+ new_span.x_end = new_span.x_start + x_in_span_last;
+ _flow._spans.push_back(new_span);
+ previous_direction = new_span.direction;
+ }
+ // end adding spans to the list, on to the next chunk...
+ }
+ TRACE((" End _outputLine\n"));
+}
+
+/**
+ * Initialises the ScanlineMaker for the first shape in the flow,
+ * or the infinite version if we're not doing wrapping.
+ */
+void Layout::Calculator::_createFirstScanlineMaker()
+{
+ _current_shape_index = 0;
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
+ if (_flow._input_wrap_shapes.empty()) {
+ // create the special no-wrapping infinite scanline maker
+ double initial_x = 0, initial_y = 0;
+ if (!text_source->x.empty()) {
+ initial_x = text_source->x.front().computed;
+ }
+ if (!text_source->y.empty()) {
+ initial_y = text_source->y.front().computed;
+ }
+ _scanline_maker = new InfiniteScanlineMaker(initial_x, initial_y, _block_progression);
+ TRACE((" wrapping disabled\n"));
+ }
+ else {
+ _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
+ TRACE((" begin wrap shape 0\n"));
+
+ // 'inline-size' uses an infinitely high (wide) shape. We must set initial y. (We only need to do it here as there is only one shape.)
+ if (_flow.wrap_mode == WRAP_INLINE_SIZE) {
+ _block_progression = _flow._blockProgression();
+ if( _block_progression == RIGHT_TO_LEFT ||
+ _block_progression == LEFT_TO_RIGHT ) {
+ // Vertical text, CJK
+ if (!text_source->x.empty()) {
+ double initial_x = text_source->x.front().computed;
+ _scanline_maker->setNewYCoordinate(initial_x);
+ } else {
+ std::cerr << "Layout::Calculator::_createFirstScanlineMaker: no x value with 'inline-size'!" << std::endl;
+ _scanline_maker->setNewYCoordinate(0);
+ }
+ } else {
+ // Horizontal text
+ if (!text_source->y.empty()) {
+ double initial_y = text_source->y.front().computed;
+ _scanline_maker->setNewYCoordinate(initial_y);
+ } else {
+ std::cerr << "Layout::Calculator::_createFirstScanlineMaker: no y value with 'inline-size'!" << std::endl;
+ _scanline_maker->setNewYCoordinate(0);
+ }
+ }
+ }
+ }
+}
+
+void Layout::Calculator::UnbrokenSpanPosition::increment()
+{
+ gchar const *text_base = &*iter_span->input_stream_first_character.base();
+ char_byte = g_utf8_next_char(text_base + char_byte) - text_base;
+ char_index++;
+ if (char_byte == iter_span->text_bytes) {
+ iter_span++;
+ char_index = char_byte = 0;
+ }
+}
+
+void Layout::Calculator::BrokenSpan::setZero()
+{
+ end = start;
+ width = 0.0;
+ whitespace_count = 0;
+ end_glyph_index = start_glyph_index = 0;
+ ends_with_whitespace = false;
+ each_whitespace_width = 0.0;
+ letter_spacing = 0.0;
+ word_spacing = 0.0;
+}
+
+///**
+// * For sections of text with a block-progression different to the rest
+// * of the flow, the best thing to do is to detect them in advance and
+// * create child TextFlow objects with just the rotated text. In the
+// * parent we then effectively use ARBITRARY_GAP fields during the
+// * flowing (because we don't allow wrapping when the block-progression
+// * changes) and copy the actual text in during the output phase.
+// *
+// * NB: this code not enabled yet.
+// */
+//void Layout::Calculator::_initialiseInputItems(ParagraphInfo *para) const
+//{
+// Direction prev_block_progression = _block_progression;
+// int run_start_input_index = para->first_input_index;
+//
+// para->free_sequence(para->input_items);
+// for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) {
+// InputItemInfo input_item;
+//
+// input_item.in_sub_flow = false;
+// input_item.sub_flow = NULL;
+// if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
+// Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
+// if ( control_code->code == SHAPE_BREAK
+// || control_code->code == PARAGRAPH_BREAK)
+// break; // stop at the end of the paragraph
+// // all other control codes we'll pick up later
+//
+// } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
+// Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
+// Direction this_block_progression = text_source->styleGetBlockProgression();
+// if (this_block_progression != prev_block_progression) {
+// if (prev_block_progression != _block_progression) {
+// // need to back up so that control codes belong outside the block-progression change
+// int run_end_input_index = input_index - 1;
+// while (run_end_input_index > run_start_input_index
+// && _flow._input_stream[run_end_input_index]->Type() != TEXT_SOURCE)
+// run_end_input_index--;
+// // now create the sub-flow
+// input_item.sub_flow = new Layout;
+// for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) {
+// input_item.in_sub_flow = true;
+// if (_flow._input_stream[sub_input_index]->Type() == CONTROL_CODE) {
+// Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[sub_input_index]);
+// input_item.sub_flow->appendControlCode(control_code->code, control_code->source, control_code->width, control_code->ascent, control_code->descent);
+// } else if (_flow._input_stream[sub_input_index]->Type() == TEXT_SOURCE) {
+// Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[sub_input_index]);
+// input_item.sub_flow->appendText(*text_source->text, text_source->style, text_source->source, NULL, 0, text_source->text_begin, text_source->text_end);
+// Layout::InputStreamTextSource *sub_flow_text_source = static_cast<Layout::InputStreamTextSource *>(input_item.sub_flow->_input_stream.back());
+// sub_flow_text_source->x = text_source->x; // this is easier than going via optionalattrs for the appendText() call
+// sub_flow_text_source->y = text_source->y; // should these actually be allowed anyway? You'll almost never get the results you expect
+// sub_flow_text_source->dx = text_source->dx; // (not that it's very clear what you should expect, anyway)
+// sub_flow_text_source->dy = text_source->dy;
+// sub_flow_text_source->rotate = text_source->rotate;
+// }
+// }
+// input_item.sub_flow->calculateFlow();
+// }
+// run_start_input_index = input_index;
+// }
+// prev_block_progression = this_block_progression;
+// }
+// para->input_items.push_back(input_item);
+// }
+//}
+
+/**
+ * Take all the text from \a _para.first_input_index to the end of the
+ * paragraph and stitch it together so that pango_itemize() can be called on
+ * the whole thing.
+ *
+ * Input: para.first_input_index.
+ * Output: para.direction, para.pango_items, para.char_attributes.
+ * Returns: the number of spans created by pango_itemize
+ */
+void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const
+{
+ TRACE(("pango version string: %s\n", pango_version_string() ));
+ TRACE((" ... compiled for font features\n"));
+
+ TRACE(("itemizing para, first input %d\n", para->first_input_index));
+
+ PangoAttrList *attributes_list = pango_attr_list_new();
+ for (unsigned input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
+ if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
+ Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
+ if ( control_code->code == SHAPE_BREAK
+ || control_code->code == PARAGRAPH_BREAK)
+ break; // stop at the end of the paragraph
+ // all other control codes we'll pick up later
+
+ } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
+ Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
+
+ // create the font_instance
+ font_instance *font = text_source->styleGetFontInstance();
+ if (font == nullptr)
+ continue; // bad news: we'll have to ignore all this text because we know of no font to render it
+
+ PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr);
+ attribute_font_description->start_index = para->text.bytes();
+
+ PangoAttribute *attribute_font_features =
+ pango_attr_font_features_new( text_source->style->getFontFeatureString().c_str());
+ attribute_font_features->start_index = para->text.bytes();
+ para->text.append(&*text_source->text_begin.base(), text_source->text_length); // build the combined text
+
+ attribute_font_description->end_index = para->text.bytes();
+ pango_attr_list_insert(attributes_list, attribute_font_description);
+
+ attribute_font_features->end_index = para->text.bytes();
+ pango_attr_list_insert(attributes_list, attribute_font_features);
+
+ // Set language
+ SPObject * object = text_source->source;
+ if (!object->lang.empty()) {
+ PangoLanguage* language = pango_language_from_string(object->lang.c_str());
+ PangoAttribute *attribute_language = pango_attr_language_new( language );
+ pango_attr_list_insert(attributes_list, attribute_language);
+ }
+
+ // ownership of attribute is assumed by the list
+ font->Unref();
+ }
+ }
+
+ TRACE(("whole para: \"%s\"\n", para->text.data()));
+// TRACE(("%d input sources used\n", input_index - para->first_input_index));
+
+ // Pango Itemize
+ GList *pango_items_glist = nullptr;
+ para->direction = LEFT_TO_RIGHT; // CSS default
+ if (_flow._input_stream[para->first_input_index]->Type() == TEXT_SOURCE) {
+ Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[para->first_input_index]);
+
+ para->direction = (text_source->style->direction.computed == SP_CSS_DIRECTION_LTR) ? LEFT_TO_RIGHT : RIGHT_TO_LEFT;
+ PangoDirection pango_direction = (text_source->style->direction.computed == SP_CSS_DIRECTION_LTR) ? PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL;
+ pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para->text.data(), 0, para->text.bytes(), attributes_list, nullptr);
+ }
+
+ if( pango_items_glist == nullptr ) {
+ // Type wasn't TEXT_SOURCE or direction was not set.
+ pango_items_glist = pango_itemize(_pango_context, para->text.data(), 0, para->text.bytes(), attributes_list, nullptr);
+ }
+
+ pango_attr_list_unref(attributes_list);
+
+ // convert the GList to our vector<> and make the font_instance for each PangoItem at the same time
+ para->pango_items.reserve(g_list_length(pango_items_glist));
+ TRACE(("para itemizes to %d sections\n", g_list_length(pango_items_glist)));
+ for (GList *current_pango_item = pango_items_glist ; current_pango_item != nullptr ; current_pango_item = current_pango_item->next) {
+ PangoItemInfo new_item;
+ new_item.item = (PangoItem*)current_pango_item->data;
+ PangoFontDescription *font_description = pango_font_describe(new_item.item->analysis.font);
+ new_item.font = (font_factory::Default())->Face(font_description);
+ pango_font_description_free(font_description); // Face() makes a copy
+ para->pango_items.push_back(new_item);
+ }
+ g_list_free(pango_items_glist);
+
+ // and get the character attributes on everything
+ para->char_attributes.resize(para->text.length() + 1);
+ pango_get_log_attrs(para->text.data(), para->text.bytes(), -1, nullptr, &*para->char_attributes.begin(), para->char_attributes.size());
+
+ // Fix for Pango 1.49 which changes the end of a paragraph to a mandatory break.
+ // This breaks Inkscape's multiline text (i.e. sodipodi:role line).
+ para->char_attributes[para->text.length()].is_mandatory_break = 0;
+
+ TRACE(("end para itemize, direction = %d\n", para->direction));
+}
+
+/**
+ * Finds the value of line_height_multiplier given the 'line-height' property. The result of
+ * multiplying \a l by \a line_height_multiplier is the inline box height as specified in css2
+ * section 10.8. http://www.w3.org/TR/CSS2/visudet.html#line-height
+ *
+ * The 'computed' value of 'line-height' does not have a consistent meaning. We need to find the
+ * 'used' value and divide that by the font size.
+ */
+double Layout::Calculator::_computeFontLineHeight( SPStyle const *style )
+{
+ // This is a bit backwards... we should be returning the absolute height
+ // but as the code expects line_height_multiplier we return that.
+ if (style->line_height.normal) {
+ return (LINE_HEIGHT_NORMAL);
+ } else if (style->line_height.unit == SP_CSS_UNIT_NONE) {
+ // Special case per CSS, computed value is multiplier
+ return style->line_height.computed;
+ } else {
+ // Normal case, computed value is absolute height. Turn it into multiplier.
+ return style->line_height.computed / style->font_size.computed;
+ }
+}
+
+bool compareGlyphWidth(const PangoGlyphInfo &a, const PangoGlyphInfo &b)
+{
+ bool retval = false;
+ if ( b.geometry.width == 0 && (a.geometry.width > 0))retval = true;
+ return (retval);
+}
+
+
+/**
+ * Split the paragraph into spans. Also call pango_shape() on them.
+ *
+ * Input: para->first_input_index, para->pango_items
+ * Output: para->spans
+ * Returns: the index of the beginning of the following paragraph in _flow._input_stream
+ */
+unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const
+{
+ unsigned pango_item_index = 0;
+ unsigned char_index_in_para = 0;
+ unsigned byte_index_in_para = 0;
+ unsigned input_index;
+ unsigned para_text_index = 0;
+
+ TRACE(("build spans\n"));
+ para->free_sequence(para->unbroken_spans);
+
+ for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
+ if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
+ Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
+
+ if ( control_code->code == SHAPE_BREAK
+ || control_code->code == PARAGRAPH_BREAK) {
+
+ // Add span to be used to calculate line spacing of blank lines.
+ UnbrokenSpan new_span;
+ new_span.pango_item_index = -1;
+ new_span.input_index = input_index;
+
+ // No pango object, so find font and line height ourselves.
+ SPObject * object = control_code->source;
+ if (object) {
+ SPStyle * style = object->style;
+ if (style) {
+ new_span.font_size = style->font_size.computed * _flow.getTextLengthMultiplierDue();
+ font_factory * factory = font_factory::Default();
+ font_instance * font = factory->FaceFromStyle( style );
+ new_span.line_height_multiplier = _computeFontLineHeight( object->style );
+ new_span.line_height.set( font );
+ new_span.line_height *= new_span.font_size;
+ }
+ }
+ new_span.text_bytes = 0;
+ new_span.char_index_in_para = char_index_in_para;
+ para->unbroken_spans.push_back(new_span);
+ TRACE(("add empty span for break %lu\n", para->unbroken_spans.size() - 1));
+ break; // stop at the end of the paragraph
+
+ } else if (control_code->code == ARBITRARY_GAP) { // Not used!
+
+ UnbrokenSpan new_span;
+ new_span.pango_item_index = -1;
+ new_span.input_index = input_index;
+ new_span.line_height.ascent = control_code->ascent * _flow.getTextLengthMultiplierDue();
+ new_span.line_height.descent = control_code->descent * _flow.getTextLengthMultiplierDue();
+ new_span.text_bytes = 0;
+ new_span.char_index_in_para = char_index_in_para;
+ para->unbroken_spans.push_back(new_span);
+ TRACE(("add gap span %lu\n", para->unbroken_spans.size() - 1));
+ }
+ } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) {
+ Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource const *>(_flow._input_stream[input_index]);
+ unsigned char_index_in_source = 0;
+ unsigned span_start_byte_in_source = 0;
+
+ // we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition
+ for ( ; ; ) {
+ /* we need to change spans at every change of PangoItem, source stream change,
+ or change in one of the attributes altering position/rotation. */
+
+ unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size()
+ ? 0
+ : ( para->pango_items[pango_item_index].item->offset
+ + para->pango_items[pango_item_index].item->length
+ - byte_index_in_para ) );
+ unsigned const text_source_bytes = ( text_source->text_end.base()
+ - text_source->text_begin.base()
+ - span_start_byte_in_source );
+ TRACE(("New Unbroken Span\n"));
+ UnbrokenSpan new_span;
+ new_span.text_bytes = std::min(text_source_bytes, pango_item_bytes);
+ new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source);
+ new_span.char_index_in_para = char_index_in_para + char_index_in_source;
+ new_span.input_index = input_index;
+
+ // cut at <tspan> attribute changes as well
+ new_span.x._set = false;
+ new_span.y._set = false;
+ new_span.dx._set = false;
+ new_span.dy._set = false;
+ new_span.rotate._set = false;
+ if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) {
+ // Horizontal text
+ if (text_source->x.size() > char_index_in_source) new_span.x = text_source->x[char_index_in_source];
+ if (text_source->y.size() > char_index_in_source) new_span.y = text_source->y[char_index_in_source];
+ if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
+ if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
+ } else {
+ // Vertical text
+ if (text_source->x.size() > char_index_in_source) new_span.y = text_source->x[char_index_in_source];
+ if (text_source->y.size() > char_index_in_source) new_span.x = text_source->y[char_index_in_source];
+ if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
+ if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source].computed * _flow.getTextLengthMultiplierDue();
+ }
+ if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
+ else if (char_index_in_source == 0) new_span.rotate = 0.f;
+ if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) {
+ // if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically
+ // so that the top of the letters is at zero, not the baseline
+ new_span.y = 0.0;
+ }
+ Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character;
+ iter_text++;
+ for (unsigned i = char_index_in_source + 1 ; ; i++, iter_text++) {
+ if (iter_text >= text_source->text_end) break;
+ if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break;
+ if ( i >= text_source->x.size() && i >= text_source->y.size()
+ && i >= text_source->dx.size() && i >= text_source->dy.size()
+ && i >= text_source->rotate.size()) break;
+ if ( (text_source->x.size() > i && text_source->x[i]._set)
+ || (text_source->y.size() > i && text_source->y[i]._set)
+ || (text_source->dx.size() > i && text_source->dx[i]._set && text_source->dx[i].computed != 0.0)
+ || (text_source->dy.size() > i && text_source->dy[i]._set && text_source->dy[i].computed != 0.0)
+ || (text_source->rotate.size() > i && text_source->rotate[i]._set
+ && (i == 0 || text_source->rotate[i].computed != text_source->rotate[i - 1].computed))) {
+ new_span.text_bytes = iter_text.base() - new_span.input_stream_first_character.base();
+ break;
+ }
+ }
+
+ // now we know the length, do some final calculations and add the UnbrokenSpan to the list
+ new_span.font_size = text_source->style->font_size.computed * _flow.getTextLengthMultiplierDue();
+ if (new_span.text_bytes) {
+ new_span.glyph_string = pango_glyph_string_new();
+ /* Some assertions intended to help diagnose bug #1277746. */
+ g_assert( 0 < new_span.text_bytes );
+ g_assert( span_start_byte_in_source < text_source->text->bytes() );
+ g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() );
+ g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast<size_t>(new_span.text_bytes))
+ == nullptr );
+
+ /* Notes as of 4/29/13. Pango_shape is not generating English language ligatures, but it is generating
+ them for Hebrew (and probably other similar languages). In the case observed 3 unicode characters (a base
+ and 2 Mark, nonspacings) are merged into two glyphs (the base + first Mn, the 2nd Mn). All of these map
+ from glyph to first character of the log_cluster range. This destroys the 1:1 correspondence between
+ characters and glyphs. A big chunk of the conditional code which immediately follows this call
+ is there to clean up the resulting mess.
+ */
+
+ // Assumption: old and new arguments are the same.
+ auto gold = std::string_view(text_source->text->data() + span_start_byte_in_source, new_span.text_bytes);
+ auto gnew = std::string_view(para->text.data() + para_text_index, new_span.text_bytes);
+ assert (gold == gnew);
+
+ // Convert characters to glyphs
+ pango_shape_full(para->text.data() + para_text_index,
+ new_span.text_bytes,
+ para->text.data(),
+ -1,
+ &para->pango_items[pango_item_index].item->analysis,
+ new_span.glyph_string);
+
+ if (para->pango_items[pango_item_index].item->analysis.level & 1) {
+ // Right to left text (Arabic, Hebrew, etc.)
+
+ // pango_shape() will reorder glyphs in rtl sections into visual order
+ // (start offsets in accending order) which messes us up because the svg
+ // spec requires us to draw glyphs in logical order so let's reverse the
+ // glyphstring.
+
+ const unsigned nglyphs = new_span.glyph_string->num_glyphs;
+ std::vector<PangoGlyphInfo> infos(nglyphs);
+ std::vector<gint> clusters(nglyphs);
+
+ for (int i = 0; i < nglyphs; ++i) {
+ std::copy(&new_span.glyph_string->glyphs[i], &new_span.glyph_string->glyphs[i+1], infos.end() - i - 1);
+ std::copy(&new_span.glyph_string->log_clusters[i], &new_span.glyph_string->log_clusters[i+1], clusters.end() - i - 1);
+ }
+
+ std::copy(infos.begin(), infos.end(), new_span.glyph_string->glyphs);
+ std::copy(clusters.begin(), clusters.end(), new_span.glyph_string->log_clusters);
+
+ // We've messed up the flag that tells a glyph it is first in a cluster.
+ for (int i = 0; i < nglyphs; ++i) {
+
+ // Set flag for start of cluster, we skip all other glyphs in cluster below.
+ new_span.glyph_string->glyphs[i].attr.is_cluster_start = 1;
+
+ // Find index of first glyph in next cluster
+ int j = i + 1;
+ while( (j < nglyphs) &&
+ (new_span.glyph_string->log_clusters[j] == new_span.glyph_string->log_clusters[i])
+ ) {
+ new_span.glyph_string->glyphs[j].attr.is_cluster_start = 0; // Zero
+ j++;
+ }
+
+ // Move on to next cluster.
+ i = j;
+ }
+
+ } // End right to left text.
+
+ // The following sorting doesn't seem to be necessary, and causes
+ // https://gitlab.com/inkscape/inkscape/-/issues/394 ...
+
+ /*
+ CAREFUL, within a log_cluster the order of glyphs may not map 1:1, or
+ even in the same order, to the original unicode characters!!! Among
+ other things, diacritical mark glyphs can end up sequentially in front of the base
+ character glyph. That makes determining kerning, even approximately, difficult
+ later on.
+
+ To resolve this to the extent possible sort the glyphs within the same
+ log_cluster into descending order by width in a special manner before copying. Diacritical marks
+ and similar have zero width and the glyph they modify has nonzero width. The order
+ of the zero width ones does not matter. A logical cluster is sorted into sequential order
+ [base] [zw_modifier1] [zw_modifier2]
+ where all the modifiers have zero width and the base does not. This works for languages like Hebrew.
+
+ Pango also creates log clusters for languages like Telugu having many glyphs with nonzero widths.
+ Since these are nonzero, their order is not modified.
+
+ If some language mixes these modes, having a log cluster having something like
+ [base1] [zw_modifier1] [base2] [zw_modifier2]
+ the result will be incorrect:
+ base1] [base2] [zw_modifier1] [zw_modifier2]
+
+ If ligatures other than with Mark, nonspacing are ever implemented in Pango this will screw up, for instance
+ changing "fi" to "if".
+ */
+
+ // If it is necessary to move zero width glyphs.. then it applies to both right-to-left and left-to-right text.
+ // const unsigned nglyphs = new_span.glyph_string->num_glyphs;
+ // for (int i = 0; i < nglyphs; ++i) {
+
+ // // Zero flag for start of cluster, we zero the rest below, and then reset it after sorting.
+ // new_span.glyph_string->glyphs[i].attr.is_cluster_start = 0;
+
+ // // Find index of first glyph in next cluster
+ // int j = i + 1;
+ // while( (j < nglyphs) &&
+ // (new_span.glyph_string->log_clusters[j] == new_span.glyph_string->log_clusters[i])
+ // ) {
+ // new_span.glyph_string->glyphs[j].attr.is_cluster_start = 0; // Zero
+ // j++;
+ // }
+
+ // if (j - i) {
+ // // More than one glyph in cluster -> sort.
+ // std::sort(&(new_span.glyph_string->glyphs[i]), &(new_span.glyph_string->glyphs[j]), compareGlyphWidth);
+ // }
+
+ // // Now we're sorted, set flag for start of cluster.
+ // new_span.glyph_string->glyphs[i].attr.is_cluster_start = 1;
+
+ // // Move on to next cluster.
+ // i = j;
+ // }
+ /* glyphs[].x_offset values are probably out of order within any log_clusters, apparently harmless */
+
+
+ new_span.pango_item_index = pango_item_index;
+ new_span.line_height_multiplier = _computeFontLineHeight( text_source->style );
+ new_span.line_height.set( para->pango_items[pango_item_index].font );
+ new_span.line_height *= new_span.font_size;
+
+ // At some point we may want to calculate baseline_shift here (to take advantage
+ // of otm features like superscript baseline), but for now we use style baseline_shift.
+ new_span.baseline_shift = text_source->style->baseline_shift.computed;
+ new_span.text_orientation = (SPCSSTextOrientation)text_source->style->text_orientation.computed;
+
+ // TODO: metrics for vertical text
+ TRACE(("add text span %lu \"%s\"\n", para->unbroken_spans.size(), text_source->text->raw().substr(span_start_byte_in_source, new_span.text_bytes).c_str()));
+ TRACE((" %d glyphs\n", new_span.glyph_string->num_glyphs));
+ } else {
+ // if there's no text we still need to initialise the styles
+ new_span.pango_item_index = -1;
+ font_instance *font = text_source->styleGetFontInstance();
+ if (font) {
+ new_span.line_height_multiplier = _computeFontLineHeight( text_source->style );
+ new_span.line_height.set( font );
+ new_span.line_height *= new_span.font_size;
+ font->Unref();
+ } else {
+ new_span.line_height *= 0.0; // Set all to zero
+ new_span.line_height_multiplier = LINE_HEIGHT_NORMAL;
+ }
+ TRACE(("add style init span %lu\n", para->unbroken_spans.size()));
+ }
+ para->unbroken_spans.push_back(new_span);
+
+ // calculations for moving to the next UnbrokenSpan
+ byte_index_in_para += new_span.text_bytes;
+ para_text_index += new_span.text_bytes;
+ char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes);
+
+ if (new_span.text_bytes >= pango_item_bytes) { // end of pango item
+ pango_item_index++;
+ if (pango_item_index == para->pango_items.size()) break; // end of paragraph
+ }
+ if (new_span.text_bytes == text_source_bytes)
+ break; // end of source
+ // else <tspan> attribute changed
+ span_start_byte_in_source += new_span.text_bytes;
+ }
+ char_index_in_para += char_index_in_source; // This seems wrong. Probably should be inside loop.
+ }
+ }
+ TRACE(("end build spans\n"));
+ return input_index;
+}
+
+/**
+ * Moves onto next shape with a new scanline_maker.
+ * If there is no next shape, creates an infinite scanline maker to stash remaining text.
+ * Returns false if an infinite scanline maker is created.
+ */
+bool Layout::Calculator::_goToNextWrapShape()
+{
+ if (_flow._input_wrap_shapes.size() == 0) {
+ // Shouldn't happen.
+ std::cerr << "Layout::Calculator::_goToNextWrapShape() called for text without shapes!" << std::endl;
+ return false;
+ }
+
+ if (_current_shape_index >= _flow._input_wrap_shapes.size()) {
+ // Shouldn't happen.
+ std::cerr << "Layout::Calculator::_goToNextWrapShape(): shape index too large!" << std::endl;
+ }
+
+ _current_shape_index++;
+
+ delete _scanline_maker;
+ _scanline_maker = nullptr;
+
+ if (_current_shape_index < _flow._input_wrap_shapes.size()) {
+ _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
+ TRACE(("begin wrap shape %u\n", _current_shape_index));
+ return true;
+ } else {
+ // Out of shapes, create infinite scanline maker to stash overflow.
+
+ // First find a suitable position for overflow text. (index - 1 exists since we just incremented index)
+ double x = _flow._input_wrap_shapes[_current_shape_index - 1].shape->leftX;
+ double y = _flow._input_wrap_shapes[_current_shape_index - 1].shape->bottomY;
+
+ _scanline_maker = new InfiniteScanlineMaker(x, y, _block_progression);
+ TRACE(("out of wrap shapes, stash leftover\n"));
+ return false;
+ }
+
+ // Shouldn't reach
+}
+
+/**
+ * Given \a para filled in and \a start_span_pos set, keeps trying to
+ * find somewhere it can fit the next line of text. The process of finding
+ * the text that fits will involve creating one or more entries in
+ * \a chunk_info describing the bounds of the fitted text and several
+ * bits of information that will prove useful when we come to output the
+ * line to #_flow. Returns with \a start_span_pos set to the end of the
+ * text that was fitted, \a chunk_info completely filled out and
+ * \a line_box_height set with the largest ascent and the largest
+ * descent (individually per CSS) on the line. The line_box_height
+ * can never be smaller than the line_box_strut (which is determined
+ * by the block level value of line_height). The return
+ * value is false only if we've run out of shapes to wrap inside (and
+ * hence stashed overflow).
+ */
+bool Layout::Calculator::_findChunksForLine(ParagraphInfo const &para,
+ UnbrokenSpanPosition *start_span_pos,
+ std::vector<ChunkInfo> *chunk_info,
+ FontMetrics *line_box_height,
+ FontMetrics const *strut_height)
+{
+ TRACE((" begin _findChunksForLine: chunks: %lu, em size: %f\n", chunk_info->size(), line_box_height->emSize() ));
+
+ // CSS 2.1 dictates that the minimum line height (i.e. the strut height)
+ // is found from the block element.
+ *line_box_height = *strut_height;
+ TRACE((" initial line_box_height (em size): %f\n", line_box_height->emSize() ));
+
+ bool truncated = false;
+
+ UnbrokenSpanPosition span_pos;
+ for( ; ; ) {
+ // Get regions where one can place one line of text (can be more than one, if filling a
+ // donut for example).
+ std::vector<ScanlineMaker::ScanRun> scan_runs;
+ scan_runs = _scanline_maker->makeScanline(*line_box_height); // 1 scan run with "InfiniteScanlineMaker"
+
+ // If scan_runs is empty, we must have reached the bottom of a shape. Go to next shape.
+ while (scan_runs.empty()) {
+ // Reset for new shape.
+ *line_box_height = *strut_height;
+
+ // Only used by ShapeScanlineMaker
+ if (!_goToNextWrapShape()) {
+ truncated = true;
+ }
+
+ // If we've run out of shapes, this will be the infinite line scanline maker with one scan_run).
+ scan_runs = _scanline_maker->makeScanline(*line_box_height);
+ }
+
+
+ TRACE((" finding line fit y=%f, %lu scan runs\n", scan_runs.front().y, scan_runs.size()));
+ chunk_info->clear();
+ chunk_info->reserve(scan_runs.size());
+ if (para.direction == RIGHT_TO_LEFT) std::reverse(scan_runs.begin(), scan_runs.end());
+ unsigned scan_run_index;
+ span_pos = *start_span_pos;
+ for (scan_run_index = 0 ; scan_run_index < scan_runs.size() ; scan_run_index++) {
+ // Returns false if some text in line requires a taller line_box_height.
+ // (We try again with a larger line_box_height.)
+ if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_box_height)) {
+ break;
+ }
+
+ if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty()) {
+ span_pos = chunk_info->back().broken_spans.back().end;
+ }
+ }
+
+ if (scan_run_index == scan_runs.size()) break; // ie when buildChunksInScanRun() succeeded
+
+ } // End for loop
+
+ *start_span_pos = span_pos;
+ TRACE((" final line_box_height: %f\n", line_box_height->emSize() ));
+ TRACE((" end _findChunksForLine: chunks: %lu, truncated: %s\n", chunk_info->size(), truncated ? "true" : "false"));
+ return !truncated;
+}
+
+/**
+ * Given a scan run and a first character, append one or more chunks to
+ * the \a chunk_info vector that describe all the spans and other detail
+ * necessary to output the greatest amount of text that will fit on this scan
+ * line (greedy line breaking algorithm). Each chunk contains one or more
+ * BrokenSpan structures that link back to UnbrokenSpan structures that link
+ * to the text itself. Normally there will be either one or zero (if the
+ * scanrun is too short to fit any text) chunk added to \a chunk_info by
+ * each call to this method, but we will add more than one if an x or y
+ * attribute has been set on a tspan. \a line_height must be set on input,
+ * and if it needs to be made larger and the #_scanline_maker can't do
+ * an in-situ resize then it will be set to the required value and the
+ * method will return false.
+ */
+bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const &para,
+ UnbrokenSpanPosition const &start_span_pos,
+ ScanlineMaker::ScanRun const &scan_run,
+ std::vector<ChunkInfo> *chunk_info,
+ FontMetrics *line_height) const
+{
+ TRACE((" begin _buildChunksInScanRun: chunks: %lu, em size: %f\n", chunk_info->size(), line_height->emSize() ));
+
+ FontMetrics line_height_saved = *line_height; // Store for recalculating line height if chunks are backed out
+
+ ChunkInfo new_chunk;
+ new_chunk.text_width = 0.0;
+ new_chunk.whitespace_count = 0;
+ new_chunk.scanrun_width = scan_run.width();
+ new_chunk.x = scan_run.x_start;
+
+ // we haven't done anything yet so the last valid break position is the beginning
+ BrokenSpan last_span_at_break, last_span_at_emergency_break;
+ last_span_at_break.start = start_span_pos;
+ last_span_at_break.setZero();
+ last_span_at_emergency_break.start = start_span_pos;
+ last_span_at_emergency_break.setZero();
+
+ TRACE((" trying chunk from %f to %g\n", scan_run.x_start, scan_run.x_end));
+ BrokenSpan new_span;
+ new_span.end = start_span_pos;
+ while (new_span.end.iter_span != para.unbroken_spans.end()) { // this loops once for each UnbrokenSpan
+ new_span.start = new_span.end;
+
+ // force a chunk change at x or y attribute change
+ if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
+
+ if (new_span.start.iter_span != start_span_pos.iter_span)
+ chunk_info->push_back(new_chunk);
+
+ new_chunk.x += new_chunk.text_width;
+ new_chunk.text_width = 0.0;
+ new_chunk.whitespace_count = 0;
+ new_chunk.broken_spans.clear();
+ if (new_span.start.iter_span->x._set) new_chunk.x = new_span.start.iter_span->x.computed;
+ // y doesn't need to be done until output time
+ }
+
+ // see if this span is too tall to fit on the current line
+ FontMetrics new_span_height = new_span.start.iter_span->line_height;
+ new_span_height.computeEffective( new_span.start.iter_span->line_height_multiplier );
+
+ /* floating point 80-bit/64-bit rounding problems require epsilon. See
+ discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */
+ if ( new_span_height.ascent > line_height->ascent + std::numeric_limits<float>::epsilon() ||
+ new_span_height.descent > line_height->descent + std::numeric_limits<float>::epsilon() ) {
+ // Take larger of each of the two ascents and two descents per CSS
+ line_height->max(new_span_height);
+
+ // Currently always true for flowed text and false for Inkscape multiline text.
+ if (!_scanline_maker->canExtendCurrentScanline(*line_height)) {
+ return false;
+ }
+ }
+
+ bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width);
+
+ new_chunk.text_width += new_span.width;
+ new_chunk.whitespace_count += new_span.whitespace_count;
+ new_chunk.broken_spans.push_back(new_span); // if !span_fitted we'll correct ourselves below
+
+ if (!span_fitted) break;
+
+ if (new_span.end.iter_span == para.unbroken_spans.end()) {
+ last_span_at_break = new_span;
+ break;
+ }
+
+ PangoLogAttr const &char_attributes = _charAttributes(para, new_span.end);
+ if (char_attributes.is_mandatory_break) {
+ last_span_at_break = new_span;
+ break;
+ }
+ }
+
+ TRACE((" chunk complete, used %f width (%d whitespaces, %lu brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size()));
+ chunk_info->push_back(new_chunk);
+
+ if (scan_run.width() >= 4.0 * line_height->emSize() && last_span_at_break.end == start_span_pos) {
+ /* **non-SVG spec bit**: See bug #1191102
+ If the user types a very long line with no spaces, the way the spec
+ is written at the moment means that when the length of the text
+ exceeds the available width of all remaining areas, the text is
+ completely hidden. This condition alters that behaviour so that if
+ the length of the line is greater than four times the line-height
+ and there are no spaces, it'll be emergency-wrapped at the last
+ character. One could read the SVG Tiny 1.2 draft as permitting this
+ sort of behaviour, but it's still a bit dodgy. The hard-coding of
+ 4x is not nice, either. */
+ last_span_at_break = last_span_at_emergency_break;
+ }
+
+ if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
+ // need to back out spans until we come to the one with the last break in it
+ while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) {
+ chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
+ chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
+ chunk_info->back().broken_spans.pop_back();
+ if (chunk_info->back().broken_spans.empty())
+ chunk_info->pop_back();
+ }
+ if (!chunk_info->empty()) {
+ chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
+ chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
+ if (last_span_at_break.start == last_span_at_break.end) {
+ chunk_info->back().broken_spans.pop_back(); // last break was at an existing boundary
+ if (chunk_info->back().broken_spans.empty())
+ chunk_info->pop_back();
+ } else {
+ chunk_info->back().broken_spans.back() = last_span_at_break;
+ chunk_info->back().text_width += last_span_at_break.width;
+ chunk_info->back().whitespace_count += last_span_at_break.whitespace_count;
+ }
+ TRACE((" correction: fitted span %lu width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
+ }
+ }
+
+ // Recalculate line_box_height after backing out chunks
+ *line_height = line_height_saved;
+ for (const auto & it_chunk : *chunk_info) {
+ for (const auto & broken_span : it_chunk.broken_spans) {
+ FontMetrics span_height = broken_span.start.iter_span->line_height;
+ TRACE((" brokenspan line_height: %f\n", span_height.emSize() ));
+ span_height.computeEffective( broken_span.start.iter_span->line_height_multiplier );
+ line_height->max( span_height );
+ }
+ }
+ TRACE((" line_box_height: %f\n", line_height->emSize()));
+
+ if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
+ // for justification we need to discard space occupied by the single whitespace at the end of the chunk
+ TRACE((" backing out whitespace\n"));
+ chunk_info->back().broken_spans.back().ends_with_whitespace = false;
+ chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width;
+ chunk_info->back().broken_spans.back().whitespace_count--;
+ chunk_info->back().text_width -= chunk_info->back().broken_spans.back().each_whitespace_width;
+ chunk_info->back().whitespace_count--;
+ }
+
+ if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() ) {
+ // for justification we need to discard line-spacing and word-spacing at end of the chunk
+ chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().letter_spacing;
+ chunk_info->back().text_width -= chunk_info->back().broken_spans.back().letter_spacing;
+ TRACE((" width after subtracting last letter_spacing: %f\n", chunk_info->back().broken_spans.back().width));
+ }
+
+ TRACE((" end _buildChunksInScanRun: chunks: %lu\n", chunk_info->size()));
+ return true;
+}
+
+#ifdef DEBUG_LAYOUT_TNG_COMPUTE
+/**
+ * For debugging, not called in distributed code
+ *
+ * Input: para->first_input_index, para->pango_items
+ */
+void Layout::Calculator::dumpPangoItemsOut(ParagraphInfo *para){
+ std::cerr << "Pango items: " << para->pango_items.size() << std::endl;
+ font_factory * factory = font_factory::Default();
+ for(unsigned pidx = 0 ; pidx < para->pango_items.size(); pidx++){
+ std::cerr
+ << "idx: " << pidx
+ << " offset: "
+ << para->pango_items[pidx].item->offset
+ << " length: "
+ << para->pango_items[pidx].item->length
+ << " font: "
+ << factory->ConstructFontSpecification( para->pango_items[pidx].font )
+ << std::endl;
+ }
+}
+
+/**
+ * For debugging, not called in distributed code
+ *
+ * Input: para->first_input_index, para->pango_items
+ */
+void Layout::Calculator::dumpUnbrokenSpans(ParagraphInfo *para){
+ std::cerr << "Unbroken Spans: " << para->unbroken_spans.size() << std::endl;
+ for(unsigned uidx = 0 ; uidx < para->unbroken_spans.size(); uidx++){
+ std::cerr
+ << "idx: " << uidx
+ << " pango_item_index: " << para->unbroken_spans[uidx].pango_item_index
+ << " input_index: " << para->unbroken_spans[uidx].input_index
+ << " char_index_in_para: " << para->unbroken_spans[uidx].char_index_in_para
+ << " text_bytes: " << para->unbroken_spans[uidx].text_bytes
+ << std::endl;
+ }
+}
+#endif //DEBUG_LAYOUT_TNG_COMPUTE
+
+/** The management function to start the whole thing off. */
+bool Layout::Calculator::calculate()
+{
+ if (_flow._input_stream.empty())
+ return false;
+ /**
+ * hm, why do we want assert (crash) the application, now do simply return false
+ * \todo check if this is the correct behaviour
+ * g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
+ */
+ if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
+ {
+ g_warning("flow text is not of type TEXT_SOURCE. Abort.");
+ return false;
+ }
+ TRACE(("begin calculate()\n"));
+
+ _flow._clearOutputObjects();
+
+ _pango_context = (font_factory::Default())->fontContext;
+
+ _font_factory_size_multiplier = (font_factory::Default())->fontSize;
+
+ _block_progression = _flow._blockProgression();
+ if( _block_progression == RIGHT_TO_LEFT || _block_progression == LEFT_TO_RIGHT ) {
+ // Vertical text, CJK
+ switch (_flow._blockTextOrientation()) {
+ case SP_CSS_TEXT_ORIENTATION_MIXED:
+ pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_EAST);
+ pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_NATURAL);
+ break;
+ case SP_CSS_TEXT_ORIENTATION_UPRIGHT:
+ pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_EAST);
+ pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_STRONG);
+ break;
+ case SP_CSS_TEXT_ORIENTATION_SIDEWAYS:
+ pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_SOUTH);
+ pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_STRONG);
+ break;
+ default:
+ std::cerr << "Layout::Calculator: Unhandled text orientation!" << std::endl;
+ }
+ } else {
+ // Horizontal text
+ pango_context_set_base_gravity(_pango_context, PANGO_GRAVITY_AUTO);
+ pango_context_set_gravity_hint(_pango_context, PANGO_GRAVITY_HINT_NATURAL);
+ }
+
+ // Minimum line box height determined by block container.
+ FontMetrics strut_height = _flow.strut;
+ _y_offset = 0.0;
+ _createFirstScanlineMaker();
+
+ ParagraphInfo para;
+ FontMetrics line_box_height; // Current value of line box height for line.
+ bool keep_going = true; // Set false if we ran out of space and had to stash overflow.
+ for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
+
+ // jump to the next wrap shape if this is a SHAPE_BREAK control code
+ if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) {
+ InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
+ if (control_code->code == SHAPE_BREAK) {
+ TRACE(("shape break control code\n"));
+ if (!_goToNextWrapShape()) {
+ std::cerr << "Layout::Calculator::calculate: Found SHAPE_BREAK but out of shapes!" << std::endl;
+ }
+ continue; // Go to next paragraph (paragraph only contained control code).
+ }
+ }
+
+ // Break things up into little pango units with unique direction, gravity, etc.
+ _buildPangoItemizationForPara(&para);
+
+ // Do shaping (convert characters to glyphs)
+ unsigned para_end_input_index = _buildSpansForPara(&para);
+
+ if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE)
+ para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
+ else
+ para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
+
+ TRACE(("para prepared, adding as #%lu\n", _flow._paragraphs.size()));
+ Layout::Paragraph new_paragraph;
+ new_paragraph.base_direction = para.direction;
+ new_paragraph.alignment = para.alignment;
+ _flow._paragraphs.push_back(new_paragraph);
+
+ // start scanning lines
+ UnbrokenSpanPosition span_pos;
+ span_pos.iter_span = para.unbroken_spans.begin();
+ span_pos.char_byte = 0;
+ span_pos.char_index = 0;
+
+ do { // Until end of paragraph
+ TRACE(("begin line\n"));
+
+ std::vector<ChunkInfo> line_chunk_info;
+
+ // Fill line.
+ // If we've run out of space, we've put the remaining text in a single line and
+ // returned false. If we ran out of space on previous paragraph, we continue with
+ // single-line scan-line maker.
+ bool flowed =_findChunksForLine(para, &span_pos, &line_chunk_info, &line_box_height, &strut_height );
+ if (!flowed) {
+ keep_going = false;
+ }
+
+ if (line_box_height.emSize() < 0.001 && line_chunk_info.empty()) {
+ // We need to avoid an infinite (or semi-infinite) loop.
+ std::cerr << "Layout::Calculator::calculate: No room for text and line advance is very small" << std::endl;
+ return false; // For the moment
+ }
+
+
+ // For Inkscape multi-line text (using role="line") we run into a problem if the first
+ // line is empty - namely, there is no character to attach a 'y' attribute value. The
+ // result is that the code that takes a baseline position (e.g. 'y') and finds the top
+ // of the layout box is bypassed resulting in wrongly placed text (we layout the text
+ // relative to the top of the box as this is required for text-in-a-shape). We don't
+ // know how to find the top of the box from the 'y' position until we have found the
+ // line height parameters for the given line (after calling _findChunksForLine() just
+ // above).
+ if (para.first_input_index == 0 && (_flow.wrap_mode == WRAP_NONE)) {
+
+ // Calculate new top of box... given specified baseline.
+ double top_of_line_box = _scanline_maker->yCoordinate(); // Set in constructor.
+ if( _block_progression == RIGHT_TO_LEFT ) {
+ // Vertical text, use em box center as baseline
+ top_of_line_box += 0.5 * line_box_height.emSize();
+ } else if (_block_progression == LEFT_TO_RIGHT ) {
+ // Vertical text, use em box center as baseline
+ top_of_line_box -= 0.5 * line_box_height.emSize();
+ } else {
+ top_of_line_box -= line_box_height.getTypoAscent();
+ }
+ TRACE((" y attribute set, next line top_of_line_box: %f\n", top_of_line_box ));
+ // Set the initial y coordinate of the for this line (see above).
+ _scanline_maker->setNewYCoordinate(top_of_line_box);
+ }
+
+ // !keep_going --> truncated --> hidden
+ _outputLine(para, line_box_height, line_chunk_info, !keep_going);
+
+ _scanline_maker->setLineHeight( line_box_height );
+ _scanline_maker->completeLine(); // Increments y by line height
+ TRACE(("end line\n"));
+ } while (span_pos.iter_span != para.unbroken_spans.end());
+
+ TRACE(("para %lu end\n\n", _flow._paragraphs.size() - 1));
+ if (keep_going) {
+ // We have more to do, setup next section.
+ bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
+ if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size())
+ || para_end_input_index + 1 < _flow._input_stream.size()) {
+ // we need a span just for the para if it's either an empty last para or a break in the middle
+ Layout::Span new_span;
+ if (_flow._spans.empty()) {
+ new_span.font = nullptr;
+ new_span.font_size = line_box_height.emSize();
+ new_span.line_height = line_box_height;
+ new_span.x_end = 0.0;
+ } else {
+ new_span = _flow._spans.back();
+ if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1)
+ new_span.x_end = 0.0;
+ }
+ new_span.in_chunk = _flow._chunks.size() - 1;
+ if (new_span.font)
+ new_span.font->Ref();
+ new_span.x_start = new_span.x_end;
+ new_span.baseline_shift = 0.0;
+ new_span.direction = para.direction;
+ new_span.block_progression = _block_progression;
+ if (para_end_input_index == _flow._input_stream.size())
+ new_span.in_input_stream_item = _flow._input_stream.size() - 1;
+ else
+ new_span.in_input_stream_item = para_end_input_index;
+ _flow._spans.push_back(new_span);
+ }
+ if (para_end_input_index + 1 < _flow._input_stream.size()) {
+ // we've got to add an invisible character between paragraphs so that we can position iterators
+ // (and hence cursors) both before and after the paragraph break
+ Layout::Character new_character;
+ new_character.the_char = '@';
+ new_character.in_span = _flow._spans.size() - 1;
+ new_character.char_attributes.is_line_break = 1;
+ new_character.char_attributes.is_mandatory_break = 1;
+ new_character.char_attributes.is_char_break = 1;
+ new_character.char_attributes.is_white = 1;
+ new_character.char_attributes.is_cursor_position = 1;
+ new_character.char_attributes.is_word_start = 0;
+ new_character.char_attributes.is_word_end = 1;
+ new_character.char_attributes.is_sentence_start = 0;
+ new_character.char_attributes.is_sentence_end = 1;
+ new_character.char_attributes.is_sentence_boundary = 1;
+ new_character.char_attributes.backspace_deletes_character = 1;
+ new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start;
+ new_character.in_glyph = -1;
+ _flow._characters.push_back(new_character);
+ }
+ }
+ // dumpPangoItemsOut(&para);
+ // dumpUnbrokenSpans(&para);
+
+ para.free();
+ para.first_input_index = para_end_input_index + 1;
+ } // Loop over paras
+
+ para.free();
+ if (_scanline_maker) {
+ delete _scanline_maker;
+ }
+
+ _flow._input_truncated = !keep_going;
+
+ if (_flow.textLength._set) {
+ // Calculate the adjustment needed to meet the textLength
+ double actual_length = _flow.getActualLength();
+ double difference = _flow.textLength.computed - actual_length;
+ _flow.textLengthMultiplier = (actual_length + difference) / actual_length;
+ _flow.textLengthIncrement = difference / (_flow._characters.size() == 1? 1 : _flow._characters.size() - 1);
+ }
+
+ return true;
+}
+
+void Layout::_calculateCursorShapeForEmpty()
+{
+ _empty_cursor_shape.position = Geom::Point(0, 0);
+ _empty_cursor_shape.height = 0.0;
+ _empty_cursor_shape.rotation = 0.0;
+ if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE)
+ return;
+
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
+
+ font_instance *font = text_source->styleGetFontInstance();
+ double font_size = text_source->style->font_size.computed;
+ double caret_slope_run = 0.0, caret_slope_rise = 1.0;
+ FontMetrics line_height;
+ if (font) {
+ const_cast<font_instance*>(font)->FontSlope(caret_slope_run, caret_slope_rise);
+ font->FontMetrics(line_height.ascent, line_height.descent, line_height.xheight);
+ line_height *= font_size;
+ font->Unref();
+ }
+
+ double caret_slope = atan2(caret_slope_run, caret_slope_rise);
+ _empty_cursor_shape.height = font_size / cos(caret_slope);
+ _empty_cursor_shape.rotation = caret_slope;
+
+ if (_input_wrap_shapes.empty()) {
+ _empty_cursor_shape.position = Geom::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
+ text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
+ } else if (wrap_mode == WRAP_INLINE_SIZE) {
+ // 'inline-size' has a wrap shape of an "infinite" rectangle, we need the place where the text should begin.
+ double x = 0;
+ double y = 0;
+ if (!text_source->x.empty())
+ x = text_source->x.front().computed;
+ if (!text_source->y.empty())
+ y = text_source->y.front().computed;
+ _empty_cursor_shape.position = Geom::Point(x, y);
+ } else {
+ Direction block_progression = text_source->styleGetBlockProgression();
+ ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression);
+ std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.makeScanline(line_height);
+ if (!scan_runs.empty()) {
+ if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) {
+ // Vertical text
+ _empty_cursor_shape.position = Geom::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
+ } else {
+ // Horizontal text
+ _empty_cursor_shape.position = Geom::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
+ }
+ }
+ }
+}
+
+bool Layout::calculateFlow()
+{
+ TRACE(("begin calculateFlow()\n"));
+ Layout::Calculator calc = Calculator(this);
+ bool result = calc.calculate();
+
+ if (textLengthIncrement != 0) {
+ TRACE(("Recalculating layout the second time to fit textLength!\n"));
+ result = calc.calculate();
+ }
+
+ if (_characters.empty()) {
+ _calculateCursorShapeForEmpty();
+ }
+ return result;
+}
+
+}//namespace Text
+}//namespace Inkscape
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/Layout-TNG-Input.cpp b/src/libnrtype/Layout-TNG-Input.cpp
new file mode 100644
index 0000000..cc7d02a
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Input.cpp
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::Text::Layout - text layout engine input functions
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifndef PANGO_ENABLE_ENGINE
+#define PANGO_ENABLE_ENGINE
+#endif
+
+#include "Layout-TNG.h"
+#include "style.h"
+#include "svg/svg-length.h"
+#include "FontFactory.h"
+
+
+namespace Inkscape {
+namespace Text {
+
+void Layout::_clearInputObjects()
+{
+ for(auto & it : _input_stream) {
+ delete it;
+ }
+
+ _input_stream.clear();
+ _input_wrap_shapes.clear();
+}
+
+// this function does nothing more than store all its parameters for future reference
+void Layout::appendText(Glib::ustring const &text,
+ SPStyle *style,
+ SPObject *source,
+ OptionalTextTagAttrs const *optional_attributes,
+ unsigned optional_attributes_offset,
+ Glib::ustring::const_iterator text_begin,
+ Glib::ustring::const_iterator text_end)
+{
+ if (style == nullptr) return;
+
+ InputStreamTextSource *new_source = new InputStreamTextSource;
+
+ new_source->source = source;
+ new_source->text = &text;
+ new_source->text_begin = text_begin;
+ new_source->text_end = text_end;
+ new_source->style = style;
+ sp_style_ref(style);
+
+ new_source->text_length = 0;
+ for ( ; text_begin != text_end && text_begin != text.end() ; ++text_begin)
+ new_source->text_length++; // save this because calculating the length of a UTF-8 string is expensive
+
+ if (optional_attributes) {
+ // we need to fill in x and y even if the text is empty so that empty paragraphs can be positioned correctly
+ _copyInputVector(optional_attributes->x, optional_attributes_offset, &new_source->x, std::max(1, new_source->text_length));
+ _copyInputVector(optional_attributes->y, optional_attributes_offset, &new_source->y, std::max(1, new_source->text_length));
+ _copyInputVector(optional_attributes->dx, optional_attributes_offset, &new_source->dx, new_source->text_length);
+ _copyInputVector(optional_attributes->dy, optional_attributes_offset, &new_source->dy, new_source->text_length);
+ _copyInputVector(optional_attributes->rotate, optional_attributes_offset, &new_source->rotate, new_source->text_length);
+ if (!optional_attributes->rotate.empty() && optional_attributes_offset >= optional_attributes->rotate.size()) {
+ SVGLength last_rotate;
+ last_rotate = 0.f;
+ for (auto it : optional_attributes->rotate)
+ if (it._set)
+ last_rotate = it;
+ new_source->rotate.resize(1, last_rotate);
+ }
+ new_source->textLength._set = optional_attributes->textLength._set;
+ new_source->textLength.value = optional_attributes->textLength.value;
+ new_source->textLength.computed = optional_attributes->textLength.computed;
+ new_source->textLength.unit = optional_attributes->textLength.unit;
+ new_source->lengthAdjust = optional_attributes->lengthAdjust;
+ }
+
+ _input_stream.push_back(new_source);
+}
+
+void Layout::_copyInputVector(std::vector<SVGLength> const &input_vector, unsigned input_offset, std::vector<SVGLength> *output_vector, size_t max_length)
+{
+ output_vector->clear();
+ if (input_offset >= input_vector.size()) return;
+ output_vector->reserve(std::min(max_length, input_vector.size() - input_offset));
+ while (input_offset < input_vector.size() && max_length != 0) {
+ if (!input_vector[input_offset]._set)
+ break;
+ output_vector->push_back(input_vector[input_offset]);
+ input_offset++;
+ max_length--;
+ }
+}
+
+// just save what we've been given, really
+void Layout::appendControlCode(TextControlCode code, SPObject *source, double width, double ascent, double descent)
+{
+ InputStreamControlCode *new_code = new InputStreamControlCode;
+
+ new_code->source = source;
+ new_code->code = code;
+ new_code->width = width;
+ new_code->ascent = ascent;
+ new_code->descent = descent;
+
+ _input_stream.push_back(new_code);
+}
+
+// more saving of the parameters
+void Layout::appendWrapShape(Shape const *shape, DisplayAlign display_align)
+{
+ _input_wrap_shapes.emplace_back();
+ _input_wrap_shapes.back().shape = shape;
+ _input_wrap_shapes.back().display_align = display_align;
+}
+
+Layout::Direction Layout::InputStreamTextSource::styleGetBlockProgression() const
+{
+ switch( style->writing_mode.computed ) {
+ case SP_CSS_WRITING_MODE_LR_TB:
+ case SP_CSS_WRITING_MODE_RL_TB:
+ return TOP_TO_BOTTOM;
+
+ case SP_CSS_WRITING_MODE_TB_RL:
+ return RIGHT_TO_LEFT;
+
+ case SP_CSS_WRITING_MODE_TB_LR:
+ return LEFT_TO_RIGHT;
+
+ default:
+ std::cerr << "Layout::InputTextStream::styleGetBlockProgression: invalid writing mode." << std::endl;
+ }
+ return TOP_TO_BOTTOM;
+}
+
+SPCSSTextOrientation Layout::InputStreamTextSource::styleGetTextOrientation() const
+{
+ return ((SPCSSTextOrientation)style->text_orientation.computed);
+}
+
+SPCSSBaseline Layout::InputStreamTextSource::styleGetDominantBaseline() const
+{
+ return ((SPCSSBaseline)style->dominant_baseline.computed);
+}
+
+static Layout::Alignment text_anchor_to_alignment(unsigned anchor, Layout::Direction para_direction)
+{
+ switch (anchor) {
+ default:
+ case SP_CSS_TEXT_ANCHOR_START: return para_direction == Layout::LEFT_TO_RIGHT ? Layout::LEFT : Layout::RIGHT;
+ case SP_CSS_TEXT_ANCHOR_MIDDLE: return Layout::CENTER;
+ case SP_CSS_TEXT_ANCHOR_END: return para_direction == Layout::LEFT_TO_RIGHT ? Layout::RIGHT : Layout::LEFT;
+ }
+}
+
+Layout::Alignment Layout::InputStreamTextSource::styleGetAlignment(Layout::Direction para_direction, bool try_text_align) const
+{
+ if (!try_text_align)
+ return text_anchor_to_alignment(style->text_anchor.computed, para_direction);
+
+ // there's no way to tell the difference between text-anchor set higher up the cascade to the default and
+ // text-anchor never set anywhere in the cascade, so in order to detect which of text-anchor or text-align
+ // to use we'll have to run up the style tree ourselves.
+ SPStyle const *this_style = style;
+
+ for ( ; ; ) {
+ // If both text-align and text-anchor are set at the same level, text-align takes
+ // precedence because it is the most expressive.
+ if (this_style->text_align.set) {
+ switch (style->text_align.computed) {
+ default:
+ case SP_CSS_TEXT_ALIGN_START: return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
+ case SP_CSS_TEXT_ALIGN_END: return para_direction == LEFT_TO_RIGHT ? RIGHT : LEFT;
+ case SP_CSS_TEXT_ALIGN_LEFT: return LEFT;
+ case SP_CSS_TEXT_ALIGN_RIGHT: return RIGHT;
+ case SP_CSS_TEXT_ALIGN_CENTER: return CENTER;
+ case SP_CSS_TEXT_ALIGN_JUSTIFY: return FULL;
+ }
+ }
+ if (this_style->text_anchor.set)
+ return text_anchor_to_alignment(this_style->text_anchor.computed, para_direction);
+ if (this_style->object == nullptr || this_style->object->parent == nullptr) break;
+ this_style = this_style->object->parent->style;
+ if (this_style == nullptr) break;
+ }
+ return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
+}
+
+font_instance *Layout::InputStreamTextSource::styleGetFontInstance() const
+{
+ PangoFontDescription *descr = styleGetFontDescription();
+ if (descr == nullptr) return nullptr;
+ font_instance *res = (font_factory::Default())->Face(descr);
+ pango_font_description_free(descr);
+ return res;
+}
+
+PangoFontDescription *Layout::InputStreamTextSource::styleGetFontDescription() const
+{
+ // This use to be done by code here but it duplicated more complete code in FontFactory.cpp.
+ PangoFontDescription *descr = ink_font_description_from_style( style );
+
+ // Font size not yet set
+#ifdef USE_PANGO_WIN32
+
+ // Damn Pango fudges the size, so we need to unfudge. See source of pango_win32_font_map_init()
+ pango_font_description_set_size(descr,
+ (int) ((font_factory::Default())->fontSize*PANGO_SCALE*72 / GetDeviceCaps(pango_win32_get_dc(),LOGPIXELSY))
+ );
+
+ // We unset stretch on Win32, because pango-win32 has no concept of it
+ // (Windows doesn't really provide any useful field it could use).
+ // If we did set stretch, then any text with a font-stretch attribute would
+ // end up falling back to a default.
+ pango_font_description_unset_fields(descr, PANGO_FONT_MASK_STRETCH);
+
+#else
+
+ // mandatory huge size (hinting workaround)
+ pango_font_description_set_size(descr, (int) ((font_factory::Default())->fontSize*PANGO_SCALE));
+
+#endif
+
+ return descr;
+}
+
+Layout::InputStreamTextSource::~InputStreamTextSource()
+{
+ sp_style_unref(style);
+}
+
+}//namespace Text
+}//namespace Inkscape
diff --git a/src/libnrtype/Layout-TNG-OutIter.cpp b/src/libnrtype/Layout-TNG-OutIter.cpp
new file mode 100644
index 0000000..dd2123e
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-OutIter.cpp
@@ -0,0 +1,1175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::Text::Layout - text layout engine output functions using iterators
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "Layout-TNG.h"
+#include "livarot/Path.h"
+#include "font-instance.h"
+#include "svg/svg-length.h"
+#include <2geom/transforms.h>
+#include <2geom/line.h>
+#include "style.h"
+
+namespace Inkscape {
+namespace Text {
+
+// Comment 18 Sept 2019:
+// Cursor code might be simpler if Character was turned into a proper
+// class and kept track of its absolute position and extent. This would
+// make handling multi-line text (including multi-line text using
+// 'white-space:pre') easier. This would also avoid problems where
+// 'dx','dy' moved the character a long distance from its nominal
+// position.
+
+Layout::iterator Layout::_cursorXOnLineToIterator(unsigned line_index, double local_x, double local_y) const
+{
+ unsigned char_index = _lineToCharacter(line_index);
+ int best_char_index = -1;
+ double best_difference = DBL_MAX;
+
+ if (char_index == _characters.size()) return end();
+ for ( ; char_index < _characters.size() ; char_index++) {
+ if (_characters[char_index].chunk(this).in_line != line_index) break;
+ //if (_characters[char_index].char_attributes.is_mandatory_break) break;
+ if (!_characters[char_index].char_attributes.is_cursor_position) continue;
+
+ double delta_x =
+ _characters[char_index].x +
+ _characters[char_index].span(this).x_start +
+ _characters[char_index].chunk(this).left_x -
+ local_x;
+
+ double delta_y =
+ _characters[char_index].span(this).y_offset +
+ _characters[char_index].line(this).baseline_y -
+ local_y;
+
+ double this_difference = std::sqrt(delta_x*delta_x + delta_y*delta_y);
+
+ if (this_difference < best_difference) {
+ best_difference = this_difference;
+ best_char_index = char_index;
+ }
+ }
+
+ // also try the very end of a para (not lines though because the space wraps)
+ if (char_index == _characters.size() || _characters[char_index].char_attributes.is_mandatory_break) {
+
+ double delta_x = 0.0;
+ double delta_y = 0.0;
+
+ if (char_index == 0) {
+ delta_x = _spans.front().x_end + _chunks.front().left_x - local_x;
+ delta_y = _spans.front().y_offset + _spans.front().line(this).baseline_y - local_y;
+ } else {
+ delta_x = _characters[char_index - 1].span(this).x_end + _characters[char_index - 1].chunk(this).left_x - local_x;
+ delta_y = _characters[char_index - 1].span(this).y_offset + _characters[char_index - 1].line(this).baseline_y - local_y;
+ }
+
+ double this_difference = std::sqrt(delta_x*delta_x + delta_y*delta_y);
+
+ if (this_difference < best_difference) {
+ best_char_index = char_index;
+ best_difference = this_difference;
+ }
+ }
+
+
+ if (best_char_index == -1) {
+ best_char_index = char_index;
+ }
+
+ if (best_char_index == _characters.size()) {
+ return end();
+ }
+
+ return iterator(this, best_char_index);
+}
+
+double Layout::_getChunkWidth(unsigned chunk_index) const
+{
+ double chunk_width = 0.0;
+ unsigned span_index;
+ if (chunk_index) {
+ span_index = _lineToSpan(_chunks[chunk_index].in_line);
+ for ( ; span_index < _spans.size() && _spans[span_index].in_chunk < chunk_index ; span_index++){};
+ } else {
+ span_index = 0;
+ }
+
+ for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++) {
+ chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
+ }
+
+ return chunk_width;
+}
+
+/* getting the cursor position for a mouse click is not as simple as it might
+seem. The two major problems are flows set up in multiple columns and large
+dy adjustments such that text does not belong to the line it appears to. In
+the worst case it's possible to have two characters on top of each other, in
+which case the one we pick is arbitrary.
+
+This is a 3-stage (2 pass) algorithm:
+1) search all the spans to see if the point is contained in one, if so take
+ that. Note that this will collect all clicks from the current UI because
+ of how the hit detection of nrarena objects works.
+2) if that fails, run through all the chunks finding a best guess of the one
+ the user wanted. This is the one whose y coordinate is nearest, or if
+ there's a tie, the x.
+3) search in that chunk using x-coordinate only to find the position.
+*/
+Layout::iterator Layout::getNearestCursorPositionTo(double x, double y) const
+{
+ if (_lines.empty()) return begin();
+ double local_x = x;
+ double local_y = y;
+
+ if (_path_fitted) {
+ Path::cut_position position = const_cast<Path*>(_path_fitted)->PointToCurvilignPosition(Geom::Point(x, y));
+ local_x = const_cast<Path*>(_path_fitted)->PositionToLength(position.piece, position.t);
+ return _cursorXOnLineToIterator(0, local_x + _chunks.front().left_x);
+ }
+
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ local_x = y;
+ local_y = x;
+ }
+
+ // stage 1:
+ for (const auto & _span : _spans) {
+ double span_left, span_right;
+ if (_span.x_start < _span.x_end) {
+ span_left = _span.x_start;
+ span_right = _span.x_end;
+ } else {
+ span_left = _span.x_end;
+ span_right = _span.x_start;
+ }
+
+ double y_line = _span.line(this).baseline_y + _span.baseline_shift + _span.y_offset;
+ if ( local_x >= _chunks[_span.in_chunk].left_x + span_left
+ && local_x <= _chunks[_span.in_chunk].left_x + span_right
+ && local_y >= y_line - _span.line_height.ascent
+ && local_y <= y_line + _span.line_height.descent) {
+ return _cursorXOnLineToIterator(_chunks[_span.in_chunk].in_line, local_x, local_y);
+ }
+ }
+
+ // stage 2:
+ unsigned span_index = 0;
+ unsigned chunk_index;
+ int best_chunk_index = -1;
+ double best_y_range = DBL_MAX;
+ double best_x_range = DBL_MAX;
+ for (chunk_index = 0 ; chunk_index < _chunks.size() ; chunk_index++) {
+ FontMetrics line_height;
+ line_height *= 0.0; // Set all metrics to zero.
+ double chunk_width = 0.0;
+ for ( ; span_index < _spans.size() && _spans[span_index].in_chunk == chunk_index ; span_index++) {
+ line_height.max(_spans[span_index].line_height);
+ chunk_width = std::max(chunk_width, (double)std::max(_spans[span_index].x_start, _spans[span_index].x_end));
+ }
+ double this_y_range;
+ if (local_y < _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent)
+ this_y_range = _lines[_chunks[chunk_index].in_line].baseline_y - line_height.ascent - local_y;
+ else if (local_y > _lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent)
+ this_y_range = local_y - (_lines[_chunks[chunk_index].in_line].baseline_y + line_height.descent);
+ else
+ this_y_range = 0.0;
+ if (this_y_range <= best_y_range) {
+ if (this_y_range < best_y_range) best_x_range = DBL_MAX;
+ double this_x_range;
+ if (local_x < _chunks[chunk_index].left_x)
+ this_x_range = _chunks[chunk_index].left_x - local_y;
+ else if (local_x > _chunks[chunk_index].left_x + chunk_width)
+ this_x_range = local_x - (_chunks[chunk_index].left_x + chunk_width);
+ else
+ this_x_range = 0.0;
+ if (this_x_range < best_x_range) {
+ best_y_range = this_y_range;
+ best_x_range = this_x_range;
+ best_chunk_index = chunk_index;
+ }
+ }
+ }
+
+ // stage 3:
+ if (best_chunk_index == -1) return begin(); // never happens
+ return _cursorXOnLineToIterator(_chunks[best_chunk_index].in_line, local_x, local_y);
+}
+
+Layout::iterator Layout::getLetterAt(double x, double y) const
+{
+ Geom::Point point(x, y);
+
+ double rotation;
+ for (iterator it = begin() ; it != end() ; it.nextCharacter()) {
+ Geom::Rect box = characterBoundingBox(it, &rotation);
+ // todo: rotation
+ if (box.contains(point)) return it;
+ }
+ return end();
+}
+
+Layout::iterator Layout::sourceToIterator(SPObject *source /*, Glib::ustring::const_iterator text_iterator*/) const
+{
+ unsigned source_index;
+ if (_characters.empty()) return end();
+ for (source_index = 0 ; source_index < _input_stream.size() ; source_index++)
+ if (_input_stream[source_index]->source == source) break;
+ if (source_index == _input_stream.size()) return end();
+
+ unsigned char_index = _sourceToCharacter(source_index);
+
+ // Fix a bug when hidding content in flow box element
+ if (char_index >= _characters.size())
+ return end();
+
+ if (_input_stream[source_index]->Type() != TEXT_SOURCE)
+ return iterator(this, char_index);
+
+ return iterator(this, char_index);
+ /* This code was never used, the text_iterator argument was "NULL" in all calling code
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[source_index]);
+
+ if (text_iterator <= text_source->text_begin) return iterator(this, char_index);
+ if (text_iterator >= text_source->text_end) {
+ if (source_index == _input_stream.size() - 1) return end();
+ return iterator(this, _sourceToCharacter(source_index + 1));
+ }
+ Glib::ustring::const_iterator iter_text = text_source->text_begin;
+ for ( ; char_index < _characters.size() ; char_index++) {
+ if (iter_text == text_iterator)
+ return iterator(this, char_index);
+ iter_text++;
+ }
+ return end(); // never happens
+ */
+}
+
+Geom::OptRect Layout::glyphBoundingBox(iterator const &it, double *rotation) const
+{
+ if (rotation) *rotation = _glyphs[it._glyph_index].rotation;
+ return _glyphs[it._glyph_index].span(this).font->BBox(_glyphs[it._glyph_index].glyph);
+}
+
+Geom::Point Layout::characterAnchorPoint(iterator const &it) const
+{
+ if (_characters.empty())
+ return _empty_cursor_shape.position;
+
+ Geom::Point res;
+ if (it._char_index == _characters.size()) {
+ res = Geom::Point(_chunks.back().left_x + _spans.back().x_end, _lines.back().baseline_y + _spans.back().baseline_shift);
+ } else {
+ res = Geom::Point(_characters[it._char_index].chunk(this).left_x
+ + _spans[_characters[it._char_index].in_span].x_start
+ + _characters[it._char_index].x,
+ _characters[it._char_index].line(this).baseline_y
+ + _characters[it._char_index].span(this).baseline_shift);
+ }
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ std::swap(res[Geom::X], res[Geom::Y]);
+ }
+ return res;
+}
+
+std::optional<Geom::Point> Layout::baselineAnchorPoint() const
+{
+ iterator pos = this->begin();
+ Geom::Point left_pt = this->characterAnchorPoint(pos);
+ pos.thisEndOfLine();
+ Geom::Point right_pt = this->characterAnchorPoint(pos);
+
+ switch (this->paragraphAlignment(pos)) {
+ case LEFT:
+ case FULL:
+ return left_pt;
+ break;
+ case CENTER:
+ return (left_pt + right_pt)/2; // middle point
+ break;
+ case RIGHT:
+ return right_pt;
+ break;
+ default:
+ return std::optional<Geom::Point>();
+ break;
+ }
+}
+
+Geom::Path Layout::baseline() const
+{
+ iterator pos = this->begin();
+ Geom::Point left_pt = this->characterAnchorPoint(pos);
+ pos.thisEndOfLine();
+ Geom::Point right_pt = this->characterAnchorPoint(pos);
+
+ Geom::Path baseline;
+ baseline.start(left_pt);
+ baseline.appendNew<Geom::LineSegment>(right_pt);
+
+ return baseline;
+}
+
+
+Geom::Point Layout::chunkAnchorPoint(iterator const &it) const
+{
+ unsigned chunk_index;
+
+ if (_chunks.empty())
+ return Geom::Point(0.0, 0.0);
+
+ if (_characters.empty())
+ chunk_index = 0;
+ else if (it._char_index == _characters.size())
+ chunk_index = _chunks.size() - 1;
+ else chunk_index = _characters[it._char_index].span(this).in_chunk;
+
+ Alignment alignment = _paragraphs[_lines[_chunks[chunk_index].in_line].in_paragraph].alignment;
+ double x = _chunks[chunk_index].left_x;
+ double y = _lines[_chunks[chunk_index].in_line].baseline_y;
+ double chunk_width = _getChunkWidth(chunk_index);
+ if (alignment == RIGHT) {
+ x += chunk_width;
+ } else if (alignment == CENTER) {
+ x += chunk_width * 0.5;
+ }
+
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ return Geom::Point(y, x);
+ } else {
+ return Geom::Point(x, y);
+ }
+}
+
+Geom::Rect Layout::characterBoundingBox(iterator const &it, double *rotation) const
+{
+ Geom::Point top_left, bottom_right;
+ unsigned char_index = it._char_index;
+
+ if (_path_fitted) {
+ double cluster_half_width = 0.0;
+ for (int glyph_index = _characters[char_index].in_glyph ; _glyphs.size() != glyph_index ; glyph_index++) {
+ if (_glyphs[glyph_index].in_character != char_index) break;
+ cluster_half_width += _glyphs[glyph_index].advance;
+ }
+ cluster_half_width *= 0.5;
+
+ double midpoint_offset = _characters[char_index].span(this).x_start + _characters[char_index].x + cluster_half_width;
+ int unused = 0;
+ Path::cut_position *midpoint_otp = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &midpoint_offset, unused);
+ if (midpoint_offset >= 0.0 && midpoint_otp != nullptr && midpoint_otp[0].piece >= 0) {
+ Geom::Point midpoint;
+ Geom::Point tangent;
+ Span const &span = _characters[char_index].span(this);
+
+ const_cast<Path*>(_path_fitted)->PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
+ top_left[Geom::X] = midpoint[Geom::X] - cluster_half_width;
+ top_left[Geom::Y] = midpoint[Geom::Y] - span.line_height.ascent;
+ bottom_right[Geom::X] = midpoint[Geom::X] + cluster_half_width;
+ bottom_right[Geom::Y] = midpoint[Geom::Y] + span.line_height.descent;
+ Geom::Point normal = tangent.cw();
+ top_left += span.baseline_shift * normal;
+ bottom_right += span.baseline_shift * normal;
+ if (rotation)
+ *rotation = atan2(tangent[1], tangent[0]);
+ }
+ g_free(midpoint_otp);
+ } else {
+ if (it._char_index == _characters.size()) {
+ top_left[Geom::X] = bottom_right[Geom::X] = _chunks.back().left_x + _spans.back().x_end;
+ char_index--;
+ } else {
+ double span_x = _spans[_characters[it._char_index].in_span].x_start + _characters[it._char_index].chunk(this).left_x;
+ top_left[Geom::X] = span_x + _characters[it._char_index].x;
+ if (it._char_index + 1 == _characters.size() || _characters[it._char_index + 1].in_span != _characters[it._char_index].in_span)
+ bottom_right[Geom::X] = _spans[_characters[it._char_index].in_span].x_end + _characters[it._char_index].chunk(this).left_x;
+ else
+ bottom_right[Geom::X] = span_x + _characters[it._char_index + 1].x;
+ }
+
+ double baseline_y = _characters[char_index].line(this).baseline_y + _characters[char_index].span(this).baseline_shift;
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ double span_height = _spans[_characters[char_index].in_span].line_height.emSize();
+ top_left[Geom::Y] = top_left[Geom::X];
+ top_left[Geom::X] = baseline_y - span_height * 0.5;
+ bottom_right[Geom::Y] = bottom_right[Geom::X];
+ bottom_right[Geom::X] = baseline_y + span_height * 0.5;
+ } else {
+ top_left[Geom::Y] = baseline_y - _spans[_characters[char_index].in_span].line_height.ascent;
+ bottom_right[Geom::Y] = baseline_y + _spans[_characters[char_index].in_span].line_height.descent;
+ }
+
+ if (rotation) {
+ if (it._glyph_index == -1)
+ *rotation = 0.0;
+ else if (it._glyph_index == (int)_glyphs.size())
+ *rotation = _glyphs.back().rotation;
+ else
+ *rotation = _glyphs[it._glyph_index].rotation;
+ }
+ }
+
+ return Geom::Rect(top_left, bottom_right);
+}
+
+std::vector<Geom::Point> Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, Geom::Affine const &transform) const
+{
+ std::vector<Geom::Point> quads;
+ unsigned char_index;
+ unsigned end_char_index;
+
+ if (it_start._char_index < it_end._char_index) {
+ char_index = it_start._char_index;
+ end_char_index = it_end._char_index;
+ } else {
+ char_index = it_end._char_index;
+ end_char_index = it_start._char_index;
+ }
+ for ( ; char_index < end_char_index ; ) {
+ if (_characters[char_index].in_glyph == -1) {
+ char_index++;
+ continue;
+ }
+ double char_rotation = _glyphs[_characters[char_index].in_glyph].rotation;
+ unsigned span_index = _characters[char_index].in_span;
+
+ Geom::Point top_left, bottom_right;
+ if (_path_fitted || char_rotation != 0.0) {
+ Geom::Rect box = characterBoundingBox(iterator(this, char_index), &char_rotation);
+ top_left = box.min();
+ bottom_right = box.max();
+ char_index++;
+ } else { // for straight text we can be faster by combining all the character boxes in a span into one box
+ double span_x = _spans[span_index].x_start + _spans[span_index].chunk(this).left_x;
+ top_left[Geom::X] = span_x + _characters[char_index].x;
+ while (char_index < end_char_index && _characters[char_index].in_span == span_index)
+ char_index++;
+ if (char_index == _characters.size() || _characters[char_index].in_span != span_index)
+ bottom_right[Geom::X] = _spans[span_index].x_end + _spans[span_index].chunk(this).left_x;
+ else
+ bottom_right[Geom::X] = span_x + _characters[char_index].x;
+
+ double baseline_y = _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift;
+ double vertical_scale = _glyphs.back().vertical_scale;
+ double offset_y = _spans[span_index].y_offset;
+
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ double span_height = vertical_scale * _spans[span_index].line_height.emSize();
+ top_left[Geom::Y] = top_left[Geom::X];
+ top_left[Geom::X] = offset_y + baseline_y - span_height * 0.5;
+ bottom_right[Geom::Y] = bottom_right[Geom::X];
+ bottom_right[Geom::X] = offset_y + baseline_y + span_height * 0.5;
+ } else {
+ top_left[Geom::Y] = offset_y + baseline_y - vertical_scale * _spans[span_index].line_height.ascent;
+ bottom_right[Geom::Y] = offset_y + baseline_y + vertical_scale * _spans[span_index].line_height.descent;
+ }
+ }
+
+ Geom::Rect char_box(top_left, bottom_right);
+ if (char_box.dimensions()[Geom::X] == 0.0 || char_box.dimensions()[Geom::Y] == 0.0)
+ continue;
+ Geom::Point center_of_rotation((top_left[Geom::X] + bottom_right[Geom::X]) * 0.5,
+ top_left[Geom::Y] + _spans[span_index].line_height.ascent);
+ Geom::Affine total_transform = Geom::Translate(-center_of_rotation) * Geom::Rotate(char_rotation) * Geom::Translate(center_of_rotation) * transform;
+ for(int i = 0; i < 4; i ++)
+ quads.push_back(char_box.corner(i) * total_transform);
+ }
+ return quads;
+}
+
+void Layout::queryCursorShape(iterator const &it, Geom::Point &position, double &height, double &rotation) const
+{
+ if (_characters.empty()) {
+ position = _empty_cursor_shape.position;
+ height = _empty_cursor_shape.height;
+ rotation = _empty_cursor_shape.rotation;
+ } else {
+ // we want to cursor to be positioned where the left edge of a character that is about to be typed will be.
+ // this means x & rotation are the current values but y & height belong to the previous character.
+ // this isn't quite right because dx attributes will be moved along, but it's good enough
+ Span const *span;
+ bool vertical_text = _directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM);
+ if (_path_fitted) {
+ // text on a path
+ double x;
+ if (it._char_index >= _characters.size()) {
+ span = &_spans.back();
+ x = span->x_end + _chunks.back().left_x - _chunks[0].left_x;
+ } else {
+ span = &_spans[_characters[it._char_index].in_span];
+ x = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x - _chunks[0].left_x;
+ if (vertical_text)
+ x -= span->line_height.descent;
+ if (it._char_index != 0)
+ span = &_spans[_characters[it._char_index - 1].in_span];
+ }
+ double path_length = const_cast<Path*>(_path_fitted)->Length();
+ double x_on_path = x;
+ if (x_on_path < 0.0) x_on_path = 0.0;
+
+ int unused = 0;
+ // as far as I know these functions are const, they're just not marked as such
+ Path::cut_position *path_parameter_list = const_cast<Path*>(_path_fitted)->CurvilignToPosition(1, &x_on_path, unused);
+ Path::cut_position path_parameter;
+ if (path_parameter_list != nullptr && path_parameter_list[0].piece >= 0)
+ path_parameter = path_parameter_list[0];
+ else {
+ path_parameter.piece = _path_fitted->descr_cmd.size() - 1;
+ path_parameter.t = 0.9999; // 1.0 will get the wrong tangent
+ }
+ g_free(path_parameter_list);
+
+ Geom::Point point;
+ Geom::Point tangent;
+ const_cast<Path*>(_path_fitted)->PointAndTangentAt(path_parameter.piece, path_parameter.t, point, tangent);
+ if (x < 0.0)
+ point += x * tangent;
+ if (x > path_length )
+ point += (x - path_length) * tangent;
+ if (vertical_text) {
+ rotation = atan2(-tangent[Geom::X], tangent[Geom::Y]);
+ position[Geom::X] = point[Geom::Y] - tangent[Geom::X] * span->baseline_shift;
+ position[Geom::Y] = point[Geom::X] + tangent[Geom::Y] * span->baseline_shift;
+ } else {
+ rotation = atan2(tangent);
+ position[Geom::X] = point[Geom::X] - tangent[Geom::Y] * span->baseline_shift;
+ position[Geom::Y] = point[Geom::Y] + tangent[Geom::X] * span->baseline_shift;
+ }
+
+ } else {
+ // text is not on a path
+
+ bool last_char_is_newline = false;
+ if (it._char_index >= _characters.size()) {
+ span = &_spans.back();
+ position[Geom::X] = _chunks[span->in_chunk].left_x + span->x_end;
+ rotation = _glyphs.empty() ? 0.0 : _glyphs.back().rotation;
+
+ // Check if last character is new line.
+ if (_characters.back().the_char == '\n') {
+ last_char_is_newline = true;
+ position[Geom::X] = chunkAnchorPoint(it)[vertical_text ? Geom::Y : Geom::X];
+ }
+ } else {
+ span = &_spans[_characters[it._char_index].in_span];
+ position[Geom::X] = _chunks[span->in_chunk].left_x + span->x_start + _characters[it._char_index].x;
+ if (it._glyph_index == -1) {
+ rotation = 0.0;
+ } else if(it._glyph_index == 0) {
+ rotation = _glyphs.empty() ? 0.0 : _glyphs[0].rotation;
+ } else{
+ rotation = _glyphs[it._glyph_index - 1].rotation;
+ }
+ // the first char in a line wants to have the y of the new line, so in that case we don't switch to the previous span
+ if (it._char_index != 0 && _characters[it._char_index - 1].chunk(this).in_line == _chunks[span->in_chunk].in_line)
+ span = &_spans[_characters[it._char_index - 1].in_span];
+ }
+ position[Geom::Y] = span->line(this).baseline_y + span->baseline_shift + span->y_offset;
+
+ if (last_char_is_newline) {
+ // Move cursor to empty new line.
+ double vertical_scale = _glyphs.empty() ? 1.0 : _glyphs.back().vertical_scale;
+ if (vertical_text) {
+ // Vertical text
+ position[Geom::Y] -= vertical_scale * span->line_height.emSize();
+ } else {
+ position[Geom::Y] += vertical_scale * span->line_height.emSize();
+ }
+ }
+ }
+
+ // up to now *position is the baseline point, not the final point which will be the bottom of the descent
+ double vertical_scale = _glyphs.empty() ? 1.0 : _glyphs.back().vertical_scale;
+
+ if (vertical_text) {
+ // Vertical text
+ height = vertical_scale * span->line_height.emSize();
+ rotation += M_PI / 2;
+ std::swap(position[Geom::X], position[Geom::Y]);
+ position[Geom::X] -= vertical_scale * sin(rotation) * height * 0.5;
+ position[Geom::Y] += vertical_scale * cos(rotation) * height * 0.5;
+ } else {
+ // Horizontal text
+ double caret_slope_run = 0.0, caret_slope_rise = 1.0;
+ if (span->font)
+ const_cast<font_instance*>(span->font)->FontSlope(caret_slope_run, caret_slope_rise);
+ double caret_slope = atan2(caret_slope_run, caret_slope_rise);
+ height = vertical_scale * (span->line_height.emSize()) / cos(caret_slope);
+ rotation += caret_slope;
+ position[Geom::X] -= sin(rotation) * vertical_scale * span->line_height.descent;
+ position[Geom::Y] += cos(rotation) * vertical_scale * span->line_height.descent;
+ }
+ }
+}
+
+bool Layout::isHidden(iterator const &it) const
+{
+ return _characters[it._char_index].line(this).hidden;
+}
+
+
+void Layout::getSourceOfCharacter(iterator const &it, SPObject **source, Glib::ustring::iterator *text_iterator) const
+{
+ if (it._char_index >= _characters.size()) {
+ *source = nullptr;
+ return;
+ }
+ InputStreamItem *stream_item = _input_stream[_spans[_characters[it._char_index].in_span].in_input_stream_item];
+ *source = stream_item->source;
+ if (text_iterator && stream_item->Type() == TEXT_SOURCE) {
+ InputStreamTextSource *text_source = dynamic_cast<InputStreamTextSource *>(stream_item);
+
+ // In order to return a non-const iterator in text_iterator, do the const_cast here.
+ // Note that, although ugly, it is safe because we do not write to *iterator anywhere.
+ Glib::ustring::iterator text_iter = const_cast<Glib::ustring *>(text_source->text)->begin();
+
+ unsigned char_index = it._char_index;
+ unsigned original_input_source_index = _spans[_characters[char_index].in_span].in_input_stream_item;
+ // confusing algorithm because the iterator goes forwards while the index goes backwards.
+ // It's just that it's faster doing it that way
+ while (char_index && _spans[_characters[char_index - 1].in_span].in_input_stream_item == original_input_source_index) {
+ ++text_iter;
+ char_index--;
+ }
+
+ if (text_iterator) {
+ *text_iterator = text_iter;
+ }
+ }
+}
+
+void Layout::simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const
+{
+ SVGLength zero_length;
+ zero_length = 0.0;
+
+ result->x.clear();
+ result->y.clear();
+ result->dx.clear();
+ result->dy.clear();
+ result->rotate.clear();
+ if (to._char_index <= from._char_index)
+ return;
+ result->dx.reserve(to._char_index - from._char_index);
+ result->dy.reserve(to._char_index - from._char_index);
+ result->rotate.reserve(to._char_index - from._char_index);
+ for (unsigned char_index = from._char_index ; char_index < to._char_index ; char_index++) {
+ if (!_characters[char_index].char_attributes.is_char_break)
+ continue;
+ if (char_index == 0)
+ continue;
+ if (_characters[char_index].chunk(this).in_line != _characters[char_index - 1].chunk(this).in_line)
+ continue;
+
+ unsigned prev_cluster_char_index;
+ for (prev_cluster_char_index = char_index - 1 ;
+ prev_cluster_char_index != 0 && !_characters[prev_cluster_char_index].char_attributes.is_cursor_position ;
+ prev_cluster_char_index--){};
+ if (_characters[char_index].span(this).in_chunk == _characters[char_index - 1].span(this).in_chunk) {
+ // dx is zero for the first char in a chunk
+ // this algorithm works by comparing the summed widths of the glyphs with the observed
+ // difference in x coordinates of characters, and subtracting the two to produce the x kerning.
+ double glyphs_width = 0.0;
+ if (_characters[prev_cluster_char_index].in_glyph != -1)
+ for (int glyph_index = _characters[prev_cluster_char_index].in_glyph ; glyph_index < _characters[char_index].in_glyph ; glyph_index++)
+ glyphs_width += _glyphs[glyph_index].advance;
+ if (_characters[char_index].span(this).direction == RIGHT_TO_LEFT)
+ glyphs_width = -glyphs_width;
+
+ double dx = (_characters[char_index].x + _characters[char_index].span(this).x_start
+ - _characters[prev_cluster_char_index].x - _characters[prev_cluster_char_index].span(this).x_start)
+ - glyphs_width;
+
+
+ InputStreamItem *input_item = _input_stream[_characters[char_index].span(this).in_input_stream_item];
+ if (input_item->Type() == TEXT_SOURCE) {
+ SPStyle const *style = static_cast<InputStreamTextSource*>(input_item)->style;
+ if (_characters[char_index].char_attributes.is_white)
+ dx -= style->word_spacing.computed * getTextLengthMultiplierDue();
+ if (_characters[char_index].char_attributes.is_cursor_position)
+ dx -= style->letter_spacing.computed * getTextLengthMultiplierDue();
+ dx -= getTextLengthIncrementDue();
+ }
+
+ if (fabs(dx) > 0.0001) {
+ result->dx.resize(char_index - from._char_index + 1, zero_length);
+ result->dx.back() = dx;
+ }
+ }
+ double dy = _characters[char_index].span(this).baseline_shift - _characters[prev_cluster_char_index].span(this).baseline_shift;
+ if (fabs(dy) > 0.0001) {
+ result->dy.resize(char_index - from._char_index + 1, zero_length);
+ result->dy.back() = dy;
+ }
+ if (_characters[char_index].in_glyph != -1 && _glyphs[_characters[char_index].in_glyph].rotation != 0.0) {
+ result->rotate.resize(char_index - from._char_index + 1, zero_length);
+ result->rotate.back() = _glyphs[_characters[char_index].in_glyph].rotation;
+ }
+ }
+}
+
+#define PREV_START_OF_ITEM(this_func) \
+ { \
+ _cursor_moving_vertically = false; \
+ if (_char_index == 0) return false; \
+ _char_index--; \
+ return this_func(); \
+ }
+// end of macro
+
+#define THIS_START_OF_ITEM(item_getter) \
+ { \
+ _cursor_moving_vertically = false; \
+ if (_char_index == 0) return false; \
+ unsigned original_item; \
+ if (_char_index == _parent_layout->_characters.size()) { \
+ _char_index--; \
+ original_item = item_getter; \
+ } else { \
+ original_item = item_getter; \
+ _char_index--; \
+ } \
+ while (item_getter == original_item) { \
+ if (_char_index == 0) { \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ } \
+ _char_index--; \
+ } \
+ _char_index++; \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+#define NEXT_START_OF_ITEM(item_getter) \
+ { \
+ _cursor_moving_vertically = false; \
+ if (_char_index == _parent_layout->_characters.size()) return false; \
+ unsigned original_item = item_getter; \
+ for( ; ; ) { \
+ _char_index++; \
+ if (_char_index == _parent_layout->_characters.size()) { \
+ _glyph_index = _parent_layout->_glyphs.size(); \
+ return false; \
+ } \
+ if (item_getter != original_item) break; \
+ } \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+bool Layout::iterator::prevStartOfSpan()
+ PREV_START_OF_ITEM(thisStartOfSpan);
+
+bool Layout::iterator::thisStartOfSpan()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
+
+bool Layout::iterator::nextStartOfSpan()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].in_span);
+
+
+bool Layout::iterator::prevStartOfChunk()
+ PREV_START_OF_ITEM(thisStartOfChunk);
+
+bool Layout::iterator::thisStartOfChunk()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
+
+bool Layout::iterator::nextStartOfChunk()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_chunk);
+
+
+bool Layout::iterator::prevStartOfLine()
+ PREV_START_OF_ITEM(thisStartOfLine);
+
+bool Layout::iterator::thisStartOfLine()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
+
+bool Layout::iterator::nextStartOfLine()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].chunk(_parent_layout).in_line);
+
+
+bool Layout::iterator::prevStartOfShape()
+ PREV_START_OF_ITEM(thisStartOfShape);
+
+bool Layout::iterator::thisStartOfShape()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
+
+bool Layout::iterator::nextStartOfShape()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_shape);
+
+
+bool Layout::iterator::prevStartOfParagraph()
+ PREV_START_OF_ITEM(thisStartOfParagraph);
+
+bool Layout::iterator::thisStartOfParagraph()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
+
+bool Layout::iterator::nextStartOfParagraph()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].line(_parent_layout).in_paragraph);
+
+
+bool Layout::iterator::prevStartOfSource()
+ PREV_START_OF_ITEM(thisStartOfSource);
+
+bool Layout::iterator::thisStartOfSource()
+ THIS_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
+
+bool Layout::iterator::nextStartOfSource()
+ NEXT_START_OF_ITEM(_parent_layout->_characters[_char_index].span(_parent_layout).in_input_stream_item);
+
+
+bool Layout::iterator::thisEndOfLine()
+{
+ if (_char_index == _parent_layout->_characters.size()) return false;
+ if (nextStartOfLine())
+ {
+ if (_char_index && _parent_layout->_characters[_char_index - 1].char_attributes.is_white)
+ return prevCursorPosition();
+ return true;
+ }
+ if (_char_index && _parent_layout->_characters[_char_index - 1].chunk(_parent_layout).in_line != _parent_layout->_lines.size() - 1)
+ return prevCursorPosition(); // for when the last paragraph is empty
+ return false;
+}
+
+void Layout::iterator::beginCursorUpDown()
+{
+ if (_char_index == _parent_layout->_characters.size())
+ _x_coordinate = _parent_layout->_chunks.back().left_x + _parent_layout->_spans.back().x_end;
+ else
+ _x_coordinate = _parent_layout->_characters[_char_index].x + _parent_layout->_characters[_char_index].span(_parent_layout).x_start + _parent_layout->_characters[_char_index].chunk(_parent_layout).left_x;
+ _cursor_moving_vertically = true;
+}
+
+bool Layout::iterator::nextLineCursor(int n)
+{
+ if (!_cursor_moving_vertically)
+ beginCursorUpDown();
+ if (_char_index == _parent_layout->_characters.size())
+ return false;
+ unsigned line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
+ if (line_index == _parent_layout->_lines.size() - 1)
+ return false; // nowhere to go
+ else
+ n = MIN (n, static_cast<int>(_parent_layout->_lines.size() - 1 - line_index));
+ if (_parent_layout->_lines[line_index + n].in_shape != _parent_layout->_lines[line_index].in_shape) {
+ // switching between shapes: adjust the stored x to compensate
+ _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index + n)].in_chunk].left_x
+ - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
+ }
+ _char_index = _parent_layout->_cursorXOnLineToIterator(line_index + n, _x_coordinate)._char_index;
+ if (_char_index == _parent_layout->_characters.size())
+ _glyph_index = _parent_layout->_glyphs.size();
+ else
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
+ return true;
+}
+
+bool Layout::iterator::prevLineCursor(int n)
+{
+ if (!_cursor_moving_vertically)
+ beginCursorUpDown();
+ int line_index;
+ if (_char_index == _parent_layout->_characters.size())
+ line_index = _parent_layout->_lines.size() - 1;
+ else
+ line_index = _parent_layout->_characters[_char_index].chunk(_parent_layout).in_line;
+ if (line_index <= 0)
+ return false; // nowhere to go
+ else
+ n = MIN (n, static_cast<int>(line_index));
+ if (_parent_layout->_lines[line_index - n].in_shape != _parent_layout->_lines[line_index].in_shape) {
+ // switching between shapes: adjust the stored x to compensate
+ _x_coordinate += _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index - n)].in_chunk].left_x
+ - _parent_layout->_chunks[_parent_layout->_spans[_parent_layout->_lineToSpan(line_index)].in_chunk].left_x;
+ }
+ _char_index = _parent_layout->_cursorXOnLineToIterator(line_index - n, _x_coordinate)._char_index;
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
+ return true;
+}
+
+#define NEXT_WITH_ATTRIBUTE_SET(attr) \
+ { \
+ _cursor_moving_vertically = false; \
+ for ( ; ; ) { \
+ if (_char_index + 1 >= _parent_layout->_characters.size()) { \
+ _char_index = _parent_layout->_characters.size(); \
+ _glyph_index = _parent_layout->_glyphs.size(); \
+ return false; \
+ } \
+ _char_index++; \
+ if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
+ } \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+#define PREV_WITH_ATTRIBUTE_SET(attr) \
+ { \
+ _cursor_moving_vertically = false; \
+ for ( ; ; ) { \
+ if (_char_index == 0) { \
+ _glyph_index = 0; \
+ return false; \
+ } \
+ _char_index--; \
+ if (_parent_layout->_characters[_char_index].char_attributes.attr) break; \
+ } \
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph; \
+ return true; \
+ }
+// end of macro
+
+bool Layout::iterator::nextCursorPosition()
+ NEXT_WITH_ATTRIBUTE_SET(is_cursor_position);
+
+bool Layout::iterator::prevCursorPosition()
+ PREV_WITH_ATTRIBUTE_SET(is_cursor_position);
+
+bool Layout::iterator::nextStartOfWord()
+ NEXT_WITH_ATTRIBUTE_SET(is_word_start);
+
+bool Layout::iterator::prevStartOfWord()
+ PREV_WITH_ATTRIBUTE_SET(is_word_start);
+
+bool Layout::iterator::nextEndOfWord()
+ NEXT_WITH_ATTRIBUTE_SET(is_word_end);
+
+bool Layout::iterator::prevEndOfWord()
+ PREV_WITH_ATTRIBUTE_SET(is_word_end);
+
+bool Layout::iterator::nextStartOfSentence()
+ NEXT_WITH_ATTRIBUTE_SET(is_sentence_start);
+
+bool Layout::iterator::prevStartOfSentence()
+ PREV_WITH_ATTRIBUTE_SET(is_sentence_start);
+
+bool Layout::iterator::nextEndOfSentence()
+ NEXT_WITH_ATTRIBUTE_SET(is_sentence_end);
+
+bool Layout::iterator::prevEndOfSentence()
+ PREV_WITH_ATTRIBUTE_SET(is_sentence_end);
+
+bool Layout::iterator::_cursorLeftOrRightLocalX(Direction direction)
+{
+ // the only reason this function is so complicated is to enable visual cursor
+ // movement moving in to or out of counterdirectional runs
+ if (_parent_layout->_characters.empty()) return false;
+ unsigned old_span_index;
+ Direction old_span_direction;
+ if (_char_index == _parent_layout->_characters.size())
+ old_span_index = _parent_layout->_spans.size() - 1;
+ else
+ old_span_index = _parent_layout->_characters[_char_index].in_span;
+ old_span_direction = _parent_layout->_spans[old_span_index].direction;
+ Direction para_direction = _parent_layout->_spans[old_span_index].paragraph(_parent_layout).base_direction;
+
+ int scan_direction;
+ unsigned old_char_index = _char_index;
+ if (old_span_direction != para_direction
+ && ((_char_index == 0 && direction == para_direction)
+ || (_char_index == _parent_layout->_characters.size() && direction != para_direction))) {
+ // the end of the text is actually in the middle because of reordering. Do cleverness
+ scan_direction = direction == para_direction ? +1 : -1;
+ } else {
+ if (direction == old_span_direction) {
+ if (!nextCursorPosition()) return false;
+ } else {
+ if (!prevCursorPosition()) return false;
+ }
+
+ unsigned new_span_index = _parent_layout->_characters[_char_index].in_span;
+ if (new_span_index == old_span_index) return true;
+ if (old_span_direction != _parent_layout->_spans[new_span_index].direction) {
+ // we must jump to the other end of a counterdirectional run
+ scan_direction = direction == para_direction ? +1 : -1;
+ } else if (_parent_layout->_spans[old_span_index].in_chunk != _parent_layout->_spans[new_span_index].in_chunk) {
+ // we might have to do a weird jump when we would have crossed a chunk/line break
+ if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph)
+ return true;
+ if (old_span_direction == para_direction)
+ return true;
+ scan_direction = direction == para_direction ? +1 : -1;
+ } else
+ return true; // same direction, same chunk: no cleverness required
+ }
+
+ unsigned new_span_index = old_span_index;
+ for ( ; ; ) {
+ if (scan_direction > 0) {
+ if (new_span_index == _parent_layout->_spans.size() - 1) {
+ if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
+ _char_index = old_char_index;
+ return false; // the visual end is in the logical middle
+ }
+ break;
+ }
+ new_span_index++;
+ } else {
+ if (new_span_index == 0) {
+ if (_parent_layout->_spans[new_span_index].direction == old_span_direction) {
+ _char_index = old_char_index;
+ return false; // the visual end is in the logical middle
+ }
+ break;
+ }
+ new_span_index--;
+ }
+ if (_parent_layout->_spans[new_span_index].direction == para_direction) {
+ if (para_direction == old_span_direction)
+ new_span_index -= scan_direction;
+ break;
+ }
+ if (_parent_layout->_spans[new_span_index].in_chunk != _parent_layout->_spans[old_span_index].in_chunk) {
+ if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph == _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph
+ && para_direction == old_span_direction)
+ new_span_index -= scan_direction;
+ break;
+ }
+ }
+
+ // found the correct span, now find the correct character
+ if (_parent_layout->_spans[old_span_index].line(_parent_layout).in_paragraph != _parent_layout->_spans[new_span_index].line(_parent_layout).in_paragraph) {
+ if (new_span_index > old_span_index)
+ _char_index = _parent_layout->_spanToCharacter(new_span_index);
+ else
+ _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
+ } else {
+ if (_parent_layout->_spans[new_span_index].direction != direction) {
+ if (new_span_index >= _parent_layout->_spans.size() - 1)
+ _char_index = _parent_layout->_characters.size();
+ else
+ _char_index = _parent_layout->_spanToCharacter(new_span_index + 1) - 1;
+ } else
+ _char_index = _parent_layout->_spanToCharacter(new_span_index);
+ }
+ if (_char_index == _parent_layout->_characters.size()) {
+ _glyph_index = _parent_layout->_glyphs.size();
+ return false;
+ }
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
+ return _char_index != 0;
+}
+
+bool Layout::iterator::_cursorLeftOrRightLocalXByWord(Direction direction)
+{
+ bool r;
+ while ((r = _cursorLeftOrRightLocalX(direction))
+ && !_parent_layout->_characters[_char_index].char_attributes.is_word_start){};
+ return r;
+}
+
+bool Layout::iterator::cursorUp(int n)
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return prevLineCursor(n);
+ else if(block_progression == BOTTOM_TO_TOP)
+ return nextLineCursor(n);
+ else
+ return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorDown(int n)
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return nextLineCursor(n);
+ else if(block_progression == BOTTOM_TO_TOP)
+ return prevLineCursor(n);
+ else
+ return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
+}
+
+bool Layout::iterator::cursorLeft()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return prevLineCursor();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return nextLineCursor();
+ else
+ return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorRight()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return nextLineCursor();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return prevLineCursor();
+ else
+ return _cursorLeftOrRightLocalX(LEFT_TO_RIGHT);
+}
+
+bool Layout::iterator::cursorUpWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return prevStartOfParagraph();
+ else if(block_progression == BOTTOM_TO_TOP)
+ return nextStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorDownWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return nextStartOfParagraph();
+ else if(block_progression == BOTTOM_TO_TOP)
+ return prevStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
+}
+
+bool Layout::iterator::cursorLeftWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return prevStartOfParagraph();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return nextStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorRightWithControl()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == LEFT_TO_RIGHT)
+ return nextStartOfParagraph();
+ else if(block_progression == RIGHT_TO_LEFT)
+ return prevStartOfParagraph();
+ else
+ return _cursorLeftOrRightLocalXByWord(LEFT_TO_RIGHT);
+}
+
+}//namespace Text
+}//namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp
new file mode 100644
index 0000000..66a1516
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Output.cpp
@@ -0,0 +1,910 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::Text::Layout - text layout engine output functions
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib.h>
+#include "Layout-TNG.h"
+#include "display/drawing-text.h"
+#include "style.h"
+#include "print.h"
+#include "extension/print.h"
+#include "livarot/Path.h"
+#include "font-instance.h"
+#include "svg/svg-length.h"
+#include "extension/internal/cairo-render-context.h"
+#include "display/curve.h"
+#include <2geom/pathvector.h>
+#include <3rdparty/libuemf/symbol_convert.h>
+
+
+using Inkscape::Extension::Internal::CairoRenderContext;
+using Inkscape::Extension::Internal::CairoGlyphInfo;
+
+namespace Inkscape {
+namespace Text {
+
+/*
+ dx array (character widths) and
+ ky (vertical kerning for entire span)
+ rtl (+1 for LTR, -1 RTL)
+
+ are smuggled through to the EMF (ignored by others) as:
+ text<nul>N w1 w2 w3 ...wN<nul>y1 y2 y3 .. yN<nul><nul>
+ The ndx, widths, y kern, and rtl are all 7 characters wide. ndx and rtl are ints, the widths and ky are
+ formatted as ' 6f'.
+*/
+char *smuggle_adxkyrtl_in(const char *string, int ndx, float *adx, float ky, float rtl){
+ int slen = strlen(string);
+ /* holds: string
+ fake terminator (one \0)
+ Number of widths (ndx)
+ series of widths (ndx entries)
+ fake terminator (one \0)
+ y kern value (one float)
+ rtl value (one float)
+ real terminator (two \0)
+ */
+ int newsize=slen + 1 + 7 + 7*ndx + 1 + 7 + 7 + 2;
+ newsize = 8*((7 + newsize)/8); // suppress valgrind messages if it is a multiple of 8 bytes???
+ char *smuggle=(char *)malloc(newsize);
+ strcpy(smuggle,string); // text to pass, includes the first fake terminator
+ char *cptr = smuggle + slen + 1; // immediately after the first fake terminator
+ sprintf(cptr,"%07d",ndx); // number of widths to pass
+ cptr+=7; // advance over ndx
+ for(int i=0; i<ndx ; i++){ // all the widths
+ sprintf(cptr," %6f",adx[i]);
+ cptr+=7; // advance over space + width
+ }
+ *cptr='\0';
+ cptr++; // second fake terminator
+ sprintf(cptr," %6f",ky); // y kern for span
+ cptr+=7; // advance over space + ky
+ sprintf(cptr," %6d",(int) rtl); // rtl multiplier for span
+ cptr+=7; // advance over rtl
+ *cptr++ = '\0'; // Set the real terminators
+ *cptr = '\0';
+ return(smuggle);
+}
+
+void Layout::_clearOutputObjects()
+{
+ _paragraphs.clear();
+ _lines.clear();
+ _chunks.clear();
+ for (auto & _span : _spans)
+ if (_span.font) _span.font->Unref();
+ _spans.clear();
+ _characters.clear();
+ _glyphs.clear();
+ _path_fitted = nullptr;
+}
+
+void Layout::FontMetrics::set(font_instance *font)
+{
+ if( font != nullptr ) {
+ ascent = font->GetTypoAscent();
+ descent = font->GetTypoDescent();
+ xheight = font->GetXHeight();
+ ascent_max = font->GetMaxAscent();
+ descent_max = font->GetMaxDescent();
+ }
+}
+
+void Layout::FontMetrics::max(FontMetrics const &other)
+{
+ if (other.ascent > ascent ) ascent = other.ascent;
+ if (other.descent > descent ) descent = other.descent;
+ if( other.xheight > xheight ) xheight = other.xheight;
+ if( other.ascent_max > ascent_max ) ascent_max = other.ascent_max;
+ if( other.descent_max > descent_max ) descent_max = other.descent_max;
+}
+
+void Layout::FontMetrics::computeEffective( const double &line_height_multiplier ) {
+ double half_leading = 0.5 * (line_height_multiplier - 1.0) * emSize();
+ ascent += half_leading;
+ descent += half_leading;
+}
+
+void Layout::_getGlyphTransformMatrix(int glyph_index, Geom::Affine *matrix) const
+{
+ Span const &span = _glyphs[glyph_index].span(this);
+ double rotation = _glyphs[glyph_index].rotation;
+ if ( (span.block_progression == LEFT_TO_RIGHT || span.block_progression == RIGHT_TO_LEFT) &&
+ _glyphs[glyph_index].orientation == ORIENTATION_SIDEWAYS ) {
+ // Vertical sideways text
+ rotation += M_PI/2.0;
+ }
+ double sin_rotation = sin(rotation);
+ double cos_rotation = cos(rotation);
+ (*matrix)[0] = span.font_size * cos_rotation;
+ (*matrix)[1] = span.font_size * sin_rotation;
+ (*matrix)[2] = span.font_size * sin_rotation;
+ (*matrix)[3] = -span.font_size * cos_rotation * (_glyphs[glyph_index].vertical_scale); // unscale vertically so the specified text height is preserved if lengthAdjust=spacingAndGlyphs
+ if (span.block_progression == LEFT_TO_RIGHT || span.block_progression == RIGHT_TO_LEFT) {
+ // Vertical text
+ // This effectively swaps x for y which changes handedness of coordinate system. This is a bit strange
+ // and not what one would expect but the compute code already reverses y so OK.
+ (*matrix)[4] = _lines[_chunks[span.in_chunk].in_line].baseline_y + _glyphs[glyph_index].y;
+ (*matrix)[5] = _chunks[span.in_chunk].left_x + _glyphs[glyph_index].x;
+ } else {
+ // Horizontal text
+ (*matrix)[4] = _chunks[span.in_chunk].left_x + _glyphs[glyph_index].x;
+ (*matrix)[5] = _lines[_chunks[span.in_chunk].in_line].baseline_y + _glyphs[glyph_index].y;
+ }
+}
+
+void Layout::show(DrawingGroup *in_arena, Geom::OptRect const &paintbox) const
+{
+ int glyph_index = 0;
+ double phase0 = 0.0;
+ for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
+ if (_input_stream[_spans[span_index].in_input_stream_item]->Type() != TEXT_SOURCE) continue;
+
+ if (_spans[span_index].line(this).hidden) continue; // Line corresponds to text overflow. Don't show!
+
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[_spans[span_index].in_input_stream_item]);
+
+ text_source->style->text_decoration_data.tspan_width = _spans[span_index].width();
+ text_source->style->text_decoration_data.ascender = _spans[span_index].line_height.getTypoAscent();
+ text_source->style->text_decoration_data.descender = _spans[span_index].line_height.getTypoDescent();
+
+ if(!span_index ||
+ (_chunks[_spans[span_index].in_chunk].in_line != _chunks[_spans[span_index-1].in_chunk].in_line)){
+ text_source->style->text_decoration_data.tspan_line_start = true;
+ }
+ else {
+ text_source->style->text_decoration_data.tspan_line_start = false;
+ }
+ if((span_index == _spans.size() -1) ||
+ (_chunks[_spans[span_index].in_chunk].in_line != _chunks[_spans[span_index+1].in_chunk].in_line)){
+ text_source->style->text_decoration_data.tspan_line_end = true;
+ }
+ else {
+ text_source->style->text_decoration_data.tspan_line_end = false;
+ }
+ if(_spans[span_index].font){
+ double underline_thickness, underline_position, line_through_thickness,line_through_position;
+ _spans[span_index].font->FontDecoration(underline_position, underline_thickness, line_through_position, line_through_thickness);
+ text_source->style->text_decoration_data.underline_thickness = underline_thickness;
+ text_source->style->text_decoration_data.underline_position = underline_position;
+ text_source->style->text_decoration_data.line_through_thickness = line_through_thickness;
+ text_source->style->text_decoration_data.line_through_position = line_through_position;
+ }
+ else { // can this case ever occur?
+ text_source->style->text_decoration_data.underline_thickness =
+ text_source->style->text_decoration_data.underline_position =
+ text_source->style->text_decoration_data.line_through_thickness =
+ text_source->style->text_decoration_data.line_through_position = 0.0;
+ }
+
+ DrawingText *nr_text = new DrawingText(in_arena->drawing());
+
+ bool first_line_glyph = true;
+ while (glyph_index < (int)_glyphs.size() && _characters[_glyphs[glyph_index].in_character].in_span == span_index) {
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph != -1) {
+ Geom::Affine glyph_matrix;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ if(first_line_glyph && text_source->style->text_decoration_data.tspan_line_start){
+ first_line_glyph = false;
+ phase0 = glyph_matrix.translation()[Geom::X];
+ }
+ // Save the starting coordinates for the line - these are needed for figuring out
+ // dot/dash/wave phase.
+ // Use maximum ascent and descent to ensure glyphs that extend outside the embox
+ // are fully drawn.
+ (void) nr_text->addComponent(_spans[span_index].font, _glyphs[glyph_index].glyph, glyph_matrix,
+ _glyphs[glyph_index].advance,
+ _spans[span_index].line_height.getMaxAscent(),
+ _spans[span_index].line_height.getMaxDescent(),
+ glyph_matrix.translation()[Geom::X] - phase0
+ );
+ }
+ glyph_index++;
+ }
+ nr_text->setStyle(text_source->style);
+ nr_text->setItemBounds(paintbox);
+ // Text spans must be painted in the right order (see inkscape/685)
+ in_arena->appendChild(nr_text);
+ // Set item bounds without filter enlargement
+ in_arena->setItemBounds(paintbox);
+ }
+}
+
+Geom::OptRect Layout::bounds(Geom::Affine const &transform, bool with_stroke, int start, int length) const
+{
+ Geom::OptRect bbox;
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
+ if (_glyphs[glyph_index].hidden) continue; // To do: This and the next line should represent the same thing, use on or the other.
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) continue;
+ if (start != -1 && (int) _glyphs[glyph_index].in_character < start) continue;
+ if (length != -1) {
+ if (start == -1)
+ start = 0;
+ if ((int) _glyphs[glyph_index].in_character > start + length) continue;
+ }
+ // this could be faster
+ Geom::Affine glyph_matrix;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ Geom::Affine total_transform = glyph_matrix;
+ total_transform *= transform;
+ if(_glyphs[glyph_index].span(this).font) {
+ Geom::OptRect glyph_rect = _glyphs[glyph_index].span(this).font->BBox(_glyphs[glyph_index].glyph);
+ if (glyph_rect) {
+ auto glyph_box = *glyph_rect * total_transform;
+ // FIXME: Expand rectangle by half stroke width, this doesn't include meters
+ // and so is not the most ideal calculation, we could use the glyph Path here.
+ if (with_stroke) {
+ Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span];
+ auto text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
+ if (!text_source->style->stroke.isNone()) {
+ double scale = transform.descrim();
+ glyph_box.expandBy(0.5 * text_source->style->stroke_width.computed * scale);
+ }
+ }
+ bbox.unionWith(glyph_box);
+ }
+ }
+ }
+ return bbox;
+}
+
+/* This version is much simpler than the old one
+*/
+void Layout::print(SPPrintContext *ctx,
+ Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox,
+ Geom::Affine const &ctm) const
+{
+bool text_to_path = ctx->module->textToPath();
+#define MAX_DX 2048
+float hold_dx[MAX_DX]; // For smuggling dx values (character widths) into print functions, unlikely any simple text output will be longer than this.
+
+Geom::Affine glyph_matrix;
+
+ if (_input_stream.empty()) return;
+ if (!_glyphs.size()) return; // yes, this can happen.
+ if (text_to_path || _path_fitted) {
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1)continue; //invisible glyphs
+ Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span];
+ Geom::PathVector const * pv = span.font->PathVector(_glyphs[glyph_index].glyph);
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
+ if (pv) {
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ Geom::PathVector temp_pv = (*pv) * glyph_matrix;
+ if (!text_source->style->fill.isNone())
+ ctx->fill(temp_pv, ctm, text_source->style, pbox, dbox, bbox);
+ if (!text_source->style->stroke.isNone())
+ ctx->stroke(temp_pv, ctm, text_source->style, pbox, dbox, bbox);
+ }
+ }
+ }
+ else {
+ /* index by characters, referencing glyphs and spans only as needed */
+ double char_x;
+ int doUTN = CanUTN(); // Unicode to Nonunicode translation enabled if true
+ Direction block_progression = _blockProgression();
+ int oldtarget = 0;
+ int ndx = 0;
+ double rtl = 1.0; // 1 L->R, -1 R->L, constant across a span. 1.0 for t->b b->t???
+
+ for (unsigned char_index = 0 ; char_index < _characters.size() ; ) {
+ Glib::ustring text_string; // accumulate text for record in this
+ Geom::Point g_pos(0,0); // all strings are output at (0,0) because we do the translation using the matrix
+ int glyph_index = _characters[char_index].in_glyph;
+ if(glyph_index == -1){ // if the character maps to an invisible glyph we cannot know its geometry, so skip it and move on
+ char_index++;
+ continue;
+ }
+ float ky = _glyphs[glyph_index].y; // For smuggling y kern value for span // same value for all positions in a span
+ unsigned span_index = _characters[char_index].in_span;
+ Span const &span = _spans[span_index];
+ char_x = 0.0;
+ Glib::ustring::const_iterator text_iter = span.input_stream_first_character;
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
+ glyph_matrix = Geom::Scale(1.0, -1.0) * (Geom::Affine)Geom::Rotate(_glyphs[glyph_index].rotation);
+ if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) {
+ glyph_matrix[4] = span.line(this).baseline_y + span.baseline_shift;
+ // since we're outputting character codes, not glyphs, we want the character x
+ glyph_matrix[5] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x;
+ } else {
+ glyph_matrix[4] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x;
+ glyph_matrix[5] = span.line(this).baseline_y + span.baseline_shift;
+ }
+ switch(span.direction){
+ case Layout::TOP_TO_BOTTOM:
+ case Layout::BOTTOM_TO_TOP:
+ case Layout::LEFT_TO_RIGHT: rtl = 1.0; break;
+ case Layout::RIGHT_TO_LEFT: rtl = -1.0; break;
+ }
+ if(doUTN){
+ oldtarget=SingleUnicodeToNon(*text_iter); // this should only ever be with a 1:1 glyph:character situation
+ }
+
+ // accumulate a record to write
+
+ unsigned lc_index = char_index;
+ unsigned hold_iisi = _spans[span_index].in_input_stream_item;
+ int newtarget = 0;
+ while(true){
+ glyph_index = _characters[lc_index].in_glyph;
+ if(glyph_index == -1){ // end of a line within a paragraph, for instance
+ lc_index++;
+ break;
+ }
+
+ // always append if here
+ text_string += *text_iter;
+
+ // figure out char widths, used by EMF, not currently used elsewhere
+ double cwidth;
+ if(lc_index == _glyphs[glyph_index].in_character){ // Glyph width is used only for the first character, these may be 0
+ cwidth = rtl * _glyphs[glyph_index].advance; // advance might be zero
+ }
+ else {
+ cwidth = 0;
+ }
+ char_x += cwidth;
+/*
+std:: cout << "DEBUG Layout::print in while "
+<< " char_index " << char_index
+<< " lc_index " << lc_index
+<< " character " << std::hex << (int) *text_iter << std::dec
+<< " glyph_index " << glyph_index
+<< " glyph_xy " << _glyphs[glyph_index].x << " , " << _glyphs[glyph_index].y
+<< " span_index " << span_index
+<< " hold_iisi " << hold_iisi
+<< std::endl; //DEBUG
+*/
+ if(ndx < MAX_DX){
+ hold_dx[ndx++] = fabs(cwidth);
+ }
+ else { // silently truncate any text line silly enough to be longer than MAX_DX
+ lc_index = _characters.size();
+ break;
+ }
+
+
+ // conditions that prevent this character from joining the record
+ lc_index++;
+ if(lc_index >= _characters.size()) break; // nothing more to process, so it must be the end of the record
+ ++text_iter;
+ if(doUTN)newtarget=SingleUnicodeToNon(*text_iter); // this should only ever be with a 1:1 glyph:character situation
+ if(newtarget != oldtarget)break; // change in unicode to nonunicode translation status
+ // MUST exit on any major span change, but not on some little events, like a font substitution event irrelevant for the file save
+ unsigned next_span_index = _characters[lc_index].in_span;
+ if(span_index != next_span_index){
+ /* on major changes break out of loop.
+ 1st case usually indicates an entire input line has been processed (out of several in a paragraph)
+ 2nd case usually indicates that a format change within a line (font/size/color/etc) is present.
+ */
+/*
+std:: cout << "DEBUG Layout::print in while --- "
+<< " char_index " << char_index
+<< " lc_index " << lc_index
+<< " cwidth " << cwidth
+<< " _char.x (next) " << (lc_index < _characters.size() ? _characters[lc_index].x : -1)
+<< " char_x (end this)" << char_x
+<< " diff " << fabs(char_x - _characters[lc_index].x)
+<< " oldy " << ky
+<< " nexty " << _glyphs[_characters[lc_index].in_glyph].y
+<< std::endl; //DEBUG
+*/
+ if(hold_iisi != _spans[next_span_index].in_input_stream_item)break; // major change, font, size, color, etc, must exit
+ if(fabs(char_x - _spans[next_span_index].x_start) >= 1e-4)break; // xkerning change
+ if(ky != _glyphs[_characters[lc_index].in_glyph].y)break; // ykerning change
+ /*
+ None of the above? Then this is a minor "pangito", update span_index and keep going.
+ The font used by the display may have failed over, but print does not care and can continue to use
+ whatever was specified in the XML.
+ */
+ span_index = next_span_index;
+ text_iter = _spans[span_index].input_stream_first_character;
+ }
+
+ }
+ // write it
+ ctx->bind(glyph_matrix, 1.0);
+
+ // the dx array is smuggled through to the EMF driver (ignored by others) as:
+ // text<nul>w1 w2 w3 ...wn<nul><nul>
+ // where the widths are floats 7 characters wide, including the space
+
+ char *smuggle_string=smuggle_adxkyrtl_in(text_string.c_str(),ndx, &hold_dx[0], ky, rtl);
+ ctx->text(smuggle_string, g_pos, text_source->style);
+ free(smuggle_string);
+ ctx->release();
+ ndx = 0;
+ char_index = lc_index;
+ }
+ }
+}
+
+
+void Layout::showGlyphs(CairoRenderContext *ctx) const
+{
+ if (_input_stream.empty()) return;
+
+ bool clip_mode = false;//(ctx->getRenderMode() == CairoRenderContext::RENDER_MODE_CLIP);
+ std::vector<CairoGlyphInfo> glyphtext;
+
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; ) {
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) {
+ // invisible glyphs
+ unsigned same_character = _glyphs[glyph_index].in_character;
+ while (_glyphs[glyph_index].in_character == same_character) {
+ glyph_index++;
+ if (glyph_index == _glyphs.size())
+ return;
+ }
+ continue;
+ }
+ Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span];
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
+
+ Geom::Affine glyph_matrix;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ if (clip_mode) {
+ Geom::PathVector const *pathv = span.font->PathVector(_glyphs[glyph_index].glyph);
+ if (pathv) {
+ Geom::PathVector pathv_trans = (*pathv) * glyph_matrix;
+ SPStyle const *style = text_source->style;
+ ctx->renderPathVector(pathv_trans, style, Geom::OptRect());
+ }
+ glyph_index++;
+ continue;
+ }
+
+ Geom::Affine font_matrix = glyph_matrix;
+ font_matrix[4] = 0;
+ font_matrix[5] = 0;
+
+ Glib::ustring::const_iterator span_iter = span.input_stream_first_character;
+ unsigned char_index = _glyphs[glyph_index].in_character;
+ unsigned original_span = _characters[char_index].in_span;
+ while (char_index && _characters[char_index - 1].in_span == original_span) {
+ char_index--;
+ ++span_iter;
+ }
+
+ // try to output as many characters as possible in one go
+ Glib::ustring span_string;
+ unsigned this_span_index = _characters[_glyphs[glyph_index].in_character].in_span;
+ unsigned int first_index = glyph_index;
+ glyphtext.clear();
+ do {
+ span_string += *span_iter;
+ ++span_iter;
+
+ unsigned same_character = _glyphs[glyph_index].in_character;
+ while (glyph_index < _glyphs.size() && _glyphs[glyph_index].in_character == same_character) {
+ if (glyph_index != first_index)
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+
+ CairoGlyphInfo info;
+ info.index = _glyphs[glyph_index].glyph;
+ // this is the translation for x,y-offset
+ info.x = glyph_matrix[4];
+ info.y = glyph_matrix[5];
+
+ glyphtext.push_back(info);
+
+ glyph_index++;
+ }
+ } while (glyph_index < _glyphs.size()
+ && _path_fitted == nullptr
+ && (font_matrix * glyph_matrix.inverse()).isIdentity()
+ && _characters[_glyphs[glyph_index].in_character].in_span == this_span_index);
+
+ // remove vertical flip
+ Geom::Affine flip_matrix;
+ flip_matrix.setIdentity();
+ flip_matrix[3] = -1.0;
+ font_matrix = flip_matrix * font_matrix;
+
+ SPStyle const *style = text_source->style;
+ float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value);
+
+ if (opacity != 1.0) {
+ ctx->pushState();
+ ctx->setStateForStyle(style);
+ ctx->pushLayer();
+ }
+ if (glyph_index - first_index > 0)
+ ctx->renderGlyphtext(span.font->pFont, font_matrix, glyphtext, style);
+ if (opacity != 1.0) {
+ ctx->popLayer();
+ ctx->popState();
+ }
+ }
+}
+
+#if DEBUG_TEXTLAYOUT_DUMPASTEXT
+// these functions are for dumpAsText() only. No need to translate
+static char const *direction_to_text(Layout::Direction d)
+{
+ switch (d) {
+ case Layout::LEFT_TO_RIGHT: return "ltr";
+ case Layout::RIGHT_TO_LEFT: return "rtl";
+ case Layout::TOP_TO_BOTTOM: return "ttb";
+ case Layout::BOTTOM_TO_TOP: return "btt";
+ }
+ return "???";
+}
+
+static char const *style_to_text(PangoStyle s)
+{
+ switch (s) {
+ case PANGO_STYLE_NORMAL: return "upright";
+ case PANGO_STYLE_ITALIC: return "italic";
+ case PANGO_STYLE_OBLIQUE: return "oblique";
+ }
+ return "???";
+}
+
+static std::string weight_to_text(PangoWeight w)
+{
+ switch (w) {
+ case PANGO_WEIGHT_THIN : return "thin";
+ case PANGO_WEIGHT_ULTRALIGHT: return "ultralight";
+ case PANGO_WEIGHT_LIGHT : return "light";
+ case PANGO_WEIGHT_SEMILIGHT : return "semilight";
+ case PANGO_WEIGHT_BOOK : return "book";
+ case PANGO_WEIGHT_NORMAL : return "normalweight";
+ case PANGO_WEIGHT_MEDIUM : return "medium";
+ case PANGO_WEIGHT_SEMIBOLD : return "semibold";
+ case PANGO_WEIGHT_BOLD : return "bold";
+ case PANGO_WEIGHT_ULTRABOLD : return "ultrabold";
+ case PANGO_WEIGHT_HEAVY : return "heavy";
+ case PANGO_WEIGHT_ULTRAHEAVY: return "ultraheavy";
+ }
+ return std::to_string(w);
+}
+#endif //DEBUG_TEXTLAYOUT_DUMPASTEXT
+
+Glib::ustring Layout::getFontFamily(unsigned span_index) const
+{
+ if (span_index >= _spans.size())
+ return "";
+
+ if (_spans[span_index].font) {
+ return sp_font_description_get_family(_spans[span_index].font->descr);
+ }
+
+ return "";
+}
+
+#if DEBUG_TEXTLAYOUT_DUMPASTEXT
+Glib::ustring Layout::dumpAsText() const
+{
+ Glib::ustring result;
+ Glib::ustring::const_iterator icc;
+ char line[256];
+
+ result = Glib::ustring::compose("spans %1\nchars %2\nglyphs %3\n", _spans.size(), _characters.size(), _glyphs.size());
+ if(_characters.size() > 1){
+ unsigned lastspan=5000;
+ for(unsigned j = 0; j < _characters.size() ; j++){
+ if(lastspan != _characters[j].in_span){
+ lastspan = _characters[j].in_span;
+ icc = _spans[lastspan].input_stream_first_character;
+ }
+ snprintf(line, sizeof(line), "char %4u: '%c' 0x%4.4x x=%8.4f glyph=%3d span=%3d\n", j, *icc, *icc, _characters[j].x, _characters[j].in_glyph, _characters[j].in_span);
+ result += line;
+ ++icc;
+ }
+ }
+ if(_glyphs.size()){
+ for(unsigned j = 0; j < _glyphs.size() ; j++){
+ snprintf(line, sizeof(line), "glyph %4u: %4d (%8.4f,%8.4f) rot=%8.4f cx=%8.4f char=%4d\n",
+ j, _glyphs[j].glyph, _glyphs[j].x, _glyphs[j].y, _glyphs[j].rotation, _glyphs[j].width, _glyphs[j].in_character);
+ result += line;
+ }
+ }
+
+ for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
+ result += Glib::ustring::compose("==== span %1 \n", span_index)
+ + Glib::ustring::compose(" in para %1 (direction=%2)\n", _lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph,
+ direction_to_text(_paragraphs[_lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph].base_direction))
+ + Glib::ustring::compose(" in source %1 (type=%2, cookie=%3)\n", _spans[span_index].in_input_stream_item,
+ _input_stream[_spans[span_index].in_input_stream_item]->Type(),
+ _input_stream[_spans[span_index].in_input_stream_item]->source)
+ + Glib::ustring::compose(" in line %1 (baseline=%2, shape=%3)\n", _chunks[_spans[span_index].in_chunk].in_line,
+ _lines[_chunks[_spans[span_index].in_chunk].in_line].baseline_y,
+ _lines[_chunks[_spans[span_index].in_chunk].in_line].in_shape)
+ + Glib::ustring::compose(" in chunk %1 (x=%2, baselineshift=%3)\n", _spans[span_index].in_chunk, _chunks[_spans[span_index].in_chunk].left_x, _spans[span_index].baseline_shift);
+
+ if (_spans[span_index].font) {
+ const char* variations = pango_font_description_get_variations(_spans[span_index].font->descr);
+ result += Glib::ustring::compose(
+ " font '%1' %2 %3 %4 %5\n",
+ sp_font_description_get_family(_spans[span_index].font->descr),
+ _spans[span_index].font_size,
+ style_to_text( pango_font_description_get_style(_spans[span_index].font->descr) ),
+ weight_to_text( pango_font_description_get_weight(_spans[span_index].font->descr) ),
+ (variations?variations:"")
+ );
+ }
+ result += Glib::ustring::compose(" x_start = %1, x_end = %2\n", _spans[span_index].x_start, _spans[span_index].x_end)
+ + Glib::ustring::compose(" line height: ascent %1, descent %2\n", _spans[span_index].line_height.ascent, _spans[span_index].line_height.descent)
+ + Glib::ustring::compose(" direction %1, block-progression %2\n", direction_to_text(_spans[span_index].direction), direction_to_text(_spans[span_index].block_progression))
+ + " ** characters:\n";
+ Glib::ustring::const_iterator iter_char = _spans[span_index].input_stream_first_character;
+ // very inefficient code. what the hell, it's only debug stuff.
+ for (unsigned char_index = 0 ; char_index < _characters.size() ; char_index++) {
+ union {const PangoLogAttr* pattr; const unsigned* uattr;} u;
+ u.pattr = &_characters[char_index].char_attributes;
+ if (_characters[char_index].in_span != span_index) continue;
+ if (_input_stream[_spans[span_index].in_input_stream_item]->Type() != TEXT_SOURCE) {
+ snprintf(line, sizeof(line), " %u: control x=%f flags=%03x glyph=%d\n", char_index, _characters[char_index].x, *u.uattr, _characters[char_index].in_glyph);
+ } else { // some text has empty tspans, iter_char cannot be dereferenced
+ snprintf(line, sizeof(line), " %u: '%c' 0x%4.4x x=%f flags=%03x glyph=%d\n", char_index, *iter_char, *iter_char, _characters[char_index].x, *u.uattr, _characters[char_index].in_glyph);
+ ++iter_char;
+ }
+ result += line;
+ }
+ result += " ** glyphs:\n";
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
+ if (_characters[_glyphs[glyph_index].in_character].in_span != span_index) continue;
+ snprintf(line, sizeof(line), " %u: %d (%f,%f) rot=%f cx=%f char=%d\n", glyph_index, _glyphs[glyph_index].glyph, _glyphs[glyph_index].x, _glyphs[glyph_index].y, _glyphs[glyph_index].rotation, _glyphs[glyph_index].width, _glyphs[glyph_index].in_character);
+ result += line;
+ }
+ result += "\n";
+ }
+ result += "EOT\n";
+ return result;
+}
+#endif //DEBUG_TEXTLAYOUT_DUMPASTEXT
+
+void Layout::fitToPathAlign(SVGLength const &startOffset, Path const &path)
+{
+ double offset = 0.0;
+
+ if (startOffset._set) {
+ if (startOffset.unit == SVGLength::PERCENT)
+ offset = startOffset.computed * const_cast<Path&>(path).Length();
+ else
+ offset = startOffset.computed;
+ }
+
+ Alignment alignment = _paragraphs.empty() ? LEFT : _paragraphs.front().alignment;
+ switch (alignment) {
+ case CENTER:
+ offset -= _getChunkWidth(0) * 0.5;
+ break;
+ case RIGHT:
+ offset -= _getChunkWidth(0);
+ break;
+ default:
+ break;
+ }
+
+ if (_characters.empty()) {
+ int unused = 0;
+ Path::cut_position *point_otp = const_cast<Path&>(path).CurvilignToPosition(1, &offset, unused);
+ if (offset >= 0.0 && point_otp != nullptr && point_otp[0].piece >= 0) {
+ Geom::Point point;
+ Geom::Point tangent;
+ const_cast<Path&>(path).PointAndTangentAt(point_otp[0].piece, point_otp[0].t, point, tangent);
+ _empty_cursor_shape.position = point;
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ _empty_cursor_shape.rotation = atan2(-tangent[Geom::X], tangent[Geom::Y]);
+ } else {
+ _empty_cursor_shape.rotation = atan2(tangent[Geom::Y], tangent[Geom::X]);
+ }
+ }
+ }
+
+ for (unsigned char_index = 0 ; char_index < _characters.size() ; ) {
+ Span const &span = _characters[char_index].span(this);
+
+ size_t next_cluster_char_index = 0; // TODO refactor to not bump via for loops
+ for (next_cluster_char_index = char_index + 1 ; next_cluster_char_index < _characters.size() ; next_cluster_char_index++) {
+ if (_characters[next_cluster_char_index].in_glyph != -1 && _characters[next_cluster_char_index].char_attributes.is_cursor_position)
+ {
+ break;
+ }
+ }
+
+ size_t next_cluster_glyph_index = 0;
+ if (next_cluster_char_index == _characters.size()) {
+ next_cluster_glyph_index = _glyphs.size();
+ } else {
+ next_cluster_glyph_index = _characters[next_cluster_char_index].in_glyph;
+ }
+
+ double start_offset = offset + span.x_start + _characters[char_index].x;
+ double cluster_width = 0.0;
+ size_t const current_cluster_glyph_index = _characters[char_index].in_glyph;
+ for (size_t glyph_index = current_cluster_glyph_index ; glyph_index < next_cluster_glyph_index ; glyph_index++)
+ {
+ cluster_width += _glyphs[glyph_index].advance;
+ }
+ // TODO block progression?
+ if (span.direction == RIGHT_TO_LEFT)
+ {
+ start_offset -= cluster_width;
+ }
+ double end_offset = start_offset + cluster_width;
+
+ int unused = 0;
+ double midpoint_offset = (start_offset + end_offset) * 0.5;
+ // as far as I know these functions are const, they're just not marked as such
+ Path::cut_position *midpoint_otp = const_cast<Path&>(path).CurvilignToPosition(1, &midpoint_offset, unused);
+ if (midpoint_offset >= 0.0 && midpoint_otp != nullptr && midpoint_otp[0].piece >= 0) {
+ Geom::Point midpoint;
+ Geom::Point tangent;
+ const_cast<Path&>(path).PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
+
+ if (start_offset >= 0.0 && end_offset >= 0.0) {
+ Path::cut_position *start_otp = const_cast<Path&>(path).CurvilignToPosition(1, &start_offset, unused);
+ if (start_otp != nullptr && start_otp[0].piece >= 0) {
+ Path::cut_position *end_otp = const_cast<Path&>(path).CurvilignToPosition(1, &end_offset, unused);
+ if (end_otp != nullptr && end_otp[0].piece >= 0) {
+ bool on_same_subpath = true;
+ for (const auto & pt : path.pts) {
+ if (pt.piece <= start_otp[0].piece) continue;
+ if (pt.piece >= end_otp[0].piece) break;
+ if (pt.isMoveTo == polyline_moveto) {
+ on_same_subpath = false;
+ break;
+ }
+ }
+ if (on_same_subpath) {
+ // both points were on the same subpath (without this test the angle is very weird)
+ Geom::Point startpoint, endpoint;
+ const_cast<Path&>(path).PointAt(start_otp[0].piece, start_otp[0].t, startpoint);
+ const_cast<Path&>(path).PointAt(end_otp[0].piece, end_otp[0].t, endpoint);
+ if (endpoint != startpoint) {
+ tangent = endpoint - startpoint;
+ tangent.normalize();
+ }
+ }
+ g_free(end_otp);
+ }
+ g_free(start_otp);
+ }
+ }
+
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ double rotation = atan2(-tangent[Geom::X], tangent[Geom::Y]);
+ for (size_t glyph_index = current_cluster_glyph_index; glyph_index < next_cluster_glyph_index ; glyph_index++) {
+ _glyphs[glyph_index].x = midpoint[Geom::Y] - tangent[Geom::X] * _glyphs[glyph_index].y - span.chunk(this).left_x;
+ _glyphs[glyph_index].y = midpoint[Geom::X] + tangent[Geom::Y] * _glyphs[glyph_index].y - _lines.front().baseline_y;
+ _glyphs[glyph_index].rotation += rotation;
+ }
+ } else {
+ double rotation = atan2(tangent[Geom::Y], tangent[Geom::X]);
+ for (size_t glyph_index = current_cluster_glyph_index; glyph_index < next_cluster_glyph_index ; glyph_index++) {
+ double tangent_shift = -cluster_width * 0.5 + _glyphs[glyph_index].x - (_characters[char_index].x + span.x_start);
+ if (span.direction == RIGHT_TO_LEFT)
+ {
+ tangent_shift += cluster_width;
+ }
+ _glyphs[glyph_index].x = midpoint[Geom::X] + tangent[Geom::X] * tangent_shift - tangent[Geom::Y] * _glyphs[glyph_index].y - span.chunk(this).left_x;
+ _glyphs[glyph_index].y = midpoint[Geom::Y] + tangent[Geom::Y] * tangent_shift + tangent[Geom::X] * _glyphs[glyph_index].y - _lines.front().baseline_y;
+ _glyphs[glyph_index].rotation += rotation;
+ }
+ }
+ _input_truncated = false;
+ } else { // outside the bounds of the path: hide the glyphs
+ _characters[char_index].in_glyph = -1;
+ _input_truncated = true;
+ }
+ g_free(midpoint_otp);
+
+ char_index = next_cluster_char_index;
+ }
+
+ for (auto & _span : _spans) {
+ _span.x_start += offset;
+ _span.x_end += offset;
+ }
+
+ _path_fitted = &path;
+}
+
+std::unique_ptr<SPCurve> Layout::convertToCurves() const
+{
+ return convertToCurves(begin(), end());
+}
+
+std::unique_ptr<SPCurve> Layout::convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const
+{
+ auto curve = std::make_unique<SPCurve>();
+
+ for (int glyph_index = from_glyph._glyph_index ; glyph_index < to_glyph._glyph_index ; glyph_index++) {
+ Geom::Affine glyph_matrix;
+ Span const &span = _glyphs[glyph_index].span(this);
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+
+ Geom::PathVector const * pathv = span.font->PathVector(_glyphs[glyph_index].glyph);
+ if (pathv) {
+ Geom::PathVector pathv_trans = (*pathv) * glyph_matrix;
+ curve->append(SPCurve(std::move(pathv_trans)));
+ }
+ }
+
+ return curve;
+}
+
+void Layout::transform(Geom::Affine const &transform)
+{
+ // this is all massively oversimplified
+ // I can't actually think of anybody who'll want to use it at the moment, so it'll stay simple
+ for (auto & _glyph : _glyphs) {
+ Geom::Point point(_glyph.x, _glyph.y);
+ point *= transform;
+ _glyph.x = point[0];
+ _glyph.y = point[1];
+ }
+}
+
+double Layout::getTextLengthIncrementDue() const
+{
+ if (textLength._set && textLengthIncrement != 0 && lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACING) {
+ return textLengthIncrement;
+ }
+ return 0;
+}
+
+
+double Layout::getTextLengthMultiplierDue() const
+{
+ if (textLength._set && textLengthMultiplier != 1 && (lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACINGANDGLYPHS)) {
+ return textLengthMultiplier;
+ }
+ return 1;
+}
+
+double Layout::getActualLength() const
+{
+ double length = 0;
+ for (std::vector<Span>::const_iterator it_span = _spans.begin() ; it_span != _spans.end() ; it_span++) {
+ // take x_end of the last span of each chunk
+ if (it_span == _spans.end() - 1 || (it_span + 1)->in_chunk != it_span->in_chunk)
+ length += it_span->x_end;
+ }
+ return length;
+
+
+}
+
+}//namespace Text
+}//namespace Inkscape
+
+std::ostream &operator<<(std::ostream &out, const Inkscape::Text::Layout::FontMetrics &f) {
+ out << " emSize: " << f.emSize()
+ << " ascent: " << f.ascent
+ << " descent: " << f.descent
+ << " xheight: " << f.xheight;
+ return out;
+}
+
+std::ostream &operator<<(std::ostream &out, const Inkscape::Text::Layout::FontMetrics *f) {
+ out << " emSize: " << f->emSize()
+ << " ascent: " << f->ascent
+ << " descent: " << f->descent
+ << " xheight: " << f->xheight;
+ return out;
+}
+
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/Layout-TNG-Scanline-Maker.h b/src/libnrtype/Layout-TNG-Scanline-Maker.h
new file mode 100644
index 0000000..c8c8232
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Scanline-Maker.h
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::Text::Layout::ScanlineMaker - text layout engine shape measurers
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef __LAYOUT_TNG_SCANLINE_MAKER_H__
+#define __LAYOUT_TNG_SCANLINE_MAKER_H__
+
+#include <vector>
+#include <cmath>
+#include "libnrtype/Layout-TNG.h"
+
+class Shape;
+
+namespace Inkscape {
+namespace Text {
+
+/** \brief private to Layout. Generates lists of chunks within a shape.
+
+This is the abstract base class for taking a given shape and scanning through
+it line-by-line to get the horizontal extents of each chunk for a line of a
+given height. There are two specialisations: One for real shapes and one that
+turns off wrapping by simulating an infinite shape. In due course there will
+be a further specialisation to optimise for the common case where the shape
+is a rectangle.
+*/
+class Layout::ScanlineMaker
+{
+public:
+ virtual ~ScanlineMaker() = default;
+
+ struct ScanRun {
+ double y; /// that's the top of the scan run, not the baseline
+ double x_start; // these are not flipped according to the text direction
+ double x_end;
+ inline double width() const {return std::abs(x_start - x_end);}
+ };
+
+ /** Returns a list of chunks on the current line which can fit text with
+ the given properties. It is up to the caller to discard any chunks which
+ are too narrow for its needs. This function may change the y coordinate
+ between calls if the new height too big to fit in the space remaining in
+ this shape. Returns an empty vector if there is no space left in the
+ current shape. */
+ virtual std::vector<ScanRun> makeScanline(Layout::FontMetrics const &line_height) =0;
+
+ /** Indicates that the caller has successfully filled the current line
+ and hence that the next call to makeScanline() should return lines on
+ the next lower line. There is no error return, the next call to
+ makeScanline() will give an error if there is no more space. */
+ virtual void completeLine() =0;
+
+ /** Returns the y coordinate of the top of the scanline that will be
+ returned by the next call to makeScanline(). */
+ virtual double yCoordinate() = 0;
+
+ /** Forces an arbitrary change in the stored y coordinate of the object.
+ The next call to makeScanline() will return runs whose top is at
+ the new coordinate. */
+ virtual void setNewYCoordinate(double new_y) =0;
+
+ /** Tests whether the caller can fit a new line with the given metrics
+ into exactly the space returned by the previous call to makeScanline().
+ This saves the caller from having to discard its wrapping solution and
+ starting at the beginning of the line again when a larger font is seen.
+ The metrics given here are considered to be the ones that are being
+ used now, and hence is the line advance height used by completeLine().
+ */
+ virtual bool canExtendCurrentScanline(Layout::FontMetrics const &line_height) =0;
+
+ /** Sets current line block height. Call before completeLine() to correct for
+ actually used line height (in case some chunks with larger font-size rolled back).
+ */
+ virtual void setLineHeight(Layout::FontMetrics const &line_height) =0;
+};
+
+/** \brief private to Layout. Generates infinite scanlines for when you don't want wrapping
+
+This is a 'fake' scanline maker which will always return infinite results,
+effectively turning off wrapping. It's a very simple implementation.
+
+It does have the curious property, however, that the input coordinates are
+'real' x and y, but the outputs are rotated according to the
+\a block_progression.
+*/
+class Layout::InfiniteScanlineMaker : public Layout::ScanlineMaker
+{
+public:
+ InfiniteScanlineMaker(double initial_x, double initial_y, Layout::Direction block_progression);
+ ~InfiniteScanlineMaker() override;
+
+ /** Returns a single infinite run at the current location */
+ std::vector<ScanRun> makeScanline(Layout::FontMetrics const &line_height) override;
+
+ /** Increments the current y by the current line height */
+ void completeLine() override;
+
+ double yCoordinate() override
+ {return _y;}
+
+ /** Just changes y */
+ void setNewYCoordinate(double new_y) override;
+
+ /** Always true, but has to save the new height */
+ bool canExtendCurrentScanline(Layout::FontMetrics const &line_height) override;
+
+ /** Sets current line block height. Call before completeLine() to correct for
+ actually used line height (in case some chunks with larger font-size rolled back).
+ */
+ void setLineHeight(Layout::FontMetrics const &line_height) override;
+
+private:
+ double _x, _y;
+ Layout::FontMetrics _current_line_height;
+ bool _negative_block_progression; /// if true, indicates that completeLine() should decrement rather than increment, ie block-progression is either rl or bt
+};
+
+/** \brief private to Layout. Generates scanlines inside an arbitrary shape
+
+This is the 'perfect', and hence slowest, implementation of a
+Layout::ScanlineMaker, which will return exact bounds for any given
+input shape.
+*/
+class Layout::ShapeScanlineMaker : public Layout::ScanlineMaker
+{
+public:
+ ShapeScanlineMaker(Shape const *shape, Layout::Direction block_progression);
+ ~ShapeScanlineMaker() override;
+
+ std::vector<ScanRun> makeScanline(Layout::FontMetrics const &line_height) override;
+
+ void completeLine() override;
+
+ double yCoordinate() override;
+
+ void setNewYCoordinate(double new_y) override;
+
+ /** never true */
+ bool canExtendCurrentScanline(Layout::FontMetrics const &line_height) override;
+
+ /** Sets current line block height. Call before completeLine() to correct for
+ actually used line height (in case some chunks with larger font-size rolled back).
+ */
+ void setLineHeight(Layout::FontMetrics const &line_height) override;
+
+private:
+ /** To generate scanlines for top-to-bottom text it is easiest if we
+ simply rotate the given shape by a multiple of 90 degrees. This stores
+ that. If no rotation was needed we can simply store the pointer we were
+ given and set shape_needs_freeing appropriately. */
+ Shape *_rotated_shape;
+
+ /// see #rotated_shape;
+ bool _shape_needs_freeing;
+
+ // Shape::BeginRaster() needs floats rather than doubles
+ float _bounding_box_top, _bounding_box_bottom;
+ float _y;
+ float _rasterizer_y;
+ int _current_rasterization_point;
+ float _current_line_height;
+
+ bool _negative_block_progression; /// if true, indicates that completeLine() should decrement rather than increment, ie block-progression is either rl or bt
+};
+
+}//namespace Text
+}//namespace Inkscape
+
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/Layout-TNG-Scanline-Makers.cpp b/src/libnrtype/Layout-TNG-Scanline-Makers.cpp
new file mode 100644
index 0000000..8398bf1
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Scanline-Makers.cpp
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::Text::Layout::ScanlineMaker - text layout engine shape measurers
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "Layout-TNG-Scanline-Maker.h"
+#include "livarot/Shape.h"
+#include "livarot/float-line.h"
+#include <limits>
+
+namespace Inkscape {
+namespace Text {
+
+// *********************** infinite version
+
+Layout::InfiniteScanlineMaker::InfiniteScanlineMaker(double initial_x, double initial_y, Layout::Direction block_progression)
+{
+ _current_line_height.setZero();
+ switch (block_progression) {
+ case LEFT_TO_RIGHT:
+ case RIGHT_TO_LEFT:
+ _x = initial_y;
+ _y = initial_x;
+ break;
+ default:
+ _x = initial_x;
+ _y = initial_y;
+ break;
+ }
+ _negative_block_progression = block_progression == RIGHT_TO_LEFT || block_progression == BOTTOM_TO_TOP;
+
+}
+
+Layout::InfiniteScanlineMaker::~InfiniteScanlineMaker()
+= default;
+
+std::vector<Layout::ScanlineMaker::ScanRun> Layout::InfiniteScanlineMaker::makeScanline(Layout::FontMetrics const &line_height)
+{
+ std::vector<ScanRun> runs(1);
+ runs[0].x_start = _x;
+ runs[0].x_end = std::numeric_limits<float>::max(); // we could use DBL_MAX, but this just seems safer
+ runs[0].y = _y;
+ _current_line_height = line_height;
+ return runs;
+}
+
+void Layout::InfiniteScanlineMaker::completeLine()
+{
+ if (_negative_block_progression)
+ _y -= _current_line_height.emSize();
+ else
+ _y += _current_line_height.emSize();
+ _current_line_height.setZero();
+}
+
+void Layout::InfiniteScanlineMaker::setNewYCoordinate(double new_y)
+{
+ _y = new_y;
+}
+
+bool Layout::InfiniteScanlineMaker::canExtendCurrentScanline(Layout::FontMetrics const &line_height)
+{
+ _current_line_height = line_height;
+ return true;
+}
+
+void Layout::InfiniteScanlineMaker::setLineHeight(Layout::FontMetrics const &line_height)
+{
+ _current_line_height = line_height;
+}
+
+// *********************** real shapes version
+
+Layout::ShapeScanlineMaker::ShapeScanlineMaker(Shape const *shape, Layout::Direction block_progression)
+{
+ if (block_progression == TOP_TO_BOTTOM) {
+ _rotated_shape = const_cast<Shape*>(shape);
+ _shape_needs_freeing = false;
+ } else {
+ Shape *temp_rotated_shape = new Shape;
+ _shape_needs_freeing = true;
+ temp_rotated_shape->Copy(const_cast<Shape*>(shape));
+ switch (block_progression) {
+ case BOTTOM_TO_TOP: temp_rotated_shape->Transform(Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)); break; // reflect about x axis
+ case LEFT_TO_RIGHT: temp_rotated_shape->Transform(Geom::Affine(0.0, 1.0, 1.0, 0.0, 0.0, 0.0)); break; // reflect about y=x
+ case RIGHT_TO_LEFT: temp_rotated_shape->Transform(Geom::Affine(0.0, -1.0, 1.0, 0.0, 0.0, 0.0)); break; // reflect about y=-x
+ default: break;
+ }
+ _rotated_shape = new Shape;
+ _rotated_shape->ConvertToShape(temp_rotated_shape);
+ delete temp_rotated_shape;
+ }
+ _rotated_shape->CalcBBox(true);
+ _bounding_box_top = _rotated_shape->topY;
+ _bounding_box_bottom = _rotated_shape->bottomY;
+ _y = _rasterizer_y = _bounding_box_top;
+ _current_rasterization_point = 0;
+ _rotated_shape->BeginRaster(_y, _current_rasterization_point);
+ _negative_block_progression = block_progression == RIGHT_TO_LEFT || block_progression == BOTTOM_TO_TOP;
+}
+
+
+Layout::ShapeScanlineMaker::~ShapeScanlineMaker()
+{
+ _rotated_shape->EndRaster();
+ if (_shape_needs_freeing)
+ delete _rotated_shape;
+}
+
+std::vector<Layout::ScanlineMaker::ScanRun> Layout::ShapeScanlineMaker::makeScanline(Layout::FontMetrics const &line_height)
+{
+ if (_y > _bounding_box_bottom)
+ return std::vector<ScanRun>();
+
+ if (_y < _bounding_box_top)
+ _y = _bounding_box_top;
+
+ FloatLigne line_rasterization;
+ FloatLigne line_decent_length_runs;
+ float line_text_height = (float)(line_height.emSize());
+ if (line_text_height < 0.001)
+ line_text_height = 0.001; // Scan() doesn't work for zero height so this will have to do
+
+ _current_line_height = (float)line_height.emSize();
+
+ // I think what's going on here is that we're moving the top of the scanline to the given position...
+ _rotated_shape->Scan(_rasterizer_y, _current_rasterization_point, _y, line_text_height);
+ // ...then actually retrieving the scanline (which alters the first two parameters)
+ _rotated_shape->Scan(_rasterizer_y, _current_rasterization_point, _y + line_text_height , &line_rasterization, true, line_text_height);
+ // sanitise the raw rasterisation, which could have weird overlaps
+ line_rasterization.Flatten();
+ // line_rasterization.Affiche();
+ // cut out runs that cover less than 90% of the line
+ line_decent_length_runs.Over(&line_rasterization, 0.9 * line_text_height);
+
+ if (line_decent_length_runs.runs.empty())
+ {
+ if (line_rasterization.runs.empty())
+ return std::vector<ScanRun>(); // stop the flow
+ // make up a pointless run: anything that's not an empty vector
+ std::vector<ScanRun> result(1);
+ result[0].x_start = line_rasterization.runs[0].st;
+ result[0].x_end = line_rasterization.runs[0].st;
+ result[0].y = _negative_block_progression ? - _y : _y;
+ return result;
+ }
+
+ // convert the FloatLigne to what we use: vector<ScanRun>
+ std::vector<ScanRun> result(line_decent_length_runs.runs.size());
+ for (unsigned i = 0 ; i < result.size() ; i++) {
+ result[i].x_start = line_decent_length_runs.runs[i].st;
+ result[i].x_end = line_decent_length_runs.runs[i].en;
+ result[i].y = _negative_block_progression ? - _y : _y;
+ }
+
+ return result;
+}
+
+void Layout::ShapeScanlineMaker::completeLine()
+{
+ _y += _current_line_height;
+}
+
+double Layout::ShapeScanlineMaker::yCoordinate()
+{
+ if (_negative_block_progression) return - _y;
+ return _y;
+}
+
+void Layout::ShapeScanlineMaker::setNewYCoordinate(double new_y)
+{
+ _y = (float)new_y;
+ if (_negative_block_progression) _y = - _y;
+ // what will happen with the rasteriser if we move off the shape?
+ // it's not an important question because <flowSpan> doesn't have a y attribute
+}
+
+bool Layout::ShapeScanlineMaker::canExtendCurrentScanline(Layout::FontMetrics const &/*line_height*/)
+{
+ //we actually could return true if only the leading changed, but that's too much effort for something that rarely happens
+ return false;
+}
+
+void Layout::ShapeScanlineMaker::setLineHeight(Layout::FontMetrics const &line_height)
+{
+ _current_line_height = line_height.emSize();
+}
+
+}//namespace Text
+}//namespace Inkscape
diff --git a/src/libnrtype/Layout-TNG.cpp b/src/libnrtype/Layout-TNG.cpp
new file mode 100644
index 0000000..96d805d
--- /dev/null
+++ b/src/libnrtype/Layout-TNG.cpp
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::Text::Layout - text layout engine misc
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "Layout-TNG.h"
+
+namespace Inkscape {
+namespace Text {
+
+const gunichar Layout::UNICODE_SOFT_HYPHEN = 0x00AD;
+const double Layout::LINE_HEIGHT_NORMAL = 1.25;
+
+Layout::Layout() = default;
+
+Layout::~Layout()
+{
+ clear();
+}
+
+void Layout::clear()
+{
+ _clearInputObjects();
+ _clearOutputObjects();
+
+ textLength._set = false;
+ textLengthMultiplier = 1;
+ textLengthIncrement = 0;
+ lengthAdjust = LENGTHADJUST_SPACING;
+}
+
+bool Layout::_directions_are_orthogonal(Direction d1, Direction d2)
+{
+ if (d1 == BOTTOM_TO_TOP) d1 = TOP_TO_BOTTOM;
+ if (d2 == BOTTOM_TO_TOP) d2 = TOP_TO_BOTTOM;
+ if (d1 == RIGHT_TO_LEFT) d1 = LEFT_TO_RIGHT;
+ if (d2 == RIGHT_TO_LEFT) d2 = LEFT_TO_RIGHT;
+ return d1 != d2;
+}
+
+}//namespace Text
+}//namespace Inkscape
diff --git a/src/libnrtype/Layout-TNG.h b/src/libnrtype/Layout-TNG.h
new file mode 100644
index 0000000..ebcc896
--- /dev/null
+++ b/src/libnrtype/Layout-TNG.h
@@ -0,0 +1,1207 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::Text::Layout - text layout engine
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef __LAYOUT_TNG_H__
+#define __LAYOUT_TNG_H__
+
+//#define DEBUG_TEXTLAYOUT_DUMPASTEXT
+
+#include <2geom/d2.h>
+#include <2geom/affine.h>
+#include <glibmm/ustring.h>
+#include <memory>
+#include <pango/pango-break.h>
+#include <algorithm>
+#include <vector>
+#include <optional>
+#include <svg/svg-length.h>
+#include "style-enums.h"
+
+namespace Inkscape {
+ namespace Extension {
+ namespace Internal {
+ class CairoRenderContext;
+ }
+ }
+}
+
+using Inkscape::Extension::Internal::CairoRenderContext;
+
+class SPStyle;
+class SPObject;
+class Shape;
+struct SPPrintContext;
+class Path;
+class SPCurve;
+class font_instance;
+typedef struct _PangoFontDescription PangoFontDescription;
+
+namespace Inkscape {
+class DrawingGroup;
+
+namespace Text {
+
+/** \brief Generates the layout for either wrapped or non-wrapped text and stores the result
+
+Use this class for all your text output needs. It takes text with formatting
+markup as input and turns that into the glyphs and their necessary positions.
+It stores the glyphs internally, but maintains enough information to both
+retrieve your own rendering information if you wish and to perform visual
+text editing where the output refers back to where it came from.
+
+Usage:
+-# Construct
+-# Set the text using appendText() and appendControlCode()
+-# If you want text wrapping, call appendWrapShape() a few times
+-# Call calculateFlow()
+-# You can go several directions from here, but the most interesting
+ things start with creating a Layout::iterator with begin() or end().
+
+Terminology, in descending order of size:
+- Flow: Not often used, but when it is it means all the text
+- Shape: A Shape object which is used to represent one of the regions inside
+ which to flow the text. Can overlap with...
+- Paragraph: Err...A paragraph. Contains one or more...
+- Line: An entire horizontal line with a common baseline. Contains one or
+ more...
+- Chunk: You only get more than one of these when a shape is sufficiently
+ complex that the text has to flow either side of some obstruction in
+ the middle. A chunk is the base unit for wrapping. Contains one or more...
+- Span: A convenient subset of a chunk with the same font, style,
+ directionality, block progression and input stream. Fill and outline
+ need not be constant because that's a later rendering stage.
+- This is where it gets weird because a span will contain one or more
+ elements of both of the following, which can overlap with each other in
+ any way:
+ - Character: a single Unicode codepoint from an input stream. Many arabic
+ characters contain multiple glyphs
+ - Glyph: a rendering primitive for font engines. A ligature glyph will
+ represent multiple characters.
+
+Other terminology:
+- Input stream: An object representing a single call to appendText() or
+ appendControlCode().
+- Control code: Metadata in the text stream to signify items that occupy
+ real space (unlike style changes) but don't belong in the text string.
+ Paragraph breaks are in this category. See Layout::TextControlCode.
+- SVG1.1: The W3C Recommendation "Scalable Vector Graphics (SVG) 1.1"
+ http://www.w3.org/TR/SVG11/
+- 'left', 'down', etc: These terms are generally used to mean what they
+ mean in left-to-right, top-to-bottom text but rotated or reflected for
+ the current directionality. Thus, the 'width' of a ttb line is actually
+ its height, and the (internally stored) y coordinate of a glyph is
+ actually its x coordinate. Confusing to the reader but much simpler in
+ the code. All public methods use real x and y.
+
+Comments:
+- There's a strong emphasis on international support in this class, but
+ that's primarily because once you can display all the insane things
+ required by various languages, simple things like styling text are
+ almost trivial.
+- There are a few places (appendText() is one) where pointers are held to
+ caller-owned objects and used for quite a long time. This is messy but
+ is safe for our usage scenario and in many cases the cost of copying the
+ objects is quite high.
+- "Why isn't foo here?": Ask yourself if it's possible to implement foo
+ externally using iterators. However this may not mean that it doesn't
+ belong as a member, though.
+- I've used floats rather than doubles to store relative distances in some
+ places (internal only) where it would save significant amounts of memory.
+ The SVG spec allows you to do this as long as intermediate calculations
+ are done double. Very very long lines might not finish precisely where
+ you want, but that's to be expected with any typesetting. Also,
+ SVGLength only uses floats.
+- If you look at the six arrays for holding the output data you'll realise
+ that there's no O(1) way to drill down from a paragraph to find its
+ starting glyph. This was a conscious decision to reduce complexity and
+ to save memory. Drilling down isn't actually that slow because a binary
+ chop will work nicely. Add this to the realisation that most of the
+ times you do this will be in response to user actions and hence you only
+ need to be faster than the user and I think the design makes sense.
+- There are a massive number of functions acting on Layout::iterator. A
+ large number are trivial and will be inline, but is it really necessary
+ to have all these, especially when some can be implemented by the caller
+ using the others?
+- The separation of methods between Layout and Layout::iterator is a
+ bit arbitrary, because many methods could go in either. I've used the STL
+ model where the iterator itself can only move around; the base class is
+ required to do anything interesting.
+- I use Pango internally, not Pangomm. The reason for this is lots of
+ Pangomm methods take Glib::ustrings as input and then output byte offsets
+ within the strings. There's simply no way to use byte offsets with
+ ustrings without some very entertaining reinterpret_cast<>s. The Pangomm
+ docs seem to be lacking quite a lot of things mentioned in the Pango
+ docs, too.
+*/
+class Layout {
+public:
+ class iterator;
+ friend class iterator;
+ class Calculator;
+ friend class Calculator;
+ class ScanlineMaker;
+ class InfiniteScanlineMaker;
+ class ShapeScanlineMaker;
+
+ Layout();
+ virtual ~Layout();
+
+ /** Used to specify any particular text direction required. Used for
+ both the 'direction' and 'block-progression' CSS attributes. */
+ enum Direction {LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP};
+
+ /** Used to specify orientation of glyphs in vertical text. */
+ enum Orientation {ORIENTATION_UPRIGHT, ORIENTATION_SIDEWAYS};
+
+
+ /** Display alignment for shapes. See appendWrapShape(). */
+ enum DisplayAlign {DISPLAY_ALIGN_BEFORE, DISPLAY_ALIGN_CENTER, DISPLAY_ALIGN_AFTER};
+
+ /** lengthAdjust values */
+ enum LengthAdjust {LENGTHADJUST_SPACING, LENGTHADJUST_SPACINGANDGLYPHS};
+
+ enum WrapMode {
+ WRAP_NONE, // No wrapping or wrapping via role="line".
+ WRAP_WHITE_SPACE, // Wrapping via 'white-space' property.
+ WRAP_INLINE_SIZE, // Wrapping via 'inline-size' property.
+ WRAP_SHAPE_INSIDE // Wrapping via 'shape-inside' propertry.
+ } wrap_mode = WRAP_NONE;
+
+ /** The optional attributes which can be applied to a SVG text or
+ related tag. See appendText(). See SVG1.1 section 10.4 for the
+ definitions of all these members. See sp_svg_length_list_read() for
+ the standard way to make these vectors. It is the responsibility of
+ the caller to deal with the inheritance of these values using its
+ knowledge of the parse tree. */
+ struct OptionalTextTagAttrs {
+ std::vector<SVGLength> x;
+ std::vector<SVGLength> y;
+ std::vector<SVGLength> dx;
+ std::vector<SVGLength> dy;
+ std::vector<SVGLength> rotate;
+ SVGLength textLength;
+ LengthAdjust lengthAdjust;
+ };
+
+ /** Control codes which can be embedded in the text to be flowed. See
+ appendControlCode(). */
+ enum TextControlCode {
+ PARAGRAPH_BREAK, /// forces the flow to move on to the next line
+ SHAPE_BREAK, /// forces the flow to ignore the remainder of the current shape (from #flow_inside_shapes) and continue at the top of the one after.
+ ARBITRARY_GAP /// inserts an arbitrarily-sized hole in the flow in line with the current text.
+ };
+
+ /** For expressing paragraph alignment. These values are rotated in the
+ case of vertical text, but are not dependent on whether the paragraph is
+ rtl or ltr, thus LEFT is always either left or top. */
+ enum Alignment {LEFT, CENTER, RIGHT, FULL, NONE};
+
+ /** The CSS spec allows line-height:normal to be whatever the user agent
+ thinks will look good. This is our value, as a multiple of font-size. */
+ static const double LINE_HEIGHT_NORMAL;
+
+ // ************************** describing the stuff to flow *************************
+
+ /** \name Input
+ Methods for describing the text you want to flow, its style, and the
+ shapes to flow in to.
+ */
+ //@{
+
+ /** Empties everything stored in this class and resets it to its
+ original state, like when it was created. All iterators on this
+ object will be invalidated (but can be revalidated using
+ validateIterator(). */
+ void clear();
+
+ /** Queries whether any calls have been made to appendText() or
+ appendControlCode() since the object was last cleared. */
+ bool inputExists() const
+ {return !_input_stream.empty();}
+
+ bool _input_truncated = false;
+ bool inputTruncated() const
+ {return _input_truncated;}
+
+ /** adds a new piece of text to the end of the current list of text to
+ be processed. This method can only add text of a consistent style.
+ To add lots of different styles, call it lots of times.
+ \param text The text. \b Note: only a \em pointer is stored. Do not
+ mess with the text until after you have called
+ calculateFlow().
+ \param style The font style. Layout will hold a reference to this
+ object for the duration of its ownership, ie until you
+ call clear() or the class is destroyed. Must not be NULL.
+ \param source Pointer to object that is source of text.
+ \param optional_attributes A structure containing additional options
+ for this text. See OptionalTextTagAttrs. The values are
+ copied to internal storage before this method returns.
+ \param optional_attributes_offset It is convenient for callers to be
+ able to use the same \a optional_attributes structure for
+ several sequential text fields, in which case the vectors
+ will need to be offset. This parameter causes the <i>n</i>th
+ element of all the vectors to be read as if it were the
+ first.
+ \param text_begin Used for selecting only a substring of \a text
+ to process.
+ \param text_end Used for selecting only a substring of \a text
+ to process.
+ */
+ void appendText(Glib::ustring const &text, SPStyle *style, SPObject *source, OptionalTextTagAttrs const *optional_attributes, unsigned optional_attributes_offset, Glib::ustring::const_iterator text_begin, Glib::ustring::const_iterator text_end);
+ inline void appendText(Glib::ustring const &text, SPStyle *style, SPObject *source, OptionalTextTagAttrs const *optional_attributes = nullptr, unsigned optional_attributes_offset = 0)
+ {appendText(text, style, source, optional_attributes, optional_attributes_offset, text.begin(), text.end());}
+
+ /** Control codes are metadata in the text stream to signify items
+ that occupy real space (unlike style changes) but don't belong in the
+ text string. See TextControlCode for the types available.
+
+ A control code \em cannot be the first item in the input stream. Use
+ appendText() with an empty string to set up the paragraph properties.
+ \param code A member of the TextFlowControlCode enumeration.
+ \param width The width in pixels that this item occupies.
+ \param ascent The number of pixels above the text baseline that this
+ control code occupies.
+ \param descent The number of pixels below the text baseline that this
+ control code occupies.
+ \param source Pointer to object that is source of control code.
+ Note that for some control codes (eg tab) the values of the \a width,
+ \a ascender and \a descender are implied by the surrounding text (and
+ in the case of tabs, the values set in tab_stops) so the values you pass
+ here are ignored.
+ */
+ void appendControlCode(TextControlCode code, SPObject *source, double width = 0.0, double ascent = 0.0, double descent = 0.0);
+
+ /** Stores another shape inside which to flow the text. If this method
+ is never called then no automatic wrapping is done and lines will
+ continue to infinity if necessary. Text can be flowed inside multiple
+ shapes in sequence, like with frames in a DTP package. If the text flows
+ past the end of the last shape all remaining text is ignored.
+
+ \param shape The Shape to use next in the flow. The storage for this
+ is managed by the caller, and need only be valid for
+ the duration of the call to calculateFlow().
+ \param display_align The vertical alignment of the text within this
+ shape. See XSL1.0 section 7.13.4. The behaviour of
+ settings other than DISPLAY_ALIGN_BEFORE when using
+ non-rectangular shapes is undefined.
+ */
+ void appendWrapShape(Shape const *shape, DisplayAlign display_align = DISPLAY_ALIGN_BEFORE);
+
+ // ************************** textLength and friends *************************
+
+ /** Gives the length target of this layout, as given by textLength attribute.
+
+ FIXME: by putting it here we only support @textLength on text and flowRoot, not on any
+ spans inside. For spans, we will need to add markers of start and end of a textLength span
+ into the _input_stream. These spans can nest (SVG 1.1, section 10.5). After a first layout
+ calculation, we would go through the input stream and, for each end of a textLength span,
+ go through its items, choose those where it wasn't yet set by a nested span, calculate
+ their number of characters, divide the length deficit by it, and set set the
+ textLengthMultiplier for those characters only. For now we do this for the entire layout,
+ without dealing with spans.
+ */
+ SVGLength textLength;
+
+ /** How do we meet textLength if specified: by letterspacing or by scaling horizontally */
+ LengthAdjust lengthAdjust = LENGTHADJUST_SPACING;
+
+ /** By how much each character needs to be wider or narrower, using the specified lengthAdjust
+ strategy, for the layout to meet its textLength target. Is set to non-zero after the layout
+ is calculated for the first time, then it is recalculated with each glyph getting its adjustment. */
+ /** This one is used by scaling strategies: each glyph width is multiplied by this */
+ double textLengthMultiplier = 1;
+ /** This one is used by letterspacing strategy: to each glyph width, this is added */
+ double textLengthIncrement = 0;
+
+ /** Get the actual spacing increment if it's due with the current values of above stuff, otherwise 0 */
+ double getTextLengthIncrementDue() const;
+ /** Get the actual scale multiplier if it's due with the current values of above stuff, otherwise 1 */
+ double getTextLengthMultiplierDue() const;
+
+ /** Get actual length of layout, by summing span lengths. For one-line non-flowed text, just
+ the width; for multiline non-flowed, sum of lengths of all lines; for flowed text, sum of
+ scanline widths for all non-last lines plus text width of last line. */
+ double getActualLength() const;
+
+
+ // ************************** doing the actual flowing *************************
+
+ /** \name Processing
+ The method to do the actual work of converting text into glyphs.
+ */
+ //@{
+
+ /** Takes all the stuff you set with the members above here and creates
+ a load of glyphs for use with the members below here. All iterators on
+ this object will be invalidated (but can be fixed with validateIterator().
+ The implementation just creates a new Layout::Calculator and calls its
+ Calculator::Calculate() method, so if you want more details on the
+ internals, go there.
+ \return false on failure.
+ */
+ bool calculateFlow();
+
+ //@}
+
+ // ************************** operating on the output glyphs *************************
+
+ /** \name Output
+ Methods for reading and interpreting the output glyphs. See also
+ Layout::iterator.
+ */
+ //@{
+
+ /** Returns true if there are some glyphs in this object, ie whether
+ computeFlow() has been called on a non-empty input since the object was
+ created or the last call to clear(). */
+ inline bool outputExists() const
+ {return !_characters.empty();}
+
+ /** Adds all the output glyphs to \a in_arena using the given \a paintbox.
+ \param in_arena The arena to add the glyphs group to
+ \param paintbox The current rendering tile
+ */
+ void show(DrawingGroup *in_arena, Geom::OptRect const &paintbox) const;
+
+ /** Calculates the smallest rectangle completely enclosing all the
+ glyphs.
+ \param bounding_box Where to store the box
+ \param transform The transform to be applied to the entire object
+ prior to calculating its bounds.
+ */
+ Geom::OptRect bounds(Geom::Affine const &transform, bool with_stroke = false, int start = -1, int length = -1) const;
+
+ /** Sends all the glyphs to the given print context.
+ \param ctx I have
+ \param pbox no idea
+ \param dbox what these
+ \param bbox parameters
+ \param ctm do yet
+ */
+ void print(SPPrintContext *ctx, Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox, Geom::Affine const &ctm) const;
+
+ /** Renders all the glyphs to the given Cairo rendering context.
+ \param ctx The Cairo rendering context to be used
+ */
+ void showGlyphs(CairoRenderContext *ctx) const;
+
+ /** Returns the font family of the indexed span */
+ Glib::ustring getFontFamily(unsigned span_index) const;
+
+#if DEBUG_TEXTLAYOUT_DUMPASTEXT
+ /** debug and unit test method. Creates a textual representation of the
+ contents of this object. The output is designed to be both human-readable
+ and comprehensible when diffed with a known-good dump. */
+ Glib::ustring dumpAsText() const;
+#endif
+
+ /** Moves all the glyphs in the structure so that the baseline of all
+ the characters sits neatly along the path specified. If the text has
+ more than one line the results are undefined. The 'align' means to
+ use the SVG align method as documented in SVG1.1 section 10.13.2.
+ NB: njh has suggested that it would be cool if we could flow from
+ shape to path and back again. This is possible, so this method will be
+ removed at some point.
+ A pointer to \a path is retained by the class for use by the cursor
+ positioning functions. */
+ void fitToPathAlign(SVGLength const &startOffset, Path const &path);
+
+ /** Convert the specified range of characters into their bezier
+ outlines.
+ */
+ std::unique_ptr<SPCurve> convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const;
+ std::unique_ptr<SPCurve> convertToCurves() 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. */
+ void transform(Geom::Affine const &transform);
+
+ //@}
+
+ // **********
+
+ /** \name Output (Iterators)
+ Methods for operating with the Layout::iterator class. The method
+ names ending with 'Index' return 0-based offsets of the number of
+ items since the beginning of the flow.
+ */
+ //@{
+
+ /** Returns an iterator pointing at the first glyph of the flowed output.
+ The first glyph is also the first character, line, paragraph, etc. */
+ inline iterator begin() const;
+
+ /** Returns an iterator pointing just past the end of the last glyph,
+ which is also just past the end of the last chunk, span, etc, etc. */
+ inline iterator end() const;
+
+ /** Returns an iterator pointing at the given character index. This
+ index should be related to the result from a prior call to
+ iteratorToCharIndex(). */
+ inline iterator charIndexToIterator(int char_index) const;
+
+ /** Returns the character index from the start of the flow represented
+ by the given iterator. This number isn't very useful, except for when
+ editing text it will stay valid across calls to computeFlow() and will
+ change in predictable ways when characters are added and removed. It's
+ also useful when transitioning old code. */
+ inline int iteratorToCharIndex(iterator const &it) const;
+
+ /** Checks the validity of the given iterator over the current layout.
+ If it points to a position out of the bounds for this layout it will
+ be corrected to the nearest valid position. If you pass an iterator
+ belonging to a different layout it will be converted to one for this
+ layout. */
+ inline void validateIterator(iterator *it) const;
+
+ /** Returns an iterator pointing to the cursor position for a mouse
+ click at the given coordinates. */
+ iterator getNearestCursorPositionTo(double x, double y) const;
+ inline iterator getNearestCursorPositionTo(Geom::Point const &point) const;
+
+ /** Returns an iterator pointing to the letter whose bounding box contains
+ the given coordinates. end() if the point is not over any letter. The
+ iterator will \em not point at the specific glyph within the character. */
+ iterator getLetterAt(double x, double y) const;
+ inline iterator getLetterAt(Geom::Point &point) const;
+
+ /* Returns an iterator pointing to the character in the output which
+ was created from the given input. If the character at the given byte
+ offset was removed (soft hyphens, for example) the next character after
+ it is returned. If no input was added with the given object, end() is
+ returned. If more than one input has the same object, the first will
+ be used regardless of the value of \a text_iterator. If
+ \a text_iterator is out of bounds, the first or last character belonging
+ to the given input will be returned accordingly.
+ iterator sourceToIterator(SPObject *source, Glib::ustring::const_iterator text_iterator) const;
+ */
+
+ /** Returns an iterator pointing to the first character in the output
+ which was created from the given source. If \a source object is invalid,
+ end() is returned. If more than one input has the same object, the
+ first one will be used. */
+ iterator sourceToIterator(SPObject *source) const;
+
+ // many functions acting on iterators, most of which are obvious
+ // also most of them don't check that \a it != end(). Be careful.
+
+ /** Returns the bounding box of the given glyph, and its rotation.
+ The centre of rotation is the horizontal centre of the box at the
+ text baseline. */
+ Geom::OptRect glyphBoundingBox(iterator const &it, double *rotation) const;
+
+ /** Returns the zero-based line number of the character pointed to by
+ \a it. */
+ inline unsigned lineIndex(iterator const &it) const;
+
+ /** Returns the zero-based number of the shape which contains the
+ character pointed to by \a it. */
+ inline unsigned shapeIndex(iterator const &it) const;
+
+ /** Returns true if the character at \a it is a whitespace, as defined
+ by Pango. This is not meant to be used for picking out words from the
+ output, use iterator::nextStartOfWord() and friends instead. */
+ inline bool isWhitespace(iterator const &it) const;
+
+ /** Returns character pointed to by \a it. If \a it == end() the result is undefined. */
+ inline gchar characterAt(iterator const &it) const;
+
+ /** Returns true if the text at \a it is hidden (i.e. overflowed). */
+ bool isHidden(iterator const &it) const;
+
+ /** Discovers where the character pointed to by \a it came from, by
+ retrieving the object that was passed to the call to appendText() or
+ appendControlCode() which generated that output. If \a it == end()
+ then NULL is returned as the object. If the character was generated
+ from a call to appendText() then the optional \a text_iterator
+ parameter is set to point to the actual character, otherwise
+ \a text_iterator is unaltered. */
+ void getSourceOfCharacter(iterator const &it, SPObject **source, Glib::ustring::iterator *text_iterator = nullptr) const;
+
+ /** For latin text, the left side of the character, on the baseline */
+ Geom::Point characterAnchorPoint(iterator const &it) const;
+
+ /** For left aligned text, the leftmost end of the baseline
+ For rightmost text, the rightmost... you probably got it by now ;-)*/
+ std::optional<Geom::Point> baselineAnchorPoint() const;
+
+ Geom::Path baseline() const;
+
+ /** This is that value to apply to the x,y attributes of tspan role=line
+ elements, and hence it takes alignment into account. */
+ Geom::Point chunkAnchorPoint(iterator const &it) const;
+
+ /** Returns the box extents (not ink extents) of the given character.
+ The centre of rotation is at the horizontal centre of the box on the
+ text baseline. */
+ Geom::Rect characterBoundingBox(iterator const &it, double *rotation = nullptr) const;
+
+ /** Basically uses characterBoundingBox() on all the characters from
+ \a start to \a end and returns the union of these boxes. The return value
+ is a list of zero or more quadrilaterals specified by a group of four
+ points for each, thus size() is always a multiple of four. */
+ std::vector<Geom::Point> createSelectionShape(iterator const &it_start, iterator const &it_end, Geom::Affine const &transform) const;
+
+ /** Returns true if \a it points to a character which is a valid cursor
+ position, as defined by Pango. */
+ inline bool isCursorPosition(iterator const &it) const;
+
+ /** Gets the ideal cursor shape for a given iterator. The result is
+ undefined if \a it is not at a valid cursor position.
+ \param it The location in the output
+ \param position The pixel location of the centre of the 'bottom' of
+ the cursor.
+ \param height The height in pixels of the surrounding text
+ \param rotation The angle to draw from \a position. Radians, zero up,
+ increasing clockwise.
+ */
+ void queryCursorShape(iterator const &it, Geom::Point &position, double &height, double &rotation) const;
+
+ /** Returns true if \a it points to a character which is a the start of
+ a word, as defined by Pango. */
+ inline bool isStartOfWord(iterator const &it) const;
+
+ /** Returns true if \a it points to a character which is a the end of
+ a word, as defined by Pango. */
+ inline bool isEndOfWord(iterator const &it) const;
+
+ /** Returns true if \a it points to a character which is a the start of
+ a sentence, as defined by Pango. */
+ inline bool isStartOfSentence(iterator const &it) const;
+
+ /** Returns true if \a it points to a character which is a the end of
+ a sentence, as defined by Pango. */
+ inline bool isEndOfSentence(iterator const &it) const;
+
+ /** Returns the zero-based number of the paragraph containing the
+ character pointed to by \a it. */
+ inline unsigned paragraphIndex(iterator const &it) const;
+
+ /** Returns the actual alignment used for the paragraph containing
+ the character pointed to by \a it. This means that the CSS 'start'
+ and 'end' are correctly translated into LEFT or RIGHT according to
+ the paragraph's directionality. For vertical text, LEFT is top
+ alignment and RIGHT is bottom. */
+ inline Alignment paragraphAlignment(iterator const &it) const;
+
+ /** Returns kerning information which could cause the current output
+ to be exactly reproduced if the letter and word spacings were zero and
+ full justification was not used. The x and y arrays are not used, but
+ they are cleared. The dx applied to the first character in a chunk
+ will always be zero. If the region between \a from and \a to crosses
+ a line break then the results may be surprising, and are undefined.
+ Trailing zeros on the returned arrays will be trimmed. */
+ void simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const;
+
+ //@}
+
+
+ /**
+ * Keep track of font metrics. Two use cases:
+ * 1. Keep track of ascent, descent, and x-height of an individual font.
+ * 2. Keep track of effective ascent and descent that includes half-leading.
+ *
+ * Note: Leading refers to the "external" leading which is added (subtracted) due to
+ * a computed value of 'line-height' that differs from 'font-size'. "Internal" leading
+ * which is specified inside a font is not used in CSS. The 'font-size' is based on
+ * the font's em size which is 'ascent' + 'descent'.
+ *
+ * This structure was renamed (and modified) from "LineHeight".
+ *
+ * It's useful for this to be public so that ScanlineMaker can use it.
+ */
+ class FontMetrics {
+
+ public:
+ FontMetrics() { reset(); }
+
+ void reset() {
+ ascent = 0.8;
+ descent = 0.2;
+ xheight = 0.5;
+ ascent_max = 0.8;
+ descent_max = 0.2;
+ }
+
+ void set( font_instance *font );
+
+ // CSS 2.1 dictates that font-size is based on em-size which is defined as ascent + descent
+ inline double emSize() const {return ascent + descent;}
+ // Alternatively name function for use 2.
+ inline double lineSize() const { return ascent + descent; }
+ inline void setZero() {ascent = descent = xheight = ascent_max = descent_max = 0.0;}
+
+ // For scaling for 'font-size'.
+ inline FontMetrics& operator*=(double x) {
+ ascent *= x; descent *= x; xheight *= x; ascent_max *= x; descent_max *= x;
+ return *this;
+ }
+
+ /// Save the larger values of ascent and descent between this and other. Needed for laying
+ /// out a line with mixed font-sizes, fonts, or line spacings.
+ void max(FontMetrics const &other);
+
+ /// Calculate the effective ascent and descent including half "leading".
+ void computeEffective( const double &line_height );
+
+ inline double getTypoAscent() const {return ascent; }
+ inline double getTypoDescent() const {return descent; }
+ inline double getXHeight() const {return xheight; }
+ inline double getMaxAscent() const {return ascent_max; }
+ inline double getMaxDescent() const {return descent_max; }
+
+ // private:
+ double ascent; // Typographic ascent.
+ double descent; // Typographic descent. (Normally positive).
+ double xheight; // Height of 'x' measured from alphabetic baseline.
+ double ascent_max; // Maximum ascent of all glyphs in font.
+ double descent_max; // Maximum descent of all glyphs in font.
+
+ }; // End FontMetrics
+
+ /** The strut is the minimum value used in calculating line height. */
+ FontMetrics strut;
+
+private:
+ /** Erases all the stuff set by the owner as input, ie #_input_stream
+ and #_input_wrap_shapes. */
+ void _clearInputObjects();
+
+ /** Erases all the stuff output by computeFlow(). Glyphs and things. */
+ void _clearOutputObjects();
+
+ static const gunichar UNICODE_SOFT_HYPHEN;
+
+ // ******************* input flow
+
+ enum InputStreamItemType {TEXT_SOURCE, CONTROL_CODE};
+
+ class InputStreamItem {
+ public:
+ virtual ~InputStreamItem() = default;
+ virtual InputStreamItemType Type() =0;
+ SPObject *source;
+ };
+
+ /** Represents a text item in the input stream. See #_input_stream.
+ Most of the members are copies of the values passed to appendText(). */
+ class InputStreamTextSource : public InputStreamItem {
+ public:
+ InputStreamItemType Type() override {return TEXT_SOURCE;}
+ ~InputStreamTextSource() override;
+ Glib::ustring const *text; /// owned by the caller
+ Glib::ustring::const_iterator text_begin, text_end;
+ int text_length; /// in characters, from text_start to text_end only
+ SPStyle *style;
+ /** These vectors can (often will) be shorter than the text
+ in this source, but never longer. */
+ std::vector<SVGLength> x;
+ std::vector<SVGLength> y;
+ std::vector<SVGLength> dx;
+ std::vector<SVGLength> dy;
+ std::vector<SVGLength> rotate;
+ SVGLength textLength;
+ LengthAdjust lengthAdjust;
+ Glib::ustring lang;
+
+ // a few functions for some of the more complicated style accesses
+ /// The return value must be freed with pango_font_description_free()
+ PangoFontDescription *styleGetFontDescription() const;
+ font_instance *styleGetFontInstance() const;
+ Direction styleGetBlockProgression() const;
+ SPCSSTextOrientation styleGetTextOrientation() const;
+ SPCSSBaseline styleGetDominantBaseline() const;
+ Alignment styleGetAlignment(Direction para_direction, bool try_text_align) const;
+ };
+
+ /** Represents a control code item in the input stream. See
+ #_input_streams. All the members are copies of the values passed to
+ appendControlCode(). */
+ class InputStreamControlCode : public InputStreamItem {
+ public:
+ InputStreamItemType Type() override {return CONTROL_CODE;}
+ TextControlCode code;
+ double ascent;
+ double descent;
+ double width;
+ };
+
+ /** This is our internal storage for all the stuff passed to the
+ appendText() and appendControlCode() functions. */
+ std::vector<InputStreamItem*> _input_stream;
+
+ /** The parameters to appendText() are allowed to be a little bit
+ complex. This copies them to be the right length and starting at zero.
+ We also don't want to write five bits of identical code just with
+ different variable names. */
+ static void _copyInputVector(std::vector<SVGLength> const &input_vector, unsigned input_offset, std::vector<SVGLength> *output_vector, size_t max_length);
+
+ /** The overall block-progression of the whole flow. */
+ inline Direction _blockProgression() const
+ {
+ if(!_input_stream.empty())
+ return static_cast<InputStreamTextSource*>(_input_stream.front())->styleGetBlockProgression();
+ return TOP_TO_BOTTOM;
+ }
+
+ /** The overall text-orientation of the whole flow. */
+ inline SPCSSTextOrientation _blockTextOrientation() const
+ {
+ if(!_input_stream.empty())
+ return static_cast<InputStreamTextSource*>(_input_stream.front())->styleGetTextOrientation();
+ return SP_CSS_TEXT_ORIENTATION_MIXED;
+ }
+
+ /** The overall text-orientation of the whole flow. */
+ inline SPCSSBaseline _blockBaseline() const
+ {
+ if(!_input_stream.empty())
+ return static_cast<InputStreamTextSource*>(_input_stream.front())->styleGetDominantBaseline();
+ return SP_CSS_BASELINE_AUTO;
+ }
+
+ /** so that LEFT_TO_RIGHT == RIGHT_TO_LEFT but != TOP_TO_BOTTOM */
+ static bool _directions_are_orthogonal(Direction d1, Direction d2);
+
+ /** If the output is empty callers still want to be able to call
+ queryCursorShape() and get a valid answer so, while #_input_wrap_shapes
+ can still be considered valid, we need to precompute the cursor shape
+ for this case. */
+ void _calculateCursorShapeForEmpty();
+
+ struct CursorShape {
+ Geom::Point position;
+ double height;
+ double rotation;
+ } _empty_cursor_shape;
+
+ // ******************* input shapes
+
+ struct InputWrapShape {
+ Shape const *shape; /// as passed to Layout::appendWrapShape()
+ DisplayAlign display_align; /// as passed to Layout::appendWrapShape()
+ };
+ std::vector<InputWrapShape> _input_wrap_shapes;
+
+ // ******************* output
+
+ /** as passed to fitToPathAlign() */
+ Path const *_path_fitted = nullptr;
+
+ struct Glyph;
+ struct Character;
+ struct Span;
+ struct Chunk;
+ struct Line;
+ struct Paragraph;
+
+ // A glyph
+ struct Glyph {
+ int glyph;
+ unsigned in_character;
+ bool hidden;
+ float x; /// relative to the start of the chunk
+ float y; /// relative to the current line's baseline
+ float rotation; /// absolute, modulo any object transforms, which we don't know about
+ Orientation orientation; /// Orientation of glyph in vertical text
+ float advance; /// for positioning next glyph
+ float vertical_scale; /// to implement lengthAdjust="spacingAndGlyphs" that must scale glyphs only horizontally; instead we change font size and then undo that change vertically only
+ inline Span const & span (Layout const *l) const {return l->_spans[l->_characters[in_character].in_span];}
+ inline Chunk const & chunk(Layout const *l) const {return l->_chunks[l->_spans[l->_characters[in_character].in_span].in_chunk];}
+ inline Line const & line (Layout const *l) const {return l->_lines[l->_chunks[l->_spans[l->_characters[in_character].in_span].in_chunk].in_line];}
+ };
+
+ // A unicode character
+ struct Character {
+ unsigned in_span;
+ float x; /// relative to the start of the *span* (so we can do block-progression)
+ PangoLogAttr char_attributes;
+ gchar the_char = '#';
+ int in_glyph; /// will be -1 if this character has no visual representation
+ inline Span const & span (Layout const *l) const {return l->_spans[in_span];}
+ inline Chunk const & chunk (Layout const *l) const {return l->_chunks[l->_spans[in_span].in_chunk];}
+ inline Line const & line (Layout const *l) const {return l->_lines[l->_chunks[l->_spans[in_span].in_chunk].in_line];}
+ inline Paragraph const & paragraph(Layout const *l) const {return l->_paragraphs[l->_lines[l->_chunks[l->_spans[in_span].in_chunk].in_line].in_paragraph];}
+ // to get the advance width of a character, subtract the x values if it's in the middle of a span, or use span.x_end if it's at the end
+ };
+
+ // A collection of characters that share the same style and position start (<text> or <tspan> x, y attributes).
+ struct Span {
+ unsigned in_chunk;
+ font_instance *font;
+ float font_size;
+ float x_start; /// relative to the start of the chunk
+ float x_end; /// relative to the start of the chunk
+ float y_offset; /// relative to line baseline (without baseline shift)
+ inline float width() const {return std::abs(x_start - x_end);}
+ FontMetrics line_height;
+ double baseline_shift; /// relative to the line's baseline (CSS)
+ SPCSSTextOrientation text_orientation;
+ Direction direction; /// See CSS3 section 3.2. Either rtl or ltr
+ Direction block_progression; /// See CSS3 section 3.2. The direction in which lines go.
+ unsigned in_input_stream_item;
+ Glib::ustring::const_iterator input_stream_first_character;
+ inline Chunk const & chunk (Layout const *l) const {return l->_chunks[in_chunk]; }
+ inline Line const & line (Layout const *l) const {return l->_lines[l->_chunks[in_chunk].in_line]; }
+ inline Paragraph const & paragraph(Layout const *l) const {return l->_paragraphs[l->_lines[l->_chunks[in_chunk].in_line].in_paragraph];}
+ };
+
+ // A part of a line that is not broken.
+ struct Chunk {
+ unsigned in_line;
+ double left_x;
+ };
+
+ // A line of text. Depending on the shape, it may contain one or more chunks.
+ struct Line {
+ unsigned in_paragraph;
+ double baseline_y;
+ unsigned in_shape;
+ bool hidden;
+ };
+
+ // A paragraph. SVG 2 does not contain native paragraphs.
+ struct Paragraph {
+ Direction base_direction; /// can be overridden by child Span objects
+ Alignment alignment;
+ };
+
+ std::vector<Paragraph> _paragraphs;
+ std::vector<Line> _lines;
+ std::vector<Chunk> _chunks;
+ std::vector<Span> _spans;
+ std::vector<Character> _characters;
+ std::vector<Glyph> _glyphs;
+
+ /** gets the overall matrix that transforms the given glyph from local
+ space to world space. */
+ void _getGlyphTransformMatrix(int glyph_index, Geom::Affine *matrix) const;
+
+ // loads of functions to drill down the object tree, all of them
+ // annoyingly similar and all of them requiring predicate functors.
+ // I'll be buggered if I can find a way to make it work with
+ // functions or with a templated functor, so macros it is.
+#define EMIT_PREDICATE(name, object_type, index_generator) \
+ class name { \
+ Layout const * const _flow; \
+ public: \
+ inline name(Layout const *flow) : _flow(flow) {} \
+ inline bool operator()(object_type const &object, unsigned index) \
+ {g_assert(_flow); return index_generator < index;} \
+ }
+// end of macro
+ EMIT_PREDICATE(PredicateLineToSpan, Span, _flow->_chunks[object.in_chunk].in_line);
+ EMIT_PREDICATE(PredicateLineToCharacter, Character, _flow->_chunks[_flow->_spans[object.in_span].in_chunk].in_line);
+ EMIT_PREDICATE(PredicateSpanToCharacter, Character, object.in_span);
+ EMIT_PREDICATE(PredicateSourceToCharacter, Character, _flow->_spans[object.in_span].in_input_stream_item);
+
+ inline unsigned _lineToSpan(unsigned line_index) const
+ {return std::lower_bound(_spans.begin(), _spans.end(), line_index, PredicateLineToSpan(this)) - _spans.begin();}
+ inline unsigned _lineToCharacter(unsigned line_index) const
+ {return std::lower_bound(_characters.begin(), _characters.end(), line_index, PredicateLineToCharacter(this)) - _characters.begin();}
+ inline unsigned _spanToCharacter(unsigned span_index) const
+ {return std::lower_bound(_characters.begin(), _characters.end(), span_index, PredicateSpanToCharacter(this)) - _characters.begin();}
+ inline unsigned _sourceToCharacter(unsigned source_index) const
+ {return std::lower_bound(_characters.begin(), _characters.end(), source_index, PredicateSourceToCharacter(this)) - _characters.begin();}
+
+ /** given an x and y coordinate and a line number, returns an iterator
+ pointing to the closest cursor position on that line to the
+ coordinate.
+ ('y' is needed to handle cases where multiline text is simulated via the 'y' attribute.) */
+ iterator _cursorXOnLineToIterator(unsigned line_index, double local_x, double local_y = 0) const;
+
+ /** calculates the width of a chunk, which is the largest x
+ coordinate (start or end) of the spans contained within it. */
+ double _getChunkWidth(unsigned chunk_index) const;
+};
+
+/** \brief Holds a position within the glyph output of Layout.
+
+Used to access the output of a Layout, query information and generally
+move around in it. See Layout for a glossary of the names of functions.
+
+I'm not going to document all the methods because most of their names make
+their function self-evident.
+
+A lot of the functions would do the same thing in a naive implementation
+for latin-only text, for example nextCharacter(), nextCursorPosition() and
+cursorRight(). Generally it's fairly obvious which one you should use in a
+given situation, but sometimes you might need to put some thought in to it.
+
+All the methods return false if the requested action would have caused the
+current position to move out of bounds. In this case the position is moved
+to either begin() or end(), depending on which direction you were going.
+
+Note that some characters do not have a glyph representation (eg line
+breaks), so if you try using prev/nextGlyph() from one of these you're
+heading for a crash.
+*/
+class Layout::iterator {
+public:
+ friend class Layout;
+ // this is just so you can create uninitialised iterators - don't actually try to use one
+ iterator() :
+ _parent_layout(nullptr),
+ _glyph_index(-1),
+ _char_index(0),
+ _cursor_moving_vertically(false),
+ _x_coordinate(0.0){}
+ // no copy constructor required, the default does what we want
+ bool operator== (iterator const &other) const
+ {return _glyph_index == other._glyph_index && _char_index == other._char_index;}
+ bool operator!= (iterator const &other) const
+ {return _glyph_index != other._glyph_index || _char_index != other._char_index;}
+
+ /* mustn't compare _glyph_index in these operators because for characters
+ that don't have glyphs (line breaks, elided soft hyphens, etc), the glyph
+ index is -1 which makes them not well-ordered. To be honest, iterating by
+ glyphs is not very useful and should be avoided. */
+ bool operator< (iterator const &other) const
+ {return _char_index < other._char_index;}
+ bool operator<= (iterator const &other) const
+ {return _char_index <= other._char_index;}
+ bool operator> (iterator const &other) const
+ {return _char_index > other._char_index;}
+ bool operator>= (iterator const &other) const
+ {return _char_index >= other._char_index;}
+
+ /* **** visual-oriented methods **** */
+
+ //glyphs
+ inline bool prevGlyph();
+ inline bool nextGlyph();
+
+ //span
+ bool prevStartOfSpan();
+ bool thisStartOfSpan();
+ bool nextStartOfSpan();
+
+ //chunk
+ bool prevStartOfChunk();
+ bool thisStartOfChunk();
+ bool nextStartOfChunk();
+
+ //line
+ bool prevStartOfLine();
+ bool thisStartOfLine();
+ bool nextStartOfLine();
+ bool thisEndOfLine();
+
+ //shape
+ bool prevStartOfShape();
+ bool thisStartOfShape();
+ bool nextStartOfShape();
+
+ /* **** text-oriented methods **** */
+
+ //characters
+ inline bool nextCharacter();
+ inline bool prevCharacter();
+
+ bool nextCursorPosition();
+ bool prevCursorPosition();
+ bool nextLineCursor(int n = 1);
+ bool prevLineCursor(int n = 1);
+
+ //words
+ bool nextStartOfWord();
+ bool prevStartOfWord();
+ bool nextEndOfWord();
+ bool prevEndOfWord();
+
+ //sentences
+ bool nextStartOfSentence();
+ bool prevStartOfSentence();
+ bool nextEndOfSentence();
+ bool prevEndOfSentence();
+
+ //paragraphs
+ bool prevStartOfParagraph();
+ bool thisStartOfParagraph();
+ bool nextStartOfParagraph();
+ //no endOfPara methods because that's just the previous char
+
+ //sources
+ bool prevStartOfSource();
+ bool thisStartOfSource();
+ bool nextStartOfSource();
+
+ //logical cursor movement
+ bool cursorUp(int n = 1);
+ bool cursorDown(int n = 1);
+ bool cursorLeft();
+ bool cursorRight();
+
+ //logical cursor movement (by word or paragraph)
+ bool cursorUpWithControl();
+ bool cursorDownWithControl();
+ bool cursorLeftWithControl();
+ bool cursorRightWithControl();
+
+private:
+ Layout const *_parent_layout;
+ int _glyph_index; /// index into Layout::glyphs, or -1
+ unsigned _char_index; /// index into Layout::character
+ bool _cursor_moving_vertically;
+ /** for cursor up/down movement we must maintain the x position where
+ we started so the cursor doesn't 'drift' left or right with the repeated
+ quantization to character boundaries. */
+ double _x_coordinate;
+
+ inline iterator(Layout const *p, unsigned c, int g)
+ : _parent_layout(p), _glyph_index(g), _char_index(c), _cursor_moving_vertically(false), _x_coordinate(0.0) {}
+ inline iterator(Layout const *p, unsigned c)
+ : _parent_layout(p), _glyph_index(p->_characters[c].in_glyph), _char_index(c), _cursor_moving_vertically(false), _x_coordinate(0.0) {}
+ // no dtor required
+ void beginCursorUpDown(); /// stores the current x coordinate so that the cursor won't drift. See #_x_coordinate
+
+ /** moves forward or backwards one cursor position according to the
+ directionality of the current paragraph, but ignoring block progression.
+ Helper for the cursor*() functions. */
+ bool _cursorLeftOrRightLocalX(Direction direction);
+
+ /** moves forward or backwards by until the next character with
+ is_word_start according to the directionality of the current paragraph,
+ but ignoring block progression. Helper for the cursor*WithControl()
+ functions. */
+ bool _cursorLeftOrRightLocalXByWord(Direction direction);
+};
+
+// ************************** inline methods
+
+inline Layout::iterator Layout::begin() const
+ {return iterator(this, 0, 0);}
+
+inline Layout::iterator Layout::end() const
+ {return iterator(this, _characters.size(), _glyphs.size());}
+
+inline Layout::iterator Layout::charIndexToIterator(int char_index) const
+{
+ if (char_index < 0) return begin();
+ if (char_index >= (int)_characters.size()) return end();
+ return iterator(this, char_index);
+}
+
+inline int Layout::iteratorToCharIndex(Layout::iterator const &it) const
+ {return it._char_index;}
+
+inline void Layout::validateIterator(Layout::iterator *it) const
+{
+ it->_parent_layout = this;
+ if (it->_char_index >= _characters.size()) {
+ it->_char_index = _characters.size();
+ it->_glyph_index = _glyphs.size();
+ } else
+ it->_glyph_index = _characters[it->_char_index].in_glyph;
+}
+
+inline Layout::iterator Layout::getNearestCursorPositionTo(Geom::Point const &point) const
+ {return getNearestCursorPositionTo(point[0], point[1]);}
+
+inline Layout::iterator Layout::getLetterAt(Geom::Point &point) const
+ {return getLetterAt(point[0], point[1]);}
+
+inline unsigned Layout::lineIndex(iterator const &it) const
+ {return it._char_index == _characters.size() ? _lines.size() - 1 : _characters[it._char_index].chunk(this).in_line;}
+
+inline unsigned Layout::shapeIndex(iterator const &it) const
+ {return it._char_index == _characters.size() ? _input_wrap_shapes.size() - 1 : _characters[it._char_index].line(this).in_shape;}
+
+inline bool Layout::isWhitespace(iterator const &it) const
+ {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_white;}
+
+inline gchar Layout::characterAt(iterator const &it) const
+{
+ return _characters[it._char_index].the_char;
+}
+
+inline bool Layout::isCursorPosition(iterator const &it) const
+ {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_cursor_position;}
+
+inline bool Layout::isStartOfWord(iterator const &it) const
+ {return it._char_index != _characters.size() && _characters[it._char_index].char_attributes.is_word_start;}
+
+inline bool Layout::isEndOfWord(iterator const &it) const
+ {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_word_end;}
+
+inline bool Layout::isStartOfSentence(iterator const &it) const
+ {return it._char_index != _characters.size() && _characters[it._char_index].char_attributes.is_sentence_start;}
+
+inline bool Layout::isEndOfSentence(iterator const &it) const
+ {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_sentence_end;}
+
+inline unsigned Layout::paragraphIndex(iterator const &it) const
+ {return it._char_index == _characters.size() ? _paragraphs.size() - 1 : _characters[it._char_index].line(this).in_paragraph;}
+
+inline Layout::Alignment Layout::paragraphAlignment(iterator const &it) const
+ {return (_paragraphs.size() == 0) ? NONE : _paragraphs[paragraphIndex(it)].alignment;}
+
+inline bool Layout::iterator::nextGlyph()
+{
+ _cursor_moving_vertically = false;
+ if (_glyph_index >= (int)_parent_layout->_glyphs.size() - 1) {
+ if (_glyph_index == (int)_parent_layout->_glyphs.size()) return false;
+ _char_index = _parent_layout->_characters.size();
+ _glyph_index = _parent_layout->_glyphs.size();
+ }
+ else _char_index = _parent_layout->_glyphs[++_glyph_index].in_character;
+ return true;
+}
+
+inline bool Layout::iterator::prevGlyph()
+{
+ _cursor_moving_vertically = false;
+ if (_glyph_index == 0) return false;
+ _char_index = _parent_layout->_glyphs[--_glyph_index].in_character;
+ return true;
+}
+
+inline bool Layout::iterator::nextCharacter()
+{
+ _cursor_moving_vertically = false;
+ if (_char_index + 1 >= _parent_layout->_characters.size()) {
+ if (_char_index == _parent_layout->_characters.size()) return false;
+ _char_index = _parent_layout->_characters.size();
+ _glyph_index = _parent_layout->_glyphs.size();
+ }
+ else _glyph_index = _parent_layout->_characters[++_char_index].in_glyph;
+ return true;
+}
+
+inline bool Layout::iterator::prevCharacter()
+{
+ _cursor_moving_vertically = false;
+ if (_char_index == 0) return false;
+ _glyph_index = _parent_layout->_characters[--_char_index].in_glyph;
+ return true;
+}
+
+}//namespace Text
+}//namespace Inkscape
+
+std::ostream &operator<<(std::ostream &out, const Inkscape::Text::Layout::FontMetrics &f);
+std::ostream &operator<<(std::ostream &out, const Inkscape::Text::Layout::FontMetrics *f);
+
+
+#endif
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/OpenTypeUtil.cpp b/src/libnrtype/OpenTypeUtil.cpp
new file mode 100644
index 0000000..9d3576d
--- /dev/null
+++ b/src/libnrtype/OpenTypeUtil.cpp
@@ -0,0 +1,447 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef USE_PANGO_WIN32
+
+#include "OpenTypeUtil.h"
+
+
+#include <iostream> // For debugging
+#include <memory>
+#include <unordered_map>
+
+// FreeType
+#include FT_FREETYPE_H
+#include FT_MULTIPLE_MASTERS_H
+#include FT_SFNT_NAMES_H
+
+// Harfbuzz
+#include <harfbuzz/hb.h>
+#include <harfbuzz/hb-ft.h>
+#include <harfbuzz/hb-ot.h>
+
+// SVG in OpenType
+#include "io/stream/gzipstream.h"
+#include "io/stream/bufferstream.h"
+
+
+// Utilities used in this file
+
+struct HbSetDeleter {
+ void operator()(hb_set_t* x) { hb_set_destroy(x); }
+};
+using HbSet = std::unique_ptr<hb_set_t, HbSetDeleter>;
+
+void dump_tag( guint32 *tag, Glib::ustring prefix = "", bool lf=true ) {
+ std::cout << prefix
+ << ((char)((*tag & 0xff000000)>>24))
+ << ((char)((*tag & 0x00ff0000)>>16))
+ << ((char)((*tag & 0x0000ff00)>> 8))
+ << ((char)((*tag & 0x000000ff) ));
+ if( lf ) {
+ std::cout << std::endl;
+ }
+}
+
+Glib::ustring extract_tag( guint32 *tag ) {
+ Glib::ustring tag_name;
+ tag_name += ((char)((*tag & 0xff000000)>>24));
+ tag_name += ((char)((*tag & 0x00ff0000)>>16));
+ tag_name += ((char)((*tag & 0x0000ff00)>> 8));
+ tag_name += ((char)((*tag & 0x000000ff) ));
+ return tag_name;
+}
+
+
+// Later (see get_glyphs) we need to lookup the Unicode codepoint for a glyph
+// but there's no direct API for that. So, we need a way to iterate over all
+// glyph mappings and build a reverse map.
+// FIXME: we should handle UVS at some point... or better, work with glyphs directly
+
+// Allows looking up the lowest Unicode codepoint mapped to a given glyph.
+// To do so, it lazily builds a reverse map.
+class GlyphToUnicodeMap {
+protected:
+ hb_font_t* font;
+ HbSet codepointSet;
+
+ std::unordered_map<hb_codepoint_t, hb_codepoint_t> mappings;
+ bool more = true; // false if we have finished iterating the set
+ hb_codepoint_t codepoint = HB_SET_VALUE_INVALID; // current iteration
+public:
+ GlyphToUnicodeMap(hb_font_t* font): font(font), codepointSet(hb_set_create()) {
+ hb_face_collect_unicodes(hb_font_get_face(font), codepointSet.get());
+ }
+
+ hb_codepoint_t lookup(hb_codepoint_t glyph) {
+ // first, try to find it in the mappings we've seen so far
+ if (auto it = mappings.find(glyph); it != mappings.end())
+ return it->second;
+
+ // populate more mappings from the set
+ while ((more = (more && hb_set_next(codepointSet.get(), &codepoint)))) {
+ // get the glyph that this codepoint is associated with, if any
+ hb_codepoint_t tGlyph;
+ if (!hb_font_get_nominal_glyph(font, codepoint, &tGlyph)) continue;
+
+ // save the mapping, and return if this is the one we were looking for
+ mappings.emplace(tGlyph, codepoint);
+ if (tGlyph == glyph) return codepoint;
+ }
+ return 0;
+ }
+};
+
+void get_glyphs(GlyphToUnicodeMap& glyphMap, HbSet& set, Glib::ustring& characters) {
+ hb_codepoint_t glyph = -1;
+ while (hb_set_next(set.get(), &glyph)) {
+ if (auto codepoint = glyphMap.lookup(glyph))
+ characters += codepoint;
+ }
+}
+
+// Make a list of all tables found in the GSUB
+// This list includes all tables regardless of script or language.
+// Use Harfbuzz, Pango's equivalent calls are deprecated.
+void readOpenTypeGsubTable (hb_font_t* hb_font,
+ std::map<Glib::ustring, OTSubstitution>& tables)
+{
+ hb_face_t* hb_face = hb_font_get_face (hb_font);
+
+ tables.clear();
+
+ // First time to get size of array
+ auto script_count = hb_ot_layout_table_get_script_tags(hb_face, HB_OT_TAG_GSUB, 0, nullptr, nullptr);
+ auto const hb_scripts = g_new(hb_tag_t, script_count + 1);
+
+ // Second time to fill array (this two step process was not necessary with Pango).
+ hb_ot_layout_table_get_script_tags(hb_face, HB_OT_TAG_GSUB, 0, &script_count, hb_scripts);
+
+ for(unsigned int i = 0; i < script_count; ++i) {
+ // std::cout << " Script: " << extract_tag(&hb_scripts[i]) << std::endl;
+ auto language_count = hb_ot_layout_script_get_language_tags(hb_face, HB_OT_TAG_GSUB, i, 0, nullptr, nullptr);
+
+ if(language_count > 0) {
+ auto const hb_languages = g_new(hb_tag_t, language_count + 1);
+ hb_ot_layout_script_get_language_tags(hb_face, HB_OT_TAG_GSUB, i, 0, &language_count, hb_languages);
+
+ for(unsigned int j = 0; j < language_count; ++j) {
+ // std::cout << " Language: " << extract_tag(&hb_languages[j]) << std::endl;
+ auto feature_count = hb_ot_layout_language_get_feature_tags(hb_face, HB_OT_TAG_GSUB, i, j, 0, nullptr, nullptr);
+ auto const hb_features = g_new(hb_tag_t, feature_count + 1);
+ hb_ot_layout_language_get_feature_tags(hb_face, HB_OT_TAG_GSUB, i, j, 0, &feature_count, hb_features);
+
+ for(unsigned int k = 0; k < feature_count; ++k) {
+ // std::cout << " Feature: " << extract_tag(&hb_features[k]) << std::endl;
+ tables[ extract_tag(&hb_features[k])];
+ }
+
+ g_free(hb_features);
+ }
+
+ g_free(hb_languages);
+
+ } else {
+
+ // Even if no languages are present there is still the default.
+ // std::cout << " Language: " << " (dflt)" << std::endl;
+ auto feature_count = hb_ot_layout_language_get_feature_tags(hb_face, HB_OT_TAG_GSUB, i,
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
+ 0, nullptr, nullptr);
+ auto const hb_features = g_new(hb_tag_t, feature_count + 1);
+ hb_ot_layout_language_get_feature_tags(hb_face, HB_OT_TAG_GSUB, i,
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
+ 0, &feature_count, hb_features);
+
+ for(unsigned int k = 0; k < feature_count; ++k) {
+ // std::cout << " Feature: " << extract_tag(&hb_features[k]) << std::endl;
+ tables[ extract_tag(&hb_features[k])];
+ }
+
+ g_free(hb_features);
+ }
+ }
+
+ // Find glyphs in OpenType substitution tables ('gsub').
+ // Note that pango's functions are just dummies. Must use harfbuzz.
+
+ GlyphToUnicodeMap glyphMap (hb_font);
+
+ // Loop over all tables
+ for (auto table: tables) {
+
+ // Only look at style substitution tables ('salt', 'ss01', etc. but not 'ssty').
+ // Also look at character substitution tables ('cv01', etc.).
+ bool style =
+ table.first == "case" /* Case-Sensitive Forms */ ||
+ table.first == "salt" /* Stylistic Alternatives */ ||
+ table.first == "swsh" /* Swash */ ||
+ table.first == "cwsh" /* Contextual Swash */ ||
+ table.first == "ornm" /* Ornaments */ ||
+ table.first == "nalt" /* Alternative Annotation */ ||
+ table.first == "hist" /* Historical Forms */ ||
+ (table.first[0] == 's' && table.first[1] == 's' && !(table.first[2] == 't')) ||
+ (table.first[0] == 'c' && table.first[1] == 'v');
+
+ bool ligature = ( table.first == "liga" || // Standard ligatures
+ table.first == "clig" || // Common ligatures
+ table.first == "dlig" || // Discretionary ligatures
+ table.first == "hlig" || // Historical ligatures
+ table.first == "calt" ); // Contextual alternatives
+
+ bool numeric = ( table.first == "lnum" || // Lining numerals
+ table.first == "onum" || // Old style
+ table.first == "pnum" || // Proportional
+ table.first == "tnum" || // Tabular
+ table.first == "frac" || // Diagonal fractions
+ table.first == "afrc" || // Stacked fractions
+ table.first == "ordn" || // Ordinal fractions
+ table.first == "zero" ); // Slashed zero
+
+ if (style || ligature || numeric) {
+
+ unsigned int feature_index;
+ if ( hb_ot_layout_language_find_feature (hb_face, HB_OT_TAG_GSUB,
+ 0, // Assume one script exists with index 0
+ HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
+ HB_TAG(table.first[0],
+ table.first[1],
+ table.first[2],
+ table.first[3]),
+ &feature_index ) ) {
+
+ // std::cout << "Table: " << table.first << std::endl;
+ // std::cout << " Found feature, number: " << feature_index << std::endl;
+ unsigned int lookup_indexes[32];
+ unsigned int lookup_count = 32;
+ int count = hb_ot_layout_feature_get_lookups (hb_face, HB_OT_TAG_GSUB,
+ feature_index,
+ 0, // Start
+ &lookup_count,
+ lookup_indexes );
+ // std::cout << " Lookup count: " << count << " total: " << lookup_count << std::endl;
+
+ for (int i = 0; i < count; ++i) {
+ HbSet glyphs_before (hb_set_create());
+ HbSet glyphs_input (hb_set_create());
+ HbSet glyphs_after (hb_set_create());
+ HbSet glyphs_output (hb_set_create());
+
+ hb_ot_layout_lookup_collect_glyphs (hb_face, HB_OT_TAG_GSUB,
+ lookup_indexes[i],
+ glyphs_before.get(),
+ glyphs_input.get(),
+ glyphs_after.get(),
+ glyphs_output.get() );
+
+ // std::cout << " Populations: "
+ // << " " << hb_set_get_population (glyphs_before)
+ // << " " << hb_set_get_population (glyphs_input)
+ // << " " << hb_set_get_population (glyphs_after)
+ // << " " << hb_set_get_population (glyphs_output)
+ // << std::endl;
+
+ get_glyphs (glyphMap, glyphs_before, tables[table.first].before);
+ get_glyphs (glyphMap, glyphs_input, tables[table.first].input );
+ get_glyphs (glyphMap, glyphs_after, tables[table.first].after );
+ get_glyphs (glyphMap, glyphs_output, tables[table.first].output);
+
+ // std::cout << " Before: " << tables[table.first].before.c_str() << std::endl;
+ // std::cout << " Input: " << tables[table.first].input.c_str() << std::endl;
+ // std::cout << " After: " << tables[table.first].after.c_str() << std::endl;
+ // std::cout << " Output: " << tables[table.first].output.c_str() << std::endl;
+ } // End count (lookups)
+
+ } else {
+ // std::cout << " Did not find '" << table.first << "'!" << std::endl;
+ }
+ }
+
+ }
+
+ g_free(hb_scripts);
+}
+
+// Harfbuzz now as API for variations (Version 2.2, Nov 29 2018).
+// Make a list of all Variation axes with ranges.
+void readOpenTypeFvarAxes(const FT_Face ft_face,
+ std::map<Glib::ustring, OTVarAxis>& axes) {
+
+#if FREETYPE_MAJOR *10000 + FREETYPE_MINOR*100 + FREETYPE_MICRO >= 20701
+ FT_MM_Var* mmvar = nullptr;
+ FT_Multi_Master mmtype;
+ if (FT_HAS_MULTIPLE_MASTERS( ft_face ) && // Font has variables
+ FT_Get_MM_Var( ft_face, &mmvar) == 0 && // We found the data
+ FT_Get_Multi_Master( ft_face, &mmtype) !=0) { // It's not an Adobe MM font
+
+ FT_Fixed coords[mmvar->num_axis];
+ FT_Get_Var_Design_Coordinates( ft_face, mmvar->num_axis, coords );
+
+ for (size_t i = 0; i < mmvar->num_axis; ++i) {
+ FT_Var_Axis* axis = &mmvar->axis[i];
+ axes[axis->name] = OTVarAxis(FTFixedToDouble(axis->minimum),
+ FTFixedToDouble(axis->def),
+ FTFixedToDouble(axis->maximum),
+ FTFixedToDouble(coords[i]),
+ i);
+ }
+
+ // for (auto a: axes) {
+ // std::cout << " " << a.first
+ // << " min: " << a.second.minimum
+ // << " max: " << a.second.maximum
+ // << " set: " << a.second.set_val << std::endl;
+ // }
+
+ }
+
+#endif /* FREETYPE Version */
+}
+
+
+// Harfbuzz now as API for named variations (Version 2.2, Nov 29 2018).
+// Make a list of all Named instances with axis values.
+void readOpenTypeFvarNamed(const FT_Face ft_face,
+ std::map<Glib::ustring, OTVarInstance>& named) {
+
+#if FREETYPE_MAJOR *10000 + FREETYPE_MINOR*100 + FREETYPE_MICRO >= 20701
+ FT_MM_Var* mmvar = nullptr;
+ FT_Multi_Master mmtype;
+ if (FT_HAS_MULTIPLE_MASTERS( ft_face ) && // Font has variables
+ FT_Get_MM_Var( ft_face, &mmvar) == 0 && // We found the data
+ FT_Get_Multi_Master( ft_face, &mmtype) !=0) { // It's not an Adobe MM font
+
+ std::cout << " Multiple Masters: variables: " << mmvar->num_axis
+ << " named styles: " << mmvar->num_namedstyles << std::endl;
+
+ // const FT_UInt numNames = FT_Get_Sfnt_Name_Count(ft_face);
+ // std::cout << " number of names: " << numNames << std::endl;
+ // FT_SfntName ft_name;
+ // for (FT_UInt i = 0; i < numNames; ++i) {
+
+ // if (FT_Get_Sfnt_Name(ft_face, i, &ft_name) != 0) {
+ // continue;
+ // }
+
+ // Glib::ustring name;
+ // for (size_t j = 0; j < ft_name.string_len; ++j) {
+ // name += (char)ft_name.string[j];
+ // }
+ // std::cout << " " << i << ": " << name << std::endl;
+ // }
+
+ }
+
+#endif /* FREETYPE Version */
+}
+
+#define HB_OT_TAG_SVG HB_TAG('S','V','G',' ')
+
+// Get SVG glyphs out of an OpenType font.
+void readOpenTypeSVGTable(hb_font_t* hb_font,
+ std::map<int, SVGTableEntry>& glyphs) {
+
+ hb_face_t* hb_face = hb_font_get_face (hb_font);
+
+ // Harfbuzz has some support for SVG fonts but it is not exposed until version 2.1 (Oct 30, 2018).
+ // We do it the hard way!
+ hb_blob_t *hb_blob = hb_face_reference_table (hb_face, HB_OT_TAG_SVG);
+
+ if (!hb_blob) {
+ // No SVG table in font!
+ return;
+ }
+
+ unsigned int svg_length = hb_blob_get_length (hb_blob);
+ if (svg_length == 0) {
+ // No SVG glyphs in table!
+ return;
+ }
+
+ const char* data = hb_blob_get_data(hb_blob, &svg_length);
+ if (!data) {
+ std::cerr << "readOpenTypeSVGTable: Failed to get data! " << std::endl;
+ return;
+ }
+
+ // OpenType fonts use Big Endian
+#if 0
+ uint16_t version = ((data[0] & 0xff) << 8) + (data[1] & 0xff);
+ // std::cout << "Version: " << version << std::endl;
+#endif
+ uint32_t offset = ((data[2] & 0xff) << 24) + ((data[3] & 0xff) << 16) + ((data[4] & 0xff) << 8) + (data[5] & 0xff);
+
+ // std::cout << "Offset: " << offset << std::endl;
+ // Bytes 6-9 are reserved.
+
+ uint16_t entries = ((data[offset] & 0xff) << 8) + (data[offset+1] & 0xff);
+ // std::cout << "Number of entries: " << entries << std::endl;
+
+ for (int entry = 0; entry < entries; ++entry) {
+ uint32_t base = offset + 2 + entry * 12;
+
+ uint16_t startGlyphID = ((data[base ] & 0xff) << 8) + (data[base+1] & 0xff);
+ uint16_t endGlyphID = ((data[base+2] & 0xff) << 8) + (data[base+3] & 0xff);
+ uint32_t offsetGlyph = ((data[base+4] & 0xff) << 24) + ((data[base+5] & 0xff) << 16) +((data[base+6] & 0xff) << 8) + (data[base+7] & 0xff);
+ uint32_t lengthGlyph = ((data[base+8] & 0xff) << 24) + ((data[base+9] & 0xff) << 16) +((data[base+10] & 0xff) << 8) + (data[base+11] & 0xff);
+
+ // std::cout << "Entry " << entry << ": Start: " << startGlyphID << " End: " << endGlyphID
+ // << " Offset: " << offsetGlyph << " Length: " << lengthGlyph << std::endl;
+
+ std::string svg;
+
+ // static cast is needed as hb_blob_get_length returns char but we are comparing to a value greater than allowed by char.
+ if (lengthGlyph > 1 && //
+ static_cast<unsigned char>(data[offset + offsetGlyph + 0]) == 0x1f &&
+ static_cast<unsigned char>(data[offset + offsetGlyph + 1]) == 0x8b) {
+ // Glyph is gzipped
+
+ std::vector<unsigned char> buffer;
+ for (unsigned int c = offsetGlyph; c < offsetGlyph + lengthGlyph; ++c) {
+ buffer.push_back(data[offset + c]);
+ }
+
+ Inkscape::IO::BufferInputStream zipped(buffer);
+ Inkscape::IO::GzipInputStream gzin(zipped);
+ for (int character = gzin.get(); character != -1; character = gzin.get()) {
+ svg+= (char)character;
+ }
+
+ } else {
+ // Glyph is not compressed
+
+ for (unsigned int c = offsetGlyph; c < offsetGlyph + lengthGlyph; ++c) {
+ svg += (unsigned char) data[offset + c];
+ }
+ }
+
+ for (unsigned int i = startGlyphID; i < endGlyphID+1; ++i) {
+ glyphs[i].svg = svg;
+ }
+
+ // for (auto glyph : glyphs) {
+ // std::cout << "Glyph: " << glyph.first << std::endl;
+ // std::cout << glyph.second.svg << std::endl;
+ // }
+ }
+}
+
+#endif /* !USE_PANGO_WIND32 */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/OpenTypeUtil.h b/src/libnrtype/OpenTypeUtil.h
new file mode 100644
index 0000000..3385698
--- /dev/null
+++ b/src/libnrtype/OpenTypeUtil.h
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_OPENTYPEUTIL_H
+#define SEEN_OPENTYPEUTIL_H
+
+#ifndef USE_PANGO_WIN32
+
+#include <map>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include <glibmm/ustring.h>
+
+/*
+ * A set of utilities to extract data from OpenType fonts.
+ *
+ * Isolates dependencies on FreeType, Harfbuzz, and Pango.
+ * All three provide variable amounts of access to data.
+ */
+
+struct hb_font_t;
+
+// OpenType substitution
+class OTSubstitution {
+public:
+ OTSubstitution() = default;;
+ Glib::ustring before;
+ Glib::ustring input;
+ Glib::ustring after;
+ Glib::ustring output;
+};
+
+// An OpenType fvar axis.
+class OTVarAxis {
+public:
+ OTVarAxis()
+ : minimum(0)
+ , def(500) // Default
+ , maximum(1000)
+ , set_val(500)
+ , index(-1) {};
+
+ OTVarAxis(double _minimum, double _def, double _maximum, double _set_val, int _index)
+ : minimum(_minimum)
+ , def(_def) // Default
+ , maximum(_maximum)
+ , set_val(_set_val)
+ , index (_index) {};
+
+ double minimum;
+ double def;
+ double maximum;
+ double set_val;
+ int index; // Index in OpenType file (since we use a map).
+};
+
+// A particular instance of a variable font.
+// A map indexed by axis name with value.
+class OTVarInstance {
+ std::map<Glib::ustring, double> axes;
+};
+
+inline double FTFixedToDouble (FT_Fixed value) {
+ return static_cast<FT_Int32>(value) / 65536.0;
+}
+
+inline FT_Fixed FTDoubleToFixed (double value) {
+ return static_cast<FT_Fixed>(value * 65536);
+}
+
+
+namespace Inkscape { class Pixbuf; }
+
+class SVGTableEntry {
+public:
+ SVGTableEntry() : pixbuf(nullptr) {};
+ std::string svg;
+ Inkscape::Pixbuf* pixbuf;
+};
+
+// This would be better if one had std::vector<OTSubstitution> instead of OTSubstitution where each
+// entry corresponded to one substitution (e.g. ff -> ff) but Harfbuzz at the moment cannot return
+// individual substitutions. See Harfbuzz issue #673.
+void readOpenTypeGsubTable (hb_font_t* hb_font,
+ std::map<Glib::ustring, OTSubstitution >& tables);
+
+void readOpenTypeFvarAxes (const FT_Face ft_face,
+ std::map<Glib::ustring, OTVarAxis>& axes);
+
+void readOpenTypeFvarNamed (const FT_Face ft_face,
+ std::map<Glib::ustring, OTVarInstance>& named);
+
+void readOpenTypeSVGTable (hb_font_t* hb_font,
+ std::map<int, SVGTableEntry>& glyphs);
+
+#endif /* !USE_PANGO_WIND32 */
+#endif /* !SEEN_OPENTYPEUTIL_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/libnrtype/font-glyph.h b/src/libnrtype/font-glyph.h
new file mode 100644
index 0000000..5dcb933
--- /dev/null
+++ b/src/libnrtype/font-glyph.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_LIBNRTYPE_FONT_GLYPH_H
+#define SEEN_LIBNRTYPE_FONT_GLYPH_H
+
+#include <2geom/forward.h>
+
+// the info for a glyph in a font. it's totally resolution- and fontsize-independent
+struct font_glyph {
+ double h_advance, h_width; // width != advance because of kerning adjustements
+ double v_advance, v_width;
+ double bbox[4]; // bbox of the path (and the artbpath), not the bbox of the glyph
+ // as the fonts sometimes contain
+ Geom::PathVector* pathvector; // outline as 2geom pathvector, for text->curve stuff (should be unified with livarot)
+};
+
+
+#endif /* !SEEN_LIBNRTYPE_FONT_GLYPH_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/libnrtype/font-instance.h b/src/libnrtype/font-instance.h
new file mode 100644
index 0000000..fb99d15
--- /dev/null
+++ b/src/libnrtype/font-instance.h
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_LIBNRTYPE_FONT_INSTANCE_H
+#define SEEN_LIBNRTYPE_FONT_INSTANCE_H
+
+#include <map>
+
+#include <pango/pango-types.h>
+#include <pango/pango-font.h>
+
+#include <2geom/d2.h>
+
+#include "FontFactory.h"
+#include "font-style.h"
+#include "OpenTypeUtil.h"
+
+namespace Inkscape { class Pixbuf; }
+
+class font_factory;
+struct font_glyph;
+
+// the font_instance are the template of several raster_font; they provide metrics and outlines
+// that are drawn by the raster_font, so the raster_font needs info relative to the way the
+// font need to be drawn. note that fontsize is a scale factor in the transform matrix
+// of the style
+class font_instance {
+public:
+ // the real source of the font
+ PangoFont* pFont = nullptr;
+ // depending on the rendering backend, different temporary data
+
+ // that's the font's fingerprint; this particular PangoFontDescription gives the entry at which this font_instance
+ // resides in the font_factory loadedFaces unordered_map
+ PangoFontDescription* descr = nullptr;
+ // refcount
+ int refCount = 0;
+ // font_factory owning this font_instance
+ font_factory* parent = nullptr;
+
+ // common glyph definitions for all the rasterfonts
+ std::map<int, int> id_to_no;
+ int nbGlyph = 0;
+ int maxGlyph = 0;
+
+ font_glyph* glyphs = nullptr;
+
+ // font is loaded with GSUB in 2 pass
+ bool fulloaded = false;
+
+ // Map of OpenType tables found in font.
+ std::map<Glib::ustring, OTSubstitution> openTypeTables;
+
+ // Maps for font variations.
+ std::map<Glib::ustring, OTVarAxis> openTypeVarAxes; // Axes with ranges
+
+ // Map of SVG in OpenType glyphs
+ std::map<int, SVGTableEntry> openTypeSVGGlyphs;
+
+ // Does OpenType font contain SVG glyphs?
+ bool fontHasSVG = false;
+
+ font_instance();
+ virtual ~font_instance();
+
+ void Ref();
+ void Unref();
+
+ bool IsOutlineFont(); // utility
+ void InstallFace(PangoFont* iFace); // utility; should reset the pFont field if loading failed
+ // in case the PangoFont is a bitmap font, for example. that way, the calling function
+ // will be able to check the validity of the font before installing it in loadedFaces
+ void InitTheFace(bool loadgsub = false);
+
+ int MapUnicodeChar(gunichar c); // calls the relevant unicode->glyph index function
+ void LoadGlyph(int glyph_id); // the main backend-dependent function
+ // loads the given glyph's info
+
+ // nota: all coordinates returned by these functions are on a [0..1] scale; you need to multiply
+ // by the fontsize to get the real sizes
+
+ // Return 2geom pathvector for glyph. Deallocated when font instance dies.
+ Geom::PathVector* PathVector(int glyph_id);
+
+ // Return font has SVG OpenType enties.
+ bool FontHasSVG() { return fontHasSVG; };
+
+ // Return pixbuf of SVG glyph or nullptr if no SVG glyph exists.
+ Inkscape::Pixbuf* PixBuf(int glyph_id);
+
+
+ // Horizontal advance if 'vertical' is false, vertical advance if true.
+ double Advance(int glyph_id, bool vertical);
+
+ double GetTypoAscent() { return _ascent; }
+ double GetTypoDescent() { return _descent; }
+ double GetXHeight() { return _xheight; }
+ double GetMaxAscent() { return _ascent_max; }
+ double GetMaxDescent() { return _descent_max; }
+ const double* GetBaselines() { return _baselines; }
+ int GetDesignUnits() { return _design_units; }
+
+ bool FontMetrics(double &ascent, double &descent, double &leading);
+ bool FontDecoration(double &underline_position, double &underline_thickness,
+ double &linethrough_position, double &linethrough_thickness);
+ bool FontSlope(double &run, double &rise);
+ // for generating slanted cursors for oblique fonts
+ Geom::OptRect BBox(int glyph_id);
+
+private:
+ void FreeTheFace();
+ // Find ascent, descent, x-height, and baselines.
+ void FindFontMetrics();
+
+ // Temp: make public
+public:
+#ifdef USE_PANGO_WIN32
+ HFONT theFace = nullptr;
+#else
+ // we need to keep around an rw copy of the (read-only) hb font to extract the freetype face
+ hb_font_t* hb_font_copy = nullptr;
+ FT_Face theFace = nullptr;
+ // it's a pointer in fact; no worries to ref/unref it, pango does its magic
+ // as long as pFont is valid, theFace is too
+#endif
+
+private:
+ // Font metrics in em-box units
+ double _ascent; // Typographic ascent.
+ double _descent; // Typographic descent.
+ double _xheight; // x-height of font.
+ double _ascent_max; // Maximum ascent of all glyphs in font.
+ double _descent_max; // Maximum descent of all glyphs in font.
+ int _design_units; // Design units, (units per em, typically 1000 or 2048).
+
+ // Baselines
+ double _baselines[SP_CSS_BASELINE_SIZE];
+};
+
+
+#endif /* !SEEN_LIBNRTYPE_FONT_INSTANCE_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/libnrtype/font-lister.cpp b/src/libnrtype/font-lister.cpp
new file mode 100644
index 0000000..a929228
--- /dev/null
+++ b/src/libnrtype/font-lister.cpp
@@ -0,0 +1,1271 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/markup.h>
+#include <glibmm/regex.h>
+
+#include <gtkmm/cellrenderertext.h>
+
+#include <libnrtype/font-instance.h>
+
+#include "font-lister.h"
+#include "FontFactory.h"
+
+#include "desktop.h"
+#include "desktop-style.h"
+#include "document.h"
+#include "inkscape.h"
+#include "preferences.h"
+
+#include "object/sp-object.h"
+
+// Following are needed to limit the source of updating font data to text and containers.
+#include "object/sp-root.h"
+#include "object/sp-object-group.h"
+#include "object/sp-anchor.h"
+#include "object/sp-text.h"
+#include "object/sp-tspan.h"
+#include "object/sp-textpath.h"
+#include "object/sp-tref.h"
+#include "object/sp-flowtext.h"
+#include "object/sp-flowdiv.h"
+
+#include "xml/repr.h"
+
+
+//#define DEBUG_FONT
+
+// CSS dictates that font family names are case insensitive.
+// This should really implement full Unicode case unfolding.
+bool familyNamesAreEqual(const Glib::ustring &a, const Glib::ustring &b)
+{
+ return (a.casefold().compare(b.casefold()) == 0);
+}
+
+static const char* sp_font_family_get_name(PangoFontFamily* family)
+{
+ const char* name = pango_font_family_get_name(family);
+ if (strncmp(name, "Sans", 4) == 0 && strlen(name) == 4)
+ return "sans-serif";
+ if (strncmp(name, "Serif", 5) == 0 && strlen(name) == 5)
+ return "serif";
+ if (strncmp(name, "Monospace", 9) == 0 && strlen(name) == 9)
+ return "monospace";
+ return name;
+}
+
+namespace Inkscape {
+
+FontLister::FontLister()
+ : current_family_row (0)
+ , current_family ("sans-serif")
+ , current_style ("Normal")
+ , block (false)
+{
+ font_list_store = Gtk::ListStore::create(FontList);
+ font_list_store->freeze_notify();
+
+ /* Create default styles for use when font-family is unknown on system. */
+ default_styles = g_list_append(nullptr, new StyleNames("Normal"));
+ default_styles = g_list_append(default_styles, new StyleNames("Italic"));
+ default_styles = g_list_append(default_styles, new StyleNames("Bold"));
+ default_styles = g_list_append(default_styles, new StyleNames("Bold Italic"));
+
+ // Get sorted font families from Pango
+ std::vector<PangoFontFamily *> familyVector;
+ font_factory::Default()->GetUIFamilies(familyVector);
+
+ // Traverse through the family names and set up the list store
+ for (auto & i : familyVector) {
+ const char* displayName = sp_font_family_get_name(i);
+
+ if (displayName == nullptr || *displayName == '\0') {
+ continue;
+ }
+
+ Glib::ustring familyName = displayName;
+ if (!familyName.empty()) {
+ Gtk::TreeModel::iterator treeModelIter = font_list_store->append();
+ (*treeModelIter)[FontList.family] = familyName;
+
+ // we don't set this now (too slow) but the style will be cached if the user
+ // ever decides to use this font
+ (*treeModelIter)[FontList.styles] = NULL;
+ // store the pango representation for generating the style
+ (*treeModelIter)[FontList.pango_family] = i;
+ (*treeModelIter)[FontList.onSystem] = true;
+ }
+ }
+
+ font_list_store->thaw_notify();
+
+ style_list_store = Gtk::ListStore::create(FontStyleList);
+
+ // Initialize style store with defaults
+ style_list_store->freeze_notify();
+ style_list_store->clear();
+ for (GList *l = default_styles; l; l = l->next) {
+ Gtk::TreeModel::iterator treeModelIter = style_list_store->append();
+ (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)l->data)->CssName;
+ (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)l->data)->DisplayName;
+ }
+ style_list_store->thaw_notify();
+}
+
+FontLister::~FontLister()
+{
+ // Delete default_styles
+ for (GList *l = default_styles; l; l = l->next) {
+ delete ((StyleNames *)l->data);
+ }
+
+ // Delete other styles
+ Gtk::TreeModel::iterator iter = font_list_store->get_iter("0");
+ while (iter != font_list_store->children().end()) {
+ Gtk::TreeModel::Row row = *iter;
+ GList *styles = row[FontList.styles];
+ for (GList *l = styles; l; l = l->next) {
+ delete ((StyleNames *)l->data);
+ }
+ ++iter;
+ }
+}
+
+FontLister *FontLister::get_instance()
+{
+ static Inkscape::FontLister *instance = new Inkscape::FontLister();
+ return instance;
+}
+
+// To do: remove model (not needed for C++ version).
+// Ensures the style list for a particular family has been created.
+void FontLister::ensureRowStyles(Glib::RefPtr<Gtk::TreeModel> model, Gtk::TreeModel::iterator const iter)
+{
+ Gtk::TreeModel::Row row = *iter;
+ if (!row[FontList.styles]) {
+ if (row[FontList.pango_family]) {
+ row[FontList.styles] = font_factory::Default()->GetUIStyles(row[FontList.pango_family]);
+ } else {
+ row[FontList.styles] = default_styles;
+ }
+ }
+}
+
+Glib::ustring FontLister::get_font_family_markup(Gtk::TreeIter const &iter)
+{
+ Gtk::TreeModel::Row row = *iter;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ Glib::ustring family = row[FontList.family];
+ bool onSystem = row[FontList.onSystem];
+
+ Glib::ustring family_escaped = Glib::Markup::escape_text( family );
+ Glib::ustring markup;
+
+ if (!onSystem) {
+ markup = "<span font-weight='bold'>";
+
+ // See if font-family is on system (separately for each family in font stack).
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", family);
+
+ for (auto token: tokens) {
+ bool found = false;
+ Gtk::TreeModel::Children children = get_font_list()->children();
+ for (auto iter2: children) {
+ Gtk::TreeModel::Row row2 = *iter2;
+ Glib::ustring family2 = row2[FontList.family];
+ bool onSystem2 = row2[FontList.onSystem];
+ if (onSystem2 && familyNamesAreEqual(token, family2)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ markup += Glib::Markup::escape_text (token);
+ markup += ", ";
+ } else {
+ markup += "<span strikethrough=\"true\" strikethrough_color=\"red\">";
+ markup += Glib::Markup::escape_text (token);
+ markup += "</span>";
+ markup += ", ";
+ }
+ }
+
+ // Remove extra comma and space from end.
+ if (markup.size() >= 2) {
+ markup.resize(markup.size() - 2);
+ }
+ markup += "</span>";
+
+ } else {
+ markup = family_escaped;
+ }
+
+ int show_sample = prefs->getInt("/tools/text/show_sample_in_list", 1);
+ if (show_sample) {
+
+ Glib::ustring sample = prefs->getString("/tools/text/font_sample");
+
+ markup += " <span foreground='gray' font_family='";
+ markup += family_escaped;
+ markup += "'>";
+ markup += sample;
+ markup += "</span>";
+ }
+
+ // std::cout << "Markup: " << markup << std::endl;
+ return markup;
+}
+
+// Example of how to use "foreach_iter"
+// bool
+// FontLister::print_document_font( const Gtk::TreeModel::iterator &iter ) {
+// Gtk::TreeModel::Row row = *iter;
+// if( !row[FontList.onSystem] ) {
+// std::cout << " Not on system: " << row[FontList.family] << std::endl;
+// return false;
+// }
+// return true;
+// }
+// font_list_store->foreach_iter( sigc::mem_fun(*this, &FontLister::print_document_font ));
+
+/* Used to insert a font that was not in the document and not on the system into the font list. */
+void FontLister::insert_font_family(Glib::ustring new_family)
+{
+ GList *styles = default_styles;
+
+ /* In case this is a fallback list, check if first font-family on system. */
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(",", new_family);
+ if (!tokens.empty() && !tokens[0].empty()) {
+ Gtk::TreeModel::iterator iter2 = font_list_store->get_iter("0");
+ while (iter2 != font_list_store->children().end()) {
+ Gtk::TreeModel::Row row = *iter2;
+ if (row[FontList.onSystem] && familyNamesAreEqual(tokens[0], row[FontList.family])) {
+ if (!row[FontList.styles]) {
+ row[FontList.styles] = font_factory::Default()->GetUIStyles(row[FontList.pango_family]);
+ }
+ styles = row[FontList.styles];
+ break;
+ }
+ ++iter2;
+ }
+ }
+
+ Gtk::TreeModel::iterator treeModelIter = font_list_store->prepend();
+ (*treeModelIter)[FontList.family] = new_family;
+ (*treeModelIter)[FontList.styles] = styles;
+ (*treeModelIter)[FontList.onSystem] = false;
+ (*treeModelIter)[FontList.pango_family] = NULL;
+
+ current_family = new_family;
+ current_family_row = 0;
+ current_style = "Normal";
+
+ emit_update();
+}
+
+void FontLister::update_font_list(SPDocument *document)
+{
+ SPObject *root = document->getRoot();
+ if (!root) {
+ return;
+ }
+
+ font_list_store->freeze_notify();
+
+ /* Find if current row is in document or system part of list */
+ gboolean row_is_system = false;
+ if (current_family_row > -1) {
+ Gtk::TreePath path;
+ path.push_back(current_family_row);
+ Gtk::TreeModel::iterator iter = font_list_store->get_iter(path);
+ if (iter) {
+ row_is_system = (*iter)[FontList.onSystem];
+ // std::cout << " In: row: " << current_family_row << " " << (*iter)[FontList.family] << std::endl;
+ }
+ }
+
+ /* Clear all old document font-family entries */
+ Gtk::TreeModel::iterator iter = font_list_store->get_iter("0");
+ while (iter != font_list_store->children().end()) {
+ Gtk::TreeModel::Row row = *iter;
+ if (!row[FontList.onSystem]) {
+ // std::cout << " Not on system: " << row[FontList.family] << std::endl;
+ iter = font_list_store->erase(iter);
+ } else {
+ // std::cout << " First on system: " << row[FontList.family] << std::endl;
+ break;
+ }
+ }
+
+ /* Get "font-family"s and styles used in document. */
+ std::map<Glib::ustring, std::set<Glib::ustring>> font_data;
+ update_font_data_recursive(*root, font_data);
+
+ /* Insert separator */
+ if (!font_data.empty()) {
+ Gtk::TreeModel::iterator treeModelIter = font_list_store->prepend();
+ (*treeModelIter)[FontList.family] = "#";
+ (*treeModelIter)[FontList.onSystem] = false;
+ }
+
+ /* Insert font-family's in document. */
+ for (auto i: font_data) {
+
+ GList *styles = default_styles;
+
+ /* See if font-family (or first in fallback list) is on system. If so, get styles. */
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(",", i.first);
+ if (!tokens.empty() && !tokens[0].empty()) {
+
+ Gtk::TreeModel::iterator iter2 = font_list_store->get_iter("0");
+ while (iter2 != font_list_store->children().end()) {
+ Gtk::TreeModel::Row row = *iter2;
+ if (row[FontList.onSystem] && familyNamesAreEqual(tokens[0], row[FontList.family])) {
+ // Found font on system, set style list to system font style list.
+ if (!row[FontList.styles]) {
+ row[FontList.styles] = font_factory::Default()->GetUIStyles(row[FontList.pango_family]);
+ }
+
+ // Add new styles (from 'font-variation-settings', these are not include in GetUIStyles()).
+ for (auto j: i.second) {
+ // std::cout << " Inserting: " << j << std::endl;
+
+ bool exists = false;
+ for(GList *temp = row[FontList.styles]; temp; temp = temp->next) {
+ if( ((StyleNames*)temp->data)->CssName.compare( j ) == 0 ) {
+ exists = true;
+ break;
+ }
+ }
+
+ if (!exists) {
+ row[FontList.styles] = g_list_append(row[FontList.styles], new StyleNames(j,j));
+ }
+ }
+
+ styles = row[FontList.styles];
+ break;
+ }
+ ++iter2;
+ }
+ }
+
+ Gtk::TreeModel::iterator treeModelIter = font_list_store->prepend();
+ (*treeModelIter)[FontList.family] = reinterpret_cast<const char *>(g_strdup((i.first).c_str()));
+ (*treeModelIter)[FontList.styles] = styles;
+ (*treeModelIter)[FontList.onSystem] = false; // false if document font
+ (*treeModelIter)[FontList.pango_family] = NULL; // CHECK ME (set to pango_family if on system?)
+
+ }
+
+ font_family_row_update(row_is_system ? font_data.size() : 0);
+ // std::cout << " Out: row: " << current_family_row << " " << current_family << std::endl;
+
+ font_list_store->thaw_notify();
+ emit_update();
+}
+
+void FontLister::update_font_data_recursive(SPObject& r, std::map<Glib::ustring, std::set<Glib::ustring>> &font_data)
+{
+ // Text nodes (i.e. the content of <text> or <tspan>) do not have their own style.
+ if (r.getRepr()->type() == Inkscape::XML::NodeType::TEXT_NODE) {
+ return;
+ }
+
+ PangoFontDescription* descr = ink_font_description_from_style( r.style );
+ const gchar* font_family_char = pango_font_description_get_family(descr);
+ if (font_family_char) {
+ Glib::ustring font_family(font_family_char);
+ pango_font_description_unset_fields( descr, PANGO_FONT_MASK_FAMILY);
+
+ gchar* font_style_char = pango_font_description_to_string(descr);
+ Glib::ustring font_style(font_style_char);
+ g_free(font_style_char);
+
+ if (!font_family.empty() && !font_style.empty()) {
+ font_data[font_family].insert(font_style);
+ }
+ } else {
+ // We're starting from root and looking at all elements... we should probably white list text/containers.
+ std::cerr << "FontLister::update_font_data_recursive: descr without font family! " << (r.getId()?r.getId():"null") << std::endl;
+ }
+ pango_font_description_free(descr);
+
+ if (SP_IS_GROUP(&r) ||
+ SP_IS_ANCHOR(&r) ||
+ SP_IS_ROOT(&r) ||
+ SP_IS_TEXT(&r) ||
+ SP_IS_TSPAN(&r) ||
+ SP_IS_TEXTPATH(&r) ||
+ SP_IS_TREF(&r) ||
+ SP_IS_FLOWTEXT(&r) ||
+ SP_IS_FLOWDIV(&r) ||
+ SP_IS_FLOWPARA(&r) ||
+ SP_IS_FLOWLINE(&r)) {
+ for (auto& child: r.children) {
+ update_font_data_recursive(child, font_data);
+ }
+ }
+}
+
+void FontLister::emit_update()
+{
+ if (block) return;
+
+ block = true;
+ update_signal.emit ();
+ block = false;
+}
+
+
+Glib::ustring FontLister::canonize_fontspec(Glib::ustring fontspec)
+{
+
+ // Pass fontspec to and back from Pango to get a the fontspec in
+ // canonical form. -inkscape-font-specification relies on the
+ // Pango constructed fontspec not changing form. If it does,
+ // this is the place to fix it.
+ PangoFontDescription *descr = pango_font_description_from_string(fontspec.c_str());
+ gchar *canonized = pango_font_description_to_string(descr);
+ Glib::ustring Canonized = canonized;
+ g_free(canonized);
+ pango_font_description_free(descr);
+
+ // Pango canonized strings remove space after comma between family names. Put it back.
+ // But don't add a space inside a 'font-variation-settings' declaration (this breaks Pango).
+ size_t i = 0;
+ while ((i = Canonized.find_first_of(",@", i)) != std::string::npos ) {
+ if (Canonized[i] == '@') // Found start of 'font-variation-settings'.
+ break;
+ Canonized.replace(i, 1, ", ");
+ i += 2;
+ }
+
+ return Canonized;
+}
+
+Glib::ustring FontLister::system_fontspec(Glib::ustring fontspec)
+{
+ // Find what Pango thinks is the closest match.
+ Glib::ustring out = fontspec;
+
+ PangoFontDescription *descr = pango_font_description_from_string(fontspec.c_str());
+ font_instance *res = (font_factory::Default())->Face(descr);
+ if (res && res->pFont) {
+ PangoFontDescription *nFaceDesc = pango_font_describe(res->pFont);
+ out = sp_font_description_get_family(nFaceDesc);
+ }
+ pango_font_description_free(descr);
+
+ return out;
+}
+
+std::pair<Glib::ustring, Glib::ustring> FontLister::ui_from_fontspec(Glib::ustring fontspec)
+{
+ PangoFontDescription *descr = pango_font_description_from_string(fontspec.c_str());
+ const gchar *family = pango_font_description_get_family(descr);
+ if (!family)
+ family = "sans-serif";
+ Glib::ustring Family = family;
+
+ // PANGO BUG...
+ // A font spec of Delicious, 500 Italic should result in a family of 'Delicious'
+ // and a style of 'Medium Italic'. It results instead with: a family of
+ // 'Delicious, 500' with a style of 'Medium Italic'. We chop of any weight numbers
+ // at the end of the family: match ",[1-9]00^".
+ Glib::RefPtr<Glib::Regex> weight = Glib::Regex::create(",[1-9]00$");
+ Family = weight->replace(Family, 0, "", Glib::REGEX_MATCH_PARTIAL);
+
+ // Pango canonized strings remove space after comma between family names. Put it back.
+ size_t i = 0;
+ while ((i = Family.find(",", i)) != std::string::npos) {
+ Family.replace(i, 1, ", ");
+ i += 2;
+ }
+
+ pango_font_description_unset_fields(descr, PANGO_FONT_MASK_FAMILY);
+ gchar *style = pango_font_description_to_string(descr);
+ Glib::ustring Style = style;
+ pango_font_description_free(descr);
+ g_free(style);
+
+ return std::make_pair(Family, Style);
+}
+
+/* Now we do a song and dance to find the correct row as the row corresponding
+ * to the current_family may have changed. We can't simply search for the
+ * family name in the list since it can occur twice, once in the document
+ * font family part and once in the system font family part. Above we determined
+ * which part it is in.
+ */
+void FontLister::font_family_row_update(int start)
+{
+ if (this->current_family_row > -1 && start > -1) {
+ int length = this->font_list_store->children().size();
+ for (int i = 0; i < length; ++i) {
+ int row = i + start;
+ if (row >= length)
+ row -= length;
+ Gtk::TreePath path;
+ path.push_back(row);
+ Gtk::TreeModel::iterator iter = this->font_list_store->get_iter(path);
+ if (iter) {
+ if (familyNamesAreEqual(this->current_family, (*iter)[FontList.family])) {
+ this->current_family_row = row;
+ break;
+ }
+ }
+ }
+ }
+}
+
+std::pair<Glib::ustring, Glib::ustring> FontLister::selection_update()
+{
+#ifdef DEBUG_FONT
+ std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
+ std::cout << "FontLister::selection_update: entrance" << std::endl;
+#endif
+ // Get fontspec from a selection, preferences, or thin air.
+ Glib::ustring fontspec;
+ SPStyle query(SP_ACTIVE_DOCUMENT);
+
+ // Directly from stored font specification.
+ int result =
+ sp_desktop_query_style(SP_ACTIVE_DESKTOP, &query, QUERY_STYLE_PROPERTY_FONT_SPECIFICATION);
+
+ //std::cout << " Attempting selected style" << std::endl;
+ if (result != QUERY_STYLE_NOTHING && query.font_specification.set) {
+ fontspec = query.font_specification.value();
+ //std::cout << " fontspec from query :" << fontspec << ":" << std::endl;
+ }
+
+ // From style
+ if (fontspec.empty()) {
+ //std::cout << " Attempting desktop style" << std::endl;
+ int rfamily = sp_desktop_query_style(SP_ACTIVE_DESKTOP, &query, QUERY_STYLE_PROPERTY_FONTFAMILY);
+ int rstyle = sp_desktop_query_style(SP_ACTIVE_DESKTOP, &query, QUERY_STYLE_PROPERTY_FONTSTYLE);
+
+ // Must have text in selection
+ if (rfamily != QUERY_STYLE_NOTHING && rstyle != QUERY_STYLE_NOTHING) {
+ fontspec = fontspec_from_style(&query);
+ }
+ //std::cout << " fontspec from style :" << fontspec << ":" << std::endl;
+ }
+
+ // From preferences
+ if (fontspec.empty()) {
+ //std::cout << " Attempting preferences" << std::endl;
+ query.readFromPrefs("/tools/text");
+ fontspec = fontspec_from_style(&query);
+ //std::cout << " fontspec from prefs :" << fontspec << ":" << std::endl;
+ }
+
+ // From thin air
+ if (fontspec.empty()) {
+ //std::cout << " Attempting thin air" << std::endl;
+ fontspec = current_family + ", " + current_style;
+ //std::cout << " fontspec from thin air :" << fontspec << ":" << std::endl;
+ }
+
+ // Need to update font family row too
+ font_family_row_update();
+
+ std::pair<Glib::ustring, Glib::ustring> ui = ui_from_fontspec(fontspec);
+ set_font_family(ui.first);
+ set_font_style(ui.second);
+
+#ifdef DEBUG_FONT
+ std::cout << " family_row: :" << current_family_row << ":" << std::endl;
+ std::cout << " family: :" << current_family << ":" << std::endl;
+ std::cout << " style: :" << current_style << ":" << std::endl;
+ std::cout << "FontLister::selection_update: exit" << std::endl;
+ std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
+#endif
+
+ emit_update();
+
+ return std::make_pair(current_family, current_style);
+}
+
+
+// Set fontspec. If check is false, best style match will not be done.
+void FontLister::set_fontspec(Glib::ustring new_fontspec, bool /*check*/)
+{
+ std::pair<Glib::ustring, Glib::ustring> ui = ui_from_fontspec(new_fontspec);
+ Glib::ustring new_family = ui.first;
+ Glib::ustring new_style = ui.second;
+
+#ifdef DEBUG_FONT
+ std::cout << "FontLister::set_fontspec: family: " << new_family
+ << " style:" << new_style << std::endl;
+#endif
+
+ set_font_family(new_family, false, false);
+ set_font_style(new_style, false);
+
+ emit_update();
+}
+
+
+// TODO: use to determine font-selector best style
+// TODO: create new function new_font_family(Gtk::TreeModel::iterator iter)
+std::pair<Glib::ustring, Glib::ustring> FontLister::new_font_family(Glib::ustring new_family, bool /*check_style*/)
+{
+#ifdef DEBUG_FONT
+ std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
+ std::cout << "FontLister::new_font_family: " << new_family << std::endl;
+#endif
+
+ // No need to do anything if new family is same as old family.
+ if (familyNamesAreEqual(new_family, current_family)) {
+#ifdef DEBUG_FONT
+ std::cout << "FontLister::new_font_family: exit: no change in family." << std::endl;
+ std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
+#endif
+ return std::make_pair(current_family, current_style);
+ }
+
+ // We need to do two things:
+ // 1. Update style list for new family.
+ // 2. Select best valid style match to old style.
+
+ // For finding style list, use list of first family in font-family list.
+ GList *styles = nullptr;
+ Gtk::TreeModel::iterator iter = font_list_store->get_iter("0");
+ while (iter != font_list_store->children().end()) {
+
+ Gtk::TreeModel::Row row = *iter;
+
+ if (familyNamesAreEqual(new_family, row[FontList.family])) {
+ if (!row[FontList.styles]) {
+ row[FontList.styles] = font_factory::Default()->GetUIStyles(row[FontList.pango_family]);
+ }
+ styles = row[FontList.styles];
+ break;
+ }
+ ++iter;
+ }
+
+ // Newly typed in font-family may not yet be in list... use default list.
+ // TODO: if font-family is list, check if first family in list is on system
+ // and set style accordingly.
+ if (styles == nullptr) {
+ styles = default_styles;
+ }
+
+ // Update style list.
+ style_list_store->freeze_notify();
+ style_list_store->clear();
+
+ for (GList *l = styles; l; l = l->next) {
+ Gtk::TreeModel::iterator treeModelIter = style_list_store->append();
+ (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)l->data)->CssName;
+ (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)l->data)->DisplayName;
+ }
+
+ style_list_store->thaw_notify();
+
+ // Find best match to the style from the old font-family to the
+ // styles available with the new font.
+ // TODO: Maybe check if an exact match exists before using Pango.
+ Glib::ustring best_style = get_best_style_match(new_family, current_style);
+
+#ifdef DEBUG_FONT
+ std::cout << "FontLister::new_font_family: exit: " << new_family << " " << best_style << std::endl;
+ std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
+#endif
+ return std::make_pair(new_family, best_style);
+}
+
+std::pair<Glib::ustring, Glib::ustring> FontLister::set_font_family(Glib::ustring new_family, bool check_style,
+ bool emit)
+{
+
+#ifdef DEBUG_FONT
+ std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
+ std::cout << "FontLister::set_font_family: " << new_family << std::endl;
+#endif
+
+ std::pair<Glib::ustring, Glib::ustring> ui = new_font_family(new_family, check_style);
+ current_family = ui.first;
+ current_style = ui.second;
+
+#ifdef DEBUG_FONT
+ std::cout << " family_row: :" << current_family_row << ":" << std::endl;
+ std::cout << " family: :" << current_family << ":" << std::endl;
+ std::cout << " style: :" << current_style << ":" << std::endl;
+ std::cout << "FontLister::set_font_family: end" << std::endl;
+ std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
+#endif
+ if (emit) {
+ emit_update();
+ }
+ return ui;
+}
+
+
+std::pair<Glib::ustring, Glib::ustring> FontLister::set_font_family(int row, bool check_style, bool emit)
+{
+
+#ifdef DEBUG_FONT
+ std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
+ std::cout << "FontLister::set_font_family( row ): " << row << std::endl;
+#endif
+
+ current_family_row = row;
+ Gtk::TreePath path;
+ path.push_back(row);
+ Glib::ustring new_family = current_family;
+ Gtk::TreeModel::iterator iter = font_list_store->get_iter(path);
+ if (iter) {
+ new_family = (*iter)[FontList.family];
+ }
+
+ std::pair<Glib::ustring, Glib::ustring> ui = set_font_family(new_family, check_style, emit);
+
+#ifdef DEBUG_FONT
+ std::cout << "FontLister::set_font_family( row ): end" << std::endl;
+ std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
+#endif
+ return ui;
+}
+
+
+void FontLister::set_font_style(Glib::ustring new_style, bool emit)
+{
+
+// TODO: Validate input using Pango. If Pango doesn't recognize a style it will
+// attach the "invalid" style to the font-family.
+
+#ifdef DEBUG_FONT
+ std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
+ std::cout << "FontLister:set_font_style: " << new_style << std::endl;
+#endif
+ current_style = new_style;
+
+#ifdef DEBUG_FONT
+ std::cout << " family: " << current_family << std::endl;
+ std::cout << " style: " << current_style << std::endl;
+ std::cout << "FontLister::set_font_style: end" << std::endl;
+ std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
+#endif
+ if (emit) {
+ emit_update();
+ }
+}
+
+
+// We do this ourselves as we can't rely on FontFactory.
+void FontLister::fill_css(SPCSSAttr *css, Glib::ustring fontspec)
+{
+ if (fontspec.empty()) {
+ fontspec = get_fontspec();
+ }
+
+ std::pair<Glib::ustring, Glib::ustring> ui = ui_from_fontspec(fontspec);
+
+ Glib::ustring family = ui.first;
+
+
+ // Font spec is single quoted... for the moment
+ Glib::ustring fontspec_quoted(fontspec);
+ css_quote(fontspec_quoted);
+ sp_repr_css_set_property(css, "-inkscape-font-specification", fontspec_quoted.c_str());
+
+ // Font families needs to be properly quoted in CSS (used unquoted in font-lister)
+ css_font_family_quote(family);
+ sp_repr_css_set_property(css, "font-family", family.c_str());
+
+ PangoFontDescription *desc = pango_font_description_from_string(fontspec.c_str());
+ PangoWeight weight = pango_font_description_get_weight(desc);
+ switch (weight) {
+ case PANGO_WEIGHT_THIN:
+ sp_repr_css_set_property(css, "font-weight", "100");
+ break;
+ case PANGO_WEIGHT_ULTRALIGHT:
+ sp_repr_css_set_property(css, "font-weight", "200");
+ break;
+ case PANGO_WEIGHT_LIGHT:
+ sp_repr_css_set_property(css, "font-weight", "300");
+ break;
+ case PANGO_WEIGHT_SEMILIGHT:
+ sp_repr_css_set_property(css, "font-weight", "350");
+ break;
+ case PANGO_WEIGHT_BOOK:
+ sp_repr_css_set_property(css, "font-weight", "380");
+ break;
+ case PANGO_WEIGHT_NORMAL:
+ sp_repr_css_set_property(css, "font-weight", "normal");
+ break;
+ case PANGO_WEIGHT_MEDIUM:
+ sp_repr_css_set_property(css, "font-weight", "500");
+ break;
+ case PANGO_WEIGHT_SEMIBOLD:
+ sp_repr_css_set_property(css, "font-weight", "600");
+ break;
+ case PANGO_WEIGHT_BOLD:
+ sp_repr_css_set_property(css, "font-weight", "bold");
+ break;
+ case PANGO_WEIGHT_ULTRABOLD:
+ sp_repr_css_set_property(css, "font-weight", "800");
+ break;
+ case PANGO_WEIGHT_HEAVY:
+ sp_repr_css_set_property(css, "font-weight", "900");
+ break;
+ case PANGO_WEIGHT_ULTRAHEAVY:
+ sp_repr_css_set_property(css, "font-weight", "1000");
+ break;
+ }
+
+ PangoStyle style = pango_font_description_get_style(desc);
+ switch (style) {
+ case PANGO_STYLE_NORMAL:
+ sp_repr_css_set_property(css, "font-style", "normal");
+ break;
+ case PANGO_STYLE_OBLIQUE:
+ sp_repr_css_set_property(css, "font-style", "oblique");
+ break;
+ case PANGO_STYLE_ITALIC:
+ sp_repr_css_set_property(css, "font-style", "italic");
+ break;
+ }
+
+ PangoStretch stretch = pango_font_description_get_stretch(desc);
+ switch (stretch) {
+ case PANGO_STRETCH_ULTRA_CONDENSED:
+ sp_repr_css_set_property(css, "font-stretch", "ultra-condensed");
+ break;
+ case PANGO_STRETCH_EXTRA_CONDENSED:
+ sp_repr_css_set_property(css, "font-stretch", "extra-condensed");
+ break;
+ case PANGO_STRETCH_CONDENSED:
+ sp_repr_css_set_property(css, "font-stretch", "condensed");
+ break;
+ case PANGO_STRETCH_SEMI_CONDENSED:
+ sp_repr_css_set_property(css, "font-stretch", "semi-condensed");
+ break;
+ case PANGO_STRETCH_NORMAL:
+ sp_repr_css_set_property(css, "font-stretch", "normal");
+ break;
+ case PANGO_STRETCH_SEMI_EXPANDED:
+ sp_repr_css_set_property(css, "font-stretch", "semi-expanded");
+ break;
+ case PANGO_STRETCH_EXPANDED:
+ sp_repr_css_set_property(css, "font-stretch", "expanded");
+ break;
+ case PANGO_STRETCH_EXTRA_EXPANDED:
+ sp_repr_css_set_property(css, "font-stretch", "extra-expanded");
+ break;
+ case PANGO_STRETCH_ULTRA_EXPANDED:
+ sp_repr_css_set_property(css, "font-stretch", "ultra-expanded");
+ break;
+ }
+
+ PangoVariant variant = pango_font_description_get_variant(desc);
+ switch (variant) {
+ case PANGO_VARIANT_NORMAL:
+ sp_repr_css_set_property(css, "font-variant", "normal");
+ break;
+ case PANGO_VARIANT_SMALL_CAPS:
+ sp_repr_css_set_property(css, "font-variant", "small-caps");
+ break;
+ }
+
+ // Convert Pango variations string to CSS format
+ const char* str = pango_font_description_get_variations(desc);
+
+ std::string variations;
+
+ if (str) {
+
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(",", str);
+
+ Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("(\\w{4})=([-+]?\\d*\\.?\\d+([eE][-+]?\\d+)?)");
+ Glib::MatchInfo matchInfo;
+ for (auto token: tokens) {
+ regex->match(token, matchInfo);
+ if (matchInfo.matches()) {
+ variations += "'";
+ variations += matchInfo.fetch(1).raw();
+ variations += "' ";
+ variations += matchInfo.fetch(2).raw();
+ variations += ", ";
+ }
+ }
+ if (variations.length() >= 2) { // Remove last comma/space
+ variations.pop_back();
+ variations.pop_back();
+ }
+ }
+
+ if (!variations.empty()) {
+ sp_repr_css_set_property(css, "font-variation-settings", variations.c_str());
+ } else {
+ sp_repr_css_unset_property(css, "font-variation-settings" );
+ }
+ pango_font_description_free(desc);
+}
+
+
+Glib::ustring FontLister::fontspec_from_style(SPStyle *style)
+{
+
+ PangoFontDescription* descr = ink_font_description_from_style( style );
+ Glib::ustring fontspec = pango_font_description_to_string( descr );
+ pango_font_description_free(descr);
+
+ //std::cout << "FontLister:fontspec_from_style: " << fontspec << std::endl;
+
+ return fontspec;
+}
+
+
+Gtk::TreeModel::Row FontLister::get_row_for_font(Glib::ustring family)
+{
+
+ Gtk::TreeModel::iterator iter = font_list_store->get_iter("0");
+ while (iter != font_list_store->children().end()) {
+
+ Gtk::TreeModel::Row row = *iter;
+
+ if (familyNamesAreEqual(family, row[FontList.family])) {
+ return row;
+ }
+
+ ++iter;
+ }
+
+ throw FAMILY_NOT_FOUND;
+}
+
+Gtk::TreePath FontLister::get_path_for_font(Glib::ustring family)
+{
+ return font_list_store->get_path(get_row_for_font(family));
+}
+
+bool FontLister::is_path_for_font(Gtk::TreePath path, Glib::ustring family)
+{
+ Gtk::TreeModel::iterator iter = font_list_store->get_iter(path);
+ if (iter) {
+ return familyNamesAreEqual(family, (*iter)[FontList.family]);
+ }
+
+ return false;
+}
+
+Gtk::TreeModel::Row FontLister::get_row_for_style(Glib::ustring style)
+{
+
+ Gtk::TreeModel::iterator iter = style_list_store->get_iter("0");
+ while (iter != style_list_store->children().end()) {
+
+ Gtk::TreeModel::Row row = *iter;
+
+ if (familyNamesAreEqual(style, row[FontStyleList.cssStyle])) {
+ return row;
+ }
+
+ ++iter;
+ }
+
+ throw STYLE_NOT_FOUND;
+}
+
+static gint compute_distance(const PangoFontDescription *a, const PangoFontDescription *b)
+{
+
+ // Weight: multiples of 100
+ gint distance = abs(pango_font_description_get_weight(a) -
+ pango_font_description_get_weight(b));
+
+ distance += 10000 * abs(pango_font_description_get_stretch(a) -
+ pango_font_description_get_stretch(b));
+
+ PangoStyle style_a = pango_font_description_get_style(a);
+ PangoStyle style_b = pango_font_description_get_style(b);
+ if (style_a != style_b) {
+ if ((style_a == PANGO_STYLE_OBLIQUE && style_b == PANGO_STYLE_ITALIC) ||
+ (style_b == PANGO_STYLE_OBLIQUE && style_a == PANGO_STYLE_ITALIC)) {
+ distance += 1000; // Oblique and italic are almost the same
+ } else {
+ distance += 100000; // Normal vs oblique/italic, not so similar
+ }
+ }
+
+ // Normal vs small-caps
+ distance += 1000000 * abs(pango_font_description_get_variant(a) -
+ pango_font_description_get_variant(b));
+ return distance;
+}
+
+// This is inspired by pango_font_description_better_match, but that routine
+// always returns false if variant or stretch are different. This means, for
+// example, that PT Sans Narrow with style Bold Condensed is never matched
+// to another font-family with Bold style.
+gboolean font_description_better_match(PangoFontDescription *target, PangoFontDescription *old_desc, PangoFontDescription *new_desc)
+{
+ if (old_desc == nullptr)
+ return true;
+ if (new_desc == nullptr)
+ return false;
+
+ int old_distance = compute_distance(target, old_desc);
+ int new_distance = compute_distance(target, new_desc);
+ //std::cout << "font_description_better_match: old: " << old_distance << std::endl;
+ //std::cout << " new: " << new_distance << std::endl;
+
+ return (new_distance < old_distance);
+}
+
+// void
+// font_description_dump( PangoFontDescription* target ) {
+// std::cout << " Font: " << pango_font_description_to_string( target ) << std::endl;
+// std::cout << " style: " << pango_font_description_get_style( target ) << std::endl;
+// std::cout << " weight: " << pango_font_description_get_weight( target ) << std::endl;
+// std::cout << " variant: " << pango_font_description_get_variant( target ) << std::endl;
+// std::cout << " stretch: " << pango_font_description_get_stretch( target ) << std::endl;
+// std::cout << " gravity: " << pango_font_description_get_gravity( target ) << std::endl;
+// }
+
+/* Returns style string */
+// TODO: Remove or turn into function to be used by new_font_family.
+Glib::ustring FontLister::get_best_style_match(Glib::ustring family, Glib::ustring target_style)
+{
+
+#ifdef DEBUG_FONT
+ std::cout << "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << std::endl;
+ std::cout << "FontLister::get_best_style_match: " << family << " : " << target_style << std::endl;
+#endif
+
+ Glib::ustring fontspec = family + ", " + target_style;
+
+ Gtk::TreeModel::Row row;
+ try
+ {
+ row = get_row_for_font(family);
+ }
+ catch (...)
+ {
+ std::cerr << "FontLister::get_best_style_match(): can't find family: " << family << std::endl;
+ return (target_style);
+ }
+
+ PangoFontDescription *target = pango_font_description_from_string(fontspec.c_str());
+ PangoFontDescription *best = nullptr;
+
+ //font_description_dump( target );
+
+ GList *styles = default_styles;
+ if (row[FontList.onSystem] && !row[FontList.styles]) {
+ row[FontList.styles] = font_factory::Default()->GetUIStyles(row[FontList.pango_family]);
+ styles = row[FontList.styles];
+ }
+
+ for (GList *l = styles; l; l = l->next) {
+ Glib::ustring fontspec = family + ", " + ((StyleNames *)l->data)->CssName;
+ PangoFontDescription *candidate = pango_font_description_from_string(fontspec.c_str());
+ //font_description_dump( candidate );
+ //std::cout << " " << font_description_better_match( target, best, candidate ) << std::endl;
+ if (font_description_better_match(target, best, candidate)) {
+ pango_font_description_free(best);
+ best = candidate;
+ //std::cout << " ... better: " << std::endl;
+ } else {
+ pango_font_description_free(candidate);
+ //std::cout << " ... not better: " << std::endl;
+ }
+ }
+
+ Glib::ustring best_style = target_style;
+ if (best) {
+ pango_font_description_unset_fields(best, PANGO_FONT_MASK_FAMILY);
+ best_style = pango_font_description_to_string(best);
+ }
+
+ if (target)
+ pango_font_description_free(target);
+ if (best)
+ pango_font_description_free(best);
+
+
+#ifdef DEBUG_FONT
+ std::cout << " Returning: " << best_style << std::endl;
+ std::cout << "FontLister::get_best_style_match: exit" << std::endl;
+ std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" << std::endl;
+#endif
+ return best_style;
+}
+
+const Glib::RefPtr<Gtk::ListStore> FontLister::get_font_list() const
+{
+ return font_list_store;
+}
+
+const Glib::RefPtr<Gtk::ListStore> FontLister::get_style_list() const
+{
+ return style_list_store;
+}
+
+} // namespace Inkscape
+
+// Helper functions
+
+// Separator function (if true, a separator will be drawn)
+bool font_lister_separator_func(const Glib::RefPtr<Gtk::TreeModel>& model,
+ const Gtk::TreeModel::iterator& iter) {
+
+ // Of what use is 'model', can we avoid using font_lister?
+ Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
+ Gtk::TreeModel::Row row = *iter;
+ Glib::ustring entry = row[font_lister->FontList.family];
+ return entry == "#";
+}
+
+// Needed until Text toolbar updated
+gboolean font_lister_separator_func2(GtkTreeModel *model, GtkTreeIter *iter, gpointer /*data*/)
+{
+ gchar *text = nullptr;
+ gtk_tree_model_get(model, iter, 0, &text, -1); // Column 0: FontList.family
+ bool result = (text && strcmp(text, "#") == 0);
+ g_free(text);
+ return result;
+}
+
+// first call do nothing
+void font_lister_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter)
+{
+}
+
+// Draw system fonts in dark blue, missing fonts with red strikeout.
+// Used by both FontSelector and Text toolbar.
+void font_lister_cell_data_func_markup (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter)
+{
+ Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
+ Glib::ustring markup = font_lister->get_font_family_markup(iter);
+ renderer->set_property("markup", markup);
+}
+
+// Needed until Text toolbar updated
+void font_lister_cell_data_func2(GtkCellLayout * /*cell_layout*/,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gchar *family;
+ gboolean onSystem = false;
+ gtk_tree_model_get(model, iter, 0, &family, 2, &onSystem, -1);
+ gchar* family_escaped = g_markup_escape_text(family, -1);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool dark = prefs->getBool("/theme/darkTheme", false);
+ Glib::ustring markup;
+
+ if (!onSystem) {
+ markup = "<span font-weight='bold'>";
+
+ /* See if font-family on system */
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", family);
+ for (auto token : tokens) {
+
+ GtkTreeIter iter;
+ gboolean valid;
+ gboolean onSystem = true;
+ gboolean found = false;
+ for (valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter);
+ valid;
+ valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter)) {
+
+ gchar *token_family = nullptr;
+ gtk_tree_model_get(model, &iter, 0, &token_family, 2, &onSystem, -1);
+ if (onSystem && familyNamesAreEqual(token, token_family)) {
+ found = true;
+ g_free(token_family);
+ break;
+ }
+ g_free(token_family);
+ }
+ if (found) {
+ markup += g_markup_escape_text(token.c_str(), -1);
+ markup += ", ";
+ } else {
+ if (dark) {
+ markup += "<span strikethrough='true' strikethrough_color='salmon'>";
+ } else {
+ markup += "<span strikethrough='true' strikethrough_color='red'>";
+ }
+ markup += g_markup_escape_text(token.c_str(), -1);
+ markup += "</span>";
+ markup += ", ";
+ }
+ }
+ // Remove extra comma and space from end.
+ if (markup.size() >= 2) {
+ markup.resize(markup.size() - 2);
+ }
+ markup += "</span>";
+ // std::cout << markup << std::endl;
+ } else {
+ markup = family_escaped;
+ }
+
+ int show_sample = prefs->getInt("/tools/text/show_sample_in_list", 1);
+ if (show_sample) {
+
+ Glib::ustring sample = prefs->getString("/tools/text/font_sample");
+ gchar* sample_escaped = g_markup_escape_text(sample.data(), -1);
+ if (data) {
+ markup += " <span alpha='55%";
+ markup += "' font_family='";
+ markup += family_escaped;
+ } else {
+ markup += " <span alpha='1";
+ }
+ markup += "'>";
+ markup += sample_escaped;
+ markup += "</span>";
+ g_free(sample_escaped);
+ }
+
+ g_object_set(G_OBJECT(cell), "markup", markup.c_str(), nullptr);
+ g_free(family);
+ g_free(family_escaped);
+}
+
+// Draw Face name with face style.
+void font_lister_style_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter)
+{
+ Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance();
+ Gtk::TreeModel::Row row = *iter;
+
+ Glib::ustring family = font_lister->get_font_family();
+ Glib::ustring style = row[font_lister->FontStyleList.cssStyle];
+
+ Glib::ustring style_escaped = Glib::Markup::escape_text( style );
+ Glib::ustring font_desc = family + ", " + style;
+ Glib::ustring markup;
+
+ markup = "<span font='" + font_desc + "'>" + style_escaped + "</span>";
+ std::cout << " markup: " << markup << std::endl;
+
+ renderer->set_property("markup", markup);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/libnrtype/font-lister.h b/src/libnrtype/font-lister.h
new file mode 100644
index 0000000..5d73119
--- /dev/null
+++ b/src/libnrtype/font-lister.h
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef FONT_LISTER_H
+#define FONT_LISTER_H
+
+/*
+ * Font selection widgets
+ *
+ * Authors:
+ * Chris Lahey <clahey@ximian.com>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Tavmjong Bah <tavmjong@free.fr>
+ *
+ * Copyright (C) 1999-2001 Ximian, Inc.
+ * Copyright (C) 2002 Lauris Kaplinski
+ * Copyright (C) 2013 Tavmjong Bah
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <map>
+#include <set>
+
+#include <glibmm/ustring.h>
+#include <glibmm/stringutils.h> // For strescape()
+
+#include <gtkmm/liststore.h>
+#include <gtkmm/treemodelcolumn.h>
+#include <gtkmm/treepath.h>
+
+class SPObject;
+class SPDocument;
+class SPCSSAttr;
+class SPStyle;
+
+namespace Gtk {
+class CellRenderer;
+}
+
+namespace Inkscape {
+
+/**
+ * This class enumerates fonts using libnrtype into reusable data stores and
+ * allows for random access to the font-family list and the font-style list.
+ * Setting the font-family updates the font-style list. "Style" in this case
+ * refers to everything but family and size (e.g. italic/oblique, weight).
+ *
+ * This class handles font-family lists and fonts that are not on the system,
+ * where there is not an entry in the fontInstanceMap.
+ *
+ * This class uses the idea of "font_spec". This is a plain text string as used by
+ * Pango. It is similar to the CSS font shorthand except that font-family comes
+ * first and in this class the font-size is not used.
+ *
+ * This class uses the FontFactory class to get a list of system fonts
+ * and to find best matches via Pango. The Pango interface is only setup
+ * to deal with fonts that are on the system so care must be taken. For
+ * example, best matches should only be done with the first font-family
+ * in a font-family list. If the first font-family is not on the system
+ * then a generic font-family should be used (sans-serif -> Sans).
+ *
+ * This class is used by the UI interface (text-toolbar, font-select, etc.).
+ * Those items can change the selected font family and style here. When that
+ * happens. this class emits a signal for those items to update their displayed
+ * values.
+ *
+ * This class is a singleton (one instance per Inkscape session). Since fonts
+ * used in a document are added to the list, there really should be one
+ * instance per document.
+ *
+ * "Font" includes family and style. It should not be used when one
+ * means font-family.
+ */
+
+class FontLister {
+public:
+ enum Exceptions {
+ FAMILY_NOT_FOUND,
+ STYLE_NOT_FOUND
+ };
+
+ virtual ~FontLister();
+
+ /**
+ * GtkTreeModelColumnRecord for the font-family list Gtk::ListStore
+ */
+ struct FontListClass : public Gtk::TreeModelColumnRecord {
+ /**
+ * Column containing the family name
+ */
+ Gtk::TreeModelColumn<Glib::ustring> family;
+
+ /**
+ * Column containing the styles for each family name.
+ */
+ Gtk::TreeModelColumn<GList *> styles;
+
+ /**
+ * Column containing flag if font is on system
+ */
+ Gtk::TreeModelColumn<bool> onSystem;
+
+ /**
+ * Not actually a column.
+ * Necessary for quick initialization of FontLister,
+ * we initially store the pango family and if the
+ * font style is actually used we'll cache it in
+ * %styles.
+ */
+ Gtk::TreeModelColumn<PangoFontFamily *> pango_family;
+
+ FontListClass()
+ {
+ add(family);
+ add(styles);
+ add(onSystem);
+ add(pango_family);
+ }
+ };
+
+ FontListClass FontList;
+
+ struct FontStyleListClass : public Gtk::TreeModelColumnRecord {
+ /**
+ * Column containing the styles as Font designer used.
+ */
+ Gtk::TreeModelColumn<Glib::ustring> displayStyle;
+
+ /**
+ * Column containing the styles in CSS/Pango format.
+ */
+ Gtk::TreeModelColumn<Glib::ustring> cssStyle;
+
+ FontStyleListClass()
+ {
+ add(cssStyle);
+ add(displayStyle);
+ }
+ };
+
+ FontStyleListClass FontStyleList;
+
+ /**
+ * @return the ListStore with the family names
+ *
+ * The return is const and the function is declared as const.
+ * The ListStore is ready to be used after class instantiation
+ * and should not be modified.
+ */
+ const Glib::RefPtr<Gtk::ListStore> get_font_list() const;
+
+ /**
+ * @return the ListStore with the styles
+ */
+ const Glib::RefPtr<Gtk::ListStore> get_style_list() const;
+
+ /**
+ * Inserts a font family or font-fallback list (for use when not
+ * already in document or on system).
+ */
+ void insert_font_family(Glib::ustring new_family);
+
+ /**
+ * Updates font list to include fonts in document.
+ */
+ void update_font_list(SPDocument *document);
+
+public:
+ static Inkscape::FontLister *get_instance();
+
+ /**
+ * Takes a hand written font spec and returns a Pango generated one in
+ * standard form.
+ */
+ Glib::ustring canonize_fontspec(Glib::ustring fontspec);
+
+ /**
+ * Find closest system font to given font.
+ */
+ Glib::ustring system_fontspec(Glib::ustring fontspec);
+
+ /**
+ * Gets font-family and style from fontspec.
+ * font-family and style returned.
+ */
+ std::pair<Glib::ustring, Glib::ustring> ui_from_fontspec(Glib::ustring fontspec);
+
+ /**
+ * Sets font-family and style after a selection change.
+ * New font-family and style returned.
+ */
+ std::pair<Glib::ustring, Glib::ustring> selection_update();
+
+ /**
+ * Sets current_fontspec, etc. If check is false, won't
+ * try to find best style match (assumes style in fontspec
+ * valid for given font-family).
+ */
+ void set_fontspec(Glib::ustring fontspec, bool check = true);
+
+ Glib::ustring get_fontspec() { return (canonize_fontspec(current_family + ", " + current_style)); }
+
+ /**
+ * Changes font-family, updating style list and attempting to find
+ * closest style to current_style style (if check_style is true).
+ * New font-family and style returned.
+ * Does NOT update current_family and current_style.
+ * (For potential use in font-selector which doesn't update until
+ * "Apply" button clicked.)
+ */
+ std::pair<Glib::ustring, Glib::ustring> new_font_family(Glib::ustring family, bool check_style = true);
+
+ /**
+ * Sets font-family, updating style list and attempting
+ * to find closest style to old current_style.
+ * New font-family and style returned.
+ * Updates current_family and current_style.
+ * Calls new_font_family().
+ * (For use in text-toolbar where update is immediate.)
+ */
+ std::pair<Glib::ustring, Glib::ustring> set_font_family(Glib::ustring family, bool check_style = true,
+ bool emit = true);
+
+ /**
+ * Sets font-family from row in list store.
+ * The row can be used to determine if we are in the
+ * document or system part of the font-family list.
+ * This is needed to handle scrolling through the
+ * font-family list correctly.
+ * Calls set_font_family().
+ */
+ std::pair<Glib::ustring, Glib::ustring> set_font_family(int row, bool check_style = true, bool emit = true);
+
+ Glib::ustring get_font_family()
+ {
+ return current_family;
+ }
+
+ int get_font_family_row()
+ {
+ return current_family_row;
+ }
+
+ /**
+ * Sets style. Does not validate style for family.
+ */
+ void set_font_style(Glib::ustring style, bool emit = true);
+
+ Glib::ustring get_font_style()
+ {
+ return current_style;
+ }
+
+ Glib::ustring fontspec_from_style(SPStyle *style);
+
+ /**
+ * Fill css using given fontspec (doesn't need to be member function).
+ */
+ void fill_css(SPCSSAttr *css, Glib::ustring fontspec = "");
+
+ Gtk::TreeModel::Row get_row_for_font() { return get_row_for_font (current_family); }
+
+ Gtk::TreeModel::Row get_row_for_font(Glib::ustring family);
+
+ Gtk::TreePath get_path_for_font(Glib::ustring family);
+
+ bool is_path_for_font(Gtk::TreePath path, Glib::ustring family);
+
+ Gtk::TreeModel::Row get_row_for_style() { return get_row_for_style (current_style); }
+
+ Gtk::TreeModel::Row get_row_for_style(Glib::ustring style);
+
+ Gtk::TreePath get_path_for_style(Glib::ustring style);
+
+ std::pair<Gtk::TreePath, Gtk::TreePath> get_paths(Glib::ustring family, Glib::ustring style);
+
+ /**
+ * Return best style match for new font given style for old font.
+ */
+ Glib::ustring get_best_style_match(Glib::ustring family, Glib::ustring style);
+
+ /**
+ * Ensures the style list for a particular family has been created.
+ */
+ void ensureRowStyles(Glib::RefPtr<Gtk::TreeModel> model, Gtk::TreeModel::iterator const iter);
+
+ /**
+ * Get markup for font-family.
+ */
+ Glib::ustring get_font_family_markup(Gtk::TreeIter const &iter);
+
+ /**
+ * Let users of FontLister know to update GUI.
+ * This is to allow synchronization of changes across multiple widgets.
+ * Handlers should block signals.
+ * Input is fontspec to set.
+ */
+ sigc::connection connectUpdate(sigc::slot<void> slot) {
+ return update_signal.connect(slot);
+ }
+
+ bool blocked() { return block; }
+
+private:
+ FontLister();
+
+ void update_font_data_recursive(SPObject& r, std::map<Glib::ustring, std::set<Glib::ustring>> &font_data);
+
+ void font_family_row_update(int start=0);
+
+ Glib::RefPtr<Gtk::ListStore> font_list_store;
+ Glib::RefPtr<Gtk::ListStore> style_list_store;
+
+ /**
+ * Info for currently selected font (what is shown in the UI).
+ * May include font-family lists and fonts not on system.
+ */
+ int current_family_row;
+ Glib::ustring current_family;
+ Glib::ustring current_style;
+
+ /**
+ * If a font-family is not on system, this list of styles is used.
+ */
+ GList *default_styles;
+
+ bool block;
+ void emit_update();
+ sigc::signal<void> update_signal;
+};
+
+} // namespace Inkscape
+
+// Helper functions
+bool font_lister_separator_func (const Glib::RefPtr<Gtk::TreeModel>& model,
+ const Gtk::TreeModel::iterator& iter);
+
+gboolean font_lister_separator_func2(GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer /*data*/);
+
+void font_lister_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter);
+
+void font_lister_cell_data_func_markup (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter);
+
+void font_lister_cell_data_func2(GtkCellLayout * /*cell_layout*/,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer /*data*/);
+
+void font_lister_style_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter);
+
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/libnrtype/font-style.h b/src/libnrtype/font-style.h
new file mode 100644
index 0000000..23a26d6
--- /dev/null
+++ b/src/libnrtype/font-style.h
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_LIBNRTYPE_FONT_STYLE_H
+#define SEEN_LIBNRTYPE_FONT_STYLE_H
+
+#include <2geom/affine.h>
+#include <livarot/LivarotDefs.h>
+
+// structure that holds data describing how to render glyphs of a font
+
+// Different raster styles.
+struct font_style {
+ Geom::Affine transform; // the ctm. contains the font-size
+ bool vertical; // should be rendered vertically or not?
+ // good font support would take the glyph alternates for vertical mode, when present
+ double stroke_width; // if 0, the glyph is filled; otherwise stroked
+ JoinType stroke_join;
+ ButtType stroke_cap;
+ float stroke_miter_limit;
+ int nbDash;
+ double dash_offset;
+ double* dashes;
+};
+
+
+#endif /* !SEEN_LIBNRTYPE_FONT_STYLE_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :