diff options
Diffstat (limited to 'src/libnrtype')
| -rw-r--r-- | src/libnrtype/FontInstance.cpp | 35 | ||||
| -rw-r--r-- | src/libnrtype/Layout-TNG-Compute.cpp | 234 | ||||
| -rw-r--r-- | src/libnrtype/Layout-TNG-Output.cpp | 324 | ||||
| -rw-r--r-- | src/libnrtype/Layout-TNG.h | 4 | ||||
| -rw-r--r-- | src/libnrtype/font-instance.h | 2 |
5 files changed, 440 insertions, 159 deletions
diff --git a/src/libnrtype/FontInstance.cpp b/src/libnrtype/FontInstance.cpp index 61225ad0c..fd0cdd3d4 100644 --- a/src/libnrtype/FontInstance.cpp +++ b/src/libnrtype/FontInstance.cpp @@ -675,6 +675,41 @@ bool font_instance::FontMetrics(double &ascent,double &descent,double &leading) return true; } +bool font_instance::FontDecoration( + double &underline_position, double &underline_thickness, + double &linethrough_position, double &linethrough_thickness +){ + 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; + underline_position = fabs(otm.otmUnderscorePosition *scale); + underline_thickness = fabs(otm.otmUnderscoreSize *scale); + linethrough_position = fabs(otm.otmStrikeoutPosition *scale); + linethrough_thickness = fabs(otm.otmStrikeoutSize *scale); +#else + if ( theFace->units_per_EM == 0 ) { + return false; // bitmap font + } + underline_position = fabs(((double)theFace->underline_position )/((double)theFace->units_per_EM)); + underline_thickness = fabs(((double)theFace->underline_thickness)/((double)theFace->units_per_EM)); + // there is no specific linethrough information, mock it up from other font fields + linethrough_position = fabs(((double)theFace->ascender / 3.0 )/((double)theFace->units_per_EM)); + linethrough_thickness = fabs(((double)theFace->underline_thickness)/((double)theFace->units_per_EM)); +#endif + return true; +} + + bool font_instance::FontSlope(double &run, double &rise) { run = 0.0; diff --git a/src/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp index 7e684e7e3..1b2704a7e 100644 --- a/src/libnrtype/Layout-TNG-Compute.cpp +++ b/src/libnrtype/Layout-TNG-Compute.cpp @@ -120,8 +120,8 @@ class Layout::Calculator 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 + 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; /// This is not the CSS line-height attribute! @@ -213,6 +213,43 @@ class Layout::Calculator /* *********************************************************************************************************/ // Per-line functions +/** + * For debugging, not called in distributed code + * + * Input: para->first_input_index, para->pango_items + */ +static void dumpPangoItemsOut(ParagraphInfo *para){ + std::cout << "Pango items: " << para->pango_items.size() << std::endl; + for(unsigned pidx = 0 ; pidx < para->pango_items.size(); pidx++){ + std::cout + << "idx: " << pidx + << " offset: " + << para->pango_items[pidx].item->offset + << " length: " + << para->pango_items[pidx].item->length + << std::endl; + } +} + +/** + * For debugging, not called in distributed code + * + * Input: para->first_input_index, para->pango_items + */ +static void dumpUnbrokenSpans(ParagraphInfo *para){ + std::cout << "Unbroken Spans: " << para->unbroken_spans.size() << std::endl; + for(unsigned uidx = 0 ; uidx < para->unbroken_spans.size(); uidx++){ + std::cout + << "idx: " << uidx + << " pango_item_index: " << para->unbroken_spans[uidx].pango_item_index + << " input_index: " << para->unbroken_spans[uidx].input_index + << " char_index_in_para: " << para->unbroken_spans[uidx].char_index_in_para + << " text_bytes: " << para->unbroken_spans[uidx].text_bytes + << std::endl; + } +} + + bool _goToNextWrapShape(); @@ -246,8 +283,10 @@ class Layout::Calculator { 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->dx._set && span->start.char_byte == 0){ + if(para.direction == RIGHT_TO_LEFT){ span->width -= span->start.iter_span->dx.computed; } + else { span->width += span->start.iter_span->dx.computed; } + } if (span->start.iter_span->pango_item_index == -1) { // if this is a style-only span there's no text in it @@ -510,15 +549,19 @@ class Layout::Calculator x = 0.0; } else { direction_sign = -1.0; - if (para.alignment == FULL && !_flow._input_wrap_shapes.empty()) + if (para.alignment == FULL && !_flow._input_wrap_shapes.empty()){ x = it_chunk->scanrun_width; - else + } + 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; + double x_in_span_last = 0.0; // set at the END when a new cluster starts + double x_in_span = 0.0; // set from the preceding at the START when a new cluster starts. if (it_span->start.char_byte == 0) { // Start of an unbroken span, we might have dx, dy or rotate still to process @@ -535,7 +578,6 @@ class Layout::Calculator } 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; @@ -581,14 +623,19 @@ class Layout::Calculator 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); + double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier); + int log_cluster_size_glyphs = 0; // Number of glyphs in this log_cluster + int log_cluster_size_chars = 0; // Number of characters in this log_cluster + unsigned end_byte = 0; 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(); - + unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base(); + int newcluster = 0; + if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start){ + newcluster = 1; + x_in_span = x_in_span_last; + } + if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes && *iter_source_text == UNICODE_SOFT_HYPHEN && glyph_index + 1 != it_span->end_glyph_index) { @@ -611,7 +658,7 @@ class Layout::Calculator // 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.in_character = _flow._characters.size(); new_glyph.rotation = glyph_rotate; /* put something like this back in when we do glyph-rotation-horizontal/vertical @@ -652,28 +699,42 @@ class Layout::Calculator } new_glyph.x -= cluster_width; } - _flow._glyphs.push_back(new_glyph); + _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 + if (newcluster){ + // find where the text ends for this log_cluster + end_byte = it_span->start.iter_span->text_bytes; // Upper limit + for(unsigned next_glyph_index = glyph_index+1; next_glyph_index < it_span->end_glyph_index; next_glyph_index++){ + if(unbroken_span.glyph_string->glyphs[next_glyph_index].attr.is_cluster_start){ + end_byte = unbroken_span.glyph_string->log_clusters[next_glyph_index]; + break; + } + } + // Figure out how many glyphs and characters are in the log_cluster. + log_cluster_size_glyphs = 0; + log_cluster_size_chars = 0; + for(; log_cluster_size_glyphs + glyph_index < it_span->end_glyph_index; log_cluster_size_glyphs++){ + if(unbroken_span.glyph_string->log_clusters[glyph_index ] != + unbroken_span.glyph_string->log_clusters[glyph_index + log_cluster_size_glyphs])break; + } + Glib::ustring::const_iterator lclist = iter_source_text; + unsigned lcb = char_byte; + while(lcb < end_byte){ + log_cluster_size_chars++; + lclist++; + lcb = lclist.base() - unbroken_span.input_stream_first_character.base(); + } } while (char_byte < end_byte) { + /* Hack to survive ligatures: in log_cluster keep the number of available chars >= number of glyphs remaining. + When there are no ligatures these two sizes are always the same. + */ + if(log_cluster_size_chars < log_cluster_size_glyphs){ + log_cluster_size_glyphs--; + break; + } Layout::Character new_character; new_character.in_span = _flow._spans.size(); new_character.x = x_in_span; @@ -687,23 +748,27 @@ class Layout::Calculator iter_source_text++; char_index_in_unbroken_span++; char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base(); + log_cluster_size_chars--; } - 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; + if (newcluster){ + advance_width *= direction_sign; + if (new_span.direction != para.direction) { + counter_directional_width_remaining -= advance_width; + x -= advance_width; + x_in_span_last -= advance_width; + } else { + x += advance_width; + x_in_span_last += advance_width; + } } + newcluster = 0; } } 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; + new_span.x_end = new_span.x_start + x_in_span_last; _flow._spans.push_back(new_span); previous_direction = new_span.direction; } @@ -879,8 +944,9 @@ void Layout::Calculator::ParagraphInfo::free() * * Input: para.first_input_index. * Output: para.direction, para.pango_items, para.char_attributes. + * Returns: the number of spans created by pango_itemize */ -void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const +void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const { Glib::ustring para_text; PangoAttrList *attributes_list; @@ -974,8 +1040,9 @@ void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font line_height->setZero(); *line_height_multiplier = 1.0; } - else + else { 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 @@ -1008,6 +1075,11 @@ void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total(); } +bool compareGlyphWidth(const PangoGlyphInfo &a, const PangoGlyphInfo &b) +{ + return (a.geometry.width > b.geometry.width); +} + /** * Split the paragraph into spans. Also call pango_shape() on them. @@ -1034,13 +1106,13 @@ unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const 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.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; + 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)); } @@ -1123,6 +1195,13 @@ unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const 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 ); + /* Notes as of 4/29/13. Pango_shape is not generating English language ligatures, but it is generating + them for Hebrew (and probably other similar languages). In the case observed 3 unicode characters (a base + and 2 Mark, nonspacings) are merged into two glyphs (the base + first Mn, the 2nd Mn). All of these map + from glyph to first character of the log_cluster range. This destroys the 1:1 correspondence between + characters and glyphs. A big chunk of the conditional code which immediately follows this call + is there to clean up the resulting mess. + */ pango_shape(text_source->text->data() + span_start_byte_in_source, new_span.text_bytes, ¶->pango_items[pango_item_index].item->analysis, @@ -1134,24 +1213,55 @@ unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const // let's reverse the glyphstring on a cluster-by-cluster basis const unsigned nglyphs = new_span.glyph_string->num_glyphs; std::vector<PangoGlyphInfo> infos(nglyphs); - std::vector<gint> clusters(nglyphs); - unsigned i, cluster_start = 0; - - for (i = 0 ; i < nglyphs ; ++i) { - if (new_span.glyph_string->glyphs[i].attr.is_cluster_start) { - if (i != cluster_start) { - std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i); - std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i); - } - cluster_start = i; + std::vector<gint> clusters(nglyphs); + unsigned i, j; + for (i = 0 ; i < nglyphs ; i++)new_span.glyph_string->glyphs[i].attr.is_cluster_start = 0; + for (i = 0 ; i < nglyphs ; i++) { + j=i; + while( (j < nglyphs-1) && + (new_span.glyph_string->log_clusters[j+1] == new_span.glyph_string->log_clusters[i]) + )j++; + /* + CAREFUL, within a log_cluster the order of glyphs may not map 1:1, or + even in the same order, to the original unicode characters!!! Among + other things, diacritical mark glyphs can end up in front of the base + character. That makes determining kerning, even approximately, difficult + later on. To resolve this somewhat sort the glyphs with the same + log_cluster into descending order by width. In theory there should be 1 + that is nonzero, and N that are zero. The order of the zero width ones + does not matter. Sort the glyphs before copying. If ligatures other than with + Mark, nonspacing are ever implemented in Pango this will screw up, for instance + changing "fi" to "if". + */ + if(j - i){ + std::sort(&(new_span.glyph_string->glyphs[i]), &(new_span.glyph_string->glyphs[j+1]), compareGlyphWidth); } - } - if (i != cluster_start) { - std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i); - std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i); + + new_span.glyph_string->glyphs[i].attr.is_cluster_start = 1; + std::copy(&new_span.glyph_string->glyphs[ i], &new_span.glyph_string->glyphs[ j+1], infos.end() - j -1); + std::copy(&new_span.glyph_string->log_clusters[i], &new_span.glyph_string->log_clusters[j+1], clusters.end() - j -1); + i = j; } std::copy(infos.begin(), infos.end(), new_span.glyph_string->glyphs); std::copy(clusters.begin(), clusters.end(), new_span.glyph_string->log_clusters); + /* glyphs[].x_offset values are probably out of order within any log_clusters, apparently harmless */ + } + else { // ltr sections are in order but glyphs in a log_cluster following a ligature may not be. Sort, but no block swapping. + const unsigned nglyphs = new_span.glyph_string->num_glyphs; + unsigned i, j; + for (i = 0 ; i < nglyphs ; i++)new_span.glyph_string->glyphs[i].attr.is_cluster_start = 0; + for (i = 0 ; i < nglyphs ; i++) { + j=i; + while( (j < nglyphs-1) && + (new_span.glyph_string->log_clusters[j+1] == new_span.glyph_string->log_clusters[i]) + )j++; + if(j - i){ + std::sort(&(new_span.glyph_string->glyphs[i]), &(new_span.glyph_string->glyphs[j+1]), compareGlyphWidth); + } + new_span.glyph_string->glyphs[i].attr.is_cluster_start = 1; + i = j; + } + /* glyphs[].x_offset values may be out of order within any log_clusters, apparently harmless */ } 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); @@ -1469,7 +1579,7 @@ bool Layout::Calculator::calculate() if (_scanline_maker == NULL) break; // we're trying to flow past the last wrap shape - _buildPangoItemizationForPara(¶); + _buildPangoItemizationForPara(¶); unsigned para_end_input_index = _buildSpansForPara(¶); if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE) diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp index 8ede0a38e..153ef1ef0 100644 --- a/src/libnrtype/Layout-TNG-Output.cpp +++ b/src/libnrtype/Layout-TNG-Output.cpp @@ -39,35 +39,44 @@ namespace Text { /* dx array (character widths) and - ky (vertical kerning for entire span) + ky (vertical kerning for entire span) + rtl (+1 for LTR, -1 RTL) are smuggled through to the EMF (ignored by others) as: text<nul>N w1 w2 w3 ...wN<nul>y1 y2 y3 .. yN<nul><nul> - where the widths and y kern values are floats 7 characters wide, including the space + The ndx, widths, y kern, and rtl are all 7 characters wide. ndx and rtl are ints, the widths and ky are + formatted as ' 6f'. */ -char *smuggle_adxky_in(const char *string, int ndx, float *adx, float ky){ - int slen=strlen(string); +char *smuggle_adxkyrtl_in(const char *string, int ndx, float *adx, float ky, float rtl){ + int slen = strlen(string); /* holds: string fake terminator (one \0) - Number of widths (ndxy) - series of widths (ndxy entries) + Number of widths (ndx) + series of widths (ndx entries) fake terminator (one \0) y kern value (one float) + rtl value (one float) real terminator (two \0) */ - int newsize=slen + 1 + 7 + 7*ndx + 1 + 7 + 2; + int newsize=slen + 1 + 7 + 7*ndx + 1 + 7 + 7 + 2; newsize = 8*((7 + newsize)/8); // suppress valgrind messages if it is a multiple of 8 bytes??? - char *smuggle=(char *)calloc(newsize,1); // initialize all bytes, inluding terminators - strcpy(smuggle,string); // text to pass + char *smuggle=(char *)malloc(newsize); + strcpy(smuggle,string); // text to pass, includes the first fake terminator char *cptr = smuggle + slen + 1; // immediately after the first fake terminator sprintf(cptr,"%07d",ndx); // number of widths to pass - cptr+=7; + cptr+=7; // advance over ndx for(int i=0; i<ndx ; i++){ // all the widths sprintf(cptr," %6f",adx[i]); - cptr+=7; + cptr+=7; // advance over space + width } + *cptr='\0'; cptr++; // second fake terminator - sprintf(cptr," %6f",ky); // y kern for span + sprintf(cptr," %6f",ky); // y kern for span + cptr+=7; // advance over space + ky + sprintf(cptr," %6d",(int) rtl); // rtl multiplier for span + cptr+=7; // advance over rtl + *cptr++ = '\0'; // Set the real terminators + *cptr = '\0'; return(smuggle); } @@ -112,21 +121,66 @@ void Layout::_getGlyphTransformMatrix(int glyph_index, Geom::Affine *matrix) con void Layout::show(DrawingGroup *in_arena, Geom::OptRect const &paintbox) const { int glyph_index = 0; + double phase0 = 0.0; for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) { if (_input_stream[_spans[span_index].in_input_stream_item]->Type() != TEXT_SOURCE) continue; InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[_spans[span_index].in_input_stream_item]); + text_source->style->text_decoration_data.tspan_width = _spans[span_index].width(); + text_source->style->text_decoration_data.ascender = _spans[span_index].line_height.getAscent(); + text_source->style->text_decoration_data.descender = _spans[span_index].line_height.getDescent(); + text_source->style->text_decoration_data.line_gap = _spans[span_index].line_height.getLeading(); + if(!span_index || + (_chunks[_spans[span_index].in_chunk].in_line != _chunks[_spans[span_index-1].in_chunk].in_line)){ + text_source->style->text_decoration_data.tspan_line_start = true; + } + else { + text_source->style->text_decoration_data.tspan_line_start = false; + } + if((span_index == _spans.size() -1) || + (_chunks[_spans[span_index].in_chunk].in_line != _chunks[_spans[span_index+1].in_chunk].in_line)){ + text_source->style->text_decoration_data.tspan_line_end = true; + } + else { + text_source->style->text_decoration_data.tspan_line_end = false; + } + if(_spans[span_index].font){ + double underline_thickness, underline_position, line_through_thickness,line_through_position; + _spans[span_index].font->FontDecoration(underline_position, underline_thickness, line_through_position, line_through_thickness); + text_source->style->text_decoration_data.underline_thickness = underline_thickness; + text_source->style->text_decoration_data.underline_position = underline_position; + text_source->style->text_decoration_data.line_through_thickness = line_through_thickness; + text_source->style->text_decoration_data.line_through_position = line_through_position; + } + else { // can this case ever occur? + text_source->style->text_decoration_data.underline_thickness = + text_source->style->text_decoration_data.underline_position = + text_source->style->text_decoration_data.line_through_thickness = + text_source->style->text_decoration_data.line_through_position = 0.0; + } + DrawingText *nr_text = new DrawingText(in_arena->drawing()); - nr_text->setStyle(text_source->style); + bool first_line_glyph = true; while (glyph_index < (int)_glyphs.size() && _characters[_glyphs[glyph_index].in_character].in_span == span_index) { if (_characters[_glyphs[glyph_index].in_character].in_glyph != -1) { Geom::Affine glyph_matrix; _getGlyphTransformMatrix(glyph_index, &glyph_matrix); - nr_text->addComponent(_spans[span_index].font, _glyphs[glyph_index].glyph, glyph_matrix); + if(first_line_glyph && text_source->style->text_decoration_data.tspan_line_start){ + first_line_glyph = false; + phase0 = glyph_matrix.translation()[Geom::X]; + } + // save the starting coordinates for the line - these are needed for figuring out dot/dash/wave phase + (void) nr_text->addComponent(_spans[span_index].font, _glyphs[glyph_index].glyph, glyph_matrix, + _glyphs[glyph_index].width, + _spans[span_index].line_height.getAscent(), + _spans[span_index].line_height.getDescent(), + glyph_matrix.translation()[Geom::X] - phase0 + ); } glyph_index++; } + nr_text->setStyle(text_source->style); nr_text->setItemBounds(paintbox); in_arena->prependChild(nr_text); } @@ -158,36 +212,30 @@ Geom::OptRect Layout::bounds(Geom::Affine const &transform, int start, int lengt return bbox; } +/* This version is much simpler than the old one +*/ void Layout::print(SPPrintContext *ctx, Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox, Geom::Affine const &ctm) const { -int doUTN=0; -int lasttarget=0; -int newtarget=0; +bool text_to_path = ctx->module->textToPath(); +int oldtarget = 0; +int newtarget = 0; #define MAX_DX 2048 -float hold_dx[MAX_DX]; // For smuggling dx values (character widths) into print functions, unlikely any simple text output will be longer than this. -float ky; // For smuggling y kern value for span -int ndx=0; +float hold_dx[MAX_DX]; // For smuggling dx values (character widths) into print functions, unlikely any simple text output will be longer than this. +float ky; // For smuggling y kern value for span +int ndx = 0; +double rtl = 1.0; // 1 L->R, -1 R->L, constant across a span. 1.0 for t->b b->t??? +Geom::Affine glyph_matrix; if (_input_stream.empty()) return; - - Direction block_progression = _blockProgression(); - bool text_to_path = ctx->module->textToPath(); - doUTN = CanUTN(); // Unicode to Nonunicode translation enabled if true - 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; - } - Geom::Affine 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) { + if (!_glyphs.size()) return; // yes, this can happen. + if (text_to_path || _path_fitted) { + for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) { + if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1)continue; //invisible glyphs + Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span]; Geom::PathVector const * pv = span.font->PathVector(_glyphs[glyph_index].glyph); + InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]); if (pv) { _getGlyphTransformMatrix(glyph_index, &glyph_matrix); Geom::PathVector temp_pv = (*pv) * glyph_matrix; @@ -196,9 +244,28 @@ int ndx=0; if (!text_source->style->stroke.isNone()) sp_print_stroke(ctx, temp_pv, ctm, text_source->style, pbox, dbox, bbox); } - glyph_index++; - } else { - Geom::Point g_pos(0,0); // all strings are output at (0,0) because we do the translation using the matrix + } + } + else { + /* index by characters, referencing glyphs and spans only as needed */ + double char_x; + int doUTN = CanUTN(); // Unicode to Nonunicode translation enabled if true + Direction block_progression = _blockProgression(); + + for (unsigned char_index = 0 ; char_index < _characters.size() ; ) { + Glib::ustring text_string; // accumulate text for record in this + Geom::Point g_pos(0,0); // all strings are output at (0,0) because we do the translation using the matrix + int glyph_index = _characters[char_index].in_glyph; + if(glyph_index == -1){ // if the character maps to an invisible glyph we cannot know its geometry, so skip it and move on + char_index++; + continue; + } + ky = _glyphs[glyph_index].y; // same value for all positions in a span + unsigned span_index = _characters[char_index].in_span; + Span const &span = _spans[span_index]; + char_x = 0.0; + Glib::ustring::const_iterator text_iter = span.input_stream_first_character; + InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]); glyph_matrix = Geom::Scale(1.0, -1.0) * (Geom::Affine)Geom::Rotate(_glyphs[glyph_index].rotation); if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) { glyph_matrix[4] = span.line(this).baseline_y + span.baseline_shift; @@ -208,78 +275,113 @@ int ndx=0; glyph_matrix[4] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x; glyph_matrix[5] = span.line(this).baseline_y + span.baseline_shift; } - 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++; + switch(span.direction){ + case Layout::TOP_TO_BOTTOM: + case Layout::BOTTOM_TO_TOP: + case Layout::LEFT_TO_RIGHT: rtl = 1.0; break; + case Layout::RIGHT_TO_LEFT: rtl = -1.0; break; } + if(doUTN)oldtarget=SingleUnicodeToNon(*text_iter); // this should only ever be with a 1:1 glyph:character situation + + // accumulate a record to write + + unsigned lc_index = char_index; + unsigned hold_iisi = _spans[span_index].in_input_stream_item; + while(1){ + glyph_index = _characters[lc_index].in_glyph; + if(glyph_index == -1){ // end of a line within a paragraph, for instance + lc_index++; + break; + } - // try to output as many characters as possible in one go by detecting kerning and stopping when we encounter it - // also break spans at changes in Unicode->nonunicode translations, so that each span - // sent down from here is translated the same way. The translation happens much later - // in the emf-print code. - // Note that the incoming stream has a predefined notion of what is in each "span" and it is not - // entirely clear why. For instance, the string "%%% text %%%%", where % is the Unicode "Sagittarius" - // character has 3 spans, with the first two ending on the spaces. Yet in the XML there is only one tspan. - // Consequently when the Unicode->NonUnicode detection is on the first space will go out by itself, - // because it is at the end of a span, whereas the second space goes with the "text". - - 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; - if(doUTN)newtarget=lasttarget=SingleUnicodeToNon(*span_iter); - - do { + // always append if here + text_string += *text_iter; + + // figure out char widths, used by EMF, not currently used elsewhere + double cwidth; + if(lc_index == _glyphs[glyph_index].in_character){ // Glyph width is used only for the first character, these may be 0 + cwidth = rtl * _glyphs[glyph_index].width; // width might be zero + } + else { + cwidth = 0; + } + char_x += cwidth; /* -std::cout << "glyph info at:" << glyph_index -<< " glyphNo:" << _glyphs[glyph_index].glyph -<< " in_character:" << _glyphs[glyph_index].in_character -<< " x:" << _glyphs[glyph_index].x -<< " y:" << _glyphs[glyph_index].y -<< " rotation:" << _glyphs[glyph_index].rotation -<< " width:" << _glyphs[glyph_index].width -<< std::endl; +std:: cout << "DEBUG Layout::print in while " +<< " char_index " << char_index +<< " lc_index " << lc_index +<< " character " << std::hex << (int) *text_iter << std::dec +<< " glyph_index " << glyph_index +<< " glyph_xy " << _glyphs[glyph_index].x << " , " << _glyphs[glyph_index].y +<< " span_index " << span_index +<< " hold_iisi " << hold_iisi +<< std::endl; //DEBUG */ - span_string += *span_iter; - span_iter++; - if(doUTN)newtarget=SingleUnicodeToNon(*span_iter); - - unsigned same_character = _glyphs[glyph_index].in_character; - ky = _glyphs[glyph_index].y; // same value for all positions in a span - while (glyph_index < _glyphs.size() && _glyphs[glyph_index].in_character == same_character) { - char_x += _glyphs[glyph_index].width; - if(ndx < MAX_DX){ - hold_dx[ndx++] = _glyphs[glyph_index].width; - } - else { // silently truncate any text line silly enough to be longer than MAX_DX - break; - } - glyph_index++; + if(ndx < MAX_DX){ + hold_dx[ndx++] = fabs(cwidth); + } + else { // silently truncate any text line silly enough to be longer than MAX_DX + lc_index = _characters.size(); + break; } - } 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-4 - && (doUTN ? (lasttarget==newtarget ? 1 : 0) : 1 ) - ); + + + // conditions that prevent this character from joining the record + lc_index++; + if(lc_index >= _characters.size()) break; // nothing more to process, so it must be the end of the record + text_iter++; + if(doUTN)newtarget=SingleUnicodeToNon(*text_iter); // this should only ever be with a 1:1 glyph:character situation + if(newtarget != oldtarget)break; // change in unicode to nonunicode translation status + // MUST exit on any major span change, but not on some little events, like a font substitution event irrelvant for the file save + unsigned next_span_index = _characters[lc_index].in_span; + if(span_index != next_span_index){ + /* on major changes break out of loop. + 1st case usually indicates an entire input line has been processed (out of several in a paragraph) + 2nd case usually indicates that a format change within a line (font/size/color/etc) is present. + */ +/* +std:: cout << "DEBUG Layout::print in while --- " +<< " char_index " << char_index +<< " lc_index " << lc_index +<< " cwidth " << cwidth +<< " _char.x (next) " << (lc_index < _characters.size() ? _characters[lc_index].x : -1) +<< " char_x (end this)" << char_x +<< " diff " << fabs(char_x - _characters[lc_index].x) +<< " oldy " << ky +<< " nexty " << _glyphs[_characters[lc_index].in_glyph].y +<< std::endl; //DEBUG +*/ + if(hold_iisi != _spans[next_span_index].in_input_stream_item)break; // major change, font, size, color, etc, must exit + if(fabs(char_x - _spans[next_span_index].x_start) >= 1e-4)break; // xkerning change + if(ky != _glyphs[_characters[lc_index].in_glyph].y)break; // ykerning change + /* + None of the above? Then this is a minor "pangito", update span_index and keep going. + The font used by the display may have failed over, but print does not care and can continue to use + whatever was specified in the XML. + */ + span_index = next_span_index; + text_iter = _spans[span_index].input_stream_first_character; + } + + } + // write it sp_print_bind(ctx, glyph_matrix, 1.0); - // the dx array is smuggled through to the EMF (ignored by others) as: + // the dx array is smuggled through to the EMF driver (ignored by others) as: // text<nul>w1 w2 w3 ...wn<nul><nul> // where the widths are floats 7 characters wide, including the space - char *smuggle_string=smuggle_adxky_in(span_string.c_str(),ndx, &hold_dx[0], ky); -// sp_print_text(ctx, span_string.c_str(), g_pos, text_source->style); + char *smuggle_string=smuggle_adxkyrtl_in(text_string.c_str(),ndx, &hold_dx[0], ky, rtl); sp_print_text(ctx, smuggle_string, g_pos, text_source->style); free(smuggle_string); sp_print_release(ctx); ndx=0; + char_index = lc_index; } } } + #ifdef HAVE_CAIRO_PDF void Layout::showGlyphs(CairoRenderContext *ctx) const { @@ -432,10 +534,38 @@ Glib::ustring Layout::getFontFamily(unsigned span_index) const Glib::ustring Layout::dumpAsText() const { Glib::ustring result; + char line[256]; + + Glib::ustring::const_iterator icc; + + snprintf(line, sizeof(line), "spans %d\n", _spans.size()); + result += line; + snprintf(line, sizeof(line), "chars %d\n", _characters.size()); + result += line; + snprintf(line, sizeof(line), "glyphs %d\n", _glyphs.size()); + result += line; + unsigned lastspan=5000; + if(_characters.size() > 1){ + for(unsigned j = 0; j < _characters.size() ; j++){ + if(lastspan != _characters[j].in_span){ + lastspan = _characters[j].in_span; + icc = _spans[lastspan].input_stream_first_character; + } + snprintf(line, sizeof(line), "char %4d: '%c' 0x%4.4x x=%8.4f glyph=%3d span=%3d\n", j, *icc, *icc, _characters[j].x, _characters[j].in_glyph, _characters[j].in_span); + result += line; + icc++; + } + } + if(_glyphs.size()){ + for(unsigned j = 0; j < _glyphs.size() ; j++){ + snprintf(line, sizeof(line), "glyph %4d: %4d (%8.4f,%8.4f) rot=%8.4f cx=%8.4f char=%4d\n", + j, _glyphs[j].glyph, _glyphs[j].x, _glyphs[j].y, _glyphs[j].rotation, _glyphs[j].width, _glyphs[j].in_character); + result += line; + } + } for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) { - char line[256]; - snprintf(line, sizeof(line), "==== span %d\n", span_index); + 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)); @@ -469,8 +599,8 @@ Glib::ustring Layout::dumpAsText() const 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, *u.uattr, _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, *u.uattr, _characters[char_index].in_glyph); + } else { // some text has empty tspans, iter_char cannot be dereferenced + snprintf(line, sizeof(line), " %d: '%c' 0x%4.4x x=%f flags=%03x glyph=%d\n", char_index, *iter_char, *iter_char, _characters[char_index].x, *u.uattr, _characters[char_index].in_glyph); iter_char++; } result += line; diff --git a/src/libnrtype/Layout-TNG.h b/src/libnrtype/Layout-TNG.h index 0f5f08a53..573301c69 100644 --- a/src/libnrtype/Layout-TNG.h +++ b/src/libnrtype/Layout-TNG.h @@ -568,6 +568,9 @@ public: 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 + inline double getAscent() const {return ascent; } + inline double getDescent() const {return descent; } + inline double getLeading() const {return leading; } }; /// see _enum_converter() @@ -717,6 +720,7 @@ private: float font_size; float x_start; /// relative to the start of the chunk float x_end; /// relative to the start of the chunk + inline float width() const {return std::abs(x_start - x_end);} LineHeight line_height; double baseline_shift; /// relative to the line's baseline Direction direction; /// See CSS3 section 3.2. Either rtl or ltr diff --git a/src/libnrtype/font-instance.h b/src/libnrtype/font-instance.h index d00569984..ef10a04d0 100644 --- a/src/libnrtype/font-instance.h +++ b/src/libnrtype/font-instance.h @@ -59,6 +59,8 @@ public: double Advance(int glyph_id, bool vertical); // nominal advance of the font. bool FontMetrics(double &ascent, double &descent, double &leading); + bool FontDecoration(double &underline_position, double &underline_thickness, + double &linethrough_position, double &linethrough_thickness); bool FontSlope(double &run, double &rise); // for generating slanted cursors for oblique fonts Geom::OptRect BBox(int glyph_id); |
