diff options
| author | Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org> | 2017-06-10 05:00:41 +0000 |
|---|---|---|
| committer | Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org> | 2017-06-10 05:00:41 +0000 |
| commit | f6a336e4b622cd668ea7ef6177369a0ef9026db8 (patch) | |
| tree | 097a34d8816977b723b68fc8515ae962054d4afa /src | |
| parent | Group all of the ParagraphInfo class implementation (diff) | |
| download | inkscape-f6a336e4b622cd668ea7ef6177369a0ef9026db8.tar.gz inkscape-f6a336e4b622cd668ea7ef6177369a0ef9026db8.zip | |
A bit more massaging of codestyle on src/libnrtype/Layout...
Diffstat (limited to 'src')
| -rw-r--r-- | src/libnrtype/Layout-TNG-Compute.cpp | 1417 |
1 files changed, 723 insertions, 694 deletions
diff --git a/src/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp index c3f634897..63364ab34 100644 --- a/src/libnrtype/Layout-TNG-Compute.cpp +++ b/src/libnrtype/Layout-TNG-Compute.cpp @@ -55,27 +55,22 @@ class Layout::Calculator { class SpanPosition; friend class SpanPosition; - Layout &_flow; - ScanlineMaker *_scanline_maker; - unsigned _current_shape_index; /// index into Layout::_input_wrap_shapes - PangoContext *_pango_context; - Direction _block_progression; - /** for y= attributes in tspan elements et al, we do the adjustment by moving each - glyph individually by this number. The spec means that this is maintained across - paragraphs. - - To do non-flow text layout, only the first "y" attribute is normally used. If there is only one - "y" attribute in a <tspan> other than the first <tspan>, it is ignored. This allows Inkscape to - insert a new line anywhere. On output, the Inkscape determined "y" is written out so other SVG - viewers know where to place the <tspans>. - */ - + /** + * For y= attributes in tspan elements et al, we do the adjustment by moving each + * glyph individually by this number. The spec means that this is maintained across + * paragraphs. + * + * To do non-flow text layout, only the first "y" attribute is normally used. If there is only one + * "y" attribute in a <tspan> other than the first <tspan>, it is ignored. This allows Inkscape to + * insert a new line anywhere. On output, the Inkscape determined "y" is written out so other SVG + * viewers know where to place the <tspans>. + */ double _y_offset; /** to stop pango from hinting its output, the font factory creates all fonts very large. @@ -89,7 +84,20 @@ class Layout::Calculator 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(); + + /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and + * operator= (and thus don't involve incrementing reference counts), yet they provide a free method + * that does delete or Unref. + * + * I suggest using the garbage collector to manage deletion. + */ + void free() + { + if (sub_flow) { + delete sub_flow; + sub_flow = NULL; + } + } }; /** Temporary storage associated with each item returned by the call to @@ -99,17 +107,36 @@ class Layout::Calculator font_instance *font; PangoItemInfo() : item(NULL), font(NULL) {} - void free(); + + /* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and + * operator= (and thus don't involve incrementing reference counts), yet they provide a free method + * that does delete or Unref. + * + * I suggest using the garbage collector to manage deletion. + */ + void free() + { + if (item) { + pango_item_free(item); + item = NULL; + } + if (font) { + font->Unref(); + font = NULL; + } + } }; + /** 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. */ + * 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 @@ -125,10 +152,48 @@ class Layout::Calculator 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;} + void free() + { + if (glyph_string) + pango_glyph_string_free(glyph_string); + glyph_string = NULL; + } + }; + + + /** 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) + { + for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) { + it->free(); + } + seq.clear(); + } + + void free() + { + free_sequence(input_items); + free_sequence(pango_items); + free_sequence(unbroken_spans); + } }; - /** a useful little iterator for moving char-by-char across spans. */ + + /** + * A useful little iterator for moving char-by-char across spans. + */ struct UnbrokenSpanPosition { std::vector<UnbrokenSpan>::iterator iter_span; unsigned char_byte; @@ -142,9 +207,11 @@ class Layout::Calculator {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. */ + /** + * 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 @@ -170,94 +237,11 @@ class Layout::Calculator 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) - { - for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) { - it->free(); - } - seq.clear(); - } - - void free() - { - free_sequence(input_items); - free_sequence(pango_items); - free_sequence(unbroken_spans); - } - }; - - - -/* *********************************************************************************************************/ -// Initialisation of ParagraphInfo structure - void _buildPangoItemizationForPara(ParagraphInfo *para) const; - - // Returns line_height_multiplier - static double _computeFontLineHeight( SPStyle const *style ); - + static double _computeFontLineHeight( SPStyle const *style ); // Returns line_height_multiplier unsigned _buildSpansForPara(ParagraphInfo *para) const; - -/* *********************************************************************************************************/ -// Per-line functions - - -#ifdef DEBUG_LAYOUT_TNG_COMPUTE -/** - * 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; - font_factory * factory = font_factory::Default(); - 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 - << " font: " - << factory->ConstructFontSpecification( para->pango_items[pidx].font ) - << std::endl; - } -} - -/** - * For debugging, not called in distributed code - * - * Input: para->first_input_index, para->pango_items - */ -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; - } -} -#endif //DEBUG_LAYOUT_TNG_COMPUTE - - bool _goToNextWrapShape(); + void _createFirstScanlineMaker(); bool _findChunksForLine(ParagraphInfo const ¶, UnbrokenSpanPosition *start_span_pos, @@ -265,12 +249,6 @@ static void dumpUnbrokenSpans(ParagraphInfo *para){ FontMetrics *line_box_height, FontMetrics const *strut_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, @@ -278,664 +256,675 @@ static void dumpUnbrokenSpans(ParagraphInfo *para){ FontMetrics *line_height, FontMetrics const *strut_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 + bool _measureUnbrokenSpan(ParagraphInfo const ¶, + BrokenSpan *span, + BrokenSpan *last_break_span, + BrokenSpan *last_emergency_break_span, + double maximum_width) const; + + double _getChunkLeftWithAlignment(ParagraphInfo const ¶, + std::vector<ChunkInfo>::const_iterator it_chunk, + double *add_to_each_whitespace) const; + + void _outputLine(ParagraphInfo const ¶, + FontMetrics const &line_height, + std::vector<ChunkInfo> const &chunk_info); + + static inline PangoLogAttr const &_charAttributes(ParagraphInfo const ¶, + UnbrokenSpanPosition const &span_pos) { - TRACE((" start _measureUnbrokenSpan %g\n", maximum_width)); - span->setZero(); + return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index]; + } - 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; - } - } +#ifdef DEBUG_LAYOUT_TNG_COMPUTE + static void dumpPangoItemsOut(ParagraphInfo *para); + static void dumpUnbrokenSpans(ParagraphInfo *para){ +#endif //DEBUG_LAYOUT_TNG_COMPUTE - 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; +public: + Calculator(Layout *text_flow) + : _flow(*text_flow) {} + + bool calculate(); +}; + + +/** + * Computes the width of a single UnbrokenSpan (pointed to by span->start.iter_span) + * and outputs its vital statistics into the other fields of \a span. + * Measuring will stop if maximum_width is reached and in that case the + * function will return false. In other cases where a line break must be + * done immediately the function will also return false. On return + * \a last_break_span will contain the vital statistics for the span only + * up to the last line breaking change. If there are no line breaking + * characters in the span then \a last_break_span will not be altered. + * Similarly, \a last_emergency_break_span will contain the vital + * statistics for the span up to the last inter-character boundary, + * or will be unaltered if there is none. + */ +bool Layout::Calculator::_measureUnbrokenSpan(ParagraphInfo const ¶, + BrokenSpan *span, + BrokenSpan *last_break_span, + BrokenSpan *last_emergency_break_span, + double maximum_width) const +{ + TRACE((" start _measureUnbrokenSpan %g\n", maximum_width)); + span->setZero(); + + if (span->start.iter_span->dx._set && span->start.char_byte == 0){ + if(para.direction == RIGHT_TO_LEFT){ + span->width -= span->start.iter_span->dx.computed; + } else { + span->width += span->start.iter_span->dx.computed; } + } - if (_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 (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 (_flow._input_stream[span->start.iter_span->input_index]->Type() != TEXT_SOURCE) + return true; // never happens - 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; - } + InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[span->start.iter_span->input_index]); - // 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 && span->end != span->start) { - TRACE((" is_mandatory_break ************\n")); - *last_emergency_break_span = *last_break_span = *span; - TRACE((" span %ld end of para; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count)); - return false; - } + if (_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; + } - if (char_attributes.is_line_break) { - TRACE((" is_line_break ************\n")); - // a suitable position to break at, record where we are - *last_emergency_break_span = *last_break_span = *span; - if (soft_hyphen_in_word) { - // if there was a previous soft hyphen we're not going to need it any more so we can remove it - span->width -= soft_hyphen_glyph_width; - if (!is_soft_hyphen) - soft_hyphen_in_word = false; - } - } else if (char_attributes.is_char_break) { - *last_emergency_break_span = *span; + // 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 && span->end != span->start) { + TRACE((" is_mandatory_break ************\n")); + *last_emergency_break_span = *last_break_span = *span; + TRACE((" span %ld end of para; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count)); + return false; + } + + if (char_attributes.is_line_break) { + TRACE((" is_line_break ************\n")); + // a suitable position to break at, record where we are + *last_emergency_break_span = *last_break_span = *span; + if (soft_hyphen_in_word) { + // if there was a previous soft hyphen we're not going to need it any more so we can remove it + span->width -= soft_hyphen_glyph_width; + if (!is_soft_hyphen) + soft_hyphen_in_word = false; } - // todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing + } else if (char_attributes.is_char_break) { + *last_emergency_break_span = *span; + } + // todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing - // sum the glyph widths, letter spacing, word spacing, and textLength adjustment to get the character width - double char_width = 0.0; - while (span->end_glyph_index < (unsigned)span->end.iter_span->glyph_string->num_glyphs - && span->end.iter_span->glyph_string->log_clusters[span->end_glyph_index] <= (int)span->end.char_byte) { - if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) { - // Vertical text + // sum the glyph widths, letter spacing, word spacing, and textLength adjustment to get the character width + double char_width = 0.0; + while (span->end_glyph_index < (unsigned)span->end.iter_span->glyph_string->num_glyphs + && span->end.iter_span->glyph_string->log_clusters[span->end_glyph_index] <= (int)span->end.char_byte) { + if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) { + // Vertical text - if( text_source->style->text_orientation.computed == SP_CSS_TEXT_ORIENTATION_SIDEWAYS || - (text_source->style->text_orientation.computed == SP_CSS_TEXT_ORIENTATION_MIXED && - para.pango_items[span->end.iter_span->pango_item_index].item->analysis.gravity == 0) ) { - // Sideways orientation - 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, false); - } else { - // Upright orientation - 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); - } + if( text_source->style->text_orientation.computed == SP_CSS_TEXT_ORIENTATION_SIDEWAYS || + (text_source->style->text_orientation.computed == SP_CSS_TEXT_ORIENTATION_MIXED && + para.pango_items[span->end.iter_span->pango_item_index].item->analysis.gravity == 0) ) { + // Sideways orientation + 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, false); } else { - // Horizontal text - char_width += font_size_multiplier * span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].geometry.width; + // Upright orientation + 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); } - span->end_glyph_index++; - } - if (char_attributes.is_cursor_position) - char_width += text_source->style->letter_spacing.computed * _flow.getTextLengthMultiplierDue(); - if (char_attributes.is_white) - char_width += text_source->style->word_spacing.computed * _flow.getTextLengthMultiplierDue(); - char_width += _flow.getTextLengthIncrementDue(); - span->width += char_width; - IFTRACE(char_count++); - - if (char_attributes.is_white) { - span->whitespace_count++; - span->each_whitespace_width = char_width; + } else { + // Horizontal text + char_width += font_size_multiplier * span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].geometry.width; } - span->ends_with_whitespace = char_attributes.is_white; + span->end_glyph_index++; + } + if (char_attributes.is_cursor_position) + char_width += text_source->style->letter_spacing.computed * _flow.getTextLengthMultiplierDue(); + if (char_attributes.is_white) + char_width += text_source->style->word_spacing.computed * _flow.getTextLengthMultiplierDue(); + char_width += _flow.getTextLengthIncrementDue(); + span->width += char_width; + IFTRACE(char_count++); + + if (char_attributes.is_white) { + span->whitespace_count++; + span->each_whitespace_width = char_width; + } + span->ends_with_whitespace = char_attributes.is_white; - is_soft_hyphen = (UNICODE_SOFT_HYPHEN == *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte)); - if (is_soft_hyphen) - soft_hyphen_glyph_width = char_width; + 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(); + span->end.increment(); - // Width should not include letter_spacing (or word_spacing) after last letter at end of line. - // word_spacing is attached to white space that is already removed from line end (?) - double test_width = span->width - text_source->style->letter_spacing.computed; + // Width should not include letter_spacing (or word_spacing) after last letter at end of line. + // word_spacing is attached to white space that is already removed from line end (?) + double test_width = span->width - text_source->style->letter_spacing.computed; - // Save letter_spacing and word_spacing for subtraction later if span is last span in line. - span->letter_spacing = text_source->style->letter_spacing.computed; - span->word_spacing = text_source->style->word_spacing.computed; + // Save letter_spacing and word_spacing for subtraction later if span is last span in line. + span->letter_spacing = text_source->style->letter_spacing.computed; + span->word_spacing = text_source->style->word_spacing.computed; - if (test_width > maximum_width && !char_attributes.is_white) { // whitespaces don't matter, we can put as many as we want at eol - TRACE((" span %ld exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count)); - return false; - } + if (test_width > maximum_width && !char_attributes.is_white) { // whitespaces don't matter, we can put as many as we want at eol + TRACE((" span %ld exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count)); + return false; + } - } while (span->end.char_byte != 0); // while we haven't wrapped to the next span + } while (span->end.char_byte != 0); // while we haven't wrapped to the next span - TRACE((" fitted span %ld width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count)); - TRACE((" end _measureUnbrokenSpan %g\n", maximum_width)); - return true; - } + TRACE((" fitted span %ld width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count)); + TRACE((" end _measureUnbrokenSpan %g\n", maximum_width)); + return true; +} /* *********************************************************************************************************/ // Per-line functions (output) - /** Uses the paragraph alignment and the chunk information to work out - where the actual left of the final chunk must be. Also sets - \a add_to_each_whitespace to be the amount of x to add at each - whitespace character to make full justification work. */ - double _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; - } - } - +/** Uses the paragraph alignment and the chunk information to work out + * where the actual left of the final chunk must be. Also sets + * \a add_to_each_whitespace to be the amount of x to add at each + * whitespace character to make full justification work. + */ +double Layout::Calculator::_getChunkLeftWithAlignment(ParagraphInfo const ¶, + 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: - 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; + return it_chunk->x - it_chunk->text_width; case CENTER: - return it_chunk->x + (it_chunk->scanrun_width - it_chunk->text_width) / 2; + return it_chunk->x - 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 ¶, FontMetrics const &line_height, std::vector<ChunkInfo> const &chunk_info) - { - TRACE((" Start _outputLine: ascent %f, descent %f, top of box %f\n", line_height.ascent, line_height.descent, _scanline_maker->yCoordinate() )); - if (chunk_info.empty()) { - TRACE((" line too short to fit anything on it, go to next\n")); - return; - } + 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; + } +} - // 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(); - - // The y coordinate is at the beginning edge of the line box (top for horizontal text, left - // edge for vertical lr text, right edge for vertical rl text. We align, by default to the - // alphabetic baseline for horizontal text and the central baseline for vertical text. - if( _block_progression == RIGHT_TO_LEFT ) { - // Vertical text, use em box center as baseline - new_line.baseline_y -= 0.5 * line_height.emSize(); - } else if ( _block_progression == LEFT_TO_RIGHT ) { - // Vertical text, use em box center as baseline - new_line.baseline_y += 0.5 * line_height.emSize(); - } else { - new_line.baseline_y += line_height.getTypoAscent(); - } +/** + * Once we've got here we have finished making changes to the line + * and are ready to output the final result to #_flow. + * This method takes its input parameters and does that. + */ +void Layout::Calculator::_outputLine(ParagraphInfo const ¶, + FontMetrics const &line_height, + std::vector<ChunkInfo> const &chunk_info) +{ + TRACE((" Start _outputLine: ascent %f, descent %f, top of box %f\n", line_height.ascent, line_height.descent, _scanline_maker->yCoordinate() )); + if (chunk_info.empty()) { + TRACE((" line too short to fit anything on it, go to next\n")); + return; + } - new_line.in_shape = _current_shape_index; - _flow._lines.push_back(new_line); - - for (std::vector<ChunkInfo>::const_iterator it_chunk = chunk_info.begin() ; it_chunk != chunk_info.end() ; it_chunk++) { - - double add_to_each_whitespace; - // add the chunk to the list - Layout::Chunk new_chunk; - new_chunk.in_line = _flow._lines.size() - 1; - TRACE((" New chunk: in_line: %d\n", new_chunk.in_line)); - new_chunk.left_x = _getChunkLeftWithAlignment(para, it_chunk, &add_to_each_whitespace); - - // we may also have y move orders to deal with here (dx, dy and rotate are done per span) - - // Comment updated: 23 July 2010: - // We must handle two cases: - // - // 1. Inkscape SVG where the first line is placed by the read-in "y" value and the - // rest are determined by 'font-size' and 'line-height' (and not by any - // y-kerning). <tspan>s in this case are marked by sodipodi:role="line". This - // allows new lines to be inserted in the middle of a <text> object. On output, - // new "y" values are calculated for each <tspan> that represents a new line. Line - // spacing is already handled by the calling routine. - // - // 2. Plain SVG where each <text> or <tspan> is placed by its own "x" and "y" values. - // Note that in this case Inkscape treats each <text> object with any included - // <tspan>s as a single line of text. This can be confusing in the code below. - - if (!it_chunk->broken_spans.empty() // Not empty paragraph - && it_chunk->broken_spans.front().start.char_byte == 0 ) { // Beginning of unbroken span - - // If empty or new line (sodipode:role="line") - if( _flow._characters.empty() || - _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) { - - // This is the Inkscape SVG case. - // - // If <tspan> "y" attribute is set, use it (initial "y" attributes in - // <tspans> other than the first have already been stripped for <tspans> - // marked with role="line", see sp-text.cpp: SPText::_buildLayoutInput). - // NOTE: for vertical text, "y" is the user-space "x" value. - if( it_chunk->broken_spans.front().start.iter_span->y._set ) { - - // Use set "y" attribute - new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed; - // Save baseline - _flow._lines.back().baseline_y = new_line.baseline_y; - - double top_of_line_box = new_line.baseline_y; - if( _block_progression == RIGHT_TO_LEFT ) { - // Vertical text, use em box center as baseline - top_of_line_box += 0.5 * line_height.emSize(); - } else if (_block_progression == LEFT_TO_RIGHT ) { - // Vertical text, use em box center as baseline - top_of_line_box -= 0.5 * line_height.emSize(); - } else { - top_of_line_box -= line_height.getTypoAscent(); - } - TRACE((" y attribute set, next line top_of_line_box: %f\n", top_of_line_box )); - // Set the initial y coordinate of the next line (see above). - _scanline_maker->setNewYCoordinate(top_of_line_box); + // 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(); + + // The y coordinate is at the beginning edge of the line box (top for horizontal text, left + // edge for vertical lr text, right edge for vertical rl text. We align, by default to the + // alphabetic baseline for horizontal text and the central baseline for vertical text. + if( _block_progression == RIGHT_TO_LEFT ) { + // Vertical text, use em box center as baseline + new_line.baseline_y -= 0.5 * line_height.emSize(); + } else if ( _block_progression == LEFT_TO_RIGHT ) { + // Vertical text, use em box center as baseline + new_line.baseline_y += 0.5 * line_height.emSize(); + } else { + new_line.baseline_y += line_height.getTypoAscent(); + } + + new_line.in_shape = _current_shape_index; + _flow._lines.push_back(new_line); + + for (std::vector<ChunkInfo>::const_iterator it_chunk = chunk_info.begin() ; it_chunk != chunk_info.end() ; it_chunk++) { + + double add_to_each_whitespace; + // add the chunk to the list + Layout::Chunk new_chunk; + new_chunk.in_line = _flow._lines.size() - 1; + TRACE((" New chunk: in_line: %d\n", new_chunk.in_line)); + new_chunk.left_x = _getChunkLeftWithAlignment(para, it_chunk, &add_to_each_whitespace); + + // we may also have y move orders to deal with here (dx, dy and rotate are done per span) + + // Comment updated: 23 July 2010: + // We must handle two cases: + // + // 1. Inkscape SVG where the first line is placed by the read-in "y" value and the + // rest are determined by 'font-size' and 'line-height' (and not by any + // y-kerning). <tspan>s in this case are marked by sodipodi:role="line". This + // allows new lines to be inserted in the middle of a <text> object. On output, + // new "y" values are calculated for each <tspan> that represents a new line. Line + // spacing is already handled by the calling routine. + // + // 2. Plain SVG where each <text> or <tspan> is placed by its own "x" and "y" values. + // Note that in this case Inkscape treats each <text> object with any included + // <tspan>s as a single line of text. This can be confusing in the code below. + + if (!it_chunk->broken_spans.empty() // Not empty paragraph + && it_chunk->broken_spans.front().start.char_byte == 0 ) { // Beginning of unbroken span + + // If empty or new line (sodipode:role="line") + if( _flow._characters.empty() || + _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) { + + // This is the Inkscape SVG case. + // + // If <tspan> "y" attribute is set, use it (initial "y" attributes in + // <tspans> other than the first have already been stripped for <tspans> + // marked with role="line", see sp-text.cpp: SPText::_buildLayoutInput). + // NOTE: for vertical text, "y" is the user-space "x" value. + if( it_chunk->broken_spans.front().start.iter_span->y._set ) { + + // Use set "y" attribute + new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed; + // Save baseline + _flow._lines.back().baseline_y = new_line.baseline_y; + + double top_of_line_box = new_line.baseline_y; + if( _block_progression == RIGHT_TO_LEFT ) { + // Vertical text, use em box center as baseline + top_of_line_box += 0.5 * line_height.emSize(); + } else if (_block_progression == LEFT_TO_RIGHT ) { + // Vertical text, use em box center as baseline + top_of_line_box -= 0.5 * line_height.emSize(); + } else { + top_of_line_box -= line_height.getTypoAscent(); } + TRACE((" y attribute set, next line top_of_line_box: %f\n", top_of_line_box )); + // Set the initial y coordinate of the next line (see above). + _scanline_maker->setNewYCoordinate(top_of_line_box); + } - // Reset relative y_offset ("dy" attribute is relative but should be reset at - // the beginning of each line since each line will have a new "y" written out.) - _y_offset = 0.0; + // Reset relative y_offset ("dy" attribute is relative but should be reset at + // the beginning of each line since each line will have a new "y" written out.) + _y_offset = 0.0; - } else { + } else { - // This is the plain SVG case - // - // "x" and "y" are used to place text, simulating lines as necessary - if( it_chunk->broken_spans.front().start.iter_span->y._set ) { - _y_offset = it_chunk->broken_spans.front().start.iter_span->y.computed - new_line.baseline_y; - } + // This is the plain SVG case + // + // "x" and "y" are used to place text, simulating lines as necessary + if( it_chunk->broken_spans.front().start.iter_span->y._set ) { + _y_offset = it_chunk->broken_spans.front().start.iter_span->y.computed - new_line.baseline_y; } } - _flow._chunks.push_back(new_chunk); - - double current_x; - double direction_sign; - Direction previous_direction = para.direction; - double counter_directional_width_remaining = 0.0; - float glyph_rotate = 0.0; - if (para.direction == LEFT_TO_RIGHT) { - direction_sign = +1.0; - current_x = 0.0; - } else { - direction_sign = -1.0; - if (para.alignment == FULL && !_flow._input_wrap_shapes.empty()){ - current_x = it_chunk->scanrun_width; - } - else { - current_x = it_chunk->text_width; - } + } + _flow._chunks.push_back(new_chunk); + + double current_x; + double direction_sign; + Direction previous_direction = para.direction; + double counter_directional_width_remaining = 0.0; + float glyph_rotate = 0.0; + if (para.direction == LEFT_TO_RIGHT) { + direction_sign = +1.0; + current_x = 0.0; + } else { + direction_sign = -1.0; + if (para.alignment == FULL && !_flow._input_wrap_shapes.empty()){ + current_x = it_chunk->scanrun_width; } + else { + current_x = it_chunk->text_width; + } + } - 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 - // (x and y are done per chunk) - if (unbroken_span.dx._set) current_x += unbroken_span.dx.computed; - if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed; - if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180); - } - - if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE - && unbroken_span.pango_item_index == -1) { - // style only, nothing to output - continue; - } + 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 + // (x and y are done per chunk) + if (unbroken_span.dx._set) current_x += unbroken_span.dx.computed; + if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed; + if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180); + } - Layout::Span new_span; + if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE + && unbroken_span.pango_item_index == -1) { + // style only, nothing to output + continue; + } - new_span.in_chunk = _flow._chunks.size() - 1; - new_span.line_height = unbroken_span.line_height; - new_span.in_input_stream_item = unbroken_span.input_index; - new_span.baseline_shift = 0.0; - new_span.block_progression = _block_progression; - new_span.text_orientation = unbroken_span.text_orientation; - if ((_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) && (new_span.font = para.pango_items[unbroken_span.pango_item_index].font)) - { - new_span.font->Ref(); - new_span.font_size = unbroken_span.font_size; - new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT; - new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte); - } else { // a control code - new_span.font = NULL; - new_span.font_size = new_span.line_height.emSize(); - new_span.direction = para.direction; - } + Layout::Span new_span; + + new_span.in_chunk = _flow._chunks.size() - 1; + new_span.line_height = unbroken_span.line_height; + new_span.in_input_stream_item = unbroken_span.input_index; + new_span.baseline_shift = 0.0; + new_span.block_progression = _block_progression; + new_span.text_orientation = unbroken_span.text_orientation; + if ((_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) && (new_span.font = para.pango_items[unbroken_span.pango_item_index].font)) + { + new_span.font->Ref(); + new_span.font_size = unbroken_span.font_size; + new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT; + new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte); + } else { // a control code + new_span.font = NULL; + new_span.font_size = new_span.line_height.emSize(); + new_span.direction = para.direction; + } - if (new_span.direction == para.direction) { - current_x -= counter_directional_width_remaining; - counter_directional_width_remaining = 0.0; - } else if (new_span.direction != previous_direction) { - // measure width of spans we need to switch round - counter_directional_width_remaining = 0.0; - std::vector<BrokenSpan>::const_iterator it_following_span; - for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) { - 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); + if (new_span.direction == para.direction) { + current_x -= counter_directional_width_remaining; + counter_directional_width_remaining = 0.0; + } else if (new_span.direction != previous_direction) { + // measure width of spans we need to switch round + counter_directional_width_remaining = 0.0; + std::vector<BrokenSpan>::const_iterator it_following_span; + for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) { + 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; } - current_x += counter_directional_width_remaining; - counter_directional_width_remaining = 0.0; // we want to go increasingly negative + counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace); } - new_span.x_start = current_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; - 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(); - 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) { - // 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; - } + current_x += counter_directional_width_remaining; + counter_directional_width_remaining = 0.0; // we want to go increasingly negative + } + new_span.x_start = current_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; + 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(); + int newcluster = 0; + if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start){ + newcluster = 1; + x_in_span = x_in_span_last; + } - // create the Layout::Glyph - PangoGlyphInfo *unbroken_span_glyph_info = &unbroken_span.glyph_string->glyphs[glyph_index]; - Layout::Glyph new_glyph; - new_glyph.glyph = unbroken_span_glyph_info->glyph; - new_glyph.in_character = _flow._characters.size(); - new_glyph.rotation = glyph_rotate; - new_glyph.orientation = ORIENTATION_UPRIGHT; // Only effects vertical text - - // We may have scaled font size to fit textLength; now, if - // @lengthAdjust=spacingAndGlyphs, this scaling must be only horizontal, - // not vertical, so we unscale it back vertically during output - if (_flow.lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACINGANDGLYPHS) - new_glyph.vertical_scale = 1.0 / _flow.getTextLengthMultiplierDue(); - else - new_glyph.vertical_scale = 1.0; - - // Position glyph -------------------- - new_glyph.x = current_x + unbroken_span_glyph_info->geometry.x_offset * font_size_multiplier; - new_glyph.y =_y_offset; - - // y-coordinate is flipped between vertical and horizontal text... delta_y is common offset but applied with opposite sign - double delta_y = unbroken_span_glyph_info->geometry.y_offset * font_size_multiplier + unbroken_span.baseline_shift; - SPCSSBaseline dominant_baseline = _flow._blockBaseline(); - - if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) { - // Vertical text - - // Default dominant baseline is determined by overall block (i.e. <text>) 'text-orientation' value. - if( _flow._blockTextOrientation() != SP_CSS_TEXT_ORIENTATION_SIDEWAYS ) { - if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_CENTRAL; - } else { - if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_ALPHABETIC; - } + if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes + && *iter_source_text == UNICODE_SOFT_HYPHEN + && glyph_index + 1 != it_span->end_glyph_index) { + // if we're looking at a soft hyphen and it's not the last glyph in the + // chunk we don't draw the glyph but we still need to add to _characters + Layout::Character new_character; + new_character.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; + } - new_glyph.y += delta_y; + // create the Layout::Glyph + PangoGlyphInfo *unbroken_span_glyph_info = &unbroken_span.glyph_string->glyphs[glyph_index]; + Layout::Glyph new_glyph; + new_glyph.glyph = unbroken_span_glyph_info->glyph; + new_glyph.in_character = _flow._characters.size(); + new_glyph.rotation = glyph_rotate; + new_glyph.orientation = ORIENTATION_UPRIGHT; // Only effects vertical text + + // We may have scaled font size to fit textLength; now, if + // @lengthAdjust=spacingAndGlyphs, this scaling must be only horizontal, + // not vertical, so we unscale it back vertically during output + if (_flow.lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACINGANDGLYPHS) + new_glyph.vertical_scale = 1.0 / _flow.getTextLengthMultiplierDue(); + else + new_glyph.vertical_scale = 1.0; + + // Position glyph -------------------- + new_glyph.x = current_x + unbroken_span_glyph_info->geometry.x_offset * font_size_multiplier; + new_glyph.y =_y_offset; + + // y-coordinate is flipped between vertical and horizontal text... delta_y is common offset but applied with opposite sign + double delta_y = unbroken_span_glyph_info->geometry.y_offset * font_size_multiplier + unbroken_span.baseline_shift; + SPCSSBaseline dominant_baseline = _flow._blockBaseline(); + + if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) { + // Vertical text + + // Default dominant baseline is determined by overall block (i.e. <text>) 'text-orientation' value. + if( _flow._blockTextOrientation() != SP_CSS_TEXT_ORIENTATION_SIDEWAYS ) { + if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_CENTRAL; + } else { + if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_ALPHABETIC; + } - // TODO: Should also check 'glyph_orientation_vertical' if 'text-orientation' is unset... - if( new_span.text_orientation == SP_CSS_TEXT_ORIENTATION_SIDEWAYS || - (new_span.text_orientation == SP_CSS_TEXT_ORIENTATION_MIXED && - para.pango_items[unbroken_span.pango_item_index].item->analysis.gravity == 0) ) { + new_glyph.y += delta_y; - // Sideways orientation (Latin characters, CJK punctuation), 90deg rotation done at output stage. zzzzzzz - new_glyph.orientation = ORIENTATION_SIDEWAYS; + // TODO: Should also check 'glyph_orientation_vertical' if 'text-orientation' is unset... + if( new_span.text_orientation == SP_CSS_TEXT_ORIENTATION_SIDEWAYS || + (new_span.text_orientation == SP_CSS_TEXT_ORIENTATION_MIXED && + para.pango_items[unbroken_span.pango_item_index].item->analysis.gravity == 0) ) { - new_glyph.y -= new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->GetBaselines()[ dominant_baseline ]; - new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span_glyph_info->glyph, false); + // Sideways orientation (Latin characters, CJK punctuation), 90deg rotation done at output stage. zzzzzzz + new_glyph.orientation = ORIENTATION_SIDEWAYS; - } else { - // Upright orientation + new_glyph.y -= new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->GetBaselines()[ dominant_baseline ]; + new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span_glyph_info->glyph, false); - new_glyph.x += new_span.line_height.ascent; + } else { + // Upright orientation - // Glyph reference point is center (shift: left edge to center glyph) - new_glyph.y -= unbroken_span_glyph_info->geometry.width * 0.5 * font_size_multiplier; - new_glyph.y -= new_span.font_size * (para.pango_items[unbroken_span.pango_item_index].font->GetBaselines()[ dominant_baseline ] - - para.pango_items[unbroken_span.pango_item_index].font->GetBaselines()[ SP_CSS_BASELINE_CENTRAL ] ); + new_glyph.x += new_span.line_height.ascent; - new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span_glyph_info->glyph, true); - if( new_glyph.width == 0 ) { - new_glyph.width = unbroken_span_glyph_info->geometry.width * font_size_multiplier; - } + // Glyph reference point is center (shift: left edge to center glyph) + new_glyph.y -= unbroken_span_glyph_info->geometry.width * 0.5 * font_size_multiplier; + new_glyph.y -= new_span.font_size * (para.pango_items[unbroken_span.pango_item_index].font->GetBaselines()[ dominant_baseline ] - + para.pango_items[unbroken_span.pango_item_index].font->GetBaselines()[ SP_CSS_BASELINE_CENTRAL ] ); + new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span_glyph_info->glyph, true); + if( new_glyph.width == 0 ) { + new_glyph.width = unbroken_span_glyph_info->geometry.width * font_size_multiplier; } - } else { - // Horizontal text - if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_ALPHABETIC; - - new_glyph.y -= delta_y; - new_glyph.y += new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->GetBaselines()[ dominant_baseline ]; - - new_glyph.width = unbroken_span_glyph_info->geometry.width * font_size_multiplier; - if ((new_glyph.width == 0) && (para.pango_items[unbroken_span.pango_item_index].font)) - new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span_glyph_info->glyph, false); - // for some reason pango returns zero width for invalid glyph characters (those empty boxes), so go to freetype for the info } + } else { + // Horizontal text + if( dominant_baseline == SP_CSS_BASELINE_AUTO ) dominant_baseline = SP_CSS_BASELINE_ALPHABETIC; - 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) - // Vertical text - 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 - // Horizontal text - cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width; - } - new_glyph.x -= cluster_width; + new_glyph.y -= delta_y; + new_glyph.y += new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->GetBaselines()[ dominant_baseline ]; + + new_glyph.width = unbroken_span_glyph_info->geometry.width * font_size_multiplier; + if ((new_glyph.width == 0) && (para.pango_items[unbroken_span.pango_item_index].font)) + new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span_glyph_info->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) + // Vertical text + 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 + // Horizontal text + cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width; } - _flow._glyphs.push_back(new_glyph); - - // create the Layout::Character(s) - double advance_width = new_glyph.width; - if (newcluster){ - newcluster = 0; - // find where the text ends for this log_cluster - end_byte = it_span->start.iter_span->text_bytes; // Upper limit - for(int next_glyph_index = glyph_index+1; next_glyph_index < unbroken_span.glyph_string->num_glyphs; next_glyph_index++){ - if(unbroken_span.glyph_string->glyphs[next_glyph_index].attr.is_cluster_start){ - end_byte = unbroken_span.glyph_string->log_clusters[next_glyph_index]; - break; - } - } - // Figure out how many 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(); + new_glyph.x -= cluster_width; + } + _flow._glyphs.push_back(new_glyph); + + // create the Layout::Character(s) + double advance_width = new_glyph.width; + if (newcluster){ + newcluster = 0; + // find where the text ends for this log_cluster + end_byte = it_span->start.iter_span->text_bytes; // Upper limit + for(int next_glyph_index = glyph_index+1; next_glyph_index < unbroken_span.glyph_string->num_glyphs; next_glyph_index++){ + if(unbroken_span.glyph_string->glyphs[next_glyph_index].attr.is_cluster_start){ + end_byte = unbroken_span.glyph_string->log_clusters[next_glyph_index]; + break; } } - 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; - 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 * _flow.getTextLengthMultiplierDue() + add_to_each_whitespace; // justification - if (new_character.char_attributes.is_cursor_position) - advance_width += text_source->style->letter_spacing.computed * _flow.getTextLengthMultiplierDue(); - advance_width += _flow.getTextLengthIncrementDue(); - 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--; + // 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; } - - advance_width *= direction_sign; - if (new_span.direction != para.direction) { - counter_directional_width_remaining -= advance_width; - current_x -= advance_width; - x_in_span_last -= advance_width; - } else { - current_x += advance_width; - x_in_span_last += advance_width; + 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(); } } - } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) { - current_x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width; - } + 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; + 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 * _flow.getTextLengthMultiplierDue() + add_to_each_whitespace; // justification + if (new_character.char_attributes.is_cursor_position) + advance_width += text_source->style->letter_spacing.computed * _flow.getTextLengthMultiplierDue(); + advance_width += _flow.getTextLengthIncrementDue(); + 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--; + } - new_span.x_end = new_span.x_start + x_in_span_last; - _flow._spans.push_back(new_span); - previous_direction = new_span.direction; + advance_width *= direction_sign; + if (new_span.direction != para.direction) { + counter_directional_width_remaining -= advance_width; + current_x -= advance_width; + x_in_span_last -= advance_width; + } else { + current_x += advance_width; + x_in_span_last += advance_width; + } + } + } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) { + current_x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width; } - // end adding spans to the list, on to the next chunk... - } - TRACE((" End _outputLine\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")); + new_span.x_end = new_span.x_start + x_in_span_last; + _flow._spans.push_back(new_span); + previous_direction = new_span.direction; } - 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; + // end adding spans to the list, on to the next chunk... } + TRACE((" End _outputLine\n")); } -void Layout::Calculator::PangoItemInfo::free() +/** + * Initialises the ScanlineMaker for the first shape in the flow, + * or the infinite version if we're not doing wrapping. + */ +void Layout::Calculator::_createFirstScanlineMaker() { - if (item) { - pango_item_free(item); - item = NULL; + _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")); } - if (font) { - font->Unref(); - font = NULL; + else { + _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression); + TRACE((" begin wrap shape 0\n")); } } @@ -1690,6 +1679,46 @@ bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const ¶, return true; } +#ifdef DEBUG_LAYOUT_TNG_COMPUTE +/** + * For debugging, not called in distributed code + * + * Input: para->first_input_index, para->pango_items + */ +static void Layout::Calculator::dumpPangoItemsOut(ParagraphInfo *para){ + std::cout << "Pango items: " << para->pango_items.size() << std::endl; + font_factory * factory = font_factory::Default(); + for(unsigned pidx = 0 ; pidx < para->pango_items.size(); pidx++){ + std::cout + << "idx: " << pidx + << " offset: " + << para->pango_items[pidx].item->offset + << " length: " + << para->pango_items[pidx].item->length + << " font: " + << factory->ConstructFontSpecification( para->pango_items[pidx].font ) + << std::endl; + } +} + +/** + * For debugging, not called in distributed code + * + * Input: para->first_input_index, para->pango_items + */ +static void Layout::Calculator::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; + } +} +#endif //DEBUG_LAYOUT_TNG_COMPUTE /** The management function to start the whole thing off. */ bool Layout::Calculator::calculate() |
