diff options
| author | Jabier Arraiza <jabier.arraiza@marker.es> | 2017-06-12 18:20:56 +0000 |
|---|---|---|
| committer | Jabier Arraiza <jabier.arraiza@marker.es> | 2017-06-12 18:20:56 +0000 |
| commit | 59de64ad8af1ef0527100ffe09b4e85617c3226c (patch) | |
| tree | 89ed20b19fcefb4e71578a37a235a15aa9450933 /src | |
| parent | adding last changes (diff) | |
| parent | Small fixes to README (diff) | |
| download | inkscape-59de64ad8af1ef0527100ffe09b4e85617c3226c.tar.gz inkscape-59de64ad8af1ef0527100ffe09b4e85617c3226c.zip | |
Updating to master
Diffstat (limited to 'src')
| -rw-r--r-- | src/libnrtype/Layout-TNG-Compute.cpp | 1423 | ||||
| -rw-r--r-- | src/libnrtype/Layout-TNG-Input.cpp | 113 | ||||
| -rw-r--r-- | src/libnrtype/Layout-TNG-Output.cpp | 12 | ||||
| -rw-r--r-- | src/libnrtype/Layout-TNG.h | 4 | ||||
| -rw-r--r-- | src/live_effects/lpe-bool.h | 2 | ||||
| -rw-r--r-- | src/sp-flowtext.cpp | 4 | ||||
| -rw-r--r-- | src/sp-text.cpp | 8 |
7 files changed, 827 insertions, 739 deletions
diff --git a/src/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp index 8e173a7c7..63364ab34 100644 --- a/src/libnrtype/Layout-TNG-Compute.cpp +++ b/src/libnrtype/Layout-TNG-Compute.cpp @@ -19,6 +19,8 @@ namespace Inkscape { namespace Text { +//#define DEBUG_LAYOUT_TNG_COMPUTE + //#define IFTRACE(_code) _code #define IFTRACE(_code) @@ -53,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. @@ -87,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 @@ -97,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 @@ -123,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; + } }; - /** a useful little iterator for moving char-by-char across spans. */ + + /** 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. + */ struct UnbrokenSpanPosition { std::vector<UnbrokenSpan>::iterator iter_span; unsigned char_byte; @@ -140,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 @@ -168,83 +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); - void free(); - }; - -/* *********************************************************************************************************/ -// Initialisation of ParagraphInfo structure - - -#if 0 /* unused */ - void _initialiseInputItems(ParagraphInfo *para) const; -#endif - 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 - -/** - * 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; - } -} - - - bool _goToNextWrapShape(); + void _createFirstScanlineMaker(); bool _findChunksForLine(ParagraphInfo const ¶, UnbrokenSpanPosition *start_span_pos, @@ -252,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, @@ -265,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")); } } @@ -949,21 +951,6 @@ void Layout::Calculator::BrokenSpan::setZero() word_spacing = 0.0; } -template<typename T> void Layout::Calculator::ParagraphInfo::free_sequence(T &seq) -{ - for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) { - it->free(); - } - seq.clear(); -} - -void Layout::Calculator::ParagraphInfo::free() -{ - free_sequence(input_items); - free_sequence(pango_items); - free_sequence(unbroken_spans); -} - ///** // * For sections of text with a block-progression different to the rest // * of the flow, the best thing to do is to detect them in advance and @@ -1692,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() diff --git a/src/libnrtype/Layout-TNG-Input.cpp b/src/libnrtype/Layout-TNG-Input.cpp index 6f5d4e5f8..98ce7d526 100644 --- a/src/libnrtype/Layout-TNG-Input.cpp +++ b/src/libnrtype/Layout-TNG-Input.cpp @@ -40,7 +40,13 @@ void Layout::_clearInputObjects() } // this function does nothing more than store all its parameters for future reference -void Layout::appendText(Glib::ustring const &text, SPStyle *style, void *source_cookie, OptionalTextTagAttrs const *optional_attributes, unsigned optional_attributes_offset, Glib::ustring::const_iterator text_begin, Glib::ustring::const_iterator text_end) +void Layout::appendText(Glib::ustring const &text, + SPStyle *style, + void *source_cookie, + OptionalTextTagAttrs const *optional_attributes, + unsigned optional_attributes_offset, + Glib::ustring::const_iterator text_begin, + Glib::ustring::const_iterator text_end) { if (style == NULL) return; @@ -120,40 +126,40 @@ void Layout::appendWrapShape(Shape const *shape, DisplayAlign display_align) int Layout::_enum_converter(int input, EnumConversionItem const *conversion_table, unsigned conversion_table_size) { - for (unsigned i = 0 ; i < conversion_table_size ; i++) + for (unsigned i = 0 ; i < conversion_table_size ; i++) { if (conversion_table[i].input == input) return conversion_table[i].output; + } return conversion_table[0].output; } Layout::Direction Layout::InputStreamTextSource::styleGetBlockProgression() const { - switch( style->writing_mode.computed ) { + switch( style->writing_mode.computed ) { + case SP_CSS_WRITING_MODE_LR_TB: + case SP_CSS_WRITING_MODE_RL_TB: + return TOP_TO_BOTTOM; - case SP_CSS_WRITING_MODE_LR_TB: - case SP_CSS_WRITING_MODE_RL_TB: - return TOP_TO_BOTTOM; - - case SP_CSS_WRITING_MODE_TB_RL: - return RIGHT_TO_LEFT; + case SP_CSS_WRITING_MODE_TB_RL: + return RIGHT_TO_LEFT; - case SP_CSS_WRITING_MODE_TB_LR: - return LEFT_TO_RIGHT; + case SP_CSS_WRITING_MODE_TB_LR: + return LEFT_TO_RIGHT; - default: - std::cerr << "Layout::InputTextStream::styleGetBlockProgression: invalid writing mode." << std::endl; - } - return TOP_TO_BOTTOM; + default: + std::cerr << "Layout::InputTextStream::styleGetBlockProgression: invalid writing mode." << std::endl; + } + return TOP_TO_BOTTOM; } SPCSSTextOrientation Layout::InputStreamTextSource::styleGetTextOrientation() const { - return ((SPCSSTextOrientation)style->text_orientation.computed); + return ((SPCSSTextOrientation)style->text_orientation.computed); } SPCSSBaseline Layout::InputStreamTextSource::styleGetDominantBaseline() const { - return ((SPCSSBaseline)style->dominant_baseline.computed); + return ((SPCSSBaseline)style->dominant_baseline.computed); } static Layout::Alignment text_anchor_to_alignment(unsigned anchor, Layout::Direction para_direction) @@ -202,22 +208,24 @@ Layout::Alignment Layout::InputStreamTextSource::styleGetAlignment(Layout::Direc static const Layout::EnumConversionItem enum_convert_spstyle_style_to_pango_style[] = { {SP_CSS_FONT_STYLE_NORMAL, PANGO_STYLE_NORMAL}, {SP_CSS_FONT_STYLE_ITALIC, PANGO_STYLE_ITALIC}, - {SP_CSS_FONT_STYLE_OBLIQUE, PANGO_STYLE_OBLIQUE}}; + {SP_CSS_FONT_STYLE_OBLIQUE, PANGO_STYLE_OBLIQUE} +}; static const Layout::EnumConversionItem enum_convert_spstyle_weight_to_pango_weight[] = { // NB: The Pango web page calls 500 "the normal font" but both CSS2 and the Pango // enumeration define 400 as normal. {SP_CSS_FONT_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL}, - {SP_CSS_FONT_WEIGHT_BOLD,PANGO_WEIGHT_BOLD}, - {SP_CSS_FONT_WEIGHT_100, PANGO_WEIGHT_THIN}, - {SP_CSS_FONT_WEIGHT_200, PANGO_WEIGHT_ULTRALIGHT}, - {SP_CSS_FONT_WEIGHT_300, PANGO_WEIGHT_LIGHT}, - {SP_CSS_FONT_WEIGHT_400, PANGO_WEIGHT_NORMAL}, - {SP_CSS_FONT_WEIGHT_500, PANGO_WEIGHT_MEDIUM}, - {SP_CSS_FONT_WEIGHT_600, PANGO_WEIGHT_SEMIBOLD}, - {SP_CSS_FONT_WEIGHT_700, PANGO_WEIGHT_BOLD}, - {SP_CSS_FONT_WEIGHT_800, PANGO_WEIGHT_ULTRABOLD}, - {SP_CSS_FONT_WEIGHT_900, PANGO_WEIGHT_HEAVY}}; + {SP_CSS_FONT_WEIGHT_BOLD, PANGO_WEIGHT_BOLD}, + {SP_CSS_FONT_WEIGHT_100, PANGO_WEIGHT_THIN}, + {SP_CSS_FONT_WEIGHT_200, PANGO_WEIGHT_ULTRALIGHT}, + {SP_CSS_FONT_WEIGHT_300, PANGO_WEIGHT_LIGHT}, + {SP_CSS_FONT_WEIGHT_400, PANGO_WEIGHT_NORMAL}, + {SP_CSS_FONT_WEIGHT_500, PANGO_WEIGHT_MEDIUM}, + {SP_CSS_FONT_WEIGHT_600, PANGO_WEIGHT_SEMIBOLD}, + {SP_CSS_FONT_WEIGHT_700, PANGO_WEIGHT_BOLD}, + {SP_CSS_FONT_WEIGHT_800, PANGO_WEIGHT_ULTRABOLD}, + {SP_CSS_FONT_WEIGHT_900, PANGO_WEIGHT_HEAVY} +}; static const Layout::EnumConversionItem enum_convert_spstyle_stretch_to_pango_stretch[] = { {SP_CSS_FONT_STRETCH_NORMAL, PANGO_STRETCH_NORMAL}, @@ -228,11 +236,13 @@ static const Layout::EnumConversionItem enum_convert_spstyle_stretch_to_pango_st {SP_CSS_FONT_STRETCH_SEMI_EXPANDED, PANGO_STRETCH_SEMI_EXPANDED}, {SP_CSS_FONT_STRETCH_EXPANDED, PANGO_STRETCH_EXPANDED}, {SP_CSS_FONT_STRETCH_EXTRA_EXPANDED, PANGO_STRETCH_EXTRA_EXPANDED}, - {SP_CSS_FONT_STRETCH_ULTRA_EXPANDED, PANGO_STRETCH_ULTRA_EXPANDED}}; + {SP_CSS_FONT_STRETCH_ULTRA_EXPANDED, PANGO_STRETCH_ULTRA_EXPANDED} +}; static const Layout::EnumConversionItem enum_convert_spstyle_variant_to_pango_variant[] = { {SP_CSS_FONT_VARIANT_NORMAL, PANGO_VARIANT_NORMAL}, - {SP_CSS_FONT_VARIANT_SMALL_CAPS, PANGO_VARIANT_SMALL_CAPS}}; + {SP_CSS_FONT_VARIANT_SMALL_CAPS, PANGO_VARIANT_SMALL_CAPS} +}; font_instance *Layout::InputStreamTextSource::styleGetFontInstance() const { @@ -265,26 +275,57 @@ PangoFontDescription *Layout::InputStreamTextSource::styleGetFontDescription() c } pango_font_description_set_family(descr,family.c_str()); - pango_font_description_set_weight(descr,(PangoWeight)_enum_converter(style->font_weight.computed, enum_convert_spstyle_weight_to_pango_weight, sizeof(enum_convert_spstyle_weight_to_pango_weight)/sizeof(enum_convert_spstyle_weight_to_pango_weight[0]))); - pango_font_description_set_style(descr,(PangoStyle)_enum_converter(style->font_style.computed, enum_convert_spstyle_style_to_pango_style, sizeof(enum_convert_spstyle_style_to_pango_style)/sizeof(enum_convert_spstyle_style_to_pango_style[0]))); - pango_font_description_set_variant(descr,(PangoVariant)_enum_converter(style->font_variant.computed, enum_convert_spstyle_variant_to_pango_variant, sizeof(enum_convert_spstyle_variant_to_pango_variant)/sizeof(enum_convert_spstyle_variant_to_pango_variant[0]))); + pango_font_description_set_weight( + descr, + (PangoWeight)_enum_converter( + style->font_weight.computed, + enum_convert_spstyle_weight_to_pango_weight, + sizeof(enum_convert_spstyle_weight_to_pango_weight) / sizeof(enum_convert_spstyle_weight_to_pango_weight[0]) + ) + ); + pango_font_description_set_style( + descr, + (PangoStyle)_enum_converter( + style->font_style.computed, + enum_convert_spstyle_style_to_pango_style, + sizeof(enum_convert_spstyle_style_to_pango_style) / sizeof(enum_convert_spstyle_style_to_pango_style[0]) + ) + ); + pango_font_description_set_variant( + descr, + (PangoVariant)_enum_converter( + style->font_variant.computed, + enum_convert_spstyle_variant_to_pango_variant, + sizeof(enum_convert_spstyle_variant_to_pango_variant) / sizeof(enum_convert_spstyle_variant_to_pango_variant[0]) + ) + ); #ifdef USE_PANGO_WIN32 // damn Pango fudges the size, so we need to unfudge. See source of pango_win32_font_map_init() - pango_font_description_set_size(descr, (int) ((font_factory::Default())->fontSize*PANGO_SCALE*72/GetDeviceCaps(pango_win32_get_dc(),LOGPIXELSY))); // mandatory huge size (hinting workaround) + pango_font_description_set_size( + descr, + (int) ((font_factory::Default())->fontSize*PANGO_SCALE*72 / GetDeviceCaps(pango_win32_get_dc(),LOGPIXELSY)) // mandatory huge size (hinting workaround) + ); // we don't set stretch on Win32, because pango-win32 has no concept of it // (Windows doesn't really provide any useful field it could use). // If we did set stretch, then any text with a font-stretch attribute would // end up falling back to Arial. #else pango_font_description_set_size(descr, (int) ((font_factory::Default())->fontSize*PANGO_SCALE)); // mandatory huge size (hinting workaround) - pango_font_description_set_stretch(descr,(PangoStretch)_enum_converter(style->font_stretch.computed, enum_convert_spstyle_stretch_to_pango_stretch, sizeof(enum_convert_spstyle_stretch_to_pango_stretch)/sizeof(enum_convert_spstyle_stretch_to_pango_stretch[0]))); + pango_font_description_set_stretch( + descr, + (PangoStretch)_enum_converter( + style->font_stretch.computed, + enum_convert_spstyle_stretch_to_pango_stretch, + sizeof(enum_convert_spstyle_stretch_to_pango_stretch) / sizeof(enum_convert_spstyle_stretch_to_pango_stretch[0]) + ) + ); #endif return descr; } Layout::InputStreamTextSource::~InputStreamTextSource() { - sp_style_unref(style); + sp_style_unref(style); } }//namespace Text diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp index a1a19f2b3..7f20dee95 100644 --- a/src/libnrtype/Layout-TNG-Output.cpp +++ b/src/libnrtype/Layout-TNG-Output.cpp @@ -515,6 +515,7 @@ void Layout::showGlyphs(CairoRenderContext *ctx) const } #endif +#if DEBUG_TEXTLAYOUT_DUMPASTEXT // these functions are for dumpAsText() only. No need to translate static char const *direction_to_text(Layout::Direction d) { @@ -557,6 +558,7 @@ static char const *weight_to_text(PangoWeight w) } return "???"; } +#endif //DEBUG_TEXTLAYOUT_DUMPASTEXT Glib::ustring Layout::getFontFamily(unsigned span_index) const { @@ -570,6 +572,7 @@ Glib::ustring Layout::getFontFamily(unsigned span_index) const return ""; } +#if DEBUG_TEXTLAYOUT_DUMPASTEXT Glib::ustring Layout::dumpAsText() const { Glib::ustring result; @@ -609,7 +612,13 @@ Glib::ustring Layout::dumpAsText() const _lines[_chunks[_spans[span_index].in_chunk].in_line].in_shape) + Glib::ustring::compose(" in chunk %1 (x=%2, baselineshift=%3)\n", _spans[span_index].in_chunk, _chunks[_spans[span_index].in_chunk].left_x, _spans[span_index].baseline_shift); if (_spans[span_index].font) { - result += Glib::ustring::compose(" font '%1' %2 %3 %4\n", sp_font_description_get_family(_spans[span_index].font->descr), _spans[span_index].font_size, style_to_text(pango_font_description_get_style(_spans[span_index].font->descr)), weight_to_text(pango_font_description_get_weight(_spans[span_index].font->descr))); + result += Glib::ustring::compose( + " font '%1' %2 %3 %4\n", + sp_font_description_get_family(_spans[span_index].font->descr), + _spans[span_index].font_size, + style_to_text( pango_font_description_get_style(_spans[span_index].font->descr) ), + weight_to_text( pango_font_description_get_weight(_spans[span_index].font->descr) ) + ); } result += Glib::ustring::compose(" x_start = %1, x_end = %2\n", _spans[span_index].x_start, _spans[span_index].x_end) + Glib::ustring::compose(" line height: ascent %1, descent %2\n", _spans[span_index].line_height.ascent, _spans[span_index].line_height.descent) @@ -640,6 +649,7 @@ Glib::ustring Layout::dumpAsText() const result += "EOT\n"; return result; } +#endif //DEBUG_TEXTLAYOUT_DUMPASTEXT void Layout::fitToPathAlign(SVGLength const &startOffset, Path const &path) { diff --git a/src/libnrtype/Layout-TNG.h b/src/libnrtype/Layout-TNG.h index c39315106..878c76af8 100644 --- a/src/libnrtype/Layout-TNG.h +++ b/src/libnrtype/Layout-TNG.h @@ -11,6 +11,8 @@ #ifndef __LAYOUT_TNG_H__ #define __LAYOUT_TNG_H__ +//#define DEBUG_TEXTLAYOUT_DUMPASTEXT + #ifdef HAVE_CONFIG_H # include "config.h" #endif @@ -402,10 +404,12 @@ public: /** Returns the font family of the indexed span */ Glib::ustring getFontFamily(unsigned span_index) const; +#if DEBUG_TEXTLAYOUT_DUMPASTEXT /** debug and unit test method. Creates a textual representation of the contents of this object. The output is designed to be both human-readable and comprehensible when diffed with a known-good dump. */ Glib::ustring dumpAsText() const; +#endif /** Moves all the glyphs in the structure so that the baseline of all the characters sits neatly along the path specified. If the text has diff --git a/src/live_effects/lpe-bool.h b/src/live_effects/lpe-bool.h index 1c0aa0243..ce46f14d1 100644 --- a/src/live_effects/lpe-bool.h +++ b/src/live_effects/lpe-bool.h @@ -42,7 +42,7 @@ public: inline friend bool_op to_bool_op(bool_op_ex val) { assert(val <= bool_op_ex_slice); - (bool_op) val; + return (bool_op) val; } private: diff --git a/src/sp-flowtext.cpp b/src/sp-flowtext.cpp index d79652bf4..741ca7886 100644 --- a/src/sp-flowtext.cpp +++ b/src/sp-flowtext.cpp @@ -463,7 +463,9 @@ void SPFlowtext::rebuildLayout() _buildLayoutInput(this, exclusion_shape, &shapes, &pending_line_break_object); delete exclusion_shape; layout.calculateFlow(); - //g_print("%s", layout.dumpAsText().c_str()); +#if DEBUG_TEXTLAYOUT_DUMPASTEXT + g_print("%s", layout.dumpAsText().c_str()); +#endif } void SPFlowtext::_clearFlow(Inkscape::DrawingGroup *in_arena) diff --git a/src/sp-text.cpp b/src/sp-text.cpp index b066ca3de..2a88c5009 100644 --- a/src/sp-text.cpp +++ b/src/sp-text.cpp @@ -620,12 +620,16 @@ void SPText::rebuildLayout() if (SP_IS_TEXTPATH(&child)) { SPTextPath const *textpath = SP_TEXTPATH(&child); if (textpath->originalPath != NULL) { - //g_print("%s", layout.dumpAsText().c_str()); +#if DEBUG_TEXTLAYOUT_DUMPASTEXT + g_print("%s", layout.dumpAsText().c_str()); +#endif layout.fitToPathAlign(textpath->startOffset, *textpath->originalPath); } } } - //g_print("%s", layout.dumpAsText().c_str()); +#if DEBUG_TEXTLAYOUT_DUMPASTEXT + g_print("%s", layout.dumpAsText().c_str()); +#endif // set the x,y attributes on role:line spans for (auto& child: children) { |
