~vcs-imports/pango/master

« back to all changes in this revision

Viewing changes to pango/pango-layout.c

  • Committer: Matthias Clasen
  • Date: 2021-11-29 13:59:31 UTC
  • mfrom: (4606.1.10)
  • Revision ID: git-v1:7b8d9efa987435a5214092157f032f9d65285d7e
Merge branch 'tab-align' into 'main'

Support tab alignment

Closes #34

See merge request GNOME/pango!527

Show diffs side-by-side

added added

removed removed

Lines of Context:
82
82
#include "pango-glyph-item.h"
83
83
#include <string.h>
84
84
#include <math.h>
 
85
#include <locale.h>
85
86
 
86
87
#include <hb-ot.h>
87
88
 
225
226
  layout->line_count = 0;
226
227
 
227
228
  layout->tab_width = -1;
 
229
  layout->decimal = 0;
228
230
  layout->unknown_glyphs_count = -1;
229
231
 
230
232
  layout->wrap = PANGO_WRAP_WORD;
1017
1019
 *
1018
1020
 * Note that tabs and justification conflict with each other:
1019
1021
 * Justification will move content away from its tab-aligned
1020
 
 * positions.
 
1022
 * positions. The same is true for alignments other than
 
1023
 * %PANGO_ALIGN_LEFT.
1021
1024
 */
1022
1025
void
1023
1026
pango_layout_set_tabs (PangoLayout   *layout,
3261
3264
 *****************/
3262
3265
 
3263
3266
static void shape_tab (PangoLayoutLine  *line,
 
3267
                       ParaBreakState   *state,
3264
3268
                       PangoItem        *item,
3265
3269
                       PangoGlyphString *glyphs);
3266
3270
 
3366
3370
    }
3367
3371
}
3368
3372
 
3369
 
/* For now we only need the tab position, we assume
3370
 
 * all tabs are left-aligned.
3371
 
 */
3372
 
static int
3373
 
get_tab_pos (PangoLayout *layout,
3374
 
             int          index,
3375
 
             gboolean    *is_default)
 
3373
static void
 
3374
get_tab_pos (PangoLayoutLine *line,
 
3375
             int              index,
 
3376
             int             *tab_pos,
 
3377
             PangoTabAlign   *alignment,
 
3378
             gunichar        *decimal,
 
3379
             gboolean        *is_default)
3376
3380
{
3377
 
  gint n_tabs;
 
3381
  PangoLayout *layout = line->layout;
 
3382
  int n_tabs;
3378
3383
  gboolean in_pixels;
 
3384
  int offset = 0;
 
3385
 
 
3386
  if (layout->alignment != PANGO_ALIGN_CENTER)
 
3387
    {
 
3388
      if (line->is_paragraph_start && layout->indent >= 0)
 
3389
        offset = layout->indent;
 
3390
      else if (!line->is_paragraph_start && layout->indent < 0)
 
3391
        offset = - layout->indent;
 
3392
    }
3379
3393
 
3380
3394
  if (layout->tabs)
3381
3395
    {
3382
3396
      n_tabs = pango_tab_array_get_size (layout->tabs);
3383
3397
      in_pixels = pango_tab_array_get_positions_in_pixels (layout->tabs);
3384
 
      if (is_default)
3385
 
        *is_default = FALSE;
 
3398
      *is_default = FALSE;
3386
3399
    }
3387
3400
  else
3388
3401
    {
3389
3402
      n_tabs = 0;
3390
3403
      in_pixels = FALSE;
3391
 
      if (is_default)
3392
 
        *is_default = TRUE;
 
3404
      *is_default = TRUE;
3393
3405
    }
3394
3406
 
3395
3407
  if (index < n_tabs)
3396
3408
    {
3397
 
      gint pos = 0;
3398
 
 
3399
 
      pango_tab_array_get_tab (layout->tabs, index, NULL, &pos);
 
3409
      pango_tab_array_get_tab (layout->tabs, index, alignment, tab_pos);
3400
3410
 
3401
3411
      if (in_pixels)
3402
 
        return pos * PANGO_SCALE;
3403
 
      else
3404
 
        return pos;
 
3412
        *tab_pos *= PANGO_SCALE;
 
3413
 
 
3414
      *decimal = pango_tab_array_get_decimal_point (layout->tabs, index);
3405
3415
    }
3406
 
 
3407
 
  if (n_tabs > 0)
 
3416
  else if (n_tabs > 0)
3408
3417
    {
3409
 
      /* Extrapolate tab position, repeating the last tab gap to
3410
 
       * infinity.
3411
 
       */
 
3418
      /* Extrapolate tab position, repeating the last tab gap to infinity. */
3412
3419
      int last_pos = 0;
3413
3420
      int next_to_last_pos = 0;
3414
3421
      int tab_width;
3415
3422
 
3416
 
      pango_tab_array_get_tab (layout->tabs, n_tabs - 1, NULL, &last_pos);
 
3423
      pango_tab_array_get_tab (layout->tabs, n_tabs - 1, alignment, &last_pos);
 
3424
      *decimal = pango_tab_array_get_decimal_point (layout->tabs, n_tabs - 1);
3417
3425
 
3418
3426
      if (n_tabs > 1)
3419
3427
        pango_tab_array_get_tab (layout->tabs, n_tabs - 2, NULL, &next_to_last_pos);
3427
3435
        }
3428
3436
 
3429
3437
      if (last_pos > next_to_last_pos)
3430
 
        {
3431
 
          tab_width = last_pos - next_to_last_pos;
3432
 
        }
 
3438
        tab_width = last_pos - next_to_last_pos;
3433
3439
      else
3434
 
        {
3435
 
          tab_width = layout->tab_width;
3436
 
        }
 
3440
        tab_width = layout->tab_width;
3437
3441
 
3438
 
      return last_pos + tab_width * (index - n_tabs + 1);
 
3442
      *tab_pos = last_pos + tab_width * (index - n_tabs + 1);
3439
3443
    }
