summaryrefslogtreecommitdiffstats
path: root/src/libnrtype/Layout-TNG-Output.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnrtype/Layout-TNG-Output.cpp')
-rw-r--r--src/libnrtype/Layout-TNG-Output.cpp910
1 files changed, 910 insertions, 0 deletions
diff --git a/src/libnrtype/Layout-TNG-Output.cpp b/src/libnrtype/Layout-TNG-Output.cpp
new file mode 100644
index 0000000..66a1516
--- /dev/null
+++ b/src/libnrtype/Layout-TNG-Output.cpp
@@ -0,0 +1,910 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Inkscape::Text::Layout - text layout engine output functions
+ *
+ * Authors:
+ * Richard Hughes <cyreve@users.sf.net>
+ *
+ * Copyright (C) 2005 Richard Hughes
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib.h>
+#include "Layout-TNG.h"
+#include "display/drawing-text.h"
+#include "style.h"
+#include "print.h"
+#include "extension/print.h"
+#include "livarot/Path.h"
+#include "font-instance.h"
+#include "svg/svg-length.h"
+#include "extension/internal/cairo-render-context.h"
+#include "display/curve.h"
+#include <2geom/pathvector.h>
+#include <3rdparty/libuemf/symbol_convert.h>
+
+
+using Inkscape::Extension::Internal::CairoRenderContext;
+using Inkscape::Extension::Internal::CairoGlyphInfo;
+
+namespace Inkscape {
+namespace Text {
+
+/*
+ dx array (character widths) and
+ ky (vertical kerning for entire span)
+ rtl (+1 for LTR, -1 RTL)
+
+ are smuggled through to the EMF (ignored by others) as:
+ text<nul>N w1 w2 w3 ...wN<nul>y1 y2 y3 .. yN<nul><nul>
+ The ndx, widths, y kern, and rtl are all 7 characters wide. ndx and rtl are ints, the widths and ky are
+ formatted as ' 6f'.
+*/
+char *smuggle_adxkyrtl_in(const char *string, int ndx, float *adx, float ky, float rtl){
+ int slen = strlen(string);
+ /* holds: string
+ fake terminator (one \0)
+ Number of widths (ndx)
+ series of widths (ndx entries)
+ fake terminator (one \0)
+ y kern value (one float)
+ rtl value (one float)
+ real terminator (two \0)
+ */
+ int newsize=slen + 1 + 7 + 7*ndx + 1 + 7 + 7 + 2;
+ newsize = 8*((7 + newsize)/8); // suppress valgrind messages if it is a multiple of 8 bytes???
+ char *smuggle=(char *)malloc(newsize);
+ strcpy(smuggle,string); // text to pass, includes the first fake terminator
+ char *cptr = smuggle + slen + 1; // immediately after the first fake terminator
+ sprintf(cptr,"%07d",ndx); // number of widths to pass
+ cptr+=7; // advance over ndx
+ for(int i=0; i<ndx ; i++){ // all the widths
+ sprintf(cptr," %6f",adx[i]);
+ cptr+=7; // advance over space + width
+ }
+ *cptr='\0';
+ cptr++; // second fake terminator
+ sprintf(cptr," %6f",ky); // y kern for span
+ cptr+=7; // advance over space + ky
+ sprintf(cptr," %6d",(int) rtl); // rtl multiplier for span
+ cptr+=7; // advance over rtl
+ *cptr++ = '\0'; // Set the real terminators
+ *cptr = '\0';
+ return(smuggle);
+}
+
+void Layout::_clearOutputObjects()
+{
+ _paragraphs.clear();
+ _lines.clear();
+ _chunks.clear();
+ for (auto & _span : _spans)
+ if (_span.font) _span.font->Unref();
+ _spans.clear();
+ _characters.clear();
+ _glyphs.clear();
+ _path_fitted = nullptr;
+}
+
+void Layout::FontMetrics::set(font_instance *font)
+{
+ if( font != nullptr ) {
+ ascent = font->GetTypoAscent();
+ descent = font->GetTypoDescent();
+ xheight = font->GetXHeight();
+ ascent_max = font->GetMaxAscent();
+ descent_max = font->GetMaxDescent();
+ }
+}
+
+void Layout::FontMetrics::max(FontMetrics const &other)
+{
+ if (other.ascent > ascent ) ascent = other.ascent;
+ if (other.descent > descent ) descent = other.descent;
+ if( other.xheight > xheight ) xheight = other.xheight;
+ if( other.ascent_max > ascent_max ) ascent_max = other.ascent_max;
+ if( other.descent_max > descent_max ) descent_max = other.descent_max;
+}
+
+void Layout::FontMetrics::computeEffective( const double &line_height_multiplier ) {
+ double half_leading = 0.5 * (line_height_multiplier - 1.0) * emSize();
+ ascent += half_leading;
+ descent += half_leading;
+}
+
+void Layout::_getGlyphTransformMatrix(int glyph_index, Geom::Affine *matrix) const
+{
+ Span const &span = _glyphs[glyph_index].span(this);
+ double rotation = _glyphs[glyph_index].rotation;
+ if ( (span.block_progression == LEFT_TO_RIGHT || span.block_progression == RIGHT_TO_LEFT) &&
+ _glyphs[glyph_index].orientation == ORIENTATION_SIDEWAYS ) {
+ // Vertical sideways text
+ rotation += M_PI/2.0;
+ }
+ double sin_rotation = sin(rotation);
+ double cos_rotation = cos(rotation);
+ (*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 * (_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) {
+ // Vertical text
+ // This effectively swaps x for y which changes handedness of coordinate system. This is a bit strange
+ // and not what one would expect but the compute code already reverses y so OK.
+ (*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;
+ } else {
+ // Horizontal text
+ (*matrix)[4] = _chunks[span.in_chunk].left_x + _glyphs[glyph_index].x;
+ (*matrix)[5] = _lines[_chunks[span.in_chunk].in_line].baseline_y + _glyphs[glyph_index].y;
+ }
+}
+
+void Layout::show(DrawingGroup *in_arena, Geom::OptRect const &paintbox) const
+{
+ int glyph_index = 0;
+ double phase0 = 0.0;
+ for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
+ if (_input_stream[_spans[span_index].in_input_stream_item]->Type() != TEXT_SOURCE) continue;
+
+ if (_spans[span_index].line(this).hidden) continue; // Line corresponds to text overflow. Don't show!
+
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[_spans[span_index].in_input_stream_item]);
+
+ text_source->style->text_decoration_data.tspan_width = _spans[span_index].width();
+ text_source->style->text_decoration_data.ascender = _spans[span_index].line_height.getTypoAscent();
+ text_source->style->text_decoration_data.descender = _spans[span_index].line_height.getTypoDescent();
+
+ if(!span_index ||
+ (_chunks[_spans[span_index].in_chunk].in_line != _chunks[_spans[span_index-1].in_chunk].in_line)){
+ text_source->style->text_decoration_data.tspan_line_start = true;
+ }
+ else {
+ text_source->style->text_decoration_data.tspan_line_start = false;
+ }
+ if((span_index == _spans.size() -1) ||
+ (_chunks[_spans[span_index].in_chunk].in_line != _chunks[_spans[span_index+1].in_chunk].in_line)){
+ text_source->style->text_decoration_data.tspan_line_end = true;
+ }
+ else {
+ text_source->style->text_decoration_data.tspan_line_end = false;
+ }
+ if(_spans[span_index].font){
+ double underline_thickness, underline_position, line_through_thickness,line_through_position;
+ _spans[span_index].font->FontDecoration(underline_position, underline_thickness, line_through_position, line_through_thickness);
+ text_source->style->text_decoration_data.underline_thickness = underline_thickness;
+ text_source->style->text_decoration_data.underline_position = underline_position;
+ text_source->style->text_decoration_data.line_through_thickness = line_through_thickness;
+ text_source->style->text_decoration_data.line_through_position = line_through_position;
+ }
+ else { // can this case ever occur?
+ text_source->style->text_decoration_data.underline_thickness =
+ text_source->style->text_decoration_data.underline_position =
+ text_source->style->text_decoration_data.line_through_thickness =
+ text_source->style->text_decoration_data.line_through_position = 0.0;
+ }
+
+ DrawingText *nr_text = new DrawingText(in_arena->drawing());
+
+ bool first_line_glyph = true;
+ while (glyph_index < (int)_glyphs.size() && _characters[_glyphs[glyph_index].in_character].in_span == span_index) {
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph != -1) {
+ Geom::Affine glyph_matrix;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ if(first_line_glyph && text_source->style->text_decoration_data.tspan_line_start){
+ first_line_glyph = false;
+ phase0 = glyph_matrix.translation()[Geom::X];
+ }
+ // Save the starting coordinates for the line - these are needed for figuring out
+ // dot/dash/wave phase.
+ // Use maximum ascent and descent to ensure glyphs that extend outside the embox
+ // are fully drawn.
+ (void) nr_text->addComponent(_spans[span_index].font, _glyphs[glyph_index].glyph, glyph_matrix,
+ _glyphs[glyph_index].advance,
+ _spans[span_index].line_height.getMaxAscent(),
+ _spans[span_index].line_height.getMaxDescent(),
+ glyph_matrix.translation()[Geom::X] - phase0
+ );
+ }
+ glyph_index++;
+ }
+ nr_text->setStyle(text_source->style);
+ nr_text->setItemBounds(paintbox);
+ // Text spans must be painted in the right order (see inkscape/685)
+ in_arena->appendChild(nr_text);
+ // Set item bounds without filter enlargement
+ in_arena->setItemBounds(paintbox);
+ }
+}
+
+Geom::OptRect Layout::bounds(Geom::Affine const &transform, bool with_stroke, int start, int length) const
+{
+ Geom::OptRect bbox;
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
+ if (_glyphs[glyph_index].hidden) continue; // To do: This and the next line should represent the same thing, use on or the other.
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) continue;
+ if (start != -1 && (int) _glyphs[glyph_index].in_character < start) continue;
+ if (length != -1) {
+ if (start == -1)
+ start = 0;
+ if ((int) _glyphs[glyph_index].in_character > start + length) continue;
+ }
+ // this could be faster
+ Geom::Affine glyph_matrix;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ Geom::Affine total_transform = glyph_matrix;
+ total_transform *= transform;
+ if(_glyphs[glyph_index].span(this).font) {
+ Geom::OptRect glyph_rect = _glyphs[glyph_index].span(this).font->BBox(_glyphs[glyph_index].glyph);
+ if (glyph_rect) {
+ auto glyph_box = *glyph_rect * total_transform;
+ // FIXME: Expand rectangle by half stroke width, this doesn't include meters
+ // and so is not the most ideal calculation, we could use the glyph Path here.
+ if (with_stroke) {
+ Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span];
+ auto text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
+ if (!text_source->style->stroke.isNone()) {
+ double scale = transform.descrim();
+ glyph_box.expandBy(0.5 * text_source->style->stroke_width.computed * scale);
+ }
+ }
+ bbox.unionWith(glyph_box);
+ }
+ }
+ }
+ return bbox;
+}
+
+/* This version is much simpler than the old one
+*/
+void Layout::print(SPPrintContext *ctx,
+ Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox,
+ Geom::Affine const &ctm) const
+{
+bool text_to_path = ctx->module->textToPath();
+#define MAX_DX 2048
+float hold_dx[MAX_DX]; // For smuggling dx values (character widths) into print functions, unlikely any simple text output will be longer than this.
+
+Geom::Affine glyph_matrix;
+
+ if (_input_stream.empty()) return;
+ if (!_glyphs.size()) return; // yes, this can happen.
+ if (text_to_path || _path_fitted) {
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1)continue; //invisible glyphs
+ Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span];
+ Geom::PathVector const * pv = span.font->PathVector(_glyphs[glyph_index].glyph);
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
+ if (pv) {
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ Geom::PathVector temp_pv = (*pv) * glyph_matrix;
+ if (!text_source->style->fill.isNone())
+ ctx->fill(temp_pv, ctm, text_source->style, pbox, dbox, bbox);
+ if (!text_source->style->stroke.isNone())
+ ctx->stroke(temp_pv, ctm, text_source->style, pbox, dbox, bbox);
+ }
+ }
+ }
+ else {
+ /* index by characters, referencing glyphs and spans only as needed */
+ double char_x;
+ int doUTN = CanUTN(); // Unicode to Nonunicode translation enabled if true
+ Direction block_progression = _blockProgression();
+ int oldtarget = 0;
+ int ndx = 0;
+ double rtl = 1.0; // 1 L->R, -1 R->L, constant across a span. 1.0 for t->b b->t???
+
+ for (unsigned char_index = 0 ; char_index < _characters.size() ; ) {
+ Glib::ustring text_string; // accumulate text for record in this
+ Geom::Point g_pos(0,0); // all strings are output at (0,0) because we do the translation using the matrix
+ int glyph_index = _characters[char_index].in_glyph;
+ if(glyph_index == -1){ // if the character maps to an invisible glyph we cannot know its geometry, so skip it and move on
+ char_index++;
+ continue;
+ }
+ float ky = _glyphs[glyph_index].y; // For smuggling y kern value for span // same value for all positions in a span
+ unsigned span_index = _characters[char_index].in_span;
+ Span const &span = _spans[span_index];
+ char_x = 0.0;
+ Glib::ustring::const_iterator text_iter = span.input_stream_first_character;
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
+ glyph_matrix = Geom::Scale(1.0, -1.0) * (Geom::Affine)Geom::Rotate(_glyphs[glyph_index].rotation);
+ if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT) {
+ glyph_matrix[4] = span.line(this).baseline_y + span.baseline_shift;
+ // since we're outputting character codes, not glyphs, we want the character x
+ glyph_matrix[5] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x;
+ } else {
+ glyph_matrix[4] = span.chunk(this).left_x + span.x_start + _characters[_glyphs[glyph_index].in_character].x;
+ glyph_matrix[5] = span.line(this).baseline_y + span.baseline_shift;
+ }
+ switch(span.direction){
+ case Layout::TOP_TO_BOTTOM:
+ case Layout::BOTTOM_TO_TOP:
+ case Layout::LEFT_TO_RIGHT: rtl = 1.0; break;
+ case Layout::RIGHT_TO_LEFT: rtl = -1.0; break;
+ }
+ if(doUTN){
+ oldtarget=SingleUnicodeToNon(*text_iter); // this should only ever be with a 1:1 glyph:character situation
+ }
+
+ // accumulate a record to write
+
+ unsigned lc_index = char_index;
+ unsigned hold_iisi = _spans[span_index].in_input_stream_item;
+ int newtarget = 0;
+ while(true){
+ glyph_index = _characters[lc_index].in_glyph;
+ if(glyph_index == -1){ // end of a line within a paragraph, for instance
+ lc_index++;
+ break;
+ }
+
+ // always append if here
+ text_string += *text_iter;
+
+ // figure out char widths, used by EMF, not currently used elsewhere
+ double cwidth;
+ if(lc_index == _glyphs[glyph_index].in_character){ // Glyph width is used only for the first character, these may be 0
+ cwidth = rtl * _glyphs[glyph_index].advance; // advance might be zero
+ }
+ else {
+ cwidth = 0;
+ }
+ char_x += cwidth;
+/*
+std:: cout << "DEBUG Layout::print in while "
+<< " char_index " << char_index
+<< " lc_index " << lc_index
+<< " character " << std::hex << (int) *text_iter << std::dec
+<< " glyph_index " << glyph_index
+<< " glyph_xy " << _glyphs[glyph_index].x << " , " << _glyphs[glyph_index].y
+<< " span_index " << span_index
+<< " hold_iisi " << hold_iisi
+<< std::endl; //DEBUG
+*/
+ if(ndx < MAX_DX){
+ hold_dx[ndx++] = fabs(cwidth);
+ }
+ else { // silently truncate any text line silly enough to be longer than MAX_DX
+ lc_index = _characters.size();
+ break;
+ }
+
+
+ // conditions that prevent this character from joining the record
+ lc_index++;
+ if(lc_index >= _characters.size()) break; // nothing more to process, so it must be the end of the record
+ ++text_iter;
+ if(doUTN)newtarget=SingleUnicodeToNon(*text_iter); // this should only ever be with a 1:1 glyph:character situation
+ if(newtarget != oldtarget)break; // change in unicode to nonunicode translation status
+ // MUST exit on any major span change, but not on some little events, like a font substitution event irrelevant for the file save
+ unsigned next_span_index = _characters[lc_index].in_span;
+ if(span_index != next_span_index){
+ /* on major changes break out of loop.
+ 1st case usually indicates an entire input line has been processed (out of several in a paragraph)
+ 2nd case usually indicates that a format change within a line (font/size/color/etc) is present.
+ */
+/*
+std:: cout << "DEBUG Layout::print in while --- "
+<< " char_index " << char_index
+<< " lc_index " << lc_index
+<< " cwidth " << cwidth
+<< " _char.x (next) " << (lc_index < _characters.size() ? _characters[lc_index].x : -1)
+<< " char_x (end this)" << char_x
+<< " diff " << fabs(char_x - _characters[lc_index].x)
+<< " oldy " << ky
+<< " nexty " << _glyphs[_characters[lc_index].in_glyph].y
+<< std::endl; //DEBUG
+*/
+ if(hold_iisi != _spans[next_span_index].in_input_stream_item)break; // major change, font, size, color, etc, must exit
+ if(fabs(char_x - _spans[next_span_index].x_start) >= 1e-4)break; // xkerning change
+ if(ky != _glyphs[_characters[lc_index].in_glyph].y)break; // ykerning change
+ /*
+ None of the above? Then this is a minor "pangito", update span_index and keep going.
+ The font used by the display may have failed over, but print does not care and can continue to use
+ whatever was specified in the XML.
+ */
+ span_index = next_span_index;
+ text_iter = _spans[span_index].input_stream_first_character;
+ }
+
+ }
+ // write it
+ ctx->bind(glyph_matrix, 1.0);
+
+ // the dx array is smuggled through to the EMF driver (ignored by others) as:
+ // text<nul>w1 w2 w3 ...wn<nul><nul>
+ // where the widths are floats 7 characters wide, including the space
+
+ char *smuggle_string=smuggle_adxkyrtl_in(text_string.c_str(),ndx, &hold_dx[0], ky, rtl);
+ ctx->text(smuggle_string, g_pos, text_source->style);
+ free(smuggle_string);
+ ctx->release();
+ ndx = 0;
+ char_index = lc_index;
+ }
+ }
+}
+
+
+void Layout::showGlyphs(CairoRenderContext *ctx) const
+{
+ if (_input_stream.empty()) return;
+
+ bool clip_mode = false;//(ctx->getRenderMode() == CairoRenderContext::RENDER_MODE_CLIP);
+ std::vector<CairoGlyphInfo> glyphtext;
+
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; ) {
+ if (_characters[_glyphs[glyph_index].in_character].in_glyph == -1) {
+ // invisible glyphs
+ unsigned same_character = _glyphs[glyph_index].in_character;
+ while (_glyphs[glyph_index].in_character == same_character) {
+ glyph_index++;
+ if (glyph_index == _glyphs.size())
+ return;
+ }
+ continue;
+ }
+ Span const &span = _spans[_characters[_glyphs[glyph_index].in_character].in_span];
+ InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream[span.in_input_stream_item]);
+
+ Geom::Affine glyph_matrix;
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+ if (clip_mode) {
+ Geom::PathVector const *pathv = span.font->PathVector(_glyphs[glyph_index].glyph);
+ if (pathv) {
+ Geom::PathVector pathv_trans = (*pathv) * glyph_matrix;
+ SPStyle const *style = text_source->style;
+ ctx->renderPathVector(pathv_trans, style, Geom::OptRect());
+ }
+ glyph_index++;
+ continue;
+ }
+
+ Geom::Affine font_matrix = glyph_matrix;
+ font_matrix[4] = 0;
+ font_matrix[5] = 0;
+
+ Glib::ustring::const_iterator span_iter = span.input_stream_first_character;
+ unsigned char_index = _glyphs[glyph_index].in_character;
+ unsigned original_span = _characters[char_index].in_span;
+ while (char_index && _characters[char_index - 1].in_span == original_span) {
+ char_index--;
+ ++span_iter;
+ }
+
+ // try to output as many characters as possible in one go
+ Glib::ustring span_string;
+ unsigned this_span_index = _characters[_glyphs[glyph_index].in_character].in_span;
+ unsigned int first_index = glyph_index;
+ glyphtext.clear();
+ do {
+ span_string += *span_iter;
+ ++span_iter;
+
+ unsigned same_character = _glyphs[glyph_index].in_character;
+ while (glyph_index < _glyphs.size() && _glyphs[glyph_index].in_character == same_character) {
+ if (glyph_index != first_index)
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+
+ CairoGlyphInfo info;
+ info.index = _glyphs[glyph_index].glyph;
+ // this is the translation for x,y-offset
+ info.x = glyph_matrix[4];
+ info.y = glyph_matrix[5];
+
+ glyphtext.push_back(info);
+
+ glyph_index++;
+ }
+ } while (glyph_index < _glyphs.size()
+ && _path_fitted == nullptr
+ && (font_matrix * glyph_matrix.inverse()).isIdentity()
+ && _characters[_glyphs[glyph_index].in_character].in_span == this_span_index);
+
+ // remove vertical flip
+ Geom::Affine flip_matrix;
+ flip_matrix.setIdentity();
+ flip_matrix[3] = -1.0;
+ font_matrix = flip_matrix * font_matrix;
+
+ SPStyle const *style = text_source->style;
+ float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value);
+
+ if (opacity != 1.0) {
+ ctx->pushState();
+ ctx->setStateForStyle(style);
+ ctx->pushLayer();
+ }
+ if (glyph_index - first_index > 0)
+ ctx->renderGlyphtext(span.font->pFont, font_matrix, glyphtext, style);
+ if (opacity != 1.0) {
+ ctx->popLayer();
+ ctx->popState();
+ }
+ }
+}
+
+#if DEBUG_TEXTLAYOUT_DUMPASTEXT
+// these functions are for dumpAsText() only. No need to translate
+static char const *direction_to_text(Layout::Direction d)
+{
+ switch (d) {
+ case Layout::LEFT_TO_RIGHT: return "ltr";
+ case Layout::RIGHT_TO_LEFT: return "rtl";
+ case Layout::TOP_TO_BOTTOM: return "ttb";
+ case Layout::BOTTOM_TO_TOP: return "btt";
+ }
+ return "???";
+}
+
+static char const *style_to_text(PangoStyle s)
+{
+ switch (s) {
+ case PANGO_STYLE_NORMAL: return "upright";
+ case PANGO_STYLE_ITALIC: return "italic";
+ case PANGO_STYLE_OBLIQUE: return "oblique";
+ }
+ return "???";
+}
+
+static std::string weight_to_text(PangoWeight w)
+{
+ switch (w) {
+ case PANGO_WEIGHT_THIN : return "thin";
+ case PANGO_WEIGHT_ULTRALIGHT: return "ultralight";
+ case PANGO_WEIGHT_LIGHT : return "light";
+ case PANGO_WEIGHT_SEMILIGHT : return "semilight";
+ case PANGO_WEIGHT_BOOK : return "book";
+ case PANGO_WEIGHT_NORMAL : return "normalweight";
+ case PANGO_WEIGHT_MEDIUM : return "medium";
+ case PANGO_WEIGHT_SEMIBOLD : return "semibold";
+ case PANGO_WEIGHT_BOLD : return "bold";
+ case PANGO_WEIGHT_ULTRABOLD : return "ultrabold";
+ case PANGO_WEIGHT_HEAVY : return "heavy";
+ case PANGO_WEIGHT_ULTRAHEAVY: return "ultraheavy";
+ }
+ return std::to_string(w);
+}
+#endif //DEBUG_TEXTLAYOUT_DUMPASTEXT
+
+Glib::ustring Layout::getFontFamily(unsigned span_index) const
+{
+ if (span_index >= _spans.size())
+ return "";
+
+ if (_spans[span_index].font) {
+ return sp_font_description_get_family(_spans[span_index].font->descr);
+ }
+
+ return "";
+}
+
+#if DEBUG_TEXTLAYOUT_DUMPASTEXT
+Glib::ustring Layout::dumpAsText() const
+{
+ Glib::ustring result;
+ Glib::ustring::const_iterator icc;
+ char line[256];
+
+ result = Glib::ustring::compose("spans %1\nchars %2\nglyphs %3\n", _spans.size(), _characters.size(), _glyphs.size());
+ if(_characters.size() > 1){
+ unsigned lastspan=5000;
+ for(unsigned j = 0; j < _characters.size() ; j++){
+ if(lastspan != _characters[j].in_span){
+ lastspan = _characters[j].in_span;
+ icc = _spans[lastspan].input_stream_first_character;
+ }
+ snprintf(line, sizeof(line), "char %4u: '%c' 0x%4.4x x=%8.4f glyph=%3d span=%3d\n", j, *icc, *icc, _characters[j].x, _characters[j].in_glyph, _characters[j].in_span);
+ result += line;
+ ++icc;
+ }
+ }
+ if(_glyphs.size()){
+ for(unsigned j = 0; j < _glyphs.size() ; j++){
+ snprintf(line, sizeof(line), "glyph %4u: %4d (%8.4f,%8.4f) rot=%8.4f cx=%8.4f char=%4d\n",
+ j, _glyphs[j].glyph, _glyphs[j].x, _glyphs[j].y, _glyphs[j].rotation, _glyphs[j].width, _glyphs[j].in_character);
+ result += line;
+ }
+ }
+
+ for (unsigned span_index = 0 ; span_index < _spans.size() ; span_index++) {
+ result += Glib::ustring::compose("==== span %1 \n", span_index)
+ + Glib::ustring::compose(" in para %1 (direction=%2)\n", _lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph,
+ direction_to_text(_paragraphs[_lines[_chunks[_spans[span_index].in_chunk].in_line].in_paragraph].base_direction))
+ + Glib::ustring::compose(" in source %1 (type=%2, cookie=%3)\n", _spans[span_index].in_input_stream_item,
+ _input_stream[_spans[span_index].in_input_stream_item]->Type(),
+ _input_stream[_spans[span_index].in_input_stream_item]->source)
+ + Glib::ustring::compose(" in line %1 (baseline=%2, shape=%3)\n", _chunks[_spans[span_index].in_chunk].in_line,
+ _lines[_chunks[_spans[span_index].in_chunk].in_line].baseline_y,
+ _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) {
+ const char* variations = pango_font_description_get_variations(_spans[span_index].font->descr);
+ result += Glib::ustring::compose(
+ " font '%1' %2 %3 %4 %5\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) ),
+ (variations?variations:"")
+ );
+ }
+ 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)
+ + Glib::ustring::compose(" direction %1, block-progression %2\n", direction_to_text(_spans[span_index].direction), direction_to_text(_spans[span_index].block_progression))
+ + " ** characters:\n";
+ Glib::ustring::const_iterator iter_char = _spans[span_index].input_stream_first_character;
+ // very inefficient code. what the hell, it's only debug stuff.
+ for (unsigned char_index = 0 ; char_index < _characters.size() ; char_index++) {
+ union {const PangoLogAttr* pattr; const unsigned* uattr;} u;
+ u.pattr = &_characters[char_index].char_attributes;
+ if (_characters[char_index].in_span != span_index) continue;
+ if (_input_stream[_spans[span_index].in_input_stream_item]->Type() != TEXT_SOURCE) {
+ snprintf(line, sizeof(line), " %u: control x=%f flags=%03x glyph=%d\n", char_index, _characters[char_index].x, *u.uattr, _characters[char_index].in_glyph);
+ } else { // some text has empty tspans, iter_char cannot be dereferenced
+ snprintf(line, sizeof(line), " %u: '%c' 0x%4.4x x=%f flags=%03x glyph=%d\n", char_index, *iter_char, *iter_char, _characters[char_index].x, *u.uattr, _characters[char_index].in_glyph);
+ ++iter_char;
+ }
+ result += line;
+ }
+ result += " ** glyphs:\n";
+ for (unsigned glyph_index = 0 ; glyph_index < _glyphs.size() ; glyph_index++) {
+ if (_characters[_glyphs[glyph_index].in_character].in_span != span_index) continue;
+ snprintf(line, sizeof(line), " %u: %d (%f,%f) rot=%f cx=%f char=%d\n", glyph_index, _glyphs[glyph_index].glyph, _glyphs[glyph_index].x, _glyphs[glyph_index].y, _glyphs[glyph_index].rotation, _glyphs[glyph_index].width, _glyphs[glyph_index].in_character);
+ result += line;
+ }
+ result += "\n";
+ }
+ result += "EOT\n";
+ return result;
+}
+#endif //DEBUG_TEXTLAYOUT_DUMPASTEXT
+
+void Layout::fitToPathAlign(SVGLength const &startOffset, Path const &path)
+{
+ double offset = 0.0;
+
+ if (startOffset._set) {
+ if (startOffset.unit == SVGLength::PERCENT)
+ offset = startOffset.computed * const_cast<Path&>(path).Length();
+ else
+ offset = startOffset.computed;
+ }
+
+ Alignment alignment = _paragraphs.empty() ? LEFT : _paragraphs.front().alignment;
+ switch (alignment) {
+ case CENTER:
+ offset -= _getChunkWidth(0) * 0.5;
+ break;
+ case RIGHT:
+ offset -= _getChunkWidth(0);
+ break;
+ default:
+ break;
+ }
+
+ if (_characters.empty()) {
+ int unused = 0;
+ Path::cut_position *point_otp = const_cast<Path&>(path).CurvilignToPosition(1, &offset, unused);
+ if (offset >= 0.0 && point_otp != nullptr && point_otp[0].piece >= 0) {
+ Geom::Point point;
+ Geom::Point tangent;
+ const_cast<Path&>(path).PointAndTangentAt(point_otp[0].piece, point_otp[0].t, point, tangent);
+ _empty_cursor_shape.position = point;
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ _empty_cursor_shape.rotation = atan2(-tangent[Geom::X], tangent[Geom::Y]);
+ } else {
+ _empty_cursor_shape.rotation = atan2(tangent[Geom::Y], tangent[Geom::X]);
+ }
+ }
+ }
+
+ for (unsigned char_index = 0 ; char_index < _characters.size() ; ) {
+ Span const &span = _characters[char_index].span(this);
+
+ size_t next_cluster_char_index = 0; // TODO refactor to not bump via for loops
+ for (next_cluster_char_index = char_index + 1 ; next_cluster_char_index < _characters.size() ; next_cluster_char_index++) {
+ if (_characters[next_cluster_char_index].in_glyph != -1 && _characters[next_cluster_char_index].char_attributes.is_cursor_position)
+ {
+ break;
+ }
+ }
+
+ size_t next_cluster_glyph_index = 0;
+ if (next_cluster_char_index == _characters.size()) {
+ next_cluster_glyph_index = _glyphs.size();
+ } else {
+ next_cluster_glyph_index = _characters[next_cluster_char_index].in_glyph;
+ }
+
+ double start_offset = offset + span.x_start + _characters[char_index].x;
+ double cluster_width = 0.0;
+ size_t const current_cluster_glyph_index = _characters[char_index].in_glyph;
+ for (size_t glyph_index = current_cluster_glyph_index ; glyph_index < next_cluster_glyph_index ; glyph_index++)
+ {
+ cluster_width += _glyphs[glyph_index].advance;
+ }
+ // TODO block progression?
+ if (span.direction == RIGHT_TO_LEFT)
+ {
+ start_offset -= cluster_width;
+ }
+ double end_offset = start_offset + cluster_width;
+
+ int unused = 0;
+ double midpoint_offset = (start_offset + end_offset) * 0.5;
+ // as far as I know these functions are const, they're just not marked as such
+ Path::cut_position *midpoint_otp = const_cast<Path&>(path).CurvilignToPosition(1, &midpoint_offset, unused);
+ if (midpoint_offset >= 0.0 && midpoint_otp != nullptr && midpoint_otp[0].piece >= 0) {
+ Geom::Point midpoint;
+ Geom::Point tangent;
+ const_cast<Path&>(path).PointAndTangentAt(midpoint_otp[0].piece, midpoint_otp[0].t, midpoint, tangent);
+
+ if (start_offset >= 0.0 && end_offset >= 0.0) {
+ Path::cut_position *start_otp = const_cast<Path&>(path).CurvilignToPosition(1, &start_offset, unused);
+ if (start_otp != nullptr && start_otp[0].piece >= 0) {
+ Path::cut_position *end_otp = const_cast<Path&>(path).CurvilignToPosition(1, &end_offset, unused);
+ if (end_otp != nullptr && end_otp[0].piece >= 0) {
+ bool on_same_subpath = true;
+ for (const auto & pt : path.pts) {
+ if (pt.piece <= start_otp[0].piece) continue;
+ if (pt.piece >= end_otp[0].piece) break;
+ if (pt.isMoveTo == polyline_moveto) {
+ on_same_subpath = false;
+ break;
+ }
+ }
+ if (on_same_subpath) {
+ // both points were on the same subpath (without this test the angle is very weird)
+ Geom::Point startpoint, endpoint;
+ const_cast<Path&>(path).PointAt(start_otp[0].piece, start_otp[0].t, startpoint);
+ const_cast<Path&>(path).PointAt(end_otp[0].piece, end_otp[0].t, endpoint);
+ if (endpoint != startpoint) {
+ tangent = endpoint - startpoint;
+ tangent.normalize();
+ }
+ }
+ g_free(end_otp);
+ }
+ g_free(start_otp);
+ }
+ }
+
+ if (_directions_are_orthogonal(_blockProgression(), TOP_TO_BOTTOM)) {
+ double rotation = atan2(-tangent[Geom::X], tangent[Geom::Y]);
+ for (size_t glyph_index = current_cluster_glyph_index; glyph_index < next_cluster_glyph_index ; glyph_index++) {
+ _glyphs[glyph_index].x = midpoint[Geom::Y] - tangent[Geom::X] * _glyphs[glyph_index].y - span.chunk(this).left_x;
+ _glyphs[glyph_index].y = midpoint[Geom::X] + tangent[Geom::Y] * _glyphs[glyph_index].y - _lines.front().baseline_y;
+ _glyphs[glyph_index].rotation += rotation;
+ }
+ } else {
+ double rotation = atan2(tangent[Geom::Y], tangent[Geom::X]);
+ for (size_t glyph_index = current_cluster_glyph_index; glyph_index < next_cluster_glyph_index ; glyph_index++) {
+ double tangent_shift = -cluster_width * 0.5 + _glyphs[glyph_index].x - (_characters[char_index].x + span.x_start);
+ if (span.direction == RIGHT_TO_LEFT)
+ {
+ tangent_shift += cluster_width;
+ }
+ _glyphs[glyph_index].x = midpoint[Geom::X] + tangent[Geom::X] * tangent_shift - tangent[Geom::Y] * _glyphs[glyph_index].y - span.chunk(this).left_x;
+ _glyphs[glyph_index].y = midpoint[Geom::Y] + tangent[Geom::Y] * tangent_shift + tangent[Geom::X] * _glyphs[glyph_index].y - _lines.front().baseline_y;
+ _glyphs[glyph_index].rotation += rotation;
+ }
+ }
+ _input_truncated = false;
+ } else { // outside the bounds of the path: hide the glyphs
+ _characters[char_index].in_glyph = -1;
+ _input_truncated = true;
+ }
+ g_free(midpoint_otp);
+
+ char_index = next_cluster_char_index;
+ }
+
+ for (auto & _span : _spans) {
+ _span.x_start += offset;
+ _span.x_end += offset;
+ }
+
+ _path_fitted = &path;
+}
+
+std::unique_ptr<SPCurve> Layout::convertToCurves() const
+{
+ return convertToCurves(begin(), end());
+}
+
+std::unique_ptr<SPCurve> Layout::convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const
+{
+ auto curve = std::make_unique<SPCurve>();
+
+ for (int glyph_index = from_glyph._glyph_index ; glyph_index < to_glyph._glyph_index ; glyph_index++) {
+ Geom::Affine glyph_matrix;
+ Span const &span = _glyphs[glyph_index].span(this);
+ _getGlyphTransformMatrix(glyph_index, &glyph_matrix);
+
+ Geom::PathVector const * pathv = span.font->PathVector(_glyphs[glyph_index].glyph);
+ if (pathv) {
+ Geom::PathVector pathv_trans = (*pathv) * glyph_matrix;
+ curve->append(SPCurve(std::move(pathv_trans)));
+ }
+ }
+
+ return curve;
+}
+
+void Layout::transform(Geom::Affine const &transform)
+{
+ // this is all massively oversimplified
+ // I can't actually think of anybody who'll want to use it at the moment, so it'll stay simple
+ for (auto & _glyph : _glyphs) {
+ Geom::Point point(_glyph.x, _glyph.y);
+ point *= transform;
+ _glyph.x = point[0];
+ _glyph.y = point[1];
+ }
+}
+
+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
+
+std::ostream &operator<<(std::ostream &out, const Inkscape::Text::Layout::FontMetrics &f) {
+ out << " emSize: " << f.emSize()
+ << " ascent: " << f.ascent
+ << " descent: " << f.descent
+ << " xheight: " << f.xheight;
+ return out;
+}
+
+std::ostream &operator<<(std::ostream &out, const Inkscape::Text::Layout::FontMetrics *f) {
+ out << " emSize: " << f->emSize()
+ << " ascent: " << f->ascent
+ << " descent: " << f->descent
+ << " xheight: " << f->xheight;
+ return out;
+}
+
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :