summaryrefslogtreecommitdiffstats
path: root/src/libnrtype
diff options
context:
space:
mode:
authorMenTaLguY <mental@rydia.net>2006-01-16 02:36:01 +0000
committermental <mental@users.sourceforge.net>2006-01-16 02:36:01 +0000
commit179fa413b047bede6e32109e2ce82437c5fb8d34 (patch)
treea5a6ac2c1708bd02288fbd8edb2ff500ff2e0916 /src/libnrtype
downloadinkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.tar.gz
inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.zip
moving trunk for module inkscape
(bzr r1)
Diffstat (limited to 'src/libnrtype')
-rw-r--r--src/libnrtype/.cvsignore5
-rw-r--r--src/libnrtype/FontFactory.cpp630
-rw-r--r--src/libnrtype/FontFactory.h112
-rw-r--r--src/libnrtype/FontInstance.cpp763
-rwxr-xr-xsrc/libnrtype/Layout-TNG-Compute.cpp1514
-rwxr-xr-xsrc/libnrtype/Layout-TNG-Input.cpp270
-rwxr-xr-xsrc/libnrtype/Layout-TNG-OutIter.cpp994
-rwxr-xr-xsrc/libnrtype/Layout-TNG-Output.cpp469
-rwxr-xr-xsrc/libnrtype/Layout-TNG-Scanline-Maker.h169
-rwxr-xr-xsrc/libnrtype/Layout-TNG-Scanline-Makers.cpp189
-rwxr-xr-xsrc/libnrtype/Layout-TNG.cpp45
-rwxr-xr-xsrc/libnrtype/Layout-TNG.h1029
-rw-r--r--src/libnrtype/Makefile_insert42
-rw-r--r--src/libnrtype/RasterFont.cpp431
-rw-r--r--src/libnrtype/RasterFont.h66
-rw-r--r--src/libnrtype/TextWrapper.cpp937
-rw-r--r--src/libnrtype/TextWrapper.h139
-rw-r--r--src/libnrtype/boundary-type.h31
-rw-r--r--src/libnrtype/font-glyph.h29
-rw-r--r--src/libnrtype/font-instance.h121
-rw-r--r--src/libnrtype/font-style-to-pos.cpp120
-rw-r--r--src/libnrtype/font-style-to-pos.h20
-rw-r--r--src/libnrtype/font-style.h38
-rw-r--r--src/libnrtype/libnrtype.def59
-rw-r--r--src/libnrtype/makefile.in17
-rw-r--r--src/libnrtype/nr-type-pos-def.cpp268
-rw-r--r--src/libnrtype/nr-type-pos-def.h102
-rw-r--r--src/libnrtype/nr-type-primitives.cpp167
-rw-r--r--src/libnrtype/nr-type-primitives.h50
-rw-r--r--src/libnrtype/nrtype-forward.h23
-rw-r--r--src/libnrtype/one-box.h28
-rw-r--r--src/libnrtype/one-glyph.h46
-rw-r--r--src/libnrtype/one-para.h20
-rw-r--r--src/libnrtype/raster-glyph.h49
-rw-r--r--src/libnrtype/raster-position.h46
-rw-r--r--src/libnrtype/text-boundary.h49
36 files changed, 9087 insertions, 0 deletions
diff --git a/src/libnrtype/.cvsignore b/src/libnrtype/.cvsignore
new file mode 100644
index 000000000..e8014d011
--- /dev/null
+++ b/src/libnrtype/.cvsignore
@@ -0,0 +1,5 @@
+Makefile
+Makefile.in
+.deps
+makefile
+.dirstamp
diff --git a/src/libnrtype/FontFactory.cpp b/src/libnrtype/FontFactory.cpp
new file mode 100644
index 000000000..5fc7e0b3b
--- /dev/null
+++ b/src/libnrtype/FontFactory.cpp
@@ -0,0 +1,630 @@
+/*
+ * FontFactory.cpp
+ * testICU
+ *
+ * Authors:
+ * fred
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ */
+
+#include "FontFactory.h"
+#include <libnrtype/font-instance.h>
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glibmm/i18n.h> // _()
+
+/* Freetype2 */
+# include <pango/pangoft2.h>
+
+
+// need to avoid using the size field
+size_t font_descr_hash::operator()( PangoFontDescription *const &x) const {
+ int h = 0;
+ h *= 1128467;
+ char const *theF = pango_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);
+ return h;
+}
+bool font_descr_equal::operator()( PangoFontDescription *const&a, PangoFontDescription *const &b) {
+ //if ( pango_font_description_equal(a,b) ) return true;
+ char const *fa = pango_font_description_get_family(a);
+ char const *fb = pango_font_description_get_family(b);
+ if ( ( fa && fb == NULL ) || ( fb && fa == NULL ) ) 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;
+ return true;
+}
+
+/////////////////// helper functions
+
+/**
+ * A wrapper for strcasestr that also provides an implementation for Win32.
+ */
+static bool
+ink_strstr(char const *haystack, char const *pneedle)
+{
+ // windows has no strcasestr implementation, so here is ours...
+ // stolen from nmap
+ /* FIXME: This is broken for e.g. ink_strstr("aab", "ab"). Report to nmap.
+ *
+ * Also, suggest use of g_ascii_todown instead of buffer stuff, and g_ascii_tolower instead
+ * of tolower. Given that haystack is a font name (i.e. fairly short), it should be ok to
+ * do g_ascii_strdown on both haystack and pneedle, and do normal strstr.
+ *
+ * Rather than fixing in inkscape, consider getting rid of this routine, instead using
+ * strdown and plain strstr at caller. We have control over the needle values, so we can
+ * modify the callers rather than calling strdown there.
+ */
+ char buf[512];
+ register char const *p;
+ char *needle, *q, *foundto;
+ if (!*pneedle) return true;
+ if (!haystack) return false;
+
+ needle = buf;
+ p = pneedle; q = needle;
+ while ((*q++ = tolower(*p++)))
+ ;
+ p = haystack - 1; foundto = needle;
+ while (*++p) {
+ if (tolower(*p) == *foundto) {
+ if (!*++foundto) {
+ /* Yeah, we found it */
+ return true;
+ }
+ } else foundto = needle;
+ }
+ return false;
+}
+
+/**
+ * Regular fonts are 'Regular', 'Roman', 'Normal', or 'Plain'
+ */
+// FIXME: make this UTF8, add non-English style names
+static bool
+is_regular(char const *s)
+{
+ if (ink_strstr(s, "Regular")) return true;
+ if (ink_strstr(s, "Roman")) return true;
+ if (ink_strstr(s, "Normal")) return true;
+ if (ink_strstr(s, "Plain")) return true;
+ return false;
+}
+
+/**
+ * Non-bold fonts are 'Medium' or 'Book'
+ */
+static bool
+is_nonbold(char const *s)
+{
+ if (ink_strstr(s, "Medium")) return true;
+ if (ink_strstr(s, "Book")) return true;
+ return false;
+}
+
+/**
+ * Italic fonts are 'Italic', 'Oblique', or 'Slanted'
+ */
+static bool
+is_italic(char const *s)
+{
+ if (ink_strstr(s, "Italic")) return true;
+ if (ink_strstr(s, "Oblique")) return true;
+ if (ink_strstr(s, "Slanted")) return true;
+ return false;
+}
+
+/**
+ * Bold fonts are 'Bold'
+ */
+static bool
+is_bold(char const *s)
+{
+ if (ink_strstr(s, "Bold")) return true;
+ return false;
+}
+
+/**
+ * Caps fonts are 'Caps'
+ */
+static bool
+is_caps(char const *s)
+{
+ if (ink_strstr(s, "Caps")) return true;
+ return false;
+}
+
+#if 0 /* FIXME: These are all unused. Please delete them or use them (presumably in
+* style_name_compare). */
+/**
+ * Monospaced fonts are 'Mono'
+ */
+static bool
+is_mono(char const *s)
+{
+ if (ink_strstr(s, "Mono")) return true;
+ return false;
+}
+
+/**
+ * Rounded fonts are 'Round'
+ */
+static bool
+is_round(char const *s)
+{
+ if (ink_strstr(s, "Round")) return true;
+ return false;
+}
+
+/**
+ * Outline fonts are 'Outline'
+ */
+static bool
+is_outline(char const *s)
+{
+ if (ink_strstr(s, "Outline")) return true;
+ return false;
+}
+
+/**
+ * Swash fonts are 'Swash'
+ */
+static bool
+is_swash(char const *s)
+{
+ if (ink_strstr(s, "Swash")) return true;
+ return false;
+}
+#endif
+
+/**
+ * Determines if two style names match. This allows us to match
+ * based on the type of style rather than simply doing string matching,
+ * because for instance 'Plain' and 'Normal' mean the same thing.
+ *
+ * Q: Shouldn't this include the other tests such as is_outline, etc.?
+ * Q: Is there a problem with strcasecmp on Win32? Should it use stricmp?
+ */
+static int
+style_name_compare(void const *aa, void const *bb)
+{
+ char const *a = (char const *) aa;
+ char const *b = (char const *) bb;
+
+ if (is_regular(a) && !is_regular(b)) return -1;
+ if (is_regular(b) && !is_regular(a)) return 1;
+
+ if (is_bold(a) && !is_bold(b)) return 1;
+ if (is_bold(b) && !is_bold(a)) return -1;
+
+ if (is_italic(a) && !is_italic(b)) return 1;
+ if (is_italic(b) && !is_italic(a)) return -1;
+
+ if (is_nonbold(a) && !is_nonbold(b)) return 1;
+ if (is_nonbold(b) && !is_nonbold(a)) return -1;
+
+ if (is_caps(a) && !is_caps(b)) return 1;
+ if (is_caps(b) && !is_caps(a)) return -1;
+
+ return strcasecmp(a, b);
+}
+
+static int
+style_record_compare(void const *aa, void const *bb)
+{
+ NRStyleRecord const *a = (NRStyleRecord const *) aa;
+ NRStyleRecord const *b = (NRStyleRecord const *) bb;
+
+ return (style_name_compare(a->name, b->name));
+}
+
+static void font_factory_name_list_destructor(NRNameList *list)
+{
+ for (unsigned int i = 0; i < list->length; i++)
+ free(list->names[i]);
+ if ( list->names ) nr_free(list->names);
+}
+
+static void font_factory_style_list_destructor(NRStyleList *list)
+{
+ for (unsigned int i = 0; i < list->length; i++) {
+ free((void *) (list->records)[i].name);
+ free((void *) (list->records)[i].descr);
+ }
+ if ( list->records ) nr_free(list->records);
+}
+
+/**
+ * On Win32 performs a stricmp(a,b), otherwise does a strcasecmp(a,b)
+ */
+static int
+family_name_compare(void const *a, void const *b)
+{
+#ifndef WIN32
+ return strcasecmp((*((char const **) a)), (*((char const **) b)));
+#else
+ return stricmp((*((char const **) a)), (*((char const **) b)));
+#endif
+}
+
+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
+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 = NULL;
+
+font_factory *font_factory::Default(void)
+{
+ if ( lUsine == NULL ) lUsine = new font_factory;
+ return lUsine;
+}
+
+font_factory::font_factory(void)
+{
+ fontSize = 512;
+ nbEnt = 0;
+ maxEnt = 32;
+ ents = (font_entry*)malloc(maxEnt*sizeof(font_entry));
+
+#ifdef USE_PANGO_WIN32
+ hScreenDC = pango_win32_get_dc();
+ fontServer = pango_win32_font_map_for_display();
+ fontContext = pango_win32_get_context();
+ pangoFontCache = pango_win32_font_map_get_font_cache(fontServer);
+#else
+ fontServer = pango_ft2_font_map_new();
+ pango_ft2_font_map_set_resolution((PangoFT2FontMap*)fontServer, 72, 72);
+ fontContext = pango_ft2_font_map_create_context((PangoFT2FontMap*)fontServer);
+ pango_ft2_font_map_set_default_substitute((PangoFT2FontMap*)fontServer,FactorySubstituteFunc,this,NULL);
+#endif
+}
+
+font_factory::~font_factory(void)
+{
+ for (int i = 0;i < nbEnt;i++) ents[i].f->Unref();
+ if ( ents ) free(ents);
+
+ g_object_unref(fontServer);
+#ifdef USE_PANGO_WIN32
+ pango_win32_shutdown_display();
+#else
+ //pango_ft2_shutdown_display();
+#endif
+ //g_object_unref(fontContext);
+}
+
+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::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 = NULL;
+
+ if ( loadedFaces.find(descr) == loadedFaces.end() ) {
+ // not yet loaded
+ PangoFont *nFace = NULL;
+
+ // workaround for bug #1025565.
+ // fonts without families blow up Pango.
+ if (pango_font_description_get_family(descr) != NULL) {
+ nFace = pango_font_map_load_font(fontServer,fontContext,descr);
+ }
+ else {
+ g_warning(_("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 hash_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->daddy = this;
+ res->InstallFace(nFace);
+ if ( res->pFont == NULL ) {
+ // failed to install face -> bitmap font
+ // printf("face failed\n");
+ res->daddy = NULL;
+ delete res;
+ res = NULL;
+ if ( canFail ) {
+ char *tc = pango_font_description_to_string(descr);
+ PANGO_DEBUG("falling back from %s to Sans because InstallFace failed\n",tc);
+ free(tc);
+ pango_font_description_set_family(descr,"Sans");
+ res = Face(descr,false);
+ }
+ } else {
+ loadedFaces[res->descr]=res;
+ res->Ref();
+ AddInCache(res);
+ }
+ } else {
+ // no match
+ if ( canFail ) {
+ PANGO_DEBUG("falling back to Sans\n");
+ pango_font_description_set_family(descr,"Sans");
+ res = Face(descr,false);
+ }
+ }
+ } else {
+ // already here
+ res = loadedFaces[descr];
+ res->Ref();
+ AddInCache(res);
+ }
+ res->InitTheFace();
+ return res;
+}
+
+font_instance *font_factory::Face(char const *family, int variant, int style, int weight, int stretch, int /*size*/, int /*spacing*/)
+{
+ 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;
+}
+
+font_instance *font_factory::Face(char const *family, NRTypePosDef apos)
+{
+ PangoFontDescription *temp_descr = pango_font_description_new();
+
+ pango_font_description_set_family(temp_descr, family);
+
+ if ( apos.variant == NR_POS_VARIANT_SMALLCAPS ) {
+ pango_font_description_set_variant(temp_descr, PANGO_VARIANT_SMALL_CAPS);
+ } else {
+ pango_font_description_set_variant(temp_descr, PANGO_VARIANT_NORMAL);
+ }
+
+ if ( apos.italic ) {
+ pango_font_description_set_style(temp_descr, PANGO_STYLE_ITALIC);
+ } else if ( apos.oblique ) {
+ pango_font_description_set_style(temp_descr, PANGO_STYLE_OBLIQUE);
+ } else {
+ pango_font_description_set_style(temp_descr, PANGO_STYLE_NORMAL);
+ }
+
+ if ( apos.weight <= NR_POS_WEIGHT_ULTRA_LIGHT ) {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_ULTRALIGHT);
+ } else if ( apos.weight <= NR_POS_WEIGHT_LIGHT ) {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_LIGHT);
+ } else if ( apos.weight <= NR_POS_WEIGHT_NORMAL ) {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_NORMAL);
+ } else if ( apos.weight <= NR_POS_WEIGHT_BOLD ) {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_BOLD);
+ } else if ( apos.weight <= NR_POS_WEIGHT_ULTRA_BOLD ) {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_ULTRABOLD);
+ } else {
+ pango_font_description_set_weight(temp_descr, PANGO_WEIGHT_HEAVY);
+ }
+
+ if ( apos.stretch <= NR_POS_STRETCH_ULTRA_CONDENSED ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_EXTRA_CONDENSED);
+ } else if ( apos.stretch <= NR_POS_STRETCH_CONDENSED ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_CONDENSED);
+ } else if ( apos.stretch <= NR_POS_STRETCH_SEMI_CONDENSED ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_SEMI_CONDENSED);
+ } else if ( apos.stretch <= NR_POS_WEIGHT_NORMAL ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_NORMAL);
+ } else if ( apos.stretch <= NR_POS_STRETCH_SEMI_EXPANDED ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_SEMI_EXPANDED);
+ } else if ( apos.stretch <= NR_POS_STRETCH_EXPANDED ) {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_EXPANDED);
+ } else {
+ pango_font_description_set_stretch(temp_descr, PANGO_STRETCH_EXTRA_EXPANDED);
+ }
+
+ font_instance *res = Face(temp_descr);
+ pango_font_description_free(temp_descr);
+ return res;
+}
+
+void font_factory::UnrefFace(font_instance *who)
+{
+ if ( who == NULL ) return;
+ 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);
+ free(tc);
+ } else {
+ loadedFaces.erase(loadedFaces.find(who->descr));
+ // printf("unrefFace %p: success\n",who);
+ }
+}
+
+NRNameList *font_factory::Families(NRNameList *flist)
+{
+ PangoFontFamily** fams = NULL;
+ int nbFam = 0;
+ pango_font_map_list_families(fontServer, &fams, &nbFam);
+
+ PANGO_DEBUG("got %d families\n", nbFam);
+
+ flist->length = nbFam;
+ flist->names = (guchar **)malloc(nbFam*sizeof(guchar*));
+ flist->destructor = font_factory_name_list_destructor;
+
+ for (int i = 0;i < nbFam;i++) {
+// Note: on Windows, pango_font_family_get_name always returns lowercase name.
+// As a result the list of fonts in the dialog is lowercase.
+// We could work around by loading the font and taking pango_font_description_get_family from its descr (that gives correct case),
+// but this is slow, and it's better to fix Pango instead.
+ flist->names[i]=(guchar*)strdup(pango_font_family_get_name(fams[i]));
+ }
+
+ qsort(flist->names, nbFam, sizeof(guchar *), family_name_compare);
+
+ g_free(fams);
+
+ return flist;
+}
+
+NRStyleList *font_factory::Styles(gchar const *family, NRStyleList *slist)
+{
+ PangoFontFamily *theFam = NULL;
+
+ // search available families
+ {
+ PangoFontFamily** fams = NULL;
+ int nbFam = 0;
+ pango_font_map_list_families(fontServer, &fams, &nbFam);
+
+ for (int i = 0;i < nbFam;i++) {
+ char const *fname = pango_font_family_get_name(fams[i]);
+ if ( fname && strcmp(family,fname) == 0 ) {
+ theFam = fams[i];
+ break;
+ }
+ }
+
+ g_free(fams);
+ }
+
+ // nothing found
+ if ( theFam == NULL ) {
+ slist->length = 0;
+ slist->records = NULL;
+ slist->destructor = NULL;
+ return slist;
+ }
+
+ // search faces in the found family
+ PangoFontFace** faces = NULL;
+ int nFaces = 0;
+ pango_font_family_list_faces(theFam, &faces, &nFaces);
+
+ slist->records = (NRStyleRecord *) malloc(nFaces * sizeof(NRStyleRecord));
+ slist->destructor = font_factory_style_list_destructor;
+
+ int nr = 0;
+ for (int i = 0; i < nFaces; i++) {
+
+ // no unnamed faces
+ if (pango_font_face_get_face_name(faces[i]) == NULL)
+ continue;
+ PangoFontDescription *nd = pango_font_face_describe(faces[i]);
+ if (nd == NULL)
+ continue;
+ char const *descr = pango_font_description_to_string(nd);
+ if (descr == NULL) {
+ pango_font_description_free(nd);
+ continue;
+ }
+
+ char const *name = g_strdup(pango_font_face_get_face_name(faces[i]));
+ pango_font_description_free(nd);
+
+ slist->records[nr].name = name;
+ slist->records[nr].descr = descr;
+ nr ++;
+ }
+
+ slist->length = nr;
+
+ qsort(slist->records, slist->length, sizeof(NRStyleRecord), style_record_compare);
+ /* effic: Consider doing strdown and all the is_italic etc. tests once off and store the
+ * results in a table, rather than having the sort invoke multiple is_italic tests per
+ * record.
+ */
+
+ g_free(faces);
+
+ return slist;
+}
+
+void font_factory::AddInCache(font_instance *who)
+{
+ if ( who == NULL ) 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 ) {
+ 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++;
+}
+
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/FontFactory.h b/src/libnrtype/FontFactory.h
new file mode 100644
index 000000000..2b4c6be0f
--- /dev/null
+++ b/src/libnrtype/FontFactory.h
@@ -0,0 +1,112 @@
+/*
+ * FontFactory.h
+ * testICU
+ *
+ */
+
+#ifndef my_font_factory
+#define my_font_factory
+
+#include <functional>
+#include <algorithm>
+#include <ext/hash_map>
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#ifdef _WIN32
+#define USE_PANGO_WIN32
+#endif
+
+#include <pango/pango.h>
+#include "nr-type-primitives.h"
+#include "nr-type-pos-def.h"
+#include <libnrtype/nrtype-forward.h>
+
+/* Freetype */
+#ifdef USE_PANGO_WIN32
+#include <pango/pangowin32.h>
+#else
+#include <pango/pangoft2.h>
+#include <freetype/freetype.h>
+#endif
+
+// 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);
+};
+
+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. */
+
+ __gnu_cxx::hash_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> loadedFaces;
+
+ font_factory();
+ ~font_factory();
+
+ /// Returns the default font_factory.
+ static font_factory* Default();
+
+ // Various functions to get a font_instance from different descriptions.
+ font_instance* FaceFromDescr(char const *family, char const *style);
+ 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);
+ font_instance* Face(char const *family, NRTypePosDef apos);
+
+ /// Semi-private: tells the font_factory taht the font_instance 'who' has died and should be removed from loadedFaces
+ void UnrefFace(font_instance* who);
+
+ // Queries for the font-selector.
+ NRNameList* Families(NRNameList *flist);
+ NRStyleList* Styles(const gchar *family, NRStyleList *slist);
+
+ // internal
+ void AddInCache(font_instance *who);
+};
+
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/FontInstance.cpp b/src/libnrtype/FontInstance.cpp
new file mode 100644
index 000000000..d2b19d0f2
--- /dev/null
+++ b/src/libnrtype/FontInstance.cpp
@@ -0,0 +1,763 @@
+/*
+ * FontInstance.cpp
+ * testICU
+ *
+ * Authors:
+ * fred
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <libnr/nr-rect.h>
+#include <libnrtype/font-glyph.h>
+#include <libnrtype/font-instance.h>
+
+/* #include <layout/LEGlyphStorage.h> */
+
+#include <livarot/Path.h>
+
+#include "RasterFont.h"
+
+/* Freetype 2 */
+# include <freetype/ftoutln.h>
+# include <freetype/ftbbox.h>
+# include <freetype/internal/tttypes.h>
+# include <freetype/internal/ftstream.h>
+# include <freetype/tttags.h>
+# include <pango/pangoft2.h>
+
+
+
+size_t font_style_hash::operator()(const font_style &x) const {
+ int h=0,n;
+ for (int i=0;i<6;i++) {
+ n=(int)floor(100*x.transform[i]);
+ h*=12186;
+ h+=n;
+ }
+ n=(int)floor(100*x.stroke_width);
+ h*=12186;
+ h+=n;
+ n=(x.vertical)?1:0;
+ h*=12186;
+ h+=n;
+ if ( x.stroke_width >= 0.01 ) {
+ n=x.stroke_cap*10+x.stroke_join+(int)(x.stroke_miter_limit*100);
+ h*=12186;
+ h+=n;
+ if ( x.nbDash > 0 ) {
+ n=x.nbDash;
+ h*=12186;
+ h+=n;
+ n=(int)floor(100*x.dash_offset);
+ h*=12186;
+ h+=n;
+ for (int i=0;i<x.nbDash;i++) {
+ n=(int)floor(100*x.dashes[i]);
+ h*=12186;
+ h+=n;
+ }
+ }
+ }
+ return h;
+}
+
+bool font_style_equal::operator()(const font_style &a,const font_style &b) {
+ NR::Matrix diff=a.transform.inverse();
+ diff*=b.transform;
+ if ( diff.is_translation(0.01) ) {
+ if ( fabs(diff[4]) < 0.01 && fabs(diff[5]) < 0.01 ) {
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ if ( a.vertical && b.vertical == false ) return false;
+ if ( a.vertical == false && b.vertical ) return false;
+ if ( a.stroke_width > 0.01 && b.stroke_width <= 0.01 ) return false;
+ if ( a.stroke_width <= 0.01 && b.stroke_width > 0.01 ) return false;
+ if ( a.stroke_width <= 0.01 && b.stroke_width <= 0.01 ) return true;
+
+ if ( a.stroke_cap != b.stroke_cap ) return false;
+ if ( a.stroke_join != b.stroke_join ) return false;
+ if ( fabs(a.stroke_miter_limit-b.stroke_miter_limit) > 0.01) return false;
+ if ( a.nbDash != b.nbDash ) return false;
+ if ( a.nbDash <= 0 ) return true;
+ if ( fabs(a.dash_offset-b.dash_offset) < 0.01 ) {
+ for (int i=0;i<a.nbDash;i++) {
+ if ( fabs(a.dashes[i]-b.dashes[i]) >= 0.01 ) return false;
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
+
+#ifndef USE_PANGO_WIN32
+/*
+ * Outline extraction
+ */
+typedef struct ft2_to_liv {
+ Path* theP;
+ double scale;
+ NR::Point last;
+} ft2_to_liv;
+
+// outline as returned by freetype -> livarot Path
+// see nr-type-ft2.cpp for the freetype -> artBPath on which this code is based
+static int ft2_move_to (FT_Vector * to, void * i_user) {
+ ft2_to_liv* user=(ft2_to_liv*)i_user;
+ NR::Point p(user->scale*to->x,user->scale*to->y);
+ // printf("m t=%f %f\n",p[0],p[1]);
+ user->theP->MoveTo(p);
+ user->last=p;
+ return 0;
+}
+
+static int ft2_line_to (FT_Vector * to, void * i_user)
+{
+ ft2_to_liv* user=(ft2_to_liv*)i_user;
+ NR::Point p(user->scale*to->x,user->scale*to->y);
+ // printf("l t=%f %f\n",p[0],p[1]);
+ user->theP->LineTo(p);
+ user->last=p;
+ return 0;
+}
+
+static int ft2_conic_to (FT_Vector * control, FT_Vector * to, void * i_user)
+{
+ ft2_to_liv* user=(ft2_to_liv*)i_user;
+ NR::Point p(user->scale*to->x,user->scale*to->y),c(user->scale*control->x,user->scale*control->y);
+ // printf("b c=%f %f t=%f %f\n",c[0],c[1],p[0],p[1]);
+ user->theP->BezierTo(p);
+ user->theP->IntermBezierTo(c);
+ user->theP->EndBezierTo();
+ user->last=p;
+ return 0;
+}
+
+static int ft2_cubic_to (FT_Vector * control1, FT_Vector * control2, FT_Vector * to, void * i_user)
+{
+ ft2_to_liv* user=(ft2_to_liv*)i_user;
+ NR::Point p(user->scale*to->x,user->scale*to->y),
+ c1(user->scale*control1->x,user->scale*control1->y),
+ c2(user->scale*control2->x,user->scale*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->last=p;
+ return 0;
+}
+#endif
+
+/*
+ *
+ */
+
+font_instance::font_instance(void)
+{
+ //printf("font instance born\n");
+ descr=NULL;
+ pFont=NULL;
+ refCount=0;
+ daddy=NULL;
+ nbGlyph=maxGlyph=0;
+ glyphs=NULL;
+ theFace=NULL;
+}
+
+font_instance::~font_instance(void)
+{
+ if ( daddy ) daddy->UnrefFace(this);
+ //printf("font instance death\n");
+ if ( pFont ) g_object_unref(pFont);
+ pFont=NULL;
+ if ( descr ) pango_font_description_free(descr);
+ descr=NULL;
+ // if ( theFace ) FT_Done_Face(theFace); // owned by pFont. don't touch
+ theFace=NULL;
+
+ for (int i=0;i<nbGlyph;i++) {
+ if ( glyphs[i].outline ) delete glyphs[i].outline;
+ if ( glyphs[i].artbpath ) free(glyphs[i].artbpath);
+ }
+ if ( glyphs ) free(glyphs);
+ nbGlyph=maxGlyph=0;
+ glyphs=NULL;
+}
+
+void font_instance::Ref(void)
+{
+ 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(void)
+{
+ 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 ) {
+ if ( daddy ) daddy->UnrefFace(this);
+ daddy=NULL;
+ delete this;
+ }
+}
+
+unsigned int font_instance::Name(gchar *str, unsigned int size)
+{
+ return Attribute("name", str, size);
+}
+
+unsigned int font_instance::Family(gchar *str, unsigned int size)
+{
+ return Attribute("family", str, size);
+}
+
+unsigned int font_instance::PSName(gchar *str, unsigned int size)
+{
+ return Attribute("psname", str, size);
+}
+
+unsigned int font_instance::Attribute(const gchar *key, gchar *str, unsigned int size)
+{
+ if ( descr == NULL ) {
+ if ( size > 0 ) str[0]=0;
+ return 0;
+ }
+ char* res=NULL;
+ bool free_res=false;
+
+ if ( strcmp(key,"name") == 0 ) {
+ PangoFontDescription* td=pango_font_description_copy(descr);
+ pango_font_description_unset_fields (td, PANGO_FONT_MASK_SIZE);
+ res=pango_font_description_to_string (td);
+ pango_font_description_free(td);
+ free_res=true;
+ } else if ( strcmp(key,"psname") == 0 ) {
+#ifndef USE_PANGO_WIN32
+ res = (char *) FT_Get_Postscript_Name (theFace); // that's the main method, seems to always work
+#endif
+ free_res=false;
+ if (res == NULL) { // a very limited workaround, only bold, italic, and oblique will work
+ PangoStyle style=pango_font_description_get_style(descr);
+ bool i = (style == PANGO_STYLE_ITALIC);
+ bool o = (style == PANGO_STYLE_OBLIQUE);
+ PangoWeight weight=pango_font_description_get_weight(descr);
+ bool b = (weight >= PANGO_WEIGHT_BOLD);
+
+ res = g_strdup_printf ("%s%s%s%s",
+ pango_font_description_get_family(descr),
+ (b || i || o) ? "-" : "",
+ (b) ? "Bold" : "",
+ (i) ? "Italic" : ((o) ? "Oblique" : "") );
+ free_res = true;
+ }
+ } else if ( strcmp(key,"family") == 0 ) {
+ res=(char*)pango_font_description_get_family(descr);
+ free_res=false;
+ } else if ( strcmp(key,"style") == 0 ) {
+ PangoStyle v=pango_font_description_get_style(descr);
+ if ( v == PANGO_STYLE_ITALIC ) {
+ res="italic";
+ } else if ( v == PANGO_STYLE_OBLIQUE ) {
+ res="oblique";
+ } else {
+ res="normal";
+ }
+ free_res=false;
+ } else if ( strcmp(key,"weight") == 0 ) {
+ PangoWeight v=pango_font_description_get_weight(descr);
+ if ( v <= PANGO_WEIGHT_ULTRALIGHT ) {
+ res="200";
+ } else if ( v <= PANGO_WEIGHT_LIGHT ) {
+ res="300";
+ } else if ( v <= PANGO_WEIGHT_NORMAL ) {
+ res="normal";
+ } else if ( v <= PANGO_WEIGHT_BOLD ) {
+ res="bold";
+ } else {
+ res="800";
+ }
+ free_res=false;
+ } else if ( strcmp(key,"stretch") == 0 ) {
+ PangoStretch v=pango_font_description_get_stretch(descr);
+ if ( v <= PANGO_STRETCH_EXTRA_CONDENSED ) {
+ res="extra-condensed";
+ } else if ( v <= PANGO_STRETCH_CONDENSED ) {
+ res="condensed";
+ } else if ( v <= PANGO_STRETCH_SEMI_CONDENSED ) {
+ res="semi-condensed";
+ } else if ( v <= PANGO_STRETCH_NORMAL ) {
+ res="normal";
+ } else if ( v <= PANGO_STRETCH_SEMI_EXPANDED ) {
+ res="semi-expanded";
+ } else if ( v <= PANGO_STRETCH_EXPANDED ) {
+ res="expanded";
+ } else {
+ res="extra-expanded";
+ }
+ free_res=false;
+ } else if ( strcmp(key,"variant") == 0 ) {
+ PangoVariant v=pango_font_description_get_variant(descr);
+ if ( v == PANGO_VARIANT_SMALL_CAPS ) {
+ res="small-caps";
+ } else {
+ res="normal";
+ }
+ free_res=false;
+ } else {
+ res = NULL;
+ free_res=false;
+ }
+ if ( res == NULL ) {
+ if ( size > 0 ) str[0]=0;
+ return 0;
+ }
+
+ if (res) {
+ unsigned int len=strlen(res);
+ unsigned int rlen=(size-1<len)?size-1:len;
+ if ( str ) {
+ if ( rlen > 0 ) memcpy(str,res,rlen);
+ if ( size > 0 ) str[rlen]=0;
+ }
+ if (free_res) free(res);
+ return len;
+ }
+ return 0;
+}
+
+void font_instance::InitTheFace()
+{
+#ifdef USE_PANGO_WIN32
+ if ( !theFace ) {
+ LOGFONT *lf=pango_win32_font_logfont(pFont);
+ g_assert(lf != NULL);
+ theFace=pango_win32_font_cache_load(daddy->pangoFontCache,lf);
+ g_free(lf);
+ }
+ XFORM identity = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0};
+ SetWorldTransform(daddy->hScreenDC, &identity);
+ SetGraphicsMode(daddy->hScreenDC, GM_COMPATIBLE);
+ SelectObject(daddy->hScreenDC,theFace);
+#else
+ theFace=pango_ft2_font_get_face(pFont);
+ FT_Select_Charmap(theFace,ft_encoding_unicode) && FT_Select_Charmap(theFace,ft_encoding_symbol);
+#endif
+}
+
+void font_instance::FreeTheFace()
+{
+#ifdef USE_PANGO_WIN32
+ SelectObject(daddy->hScreenDC,GetStockObject(SYSTEM_FONT));
+ pango_win32_font_cache_unload(daddy->pangoFontCache,theFace);
+#endif
+ theFace=NULL;
+}
+
+void font_instance::InstallFace(PangoFont* iFace)
+{
+ if ( !iFace )
+ return;
+ pFont=iFace;
+
+ InitTheFace();
+
+ if ( pFont && IsOutlineFont() == false ) {
+ FreeTheFace();
+ if ( pFont ) g_object_unref(pFont);
+ pFont=NULL;
+ }
+}
+
+bool font_instance::IsOutlineFont(void)
+{
+ if ( pFont == NULL ) return false;
+ InitTheFace();
+#ifdef USE_PANGO_WIN32
+ TEXTMETRIC tm;
+ return GetTextMetrics(daddy->hScreenDC,&tm) && tm.tmPitchAndFamily&TMPF_TRUETYPE;
+#else
+ return FT_IS_SCALABLE(theFace);
+#endif
+}
+
+int font_instance::MapUnicodeChar(gunichar c)
+{
+ if ( pFont == NULL ) return 0;
+#ifdef USE_PANGO_WIN32
+ return pango_win32_font_get_glyph_index(pFont,c);
+#else
+ int res=0;
+ theFace=pango_ft2_font_get_face(pFont);
+ if ( c > 0xf0000 ) {
+ res=CLAMP(c,0xf0000,0x1fffff)-0xf0000;
+ } else {
+ res=FT_Get_Char_Index(theFace, c);
+ }
+ return res;
+#endif
+}
+
+
+#ifdef USE_PANGO_WIN32
+static inline NR::Point pointfx_to_nrpoint(const POINTFX &p, double scale)
+{
+ return NR::Point(*(long*)&p.x / 65536.0 * scale,
+ *(long*)&p.y / 65536.0 * scale);
+}
+#endif
+
+void font_instance::LoadGlyph(int glyph_id)
+{
+ if ( pFont == NULL ) return;
+ InitTheFace();
+#ifndef USE_PANGO_WIN32
+ if ( theFace->units_per_EM == 0 ) return; // bitmap font
+#endif
+
+ if ( id_to_no.find(glyph_id) == id_to_no.end() ) {
+ if ( nbGlyph >= maxGlyph ) {
+ maxGlyph=2*nbGlyph+1;
+ glyphs=(font_glyph*)realloc(glyphs,maxGlyph*sizeof(font_glyph));
+ }
+ font_glyph n_g;
+ n_g.outline=NULL;
+ n_g.artbpath=NULL;
+ n_g.bbox[0]=n_g.bbox[1]=n_g.bbox[2]=n_g.bbox[3]=0;
+ bool doAdd=false;
+
+#ifdef USE_PANGO_WIN32
+ MAT2 identity = {{0,1},{0,0},{0,0},{0,1}};
+ OUTLINETEXTMETRIC otm;
+ GetOutlineTextMetrics(daddy->hScreenDC, sizeof(otm), &otm);
+ GLYPHMETRICS metrics;
+ DWORD bufferSize=GetGlyphOutline (daddy->hScreenDC, glyph_id, GGO_GLYPH_INDEX | GGO_NATIVE | GGO_UNHINTED, &metrics, 0, NULL, &identity);
+ double scale=1.0/daddy->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;
+ n_g.outline=NULL;
+ if ( bufferSize == GDI_ERROR) {
+ // shit happened
+ } else if ( bufferSize == 0) {
+ // character has no visual representation, but is valid (eg whitespace)
+ doAdd=true;
+ } else {
+ std::auto_ptr<char> buffer(new char[bufferSize]);
+ if ( GetGlyphOutline (daddy->hScreenDC, glyph_id, GGO_GLYPH_INDEX | GGO_NATIVE | GGO_UNHINTED, &metrics, bufferSize, buffer.get(), &identity) <= 0 ) {
+ // shit happened
+ } else {
+ // Platform SDK is rubbish, read KB87115 instead
+ n_g.outline=new Path;
+ DWORD polyOffset=0;
+ while ( polyOffset < bufferSize ) {
+ TTPOLYGONHEADER const *polyHeader=(TTPOLYGONHEADER const *)(buffer.get()+polyOffset);
+ if (polyOffset+polyHeader->cb > bufferSize) break;
+
+ if (polyHeader->dwType == TT_POLYGON_TYPE) {
+ n_g.outline->MoveTo(pointfx_to_nrpoint(polyHeader->pfxStart, scale));
+ DWORD curveOffset=polyOffset+sizeof(TTPOLYGONHEADER);
+
+ while ( curveOffset < polyOffset+polyHeader->cb ) {
+ TTPOLYCURVE const *polyCurve=(TTPOLYCURVE const *)(buffer.get()+curveOffset);
+ POINTFX const *p=polyCurve->apfx;
+ POINTFX const *endp=p+polyCurve->cpfx;
+
+ switch (polyCurve->wType) {
+ case TT_PRIM_LINE:
+ while ( p != endp )
+ n_g.outline->LineTo(pointfx_to_nrpoint(*p++, scale));
+ break;
+
+ case TT_PRIM_QSPLINE:
+ {
+ g_assert(polyCurve->cpfx >= 2);
+ endp -= 2;
+ NR::Point this_mid=pointfx_to_nrpoint(p[0], scale);
+ while ( p != endp ) {
+ NR::Point next_mid=pointfx_to_nrpoint(p[1], scale);
+ n_g.outline->BezierTo((next_mid+this_mid)/2);
+ n_g.outline->IntermBezierTo(this_mid);
+ n_g.outline->EndBezierTo();
+ ++p;
+ this_mid=next_mid;
+ }
+ n_g.outline->BezierTo(pointfx_to_nrpoint(p[1], scale));
+ n_g.outline->IntermBezierTo(this_mid);
+ n_g.outline->EndBezierTo();
+ break;
+ }
+
+ case 3: // TT_PRIM_CSPLINE
+ g_assert(polyCurve->cpfx % 3 == 0);
+ while ( p != endp ) {
+ n_g.outline->CubicTo(pointfx_to_nrpoint(p[2], scale), pointfx_to_nrpoint(p[0], scale), pointfx_to_nrpoint(p[1], scale));
+ p += 3;
+ }
+ break;
+ }
+ curveOffset += sizeof(TTPOLYCURVE)+sizeof(POINTFX)*(polyCurve->cpfx-1);
+ }
+ n_g.outline->Close();
+ }
+ polyOffset += polyHeader->cb;
+ }
+ doAdd=true;
+ }
+ }
+#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 {
+ n_g.v_width=n_g.v_advance=((double)theFace->height)/((double)theFace->units_per_EM);
+ }
+ 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
+ };
+ n_g.outline=new Path;
+ ft2_to_liv tData;
+ tData.theP=n_g.outline;
+ tData.scale=1.0/((double)theFace->units_per_EM);
+ tData.last=NR::Point(0,0);
+ FT_Outline_Decompose (&theFace->glyph->outline, &ft2_outline_funcs, &tData);
+ }
+ doAdd=true;
+ }
+#endif
+
+ if ( doAdd ) {
+ if ( n_g.outline ) {
+ n_g.outline->FastBBox(n_g.bbox[0],n_g.bbox[1],n_g.bbox[2],n_g.bbox[3]);
+ n_g.artbpath=n_g.outline->MakeArtBPath();
+ }
+ glyphs[nbGlyph]=n_g;
+ id_to_no[glyph_id]=nbGlyph;
+ nbGlyph++;
+ }
+ } else {
+ }
+}
+
+bool font_instance::FontMetrics(double &ascent,double &descent,double &leading)
+{
+ if ( pFont == NULL ) return false;
+ InitTheFace();
+ if ( theFace == NULL ) return false;
+#ifdef USE_PANGO_WIN32
+ OUTLINETEXTMETRIC otm;
+ if ( !GetOutlineTextMetrics(daddy->hScreenDC,sizeof(otm),&otm) ) return false;
+ double scale=1.0/daddy->fontSize;
+ ascent=fabs(otm.otmAscent*scale);
+ descent=fabs(otm.otmDescent*scale);
+ leading=fabs(otm.otmLineGap*scale);
+#else
+ if ( theFace->units_per_EM == 0 ) return false; // bitmap font
+ ascent=fabs(((double)theFace->ascender)/((double)theFace->units_per_EM));
+ descent=fabs(((double)theFace->descender)/((double)theFace->units_per_EM));
+ leading=fabs(((double)theFace->height)/((double)theFace->units_per_EM));
+ leading-=ascent+descent;
+#endif
+ return true;
+}
+
+bool font_instance::FontSlope(double &run, double &rise)
+{
+ run = 0.0;
+ rise = 1.0;
+
+ if ( pFont == NULL ) return false;
+ InitTheFace();
+ if ( theFace == NULL ) return false;
+
+#ifdef USE_PANGO_WIN32
+ OUTLINETEXTMETRIC otm;
+ if ( !GetOutlineTextMetrics(daddy->hScreenDC,sizeof(otm),&otm) ) return false;
+ run=otm.otmsCharSlopeRun;
+ rise=otm.otmsCharSlopeRise;
+#else
+ if ( theFace->units_per_EM == 0 ) return false; // bitmap font
+
+ TT_HoriHeader *hhea = (TT_HoriHeader*)FT_Get_Sfnt_Table(theFace, ft_sfnt_hhea);
+ if (hhea == NULL) return false;
+ run = hhea->caret_Slope_Run;
+ rise = hhea->caret_Slope_Rise;
+#endif
+ return true;
+}
+
+NR::Rect 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 NR::Rect(NR::Point(0,0),NR::Point(0,0));
+ NR::Point rmin(glyphs[no].bbox[0],glyphs[no].bbox[1]);
+ NR::Point rmax(glyphs[no].bbox[2],glyphs[no].bbox[3]);
+ NR::Rect res(rmin,rmax);
+ return res;
+}
+
+Path* font_instance::Outline(int glyph_id,Path* copyInto)
+{
+ 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 NULL;
+ Path* src_o=glyphs[no].outline;
+ if ( copyInto ) {
+ copyInto->Reset();
+ copyInto->Copy(src_o);
+ return copyInto;
+ }
+ return src_o;
+}
+
+void* font_instance::ArtBPath(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 NULL;
+ return glyphs[no].artbpath;
+}
+
+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;
+}
+
+
+raster_font* font_instance::RasterFont(const NR::Matrix &trs,double stroke_width,bool vertical,JoinType stroke_join,ButtType stroke_cap,float miter_limit)
+{
+ font_style nStyle;
+ nStyle.transform=trs;
+ nStyle.vertical=vertical;
+ nStyle.stroke_width=stroke_width;
+ nStyle.stroke_cap=stroke_cap;
+ nStyle.stroke_join=stroke_join;
+ nStyle.nbDash=0;
+ nStyle.dash_offset=0;
+ nStyle.dashes=NULL;
+ return RasterFont(nStyle);
+}
+
+raster_font* font_instance::RasterFont(const font_style &inStyle)
+{
+ raster_font *res=NULL;
+ double *savDashes=NULL;
+ font_style nStyle=inStyle;
+ // for some evil reason font_style doesn't have a copy ctor, so the
+ // stuff that should be done there is done here instead (because the
+ // raster_font ctor copies nStyle).
+ if ( nStyle.stroke_width > 0 && nStyle.nbDash > 0 && nStyle.dashes ) {
+ savDashes=nStyle.dashes;
+ nStyle.dashes=(double*)malloc(nStyle.nbDash*sizeof(double));
+ memcpy(nStyle.dashes,savDashes,nStyle.nbDash*sizeof(double));
+ }
+ if ( loadedStyles.find(nStyle) == loadedStyles.end() ) {
+ raster_font *nR = new raster_font(nStyle);
+ nR->Ref();
+ nR->daddy=this;
+ loadedStyles[nStyle]=nR;
+ res=nR;
+ if ( res ) Ref();
+ } else {
+ res=loadedStyles[nStyle];
+ res->Ref();
+ if ( nStyle.dashes ) free(nStyle.dashes); // since they're not taken by a new rasterfont
+ }
+ nStyle.dashes=savDashes;
+ return res;
+}
+
+void font_instance::RemoveRasterFont(raster_font* who)
+{
+ if ( who == NULL ) return;
+ if ( loadedStyles.find(who->style) == loadedStyles.end() ) {
+ //g_print("RemoveRasterFont failed \n");
+ // not found
+ } else {
+ loadedStyles.erase(loadedStyles.find(who->style));
+ //g_print("RemoveRasterFont\n");
+ Unref();
+ }
+}
+
+
+
+/*
+ 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 100755
index 000000000..0fbbb6f0c
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Compute.cpp
@@ -0,0 +1,1514 @@
+/*
+ * 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, read the file 'COPYING' for more information
+ */
+#include "Layout-TNG.h"
+#include "style.h"
+#include "font-instance.h"
+#include "svg/svg-length.h"
+#include "sp-object.h"
+#include "Layout-TNG-Scanline-Maker.h"
+
+namespace Inkscape {
+namespace Text {
+
+//#define IFTRACE(_code) _code
+//#define TRACE(_args) g_print _args
+#define IFTRACE(_code)
+#define TRACE(_args)
+
+// ******* enum conversion tables
+static Layout::EnumConversionItem const enum_convert_spstyle_direction_to_pango_direction[] = {
+ {SP_CSS_WRITING_MODE_LR_TB, PANGO_DIRECTION_LTR},
+ {SP_CSS_WRITING_MODE_RL_TB, PANGO_DIRECTION_RTL},
+ {SP_CSS_WRITING_MODE_TB_LR, PANGO_DIRECTION_LTR}}; // this is correct
+
+static Layout::EnumConversionItem const enum_convert_spstyle_direction_to_my_direction[] = {
+ {SP_CSS_WRITING_MODE_LR_TB, Layout::LEFT_TO_RIGHT},
+ {SP_CSS_WRITING_MODE_RL_TB, Layout::RIGHT_TO_LEFT},
+ {SP_CSS_WRITING_MODE_TB_LR, Layout::LEFT_TO_RIGHT}}; // this is correct
+
+/** \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. */
+ 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(NULL) {}
+ void free();
+ };
+
+ /** Temporary storage associated with each item returned by the call to
+ pango_itemize(). */
+ struct PangoItemInfo {
+ PangoItem *item;
+ font_instance *font;
+
+ PangoItemInfo() : item(NULL), font(NULL) {}
+ void free();
+ };
+
+ /** 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 source 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;
+ LineHeight line_height;
+ double line_height_multiplier; /// calculated from the font-height css property
+ 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(NULL) {}
+ void free() {if (glyph_string) pango_glyph_string_free(glyph_string); glyph_string = NULL;}
+ };
+
+ /** 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;
+ void setZero();
+ };
+
+ /** The definition of a chunk used here is the same as that used in Layout. */
+ 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;
+ };
+
+ /** 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 {
+ 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);
+ void free();
+ };
+
+/* *********************************************************************************************************/
+// Initialisation of ParagraphInfo structure
+
+
+#if 0 /* unused */
+ void _initialiseInputItems(ParagraphInfo *para) const;
+#endif
+
+ void _buildPangoItemizationForPara(ParagraphInfo *para) const;
+
+ static void _computeFontLineHeight(font_instance *font, double font_size,
+ SPStyle const *style, LineHeight *line_height,
+ double *line_height_multiplier);
+
+ unsigned _buildSpansForPara(ParagraphInfo *para) const;
+
+/* *********************************************************************************************************/
+// Per-line functions
+
+
+ bool _goToNextWrapShape();
+
+ bool _findChunksForLine(ParagraphInfo const &para, UnbrokenSpanPosition *start_span_pos,
+ std::vector<ChunkInfo> *chunk_info, LineHeight *line_height);
+
+ 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];
+ }
+
+ bool _buildChunksInScanRun(ParagraphInfo const &para,
+ UnbrokenSpanPosition const &start_span_pos,
+ ScanlineMaker::ScanRun const &scan_run,
+ std::vector<ChunkInfo> *chunk_info,
+ LineHeight *line_height) const;
+
+ /** 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. */
+ bool _measureUnbrokenSpan(ParagraphInfo const &para, BrokenSpan *span, BrokenSpan *last_break_span, BrokenSpan *last_emergency_break_span, double maximum_width) const
+ {
+ span->setZero();
+
+ if (span->start.iter_span->dx._set && span->start.char_byte == 0)
+ 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) {
+ 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);
+
+ if (char_attributes.is_mandatory_break) {
+ *last_emergency_break_span = *last_break_span = *span;
+ TRACE(("span %d 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) {
+ // 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 and word spacing 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) {
+ if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
+ char_width += span->start.iter_span->font_size * para.pango_items[span->end.iter_span->pango_item_index].font->Advance(span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].glyph, true);
+ else
+ char_width += font_size_multiplier * span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].geometry.width;
+ span->end_glyph_index++;
+ }
+ if (char_attributes.is_cursor_position)
+ char_width += text_source->style->letter_spacing.computed;
+ if (char_attributes.is_white)
+ char_width += text_source->style->word_spacing.computed;
+ 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;
+
+ span->end.increment();
+
+ if (span->width > maximum_width && !char_attributes.is_white) { // whitespaces don't matter, we can put as many as we want at eol
+ TRACE(("span %d 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 %d width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
+ 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 _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:
+ if (!it_chunk->broken_spans.empty()
+ && it_chunk->broken_spans.back().end.iter_span != para.unbroken_spans.end()) { // don't justify the last chunk in the para
+ if (it_chunk->whitespace_count)
+ *add_to_each_whitespace = (it_chunk->scanrun_width - it_chunk->text_width) / it_chunk->whitespace_count;
+ //else
+ //add_to_each_charspace = something
+ }
+ 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 _outputLine(ParagraphInfo const &para, LineHeight const &line_height, std::vector<ChunkInfo> const &chunk_info)
+ {
+ 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() + line_height.ascent;
+ 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;
+ 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)
+ if (!it_chunk->broken_spans.empty() // this one only happens for empty paragraphs
+ && it_chunk->broken_spans.front().start.char_byte == 0
+ && it_chunk->broken_spans.front().start.iter_span->y._set) {
+ // if this is the start of a line, we should change the baseline rather than each glyph individually
+ if (_flow._characters.empty() || _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
+ new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed;
+ _flow._lines.back().baseline_y = new_line.baseline_y;
+ _y_offset = 0.0;
+ _scanline_maker->setNewYCoordinate(new_line.baseline_y - line_height.ascent);
+ } else
+ _y_offset = it_chunk->broken_spans.front().start.iter_span->y.computed - new_line.baseline_y;
+ }
+ _flow._chunks.push_back(new_chunk);
+
+ double 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;
+ x = 0.0;
+ } else {
+ direction_sign = -1.0;
+ if (para.alignment == FULL && !_flow._input_wrap_shapes.empty())
+ x = it_chunk->scanrun_width;
+ else
+ x = it_chunk->text_width;
+ }
+
+ 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;
+
+ 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) 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;
+ double x_in_span = 0.0;
+
+ 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 = _y_offset;
+ new_span.block_progression = _block_progression;
+ 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 = NULL;
+ new_span.font_size = new_span.line_height.ascent + new_span.line_height.descent;
+ new_span.direction = para.direction;
+ }
+
+ if (new_span.direction == para.direction) {
+ 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++) {
+ 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 (it_following_span->start.iter_span->pango_item_index == -1) { // when the span came from a control code
+ if (new_span.direction != para.direction) break;
+ } else
+ 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);
+ }
+ x += counter_directional_width_remaining;
+ counter_directional_width_remaining = 0.0; // we want to go increasingly negative
+ }
+ new_span.x_start = x;
+
+ 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;
+ unsigned cluster_start_char_index = _flow._characters.size();
+ double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier);
+
+ 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();
+ if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start)
+ cluster_start_char_index = _flow._characters.size();
+
+ if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes
+ && *iter_source_text == UNICODE_SOFT_HYPHEN
+ && it_span + 1 != it_chunk->broken_spans.end()
+ && 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.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
+ Layout::Glyph new_glyph;
+ new_glyph.glyph = unbroken_span.glyph_string->glyphs[glyph_index].glyph;
+ new_glyph.in_character = cluster_start_char_index;
+ new_glyph.rotation = glyph_rotate;
+
+ /* put something like this back in when we do glyph-rotation-horizontal/vertical
+ if (new_span.block_progression == LEFT_TO_RIGHT || new_span.block_progression == RIGHT_TO_LEFT) {
+ new_glyph.x += new_span.line_height.ascent;
+ new_glyph.y -= unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier * 0.5;
+ new_glyph.width = new_span.line_height.ascent + new_span.line_height.descent;
+ } else */
+
+ if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
+ new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier + new_span.line_height.ascent;
+ new_glyph.y = _y_offset + (unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset - unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * 0.5) * font_size_multiplier;
+ new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, true);
+ } else {
+ new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier;
+ new_glyph.y = _y_offset + unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier;
+ new_glyph.width = unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier;
+ if (new_glyph.width == 0)
+ new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, false);
+ // for some reason pango returns zero width for invalid glyph characters (those empty boxes), so go to freetype for the info
+ }
+ if (new_span.direction == RIGHT_TO_LEFT) {
+ // pango wanted to give us glyphs in visual order but we refused, so we need to work
+ // out where the cluster start is ourselves
+ double cluster_width = 0.0;
+ 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;
+ if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
+ cluster_width += new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[rtl_index].glyph, true);
+ else
+ cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
+ }
+ new_glyph.x -= cluster_width;
+ }
+ _flow._glyphs.push_back(new_glyph);
+
+ // create the Layout::Character(s)
+ double advance_width = new_glyph.width;
+ unsigned end_byte;
+ if (glyph_index == (unsigned)unbroken_span.glyph_string->num_glyphs - 1)
+ end_byte = it_span->start.iter_span->text_bytes;
+ else {
+ // output chars for the whole cluster that is commenced by this glyph
+ if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) {
+ int next_cluster_glyph_index = glyph_index + 1;
+ while (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs
+ && !unbroken_span.glyph_string->glyphs[next_cluster_glyph_index].attr.is_cluster_start)
+ next_cluster_glyph_index++;
+ if (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs)
+ end_byte = unbroken_span.glyph_string->log_clusters[next_cluster_glyph_index];
+ else
+ end_byte = it_span->start.iter_span->text_bytes;
+ } else
+ end_byte = char_byte; // don't output any chars if we're not at the start of a cluster
+ }
+ while (char_byte < end_byte) {
+ Layout::Character new_character;
+ 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 = _flow._glyphs.size() - 1;
+ _flow._characters.push_back(new_character);
+ if (new_character.char_attributes.is_white)
+ advance_width += text_source->style->word_spacing.computed + add_to_each_whitespace; // justification
+ if (new_character.char_attributes.is_cursor_position)
+ advance_width += text_source->style->letter_spacing.computed;
+ iter_source_text++;
+ char_index_in_unbroken_span++;
+ char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
+ glyph_rotate = 0.0;
+ }
+
+ advance_width *= direction_sign;
+ if (new_span.direction != para.direction) {
+ counter_directional_width_remaining -= advance_width;
+ x -= advance_width;
+ x_in_span -= advance_width;
+ } else {
+ x += advance_width;
+ x_in_span += advance_width;
+ }
+ }
+ } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) {
+ x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
+ }
+
+ new_span.x_end = new_span.x_start + x_in_span;
+ _flow._spans.push_back(new_span);
+ previous_direction = new_span.direction;
+ }
+ // end adding spans to the list, on to the next chunk...
+ }
+ TRACE(("output done\n"));
+ }
+
+/* *********************************************************************************************************/
+// Setup and top-level functions
+
+ /** initialises the ScanlineMaker for the first shape in the flow, or
+ the infinite version if we're not doing wrapping. */
+ void _createFirstScanlineMaker()
+ {
+ _current_shape_index = 0;
+ if (_flow._input_wrap_shapes.empty()) {
+ // create the special no-wrapping infinite scanline maker
+ double initial_x = 0, initial_y = 0;
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
+ 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"));
+ }
+ }
+
+public:
+ Calculator(Layout *text_flow)
+ : _flow(*text_flow) {}
+
+ bool calculate();
+};
+
+/* 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 Layout::Calculator::InputItemInfo::free()
+{
+ if (sub_flow) {
+ delete sub_flow;
+ sub_flow = NULL;
+ }
+}
+
+void Layout::Calculator::PangoItemInfo::free()
+{
+ if (item) {
+ pango_item_free(item);
+ item = NULL;
+ }
+ if (font) {
+ font->Unref();
+ font = NULL;
+ }
+}
+
+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;
+}
+
+template<typename T> void Layout::Calculator::ParagraphInfo::free_sequence(T &seq)
+{
+ for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
+ it->free();
+ }
+ seq.clear();
+}
+
+void Layout::Calculator::ParagraphInfo::free()
+{
+ free_sequence(input_items);
+ free_sequence(pango_items);
+ free_sequence(unbroken_spans);
+}
+
+///**
+// * 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_cookie, 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_cookie, 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.
+ */
+void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const
+{
+ Glib::ustring para_text;
+ PangoAttrList *attributes_list;
+ unsigned input_index;
+
+ para->free_sequence(para->pango_items);
+ para->char_attributes.clear();
+
+ TRACE(("itemizing para, first input %d\n", para->first_input_index));
+
+ attributes_list = pango_attr_list_new();
+ 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)
+ 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 == NULL)
+ 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();
+ 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);
+ // ownership of attribute is assumed by the list
+ }
+ }
+
+ TRACE(("whole para: \"%s\"\n", para_text.data()));
+ TRACE(("%d input sources used\n", input_index - para->first_input_index));
+
+ // do the pango_itemize()
+ GList *pango_items_glist = NULL;
+ 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]);
+ if (text_source->style->direction.set) {
+ PangoDirection pango_direction = (PangoDirection)_enum_converter(text_source->style->direction.computed, enum_convert_spstyle_direction_to_pango_direction, sizeof(enum_convert_spstyle_direction_to_pango_direction)/sizeof(enum_convert_spstyle_direction_to_pango_direction[0]));
+ pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
+ para->direction = (Layout::Direction)_enum_converter(text_source->style->direction.computed, enum_convert_spstyle_direction_to_my_direction, sizeof(enum_convert_spstyle_direction_to_my_direction)/sizeof(enum_convert_spstyle_direction_to_my_direction[0]));
+ }
+ }
+ if (pango_items_glist == NULL) { // no direction specified, guess it
+ pango_items_glist = pango_itemize(_pango_context, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
+
+ // I think according to the css spec this is wrong and we're never allowed to guess the directionality
+ // of a paragraph. Need to talk to an rtl speaker.
+ if (pango_items_glist == NULL || pango_items_glist->data == NULL) para->direction = LEFT_TO_RIGHT;
+ else para->direction = (((PangoItem*)pango_items_glist->data)->analysis.level & 1) ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
+ }
+ 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 != NULL ; 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, NULL, &*para->char_attributes.begin(), para->char_attributes.size());
+
+ TRACE(("end para itemize, direction = %d\n", para->direction));
+}
+
+/**
+ * Gets the ascent, descent and leading for a font and the alteration that has to be performed
+ * according to the value specified by the line-height css property. The result of multiplying
+ * \a line_height by \a line_height_multiplier is the inline box height as specified in css2
+ * section 10.8.
+ */
+void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font_size,
+ SPStyle const *style, LineHeight *line_height,
+ double *line_height_multiplier)
+{
+ if (font == NULL) {
+ line_height->setZero();
+ *line_height_multiplier = 1.0;
+ }
+ font->FontMetrics(line_height->ascent, line_height->descent, line_height->leading);
+ *line_height *= font_size;
+
+ // yet another borked SPStyle member that we're going to have to fix ourselves
+ for ( ; ; ) {
+ if (style->line_height.set && !style->line_height.inherit) {
+ if (style->line_height.normal)
+ break;
+ switch (style->line_height.unit) {
+ case SP_CSS_UNIT_NONE:
+ *line_height_multiplier = style->line_height.computed * font_size / line_height->total();
+ return;
+ case SP_CSS_UNIT_EX:
+ *line_height_multiplier = style->line_height.value * 0.5 * font_size / line_height->total();
+ // 0.5 is an approximation of the x-height. Fixme.
+ return;
+ case SP_CSS_UNIT_EM:
+ case SP_CSS_UNIT_PERCENT:
+ *line_height_multiplier = style->line_height.value * font_size / line_height->total();
+ return;
+ default: // absolute values
+ *line_height_multiplier = style->line_height.computed / line_height->total();
+ return;
+ }
+ break;
+ }
+ if (style->object->parent == NULL) break;
+ style = style->object->parent->style;
+ if (style == NULL) break;
+ }
+ *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total();
+}
+
+/**
+ * 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;
+
+ 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)
+ break; // stop at the end of the paragraph
+ else if (control_code->code == ARBITRARY_GAP) {
+ UnbrokenSpan new_span;
+ new_span.pango_item_index = -1;
+ new_span.input_index = input_index;
+ new_span.line_height.ascent = control_code->ascent;
+ new_span.line_height.descent = control_code->descent;
+ new_span.line_height.leading = 0.0;
+ 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 %d\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 );
+ 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) {
+ 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];
+ if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source];
+ } else {
+ 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];
+ if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source];
+ }
+ if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
+ 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->styleComputeFontSize();
+ if (new_span.text_bytes) {
+ int const original_bidi_level = para->pango_items[pango_item_index].item->analysis.level;
+ para->pango_items[pango_item_index].item->analysis.level = 0;
+ // pango_shape() will reorder glyphs in rtl sections which messes us up because
+ // the svg spec requires us to draw glyphs in character order
+ 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))
+ == NULL );
+ pango_shape(text_source->text->data() + span_start_byte_in_source,
+ new_span.text_bytes,
+ &para->pango_items[pango_item_index].item->analysis,
+ new_span.glyph_string);
+ para->pango_items[pango_item_index].item->analysis.level = original_bidi_level;
+ new_span.pango_item_index = pango_item_index;
+ _computeFontLineHeight(para->pango_items[pango_item_index].font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
+ // TODO: metrics for vertical text
+ TRACE(("add text span %d \"%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) {
+ _computeFontLineHeight(font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
+ font->Unref();
+ } else {
+ new_span.line_height.setZero();
+ new_span.line_height_multiplier = 1.0;
+ }
+ TRACE(("add style init span %d\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;
+ 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;
+ }
+ }
+ TRACE(("end build spans\n"));
+ return input_index;
+}
+
+/**
+ * Reinitialises the variables required on completion of one shape and
+ * moving on to the next. Returns false if there are no more shapes to wrap
+ * in to.
+ */
+bool Layout::Calculator::_goToNextWrapShape()
+{
+ delete _scanline_maker;
+ _scanline_maker = NULL;
+ _current_shape_index++;
+ if (_current_shape_index == _flow._input_wrap_shapes.size()) return false;
+ _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
+ TRACE(("begin wrap shape %d\n", _current_shape_index));
+ return true;
+}
+
+/**
+ * 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_height set to the largest line box on the line. The return
+ * value is false only if we've run out of shapes to wrap inside (and
+ * hence couldn't create any chunks).
+ */
+bool Layout::Calculator::_findChunksForLine(ParagraphInfo const &para,
+ UnbrokenSpanPosition *start_span_pos,
+ std::vector<ChunkInfo> *chunk_info,
+ LineHeight *line_height)
+{
+ // init the initial line_height
+ if (start_span_pos->iter_span == para.unbroken_spans.end()) {
+ if (_flow._spans.empty()) {
+ // empty first para: create a font for the sole purpose of measuring it
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
+ font_instance *font = text_source->styleGetFontInstance();
+ if (font) {
+ double font_size = text_source->styleComputeFontSize();
+ double multiplier;
+ _computeFontLineHeight(font, font_size, text_source->style, line_height, &multiplier);
+ font->Unref();
+ *line_height *= multiplier;
+ _scanline_maker->setNewYCoordinate(_scanline_maker->yCoordinate() - line_height->ascent);
+ }
+ }
+ // else empty subsequent para: keep the old line height
+ } else {
+ if (_flow._input_wrap_shapes.empty()) {
+ // if we're not wrapping set the line_height big and negative so we can use negative line height
+ line_height->ascent = -1.0e10;
+ line_height->descent = -1.0e10;
+ line_height->leading = -1.0e10;
+ }
+ else
+ line_height->setZero();
+ }
+
+ UnbrokenSpanPosition span_pos;
+ for( ; ; ) {
+ std::vector<ScanlineMaker::ScanRun> scan_runs;
+ scan_runs = _scanline_maker->makeScanline(*line_height);
+ while (scan_runs.empty()) {
+ if (!_goToNextWrapShape()) return false; // no more shapes to wrap in to
+ scan_runs = _scanline_maker->makeScanline(*line_height);
+ }
+
+ TRACE(("finding line fit y=%f, %d 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++) {
+ if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_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
+ }
+ *start_span_pos = span_pos;
+ return true;
+}
+
+/**
+ * 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,
+ LineHeight *line_height) const
+{
+ 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
+ LineHeight total_height = new_span.start.iter_span->line_height;
+ total_height *= 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 ( total_height.ascent > line_height->ascent + FLT_EPSILON
+ || total_height.descent > line_height->descent + FLT_EPSILON
+ || total_height.leading > line_height->leading + FLT_EPSILON) {
+ line_height->max(total_height);
+ 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;
+ }
+ }
+
+ TRACE(("chunk complete, used %f width (%d whitespaces, %d 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->total() && 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 %d width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
+ }
+ }
+
+ 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
+ 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--;
+ }
+
+ return true;
+}
+
+/** The management function to start the whole thing off. */
+bool Layout::Calculator::calculate()
+{
+ if (_flow._input_stream.empty())
+ return false;
+ g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
+ if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
+ return false;
+
+ TRACE(("begin calculateFlow()\n"));
+
+ _flow._clearOutputObjects();
+
+ _pango_context = (font_factory::Default())->fontContext;
+ _font_factory_size_multiplier = (font_factory::Default())->fontSize;
+
+ _block_progression = _flow._blockProgression();
+ _y_offset = 0.0;
+ _createFirstScanlineMaker();
+
+ ParagraphInfo para;
+ LineHeight line_height; // needs to be maintained across paragraphs to be able to deal with blank paras (this is wrong)
+ 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()) break;
+ continue;
+ }
+ }
+ if (_scanline_maker == NULL)
+ break; // we're trying to flow past the last wrap shape
+
+ _buildPangoItemizationForPara(&para);
+ 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 #%d\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 { // for each line in the paragraph
+ TRACE(("begin line\n"));
+ std::vector<ChunkInfo> line_chunk_info;
+ if (!_findChunksForLine(para, &span_pos, &line_chunk_info, &line_height))
+ break; // out of shapes to wrap in to
+
+ _outputLine(para, line_height, line_chunk_info);
+ _scanline_maker->completeLine();
+ } while (span_pos.iter_span != para.unbroken_spans.end());
+
+ TRACE(("para %d end\n\n", _flow._paragraphs.size() - 1));
+ if (_scanline_maker != NULL) {
+ 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 = NULL;
+ new_span.font_size = line_height.ascent + line_height.descent;
+ new_span.line_height = line_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.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);
+ }
+ }
+ para.free();
+ para.first_input_index = para_end_input_index + 1;
+ }
+
+ para.free();
+ if (_scanline_maker)
+ delete _scanline_maker;
+
+ return true;
+}
+
+void Layout::_calculateCursorShapeForEmpty()
+{
+ _empty_cursor_shape.position = NR::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->styleComputeFontSize();
+ double caret_slope_run = 0.0, caret_slope_rise = 1.0;
+ LineHeight 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.leading);
+ line_height *= font_size;
+ font->Unref();
+ } else {
+ line_height.ascent = font_size * 0.85; // random guesses
+ line_height.descent = font_size * 0.15;
+ line_height.leading = 0.0;
+ }
+ 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 = NR::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 {
+ 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)
+ _empty_cursor_shape.position = NR::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
+ else
+ _empty_cursor_shape.position = NR::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
+ }
+ }
+}
+
+bool Layout::calculateFlow()
+{
+ bool result = Calculator(this).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:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/Layout-TNG-Input.cpp b/src/libnrtype/Layout-TNG-Input.cpp
new file mode 100755
index 000000000..8b695af04
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Input.cpp
@@ -0,0 +1,270 @@
+/*
+ * Inkscape::Text::Layout - text layout engine input functions
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#include "Layout-TNG.h"
+#include "style.h"
+#include "svg/svg-length.h"
+#include "sp-object.h"
+#include "FontFactory.h"
+
+namespace Inkscape {
+namespace Text {
+
+void Layout::_clearInputObjects()
+{
+ for(std::vector<InputStreamItem*>::iterator it = _input_stream.begin() ; it != _input_stream.end() ; it++)
+ 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, void *source_cookie, OptionalTextTagAttrs const *optional_attributes, unsigned optional_attributes_offset, Glib::ustring::const_iterator text_begin, Glib::ustring::const_iterator text_end)
+{
+ if (style == NULL) return;
+
+ InputStreamTextSource *new_source = new InputStreamTextSource;
+
+ new_source->source_cookie = source_cookie;
+ 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);
+ }
+
+ _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, void *source_cookie, double width, double ascent, double descent)
+{
+ InputStreamControlCode *new_code = new InputStreamControlCode;
+
+ new_code->source_cookie = source_cookie;
+ 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.push_back(InputWrapShape());
+ _input_wrap_shapes.back().shape = shape;
+ _input_wrap_shapes.back().display_align = display_align;
+}
+
+int Layout::_enum_converter(int input, EnumConversionItem const *conversion_table, unsigned conversion_table_size)
+{
+ for (unsigned i = 0 ; i < conversion_table_size ; i++)
+ if (conversion_table[i].input == input)
+ return conversion_table[i].output;
+ return conversion_table[0].output;
+}
+
+// ***** the style format interface
+// this doesn't include all accesses to SPStyle, only the ones that are non-trivial
+
+static const float medium_font_size = 12.0; // more of a default if all else fails than anything else
+float Layout::InputStreamTextSource::styleComputeFontSize() const
+{
+ return style->font_size.computed;
+
+ // in case the computed value's not good enough, here's some manual code held in reserve:
+ SPStyle const *this_style = style;
+ float inherit_multiplier = 1.0;
+
+ for ( ; ; ) {
+ if (this_style->font_size.set && !this_style->font_size.inherit) {
+ switch (this_style->font_size.type) {
+ case SP_FONT_SIZE_LITERAL: {
+ switch(this_style->font_size.value) { // these multipliers are straight out of the CSS spec
+ case SP_CSS_FONT_SIZE_XX_SMALL: return medium_font_size * inherit_multiplier * (3.0/5.0);
+ case SP_CSS_FONT_SIZE_X_SMALL: return medium_font_size * inherit_multiplier * (3.0/4.0);
+ case SP_CSS_FONT_SIZE_SMALL: return medium_font_size * inherit_multiplier * (8.0/9.0);
+ default:
+ case SP_CSS_FONT_SIZE_MEDIUM: return medium_font_size * inherit_multiplier;
+ case SP_CSS_FONT_SIZE_LARGE: return medium_font_size * inherit_multiplier * (6.0/5.0);
+ case SP_CSS_FONT_SIZE_X_LARGE: return medium_font_size * inherit_multiplier * (3.0/2.0);
+ case SP_CSS_FONT_SIZE_XX_LARGE: return medium_font_size * inherit_multiplier * 2.0;
+ case SP_CSS_FONT_SIZE_SMALLER: inherit_multiplier *= 0.84; break; //not exactly according to spec
+ case SP_CSS_FONT_SIZE_LARGER: inherit_multiplier *= 1.26; break; //not exactly according to spec
+ }
+ break;
+ }
+ case SP_FONT_SIZE_PERCENTAGE: { // 'em' units should be in here, but aren't. Fix in style.cpp.
+ inherit_multiplier *= this_style->font_size.value;
+ break;
+ }
+ case SP_FONT_SIZE_LENGTH: {
+ return this_style->font_size.value * inherit_multiplier;
+ }
+ }
+ }
+ if (this_style->object->parent == NULL) break;
+ this_style = this_style->object->parent->style;
+ if (this_style == NULL) break;
+ }
+ return medium_font_size * inherit_multiplier;
+}
+
+static const Layout::EnumConversionItem enum_convert_spstyle_block_progression_to_direction[] = {
+ {SP_CSS_BLOCK_PROGRESSION_TB, Layout::TOP_TO_BOTTOM},
+ {SP_CSS_BLOCK_PROGRESSION_LR, Layout::LEFT_TO_RIGHT},
+ {SP_CSS_BLOCK_PROGRESSION_RL, Layout::RIGHT_TO_LEFT}};
+
+static const Layout::EnumConversionItem enum_convert_spstyle_writing_mode_to_direction[] = {
+ {SP_CSS_WRITING_MODE_LR_TB, Layout::TOP_TO_BOTTOM},
+ {SP_CSS_WRITING_MODE_RL_TB, Layout::TOP_TO_BOTTOM},
+ {SP_CSS_WRITING_MODE_TB_RL, Layout::RIGHT_TO_LEFT},
+ {SP_CSS_WRITING_MODE_TB_LR, Layout::LEFT_TO_RIGHT}};
+
+Layout::Direction Layout::InputStreamTextSource::styleGetBlockProgression() const
+{
+ // this function shouldn't be necessary, but since style.cpp doesn't support
+ // shorthand properties yet, it is.
+ SPStyle const *this_style = style;
+
+ for ( ; ; ) {
+ if (this_style->block_progression.set)
+ return (Layout::Direction)_enum_converter(this_style->block_progression.computed, enum_convert_spstyle_block_progression_to_direction, sizeof(enum_convert_spstyle_block_progression_to_direction)/sizeof(enum_convert_spstyle_block_progression_to_direction[0]));
+ if (this_style->writing_mode.set)
+ return (Layout::Direction)_enum_converter(this_style->writing_mode.computed, enum_convert_spstyle_writing_mode_to_direction, sizeof(enum_convert_spstyle_writing_mode_to_direction)/sizeof(enum_convert_spstyle_writing_mode_to_direction[0]));
+ if (this_style->object->parent == NULL) break;
+ this_style = this_style->object->parent->style;
+ if (this_style == NULL) break;
+ }
+ return TOP_TO_BOTTOM;
+
+}
+
+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->parent == NULL) break;
+ this_style = this_style->object->parent->style;
+ if (this_style == NULL) break;
+ }
+ return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
+}
+
+static const Layout::EnumConversionItem enum_convert_spstyle_style_to_pango_style[] = {
+ {SP_CSS_FONT_STYLE_NORMAL, PANGO_STYLE_NORMAL},
+ {SP_CSS_FONT_STYLE_ITALIC, PANGO_STYLE_ITALIC},
+ {SP_CSS_FONT_STYLE_OBLIQUE, PANGO_STYLE_OBLIQUE}};
+
+static const Layout::EnumConversionItem enum_convert_spstyle_weight_to_pango_weight[] = {
+ {SP_CSS_FONT_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL},
+ {SP_CSS_FONT_WEIGHT_100, PANGO_WEIGHT_ULTRALIGHT},
+ {SP_CSS_FONT_WEIGHT_200, PANGO_WEIGHT_ULTRALIGHT},
+ {SP_CSS_FONT_WEIGHT_300, PANGO_WEIGHT_LIGHT},
+ {SP_CSS_FONT_WEIGHT_400, PANGO_WEIGHT_NORMAL},
+ {SP_CSS_FONT_WEIGHT_500, PANGO_WEIGHT_SEMIBOLD},
+ {SP_CSS_FONT_WEIGHT_600, PANGO_WEIGHT_BOLD},
+ {SP_CSS_FONT_WEIGHT_BOLD,PANGO_WEIGHT_BOLD},
+ {SP_CSS_FONT_WEIGHT_700, PANGO_WEIGHT_BOLD},
+ {SP_CSS_FONT_WEIGHT_800, PANGO_WEIGHT_ULTRABOLD},
+ {SP_CSS_FONT_WEIGHT_900, PANGO_WEIGHT_HEAVY}};
+
+static const Layout::EnumConversionItem enum_convert_spstyle_stretch_to_pango_stretch[] = {
+ {SP_CSS_FONT_STRETCH_NORMAL, PANGO_STRETCH_NORMAL},
+ {SP_CSS_FONT_STRETCH_ULTRA_CONDENSED, PANGO_STRETCH_ULTRA_CONDENSED},
+ {SP_CSS_FONT_STRETCH_EXTRA_CONDENSED, PANGO_STRETCH_EXTRA_CONDENSED},
+ {SP_CSS_FONT_STRETCH_CONDENSED, PANGO_STRETCH_CONDENSED},
+ {SP_CSS_FONT_STRETCH_SEMI_CONDENSED, PANGO_STRETCH_SEMI_CONDENSED},
+ {SP_CSS_FONT_STRETCH_SEMI_EXPANDED, PANGO_STRETCH_SEMI_EXPANDED},
+ {SP_CSS_FONT_STRETCH_EXPANDED, PANGO_STRETCH_EXPANDED},
+ {SP_CSS_FONT_STRETCH_EXTRA_EXPANDED, PANGO_STRETCH_EXTRA_EXPANDED},
+ {SP_CSS_FONT_STRETCH_ULTRA_EXPANDED, PANGO_STRETCH_ULTRA_EXPANDED}};
+
+static const Layout::EnumConversionItem enum_convert_spstyle_variant_to_pango_variant[] = {
+ {SP_CSS_FONT_VARIANT_NORMAL, PANGO_VARIANT_NORMAL},
+ {SP_CSS_FONT_VARIANT_SMALL_CAPS, PANGO_VARIANT_SMALL_CAPS}};
+
+font_instance *Layout::InputStreamTextSource::styleGetFontInstance() const
+{
+ if (style->text == NULL) return NULL;
+ return (font_factory::Default())->Face(style->text->font_family.value,
+ _enum_converter(style->font_variant.computed, enum_convert_spstyle_variant_to_pango_variant, sizeof(enum_convert_spstyle_variant_to_pango_variant)/sizeof(enum_convert_spstyle_variant_to_pango_variant[0])),
+ _enum_converter(style->font_style.computed, enum_convert_spstyle_style_to_pango_style, sizeof(enum_convert_spstyle_style_to_pango_style)/sizeof(enum_convert_spstyle_style_to_pango_style[0])),
+ _enum_converter(style->font_weight.computed, enum_convert_spstyle_weight_to_pango_weight, sizeof(enum_convert_spstyle_weight_to_pango_weight)/sizeof(enum_convert_spstyle_weight_to_pango_weight[0])),
+ _enum_converter(style->font_stretch.computed, enum_convert_spstyle_stretch_to_pango_stretch, sizeof(enum_convert_spstyle_stretch_to_pango_stretch)/sizeof(enum_convert_spstyle_stretch_to_pango_stretch[0])));
+}
+
+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 100755
index 000000000..3dd043a68
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-OutIter.cpp
@@ -0,0 +1,994 @@
+/*
+ * 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, 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 "libnr/nr-matrix-translate-ops.h"
+#include "libnr/nr-translate-rotate-ops.h"
+#include "style.h"
+
+namespace Inkscape {
+namespace Text {
+
+Layout::iterator Layout::_cursorXOnLineToIterator(unsigned line_index, double local_x) const
+{
+ unsigned char_index = _lineToCharacter(line_index);
+ int best_char_index = -1;
+ double best_x_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 this_x_difference = fabs(_characters[char_index].x + _characters[char_index].span(this).x_start + _characters[char_index].chunk(this).left_x - local_x);
+ if (this_x_difference < best_x_difference) {
+ best_char_index = char_index;
+ best_x_difference = this_x_difference;
+ }
+ }
+ // 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 this_x_difference;
+ if (char_index == 0) this_x_difference = fabs(_spans.front().x_end + _chunks.front().left_x - local_x);
+ else this_x_difference = fabs(_characters[char_index - 1].span(this).x_end + _characters[char_index - 1].chunk(this).left_x - local_x);
+ if (this_x_difference < best_x_difference) {
+ best_char_index = char_index;
+ best_x_difference = this_x_difference;
+ }
+ }
+ if (best_char_index == -1) return iterator(this, char_index);
+ 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(NR::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 (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
+ double span_left, span_right;
+ if (_spans[span_index].x_start < _spans[span_index].x_end) {
+ span_left = _spans[span_index].x_start;
+ span_right = _spans[span_index].x_end;
+ } else {
+ span_left = _spans[span_index].x_end;
+ span_right = _spans[span_index].x_start;
+ }
+ if ( local_x >= _chunks[_spans[span_index].in_chunk].left_x + span_left
+ && local_x <= _chunks[_spans[span_index].in_chunk].left_x + span_right
+ && local_y >= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift - _spans[span_index].line_height.ascent
+ && local_y <= _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift + _spans[span_index].line_height.descent) {
+ return _cursorXOnLineToIterator(_chunks[_spans[span_index].in_chunk].in_line, local_x);
+ }
+ }
+
+ // 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++) {
+ LineHeight line_height = {0.0, 0.0, 0.0};
+ 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);
+}
+
+Layout::iterator Layout::getLetterAt(double x, double y) const
+{
+ NR::Point point(x, y);
+
+ double rotation;
+ for (iterator it = begin() ; it != end() ; it.nextCharacter()) {
+ NR::Rect box = characterBoundingBox(it, &rotation);
+ // todo: rotation
+ if (box.contains(point)) return it;
+ }
+ return end();
+}
+
+Layout::iterator Layout::sourceToIterator(void *source_cookie, 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_cookie == source_cookie) break;
+ if (source_index == _input_stream.size()) return end();
+
+ unsigned char_index = _sourceToCharacter(source_index);
+
+ if (_input_stream[source_index]->Type() != TEXT_SOURCE)
+ return iterator(this, char_index);
+
+ 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
+}
+
+Layout::iterator Layout::sourceToIterator(void *source_cookie) const
+{
+ return sourceToIterator(source_cookie, Glib::ustring::const_iterator(std::string::const_iterator(NULL)));
+}
+
+NR::Rect 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);
+}
+
+NR::Point Layout::characterAnchorPoint(iterator const &it) const
+{
+ if (_characters.empty())
+ return _empty_cursor_shape.position;
+ if (it._char_index == _characters.size()) {
+ return NR::Point(_chunks.back().left_x + _spans.back().x_end, _lines.back().baseline_y + _spans.back().baseline_shift);
+ } else {
+ return NR::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);
+ }
+}
+
+NR::Point Layout::chunkAnchorPoint(iterator const &it) const
+{
+ unsigned chunk_index;
+
+ if (_chunks.empty())
+ return NR::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;
+ if (alignment == LEFT || alignment == FULL)
+ return NR::Point(_chunks[chunk_index].left_x, _lines[chunk_index].baseline_y);
+
+ double chunk_width = _getChunkWidth(chunk_index);
+ if (alignment == RIGHT)
+ return NR::Point(_chunks[chunk_index].left_x + chunk_width, _lines[chunk_index].baseline_y);
+ //centre
+ return NR::Point(_chunks[chunk_index].left_x + chunk_width * 0.5, _lines[chunk_index].baseline_y);
+}
+
+NR::Rect Layout::characterBoundingBox(iterator const &it, double *rotation) const
+{
+ NR::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[glyph_index].in_character == char_index ; glyph_index++)
+ cluster_half_width += _glyphs[glyph_index].width;
+ 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 != NULL && midpoint_otp[0].piece >= 0) {
+ NR::Point midpoint;
+ NR::Point tangent;
+ Span const &span = _characters[char_index].span(this);
+ double top = span.baseline_shift - span.line_height.ascent;
+ double bottom = span.baseline_shift + span.line_height.descent;
+
+ const_cast<Path*>(_path_fitted)->PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
+ top_left[NR::X] = midpoint[NR::X] - cluster_half_width;
+ top_left[NR::Y] = midpoint[NR::Y] + top;
+ bottom_right[NR::X] = midpoint[NR::X] + cluster_half_width;
+ bottom_right[NR::Y] = midpoint[NR::Y] + bottom;
+ if (rotation)
+ *rotation = atan2(tangent[1], tangent[0]);
+ }
+ g_free(midpoint_otp);
+ } else {
+ if (it._char_index == _characters.size()) {
+ top_left[NR::X] = bottom_right[NR::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[NR::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[NR::X] = _spans[_characters[it._char_index].in_span].x_end + _characters[it._char_index].chunk(this).left_x;
+ else
+ bottom_right[NR::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.ascent + _spans[_characters[char_index].in_span].line_height.descent;
+ top_left[NR::Y] = top_left[NR::X];
+ top_left[NR::X] = baseline_y - span_height * 0.5;
+ bottom_right[NR::Y] = bottom_right[NR::X];
+ bottom_right[NR::X] = baseline_y + span_height * 0.5;
+ } else {
+ top_left[NR::Y] = baseline_y - _spans[_characters[char_index].in_span].line_height.ascent;
+ bottom_right[NR::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 NR::Rect(top_left, bottom_right);
+}
+
+std::vector<NR::Point> Layout::createSelectionShape(iterator const &it_start, iterator const &it_end, NR::Matrix const &transform) const
+{
+ std::vector<NR::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;
+
+ NR::Point top_left, bottom_right;
+ if (_path_fitted || char_rotation != 0.0) {
+ NR::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[NR::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[NR::X] = _spans[span_index].x_end + _spans[span_index].chunk(this).left_x;
+ else
+ bottom_right[NR::X] = span_x + _characters[char_index].x;
+
+ double baseline_y = _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift;
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ double span_height = _spans[span_index].line_height.ascent + _spans[span_index].line_height.descent;
+ top_left[NR::Y] = top_left[NR::X];
+ top_left[NR::X] = baseline_y - span_height * 0.5;
+ bottom_right[NR::Y] = bottom_right[NR::X];
+ bottom_right[NR::X] = baseline_y + span_height * 0.5;
+ } else {
+ top_left[NR::Y] = baseline_y - _spans[span_index].line_height.ascent;
+ bottom_right[NR::Y] = baseline_y + _spans[span_index].line_height.descent;
+ }
+ }
+
+ NR::Rect char_box(top_left, bottom_right);
+ if (char_box.extent(NR::X) == 0.0 || char_box.extent(NR::Y) == 0.0)
+ continue;
+ NR::Point center_of_rotation((top_left[NR::X] + bottom_right[NR::X]) * 0.5,
+ top_left[NR::Y] + _spans[span_index].line_height.ascent);
+ NR::Matrix total_transform = NR::translate(-center_of_rotation) * NR::rotate(char_rotation) * NR::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, NR::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;
+ 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 (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 != NULL && 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);
+
+ NR::Point point;
+ NR::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;
+ *rotation = atan2(tangent);
+ (*position)[NR::X] = point[NR::X] - tangent[NR::Y] * span->baseline_shift;
+ (*position)[NR::Y] = point[NR::Y] + tangent[NR::X] * span->baseline_shift;
+ } else {
+ // text is not on a path
+ if (it._char_index >= _characters.size()) {
+ span = &_spans.back();
+ (*position)[NR::X] = _chunks[span->in_chunk].left_x + span->x_end;
+ *rotation = _glyphs.empty() ? 0.0 : _glyphs.back().rotation;
+ } else {
+ span = &_spans[_characters[it._char_index].in_span];
+ (*position)[NR::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[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)[NR::Y] = span->line(this).baseline_y + span->baseline_shift;
+ }
+ // up to now *position is the baseline point, not the final point which will be the bottom of the descent
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ *height = span->line_height.ascent + span->line_height.descent;
+ *rotation += M_PI / 2;
+ std::swap((*position)[NR::X], (*position)[NR::Y]);
+ (*position)[NR::X] -= sin(*rotation) * *height * 0.5;
+ (*position)[NR::Y] += cos(*rotation) * *height * 0.5;
+ } else {
+ 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 = (span->line_height.ascent + span->line_height.descent) / cos(caret_slope);
+ *rotation += caret_slope;
+ (*position)[NR::X] -= sin(*rotation) * span->line_height.descent;
+ (*position)[NR::Y] += cos(*rotation) * span->line_height.descent;
+ }
+ }
+}
+
+void Layout::getSourceOfCharacter(iterator const &it, void **source_cookie, Glib::ustring::iterator *text_iterator) const
+{
+ if (it._char_index == _characters.size()) {
+ *source_cookie = NULL;
+ return;
+ }
+ InputStreamItem *stream_item = _input_stream[_spans[_characters[it._char_index].in_span].in_input_stream_item];
+ *source_cookie = stream_item->source_cookie;
+ if (text_iterator && stream_item->Type() == TEXT_SOURCE) {
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(stream_item);
+ Glib::ustring::const_iterator text_iter_const = 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_const;
+ char_index--;
+ }
+ text_source->text->begin().base() + (text_iter_const.base() - text_source->text->begin().base());
+ *text_iterator = Glib::ustring::iterator(std::string::iterator(const_cast<char*>(&*text_source->text->begin().base() + (text_iter_const.base() - text_source->text->begin().base()))));
+ // the caller owns the string, so they're going to want a non-const iterator
+ }
+}
+
+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].width;
+ 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;
+ if (_characters[char_index].char_attributes.is_cursor_position)
+ dx -= style->letter_spacing.computed;
+ }
+
+ 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()
+{
+ 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;
+ if (_parent_layout->_lines[line_index + 1].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 + 1)].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 + 1, _x_coordinate)._char_index;
+ _glyph_index = _parent_layout->_characters[_char_index].in_glyph;
+ return true;
+}
+
+bool Layout::iterator::prevLineCursor()
+{
+ if (!_cursor_moving_vertically)
+ beginCursorUpDown();
+ unsigned 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;
+ if (_parent_layout->_lines[line_index - 1].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 - 1)].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 - 1, _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 true; \
+ } \
+ _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()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return prevLineCursor();
+ else if(block_progression == BOTTOM_TO_TOP)
+ return nextLineCursor();
+ else
+ return _cursorLeftOrRightLocalX(RIGHT_TO_LEFT);
+}
+
+bool Layout::iterator::cursorDown()
+{
+ Direction block_progression = _parent_layout->_blockProgression();
+ if(block_progression == TOP_TO_BOTTOM)
+ return nextLineCursor();
+ else if(block_progression == BOTTOM_TO_TOP)
+ return prevLineCursor();
+ 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
diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp
new file mode 100755
index 000000000..08eb403db
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Output.cpp
@@ -0,0 +1,469 @@
+/*
+ * Inkscape::Text::Layout - text layout engine output functions
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#include "Layout-TNG.h"
+#include "display/nr-arena-glyphs.h"
+#include "style.h"
+#include "print.h"
+#include "extension/print.h"
+#include "livarot/Path.h"
+#include "libnr/nr-scale-matrix-ops.h"
+#include "font-instance.h"
+#include "svg/svg-length.h"
+
+namespace Inkscape {
+namespace Text {
+
+void Layout::_clearOutputObjects()
+{
+ _paragraphs.clear();
+ _lines.clear();
+ _chunks.clear();
+ for (std::vector<Span>::iterator it_span = _spans.begin() ; it_span != _spans.end() ; it_span++)
+ if (it_span->font) it_span->font->Unref();
+ _spans.clear();
+ _characters.clear();
+ _glyphs.clear();
+ _path_fitted = NULL;
+}
+
+void Layout::LineHeight::max(LineHeight const &other)
+{
+ if (other.ascent > ascent) ascent = other.ascent;
+ if (other.descent > descent) descent = other.descent;
+ if (other.leading > leading) leading = other.leading;
+}
+
+void Layout::_getGlyphTransformMatrix(int glyph_index, NRMatrix *matrix) const
+{
+ Span const &span = _glyphs[glyph_index].span(this);
+ double sin_rotation = sin(_glyphs[glyph_index].rotation);
+ double cos_rotation = cos(_glyphs[glyph_index].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;
+ if (span.block_progression == LEFT_TO_RIGHT || span.block_progression == RIGHT_TO_LEFT) {
+ (*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 {
+ (*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(NRArenaGroup *in_arena, NRRect const *paintbox) const
+{
+ int glyph_index = 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;
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[_spans[span_index].in_input_stream_item]);
+ NRArenaGlyphsGroup *nr_group = NRArenaGlyphsGroup::create(in_arena->arena);
+ nr_arena_item_add_child(in_arena, nr_group, NULL);
+ nr_arena_item_unref(nr_group);
+
+ nr_arena_glyphs_group_set_style(nr_group, text_source->style);
+ 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) {
+ NRMatrix glyph_matrix;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ nr_arena_glyphs_group_add_component(nr_group, _spans[span_index].font, _glyphs[glyph_index].glyph, &glyph_matrix);
+ }
+ glyph_index++;
+ }
+ nr_arena_glyphs_group_set_paintbox(NR_ARENA_GLYPHS_GROUP(nr_group), paintbox);
+ }
+ nr_arena_item_request_update(NR_ARENA_ITEM(in_arena), NR_ARENA_ITEM_STATE_ALL, FALSE);
+}
+
+void Layout::getBoundingBox(NRRect *bounding_box, NR::Matrix const &transform) const
+{
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) continue;
+ // this could be faster
+ NRMatrix glyph_matrix;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ NR::Matrix total_transform = glyph_matrix;
+ total_transform *= transform;
+ NR::Rect glyph_rect = _glyphs[glyph_index].span(this).font->BBox(_glyphs[glyph_index].glyph);
+ NR::Point bmi = glyph_rect.min(), bma = glyph_rect.max();
+ NR::Point tlp(bmi[0],bmi[1]), trp(bma[0],bmi[1]), blp(bmi[0],bma[1]), brp(bma[0],bma[1]);
+ tlp *= total_transform;
+ trp *= total_transform;
+ blp *= total_transform;
+ brp *= total_transform;
+ glyph_rect = NR::Rect(tlp,trp);
+ glyph_rect.expandTo(blp);
+ glyph_rect.expandTo(brp);
+ if ( (glyph_rect.min())[0] < bounding_box->x0 ) bounding_box->x0=(glyph_rect.min())[0];
+ if ( (glyph_rect.max())[0] > bounding_box->x1 ) bounding_box->x1=(glyph_rect.max())[0];
+ if ( (glyph_rect.min())[1] < bounding_box->y0 ) bounding_box->y0=(glyph_rect.min())[1];
+ if ( (glyph_rect.max())[1] > bounding_box->y1 ) bounding_box->y1=(glyph_rect.max())[1];
+ }
+}
+
+void Layout::print(SPPrintContext *ctx,
+ NRRect const *pbox, NRRect const *dbox, NRRect const *bbox,
+ NRMatrix const &ctm) const
+{
+ if (_input_stream.empty()) return;
+
+ Direction block_progression = _blockProgression();
+ bool text_to_path = ctx->module->textToPath();
+ 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++;
+ continue;
+ }
+ NRMatrix glyph_matrix;
+ 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]);
+ if (text_to_path || _path_fitted) {
+ NRBPath bpath;
+ bpath.path = (NArtBpath*)span.font->ArtBPath(_glyphs[glyph_index].glyph);
+ if (bpath.path) {
+ NRBPath abp;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ abp.path = nr_artpath_affine(bpath.path, glyph_matrix);
+ if (text_source->style->fill.type != SP_PAINT_TYPE_NONE)
+ sp_print_fill(ctx, &abp, &ctm, text_source->style, pbox, dbox, bbox);
+ if (text_source->style->stroke.type != SP_PAINT_TYPE_NONE)
+ sp_print_stroke(ctx, &abp, &ctm, text_source->style, pbox, dbox, bbox);
+ nr_free(abp.path);
+ }
+ glyph_index++;
+ } else {
+ NR::Point g_pos(0,0); // all strings are output at (0,0) because we do the translation using the matrix
+ glyph_matrix = NR::Matrix(NR::scale(1.0, -1.0) * NR::Matrix(NR::rotate(_glyphs[glyph_index].rotation)));
+ if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) {
+ glyph_matrix.c[4] = span.line(this).baseline_y + span.baseline_shift;
+ // since we're outputting character codes, not glyphs, we want the character x
+ glyph_matrix.c[5] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x;
+ } else {
+ glyph_matrix.c[4] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x;
+ glyph_matrix.c[5] = span.line(this).baseline_y + span.baseline_shift;
+ }
+ 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 by detecting kerning and stopping when we encounter it
+ Glib::ustring span_string;
+ double char_x = _characters[_glyphs[glyph_index].in_character].x;
+ unsigned this_span_index = _characters[_glyphs[glyph_index].in_character].in_span;
+ 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) {
+ char_x += _glyphs[glyph_index].width;
+ glyph_index++;
+ }
+ } while (glyph_index < _glyphs.size()
+ && _path_fitted == NULL
+ && _characters[_glyphs[glyph_index].in_character].in_span == this_span_index
+ && fabs(char_x - _characters[_glyphs[glyph_index].in_character].x) < 1e-5);
+ sp_print_bind(ctx, glyph_matrix, 1.0);
+ sp_print_text(ctx, span_string.c_str(), g_pos, text_source->style);
+ sp_print_release(ctx);
+ }
+ }
+}
+
+// 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 char const *weight_to_text(PangoWeight w)
+{
+ switch (w) {
+ case PANGO_WEIGHT_ULTRALIGHT: return "ultralight";
+ case PANGO_WEIGHT_LIGHT : return "light";
+ case PANGO_WEIGHT_SEMIBOLD : return "semibold";
+ case PANGO_WEIGHT_NORMAL : return "normalweight";
+ case PANGO_WEIGHT_BOLD : return "bold";
+ case PANGO_WEIGHT_ULTRABOLD : return "ultrabold";
+ case PANGO_WEIGHT_HEAVY : return "heavy";
+ }
+ return "???";
+}
+
+Glib::ustring Layout::dumpAsText() const
+{
+ Glib::ustring result;
+
+ for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
+ char line[256];
+ snprintf(line, sizeof(line), "==== span %d\n", span_index);
+ result += line;
+ snprintf(line, sizeof(line), " in para %d (direction=%s)\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));
+ result += line;
+ snprintf(line, sizeof(line), " in source %d (type=%d, cookie=%p)\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_cookie);
+ result += line;
+ snprintf(line, sizeof(line), " in line %d (baseline=%f, shape=%d)\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);
+ result += line;
+ snprintf(line, sizeof(line), " in chunk %d (x=%f, baselineshift=%f)\n", _spans[span_index].in_chunk, _chunks[_spans[span_index].in_chunk].left_x, _spans[span_index].baseline_shift);
+ result += line;
+ if (_spans[span_index].font) {
+ snprintf(line, sizeof(line), " font '%s' %f %s %s\n", pango_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)));
+ result += line;
+ }
+ snprintf(line, sizeof(line), " x_start = %f, x_end = %f\n", _spans[span_index].x_start, _spans[span_index].x_end);
+ result += line;
+ snprintf(line, sizeof(line), " line height: ascent %f, descent %f leading %f\n", _spans[span_index].line_height.ascent, _spans[span_index].line_height.descent, _spans[span_index].line_height.leading);
+ result += line;
+ snprintf(line, sizeof(line), " direction %s, block-progression %s\n", direction_to_text(_spans[span_index].direction), direction_to_text(_spans[span_index].block_progression));
+ result += line;
+ result += " ** characters:\n";
+ Glib::ustring::const_iterator iter_char = _spans[span_index].input_stream_first_character;
+ // very inefficent code. what the hell, it's only debug stuff.
+ for (unsigned char_index = 0 ; char_index < _characters.size() ; char_index++) {
+ 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), " %d: control x=%f flags=%03x glyph=%d\n", char_index, _characters[char_index].x, *(unsigned*)&_characters[char_index].char_attributes, _characters[char_index].in_glyph);
+ } else {
+ snprintf(line, sizeof(line), " %d: '%c' x=%f flags=%03x glyph=%d\n", char_index, *iter_char, _characters[char_index].x, *(unsigned*)&_characters[char_index].char_attributes, _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), " %d: %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;
+}
+
+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;
+ }
+
+ switch (_paragraphs.front().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 != NULL && point_otp[0].piece >= 0) {
+ NR::Point point;
+ NR::Point tangent;
+ const_cast<Path&>(path).PointAndTangentAt(point_otp[0].piece, point_otp[0].t, point, tangent);
+ _empty_cursor_shape.position = point;
+ _empty_cursor_shape.rotation = atan2(tangent[NR::Y], tangent[NR::X]);
+ }
+ }
+
+ for (unsigned char_index = 0 ; char_index < _characters.size() ; ) {
+ int next_cluster_glyph_index;
+ unsigned next_cluster_char_index;
+ double character_advance;
+ Span const &span = _characters[char_index].span(this);
+
+ for (next_cluster_char_index = char_index + 1 ;
+ next_cluster_char_index < _characters.size() && !_characters[next_cluster_char_index].char_attributes.is_cursor_position;
+ next_cluster_char_index++);
+
+ if (next_cluster_char_index == _characters.size()) {
+ next_cluster_glyph_index = _glyphs.size();
+ character_advance = 0.0; // arbitrary because we're not going to advance
+ } else {
+ next_cluster_glyph_index = _characters[next_cluster_char_index].in_glyph;
+ character_advance = (_glyphs[next_cluster_glyph_index].x + _glyphs[next_cluster_glyph_index].chunk(this).left_x)
+ - (_glyphs[_characters[char_index].in_glyph].x + span.chunk(this).left_x);
+ }
+
+ double start_offset = offset + span.x_start + _characters[char_index].x;
+ double cluster_width = 0.0;
+ for (int glyph_index = _characters[char_index].in_glyph ; glyph_index < next_cluster_glyph_index ; glyph_index++)
+ cluster_width += _glyphs[glyph_index].width;
+ 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 != NULL && midpoint_otp[0].piece >= 0) {
+ NR::Point midpoint;
+ NR::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 != NULL && start_otp[0].piece >= 0) {
+ Path::cut_position *end_otp = const_cast<Path&>(path).CurvilignToPosition(1, &end_offset, unused);
+ if (end_otp != NULL && end_otp[0].piece >= 0) {
+ bool on_same_subpath = true;
+ for (size_t i = 0 ; i < path.pts.size() ; i++) {
+ if (path.pts[i].piece <= start_otp[0].piece) continue;
+ if (path.pts[i].piece >= end_otp[0].piece) break;
+ if (path.pts[i].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)
+ NR::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();
+ } else {
+ tangent = NR::Point (0,0);
+ }
+ }
+ g_free(end_otp);
+ }
+ g_free(start_otp);
+ }
+ }
+
+ double rotation = atan2(tangent[1], tangent[0]);
+ for (int glyph_index = _characters[char_index].in_glyph ; 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);
+ double normal_shift = _glyphs[glyph_index].y;
+ if (span.direction == RIGHT_TO_LEFT)
+ tangent_shift += cluster_width;
+ _glyphs[glyph_index].x = midpoint[0] - span.chunk(this).left_x + tangent[0] * tangent_shift - tangent[1] * normal_shift;
+ _glyphs[glyph_index].y = midpoint[1] - _lines.front().baseline_y + tangent[1] * tangent_shift + tangent[0] * normal_shift;
+ _glyphs[glyph_index].rotation += rotation;
+ }
+ } else { // outside the bounds of the path: hide the glyphs
+ _characters[char_index].in_glyph = -1;
+ }
+ g_free(midpoint_otp);
+
+ char_index = next_cluster_char_index;
+ }
+
+ for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
+ _spans[span_index].x_start += offset;
+ _spans[span_index].x_end += offset;
+ }
+
+ _path_fitted = &path;
+}
+
+SPCurve *Layout::convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const
+{
+ GSList *cc = NULL;
+
+ for (int glyph_index = from_glyph._glyph_index ; glyph_index < to_glyph._glyph_index ; glyph_index++) {
+ NRMatrix glyph_matrix;
+ Span const &span = _glyphs[glyph_index].span(this);
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+
+ NRBPath bpath;
+ bpath.path = (NArtBpath*)span.font->ArtBPath(_glyphs[glyph_index].glyph);
+ if (bpath.path) {
+ NArtBpath *abp = nr_artpath_affine(bpath.path, glyph_matrix);
+ SPCurve *c = sp_curve_new_from_bpath(abp);
+ if (c) cc = g_slist_prepend(cc, c);
+ }
+ }
+ cc = g_slist_reverse(cc);
+
+ SPCurve *curve;
+ if ( cc ) {
+ curve = sp_curve_concat(cc);
+ } else {
+ curve = sp_curve_new();
+ }
+
+ while (cc) {
+ /* fixme: This is dangerous, as we are mixing art_alloc and g_new */
+ sp_curve_unref((SPCurve *) cc->data);
+ cc = g_slist_remove(cc, cc->data);
+ }
+
+ return curve;
+}
+
+void Layout::transform(NR::Matrix 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 (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
+ NR::Point point(_glyphs[glyph_index].x, _glyphs[glyph_index].y);
+ point *= transform;
+ _glyphs[glyph_index].x = point[0];
+ _glyphs[glyph_index].y = point[1];
+ }
+}
+
+}//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:encoding=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 100755
index 000000000..3865e2c12
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Scanline-Maker.h
@@ -0,0 +1,169 @@
+/*
+ * 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, 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() {}
+
+ 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::LineHeight 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::LineHeight 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();
+
+ /** Returns a single infinite run at the current location */
+ virtual std::vector<ScanRun> makeScanline(Layout::LineHeight const &line_height);
+
+ /** Increments the current y by the current line height */
+ virtual void completeLine();
+
+ virtual double yCoordinate()
+ {return _y;}
+
+ /** Just changes y */
+ virtual void setNewYCoordinate(double new_y);
+
+ /** Always true, but has to save the new height */
+ virtual bool canExtendCurrentScanline(Layout::LineHeight const &line_height);
+
+private:
+ double _x, _y;
+ Layout::LineHeight _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();
+
+ virtual std::vector<ScanRun> makeScanline(Layout::LineHeight const &line_height);
+
+ virtual void completeLine();
+
+ virtual double yCoordinate();
+
+ virtual void setNewYCoordinate(double new_y);
+
+ /** never true */
+ virtual bool canExtendCurrentScanline(Layout::LineHeight const &line_height);
+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:encoding=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 100755
index 000000000..73b4dd6fa
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Scanline-Makers.cpp
@@ -0,0 +1,189 @@
+/*
+ * 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, read the file 'COPYING' for more information
+ */
+#include "Layout-TNG-Scanline-Maker.h"
+#include "livarot/Shape.h"
+#include "livarot/float-line.h"
+
+namespace Inkscape {
+namespace Text {
+
+// *********************** infinite version
+
+Layout::InfiniteScanlineMaker::InfiniteScanlineMaker(double initial_x, double initial_y, Layout::Direction block_progression)
+{
+ _current_line_height.ascent = 0.0;
+ _current_line_height.descent = 0.0;
+ _current_line_height.leading = 0.0;
+ 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()
+{
+}
+
+std::vector<Layout::ScanlineMaker::ScanRun> Layout::InfiniteScanlineMaker::makeScanline(Layout::LineHeight const &line_height)
+{
+ std::vector<ScanRun> runs(1);
+ runs[0].x_start = _x;
+ runs[0].x_end = FLT_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.total();
+ else
+ _y += _current_line_height.total();
+ _current_line_height.ascent = 0.0;
+ _current_line_height.descent = 0.0;
+ _current_line_height.leading = 0.0;
+}
+
+void Layout::InfiniteScanlineMaker::setNewYCoordinate(double new_y)
+{
+ _y = new_y;
+}
+
+bool Layout::InfiniteScanlineMaker::canExtendCurrentScanline(Layout::LineHeight const &line_height)
+{
+ _current_line_height = line_height;
+ return true;
+}
+
+// *********************** 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(NR::Matrix(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(NR::Matrix(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(NR::Matrix(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::LineHeight const &line_height)
+{
+ FloatLigne line_rasterization;
+ FloatLigne line_decent_length_runs;
+ float line_text_height = (float)(line_height.ascent + line_height.descent);
+
+ if (_y > _bounding_box_bottom)
+ return std::vector<ScanRun>();
+
+ if (_y < _bounding_box_top)
+ _y = _bounding_box_top;
+
+ if (line_text_height == 0.0)
+ line_text_height = 0.001; // Scan() doesn't work for zero height so this will have to do
+
+ _current_line_height = (float)line_height.total();
+
+ // 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 retreiving 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();
+ // 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 ? -_current_line_height - _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 ? -_current_line_height - _y : _y;
+ }
+
+ return result;
+}
+
+void Layout::ShapeScanlineMaker::completeLine()
+{
+ _y += _current_line_height;
+}
+
+double Layout::ShapeScanlineMaker::yCoordinate()
+{
+ if (_negative_block_progression) return -_current_line_height - _y;
+ return _y;
+}
+
+void Layout::ShapeScanlineMaker::setNewYCoordinate(double new_y)
+{
+ _y = (float)new_y;
+ if (_negative_block_progression) _y = -_current_line_height - _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::LineHeight 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;
+}
+
+}//namespace Text
+}//namespace Inkscape
diff --git a/src/libnrtype/Layout-TNG.cpp b/src/libnrtype/Layout-TNG.cpp
new file mode 100755
index 000000000..bda0d1697
--- /dev/null
+++ b/src/libnrtype/Layout-TNG.cpp
@@ -0,0 +1,45 @@
+/*
+ * Inkscape::Text::Layout - text layout engine misc
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, 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()
+{
+ _path_fitted = NULL;
+}
+
+Layout::~Layout()
+{
+ clear();
+}
+
+void Layout::clear()
+{
+ _clearInputObjects();
+ _clearOutputObjects();
+}
+
+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 100755
index 000000000..6400ee77b
--- /dev/null
+++ b/src/libnrtype/Layout-TNG.h
@@ -0,0 +1,1029 @@
+/*
+ * Inkscape::Text::Layout - text layout engine
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+#ifndef __LAYOUT_TNG_H__
+#define __LAYOUT_TNG_H__
+
+#include "libnr/nr-rect.h"
+#include "libnr/nr-matrix.h"
+#include "libnr/nr-matrix-ops.h"
+#include "libnr/nr-rotate-ops.h"
+#include <glibmm/ustring.h>
+#include <pango/pango-break.h>
+#include <vector>
+
+class SPStyle;
+class Shape;
+class NRArenaGroup;
+class SPPrintContext;
+class SVGLength;
+class Path;
+class SPCurve;
+class font_instance;
+
+namespace Inkscape {
+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};
+
+ /** Display alignment for shapes. See appendWrapShape(). */
+ enum DisplayAlign {DISPLAY_ALIGN_BEFORE, DISPLAY_ALIGN_CENTER, DISPLAY_ALIGN_AFTER};
+
+ /** 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;
+ };
+
+ /** 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};
+
+ /** 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();}
+
+ /** 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_cookie This pointer is treated as opaque by Layout
+ but will be passed through the flowing process intact so
+ that callers can use it to refer to the original object
+ that generated a particular glyph. See Layout::iterator.
+ Implementation detail: currently all callers put an
+ SPString in here.
+ \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, void *source_cookie, 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, void *source_cookie, OptionalTextTagAttrs const *optional_attributes = NULL, unsigned optional_attributes_offset = 0)
+ {appendText(text, style, source_cookie, 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_cookie This pointer is treated as opaque by Layout
+ but will be passed through the flowing process intact so
+ that callers can use it to refer to the original object
+ that generated a particular area. See Layout::iterator.
+ Implementation detail: currently all callers put an
+ SPObject in here.
+ 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, void *source_cookie, 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);
+
+ //@}
+
+ // ************************** 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(NRArenaGroup *in_arena, NRRect 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.
+ */
+ void getBoundingBox(NRRect *bounding_box, NR::Matrix const &transform) 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, NRRect const *pbox, NRRect const *dbox, NRRect const *bbox, NRMatrix const &ctm) const;
+
+ /** 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;
+
+ /** 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.
+ */
+ SPCurve* convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const;
+ inline 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(NR::Matrix 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(NR::Point &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(NR::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 cookie, end() is
+ returned. If more than one input has the same cookie, 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(void *source_cookie, 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_cookie is invalid,
+ end() is returned. If more than one input has the same cookie, the
+ first one will be used. */
+ iterator sourceToIterator(void *source_cookie) 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. */
+ NR::Rect 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 the unicode character code of the character pointed to by
+ \a it. If \a it == end() the result is undefined. */
+ inline int characterAt(iterator const &it) const;
+
+ /** Discovers where the character pointed to by \a it came from, by
+ retrieving the cookie that was passed to the call to appendText() or
+ appendControlCode() which generated that output. If \a it == end()
+ then NULL is returned as the cookie. 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, void **source_cookie, Glib::ustring::iterator *text_iterator = NULL) const;
+
+ /** For latin text, the left side of the character, on the baseline */
+ NR::Point characterAnchorPoint(iterator const &it) const;
+
+ /** This is that value to apply to the x,y attributes of tspan role=line
+ elements, and hence it takes alignment into account. */
+ NR::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. */
+ NR::Rect characterBoundingBox(iterator const &it, double *rotation = NULL) 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<NR::Point> createSelectionShape(iterator const &it_start, iterator const &it_end, NR::Matrix 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, NR::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;
+
+ //@}
+
+ /// it's useful for this to be public so that ScanlineMaker can use it
+ struct LineHeight {
+ double ascent;
+ double descent;
+ double leading;
+ inline double total() const {return ascent + descent + leading;}
+ inline void setZero() {ascent = descent = leading = 0.0;}
+ inline LineHeight& operator*=(double x) {ascent *= x; descent *= x; leading *= x; return *this;}
+ void max(LineHeight const &other); /// makes this object contain the largest of all three members between this object and other
+ };
+
+ /// see _enum_converter()
+ struct EnumConversionItem {
+ int input, output;
+ };
+
+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() {}
+ virtual InputStreamItemType Type() =0;
+ void *source_cookie;
+ };
+
+ /** 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:
+ virtual InputStreamItemType Type() {return TEXT_SOURCE;}
+ virtual ~InputStreamTextSource();
+ 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;
+
+ // a few functions for some of the more complicated style accesses
+ float styleComputeFontSize() const;
+ font_instance *styleGetFontInstance() const;
+ Direction styleGetBlockProgression() 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:
+ virtual InputStreamItemType Type() {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);
+
+ /** There are a few cases where we have different sets of enums meaning
+ the same thing, eg Pango font styles vs. SPStyle font styles. These need
+ converting. */
+ static int _enum_converter(int input, EnumConversionItem const *conversion_table, unsigned conversion_table_size);
+
+ /** The overall block-progression of the whole flow. */
+ inline Direction _blockProgression() const
+ {return static_cast<InputStreamTextSource*>(_input_stream.front())->styleGetBlockProgression();}
+
+ /** 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 {
+ NR::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;
+
+ struct Glyph;
+ struct Character;
+ struct Span;
+ struct Chunk;
+ struct Line;
+ struct Paragraph;
+
+ struct Glyph {
+ int glyph;
+ unsigned in_character;
+ 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
+ float width;
+ 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];}
+ };
+ struct Character {
+ unsigned in_span;
+ float x; /// relative to the start of the *span* (so we can do block-progression)
+ PangoLogAttr char_attributes;
+ 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
+ };
+ 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
+ LineHeight line_height;
+ double baseline_shift; /// relative to the line's baseline
+ 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];}
+ };
+ struct Chunk {
+ unsigned in_line;
+ double left_x;
+ };
+ struct Line {
+ unsigned in_paragraph;
+ double baseline_y;
+ unsigned in_shape;
+ };
+ 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, NRMatrix *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) \
+ {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 coordinate and a line number, returns an iterator
+ pointing to the closest cursor position on that line to the
+ coordinate. */
+ iterator _cursorXOnLineToIterator(unsigned line_index, double local_x) 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(NULL) {}
+ // 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, interating 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();
+ bool prevLineCursor();
+
+ //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();
+ bool cursorDown();
+ 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 SPCurve* Layout::convertToCurves() const
+ {return convertToCurves(begin(), end());}
+
+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(NR::Point &point) const
+ {return getNearestCursorPositionTo(point[0], point[1]);}
+
+inline Layout::iterator Layout::getLetterAt(NR::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 int Layout::characterAt(iterator const &it) const
+{
+ void *unused;
+ Glib::ustring::iterator text_iter;
+ getSourceOfCharacter(it, &unused, &text_iter);
+ return *text_iter;
+}
+
+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[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
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/Makefile_insert b/src/libnrtype/Makefile_insert
new file mode 100644
index 000000000..218cbad6b
--- /dev/null
+++ b/src/libnrtype/Makefile_insert
@@ -0,0 +1,42 @@
+## Makefile.am fragment sourced by src/Makefile.am.
+
+libnrtype/all: libnrtype/libnrtype.a
+
+libnrtype/clean:
+ rm -f libnrtype/libnrtype.a $(libnrtype_libnrtype_a_OBJECTS)
+
+libnrtype_libnrtype_a_SOURCES = \
+ libnrtype/boundary-type.h \
+ libnrtype/font-style-to-pos.cpp \
+ libnrtype/font-style-to-pos.h \
+ libnrtype/font-glyph.h \
+ libnrtype/font-instance.h \
+ libnrtype/font-style.h \
+ libnrtype/nr-type-pos-def.cpp \
+ libnrtype/nr-type-pos-def.h \
+ libnrtype/nr-type-primitives.cpp \
+ libnrtype/nr-type-primitives.h \
+ libnrtype/nrtype-forward.h \
+ libnrtype/FontFactory.cpp \
+ libnrtype/FontFactory.h \
+ libnrtype/FontInstance.cpp \
+ libnrtype/one-box.h \
+ libnrtype/one-glyph.h \
+ libnrtype/one-para.h \
+ libnrtype/RasterFont.cpp \
+ libnrtype/RasterFont.h \
+ libnrtype/raster-glyph.h \
+ libnrtype/raster-position.h \
+ libnrtype/text-boundary.h \
+ libnrtype/TextWrapper.cpp \
+ libnrtype/TextWrapper.h \
+ libnrtype/Layout-TNG-Compute.cpp \
+ libnrtype/Layout-TNG-Input.cpp \
+ libnrtype/Layout-TNG-OutIter.cpp \
+ libnrtype/Layout-TNG-Output.cpp \
+ libnrtype/Layout-TNG-Scanline-Maker.h \
+ libnrtype/Layout-TNG-Scanline-Makers.cpp \
+ libnrtype/Layout-TNG.cpp \
+ libnrtype/Layout-TNG.h
+
+
diff --git a/src/libnrtype/RasterFont.cpp b/src/libnrtype/RasterFont.cpp
new file mode 100644
index 000000000..68ecb2e4d
--- /dev/null
+++ b/src/libnrtype/RasterFont.cpp
@@ -0,0 +1,431 @@
+/*
+ * RasterFont.cpp
+ * testICU
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include "RasterFont.h"
+
+#include <livarot/float-line.h>
+#include <livarot/int-line.h>
+#include <livarot/Path.h>
+#include <livarot/Shape.h>
+#include <libnr/nr-pixblock.h>
+#include <libnrtype/font-instance.h>
+#include <libnrtype/raster-glyph.h>
+#include <libnrtype/raster-position.h>
+
+
+static void glyph_run_A8_OR (raster_info &dest,void */*data*/,int st,float vst,int en,float ven);
+
+void font_style::Apply(Path* src,Shape* dest) {
+ src->Convert(1);
+ if ( stroke_width > 0 ) {
+ if ( nbDash > 0 ) {
+ double dlen = 0.0;
+ const float scale = 1/*NR_MATRIX_DF_EXPANSION (&transform)*/;
+ for (int i = 0; i < nbDash; i++) dlen += dashes[i] * scale;
+ if (dlen >= 0.01) {
+ float sc_offset = dash_offset * scale;
+ float *tdashs=(float*)malloc((nbDash+1)*sizeof(float));
+ while ( sc_offset >= dlen ) sc_offset-=dlen;
+ tdashs[0]=dashes[0] * scale;
+ for (int i=1;i<nbDash;i++) tdashs[i] = tdashs[i - 1] + dashes[i] * scale;
+ src->DashPolyline(0.0,0.0,dlen,nbDash,tdashs,true,sc_offset);
+ free(tdashs);
+ }
+ }
+ src->Stroke(dest, false, 0.5*stroke_width, stroke_join, stroke_cap, 0.5*stroke_width*stroke_miter_limit);
+ } else {
+ src->Fill(dest,0);
+ }
+}
+
+raster_font::raster_font(font_style const &fstyle) :
+ daddy(NULL),
+ refCount(0),
+ style(fstyle),
+ glyph_id_to_raster_glyph_no(),
+ nbBase(0),
+ maxBase(0),
+ bases(NULL)
+{
+ // printf("raster font born\n");
+}
+
+raster_font::~raster_font(void)
+{
+// printf("raster font death\n");
+ if ( daddy ) daddy->RemoveRasterFont(this);
+ daddy=NULL;
+ if ( style.dashes ) free(style.dashes);
+ style.dashes=NULL;
+ for (int i=0;i<nbBase;i++) delete bases[i];
+ if ( bases ) free(bases);
+}
+
+void raster_font::Unref(void)
+{
+ refCount--;
+// printf("raster %x unref'd %i\n",this,refCount);
+ if ( refCount <= 0 ) {
+ if ( daddy ) daddy->RemoveRasterFont(this);
+ daddy=NULL;
+ delete this;
+ }
+}
+void raster_font::Ref(void)
+{
+ refCount++;
+// printf("raster %x ref'd %i\n",this,refCount);
+}
+raster_glyph* raster_font::GetGlyph(int glyph_id)
+{
+ raster_glyph *res=NULL;
+ if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) {
+ LoadRasterGlyph(glyph_id);
+ if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) { // recheck
+ } else {
+ res=bases[glyph_id_to_raster_glyph_no[glyph_id]];
+ }
+ } else {
+ res=bases[glyph_id_to_raster_glyph_no[glyph_id]];
+ }
+ return res;
+}
+NR::Point raster_font::Advance(int glyph_id)
+{
+ if ( daddy == NULL ) return NR::Point(0,0);
+ double a=daddy->Advance(glyph_id,style.vertical);
+ NR::Point f_a=(style.vertical)?NR::Point(0,a):NR::Point(a,0);
+ return f_a*style.transform;
+}
+void raster_font::BBox(int glyph_id,NRRect *area)
+{
+ area->x0=area->y0=area->x1=area->y1=0;
+ if ( daddy == NULL ) return;
+ NR::Rect res=daddy->BBox(glyph_id);
+ NR::Point bmi=res.min(),bma=res.max();
+ NR::Point tlp(bmi[0],bmi[1]),trp(bma[0],bmi[1]),blp(bmi[0],bma[1]),brp(bma[0],bma[1]);
+ tlp=tlp*style.transform;
+ trp=trp*style.transform;
+ blp=blp*style.transform;
+ brp=brp*style.transform;
+ res=NR::Rect(tlp,trp);
+ res.expandTo(blp);
+ res.expandTo(brp);
+ area->x0=(res.min())[0];
+ area->y0=(res.min())[1];
+ area->x1=(res.max())[0];
+ area->y1=(res.max())[1];
+}
+
+void raster_font::LoadRasterGlyph(int glyph_id)
+{
+ raster_glyph *res=NULL;
+ if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) {
+ res=new raster_glyph();
+ res->daddy=this;
+ res->glyph_id=glyph_id;
+ if ( nbBase >= maxBase ) {
+ maxBase=2*nbBase+1;
+ bases=(raster_glyph**)realloc(bases,maxBase*sizeof(raster_glyph*));
+ }
+ bases[nbBase]=res;
+ glyph_id_to_raster_glyph_no[glyph_id]=nbBase;
+ nbBase++;
+ } else {
+ res=bases[glyph_id_to_raster_glyph_no[glyph_id]];
+ }
+ if ( res == NULL ) return;
+ if ( res->polygon ) return;
+ if ( res->outline == NULL ) {
+ if ( daddy == NULL ) return;
+ Path* outline=daddy->Outline(glyph_id,NULL);
+ res->outline=new Path;
+ if ( outline ) {
+ res->outline->Copy(outline);
+ }
+ res->outline->Transform(style.transform);
+ }
+ Shape* temp=new Shape;
+ res->polygon=new Shape;
+ style.Apply(res->outline,temp);
+ if ( style.stroke_width > 0 ) {
+ res->polygon->ConvertToShape(temp,fill_nonZero);
+ } else {
+ res->polygon->ConvertToShape(temp,fill_oddEven);
+ }
+ delete temp;
+
+ res->SetSubPixelPositionning(4);
+}
+void raster_font::RemoveRasterGlyph(raster_glyph* who)
+{
+ if ( who == NULL ) return;
+ int glyph_id=who->glyph_id;
+ if ( glyph_id_to_raster_glyph_no.find(glyph_id) == glyph_id_to_raster_glyph_no.end() ) {
+ int no=glyph_id_to_raster_glyph_no[glyph_id];
+ if ( no >= nbBase-1 ) {
+ } else {
+ bases[no]=bases[--nbBase];
+ glyph_id_to_raster_glyph_no[bases[no]->glyph_id]=no;
+ }
+ glyph_id_to_raster_glyph_no.erase(glyph_id_to_raster_glyph_no.find(glyph_id));
+ } else {
+ // not here
+ }
+}
+
+/*int top,bottom; // baseline is y=0
+ int* run_on_line; // array of size (bottom-top+1): run_on_line[i] gives the number of runs on line top+i
+ int nbRun;
+ float_ligne_run* runs;*/
+
+raster_position::raster_position(void)
+{
+ top=0;
+ bottom=-1;
+ run_on_line=NULL;
+ nbRun=0;
+ runs=NULL;
+}
+raster_position::~raster_position(void)
+{
+ if ( run_on_line ) free(run_on_line);
+ if ( runs ) free(runs);
+}
+
+void raster_position::AppendRuns(std::vector<float_ligne_run> const &r,int y)
+{
+ if ( top > bottom ) {
+ top=bottom=y;
+ if ( run_on_line ) free(run_on_line);
+ run_on_line=(int*)malloc(sizeof(int));
+ run_on_line[0]=0;
+ } else {
+ if ( y < top ) {
+ // printf("wtf?\n");
+ return;
+ } else if ( y > bottom ) {
+ int ob=bottom;
+ bottom=y;
+ run_on_line=(int*)realloc(run_on_line,(bottom-top+1)*sizeof(int));
+ for (int i=ob+1;i<=bottom;i++) run_on_line[i-top]=0;
+ }
+ }
+
+ if ( r.empty() == false) {
+ run_on_line[y - top] = r.size();
+ runs = (float_ligne_run *) realloc(runs, (nbRun + r.size()) * sizeof(float_ligne_run));
+
+ for (int i = 0; i < int(r.size()); i++) {
+ runs[nbRun + i] = r[i];
+ }
+
+ nbRun += r.size();
+ }
+}
+void raster_position::Blit(float ph,int pv,NRPixBlock &over)
+{
+ int base_y=top+pv;
+ int first_y=top+pv,last_y=bottom+pv;
+ if ( first_y < over.area.y0 ) first_y=over.area.y0;
+ if ( last_y >= over.area.y1 ) last_y=over.area.y1-1;
+ if ( first_y > last_y ) return;
+ IntLigne *theIL=new IntLigne();
+ FloatLigne *theI=new FloatLigne();
+
+ char* mdata=(char*)over.data.px;
+ if ( over.size == NR_PIXBLOCK_SIZE_TINY ) mdata=(char*)over.data.p;
+
+ for (int y=first_y;y<=last_y;y++) {
+ int first_r=0,last_r=0;
+ for (int i=base_y;i<y;i++) first_r+=run_on_line[i-base_y];
+ last_r=first_r+run_on_line[y-base_y]-1;
+ if ( first_r <= last_r ) {
+ theI->Reset();
+ for (int i=first_r;i<=last_r;i++) theI->AddRun(runs[i].st+ph,runs[i].en+ph,runs[i].vst,runs[i].ven,runs[i].pente);
+// for (int i=first_r;i<=last_r;i++) {runs[i].st+=ph;runs[i].en+=ph;}
+// theI->nbRun=theI->maxRun=last_r-first_r+1;
+// theI->runs=runs+first_r;
+
+ theIL->Copy(theI);
+ raster_info dest;
+ dest.startPix=over.area.x0;
+ dest.endPix=over.area.x1;
+ dest.sth=over.area.x0;
+ dest.stv=y;
+ dest.buffer=((uint32_t*)(mdata+(over.rs*(y-over.area.y0))));
+ theIL->Raster(dest,NULL,glyph_run_A8_OR);
+
+// theI->nbRun=theI->maxRun=0;
+// theI->runs=NULL;
+// for (int i=first_r;i<=last_r;i++) {runs[i].st-=ph;runs[i].en-=ph;}
+ }
+ }
+ delete theIL;
+ delete theI;
+}
+
+
+/* raster_font* daddy;
+ int glyph_id;
+
+ Path* outline;
+ Shape* polygon;
+
+ int nb_sub_pixel;
+ raster_position* sub_pixel;*/
+
+raster_glyph::raster_glyph(void)
+{
+ daddy=NULL;
+ glyph_id=0;
+ outline=NULL;
+ polygon=NULL;
+ nb_sub_pixel=0;
+ sub_pixel=NULL;
+}
+raster_glyph::~raster_glyph(void)
+{
+ if ( outline ) delete outline;
+ if ( polygon ) delete polygon;
+ if ( sub_pixel ) delete [] sub_pixel;
+}
+
+
+void raster_glyph::SetSubPixelPositionning(int nb_pos)
+{
+ nb_sub_pixel=nb_pos;
+ if ( nb_sub_pixel <= 0 ) nb_sub_pixel=0;
+ if ( sub_pixel ) delete [] sub_pixel;
+ sub_pixel=NULL;
+ if ( nb_sub_pixel > 0 ) {
+ sub_pixel=new raster_position[nb_pos];
+ if ( polygon ) {
+ for (int i=0;i<nb_sub_pixel;i++) LoadSubPixelPosition(i);
+ }
+ }
+}
+void raster_glyph::LoadSubPixelPosition(int no)
+{
+ if ( no < 0 || no >= nb_sub_pixel ) return;
+ if ( sub_pixel[no].top <= sub_pixel[no].bottom ) return;
+ if ( polygon == NULL ) {
+ if ( daddy == NULL ) return;
+ daddy->LoadRasterGlyph(glyph_id);
+ if ( polygon == NULL ) return;
+ }
+
+ float sub_delta=((float)no)/((float)nb_sub_pixel);
+
+ polygon->CalcBBox();
+
+ float l=polygon->leftX,r=polygon->rightX,t=polygon->topY,b=polygon->bottomY;
+ int il,ir,it,ib;
+ il=(int)floor(l);
+ ir=(int)ceil(r);
+ it=(int)floor(t);
+ ib=(int)ceil(b);
+
+ // version par FloatLigne
+ int curPt;
+ float curY;
+ polygon->BeginQuickRaster(curY, curPt);
+
+ FloatLigne* theI=new FloatLigne();
+
+ polygon->DirectQuickScan(curY,curPt,(float)(it-1)+sub_delta,true,1.0);
+
+ for (int y=it-1;y<ib;y++) {
+ theI->Reset();
+ polygon->QuickScan(curY,curPt,((float)(y+1))+sub_delta,theI,1.0);
+ theI->Flatten();
+
+ sub_pixel[no].AppendRuns(theI->runs, y);
+ }
+ polygon->EndQuickRaster();
+ delete theI;
+}
+
+void raster_glyph::Blit(const NR::Point &at,NRPixBlock &over)
+{
+ if ( nb_sub_pixel <= 0 ) return;
+ int pv=(int)ceil(at[1]);
+ double dec=4*(ceil(at[1])-at[1]);
+ int no=(int)floor(dec);
+ sub_pixel[no].Blit(at[0],pv,over);
+}
+
+
+
+static void
+glyph_run_A8_OR (raster_info &dest,void */*data*/,int st,float vst,int en,float ven)
+{
+ if ( st >= en ) return;
+ if ( vst < 0 ) vst=0;
+ if ( vst > 1 ) vst=1;
+ if ( ven < 0 ) ven=0;
+ if ( ven > 1 ) ven=1;
+ float sv=vst;
+ float dv=ven-vst;
+ int len=en-st;
+ unsigned char* d=(unsigned char*)dest.buffer;
+ d+=(st-dest.startPix);
+ if ( fabs(dv) < 0.001 ) {
+ if ( vst > 0.999 ) {
+ /* Simple copy */
+ while (len > 0) {
+ d[0] = 255;
+ d += 1;
+ len -= 1;
+ }
+ } else {
+ sv*=256;
+ unsigned int c0_24=(int)sv;
+ c0_24&=0xFF;
+ while (len > 0) {
+ unsigned int da;
+ /* Draw */
+ da = 65025 - (255 - c0_24) * (255 - d[0]);
+ d[0] = (da + 127) / 255;
+ d += 1;
+ len -= 1;
+ }
+ }
+ } else {
+ if ( en <= st+1 ) {
+ sv=0.5*(vst+ven);
+ sv*=256;
+ unsigned int c0_24=(int)sv;
+ c0_24&=0xFF;
+ unsigned int da;
+ /* Draw */
+ da = 65025 - (255 - c0_24) * (255 - d[0]);
+ d[0] = (da + 127) / 255;
+ } else {
+ dv/=len;
+ sv+=0.5*dv; // correction trapezoidale
+ sv*=16777216;
+ dv*=16777216;
+ int c0_24 = static_cast<int>(CLAMP(sv, 0, 16777216));
+ int s0_24 = static_cast<int>(dv);
+ while (len > 0) {
+ unsigned int ca, da;
+ /* Draw */
+ ca = c0_24 >> 16;
+ if ( ca > 255 ) ca=255;
+ da = 65025 - (255 - ca) * (255 - d[0]);
+ d[0] = (da + 127) / 255;
+ d += 1;
+ c0_24 += s0_24;
+ c0_24 = CLAMP (c0_24, 0, 16777216);
+ len -= 1;
+ }
+ }
+ }
+}
diff --git a/src/libnrtype/RasterFont.h b/src/libnrtype/RasterFont.h
new file mode 100644
index 000000000..03665c69a
--- /dev/null
+++ b/src/libnrtype/RasterFont.h
@@ -0,0 +1,66 @@
+/*
+ * RasterFont.h
+ * testICU
+ *
+ */
+
+#ifndef my_raster_font
+#define my_raster_font
+
+#include <ext/hash_map>
+
+#include <libnr/nr-forward.h>
+#include <libnrtype/nrtype-forward.h>
+#include <libnrtype/font-style.h>
+
+// one rasterfont is one way to draw a font on the screen
+// the way it's drawn is stored in style
+class raster_font {
+public:
+ font_instance* daddy;
+ int refCount;
+
+ font_style style;
+
+ __gnu_cxx::hash_map<int,int> glyph_id_to_raster_glyph_no;
+ // an array of glyphs in this rasterfont.
+ // it's a bit redundant with the one in the daddy font_instance, but these glyphs
+ // contains the real rasterization data
+ int nbBase,maxBase;
+ raster_glyph** bases;
+
+ explicit raster_font(font_style const &fstyle);
+ ~raster_font(void);
+
+ void Unref(void);
+ void Ref(void);
+
+ // utility functions
+ NR::Point Advance(int glyph_id);
+ void BBox(int glyph_id,NRRect *area);
+
+ // attempts to load a glyph and return a raster_glyph on which you can call Blit
+ raster_glyph* GetGlyph(int glyph_id);
+ // utility
+ void LoadRasterGlyph(int glyph_id); // refreshes outline/polygon if needed
+ void RemoveRasterGlyph(raster_glyph* who);
+
+private:
+ /* Disable the default copy constructor and operator=: they do the wrong thing for refCount. */
+ raster_font(raster_font const &);
+ raster_font &operator=(raster_font const &);
+};
+
+#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 :
diff --git a/src/libnrtype/TextWrapper.cpp b/src/libnrtype/TextWrapper.cpp
new file mode 100644
index 000000000..ce8797322
--- /dev/null
+++ b/src/libnrtype/TextWrapper.cpp
@@ -0,0 +1,937 @@
+/*
+ * TextWrapper.cpp
+ * testICU
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include "TextWrapper.h"
+
+#include <libnrtype/font-instance.h>
+#include "libnrtype/text-boundary.h"
+#include "libnrtype/one-glyph.h"
+#include "libnrtype/one-box.h"
+#include "libnrtype/one-para.h"
+
+#include <svg/svg.h>
+
+text_wrapper::text_wrapper(void)
+{
+ // voids everything
+ utf8_text = NULL;
+ uni32_text = NULL;
+ glyph_text = NULL;
+ utf8_length = 0;
+ uni32_length = 0;
+ glyph_length = 0;
+ utf8_codepoint = NULL;
+ uni32_codepoint = NULL;
+ default_font = NULL;
+ bounds = NULL;
+ nbBound = maxBound = 0;
+ boxes = NULL;
+ nbBox = maxBox = 0;
+ paras = NULL;
+ nbPara = maxPara = 0;
+ kern_x = kern_y = NULL;
+ last_addition = -1;
+ // inits the pangolayout with default params
+ font_factory *font_src = font_factory::Default();
+ pLayout = pango_layout_new(font_src->fontContext);
+ pango_layout_set_single_paragraph_mode(pLayout, true);
+ pango_layout_set_width(pLayout, -1);
+}
+text_wrapper::~text_wrapper(void)
+{
+ // frees everything
+ //printf("delete\n");
+ g_object_unref(pLayout);
+ if ( utf8_text ) free(utf8_text);
+ if ( uni32_text ) free(uni32_text);
+ if ( glyph_text ) free(glyph_text);
+ if ( utf8_codepoint ) free(utf8_codepoint);
+ if ( uni32_codepoint ) free(uni32_codepoint);
+ if ( default_font ) default_font->Unref();
+ if ( boxes ) free(boxes);
+ if ( paras ) free(paras);
+ if ( kern_x ) free(kern_x);
+ if ( kern_y ) free(kern_y);
+ for (unsigned i = 0; i < nbBound; i++) {
+ switch ( bounds[i].type ) {
+ default:
+ break;
+ }
+ }
+ if ( bounds ) free(bounds);
+ default_font = NULL;
+
+}
+
+void text_wrapper::SetDefaultFont(font_instance *iFont)
+{
+ // refcounts the font for our internal uses
+ if ( iFont ) iFont->Ref();
+ if ( default_font ) default_font->Unref();
+ default_font = iFont;
+}
+
+void text_wrapper::AppendUTF8(char const *text, int len)
+{
+ // appends text to what needs to be handled
+ if ( utf8_length <= 0 ) {
+ // a first check to prevent the text from containing a leading line return (which
+ // is probably a bug anyway)
+ if ( text[0] == '\n' || text[0] == '\r' ) {
+ /* fixme: Should the below be `0 <= len' ? The existing code looks wrong
+ * for the case that len==0.
+ * TODO: Document the meaning of the len parameter. */
+ if ( len > 0 ) {
+ while ( len > 0 && ( *text == '\n' || *text == '\r' ) ) {text++; len--;}
+ } else {
+ while ( *text == '\n' || *text == '\r' ) text++;
+ }
+ }
+ }
+ if ( len == 0 || text == NULL || *text == 0 ) return;
+ g_return_if_fail(g_utf8_validate(text, len, NULL));
+
+ // compute the length
+ int const nlen = ( len < 0
+ ? strlen(text)
+ : len );
+ /* effic: Use g_utf8_validate's last param to do this. */
+
+ // prepare to store the additional text
+ /* effic: (Not an issue for the sole caller at the time of writing.) This implementation
+ takes quadratic time if the text is composed of n appends. Use a proper data structure.
+ STL vector would suffice. */
+ utf8_text = (char*)realloc(utf8_text, (utf8_length + nlen + 1) * sizeof(char));
+ uni32_codepoint = (int*)realloc(uni32_codepoint, (utf8_length + nlen + 1) * sizeof(int));
+
+ // copy the source text in the newly lengthened array
+ memcpy(utf8_text + utf8_length, text, nlen * sizeof(char));
+ utf8_length += nlen;
+ utf8_text[utf8_length] = 0;
+ // remember where the text ended, before we recompute it, for the dx/dy we'll add after that (if any)
+ last_addition = uni32_length;
+ // free old uni32 structures (instead of incrementally putting the text)
+ if ( uni32_text ) free(uni32_text);
+ if ( utf8_codepoint ) free(utf8_codepoint);
+ uni32_text = NULL;
+ utf8_codepoint = NULL;
+ uni32_length = 0;
+ {
+ // recompute length of uni32 text
+ char *p = utf8_text;
+ while ( *p ) {
+ p = g_utf8_next_char(p); // since we validated the input text, we can use this 'fast' macro
+ uni32_length++;
+ }
+ }
+ // realloc the arrays
+ uni32_text = (gunichar*)malloc((uni32_length + 1) * sizeof(gunichar));
+ utf8_codepoint = (int*)malloc((uni32_length + 1) * sizeof(int));
+ {
+ // read the utf8 string and compute codepoints positions
+ char *p = utf8_text;
+ int i = 0;
+ int l_o = 0;
+ while ( *p ) {
+ // get the new codepoint
+ uni32_text[i] = g_utf8_get_char(p);
+ // compute the offset in the utf8_string
+ unsigned int n_o = (unsigned int)(p - utf8_text);
+ // record the codepoint's start
+ utf8_codepoint[i] = n_o;
+ // record the codepoint's correspondance in the utf8 string
+ for (unsigned int j = l_o; j < n_o; j++) uni32_codepoint[j] = i - 1;
+ // and move on
+ l_o = n_o;
+ p = g_utf8_next_char(p);
+ i++;
+ }
+ // the termination of the loop
+ for (int j = l_o; j < utf8_length; j++) uni32_codepoint[j] = uni32_length - 1;
+ uni32_codepoint[utf8_length] = uni32_length;
+ uni32_text[uni32_length] = 0;
+ utf8_codepoint[uni32_length] = utf8_length;
+ }
+ // if needed, fill the dx/dy arrays with 0 for the newly created part
+ // these will be filled by a KernXForLastAddition() right after this function
+ // note that the SVG spec doesn't require you to give a dx for each codepoint,
+ // so setting the dx to 0 is mandatory
+ if ( uni32_length > last_addition ) {
+ if ( kern_x ) {
+ kern_x = (double*)realloc(kern_x, (uni32_length + 1) * sizeof(double));
+ for (int i = last_addition; i <= uni32_length; i++) kern_x[i] = 0;
+ }
+ if ( kern_y ) {
+ kern_y = (double*)realloc(kern_y, (uni32_length + 1) * sizeof(double));
+ for (int i = last_addition; i <= uni32_length; i++) kern_y[i] = 0;
+ }
+ }
+}
+
+void text_wrapper::DoLayout(void)
+{
+ // THE function
+ // first some sanity checks
+ if ( default_font == NULL ) return;
+ if ( uni32_length <= 0 || utf8_length <= 0 ) return;
+ // prepare the pangolayout object
+ {
+ //char *tc = pango_font_description_to_string(default_font->descr);
+ //printf("layout with %s\n", tc);
+ //free(tc);
+ }
+ pango_layout_set_font_description(pLayout, default_font->descr);
+ pango_layout_set_text(pLayout, utf8_text, utf8_length);
+ // reset the glyph string
+ if ( glyph_text ) free(glyph_text);
+ glyph_text = NULL;
+ glyph_length = 0;
+
+ double pango_to_ink = (1.0 / ((double)PANGO_SCALE)); // utility
+ int max_g = 0;
+ PangoLayoutIter *pIter = pango_layout_get_iter(pLayout); // and go!
+ do {
+ PangoLayoutLine *pLine = pango_layout_iter_get_line(pIter); // no need for unref
+ int plOffset = pLine->start_index; // start of the line in the uni32_text
+ PangoRectangle ink_r, log_r;
+ pango_layout_iter_get_line_extents(pIter, &ink_r, &log_r);
+ double plY = (1.0 / ((double)PANGO_SCALE)) * ((double)log_r.y); // start position of this line of the layout
+ double plX = (1.0 / ((double)PANGO_SCALE)) * ((double)log_r.x);
+ GSList *curR = pLine->runs; // get ready to iterate over the runs of this line
+ while ( curR ) {
+ PangoLayoutRun *pRun = (PangoLayoutRun*)curR->data;
+ int prOffset = pRun->item->offset; // start of the run in the line
+ if ( pRun ) {
+ // a run has uniform font/directionality/etc...
+ int o_g_l = glyph_length; // save the index of the first glyph we'll add
+ for (int i = 0; i < pRun->glyphs->num_glyphs; i++) { // add glyph sequentially, reading them from the run
+ // realloc the structures
+ if ( glyph_length >= max_g ) {
+ max_g = 2 * glyph_length + 1;
+ glyph_text = (one_glyph*)realloc(glyph_text, (max_g + 1) * sizeof(one_glyph));
+ }
+ // fill the glyph info
+ glyph_text[glyph_length].font = pRun->item->analysis.font;
+ glyph_text[glyph_length].gl = pRun->glyphs->glyphs[i].glyph;
+ glyph_text[glyph_length].uni_st = plOffset + prOffset + pRun->glyphs->log_clusters[i];
+ // depending on the directionality, the last uni32 codepoint for this glyph is the first of the next char
+ // or the first of the previous
+ if ( pRun->item->analysis.level == 1 ) {
+ // rtl
+ if ( i < pRun->glyphs->num_glyphs - 1 ) {
+ glyph_text[glyph_length + 1].uni_en = glyph_text[glyph_length].uni_st;
+ }
+ glyph_text[glyph_length].uni_dir = 1;
+ glyph_text[glyph_length + 1].uni_dir = 1; // set the directionality for the next too, so that the last glyph in
+ // the array has the correct direction
+ } else {
+ // ltr
+ if ( i > 0 ) {
+ glyph_text[glyph_length - 1].uni_en = glyph_text[glyph_length].uni_st;
+ }
+ glyph_text[glyph_length].uni_dir = 0;
+ glyph_text[glyph_length + 1].uni_dir = 0;
+ }
+ // set the position
+ // the layout is an infinite line
+ glyph_text[glyph_length].x = plX + pango_to_ink * ((double)pRun->glyphs->glyphs[i].geometry.x_offset);
+ glyph_text[glyph_length].y = plY + pango_to_ink * ((double)pRun->glyphs->glyphs[i].geometry.y_offset);
+ // advance to the next glyph
+ plX += pango_to_ink * ((double)pRun->glyphs->glyphs[i].geometry.width);
+ // and set the next glyph's position, in case it's the terminating glyph
+ glyph_text[glyph_length + 1].x = plX;
+ glyph_text[glyph_length + 1].y = plY;
+ glyph_length++;
+ }
+ // and finish filling the info
+ // notably, the uni_en of the last char in ltr text and the uni_en of the first in rtl are still not set
+ if ( pRun->item->analysis.level == 1 ) {
+ // rtl
+ if ( glyph_length > o_g_l ) glyph_text[o_g_l].uni_en = plOffset + prOffset + pRun->item->length;
+ } else {
+ if ( glyph_length > 0 ) glyph_text[glyph_length - 1].uni_en = plOffset + prOffset + pRun->item->length;
+ }
+ // the terminating glyph has glyph_id=0 because it means 'no glyph'
+ glyph_text[glyph_length].gl = 0;
+ // and is associated with no text (but you cannot set uni_st=uni_en=0, because the termination
+ // is expected to be the glyph for the termination of the uni32_text)
+ glyph_text[glyph_length].uni_st = glyph_text[glyph_length].uni_en = plOffset + prOffset + pRun->item->length;
+ }
+ curR = curR->next;
+ }
+ } while ( pango_layout_iter_next_line(pIter) );
+ pango_layout_iter_free(pIter);
+
+ // grunt work done. now some additional info for layout: computing letters, mostly (one letter = several glyphs sometimes)
+ PangoLogAttr *pAttrs = NULL;
+ int nbAttr = 0;
+ // get the layout attrs, they hold the boundaries pango computed
+ pango_layout_get_log_attrs(pLayout, &pAttrs, &nbAttr);
+ // feed to MakeTextBoundaries which knows what to do with these
+ MakeTextBoundaries(pAttrs, nbAttr);
+ // the array of boundaries is full, but out-of-order
+ SortBoundaries();
+ // boundary array is ready to be used, call chunktext to fill the *_start fields of the glyphs, and compute
+ // the boxed version of the text for sp-typeset
+ ChunkText();
+ // get rid of the attributes
+ if ( pAttrs ) g_free(pAttrs);
+
+ // cleaning up
+ for (int i = 0; i < glyph_length; i++) {
+ glyph_text[i].uni_st = uni32_codepoint[glyph_text[i].uni_st];
+ glyph_text[i].uni_en = uni32_codepoint[glyph_text[i].uni_en];
+ glyph_text[i].x /= 512; // why is this not default_font->daddy->fontsize?
+ glyph_text[i].y /= 512;
+ }
+ if ( glyph_length > 0 ) {
+ glyph_text[glyph_length].x /= 512;
+ glyph_text[glyph_length].y /= 512;
+ }
+}
+
+void text_wrapper::ChunkText(void)
+{
+ int c_st = -1, c_en = -1;
+ for (int i = 0; i < glyph_length; i++) {
+ int g_st = glyph_text[i].uni_st, g_en = glyph_text[i].uni_en;
+ glyph_text[i].char_start = false;
+ glyph_text[i].word_start = false;
+ glyph_text[i].para_start = false;
+ // boundaries depend on the directionality
+ // letter boundaries correspond to the glyphs starting one letter when you read them left to right (always)
+ // because that's the order they are stored into in the glyph_text array
+ if ( glyph_text[i].uni_dir == 0 ) {
+ if ( IsBound(bnd_char, g_st, c_st) ) { // check if there is a charcater (=letter in pango speak) at this position
+ // can be a 'start' boundary or a 'end' boundary, doesn't matter, as long
+ // as you get from one letter to the next at this position
+ if ( g_st == bounds[c_st].uni_pos ) glyph_text[i].char_start = true;
+ }
+ if ( IsBound(bnd_word, g_st, c_st) ) {
+ if ( g_st == bounds[c_st].uni_pos ) glyph_text[i].word_start = true;
+ }
+ if ( IsBound(bnd_para, g_st, c_st) ) {
+ if ( g_st == bounds[c_st].uni_pos ) glyph_text[i].para_start = true;
+ }
+ } else {
+ if ( IsBound(bnd_char, g_en, c_en) ) {
+ if ( g_en == bounds[c_en].uni_pos ) glyph_text[i].char_start = true;
+ }
+ if ( IsBound(bnd_word, g_en, c_en) ) {
+ if ( g_en == bounds[c_en].uni_pos ) glyph_text[i].word_start = true;
+ }
+ if ( IsBound(bnd_para, g_en, c_en) ) {
+ if ( g_en == bounds[c_en].uni_pos ) glyph_text[i].para_start = true;
+ }
+ }
+ }
+
+ if ( glyph_length > 0 ) {
+ glyph_text[glyph_length].char_start = true;
+ glyph_text[glyph_length].word_start = true;
+ glyph_text[glyph_length].para_start = true;
+ }
+ {
+ // doing little boxes
+ int g_st = -1, g_en = -1;
+ while ( NextWord(g_st, g_en) ) {
+ // check uniformity of fonts
+ if ( g_st < g_en ) {
+ int n_st = g_st;
+ int n_en = g_st;
+ bool first = true;
+ do {
+ n_st = n_en;
+ PangoFont *curPF = glyph_text[n_st].font;
+ do {
+ n_en++;
+ } while ( n_en < g_en && glyph_text[n_en].font == curPF );
+ if ( nbBox >= maxBox ) {
+ maxBox = 2 * nbBox + 1;
+ boxes = (one_box*)realloc(boxes, maxBox * sizeof(one_box));
+ }
+ boxes[nbBox].g_st = n_st;
+ boxes[nbBox].g_en = n_en;
+ boxes[nbBox].word_start = first;
+ boxes[nbBox].word_end = (n_en >= g_en);
+ nbBox++;
+ first = false;
+ } while ( n_en < g_en );
+ }
+ }
+ }
+ {
+ // doing little paras
+ int g_st = -1, g_en = -1;
+ while ( NextPara(g_st, g_en) ) {
+ int b_st = 0;
+ while ( b_st < nbBox && boxes[b_st].g_st < g_st ) b_st++;
+ if ( b_st < nbBox && boxes[b_st].g_st == g_st ) {
+ int b_en = b_st;
+ while ( b_en < nbBox && boxes[b_en].g_en < g_en ) b_en++;
+ if ( b_en < nbBox && boxes[b_en].g_en == g_en ) {
+ if ( nbPara >= maxPara ) {
+ maxPara = 2 * nbPara + 1;
+ paras = (one_para*)realloc(paras, maxPara * sizeof(one_para));
+ }
+ paras[nbPara].b_st = b_st;
+ paras[nbPara].b_en = b_en;
+ nbPara++;
+ }
+ }
+ }
+ }
+}
+
+void text_wrapper::MakeVertical(void)
+{
+ if ( glyph_length <= 0 ) return;
+ font_factory *font_src = font_factory::Default();
+
+ // explanation: when laying out text vertically, you must keep the glyphs of a single letter together
+ double baseY = glyph_text[0].y;
+ double lastY = baseY;
+ int g_st = 0, g_en = 0;
+ int nbLetter = 0;
+ PangoFont *curPF = NULL;
+ font_instance *curF = NULL;
+ do {
+ // move to the next letter boundary
+ g_st = g_en;
+ do {
+ g_en++;
+ } while ( g_en < glyph_length && glyph_text[g_en].char_start == false );
+ // got a letter
+ if ( g_st < g_en && g_en <= glyph_length ) {
+ // we need to compute the letter's width (in case sometimes we implement the flushleft and flushright)
+ // and the total height for this letter. for example accents usually have 0 width, so this is not
+ // stupid
+ double n_adv = 0;
+ double minX = glyph_text[g_st].x, maxX = glyph_text[g_st].x;
+ for (int i = g_st; i < g_en; i++) {
+ if ( glyph_text[i].font != curPF ) { // font is not the same as the one of the previous glyph
+ // so we need to update curF
+ if ( curF ) curF->Unref();
+ curF = NULL;
+ curPF = glyph_text[i].font;
+ if ( curPF ) {
+ PangoFontDescription *pfd = pango_font_describe(curPF);
+ curF = font_src->Face(pfd);
+ pango_font_description_free(pfd);
+ }
+ }
+ double x = ( curF
+ ? curF->Advance(glyph_text[i].gl, true)
+ : 0 );
+ if ( x > n_adv ) n_adv = x;
+ if ( glyph_text[i].x < minX ) minX = glyph_text[i].x;
+ if ( glyph_text[i].x > maxX ) maxX = glyph_text[i].x;
+ }
+ lastY += n_adv;
+ // and put the glyphs of this letter at their new position
+ for (int i = g_st; i < g_en; i++) {
+ glyph_text[i].x -= minX;
+ glyph_text[i].y += lastY;
+ }
+ g_st = g_en;
+ }
+ nbLetter++;
+ } while ( g_st < glyph_length );
+ if ( curF ) curF->Unref();
+}
+
+void text_wrapper::MergeWhiteSpace(void)
+{
+ if ( glyph_length <= 0 ) return;
+ // scans the glyphs and shifts them accordingly
+ double delta_x = 0, delta_y = 0;
+ bool inWhite = true;
+ int wpos = 0, rpos = 0; // wpos is the position where we read glyphs, rpos is the position where we write them back
+ // since we only eat whitespace, wpos <= rpos
+ for (rpos = 0; rpos < glyph_length; rpos++) {
+ // copy the glyph at its new position
+ glyph_text[wpos].gl = glyph_text[rpos].gl;
+ glyph_text[wpos].uni_st = glyph_text[rpos].uni_st;
+ glyph_text[wpos].uni_en = glyph_text[rpos].uni_en;
+ glyph_text[wpos].font = glyph_text[rpos].font;
+ glyph_text[wpos].x = glyph_text[rpos].x - delta_x;
+ glyph_text[wpos].y = glyph_text[rpos].y - delta_y;
+ wpos++; // move the write position
+ if ( g_unichar_isspace(uni32_text[glyph_text[rpos].uni_st]) ) {
+ if ( inWhite ) {
+ // eat me: 2 steps: first add the shift in position to the cumulated shift
+ delta_x += glyph_text[rpos + 1].x - glyph_text[rpos].x;
+ delta_y += glyph_text[rpos + 1].y - glyph_text[rpos].y;
+ // then move the write position back. this way, we'll overwrite the previous whitespace with the new glyph
+ // since this is only done after the first whitespace, we only keep the first whitespace
+ wpos--;
+ }
+ inWhite = true;
+ } else {
+ inWhite = false;
+ }
+ }
+ // and the terminating glyph (we should probably copy the rest of the glyph's info, too)
+ glyph_text[wpos].x = glyph_text[rpos].x - delta_x;
+ glyph_text[wpos].y = glyph_text[rpos].y - delta_y;
+ // sets the new length
+ glyph_length = wpos;
+}
+
+// utility: computes the number of letters in the layout
+int text_wrapper::NbLetter(int g_st, int g_en)
+{
+ if ( glyph_length <= 0 ) return 0;
+ if ( g_st < 0 || g_st >= g_en ) {
+ g_st = 0;
+ g_en = glyph_length;
+ }
+ int nbLetter = 0;
+ for (int i = g_st; i < g_en; i++) {
+ if ( glyph_text[i].char_start ) nbLetter++;
+ }
+ return nbLetter;
+}
+
+void text_wrapper::AddLetterSpacing(double dx, double dy, int g_st, int g_en)
+{
+ if ( glyph_length <= 0 ) return;
+ if ( g_st < 0 || g_st >= g_en ) {
+ g_st = 0;
+ g_en = glyph_length;
+ }
+ int nbLetter = 0;
+
+ // letterspacing means: add 'dx * (nbLetter - 1)' to the x position
+ // so we just scan the glyph string
+ for (int i = g_st; i < g_en; i++) {
+ if ( i > g_st && glyph_text[i].char_start ) nbLetter++;
+ glyph_text[i].x += dx * nbLetter;
+ glyph_text[i].y += dy * nbLetter;
+ }
+ if ( glyph_text[g_en].char_start ) nbLetter++;
+ glyph_text[g_en].x += dx * nbLetter;
+ glyph_text[g_en].y += dy * nbLetter;
+}
+
+/** @name Movement commands
+ * Miscellaneous functions for moving about glyphs.
+ * \a st and \en are start and end glyph indices.
+ * The three methods differ only in whether they look for .char_start, .word_start or .para_start.
+ * \return True iff a next character was found. (False iff we've already reached the end.)
+ */
+//@{
+bool text_wrapper::NextChar(int &st, int &en) const
+{
+ if ( st < 0 || en < 0 ) {st = 0; en = 0;}
+ if ( st >= en ) en = st;
+ if ( st >= glyph_length || en >= glyph_length ) return false; // finished
+ st = en;
+ do {
+ en++;
+ } while ( en < glyph_length && glyph_text[en].char_start == false );
+ return true;
+}
+bool text_wrapper::NextWord(int &st, int &en) const
+{
+ if ( st < 0 || en < 0 ) {st = 0; en = 0;}
+ if ( st >= en ) en = st;
+ if ( st >= glyph_length || en >= glyph_length ) return false; // finished
+ st = en;
+ do {
+ en++;
+ } while ( en < glyph_length && glyph_text[en].word_start == false );
+ return true;
+}
+bool text_wrapper::NextPara(int &st, int &en) const
+{
+ if ( st < 0 || en < 0 ) {st = 0; en = 0;}
+ if ( st >= en ) en = st;
+ if ( st >= glyph_length || en >= glyph_length ) return false; // finished
+ st = en;
+ do {
+ en++;
+ } while ( en < glyph_length && glyph_text[en].para_start == false );
+ return true;
+}
+//@}
+
+// boundary handling
+/**
+ * Append \a ib to our bounds array.
+ * \return The index of the new element.
+ */
+unsigned text_wrapper::AddBoundary(text_boundary const &ib)
+{
+ if ( nbBound >= maxBound ) {
+ maxBound = 2 * nbBound + 1;
+ bounds = (text_boundary*)realloc(bounds, maxBound * sizeof(text_boundary));
+ }
+ unsigned const ix = nbBound++;
+ bounds[ix] = ib;
+ return ix;
+}
+
+/**
+ * Add the start \& end boundaries \a is \& \a ie to bounds.
+ */
+void text_wrapper::AddTwinBoundaries(text_boundary const &is, text_boundary const &ie)
+{
+ unsigned const ns = AddBoundary(is);
+ unsigned const ne = AddBoundary(ie);
+ bounds[ns].start = true;
+ bounds[ns].other = ne;
+ bounds[ne].start = false;
+ bounds[ne].other = ns;
+}
+
+static int CmpBound(void const *a, void const *b) {
+ text_boundary const &ta = *reinterpret_cast<text_boundary const *>(a);
+ text_boundary const &tb = *reinterpret_cast<text_boundary const *>(b);
+ if ( ta.uni_pos < tb.uni_pos ) return -1;
+ if ( ta.uni_pos > tb.uni_pos ) return 1;
+ /* TODO: I'd guess that for a given uni_pos it would be better for the end boundary to precede the start boundary. */
+ if ( ta.start && !tb.start ) return -1;
+ if ( !ta.start && tb.start ) return 1;
+ return 0;
+}
+/**
+ * Sort this.bounds by b.uni_pos, updating the .other index values appropriately.
+ */
+void text_wrapper::SortBoundaries(void)
+{
+ /* effic: If this function (including descendents such as the qsort calll) ever takes
+ * non-negligible time, then we can fairly easily improve it by changing MakeBoundaries add in
+ * sorted order. It would just have to remember for itself the index of each start boundary
+ * for updating the .other fields appropriately.
+ *
+ * A simpler speedup is just to change qsort to std::sort, which can inline the comparison
+ * function.
+ */
+
+ /* The 'other' field needs to be updated after sorting by qsort, so we build the inverse
+ * permutation. */
+ for (unsigned i = 0; i < nbBound; i++) {
+ bounds[i].old_ix = i;
+ }
+ qsort(bounds, nbBound, sizeof(text_boundary), CmpBound);
+ unsigned *const old2new = g_new(unsigned, nbBound);
+ for (unsigned new_ix = 0; new_ix < nbBound; new_ix++) { // compute inverse permutation
+ old2new[bounds[new_ix].old_ix] = new_ix;
+ }
+ for (unsigned i = 0; i < nbBound; i++) { // update 'other'
+ if ( bounds[i].other < nbBound ) {
+ bounds[i].other = old2new[bounds[i].other];
+ }
+ }
+ g_free(old2new);
+}
+void text_wrapper::MakeTextBoundaries(PangoLogAttr *pAttrs, int nAttr)
+{
+ if ( pAttrs == NULL || nAttr <= 0 || uni32_length <= 0 ) return;
+ if ( nAttr > uni32_length + 1 ) nAttr = uni32_length + 1;
+ int last_c_st = -1;
+ int last_w_st = -1;
+ int last_s_st = -1;
+ int last_p_st = 0;
+ // reads the text and adds a pair of boundaries each time we encounter a stop
+ // last_* are used to keep track of the start of new text chunk
+ for (int i = 0; i <= nAttr; i++) {
+ text_boundary nbs;
+ text_boundary nbe;
+ nbs.uni_pos = i;
+ nbs.start = true;
+ nbe.uni_pos = i;
+ nbe.start = false;
+ // letters
+ if ( i == nAttr || pAttrs[i].is_cursor_position ) {
+ if ( last_c_st >= 0 ) {
+ nbs.type = nbe.type = bnd_char;
+ nbs.uni_pos = last_c_st;
+ nbe.uni_pos = i;
+ AddTwinBoundaries(nbs, nbe);
+ }
+ last_c_st = i;
+ }
+ // words
+ if ( i == nAttr || pAttrs[i].is_word_start ) {
+ if ( last_w_st >= 0 ) {
+ nbs.type = nbe.type = bnd_word;
+ nbs.uni_pos = last_w_st;
+ nbe.uni_pos = i;
+ nbs.data.i = nbe.data.i = ( pAttrs[last_w_st].is_white ? 1 : 0 );
+ AddTwinBoundaries(nbs, nbe);
+ }
+ last_w_st = i;
+ }
+ if ( i < nAttr && pAttrs[i].is_word_end ) {
+ if ( last_w_st >= 0 ) {
+ nbs.type = nbe.type = bnd_word;
+ nbs.uni_pos = last_w_st;
+ nbe.uni_pos = i;
+ nbs.data.i = nbe.data.i = ( pAttrs[last_w_st].is_white ? 1 : 0 );
+ AddTwinBoundaries(nbs, nbe);
+ }
+ last_w_st = i;
+ }
+ // sentences
+ if ( i == nAttr || pAttrs[i].is_sentence_boundary ) {
+ if ( last_s_st >= 0 ) {
+ nbs.type = nbe.type = bnd_sent;
+ nbs.uni_pos = last_s_st;
+ nbe.uni_pos = i;
+ AddTwinBoundaries(nbs, nbe);
+ }
+ last_s_st = i;
+ }
+ // paragraphs
+ if ( uni32_text[i] == '\n' || uni32_text[i] == '\r' || i == nAttr ) { // too simple to be true?
+ nbs.type = nbe.type = bnd_para;
+ nbs.uni_pos = last_p_st;
+ nbe.uni_pos = i + 1;
+ AddTwinBoundaries(nbs, nbe);
+ last_p_st = i + 1;
+ }
+ }
+}
+
+bool text_wrapper::IsBound(BoundaryType const bnd_type, int g_st, int &c_st)
+{
+ if ( c_st < 0 ) c_st = 0;
+ int scan_dir = 0;
+ while ( unsigned(c_st) < nbBound ) {
+ if ( bounds[c_st].uni_pos == g_st && bounds[c_st].type == bnd_type ) {
+ return true;
+ }
+ if ( bounds[c_st].uni_pos < g_st ) {
+ if ( scan_dir < 0 ) break;
+ c_st++;
+ scan_dir = 1;
+ } else if ( bounds[c_st].uni_pos > g_st ) {
+ if ( scan_dir > 0 ) break;
+ c_st--;
+ scan_dir = -1;
+ } else {
+ // good pos, wrong type
+ while ( c_st > 0 && bounds[c_st].uni_pos == g_st ) {
+ c_st--;
+ }
+ if ( bounds[c_st].uni_pos < g_st ) c_st++;
+ while ( unsigned(c_st) < nbBound && bounds[c_st].uni_pos == g_st ) {
+ if ( bounds[c_st].type == bnd_type ) {
+ return true;
+ }
+ c_st++;
+ }
+ break;
+ }
+ }
+ return false;
+}
+
+/* Unused. Retained only because I haven't asked cyreve (Richard Hughes) whether he intends ever
+ * to use it. You can probably safely remove it. */
+//bool text_wrapper::Contains(BoundaryType const bnd_type, int g_st, int g_en, int &c_st, int &c_en)
+//{
+// if ( c_st < 0 ) c_st = 0;
+// bool found = false;
+// int scan_dir = 0;
+// while ( unsigned(c_st) < nbBound ) {
+// if ( bounds[c_st].type == bnd_type ) {
+// if ( bounds[c_st].start ) {
+// c_en = bounds[c_st].other;
+// } else {
+// }
+// }
+// if ( bounds[c_st].type == bnd_type && unsigned(c_en) == bounds[c_st].other ) {
+// if ( g_st >= bounds[c_st].uni_pos && g_en <= bounds[c_en].uni_pos ) {
+// // character found
+// found = true;
+// break;
+// }
+// }
+// if ( bounds[c_st].uni_pos < g_st ) {
+// if ( scan_dir < 0 ) break;
+// c_st++;
+// scan_dir = 1;
+// } else if ( bounds[c_st].uni_pos > g_st ) {
+// if ( scan_dir > 0 ) break;
+// c_st--;
+// scan_dir = -1;
+// } else {
+// // good pos, wrong type
+// while ( c_st > 0 && bounds[c_st].uni_pos == g_st ) {
+// c_st--;
+// }
+// if ( bounds[c_st].uni_pos < g_st ) c_st++;
+// while ( unsigned(c_st) < nbBound && bounds[c_st].uni_pos == g_st ) {
+// if ( bounds[c_st].type == bnd_type ) {
+// if ( bounds[c_st].start ) {
+// c_en = bounds[c_st].other;
+// } else {
+// }
+// }
+// if ( bounds[c_st].type == bnd_type && unsigned(c_en) == bounds[c_st].other ) {
+// if ( g_st >= bounds[c_st].uni_pos && g_en <= bounds[c_en].uni_pos ) {
+// // character found
+// return true;
+// }
+// }
+// c_st++;
+// }
+//
+// break;
+// }
+// }
+// return found;
+//}
+
+void text_wrapper::MeasureBoxes(void)
+{
+ font_factory *f_src = font_factory::Default();
+ for (int i = 0; i < nbBox; i++) {
+ boxes[i].ascent = 0;
+ boxes[i].descent = 0;
+ boxes[i].leading = 0;
+ boxes[i].width = 0;
+
+ PangoFont *curPF = glyph_text[boxes[i].g_st].font;
+ if ( curPF ) {
+ PangoFontDescription *pfd = pango_font_describe(curPF);
+ font_instance *curF = f_src->Face(pfd);
+ if ( curF ) {
+ curF->FontMetrics(boxes[i].ascent, boxes[i].descent, boxes[i].leading);
+ curF->Unref();
+ }
+ pango_font_description_free(pfd);
+ boxes[i].width = glyph_text[boxes[i].g_en].x - glyph_text[boxes[i].g_st].x;
+ }
+ }
+}
+
+
+void text_wrapper::KernXForLastAddition(double *i_kern_x, int i_len, double scale)
+{
+ if ( i_kern_x == NULL || i_len <= 0 || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return;
+ if ( kern_x == NULL ) {
+ kern_x = (double*)malloc((uni32_length + 1) * sizeof(double));
+ for (int i = 0; i <= uni32_length; i++) kern_x[i] = 0;
+ }
+ int last_len = uni32_length - last_addition;
+ if ( i_len > last_len ) i_len = last_len;
+ for (int i = 0; i < i_len; i++) kern_x[last_addition + i] = i_kern_x[i] * scale;
+}
+
+void text_wrapper::KernYForLastAddition(double *i_kern_y, int i_len, double scale)
+{
+ if ( i_kern_y == NULL || i_len <= 0 || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return;
+ if ( kern_y == NULL ) {
+ kern_y = (double*)malloc((uni32_length + 1) * sizeof(double));
+ for (int i = 0; i <= uni32_length; i++) kern_y[i] = 0;
+ }
+ int last_len = uni32_length - last_addition;
+ if ( i_len > last_len ) i_len = last_len;
+ for (int i = 0; i < i_len; i++) kern_y[last_addition + i] = i_kern_y[i] * scale;
+}
+
+void text_wrapper::KernXForLastAddition(GList *i_kern_x, double scale)
+{
+ if ( i_kern_x == NULL || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return;
+ if ( kern_x == NULL ) {
+ kern_x = (double*)malloc((uni32_length + 1) * sizeof(double));
+ for (int i = 0; i <= uni32_length; i++) kern_x[i] = 0;
+ }
+ int last_len = uni32_length - last_addition;
+ GList *l = i_kern_x;
+ for (int i = 0; i < last_len && l && l->data; i++, l = l->next) {
+ kern_x[last_addition + i] = ((SVGLength *) l->data)->computed * scale;
+ }
+}
+
+void text_wrapper::KernYForLastAddition(GList *i_kern_y, double scale)
+{
+ if ( i_kern_y == NULL || last_addition < 0 || last_addition >= uni32_length || uni32_length <= 0 ) return;
+ if ( kern_y == NULL ) {
+ kern_y = (double*)malloc((uni32_length + 1) * sizeof(double));
+ for (int i = 0; i <= uni32_length; i++) kern_y[i] = 0;
+ }
+ int last_len = uni32_length - last_addition;
+ GList *l = i_kern_y;
+ for (int i = 0; i < last_len && l && l->data; i++, l = l->next) {
+ kern_y[last_addition + i] = ((SVGLength *) l->data)->computed * scale;
+ }
+}
+
+
+void text_wrapper::AddDxDy(void)
+{
+ if ( glyph_length <= 0 ) return;
+ if ( kern_x ) {
+ double sum = 0;
+ int l_pos = -1;
+ for (int i = 0; i < glyph_length; i++) {
+ int n_pos = glyph_text[i].uni_st;
+ if ( l_pos < n_pos ) {
+ for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_x[j];
+ } else if ( l_pos > n_pos ) {
+ for (int j = l_pos; j > n_pos; j--) sum -= kern_x[j];
+ }
+ l_pos = n_pos;
+
+ glyph_text[i].x += sum;
+ }
+ {
+ int n_pos = uni32_length;
+ if ( l_pos < n_pos ) {
+ for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_x[j];
+ } else if ( l_pos > n_pos ) {
+ for (int j = l_pos; j > n_pos; j--) sum -= kern_x[j];
+ }
+ l_pos = n_pos;
+ glyph_text[glyph_length].x += sum;
+ }
+ }
+ if ( kern_y ) {
+ double sum = 0;
+ int l_pos = -1;
+ for (int i = 0; i < glyph_length; i++) {
+ int n_pos = glyph_text[i].uni_st;
+ if ( l_pos < n_pos ) {
+ for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_y[j];
+ } else if ( l_pos > n_pos ) {
+ for (int j = l_pos; j > n_pos; j--) sum -= kern_y[j];
+ }
+ l_pos = n_pos;
+
+ glyph_text[i].y += sum;
+ }
+ {
+ int n_pos = uni32_length;
+ if ( l_pos < n_pos ) {
+ for (int j = l_pos + 1; j <= n_pos; j++) sum += kern_y[j];
+ } else if ( l_pos > n_pos ) {
+ for (int j = l_pos; j > n_pos; j--) sum -= kern_y[j];
+ }
+ l_pos = n_pos;
+ glyph_text[glyph_length].y += sum;
+ }
+ }
+}
+
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/TextWrapper.h b/src/libnrtype/TextWrapper.h
new file mode 100644
index 000000000..188f04ee1
--- /dev/null
+++ b/src/libnrtype/TextWrapper.h
@@ -0,0 +1,139 @@
+/*
+ * TextWrapper.h
+ * testICU
+ *
+ */
+
+#ifndef my_text_wrapper
+#define my_text_wrapper
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <pango/pango.h>
+
+#include <libnrtype/nrtype-forward.h>
+#include "libnrtype/boundary-type.h"
+
+// miscanellous but useful data for a given text: chunking into logical pieces
+// pieces include sentence/word, needed for example for the word-spacing property,
+// and more important stuff like letter (ie visual letters)
+
+struct text_boundary;
+struct one_glyph;
+struct one_box;
+struct one_para;
+
+class text_wrapper {
+public:
+ char *utf8_text; // source text
+ gunichar *uni32_text; // ucs4 text computed from utf8_text
+ one_glyph *glyph_text; // glyph string computed for uni32_text
+
+ // maps between the 2
+ // These should most definitely be size_t, not int.
+ // I am quite sure (but not bored enough to actually test it
+ // on a 500MHz machine with 256MB RAM ) that this will crash
+ // for text longer than 2GB on architectures where
+ // sizeof(size_t) != sizeof(int)
+ int utf8_length; // utf8_text length
+ int uni32_length; // uni32_text length
+ int glyph_length; /**< Number of glyph in the glyph_text array.
+ * The size of the array is (glyph_length+1) in fact; the last glyph is kind of a '0' char. */
+ int *uni32_codepoint; // uni32_codepoint[i] is the index in uni32_text corresponding to utf8_text[i]
+ int *utf8_codepoint; // utf8_codepoint[i] is the index in utf8_text of the beginning of uni32_text[i]
+
+ // layout
+ font_instance *default_font; // font set as the default font (would need at least one alternate per language)
+ PangoLayout *pLayout; // private structure
+
+ // kerning additions
+ int last_addition; // index in uni32_text of the beginning of the text added by the last AppendUTF8 call
+ double *kern_x; // dx[i] is the dx for the ith unicode char
+ double *kern_y;
+
+ // boundaries, in an array
+ unsigned nbBound, maxBound;
+ text_boundary *bounds;
+
+ // text organization
+ int nbBox, maxBox;
+ one_box *boxes;
+ int nbPara, maxPara;
+ one_para *paras;
+
+ text_wrapper(void);
+ ~text_wrapper(void);
+
+ // filling the structure with input data
+ void SetDefaultFont(font_instance *iFont);
+
+ /**
+ * Append the specified text to utf8_text and uni32_codepoint.
+ *
+ * Note: Despite the name, the current implementation is primarily suited for a single
+ * call to set the text, rather than repeated calls to AppendUTF8: the implementation is
+ * Omega(n) in the new total length of the string, rather than just in the length of the
+ * text being appended. This can probably be addressed fairly easily (see comments in
+ * code) if this is an issue for new callers.
+ *
+ * \pre text is valid UTF-8, or null.
+ * Formally: text==NULL || g_utf8_validate(text, len, NULL).
+ *
+ * \param len Our sole existing caller (widgets/font_selector.cpp) uses len=-1. N.B. The current
+ * implementation may be buggy for non-negative len, especially for len==0.
+ */
+ void AppendUTF8(char const *text, int len);
+
+ // adds dx or dy for the text added by the last AppendUTF8() call
+ void KernXForLastAddition(double *i_kern_x, int i_len, double scale = 1.0);
+ void KernYForLastAddition(double *i_kern_y, int i_len, double scale = 1.0);
+ void KernXForLastAddition(GList *i_kern_x, double scale = 1.0);
+ void KernYForLastAddition(GList *i_kern_y, double scale = 1.0);
+ // compute the layout and stuff
+ void DoLayout(void);
+ // semi-private: computes boundaries in the input text
+ void ChunkText(void);
+ // utility function to move to the next element
+ bool NextChar(int &st, int &en) const;
+ bool NextWord(int &st, int &en) const;
+ bool NextPara(int &st, int &en) const;
+
+ // post-processing after the initial layout
+ // for the xml-space property: merges consecutive whitespace, and eats leading whitespace in the text
+ void MergeWhiteSpace(void);
+ // makes vertical 'x' and 'y' fields in the glyph_text based on the computed positions
+ void MakeVertical(void);
+ // as the names says...
+ void AddLetterSpacing(double dx, double dy, int g_st = -1, int g_en = -1);
+ // adds the kerning specified by the KernXForLastAddition call to the layout
+ void AddDxDy(void);
+
+ // boundary handling
+private:
+ unsigned AddBoundary(text_boundary const &ib);
+public:
+ void AddTwinBoundaries(text_boundary const &is, text_boundary const &ie);
+ void SortBoundaries(void);
+ void MakeTextBoundaries(PangoLogAttr *pAttrs, int nAttr);
+ //bool Contains(BoundaryType type, int g_st, int g_en, int &c_st, int &c_en);
+ bool IsBound(BoundaryType type, int g_st, int &c_st);
+
+ void MeasureBoxes(void);
+ int NbLetter(int g_st, int g_en);
+};
+
+#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:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/boundary-type.h b/src/libnrtype/boundary-type.h
new file mode 100644
index 000000000..7f8ecea90
--- /dev/null
+++ b/src/libnrtype/boundary-type.h
@@ -0,0 +1,31 @@
+#ifndef LIBNRTYPE_BOUNDARY_TYPE_H_INKSCAPE
+#define LIBNRTYPE_BOUNDARY_TYPE_H_INKSCAPE
+
+/** \file Definition of the BoundaryType enum. */
+
+/**
+ * The different kinds of semantic boundaries in text; or rather,
+ * the different things that may be delimited by a text_boundary.
+ */
+enum BoundaryType {
+ bnd_none = 0,
+ bnd_char,
+ bnd_word,
+ bnd_sent, /**< Sentence. Not currently used, and pango (1.8) does a bad job of determining
+ * sentence boundaries anyway. */
+ bnd_para
+};
+
+
+#endif /* !LIBNRTYPE_BOUNDARY_TYPE_H_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:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/font-glyph.h b/src/libnrtype/font-glyph.h
new file mode 100644
index 000000000..c79888c12
--- /dev/null
+++ b/src/libnrtype/font-glyph.h
@@ -0,0 +1,29 @@
+#ifndef SEEN_LIBNRTYPE_FONT_GLYPH_H
+#define SEEN_LIBNRTYPE_FONT_GLYPH_H
+
+#include <libnrtype/nrtype-forward.h>
+#include <livarot/livarot-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
+ Path* outline; // outline as a livarot Path
+ void* artbpath; // outline as a artbpath, 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 000000000..5d57ff549
--- /dev/null
+++ b/src/libnrtype/font-instance.h
@@ -0,0 +1,121 @@
+#ifndef SEEN_LIBNRTYPE_FONT_INSTANCE_H
+#define SEEN_LIBNRTYPE_FONT_INSTANCE_H
+
+#include <ext/hash_map>
+#include <pango/pango-types.h>
+#include <pango/pango-font.h>
+#include <require-config.h>
+#include "FontFactory.h"
+
+#include <libnr/nr-forward.h>
+#include <libnrtype/nrtype-forward.h>
+#include <libnrtype/font-style.h>
+#include <livarot/livarot-forward.h>
+
+// 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
+// the various raster_font in use at a given time are held in a hash_map whose indices are the
+// styles, hence the 2 following 'classes'
+struct font_style_hash : public std::unary_function<font_style, size_t> {
+ size_t operator()(font_style const &x) const;
+};
+
+struct font_style_equal : public std::binary_function<font_style, font_style, bool> {
+ bool operator()(font_style const &a, font_style const &b);
+};
+
+class font_instance {
+public:
+ // hashmap to get the raster_font for a given style
+ __gnu_cxx::hash_map<font_style, raster_font*, font_style_hash, font_style_equal> loadedStyles;
+ // the real source of the font
+ PangoFont* pFont;
+ // 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 hash_map
+ PangoFontDescription* descr;
+ // refcount
+ int refCount;
+ // font_factory owning this font_instance
+ font_factory* daddy;
+
+ // common glyph definitions for all the rasterfonts
+ __gnu_cxx::hash_map<int, int> id_to_no;
+ int nbGlyph, maxGlyph;
+ font_glyph* glyphs;
+
+ font_instance(void);
+ ~font_instance(void);
+
+ void Ref(void);
+ void Unref(void);
+
+ bool IsOutlineFont(void); // 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();
+
+ 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
+ Path* Outline(int glyph_id, Path *copyInto=NULL);
+ // queries the outline of the glyph (in livarot Path form), and copies it into copyInto instead
+ // of allocating a new Path if copyInto != NULL
+ void* ArtBPath(int glyph_id);
+ // returns the artbpath for this glyph. no refcounting needed, it's deallocated when the
+ // font_instance dies
+ double Advance(int glyph_id, bool vertical);
+ // nominal advance of the font.
+ bool FontMetrics(double &ascent, double &descent, double &leading);
+ bool FontSlope(double &run, double &rise);
+ // for generating slanted cursors for oblique fonts
+ NR::Rect BBox(int glyph_id);
+
+ // creates a rasterfont for the given style
+ raster_font* RasterFont(NR::Matrix const &trs, double stroke_width,
+ bool vertical = false, JoinType stroke_join = join_straight,
+ ButtType stroke_cap = butt_straight, float miter_limit = 4.0);
+ // the dashes array in iStyle is copied
+ raster_font* RasterFont(font_style const &iStyle);
+ // private use: tells the font_instance that the raster_font 'who' has died
+ void RemoveRasterFont(raster_font *who);
+
+ // attribute queries
+ unsigned Name(gchar *str, unsigned size);
+ unsigned PSName(gchar *str, unsigned size);
+ unsigned Family(gchar *str, unsigned size);
+ unsigned Attribute(gchar const *key, gchar *str, unsigned size);
+
+private:
+ void FreeTheFace();
+
+#ifdef USE_PANGO_WIN32
+ HFONT theFace;
+#else
+ FT_Face theFace;
+ // 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
+
+};
+
+
+#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-style-to-pos.cpp b/src/libnrtype/font-style-to-pos.cpp
new file mode 100644
index 000000000..02f5ef37c
--- /dev/null
+++ b/src/libnrtype/font-style-to-pos.cpp
@@ -0,0 +1,120 @@
+#include "font-style-to-pos.h"
+#include <style.h>
+
+/* 'lighter' and 'darker' have to be resolved earlier */
+/**
+Given a style struct (CSS representation), sets the corresponding fields in a NRTypePosDef.
+ */
+NRTypePosDef
+font_style_to_pos (SPStyle const &style)
+{
+ NRTypePosDef ret;
+
+ switch (style.font_weight.computed) {
+ case SP_CSS_FONT_WEIGHT_100:
+ ret.weight = NR_POS_WEIGHT_CSS100;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_200:
+ ret.weight = NR_POS_WEIGHT_CSS200;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_300:
+ ret.weight = NR_POS_WEIGHT_CSS300;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_400:
+ case SP_CSS_FONT_WEIGHT_NORMAL:
+ ret.weight = NR_POS_WEIGHT_CSS400;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_500:
+ ret.weight = NR_POS_WEIGHT_CSS500;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_600:
+ ret.weight = NR_POS_WEIGHT_CSS600;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_700:
+ case SP_CSS_FONT_WEIGHT_BOLD:
+ ret.weight = NR_POS_WEIGHT_CSS700;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_800:
+ ret.weight = NR_POS_WEIGHT_CSS800;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_900:
+ ret.weight = NR_POS_WEIGHT_CSS900;
+ break;
+
+ case SP_CSS_FONT_WEIGHT_LIGHTER:
+ case SP_CSS_FONT_WEIGHT_BOLDER:
+ default:
+ g_warning("Unrecognized font_weight.computed value");
+ ret.weight = NR_POS_WEIGHT_NORMAL;
+ break;
+ }
+
+ switch (style.font_style.computed) {
+ case SP_CSS_FONT_STYLE_ITALIC:
+ ret.italic = 1;
+ break;
+
+ case SP_CSS_FONT_STYLE_OBLIQUE:
+ ret.oblique = 1;
+ break;
+
+ case SP_CSS_FONT_STYLE_NORMAL:
+ default:
+ ret.italic = 0;
+ ret.oblique = 0;
+ break;
+ }
+
+ switch (style.font_stretch.computed) {
+ case SP_CSS_FONT_STRETCH_ULTRA_CONDENSED:
+ case SP_CSS_FONT_STRETCH_EXTRA_CONDENSED:
+ ret.stretch = NR_POS_STRETCH_EXTRA_CONDENSED;
+ break;
+
+ case SP_CSS_FONT_STRETCH_CONDENSED:
+ case SP_CSS_FONT_STRETCH_NARROWER:
+ ret.stretch = NR_POS_STRETCH_CONDENSED;
+ break;
+
+ case SP_CSS_FONT_STRETCH_SEMI_CONDENSED:
+ ret.stretch = NR_POS_STRETCH_SEMI_CONDENSED;
+ break;
+
+ case SP_CSS_FONT_STRETCH_SEMI_EXPANDED:
+ ret.stretch = NR_POS_STRETCH_SEMI_EXPANDED;
+ break;
+
+ case SP_CSS_FONT_STRETCH_EXPANDED:
+ case SP_CSS_FONT_STRETCH_WIDER:
+ ret.stretch = NR_POS_STRETCH_EXPANDED;
+ break;
+
+ case SP_CSS_FONT_STRETCH_EXTRA_EXPANDED:
+ case SP_CSS_FONT_STRETCH_ULTRA_EXPANDED:
+ ret.stretch = NR_POS_STRETCH_EXTRA_EXPANDED;
+ break;
+
+ default:
+ ret.stretch = NR_POS_STRETCH_NORMAL;
+ break;
+ }
+
+ switch (style.font_variant.computed) {
+ case SP_CSS_FONT_VARIANT_SMALL_CAPS:
+ ret.variant = NR_POS_VARIANT_SMALLCAPS;
+ break;
+ default:
+ ret.variant = NR_POS_VARIANT_NORMAL;
+ break;
+ }
+
+ return ret;
+}
diff --git a/src/libnrtype/font-style-to-pos.h b/src/libnrtype/font-style-to-pos.h
new file mode 100644
index 000000000..f58fdda3f
--- /dev/null
+++ b/src/libnrtype/font-style-to-pos.h
@@ -0,0 +1,20 @@
+#ifndef __FONT_STYLE_TO_POS_H__
+#define __FONT_STYLE_TO_POS_H__
+
+#include <forward.h> /* SPStyle */
+#include <libnrtype/nr-type-pos-def.h>
+
+NRTypePosDef font_style_to_pos (SPStyle const &style);
+
+#endif /* __FONT_STYLE_TO_POS_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:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/font-style.h b/src/libnrtype/font-style.h
new file mode 100644
index 000000000..748d2020a
--- /dev/null
+++ b/src/libnrtype/font-style.h
@@ -0,0 +1,38 @@
+#ifndef SEEN_LIBNRTYPE_FONT_STYLE_H
+#define SEEN_LIBNRTYPE_FONT_STYLE_H
+
+#include <libnr/nr-matrix.h>
+#include <livarot/LivarotDefs.h>
+#include <livarot/livarot-forward.h>
+
+// structure that holds data describing how to render glyphs of a font
+
+// Different raster styles.
+struct font_style {
+ NR::Matrix 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;
+
+ void Apply(Path *src, Shape *dst); // utility: applies the style to the path and stores the result in the shape
+};
+
+
+#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 :
diff --git a/src/libnrtype/libnrtype.def b/src/libnrtype/libnrtype.def
new file mode 100644
index 000000000..34051de2d
--- /dev/null
+++ b/src/libnrtype/libnrtype.def
@@ -0,0 +1,59 @@
+EXPORTS
+ nr_font_generic_free
+ nr_font_generic_glyph_advance_get
+ nr_font_generic_glyph_area_get
+ nr_font_generic_glyph_outline_get
+ nr_font_generic_glyph_outline_unref
+ nr_font_generic_new
+ nr_font_generic_rasterfont_free
+ nr_font_generic_rasterfont_new
+ nr_font_glyph_advance_get
+ nr_font_glyph_area_get
+ nr_font_glyph_outline_get
+ nr_font_glyph_outline_unref
+ nr_font_new_default
+ nr_font_ref
+ nr_font_unref
+ nr_name_list_release
+ nr_rasterfont_generic_free
+ nr_rasterfont_generic_glyph_advance_get
+ nr_rasterfont_generic_glyph_area_get
+ nr_rasterfont_generic_glyph_mask_render
+ nr_rasterfont_generic_new
+ nr_rasterfont_glyph_advance_get
+ nr_rasterfont_glyph_area_get
+ nr_rasterfont_glyph_mask_render
+ nr_rasterfont_new
+ nr_rasterfont_ref
+ nr_rasterfont_unref
+ nr_type_dict_insert
+ nr_type_dict_lookup
+ nr_type_dict_new
+ nr_type_directory_family_list_get
+ nr_type_directory_forget_face
+ nr_type_directory_lookup
+ nr_type_directory_lookup_fuzzy
+ nr_type_directory_style_list_get
+ nr_type_empty_build_def
+ nr_type_ft2_build_def
+; nr_type_gnome_build_def
+; nr_type_gnome_families_get
+; nr_type_gnome_typefaces_get
+ nr_type_w32_build_def
+ nr_type_w32_families_get
+ nr_type_w32_typefaces_get
+; nr_type_xft_build_def
+; nr_type_xft_families_get
+; nr_type_xft_typefaces_get
+ nr_typeface_attribute_get
+ nr_typeface_family_name_get
+ nr_typeface_ft2_get_type
+ nr_typeface_get_type
+ nr_typeface_glyph_advance_get
+ nr_typeface_glyph_outline_get
+ nr_typeface_glyph_outline_unref
+; nr_typeface_gnome_get_type
+ nr_typeface_lookup_default
+ nr_typeface_name_get
+ nr_typeface_new
+ nr_typeface_w32_get_type
diff --git a/src/libnrtype/makefile.in b/src/libnrtype/makefile.in
new file mode 100644
index 000000000..fbebd763d
--- /dev/null
+++ b/src/libnrtype/makefile.in
@@ -0,0 +1,17 @@
+# Convenience stub makefile to call the real Makefile.
+
+@SET_MAKE@
+
+# Explicit so that it's the default rule.
+all:
+ cd .. && $(MAKE) libnrtype/all
+
+clean %.a %.o:
+ cd .. && $(MAKE) libnrtype/$@
+
+.PHONY: all clean
+
+OBJEXT = @OBJEXT@
+
+.SUFFIXES:
+.SUFFIXES: .a .$(OBJEXT)
diff --git a/src/libnrtype/nr-type-pos-def.cpp b/src/libnrtype/nr-type-pos-def.cpp
new file mode 100644
index 000000000..fef04039e
--- /dev/null
+++ b/src/libnrtype/nr-type-pos-def.cpp
@@ -0,0 +1,268 @@
+#include "nr-type-pos-def.h"
+#include <glib.h>
+#include <string.h>
+
+/**
+ * Given a font name or style name, returns a constant describing its
+ * apparent style (normal/italic/oblique).
+*/
+int
+parse_name_for_style (char const *cc)
+{
+ g_assert ( cc != NULL );
+ gchar *c = g_ascii_strdown (cc, -1);
+
+ gint style;
+ // first dab at i18n... french and german
+ if (strstr (c, "italic") || strstr (c, "italique") || strstr (c, "kursiv")) {
+ style = NR_POS_STYLE_ITALIC;
+ } else if (strstr (c, "oblique")) {
+ style = NR_POS_STYLE_OBLIQUE;
+ } else {
+ style = NR_POS_STYLE_NORMAL;
+ }
+
+ g_free (c);
+ return style;
+}
+
+
+/**
+ * Given a font name or style name, returns a constant describing its
+ * apparent weight.
+*/
+int
+parse_name_for_weight (char const *cc)
+{
+ g_assert ( cc != NULL );
+ gchar *c = g_ascii_strdown (cc, -1);
+
+ gint weight;
+ if (strstr (c, "thin")) {
+ weight = NR_POS_WEIGHT_THIN;
+ } else if (strstr (c, "extra light")) {
+ weight = NR_POS_WEIGHT_EXTRA_LIGHT;
+ } else if (strstr (c, "ultra light")) {
+ weight = NR_POS_WEIGHT_ULTRA_LIGHT;
+ } else if (strstr (c, "light")) {
+ weight = NR_POS_WEIGHT_LIGHT;
+ } else if (strstr (c, "book")) {
+ weight = NR_POS_WEIGHT_BOOK;
+ } else if (strstr (c, "medium")) {
+ weight = NR_POS_WEIGHT_MEDIUM;
+ } else if (strstr (c, "semi bold")) {
+ weight = NR_POS_WEIGHT_SEMIBOLD;
+ } else if (strstr (c, "semibold")) {
+ weight = NR_POS_WEIGHT_SEMIBOLD;
+ } else if (strstr (c, "demi bold")) {
+ weight = NR_POS_WEIGHT_DEMIBOLD;
+ } else if (strstr (c, "demibold") || strstr (c, "demi")) {
+ weight = NR_POS_WEIGHT_DEMIBOLD;
+ } else if (strstr (c, "ultra bold")) {
+ weight = NR_POS_WEIGHT_ULTRA_BOLD;
+ } else if (strstr (c, "extra bold") || strstr (c, "xbold") || strstr (c, "xtrabold")) {
+ weight = NR_POS_WEIGHT_EXTRA_BOLD;
+ } else if (strstr (c, "black") || strstr (c, "heavy")) {
+ weight = NR_POS_WEIGHT_BLACK;
+ } else if (strstr (c, "bold")) {
+ /* Must come after the checks for `blah bold'. */
+ weight = NR_POS_WEIGHT_BOLD;
+ } else {
+ weight = NR_POS_WEIGHT_NORMAL;
+ }
+
+ g_free (c);
+ return weight;
+}
+
+/**
+ * Given a font name or style name, returns a constant describing its
+ * apparent stretch.
+*/
+int
+parse_name_for_stretch (char const *cc)
+{
+ g_assert ( cc != NULL );
+ gchar *c = g_ascii_strdown (cc, -1);
+
+ gint stretch;
+ if (strstr (c, "ultra narrow") || strstr (c, "ultra condensed") || strstr (c, "extra condensed")) {
+ stretch = NR_POS_STRETCH_EXTRA_CONDENSED;
+ } else if (strstr (c, "ultra wide") || strstr (c, "ultra expanded") || strstr (c, "ultra extended") || strstr (c, "extra expanded")) {
+ stretch = NR_POS_STRETCH_EXTRA_EXPANDED;
+ } else if (strstr (c, "semi condensed") || strstr (c, "semicondensed")) {
+ stretch = NR_POS_STRETCH_SEMI_CONDENSED;
+ } else if (strstr (c, "semi extended") || strstr (c, "semiextended")) {
+ stretch = NR_POS_STRETCH_SEMI_EXPANDED;
+ } else if (strstr (c, "narrow") || strstr (c, "condensed")) {
+ stretch = NR_POS_STRETCH_CONDENSED;
+ } else if (strstr (c, "wide") || strstr (c, "expanded") || strstr (c, "extended")) {
+ stretch = NR_POS_STRETCH_EXPANDED;
+ } else {
+ stretch = NR_POS_STRETCH_NORMAL;
+ }
+
+ g_free (c);
+ return stretch;
+}
+
+/**
+ * Given a font name or style name, returns a constant describing its
+ * apparent variant (normal/smallcaps).
+*/
+int
+parse_name_for_variant (char const *cc)
+{
+ g_assert ( cc != NULL );
+ gchar *c = g_ascii_strdown (cc, -1);
+
+ gint variant;
+ if (strstr (c, "small caps") || strstr (c, "smallcaps") || strstr (c, "caps")) {
+ variant = NR_POS_VARIANT_SMALLCAPS;
+ } else {
+ variant = NR_POS_VARIANT_NORMAL;
+ }
+
+ g_free (c);
+ return variant;
+}
+
+/**
+ * Given a style constant, returns the CSS value for font-style.
+*/
+const char *
+style_to_css (int style)
+{
+ switch (style) {
+ case NR_POS_STYLE_NORMAL:
+ return "normal";
+ break;
+ case NR_POS_STYLE_ITALIC:
+ return "italic";
+ break;
+ case NR_POS_STYLE_OBLIQUE:
+ return "oblique";
+ break;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+
+/**
+ * Given a weight constant, returns the CSS value for font-weight.
+*/
+const char *
+weight_to_css (int weight)
+{
+ switch (weight) {
+ case NR_POS_WEIGHT_THIN:
+ return "100";
+ break;
+ case NR_POS_WEIGHT_EXTRA_LIGHT:
+ return "200";
+ break;
+ case NR_POS_WEIGHT_LIGHT:
+ return "300";
+ break;
+ case NR_POS_WEIGHT_BOOK:
+ return "normal";
+ break;
+ case NR_POS_WEIGHT_MEDIUM:
+ return "500";
+ break;
+ case NR_POS_WEIGHT_SEMIBOLD:
+ return "600";
+ break;
+ case NR_POS_WEIGHT_BOLD:
+ return "bold";
+ break;
+ case NR_POS_WEIGHT_EXTRA_BOLD:
+ return "800";
+ break;
+ case NR_POS_WEIGHT_BLACK:
+ return "900";
+ break;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+/**
+ * Given a stretch constant, returns the CSS value for font-stretch.
+*/
+const char *
+stretch_to_css (int stretch)
+{
+ switch (stretch) {
+ case NR_POS_STRETCH_EXTRA_CONDENSED:
+ return "extra-condensed";
+ break;
+ case NR_POS_STRETCH_CONDENSED:
+ return "condensed";
+ break;
+ case NR_POS_STRETCH_SEMI_CONDENSED:
+ return "semi-condensed";
+ break;
+ case NR_POS_STRETCH_NORMAL:
+ return "normal";
+ break;
+ case NR_POS_STRETCH_SEMI_EXPANDED:
+ return "semi-expanded";
+ break;
+ case NR_POS_STRETCH_EXPANDED:
+ return "expanded";
+ break;
+ case NR_POS_STRETCH_EXTRA_EXPANDED:
+ return "extra-expanded";
+ break;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+/**
+ * Given a variant constant, returns the CSS value for font-variant.
+*/
+const char *
+variant_to_css (int stretch)
+{
+ switch (stretch) {
+ case NR_POS_VARIANT_SMALLCAPS:
+ return "small-caps";
+ break;
+ case NR_POS_VARIANT_NORMAL:
+ return "normal";
+ break;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+
+/**
+ * Constructor for NRTypePostDef. Sets the italic, oblique, weight,
+ * stretch, and variant.
+ */
+NRTypePosDef::NRTypePosDef(char const *description) {
+ // we cannot use strcasestr, it's linux only... so we must lowercase the string first
+ g_assert ( description != NULL );
+ gchar *c = g_ascii_strdown (description, -1);
+
+ /* copied from nr-type-directory.cpp:nr_type_calculate_position. */
+
+ italic = (strstr (c, "italic") != NULL);
+ oblique = (strstr (c, "oblique") != NULL);
+
+ weight = parse_name_for_weight (c);
+
+ stretch = parse_name_for_stretch (c);
+
+ variant = parse_name_for_variant (c);
+
+ g_free (c);
+}
diff --git a/src/libnrtype/nr-type-pos-def.h b/src/libnrtype/nr-type-pos-def.h
new file mode 100644
index 000000000..ab9f5b45c
--- /dev/null
+++ b/src/libnrtype/nr-type-pos-def.h
@@ -0,0 +1,102 @@
+#ifndef __NR_TYPE_POS_DEF_H__
+#define __NR_TYPE_POS_DEF_H__
+
+#define NR_POS_STYLE_NORMAL 0
+#define NR_POS_STYLE_ITALIC 1
+#define NR_POS_STYLE_OBLIQUE 2
+
+#define NR_POS_WEIGHT_THIN 32
+#define NR_POS_WEIGHT_EXTRA_LIGHT 64
+#define NR_POS_WEIGHT_ULTRA_LIGHT 64
+#define NR_POS_WEIGHT_LIGHT 96
+#define NR_POS_WEIGHT_BOOK 128
+#define NR_POS_WEIGHT_NORMAL 128
+#define NR_POS_WEIGHT_MEDIUM 144
+#define NR_POS_WEIGHT_SEMIBOLD 160
+#define NR_POS_WEIGHT_DEMIBOLD 160
+#define NR_POS_WEIGHT_BOLD 192
+#define NR_POS_WEIGHT_ULTRA_BOLD 224
+#define NR_POS_WEIGHT_EXTRA_BOLD 224
+#define NR_POS_WEIGHT_BLACK 255
+
+#define NR_POS_STRETCH_ULTRA_CONDENSED 48
+#define NR_POS_STRETCH_EXTRA_CONDENSED 48
+#define NR_POS_STRETCH_CONDENSED 88
+#define NR_POS_STRETCH_SEMI_CONDENSED 108
+#define NR_POS_STRETCH_NORMAL 128
+#define NR_POS_STRETCH_SEMI_EXPANDED 148
+#define NR_POS_STRETCH_EXPANDED 168
+#define NR_POS_STRETCH_EXTRA_EXPANDED 228
+#define NR_POS_STRETCH_ULTRA_EXPANDED 228
+
+// This is an enumerate, rather than on/off property,
+// for I sincerely hope the vocabulary of this property will be
+// extended by the W3C in the future to allow for more fancy fonts
+#define NR_POS_VARIANT_NORMAL 0
+#define NR_POS_VARIANT_SMALLCAPS 1
+
+/* Mapping from CSS weight numbers.
+
+ for i in `seq 9`; do
+ if [ $i -le 4 ]; then w=$((32 * $i));
+ elif [ $i = 5 ]; then w=144;
+ elif [ $i -lt 9 ]; then w=$((32 * $(($i - 1))));
+ else w=255;
+ fi;
+ printf '#define NR_POS_WEIGHT_CSS%d00\t\t%3d\n' $i $w;
+ done
+
+ This calculation approximately matches the old to-and-from-text code,
+ I don't claim it to be reasonable. ("approximately": some of the old
+ code wrote strings like "semi" and "heavy" that weren't being parsed
+ at the other end, and it had CSS100 darker than CSS200.)
+ */
+#define NR_POS_WEIGHT_CSS100 32
+#define NR_POS_WEIGHT_CSS200 64
+#define NR_POS_WEIGHT_CSS300 96
+#define NR_POS_WEIGHT_CSS400 128
+#define NR_POS_WEIGHT_CSS500 144
+#define NR_POS_WEIGHT_CSS600 160
+#define NR_POS_WEIGHT_CSS700 192
+#define NR_POS_WEIGHT_CSS800 224
+#define NR_POS_WEIGHT_CSS900 255
+
+
+class NRTypePosDef {
+public:
+ unsigned int italic : 1;
+ unsigned int oblique : 1;
+ unsigned int weight : 8;
+ unsigned int stretch : 8;
+ unsigned int variant : 8;
+ /* These can probably be made sensible sizes rather than bitfields; for the moment we'll
+ keep the old definition. */
+
+public:
+ NRTypePosDef() :
+ italic(0),
+ oblique(0),
+ weight(NR_POS_WEIGHT_NORMAL),
+ stretch(NR_POS_STRETCH_NORMAL),
+ variant(NR_POS_VARIANT_NORMAL)
+ { }
+
+ NRTypePosDef(char const *description);
+
+ bool signature() {return this->italic +
+ this->oblique * 255 +
+ this->weight * 255 * 255 +
+ this->stretch * 255 * 255 * 255 +
+ this->variant * 255 * 255 * 255 * 255;};
+};
+
+int parse_name_for_style (char const *c);
+int parse_name_for_weight (char const *c);
+int parse_name_for_stretch (char const *c);
+int parse_name_for_variant (char const *c);
+const char *style_to_css (int style);
+const char *weight_to_css (int weight);
+const char *stretch_to_css (int stretch);
+const char *variant_to_css (int variant);
+
+#endif /* __NR_TYPE_POS_DEF_H__ */
diff --git a/src/libnrtype/nr-type-primitives.cpp b/src/libnrtype/nr-type-primitives.cpp
new file mode 100644
index 000000000..616134383
--- /dev/null
+++ b/src/libnrtype/nr-type-primitives.cpp
@@ -0,0 +1,167 @@
+#define __NR_TYPE_PRIMITIVES_C__
+
+/*
+ * Typeface and script library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * This code is in public domain
+ */
+
+/* This should be enough for approximately 10000 fonts */
+#define NR_DICTSIZE 2777
+
+#include <stdlib.h>
+#include <string.h>
+#include <libnr/nr-macros.h>
+#include "nr-type-primitives.h"
+
+/**
+ * An entry in a list of key->value pairs
+ */
+struct NRTDEntry {
+ NRTDEntry *next;
+ const gchar *key;
+ void *val;
+};
+
+/**
+ * Type Dictionary, consisting of size number of key-value entries
+ */
+struct NRTypeDict {
+ unsigned int size;
+ NRTDEntry **entries;
+};
+
+static NRTDEntry *nr_td_entry_new (void);
+
+/**
+ * Calls the destructor for each item in list
+ */
+void
+nr_name_list_release (NRNameList *list)
+{
+ if (list->destructor) {
+ list->destructor (list);
+ }
+}
+
+void
+nr_style_list_release (NRStyleList *list)
+{
+ if (list->destructor) {
+ list->destructor (list);
+ }
+}
+
+/**
+ * Creates a new typeface dictionary of size NR_DICTSIZE
+ * and initalizes all the entries to NULL
+ */
+NRTypeDict *
+nr_type_dict_new (void)
+{
+ NRTypeDict *td;
+ int i;
+
+ td = nr_new (NRTypeDict, 1);
+
+ td->size = NR_DICTSIZE;
+ td->entries = nr_new (NRTDEntry *, td->size);
+ for (i = 0; i < NR_DICTSIZE; i++) {
+ td->entries[i] = NULL;
+ }
+
+ return td;
+}
+
+/**
+ * Hashes a string and returns the int
+ */
+static unsigned int
+nr_str_hash (const gchar *p)
+{
+ unsigned int h;
+
+ h = *p;
+
+ if (h != 0) {
+ for (p += 1; *p; p++) h = (h << 5) - h + *p;
+ }
+
+ return h;
+}
+
+/**
+ * Inserts a key/value into a typeface dictionary
+ */
+void
+nr_type_dict_insert (NRTypeDict *td, const gchar *key, void *val)
+{
+ if (key) {
+ NRTDEntry *tde;
+ unsigned int hval;
+
+ hval = nr_str_hash (key) % td->size;
+
+ for (tde = td->entries[hval]; tde; tde = tde->next) {
+ if (!strcmp (key, tde->key)) {
+ tde->val = val;
+ return;
+ }
+ }
+
+ tde = nr_td_entry_new ();
+ tde->next = td->entries[hval];
+ tde->key = key;
+ tde->val = val;
+ td->entries[hval] = tde;
+ }
+}
+
+/**
+ * Looks up the given key from the typeface dictionary
+ */
+void *
+nr_type_dict_lookup (NRTypeDict *td, const gchar *key)
+{
+ if (key) {
+ NRTDEntry *tde;
+ unsigned int hval;
+ hval = nr_str_hash (key) % td->size;
+ for (tde = td->entries[hval]; tde; tde = tde->next) {
+ if (!strcmp (key, tde->key)) return tde->val;
+ }
+ }
+
+ return NULL;
+}
+
+#define NR_TDE_BLOCK_SIZE 32
+
+static NRTDEntry *nr_tde_free_list;
+
+/**
+ * Creates a new TDEntry
+ */
+static NRTDEntry *
+nr_td_entry_new (void)
+{
+ NRTDEntry *tde;
+
+ if (!nr_tde_free_list) {
+ int i;
+ nr_tde_free_list = nr_new (NRTDEntry, NR_TDE_BLOCK_SIZE);
+ for (i = 0; i < (NR_TDE_BLOCK_SIZE - 1); i++) {
+ nr_tde_free_list[i].next = nr_tde_free_list + i + 1;
+ }
+ nr_tde_free_list[i].next = NULL;
+ }
+
+ tde = nr_tde_free_list;
+ nr_tde_free_list = tde->next;
+
+ return tde;
+}
+
diff --git a/src/libnrtype/nr-type-primitives.h b/src/libnrtype/nr-type-primitives.h
new file mode 100644
index 000000000..9bb181c4b
--- /dev/null
+++ b/src/libnrtype/nr-type-primitives.h
@@ -0,0 +1,50 @@
+#ifndef __NR_TYPE_PRIMITIVES_H__
+#define __NR_TYPE_PRIMITIVES_H__
+
+/*
+ * Typeface and script library
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * g++ port: Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * This code is in public domain
+ */
+
+#include <glib.h>
+
+struct NRNameList;
+struct NRStyleList;
+struct NRTypeDict;
+
+typedef void (* NRNameListDestructor) (NRNameList *list);
+typedef void (* NRStyleListDestructor) (NRStyleList *list);
+
+struct NRNameList {
+ guint length;
+ guchar **names;
+ guchar **families;
+ NRNameListDestructor destructor;
+};
+
+struct NRStyleRecord {
+ const char *name;
+ const char *descr;
+};
+
+struct NRStyleList {
+ guint length;
+ NRStyleRecord *records;
+ NRStyleListDestructor destructor;
+};
+
+void nr_name_list_release (NRNameList *list);
+void nr_style_list_release (NRStyleList *list);
+
+NRTypeDict *nr_type_dict_new (void);
+
+void nr_type_dict_insert (NRTypeDict *td, const gchar *key, void *val);
+
+void *nr_type_dict_lookup (NRTypeDict *td, const gchar *key);
+
+#endif
diff --git a/src/libnrtype/nrtype-forward.h b/src/libnrtype/nrtype-forward.h
new file mode 100644
index 000000000..f3344f2fd
--- /dev/null
+++ b/src/libnrtype/nrtype-forward.h
@@ -0,0 +1,23 @@
+#ifndef SEEN_LIBNRTYPE_NRTYPE_FORWARD_H
+#define SEEN_LIBNRTYPE_NRTYPE_FORWARD_H
+
+class font_factory;
+struct font_glyph;
+class font_instance;
+struct font_style;
+class raster_font;
+class raster_glyph;
+class raster_position;
+
+#endif /* !SEEN_LIBNRTYPE_NRTYPE_FORWARD_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/one-box.h b/src/libnrtype/one-box.h
new file mode 100644
index 000000000..1040e2be9
--- /dev/null
+++ b/src/libnrtype/one-box.h
@@ -0,0 +1,28 @@
+/** \file Definition of struct one_box. */
+
+#ifndef LIBNRTYPE_ONE_BOX_H_INKSCAPE
+#define LIBNRTYPE_ONE_BOX_H_INKSCAPE
+
+// text chunking 2, the comeback
+// this time for sp-typeset
+struct one_box {
+ int g_st, g_en; ///< First and last glyph of this word.
+ double ascent, descent, leading;
+ double width;
+ bool word_start, word_end;
+};
+
+
+#endif /* !LIBNRTYPE_ONE_BOX_H_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:encoding=utf-8:textwidth=99 :
+
diff --git a/src/libnrtype/one-glyph.h b/src/libnrtype/one-glyph.h
new file mode 100644
index 000000000..667c7743b
--- /dev/null
+++ b/src/libnrtype/one-glyph.h
@@ -0,0 +1,46 @@
+/** \file Definition of struct one_glyph. */
+
+/*
+ * License: May be redistributed with or without modifications under the terms of the Gnu General
+ * Public License as published by the Free Software Foundation, version 2 or (at your option) any
+ * later version.
+ */
+
+#ifndef LIBNRTYPE_ONE_GLYPH_H_INKSCAPE
+#define LIBNRTYPE_ONE_GLYPH_H_INKSCAPE
+
+#include <pango/pango-types.h>
+
+/**
+ * Information for a single glyph.
+ *
+ * Pango converts the text into glyphs, but scatters the info for a given glyph; here is a
+ * structure holding what inkscape needs to know.
+ */
+struct one_glyph {
+ int gl; ///< glyph_id
+ double x, y; ///< glyph position in the layout (nominal sizes, in the [0..1] range).
+ bool char_start; /**< Whether this glyph is the beginning of a letter. (RTL is taken in
+ * account.) */
+ bool word_start; ///< Whether this glyph is the beginning of a word.
+ bool para_start; ///< Whether this glyph is the beginning of a paragraph (for indentation).
+ char uni_dir; ///< BiDi orientation of the run containing this glyph.
+ int uni_st, uni_en; /**< Start and end positions of the text corresponding to this glyph.
+ * You always have uni_st < uni_en. */
+ PangoFont *font; /**< Font this glyph uses. (For bidi text, you need several fonts.)
+ * When rendering glyphs, check if this font is the one you're using. */
+};
+
+
+#endif /* !LIBNRTYPE_ONE_GLYPH_H_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:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/one-para.h b/src/libnrtype/one-para.h
new file mode 100644
index 000000000..60e59531f
--- /dev/null
+++ b/src/libnrtype/one-para.h
@@ -0,0 +1,20 @@
+#ifndef LIBNRTYPE_ONE_PARA_H_INKSCAPE
+#define LIBNRTYPE_ONE_PARA_H_INKSCAPE
+
+struct one_para {
+ int b_st, b_en;
+};
+
+
+#endif /* !LIBNRTYPE_ONE_PARA_H_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:encoding=utf-8:textwidth=99 :
diff --git a/src/libnrtype/raster-glyph.h b/src/libnrtype/raster-glyph.h
new file mode 100644
index 000000000..e39cc4e41
--- /dev/null
+++ b/src/libnrtype/raster-glyph.h
@@ -0,0 +1,49 @@
+#ifndef SEEN_LIBNRTYPE_RASTER_GLYPH_H
+#define SEEN_LIBNRTYPE_RASTER_GLYPH_H
+
+#include <libnr/nr-forward.h>
+#include <libnrtype/nrtype-forward.h>
+#include <livarot/livarot-forward.h>
+
+// a little utility class that holds data to render a styled glyph
+// ie. it's like a polygon. its function is to wrap the subpixel positionning
+class raster_glyph {
+public:
+ // raster_font that created me
+ raster_font* daddy;
+ // the glyph i am (the style is in daddy)
+ int glyph_id;
+ // internal structure: the styled path, and the associated uncrossed polygon
+ // they could be removed after the raster_position have been computed
+ Path* outline; // transformed by the matrix in style (may be factorized, but is small)
+ Shape* polygon;
+ // subpixel positions
+ // nb_sub_pixel is set to 4 when the glyph is created (it's hardcoded)
+ int nb_sub_pixel;
+ raster_position* sub_pixel;
+
+ raster_glyph(void);
+ ~raster_glyph(void);
+
+ // utility
+ void SetSubPixelPositionning(int nb_pos);
+ void LoadSubPixelPosition(int no);
+
+ // the interesting function: blits the glyph onto over
+ // over should be a mask, ie a NRPixBlock with one 8bit plane
+ void Blit(NR::Point const &at, NRPixBlock &over); // alpha only
+};
+
+
+#endif /* !SEEN_LIBNRTYPE_RASTER_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/raster-position.h b/src/libnrtype/raster-position.h
new file mode 100644
index 000000000..ef12b93ec
--- /dev/null
+++ b/src/libnrtype/raster-position.h
@@ -0,0 +1,46 @@
+#ifndef SEEN_LIBNRTYPE_RASTER_POSITION_H
+#define SEEN_LIBNRTYPE_RASTER_POSITION_H
+
+#include <vector>
+
+#include <libnr/nr-forward.h>
+#include <libnrtype/nrtype-forward.h>
+#include <livarot/livarot-forward.h>
+
+// one subpixel position
+// it's basically a set of trapezoids (=float_ligne_run) representing the black areas of the glyph
+// all trapezoids are in the same array, hence the run_on_line array to give the number of
+// trapezoids on each line
+// trapezoids store the x-positions as float, and are shifted to the x blit position
+// so it's "exact" in the x direction and subpixel in the y direction
+class raster_position {
+public:
+ int top, bottom; // baseline is y=0
+ // top is the first pixel, bottom is the last
+ int* run_on_line; // array of size (bottom-top+1): run_on_line[i] gives the number of runs on line top+i
+ int nbRun;
+ float_ligne_run* runs;
+
+public:
+ raster_position();
+ ~raster_position();
+
+ // stuff runs into the structure
+ void AppendRuns(std::vector<float_ligne_run> const &r, int y);
+ // blits the trapezoids.
+ void Blit(float ph, int pv, NRPixBlock &over);
+};
+
+
+#endif /* !SEEN_LIBNRTYPE_RASTER_POSITION_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/text-boundary.h b/src/libnrtype/text-boundary.h
new file mode 100644
index 000000000..83a825b55
--- /dev/null
+++ b/src/libnrtype/text-boundary.h
@@ -0,0 +1,49 @@
+#ifndef TEXT_BOUNDARY_H_INKSCAPE
+#define TEXT_BOUNDARY_H_INKSCAPE
+
+/** \file Definition of text_boundary. */
+
+/*
+ * License: May be redistributed with or without modifications under the terms of the Gnu General
+ * Public License as published by the Free Software Foundation, version 2 or (at your option) any
+ * later version.
+ */
+
+#include "libnrtype/boundary-type.h"
+
+
+/**
+ * A character/word/paragraph boundary in the text, used by TextWrapper.
+ *
+ * (Boundaries are paired.)
+ */
+struct text_boundary {
+ /** Index of the boundary in the text: first char of the text chunk if 'start' boundary, char
+ * right after the boundary otherwise.
+ */
+ int uni_pos;
+ BoundaryType type; ///< Kind of boundary.
+ bool start; ///< Indicates whether this marks the beginning or end of a chunk.
+ unsigned other; ///< Index in bounds[] of the corresponding end/beginning boundary.
+ unsigned old_ix; ///< Temporary storage used solely SortBoundaries.
+ /// Data for this boundary; usually, one int is enough.
+ union {
+ int i;
+ double f;
+ void *p;
+ } data;
+};
+
+
+#endif /* !TEXT_BOUNDARY_H_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:encoding=utf-8:textwidth=99 :