3440
3444
  else
3441
3445
    {
3442
 
      /* No tab array set, so use default tab width
3443
 
       */
3444
 
      return layout->tab_width * index;
 
3446
      /* No tab array set, so use default tab width */
 
3447
      *tab_pos = layout->tab_width * index;
 
3448
      *alignment = PANGO_TAB_LEFT;
 
3449
      *decimal = 0;
3445
3450
    }
 
3451
 
 
3452
  *tab_pos -= offset;
3446
3453
}
3447
3454
 
3448
3455
static int
3459
3466
    {
3460
3467
      PangoLayoutRun *run = l->data;
3461
3468
 
3462
 
      for (i=0; i < run->glyphs->num_glyphs; i++)
 
3469
      for (i = 0; i < run->glyphs->num_glyphs; i++)
3463
3470
        width += run->glyphs->glyphs[i].geometry.width;
3464
3471
    }
3465
3472
 
3483
3490
  return FALSE;
3484
3491
}
3485
3492
 
 
3493
static void break_state_set_last_tab (ParaBreakState   *state,
 
3494
                                      PangoGlyphString *glyphs,
 
3495
                                      int               width,
 
3496
                                      int               tab_pos,
 
3497
                                      PangoTabAlign     tab_align,
 
3498
                                      gunichar          tab_decimal);
 
3499
 
 
3500
static void
 
3501
ensure_decimal (PangoLayout *layout)
 
3502
{
 
3503
  if (layout->decimal == 0)
 
3504
    layout->decimal = g_utf8_get_char (localeconv ()->decimal_point);
 
3505
}
 
3506
 
3486
3507
static void
3487
3508
shape_tab (PangoLayoutLine  *line,
 
3509
           ParaBreakState   *state,
3488
3510
           PangoItem        *item,
3489
3511
           PangoGlyphString *glyphs)
