summaryrefslogtreecommitdiffstats
path: root/src/libnrtype
diff options
context:
space:
mode:
authorTavmjong Bah <tavmjong@free.fr>2015-01-05 10:17:51 +0000
committertavmjong-free <tavmjong@free.fr>2015-01-05 10:17:51 +0000
commit374aa610cb550cf7dcd283ca93ad0ad844732d32 (patch)
tree067c340e29085c65fc3bde1b680ecfde1161bae1 /src/libnrtype
parentComment out error message about style ref counting and add explanation. (diff)
downloadinkscape-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.cpp50
-rw-r--r--src/libnrtype/Layout-TNG-Input.cpp5
-rw-r--r--src/libnrtype/Layout-TNG-OutIter.cpp27
-rw-r--r--src/libnrtype/Layout-TNG-Output.cpp32
-rw-r--r--src/libnrtype/Layout-TNG.cpp9
-rw-r--r--src/libnrtype/Layout-TNG.h47
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 &para,
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];}