From 303b4fb73eb8848d18abbda4d151461bd86f1000 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 29 Aug 2021 00:10:03 -0400 Subject: Implement baseline shifts Add a new baseline-shift attribute, which is similar to rise, but accumulates. In addition, it supports font- relative values such as superscript and subscript. We implement support for this by computing baseline shifts for run during line post-processing, and storing them in the runs. The renderer now takes these shifts into account when rendering layout lines. --- pango/pango-attributes.c | 25 +++++++ pango/pango-attributes.h | 21 ++++++ pango/pango-glyph-item.c | 5 +- pango/pango-glyph-item.h | 5 +- pango/pango-layout.c | 168 +++++++++++++++++++++++++++++++++++++++-------- pango/pango-markup.c | 24 +++++++ pango/pango-renderer.c | 27 +++----- tests/test-itemize.c | 1 + 8 files changed, 231 insertions(+), 45 deletions(-) diff --git a/pango/pango-attributes.c b/pango/pango-attributes.c index 326234d2..8507c963 100644 --- a/pango/pango-attributes.c +++ b/pango/pango-attributes.c @@ -923,6 +923,30 @@ pango_attr_rise_new (int rise) return pango_attr_int_new (&klass, (int)rise); } +/** + * pango_attr_baseline_shift_new: + * @shift: either a `PangoBaselineShift` enumeration value or an absolute value (> 1024) + * in Pango units, relative to the baseline of the previous run. + * Positive values displace the text upwards. + * + * Create a new baseline displacement attribute. + * + * Return value: (transfer full): the newly allocated + * `PangoAttribute`, which should be freed with + * [method@Pango.Attribute.destroy] + */ +PangoAttribute * +pango_attr_baseline_shift_new (int rise) +{ + static const PangoAttrClass klass = { + PANGO_ATTR_BASELINE_SHIFT, + pango_attr_int_copy, + pango_attr_int_destroy, + pango_attr_int_equal + }; + + return pango_attr_int_new (&klass, (int)rise); +} /** * pango_attr_scale_new: * @scale_factor: factor to scale the font @@ -1533,6 +1557,7 @@ pango_attribute_as_int (PangoAttribute *attr) case PANGO_ATTR_TEXT_TRANSFORM: case PANGO_ATTR_WORD: case PANGO_ATTR_SENTENCE: + case PANGO_ATTR_BASELINE_SHIFT: return (PangoAttrInt *)attr; default: diff --git a/pango/pango-attributes.h b/pango/pango-attributes.h index 613aa021..1c9df2c5 100644 --- a/pango/pango-attributes.h +++ b/pango/pango-attributes.h @@ -79,6 +79,7 @@ typedef struct _PangoAttrFontFeatures PangoAttrFontFeatures; * @PANGO_ATTR_ABSOLUTE_LINE_HEIGHT: line height ([struct@Pango.AttrInt]). Since: 1.50 * @PANGO_ATTR_WORD: override segmentation to classify the range of the attribute as a single word ([struct@Pango.AttrInt]). Since 1.50 * @PANGO_ATTR_SENTENCE: override segmentation to classify the range of the attribute as a single sentence ([struct@Pango.AttrInt]). Since 1.50 + * @PANGO_ATTR_BASELINE_SHIFT: baseline displacement ([struct@Pango.AttrSize]). Since 1.50 * * The `PangoAttrType` distinguishes between different types of attributes. * @@ -125,6 +126,7 @@ typedef enum PANGO_ATTR_TEXT_TRANSFORM, /* PangoAttrInt */ PANGO_ATTR_WORD, /* PangoAttrInt */ PANGO_ATTR_SENTENCE, /* PangoAttrInt */ + PANGO_ATTR_BASELINE_SHIFT, /* PangoAttrSize */ } PangoAttrType; /** @@ -226,6 +228,23 @@ typedef enum { PANGO_TEXT_TRANSFORM_CAPITALIZE, } PangoTextTransform; +/** + * PangoBaselineShift: + * @PANGO_BASELINE_SHIFT_SUPERSCRIPT: Shift the baseline to the superscript position, + * relative to the previous run + * @PANGO_BASELINE_SHIFT_SUBSCRIPT: Shift the baseline to the subscript position, + * relative to the previous run + * + * An enumeration that affects baseline shifts between runs. + * + * Since: 1.50 + */ +typedef enum { + PANGO_BASELINE_SHIFT_NONE, + PANGO_BASELINE_SHIFT_SUPERSCRIPT, + PANGO_BASELINE_SHIFT_SUBSCRIPT, +} PangoBaselineShift; + /** * PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING: * @@ -515,6 +534,8 @@ PangoAttribute * pango_attr_strikethrough_color_new (guint16 guint16 blue); PANGO_AVAILABLE_IN_ALL PangoAttribute * pango_attr_rise_new (int rise); +PANGO_AVAILABLE_IN_1_50 +PangoAttribute * pango_attr_baseline_shift_new (int shift); PANGO_AVAILABLE_IN_ALL PangoAttribute * pango_attr_scale_new (double scale_factor); PANGO_AVAILABLE_IN_1_4 diff --git a/pango/pango-glyph-item.c b/pango/pango-glyph-item.c index 5e6ca7b6..c64bfa13 100644 --- a/pango/pango-glyph-item.c +++ b/pango/pango-glyph-item.c @@ -129,6 +129,8 @@ pango_glyph_item_split (PangoGlyphItem *orig, pango_glyph_string_set_size (orig->glyphs, orig->glyphs->num_glyphs - num_glyphs); + new->y_offset = orig->y_offset; + return new; } @@ -154,6 +156,7 @@ pango_glyph_item_copy (PangoGlyphItem *orig) result->item = pango_item_copy (orig->item); result->glyphs = pango_glyph_string_copy (orig->glyphs); + result->y_offset = orig->y_offset; return result; } @@ -196,7 +199,7 @@ G_DEFINE_BOXED_TYPE (PangoGlyphItem, pango_glyph_item, * Since: 1.22 */ PangoGlyphItemIter * -pango_glyph_item_iter_copy (PangoGlyphItemIter *orig) +pango_glyph_item_iter_copy (PangoGlyphItemIter *orig) { PangoGlyphItemIter *result; diff --git a/pango/pango-glyph-item.h b/pango/pango-glyph-item.h index 6c2f9249..baea69fc 100644 --- a/pango/pango-glyph-item.h +++ b/pango/pango-glyph-item.h @@ -33,6 +33,8 @@ G_BEGIN_DECLS * PangoGlyphItem: * @item: corresponding `PangoItem` * @glyphs: corresponding `PangoGlyphString` + * @baseline: shift of the baseline, relative to the + * containing lines baseline. Positive values shift upwards * * A `PangoGlyphItem` is a pair of a `PangoItem` and the glyphs * resulting from shaping the items text. @@ -45,8 +47,9 @@ typedef struct _PangoGlyphItem PangoGlyphItem; struct _PangoGlyphItem { - PangoItem *item; + PangoItem *item; PangoGlyphString *glyphs; + int y_offset; }; #define PANGO_TYPE_GLYPH_ITEM (pango_glyph_item_get_type ()) diff --git a/pango/pango-layout.c b/pango/pango-layout.c index d3d93e60..58e3a91c 100644 --- a/pango/pango-layout.c +++ b/pango/pango-layout.c @@ -92,7 +92,7 @@ typedef struct _ItemProperties ItemProperties; typedef struct _ParaBreakState ParaBreakState; -/* Note that rise, letter_spacing, shape are constant across items, +/* Note that letter_spacing and shape are constant across items, * since we pass them into itemization. * * uline and strikethrough can vary across an item, so we collect @@ -108,7 +108,6 @@ struct _ItemProperties guint uline_error : 1; guint strikethrough : 1; guint oline_single : 1; - gint rise; gint letter_spacing; gboolean shape_set; PangoRectangle *shape_ink_rect; @@ -3621,6 +3620,8 @@ struct _ParaBreakState 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; }; static gboolean @@ -4324,6 +4325,7 @@ affects_itemization (PangoAttribute *attr, 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: @@ -4485,6 +4487,7 @@ pango_layout_check_lines (PangoLayout *layout) state.log_widths = NULL; state.num_log_widths = 0; + state.baseline_shifts = NULL; do { @@ -4593,6 +4596,7 @@ pango_layout_check_lines (PangoLayout *layout) 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); @@ -5220,6 +5224,7 @@ pango_layout_run_get_extents_and_height (PangoLayoutRun *run, PangoFontMetrics *metrics = NULL; gboolean has_underline; gboolean has_overline; + int y_offset; if (G_UNLIKELY (!run_ink && !run_logical && !line_logical && !height)) return; @@ -5313,6 +5318,8 @@ pango_layout_run_get_extents_and_height (PangoLayoutRun *run, *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; @@ -5321,17 +5328,14 @@ pango_layout_run_get_extents_and_height (PangoLayoutRun *run, if (is_hinted) adjustment = PANGO_UNITS_ROUND (adjustment); - properties.rise += adjustment; + y_offset += adjustment; } - if (properties.rise != 0) - { - if (run_ink) - run_ink->y -= properties.rise; + if (run_ink) + run_ink->y -= y_offset; - if (run_logical) - run_logical->y -= properties.rise; - } + if (run_logical) + run_logical->y -= y_offset; if (line_logical) { @@ -6147,6 +6151,129 @@ justify_words (PangoLayoutLine *line, state->remaining_width -= added_so_far; } +typedef struct { + PangoAttribute *attr; + int shift; +} BaselineItem; + +static void +collect_shifts (ParaBreakState *state, + PangoItem *item, + PangoItem *prev, + int *start_shift, + int *end_shift) +{ + *start_shift = 0; + *end_shift = 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_shift += value; + *end_shift -= 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; + + value = ((PangoAttrInt *)attr)->value; + + if (value > 1024 || value < -1024) + { + entry->shift = value; + } + else + { + int superscript_shift = 0; + int subscript_shift = 0; + hb_font_t *hb_font; + + + if (prev) + { + 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_shift); + hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_SUBSCRIPT_EM_Y_OFFSET, &subscript_shift); + } + + if (superscript_shift == 0) + superscript_shift = 5000; + if (subscript_shift == 0) + subscript_shift = 5000; + + switch (value) + { + case PANGO_BASELINE_SHIFT_NONE: + entry->shift = 0; + break; + case PANGO_BASELINE_SHIFT_SUPERSCRIPT: + entry->shift = superscript_shift; + break; + case PANGO_BASELINE_SHIFT_SUBSCRIPT: + entry->shift = -subscript_shift; + break; + default: + g_assert_not_reached (); + } + } + + *start_shift += entry->shift; + state->baseline_shifts = g_list_prepend (state->baseline_shifts, entry); + } + if (attr->end_index == item->offset + item->length) + { + BaselineItem *entry = state->baseline_shifts->data; + + if (attr->start_index == entry->attr->start_index && + attr->end_index == entry->attr->end_index && + ((PangoAttrInt *)attr)->value == ((PangoAttrInt *)entry->attr)->value) + *end_shift -= entry->shift; + else + g_warning ("Baseline attributes mismatch\n"); + + state->baseline_shifts = g_list_remove (state->baseline_shifts, entry); + g_free (entry); + } + } + } +} + +static void +apply_baseline_shifts (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_y_offset, end_y_offset; + + collect_shifts (state, item, prev, &start_y_offset, &end_y_offset); + + y_offset += start_y_offset; + + run->y_offset = y_offset; + + y_offset += end_y_offset; + + prev = item; + } +} + static void pango_layout_line_postprocess (PangoLayoutLine *line, ParaBreakState *state, @@ -6167,6 +6294,8 @@ pango_layout_line_postprocess (PangoLayoutLine *line, */ line->runs = g_slist_reverse (line->runs); + apply_baseline_shifts (line, state); + /* Ellipsize the line if necessary */ if (G_UNLIKELY (state->line_width >= 0 && @@ -6224,7 +6353,6 @@ pango_layout_get_item_properties (PangoItem *item, properties->oline_single = FALSE; properties->strikethrough = FALSE; properties->letter_spacing = 0; - properties->rise = 0; properties->shape_set = FALSE; properties->shape_ink_rect = NULL; properties->shape_logical_rect = NULL; @@ -6279,10 +6407,6 @@ pango_layout_get_item_properties (PangoItem *item, properties->strikethrough = ((PangoAttrInt *)attr)->value; break; - case PANGO_ATTR_RISE: - properties->rise = ((PangoAttrInt *)attr)->value; - break; - case PANGO_ATTR_LETTER_SPACING: properties->letter_spacing = ((PangoAttrInt *)attr)->value; break; @@ -7110,8 +7234,6 @@ pango_layout_iter_get_cluster_extents (PangoLayoutIter *iter, PangoRectangle *ink_rect, PangoRectangle *logical_rect) { - ItemProperties properties; - if (ITER_IS_INVALID (iter)) return; @@ -7124,8 +7246,6 @@ pango_layout_iter_get_cluster_extents (PangoLayoutIter *iter, return; } - pango_layout_get_item_properties (iter->run->item, &properties); - pango_glyph_string_extents_range (iter->run->glyphs, iter->cluster_start, iter->next_cluster_glyph, @@ -7136,7 +7256,7 @@ pango_layout_iter_get_cluster_extents (PangoLayoutIter *iter, if (ink_rect) { ink_rect->x += iter->cluster_x; - ink_rect->y -= properties.rise; + ink_rect->y -= iter->run->y_offset; offset_y (iter, &ink_rect->y); } @@ -7144,7 +7264,7 @@ pango_layout_iter_get_cluster_extents (PangoLayoutIter *iter, { g_assert (logical_rect->width == iter->cluster_width); logical_rect->x += iter->cluster_x; - logical_rect->y -= properties.rise; + logical_rect->y -= iter->run->y_offset; offset_y (iter, &logical_rect->y); } } @@ -7325,17 +7445,13 @@ pango_layout_iter_get_baseline (PangoLayoutIter *iter) int pango_layout_iter_get_run_baseline (PangoLayoutIter *iter) { - ItemProperties properties; - if (ITER_IS_INVALID (iter)) return 0; if (!iter->run) return iter->line_extents[iter->line_index].baseline; - pango_layout_get_item_properties (iter->run->item, &properties); - - return iter->line_extents[iter->line_index].baseline - properties.rise; + return iter->line_extents[iter->line_index].baseline - iter->run->y_offset; } /** diff --git a/pango/pango-markup.c b/pango/pango-markup.c index a9df8ed0..65396547 100644 --- a/pango/pango-markup.c +++ b/pango/pango-markup.c @@ -1217,6 +1217,7 @@ span_parse_func (MarkupData *md G_GNUC_UNUSED, const char *strikethrough = NULL; const char *strikethrough_color = NULL; const char *rise = NULL; + const char *baseline_shift = NULL; const char *letter_spacing = NULL; const char *lang = NULL; const char *fallback = NULL; @@ -1268,6 +1269,7 @@ span_parse_func (MarkupData *md G_GNUC_UNUSED, CHECK_ATTRIBUTE2(background, "bgcolor"); CHECK_ATTRIBUTE (background_alpha); CHECK_ATTRIBUTE2(background_alpha, "bgalpha"); + CHECK_ATTRIBUTE(baseline_shift); break; case 'c': CHECK_ATTRIBUTE2(foreground, "color"); @@ -1675,6 +1677,28 @@ span_parse_func (MarkupData *md G_GNUC_UNUSED, add_attribute (tag, pango_attr_rise_new (n)); } + if (G_UNLIKELY (baseline_shift)) + { + gint shift = 0; + + if (span_parse_enum ("baseline_shift", baseline_shift, PANGO_TYPE_BASELINE_SHIFT, (int*)(void*)&shift, line_number, NULL)) + add_attribute (tag, pango_attr_baseline_shift_new (shift)); + else if (parse_length (baseline_shift, &shift) && (shift > 1024 || shift < -1024)) + add_attribute (tag, pango_attr_baseline_shift_new (shift)); + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + _("Value of 'baseline_shift' attribute on tag on line %d " + "could not be parsed; should be 'superscript' or 'subscript' or " + "an integer, or a string such as '5.5pt', not '%s'"), + line_number, baseline_shift); + goto error; + } + + } + if (G_UNLIKELY (letter_spacing)) { gint n = 0; diff --git a/pango/pango-renderer.c b/pango/pango-renderer.c index 055cdd97..231ebe7d 100644 --- a/pango/pango-renderer.c +++ b/pango/pango-renderer.c @@ -502,14 +502,10 @@ add_strikethrough (PangoRenderer *renderer, static void get_item_properties (PangoItem *item, - gint *rise, PangoAttrShape **shape_attr) { GSList *l; - if (rise) - *rise = 0; - if (shape_attr) *shape_attr = NULL; @@ -524,11 +520,6 @@ get_item_properties (PangoItem *item, *shape_attr = (PangoAttrShape *)attr; break; - case PANGO_ATTR_RISE: - if (rise) - *rise = ((PangoAttrInt *)attr)->value; - break; - default: break; } @@ -589,6 +580,7 @@ pango_renderer_draw_layout_line (PangoRenderer *renderer, gboolean got_overall = FALSE; PangoRectangle overall_rect; const char *text; + int y_off; g_return_if_fail (PANGO_IS_RENDERER_FAST (renderer)); @@ -616,7 +608,6 @@ pango_renderer_draw_layout_line (PangoRenderer *renderer, for (l = line->runs; l; l = l->next) { PangoFontMetrics *metrics; - gint rise; PangoLayoutRun *run = l->data; PangoAttrShape *shape_attr; PangoRectangle ink_rect, *ink = NULL; @@ -627,7 +618,7 @@ pango_renderer_draw_layout_line (PangoRenderer *renderer, pango_renderer_prepare_run (renderer, run); - get_item_properties (run->item, &rise, &shape_attr); + get_item_properties (run->item, &shape_attr); if (shape_attr) { @@ -660,6 +651,8 @@ pango_renderer_draw_layout_line (PangoRenderer *renderer, state.logical_rect_end = x + x_off + glyph_string_width; + y_off = run->y_offset; + if (run->item->analysis.flags & PANGO_ANALYSIS_FLAG_CENTERED_BASELINE) { gboolean is_hinted = ((logical_rect.y | logical_rect.height) & (PANGO_SCALE - 1)) == 0; @@ -668,7 +661,7 @@ pango_renderer_draw_layout_line (PangoRenderer *renderer, if (is_hinted) adjustment = PANGO_UNITS_ROUND (adjustment); - rise += adjustment; + y_off += adjustment; } @@ -690,14 +683,14 @@ pango_renderer_draw_layout_line (PangoRenderer *renderer, if (shape_attr) { - draw_shaped_glyphs (renderer, run->glyphs, shape_attr, x + x_off, y - rise); + draw_shaped_glyphs (renderer, run->glyphs, shape_attr, x + x_off, y - y_off); } else { pango_renderer_draw_glyph_item (renderer, text, run, - x + x_off, y - rise); + x + x_off, y - y_off); } if (renderer->underline != PANGO_UNDERLINE_NONE || @@ -709,17 +702,17 @@ pango_renderer_draw_layout_line (PangoRenderer *renderer, if (renderer->underline != PANGO_UNDERLINE_NONE) add_underline (renderer, &state,metrics, - x + x_off, y - rise, + x + x_off, y - y_off, ink, logical); if (renderer->priv->overline != PANGO_OVERLINE_NONE) add_overline (renderer, &state,metrics, - x + x_off, y - rise, + x + x_off, y - y_off, ink, logical); if (renderer->strikethrough) add_strikethrough (renderer, &state, metrics, - x + x_off, y - rise, + x + x_off, y - y_off, ink, logical, run->glyphs->num_glyphs); pango_font_metrics_unref (metrics); diff --git a/tests/test-itemize.c b/tests/test-itemize.c index db6a715f..29f59210 100644 --- a/tests/test-itemize.c +++ b/tests/test-itemize.c @@ -77,6 +77,7 @@ affects_itemization (PangoAttribute *attr, 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: -- cgit v1.2.1