3490
3512
{
3491
3513
  int i, space_width;
 
3514
  int current_width;
 
3515
  int tab_pos;
 
3516
  PangoTabAlign tab_align;
 
3517
  gunichar tab_decimal;
3492
3518
 
3493
 
  int current_width = line_width (line);
 
3519
  current_width = line_width (line);
3494
3520
 
3495
3521
  pango_glyph_string_set_size (glyphs, 1);
3496
3522
 
3498
3524
    glyphs->glyphs[0].glyph = PANGO_GET_UNKNOWN_GLYPH ('\t');
3499
3525
  else
3500
3526
    glyphs->glyphs[0].glyph = PANGO_GLYPH_EMPTY;
 
3527
 
3501
3528
  glyphs->glyphs[0].geometry.x_offset = 0;
3502
3529
  glyphs->glyphs[0].geometry.y_offset = 0;
3503
3530
  glyphs->glyphs[0].attr.is_cluster_start = 1;
3508
3535
  ensure_tab_width (line->layout);
3509
3536
  space_width = line->layout->tab_width / 8;
3510
3537
 
3511
 
  for (i=0;;i++)
 
3538
  for (i = 0; ; i++)
3512
3539
    {
3513
3540
      gboolean is_default;
3514
 
      int tab_pos = get_tab_pos (line->layout, i, &is_default);
 
3541
 
 
3542
      get_tab_pos (line, i, &tab_pos, &tab_align, &tab_decimal, &is_default);
 
3543
 
3515
3544
      /* Make sure there is at least a space-width of space between
3516
 
       * tab-aligned text and the text before it.  However, only do
 
3545
       * tab-aligned text and the text before it. However, only do
3517
3546
       * this if no tab array is set on the layout, ie. using default
3518
 
       * tab positions.  If use has set tab positions, respect it to
3519
 
       * the pixel.
 
3547
       * tab positions. If the user has set tab positions, respect it
 
3548
       * to the pixel.
3520
3549
       */
3521
3550
      if (tab_pos >= current_width + (is_default ? space_width : 1))
3522
3551
        {
3524
3553
          break;
3525
3554
        }
3526
3555
    }
 
3556
 
 
3557
  if (tab_decimal == 0)
 
3558
    {
 
3559
      ensure_decimal (line->layout);
 
3560
      tab_decimal = line->layout->decimal;
 
3561
    }
 
3562
 
 
3563
  break_state_set_last_tab (state, glyphs, current_width, tab_pos, tab_align, tab_decimal);
3527
3564
}
3528
3565
 
3529
3566
static inline gboolean
3607
3644
  int hyphen_width;             /* How much space a hyphen will take */
3608
3645
 
3609
3646
  GList *baseline_shifts;
 
3647
 
 
3648
  PangoGlyphString *last_tab;
 
3649
  int last_tab_width;
 
3650
  int last_tab_pos;
 
3651
  PangoTabAlign last_tab_align;
 
3652
  gunichar last_tab_decimal;
3610
3653
};
3611
3654
 
 
3655
static void
 
3656
break_state_set_last_tab (ParaBreakState   *state,
 
3657
                          PangoGlyphString *glyphs,
 
3658
                          int               width,
 
3659
                          int               tab_pos,
 
3660
                          PangoTabAlign     tab_align,
 
3661
                          gunichar          tab_decimal)
 
3662
{
 
3663
 
 
3664
  state->last_tab = glyphs;
 
3665
  state->last_tab_width = width;
 
3666
  state->last_tab_pos = tab_pos;
 
3667
  state->last_tab_align = tab_align;
 
3668
  state->last_tab_decimal = tab_decimal;
 
3669
}
 
3670
 
3612
3671
static gboolean
3613
3672
should_ellipsize_current_line (PangoLayout    *layout,
3614
3673
                               ParaBreakState *state);
3615
3674
 
 
3675
static void
 
3676
get_decimal_prefix_width (PangoItem        *item,
 
3677
                          PangoGlyphString *glyphs,
 
3678
                          const char       *text,
 
3679
                          gunichar          decimal,
 
3680
                          int              *width,
 
3681
                          gboolean         *found)
 
3682
{
 
3683
  PangoGlyphItem glyph_item = { item, glyphs, 0, 0, 0 };
 
3684
  int *log_widths;
 
3685
  int i;
 
3686
  const char *p;
 
3687
 
 
3688
  log_widths = g_new (int, item->num_chars);
 
3689
 
 
3690
  pango_glyph_item_get_logical_widths (&glyph_item, text, log_widths);
 
3691
 
 
3692
  *width = 0;
 
3693
  *found = FALSE;
 
3694
 
 
3695
  for (i = 0, p = text + item->offset; i < item->num_chars; i++, p = g_utf8_next_char (p))
 
3696
    {
 
3697
      if (g_utf8_get_char (p) == decimal)
 
3698
        {
 
3699
          *width += log_widths[i] / 2;
 
3700
          *found = TRUE;
 
3701
          break;
 
3702
        }
 
3703
 
 
3704
      *width += log_widths[i];
 
3705
    }
 
3706
 
 
3707
  g_free (log_widths);
 
3708
}
 
3709
 
