diff options
| author | Tavmjong Bah <tavmjong@free.fr> | 2015-01-05 10:17:51 +0000 |
|---|---|---|
| committer | tavmjong-free <tavmjong@free.fr> | 2015-01-05 10:17:51 +0000 |
| commit | 374aa610cb550cf7dcd283ca93ad0ad844732d32 (patch) | |
| tree | 067c340e29085c65fc3bde1b680ecfde1161bae1 /src/libnrtype | |
| parent | Comment out error message about style ref counting and add explanation. (diff) | |
| download | inkscape-374aa610cb550cf7dcd283ca93ad0ad844732d32.tar.gz inkscape-374aa610cb550cf7dcd283ca93ad0ad844732d32.zip | |
Implement SVG 1.1 textLength and textAdjust attributes. Code from Dmitry Kirsanov (buliabyak).
(bzr r13839)
Diffstat (limited to 'src/libnrtype')
| -rw-r--r-- | src/libnrtype/Layout-TNG-Compute.cpp | 50 | ||||
| -rw-r--r-- | src/libnrtype/Layout-TNG-Input.cpp | 5 | ||||
| -rw-r--r-- | src/libnrtype/Layout-TNG-OutIter.cpp | 27 | ||||
| -rw-r--r-- | src/libnrtype/Layout-TNG-Output.cpp | 32 | ||||
| -rw-r--r-- | src/libnrtype/Layout-TNG.cpp | 9 | ||||
| -rw-r--r-- | src/libnrtype/Layout-TNG.h | 47 |
6 files changed, 144 insertions, 26 deletions
diff --git a/src/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp index bd1acbe46..13f4b9ab2 100644 --- a/src/libnrtype/Layout-TNG-Compute.cpp +++ b/src/libnrtype/Layout-TNG-Compute.cpp @@ -362,7 +362,7 @@ static void dumpUnbrokenSpans(ParagraphInfo *para){ } // todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing - // sum the glyph widths, letter spacing and word spacing to get the character width + // 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) { @@ -373,9 +373,10 @@ static void dumpUnbrokenSpans(ParagraphInfo *para){ span->end_glyph_index++; } if (char_attributes.is_cursor_position) - char_width += text_source->style->letter_spacing.computed; + char_width += text_source->style->letter_spacing.computed * _flow.getTextLengthMultiplierDue(); if (char_attributes.is_white) - char_width += text_source->style->word_spacing.computed; + char_width += text_source->style->word_spacing.computed * _flow.getTextLengthMultiplierDue(); + char_width += _flow.getTextLengthIncrementDue(); span->width += char_width; IFTRACE(char_count++); @@ -661,6 +662,14 @@ static void dumpUnbrokenSpans(ParagraphInfo *para){ new_glyph.in_character = _flow._characters.size(); new_glyph.rotation = glyph_rotate; + // 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; + /* put something like this back in when we do glyph-rotation-horizontal/vertical if (new_span.block_progression == LEFT_TO_RIGHT || new_span.block_progression == RIGHT_TO_LEFT) { new_glyph.x += new_span.line_height.ascent; @@ -743,9 +752,10 @@ static void dumpUnbrokenSpans(ParagraphInfo *para){ new_character.in_glyph = _flow._glyphs.size() - 1; _flow._characters.push_back(new_character); if (new_character.char_attributes.is_white) - advance_width += text_source->style->word_spacing.computed + add_to_each_whitespace; // justification + 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; + 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(); @@ -1111,8 +1121,8 @@ unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const UnbrokenSpan new_span; new_span.pango_item_index = -1; new_span.input_index = input_index; - new_span.line_height.ascent = control_code->ascent; - new_span.line_height.descent = control_code->descent; + new_span.line_height.ascent = control_code->ascent * _flow.getTextLengthMultiplierDue(); + new_span.line_height.descent = control_code->descent * _flow.getTextLengthMultiplierDue(); new_span.line_height.leading = 0.0; new_span.text_bytes = 0; new_span.char_index_in_para = char_index_in_para; @@ -1154,13 +1164,13 @@ unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) { if (text_source->x.size() > char_index_in_source) new_span.x = text_source->x[char_index_in_source]; if (text_source->y.size() > char_index_in_source) new_span.y = text_source->y[char_index_in_source]; - if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source]; - if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source]; + if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source].computed * _flow.getTextLengthMultiplierDue(); + if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source].computed * _flow.getTextLengthMultiplierDue(); } else { if (text_source->x.size() > char_index_in_source) new_span.y = text_source->x[char_index_in_source]; if (text_source->y.size() > char_index_in_source) new_span.x = text_source->y[char_index_in_source]; - if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source]; - if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source]; + if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source].computed * _flow.getTextLengthMultiplierDue(); + if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source].computed * _flow.getTextLengthMultiplierDue(); } if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source]; else if (char_index_in_source == 0) new_span.rotate = 0.f; @@ -1189,7 +1199,7 @@ unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const } // now we know the length, do some final calculations and add the UnbrokenSpan to the list - new_span.font_size = text_source->styleComputeFontSize(); + new_span.font_size = text_source->styleComputeFontSize() * _flow.getTextLengthMultiplierDue(); if (new_span.text_bytes) { new_span.glyph_string = pango_glyph_string_new(); /* Some assertions intended to help diagnose bug #1277746. */ @@ -1556,6 +1566,7 @@ bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const ¶, return true; } + /** The management function to start the whole thing off. */ bool Layout::Calculator::calculate() { @@ -1690,6 +1701,14 @@ bool Layout::Calculator::calculate() _flow._input_truncated = true; } + if (_flow.textLength._set) { + // Calculate the adjustment needed to meet the textLength + double actual_length = _flow.getActualLength(); + double difference = _flow.textLength.computed - actual_length; + _flow.textLengthMultiplier = (actual_length + difference) / actual_length; + _flow.textLengthIncrement = difference / (_flow._characters.size() == 1? 1 : _flow._characters.size() - 1); + } + return true; } @@ -1739,7 +1758,12 @@ void Layout::_calculateCursorShapeForEmpty() bool Layout::calculateFlow() { - bool result = Calculator(this).calculate(); + Layout::Calculator calc = Calculator(this); + bool result = calc.calculate(); + if (textLengthIncrement != 0) { + TRACE(("Recalculating layout the second time to fit textLength!\n")); + result = calc.calculate(); + } if (_characters.empty()) _calculateCursorShapeForEmpty(); return result; diff --git a/src/libnrtype/Layout-TNG-Input.cpp b/src/libnrtype/Layout-TNG-Input.cpp index cd9179c5f..84f3f260e 100644 --- a/src/libnrtype/Layout-TNG-Input.cpp +++ b/src/libnrtype/Layout-TNG-Input.cpp @@ -72,6 +72,11 @@ void Layout::appendText(Glib::ustring const &text, SPStyle *style, void *source_ last_rotate = *it; new_source->rotate.resize(1, last_rotate); } + new_source->textLength._set = optional_attributes->textLength._set; + new_source->textLength.value = optional_attributes->textLength.value; + new_source->textLength.computed = optional_attributes->textLength.computed; + new_source->textLength.unit = optional_attributes->textLength.unit; + new_source->lengthAdjust = optional_attributes->lengthAdjust; } _input_stream.push_back(new_source); diff --git a/src/libnrtype/Layout-TNG-OutIter.cpp b/src/libnrtype/Layout-TNG-OutIter.cpp index 6dc42d998..c9c318960 100644 --- a/src/libnrtype/Layout-TNG-OutIter.cpp +++ b/src/libnrtype/Layout-TNG-OutIter.cpp @@ -401,15 +401,17 @@ std::vector<Geom::Point> Layout::createSelectionShape(iterator const &it_start, bottom_right[Geom::X] = span_x + _characters[char_index].x; double baseline_y = _spans[span_index].line(this).baseline_y + _spans[span_index].baseline_shift; + double vertical_scale = _glyphs.back().vertical_scale; + if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) { - double span_height = _spans[span_index].line_height.ascent + _spans[span_index].line_height.descent; + double span_height = vertical_scale * (_spans[span_index].line_height.ascent + _spans[span_index].line_height.descent); top_left[Geom::Y] = top_left[Geom::X]; top_left[Geom::X] = baseline_y - span_height * 0.5; bottom_right[Geom::Y] = bottom_right[Geom::X]; bottom_right[Geom::X] = baseline_y + span_height * 0.5; } else { - top_left[Geom::Y] = baseline_y - _spans[span_index].line_height.ascent; - bottom_right[Geom::Y] = baseline_y + _spans[span_index].line_height.descent; + top_left[Geom::Y] = baseline_y - vertical_scale * _spans[span_index].line_height.ascent; + bottom_right[Geom::Y] = baseline_y + vertical_scale * _spans[span_index].line_height.descent; } } @@ -505,21 +507,23 @@ void Layout::queryCursorShape(iterator const &it, Geom::Point &position, double position[Geom::Y] = span->line(this).baseline_y + span->baseline_shift; } // up to now *position is the baseline point, not the final point which will be the bottom of the descent + double vertical_scale = _glyphs.back().vertical_scale; + if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) { - height = span->line_height.ascent + span->line_height.descent; + height = vertical_scale * span->line_height.ascent + span->line_height.descent; rotation += M_PI / 2; std::swap(position[Geom::X], position[Geom::Y]); - position[Geom::X] -= sin(rotation) * height * 0.5; - position[Geom::Y] += cos(rotation) * height * 0.5; + position[Geom::X] -= vertical_scale * sin(rotation) * height * 0.5; + position[Geom::Y] += vertical_scale * cos(rotation) * height * 0.5; } else { double caret_slope_run = 0.0, caret_slope_rise = 1.0; if (span->font) const_cast<font_instance*>(span->font)->FontSlope(caret_slope_run, caret_slope_rise); double caret_slope = atan2(caret_slope_run, caret_slope_rise); - height = (span->line_height.ascent + span->line_height.descent) / cos(caret_slope); + height = vertical_scale * (span->line_height.ascent + span->line_height.descent) / cos(caret_slope); rotation += caret_slope; - position[Geom::X] -= sin(rotation) * span->line_height.descent; - position[Geom::Y] += cos(rotation) * span->line_height.descent; + position[Geom::X] -= sin(rotation) * vertical_scale * span->line_height.descent; + position[Geom::Y] += cos(rotation) * vertical_scale * span->line_height.descent; } } } @@ -601,9 +605,10 @@ void Layout::simulateLayoutUsingKerning(iterator const &from, iterator const &to if (input_item->Type() == TEXT_SOURCE) { SPStyle const *style = static_cast<InputStreamTextSource*>(input_item)->style; if (_characters[char_index].char_attributes.is_white) - dx -= style->word_spacing.computed; + dx -= style->word_spacing.computed * getTextLengthMultiplierDue(); if (_characters[char_index].char_attributes.is_cursor_position) - dx -= style->letter_spacing.computed; + dx -= style->letter_spacing.computed * getTextLengthMultiplierDue(); + dx -= getTextLengthIncrementDue(); } if (fabs(dx) > 0.0001) { diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp index 61a141cc3..6e3faf33b 100644 --- a/src/libnrtype/Layout-TNG-Output.cpp +++ b/src/libnrtype/Layout-TNG-Output.cpp @@ -108,7 +108,7 @@ void Layout::_getGlyphTransformMatrix(int glyph_index, Geom::Affine *matrix) con (*matrix)[0] = span.font_size * cos_rotation; (*matrix)[1] = span.font_size * sin_rotation; (*matrix)[2] = span.font_size * sin_rotation; - (*matrix)[3] = -span.font_size * cos_rotation; + (*matrix)[3] = -span.font_size * cos_rotation * (_glyphs[glyph_index].vertical_scale); // unscale vertically so the specified text height is preserved if lengthAdjust=spacingAndGlyphs if (span.block_progression == LEFT_TO_RIGHT || span.block_progression == RIGHT_TO_LEFT) { (*matrix)[4] = _lines[_chunks[span.in_chunk].in_line].baseline_y + _glyphs[glyph_index].y; (*matrix)[5] = _chunks[span.in_chunk].left_x + _glyphs[glyph_index].x; @@ -804,6 +804,36 @@ void Layout::transform(Geom::Affine const &transform) } } +double Layout::getTextLengthIncrementDue() const +{ + if (textLength._set && textLengthIncrement != 0 && lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACING) { + return textLengthIncrement; + } + return 0; +} + + +double Layout::getTextLengthMultiplierDue() const +{ + if (textLength._set && textLengthMultiplier != 1 && (lengthAdjust == Inkscape::Text::Layout::LENGTHADJUST_SPACINGANDGLYPHS)) { + return textLengthMultiplier; + } + return 1; +} + +double Layout::getActualLength() const +{ + double length = 0; + for (std::vector<Span>::const_iterator it_span = _spans.begin() ; it_span != _spans.end() ; it_span++) { + // take x_end of the last span of each chunk + if (it_span == _spans.end() - 1 || (it_span + 1)->in_chunk != it_span->in_chunk) + length += it_span->x_end; + } + return length; + + +} + }//namespace Text }//namespace Inkscape diff --git a/src/libnrtype/Layout-TNG.cpp b/src/libnrtype/Layout-TNG.cpp index dd9c90949..8b0889188 100644 --- a/src/libnrtype/Layout-TNG.cpp +++ b/src/libnrtype/Layout-TNG.cpp @@ -20,6 +20,10 @@ Layout::Layout() : _input_truncated(0), _path_fitted(NULL) { + textLength._set = false; + textLengthMultiplier = 1; + textLengthIncrement = 0; + lengthAdjust = LENGTHADJUST_SPACING; } Layout::~Layout() @@ -31,6 +35,11 @@ void Layout::clear() { _clearInputObjects(); _clearOutputObjects(); + + textLength._set = false; + textLengthMultiplier = 1; + textLengthIncrement = 0; + lengthAdjust = LENGTHADJUST_SPACING; } bool Layout::_directions_are_orthogonal(Direction d1, Direction d2) diff --git a/src/libnrtype/Layout-TNG.h b/src/libnrtype/Layout-TNG.h index fd5bbfaef..e91c32ebe 100644 --- a/src/libnrtype/Layout-TNG.h +++ b/src/libnrtype/Layout-TNG.h @@ -21,6 +21,7 @@ #include <algorithm> #include <vector> #include <boost/optional.hpp> +#include <svg/svg-length.h> #ifdef HAVE_CAIRO_PDF namespace Inkscape { @@ -37,7 +38,6 @@ using Inkscape::Extension::Internal::CairoRenderContext; class SPStyle; class Shape; struct SPPrintContext; -class SVGLength; class Path; class SPCurve; class font_instance; @@ -160,6 +160,9 @@ public: /** Display alignment for shapes. See appendWrapShape(). */ enum DisplayAlign {DISPLAY_ALIGN_BEFORE, DISPLAY_ALIGN_CENTER, DISPLAY_ALIGN_AFTER}; + /** lengthAdjust values */ + enum LengthAdjust {LENGTHADJUST_SPACING, LENGTHADJUST_SPACINGANDGLYPHS}; + /** The optional attributes which can be applied to a SVG text or related tag. See appendText(). See SVG1.1 section 10.4 for the definitions of all these members. See sp_svg_length_list_read() for @@ -172,6 +175,8 @@ public: std::vector<SVGLength> dx; std::vector<SVGLength> dy; std::vector<SVGLength> rotate; + SVGLength textLength; + LengthAdjust lengthAdjust; }; /** Control codes which can be embedded in the text to be flowed. See @@ -290,6 +295,43 @@ public: //@} + // ************************** textLength and friends ************************* + + /** Gives the length target of this layout, as given by textLength attribute. + + FIXME: by putting it here we only support @textLength on text and flowRoot, not on any + spans inside. For spans, we will need to add markers of start and end of a textLength span + into the _input_stream. These spans can nest (SVG 1.1, section 10.5). After a first layout + calculation, we would go through the input stream and, for each end of a textLength span, + go through its items, choose those where it wasn't yet set by a nested span, calculate + their number of characters, divide the length deficit by it, and set set the + textLengthMultiplier for those characters only. For now we do this for the entire layout, + without dealing with spans. + */ + SVGLength textLength; + + /** How do we meet textLength if specified: by letterspacing or by scaling horizontally */ + LengthAdjust lengthAdjust; + + /** By how much each character needs to be wider or narrower, using the specified lengthAdjust + strategy, for the layout to meet its textLength target. Is set to non-zero after the layout + is calculated for the first time, then it is recalculated with each glyph getting its adjustment. */ + /** This one is used by scaling strategies: each glyph width is multiplied by this */ + double textLengthMultiplier; + /** This one is used by letterspacing strategy: to each glyph width, this is added */ + double textLengthIncrement; + + /** Get the actual spacing increment if it's due with the current values of above stuff, otherwise 0 */ + double getTextLengthIncrementDue() const; + /** Get the actual scale multiplier if it's due with the current values of above stuff, otherwise 1 */ + double getTextLengthMultiplierDue() const; + + /** Get actual length of layout, by summing span lengths. For one-line non-flowed text, just + the width; for multiline non-flowed, sum of lengths of all lines; for flowed text, sum of + scanline widths for all non-last lines plus text width of last line. */ + double getActualLength() const; + + // ************************** doing the actual flowing ************************* /** \name Processing @@ -618,6 +660,8 @@ private: std::vector<SVGLength> dx; std::vector<SVGLength> dy; std::vector<SVGLength> rotate; + SVGLength textLength; + LengthAdjust lengthAdjust; // a few functions for some of the more complicated style accesses float styleComputeFontSize() const; @@ -701,6 +745,7 @@ private: float y; /// relative to the current line's baseline float rotation; /// absolute, modulo any object transforms, which we don't know about float width; + float vertical_scale; /// to implement lengthAdjust="spacingAndGlyphs" that must scale glyphs only horizontally; instead we change font size and then undo that change vertically only inline Span const & span(Layout const *l) const {return l->_spans[l->_characters[in_character].in_span];} inline Chunk const & chunk(Layout const *l) const {return l->_chunks[l->_spans[l->_characters[in_character].in_span].in_chunk];} inline Line const & line(Layout const *l) const {return l->_lines[l->_chunks[l->_spans[l->_characters[in_character].in_span].in_chunk].in_line];} |
