diff options
| author | MenTaLguY <mental@rydia.net> | 2006-01-16 02:36:01 +0000 |
|---|---|---|
| committer | mental <mental@users.sourceforge.net> | 2006-01-16 02:36:01 +0000 |
| commit | 179fa413b047bede6e32109e2ce82437c5fb8d34 (patch) | |
| tree | a5a6ac2c1708bd02288fbd8edb2ff500ff2e0916 /src/libnrtype | |
| download | inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.tar.gz inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.zip | |
moving trunk for module inkscape
(bzr r1)
Diffstat (limited to 'src/libnrtype')
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 ¶, UnbrokenSpanPosition *start_span_pos, + std::vector<ChunkInfo> *chunk_info, LineHeight *line_height); + + static inline PangoLogAttr const &_charAttributes(ParagraphInfo const ¶, + UnbrokenSpanPosition const &span_pos) + { + return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index]; + } + + bool _buildChunksInScanRun(ParagraphInfo const ¶, + 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 ¶, 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 ¶, 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 ¶, 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, + ¶->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 ¶, + 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 ¶, + 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(¶); + unsigned para_end_input_index = _buildSpansForPara(¶); + + 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 : |