3616
3710
static PangoGlyphString *
3617
3711
shape_run (PangoLayoutLine *line,
3618
3712
           ParaBreakState  *state,
3622
3716
  PangoGlyphString *glyphs = pango_glyph_string_new ();
3623
3717
 
3624
3718
  if (layout->text[item->offset] == '\t')
3625
 
    shape_tab (line, item, glyphs);
 
3719
    shape_tab (line, state, item, glyphs);
3626
3720
  else
3627
3721
    {
3628
3722
      PangoShapeFlags shape_flags = PANGO_SHAPE_NONE;
3660
3754
          glyphs->glyphs[0].geometry.x_offset += space_left;
3661
3755
          glyphs->glyphs[glyphs->num_glyphs - 1].geometry.width += space_right;
3662
3756
        }
 
3757
 
 
3758
      if (state->last_tab != NULL)
 
3759
        {
 
3760
          int w;
 
3761
 
 
3762
          g_assert (state->last_tab->num_glyphs == 1);
 
3763
 
 
3764
          /* Update the width of the current tab to position this run properly */
 
3765
 
 
3766
          w = state->last_tab_pos - state->last_tab_width;
 
3767
 
 
3768
          if (state->last_tab_align == PANGO_TAB_RIGHT)
 
3769
            w -= pango_glyph_string_get_width (glyphs);
 
3770
          else if (state->last_tab_align == PANGO_TAB_CENTER)
 
3771
            w -= pango_glyph_string_get_width (glyphs) / 2;
 
3772
          else if (state->last_tab_align == PANGO_TAB_DECIMAL)
 
3773
            {
 
3774
              int width;
 
3775
              gboolean found;
 
3776
 
 
3777
              get_decimal_prefix_width (item, glyphs, layout->text, state->last_tab_decimal, &width, &found);
 
3778
 
 
3779
              w -= width;
 
3780
            }
 
3781
 
 
3782
          state->last_tab->glyphs[0].geometry.width = MAX (w, 0);
 
3783
        }
3663
3784
    }
3664
3785
 
3665
3786
  return glyphs;
3680
3801
    run->glyphs = glyphs;
3681
3802
  else if (last_run && state->log_widths_offset == 0 &&
3682
3803
           !(run_item->analysis.flags & PANGO_ANALYSIS_FLAG_NEED_HYPHEN))
3683
 
    run->glyphs = state->glyphs;
 
3804
    {
 
3805
      run->glyphs = state->glyphs;
 
3806
      state->glyphs = NULL;
 
3807
    }
3684
3808
  else
3685
3809
    run->glyphs = shape_run (line, state, run_item);
3686
3810
 
3687
 
  if (last_run)
 
3811
  if (last_run && state->glyphs)
3688
3812
    {
3689
 
      if (state->log_widths_offset > 0)
3690
 
        pango_glyph_string_free (state->glyphs);
 
3813
      pango_glyph_string_free (state->glyphs);
3691
3814
      state->glyphs = NULL;
3692
3815
    }
3693
3816
 
3694
3817
  line->runs = g_slist_prepend (line->runs, run);
3695
3818
  line->length += run_item->length;
 
3819
 
 
3820
  if (state->last_tab && run->glyphs != state->last_tab)
 
3821
    {
 
3822
      /* Adjust the tab position so placing further runs will continue to
 
3823
       * maintain the tab placement. In the case of decimal tabs, we are
 
3824
       * done once we've placed the run with the decimal point.
 
3825
       */
 
3826
 
 
3827
      if (state->last_tab_align == PANGO_TAB_RIGHT)
 
3828
        state->last_tab_width += pango_glyph_string_get_width (run->glyphs);
 
3829
      else if (state->last_tab_align == PANGO_TAB_CENTER)
 
3830
        state->last_tab_width += pango_glyph_string_get_width (run->glyphs) / 2;
 
3831
      else if (state->last_tab_align == PANGO_TAB_DECIMAL)
 
3832
        {
 
3833
          int width;
 
3834
          gboolean found;
 
3835
 
 
3836
          get_decimal_prefix_width (run->item, run->glyphs, line->layout->text, state->last_tab_decimal, &width, &found);
 
3837
 
 
3838
          state->last_tab_width += width;
 
3839
          if (found)
 
3840
            state->last_tab = NULL;
 
3841
        }
 
3842
    }
3696
3843
}
3697
3844
 
3698
3845
static gboolean
3800
3947
  pango_glyph_item_get_logical_widths (&glyph_item, layout->text, state->log_widths);
3801
3948
}
3802
3949
 
 
3950
/* If last_tab is set, we've added a tab and remaining_width has been updated to
 
3951
 * account for its origin width, which is last_tab_pos - last_tab_width. shape_run
 
3952
 * updates the tab width, so we need to consider the delta when comparing
 
3953
 * against remaining_width.
 
3954
 */
 
3955
static int
 
3956
tab_width_change (ParaBreakState *state)
 
