diff options
Diffstat (limited to 'pango/pango-layout.c')
-rw-r--r-- | pango/pango-layout.c | 8336 |
1 files changed, 1011 insertions, 7325 deletions
diff --git a/pango/pango-layout.c b/pango/pango-layout.c index 0bd62cef..9671ab61 100644 --- a/pango/pango-layout.c +++ b/pango/pango-layout.c @@ -1,177 +1,66 @@ -/* Pango - * pango-layout.c: High-level layout driver - * - * Copyright (C) 2000, 2001, 2006 Red Hat Software - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -/** - * PangoLayout: - * - * A `PangoLayout` structure represents an entire paragraph of text. - * - * While complete access to the layout capabilities of Pango is provided - * using the detailed interfaces for itemization and shaping, using - * that functionality directly involves writing a fairly large amount - * of code. `PangoLayout` provides a high-level driver for formatting - * entire paragraphs of text at once. This includes paragraph-level - * functionality such as line breaking, justification, alignment and - * ellipsization. - * - * A `PangoLayout` is initialized with a `PangoContext`, UTF-8 string - * and set of attributes for that string. Once that is done, the set of - * formatted lines can be extracted from the object, the layout can be - * rendered, and conversion between logical character positions within - * the layout's text, and the physical position of the resulting glyphs - * can be made. - * - * There are a number of parameters to adjust the formatting of a - * `PangoLayout`. The following image shows adjustable parameters - * (on the left) and font metrics (on the right): - * - * <picture> - * <source srcset="layout-dark.png" media="(prefers-color-scheme: dark)"> - * <img alt="Pango Layout Parameters" src="layout-light.png"> - * </picture> - * - * The following images demonstrate the effect of alignment and - * justification on the layout of text: - * - * | | | - * | --- | --- | - * | ![align=left](align-left.png) | ![align=left, justify](align-left-justify.png) | - * | ![align=center](align-center.png) | ![align=center, justify](align-center-justify.png) | - * | ![align=right](align-right.png) | ![align=right, justify](align-right-justify.png) | - * - * - * It is possible, as well, to ignore the 2-D setup, - * and simply treat the results of a `PangoLayout` as a list of lines. - */ - -/** - * PangoLayoutIter: - * - * A `PangoLayoutIter` can be used to iterate over the visual - * extents of a `PangoLayout`. - * - * To obtain a `PangoLayoutIter`, use [method@Pango.Layout.get_iter]. - * - * The `PangoLayoutIter` structure is opaque, and has no user-visible fields. - */ - #include "config.h" -#include "pango-glyph.h" /* For pango_shape() */ -#include "pango-break.h" -#include "pango-item-private.h" -#include "pango-engine.h" -#include "pango-impl-utils.h" -#include "pango-glyph-item.h" -#include <string.h> -#include <math.h> -#include <locale.h> - -#include <hb-ot.h> -#include "pango-layout-private.h" -#include "pango-attributes-private.h" -#include "pango-font-private.h" +#include "pango-layout.h" +#include "pango-line-breaker.h" +#include "pango-layout-line-private.h" +#include "pango-enum-types.h" +#include "pango-markup.h" +#include "pango-context.h" -typedef struct _ParaBreakState ParaBreakState; -typedef struct _LastTabState LastTabState; +/* {{{ PangoLayout implementation */ -typedef struct _PangoLayoutLinePrivate PangoLayoutLinePrivate; - -struct _PangoLayoutLinePrivate +struct _PangoLayout { - PangoLayoutLine line; - guint ref_count; + GObject parent_instance; - /* Extents cache status: - * - * LEAKED means that the user has access to this line structure or a - * run included in this line, and so can change the glyphs/glyph-widths. - * If this is true, extents caching will be disabled. - */ - enum { - NOT_CACHED, - CACHED, - LEAKED - } cache_status; - PangoRectangle ink_rect; - PangoRectangle logical_rect; + PangoContext *context; + char *text; + int length; + PangoAttrList *attrs; + PangoFontDescription *font_desc; + float line_spacing; + int width; int height; + PangoTabArray *tabs; + gboolean single_paragraph; + PangoWrapMode wrap; + int indent; + guint serial; + guint context_serial; + PangoAlignment alignment; + PangoEllipsizeMode ellipsize; + gboolean auto_dir; + + PangoLines *lines; }; struct _PangoLayoutClass { GObjectClass parent_class; - - }; -#define LINE_IS_VALID(line) ((line) && (line)->layout != NULL) - -#ifdef G_DISABLE_CHECKS -#define ITER_IS_INVALID(iter) FALSE -#else -#define ITER_IS_INVALID(iter) G_UNLIKELY (check_invalid ((iter), G_STRLOC)) -static gboolean -check_invalid (PangoLayoutIter *iter, - const char *loc) -{ - if (iter->line->layout == NULL) - { - g_warning ("%s: PangoLayout changed since PangoLayoutIter was created, iterator invalid", loc); - return TRUE; - } - else - { - return FALSE; - } -} -#endif - -static void check_context_changed (PangoLayout *layout); -static void layout_changed (PangoLayout *layout); - -static void pango_layout_clear_lines (PangoLayout *layout); -static void pango_layout_check_lines (PangoLayout *layout); - -static PangoAttrList *pango_layout_get_effective_attributes (PangoLayout *layout); - -static PangoLayoutLine * pango_layout_line_new (PangoLayout *layout); -static void pango_layout_line_postprocess (PangoLayoutLine *line, - ParaBreakState *state, - gboolean wrapped); - -static void pango_layout_line_leaked (PangoLayoutLine *line); - -/* doesn't leak line */ -static PangoLayoutLine * _pango_layout_iter_get_line (PangoLayoutIter *iter); -static PangoLayoutRun * _pango_layout_iter_get_run (PangoLayoutIter *iter); - -static void pango_layout_get_empty_extents_and_height_at_index (PangoLayout *layout, - int index, - PangoRectangle *logical_rect, - gboolean apply_line_height, - int *height); +enum +{ + PROP_CONTEXT = 1, + PROP_TEXT, + PROP_ATTRIBUTES, + PROP_FONT_DESCRIPTION, + PROP_LINE_SPACING, + PROP_WIDTH, + PROP_HEIGHT, + PROP_TABS, + PROP_SINGLE_PARAGRAPH, + PROP_WRAP, + PROP_INDENT, + PROP_ALIGNMENT, + PROP_ELLIPSIZE, + PROP_AUTO_DIR, + PROP_LINES, + NUM_PROPERTIES +}; -static void pango_layout_finalize (GObject *object); +static GParamSpec *props[NUM_PROPERTIES] = { NULL, }; G_DEFINE_TYPE (PangoLayout, pango_layout, G_TYPE_OBJECT) @@ -179,1304 +68,684 @@ static void pango_layout_init (PangoLayout *layout) { layout->serial = 1; - layout->attrs = NULL; - layout->font_desc = NULL; - layout->text = NULL; - layout->length = 0; layout->width = -1; layout->height = -1; layout->indent = 0; - layout->spacing = 0; - layout->line_spacing = 0.0; - - layout->alignment = PANGO_ALIGN_LEFT; - layout->justify = FALSE; - layout->justify_last_line = FALSE; - layout->auto_dir = TRUE; - layout->single_paragraph = FALSE; - - layout->log_attrs = NULL; - layout->lines = NULL; - layout->line_count = 0; - - layout->tab_width = -1; - layout->decimal = 0; - layout->unknown_glyphs_count = -1; - layout->wrap = PANGO_WRAP_WORD; - layout->is_wrapped = FALSE; + layout->alignment = PANGO_ALIGN_LEFT; layout->ellipsize = PANGO_ELLIPSIZE_NONE; - layout->is_ellipsized = FALSE; -} - -static void -pango_layout_class_init (PangoLayoutClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = pango_layout_finalize; + layout->line_spacing = 0.0; + layout->auto_dir = TRUE; + layout->text = g_strdup (""); + layout->length = 0; } static void pango_layout_finalize (GObject *object) { - PangoLayout *layout; - - layout = PANGO_LAYOUT (object); - - pango_layout_clear_lines (layout); - g_free (layout->log_attrs); - - if (layout->context) - g_object_unref (layout->context); - - if (layout->attrs) - pango_attr_list_unref (layout->attrs); + PangoLayout *layout = PANGO_LAYOUT (object); + g_clear_pointer (&layout->font_desc, pango_font_description_free); + g_object_unref (layout->context); g_free (layout->text); - - if (layout->font_desc) - pango_font_description_free (layout->font_desc); - - if (layout->tabs) - pango_tab_array_free (layout->tabs); + g_clear_pointer (&layout->attrs, pango_attr_list_unref); + g_clear_pointer (&layout->tabs, pango_tab_array_free); + g_clear_object (&layout->lines); G_OBJECT_CLASS (pango_layout_parent_class)->finalize (object); } -/** - * pango_layout_new: - * @context: a `PangoContext` - * - * Create a new `PangoLayout` object with attributes initialized to - * default values for a particular `PangoContext`. - * - * Return value: the newly allocated `PangoLayout` - */ -PangoLayout * -pango_layout_new (PangoContext *context) +static void +pango_layout_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) { - PangoLayout *layout; + PangoLayout *layout = PANGO_LAYOUT (object); - g_return_val_if_fail (context != NULL, NULL); - - layout = g_object_new (PANGO_TYPE_LAYOUT, NULL); + switch (prop_id) + { + case PROP_CONTEXT: + layout->context = g_value_dup_object (value); + layout->context_serial = pango_context_get_serial (layout->context); + break; - layout->context = context; - layout->context_serial = pango_context_get_serial (context); - g_object_ref (context); + case PROP_TEXT: + pango_layout_set_text (layout, g_value_get_string (value), -1); + break; - return layout; -} + case PROP_ATTRIBUTES: + pango_layout_set_attributes (layout, g_value_get_boxed (value)); + break; -/** - * pango_layout_copy: - * @src: a `PangoLayout` - * - * Creates a deep copy-by-value of the layout. - * - * The attribute list, tab array, and text from the original layout - * are all copied by value. - * - * Return value: (transfer full): the newly allocated `PangoLayout` - */ -PangoLayout* -pango_layout_copy (PangoLayout *src) -{ - PangoLayout *layout; + case PROP_FONT_DESCRIPTION: + pango_layout_set_font_description (layout, g_value_get_boxed (value)); + break; - g_return_val_if_fail (PANGO_IS_LAYOUT (src), NULL); + case PROP_LINE_SPACING: + pango_layout_set_line_spacing (layout, g_value_get_float (value)); + break; - /* Copy referenced members */ + case PROP_WIDTH: + pango_layout_set_width (layout, g_value_get_int (value)); + break; - layout = pango_layout_new (src->context); - if (src->attrs) - layout->attrs = pango_attr_list_copy (src->attrs); - if (src->font_desc) - layout->font_desc = pango_font_description_copy (src->font_desc); - if (src->tabs) - layout->tabs = pango_tab_array_copy (src->tabs); + case PROP_HEIGHT: + pango_layout_set_height (layout, g_value_get_int (value)); + break; - /* Dupped */ - layout->text = g_strdup (src->text); + case PROP_TABS: + pango_layout_set_tabs (layout, g_value_get_boxed (value)); + break; - /* Value fields */ - memcpy (&layout->copy_begin, &src->copy_begin, - G_STRUCT_OFFSET (PangoLayout, copy_end) - G_STRUCT_OFFSET (PangoLayout, copy_begin)); + case PROP_SINGLE_PARAGRAPH: + pango_layout_set_single_paragraph (layout, g_value_get_boolean (value)); + break; - return layout; -} + case PROP_WRAP: + pango_layout_set_wrap (layout, g_value_get_enum (value)); + break; -/** - * pango_layout_get_context: - * @layout: a `PangoLayout` - * - * Retrieves the `PangoContext` used for this layout. - * - * Return value: (transfer none): the `PangoContext` for the layout - */ -PangoContext * -pango_layout_get_context (PangoLayout *layout) -{ - g_return_val_if_fail (layout != NULL, NULL); + case PROP_INDENT: + pango_layout_set_indent (layout, g_value_get_int (value)); + break; - return layout->context; -} + case PROP_ALIGNMENT: + pango_layout_set_alignment (layout, g_value_get_enum (value)); + break; -/** - * pango_layout_set_width: - * @layout: a `PangoLayout`. - * @width: the desired width in Pango units, or -1 to indicate that no - * wrapping or ellipsization should be performed. - * - * Sets the width to which the lines of the `PangoLayout` should wrap or - * ellipsized. - * - * The default value is -1: no width set. - */ -void -pango_layout_set_width (PangoLayout *layout, - int width) -{ - g_return_if_fail (layout != NULL); + case PROP_ELLIPSIZE: + pango_layout_set_ellipsize (layout, g_value_get_enum (value)); + break; - if (width < 0) - width = -1; + case PROP_AUTO_DIR: + pango_layout_set_auto_dir (layout, g_value_get_boolean (value)); + break; - if (width != layout->width) - { - layout->width = width; - layout_changed (layout); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; } } -/** - * pango_layout_get_width: - * @layout: a `PangoLayout` - * - * Gets the width to which the lines of the `PangoLayout` should wrap. - * - * Return value: the width in Pango units, or -1 if no width set. - */ -int -pango_layout_get_width (PangoLayout *layout) -{ - g_return_val_if_fail (layout != NULL, 0); - return layout->width; -} - -/** - * pango_layout_set_height: - * @layout: a `PangoLayout`. - * @height: the desired height of the layout in Pango units if positive, - * or desired number of lines if negative. - * - * Sets the height to which the `PangoLayout` should be ellipsized at. - * - * There are two different behaviors, based on whether @height is positive - * or negative. - * - * If @height is positive, it will be the maximum height of the layout. Only - * lines would be shown that would fit, and if there is any text omitted, - * an ellipsis added. At least one line is included in each paragraph regardless - * of how small the height value is. A value of zero will render exactly one - * line for the entire layout. - * - * If @height is negative, it will be the (negative of) maximum number of lines - * per paragraph. That is, the total number of lines shown may well be more than - * this value if the layout contains multiple paragraphs of text. - * The default value of -1 means that the first line of each paragraph is ellipsized. - * This behavior may be changed in the future to act per layout instead of per - * paragraph. File a bug against pango at - * [https://gitlab.gnome.org/gnome/pango](https://gitlab.gnome.org/gnome/pango) - * if your code relies on this behavior. - * - * Height setting only has effect if a positive width is set on - * @layout and ellipsization mode of @layout is not %PANGO_ELLIPSIZE_NONE. - * The behavior is undefined if a height other than -1 is set and - * ellipsization mode is set to %PANGO_ELLIPSIZE_NONE, and may change in the - * future. - * - * Since: 1.20 - */ -void -pango_layout_set_height (PangoLayout *layout, - int height) +static void +pango_layout_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { - g_return_if_fail (layout != NULL); + PangoLayout *layout = PANGO_LAYOUT (object); - if (height != layout->height) + switch (prop_id) { - layout->height = height; - - /* Do not invalidate if the number of lines requested is - * larger than the total number of lines in layout. - * Bug 549003 - */ - if (layout->ellipsize != PANGO_ELLIPSIZE_NONE && - !(layout->lines && layout->is_ellipsized == FALSE && - height < 0 && layout->line_count <= (guint) -height)) - layout_changed (layout); - } -} - -/** - * pango_layout_get_height: - * @layout: a `PangoLayout` - * - * Gets the height of layout used for ellipsization. - * - * See [method@Pango.Layout.set_height] for details. - * - * Return value: the height, in Pango units if positive, - * or number of lines if negative. - * - * Since: 1.20 - */ -int -pango_layout_get_height (PangoLayout *layout) -{ - g_return_val_if_fail (layout != NULL, 0); - return layout->height; -} - -/** - * pango_layout_set_wrap: - * @layout: a `PangoLayout` - * @wrap: the wrap mode - * - * Sets the wrap mode. - * - * The wrap mode only has effect if a width is set on the layout - * with [method@Pango.Layout.set_width]. To turn off wrapping, - * set the width to -1. - * - * The default value is %PANGO_WRAP_WORD. - */ -void -pango_layout_set_wrap (PangoLayout *layout, - PangoWrapMode wrap) -{ - g_return_if_fail (PANGO_IS_LAYOUT (layout)); + case PROP_CONTEXT: + g_value_set_object (value, layout->context); + break; - if (layout->wrap != wrap) - { - layout->wrap = wrap; + case PROP_TEXT: + g_value_set_string (value, layout->text); + break; - if (layout->width != -1) - layout_changed (layout); - } -} + case PROP_ATTRIBUTES: + g_value_set_boxed (value, layout->attrs); + break; -/** - * pango_layout_get_wrap: - * @layout: a `PangoLayout` - * - * Gets the wrap mode for the layout. - * - * Use [method@Pango.Layout.is_wrapped] to query whether - * any paragraphs were actually wrapped. - * - * Return value: active wrap mode. - */ -PangoWrapMode -pango_layout_get_wrap (PangoLayout *layout) -{ - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), 0); + case PROP_FONT_DESCRIPTION: + g_value_set_boxed (value, layout->font_desc); + break; - return layout->wrap; -} + case PROP_LINE_SPACING: + g_value_set_float (value, layout->line_spacing); + break; -/** - * pango_layout_is_wrapped: - * @layout: a `PangoLayout` - * - * Queries whether the layout had to wrap any paragraphs. - * - * This returns %TRUE if a positive width is set on @layout, - * ellipsization mode of @layout is set to %PANGO_ELLIPSIZE_NONE, - * and there are paragraphs exceeding the layout width that have - * to be wrapped. - * - * Return value: %TRUE if any paragraphs had to be wrapped, %FALSE - * otherwise - * - * Since: 1.16 - */ -gboolean -pango_layout_is_wrapped (PangoLayout *layout) -{ - g_return_val_if_fail (layout != NULL, FALSE); + case PROP_WIDTH: + g_value_set_int (value, layout->width); + break; - pango_layout_check_lines (layout); + case PROP_HEIGHT: + g_value_set_int (value, layout->height); + break; - return layout->is_wrapped; -} + case PROP_TABS: + g_value_set_boxed (value, layout->tabs); + break; -/** - * pango_layout_set_indent: - * @layout: a `PangoLayout` - * @indent: the amount by which to indent - * - * Sets the width in Pango units to indent each paragraph. - * - * A negative value of @indent will produce a hanging indentation. - * That is, the first line will have the full width, and subsequent - * lines will be indented by the absolute value of @indent. - * - * The indent setting is ignored if layout alignment is set to - * %PANGO_ALIGN_CENTER. - * - * The default value is 0. - */ -void -pango_layout_set_indent (PangoLayout *layout, - int indent) -{ - g_return_if_fail (layout != NULL); + case PROP_SINGLE_PARAGRAPH: + g_value_set_boolean (value, layout->single_paragraph); + break; - if (indent != layout->indent) - { - layout->indent = indent; - layout_changed (layout); - } -} + case PROP_WRAP: + g_value_set_enum (value, layout->wrap); + break; -/** - * pango_layout_get_indent: - * @layout: a `PangoLayout` - * - * Gets the paragraph indent width in Pango units. - * - * A negative value indicates a hanging indentation. - * - * Return value: the indent in Pango units - */ -int -pango_layout_get_indent (PangoLayout *layout) -{ - g_return_val_if_fail (layout != NULL, 0); - return layout->indent; -} + case PROP_INDENT: + g_value_set_int (value, layout->indent); + break; -/** - * pango_layout_set_spacing: - * @layout: a `PangoLayout` - * @spacing: the amount of spacing - * - * Sets the amount of spacing in Pango units between - * the lines of the layout. - * - * When placing lines with spacing, Pango arranges things so that - * - * line2.top = line1.bottom + spacing - * - * The default value is 0. - * - * Note: Since 1.44, Pango is using the line height (as determined - * by the font) for placing lines when the line spacing factor is set - * to a non-zero value with [method@Pango.Layout.set_line_spacing]. - * In that case, the @spacing set with this function is ignored. - * - * Note: for semantics that are closer to the CSS line-height - * property, see [func@Pango.attr_line_height_new]. - */ -void -pango_layout_set_spacing (PangoLayout *layout, - int spacing) -{ - g_return_if_fail (layout != NULL); + case PROP_ALIGNMENT: + g_value_set_enum (value, layout->alignment); + break; - if (spacing != layout->spacing) - { - layout->spacing = spacing; - layout_changed (layout); - } -} + case PROP_ELLIPSIZE: + g_value_set_enum (value, layout->ellipsize); + break; -/** - * pango_layout_get_spacing: - * @layout: a `PangoLayout` - * - * Gets the amount of spacing between the lines of the layout. - * - * Return value: the spacing in Pango units - */ -int -pango_layout_get_spacing (PangoLayout *layout) -{ - g_return_val_if_fail (layout != NULL, 0); - return layout->spacing; -} + case PROP_AUTO_DIR: + g_value_set_boolean (value, layout->auto_dir); + break; -/** - * pango_layout_set_line_spacing: - * @layout: a `PangoLayout` - * @factor: the new line spacing factor - * - * Sets a factor for line spacing. - * - * Typical values are: 0, 1, 1.5, 2. The default values is 0. - * - * If @factor is non-zero, lines are placed so that - * - * baseline2 = baseline1 + factor * height2 - * - * where height2 is the line height of the second line - * (as determined by the font(s)). In this case, the spacing - * set with [method@Pango.Layout.set_spacing] is ignored. - * - * If @factor is zero (the default), spacing is applied as before. - * - * Note: for semantics that are closer to the CSS line-height - * property, see [func@Pango.attr_line_height_new]. - * - * Since: 1.44 - */ -void -pango_layout_set_line_spacing (PangoLayout *layout, - float factor) -{ - g_return_if_fail (layout != NULL); + case PROP_LINES: + g_value_set_object (value, pango_layout_get_lines (layout)); + break; - if (layout->line_spacing != factor) - { - layout->line_spacing = factor; - layout_changed (layout); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; } } -/** - * pango_layout_get_line_spacing: - * @layout: a `PangoLayout` - * - * Gets the line spacing factor of @layout. - * - * See [method@Pango.Layout.set_line_spacing]. - * - * Since: 1.44 - */ -float -pango_layout_get_line_spacing (PangoLayout *layout) -{ - g_return_val_if_fail (layout != NULL, 1.0); - return layout->line_spacing; -} - -/** - * pango_layout_set_attributes: - * @layout: a `PangoLayout` - * @attrs: (nullable) (transfer none): a `PangoAttrList` - * - * Sets the text attributes for a layout object. - * - * References @attrs, so the caller can unref its reference. - */ -void -pango_layout_set_attributes (PangoLayout *layout, - PangoAttrList *attrs) +static void +pango_layout_class_init (PangoLayoutClass *class) { - PangoAttrList *old_attrs; + GObjectClass *object_class = G_OBJECT_CLASS (class); - g_return_if_fail (layout != NULL); - - /* Both empty */ - if (!attrs && !layout->attrs) - return; + object_class->finalize = pango_layout_finalize; + object_class->set_property = pango_layout_set_property; + object_class->get_property = pango_layout_get_property; - if (layout->attrs && - pango_attr_list_equal (layout->attrs, attrs)) - return; + /** + * PangoLayout:context: (attributes org.gtk.Property.get=pango_layout_get_context) + * + * The context for the `PangoLayout`. + */ + props[PROP_CONTEXT] = g_param_spec_object ("context", "context", "context", + PANGO_TYPE_CONTEXT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); - old_attrs = layout->attrs; + /** + * PangoLayout:text: (attributes org.gtk.Property.get=pango_layout_get_text org.gtk.Property.set=pango_layout_set_text) + * + * The text of the `PangoLayout`. + */ + props[PROP_TEXT] = g_param_spec_string ("text", "text", "text", + "", + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); - /* We always clear lines such that this function can be called - * whenever attrs changes. + /** + * PangoLayout:attributes: (attributes org.gtk.Property.get=pango_layout_get_attributes org.gtk.Property.set=pango_layout_set_attributes) + * + * The attributes of the `PangoLayout`. + * + * Attributes can affect how the text is formatted. */ - layout->attrs = attrs; - if (layout->attrs) - pango_attr_list_ref (layout->attrs); + props[PROP_ATTRIBUTES] = g_param_spec_boxed ("attributes", "attributes", "attributes", + PANGO_TYPE_ATTR_LIST, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); - g_clear_pointer (&layout->log_attrs, g_free); - layout_changed (layout); + /** + * PangoLayout:font-description: (attributes org.gtk.Property.get=pango_layout_get_font_description org.gtk.Property.set=pango_layout_set_font_description) + * + * The font description of the `PangoLayout`. + */ + props[PROP_FONT_DESCRIPTION] = g_param_spec_boxed ("font-description", "font-description", "font-description", + PANGO_TYPE_FONT_DESCRIPTION, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); - if (old_attrs) - pango_attr_list_unref (old_attrs); - layout->tab_width = -1; -} + /** + * PangoLayout:line-spacing: (attributes org.gtk.Property.get=pango_layout_get_line_spacing org.gtk.Property.set=pango_layout_set_line_spacing) + * + * The line spacing factor of the `PangoLayout`. + */ + props[PROP_LINE_SPACING] = g_param_spec_float ("line-spacing", "line-spacing", "line-spacing", + 0., G_MAXFLOAT, 0., + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); -/** - * pango_layout_get_attributes: - * @layout: a `PangoLayout` - * - * Gets the attribute list for the layout, if any. - * - * Return value: (transfer none) (nullable): a `PangoAttrList` - */ -PangoAttrList* -pango_layout_get_attributes (PangoLayout *layout) -{ - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); + /** + * PangoLayout:width: (attributes org.gtk.Property.get=pango_layout_get_width org.gtk.Property.set=pango_layout_set_width) + * + * The width to which the text of `PangoLayout` will be broken. + * + * The width is specified in Pango units, with -1 meaning unlimited. + * + * The default value is -1. + */ + props[PROP_WIDTH] = g_param_spec_int ("width", "width", "width", + -1, G_MAXINT, -1, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); - return layout->attrs; -} + /** + * PangoLayout:height: (attributes org.gtk.Property.get=pango_layout_get_height org.gtk.Property.set=pango_layout_set_height) + * + * The height to which the `PangoLayout` will be ellipsized. + * + * If @height is positive, it will be the maximum height of the + * layout. Only lines would be shown that would fit, and if there + * is any text omitted, an ellipsis added. At least one line is + * included in each paragraph regardless of how small the height + * value is. A value of zero will render exactly one line for the + * entire layout. + * + * If @height is negative, it will be the (negative of) maximum + * number of lines per paragraph. That is, the total number of lines + * shown may well be more than this value if the layout contains + * multiple paragraphs of text. + * + * The default value of -1 means that the first line of each + * paragraph is ellipsized. + * + * Height setting only has effect if a positive width is set on the + * layout and its ellipsization mode is not `PANGO_ELLIPSIZE_NONE`. + * The behavior is undefined if a height other than -1 is set and + * ellipsization mode is set to `PANGO_ELLIPSIZE_NONE`. + * + * The default value is -1. + */ + props[PROP_HEIGHT] = g_param_spec_int ("height", "height", "height", + -G_MAXINT, G_MAXINT, -1, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); -/** - * pango_layout_set_font_description: - * @layout: a `PangoLayout` - * @desc: (nullable): the new `PangoFontDescription` - * to unset the current font description - * - * Sets the default font description for the layout. - * - * If no font description is set on the layout, the - * font description from the layout's context is used. - */ -void -pango_layout_set_font_description (PangoLayout *layout, - const PangoFontDescription *desc) -{ - g_return_if_fail (layout != NULL); + /** + * PangoLayout:tabs: (attributes org.gtk.Property.get=pango_layout_get_tabs org.gtk.Property.set=pango_layout_set_tabs) + * + * The tabs to use when formatting the text of `PangoLayout`. + * + * `PangoLayout` will place content at the next tab position + * whenever it meets a Tab character (U+0009). + */ + props[PROP_TABS] = g_param_spec_boxed ("tabs", "tabs", "tabs", + PANGO_TYPE_TAB_ARRAY, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); - if (desc != layout->font_desc && - (!desc || !layout->font_desc || !pango_font_description_equal(desc, layout->font_desc))) - { - if (layout->font_desc) - pango_font_description_free (layout->font_desc); + /** + * PangoLayout:single-paragraph: (attributes org.gtk.Property.get=pango_layout_get_single_paragraph org.gtk.Property.set=pango_layout_set_single_paragraph) + * + * Whether to treat newlines and similar characters as paragraph + * separators or not. If this property is `TRUE`, all text is kept + * in a single paragraph, and paragraph separator characters are + * displayed with a glyph. + * + * This is useful to allow editing of newlines on a single text line. + * + * The default value is `FALSE`. + */ + props[PROP_SINGLE_PARAGRAPH] = g_param_spec_boolean ("single-paragraph", "single-paragraph", "single-paragraph", + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); - layout->font_desc = desc ? pango_font_description_copy (desc) : NULL; + /** + * PangoLayout:wrap: (attributes org.gtk.Property.get=pango_layout_get_wrap org.gtk.Property.set=pango_layout_set_wrap) + * + * The wrap mode of this `PangoLayout. + * + * The wrap mode influences how Pango chooses line breaks + * when text needs to be wrapped. + * + * The default value is `PANGO_WRAP_WORD`. + */ + props[PROP_WRAP] = g_param_spec_enum ("wrap", "wrap", "wrap", + PANGO_TYPE_WRAP_MODE, + PANGO_WRAP_WORD, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); - layout_changed (layout); - layout->tab_width = -1; - } -} + /** + * PangoLayout:indent: (attributes org.gtk.Property.get=pango_layout_get_indent org.gtk.Property.set=pango_layout_set_indent) + * + * The indent of this `PangoLayout. + * + * The indent is specified in Pango units. + * + * A negative value of @indent will produce a hanging indentation. + * That is, the first line will have the full width, and subsequent + * lines will be indented by the absolute value of @indent. + * + * The default value is 0. + */ + props[PROP_INDENT] = g_param_spec_int ("indent", "indent", "indent", + G_MININT, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); -/** - * pango_layout_get_font_description: - * @layout: a `PangoLayout` - * - * Gets the font description for the layout, if any. - * - * Return value: (transfer none) (nullable): a pointer to the - * layout's font description, or %NULL if the font description - * from the layout's context is inherited. - * - * Since: 1.8 - */ -const PangoFontDescription * -pango_layout_get_font_description (PangoLayout *layout) -{ - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); + /** + * PangoLayout:alignment: (attributes org.gtk.Property.get=pango_layout_get_alignment org.gtk.Property.set=pango_layout_set_alignment) + * + * The alignment mode of this `PangoLayout. + * + * The default value is `PANGO_ALIGNMENT_LEFT`. + */ + props[PROP_ALIGNMENT] = g_param_spec_enum ("alignment", "alignment", "alignment", + PANGO_TYPE_ALIGNMENT, + PANGO_ALIGN_LEFT, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); - return layout->font_desc; -} + /** + * PangoLayout:ellipsize: (attributes org.gtk.Property.get=pango_layout_get_ellipsize org.gtk.Property.set=pango_layout_set_ellipsize) + * + * The ellipsization mode of this `PangoLayout. + * + * The default value is `PANGO_ELLIPSIZE_NONE`. + */ + props[PROP_ELLIPSIZE] = g_param_spec_enum ("ellipsize", "ellipsize", "ellipsize", + PANGO_TYPE_ELLIPSIZE_MODE, + PANGO_ELLIPSIZE_NONE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); -/** - * pango_layout_set_justify: - * @layout: a `PangoLayout` - * @justify: whether the lines in the layout should be justified - * - * Sets whether each complete line should be stretched to fill the - * entire width of the layout. - * - * Stretching is typically done by adding whitespace, but for some scripts - * (such as Arabic), the justification may be done in more complex ways, - * like extending the characters. - * - * Note that this setting is not implemented and so is ignored in - * Pango older than 1.18. - * - * Note that tabs and justification conflict with each other: - * Justification will move content away from its tab-aligned - * positions. - * - * The default value is %FALSE. - * - * Also see [method@Pango.Layout.set_justify_last_line]. - */ -void -pango_layout_set_justify (PangoLayout *layout, - gboolean justify) -{ - g_return_if_fail (layout != NULL); + /** + * PangoLayout:auto-dir: (attributes org.gtk.Property.get=pango_layout_get_auto_dir org.gtk.Property.set=pango_layout_set_auto_dir) + * + * Whether this `PangoLayout` determines the + * base direction from the content. + * + * The default value is `TRUE`. + */ + props[PROP_AUTO_DIR] = g_param_spec_boolean ("auto-dir", "auto-dir", "auto-dir", + TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); - if (justify != layout->justify) - { - layout->justify = justify; + /** + * PangoLayout:lines: (attributes org.gtk.Property.get=pango_layout_get_lines) + * + * The `PangoLines` object holding the formatted lines. + */ + props[PROP_LINES] = g_param_spec_object ("lines", "lines", "lines", + PANGO_TYPE_LINES, + G_PARAM_READABLE); - if (layout->is_ellipsized || - layout->is_wrapped || - layout->justify_last_line) - layout_changed (layout); - } + g_object_class_install_properties (object_class, NUM_PROPERTIES, props); } -/** - * pango_layout_get_justify: - * @layout: a `PangoLayout` - * - * Gets whether each complete line should be stretched to fill the entire - * width of the layout. - * - * Return value: the justify value - */ -gboolean -pango_layout_get_justify (PangoLayout *layout) -{ - g_return_val_if_fail (layout != NULL, FALSE); - return layout->justify; -} +/* }}} */ +/* {{{ Utilities */ -/** - * pango_layout_set_justify_last_line: - * @layout: a `PangoLayout` - * @justify: whether the last line in the layout should be justified - * - * Sets whether the last line should be stretched to fill the - * entire width of the layout. - * - * This only has an effect if [method@Pango.Layout.set_justify] has - * been called as well. - * - * The default value is %FALSE. - * - * Since: 1.50 - */ -void -pango_layout_set_justify_last_line (PangoLayout *layout, - gboolean justify) +static void +layout_changed (PangoLayout *layout) { - g_return_if_fail (layout != NULL); - - if (justify != layout->justify_last_line) - { - layout->justify_last_line = justify; - - if (layout->justify) - layout_changed (layout); - } -} + layout->serial++; + if (layout->serial == 0) + layout->serial++; -/** - * pango_layout_get_justify_last_line: - * @layout: a `PangoLayout` - * - * Gets whether the last line should be stretched - * to fill the entire width of the layout. - * - * Return value: the justify value - * - * Since: 1.50 - */ -gboolean -pango_layout_get_justify_last_line (PangoLayout *layout) -{ - g_return_val_if_fail (layout != NULL, FALSE); - return layout->justify_last_line; + g_clear_object (&layout->lines); + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_LINES]); } -/** - * pango_layout_set_auto_dir: - * @layout: a `PangoLayout` - * @auto_dir: if %TRUE, compute the bidirectional base direction - * from the layout's contents - * - * Sets whether to calculate the base direction - * for the layout according to its contents. - * - * When this flag is on (the default), then paragraphs in @layout that - * begin with strong right-to-left characters (Arabic and Hebrew principally), - * will have right-to-left layout, paragraphs with letters from other scripts - * will have left-to-right layout. Paragraphs with only neutral characters - * get their direction from the surrounding paragraphs. - * - * When %FALSE, the choice between left-to-right and right-to-left - * layout is done according to the base direction of the layout's - * `PangoContext`. (See [method@Pango.Context.set_base_dir]). - * - * When the auto-computed direction of a paragraph differs from the - * base direction of the context, the interpretation of - * %PANGO_ALIGN_LEFT and %PANGO_ALIGN_RIGHT are swapped. - * - * Since: 1.4 - */ -void -pango_layout_set_auto_dir (PangoLayout *layout, - gboolean auto_dir) +static void +check_context_changed (PangoLayout *layout) { - g_return_if_fail (PANGO_IS_LAYOUT (layout)); + guint old_serial = layout->context_serial; - auto_dir = auto_dir != FALSE; + layout->context_serial = pango_context_get_serial (layout->context); - if (auto_dir != layout->auto_dir) - { - layout->auto_dir = auto_dir; - layout_changed (layout); - } + if (old_serial != layout->context_serial) + pango_layout_context_changed (layout); } -/** - * pango_layout_get_auto_dir: - * @layout: a `PangoLayout` - * - * Gets whether to calculate the base direction for the layout - * according to its contents. - * - * See [method@Pango.Layout.set_auto_dir]. - * - * Return value: %TRUE if the bidirectional base direction - * is computed from the layout's contents, %FALSE otherwise - * - * Since: 1.4 - */ -gboolean -pango_layout_get_auto_dir (PangoLayout *layout) +static PangoAttrList * +ensure_attrs (PangoLayout *layout, + PangoAttrList *attrs) { - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), TRUE); - - return layout->auto_dir; + if (attrs) + return attrs; + else if (layout->attrs) + return pango_attr_list_copy (layout->attrs); + else + return pango_attr_list_new (); } -/** - * pango_layout_set_alignment: - * @layout: a `PangoLayout` - * @alignment: the alignment - * - * Sets the alignment for the layout: how partial lines are - * positioned within the horizontal space available. - * - * The default alignment is %PANGO_ALIGN_LEFT. - */ -void -pango_layout_set_alignment (PangoLayout *layout, - PangoAlignment alignment) +static PangoAttrList * +get_effective_attributes (PangoLayout *layout) { - g_return_if_fail (layout != NULL); + PangoAttrList *attrs = NULL; - if (alignment != layout->alignment) + if (layout->font_desc) { - layout->alignment = alignment; - layout_changed (layout); + attrs = ensure_attrs (layout, attrs); + pango_attr_list_insert_before (attrs, + pango_attr_font_desc_new (layout->font_desc)); } -} -/** - * pango_layout_get_alignment: - * @layout: a `PangoLayout` - * - * Gets the alignment for the layout: how partial lines are - * positioned within the horizontal space available. - * - * Return value: the alignment - */ -PangoAlignment -pango_layout_get_alignment (PangoLayout *layout) -{ - g_return_val_if_fail (layout != NULL, PANGO_ALIGN_LEFT); - return layout->alignment; -} - - -/** - * pango_layout_set_tabs: - * @layout: a `PangoLayout` - * @tabs: (nullable): a `PangoTabArray` - * - * Sets the tabs to use for @layout, overriding the default tabs. - * - * `PangoLayout` will place content at the next tab position - * whenever it meets a Tab character (U+0009). - * - * By default, tabs are every 8 spaces. If @tabs is %NULL, the - * default tabs are reinstated. @tabs is copied into the layout; - * you must free your copy of @tabs yourself. - * - * Note that tabs and justification conflict with each other: - * Justification will move content away from its tab-aligned - * positions. The same is true for alignments other than - * %PANGO_ALIGN_LEFT. - */ -void -pango_layout_set_tabs (PangoLayout *layout, - PangoTabArray *tabs) -{ - g_return_if_fail (PANGO_IS_LAYOUT (layout)); - - - if (tabs != layout->tabs) + if (layout->line_spacing != 0.0) { - g_clear_pointer (&layout->tabs, pango_tab_array_free); - - if (tabs) - { - layout->tabs = pango_tab_array_copy (tabs); - pango_tab_array_sort (layout->tabs); - } - - layout_changed (layout); + attrs = ensure_attrs (layout, attrs); + pango_attr_list_insert_before (attrs, + pango_attr_line_height_new (layout->line_spacing)); } -} -/** - * pango_layout_get_tabs: - * @layout: a `PangoLayout` - * - * Gets the current `PangoTabArray` used by this layout. - * - * If no `PangoTabArray` has been set, then the default tabs are - * in use and %NULL is returned. Default tabs are every 8 spaces. - * - * The return value should be freed with [method@Pango.TabArray.free]. - * - * Return value: (transfer full) (nullable): a copy of the tabs for this layout - */ -PangoTabArray* -pango_layout_get_tabs (PangoLayout *layout) -{ - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); - - if (layout->tabs) - return pango_tab_array_copy (layout->tabs); - else - return NULL; -} - -/** - * pango_layout_set_single_paragraph_mode: - * @layout: a `PangoLayout` - * @setting: new setting - * - * Sets the single paragraph mode of @layout. - * - * If @setting is %TRUE, do not treat newlines and similar characters - * as paragraph separators; instead, keep all text in a single paragraph, - * and display a glyph for paragraph separator characters. Used when - * you want to allow editing of newlines on a single text line. - * - * The default value is %FALSE. - */ -void -pango_layout_set_single_paragraph_mode (PangoLayout *layout, - gboolean setting) -{ - g_return_if_fail (PANGO_IS_LAYOUT (layout)); - - setting = setting != FALSE; - - if (layout->single_paragraph != setting) + if (layout->single_paragraph) { - layout->single_paragraph = setting; - layout_changed (layout); + attrs = ensure_attrs (layout, attrs); + pango_attr_list_insert_before (attrs, + pango_attr_paragraph_new ()); } -} -/** - * pango_layout_get_single_paragraph_mode: - * @layout: a `PangoLayout` - * - * Obtains whether @layout is in single paragraph mode. - * - * See [method@Pango.Layout.set_single_paragraph_mode]. - * - * Return value: %TRUE if the layout does not break paragraphs - * at paragraph separator characters, %FALSE otherwise - */ -gboolean -pango_layout_get_single_paragraph_mode (PangoLayout *layout) -{ - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), FALSE); + if (attrs) + return attrs; - return layout->single_paragraph; + return pango_attr_list_ref (layout->attrs); } -/** - * pango_layout_set_ellipsize: - * @layout: a `PangoLayout` - * @ellipsize: the new ellipsization mode for @layout - * - * Sets the type of ellipsization being performed for @layout. - * - * Depending on the ellipsization mode @ellipsize text is - * removed from the start, middle, or end of text so they - * fit within the width and height of layout set with - * [method@Pango.Layout.set_width] and [method@Pango.Layout.set_height]. - * - * If the layout contains characters such as newlines that - * force it to be layed out in multiple paragraphs, then whether - * each paragraph is ellipsized separately or the entire layout - * is ellipsized as a whole depends on the set height of the layout. - * - * The default value is %PANGO_ELLIPSIZE_NONE. - * - * See [method@Pango.Layout.set_height] for details. - * - * Since: 1.6 - */ -void -pango_layout_set_ellipsize (PangoLayout *layout, - PangoEllipsizeMode ellipsize) +static gboolean +ends_with_paragraph_separator (PangoLayout *layout) { - g_return_if_fail (PANGO_IS_LAYOUT (layout)); - - if (ellipsize != layout->ellipsize) - { - layout->ellipsize = ellipsize; + if (layout->single_paragraph) + return FALSE; - if (layout->is_ellipsized || layout->is_wrapped) - layout_changed (layout); - } + return g_str_has_suffix (layout->text, "\n") || + g_str_has_suffix (layout->text, "\r") || + g_str_has_suffix (layout->text, "\r\n") || + g_str_has_suffix (layout->text, "
"); } -/** - * pango_layout_get_ellipsize: - * @layout: a `PangoLayout` - * - * Gets the type of ellipsization being performed for @layout. - * - * See [method@Pango.Layout.set_ellipsize]. - * - * Use [method@Pango.Layout.is_ellipsized] to query whether any - * paragraphs were actually ellipsized. - * - * Return value: the current ellipsization mode for @layout - * - * Since: 1.6 - */ -PangoEllipsizeMode -pango_layout_get_ellipsize (PangoLayout *layout) +static void +ensure_lines (PangoLayout *layout) { - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), PANGO_ELLIPSIZE_NONE); + PangoLineBreaker *breaker; + PangoAttrList *attrs; + int x, y, width; + int line_no; - return layout->ellipsize; -} + check_context_changed (layout); -/** - * pango_layout_is_ellipsized: - * @layout: a `PangoLayout` - * - * Queries whether the layout had to ellipsize any paragraphs. - * - * This returns %TRUE if the ellipsization mode for @layout - * is not %PANGO_ELLIPSIZE_NONE, a positive width is set on @layout, - * and there are paragraphs exceeding that width that have to be - * ellipsized. - * - * Return value: %TRUE if any paragraphs had to be ellipsized, - * %FALSE otherwise - * - * Since: 1.16 - */ -gboolean -pango_layout_is_ellipsized (PangoLayout *layout) -{ - g_return_val_if_fail (layout != NULL, FALSE); + if (layout->lines) + return; - pango_layout_check_lines (layout); + breaker = pango_line_breaker_new (layout->context); - return layout->is_ellipsized; -} + pango_line_breaker_set_tabs (breaker, layout->tabs); + pango_line_breaker_set_base_dir (breaker, + layout->auto_dir + ? PANGO_DIRECTION_NEUTRAL + : pango_context_get_base_dir (layout->context)); -/** - * pango_layout_set_text: - * @layout: a `PangoLayout` - * @text: the text - * @length: maximum length of @text, in bytes. -1 indicates that - * the string is nul-terminated and the length should be calculated. - * The text will also be truncated on encountering a nul-termination - * even when @length is positive. - * - * Sets the text of the layout. - * - * This function validates @text and renders invalid UTF-8 - * with a placeholder glyph. - * - * Note that if you have used [method@Pango.Layout.set_markup] or - * [method@Pango.Layout.set_markup_with_accel] on @layout before, you - * may want to call [method@Pango.Layout.set_attributes] to clear the - * attributes set on the layout from the markup as this function does - * not clear attributes. - */ -void -pango_layout_set_text (PangoLayout *layout, - const char *text, - int length) -{ - char *old_text, *start, *end; - - g_return_if_fail (layout != NULL); - g_return_if_fail (length == 0 || text != NULL); + attrs = get_effective_attributes (layout); + pango_line_breaker_add_text (breaker, layout->text ? layout->text : "", -1, attrs); + if (attrs) + pango_attr_list_unref (attrs); - old_text = layout->text; + layout->lines = pango_lines_new (); - if (length < 0) - { - layout->length = strlen (text); - layout->text = g_strndup (text, layout->length); - } - else if (length > 0) - { - /* This is not exactly what we want. We don't need the padding... - */ - layout->length = length; - layout->text = g_strndup (text, length); - } - else + x = y = 0; + line_no = 0; + while (!pango_line_breaker_done (breaker)) { - layout->length = 0; - layout->text = g_malloc0 (1); - } + PangoLayoutLine *line; + PangoRectangle ext; + int offset; + PangoEllipsizeMode ellipsize = PANGO_ELLIPSIZE_NONE; - /* validate it, and replace invalid bytes with -1 */ - start = layout->text; - for (;;) { - gboolean valid; + if ((line_no == 0) == (layout->indent > 0)) + { + x = abs (layout->indent); + width = layout->width - x; + } + else + { + x = 0; + width = layout->width; + } - valid = g_utf8_validate (start, -1, (const char **)&end); + if (layout->height < 0 && line_no + 1 == - layout->height) + ellipsize = layout->ellipsize; - if (!*end) - break; +retry: + line = pango_line_breaker_next_line (breaker, x, width, layout->wrap, ellipsize); + pango_layout_line_get_extents (line, NULL, &ext); - /* Replace invalid bytes with -1. The -1 will be converted to - * ((gunichar) -1) by glib, and that in turn yields a glyph value of - * ((PangoGlyph) -1) by PANGO_GET_UNKNOWN_GLYPH(-1), - * and that's PANGO_GLYPH_INVALID_INPUT. - */ - if (!valid) - *end++ = -1; + if (layout->height >= 0 && y + 2 * ext.height >= layout->height && + ellipsize != layout->ellipsize) + { + if (pango_line_breaker_undo_line (breaker, line)) + { + g_clear_object (&line); + ellipsize = layout->ellipsize; + goto retry; + } + } - start = end; - } + /* Handle alignment and justification */ + offset = 0; + switch (layout->alignment) + { + case PANGO_ALIGN_LEFT: + break; + case PANGO_ALIGN_CENTER: + if (ext.width < width) + offset = (width - ext.width) / 2; + break; + case PANGO_ALIGN_RIGHT: + if (ext.width < width) + offset = width - ext.width; + break; + case PANGO_ALIGN_JUSTIFY: + if (!pango_layout_line_ends_paragraph (line)) + line = pango_layout_line_justify (line, width); + break; + case PANGO_ALIGN_JUSTIFY_ALL: + line = pango_layout_line_justify (line, width); + break; + default: g_assert_not_reached (); + } - if (start != layout->text) - /* TODO: Write out the beginning excerpt of text? */ - g_warning ("Invalid UTF-8 string passed to pango_layout_set_text()"); + pango_lines_add_line (layout->lines, line, x + offset, y - ext.y); - layout->n_chars = pango_utf8_strlen (layout->text, -1); - layout->length = strlen (layout->text); + y += ext.height; + line_no++; + } - g_clear_pointer (&layout->log_attrs, g_free); - layout_changed (layout); + /* Append an empty line if we end with a newline. + * And always provide at least one line + */ + if (pango_lines_get_line_count (layout->lines) == 0 || + ends_with_paragraph_separator (layout)) + { + LineData *data; + int start_index; + int start_offset; + int offset; + PangoLayoutLine *line; + PangoRectangle ext; - g_free (old_text); -} + if (pango_lines_get_line_count (layout->lines) > 0) + { + PangoLayoutLine *last; -/** - * pango_layout_get_text: - * @layout: a `PangoLayout` - * - * Gets the text in the layout. - * - * The returned text should not be freed or modified. - * - * Return value: (transfer none): the text in the @layout - */ -const char* -pango_layout_get_text (PangoLayout *layout) -{ - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); + last = pango_lines_get_line (layout->lines, + pango_lines_get_line_count (layout->lines) - 1, + NULL, NULL); + data = last->data; + start_index = data->length; + start_offset = last->data->n_chars; + offset = MAX (layout->indent, 0); + } + else + { + data = line_data_new (); + data->text = g_strdup (""); + data->length = 0; + data->attrs = get_effective_attributes (layout); + data->log_attrs = g_new0 (PangoLogAttr, 1); + data->log_attrs[0].is_cursor_position = TRUE; + start_index = 0; + start_offset = 0; + offset = 0; + } - /* We don't ever want to return NULL as the text. - */ - if (G_UNLIKELY (!layout->text)) - return ""; + line = pango_layout_line_new (layout->context, data); + line->starts_paragraph = TRUE; + line->ends_paragraph = TRUE; + line->start_index = start_index; + line->length = 0; + line->start_offset = start_offset; + line->n_chars = 0; - return layout->text; -} + pango_layout_line_get_extents (line, NULL, &ext); -/** - * pango_layout_get_character_count: - * @layout: a `PangoLayout` - * - * Returns the number of Unicode characters in the - * the text of @layout. - * - * Return value: the number of Unicode characters - * in the text of @layout - * - * Since: 1.30 - */ -gint -pango_layout_get_character_count (PangoLayout *layout) -{ - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), 0); + pango_lines_add_line (layout->lines, line, x + offset, y - ext.y); - return layout->n_chars; -} + line_data_unref (data); + } -/** - * pango_layout_set_markup: - * @layout: a `PangoLayout` - * @markup: marked-up text - * @length: length of marked-up text in bytes, or -1 if @markup is - * `NUL`-terminated - * - * Sets the layout text and attribute list from marked-up text. - * - * See [Pango Markup](pango_markup.html)). - * - * Replaces the current text and attribute list. - * - * This is the same as [method@Pango.Layout.set_markup_with_accel], - * but the markup text isn't scanned for accelerators. - */ -void -pango_layout_set_markup (PangoLayout *layout, - const char *markup, - int length) -{ - pango_layout_set_markup_with_accel (layout, markup, length, 0, NULL); + g_object_unref (breaker); } +/* }}} */ +/* {{{ Public API */ + /** - * pango_layout_set_markup_with_accel: - * @layout: a `PangoLayout` - * @markup: marked-up text (see [Pango Markup](pango_markup.html)) - * @length: length of marked-up text in bytes, or -1 if @markup is - * `NUL`-terminated - * @accel_marker: marker for accelerators in the text - * @accel_char: (out caller-allocates) (optional): return location - * for first located accelerator - * - * Sets the layout text and attribute list from marked-up text. + * pango_layout_new: + * @context: a `PangoContext` * - * See [Pango Markup](pango_markup.html)). + * Creates a new `PangoLayout` with attribute initialized to + * default values for a particular `PangoContext` * - * Replaces the current text and attribute list. - * - * If @accel_marker is nonzero, the given character will mark the - * character following it as an accelerator. For example, @accel_marker - * might be an ampersand or underscore. All characters marked - * as an accelerator will receive a %PANGO_UNDERLINE_LOW attribute, - * and the first character so marked will be returned in @accel_char. - * Two @accel_marker characters following each other produce a single - * literal @accel_marker character. + * Return value: a newly allocated `PangoLayout` */ -void -pango_layout_set_markup_with_accel (PangoLayout *layout, - const char *markup, - int length, - gunichar accel_marker, - gunichar *accel_char) +PangoLayout * +pango_layout_new (PangoContext *context) { - PangoAttrList *list = NULL; - char *text = NULL; - GError *error; - - g_return_if_fail (PANGO_IS_LAYOUT (layout)); - g_return_if_fail (markup != NULL); + g_return_val_if_fail (PANGO_IS_CONTEXT (context), NULL); - error = NULL; - if (!pango_parse_markup (markup, length, - accel_marker, - &list, &text, - accel_char, - &error)) - { - g_warning ("pango_layout_set_markup_with_accel: %s", error->message); - g_error_free (error); - return; - } - - pango_layout_set_text (layout, text, -1); - pango_layout_set_attributes (layout, list); - pango_attr_list_unref (list); - g_free (text); + return g_object_new (PANGO_TYPE_LAYOUT, "context", context, NULL); } /** - * pango_layout_get_unknown_glyphs_count: + * pango_layout_copy: * @layout: a `PangoLayout` * - * Counts the number of unknown glyphs in @layout. - * - * This function can be used to determine if there are any fonts - * available to render all characters in a certain string, or when - * used in combination with %PANGO_ATTR_FALLBACK, to check if a - * certain font supports all the characters in the string. + * Creates a deep copy-by-value of the layout. * - * Return value: The number of unknown glyphs in @layout + * The attribute list, tab array, and text from the original layout + * are all copied by value. * - * Since: 1.16 + * Return value: (transfer full): the newly allocated `PangoLayout` */ -int -pango_layout_get_unknown_glyphs_count (PangoLayout *layout) -{ - PangoLayoutLine *line; - PangoLayoutRun *run; - GSList *lines_list; - GSList *runs_list; - int i, count = 0; - - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), 0); - - pango_layout_check_lines (layout); - - if (layout->unknown_glyphs_count >= 0) - return layout->unknown_glyphs_count; - - lines_list = layout->lines; - while (lines_list) - { - line = lines_list->data; - runs_list = line->runs; - - while (runs_list) - { - run = runs_list->data; - - for (i = 0; i < run->glyphs->num_glyphs; i++) - { - if (run->glyphs->glyphs[i].glyph & PANGO_GLYPH_UNKNOWN_FLAG) - count++; - } - - runs_list = runs_list->next; - } - lines_list = lines_list->next; - } - - layout->unknown_glyphs_count = count; - return count; -} - -static void -check_context_changed (PangoLayout *layout) +PangoLayout * +pango_layout_copy (PangoLayout *layout) { - guint old_serial = layout->context_serial; - - layout->context_serial = pango_context_get_serial (layout->context); - - if (old_serial != layout->context_serial) - pango_layout_context_changed (layout); -} + PangoLayout *copy; -static void -layout_changed (PangoLayout *layout) -{ - layout->serial++; - if (layout->serial == 0) - layout->serial++; + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); - pango_layout_clear_lines (layout); -} + copy = pango_layout_new (layout->context); -/** - * pango_layout_context_changed: - * @layout: a `PangoLayout` - * - * Forces recomputation of any state in the `PangoLayout` that - * might depend on the layout's context. - * - * This function should be called if you make changes to the context - * subsequent to creating the layout. - */ -void -pango_layout_context_changed (PangoLayout *layout) -{ - g_return_if_fail (PANGO_IS_LAYOUT (layout)); + copy->text = g_strdup (layout->text); + copy->length = layout->length; + if (layout->attrs) + copy->attrs = pango_attr_list_copy (layout->attrs); + if (layout->font_desc) + copy->font_desc = pango_font_description_copy (layout->font_desc); + copy->line_spacing = layout->line_spacing; + copy->width = layout->width; + copy->height = layout->height; + if (layout->tabs) + copy->tabs = pango_tab_array_copy (layout->tabs); + copy->single_paragraph = layout->single_paragraph; + copy->wrap = layout->wrap; + copy->indent = layout->indent; + copy->serial = layout->serial; + copy->context_serial = layout->context_serial; + copy->alignment = layout->alignment; + copy->ellipsize = layout->ellipsize; + copy->auto_dir = layout->auto_dir; - layout_changed (layout); - layout->tab_width = -1; + return copy; } /** @@ -1488,6399 +757,816 @@ pango_layout_context_changed (PangoLayout *layout) * The serial number is initialized to an small number larger than zero * when a new layout is created and is increased whenever the layout is * changed using any of the setter functions, or the `PangoContext` it - * uses has changed. The serial may wrap, but will never have the value 0. - * Since it can wrap, never compare it with "less than", always use "not equals". + * uses has changed. + * + * The serial may wrap, but will never have the value 0. Since it can + * wrap, never compare it with "less than", always use "not equals". * * This can be used to automatically detect changes to a `PangoLayout`, * and is useful for example to decide whether a layout needs redrawing. - * To force the serial to be increased, use - * [method@Pango.Layout.context_changed]. * - * Return value: The current serial number of @layout. - * - * Since: 1.32.4 + * Return value: The current serial number of @layout */ guint pango_layout_get_serial (PangoLayout *layout) { check_context_changed (layout); - return layout->serial; -} - -/** - * pango_layout_get_log_attrs: - * @layout: a `PangoLayout` - * @attrs: (out)(array length=n_attrs)(transfer container): - * location to store a pointer to an array of logical attributes. - * This value must be freed with g_free(). - * @n_attrs: (out): location to store the number of the attributes in the - * array. (The stored value will be one more than the total number - * of characters in the layout, since there need to be attributes - * corresponding to both the position before the first character - * and the position after the last character.) - * - * Retrieves an array of logical attributes for each character in - * the @layout. - */ -void -pango_layout_get_log_attrs (PangoLayout *layout, - PangoLogAttr **attrs, - gint *n_attrs) -{ - g_return_if_fail (layout != NULL); - - pango_layout_check_lines (layout); - if (attrs) - { - *attrs = g_new (PangoLogAttr, layout->n_chars + 1); - memcpy (*attrs, layout->log_attrs, sizeof(PangoLogAttr) * (layout->n_chars + 1)); - } - - if (n_attrs) - *n_attrs = layout->n_chars + 1; + return layout->serial; } /** - * pango_layout_get_log_attrs_readonly: + * pango_layout_context_changed: * @layout: a `PangoLayout` - * @n_attrs: (out): location to store the number of the attributes in - * the array - * - * Retrieves an array of logical attributes for each character in - * the @layout. - * - * This is a faster alternative to [method@Pango.Layout.get_log_attrs]. - * The returned array is part of @layout and must not be modified. - * Modifying the layout will invalidate the returned array. - * - * The number of attributes returned in @n_attrs will be one more - * than the total number of characters in the layout, since there - * need to be attributes corresponding to both the position before - * the first character and the position after the last character. - * - * Returns: (array length=n_attrs): an array of logical attributes - * - * Since: 1.30 - */ -const PangoLogAttr * -pango_layout_get_log_attrs_readonly (PangoLayout *layout, - gint *n_attrs) -{ - if (n_attrs) - *n_attrs = 0; - g_return_val_if_fail (layout != NULL, NULL); - - pango_layout_check_lines (layout); - - if (n_attrs) - *n_attrs = layout->n_chars + 1; - - return layout->log_attrs; -} - - -/** - * pango_layout_get_line_count: - * @layout: `PangoLayout` * - * Retrieves the count of lines for the @layout. + * Forces recomputation of any state in the `PangoLayout` that + * might depend on the layout's context. * - * Return value: the line count + * This function should be called if you make changes to the + * context subsequent to creating the layout. */ -int -pango_layout_get_line_count (PangoLayout *layout) +void +pango_layout_context_changed (PangoLayout *layout) { - g_return_val_if_fail (layout != NULL, 0); + g_return_if_fail (PANGO_IS_LAYOUT (layout)); - pango_layout_check_lines (layout); - return layout->line_count; + layout_changed (layout); } -/** - * pango_layout_get_lines: - * @layout: a `PangoLayout` - * - * Returns the lines of the @layout as a list. - * - * Use the faster [method@Pango.Layout.get_lines_readonly] if you do not - * plan to modify the contents of the lines (glyphs, glyph widths, etc.). - * - * Return value: (element-type Pango.LayoutLine) (transfer none): a `GSList` - * containing the lines in the layout. This points to internal data of the - * `PangoLayout` and must be used with care. It will become invalid on any - * change to the layout's text or properties. - */ -GSList * -pango_layout_get_lines (PangoLayout *layout) -{ - pango_layout_check_lines (layout); - - if (layout->lines) - { - GSList *tmp_list = layout->lines; - while (tmp_list) - { - PangoLayoutLine *line = tmp_list->data; - tmp_list = tmp_list->next; - - pango_layout_line_leaked (line); - } - } - - return layout->lines; -} +/* {{{ Property getters and setters */ /** - * pango_layout_get_lines_readonly: + * pango_layout_get_context: * @layout: a `PangoLayout` * - * Returns the lines of the @layout as a list. - * - * This is a faster alternative to [method@Pango.Layout.get_lines], - * but the user is not expected to modify the contents of the lines - * (glyphs, glyph widths, etc.). - * - * Return value: (element-type Pango.LayoutLine) (transfer none): a `GSList` - * containing the lines in the layout. This points to internal data of the - * `PangoLayout` and must be used with care. It will become invalid on any - * change to the layout's text or properties. No changes should be made to - * the lines. + * Retrieves the `PangoContext` used for this layout. * - * Since: 1.16 + * Return value: (transfer none): the `PangoContext` for the layout */ -GSList * -pango_layout_get_lines_readonly (PangoLayout *layout) +PangoContext * +pango_layout_get_context (PangoLayout *layout) { - pango_layout_check_lines (layout); + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); - return layout->lines; + return layout->context; } /** - * pango_layout_get_line: + * pango_layout_set_text: * @layout: a `PangoLayout` - * @line: the index of a line, which must be between 0 and - * `pango_layout_get_line_count(layout) - 1`, inclusive. - * - * Retrieves a particular line from a `PangoLayout`. - * - * Use the faster [method@Pango.Layout.get_line_readonly] if you do not - * plan to modify the contents of the line (glyphs, glyph widths, etc.). + * @text: the text + * @length: maximum length of @text, in bytes. -1 indicates that + * the string is nul-terminated * - * Return value: (transfer none) (nullable): the requested `PangoLayoutLine`, - * or %NULL if the index is out of range. This layout line can be ref'ed - * and retained, but will become invalid if changes are made to the - * `PangoLayout`. + * Sets the text of the layout. */ -PangoLayoutLine * -pango_layout_get_line (PangoLayout *layout, - int line) +void +pango_layout_set_text (PangoLayout *layout, + const char *text, + int length) { - GSList *list_item; - g_return_val_if_fail (layout != NULL, NULL); - - if (line < 0) - return NULL; - - pango_layout_check_lines (layout); - - list_item = g_slist_nth (layout->lines, line); + g_return_if_fail (PANGO_IS_LAYOUT (layout)); - if (list_item) - { - PangoLayoutLine *line = list_item->data; + if (length < 0) + length = strlen (text); - pango_layout_line_leaked (line); - return line; - } + g_free (layout->text); + layout->text = g_strndup (text, length); + layout->length = length; - return NULL; + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_TEXT]); + layout_changed (layout); } /** - * pango_layout_get_line_readonly: + * pango_layout_get_text: * @layout: a `PangoLayout` - * @line: the index of a line, which must be between 0 and - * `pango_layout_get_line_count(layout) - 1`, inclusive. - * - * Retrieves a particular line from a `PangoLayout`. - * - * This is a faster alternative to [method@Pango.Layout.get_line], - * but the user is not expected to modify the contents of the line - * (glyphs, glyph widths, etc.). * - * Return value: (transfer none) (nullable): the requested `PangoLayoutLine`, - * or %NULL if the index is out of range. This layout line can be ref'ed - * and retained, but will become invalid if changes are made to the - * `PangoLayout`. No changes should be made to the line. + * Gets the text in the layout. * - * Since: 1.16 - */ -PangoLayoutLine * -pango_layout_get_line_readonly (PangoLayout *layout, - int line) -{ - GSList *list_item; - g_return_val_if_fail (layout != NULL, NULL); - - if (line < 0) - return NULL; - - pango_layout_check_lines (layout); - - list_item = g_slist_nth (layout->lines, line); - - if (list_item) - { - PangoLayoutLine *line = list_item->data; - return line; - } - - return NULL; -} - -/** - * pango_layout_line_index_to_x: - * @line: a `PangoLayoutLine` - * @index_: byte offset of a grapheme within the layout - * @trailing: an integer indicating the edge of the grapheme to retrieve - * the position of. If > 0, the trailing edge of the grapheme, - * if 0, the leading of the grapheme - * @x_pos: (out): location to store the x_offset (in Pango units) + * The returned text should not be freed or modified. * - * Converts an index within a line to a X position. + * Return value: (transfer none): the text in the @layout */ -void -pango_layout_line_index_to_x (PangoLayoutLine *line, - int index, - int trailing, - int *x_pos) -{ - PangoLayout *layout = line->layout; - GSList *run_list = line->runs; - int width = 0; - - while (run_list) - { - PangoLayoutRun *run = run_list->data; - - if (run->item->offset <= index && run->item->offset + run->item->length > index) - { - int offset = g_utf8_pointer_to_offset (layout->text, layout->text + index); - int attr_offset; - - if (trailing) - { - while (index < line->start_index + line->length && - offset + 1 < layout->n_chars && - !layout->log_attrs[offset + 1].is_cursor_position) - { - offset++; - index = g_utf8_next_char (layout->text + index) - layout->text; - } - } - else - { - while (index > line->start_index && - !layout->log_attrs[offset].is_cursor_position) - { - offset--; - index = g_utf8_prev_char (layout->text + index) - layout->text; - } - } - - /* Note: we simply assert here, since our items are all internally - * created. If that ever changes, we need to add a fallback here. - */ - g_assert (run->item->analysis.flags & PANGO_ANALYSIS_FLAG_HAS_CHAR_OFFSET); - attr_offset = ((PangoItemPrivate *)run->item)->char_offset; - - pango_glyph_string_index_to_x_full (run->glyphs, - layout->text + run->item->offset, - run->item->length, - &run->item->analysis, - layout->log_attrs + attr_offset, - index - run->item->offset, trailing, x_pos); - if (x_pos) - *x_pos += width; - - return; - } - - width += pango_glyph_string_get_width (run->glyphs); - - run_list = run_list->next; - } - - if (x_pos) - *x_pos = width; -} - -static PangoLayoutLine * -pango_layout_index_to_line (PangoLayout *layout, - int index, - int *line_nr, - PangoLayoutLine **line_before, - PangoLayoutLine **line_after) -{ - GSList *tmp_list; - GSList *line_list; - PangoLayoutLine *line = NULL; - PangoLayoutLine *prev_line = NULL; - int i = -1; - - line_list = tmp_list = layout->lines; - while (tmp_list) - { - PangoLayoutLine *tmp_line = tmp_list->data; - - if (tmp_line->start_index > index) - break; /* index was in paragraph delimiters */ - - prev_line = line; - line = tmp_line; - line_list = tmp_list; - i++; - - if (line->start_index + line->length > index) - break; - - tmp_list = tmp_list->next; - } - - if (line_nr) - *line_nr = i; - - if (line_before) - *line_before = prev_line; - - if (line_after) - *line_after = (line_list && line_list->next) ? line_list->next->data : NULL; - - return line; -} - -static PangoLayoutLine * -pango_layout_index_to_line_and_extents (PangoLayout *layout, - int index, - PangoRectangle *line_rect, - PangoRectangle *run_rect) +const char * +pango_layout_get_text (PangoLayout *layout) { - PangoLayoutIter iter; - PangoLayoutLine *line = NULL; - - _pango_layout_get_iter (layout, &iter); - - if (!ITER_IS_INVALID (&iter)) - while (TRUE) - { - PangoLayoutLine *tmp_line = _pango_layout_iter_get_line (&iter); - - if (tmp_line->start_index > index) - break; /* index was in paragraph delimiters */ - - line = tmp_line; - - pango_layout_iter_get_line_extents (&iter, NULL, line_rect); - - if (!iter.line_list_link->next || - ((PangoLayoutLine *)iter.line_list_link->next->data)->start_index > index) - { - if (run_rect) - { - while (TRUE) - { - PangoLayoutRun *run = _pango_layout_iter_get_run (&iter); - - pango_layout_iter_get_run_extents (&iter, NULL, run_rect); - - if (!run) - break; - - if (run->item->offset <= index && index < run->item->offset + run->item->length) - break; - - if (!pango_layout_iter_next_run (&iter)) - break; - } - } - - break; - } - - if (!pango_layout_iter_next_line (&iter)) - break; /* Use end of last line */ - } - - _pango_layout_iter_destroy (&iter); + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); - return line; + return layout->text; } /** - * pango_layout_index_to_line_x: + * pango_layout_set_attributes: * @layout: a `PangoLayout` - * @index_: the byte index of a grapheme within the layout - * @trailing: an integer indicating the edge of the grapheme to retrieve the - * position of. If > 0, the trailing edge of the grapheme, if 0, - * the leading of the grapheme - * @line: (out) (optional): location to store resulting line index. (which will - * between 0 and pango_layout_get_line_count(layout) - 1) - * @x_pos: (out) (optional): location to store resulting position within line - * (%PANGO_SCALE units per device unit) + * @attrs: (nullable) (transfer none): a `PangoAttrList` * - * Converts from byte @index_ within the @layout to line and X position. + * Sets the attributes for a layout object. * - * The X position is measured from the left edge of the line. + * References @attrs, so the caller can unref its reference. */ void -pango_layout_index_to_line_x (PangoLayout *layout, - int index, - gboolean trailing, - int *line, - int *x_pos) -{ - int line_num; - PangoLayoutLine *layout_line = NULL; - - g_return_if_fail (layout != NULL); - g_return_if_fail (index >= 0); - g_return_if_fail (index <= layout->length); - - pango_layout_check_lines (layout); - - layout_line = pango_layout_index_to_line (layout, index, - &line_num, NULL, NULL); - - if (layout_line) - { - /* use end of line if index was in the paragraph delimiters */ - if (index > layout_line->start_index + layout_line->length) - index = layout_line->start_index + layout_line->length; - - if (line) - *line = line_num; - - pango_layout_line_index_to_x (layout_line, index, trailing, x_pos); - } - else - { - if (line) - *line = -1; - if (x_pos) - *x_pos = -1; - } -} - -typedef struct { - int x; - int pos; -} CursorPos; - -static int -compare_cursor (gconstpointer v1, - gconstpointer v2) -{ - const CursorPos *c1 = v1; - const CursorPos *c2 = v2; - - return c1->x - c2->x; -} - -static void -pango_layout_line_get_cursors (PangoLayoutLine *line, - gboolean strong, - GArray *cursors) +pango_layout_set_attributes (PangoLayout *layout, + PangoAttrList *attrs) { - PangoLayout *layout = line->layout; - int line_no; - PangoLayoutLine *line2; - const char *start, *end; - int start_offset; - int j; - const char *p; - PangoRectangle pos; - - g_assert (g_array_get_element_size (cursors) == sizeof (CursorPos)); - g_assert (cursors->len == 0); - - start = layout->text + line->start_index; - end = start + line->length; - start_offset = g_utf8_pointer_to_offset (layout->text, start); - - pango_layout_index_to_line_x (layout, line->start_index + line->length, 0, &line_no, NULL); - line2 = pango_layout_get_line (layout, line_no); - if (line2 == line) - end++; - - for (j = start_offset, p = start; p < end; j++, p = g_utf8_next_char (p)) - { - if (layout->log_attrs[j].is_cursor_position) - { - CursorPos cursor; - - pango_layout_get_cursor_pos (layout, p - layout->text, - strong ? &pos : NULL, - strong ? NULL : &pos); + g_return_if_fail (PANGO_IS_LAYOUT (layout)); - cursor.x = pos.x; - cursor.pos = p - layout->text; - g_array_append_val (cursors, cursor); - } - } + g_clear_pointer (&layout->attrs, pango_attr_list_unref); + layout->attrs = attrs; + if (layout->attrs) + pango_attr_list_ref (layout->attrs); - g_array_sort (cursors, compare_cursor); + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_ATTRIBUTES]); + layout_changed (layout); } /** - * pango_layout_move_cursor_visually: + * pango_layout_get_attributes: * @layout: a `PangoLayout` - * @strong: whether the moving cursor is the strong cursor or the - * weak cursor. The strong cursor is the cursor corresponding - * to text insertion in the base direction for the layout. - * @old_index: the byte index of the current cursor position - * @old_trailing: if 0, the cursor was at the leading edge of the - * grapheme indicated by @old_index, if > 0, the cursor - * was at the trailing edge. - * @direction: direction to move cursor. A negative - * value indicates motion to the left - * @new_index: (out): location to store the new cursor byte index. - * A value of -1 indicates that the cursor has been moved off the - * beginning of the layout. A value of %G_MAXINT indicates that - * the cursor has been moved off the end of the layout. - * @new_trailing: (out): number of characters to move forward from - * the location returned for @new_index to get the position where - * the cursor should be displayed. This allows distinguishing the - * position at the beginning of one line from the position at the - * end of the preceding line. @new_index is always on the line where - * the cursor should be displayed. - * - * Computes a new cursor position from an old position and a direction. * - * If @direction is positive, then the new position will cause the strong - * or weak cursor to be displayed one position to right of where it was - * with the old cursor position. If @direction is negative, it will be - * moved to the left. - * - * In the presence of bidirectional text, the correspondence between - * logical and visual order will depend on the direction of the current - * run, and there may be jumps when the cursor is moved off of the end - * of a run. + * Gets the attribute list for the layout, if any. * - * Motion here is in cursor positions, not in characters, so a single - * call to this function may move the cursor over multiple characters - * when multiple characters combine to form a single grapheme. + * Return value: (transfer none) (nullable): a `PangoAttrList` */ -void -pango_layout_move_cursor_visually (PangoLayout *layout, - gboolean strong, - int old_index, - int old_trailing, - int direction, - int *new_index, - int *new_trailing) +PangoAttrList * +pango_layout_get_attributes (PangoLayout *layout) { - PangoLayoutLine *line = NULL; - PangoLayoutLine *prev_line; - PangoLayoutLine *next_line; - GArray *cursors; - int n_vis; - int vis_pos; - int start_offset; - gboolean off_start = FALSE; - gboolean off_end = FALSE; - PangoRectangle pos; - int j; - - g_return_if_fail (layout != NULL); - g_return_if_fail (old_index >= 0 && old_index <= layout->length); - g_return_if_fail (old_trailing >= 0); - g_return_if_fail (old_index < layout->length || old_trailing == 0); - g_return_if_fail (new_index != NULL); - g_return_if_fail (new_trailing != NULL); - - direction = (direction >= 0 ? 1 : -1); - - pango_layout_check_lines (layout); - - /* Find the line the old cursor is on */ - line = pango_layout_index_to_line (layout, old_index, NULL, &prev_line, &next_line); - - while (old_trailing--) - old_index = g_utf8_next_char (layout->text + old_index) - layout->text; - - /* Clamp old_index to fit on the line */ - if (old_index > (line->start_index + line->length)) - old_index = line->start_index + line->length; - - cursors = g_array_new (FALSE, FALSE, sizeof (CursorPos)); - pango_layout_line_get_cursors (line, strong, cursors); - - pango_layout_get_cursor_pos (layout, old_index, strong ? &pos : NULL, strong ? NULL : &pos); - - vis_pos = -1; - for (j = 0; j < cursors->len; j++) - { - CursorPos *cursor = &g_array_index (cursors, CursorPos, j); - if (cursor->x == pos.x) - { - vis_pos = j; - - /* If moving left, we pick the leftmost match, otherwise - * the rightmost one. Without this, we can get stuck - */ - if (direction < 0) - break; - } - } - - if (vis_pos == -1 && - old_index == line->start_index + line->length) - { - if (line->resolved_dir == PANGO_DIRECTION_LTR) - vis_pos = cursors->len; - else - vis_pos = 0; - } - - /* Handling movement between lines */ - if (line->resolved_dir == PANGO_DIRECTION_LTR) - { - if (old_index == line->start_index && direction < 0) - off_start = TRUE; - if (old_index == line->start_index + line->length && direction > 0) - off_end = TRUE; - } - else - { - if (old_index == line->start_index + line->length && direction < 0) - off_start = TRUE; - if (old_index == line->start_index && direction > 0) - off_end = TRUE; - } - - if (off_start || off_end) - { - /* If we move over a paragraph boundary, count that as - * an extra position in the motion - */ - gboolean paragraph_boundary; - - if (off_start) - { - if (!prev_line) - { - *new_index = -1; - *new_trailing = 0; - g_array_unref (cursors); - return; - } - line = prev_line; - paragraph_boundary = (line->start_index + line->length != old_index); - } - else - { - if (!next_line) - { - *new_index = G_MAXINT; - *new_trailing = 0; - g_array_unref (cursors); - return; - } - line = next_line; - paragraph_boundary = (line->start_index != old_index); - } - - g_array_set_size (cursors, 0); - pango_layout_line_get_cursors (line, strong, cursors); - - n_vis = cursors->len; - - if (off_start && direction < 0) - { - vis_pos = n_vis; - if (paragraph_boundary) - vis_pos++; - } - else if (off_end && direction > 0) - { - vis_pos = 0; - if (paragraph_boundary) - vis_pos--; - } - } - - if (direction < 0) - vis_pos--; - else - vis_pos++; - - if (0 <= vis_pos && vis_pos < cursors->len) - *new_index = g_array_index (cursors, CursorPos, vis_pos).pos; - else if (vis_pos >= cursors->len - 1) - *new_index = line->start_index + line->length; - - *new_trailing = 0; - - if (*new_index == line->start_index + line->length && line->length > 0) - { - int log_pos; - - start_offset = g_utf8_pointer_to_offset (layout->text, layout->text + line->start_index); - log_pos = start_offset + pango_utf8_strlen (layout->text + line->start_index, line->length); - do - { - log_pos--; - *new_index = g_utf8_prev_char (layout->text + *new_index) - layout->text; - (*new_trailing)++; - } - while (log_pos > start_offset && !layout->log_attrs[log_pos].is_cursor_position); - } + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); - g_array_unref (cursors); + return layout->attrs; } /** - * pango_layout_xy_to_index: + * pango_layout_set_font_description: * @layout: a `PangoLayout` - * @x: the X offset (in Pango units) from the left edge of the layout - * @y: the Y offset (in Pango units) from the top edge of the layout - * @index_: (out): location to store calculated byte index - * @trailing: (out): location to store a integer indicating where - * in the grapheme the user clicked. It will either be zero, or the - * number of characters in the grapheme. 0 represents the leading edge - * of the grapheme. - * - * Converts from X and Y position within a layout to the byte index to the - * character at that logical position. - * - * If the Y position is not inside the layout, the closest position is - * chosen (the position will be clamped inside the layout). If the X position - * is not within the layout, then the start or the end of the line is - * chosen as described for [method@Pango.LayoutLine.x_to_index]. If either - * the X or Y positions were not inside the layout, then the function returns - * %FALSE; on an exact hit, it returns %TRUE. + * @desc: (nullable): the new `PangoFontDescription` * - * Return value: %TRUE if the coordinates were inside text, %FALSE otherwise - */ -gboolean -pango_layout_xy_to_index (PangoLayout *layout, - int x, - int y, - int *index, - gint *trailing) -{ - PangoLayoutIter iter; - PangoLayoutLine *prev_line = NULL; - PangoLayoutLine *found = NULL; - int found_line_x = 0; - int prev_last = 0; - int prev_line_x = 0; - gboolean retval = FALSE; - gboolean outside = FALSE; - - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), FALSE); - - _pango_layout_get_iter (layout, &iter); - - do - { - PangoRectangle line_logical; - int first_y, last_y; - - g_assert (!ITER_IS_INVALID (&iter)); - - pango_layout_iter_get_line_extents (&iter, NULL, &line_logical); - pango_layout_iter_get_line_yrange (&iter, &first_y, &last_y); - - if (y < first_y) - { - if (prev_line && y < (prev_last + (first_y - prev_last) / 2)) - { - found = prev_line; - found_line_x = prev_line_x; - } - else - { - if (prev_line == NULL) - outside = TRUE; /* off the top */ - - found = _pango_layout_iter_get_line (&iter); - found_line_x = x - line_logical.x; - } - } - else if (y >= first_y && - y < last_y) - { - found = _pango_layout_iter_get_line (&iter); - found_line_x = x - line_logical.x; - } - - prev_line = _pango_layout_iter_get_line (&iter); - prev_last = last_y; - prev_line_x = x - line_logical.x; - - if (found != NULL) - break; - } - while (pango_layout_iter_next_line (&iter)); - - _pango_layout_iter_destroy (&iter); - - if (found == NULL) - { - /* Off the bottom of the layout */ - outside = TRUE; - - found = prev_line; - found_line_x = prev_line_x; - } - - retval = pango_layout_line_x_to_index (found, - found_line_x, - index, trailing); - - if (outside) - retval = FALSE; - - return retval; -} - -/** - * pango_layout_index_to_pos: - * @layout: a `PangoLayout` - * @index_: byte index within @layout - * @pos: (out): rectangle in which to store the position of the grapheme + * Sets the default font description for the layout. * - * Converts from an index within a `PangoLayout` to the onscreen position - * corresponding to the grapheme at that index. + * If no font description is set on the layout, the + * font description from the layout's context is used. * - * The return value is represented as rectangle. Note that `pos->x` is - * always the leading edge of the grapheme and `pos->x + pos->width` the - * trailing edge of the grapheme. If the directionality of the grapheme - * is right-to-left, then `pos->width` will be negative. + * This method is a shorthand for adding a font-desc attribute. */ void -pango_layout_index_to_pos (PangoLayout *layout, - int index, - PangoRectangle *pos) -{ - PangoRectangle line_logical_rect = { 0, }; - PangoRectangle run_logical_rect = { 0, }; - PangoLayoutIter iter; - PangoLayoutLine *layout_line = NULL; - int x_pos; - - g_return_if_fail (layout != NULL); - g_return_if_fail (index >= 0); - g_return_if_fail (pos != NULL); - - _pango_layout_get_iter (layout, &iter); - - if (!ITER_IS_INVALID (&iter)) - { - while (TRUE) - { - PangoLayoutLine *tmp_line = _pango_layout_iter_get_line (&iter); - - if (tmp_line->start_index > index) - { - /* index is in the paragraph delimiters, move to - * end of previous line - * - * This shouldn’t occur in the first loop iteration as the first - * line’s start_index should always be 0. - */ - g_assert (layout_line != NULL); - index = layout_line->start_index + layout_line->length; - break; - } - - pango_layout_iter_get_line_extents (&iter, NULL, &line_logical_rect); - - layout_line = tmp_line; - - if (layout_line->start_index + layout_line->length >= index) - { - do - { - PangoLayoutRun *run = _pango_layout_iter_get_run (&iter); - - pango_layout_iter_get_run_extents (&iter, NULL, &run_logical_rect); - - if (!run) - break; - - if (run->item->offset <= index && index < run->item->offset + run->item->length) - break; - } - while (pango_layout_iter_next_run (&iter)); - - if (layout_line->start_index + layout_line->length > index) - break; - } - - if (!pango_layout_iter_next_line (&iter)) - { - index = layout_line->start_index + layout_line->length; - break; - } - } - - pos->y = run_logical_rect.y; - pos->height = run_logical_rect.height; - - pango_layout_line_index_to_x (layout_line, index, 0, &x_pos); - pos->x = line_logical_rect.x + x_pos; - - if (index < layout_line->start_index + layout_line->length) - { - pango_layout_line_index_to_x (layout_line, index, 1, &x_pos); - pos->width = (line_logical_rect.x + x_pos) - pos->x; - } - else - pos->width = 0; - } - - _pango_layout_iter_destroy (&iter); -} - -static PangoLayoutRun * -pango_layout_line_get_run (PangoLayoutLine *line, - int index) +pango_layout_set_font_description (PangoLayout *layout, + const PangoFontDescription *desc) { - GSList *run_list; + g_return_if_fail (PANGO_IS_LAYOUT (layout)); - run_list = line->runs; - while (run_list) + if (desc != layout->font_desc && + (!desc || !layout->font_desc || !pango_font_description_equal (desc, layout->font_desc))) { - PangoLayoutRun *run = run_list->data; + if (layout->font_desc) + pango_font_description_free (layout->font_desc); - if (run->item->offset <= index && run->item->offset + run->item->length > index) - return run; + layout->font_desc = desc ? pango_font_description_copy (desc) : NULL; - run_list = run_list->next; + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_FONT_DESCRIPTION]); + layout_changed (layout); } - - return NULL; -} - -static int -pango_layout_line_get_char_level (PangoLayoutLine *line, - int index) -{ - PangoLayoutRun *run; - - run = pango_layout_line_get_run (line, index); - - if (run) - return run->item->analysis.level; - - return 0; -} - -static PangoDirection -pango_layout_line_get_char_direction (PangoLayoutLine *layout_line, - int index) -{ - return pango_layout_line_get_char_level (layout_line, index) % 2 - ? PANGO_DIRECTION_RTL - : PANGO_DIRECTION_LTR; } /** - * pango_layout_get_direction: + * pango_layout_get_font_description: * @layout: a `PangoLayout` - * @index: the byte index of the char * - * Gets the text direction at the given character position in @layout. + * Gets the font description for the layout, if any. * - * Returns: the text direction at @index + * Return value: (transfer none) (nullable): a pointer to the + * layout's font description, or %NULL if the font description + * from the layout's context is inherited. * - * Since: 1.46 + * Since: 1.8 */ -PangoDirection -pango_layout_get_direction (PangoLayout *layout, - int index) +const PangoFontDescription * +pango_layout_get_font_description (PangoLayout *layout) { - PangoLayoutLine *line; - - line = pango_layout_index_to_line_and_extents (layout, index, NULL, NULL); - - if (line) - return pango_layout_line_get_char_direction (line, index); + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); - return PANGO_DIRECTION_LTR; + return layout->font_desc; } /** - * pango_layout_get_cursor_pos: + * pango_layout_set_line_spacing: * @layout: a `PangoLayout` - * @index_: the byte index of the cursor - * @strong_pos: (out) (optional): location to store the strong cursor position - * @weak_pos: (out) (optional): location to store the weak cursor position + * @line_spacing: the new line spacing factor * - * Given an index within a layout, determines the positions that of the - * strong and weak cursors if the insertion point is at that index. - * - * The position of each cursor is stored as a zero-width rectangle - * with the height of the run extents. - * - * <picture> - * <source srcset="cursor-positions-dark.png" media="(prefers-color-scheme: dark)"> - * <img alt="Cursor positions" src="cursor-positions-light.png"> - * </picture> - * - * The strong cursor location is the location where characters of the - * directionality equal to the base direction of the layout are inserted. - * The weak cursor location is the location where characters of the - * directionality opposite to the base direction of the layout are inserted. - * - * The following example shows text with both a strong and a weak cursor. - * - * <picture> - * <source srcset="split-cursor-dark.png" media="(prefers-color-scheme: dark)"> - * <img alt="Strong and weak cursors" src="split-cursor-light.png"> - * </picture> + * Sets a factor for line spacing. * - * The strong cursor has a little arrow pointing to the right, the weak - * cursor to the left. Typing a 'c' in this situation will insert the - * character after the 'b', and typing another Hebrew character, like 'ג', - * will insert it at the end. - */ -void -pango_layout_get_cursor_pos (PangoLayout *layout, - int index, - PangoRectangle *strong_pos, - PangoRectangle *weak_pos) -{ - PangoDirection dir1, dir2; - int level1, level2; - PangoRectangle line_rect = { 666, }; - PangoRectangle run_rect = { 666, }; - PangoLayoutLine *layout_line = NULL; /* Quiet GCC */ - int x1_trailing; - int x2; - - g_return_if_fail (layout != NULL); - g_return_if_fail (index >= 0 && index <= layout->length); - - layout_line = pango_layout_index_to_line_and_extents (layout, index, - &line_rect, &run_rect); - - g_assert (index >= layout_line->start_index); - - /* Examine the trailing edge of the character before the cursor */ - if (index == layout_line->start_index) - { - dir1 = layout_line->resolved_dir; - level1 = dir1 == PANGO_DIRECTION_LTR ? 0 : 1; - if (layout_line->resolved_dir == PANGO_DIRECTION_LTR) - x1_trailing = 0; - else - x1_trailing = line_rect.width; - } - else - { - gint prev_index = g_utf8_prev_char (layout->text + index) - layout->text; - level1 = pango_layout_line_get_char_level (layout_line, prev_index); - dir1 = level1 % 2 ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR; - pango_layout_line_index_to_x (layout_line, prev_index, TRUE, &x1_trailing); - } - - /* Examine the leading edge of the character after the cursor */ - if (index >= layout_line->start_index + layout_line->length) - { - dir2 = layout_line->resolved_dir; - level2 = dir2 == PANGO_DIRECTION_LTR ? 0 : 1; - if (layout_line->resolved_dir == PANGO_DIRECTION_LTR) - x2 = line_rect.width; - else - x2 = 0; - } - else - { - pango_layout_line_index_to_x (layout_line, index, FALSE, &x2); - level2 = pango_layout_line_get_char_level (layout_line, index); - dir2 = level2 % 2 ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR; - } - - if (strong_pos) - { - strong_pos->x = line_rect.x; - - if (dir1 == layout_line->resolved_dir && - (dir2 != dir1 || level1 < level2)) - strong_pos->x += x1_trailing; - else - strong_pos->x += x2; - - strong_pos->y = run_rect.y; - strong_pos->width = 0; - strong_pos->height = run_rect.height; - } - - if (weak_pos) - { - weak_pos->x = line_rect.x; - - if (dir1 == layout_line->resolved_dir && - (dir2 != dir1 || level1 < level2)) - weak_pos->x += x2; - else - weak_pos->x += x1_trailing; - - weak_pos->y = run_rect.y; - weak_pos->width = 0; - weak_pos->height = run_rect.height; - } -} - -/** - * pango_layout_get_caret_pos: - * @layout: a `PangoLayout` - * @index_: the byte index of the cursor - * @strong_pos: (out) (optional): location to store the strong cursor position - * @weak_pos: (out) (optional): location to store the weak cursor position + * Typical values are: 0, 1, 1.5, 2. The default values is 0. * - * Given an index within a layout, determines the positions that of the - * strong and weak cursors if the insertion point is at that index. + * If @line_spacing is non-zero, lines are placed so that * - * This is a variant of [method@Pango.Layout.get_cursor_pos] that applies - * font metric information about caret slope and offset to the positions - * it returns. + * baseline2 = baseline1 + factor * height2 * - * <picture> - * <source srcset="caret-metrics-dark.png" media="(prefers-color-scheme: dark)"> - * <img alt="Caret metrics" src="caret-metrics-light.png"> - * </picture> + * where height2 is the line height of the second line (as determined + * by the font). * - * Since: 1.50 + * This method is a shorthand for adding a line-height attribute. */ void -pango_layout_get_caret_pos (PangoLayout *layout, - int index, - PangoRectangle *strong_pos, - PangoRectangle *weak_pos) -{ - PangoLayoutLine *line; - PangoLayoutRun *run; - hb_font_t *hb_font; - hb_position_t caret_offset, caret_run, caret_rise, descender; - - pango_layout_get_cursor_pos (layout, index, strong_pos, weak_pos); - - /* FIXME: not very efficient to re-iterate here */ - line = pango_layout_index_to_line_and_extents (layout, index, NULL, NULL); - run = pango_layout_line_get_run (line, index); - if (!run) - run = pango_layout_line_get_run (line, index - 1); - - if (!run) - return; - - hb_font = pango_font_get_hb_font (run->item->analysis.font); - - if (hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_HORIZONTAL_CARET_RISE, &caret_rise) && - hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_HORIZONTAL_CARET_RUN, &caret_run) && - hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_HORIZONTAL_CARET_OFFSET, &caret_offset) && - hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_HORIZONTAL_DESCENDER, &descender)) - { - double slope_inv; - int x_scale, y_scale; - - if (strong_pos) - strong_pos->x += caret_offset; - - if (weak_pos) - weak_pos->x += caret_offset; - - if (caret_rise == 0) - return; - - hb_font_get_scale (hb_font, &x_scale, &y_scale); - slope_inv = (caret_run * y_scale) / (double) (caret_rise * x_scale); - - if (strong_pos) - { - strong_pos->x += descender * slope_inv; - strong_pos->width = strong_pos->height * slope_inv; - if (slope_inv < 0) - strong_pos->x -= strong_pos->width; - } - - if (weak_pos) - { - weak_pos->x += descender * slope_inv; - weak_pos->width = weak_pos->height * slope_inv; - if (slope_inv < 0) - weak_pos->x -= weak_pos->width; - } - } -} - -static inline int -direction_simple (PangoDirection d) -{ - switch (d) - { - case PANGO_DIRECTION_LTR : - case PANGO_DIRECTION_WEAK_LTR : - case PANGO_DIRECTION_TTB_RTL : - return 1; - case PANGO_DIRECTION_RTL : - case PANGO_DIRECTION_WEAK_RTL : - case PANGO_DIRECTION_TTB_LTR : - return -1; - case PANGO_DIRECTION_NEUTRAL : - return 0; - default: - break; - } - /* not reached */ - return 0; -} - -static PangoAlignment -get_alignment (PangoLayout *layout, - PangoLayoutLine *line) -{ - PangoAlignment alignment = layout->alignment; - - if (alignment != PANGO_ALIGN_CENTER && line->layout->auto_dir && - direction_simple (line->resolved_dir) == - -direction_simple (pango_context_get_base_dir (layout->context))) - { - if (alignment == PANGO_ALIGN_LEFT) - alignment = PANGO_ALIGN_RIGHT; - else if (alignment == PANGO_ALIGN_RIGHT) - alignment = PANGO_ALIGN_LEFT; - } - - return alignment; -} - -static void -get_x_offset (PangoLayout *layout, - PangoLayoutLine *line, - int layout_width, - int line_width, - int *x_offset) -{ - PangoAlignment alignment = get_alignment (layout, line); - - /* Alignment */ - if (layout_width == 0) - *x_offset = 0; - else if (alignment == PANGO_ALIGN_RIGHT) - *x_offset = layout_width - line_width; - else if (alignment == PANGO_ALIGN_CENTER) { - *x_offset = (layout_width - line_width) / 2; - /* hinting */ - if (((layout_width | line_width) & (PANGO_SCALE - 1)) == 0) - { - *x_offset = PANGO_UNITS_ROUND (*x_offset); - } - } else - *x_offset = 0; - - /* Indentation */ - - /* For center, we ignore indentation; I think I've seen word - * processors that still do the indentation here as if it were - * indented left/right, though we can't sensibly do that without - * knowing whether left/right is the "normal" thing for this text - */ - - if (alignment == PANGO_ALIGN_CENTER) - return; - - if (line->is_paragraph_start) - { - if (layout->indent > 0) - { - if (alignment == PANGO_ALIGN_LEFT) - *x_offset += layout->indent; - else - *x_offset -= layout->indent; - } - } - else - { - if (layout->indent < 0) - { - if (alignment == PANGO_ALIGN_LEFT) - *x_offset -= layout->indent; - else - *x_offset += layout->indent; - } - } -} - -static void -pango_layout_line_get_extents_and_height (PangoLayoutLine *line, - PangoRectangle *ink, - PangoRectangle *logical, - int *height); -static void -get_line_extents_layout_coords (PangoLayout *layout, - PangoLayoutLine *line, - int layout_width, - int y_offset, - int *baseline, - PangoRectangle *line_ink_layout, - PangoRectangle *line_logical_layout) -{ - int x_offset; - /* Line extents in line coords (origin at line baseline) */ - PangoRectangle line_ink; - PangoRectangle line_logical; - gboolean first_line; - int new_baseline; - int height; - - if (layout->lines->data == line) - first_line = TRUE; - else - first_line = FALSE; - - pango_layout_line_get_extents_and_height (line, line_ink_layout ? &line_ink : NULL, - &line_logical, - &height); - - get_x_offset (layout, line, layout_width, line_logical.width, &x_offset); - - if (first_line || !baseline || layout->line_spacing == 0.0) - new_baseline = y_offset - line_logical.y; - else - new_baseline = *baseline + layout->line_spacing * height; - - /* Convert the line's extents into layout coordinates */ - if (line_ink_layout) - { - *line_ink_layout = line_ink; - line_ink_layout->x = line_ink.x + x_offset; - line_ink_layout->y = new_baseline + line_ink.y; - } - - if (line_logical_layout) - { - *line_logical_layout = line_logical; - line_logical_layout->x = line_logical.x + x_offset; - line_logical_layout->y = new_baseline + line_logical.y; - } - - if (baseline) - *baseline = new_baseline; -} - -/* if non-NULL line_extents returns a list of line extents - * in layout coordinates - */ -static void -pango_layout_get_extents_internal (PangoLayout *layout, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect, - Extents **line_extents) +pango_layout_set_line_spacing (PangoLayout *layout, + float line_spacing) { - GSList *line_list; - int y_offset = 0; - int width; - gboolean need_width = FALSE; - int line_index = 0; - int baseline; - - g_return_if_fail (layout != NULL); - - pango_layout_check_lines (layout); + g_return_if_fail (PANGO_IS_LAYOUT (layout)); - if (ink_rect && layout->ink_rect_cached) - { - *ink_rect = layout->ink_rect; - ink_rect = NULL; - } - if (logical_rect && layout->logical_rect_cached) - { - *logical_rect = layout->logical_rect; - logical_rect = NULL; - } - if (!ink_rect && !logical_rect && !line_extents) + if (layout->line_spacing == line_spacing) return; - /* When we are not wrapping, we need the overall width of the layout to - * figure out the x_offsets of each line. However, we only need the - * x_offsets if we are computing the ink_rect or individual line extents. - */ - width = layout->width; - - if (layout->auto_dir) - { - /* If one of the lines of the layout is not left aligned, then we need - * the width of the layout to calculate line x-offsets; this requires - * looping through the lines for layout->auto_dir. - */ - line_list = layout->lines; - while (line_list && !need_width) - { - PangoLayoutLine *line = line_list->data; - - if (get_alignment (layout, line) != PANGO_ALIGN_LEFT) - need_width = TRUE; - - line_list = line_list->next; - } - } - else if (layout->alignment != PANGO_ALIGN_LEFT) - need_width = TRUE; - - if (width == -1 && need_width && (ink_rect || line_extents)) - { - PangoRectangle overall_logical; - - pango_layout_get_extents_internal (layout, NULL, &overall_logical, NULL); - width = overall_logical.width; - } - - if (logical_rect) - { - logical_rect->x = 0; - logical_rect->y = 0; - logical_rect->width = 0; - logical_rect->height = 0; - } - - - if (line_extents && layout->line_count > 0) - { - *line_extents = g_malloc (sizeof (Extents) * layout->line_count); - } + layout->line_spacing = line_spacing; - baseline = 0; - line_list = layout->lines; - while (line_list) - { - PangoLayoutLine *line = line_list->data; - /* Line extents in layout coords (origin at 0,0 of the layout) */ - PangoRectangle line_ink_layout; - PangoRectangle line_logical_layout; - - int new_pos; - - /* This block gets the line extents in layout coords */ - { - get_line_extents_layout_coords (layout, line, - width, y_offset, - &baseline, - ink_rect ? &line_ink_layout : NULL, - &line_logical_layout); - - if (line_extents && layout->line_count > 0) - { - Extents *ext = &(*line_extents)[line_index]; - ext->baseline = baseline; - ext->ink_rect = line_ink_layout; - ext->logical_rect = line_logical_layout; - } - } - - if (ink_rect) - { - /* Compute the union of the current ink_rect with - * line_ink_layout - */ - - if (line_list == layout->lines) - { - *ink_rect = line_ink_layout; - } - else - { - new_pos = MIN (ink_rect->x, line_ink_layout.x); - ink_rect->width = - MAX (ink_rect->x + ink_rect->width, - line_ink_layout.x + line_ink_layout.width) - new_pos; - ink_rect->x = new_pos; - - new_pos = MIN (ink_rect->y, line_ink_layout.y); - ink_rect->height = - MAX (ink_rect->y + ink_rect->height, - line_ink_layout.y + line_ink_layout.height) - new_pos; - ink_rect->y = new_pos; - } - } - - if (logical_rect) - { - if (layout->width == -1) - { - /* When no width is set on layout, we can just compute the max of the - * line lengths to get the horizontal extents ... logical_rect.x = 0. - */ - logical_rect->width = MAX (logical_rect->width, line_logical_layout.width); - } - else - { - /* When a width is set, we have to compute the union of the horizontal - * extents of all the lines. - */ - if (line_list == layout->lines) - { - logical_rect->x = line_logical_layout.x; - logical_rect->width = line_logical_layout.width; - } - else - { - new_pos = MIN (logical_rect->x, line_logical_layout.x); - logical_rect->width = - MAX (logical_rect->x + logical_rect->width, - line_logical_layout.x + line_logical_layout.width) - new_pos; - logical_rect->x = new_pos; - - } - } - - logical_rect->height = line_logical_layout.y + line_logical_layout.height - logical_rect->y; - } - - y_offset = line_logical_layout.y + line_logical_layout.height + layout->spacing; - line_list = line_list->next; - line_index ++; - } - - if (ink_rect) - { - layout->ink_rect = *ink_rect; - layout->ink_rect_cached = TRUE; - } - if (logical_rect) - { - layout->logical_rect = *logical_rect; - layout->logical_rect_cached = TRUE; - } + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_LINE_SPACING]); + layout_changed (layout); } /** - * pango_layout_get_extents: + * pango_layout_get_line_spacing: * @layout: a `PangoLayout` - * @ink_rect: (out) (optional): rectangle used to store the extents of the - * layout as drawn - * @logical_rect: (out) (optional):rectangle used to store the logical - * extents of the layout - * - * Computes the logical and ink extents of @layout. * - * Logical extents are usually what you want for positioning things. Note - * that both extents may have non-zero x and y. You may want to use those - * to offset where you render the layout. Not doing that is a very typical - * bug that shows up as right-to-left layouts not being correctly positioned - * in a layout with a set width. + * Gets the line spacing factor of @layout. * - * The extents are given in layout coordinates and in Pango units; layout - * coordinates begin at the top left corner of the layout. + * See [method@Pango.Layout.set_line_spacing]. */ -void -pango_layout_get_extents (PangoLayout *layout, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect) +float +pango_layout_get_line_spacing (PangoLayout *layout) { - g_return_if_fail (layout != NULL); + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), 0.0); - pango_layout_get_extents_internal (layout, ink_rect, logical_rect, NULL); + return layout->line_spacing; } /** - * pango_layout_get_pixel_extents: - * @layout: a `PangoLayout` - * @ink_rect: (out) (optional): rectangle used to store the extents of the - * layout as drawn - * @logical_rect: (out) (optional): rectangle used to store the logical - * extents of the layout + * pango_layout_set_width: + * @layout: a `PangoLayout`. + * @width: the desired width in Pango units, or -1 to indicate that no + * wrapping or ellipsization should be performed. * - * Computes the logical and ink extents of @layout in device units. + * Sets the width to which the lines of the layout should + * be wrapped or ellipsized. * - * This function just calls [method@Pango.Layout.get_extents] followed by - * two [func@extents_to_pixels] calls, rounding @ink_rect and @logical_rect - * such that the rounded rectangles fully contain the unrounded one (that is, - * passes them as first argument to [func@Pango.extents_to_pixels]). + * The default value is -1: no width set. */ void -pango_layout_get_pixel_extents (PangoLayout *layout, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect) +pango_layout_set_width (PangoLayout *layout, + int width) { g_return_if_fail (PANGO_IS_LAYOUT (layout)); - pango_layout_get_extents (layout, ink_rect, logical_rect); - pango_extents_to_pixels (ink_rect, NULL); - pango_extents_to_pixels (logical_rect, NULL); -} - -/** - * pango_layout_get_size: - * @layout: a `PangoLayout` - * @width: (out) (optional): location to store the logical width - * @height: (out) (optional): location to store the logical height - * - * Determines the logical width and height of a `PangoLayout` in Pango - * units. - * - * This is simply a convenience function around [method@Pango.Layout.get_extents]. - */ -void -pango_layout_get_size (PangoLayout *layout, - int *width, - int *height) -{ - PangoRectangle logical_rect; - - pango_layout_get_extents (layout, NULL, &logical_rect); - - if (width) - *width = logical_rect.width; - if (height) - *height = logical_rect.height; -} + if (width < -1) + width = -1; -/** - * pango_layout_get_pixel_size: - * @layout: a `PangoLayout` - * @width: (out) (optional): location to store the logical width - * @height: (out) (optional): location to store the logical height - * - * Determines the logical width and height of a `PangoLayout` in device - * units. - * - * [method@Pango.Layout.get_size] returns the width and height - * scaled by %PANGO_SCALE. This is simply a convenience function - * around [method@Pango.Layout.get_pixel_extents]. - */ -void -pango_layout_get_pixel_size (PangoLayout *layout, - int *width, - int *height) -{ - PangoRectangle logical_rect; + if (layout->width == width) + return; - pango_layout_get_extents_internal (layout, NULL, &logical_rect, NULL); - pango_extents_to_pixels (&logical_rect, NULL); + layout->width = width; - if (width) - *width = logical_rect.width; - if (height) - *height = logical_rect.height; + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_WIDTH]); + layout_changed (layout); } /** - * pango_layout_get_baseline: + * pango_layout_get_width: * @layout: a `PangoLayout` * - * Gets the Y position of baseline of the first line in @layout. + * Gets the width to which the lines of the layout should wrap. * - * Return value: baseline of first line, from top of @layout - * - * Since: 1.22 + * Return value: the width in Pango units, or -1 if no width set. */ int -pango_layout_get_baseline (PangoLayout *layout) -{ - int baseline; - Extents *extents = NULL; - - /* XXX this is kinda inefficient */ - pango_layout_get_extents_internal (layout, NULL, NULL, &extents); - baseline = extents ? extents[0].baseline : 0; - - g_free (extents); - - return baseline; -} - -static void -pango_layout_clear_lines (PangoLayout *layout) +pango_layout_get_width (PangoLayout *layout) { - if (layout->lines) - { - GSList *tmp_list = layout->lines; - while (tmp_list) - { - PangoLayoutLine *line = tmp_list->data; - tmp_list = tmp_list->next; - - line->layout = NULL; - pango_layout_line_unref (line); - } - - g_slist_free (layout->lines); - layout->lines = NULL; - layout->line_count = 0; - } - - layout->unknown_glyphs_count = -1; - layout->logical_rect_cached = FALSE; - layout->ink_rect_cached = FALSE; - layout->is_ellipsized = FALSE; - layout->is_wrapped = FALSE; -} - -static void -pango_layout_line_leaked (PangoLayoutLine *line) -{ - PangoLayoutLinePrivate *private = (PangoLayoutLinePrivate *)line; - - private->cache_status = LEAKED; - - if (line->layout) - { - line->layout->logical_rect_cached = FALSE; - line->layout->ink_rect_cached = FALSE; - } -} - - -/***************** - * Line Breaking * - *****************/ - -static void shape_tab (PangoLayoutLine *line, - LastTabState *tab_state, - ItemProperties *properties, - int current_width, - PangoItem *item, - PangoGlyphString *glyphs); - -static void -free_run (PangoLayoutRun *run, gpointer data) -{ - gboolean free_item = data != NULL; - if (free_item) - pango_item_free (run->item); - - pango_glyph_string_free (run->glyphs); - g_slice_free (PangoLayoutRun, run); -} - -static PangoItem * -uninsert_run (PangoLayoutLine *line) -{ - PangoLayoutRun *run; - PangoItem *item; - - GSList *tmp_node = line->runs; - - run = tmp_node->data; - item = run->item; - - line->runs = tmp_node->next; - line->length -= item->length; - - g_slist_free_1 (tmp_node); - free_run (run, (gpointer)FALSE); - - return item; -} - -static void -ensure_tab_width (PangoLayout *layout) -{ - if (layout->tab_width == -1) - { - /* Find out how wide 8 spaces are in the context's default - * font. Utter performance killer. :-( - */ - PangoGlyphString *glyphs = pango_glyph_string_new (); - PangoItem *item; - GList *items; - PangoAttribute *attr; - PangoAttrList *layout_attrs; - PangoAttrList tmp_attrs; - PangoFontDescription *font_desc = pango_font_description_copy_static (pango_context_get_font_description (layout->context)); - PangoLanguage *language = NULL; - PangoShapeFlags shape_flags = PANGO_SHAPE_NONE; - - if (pango_context_get_round_glyph_positions (layout->context)) - shape_flags |= PANGO_SHAPE_ROUND_POSITIONS; - - layout_attrs = pango_layout_get_effective_attributes (layout); - if (layout_attrs) - { - PangoAttrIterator iter; - - _pango_attr_list_get_iterator (layout_attrs, &iter); - pango_attr_iterator_get_font (&iter, font_desc, &language, NULL); - _pango_attr_iterator_destroy (&iter); - } - - _pango_attr_list_init (&tmp_attrs); - - attr = pango_attr_font_desc_new (font_desc); - pango_font_description_free (font_desc); - pango_attr_list_insert_before (&tmp_attrs, attr); - - if (language) - { - attr = pango_attr_language_new (language); - pango_attr_list_insert_before (&tmp_attrs, attr); - } - - items = pango_itemize (layout->context, " ", 0, 1, &tmp_attrs, NULL); - - if (layout_attrs != layout->attrs) - { - pango_attr_list_unref (layout_attrs); - layout_attrs = NULL; - } - _pango_attr_list_destroy (&tmp_attrs); - - item = items->data; - pango_shape_with_flags (" ", 8, " ", 8, &item->analysis, glyphs, shape_flags); + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), -1); - pango_item_free (item); - g_list_free (items); - - layout->tab_width = pango_glyph_string_get_width (glyphs); - - pango_glyph_string_free (glyphs); - - /* We need to make sure the tab_width is > 0 so finding tab positions - * terminates. This check should be necessary only under extreme - * problems with the font. - */ - if (layout->tab_width <= 0) - layout->tab_width = 50 * PANGO_SCALE; /* pretty much arbitrary */ - } -} - -static void -get_tab_pos (PangoLayoutLine *line, - int index, - int *tab_pos, - PangoTabAlign *alignment, - gunichar *decimal, - gboolean *is_default) -{ - PangoLayout *layout = line->layout; - int n_tabs; - gboolean in_pixels; - int offset = 0; - - if (layout->alignment != PANGO_ALIGN_CENTER) - { - if (line->is_paragraph_start && layout->indent >= 0) - offset = layout->indent; - else if (!line->is_paragraph_start && layout->indent < 0) - offset = - layout->indent; - } - - if (layout->tabs) - { - n_tabs = pango_tab_array_get_size (layout->tabs); - in_pixels = pango_tab_array_get_positions_in_pixels (layout->tabs); - *is_default = FALSE; - } - else - { - n_tabs = 0; - in_pixels = FALSE; - *is_default = TRUE; - } - - if (index < n_tabs) - { - pango_tab_array_get_tab (layout->tabs, index, alignment, tab_pos); - - if (in_pixels) - *tab_pos *= PANGO_SCALE; - - *decimal = pango_tab_array_get_decimal_point (layout->tabs, index); - } - else if (n_tabs > 0) - { - /* Extrapolate tab position, repeating the last tab gap to infinity. */ - int last_pos = 0; - int next_to_last_pos = 0; - int tab_width; - - pango_tab_array_get_tab (layout->tabs, n_tabs - 1, alignment, &last_pos); - *decimal = pango_tab_array_get_decimal_point (layout->tabs, n_tabs - 1); - - if (n_tabs > 1) - pango_tab_array_get_tab (layout->tabs, n_tabs - 2, NULL, &next_to_last_pos); - else - next_to_last_pos = 0; - - if (in_pixels) - { - next_to_last_pos *= PANGO_SCALE; - last_pos *= PANGO_SCALE; - } - - if (last_pos > next_to_last_pos) - tab_width = last_pos - next_to_last_pos; - else - tab_width = layout->tab_width; - - *tab_pos = last_pos + tab_width * (index - n_tabs + 1); - } - else - { - /* No tab array set, so use default tab width */ - *tab_pos = layout->tab_width * index; - *alignment = PANGO_TAB_LEFT; - *decimal = 0; - } - - *tab_pos -= offset; -} - -static void -ensure_decimal (PangoLayout *layout) -{ - if (layout->decimal == 0) - layout->decimal = g_utf8_get_char (localeconv ()->decimal_point); -} - -struct _LastTabState { - PangoGlyphString *glyphs; - int index; - int width; - int pos; - PangoTabAlign align; - gunichar decimal; -}; - -static void -shape_tab (PangoLayoutLine *line, - LastTabState *tab_state, - ItemProperties *properties, - int current_width, - PangoItem *item, - PangoGlyphString *glyphs) -{ - int i, space_width; - int tab_pos; - PangoTabAlign tab_align; - gunichar tab_decimal; - - pango_glyph_string_set_size (glyphs, 1); - - if (properties->showing_space) - glyphs->glyphs[0].glyph = PANGO_GET_UNKNOWN_GLYPH ('\t'); - else - glyphs->glyphs[0].glyph = PANGO_GLYPH_EMPTY; - - glyphs->glyphs[0].geometry.x_offset = 0; - glyphs->glyphs[0].geometry.y_offset = 0; - glyphs->glyphs[0].attr.is_cluster_start = 1; - glyphs->glyphs[0].attr.is_color = 0; - - glyphs->log_clusters[0] = 0; - - ensure_tab_width (line->layout); - space_width = line->layout->tab_width / 8; - - for (i = tab_state->index; ; i++) - { - gboolean is_default; - - get_tab_pos (line, i, &tab_pos, &tab_align, &tab_decimal, &is_default); - - /* Make sure there is at least a space-width of space between - * tab-aligned text and the text before it. However, only do - * this if no tab array is set on the layout, ie. using default - * tab positions. If the user has set tab positions, respect it - * to the pixel. - */ - if (tab_pos >= current_width + (is_default ? space_width : 1)) - { - glyphs->glyphs[0].geometry.width = tab_pos - current_width; - break; - } - } - - if (tab_decimal == 0) - { - ensure_decimal (line->layout); - tab_decimal = line->layout->decimal; - } - - tab_state->glyphs = glyphs; - tab_state->index = i; - tab_state->width = current_width; - tab_state->pos = tab_pos; - tab_state->align = tab_align; - tab_state->decimal = tab_decimal; -} - -static inline gboolean -can_break_at (PangoLayout *layout, - gint offset, - PangoWrapMode wrap) -{ - if (offset == layout->n_chars) - return TRUE; - else if (wrap == PANGO_WRAP_CHAR) - return layout->log_attrs[offset].is_char_break; - else - return layout->log_attrs[offset].is_line_break; -} - -static inline gboolean -can_break_in (PangoLayout *layout, - int start_offset, - int num_chars, - gboolean allow_break_at_start) -{ - int i; - - for (i = allow_break_at_start ? 0 : 1; i < num_chars; i++) - if (can_break_at (layout, start_offset + i, layout->wrap)) - return TRUE; - - return FALSE; -} - -static inline void -distribute_letter_spacing (int letter_spacing, - int *space_left, - int *space_right) -{ - *space_left = letter_spacing / 2; - /* hinting */ - if ((letter_spacing & (PANGO_SCALE - 1)) == 0) - { - *space_left = PANGO_UNITS_ROUND (*space_left); - } - *space_right = letter_spacing - *space_left; -} - -typedef enum -{ - BREAK_NONE_FIT, - BREAK_SOME_FIT, - BREAK_ALL_FIT, - BREAK_EMPTY_FIT, - BREAK_LINE_SEPARATOR -} BreakResult; - -struct _ParaBreakState -{ - /* maintained per layout */ - int line_height; /* Estimate of height of current line; < 0 is no estimate */ - int remaining_height; /* Remaining height of the layout; only defined if layout->height >= 0 */ - - /* maintained per paragraph */ - PangoAttrList *attrs; /* Attributes being used for itemization */ - GList *items; /* This paragraph turned into items */ - PangoDirection base_dir; /* Current resolved base direction */ - int line_of_par; /* Line of the paragraph, starting at 1 for first line */ - - PangoGlyphString *glyphs; /* Glyphs for the first item in state->items */ - int start_offset; /* Character offset of first item in state->items in layout->text */ - ItemProperties properties; /* Properties for the first item in state->items */ - int *log_widths; /* Logical widths for first item in state->items.. */ - int num_log_widths; /* Length of log_widths */ - int log_widths_offset; /* Offset into log_widths to the point corresponding - * to the remaining portion of the first item */ - - int line_start_index; /* Start index (byte offset) of line in layout->text */ - int line_start_offset; /* Character offset of line in layout->text */ - - /* maintained per line */ - int line_width; /* Goal width of line currently processing; < 0 is infinite */ - int remaining_width; /* Amount of space remaining on line; < 0 is infinite */ - - int hyphen_width; /* How much space a hyphen will take */ - - GList *baseline_shifts; - - LastTabState last_tab; -}; - -static gboolean -should_ellipsize_current_line (PangoLayout *layout, - ParaBreakState *state); - -static void -get_decimal_prefix_width (PangoItem *item, - PangoGlyphString *glyphs, - const char *text, - gunichar decimal, - int *width, - gboolean *found) -{ - PangoGlyphItem glyph_item = { item, glyphs, 0, 0, 0 }; - int *log_widths; - int i; - const char *p; - - log_widths = g_new (int, item->num_chars); - - pango_glyph_item_get_logical_widths (&glyph_item, text, log_widths); - - *width = 0; - *found = FALSE; - - for (i = 0, p = text + item->offset; i < item->num_chars; i++, p = g_utf8_next_char (p)) - { - if (g_utf8_get_char (p) == decimal) - { - *width += log_widths[i] / 2; - *found = TRUE; - break; - } - - *width += log_widths[i]; - } - - g_free (log_widths); -} - -static int -line_width (ParaBreakState *state, - PangoLayoutLine *line) -{ - GSList *l; - int i; - int width = 0; - - if (state->remaining_width > -1) - return state->line_width - state->remaining_width; - - /* Compute the width of the line currently - inefficient, but easier - * than keeping the current width of the line up to date everywhere - */ - for (l = line->runs; l; l = l->next) - { - PangoLayoutRun *run = l->data; - - for (i = 0; i < run->glyphs->num_glyphs; i++) - width += run->glyphs->glyphs[i].geometry.width; - } - - return width; -} - -static PangoGlyphString * -shape_run (PangoLayoutLine *line, - ParaBreakState *state, - PangoItem *item) -{ - PangoLayout *layout = line->layout; - PangoGlyphString *glyphs = pango_glyph_string_new (); - - if (layout->text[item->offset] == '\t') - shape_tab (line, &state->last_tab, &state->properties, line_width (state, line), item, glyphs); - else - { - PangoShapeFlags shape_flags = PANGO_SHAPE_NONE; - - if (pango_context_get_round_glyph_positions (layout->context)) - shape_flags |= PANGO_SHAPE_ROUND_POSITIONS; - - if (state->properties.shape_set) - _pango_shape_shape (layout->text + item->offset, item->num_chars, - state->properties.shape_ink_rect, state->properties.shape_logical_rect, - glyphs); - else - pango_shape_item (item, - layout->text, layout->length, - layout->log_attrs + state->start_offset, - glyphs, - shape_flags); - - if (state->properties.letter_spacing) - { - PangoGlyphItem glyph_item; - int space_left, space_right; - - glyph_item.item = item; - glyph_item.glyphs = glyphs; - - pango_glyph_item_letter_space (&glyph_item, - layout->text, - layout->log_attrs + state->start_offset, - state->properties.letter_spacing); - - distribute_letter_spacing (state->properties.letter_spacing, &space_left, &space_right); - - glyphs->glyphs[0].geometry.width += space_left; - glyphs->glyphs[0].geometry.x_offset += space_left; - glyphs->glyphs[glyphs->num_glyphs - 1].geometry.width += space_right; - } - - if (state->last_tab.glyphs != NULL) - { - int w; - - g_assert (state->last_tab.glyphs->num_glyphs == 1); - - /* Update the width of the current tab to position this run properly */ - - w = state->last_tab.pos - state->last_tab.width; - - if (state->last_tab.align == PANGO_TAB_RIGHT) - w -= pango_glyph_string_get_width (glyphs); - else if (state->last_tab.align == PANGO_TAB_CENTER) - w -= pango_glyph_string_get_width (glyphs) / 2; - else if (state->last_tab.align == PANGO_TAB_DECIMAL) - { - int width; - gboolean found; - - get_decimal_prefix_width (item, glyphs, layout->text, state->last_tab.decimal, &width, &found); - - w -= width; - } - - state->last_tab.glyphs->glyphs[0].geometry.width = MAX (w, 0); - } - } - - return glyphs; -} - -static void -insert_run (PangoLayoutLine *line, - ParaBreakState *state, - PangoItem *run_item, - PangoGlyphString *glyphs, - gboolean last_run) -{ - PangoLayoutRun *run = g_slice_new (PangoLayoutRun); - - run->item = run_item; - - if (glyphs) - run->glyphs = glyphs; - else if (last_run && state->log_widths_offset == 0 && - !(run_item->analysis.flags & PANGO_ANALYSIS_FLAG_NEED_HYPHEN)) - { - run->glyphs = state->glyphs; - state->glyphs = NULL; - } - else - run->glyphs = shape_run (line, state, run_item); - - if (last_run && state->glyphs) - { - pango_glyph_string_free (state->glyphs); - state->glyphs = NULL; - } - - line->runs = g_slist_prepend (line->runs, run); - line->length += run_item->length; - - if (state->last_tab.glyphs && run->glyphs != state->last_tab.glyphs) - { - gboolean found_decimal = FALSE; - int width; - - /* Adjust the tab position so placing further runs will continue to - * maintain the tab placement. In the case of decimal tabs, we are - * done once we've placed the run with the decimal point. - */ - - if (state->last_tab.align == PANGO_TAB_RIGHT) - state->last_tab.width += pango_glyph_string_get_width (run->glyphs); - else if (state->last_tab.align == PANGO_TAB_CENTER) - state->last_tab.width += pango_glyph_string_get_width (run->glyphs) / 2; - else if (state->last_tab.align == PANGO_TAB_DECIMAL) - { - int width; - - get_decimal_prefix_width (run->item, run->glyphs, line->layout->text, state->last_tab.decimal, &width, &found_decimal); - - state->last_tab.width += width; - } - - width = MAX (state->last_tab.pos - state->last_tab.width, 0); - state->last_tab.glyphs->glyphs[0].geometry.width = width; - - if (found_decimal || width == 0) - state->last_tab.glyphs = NULL; - } -} - -static gboolean -break_needs_hyphen (PangoLayout *layout, - ParaBreakState *state, - int pos) -{ - return layout->log_attrs[state->start_offset + pos].break_inserts_hyphen || - layout->log_attrs[state->start_offset + pos].break_removes_preceding; -} - -static int -find_hyphen_width (PangoItem *item) -{ - hb_font_t *hb_font; - hb_codepoint_t glyph; - - if (!item->analysis.font) - return 0; - - /* This is not technically correct, since - * a) we may end up inserting a different hyphen - * b) we should reshape the entire run - * But it is close enough in practice - */ - hb_font = pango_font_get_hb_font (item->analysis.font); - if (hb_font_get_nominal_glyph (hb_font, 0x2010, &glyph) || - hb_font_get_nominal_glyph (hb_font, '-', &glyph)) - return hb_font_get_glyph_h_advance (hb_font, glyph); - - return 0; -} - -static inline void -ensure_hyphen_width (ParaBreakState *state) -{ - if (state->hyphen_width < 0) - { - PangoItem *item = state->items->data; - state->hyphen_width = find_hyphen_width (item); - } -} - -static int -find_break_extra_width (PangoLayout *layout, - ParaBreakState *state, - int pos) -{ - /* Check whether to insert a hyphen, - * or whether we are breaking after one of those - * characters that turn into a hyphen, - * or after a space. - */ - if (layout->log_attrs[state->start_offset + pos].break_inserts_hyphen) - { - ensure_hyphen_width (state); - - if (layout->log_attrs[state->start_offset + pos].break_removes_preceding && pos > 0) - return state->hyphen_width - state->log_widths[state->log_widths_offset + pos - 1]; - else - return state->hyphen_width; - } - else if (pos > 0 && - layout->log_attrs[state->start_offset + pos - 1].is_white) - { - return - state->log_widths[state->log_widths_offset + pos - 1]; - } - - return 0; -} - -#if 0 -# define DEBUG debug -static int pango_layout_line_get_width (PangoLayoutLine *line); -static void -debug (const char *where, PangoLayoutLine *line, ParaBreakState *state) -{ - int line_width = pango_layout_line_get_width (line); - - g_debug ("rem %d + line %d = %d %s", - state->remaining_width, - line_width, - state->remaining_width + line_width, - where); -} -# define DEBUG1(...) g_debug (__VA_ARGS__) -#else -# define DEBUG(where, line, state) do { } while (0) -# define DEBUG1(...) do { } while (0) -#endif - -static inline void -compute_log_widths (PangoLayout *layout, - ParaBreakState *state) -{ - PangoItem *item = state->items->data; - PangoGlyphItem glyph_item = { item, state->glyphs }; - - if (item->num_chars > state->num_log_widths) - { - state->log_widths = g_renew (int, state->log_widths, item->num_chars); - state->num_log_widths = item->num_chars; - } - - pango_glyph_item_get_logical_widths (&glyph_item, layout->text, state->log_widths); -} - -/* If last_tab is set, we've added a tab and remaining_width has been updated to - * account for its origin width, which is last_tab_pos - last_tab_width. shape_run - * updates the tab width, so we need to consider the delta when comparing - * against remaining_width. - */ -static int -tab_width_change (ParaBreakState *state) -{ - if (state->last_tab.glyphs) - return state->last_tab.glyphs->glyphs[0].geometry.width - (state->last_tab.pos - state->last_tab.width); - - return 0; + return layout->width; } -/* Tries to insert as much as possible of the item at the head of - * state->items onto @line. Five results are possible: - * - * %BREAK_NONE_FIT: Couldn't fit anything. - * %BREAK_SOME_FIT: The item was broken in the middle. - * %BREAK_ALL_FIT: Everything fit. - * %BREAK_EMPTY_FIT: Nothing fit, but that was ok, as we can break at the first char. - * %BREAK_LINE_SEPARATOR: Item begins with a line separator. - * - * If @force_fit is %TRUE, then %BREAK_NONE_FIT will never - * be returned, a run will be added even if inserting the minimum amount - * will cause the line to overflow. This is used at the start of a line - * and until we've found at least some place to break. - * - * If @no_break_at_end is %TRUE, then %BREAK_ALL_FIT will never be - * returned even everything fits; the run will be broken earlier, - * or %BREAK_NONE_FIT returned. This is used when the end of the - * run is not a break position. - * - * This function is the core of our line-breaking, and it is long and involved. - * Here is an outline of the algorithm, without all the bookkeeping: - * - * if item appears to fit entirely - * measure it - * if it actually fits - * return BREAK_ALL_FIT +/** + * pango_layout_set_height: + * @layout: a `PangoLayout`. + * @height: the desired height * - * retry_break: - * for each position p in the item - * if adding more is 'obviously' not going to help and we have a breakpoint - * exit the loop - * if p is a possible break position - * if p is 'obviously' going to fit - * bc = p - * else - * measure breaking at p (taking extra break width into account - * if we don't have a break candidate yet - * bc = p - * else - * if p is better than bc - * bc = p + * Sets the height to which the `PangoLayout` should be ellipsized at. * - * if bc does not fit and we can loosen break conditions - * loosen break conditions and retry break + * There are two different behaviors, based on whether @height is positive + * or negative. * - * return bc - */ -static BreakResult -process_item (PangoLayout *layout, - PangoLayoutLine *line, - ParaBreakState *state, - gboolean force_fit, - gboolean no_break_at_end, - gboolean is_last_item) -{ - PangoItem *item = state->items->data; - gboolean shape_set = FALSE; - int width; - int extra_width; - int orig_extra_width; - int length; - int i; - int processing_new_item; - int num_chars; - int orig_width; - PangoWrapMode wrap; - int break_num_chars; - int break_width; - int break_extra_width; - PangoGlyphString *break_glyphs; - PangoFontMetrics *metrics; - int safe_distance; - - DEBUG1 ("process item '%.*s'. Remaining width %d", - item->length, layout->text + item->offset, - state->remaining_width); - - /* We don't want to shape more than necessary, so we keep the results - * of shaping a new item in state->glyphs, state->log_widths. Once - * we break off initial parts of the item, we update state->log_widths_offset - * to take that into account. Note that the widths we calculate from the - * log_widths are an approximation, because a) log_widths are just - * evenly divided for clusters, and b) clusters may change as we - * break in the middle (think ff- i). - * - * We use state->log_widths_offset != 0 to detect if we are dealing - * with the original item, or one that has been chopped off. - */ - if (!state->glyphs) - { - pango_item_get_properties (item, &state->properties); - state->glyphs = shape_run (line, state, item); - state->log_widths_offset = 0; - processing_new_item = TRUE; - } - else - processing_new_item = FALSE; - - /* Only one character has type G_UNICODE_LINE_SEPARATOR in Unicode 5.0; - * update this if that changes. - */ -#define LINE_SEPARATOR 0x2028 - - if (!layout->single_paragraph && - g_utf8_get_char (layout->text + item->offset) == LINE_SEPARATOR && - !should_ellipsize_current_line (layout, state)) - { - insert_run (line, state, item, NULL, TRUE); - state->log_widths_offset += item->num_chars; - - return BREAK_LINE_SEPARATOR; - } - - if (state->remaining_width < 0 && !no_break_at_end) /* Wrapping off */ - { - insert_run (line, state, item, NULL, TRUE); - DEBUG1 ("no wrapping, all-fit"); - return BREAK_ALL_FIT; - } - - if (processing_new_item) - { - width = pango_glyph_string_get_width (state->glyphs); - } - else - { - width = 0; - for (i = 0; i < item->num_chars; i++) - width += state->log_widths[state->log_widths_offset + i]; - } - - if (layout->text[item->offset] == '\t') - { - insert_run (line, state, item, NULL, TRUE); - state->remaining_width -= width; - state->remaining_width = MAX (state->remaining_width, 0); - - DEBUG1 ("tab run, all-fit"); - return BREAK_ALL_FIT; - } - - wrap = layout->wrap; - - if (!no_break_at_end && - can_break_at (layout, state->start_offset + item->num_chars, wrap)) - { - if (processing_new_item) - { - compute_log_widths (layout, state); - processing_new_item = FALSE; - } - - extra_width = find_break_extra_width (layout, state, item->num_chars); - } - else - extra_width = 0; - - if ((width + extra_width <= state->remaining_width || (item->num_chars == 1 && !line->runs) || - (state->last_tab.glyphs && state->last_tab.align != PANGO_TAB_LEFT)) && - !no_break_at_end) - { - PangoGlyphString *glyphs; - - DEBUG1 ("%d + %d <= %d", width, extra_width, state->remaining_width); - glyphs = shape_run (line, state, item); - - width = pango_glyph_string_get_width (glyphs) + tab_width_change (state); - - if (width + extra_width <= state->remaining_width || (item->num_chars == 1 && !line->runs)) - { - insert_run (line, state, item, glyphs, TRUE); - - state->remaining_width -= width; - state->remaining_width = MAX (state->remaining_width, 0); - - DEBUG1 ("early accept '%.*s', all-fit, remaining %d", - item->length, layout->text + item->offset, - state->remaining_width); - return BREAK_ALL_FIT; - } - - /* if it doesn't fit after shaping, discard and proceed to break the item */ - pango_glyph_string_free (glyphs); - } - - /*** From here on, we look for a way to break item ***/ - - orig_width = width; - orig_extra_width = extra_width; - break_width = width; - break_extra_width = extra_width; - break_num_chars = item->num_chars; - wrap = layout->wrap; - break_glyphs = NULL; - - /* Add some safety margin here. If we are farther away from the end of the - * line than this, we don't look carefully at a break possibility. - */ - metrics = pango_font_get_metrics (item->analysis.font, item->analysis.language); - safe_distance = pango_font_metrics_get_approximate_char_width (metrics) * 3; - pango_font_metrics_unref (metrics); - - if (processing_new_item) - { - compute_log_widths (layout, state); - processing_new_item = FALSE; - } - -retry_break: - - for (num_chars = 0, width = 0; num_chars < (no_break_at_end ? item->num_chars : (item->num_chars + 1)); num_chars++) - { - extra_width = find_break_extra_width (layout, state, num_chars); - - /* We don't want to walk the entire item if we can help it, but - * we need to keep going at least until we've found a breakpoint - * that 'works' (as in, it doesn't overflow the budget we have, - * or there is no hope of finding a better one). - * - * We rely on the fact that MIN(width + extra_width, width) is - * monotonically increasing. - */ - - if (MIN (width + extra_width, width) > state->remaining_width + safe_distance && - break_num_chars < item->num_chars) - { - DEBUG1 ("at %d, MIN(%d, %d + %d) > %d + MARGIN, breaking at %d", - num_chars, width, extra_width, width, state->remaining_width, break_num_chars); - break; - } - - /* If there are no previous runs we have to take care to grab at least one char. */ - if (can_break_at (layout, state->start_offset + num_chars, wrap) && - (num_chars > 0 || line->runs)) - { - DEBUG1 ("possible breakpoint: %d, extra_width %d", num_chars, extra_width); - if (num_chars == 0 || - width + extra_width < state->remaining_width - safe_distance) - { - DEBUG1 ("trivial accept"); - break_num_chars = num_chars; - break_width = width; - break_extra_width = extra_width; - } - else - { - int length; - int new_break_width; - PangoItem *new_item; - PangoGlyphString *glyphs; - - length = g_utf8_offset_to_pointer (layout->text + item->offset, num_chars) - (layout->text + item->offset); - - if (num_chars < item->num_chars) - { - new_item = pango_item_split (item, length, num_chars); - - if (break_needs_hyphen (layout, state, num_chars)) - new_item->analysis.flags |= PANGO_ANALYSIS_FLAG_NEED_HYPHEN; - else - new_item->analysis.flags &= ~PANGO_ANALYSIS_FLAG_NEED_HYPHEN; - } - else - new_item = item; - - glyphs = shape_run (line, state, new_item); - - new_break_width = pango_glyph_string_get_width (glyphs) + tab_width_change (state); - - if (num_chars > 0 && - (item != new_item || !is_last_item) && /* We don't collapse space at the very end */ - layout->log_attrs[state->start_offset + num_chars - 1].is_white) - extra_width = - state->log_widths[state->log_widths_offset + num_chars - 1]; - else if (item == new_item && !is_last_item && - break_needs_hyphen (layout, state, num_chars)) - extra_width = state->hyphen_width; - else - extra_width = 0; - - DEBUG1 ("measured breakpoint %d: %d, extra %d", num_chars, new_break_width, extra_width); - - if (new_item != item) - { - pango_item_free (new_item); - pango_item_unsplit (item, length, num_chars); - } - - if (break_num_chars == item->num_chars || - new_break_width + extra_width <= state->remaining_width || - new_break_width + extra_width < break_width + break_extra_width) - { - DEBUG1 ("accept breakpoint %d: %d + %d <= %d + %d", - num_chars, new_break_width, extra_width, break_width, break_extra_width); - DEBUG1 ("replace bp %d by %d", break_num_chars, num_chars); - break_num_chars = num_chars; - break_width = new_break_width; - break_extra_width = extra_width; - - if (break_glyphs) - pango_glyph_string_free (break_glyphs); - break_glyphs = glyphs; - } - else - { - DEBUG1 ("ignore breakpoint %d", num_chars); - pango_glyph_string_free (glyphs); - } - } - } - - DEBUG1 ("bp now %d", break_num_chars); - if (num_chars < item->num_chars) - width += state->log_widths[state->log_widths_offset + num_chars]; - } - - if (wrap == PANGO_WRAP_WORD_CHAR && force_fit && break_width + break_extra_width > state->remaining_width) - { - /* Try again, with looser conditions */ - DEBUG1 ("does not fit, try again with wrap-char"); - wrap = PANGO_WRAP_CHAR; - break_num_chars = item->num_chars; - break_width = orig_width; - break_extra_width = orig_extra_width; - if (break_glyphs) - pango_glyph_string_free (break_glyphs); - break_glyphs = NULL; - goto retry_break; - } - - if (force_fit || break_width + break_extra_width <= state->remaining_width) /* Successfully broke the item */ - { - if (state->remaining_width >= 0) - { - state->remaining_width -= break_width + break_extra_width; - state->remaining_width = MAX (state->remaining_width, 0); - } - - if (break_num_chars == item->num_chars) - { - if (can_break_at (layout, state->start_offset + break_num_chars, wrap) && - break_needs_hyphen (layout, state, break_num_chars)) - item->analysis.flags |= PANGO_ANALYSIS_FLAG_NEED_HYPHEN; - - insert_run (line, state, item, NULL, TRUE); - - if (break_glyphs) - pango_glyph_string_free (break_glyphs); - - DEBUG1 ("all-fit '%.*s', remaining %d", - item->length, layout->text + item->offset, - state->remaining_width); - return BREAK_ALL_FIT; - } - else if (break_num_chars == 0) - { - if (break_glyphs) - pango_glyph_string_free (break_glyphs); - - DEBUG1 ("empty-fit, remaining %d", state->remaining_width); - return BREAK_EMPTY_FIT; - } - else - { - PangoItem *new_item; - - length = g_utf8_offset_to_pointer (layout->text + item->offset, break_num_chars) - (layout->text + item->offset); - - new_item = pango_item_split (item, length, break_num_chars); - - insert_run (line, state, new_item, break_glyphs, FALSE); - - state->log_widths_offset += break_num_chars; - - /* Shaped items should never be broken */ - g_assert (!shape_set); - - DEBUG1 ("some-fit '%.*s', remaining %d", - new_item->length, layout->text + new_item->offset, - state->remaining_width); - return BREAK_SOME_FIT; - } - } - else - { - pango_glyph_string_free (state->glyphs); - state->glyphs = NULL; - - if (break_glyphs) - pango_glyph_string_free (break_glyphs); - - DEBUG1 ("none-fit, remaining %d", state->remaining_width); - return BREAK_NONE_FIT; - } -} - -/* The resolved direction for the line is always one - * of LTR/RTL; not a week or neutral directions + * See [property@Pango.Layout:height] for details. */ -static void -line_set_resolved_dir (PangoLayoutLine *line, - PangoDirection direction) -{ - switch (direction) - { - default: - case PANGO_DIRECTION_LTR: - case PANGO_DIRECTION_TTB_RTL: - case PANGO_DIRECTION_WEAK_LTR: - case PANGO_DIRECTION_NEUTRAL: - line->resolved_dir = PANGO_DIRECTION_LTR; - break; - case PANGO_DIRECTION_RTL: - case PANGO_DIRECTION_WEAK_RTL: - case PANGO_DIRECTION_TTB_LTR: - line->resolved_dir = PANGO_DIRECTION_RTL; - break; - } - - /* The direction vs. gravity dance: - * - If gravity is SOUTH, leave direction untouched. - * - If gravity is NORTH, switch direction. - * - If gravity is EAST, set to LTR, as - * it's a clockwise-rotated layout, so the rotated - * top is unrotated left. - * - If gravity is WEST, set to RTL, as - * it's a counter-clockwise-rotated layout, so the rotated - * top is unrotated right. - * - * A similar dance is performed in pango-context.c: - * itemize_state_add_character(). Keep in synch. - */ - switch (pango_context_get_gravity (line->layout->context)) - { - default: - case PANGO_GRAVITY_AUTO: - case PANGO_GRAVITY_SOUTH: - break; - case PANGO_GRAVITY_NORTH: - line->resolved_dir = PANGO_DIRECTION_LTR - + PANGO_DIRECTION_RTL - - line->resolved_dir; - break; - case PANGO_GRAVITY_EAST: - /* This is in fact why deprecated TTB_RTL is LTR */ - line->resolved_dir = PANGO_DIRECTION_LTR; - break; - case PANGO_GRAVITY_WEST: - /* This is in fact why deprecated TTB_LTR is RTL */ - line->resolved_dir = PANGO_DIRECTION_RTL; - break; - } -} - -static gboolean -should_ellipsize_current_line (PangoLayout *layout, - ParaBreakState *state) -{ - if (G_LIKELY (layout->ellipsize == PANGO_ELLIPSIZE_NONE || layout->width < 0)) - return FALSE; - - if (layout->height >= 0) - { - /* state->remaining_height is height of layout left */ - - /* if we can't stuff two more lines at the current guess of line height, - * the line we are going to produce is going to be the last line - */ - return state->line_height * 2 > state->remaining_height; - } - else - { - /* -layout->height is number of lines per paragraph to show */ - return state->line_of_par == - layout->height; - } -} - -static void -add_line (PangoLayoutLine *line, - ParaBreakState *state) -{ - PangoLayout *layout = line->layout; - - /* we prepend, then reverse the list later */ - layout->lines = g_slist_prepend (layout->lines, line); - layout->line_count++; - - if (layout->height >= 0) - { - PangoRectangle logical_rect; - pango_layout_line_get_extents (line, NULL, &logical_rect); - state->remaining_height -= logical_rect.height; - state->remaining_height -= layout->spacing; - state->line_height = logical_rect.height; - } -} - -static void -process_line (PangoLayout *layout, - ParaBreakState *state) -{ - PangoLayoutLine *line; - - gboolean have_break = FALSE; /* If we've seen a possible break yet */ - int break_remaining_width = 0; /* Remaining width before adding run with break */ - int break_start_offset = 0; /* Start offset before adding run with break */ - GSList *break_link = NULL; /* Link holding run before break */ - gboolean wrapped = FALSE; /* If we had to wrap the line */ - - line = pango_layout_line_new (layout); - line->start_index = state->line_start_index; - line->is_paragraph_start = state->line_of_par == 1; - line_set_resolved_dir (line, state->base_dir); - - state->line_width = layout->width; - if (state->line_width >= 0 && layout->alignment != PANGO_ALIGN_CENTER) - { - if (line->is_paragraph_start && layout->indent >= 0) - state->line_width -= layout->indent; - else if (!line->is_paragraph_start && layout->indent < 0) - state->line_width += layout->indent; - - if (state->line_width < 0) - state->line_width = 0; - } - - if (G_UNLIKELY (should_ellipsize_current_line (layout, state))) - state->remaining_width = -1; - else - state->remaining_width = state->line_width; - - state->last_tab.glyphs = NULL; - state->last_tab.index = 0; - state->last_tab.align = PANGO_TAB_LEFT; - - DEBUG ("starting to fill line", line, state); - - while (state->items) - { - PangoItem *item = state->items->data; - BreakResult result; - int old_num_chars; - int old_remaining_width; - gboolean first_item_in_line; - gboolean last_item_in_line; - - old_num_chars = item->num_chars; - old_remaining_width = state->remaining_width; - first_item_in_line = line->runs == NULL; - last_item_in_line = state->items->next == NULL; - - result = process_item (layout, line, state, !have_break, FALSE, last_item_in_line); - - switch (result) - { - case BREAK_ALL_FIT: - if (layout->text[item->offset] != '\t' && - can_break_in (layout, state->start_offset, old_num_chars, !first_item_in_line)) - { - have_break = TRUE; - break_remaining_width = old_remaining_width; - break_start_offset = state->start_offset; - break_link = line->runs->next; - DEBUG1 ("all-fit, have break"); - } - - state->items = g_list_delete_link (state->items, state->items); - state->start_offset += old_num_chars; - - break; - - case BREAK_EMPTY_FIT: - wrapped = TRUE; - goto done; - - case BREAK_SOME_FIT: - state->start_offset += old_num_chars - item->num_chars; - wrapped = TRUE; - goto done; - - case BREAK_NONE_FIT: - /* Back up over unused runs to run where there is a break */ - while (line->runs && line->runs != break_link) - { - PangoLayoutRun *run = line->runs->data; - - /* If we uninsert the current tab run, - * we need to reset the tab state - */ - if (run->glyphs == state->last_tab.glyphs) - { - state->last_tab.glyphs = NULL; - state->last_tab.index = 0; - state->last_tab.align = PANGO_TAB_LEFT; - } - - state->items = g_list_prepend (state->items, uninsert_run (line)); - } - - state->start_offset = break_start_offset; - state->remaining_width = break_remaining_width; - last_item_in_line = state->items->next == NULL; - - /* Reshape run to break */ - item = state->items->data; - - old_num_chars = item->num_chars; - result = process_item (layout, line, state, TRUE, TRUE, last_item_in_line); - g_assert (result == BREAK_SOME_FIT || result == BREAK_EMPTY_FIT); - - state->start_offset += old_num_chars - item->num_chars; - - wrapped = TRUE; - goto done; - - case BREAK_LINE_SEPARATOR: - state->items = g_list_delete_link (state->items, state->items); - state->start_offset += old_num_chars; - /* A line-separate is just a forced break. Set wrapped, so we do justification */ - wrapped = TRUE; - goto done; - - default: - break; - } - } - - done: - pango_layout_line_postprocess (line, state, wrapped); - DEBUG1 ("line %d done. remaining %d", state->line_of_par, state->remaining_width); - add_line (line, state); - state->line_of_par++; - state->line_start_index += line->length; - state->line_start_offset = state->start_offset; -} - -static void -get_items_log_attrs (const char *text, - int start, - int length, - GList *items, - PangoAttrList *attrs, - PangoLogAttr *log_attrs, - int log_attrs_len) -{ - int offset = 0; - GList *l; - - pango_default_break (text + start, length, NULL, log_attrs, log_attrs_len); - - for (l = items; l; l = l->next) - { - PangoItem *item = l->data; - g_assert (item->offset <= start + length); - g_assert (item->length <= (start + length) - item->offset); - - pango_tailor_break (text + item->offset, - item->length, - &item->analysis, - -1, - log_attrs + offset, - item->num_chars + 1); - - offset += item->num_chars; - } - - if (attrs && items) - { - PangoItem *item = items->data; - pango_attr_break (text + start, length, attrs, item->offset, log_attrs, log_attrs_len); - } -} - -static PangoAttrList * -pango_layout_get_effective_attributes (PangoLayout *layout) -{ - PangoAttrList *attrs; - - if (layout->attrs) - attrs = pango_attr_list_copy (layout->attrs); - else - attrs = NULL; - - if (layout->font_desc) - { - PangoAttribute *attr = pango_attr_font_desc_new (layout->font_desc); - - if (!attrs) - attrs = pango_attr_list_new (); - - pango_attr_list_insert_before (attrs, attr); - } - - if (layout->single_paragraph) - { - PangoAttribute *attr = pango_attr_show_new (PANGO_SHOW_LINE_BREAKS); - - if (!attrs) - attrs = pango_attr_list_new (); - - pango_attr_list_insert_before (attrs, attr); - } - - return attrs; -} - -static gboolean -affects_itemization (PangoAttribute *attr, - gpointer data) -{ - switch ((int)attr->klass->type) - { - /* These affect font selection */ - case PANGO_ATTR_LANGUAGE: - case PANGO_ATTR_FAMILY: - case PANGO_ATTR_STYLE: - case PANGO_ATTR_WEIGHT: - case PANGO_ATTR_VARIANT: - case PANGO_ATTR_STRETCH: - case PANGO_ATTR_SIZE: - case PANGO_ATTR_FONT_DESC: - case PANGO_ATTR_SCALE: - case PANGO_ATTR_FALLBACK: - case PANGO_ATTR_ABSOLUTE_SIZE: - case PANGO_ATTR_GRAVITY: - case PANGO_ATTR_GRAVITY_HINT: - case PANGO_ATTR_FONT_SCALE: - /* These need to be constant across runs */ - case PANGO_ATTR_LETTER_SPACING: - case PANGO_ATTR_SHAPE: - case PANGO_ATTR_RISE: - case PANGO_ATTR_BASELINE_SHIFT: - case PANGO_ATTR_LINE_HEIGHT: - case PANGO_ATTR_ABSOLUTE_LINE_HEIGHT: - case PANGO_ATTR_TEXT_TRANSFORM: - return TRUE; - default: - return FALSE; - } -} - -static gboolean -affects_break_or_shape (PangoAttribute *attr, - gpointer data) -{ - switch ((int)attr->klass->type) - { - /* Affects breaks */ - case PANGO_ATTR_ALLOW_BREAKS: - case PANGO_ATTR_WORD: - case PANGO_ATTR_SENTENCE: - /* Affects shaping */ - case PANGO_ATTR_INSERT_HYPHENS: - case PANGO_ATTR_FONT_FEATURES: - case PANGO_ATTR_SHOW: - return TRUE; - default: - return FALSE; - } -} - -static void -apply_attributes_to_items (GList *items, - PangoAttrList *attrs) -{ - GList *l; - PangoAttrIterator iter; - - if (!attrs) - return; - - _pango_attr_list_get_iterator (attrs, &iter); - - for (l = items; l; l = l->next) - { - PangoItem *item = l->data; - pango_item_apply_attrs (item, &iter); - } - - _pango_attr_iterator_destroy (&iter); -} - -static void -apply_attributes_to_runs (PangoLayout *layout, - PangoAttrList *attrs) -{ - GSList *ll; - - if (!attrs) - return; - - for (ll = layout->lines; ll; ll = ll->next) - { - PangoLayoutLine *line = ll->data; - GSList *old_runs = g_slist_reverse (line->runs); - GSList *rl; - - line->runs = NULL; - for (rl = old_runs; rl; rl = rl->next) - { - PangoGlyphItem *glyph_item = rl->data; - GSList *new_runs; - - new_runs = pango_glyph_item_apply_attrs (glyph_item, - layout->text, - attrs); - - line->runs = g_slist_concat (new_runs, line->runs); - } - - g_slist_free (old_runs); - } -} - -static int -compute_n_chars (PangoLayoutLine *line) -{ - int n_chars = 0; - - for (GSList *l = line->runs; l; l = l->next) - { - PangoGlyphItem *run = l->data; - n_chars += run->item->num_chars; - } - - return n_chars; -} - -static void -pango_layout_line_check_invariants (PangoLayoutLine *line, - const char *text) -{ - int n_chars; - - n_chars = compute_n_chars (line); - - /* Check that byte and char positions agree */ - g_assert (g_utf8_strlen (text + line->start_index, line->length) == n_chars); - g_assert (g_utf8_offset_to_pointer (text + line->start_index, n_chars) == text + line->start_index + line->length); - - /* Check that runs are sane */ - if (line->runs) - { - int run_min, run_max; - - run_min = G_MAXINT; - run_max = 0; - for (GSList *l = line->runs; l; l = l->next) - { - PangoGlyphItem *run = l->data; - - run_min = MIN (run_min, run->item->offset); - run_max = MAX (run_max, run->item->offset + run->item->length); - } - - g_assert (run_min == line->start_index); - g_assert (run_max == line->start_index + line->length); - } -} - - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - -static void -pango_layout_check_lines (PangoLayout *layout) +void +pango_layout_set_height (PangoLayout *layout, + int height) { - const char *start; - gboolean done = FALSE; - int start_offset; - PangoAttrList *attrs; - PangoAttrList *itemize_attrs; - PangoAttrList *shape_attrs; - PangoAttrIterator iter; - PangoDirection prev_base_dir = PANGO_DIRECTION_NEUTRAL; - PangoDirection base_dir = PANGO_DIRECTION_NEUTRAL; - ParaBreakState state; - gboolean need_log_attrs; - - check_context_changed (layout); + g_return_if_fail (PANGO_IS_LAYOUT (layout)); - if (G_LIKELY (layout->lines)) + if (layout->height == height) return; - /* For simplicity, we make sure at this point that layout->text - * is non-NULL even if it is zero length - */ - if (G_UNLIKELY (!layout->text)) - pango_layout_set_text (layout, NULL, 0); - - attrs = pango_layout_get_effective_attributes (layout); - if (attrs) - { - shape_attrs = pango_attr_list_filter (attrs, affects_break_or_shape, NULL); - itemize_attrs = pango_attr_list_filter (attrs, affects_itemization, NULL); - - if (itemize_attrs) - _pango_attr_list_get_iterator (itemize_attrs, &iter); - } - else - { - shape_attrs = NULL; - itemize_attrs = NULL; - } - - if (!layout->log_attrs) - { - layout->log_attrs = g_new0 (PangoLogAttr, layout->n_chars + 1); - need_log_attrs = TRUE; - } - else - { - need_log_attrs = FALSE; - } - - start_offset = 0; - start = layout->text; - - /* Find the first strong direction of the text */ - if (layout->auto_dir) - { - prev_base_dir = pango_find_base_dir (layout->text, layout->length); - if (prev_base_dir == PANGO_DIRECTION_NEUTRAL) - prev_base_dir = pango_context_get_base_dir (layout->context); - } - else - base_dir = pango_context_get_base_dir (layout->context); - - /* these are only used if layout->height >= 0 */ - state.remaining_height = layout->height; - state.line_height = -1; - if (layout->height >= 0) - { - PangoRectangle logical = { 0, }; - int height = 0; - pango_layout_get_empty_extents_and_height_at_index (layout, 0, &logical, TRUE, &height); - state.line_height = layout->line_spacing == 0.0 ? logical.height : layout->line_spacing * height; - } - - state.log_widths = NULL; - state.num_log_widths = 0; - state.baseline_shifts = NULL; + layout->height = height; - DEBUG1 ("START layout"); - do - { - int delim_len; - const char *end; - int delimiter_index, next_para_index; - - if (layout->single_paragraph) - { - delimiter_index = layout->length; - next_para_index = layout->length; - } - else - { - pango_find_paragraph_boundary (start, - (layout->text + layout->length) - start, - &delimiter_index, - &next_para_index); - } - - g_assert (next_para_index >= delimiter_index); - - if (layout->auto_dir) - { - base_dir = pango_find_base_dir (start, delimiter_index); - - /* Propagate the base direction for neutral paragraphs */ - if (base_dir == PANGO_DIRECTION_NEUTRAL) - base_dir = prev_base_dir; - else - prev_base_dir = base_dir; - } - - end = start + delimiter_index; - - delim_len = next_para_index - delimiter_index; - - if (end == (layout->text + layout->length)) - done = TRUE; - - g_assert (end <= (layout->text + layout->length)); - g_assert (start <= (layout->text + layout->length)); - g_assert (delim_len < 4); /* PS is 3 bytes */ - g_assert (delim_len >= 0); - - state.attrs = itemize_attrs; - state.items = pango_itemize_with_font (layout->context, - base_dir, - layout->text, - start - layout->text, - end - start, - itemize_attrs, - itemize_attrs ? &iter : NULL, - NULL); - - apply_attributes_to_items (state.items, shape_attrs); - - if (need_log_attrs) - get_items_log_attrs (layout->text, - start - layout->text, - delimiter_index + delim_len, - state.items, - shape_attrs, - layout->log_attrs + start_offset, - layout->n_chars + 1 - start_offset); - - state.items = pango_itemize_post_process_items (layout->context, - layout->text, - layout->log_attrs + start_offset, - state.items); - - state.base_dir = base_dir; - state.line_of_par = 1; - state.start_offset = start_offset; - state.line_start_offset = start_offset; - state.line_start_index = start - layout->text; - - state.glyphs = NULL; - - /* for deterministic bug hunting's sake set everything! */ - state.line_width = -1; - state.remaining_width = -1; - state.log_widths_offset = 0; - - state.hyphen_width = -1; - - if (state.items) - { - while (state.items) - process_line (layout, &state); - } - else - { - PangoLayoutLine *empty_line; - - empty_line = pango_layout_line_new (layout); - empty_line->start_index = state.line_start_index; - empty_line->is_paragraph_start = TRUE; - line_set_resolved_dir (empty_line, base_dir); - - add_line (empty_line, &state); - } - - if (layout->height >= 0 && state.remaining_height < state.line_height) - done = TRUE; - - if (!done) - start_offset += pango_utf8_strlen (start, (end - start) + delim_len); - - start = end + delim_len; - } - while (!done); - - g_free (state.log_widths); - g_list_free_full (state.baseline_shifts, g_free); - - apply_attributes_to_runs (layout, attrs); - layout->lines = g_slist_reverse (layout->lines); - - if (itemize_attrs) - { - pango_attr_list_unref (itemize_attrs); - _pango_attr_iterator_destroy (&iter); - } - - pango_attr_list_unref (shape_attrs); - pango_attr_list_unref (attrs); - - for (GSList *l = layout->lines; l; l = l->next) - { - PangoLayoutLine *line = l->data; - pango_layout_line_check_invariants (line, layout->text); - } - - int w, h; - pango_layout_get_size (layout, &w, &h); - DEBUG1 ("DONE %d %d", w, h); + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_HEIGHT]); + layout_changed (layout); } -#pragma GCC diagnostic pop - /** - * pango_layout_line_ref: - * @line: (nullable): a `PangoLayoutLine` + * pango_layout_get_height: + * @layout: a `PangoLayout` * - * Increase the reference count of a `PangoLayoutLine` by one. + * Gets the height to which the lines of the layout should ellipsize. * - * Return value: the line passed in. + * See [property@Pango.Layout:height] for details. * - * Since: 1.10 + * Return value: the height */ -PangoLayoutLine * -pango_layout_line_ref (PangoLayoutLine *line) +int +pango_layout_get_height (PangoLayout *layout) { - PangoLayoutLinePrivate *private = (PangoLayoutLinePrivate *)line; + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), -1); - if (line == NULL) - return NULL; - - g_atomic_int_inc ((int *) &private->ref_count); - - return line; + return layout->height; } /** - * pango_layout_line_unref: - * @line: a `PangoLayoutLine` + * pango_layout_set_tabs: + * @layout: a `PangoLayout` + * @tabs: (nullable): a `PangoTabArray` * - * Decrease the reference count of a `PangoLayoutLine` by one. + * Sets the tabs to use for @layout, overriding the default tabs. * - * If the result is zero, the line and all associated memory - * will be freed. + * See [method@Pango.LineBreaker.set_tabs] for details. */ void -pango_layout_line_unref (PangoLayoutLine *line) +pango_layout_set_tabs (PangoLayout *layout, + PangoTabArray *tabs) { - PangoLayoutLinePrivate *private = (PangoLayoutLinePrivate *)line; + g_return_if_fail (PANGO_IS_LAYOUT (layout)); - if (line == NULL) + if (layout->tabs == tabs) return; - g_return_if_fail (private->ref_count > 0); - - if (g_atomic_int_dec_and_test ((int *) &private->ref_count)) - { - g_slist_foreach (line->runs, (GFunc)free_run, GINT_TO_POINTER (1)); - g_slist_free (line->runs); - g_slice_free (PangoLayoutLinePrivate, private); - } -} - -G_DEFINE_BOXED_TYPE (PangoLayoutLine, pango_layout_line, - pango_layout_line_ref, - pango_layout_line_unref); + g_clear_pointer (&layout->tabs, pango_tab_array_free); + layout->tabs = pango_tab_array_copy (tabs); -/** - * pango_layout_line_get_start_index: - * @line: a `PangoLayoutLine` - * - * Returns the start index of the line, as byte index - * into the text of the layout. - * - * Returns: the start index of the line - * - * Since: 1.50 - */ -int -pango_layout_line_get_start_index (PangoLayoutLine *line) -{ - return line->start_index; + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_TABS]); + layout_changed (layout); } /** - * pango_layout_line_get_length: - * @line: a `PangoLayoutLine` + * pango_layout_get_tabs: + * @layout: a `PangoLayout` * - * Returns the length of the line, in bytes. + * Gets the current `PangoTabArray` used by this layout. * - * Returns: the length of the line + * If no `PangoTabArray` has been set, then the default tabs are + * in use and %NULL is returned. Default tabs are every 8 spaces. * - * Since: 1.50 + * Return value: (transfer none) (nullable): the tabs for this layout */ -int -pango_layout_line_get_length (PangoLayoutLine *line) +PangoTabArray * +pango_layout_get_tabs (PangoLayout *layout) { - return line->length; -} + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); -/** - * pango_layout_line_is_paragraph_start: - * @line: a `PangoLayoutLine` - * - * Returns whether this is the first line of the paragraph. - * - * Returns: %TRUE if this is the first line - * - * Since: 1.50 - */ -gboolean -pango_layout_line_is_paragraph_start (PangoLayoutLine *line) -{ - return line->is_paragraph_start; + return layout->tabs; } /** - * pango_layout_line_get_resolved_direction: - * @line: a `PangoLayoutLine` - * - * Returns the resolved direction of the line. - * - * Returns: the resolved direction of the line - * - * Since: 1.50 - */ -PangoDirection -pango_layout_line_get_resolved_direction (PangoLayoutLine *line) -{ - return (PangoDirection) line->resolved_dir; -} - -/** - * pango_layout_line_x_to_index: - * @line: a `PangoLayoutLine` - * @x_pos: the X offset (in Pango units) from the left edge of the line. - * @index_: (out): location to store calculated byte index for the grapheme - * in which the user clicked - * @trailing: (out): location to store an integer indicating where in the - * grapheme the user clicked. It will either be zero, or the number of - * characters in the grapheme. 0 represents the leading edge of the grapheme. + * pango_layout_set_single_paragraph: + * @layout: a `PangoLayout` + * @single_paragraph: the new setting * - * Converts from x offset to the byte index of the corresponding character - * within the text of the layout. + * Sets the single paragraph mode of @layout. * - * If @x_pos is outside the line, @index_ and @trailing will point to the very - * first or very last position in the line. This determination is based on the - * resolved direction of the paragraph; for example, if the resolved direction - * is right-to-left, then an X position to the right of the line (after it) - * results in 0 being stored in @index_ and @trailing. An X position to the - * left of the line results in @index_ pointing to the (logical) last grapheme - * in the line and @trailing being set to the number of characters in that - * grapheme. The reverse is true for a left-to-right line. + * If @single_paragraph is `TRUE`, do not treat newlines and similar + * characters as paragraph separators; instead, keep all text in a + * single paragraph, and display a glyph for paragraph separator + * characters. * - * Return value: %FALSE if @x_pos was outside the line, %TRUE if inside - */ -gboolean -pango_layout_line_x_to_index (PangoLayoutLine *line, - int x_pos, - int *index, - int *trailing) -{ - GSList *tmp_list; - gint start_pos = 0; - gint first_index = 0; /* line->start_index */ - gint first_offset; - gint last_index; /* start of last grapheme in line */ - gint last_offset; - gint end_index; /* end iterator for line */ - gint end_offset; /* end iterator for line */ - PangoLayout *layout; - gint last_trailing; - gboolean suppress_last_trailing; - - g_return_val_if_fail (LINE_IS_VALID (line), FALSE); - - layout = line->layout; - - /* Find the last index in the line - */ - first_index = line->start_index; - - if (line->length == 0) - { - if (index) - *index = first_index; - if (trailing) - *trailing = 0; - - return FALSE; - } - - g_assert (line->length > 0); - - first_offset = g_utf8_pointer_to_offset (layout->text, layout->text + line->start_index); - - end_index = first_index + line->length; - end_offset = first_offset + g_utf8_pointer_to_offset (layout->text + first_index, layout->text + end_index); - - last_index = end_index; - last_offset = end_offset; - last_trailing = 0; - do - { - last_index = g_utf8_prev_char (layout->text + last_index) - layout->text; - last_offset--; - last_trailing++; - } - while (last_offset > first_offset && !layout->log_attrs[last_offset].is_cursor_position); - - /* This is a HACK. If a program only keeps track of cursor (etc) - * indices and not the trailing flag, then the trailing index of the - * last character on a wrapped line is identical to the leading - * index of the next line. So, we fake it and set the trailing flag - * to zero. - * - * That is, if the text is "now is the time", and is broken between - * 'now' and 'is' - * - * Then when the cursor is actually at: - * - * n|o|w| |i|s| - * ^ - * we lie and say it is at: - * - * n|o|w| |i|s| - * ^ - * - * So the cursor won't appear on the next line before 'the'. - * - * Actually, any program keeping cursor - * positions with wrapped lines should distinguish leading and - * trailing cursors. - */ - tmp_list = layout->lines; - while (tmp_list->data != line) - tmp_list = tmp_list->next; - - if (tmp_list->next && - line->start_index + line->length == ((PangoLayoutLine *)tmp_list->next->data)->start_index) - suppress_last_trailing = TRUE; - else - suppress_last_trailing = FALSE; - - if (x_pos < 0) - { - /* pick the leftmost char */ - if (index) - *index = (line->resolved_dir == PANGO_DIRECTION_LTR) ? first_index : last_index; - /* and its leftmost edge */ - if (trailing) - *trailing = (line->resolved_dir == PANGO_DIRECTION_LTR || suppress_last_trailing) ? 0 : last_trailing; - - return FALSE; - } - - tmp_list = line->runs; - while (tmp_list) - { - PangoLayoutRun *run = tmp_list->data; - int logical_width; - - logical_width = pango_glyph_string_get_width (run->glyphs); - - if (x_pos >= start_pos && x_pos < start_pos + logical_width) - { - int offset; - gboolean char_trailing; - int grapheme_start_index; - int grapheme_start_offset; - int grapheme_end_offset; - int pos; - int char_index; - - pango_glyph_string_x_to_index (run->glyphs, - layout->text + run->item->offset, run->item->length, - &run->item->analysis, - x_pos - start_pos, - &pos, &char_trailing); - - char_index = run->item->offset + pos; - - /* Convert from characters to graphemes */ - - offset = g_utf8_pointer_to_offset (layout->text, layout->text + char_index); - - grapheme_start_offset = offset; - grapheme_start_index = char_index; - while (grapheme_start_offset > first_offset && - !layout->log_attrs[grapheme_start_offset].is_cursor_position) - { - grapheme_start_index = g_utf8_prev_char (layout->text + grapheme_start_index) - layout->text; - grapheme_start_offset--; - } - - grapheme_end_offset = offset; - do - { - grapheme_end_offset++; - } - while (grapheme_end_offset < end_offset && - !layout->log_attrs[grapheme_end_offset].is_cursor_position); - - if (index) - *index = grapheme_start_index; - - if (trailing) - { - if ((grapheme_end_offset == end_offset && suppress_last_trailing) || - offset + char_trailing <= (grapheme_start_offset + grapheme_end_offset) / 2) - *trailing = 0; - else - *trailing = grapheme_end_offset - grapheme_start_offset; - } - - return TRUE; - } - - start_pos += logical_width; - tmp_list = tmp_list->next; - } - - /* pick the rightmost char */ - if (index) - *index = (line->resolved_dir == PANGO_DIRECTION_LTR) ? last_index : first_index; - - /* and its rightmost edge */ - if (trailing) - *trailing = (line->resolved_dir == PANGO_DIRECTION_LTR && !suppress_last_trailing) ? last_trailing : 0; - - return FALSE; -} - -static int -pango_layout_line_get_width (PangoLayoutLine *line) -{ - int width = 0; - GSList *tmp_list = line->runs; - - while (tmp_list) - { - PangoLayoutRun *run = tmp_list->data; - - width += pango_glyph_string_get_width (run->glyphs); - - tmp_list = tmp_list->next; - } - - return width; -} - -/** - * pango_layout_line_get_x_ranges: - * @line: a `PangoLayoutLine` - * @start_index: Start byte index of the logical range. If this value - * is less than the start index for the line, then the first range - * will extend all the way to the leading edge of the layout. Otherwise, - * it will start at the leading edge of the first character. - * @end_index: Ending byte index of the logical range. If this value is - * greater than the end index for the line, then the last range will - * extend all the way to the trailing edge of the layout. Otherwise, - * it will end at the trailing edge of the last character. - * @ranges: (out) (array length=n_ranges) (transfer full): location to - * store a pointer to an array of ranges. The array will be of length - * `2*n_ranges`, with each range starting at `(*ranges)[2*n]` and of - * width `(*ranges)[2*n + 1] - (*ranges)[2*n]`. This array must be freed - * with g_free(). The coordinates are relative to the layout and are in - * Pango units. - * @n_ranges: The number of ranges stored in @ranges + * Used when you want to allow editing of newlines on a single text line. * - * Gets a list of visual ranges corresponding to a given logical range. - * - * This list is not necessarily minimal - there may be consecutive - * ranges which are adjacent. The ranges will be sorted from left to - * right. The ranges are with respect to the left edge of the entire - * layout, not with respect to the line. + * The default value is %FALSE. */ void -pango_layout_line_get_x_ranges (PangoLayoutLine *line, - int start_index, - int end_index, - int **ranges, - int *n_ranges) -{ - gint line_start_index = 0; - GSList *tmp_list; - int range_count = 0; - int accumulated_width = 0; - int x_offset; - int width, line_width; - PangoAlignment alignment; - - g_return_if_fail (line != NULL); - g_return_if_fail (line->layout != NULL); - g_return_if_fail (start_index <= end_index); - - alignment = get_alignment (line->layout, line); - - width = line->layout->width; - if (width == -1 && alignment != PANGO_ALIGN_LEFT) - { - PangoRectangle logical_rect; - pango_layout_get_extents (line->layout, NULL, &logical_rect); - width = logical_rect.width; - } - - /* FIXME: The computations here could be optimized, by moving the - * computations of the x_offset after we go through and figure - * out where each range is. - */ - - { - PangoRectangle logical_rect; - pango_layout_line_get_extents (line, NULL, &logical_rect); - line_width = logical_rect.width; - } - - get_x_offset (line->layout, line, width, line_width, &x_offset); - - line_start_index = line->start_index; - - /* Allocate the maximum possible size */ - if (ranges) - *ranges = g_new (int, 2 * (2 + g_slist_length (line->runs))); - - if (x_offset > 0 && - ((line->resolved_dir == PANGO_DIRECTION_LTR && start_index < line_start_index) || - (line->resolved_dir == PANGO_DIRECTION_RTL && end_index > line_start_index + line->length))) - { - if (ranges) - { - (*ranges)[2*range_count] = 0; - (*ranges)[2*range_count + 1] = x_offset; - } - - range_count ++; - } - - tmp_list = line->runs; - while (tmp_list) - { - PangoLayoutRun *run = (PangoLayoutRun *)tmp_list->data; - - if ((start_index < run->item->offset + run->item->length && - end_index > run->item->offset)) - { - if (ranges) - { - int run_start_index = MAX (start_index, run->item->offset); - int run_end_index = MIN (end_index, run->item->offset + run->item->length); - int run_start_x, run_end_x; - int attr_offset; - - g_assert (run_end_index > 0); - - /* Back the end_index off one since we want to find the trailing edge of the preceding character */ - - run_end_index = g_utf8_prev_char (line->layout->text + run_end_index) - line->layout->text; - - /* Note: we simply assert here, since our items are all internally - * created. If that ever changes, we need to add a fallback here. - */ - g_assert (run->item->analysis.flags & PANGO_ANALYSIS_FLAG_HAS_CHAR_OFFSET); - attr_offset = ((PangoItemPrivate *)run->item)->char_offset; - - pango_glyph_string_index_to_x_full (run->glyphs, - line->layout->text + run->item->offset, - run->item->length, - &run->item->analysis, - line->layout->log_attrs + attr_offset, - run_start_index - run->item->offset, FALSE, - &run_start_x); - pango_glyph_string_index_to_x_full (run->glyphs, - line->layout->text + run->item->offset, - run->item->length, - &run->item->analysis, - line->layout->log_attrs + attr_offset, - run_end_index - run->item->offset, TRUE, - &run_end_x); - - (*ranges)[2*range_count] = x_offset + accumulated_width + MIN (run_start_x, run_end_x); - (*ranges)[2*range_count + 1] = x_offset + accumulated_width + MAX (run_start_x, run_end_x); - } - - range_count++; - } - - if (tmp_list->next) - accumulated_width += pango_glyph_string_get_width (run->glyphs); - - tmp_list = tmp_list->next; - } - - if (x_offset + line_width < line->layout->width && - ((line->resolved_dir == PANGO_DIRECTION_LTR && end_index > line_start_index + line->length) || - (line->resolved_dir == PANGO_DIRECTION_RTL && start_index < line_start_index))) - { - if (ranges) - { - (*ranges)[2*range_count] = x_offset + line_width; - (*ranges)[2*range_count + 1] = line->layout->width; - } - - range_count ++; - } - - if (n_ranges) - *n_ranges = range_count; -} - -static void -pango_layout_get_empty_extents_and_height_at_index (PangoLayout *layout, - int index, - PangoRectangle *logical_rect, - gboolean apply_line_height, - int *height) -{ - if (logical_rect) - { - PangoFont *font; - PangoFontDescription *font_desc = NULL; - gboolean free_font_desc = FALSE; - double line_height_factor = 0.0; - int absolute_line_height = 0; - - font_desc = pango_context_get_font_description (layout->context); - - if (layout->font_desc) - { - font_desc = pango_font_description_copy_static (font_desc); - pango_font_description_merge (font_desc, layout->font_desc, TRUE); - free_font_desc = TRUE; - } - - /* Find the font description for this line - */ - if (layout->attrs) - { - PangoAttrIterator iter; - int start, end; - - _pango_attr_list_get_iterator (layout->attrs, &iter); - - do - { - pango_attr_iterator_range (&iter, &start, &end); - - if (start <= index && index < end) - { - PangoAttribute *attr; - - if (!free_font_desc) - { - font_desc = pango_font_description_copy_static (font_desc); - free_font_desc = TRUE; - } - - pango_attr_iterator_get_font (&iter, font_desc, NULL, NULL); - - attr = pango_attr_iterator_get (&iter, PANGO_ATTR_LINE_HEIGHT); - if (attr) - line_height_factor = ((PangoAttrFloat *)attr)->value; - - attr = pango_attr_iterator_get (&iter, PANGO_ATTR_ABSOLUTE_LINE_HEIGHT); - if (attr) - absolute_line_height = ((PangoAttrInt *)attr)->value; - - break; - } - - } - while (pango_attr_iterator_next (&iter)); - - _pango_attr_iterator_destroy (&iter); - } - - font = pango_context_load_font (layout->context, font_desc); - if (font) - { - PangoFontMetrics *metrics; - - metrics = pango_font_get_metrics (font, - pango_context_get_language (layout->context)); - - if (metrics) - { - logical_rect->y = - pango_font_metrics_get_ascent (metrics); - logical_rect->height = - logical_rect->y + pango_font_metrics_get_descent (metrics); - if (height) - *height = pango_font_metrics_get_height (metrics); - - pango_font_metrics_unref (metrics); - - if (apply_line_height && - (absolute_line_height != 0 || line_height_factor != 0.0)) - { - int line_height, leading; - - line_height = MAX (absolute_line_height, ceilf (line_height_factor * logical_rect->height)); - - leading = line_height - logical_rect->height; - logical_rect->y -= leading / 2; - logical_rect->height += leading; - } - } - else - { - logical_rect->y = 0; - logical_rect->height = 0; - } - g_object_unref (font); - } - else - { - logical_rect->y = 0; - logical_rect->height = 0; - } - - if (free_font_desc) - pango_font_description_free (font_desc); - - logical_rect->x = 0; - logical_rect->width = 0; - } -} - -void pango_layout_run_get_extents_and_height (PangoLayoutRun *run, - PangoRectangle *run_ink, - PangoRectangle *run_logical, - PangoRectangle *line_logical, - int *height); - -void -pango_layout_run_get_extents_and_height (PangoLayoutRun *run, - PangoRectangle *run_ink, - PangoRectangle *run_logical, - PangoRectangle *line_logical, - int *height) +pango_layout_set_single_paragraph (PangoLayout *layout, + gboolean single_paragraph) { - PangoRectangle logical; - ItemProperties properties; - PangoFontMetrics *metrics = NULL; - gboolean has_underline; - gboolean has_overline; - int y_offset; - - if (G_UNLIKELY (!run_ink && !run_logical && !line_logical && !height)) - return; - - pango_item_get_properties (run->item, &properties); - - has_underline = properties.uline_single || properties.uline_double || - properties.uline_low || properties.uline_error; - has_overline = properties.oline_single; - - if (!run_logical && (run->item->analysis.flags & PANGO_ANALYSIS_FLAG_CENTERED_BASELINE)) - run_logical = &logical; - - if (!run_logical && (has_underline || has_overline || properties.strikethrough)) - run_logical = &logical; - - if (!run_logical && line_logical) - run_logical = &logical; - - if (properties.shape_set) - _pango_shape_get_extents (run->item->num_chars, - properties.shape_ink_rect, - properties.shape_logical_rect, - run_ink, run_logical); - else - pango_glyph_string_extents (run->glyphs, run->item->analysis.font, - run_ink, run_logical); - - if (run_ink && (has_underline || has_overline || properties.strikethrough)) - { - int underline_thickness; - int underline_position; - int strikethrough_thickness; - int strikethrough_position; - int new_pos; - - if (!metrics) - metrics = pango_font_get_metrics (run->item->analysis.font, - run->item->analysis.language); - - underline_thickness = pango_font_metrics_get_underline_thickness (metrics); - underline_position = pango_font_metrics_get_underline_position (metrics); - strikethrough_thickness = pango_font_metrics_get_strikethrough_thickness (metrics); - strikethrough_position = pango_font_metrics_get_strikethrough_position (metrics); - - /* the underline/strikethrough takes x,width of logical_rect. reflect - * that into ink_rect. - */ - new_pos = MIN (run_ink->x, run_logical->x); - run_ink->width = MAX (run_ink->x + run_ink->width, run_logical->x + run_logical->width) - new_pos; - run_ink->x = new_pos; - - /* We should better handle the case of height==0 in the following cases. - * If run_ink->height == 0, we should adjust run_ink->y appropriately. - */ - - if (properties.strikethrough) - { - if (run_ink->height == 0) - { - run_ink->height = strikethrough_thickness; - run_ink->y = -strikethrough_position; - } - } - - if (properties.oline_single) - { - run_ink->y -= underline_thickness; - run_ink->height += underline_thickness; - } - - if (properties.uline_low) - run_ink->height += 2 * underline_thickness; - if (properties.uline_single) - run_ink->height = MAX (run_ink->height, - underline_thickness - underline_position - run_ink->y); - if (properties.uline_double) - run_ink->height = MAX (run_ink->height, - 3 * underline_thickness - underline_position - run_ink->y); - if (properties.uline_error) - run_ink->height = MAX (run_ink->height, - 3 * underline_thickness - underline_position - run_ink->y); - } - - if (height) - { - if (pango_analysis_get_size_font (&run->item->analysis)) - { - PangoFontMetrics *height_metrics; - - height_metrics = pango_font_get_metrics (pango_analysis_get_size_font (&run->item->analysis), - run->item->analysis.language); - - *height = pango_font_metrics_get_height (height_metrics); - - pango_font_metrics_unref (height_metrics); - } - else - { - if (!metrics) - metrics = pango_font_get_metrics (run->item->analysis.font, - run->item->analysis.language); - - *height = pango_font_metrics_get_height (metrics); - } - } - - y_offset = run->y_offset; - - if (run->item->analysis.flags & PANGO_ANALYSIS_FLAG_CENTERED_BASELINE) - { - gboolean is_hinted = (run_logical->y & run_logical->height & (PANGO_SCALE - 1)) == 0; - int adjustment = run_logical->y + run_logical->height / 2; - - if (is_hinted) - adjustment = PANGO_UNITS_ROUND (adjustment); - - y_offset += adjustment; - } - - if (run_ink) - run_ink->y -= y_offset; - - if (run_logical) - run_logical->y -= y_offset; - - if (line_logical) - { - *line_logical = *run_logical; - - if (properties.absolute_line_height != 0 || properties.line_height != 0.0) - { - int line_height, leading; - - line_height = MAX (properties.absolute_line_height, ceilf (properties.line_height * line_logical->height)); - - leading = line_height - line_logical->height; - line_logical->y -= leading / 2; - line_logical->height += leading; - } - } - - if (metrics) - pango_font_metrics_unref (metrics); -} - -static void -pango_layout_line_get_extents_and_height (PangoLayoutLine *line, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect, - int *height) -{ - PangoLayoutLinePrivate *private = (PangoLayoutLinePrivate *)line; - GSList *tmp_list; - int x_pos = 0; - gboolean caching = FALSE; - - g_return_if_fail (LINE_IS_VALID (line)); + g_return_if_fail (PANGO_IS_LAYOUT (layout)); - if (G_UNLIKELY (!ink_rect && !logical_rect && !height)) + if (layout->single_paragraph == single_paragraph) return; - switch (private->cache_status) - { - case CACHED: - if (ink_rect) - *ink_rect = private->ink_rect; - if (logical_rect) - *logical_rect = private->logical_rect; - if (height) - *height = private->height; - return; - - case NOT_CACHED: - caching = TRUE; - if (!ink_rect) - ink_rect = &private->ink_rect; - if (!logical_rect) - logical_rect = &private->logical_rect; - if (!height) - height = &private->height; - break; - - case LEAKED: - default: - break; - } - - if (ink_rect) - { - ink_rect->x = 0; - ink_rect->y = 0; - ink_rect->width = 0; - ink_rect->height = 0; - } - - if (logical_rect) - { - logical_rect->x = 0; - logical_rect->y = 0; - logical_rect->width = 0; - logical_rect->height = 0; - } + layout->single_paragraph = single_paragraph; - if (height) - *height = 0; - - tmp_list = line->runs; - while (tmp_list) - { - PangoLayoutRun *run = tmp_list->data; - int new_pos; - PangoRectangle run_ink; - PangoRectangle run_logical; - int run_height; - - pango_layout_run_get_extents_and_height (run, - ink_rect ? &run_ink : NULL, - NULL, - &run_logical, - height ? &run_height : NULL); - - if (ink_rect) - { - if (ink_rect->width == 0 || ink_rect->height == 0) - { - *ink_rect = run_ink; - ink_rect->x += x_pos; - } - else if (run_ink.width != 0 && run_ink.height != 0) - { - new_pos = MIN (ink_rect->x, x_pos + run_ink.x); - ink_rect->width = MAX (ink_rect->x + ink_rect->width, - x_pos + run_ink.x + run_ink.width) - new_pos; - ink_rect->x = new_pos; - - new_pos = MIN (ink_rect->y, run_ink.y); - ink_rect->height = MAX (ink_rect->y + ink_rect->height, - run_ink.y + run_ink.height) - new_pos; - ink_rect->y = new_pos; - } - } - - if (logical_rect) - { - new_pos = MIN (logical_rect->x, x_pos + run_logical.x); - logical_rect->width = MAX (logical_rect->x + logical_rect->width, - x_pos + run_logical.x + run_logical.width) - new_pos; - logical_rect->x = new_pos; - - new_pos = MIN (logical_rect->y, run_logical.y); - logical_rect->height = MAX (logical_rect->y + logical_rect->height, - run_logical.y + run_logical.height) - new_pos; - logical_rect->y = new_pos; - } - - if (height) - *height = MAX (*height, abs (run_height)); - - x_pos += run_logical.width; - tmp_list = tmp_list->next; - } - - if (!line->runs) - { - PangoRectangle r, *rect; - - rect = logical_rect ? logical_rect : &r; - pango_layout_get_empty_extents_and_height_at_index (line->layout, line->start_index, rect, TRUE, height); - } - - if (caching) - { - if (&private->ink_rect != ink_rect) - private->ink_rect = *ink_rect; - if (&private->logical_rect != logical_rect) - private->logical_rect = *logical_rect; - if (&private->height != height) - private->height = *height; - private->cache_status = CACHED; - } -} - -/** - * pango_layout_line_get_extents: - * @line: a `PangoLayoutLine` - * @ink_rect: (out) (optional): rectangle used to store the extents of - * the glyph string as drawn - * @logical_rect: (out) (optional): rectangle used to store the logical - * extents of the glyph string - * - * Computes the logical and ink extents of a layout line. - * - * See [method@Pango.Font.get_glyph_extents] for details - * about the interpretation of the rectangles. - */ -void -pango_layout_line_get_extents (PangoLayoutLine *line, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect) -{ - pango_layout_line_get_extents_and_height (line, ink_rect, logical_rect, NULL); + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_SINGLE_PARAGRAPH]); + layout_changed (layout); } /** - * pango_layout_line_get_height: - * @line: a `PangoLayoutLine` - * @height: (out) (optional): return location for the line height + * pango_layout_get_single_paragraph: + * @layout: a `PangoLayout` * - * Computes the height of the line, as the maximum of the heights - * of fonts used in this line. + * Obtains whether @layout is in single paragraph mode. * - * Note that the actual baseline-to-baseline distance between lines - * of text is influenced by other factors, such as - * [method@Pango.Layout.set_spacing] and - * [method@Pango.Layout.set_line_spacing]. + * See [method@Pango.Layout.set_single_paragraph]. * - * Since: 1.44 + * Return value: `TRUE` if the layout does not break paragraphs + * at paragraph separator characters, %FALSE otherwise */ -void -pango_layout_line_get_height (PangoLayoutLine *line, - int *height) -{ - pango_layout_line_get_extents_and_height (line, NULL, NULL, height); -} - -static PangoLayoutLine * -pango_layout_line_new (PangoLayout *layout) +gboolean +pango_layout_get_single_paragraph (PangoLayout *layout) { - PangoLayoutLinePrivate *private = g_slice_new (PangoLayoutLinePrivate); - - private->ref_count = 1; - private->line.layout = layout; - private->line.runs = NULL; - private->line.length = 0; - private->cache_status = NOT_CACHED; - - /* Note that we leave start_index, resolved_dir, and is_paragraph_start - * uninitialized */ + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), FALSE); - return (PangoLayoutLine *) private; + return layout->single_paragraph; } /** - * pango_layout_line_get_pixel_extents: - * @layout_line: a `PangoLayoutLine` - * @ink_rect: (out) (optional): rectangle used to store the extents of - * the glyph string as drawn - * @logical_rect: (out) (optional): rectangle used to store the logical - * extents of the glyph string + * pango_layout_set_wrap: + * @layout: a `PangoLayout` + * @wrap: the wrap mode * - * Computes the logical and ink extents of @layout_line in device units. + * Sets the wrap mode. * - * This function just calls [method@Pango.LayoutLine.get_extents] followed by - * two [func@extents_to_pixels] calls, rounding @ink_rect and @logical_rect - * such that the rounded rectangles fully contain the unrounded one (that is, - * passes them as first argument to [func@extents_to_pixels]). - */ -void -pango_layout_line_get_pixel_extents (PangoLayoutLine *layout_line, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect) -{ - g_return_if_fail (LINE_IS_VALID (layout_line)); - - pango_layout_line_get_extents (layout_line, ink_rect, logical_rect); - pango_extents_to_pixels (ink_rect, NULL); - pango_extents_to_pixels (logical_rect, NULL); -} - -/* - * NB: This implement the exact same algorithm as - * reorder-items.c:pango_reorder_items(). - */ -static GSList * -reorder_runs_recurse (GSList *items, - int n_items) -{ - GSList *tmp_list, *level_start_node; - int i, level_start_i; - int min_level = G_MAXINT; - GSList *result = NULL; - - if (n_items == 0) - return NULL; - - tmp_list = items; - for (i=0; i<n_items; i++) - { - PangoLayoutRun *run = tmp_list->data; - - min_level = MIN (min_level, run->item->analysis.level); - - tmp_list = tmp_list->next; - } - - level_start_i = 0; - level_start_node = items; - tmp_list = items; - for (i=0; i<n_items; i++) - { - PangoLayoutRun *run = tmp_list->data; - - if (run->item->analysis.level == min_level) - { - if (min_level % 2) - { - if (i > level_start_i) - result = g_slist_concat (reorder_runs_recurse (level_start_node, i - level_start_i), result); - result = g_slist_prepend (result, run); - } - else - { - if (i > level_start_i) - result = g_slist_concat (result, reorder_runs_recurse (level_start_node, i - level_start_i)); - result = g_slist_append (result, run); - } - - level_start_i = i + 1; - level_start_node = tmp_list->next; - } - - tmp_list = tmp_list->next; - } - - if (min_level % 2) - { - if (i > level_start_i) - result = g_slist_concat (reorder_runs_recurse (level_start_node, i - level_start_i), result); - } - else - { - if (i > level_start_i) - result = g_slist_concat (result, reorder_runs_recurse (level_start_node, i - level_start_i)); - } - - return result; -} - -static void -pango_layout_line_reorder (PangoLayoutLine *line) -{ - GSList *logical_runs = line->runs; - GSList *tmp_list; - gboolean all_even, all_odd; - guint8 level_or = 0, level_and = 1; - int length = 0; - - /* Check if all items are in the same direction, in that case, the - * line does not need modification and we can avoid the expensive - * reorder runs recurse procedure. - */ - for (tmp_list = logical_runs; tmp_list != NULL; tmp_list = tmp_list->next) - { - PangoLayoutRun *run = tmp_list->data; - - level_or |= run->item->analysis.level; - level_and &= run->item->analysis.level; - - length++; - } - - /* If none of the levels had the LSB set, all numbers were even. */ - all_even = (level_or & 0x1) == 0; - - /* If all of the levels had the LSB set, all numbers were odd. */ - all_odd = (level_and & 0x1) == 1; - - if (!all_even && !all_odd) - { - line->runs = reorder_runs_recurse (logical_runs, length); - g_slist_free (logical_runs); - } - else if (all_odd) - line->runs = g_slist_reverse (logical_runs); -} - -static int -get_item_letter_spacing (PangoItem *item) -{ - ItemProperties properties; - - pango_item_get_properties (item, &properties); - - return properties.letter_spacing; -} - -static void -pad_glyphstring_right (PangoGlyphString *glyphs, - ParaBreakState *state, - int adjustment) -{ - int glyph = glyphs->num_glyphs - 1; - - while (glyph >= 0 && glyphs->glyphs[glyph].geometry.width == 0) - glyph--; - - if (glyph < 0) - return; - - state->remaining_width -= adjustment; - glyphs->glyphs[glyph].geometry.width += adjustment; - if (glyphs->glyphs[glyph].geometry.width < 0) - { - state->remaining_width += glyphs->glyphs[glyph].geometry.width; - glyphs->glyphs[glyph].geometry.width = 0; - } -} - -static void -pad_glyphstring_left (PangoGlyphString *glyphs, - ParaBreakState *state, - int adjustment) -{ - int glyph = 0; - - while (glyph < glyphs->num_glyphs && glyphs->glyphs[glyph].geometry.width == 0) - glyph++; - - if (glyph == glyphs->num_glyphs) - return; - - state->remaining_width -= adjustment; - glyphs->glyphs[glyph].geometry.width += adjustment; - glyphs->glyphs[glyph].geometry.x_offset += adjustment; -} - -static gboolean -is_tab_run (PangoLayout *layout, - PangoLayoutRun *run) -{ - return (layout->text[run->item->offset] == '\t'); -} - -static void -add_missing_hyphen (PangoLayoutLine *line, - ParaBreakState *state, - PangoLayoutRun *run) -{ - PangoLayout *layout = line->layout; - PangoItem *item = run->item; - int line_chars; - - line_chars = 0; - for (GSList *l = line->runs; l; l = l->next) - { - PangoLayoutRun *r = l->data; - - if (r) - line_chars += r->item->num_chars; - } - - if (layout->log_attrs[state->line_start_offset + line_chars].break_inserts_hyphen && - !(item->analysis.flags & PANGO_ANALYSIS_FLAG_NEED_HYPHEN)) - { - int width; - int start_offset; - - DEBUG1("add a missing hyphen"); - /* The last run fit onto the line without breaking it, but it still needs a hyphen */ - - width = pango_glyph_string_get_width (run->glyphs); - - /* Ugly, shape_run uses state->start_offset, so temporarily rewind things - * to the state before the run was inserted. Otherwise, we end up passing - * the wrong log attrs to the shaping machinery. - */ - start_offset = state->start_offset; - state->start_offset = state->line_start_offset + line_chars - item->num_chars; - - pango_glyph_string_free (run->glyphs); - item->analysis.flags |= PANGO_ANALYSIS_FLAG_NEED_HYPHEN; - run->glyphs = shape_run (line, state, item); - - state->start_offset = start_offset; - - state->remaining_width += pango_glyph_string_get_width (run->glyphs) - width; - } -} - -static void -zero_line_final_space (PangoLayoutLine *line, - ParaBreakState *state, - PangoLayoutRun *run) -{ - PangoLayout *layout = line->layout; - PangoItem *item = run->item; - PangoGlyphString *glyphs; - int glyph; - - glyphs = run->glyphs; - glyph = item->analysis.level % 2 ? 0 : glyphs->num_glyphs - 1; - - if (glyphs->glyphs[glyph].glyph == PANGO_GET_UNKNOWN_GLYPH (0x2028)) - { - DEBUG1 ("zero final space: visible space"); - return; /* this LS is visible */ - } - - /* if the final char of line forms a cluster, and it's - * a whitespace char, zero its glyph's width as it's been wrapped - */ - if (glyphs->num_glyphs < 1 || state->start_offset == 0 || - !layout->log_attrs[state->start_offset - 1].is_white) - { - DEBUG1 ("zero final space: not whitespace"); - return; - } - - if (glyphs->num_glyphs >= 2 && - glyphs->log_clusters[glyph] == glyphs->log_clusters[glyph + (item->analysis.level % 2 ? 1 : -1)]) - { - - DEBUG1 ("zero final space: its a cluster"); - return; - } - - DEBUG1 ("zero line final space: collapsing the space"); - glyphs->glyphs[glyph].geometry.width = 0; - glyphs->glyphs[glyph].glyph = PANGO_GLYPH_EMPTY; -} - -/* When doing shaping, we add the letter spacing value for a - * run after every grapheme in the run. This produces ugly - * asymmetrical results, so what this routine is redistributes - * that space to the beginning and the end of the run. + * The wrap mode only has effect if a width is set on the layout + * with [method@Pango.Layout.set_width]. To turn off wrapping, + * set the width to -1. * - * We also trim the letter spacing from runs adjacent to - * tabs and from the outside runs of the lines so that things - * line up properly. The line breaking and tab positioning - * were computed without this trimming so they are no longer - * exactly correct, but this won't be very noticeable in most - * cases. + * The default value is %PANGO_WRAP_WORD. */ -static void -adjust_line_letter_spacing (PangoLayoutLine *line, - ParaBreakState *state) -{ - PangoLayout *layout = line->layout; - gboolean reversed; - PangoLayoutRun *last_run; - int tab_adjustment; - GSList *l; - - /* If we have tab stops and the resolved direction of the - * line is RTL, then we need to walk through the line - * in reverse direction to figure out the corrections for - * tab stops. - */ - reversed = FALSE; - if (line->resolved_dir == PANGO_DIRECTION_RTL) - { - for (l = line->runs; l; l = l->next) - if (is_tab_run (layout, l->data)) - { - line->runs = g_slist_reverse (line->runs); - reversed = TRUE; - break; - } - } - - /* Walk over the runs in the line, redistributing letter - * spacing from the end of the run to the start of the - * run and trimming letter spacing from the ends of the - * runs adjacent to the ends of the line or tab stops. - * - * We accumulate a correction factor from this trimming - * which we add onto the next tab stop space to keep the - * things properly aligned. - */ - last_run = NULL; - tab_adjustment = 0; - for (l = line->runs; l; l = l->next) - { - PangoLayoutRun *run = l->data; - PangoLayoutRun *next_run = l->next ? l->next->data : NULL; - - if (is_tab_run (layout, run)) - { - pad_glyphstring_right (run->glyphs, state, tab_adjustment); - tab_adjustment = 0; - } - else - { - PangoLayoutRun *visual_next_run = reversed ? last_run : next_run; - PangoLayoutRun *visual_last_run = reversed ? next_run : last_run; - int run_spacing = get_item_letter_spacing (run->item); - int space_left, space_right; - - distribute_letter_spacing (run_spacing, &space_left, &space_right); - - if (run->glyphs->glyphs[0].geometry.width == 0) - { - /* we've zeroed this space glyph at the end of line, now remove - * the letter spacing added to its adjacent glyph */ - pad_glyphstring_left (run->glyphs, state, - space_left); - } - else if (!visual_last_run || is_tab_run (layout, visual_last_run)) - { - pad_glyphstring_left (run->glyphs, state, - space_left); - tab_adjustment += space_left; - } - - if (run->glyphs->glyphs[run->glyphs->num_glyphs - 1].geometry.width == 0) - { - /* we've zeroed this space glyph at the end of line, now remove - * the letter spacing added to its adjacent glyph */ - pad_glyphstring_right (run->glyphs, state, - space_right); - } - else if (!visual_next_run || is_tab_run (layout, visual_next_run)) - { - pad_glyphstring_right (run->glyphs, state, - space_right); - tab_adjustment += space_right; - } - } - - last_run = run; - } - - if (reversed) - line->runs = g_slist_reverse (line->runs); -} - -static void -justify_clusters (PangoLayoutLine *line, - ParaBreakState *state) +void +pango_layout_set_wrap (PangoLayout *layout, + PangoWrapMode wrap) { - const gchar *text = line->layout->text; - const PangoLogAttr *log_attrs = line->layout->log_attrs; - - int total_remaining_width, total_gaps = 0; - int added_so_far, gaps_so_far; - gboolean is_hinted; - GSList *run_iter; - enum { - MEASURE, - ADJUST - } mode; - - total_remaining_width = state->remaining_width; - if (total_remaining_width <= 0) - return; - - /* hint to full pixel if total remaining width was so */ - is_hinted = (total_remaining_width & (PANGO_SCALE - 1)) == 0; - - for (mode = MEASURE; mode <= ADJUST; mode++) - { - gboolean leftedge = TRUE; - PangoGlyphString *rightmost_glyphs = NULL; - int rightmost_space = 0; - int residual = 0; - - added_so_far = 0; - gaps_so_far = 0; - - for (run_iter = line->runs; run_iter; run_iter = run_iter->next) - { - PangoLayoutRun *run = run_iter->data; - PangoGlyphString *glyphs = run->glyphs; - PangoGlyphItemIter cluster_iter; - gboolean have_cluster; - int dir; - int offset; - - dir = run->item->analysis.level % 2 == 0 ? +1 : -1; - - /* Note: we simply assert here, since our items are all internally - * created. If that ever changes, we need to add a fallback here. - */ - g_assert (run->item->analysis.flags & PANGO_ANALYSIS_FLAG_HAS_CHAR_OFFSET); - offset = ((PangoItemPrivate *)run->item)->char_offset; - - for (have_cluster = dir > 0 ? - pango_glyph_item_iter_init_start (&cluster_iter, run, text) : - pango_glyph_item_iter_init_end (&cluster_iter, run, text); - have_cluster; - have_cluster = dir > 0 ? - pango_glyph_item_iter_next_cluster (&cluster_iter) : - pango_glyph_item_iter_prev_cluster (&cluster_iter)) - { - int i; - int width = 0; - - /* don't expand in the middle of graphemes */ - if (!log_attrs[offset + cluster_iter.start_char].is_cursor_position) - continue; - - for (i = cluster_iter.start_glyph; i != cluster_iter.end_glyph; i += dir) - width += glyphs->glyphs[i].geometry.width; - - /* also don't expand zero-width clusters. */ - if (width == 0) - continue; - - gaps_so_far++; - - if (mode == ADJUST) - { - int leftmost, rightmost; - int adjustment, space_left, space_right; - - adjustment = total_remaining_width / total_gaps + residual; - if (is_hinted) - { - int old_adjustment = adjustment; - adjustment = PANGO_UNITS_ROUND (adjustment); - residual = old_adjustment - adjustment; - } - /* distribute to before/after */ - distribute_letter_spacing (adjustment, &space_left, &space_right); - - if (cluster_iter.start_glyph < cluster_iter.end_glyph) - { - /* LTR */ - leftmost = cluster_iter.start_glyph; - rightmost = cluster_iter.end_glyph - 1; - } - else - { - /* RTL */ - leftmost = cluster_iter.end_glyph + 1; - rightmost = cluster_iter.start_glyph; - } - /* Don't add to left-side of left-most glyph of left-most non-zero run. */ - if (leftedge) - leftedge = FALSE; - else - { - glyphs->glyphs[leftmost].geometry.width += space_left ; - glyphs->glyphs[leftmost].geometry.x_offset += space_left ; - added_so_far += space_left; - } - /* Don't add to right-side of right-most glyph of right-most non-zero run. */ - { - /* Save so we can undo later. */ - rightmost_glyphs = glyphs; - rightmost_space = space_right; - - glyphs->glyphs[rightmost].geometry.width += space_right; - added_so_far += space_right; - } - } - } - } - - if (mode == MEASURE) - { - total_gaps = gaps_so_far - 1; - - if (total_gaps == 0) - { - /* a single cluster, can't really justify it */ - return; - } - } - else /* mode == ADJUST */ - { - if (rightmost_glyphs) - { - rightmost_glyphs->glyphs[rightmost_glyphs->num_glyphs - 1].geometry.width -= rightmost_space; - added_so_far -= rightmost_space; - } - } - } - - state->remaining_width -= added_so_far; -} + g_return_if_fail (PANGO_IS_LAYOUT (layout)); -static void -justify_words (PangoLayoutLine *line, - ParaBreakState *state) -{ - const gchar *text = line->layout->text; - const PangoLogAttr *log_attrs = line->layout->log_attrs; - - int total_remaining_width, total_space_width = 0; - int added_so_far, spaces_so_far; - gboolean is_hinted; - GSList *run_iter; - enum { - MEASURE, - ADJUST - } mode; - - total_remaining_width = state->remaining_width; - if (total_remaining_width <= 0) + if (layout->wrap == wrap) return; - /* hint to full pixel if total remaining width was so */ - is_hinted = (total_remaining_width & (PANGO_SCALE - 1)) == 0; - - for (mode = MEASURE; mode <= ADJUST; mode++) - { - added_so_far = 0; - spaces_so_far = 0; - - for (run_iter = line->runs; run_iter; run_iter = run_iter->next) - { - PangoLayoutRun *run = run_iter->data; - PangoGlyphString *glyphs = run->glyphs; - PangoGlyphItemIter cluster_iter; - gboolean have_cluster; - int offset; - - /* Note: we simply assert here, since our items are all internally - * created. If that ever changes, we need to add a fallback here. - */ - g_assert (run->item->analysis.flags & PANGO_ANALYSIS_FLAG_HAS_CHAR_OFFSET); - offset = ((PangoItemPrivate *)run->item)->char_offset; - - for (have_cluster = pango_glyph_item_iter_init_start (&cluster_iter, run, text); - have_cluster; - have_cluster = pango_glyph_item_iter_next_cluster (&cluster_iter)) - { - int i; - int dir; - - if (!log_attrs[offset + cluster_iter.start_char].is_expandable_space) - continue; - - dir = (cluster_iter.start_glyph < cluster_iter.end_glyph) ? 1 : -1; - for (i = cluster_iter.start_glyph; i != cluster_iter.end_glyph; i += dir) - { - int glyph_width = glyphs->glyphs[i].geometry.width; - - if (glyph_width == 0) - continue; - - spaces_so_far += glyph_width; - - if (mode == ADJUST) - { - int adjustment; - - adjustment = ((guint64) spaces_so_far * total_remaining_width) / total_space_width - added_so_far; - if (is_hinted) - adjustment = PANGO_UNITS_ROUND (adjustment); - - glyphs->glyphs[i].geometry.width += adjustment; - added_so_far += adjustment; - } - } - } - } - - if (mode == MEASURE) - { - total_space_width = spaces_so_far; - - if (total_space_width == 0) - { - justify_clusters (line, state); - return; - } - } - } - - state->remaining_width -= added_so_far; -} - -typedef struct { - PangoAttribute *attr; - int x_offset; - int y_offset; -} BaselineItem; - -static void -collect_baseline_shift (ParaBreakState *state, - PangoItem *item, - PangoItem *prev, - int *start_x_offset, - int *start_y_offset, - int *end_x_offset, - int *end_y_offset) -{ - *start_x_offset = 0; - *start_y_offset = 0; - *end_x_offset = 0; - *end_y_offset = 0; - - for (GSList *l = item->analysis.extra_attrs; l; l = l->next) - { - PangoAttribute *attr = l->data; - - if (attr->klass->type == PANGO_ATTR_RISE) - { - int value = ((PangoAttrInt *)attr)->value; - - *start_y_offset += value; - *end_y_offset -= value; - } - else if (attr->klass->type == PANGO_ATTR_BASELINE_SHIFT) - { - if (attr->start_index == item->offset) - { - BaselineItem *entry; - int value; - - entry = g_new0 (BaselineItem, 1); - entry->attr = attr; - state->baseline_shifts = g_list_prepend (state->baseline_shifts, entry); - - value = ((PangoAttrInt *)attr)->value; - - if (value > 1024 || value < -1024) - { - entry->y_offset = value; - /* FIXME: compute an x_offset from value to italic angle */ - } - else - { - int superscript_x_offset = 0; - int superscript_y_offset = 0; - int subscript_x_offset = 0; - int subscript_y_offset = 0; - - - if (prev) - { - hb_font_t *hb_font = pango_font_get_hb_font (prev->analysis.font); - hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_SUPERSCRIPT_EM_Y_OFFSET, &superscript_y_offset); - hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_SUPERSCRIPT_EM_X_OFFSET, &superscript_x_offset); - hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_SUBSCRIPT_EM_Y_OFFSET, &subscript_y_offset); - hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_SUBSCRIPT_EM_X_OFFSET, &subscript_x_offset); - } - - if (superscript_y_offset == 0) - superscript_y_offset = 5000; - if (subscript_y_offset == 0) - subscript_y_offset = 5000; - - switch (value) - { - case PANGO_BASELINE_SHIFT_NONE: - entry->x_offset = 0; - entry->y_offset = 0; - break; - case PANGO_BASELINE_SHIFT_SUPERSCRIPT: - entry->x_offset = superscript_x_offset; - entry->y_offset = superscript_y_offset; - break; - case PANGO_BASELINE_SHIFT_SUBSCRIPT: - entry->x_offset = subscript_x_offset; - entry->y_offset = -subscript_y_offset; - break; - default: - g_assert_not_reached (); - } - } - - *start_x_offset += entry->x_offset; - *start_y_offset += entry->y_offset; - } - - if (attr->end_index == item->offset + item->length) - { - GList *t; - - for (t = state->baseline_shifts; t; t = t->next) - { - BaselineItem *entry = t->data; - - if (attr->start_index == entry->attr->start_index && - attr->end_index == entry->attr->end_index && - ((PangoAttrInt *)attr)->value == ((PangoAttrInt *)entry->attr)->value) - { - *end_x_offset -= entry->x_offset; - *end_y_offset -= entry->y_offset; - } - - state->baseline_shifts = g_list_remove (state->baseline_shifts, entry); - g_free (entry); - break; - } - if (t == NULL) - g_warning ("Baseline attributes mismatch\n"); - } - } - } -} - -static void -apply_baseline_shift (PangoLayoutLine *line, - ParaBreakState *state) -{ - int y_offset = 0; - PangoItem *prev = NULL; - - for (GSList *l = line->runs; l; l = l->next) - { - PangoLayoutRun *run = l->data; - PangoItem *item = run->item; - int start_x_offset, end_x_offset; - int start_y_offset, end_y_offset; - - collect_baseline_shift (state, item, prev, &start_x_offset, &start_y_offset, &end_x_offset, &end_y_offset); - - y_offset += start_y_offset; - - run->y_offset = y_offset; - run->start_x_offset = start_x_offset; - run->end_x_offset = end_x_offset; - - y_offset += end_y_offset; - - prev = item; - } -} - -static void -pango_layout_line_postprocess (PangoLayoutLine *line, - ParaBreakState *state, - gboolean wrapped) -{ - gboolean ellipsized = FALSE; - - DEBUG1 ("postprocessing line, %s", wrapped ? "wrapped" : "not wrapped"); - - add_missing_hyphen (line, state, line->runs->data); - DEBUG ("after hyphen addition", line, state); - - /* Truncate the logical-final whitespace in the line if we broke the line at it */ - if (wrapped) - zero_line_final_space (line, state, line->runs->data); - - DEBUG ("after removing final space", line, state); - - /* Reverse the runs */ - line->runs = g_slist_reverse (line->runs); - - apply_baseline_shift (line, state); - - /* Ellipsize the line if necessary */ - if (G_UNLIKELY (state->line_width >= 0 && - should_ellipsize_current_line (line->layout, state))) - { - PangoShapeFlags shape_flags = PANGO_SHAPE_NONE; - - if (pango_context_get_round_glyph_positions (line->layout->context)) - shape_flags |= PANGO_SHAPE_ROUND_POSITIONS; - - ellipsized = _pango_layout_line_ellipsize (line, state->attrs, shape_flags, state->line_width); - } - - /* Now convert logical to visual order */ - pango_layout_line_reorder (line); - - DEBUG ("after reordering", line, state); - - /* Fixup letter spacing between runs */ - adjust_line_letter_spacing (line, state); + layout->wrap = wrap; - DEBUG ("after letter spacing", line, state); - - /* Distribute extra space between words if justifying and line was wrapped */ - if (line->layout->justify && (wrapped || ellipsized || line->layout->justify_last_line)) - { - /* if we ellipsized, we don't have remaining_width set */ - if (state->remaining_width < 0) - state->remaining_width = state->line_width - pango_layout_line_get_width (line); - - justify_words (line, state); - } - - DEBUG ("after justification", line, state); - - line->layout->is_wrapped |= wrapped; - line->layout->is_ellipsized |= ellipsized; -} - -static int -next_cluster_start (PangoGlyphString *gs, - int cluster_start) -{ - int i; - - i = cluster_start + 1; - while (i < gs->num_glyphs) - { - if (gs->glyphs[i].attr.is_cluster_start) - return i; - - i++; - } - - return gs->num_glyphs; -} - -static int -cluster_width (PangoGlyphString *gs, - int cluster_start) -{ - int i; - int width; - - width = gs->glyphs[cluster_start].geometry.width; - i = cluster_start + 1; - while (i < gs->num_glyphs) - { - if (gs->glyphs[i].attr.is_cluster_start) - break; - - width += gs->glyphs[i].geometry.width; - i++; - } - - return width; -} - -static inline void -offset_y (PangoLayoutIter *iter, - int *y) -{ - *y += iter->line_extents[iter->line_index].baseline; -} - -/* Sets up the iter for the start of a new cluster. cluster_start_index - * is the byte index of the cluster start relative to the run. - */ -static void -update_cluster (PangoLayoutIter *iter, - int cluster_start_index) -{ - char *cluster_text; - PangoGlyphString *gs; - int cluster_length; - - iter->character_position = 0; - - gs = iter->run->glyphs; - iter->cluster_width = cluster_width (gs, iter->cluster_start); - iter->next_cluster_glyph = next_cluster_start (gs, iter->cluster_start); - - if (iter->ltr) - { - /* For LTR text, finding the length of the cluster is easy - * since logical and visual runs are in the same direction. - */ - if (iter->next_cluster_glyph < gs->num_glyphs) - cluster_length = gs->log_clusters[iter->next_cluster_glyph] - cluster_start_index; - else - cluster_length = iter->run->item->length - cluster_start_index; - } - else - { - /* For RTL text, we have to scan backwards to find the previous - * visual cluster which is the next logical cluster. - */ - int i = iter->cluster_start; - while (i > 0 && gs->log_clusters[i - 1] == cluster_start_index) - i--; - - if (i == 0) - cluster_length = iter->run->item->length - cluster_start_index; - else - cluster_length = gs->log_clusters[i - 1] - cluster_start_index; - } - - cluster_text = iter->layout->text + iter->run->item->offset + cluster_start_index; - iter->cluster_num_chars = pango_utf8_strlen (cluster_text, cluster_length); - - if (iter->ltr) - iter->index = cluster_text - iter->layout->text; - else - iter->index = g_utf8_prev_char (cluster_text + cluster_length) - iter->layout->text; -} - -static void -update_run (PangoLayoutIter *iter, - int run_start_index) -{ - const Extents *line_ext = &iter->line_extents[iter->line_index]; - - /* Note that in iter_new() the iter->run_width - * is garbage but we don't use it since we're on the first run of - * a line. - */ - if (iter->run_list_link == iter->line->runs) - iter->run_x = line_ext->logical_rect.x; - else - { - iter->run_x += iter->end_x_offset + iter->run_width; - if (iter->run) - iter->run_x += iter->run->start_x_offset; - } - - if (iter->run) - { - iter->run_width = pango_glyph_string_get_width (iter->run->glyphs); - iter->end_x_offset = iter->run->end_x_offset; - } - else - { - /* The empty run at the end of a line */ - iter->run_width = 0; - iter->end_x_offset = 0; - } - - if (iter->run) - iter->ltr = (iter->run->item->analysis.level % 2) == 0; - else - iter->ltr = TRUE; - - iter->cluster_start = 0; - iter->cluster_x = iter->run_x; - - if (iter->run) - { - update_cluster (iter, iter->run->glyphs->log_clusters[0]); - } - else - { - iter->cluster_width = 0; - iter->character_position = 0; - iter->cluster_num_chars = 0; - iter->index = run_start_index; - } + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_WRAP]); + layout_changed (layout); } /** - * pango_layout_iter_copy: - * @iter: (nullable): a `PangoLayoutIter` - * - * Copies a `PangoLayoutIter`. + * pango_layout_get_wrap: + * @layout: a `PangoLayout` * - * Return value: (nullable): the newly allocated `PangoLayoutIter` + * Gets the wrap mode for the layout. * - * Since: 1.20 + * Return value: active wrap mode. */ -PangoLayoutIter * -pango_layout_iter_copy (PangoLayoutIter *iter) +PangoWrapMode +pango_layout_get_wrap (PangoLayout *layout) { - PangoLayoutIter *new; - - if (iter == NULL) - return NULL; - - new = g_slice_new (PangoLayoutIter); - - new->layout = g_object_ref (iter->layout); - new->line_list_link = iter->line_list_link; - new->line = iter->line; - pango_layout_line_ref (new->line); - - new->run_list_link = iter->run_list_link; - new->run = iter->run; - new->index = iter->index; - - new->line_extents = NULL; - if (iter->line_extents != NULL) - { - new->line_extents = g_memdup2 (iter->line_extents, - iter->layout->line_count * sizeof (Extents)); - - } - new->line_index = iter->line_index; + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), PANGO_WRAP_WORD); - new->run_x = iter->run_x; - new->run_width = iter->run_width; - new->ltr = iter->ltr; - - new->cluster_x = iter->cluster_x; - new->cluster_width = iter->cluster_width; - - new->cluster_start = iter->cluster_start; - new->next_cluster_glyph = iter->next_cluster_glyph; - - new->cluster_num_chars = iter->cluster_num_chars; - new->character_position = iter->character_position; - - new->layout_width = iter->layout_width; - - return new; + return layout->wrap; } -G_DEFINE_BOXED_TYPE (PangoLayoutIter, pango_layout_iter, - pango_layout_iter_copy, - pango_layout_iter_free); - /** - * pango_layout_get_iter: + * pango_layout_set_indent: * @layout: a `PangoLayout` + * @indent: the amount by which to indent * - * Returns an iterator to iterate over the visual extents of the layout. + * Sets the width in Pango units to indent each paragraph. * - * Return value: the new `PangoLayoutIter` + * A negative value of @indent will produce a hanging indentation. + * That is, the first line will have the full width, and subsequent + * lines will be indented by the absolute value of @indent. + * + * The default value is 0. */ -PangoLayoutIter* -pango_layout_get_iter (PangoLayout *layout) -{ - PangoLayoutIter *iter; - - g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); - - iter = g_slice_new (PangoLayoutIter); - - _pango_layout_get_iter (layout, iter); - - return iter; -} - void -_pango_layout_get_iter (PangoLayout *layout, - PangoLayoutIter*iter) +pango_layout_set_indent (PangoLayout *layout, + int indent) { - int run_start_index; - g_return_if_fail (PANGO_IS_LAYOUT (layout)); - iter->layout = g_object_ref (layout); - - pango_layout_check_lines (layout); - - iter->line_list_link = layout->lines; - iter->line = iter->line_list_link->data; - pango_layout_line_ref (iter->line); - - run_start_index = iter->line->start_index; - iter->run_list_link = iter->line->runs; - - if (iter->run_list_link) - { - iter->run = iter->run_list_link->data; - run_start_index = iter->run->item->offset; - } - else - iter->run = NULL; - - iter->line_extents = NULL; - - if (layout->width == -1) - { - PangoRectangle logical_rect; - - pango_layout_get_extents_internal (layout, - NULL, - &logical_rect, - &iter->line_extents); - iter->layout_width = logical_rect.width; - } - else - { - pango_layout_get_extents_internal (layout, - NULL, - NULL, - &iter->line_extents); - iter->layout_width = layout->width; - } - iter->line_index = 0; - - update_run (iter, run_start_index); -} - -void -_pango_layout_iter_destroy (PangoLayoutIter *iter) -{ - if (iter == NULL) + if (layout->indent == indent) return; - g_free (iter->line_extents); - pango_layout_line_unref (iter->line); - g_object_unref (iter->layout); -} + layout->indent = indent; -/** - * pango_layout_iter_free: - * @iter: (nullable): a `PangoLayoutIter`, may be %NULL - * - * Frees an iterator that's no longer in use. - **/ -void -pango_layout_iter_free (PangoLayoutIter *iter) -{ - if (iter == NULL) - return; - - _pango_layout_iter_destroy (iter); - g_slice_free (PangoLayoutIter, iter); + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_INDENT]); + layout_changed (layout); } /** - * pango_layout_iter_get_index: - * @iter: a `PangoLayoutIter` + * pango_layout_get_indent: + * @layout: a `PangoLayout` * - * Gets the current byte index. + * Gets the paragraph indent width in Pango units. * - * Note that iterating forward by char moves in visual order, - * not logical order, so indexes may not be sequential. Also, - * the index may be equal to the length of the text in the - * layout, if on the %NULL run (see [method@Pango.LayoutIter.get_run]). + * A negative value indicates a hanging indentation. * - * Return value: current byte index + * Return value: the indent in Pango units */ int -pango_layout_iter_get_index (PangoLayoutIter *iter) -{ - if (ITER_IS_INVALID (iter)) - return 0; - - return iter->index; -} - -/** - * pango_layout_iter_get_run: - * @iter: a `PangoLayoutIter` - * - * Gets the current run. - * - * When iterating by run, at the end of each line, there's a position - * with a %NULL run, so this function can return %NULL. The %NULL run - * at the end of each line ensures that all lines have at least one run, - * even lines consisting of only a newline. - * - * Use the faster [method@Pango.LayoutIter.get_run_readonly] if you do not - * plan to modify the contents of the run (glyphs, glyph widths, etc.). - * - * Return value: (transfer none) (nullable): the current run - */ -PangoLayoutRun* -pango_layout_iter_get_run (PangoLayoutIter *iter) -{ - if (ITER_IS_INVALID (iter)) - return NULL; - - pango_layout_line_leaked (iter->line); - - return iter->run; -} - -/** - * pango_layout_iter_get_run_readonly: - * @iter: a `PangoLayoutIter` - * - * Gets the current run for read-only access. - * - * When iterating by run, at the end of each line, there's a position - * with a %NULL run, so this function can return %NULL. The %NULL run - * at the end of each line ensures that all lines have at least one run, - * even lines consisting of only a newline. - * - * This is a faster alternative to [method@Pango.LayoutIter.get_run], - * but the user is not expected to modify the contents of the run (glyphs, - * glyph widths, etc.). - * - * Return value: (transfer none) (nullable): the current run, that - * should not be modified - * - * Since: 1.16 - */ -PangoLayoutRun* -pango_layout_iter_get_run_readonly (PangoLayoutIter *iter) -{ - if (ITER_IS_INVALID (iter)) - return NULL; - - pango_layout_line_leaked (iter->line); - - return iter->run; -} - -/* an inline-able version for local use */ -static PangoLayoutLine* -_pango_layout_iter_get_line (PangoLayoutIter *iter) +pango_layout_get_indent (PangoLayout *layout) { - return iter->line; -} + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), 0); -static PangoLayoutRun * -_pango_layout_iter_get_run (PangoLayoutIter *iter) -{ - return iter->run; + return layout->indent; } /** - * pango_layout_iter_get_line: - * @iter: a `PangoLayoutIter` - * - * Gets the current line. + * pango_layout_set_alignment: + * @layout: a `PangoLayout` + * @alignment: the alignment * - * Use the faster [method@Pango.LayoutIter.get_line_readonly] if - * you do not plan to modify the contents of the line (glyphs, - * glyph widths, etc.). + * Sets the alignment for the layout: how short lines are + * positioned within the horizontal space available. * - * Return value: (transfer none): the current line + * The default alignment is `PANGO_ALIGN_LEFT`. */ -PangoLayoutLine* -pango_layout_iter_get_line (PangoLayoutIter *iter) +void +pango_layout_set_alignment (PangoLayout *layout, + PangoAlignment alignment) { - if (ITER_IS_INVALID (iter)) - return NULL; - - pango_layout_line_leaked (iter->line); + g_return_if_fail (PANGO_IS_LAYOUT (layout)); - return iter->line; -} + if (layout->alignment == alignment) + return; -/** - * pango_layout_iter_get_line_readonly: - * @iter: a `PangoLayoutIter` - * - * Gets the current line for read-only access. - * - * This is a faster alternative to [method@Pango.LayoutIter.get_line], - * but the user is not expected to modify the contents of the line - * (glyphs, glyph widths, etc.). - * - * Return value: (transfer none): the current line, that should not be - * modified - * - * Since: 1.16 - */ -PangoLayoutLine* -pango_layout_iter_get_line_readonly (PangoLayoutIter *iter) -{ - if (ITER_IS_INVALID (iter)) - return NULL; + layout->alignment = alignment; - return iter->line; + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_ALIGNMENT]); + layout_changed (layout); } /** - * pango_layout_iter_at_last_line: - * @iter: a `PangoLayoutIter` + * pango_layout_get_alignment: + * @layout: a `PangoLayout` * - * Determines whether @iter is on the last line of the layout. + * Gets the alignment for the layout: how short lines are + * positioned within the horizontal space available. * - * Return value: %TRUE if @iter is on the last line + * Return value: the alignment */ -gboolean -pango_layout_iter_at_last_line (PangoLayoutIter *iter) +PangoAlignment +pango_layout_get_alignment (PangoLayout *layout) { - if (ITER_IS_INVALID (iter)) - return FALSE; + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), PANGO_ALIGN_LEFT); - return iter->line_index == iter->layout->line_count - 1; + return layout->alignment; } /** - * pango_layout_iter_get_layout: - * @iter: a `PangoLayoutIter` + * pango_layout_set_ellipsize: + * @layout: a `PangoLayout` + * @ellipsize: the new ellipsization mode for @layout * - * Gets the layout associated with a `PangoLayoutIter`. + * Sets the type of ellipsization being performed for @layout. * - * Return value: (transfer none): the layout associated with @iter + * Depending on the ellipsization mode @ellipsize text is removed + * from the start, middle, or end of text so they fit within the + * width of layout set with [method@Pango.Layout.set_width]. * - * Since: 1.20 + * The default value is `PANGO_ELLIPSIZE_NONE`. */ -PangoLayout* -pango_layout_iter_get_layout (PangoLayoutIter *iter) -{ - /* check is redundant as it simply checks that iter->layout is not NULL */ - if (ITER_IS_INVALID (iter)) - return NULL; - - return iter->layout; -} - -static gboolean -line_is_terminated (PangoLayoutIter *iter) -{ - /* There is a real terminator at the end of each paragraph other - * than the last. - */ - if (iter->line_list_link->next) - { - PangoLayoutLine *next_line = iter->line_list_link->next->data; - if (next_line->is_paragraph_start) - return TRUE; - } - - return FALSE; -} - -/* Moves to the next non-empty line. If @include_terminators - * is set, a line with just an explicit paragraph separator - * is considered non-empty. - */ -static gboolean -next_nonempty_line (PangoLayoutIter *iter, - gboolean include_terminators) -{ - gboolean result; - - while (TRUE) - { - result = pango_layout_iter_next_line (iter); - if (!result) - break; - - if (iter->line->runs) - break; - - if (include_terminators && line_is_terminated (iter)) - break; - } - - return result; -} - -/* Moves to the next non-empty run. If @include_terminators - * is set, the trailing run at the end of a line with an explicit - * paragraph separator is considered non-empty. - */ -static gboolean -next_nonempty_run (PangoLayoutIter *iter, - gboolean include_terminators) -{ - gboolean result; - - while (TRUE) - { - result = pango_layout_iter_next_run (iter); - if (!result) - break; - - if (iter->run) - break; - - if (include_terminators && line_is_terminated (iter)) - break; - } - - return result; -} - -/* Like pango_layout_next_cluster(), but if @include_terminators - * is set, includes the fake runs/clusters for empty lines. - * (But not positions introduced by line wrapping). - */ -static gboolean -next_cluster_internal (PangoLayoutIter *iter, - gboolean include_terminators) +void +pango_layout_set_ellipsize (PangoLayout *layout, + PangoEllipsizeMode ellipsize) { - PangoGlyphString *gs; - int next_start; - - if (ITER_IS_INVALID (iter)) - return FALSE; - - if (iter->run == NULL) - return next_nonempty_line (iter, include_terminators); + g_return_if_fail (PANGO_IS_LAYOUT (layout)); - gs = iter->run->glyphs; + if (layout->ellipsize == ellipsize) + return; - next_start = iter->next_cluster_glyph; - if (next_start == gs->num_glyphs) - { - return next_nonempty_run (iter, include_terminators); - } - else - { - iter->cluster_start = next_start; - iter->cluster_x += iter->cluster_width; - update_cluster(iter, gs->log_clusters[iter->cluster_start]); + layout->ellipsize = ellipsize; - return TRUE; - } + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_ELLIPSIZE]); + layout_changed (layout); } /** - * pango_layout_iter_next_char: - * @iter: a `PangoLayoutIter` + * pango_layout_get_ellipsize: + * @layout: a `PangoLayout` * - * Moves @iter forward to the next character in visual order. + * Gets the type of ellipsization being performed for @layout. * - * If @iter was already at the end of the layout, returns %FALSE. + * See [method@Pango.Layout.set_ellipsize]. * - * Return value: whether motion was possible + * Return value: the current ellipsization mode for @layout */ -gboolean -pango_layout_iter_next_char (PangoLayoutIter *iter) +PangoEllipsizeMode +pango_layout_get_ellipsize (PangoLayout *layout) { - const char *text; - - if (ITER_IS_INVALID (iter)) - return FALSE; - - if (iter->run == NULL) - { - /* We need to fake an iterator position in the middle of a \r\n line terminator */ - if (line_is_terminated (iter) && - strncmp (iter->layout->text + iter->line->start_index + iter->line->length, "\r\n", 2) == 0 && - iter->character_position == 0) - { - iter->character_position++; - return TRUE; - } - - return next_nonempty_line (iter, TRUE); - } - - iter->character_position++; - if (iter->character_position >= iter->cluster_num_chars) - return next_cluster_internal (iter, TRUE); - - text = iter->layout->text; - if (iter->ltr) - iter->index = g_utf8_next_char (text + iter->index) - text; - else - iter->index = g_utf8_prev_char (text + iter->index) - text; + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), PANGO_ELLIPSIZE_NONE); - return TRUE; + return layout->ellipsize; } /** - * pango_layout_iter_next_cluster: - * @iter: a `PangoLayoutIter` - * - * Moves @iter forward to the next cluster in visual order. - * - * If @iter was already at the end of the layout, returns %FALSE. + * pango_layout_set_auto_dir: + * @layout: a `PangoLayout` + * @auto_dir: if %TRUE, compute the bidirectional base direction + * from the layout's contents * - * Return value: whether motion was possible - */ -gboolean -pango_layout_iter_next_cluster (PangoLayoutIter *iter) -{ - return next_cluster_internal (iter, FALSE); -} - -/** - * pango_layout_iter_next_run: - * @iter: a `PangoLayoutIter` + * Sets whether to calculate the base direction + * for the layout according to its contents. * - * Moves @iter forward to the next run in visual order. + * When this flag is on (the default), then paragraphs in + * @layout that begin with strong right-to-left characters + * (Arabic and Hebrew principally), will have right-to-left + * layout, paragraphs with letters from other scripts will + * have left-to-right layout. Paragraphs with only neutral + * characters get their direction from the surrounding + * paragraphs. * - * If @iter was already at the end of the layout, returns %FALSE. + * When %FALSE, the choice between left-to-right and right-to-left + * layout is done according to the base direction of the layout's + * `PangoContext`. (See [method@Pango.Context.set_base_dir]). * - * Return value: whether motion was possible + * When the auto-computed direction of a paragraph differs + * from the base direction of the context, the interpretation + * of %PANGO_ALIGN_LEFT and %PANGO_ALIGN_RIGHT are swapped. */ -gboolean -pango_layout_iter_next_run (PangoLayoutIter *iter) +void +pango_layout_set_auto_dir (PangoLayout *layout, + gboolean auto_dir) { - int next_run_start; /* byte index */ - GSList *next_link; - - if (ITER_IS_INVALID (iter)) - return FALSE; - - if (iter->run == NULL) - return pango_layout_iter_next_line (iter); - - next_link = iter->run_list_link->next; + g_return_if_fail (PANGO_IS_LAYOUT (layout)); - if (next_link == NULL) - { - /* Moving on to the zero-width "virtual run" at the end of each - * line - */ - next_run_start = iter->run->item->offset + iter->run->item->length; - iter->run = NULL; - iter->run_list_link = NULL; - } - else - { - iter->run_list_link = next_link; - iter->run = iter->run_list_link->data; - next_run_start = iter->run->item->offset; - } + if (auto_dir == layout->auto_dir) + return; - update_run (iter, next_run_start); + layout->auto_dir = auto_dir; - return TRUE; + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_AUTO_DIR]); + layout_changed (layout); } /** - * pango_layout_iter_next_line: - * @iter: a `PangoLayoutIter` + * pango_layout_get_auto_dir: + * @layout: a `PangoLayout` * - * Moves @iter forward to the start of the next line. + * Gets whether to calculate the base direction for the layout + * according to its contents. * - * If @iter is already on the last line, returns %FALSE. + * See [method@Pango.Layout.set_auto_dir]. * - * Return value: whether motion was possible + * Return value: %TRUE if the bidirectional base direction + * is computed from the layout's contents, %FALSE otherwise */ gboolean -pango_layout_iter_next_line (PangoLayoutIter *iter) +pango_layout_get_auto_dir (PangoLayout *layout) { - GSList *next_link; - - if (ITER_IS_INVALID (iter)) - return FALSE; - - next_link = iter->line_list_link->next; - - if (next_link == NULL) - return FALSE; - - iter->line_list_link = next_link; - - pango_layout_line_unref (iter->line); - - iter->line = iter->line_list_link->data; - - pango_layout_line_ref (iter->line); - - iter->run_list_link = iter->line->runs; - - if (iter->run_list_link) - iter->run = iter->run_list_link->data; - else - iter->run = NULL; - - iter->line_index ++; - - update_run (iter, iter->line->start_index); + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), TRUE); - return TRUE; + return layout->auto_dir; } +/* }}} */ +/* {{{ Miscellaneous */ + /** - * pango_layout_iter_get_char_extents: - * @iter: a `PangoLayoutIter` - * @logical_rect: (out caller-allocates): rectangle to fill with - * logical extents + * pango_layout_set_markup: + * @layout: a `PangoLayout` + * @markup: marked-up text + * @length: length of @markup in bytes, or -1 if it is `NUL`-terminated * - * Gets the extents of the current character, in layout coordinates. + * Sets the layout text and attribute list from marked-up text. * - * Layout coordinates have the origin at the top left of the entire layout. + * See [Pango Markup](pango_markup.html)). * - * Only logical extents can sensibly be obtained for characters; - * ink extents make sense only down to the level of clusters. + * Replaces the current text and attribute list. */ void -pango_layout_iter_get_char_extents (PangoLayoutIter *iter, - PangoRectangle *logical_rect) +pango_layout_set_markup (PangoLayout *layout, + const char *markup, + int length) { - PangoRectangle cluster_rect; - int x0, x1; - - if (ITER_IS_INVALID (iter)) - return; - - if (logical_rect == NULL) - return; - - pango_layout_iter_get_cluster_extents (iter, NULL, &cluster_rect); - - if (iter->run == NULL) - { - /* When on the NULL run, cluster, char, and run all have the - * same extents - */ - *logical_rect = cluster_rect; - return; - } - - if (iter->cluster_num_chars) - { - x0 = (iter->character_position * cluster_rect.width) / iter->cluster_num_chars; - x1 = ((iter->character_position + 1) * cluster_rect.width) / iter->cluster_num_chars; - } - else - { - x0 = x1 = 0; - } - - logical_rect->width = x1 - x0; - logical_rect->height = cluster_rect.height; - logical_rect->y = cluster_rect.y; - logical_rect->x = cluster_rect.x + x0; -} + PangoAttrList *attrs; + char *text; + GError *error = NULL; -/** - * pango_layout_iter_get_cluster_extents: - * @iter: a `PangoLayoutIter` - * @ink_rect: (out) (optional): rectangle to fill with ink extents - * @logical_rect: (out) (optional): rectangle to fill with logical extents - * - * Gets the extents of the current cluster, in layout coordinates. - * - * Layout coordinates have the origin at the top left of the entire layout. - */ -void -pango_layout_iter_get_cluster_extents (PangoLayoutIter *iter, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect) -{ - if (ITER_IS_INVALID (iter)) - return; + g_return_if_fail (PANGO_IS_LAYOUT (layout)); + g_return_if_fail (markup != NULL); - if (iter->run == NULL) + if (!pango_parse_markup (markup, length, 0, &attrs, &text, NULL, &error)) { - /* When on the NULL run, cluster, char, and run all have the - * same extents - */ - pango_layout_iter_get_run_extents (iter, ink_rect, logical_rect); + g_warning ("pango_layout_set_markup_with_accel: %s", error->message); + g_error_free (error); return; } - pango_glyph_string_extents_range (iter->run->glyphs, - iter->cluster_start, - iter->next_cluster_glyph, - iter->run->item->analysis.font, - ink_rect, - logical_rect); + g_free (layout->text); + layout->text = text; + layout->length = strlen (text); - if (ink_rect) - { - ink_rect->x += iter->cluster_x + iter->run->start_x_offset; - ink_rect->y -= iter->run->y_offset; - offset_y (iter, &ink_rect->y); - } + g_clear_pointer (&layout->attrs, pango_attr_list_unref); + layout->attrs = attrs; - if (logical_rect) - { - g_assert (logical_rect->width == iter->cluster_width); - logical_rect->x += iter->cluster_x + iter->run->start_x_offset; - logical_rect->y -= iter->run->y_offset; - offset_y (iter, &logical_rect->y); - } + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_TEXT]); + g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_ATTRIBUTES]); + layout_changed (layout); } /** - * pango_layout_iter_get_run_extents: - * @iter: a `PangoLayoutIter` - * @ink_rect: (out) (optional): rectangle to fill with ink extents - * @logical_rect: (out) (optional): rectangle to fill with logical extents + * pango_layout_get_character_count: + * @layout: a `PangoLayout` * - * Gets the extents of the current run in layout coordinates. + * Returns the number of Unicode characters in the + * the text of @layout. * - * Layout coordinates have the origin at the top left of the entire layout. + * Return value: the number of Unicode characters in @layout */ -void -pango_layout_iter_get_run_extents (PangoLayoutIter *iter, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect) +int +pango_layout_get_character_count (PangoLayout *layout) { - if (G_UNLIKELY (!ink_rect && !logical_rect)) - return; - - if (ITER_IS_INVALID (iter)) - return; - - if (iter->run) - { - pango_layout_run_get_extents_and_height (iter->run, ink_rect, logical_rect, NULL, NULL); - - if (ink_rect) - { - offset_y (iter, &ink_rect->y); - ink_rect->x += iter->run_x; - } - - if (logical_rect) - { - offset_y (iter, &logical_rect->y); - logical_rect->x += iter->run_x; - } - } - else - { - if (iter->line->runs) - { - /* The empty run at the end of a non-empty line */ - PangoLayoutRun *run = g_slist_last (iter->line->runs)->data; - pango_layout_run_get_extents_and_height (run, ink_rect, logical_rect, NULL, NULL); - } - else - { - PangoRectangle r; - - pango_layout_get_empty_extents_and_height_at_index (iter->layout, 0, &r, FALSE, NULL); + PangoLayoutLine *line; - if (ink_rect) - *ink_rect = r; + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), 0); - if (logical_rect) - *logical_rect = r; - } + ensure_lines (layout); - if (ink_rect) - { - offset_y (iter, &ink_rect->y); - ink_rect->x = iter->run_x; - ink_rect->width = 0; - } + line = pango_lines_get_line (layout->lines, 0, NULL, NULL); - if (logical_rect) - { - offset_y (iter, &logical_rect->y); - logical_rect->x = iter->run_x; - logical_rect->width = 0; - } - } + return line->data->n_chars; } +/* }}} */ +/* {{{ Output getters */ + /** - * pango_layout_iter_get_line_extents: - * @iter: a `PangoLayoutIter` - * @ink_rect: (out) (optional): rectangle to fill with ink extents - * @logical_rect: (out) (optional): rectangle to fill with logical extents + * pango_layout_get_lines: + * @layout: a `PangoLayout` + * + * Gets the lines of the @layout. * - * Obtains the extents of the current line. + * The returned object will become invalid when any + * property of @layout is changed. Take a reference + * to keep it. * - * Extents are in layout coordinates (origin is the top-left corner - * of the entire `PangoLayout`). Thus the extents returned by this - * function will be the same width/height but not at the same x/y - * as the extents returned from [method@Pango.LayoutLine.get_extents]. + * Return value: (transfer none): a `PangoLines` object + * with the lines of @layout */ -void -pango_layout_iter_get_line_extents (PangoLayoutIter *iter, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect) +PangoLines * +pango_layout_get_lines (PangoLayout *layout) { - const Extents *ext; - - if (ITER_IS_INVALID (iter)) - return; - - ext = &iter->line_extents[iter->line_index]; + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); - if (ink_rect) - { - get_line_extents_layout_coords (iter->layout, iter->line, - iter->layout_width, - ext->logical_rect.y, - NULL, - ink_rect, - NULL); - } + ensure_lines (layout); - if (logical_rect) - *logical_rect = ext->logical_rect; + return layout->lines; } /** - * pango_layout_iter_get_line_yrange: - * @iter: a `PangoLayoutIter` - * @y0_: (out) (optional): start of line - * @y1_: (out) (optional): end of line + * pango_layout_get_log_attrs: + * @layout: a `PangoLayout` + * @n_attrs: (out): return location for the length of the array * - * Divides the vertical space in the `PangoLayout` being iterated over - * between the lines in the layout, and returns the space belonging to - * the current line. + * Gets the `PangoLogAttr` array for the content + * of @layout. * - * A line's range includes the line's logical extents. plus half of the - * spacing above and below the line, if [method@Pango.Layout.set_spacing] - * has been called to set layout spacing. The Y positions are in layout - * coordinates (origin at top left of the entire layout). + * The returned array becomes invalid when + * any properties of @layout change. Make a + * copy if you want to keep it. * - * Note: Since 1.44, Pango uses line heights for placing lines, and there - * may be gaps between the ranges returned by this function. + * Returns: (transfer none): the `PangoLogAttr` array */ -void -pango_layout_iter_get_line_yrange (PangoLayoutIter *iter, - int *y0, - int *y1) +const PangoLogAttr * +pango_layout_get_log_attrs (PangoLayout *layout, + int *n_attrs) { - const Extents *ext; - int half_spacing; - - if (ITER_IS_INVALID (iter)) - return; - - ext = &iter->line_extents[iter->line_index]; - - half_spacing = iter->layout->spacing / 2; - - /* Note that if layout->spacing is odd, the remainder spacing goes - * above the line (this is pretty arbitrary of course) - */ + PangoLayoutLine *line; - if (y0) - { - /* No spacing above the first line */ + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); - if (iter->line_index == 0) - *y0 = ext->logical_rect.y; - else - *y0 = ext->logical_rect.y - (iter->layout->spacing - half_spacing); - } + ensure_lines (layout); - if (y1) - { - /* No spacing below the last line */ - if (iter->line_index == iter->layout->line_count - 1) - *y1 = ext->logical_rect.y + ext->logical_rect.height; - else - *y1 = ext->logical_rect.y + ext->logical_rect.height + half_spacing; - } -} + line = pango_lines_get_line (layout->lines, 0, NULL, NULL); -/** - * pango_layout_iter_get_baseline: - * @iter: a `PangoLayoutIter` - * - * Gets the Y position of the current line's baseline, in layout - * coordinates. - * - * Layout coordinates have the origin at the top left of the entire layout. - * - * Return value: baseline of current line - */ -int -pango_layout_iter_get_baseline (PangoLayoutIter *iter) -{ - if (ITER_IS_INVALID (iter)) - return 0; + if (n_attrs) + *n_attrs = line->data->n_chars + 1; - return iter->line_extents[iter->line_index].baseline; + return line->data->log_attrs; } /** - * pango_layout_iter_get_run_baseline: - * @iter: a `PangoLayoutIter` - * - * Gets the Y position of the current run's baseline, in layout - * coordinates. + * pango_layout_get_iter: + * @layout: a `PangoLayout` * - * Layout coordinates have the origin at the top left of the entire layout. + * Returns an iterator to iterate over the visual extents + * of the layout. * - * The run baseline can be different from the line baseline, for - * example due to superscript or subscript positioning. + * This is a convenience wrapper for [method@Pango.Lines.get_iter]. * - * Since: 1.50 + * Returns: the new `PangoLayoutIter` */ -int -pango_layout_iter_get_run_baseline (PangoLayoutIter *iter) +PangoLayoutIter * +pango_layout_get_iter (PangoLayout *layout) { - if (ITER_IS_INVALID (iter)) - return 0; + g_return_val_if_fail (PANGO_IS_LAYOUT (layout), NULL); - if (!iter->run) - return iter->line_extents[iter->line_index].baseline; + ensure_lines (layout); - return iter->line_extents[iter->line_index].baseline - iter->run->y_offset; + return pango_lines_get_iter (layout->lines); } -/** - * pango_layout_iter_get_layout_extents: - * @iter: a `PangoLayoutIter` - * @ink_rect: (out) (optional): rectangle to fill with ink extents - * @logical_rect: (out) (optional): rectangle to fill with logical extents - * - * Obtains the extents of the `PangoLayout` being iterated over. - */ -void -pango_layout_iter_get_layout_extents (PangoLayoutIter *iter, - PangoRectangle *ink_rect, - PangoRectangle *logical_rect) -{ - if (ITER_IS_INVALID (iter)) - return; +/* }}} */ +/* }}} */ - pango_layout_get_extents (iter->layout, ink_rect, logical_rect); -} +/* vim:set foldmethod=marker expandtab: */ |