diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /src/libnrtype/Layout-TNG-Output.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/libnrtype/Layout-TNG-Output.cpp | 910 |
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 : |