3957
{
 
3958
  if (state->last_tab)
 
3959
    return state->last_tab->glyphs[0].geometry.width - (state->last_tab_pos - state->last_tab_width);
 
3960
 
 
3961
  return 0;
 
3962
}
 
3963
 
3803
3964
/* Tries to insert as much as possible of the item at the head of
3804
3965
 * state->items onto @line. Five results are possible:
3805
3966
 *
3915
4076
  if (state->remaining_width < 0 && !no_break_at_end)  /* Wrapping off */
3916
4077
    {
3917
4078
      insert_run (line, state, item, NULL, TRUE);
3918
 
 
3919
4079
      DEBUG1 ("no wrapping, all-fit");
3920
4080
      return BREAK_ALL_FIT;
3921
4081
    }
3931
4091
        width += state->log_widths[state->log_widths_offset + i];
3932
4092
    }
3933
4093
 
 
4094
  if (layout->text[item->offset] == '\t')
 
4095
    {
 
4096
      insert_run (line, state, item, NULL, TRUE);
 
4097
      state->remaining_width -= width;
 
4098
      state->remaining_width = MAX (state->remaining_width, 0);
 
4099
 
 
4100
      DEBUG1 ("tab run, all-fit");
 
4101
      return BREAK_ALL_FIT;
 
4102
    }
 
4103
 
3934
4104
  if (!no_break_at_end &&
3935
4105
      can_break_at (layout, state->start_offset + item->num_chars, wrap))
3936
4106
    {
3945
4115
  else
3946
4116
    extra_width = 0;
3947
4117
 
3948
 
  if ((width + extra_width <= state->remaining_width || (item->num_chars == 1 && !line->runs)) &&
 
4118
  if ((width + extra_width <= state->remaining_width || (item->num_chars == 1 && !line->runs) ||
 
4119
      (state->last_tab && state->last_tab_align != PANGO_TAB_LEFT)) &&
3949
4120
      !no_break_at_end)
3950
4121
    {
 
4122
      PangoGlyphString *glyphs;
 
4123
 
3951
4124
      DEBUG1 ("%d + %d <= %d", width, extra_width, state->remaining_width);
3952
 
      insert_run (line, state, item, NULL, FALSE);
 
4125
      glyphs = shape_run (line, state, item);
3953
4126
 
3954
 
      width = pango_glyph_string_get_width (((PangoGlyphItem *)(line->runs->data))->glyphs);
 
4127
      width = pango_glyph_string_get_width (glyphs) + tab_width_change (state);
3955
4128
 
3956
4129
      if (width + extra_width <= state->remaining_width || (item->num_chars == 1 && !line->runs))
3957
4130
        {
 
4131
          insert_run (line, state, item, glyphs, TRUE);
 
4132
 
3958
4133
          state->remaining_width -= width;
3959
4134
          state->remaining_width = MAX (state->remaining_width, 0);
3960
4135
 
3961
 
          /* We passed last_run == FALSE to insert_run, so it did not do this */
3962
 
          pango_glyph_string_free (state->glyphs);
3963
 
          state->glyphs = NULL;
3964
 
 
3965
4136
          DEBUG1 ("early accept '%.*s', all-fit, remaining %d",
3966
4137
                  item->length, layout->text + item->offset,
3967
4138
                  state->remaining_width);
3968
4139
          return BREAK_ALL_FIT;
3969
4140
        }
3970
4141
 
3971
 
      /* if it doesn't fit after shaping, revert and proceed to break the item */
3972
 
      uninsert_run (line);
 
4142
      /* if it doesn't fit after shaping, discard and proceed to break the item */
 
4143
      pango_glyph_string_free (glyphs);
3973
4144
    }
3974
4145
 
3975
4146
  /*** From here on, we look for a way to break item ***/
4054
4225
 
4055
4226
              glyphs = shape_run (line, state, new_item);
4056
4227
 
4057
 
              new_break_width = pango_glyph_string_get_width (glyphs);
 
4228
              new_break_width = pango_glyph_string_get_width (glyphs) + tab_width_change (state);
4058
4229
 
4059
4230
              if (num_chars > 0 &&
4060
4231
                  layout->log_attrs[state->start_offset + num_chars - 1].is_white)
4315
4486
    state->remaining_width = -1;
4316
4487
  else
4317
4488
    state->remaining_width = state->line_width;
 
4489
 
 
4490
  state->last_tab = NULL;
 
4491
  state->last_tab_align = PANGO_TAB_LEFT;
 
4492
 
4318
4493
  DEBUG ("starting to fill line", line, state);
4319
4494
 
4320
4495
  while (state->items)