// SPDX-License-Identifier: GPL-2.0-or-later /* * Inkscape::Text::Layout - text layout engine * * Authors: * Richard Hughes * * Copyright (C) 2005 Richard Hughes * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #ifndef __LAYOUT_TNG_H__ #define __LAYOUT_TNG_H__ //#define DEBUG_TEXTLAYOUT_DUMPASTEXT #include <2geom/d2.h> #include <2geom/affine.h> #include #include #include #include #include #include #include #include "style-enums.h" namespace Inkscape { namespace Extension { namespace Internal { class CairoRenderContext; } } } using Inkscape::Extension::Internal::CairoRenderContext; class SPStyle; class SPObject; class Shape; struct SPPrintContext; class Path; class SPCurve; class font_instance; typedef struct _PangoFontDescription PangoFontDescription; namespace Inkscape { class DrawingGroup; namespace Text { /** \brief Generates the layout for either wrapped or non-wrapped text and stores the result Use this class for all your text output needs. It takes text with formatting markup as input and turns that into the glyphs and their necessary positions. It stores the glyphs internally, but maintains enough information to both retrieve your own rendering information if you wish and to perform visual text editing where the output refers back to where it came from. Usage: -# Construct -# Set the text using appendText() and appendControlCode() -# If you want text wrapping, call appendWrapShape() a few times -# Call calculateFlow() -# You can go several directions from here, but the most interesting things start with creating a Layout::iterator with begin() or end(). Terminology, in descending order of size: - Flow: Not often used, but when it is it means all the text - Shape: A Shape object which is used to represent one of the regions inside which to flow the text. Can overlap with... - Paragraph: Err...A paragraph. Contains one or more... - Line: An entire horizontal line with a common baseline. Contains one or more... - Chunk: You only get more than one of these when a shape is sufficiently complex that the text has to flow either side of some obstruction in the middle. A chunk is the base unit for wrapping. Contains one or more... - Span: A convenient subset of a chunk with the same font, style, directionality, block progression and input stream. Fill and outline need not be constant because that's a later rendering stage. - This is where it gets weird because a span will contain one or more elements of both of the following, which can overlap with each other in any way: - Character: a single Unicode codepoint from an input stream. Many arabic characters contain multiple glyphs - Glyph: a rendering primitive for font engines. A ligature glyph will represent multiple characters. Other terminology: - Input stream: An object representing a single call to appendText() or appendControlCode(). - Control code: Metadata in the text stream to signify items that occupy real space (unlike style changes) but don't belong in the text string. Paragraph breaks are in this category. See Layout::TextControlCode. - SVG1.1: The W3C Recommendation "Scalable Vector Graphics (SVG) 1.1" http://www.w3.org/TR/SVG11/ - 'left', 'down', etc: These terms are generally used to mean what they mean in left-to-right, top-to-bottom text but rotated or reflected for the current directionality. Thus, the 'width' of a ttb line is actually its height, and the (internally stored) y coordinate of a glyph is actually its x coordinate. Confusing to the reader but much simpler in the code. All public methods use real x and y. Comments: - There's a strong emphasis on international support in this class, but that's primarily because once you can display all the insane things required by various languages, simple things like styling text are almost trivial. - There are a few places (appendText() is one) where pointers are held to caller-owned objects and used for quite a long time. This is messy but is safe for our usage scenario and in many cases the cost of copying the objects is quite high. - "Why isn't foo here?": Ask yourself if it's possible to implement foo externally using iterators. However this may not mean that it doesn't belong as a member, though. - I've used floats rather than doubles to store relative distances in some places (internal only) where it would save significant amounts of memory. The SVG spec allows you to do this as long as intermediate calculations are done double. Very very long lines might not finish precisely where you want, but that's to be expected with any typesetting. Also, SVGLength only uses floats. - If you look at the six arrays for holding the output data you'll realise that there's no O(1) way to drill down from a paragraph to find its starting glyph. This was a conscious decision to reduce complexity and to save memory. Drilling down isn't actually that slow because a binary chop will work nicely. Add this to the realisation that most of the times you do this will be in response to user actions and hence you only need to be faster than the user and I think the design makes sense. - There are a massive number of functions acting on Layout::iterator. A large number are trivial and will be inline, but is it really necessary to have all these, especially when some can be implemented by the caller using the others? - The separation of methods between Layout and Layout::iterator is a bit arbitrary, because many methods could go in either. I've used the STL model where the iterator itself can only move around; the base class is required to do anything interesting. - I use Pango internally, not Pangomm. The reason for this is lots of Pangomm methods take Glib::ustrings as input and then output byte offsets within the strings. There's simply no way to use byte offsets with ustrings without some very entertaining reinterpret_cast<>s. The Pangomm docs seem to be lacking quite a lot of things mentioned in the Pango docs, too. */ class Layout { public: class iterator; friend class iterator; class Calculator; friend class Calculator; class ScanlineMaker; class InfiniteScanlineMaker; class ShapeScanlineMaker; Layout(); virtual ~Layout(); /** Used to specify any particular text direction required. Used for both the 'direction' and 'block-progression' CSS attributes. */ enum Direction {LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP}; /** Used to specify orientation of glyphs in vertical text. */ enum Orientation {ORIENTATION_UPRIGHT, ORIENTATION_SIDEWAYS}; /** 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}; enum WrapMode { WRAP_NONE, // No wrapping or wrapping via role="line". WRAP_WHITE_SPACE, // Wrapping via 'white-space' property. WRAP_INLINE_SIZE, // Wrapping via 'inline-size' property. WRAP_SHAPE_INSIDE // Wrapping via 'shape-inside' propertry. } wrap_mode = WRAP_NONE; /** 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 the standard way to make these vectors. It is the responsibility of the caller to deal with the inheritance of these values using its knowledge of the parse tree. */ struct OptionalTextTagAttrs { std::vector x; std::vector y; std::vector dx; std::vector dy; std::vector rotate; SVGLength textLength; LengthAdjust lengthAdjust; }; /** Control codes which can be embedded in the text to be flowed. See appendControlCode(). */ enum TextControlCode { PARAGRAPH_BREAK, /// forces the flow to move on to the next line SHAPE_BREAK, /// forces the flow to ignore the remainder of the current shape (from #flow_inside_shapes) and continue at the top of the one after. ARBITRARY_GAP /// inserts an arbitrarily-sized hole in the flow in line with the current text. }; /** For expressing paragraph alignment. These values are rotated in the case of vertical text, but are not dependent on whether the paragraph is rtl or ltr, thus LEFT is always either left or top. */ enum Alignment {LEFT, CENTER, RIGHT, FULL, NONE}; /** The CSS spec allows line-height:normal to be whatever the user agent thinks will look good. This is our value, as a multiple of font-size. */ static const double LINE_HEIGHT_NORMAL; // ************************** describing the stuff to flow ************************* /** \name Input Methods for describing the text you want to flow, its style, and the shapes to flow in to. */ //@{ /** Empties everything stored in this class and resets it to its original state, like when it was created. All iterators on this object will be invalidated (but can be revalidated using validateIterator(). */ void clear(); /** Queries whether any calls have been made to appendText() or appendControlCode() since the object was last cleared. */ bool inputExists() const {return !_input_stream.empty();} bool _input_truncated = false; bool inputTruncated() const {return _input_truncated;} /** adds a new piece of text to the end of the current list of text to be processed. This method can only add text of a consistent style. To add lots of different styles, call it lots of times. \param text The text. \b Note: only a \em pointer is stored. Do not mess with the text until after you have called calculateFlow(). \param style The font style. Layout will hold a reference to this object for the duration of its ownership, ie until you call clear() or the class is destroyed. Must not be NULL. \param source Pointer to object that is source of text. \param optional_attributes A structure containing additional options for this text. See OptionalTextTagAttrs. The values are copied to internal storage before this method returns. \param optional_attributes_offset It is convenient for callers to be able to use the same \a optional_attributes structure for several sequential text fields, in which case the vectors will need to be offset. This parameter causes the nth element of all the vectors to be read as if it were the first. \param text_begin Used for selecting only a substring of \a text to process. \param text_end Used for selecting only a substring of \a text to process. */ void appendText(Glib::ustring const &text, SPStyle *style, SPObject *source, OptionalTextTagAttrs const *optional_attributes, unsigned optional_attributes_offset, Glib::ustring::const_iterator text_begin, Glib::ustring::const_iterator text_end); inline void appendText(Glib::ustring const &text, SPStyle *style, SPObject *source, OptionalTextTagAttrs const *optional_attributes = nullptr, unsigned optional_attributes_offset = 0) {appendText(text, style, source, optional_attributes, optional_attributes_offset, text.begin(), text.end());} /** Control codes are metadata in the text stream to signify items that occupy real space (unlike style changes) but don't belong in the text string. See TextControlCode for the types available. A control code \em cannot be the first item in the input stream. Use appendText() with an empty string to set up the paragraph properties. \param code A member of the TextFlowControlCode enumeration. \param width The width in pixels that this item occupies. \param ascent The number of pixels above the text baseline that this control code occupies. \param descent The number of pixels below the text baseline that this control code occupies. \param source Pointer to object that is source of control code. Note that for some control codes (eg tab) the values of the \a width, \a ascender and \a descender are implied by the surrounding text (and in the case of tabs, the values set in tab_stops) so the values you pass here are ignored. */ void appendControlCode(TextControlCode code, SPObject *source, double width = 0.0, double ascent = 0.0, double descent = 0.0); /** Stores another shape inside which to flow the text. If this method is never called then no automatic wrapping is done and lines will continue to infinity if necessary. Text can be flowed inside multiple shapes in sequence, like with frames in a DTP package. If the text flows past the end of the last shape all remaining text is ignored. \param shape The Shape to use next in the flow. The storage for this is managed by the caller, and need only be valid for the duration of the call to calculateFlow(). \param display_align The vertical alignment of the text within this shape. See XSL1.0 section 7.13.4. The behaviour of settings other than DISPLAY_ALIGN_BEFORE when using non-rectangular shapes is undefined. */ void appendWrapShape(Shape const *shape, DisplayAlign display_align = DISPLAY_ALIGN_BEFORE); // ************************** 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 = LENGTHADJUST_SPACING; /** 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 = 1; /** This one is used by letterspacing strategy: to each glyph width, this is added */ double textLengthIncrement = 0; /** 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 The method to do the actual work of converting text into glyphs. */ //@{ /** Takes all the stuff you set with the members above here and creates a load of glyphs for use with the members below here. All iterators on this object will be invalidated (but can be fixed with validateIterator(). The implementation just creates a new Layout::Calculator and calls its Calculator::Calculate() method, so if you want more details on the internals, go there. \return false on failure. */ bool calculateFlow(); //@} // ************************** operating on the output glyphs ************************* /** \name Output Methods for reading and interpreting the output glyphs. See also Layout::iterator. */ //@{ /** Returns true if there are some glyphs in this object, ie whether computeFlow() has been called on a non-empty input since the object was created or the last call to clear(). */ inline bool outputExists() const {return !_characters.empty();} /** Adds all the output glyphs to \a in_arena using the given \a paintbox. \param in_arena The arena to add the glyphs group to \param paintbox The current rendering tile */ void show(DrawingGroup *in_arena, Geom::OptRect const &paintbox) const; /** Calculates the smallest rectangle completely enclosing all the glyphs. \param bounding_box Where to store the box \param transform The transform to be applied to the entire object prior to calculating its bounds. */ Geom::OptRect bounds(Geom::Affine const &transform, bool with_stroke = false, int start = -1, int length = -1) const; /** Sends all the glyphs to the given print context. \param ctx I have \param pbox no idea \param dbox what these \param bbox parameters \param ctm do yet */ void print(SPPrintContext *ctx, Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox, Geom::Affine const &ctm) const; /** Renders all the glyphs to the given Cairo rendering context. \param ctx The Cairo rendering context to be used */ void showGlyphs(CairoRenderContext *ctx) const; /** Returns the font family of the indexed span */ Glib::ustring getFontFamily(unsigned span_index) const; #if DEBUG_TEXTLAYOUT_DUMPASTEXT /** debug and unit test method. Creates a textual representation of the contents of this object. The output is designed to be both human-readable and comprehensible when diffed with a known-good dump. */ Glib::ustring dumpAsText() const; #endif /** Moves all the glyphs in the structure so that the baseline of all the characters sits neatly along the path specified. If the text has more than one line the results are undefined. The 'align' means to use the SVG align method as documented in SVG1.1 section 10.13.2. NB: njh has suggested that it would be cool if we could flow from shape to path and back again. This is possible, so this method will be removed at some point. A pointer to \a path is retained by the class for use by the cursor positioning functions. */ void fitToPathAlign(SVGLength const &startOffset, Path const &path); /** Convert the specified range of characters into their bezier outlines. */ std::unique_ptr convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const; std::unique_ptr convertToCurves() const; /** Apply the given transform to all the output presently stored in this object. This only transforms the glyph positions, The glyphs themselves will not be transformed. */ void transform(Geom::Affine const &transform); //@} // ********** /** \name Output (Iterators) Methods for operating with the Layout::iterator class. The method names ending with 'Index' return 0-based offsets of the number of items since the beginning of the flow. */ //@{ /** Returns an iterator pointing at the first glyph of the flowed output. The first glyph is also the first character, line, paragraph, etc. */ inline iterator begin() const; /** Returns an iterator pointing just past the end of the last glyph, which is also just past the end of the last chunk, span, etc, etc. */ inline iterator end() const; /** Returns an iterator pointing at the given character index. This index should be related to the result from a prior call to iteratorToCharIndex(). */ inline iterator charIndexToIterator(int char_index) const; /** Returns the character index from the start of the flow represented by the given iterator. This number isn't very useful, except for when editing text it will stay valid across calls to computeFlow() and will change in predictable ways when characters are added and removed. It's also useful when transitioning old code. */ inline int iteratorToCharIndex(iterator const &it) const; /** Checks the validity of the given iterator over the current layout. If it points to a position out of the bounds for this layout it will be corrected to the nearest valid position. If you pass an iterator belonging to a different layout it will be converted to one for this layout. */ inline void validateIterator(iterator *it) const; /** Returns an iterator pointing to the cursor position for a mouse click at the given coordinates. */ iterator getNearestCursorPositionTo(double x, double y) const; inline iterator getNearestCursorPositionTo(Geom::Point const &point) const; /** Returns an iterator pointing to the letter whose bounding box contains the given coordinates. end() if the point is not over any letter. The iterator will \em not point at the specific glyph within the character. */ iterator getLetterAt(double x, double y) const; inline iterator getLetterAt(Geom::Point &point) const; /* Returns an iterator pointing to the character in the output which was created from the given input. If the character at the given byte offset was removed (soft hyphens, for example) the next character after it is returned. If no input was added with the given object, end() is returned. If more than one input has the same object, the first will be used regardless of the value of \a text_iterator. If \a text_iterator is out of bounds, the first or last character belonging to the given input will be returned accordingly. iterator sourceToIterator(SPObject *source, Glib::ustring::const_iterator text_iterator) const; */ /** Returns an iterator pointing to the first character in the output which was created from the given source. If \a source object is invalid, end() is returned. If more than one input has the same object, the first one will be used. */ iterator sourceToIterator(SPObject *source) const; // many functions acting on iterators, most of which are obvious // also most of them don't check that \a it != end(). Be careful. /** Returns the bounding box of the given glyph, and its rotation. The centre of rotation is the horizontal centre of the box at the text baseline. */ Geom::OptRect glyphBoundingBox(iterator const &it, double *rotation) const; /** Returns the zero-based line number of the character pointed to by \a it. */ inline unsigned lineIndex(iterator const &it) const; /** Returns the zero-based number of the shape which contains the character pointed to by \a it. */ inline unsigned shapeIndex(iterator const &it) const; /** Returns true if the character at \a it is a whitespace, as defined by Pango. This is not meant to be used for picking out words from the output, use iterator::nextStartOfWord() and friends instead. */ inline bool isWhitespace(iterator const &it) const; /** Returns character pointed to by \a it. If \a it == end() the result is undefined. */ inline gchar characterAt(iterator const &it) const; /** Returns true if the text at \a it is hidden (i.e. overflowed). */ bool isHidden(iterator const &it) const; /** Discovers where the character pointed to by \a it came from, by retrieving the object that was passed to the call to appendText() or appendControlCode() which generated that output. If \a it == end() then NULL is returned as the object. If the character was generated from a call to appendText() then the optional \a text_iterator parameter is set to point to the actual character, otherwise \a text_iterator is unaltered. */ void getSourceOfCharacter(iterator const &it, SPObject **source, Glib::ustring::iterator *text_iterator = nullptr) const; /** For latin text, the left side of the character, on the baseline */ Geom::Point characterAnchorPoint(iterator const &it) const; /** For left aligned text, the leftmost end of the baseline For rightmost text, the rightmost... you probably got it by now ;-)*/ std::optional baselineAnchorPoint() const; Geom::Path baseline() const; /** This is that value to apply to the x,y attributes of tspan role=line elements, and hence it takes alignment into account. */ Geom::Point chunkAnchorPoint(iterator const &it) const; /** Returns the box extents (not ink extents) of the given character. The centre of rotation is at the horizontal centre of the box on the text baseline. */ Geom::Rect characterBoundingBox(iterator const &it, double *rotation = nullptr) const; /** Basically uses characterBoundingBox() on all the characters from \a start to \a end and returns the union of these boxes. The return value is a list of zero or more quadrilaterals specified by a group of four points for each, thus size() is always a multiple of four. */ std::vector createSelectionShape(iterator const &it_start, iterator const &it_end, Geom::Affine const &transform) const; /** Returns true if \a it points to a character which is a valid cursor position, as defined by Pango. */ inline bool isCursorPosition(iterator const &it) const; /** Gets the ideal cursor shape for a given iterator. The result is undefined if \a it is not at a valid cursor position. \param it The location in the output \param position The pixel location of the centre of the 'bottom' of the cursor. \param height The height in pixels of the surrounding text \param rotation The angle to draw from \a position. Radians, zero up, increasing clockwise. */ void queryCursorShape(iterator const &it, Geom::Point &position, double &height, double &rotation) const; /** Returns true if \a it points to a character which is a the start of a word, as defined by Pango. */ inline bool isStartOfWord(iterator const &it) const; /** Returns true if \a it points to a character which is a the end of a word, as defined by Pango. */ inline bool isEndOfWord(iterator const &it) const; /** Returns true if \a it points to a character which is a the start of a sentence, as defined by Pango. */ inline bool isStartOfSentence(iterator const &it) const; /** Returns true if \a it points to a character which is a the end of a sentence, as defined by Pango. */ inline bool isEndOfSentence(iterator const &it) const; /** Returns the zero-based number of the paragraph containing the character pointed to by \a it. */ inline unsigned paragraphIndex(iterator const &it) const; /** Returns the actual alignment used for the paragraph containing the character pointed to by \a it. This means that the CSS 'start' and 'end' are correctly translated into LEFT or RIGHT according to the paragraph's directionality. For vertical text, LEFT is top alignment and RIGHT is bottom. */ inline Alignment paragraphAlignment(iterator const &it) const; /** Returns kerning information which could cause the current output to be exactly reproduced if the letter and word spacings were zero and full justification was not used. The x and y arrays are not used, but they are cleared. The dx applied to the first character in a chunk will always be zero. If the region between \a from and \a to crosses a line break then the results may be surprising, and are undefined. Trailing zeros on the returned arrays will be trimmed. */ void simulateLayoutUsingKerning(iterator const &from, iterator const &to, OptionalTextTagAttrs *result) const; //@} /** * Keep track of font metrics. Two use cases: * 1. Keep track of ascent, descent, and x-height of an individual font. * 2. Keep track of effective ascent and descent that includes half-leading. * * Note: Leading refers to the "external" leading which is added (subtracted) due to * a computed value of 'line-height' that differs from 'font-size'. "Internal" leading * which is specified inside a font is not used in CSS. The 'font-size' is based on * the font's em size which is 'ascent' + 'descent'. * * This structure was renamed (and modified) from "LineHeight". * * It's useful for this to be public so that ScanlineMaker can use it. */ class FontMetrics { public: FontMetrics() { reset(); } void reset() { ascent = 0.8; descent = 0.2; xheight = 0.5; ascent_max = 0.8; descent_max = 0.2; } void set( font_instance *font ); // CSS 2.1 dictates that font-size is based on em-size which is defined as ascent + descent inline double emSize() const {return ascent + descent;} // Alternatively name function for use 2. inline double lineSize() const { return ascent + descent; } inline void setZero() {ascent = descent = xheight = ascent_max = descent_max = 0.0;} // For scaling for 'font-size'. inline FontMetrics& operator*=(double x) { ascent *= x; descent *= x; xheight *= x; ascent_max *= x; descent_max *= x; return *this; } /// Save the larger values of ascent and descent between this and other. Needed for laying /// out a line with mixed font-sizes, fonts, or line spacings. void max(FontMetrics const &other); /// Calculate the effective ascent and descent including half "leading". void computeEffective( const double &line_height ); inline double getTypoAscent() const {return ascent; } inline double getTypoDescent() const {return descent; } inline double getXHeight() const {return xheight; } inline double getMaxAscent() const {return ascent_max; } inline double getMaxDescent() const {return descent_max; } // private: double ascent; // Typographic ascent. double descent; // Typographic descent. (Normally positive). double xheight; // Height of 'x' measured from alphabetic baseline. double ascent_max; // Maximum ascent of all glyphs in font. double descent_max; // Maximum descent of all glyphs in font. }; // End FontMetrics /** The strut is the minimum value used in calculating line height. */ FontMetrics strut; private: /** Erases all the stuff set by the owner as input, ie #_input_stream and #_input_wrap_shapes. */ void _clearInputObjects(); /** Erases all the stuff output by computeFlow(). Glyphs and things. */ void _clearOutputObjects(); static const gunichar UNICODE_SOFT_HYPHEN; // ******************* input flow enum InputStreamItemType {TEXT_SOURCE, CONTROL_CODE}; class InputStreamItem { public: virtual ~InputStreamItem() = default; virtual InputStreamItemType Type() =0; SPObject *source; }; /** Represents a text item in the input stream. See #_input_stream. Most of the members are copies of the values passed to appendText(). */ class InputStreamTextSource : public InputStreamItem { public: InputStreamItemType Type() override {return TEXT_SOURCE;} ~InputStreamTextSource() override; Glib::ustring const *text; /// owned by the caller Glib::ustring::const_iterator text_begin, text_end; int text_length; /// in characters, from text_start to text_end only SPStyle *style; /** These vectors can (often will) be shorter than the text in this source, but never longer. */ std::vector x; std::vector y; std::vector dx; std::vector dy; std::vector rotate; SVGLength textLength; LengthAdjust lengthAdjust; Glib::ustring lang; // a few functions for some of the more complicated style accesses /// The return value must be freed with pango_font_description_free() PangoFontDescription *styleGetFontDescription() const; font_instance *styleGetFontInstance() const; Direction styleGetBlockProgression() const; SPCSSTextOrientation styleGetTextOrientation() const; SPCSSBaseline styleGetDominantBaseline() const; Alignment styleGetAlignment(Direction para_direction, bool try_text_align) const; }; /** Represents a control code item in the input stream. See #_input_streams. All the members are copies of the values passed to appendControlCode(). */ class InputStreamControlCode : public InputStreamItem { public: InputStreamItemType Type() override {return CONTROL_CODE;} TextControlCode code; double ascent; double descent; double width; }; /** This is our internal storage for all the stuff passed to the appendText() and appendControlCode() functions. */ std::vector _input_stream; /** The parameters to appendText() are allowed to be a little bit complex. This copies them to be the right length and starting at zero. We also don't want to write five bits of identical code just with different variable names. */ static void _copyInputVector(std::vector const &input_vector, unsigned input_offset, std::vector *output_vector, size_t max_length); /** The overall block-progression of the whole flow. */ inline Direction _blockProgression() const { if(!_input_stream.empty()) return static_cast(_input_stream.front())->styleGetBlockProgression(); return TOP_TO_BOTTOM; } /** The overall text-orientation of the whole flow. */ inline SPCSSTextOrientation _blockTextOrientation() const { if(!_input_stream.empty()) return static_cast(_input_stream.front())->styleGetTextOrientation(); return SP_CSS_TEXT_ORIENTATION_MIXED; } /** The overall text-orientation of the whole flow. */ inline SPCSSBaseline _blockBaseline() const { if(!_input_stream.empty()) return static_cast(_input_stream.front())->styleGetDominantBaseline(); return SP_CSS_BASELINE_AUTO; } /** so that LEFT_TO_RIGHT == RIGHT_TO_LEFT but != TOP_TO_BOTTOM */ static bool _directions_are_orthogonal(Direction d1, Direction d2); /** If the output is empty callers still want to be able to call queryCursorShape() and get a valid answer so, while #_input_wrap_shapes can still be considered valid, we need to precompute the cursor shape for this case. */ void _calculateCursorShapeForEmpty(); struct CursorShape { Geom::Point position; double height; double rotation; } _empty_cursor_shape; // ******************* input shapes struct InputWrapShape { Shape const *shape; /// as passed to Layout::appendWrapShape() DisplayAlign display_align; /// as passed to Layout::appendWrapShape() }; std::vector _input_wrap_shapes; // ******************* output /** as passed to fitToPathAlign() */ Path const *_path_fitted = nullptr; struct Glyph; struct Character; struct Span; struct Chunk; struct Line; struct Paragraph; // A glyph struct Glyph { int glyph; unsigned in_character; bool hidden; float x; /// relative to the start of the chunk float y; /// relative to the current line's baseline float rotation; /// absolute, modulo any object transforms, which we don't know about Orientation orientation; /// Orientation of glyph in vertical text float advance; /// for positioning next glyph 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];} }; // A unicode character struct Character { unsigned in_span; float x; /// relative to the start of the *span* (so we can do block-progression) PangoLogAttr char_attributes; gchar the_char = '#'; int in_glyph; /// will be -1 if this character has no visual representation inline Span const & span (Layout const *l) const {return l->_spans[in_span];} inline Chunk const & chunk (Layout const *l) const {return l->_chunks[l->_spans[in_span].in_chunk];} inline Line const & line (Layout const *l) const {return l->_lines[l->_chunks[l->_spans[in_span].in_chunk].in_line];} inline Paragraph const & paragraph(Layout const *l) const {return l->_paragraphs[l->_lines[l->_chunks[l->_spans[in_span].in_chunk].in_line].in_paragraph];} // to get the advance width of a character, subtract the x values if it's in the middle of a span, or use span.x_end if it's at the end }; // A collection of characters that share the same style and position start ( or x, y attributes). struct Span { unsigned in_chunk; font_instance *font; float font_size; float x_start; /// relative to the start of the chunk float x_end; /// relative to the start of the chunk float y_offset; /// relative to line baseline (without baseline shift) inline float width() const {return std::abs(x_start - x_end);} FontMetrics line_height; double baseline_shift; /// relative to the line's baseline (CSS) SPCSSTextOrientation text_orientation; Direction direction; /// See CSS3 section 3.2. Either rtl or ltr Direction block_progression; /// See CSS3 section 3.2. The direction in which lines go. unsigned in_input_stream_item; Glib::ustring::const_iterator input_stream_first_character; inline Chunk const & chunk (Layout const *l) const {return l->_chunks[in_chunk]; } inline Line const & line (Layout const *l) const {return l->_lines[l->_chunks[in_chunk].in_line]; } inline Paragraph const & paragraph(Layout const *l) const {return l->_paragraphs[l->_lines[l->_chunks[in_chunk].in_line].in_paragraph];} }; // A part of a line that is not broken. struct Chunk { unsigned in_line; double left_x; }; // A line of text. Depending on the shape, it may contain one or more chunks. struct Line { unsigned in_paragraph; double baseline_y; unsigned in_shape; bool hidden; }; // A paragraph. SVG 2 does not contain native paragraphs. struct Paragraph { Direction base_direction; /// can be overridden by child Span objects Alignment alignment; }; std::vector _paragraphs; std::vector _lines; std::vector _chunks; std::vector _spans; std::vector _characters; std::vector _glyphs; /** gets the overall matrix that transforms the given glyph from local space to world space. */ void _getGlyphTransformMatrix(int glyph_index, Geom::Affine *matrix) const; // loads of functions to drill down the object tree, all of them // annoyingly similar and all of them requiring predicate functors. // I'll be buggered if I can find a way to make it work with // functions or with a templated functor, so macros it is. #define EMIT_PREDICATE(name, object_type, index_generator) \ class name { \ Layout const * const _flow; \ public: \ inline name(Layout const *flow) : _flow(flow) {} \ inline bool operator()(object_type const &object, unsigned index) \ {g_assert(_flow); return index_generator < index;} \ } // end of macro EMIT_PREDICATE(PredicateLineToSpan, Span, _flow->_chunks[object.in_chunk].in_line); EMIT_PREDICATE(PredicateLineToCharacter, Character, _flow->_chunks[_flow->_spans[object.in_span].in_chunk].in_line); EMIT_PREDICATE(PredicateSpanToCharacter, Character, object.in_span); EMIT_PREDICATE(PredicateSourceToCharacter, Character, _flow->_spans[object.in_span].in_input_stream_item); inline unsigned _lineToSpan(unsigned line_index) const {return std::lower_bound(_spans.begin(), _spans.end(), line_index, PredicateLineToSpan(this)) - _spans.begin();} inline unsigned _lineToCharacter(unsigned line_index) const {return std::lower_bound(_characters.begin(), _characters.end(), line_index, PredicateLineToCharacter(this)) - _characters.begin();} inline unsigned _spanToCharacter(unsigned span_index) const {return std::lower_bound(_characters.begin(), _characters.end(), span_index, PredicateSpanToCharacter(this)) - _characters.begin();} inline unsigned _sourceToCharacter(unsigned source_index) const {return std::lower_bound(_characters.begin(), _characters.end(), source_index, PredicateSourceToCharacter(this)) - _characters.begin();} /** given an x and y coordinate and a line number, returns an iterator pointing to the closest cursor position on that line to the coordinate. ('y' is needed to handle cases where multiline text is simulated via the 'y' attribute.) */ iterator _cursorXOnLineToIterator(unsigned line_index, double local_x, double local_y = 0) const; /** calculates the width of a chunk, which is the largest x coordinate (start or end) of the spans contained within it. */ double _getChunkWidth(unsigned chunk_index) const; }; /** \brief Holds a position within the glyph output of Layout. Used to access the output of a Layout, query information and generally move around in it. See Layout for a glossary of the names of functions. I'm not going to document all the methods because most of their names make their function self-evident. A lot of the functions would do the same thing in a naive implementation for latin-only text, for example nextCharacter(), nextCursorPosition() and cursorRight(). Generally it's fairly obvious which one you should use in a given situation, but sometimes you might need to put some thought in to it. All the methods return false if the requested action would have caused the current position to move out of bounds. In this case the position is moved to either begin() or end(), depending on which direction you were going. Note that some characters do not have a glyph representation (eg line breaks), so if you try using prev/nextGlyph() from one of these you're heading for a crash. */ class Layout::iterator { public: friend class Layout; // this is just so you can create uninitialised iterators - don't actually try to use one iterator() : _parent_layout(nullptr), _glyph_index(-1), _char_index(0), _cursor_moving_vertically(false), _x_coordinate(0.0){} // no copy constructor required, the default does what we want bool operator== (iterator const &other) const {return _glyph_index == other._glyph_index && _char_index == other._char_index;} bool operator!= (iterator const &other) const {return _glyph_index != other._glyph_index || _char_index != other._char_index;} /* mustn't compare _glyph_index in these operators because for characters that don't have glyphs (line breaks, elided soft hyphens, etc), the glyph index is -1 which makes them not well-ordered. To be honest, iterating by glyphs is not very useful and should be avoided. */ bool operator< (iterator const &other) const {return _char_index < other._char_index;} bool operator<= (iterator const &other) const {return _char_index <= other._char_index;} bool operator> (iterator const &other) const {return _char_index > other._char_index;} bool operator>= (iterator const &other) const {return _char_index >= other._char_index;} /* **** visual-oriented methods **** */ //glyphs inline bool prevGlyph(); inline bool nextGlyph(); //span bool prevStartOfSpan(); bool thisStartOfSpan(); bool nextStartOfSpan(); //chunk bool prevStartOfChunk(); bool thisStartOfChunk(); bool nextStartOfChunk(); //line bool prevStartOfLine(); bool thisStartOfLine(); bool nextStartOfLine(); bool thisEndOfLine(); //shape bool prevStartOfShape(); bool thisStartOfShape(); bool nextStartOfShape(); /* **** text-oriented methods **** */ //characters inline bool nextCharacter(); inline bool prevCharacter(); bool nextCursorPosition(); bool prevCursorPosition(); bool nextLineCursor(int n = 1); bool prevLineCursor(int n = 1); //words bool nextStartOfWord(); bool prevStartOfWord(); bool nextEndOfWord(); bool prevEndOfWord(); //sentences bool nextStartOfSentence(); bool prevStartOfSentence(); bool nextEndOfSentence(); bool prevEndOfSentence(); //paragraphs bool prevStartOfParagraph(); bool thisStartOfParagraph(); bool nextStartOfParagraph(); //no endOfPara methods because that's just the previous char //sources bool prevStartOfSource(); bool thisStartOfSource(); bool nextStartOfSource(); //logical cursor movement bool cursorUp(int n = 1); bool cursorDown(int n = 1); bool cursorLeft(); bool cursorRight(); //logical cursor movement (by word or paragraph) bool cursorUpWithControl(); bool cursorDownWithControl(); bool cursorLeftWithControl(); bool cursorRightWithControl(); private: Layout const *_parent_layout; int _glyph_index; /// index into Layout::glyphs, or -1 unsigned _char_index; /// index into Layout::character bool _cursor_moving_vertically; /** for cursor up/down movement we must maintain the x position where we started so the cursor doesn't 'drift' left or right with the repeated quantization to character boundaries. */ double _x_coordinate; inline iterator(Layout const *p, unsigned c, int g) : _parent_layout(p), _glyph_index(g), _char_index(c), _cursor_moving_vertically(false), _x_coordinate(0.0) {} inline iterator(Layout const *p, unsigned c) : _parent_layout(p), _glyph_index(p->_characters[c].in_glyph), _char_index(c), _cursor_moving_vertically(false), _x_coordinate(0.0) {} // no dtor required void beginCursorUpDown(); /// stores the current x coordinate so that the cursor won't drift. See #_x_coordinate /** moves forward or backwards one cursor position according to the directionality of the current paragraph, but ignoring block progression. Helper for the cursor*() functions. */ bool _cursorLeftOrRightLocalX(Direction direction); /** moves forward or backwards by until the next character with is_word_start according to the directionality of the current paragraph, but ignoring block progression. Helper for the cursor*WithControl() functions. */ bool _cursorLeftOrRightLocalXByWord(Direction direction); }; // ************************** inline methods inline Layout::iterator Layout::begin() const {return iterator(this, 0, 0);} inline Layout::iterator Layout::end() const {return iterator(this, _characters.size(), _glyphs.size());} inline Layout::iterator Layout::charIndexToIterator(int char_index) const { if (char_index < 0) return begin(); if (char_index >= (int)_characters.size()) return end(); return iterator(this, char_index); } inline int Layout::iteratorToCharIndex(Layout::iterator const &it) const {return it._char_index;} inline void Layout::validateIterator(Layout::iterator *it) const { it->_parent_layout = this; if (it->_char_index >= _characters.size()) { it->_char_index = _characters.size(); it->_glyph_index = _glyphs.size(); } else it->_glyph_index = _characters[it->_char_index].in_glyph; } inline Layout::iterator Layout::getNearestCursorPositionTo(Geom::Point const &point) const {return getNearestCursorPositionTo(point[0], point[1]);} inline Layout::iterator Layout::getLetterAt(Geom::Point &point) const {return getLetterAt(point[0], point[1]);} inline unsigned Layout::lineIndex(iterator const &it) const {return it._char_index == _characters.size() ? _lines.size() - 1 : _characters[it._char_index].chunk(this).in_line;} inline unsigned Layout::shapeIndex(iterator const &it) const {return it._char_index == _characters.size() ? _input_wrap_shapes.size() - 1 : _characters[it._char_index].line(this).in_shape;} inline bool Layout::isWhitespace(iterator const &it) const {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_white;} inline gchar Layout::characterAt(iterator const &it) const { return _characters[it._char_index].the_char; } inline bool Layout::isCursorPosition(iterator const &it) const {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_cursor_position;} inline bool Layout::isStartOfWord(iterator const &it) const {return it._char_index != _characters.size() && _characters[it._char_index].char_attributes.is_word_start;} inline bool Layout::isEndOfWord(iterator const &it) const {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_word_end;} inline bool Layout::isStartOfSentence(iterator const &it) const {return it._char_index != _characters.size() && _characters[it._char_index].char_attributes.is_sentence_start;} inline bool Layout::isEndOfSentence(iterator const &it) const {return it._char_index == _characters.size() || _characters[it._char_index].char_attributes.is_sentence_end;} inline unsigned Layout::paragraphIndex(iterator const &it) const {return it._char_index == _characters.size() ? _paragraphs.size() - 1 : _characters[it._char_index].line(this).in_paragraph;} inline Layout::Alignment Layout::paragraphAlignment(iterator const &it) const {return (_paragraphs.size() == 0) ? NONE : _paragraphs[paragraphIndex(it)].alignment;} inline bool Layout::iterator::nextGlyph() { _cursor_moving_vertically = false; if (_glyph_index >= (int)_parent_layout->_glyphs.size() - 1) { if (_glyph_index == (int)_parent_layout->_glyphs.size()) return false; _char_index = _parent_layout->_characters.size(); _glyph_index = _parent_layout->_glyphs.size(); } else _char_index = _parent_layout->_glyphs[++_glyph_index].in_character; return true; } inline bool Layout::iterator::prevGlyph() { _cursor_moving_vertically = false; if (_glyph_index == 0) return false; _char_index = _parent_layout->_glyphs[--_glyph_index].in_character; return true; } inline bool Layout::iterator::nextCharacter() { _cursor_moving_vertically = false; if (_char_index + 1 >= _parent_layout->_characters.size()) { if (_char_index == _parent_layout->_characters.size()) return false; _char_index = _parent_layout->_characters.size(); _glyph_index = _parent_layout->_glyphs.size(); } else _glyph_index = _parent_layout->_characters[++_char_index].in_glyph; return true; } inline bool Layout::iterator::prevCharacter() { _cursor_moving_vertically = false; if (_char_index == 0) return false; _glyph_index = _parent_layout->_characters[--_char_index].in_glyph; return true; } }//namespace Text }//namespace Inkscape std::ostream &operator<<(std::ostream &out, const Inkscape::Text::Layout::FontMetrics &f); std::ostream &operator<<(std::ostream &out, const Inkscape::Text::Layout::FontMetrics *f); #endif /* 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 :