/* * 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 ( i == nAttr || uni32_text[i] == '\n' || uni32_text[i] == '\r' ) { // 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 :