summaryrefslogtreecommitdiff
path: root/pango2
diff options
context:
space:
mode:
Diffstat (limited to 'pango2')
-rw-r--r--pango2/break-arabic.c91
-rw-r--r--pango2/break-indic.c212
-rw-r--r--pango2/break-latin.c60
-rw-r--r--pango2/break-thai.c124
-rw-r--r--pango2/break.c2359
-rw-r--r--pango2/ellipsize.c797
-rw-r--r--pango2/emoji_presentation_scanner.c280
-rw-r--r--pango2/emoji_presentation_scanner.rl99
-rw-r--r--pango2/glyphstring.c793
-rw-r--r--pango2/itemize.c1634
-rw-r--r--pango2/json/gtkjsonparser.c1737
-rw-r--r--pango2/json/gtkjsonparserprivate.h99
-rw-r--r--pango2/json/gtkjsonprinter.c405
-rw-r--r--pango2/json/gtkjsonprinterprivate.h79
-rw-r--r--pango2/meson.build298
-rw-r--r--pango2/pango-attr-iterator-private.h39
-rw-r--r--pango2/pango-attr-iterator.c509
-rw-r--r--pango2/pango-attr-iterator.h66
-rw-r--r--pango2/pango-attr-list-private.h36
-rw-r--r--pango2/pango-attr-list.c1286
-rw-r--r--pango2/pango-attr-list.h112
-rw-r--r--pango2/pango-attr-private.h41
-rw-r--r--pango2/pango-attr.c761
-rw-r--r--pango2/pango-attr.h245
-rw-r--r--pango2/pango-attributes-private.h36
-rw-r--r--pango2/pango-attributes.c866
-rw-r--r--pango2/pango-attributes.h318
-rw-r--r--pango2/pango-bidi-private.h32
-rw-r--r--pango2/pango-bidi.c223
-rw-r--r--pango2/pango-break-table.h864
-rw-r--r--pango2/pango-break.h131
-rw-r--r--pango2/pango-color-table.h1349
-rw-r--r--pango2/pango-color.c337
-rw-r--r--pango2/pango-color.h71
-rw-r--r--pango2/pango-context-private.h58
-rw-r--r--pango2/pango-context.c1068
-rw-r--r--pango2/pango-context.h102
-rw-r--r--pango2/pango-direction.h57
-rw-r--r--pango2/pango-emoji-private.h54
-rw-r--r--pango2/pango-emoji-table.h407
-rw-r--r--pango2/pango-emoji.c290
-rw-r--r--pango2/pango-enum-types.c.template38
-rw-r--r--pango2/pango-enum-types.h.template25
-rw-r--r--pango2/pango-features.h.meson9
-rw-r--r--pango2/pango-font-description-private.h43
-rw-r--r--pango2/pango-font-description.c1743
-rw-r--r--pango2/pango-font-description.h324
-rw-r--r--pango2/pango-font-face-private.h85
-rw-r--r--pango2/pango-font-face.c543
-rw-r--r--pango2/pango-font-face.h61
-rw-r--r--pango2/pango-font-family-private.h66
-rw-r--r--pango2/pango-font-family.c267
-rw-r--r--pango2/pango-font-family.h44
-rw-r--r--pango2/pango-font-metrics-private.h40
-rw-r--r--pango2/pango-font-metrics.c249
-rw-r--r--pango2/pango-font-metrics.h78
-rw-r--r--pango2/pango-font-private.h128
-rw-r--r--pango2/pango-font.c478
-rw-r--r--pango2/pango-font.h74
-rw-r--r--pango2/pango-fontmap-private.h74
-rw-r--r--pango2/pango-fontmap.c1294
-rw-r--r--pango2/pango-fontmap.h93
-rw-r--r--pango2/pango-fontset-cached-private.h65
-rw-r--r--pango2/pango-fontset-cached.c347
-rw-r--r--pango2/pango-fontset-private.h51
-rw-r--r--pango2/pango-fontset.c212
-rw-r--r--pango2/pango-fontset.h60
-rw-r--r--pango2/pango-generic-family-private.h38
-rw-r--r--pango2/pango-generic-family.c226
-rw-r--r--pango2/pango-generic-family.h38
-rw-r--r--pango2/pango-glyph-item-private.h88
-rw-r--r--pango2/pango-glyph-item.c837
-rw-r--r--pango2/pango-glyph-iter-private.h110
-rw-r--r--pango2/pango-glyph.h297
-rw-r--r--pango2/pango-gravity.c457
-rw-r--r--pango2/pango-gravity.h125
-rw-r--r--pango2/pango-hbface-private.h52
-rw-r--r--pango2/pango-hbface.c1087
-rw-r--r--pango2/pango-hbface.h83
-rw-r--r--pango2/pango-hbfamily-private.h48
-rw-r--r--pango2/pango-hbfamily.c332
-rw-r--r--pango2/pango-hbfont-private.h56
-rw-r--r--pango2/pango-hbfont.c1097
-rw-r--r--pango2/pango-hbfont.h59
-rw-r--r--pango2/pango-impl-utils.h240
-rw-r--r--pango2/pango-item-private.h129
-rw-r--r--pango2/pango-item.c652
-rw-r--r--pango2/pango-item.h99
-rw-r--r--pango2/pango-language-sample-table.h675
-rw-r--r--pango2/pango-language-set-private.h44
-rw-r--r--pango2/pango-language-set-simple-private.h32
-rw-r--r--pango2/pango-language-set-simple.c125
-rw-r--r--pango2/pango-language-set.c87
-rw-r--r--pango2/pango-language.c1029
-rw-r--r--pango2/pango-language.h65
-rw-r--r--pango2/pango-layout.c1734
-rw-r--r--pango2/pango-layout.h219
-rw-r--r--pango2/pango-line-breaker.c2663
-rw-r--r--pango2/pango-line-breaker.h76
-rw-r--r--pango2/pango-line-iter-private.h25
-rw-r--r--pango2/pango-line-iter.c881
-rw-r--r--pango2/pango-line-iter.h102
-rw-r--r--pango2/pango-line-private.h87
-rw-r--r--pango2/pango-line.c1603
-rw-r--r--pango2/pango-line.h126
-rw-r--r--pango2/pango-lines-private.h38
-rw-r--r--pango2/pango-lines.c1332
-rw-r--r--pango2/pango-lines.h154
-rw-r--r--pango2/pango-markup.c2016
-rw-r--r--pango2/pango-markup.h47
-rw-r--r--pango2/pango-matrix.c528
-rw-r--r--pango2/pango-matrix.h129
-rw-r--r--pango2/pango-renderer.c1731
-rw-r--r--pango2/pango-renderer.h273
-rw-r--r--pango2/pango-run-private.h38
-rw-r--r--pango2/pango-run.c271
-rw-r--r--pango2/pango-run.h37
-rw-r--r--pango2/pango-script-lang-table.h261
-rw-r--r--pango2/pango-script-private.h52
-rw-r--r--pango2/pango-script.c373
-rw-r--r--pango2/pango-script.h58
-rw-r--r--pango2/pango-tabs.c617
-rw-r--r--pango2/pango-tabs.h113
-rw-r--r--pango2/pango-trace-private.h49
-rw-r--r--pango2/pango-trace.c40
-rw-r--r--pango2/pango-types.h361
-rw-r--r--pango2/pango-userface-private.h40
-rw-r--r--pango2/pango-userface.c481
-rw-r--r--pango2/pango-userface.h81
-rw-r--r--pango2/pango-userfont-private.h30
-rw-r--r--pango2/pango-userfont.c442
-rw-r--r--pango2/pango-userfont.h46
-rw-r--r--pango2/pango-utils.c377
-rw-r--r--pango2/pango-utils.h116
-rw-r--r--pango2/pango-version-macros.h157
-rw-r--r--pango2/pango.h61
-rw-r--r--pango2/pango.rc.in30
-rw-r--r--pango2/pangocairo-context.c274
-rw-r--r--pango2/pangocairo-context.h44
-rw-r--r--pango2/pangocairo-dwrite-font.cpp53
-rw-r--r--pango2/pangocairo-font.c796
-rw-r--r--pango2/pangocairo-font.h34
-rw-r--r--pango2/pangocairo-private.h74
-rw-r--r--pango2/pangocairo-render.c1255
-rw-r--r--pango2/pangocairo-render.h63
-rw-r--r--pango2/pangocairo.h24
-rw-r--r--pango2/pangocoretext-fontmap.c460
-rw-r--r--pango2/pangocoretext-fontmap.h35
-rw-r--r--pango2/pangodwrite-fontmap.cpp392
-rw-r--r--pango2/pangodwrite-fontmap.h40
-rw-r--r--pango2/pangofc-fontmap.c624
-rw-r--r--pango2/pangofc-fontmap.h42
-rw-r--r--pango2/pangofc-language-set-private.h46
-rw-r--r--pango2/pangofc-language-set.c142
-rw-r--r--pango2/serializer.c1803
-rw-r--r--pango2/shape.c868
156 files changed, 58087 insertions, 0 deletions
diff --git a/pango2/break-arabic.c b/pango2/break-arabic.c
new file mode 100644
index 00000000..2368586b
--- /dev/null
+++ b/pango2/break-arabic.c
@@ -0,0 +1,91 @@
+/* Pango2
+ * break-arabic.c:
+ *
+ * Copyright (C) 2006 Red Hat Software
+ * Copyright (C) 2006 Sharif FarsiWeb, Inc.
+ * Authors: Behdad Esfahbod <besfahbo@redhat.com>
+ * Roozbeh Pournader <roozbeh@farsiweb.info>
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "pango-break.h"
+
+#define ALEF_WITH_MADDA_ABOVE 0x0622
+#define YEH_WITH_HAMZA_ABOVE 0x0626
+#define ALEF 0x0627
+#define WAW 0x0648
+#define YEH 0x064A
+
+#define MADDAH_ABOVE 0x0653
+#define HAMZA_ABOVE 0x0654
+#define HAMZA_BELOW 0x0655
+
+/*
+ * Arabic characters with canonical decompositions that are not just
+ * ligatures. The characters U+06C0, U+06C2, and U+06D3 are intentionally
+ * excluded as they are marked as "not an independent letter" in Unicode
+ * Character Database's NamesList.txt
+ */
+#define IS_COMPOSITE(c) (ALEF_WITH_MADDA_ABOVE <= (c) && (c) <= YEH_WITH_HAMZA_ABOVE)
+
+/* If a character is the second part of a composite Arabic character with an Alef */
+#define IS_COMPOSITE_WITH_ALEF(c) (MADDAH_ABOVE <= (c) && (c) <= HAMZA_BELOW)
+
+static void
+break_arabic (const char *text,
+ int length,
+ const Pango2Analysis *analysis G_GNUC_UNUSED,
+ Pango2LogAttr *attrs,
+ int attrs_len G_GNUC_UNUSED)
+{
+ int i;
+ const char *p;
+ gunichar prev_wc, this_wc;
+
+ /* See http://bugzilla.gnome.org/show_bug.cgi?id=350132 for issues this
+ * module tries to solve.
+ */
+
+ for (p = text, i = 0, prev_wc = 0;
+ p < text + length;
+ p = g_utf8_next_char (p), i++, prev_wc = this_wc)
+ {
+ this_wc = g_utf8_get_char (p);
+
+ /*
+ * Unset backspace_deletes_character for various combinations.
+ *
+ * A few more combinations may need to be handled here, but are not
+ * handled yet, as expectations of users is not known or may differ
+ * among different languages or users:
+ * some letters combined with U+0658 ARABIC MARK NOON GHUNNA;
+ * combinations considered one letter in Azerbaijani (WAW+SUKUN and
+ * FARSI_YEH+HAMZA_ABOVE); combinations of YEH and ALEF_MAKSURA with
+ * HAMZA_BELOW (Qur'anic); TATWEEL+HAMZA_ABOVE (Qur'anic).
+ *
+ * FIXME: Ordering these in some other way may lower the time spent here, or not.
+ */
+ if (G_UNLIKELY (
+ IS_COMPOSITE (this_wc) ||
+ (prev_wc == ALEF && IS_COMPOSITE_WITH_ALEF (this_wc)) ||
+ (this_wc == HAMZA_ABOVE && (prev_wc == WAW || prev_wc == YEH))
+ ))
+ attrs[i+1].backspace_deletes_character = FALSE;
+ }
+}
diff --git a/pango2/break-indic.c b/pango2/break-indic.c
new file mode 100644
index 00000000..62ecdaab
--- /dev/null
+++ b/pango2/break-indic.c
@@ -0,0 +1,212 @@
+/* Pango2
+ * break-indic.c:
+ *
+ * Copyright (C) 2006 Red Hat Software
+ * Author: Akira TAGOH <tagoh@redhat.com>
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "pango-break.h"
+#include "pango-item-private.h"
+
+#define DEV_RRA 0x0931 /* 0930 + 093c */
+#define DEV_QA 0x0958 /* 0915 + 093c */
+#define DEV_YA 0x095F /* 092f + 003c */
+#define DEV_KHHA 0x0959
+#define DEV_GHHA 0x095A
+#define DEV_ZA 0x095B
+#define DEV_DDDHA 0x095C
+#define DEV_RHA 0x095D
+#define DEV_FA 0x095E
+#define DEV_YYA 0x095F
+
+/* Bengali */
+/* for split matras in all brahmi based script */
+#define BENGALI_SIGN_O 0x09CB /* 09c7 + 09be */
+#define BENGALI_SIGN_AU 0x09CC /* 09c7 + 09d7 */
+#define BENGALI_RRA 0x09DC
+#define BENGALI_RHA 0x09DD
+#define BENGALI_YYA 0x09DF
+
+/* Gurumukhi */
+#define GURUMUKHI_LLA 0x0A33
+#define GURUMUKHI_SHA 0x0A36
+#define GURUMUKHI_KHHA 0x0A59
+#define GURUMUKHI_GHHA 0x0A5A
+#define GURUMUKHI_ZA 0x0A5B
+#define GURUMUKHI_RRA 0x0A5C
+#define GURUMUKHI_FA 0x0A5E
+
+/* Oriya */
+#define ORIYA_AI 0x0B48
+#define ORIYA_O 0x0B4B
+#define ORIYA_AU 0x0B4C
+
+/* Telugu */
+#define TELUGU_EE 0x0C47
+#define TELUGU_AI 0x0C48
+
+/* Tamil */
+#define TAMIL_O 0x0BCA
+#define TAMIL_OO 0x0BCB
+#define TAMIL_AU 0x0BCC
+
+/* Kannada */
+#define KNDA_EE 0x0CC7
+#define KNDA_AI 0x0CC8
+#define KNDA_O 0x0CCA
+#define KNDA_OO 0x0CCB
+
+/* Malayalam */
+#define MLYM_O 0x0D4A
+#define MLYM_OO 0x0D4B
+#define MLYM_AU 0x0D4C
+
+#define IS_COMPOSITE_WITH_BRAHMI_NUKTA(c) ( \
+ (c >= BENGALI_RRA && c <= BENGALI_YYA) || \
+ (c >= DEV_QA && c <= DEV_YA) || (c == DEV_RRA) || (c >= DEV_KHHA && c <= DEV_YYA) || \
+ (c >= KNDA_EE && c <= KNDA_AI) ||(c >= KNDA_O && c <= KNDA_OO) || \
+ (c == TAMIL_O) || (c == TAMIL_OO) || (c == TAMIL_AU) || \
+ (c == TELUGU_EE) || (c == TELUGU_AI) || \
+ (c == ORIYA_AI) || (c == ORIYA_O) || (c == ORIYA_AU) || \
+ (c >= GURUMUKHI_KHHA && c <= GURUMUKHI_RRA) || (c == GURUMUKHI_FA)|| (c == GURUMUKHI_LLA)|| (c == GURUMUKHI_SHA) || \
+ FALSE)
+#define IS_SPLIT_MATRA_BRAHMI(c) ( \
+ (c == BENGALI_SIGN_O) || (c == BENGALI_SIGN_AU) || \
+ (c >= MLYM_O && c <= MLYM_AU) || \
+ FALSE)
+
+static void
+not_cursor_position (Pango2LogAttr *attr)
+{
+ if (!attr->is_mandatory_break)
+ {
+ attr->is_cursor_position = FALSE;
+ attr->is_char_break = FALSE;
+ attr->is_line_break = FALSE;
+ attr->is_mandatory_break = FALSE;
+ }
+}
+
+static void
+break_indic (const char *text,
+ int length,
+ const Pango2Analysis *analysis,
+ Pango2LogAttr *attrs,
+ int attrs_len G_GNUC_UNUSED)
+{
+ const char *p, *next = NULL, *next_next;
+ gunichar prev_wc, this_wc, next_wc, next_next_wc;
+ gboolean is_conjunct = FALSE;
+ int i;
+
+ for (p = text, prev_wc = 0, i = 0;
+ p != NULL && p < (text + length);
+ p = next, prev_wc = this_wc, i++)
+ {
+ this_wc = g_utf8_get_char (p);
+ next = g_utf8_next_char (p);
+
+ if (G_UNLIKELY (
+ IS_COMPOSITE_WITH_BRAHMI_NUKTA(this_wc) || IS_SPLIT_MATRA_BRAHMI(this_wc))) {
+ attrs[i+1].backspace_deletes_character = FALSE;
+ }
+
+ if (next != NULL && next < (text + length))
+ {
+ next_wc = g_utf8_get_char (next);
+ next_next = g_utf8_next_char (next);
+ }
+ else
+ {
+ next_wc = 0;
+ next_next = NULL;
+ }
+ if (next_next != NULL && next_next < (text + length))
+ next_next_wc = g_utf8_get_char (next_next);
+ else
+ next_next_wc = 0;
+
+ switch (analysis->script)
+ {
+ case G_UNICODE_SCRIPT_SINHALA:
+ /*
+ * TODO: The cursor position should be based on the state table.
+ * This is the wrong place to be doing this.
+ */
+
+ /*
+ * The cursor should treat as a single glyph:
+ * SINHALA CONS + 0x0DCA + 0x200D + SINHALA CONS
+ * SINHALA CONS + 0x200D + 0x0DCA + SINHALA CONS
+ */
+ if ((this_wc == 0x0DCA && next_wc == 0x200D)
+ || (this_wc == 0x200D && next_wc == 0x0DCA))
+ {
+ not_cursor_position(&attrs[i]);
+ not_cursor_position(&attrs[i + 1]);
+ is_conjunct = TRUE;
+ }
+ else if (is_conjunct
+ && (prev_wc == 0x200D || prev_wc == 0x0DCA)
+ && this_wc >= 0x0D9A
+ && this_wc <= 0x0DC6)
+ {
+ not_cursor_position(&attrs[i]);
+ is_conjunct = FALSE;
+ }
+ /*
+ * Consonant clusters do NOT result in implicit conjuncts
+ * in SINHALA orthography.
+ */
+ else if (!is_conjunct && prev_wc == 0x0DCA && this_wc != 0x200D)
+ {
+ attrs[i].is_cursor_position = TRUE;
+ }
+
+ break;
+
+ default:
+
+ if (prev_wc != 0 && (this_wc == 0x200D || this_wc == 0x200C))
+ {
+ not_cursor_position(&attrs[i]);
+ if (next_wc != 0)
+ {
+ not_cursor_position(&attrs[i+1]);
+ if ((next_next_wc != 0) &&
+ (next_wc == 0x09CD || /* Bengali */
+ next_wc == 0x0ACD || /* Gujarati */
+ next_wc == 0x094D || /* Hindi */
+ next_wc == 0x0CCD || /* Kannada */
+ next_wc == 0x0D4D || /* Malayalam */
+ next_wc == 0x0B4D || /* Oriya */
+ next_wc == 0x0A4D || /* Punjabi */
+ next_wc == 0x0BCD || /* Tamil */
+ next_wc == 0x0C4D)) /* Telugu */
+ {
+ not_cursor_position(&attrs[i+2]);
+ }
+ }
+ }
+
+ break;
+ }
+ }
+}
diff --git a/pango2/break-latin.c b/pango2/break-latin.c
new file mode 100644
index 00000000..ffbc8f9e
--- /dev/null
+++ b/pango2/break-latin.c
@@ -0,0 +1,60 @@
+/* Pango2
+ * break-latin.c:
+ *
+ * Copyright (C) 2021 Jordi Mas i Hernàndez <jmas@softcatala.org>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "pango-break.h"
+#include "pango-item-private.h"
+#include "pango-impl-utils.h"
+
+static void
+break_latin (const char *text,
+ int length,
+ const Pango2Analysis *analysis,
+ Pango2LogAttr *attrs,
+ int attrs_len G_GNUC_UNUSED)
+{
+ int i;
+ const char *p, *next;
+ gunichar wc, prev_wc;
+
+ if (!analysis || !analysis->language ||
+ g_ascii_strncasecmp (pango2_language_to_string (analysis->language), "ca-", 3) != 0)
+ return;
+
+ for (p = text, i = 0, prev_wc = 0;
+ p < text + length;
+ p = next, i++, prev_wc = wc)
+ {
+ wc = g_utf8_get_char (p);
+ next = g_utf8_next_char (p);
+
+ /* Catalan middle dot does not break words */
+ if (wc == 0x00b7)
+ {
+ gunichar middle_next = g_utf8_get_char (next);
+ if (g_unichar_tolower (middle_next) == 'l' && g_unichar_tolower (prev_wc) == 'l')
+ {
+ attrs[i].is_word_end = FALSE;
+ attrs[i+1].is_word_start = FALSE;
+ }
+ }
+ }
+}
diff --git a/pango2/break-thai.c b/pango2/break-thai.c
new file mode 100644
index 00000000..ce38cfc1
--- /dev/null
+++ b/pango2/break-thai.c
@@ -0,0 +1,124 @@
+/* Pango2
+ * break-thai.c:
+ *
+ * Copyright (C) 2003 Theppitak Karoonboonyanan <thep@linux.thai.net>
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "pango-break.h"
+#include "pango-impl-utils.h"
+
+#ifdef HAVE_LIBTHAI
+#include <thai/thwchar.h>
+#include <thai/thbrk.h>
+
+G_LOCK_DEFINE_STATIC (thai_brk);
+
+#ifdef HAVE_TH_BRK_FIND_BREAKS
+static ThBrk *thai_brk = NULL;
+#endif
+
+/*
+ * tis_text is assumed to be large enough to hold the converted string,
+ * i.e. it must be at least pango2_utf8_strlen(text, len)+1 bytes.
+ */
+static thchar_t *
+utf8_to_tis (const char *text, int len, thchar_t *tis_text, int *tis_cnt)
+{
+ thchar_t *tis_p;
+ const char *text_p;
+
+ tis_p = tis_text;
+ for (text_p = text; text_p < text + len; text_p = g_utf8_next_char (text_p))
+ *tis_p++ = th_uni2tis (g_utf8_get_char (text_p));
+ *tis_p++ = '\0';
+
+ *tis_cnt = tis_p - tis_text;
+ return tis_text;
+}
+
+#endif
+static void
+break_thai (const char *text,
+ int len,
+ const Pango2Analysis *analysis G_GNUC_UNUSED,
+ Pango2LogAttr *attrs,
+ int attrs_len G_GNUC_UNUSED)
+{
+#ifdef HAVE_LIBTHAI
+ thchar_t tis_stack[512];
+ int brk_stack[512];
+ thchar_t *tis_text;
+ int *brk_pnts;
+ int cnt;
+
+ cnt = pango2_utf8_strlen (text, len) + 1;
+
+ tis_text = tis_stack;
+ if (cnt > (int) G_N_ELEMENTS (tis_stack))
+ tis_text = g_new (thchar_t, cnt);
+
+ utf8_to_tis (text, len, tis_text, &cnt);
+
+ brk_pnts = brk_stack;
+ if (cnt > (int) G_N_ELEMENTS (brk_stack))
+ brk_pnts = g_new (int, cnt);
+
+ /* find line break positions */
+
+ G_LOCK (thai_brk);
+#ifdef HAVE_TH_BRK_FIND_BREAKS
+ if (thai_brk == NULL)
+ thai_brk = th_brk_new(NULL);
+ len = th_brk_find_breaks(thai_brk, tis_text, brk_pnts, cnt);
+#else
+ len = th_brk (tis_text, brk_pnts, cnt);
+#endif
+ G_UNLOCK (thai_brk);
+
+ for (cnt = 0; cnt < len; cnt++)
+ {
+ if (!attrs[brk_pnts[cnt]].is_line_break)
+ {
+ /* Insert line breaks where there wasn't one.
+ * Satisfy invariants by marking it as char break too.
+ */
+ attrs[brk_pnts[cnt]].is_char_break = TRUE;
+ attrs[brk_pnts[cnt]].is_line_break = TRUE;
+ }
+ if (!(attrs[brk_pnts[cnt]].is_word_start ||
+ attrs[brk_pnts[cnt]].is_word_end))
+ {
+ /* If we find a break in the middle of a sequence
+ * of characters, end and start a word. We must
+ * be careful only to do that if default_break
+ * did not already find a word start or end,
+ * otherwise we mess up the sequence.
+ */
+ attrs[brk_pnts[cnt]].is_word_start = TRUE;
+ attrs[brk_pnts[cnt]].is_word_end = TRUE;
+ }
+ }
+
+ if (brk_pnts != brk_stack)
+ g_free (brk_pnts);
+
+ if (tis_text != tis_stack)
+ g_free (tis_text);
+#endif
+}
diff --git a/pango2/break.c b/pango2/break.c
new file mode 100644
index 00000000..404f3058
--- /dev/null
+++ b/pango2/break.c
@@ -0,0 +1,2359 @@
+/* Pango2
+ * break.c:
+ *
+ * Copyright (C) 1999 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.
+ */
+
+#include "config.h"
+
+#include "pango-break.h"
+#include "pango-script-private.h"
+#include "pango-emoji-private.h"
+#include "pango-attributes.h"
+#include "pango-attr-private.h"
+#include "pango-attr-list-private.h"
+#include "pango-attr-iterator-private.h"
+#include "pango-break-table.h"
+#include "pango-item-private.h"
+#include "pango-impl-utils.h"
+#include <string.h>
+
+/* {{{ Unicode line breaking and segmentation */
+
+#define PARAGRAPH_SEPARATOR 0x2029
+
+/* See http://www.unicode.org/unicode/reports/tr14/ if you hope
+ * to understand the line breaking code.
+ */
+
+typedef enum
+{
+ BREAK_ALREADY_HANDLED, /* didn't use the table */
+ BREAK_PROHIBITED, /* no break, even if spaces intervene */
+ BREAK_IF_SPACES, /* "indirect break" (only if there are spaces) */
+ BREAK_ALLOWED /* "direct break" (can always break here) */
+ /* TR 14 has two more break-opportunity classes,
+ * "indirect break opportunity for combining marks following a space"
+ * and "prohibited break for combining marks"
+ * but we handle that inline in the code.
+ */
+} BreakOpportunity;
+
+/* need to sync the break range to glib/gunicode.h . */
+#define BREAK_TYPE_SAFE(btype) \
+ ((btype) <= G_UNICODE_BREAK_ZERO_WIDTH_JOINER ? (btype) : G_UNICODE_BREAK_UNKNOWN)
+
+
+/*
+ * Hangul Conjoining Jamo handling.
+ *
+ * The way we implement it is just a bit different from TR14,
+ * but produces the same results.
+ * The same algorithm is also used in TR29 for cluster boundaries.
+ *
+ */
+
+
+/* An enum that works as the states of the Hangul syllables system.
+ **/
+typedef enum
+{
+ JAMO_L, /* G_UNICODE_BREAK_HANGUL_L_JAMO */
+ JAMO_V, /* G_UNICODE_BREAK_HANGUL_V_JAMO */
+ JAMO_T, /* G_UNICODE_BREAK_HANGUL_T_JAMO */
+ JAMO_LV, /* G_UNICODE_BREAK_HANGUL_LV_SYLLABLE */
+ JAMO_LVT, /* G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE */
+ NO_JAMO /* Other */
+} JamoType;
+
+/* There are Hangul syllables encoded as characters, that act like a
+ * sequence of Jamos. For each character we define a JamoType
+ * that the character starts with, and one that it ends with. This
+ * decomposes JAMO_LV and JAMO_LVT to simple other JAMOs. So for
+ * example, a character with LineBreak type
+ * G_UNICODE_BREAK_HANGUL_LV_SYLLABLE has start=JAMO_L and end=JAMO_V.
+ */
+typedef struct _CharJamoProps
+{
+ JamoType start, end;
+} CharJamoProps;
+
+/* Map from JamoType to CharJamoProps that hold only simple
+ * JamoTypes (no LV or LVT) or none.
+ */
+static const CharJamoProps HangulJamoProps[] = {
+ {JAMO_L, JAMO_L}, /* JAMO_L */
+ {JAMO_V, JAMO_V}, /* JAMO_V */
+ {JAMO_T, JAMO_T}, /* JAMO_T */
+ {JAMO_L, JAMO_V}, /* JAMO_LV */
+ {JAMO_L, JAMO_T}, /* JAMO_LVT */
+ {NO_JAMO, NO_JAMO} /* NO_JAMO */
+};
+
+/* A character forms a syllable with the previous character if and only if:
+ * JamoType(this) is not NO_JAMO and:
+ *
+ * HangulJamoProps[JamoType(prev)].end and
+ * HangulJamoProps[JamoType(this)].start are equal,
+ * or the former is one less than the latter.
+ */
+
+#define IS_JAMO(btype) \
+ ((btype >= G_UNICODE_BREAK_HANGUL_L_JAMO) && \
+ (btype <= G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE))
+#define JAMO_TYPE(btype) \
+ (IS_JAMO(btype) ? (btype - G_UNICODE_BREAK_HANGUL_L_JAMO) : NO_JAMO)
+
+/* Types of Japanese characters */
+#define JAPANESE(wc) ((wc) >= 0x2F00 && (wc) <= 0x30FF)
+#define KANJI(wc) ((wc) >= 0x2F00 && (wc) <= 0x2FDF)
+#define HIRAGANA(wc) ((wc) >= 0x3040 && (wc) <= 0x309F)
+#define KATAKANA(wc) ((wc) >= 0x30A0 && (wc) <= 0x30FF)
+
+#define LATIN(wc) (((wc) >= 0x0020 && (wc) <= 0x02AF) || ((wc) >= 0x1E00 && (wc) <= 0x1EFF))
+#define CYRILLIC(wc) (((wc) >= 0x0400 && (wc) <= 0x052F))
+#define GREEK(wc) (((wc) >= 0x0370 && (wc) <= 0x3FF) || ((wc) >= 0x1F00 && (wc) <= 0x1FFF))
+#define KANA(wc) ((wc) >= 0x3040 && (wc) <= 0x30FF)
+#define HANGUL(wc) ((wc) >= 0xAC00 && (wc) <= 0xD7A3)
+#define EMOJI(wc) (_pango2_Is_Emoji_Base_Character (wc))
+#define BACKSPACE_DELETES_CHARACTER(wc) (!LATIN (wc) && !CYRILLIC (wc) && !GREEK (wc) && !KANA (wc) && !HANGUL (wc) && !EMOJI (wc))
+
+/* Previously "123foo" was two words. But in UAX 29 of Unicode,
+ * we know don't break words between consecutive letters and numbers
+ */
+typedef enum
+{
+ WordNone,
+ WordLetters,
+ WordNumbers
+} WordType;
+
+static void
+default_break (const char *text,
+ int length,
+ Pango2LogAttr *attrs,
+ int attrs_len G_GNUC_UNUSED)
+{
+ /* The rationale for all this is in section 5.15 of the Unicode 3.0 book,
+ * the line breaking stuff is also in TR14 on unicode.org
+ */
+
+ /* This is a default break implementation that should work for nearly all
+ * languages. Language engines can override it optionally.
+ */
+
+ /* FIXME one cheesy optimization here would be to memset attrs to 0
+ * before we start, and then never assign %FALSE to anything
+ */
+
+ const char *next;
+ int i;
+
+ gunichar prev_wc;
+ gunichar next_wc;
+
+ JamoType prev_jamo;
+
+ GUnicodeBreakType next_break_type;
+ GUnicodeBreakType prev_break_type;
+ GUnicodeBreakType prev_prev_break_type;
+
+ GUnicodeScript prev_script;
+
+ /* See Grapheme_Cluster_Break Property Values table of UAX#29 */
+ typedef enum
+ {
+ GB_Other,
+ GB_ControlCRLF,
+ GB_Extend,
+ GB_ZWJ,
+ GB_Prepend,
+ GB_SpacingMark,
+ GB_InHangulSyllable, /* Handles all of L, V, T, LV, LVT rules */
+ /* Use state machine to handle emoji sequence */
+ /* Rule GB12 and GB13 */
+ GB_RI_Odd, /* Meets odd number of RI */
+ GB_RI_Even, /* Meets even number of RI */
+ } GraphemeBreakType;
+ GraphemeBreakType prev_GB_type = GB_Other;
+ gboolean met_Extended_Pictographic = FALSE;
+
+ /* See Word_Break Property Values table of UAX#29 */
+ typedef enum
+ {
+ WB_Other,
+ WB_NewlineCRLF,
+ WB_ExtendFormat,
+ WB_Katakana,
+ WB_Hebrew_Letter,
+ WB_ALetter,
+ WB_MidNumLet,
+ WB_MidLetter,
+ WB_MidNum,
+ WB_Numeric,
+ WB_ExtendNumLet,
+ WB_RI_Odd,
+ WB_RI_Even,
+ WB_WSegSpace,
+ } WordBreakType;
+ WordBreakType prev_prev_WB_type = WB_Other, prev_WB_type = WB_Other;
+ int prev_WB_i = -1;
+
+ /* See Sentence_Break Property Values table of UAX#29 */
+ typedef enum
+ {
+ SB_Other,
+ SB_ExtendFormat,
+ SB_ParaSep,
+ SB_Sp,
+ SB_Lower,
+ SB_Upper,
+ SB_OLetter,
+ SB_Numeric,
+ SB_ATerm,
+ SB_SContinue,
+ SB_STerm,
+ SB_Close,
+ /* Rules SB8 and SB8a */
+ SB_ATerm_Close_Sp,
+ SB_STerm_Close_Sp,
+ } SentenceBreakType;
+ SentenceBreakType prev_prev_SB_type = SB_Other, prev_SB_type = SB_Other;
+ int prev_SB_i = -1;
+
+ /* Rule LB25 with Example 7 of Customization */
+ typedef enum
+ {
+ LB_Other,
+ LB_Numeric,
+ LB_Numeric_Close,
+ LB_RI_Odd,
+ LB_RI_Even,
+ } LineBreakType;
+ LineBreakType prev_LB_type = LB_Other;
+
+ WordType current_word_type = WordNone;
+ gunichar last_word_letter = 0;
+ gunichar base_character = 0;
+
+ int last_sentence_start = -1;
+ int last_non_space = -1;
+
+ gboolean prev_space_or_hyphen;
+
+ gboolean almost_done = FALSE;
+ gboolean done = FALSE;
+
+ g_return_if_fail (length == 0 || text != NULL);
+ g_return_if_fail (attrs != NULL);
+
+ next = text;
+
+ prev_break_type = G_UNICODE_BREAK_UNKNOWN;
+ prev_prev_break_type = G_UNICODE_BREAK_UNKNOWN;
+ prev_wc = 0;
+ prev_script = G_UNICODE_SCRIPT_COMMON;
+ prev_jamo = NO_JAMO;
+ prev_space_or_hyphen = FALSE;
+
+ if (length == 0 || *text == '\0')
+ {
+ next_wc = PARAGRAPH_SEPARATOR;
+ almost_done = TRUE;
+ }
+ else
+ next_wc = g_utf8_get_char (next);
+
+ next_break_type = g_unichar_break_type (next_wc);
+ next_break_type = BREAK_TYPE_SAFE (next_break_type);
+
+ for (i = 0; !done ; i++)
+ {
+ GUnicodeType type;
+ gunichar wc;
+ GUnicodeBreakType break_type;
+ GUnicodeBreakType row_break_type;
+ BreakOpportunity break_op;
+ JamoType jamo;
+ gboolean makes_hangul_syllable;
+
+ /* UAX#29 boundaries */
+ gboolean is_grapheme_boundary;
+ gboolean is_word_boundary;
+ gboolean is_sentence_boundary;
+
+ /* Emoji extended pictographics */
+ gboolean is_Extended_Pictographic;
+
+ GUnicodeScript script;
+
+ wc = next_wc;
+ break_type = next_break_type;
+
+ if (almost_done)
+ {
+ /*
+ * If we have already reached the end of @text g_utf8_next_char()
+ * may not increment next
+ */
+ next_wc = 0;
+ next_break_type = G_UNICODE_BREAK_UNKNOWN;
+ done = TRUE;
+ }
+ else
+ {
+ next = g_utf8_next_char (next);
+
+ if ((length >= 0 && next >= text + length) || *next == '\0')
+ {
+ /* This is how we fill in the last element (end position) of the
+ * attr array - assume there's a paragraph separators off the end
+ * of @text.
+ */
+ next_wc = PARAGRAPH_SEPARATOR;
+ almost_done = TRUE;
+ }
+ else
+ next_wc = g_utf8_get_char (next);
+
+ next_break_type = g_unichar_break_type (next_wc);
+ next_break_type = BREAK_TYPE_SAFE (next_break_type);
+ }
+
+ type = g_unichar_type (wc);
+ jamo = JAMO_TYPE (break_type);
+
+ /* Determine wheter this forms a Hangul syllable with prev. */
+ if (jamo == NO_JAMO)
+ makes_hangul_syllable = FALSE;
+ else
+ {
+ JamoType prev_end = HangulJamoProps[prev_jamo].end ;
+ JamoType this_start = HangulJamoProps[ jamo].start;
+
+ /* See comments before IS_JAMO */
+ makes_hangul_syllable = (prev_end == this_start) || (prev_end + 1 == this_start);
+ }
+
+ switch ((int)type)
+ {
+ case G_UNICODE_SPACE_SEPARATOR:
+ case G_UNICODE_LINE_SEPARATOR:
+ case G_UNICODE_PARAGRAPH_SEPARATOR:
+ attrs[i].is_white = TRUE;
+ break;
+ case G_UNICODE_CONTROL:
+ if (wc == '\t' || wc == '\n' || wc == '\r' || wc == '\f')
+ attrs[i].is_white = TRUE;
+ else
+ attrs[i].is_white = FALSE;
+ break;
+ default:
+ attrs[i].is_white = FALSE;
+ break;
+ }
+
+ /* Just few spaces have variable width. So explicitly mark them.
+ */
+ attrs[i].is_expandable_space = (0x0020 == wc || 0x00A0 == wc);
+ is_Extended_Pictographic =
+ _pango2_Is_Emoji_Extended_Pictographic (wc);
+
+
+ /* ---- UAX#29 Grapheme Boundaries ---- */
+ {
+ GraphemeBreakType GB_type;
+
+ /* Find the GraphemeBreakType of wc */
+ GB_type = GB_Other;
+ switch ((int)type)
+ {
+ case G_UNICODE_FORMAT:
+ if (G_UNLIKELY (wc == 0x200C))
+ {
+ GB_type = GB_Extend;
+ break;
+ }
+ if (G_UNLIKELY (wc == 0x200D))
+ {
+ GB_type = GB_ZWJ;
+ break;
+ }
+ if (G_UNLIKELY((wc >= 0x600 && wc <= 0x605) ||
+ wc == 0x6DD ||
+ wc == 0x70F ||
+ wc == 0x8E2 ||
+ wc == 0x110BD ||
+ wc == 0x110CD))
+ {
+ GB_type = GB_Prepend;
+ break;
+ }
+ /* Tag chars */
+ if (wc >= 0xE0020 && wc <= 0xE00FF)
+ {
+ GB_type = GB_Extend;
+ break;
+ }
+ G_GNUC_FALLTHROUGH;
+ case G_UNICODE_CONTROL:
+ case G_UNICODE_LINE_SEPARATOR:
+ case G_UNICODE_PARAGRAPH_SEPARATOR:
+ case G_UNICODE_SURROGATE:
+ GB_type = GB_ControlCRLF;
+ break;
+
+ case G_UNICODE_UNASSIGNED:
+ /* Unassigned default ignorables */
+ if ((wc >= 0xFFF0 && wc <= 0xFFF8) ||
+ (wc >= 0xE0000 && wc <= 0xE0FFF))
+ {
+ GB_type = GB_ControlCRLF;
+ break;
+ }
+ G_GNUC_FALLTHROUGH;
+
+ case G_UNICODE_OTHER_LETTER:
+ if (makes_hangul_syllable)
+ GB_type = GB_InHangulSyllable;
+
+ if (_pango2_is_Consonant_Preceding_Repha (wc) ||
+ _pango2_is_Consonant_Prefixed (wc))
+ GB_type = GB_Prepend;
+ break;
+
+ case G_UNICODE_MODIFIER_LETTER:
+ if (wc >= 0xFF9E && wc <= 0xFF9F)
+ GB_type = GB_Extend; /* Other_Grapheme_Extend */
+ break;
+
+ case G_UNICODE_SPACING_MARK:
+ GB_type = GB_SpacingMark; /* SpacingMark */
+ if (wc >= 0x0900)
+ {
+ if (wc == 0x09BE || wc == 0x09D7 ||
+ wc == 0x0B3E || wc == 0x0B57 || wc == 0x0BBE || wc == 0x0BD7 ||
+ wc == 0x0CC2 || wc == 0x0CD5 || wc == 0x0CD6 ||
+ wc == 0x0D3E || wc == 0x0D57 || wc == 0x0DCF || wc == 0x0DDF ||
+ wc == 0x1D165 || (wc >= 0x1D16E && wc <= 0x1D172))
+ GB_type = GB_Extend; /* Other_Grapheme_Extend */
+ }
+ break;
+
+ case G_UNICODE_ENCLOSING_MARK:
+ case G_UNICODE_NON_SPACING_MARK:
+ GB_type = GB_Extend; /* Grapheme_Extend */
+ break;
+
+ case G_UNICODE_OTHER_SYMBOL:
+ if (G_UNLIKELY(wc >=0x1F1E6 && wc <=0x1F1FF))
+ {
+ if (prev_GB_type == GB_RI_Odd)
+ GB_type = GB_RI_Even;
+ else
+ GB_type = GB_RI_Odd;
+ break;
+ }
+ break;
+
+ case G_UNICODE_MODIFIER_SYMBOL:
+ /* Fitzpatrick modifiers */
+ if (wc >= 0x1F3FB && wc <= 0x1F3FF)
+ GB_type = GB_Extend;
+ break;
+
+ default:
+ break;
+ }
+
+ /* Rule GB11 */
+ if (met_Extended_Pictographic)
+ {
+ if (GB_type == GB_Extend)
+ met_Extended_Pictographic = TRUE;
+ else if (_pango2_Is_Emoji_Extended_Pictographic (prev_wc) &&
+ GB_type == GB_ZWJ)
+ met_Extended_Pictographic = TRUE;
+ else if (prev_GB_type == GB_Extend && GB_type == GB_ZWJ)
+ met_Extended_Pictographic = TRUE;
+ else if (prev_GB_type == GB_ZWJ && is_Extended_Pictographic)
+ met_Extended_Pictographic = TRUE;
+ else
+ met_Extended_Pictographic = FALSE;
+ }
+
+ /* Grapheme Cluster Boundary Rules */
+ is_grapheme_boundary = TRUE; /* Rule GB999 */
+
+ /* We apply Rules GB1 and GB2 at the end of the function */
+ if (wc == '\n' && prev_wc == '\r')
+ is_grapheme_boundary = FALSE; /* Rule GB3 */
+ else if (prev_GB_type == GB_ControlCRLF || GB_type == GB_ControlCRLF)
+ is_grapheme_boundary = TRUE; /* Rules GB4 and GB5 */
+ else if (GB_type == GB_InHangulSyllable)
+ is_grapheme_boundary = FALSE; /* Rules GB6, GB7, GB8 */
+ else if (GB_type == GB_Extend)
+ is_grapheme_boundary = FALSE; /* Rule GB9 */
+ else if (GB_type == GB_ZWJ)
+ is_grapheme_boundary = FALSE; /* Rule GB9 */
+ else if (GB_type == GB_SpacingMark)
+ is_grapheme_boundary = FALSE; /* Rule GB9a */
+ else if (prev_GB_type == GB_Prepend)
+ is_grapheme_boundary = FALSE; /* Rule GB9b */
+ else if (is_Extended_Pictographic)
+ { /* Rule GB11 */
+ if (prev_GB_type == GB_ZWJ && met_Extended_Pictographic)
+ is_grapheme_boundary = FALSE;
+ }
+ else if (prev_GB_type == GB_RI_Odd && GB_type == GB_RI_Even)
+ is_grapheme_boundary = FALSE; /* Rule GB12 and GB13 */
+
+ if (is_Extended_Pictographic)
+ met_Extended_Pictographic = TRUE;
+
+ attrs[i].is_cursor_position = is_grapheme_boundary;
+ /* If this is a grapheme boundary, we have to decide if backspace
+ * deletes a character or the whole grapheme cluster */
+ if (is_grapheme_boundary)
+ {
+ attrs[i].backspace_deletes_character = BACKSPACE_DELETES_CHARACTER (base_character);
+
+ /* Dependent Vowels for Indic language */
+ if (_pango2_is_Virama (prev_wc) ||
+ _pango2_is_Vowel_Dependent (prev_wc))
+ attrs[i].backspace_deletes_character = TRUE;
+ }
+ else
+ attrs[i].backspace_deletes_character = FALSE;
+
+ prev_GB_type = GB_type;
+ }
+
+ script = g_unichar_get_script (wc);
+ /* ---- UAX#29 Word Boundaries ---- */
+ {
+ is_word_boundary = FALSE;
+ if (is_grapheme_boundary ||
+ G_UNLIKELY(wc >=0x1F1E6 && wc <=0x1F1FF)) /* Rules WB3 and WB4 */
+ {
+ WordBreakType WB_type;
+
+ /* Find the WordBreakType of wc */
+ WB_type = WB_Other;
+
+ if (script == G_UNICODE_SCRIPT_KATAKANA)
+ WB_type = WB_Katakana;
+
+ if (script == G_UNICODE_SCRIPT_HEBREW && type == G_UNICODE_OTHER_LETTER)
+ WB_type = WB_Hebrew_Letter;
+
+ if (WB_type == WB_Other)
+ switch (wc >> 8)
+ {
+ case 0x30:
+ if (wc == 0x3031 || wc == 0x3032 || wc == 0x3033 || wc == 0x3034 || wc == 0x3035 ||
+ wc == 0x309b || wc == 0x309c || wc == 0x30a0 || wc == 0x30fc)
+ WB_type = WB_Katakana; /* Katakana exceptions */
+ break;
+ case 0xFF:
+ if (wc == 0xFF70)
+ WB_type = WB_Katakana; /* Katakana exceptions */
+ else if (wc >= 0xFF9E && wc <= 0xFF9F)
+ WB_type = WB_ExtendFormat; /* Other_Grapheme_Extend */
+ break;
+ case 0x05:
+ if (wc == 0x058A)
+ WB_type = WB_ALetter; /* ALetter exceptions */
+ break;
+ default:
+ break;
+ }
+
+ if (WB_type == WB_Other)
+ switch ((int) break_type)
+ {
+ case G_UNICODE_BREAK_NUMERIC:
+ if (wc != 0x066C)
+ WB_type = WB_Numeric; /* Numeric */
+ break;
+ case G_UNICODE_BREAK_INFIX_SEPARATOR:
+ if (wc != 0x003A && wc != 0xFE13 && wc != 0x002E)
+ WB_type = WB_MidNum; /* MidNum */
+ break;
+ default:
+ break;
+ }
+
+ if (WB_type == WB_Other)
+ switch ((int) type)
+ {
+ case G_UNICODE_CONTROL:
+ if (wc != 0x000D && wc != 0x000A && wc != 0x000B && wc != 0x000C && wc != 0x0085)
+ break;
+ G_GNUC_FALLTHROUGH;
+ case G_UNICODE_LINE_SEPARATOR:
+ case G_UNICODE_PARAGRAPH_SEPARATOR:
+ WB_type = WB_NewlineCRLF; /* CR, LF, Newline */
+ break;
+
+ case G_UNICODE_FORMAT:
+ case G_UNICODE_SPACING_MARK:
+ case G_UNICODE_ENCLOSING_MARK:
+ case G_UNICODE_NON_SPACING_MARK:
+ WB_type = WB_ExtendFormat; /* Extend, Format */
+ break;
+
+ case G_UNICODE_CONNECT_PUNCTUATION:
+ WB_type = WB_ExtendNumLet; /* ExtendNumLet */
+ break;
+
+ case G_UNICODE_INITIAL_PUNCTUATION:
+ case G_UNICODE_FINAL_PUNCTUATION:
+ if (wc == 0x2018 || wc == 0x2019)
+ WB_type = WB_MidNumLet; /* MidNumLet */
+ break;
+ case G_UNICODE_OTHER_PUNCTUATION:
+ if ((wc >= 0x055a && wc <= 0x055c) ||
+ wc == 0x055e || wc == 0x05f3)
+ WB_type = WB_ALetter; /* ALetter */
+ else if (wc == 0x0027 || wc == 0x002e || wc == 0x2024 ||
+ wc == 0xfe52 || wc == 0xff07 || wc == 0xff0e)
+ WB_type = WB_MidNumLet; /* MidNumLet */
+ else if (wc == 0x00b7 || wc == 0x05f4 || wc == 0x2027 ||
+ wc == 0x003a || wc == 0x0387 || wc == 0x055f ||
+ wc == 0xfe13 || wc == 0xfe55 || wc == 0xff1a)
+ WB_type = WB_MidLetter; /* MidLetter */
+ else if (wc == 0x066c ||
+ wc == 0xfe50 || wc == 0xfe54 || wc == 0xff0c || wc == 0xff1b)
+ WB_type = WB_MidNum; /* MidNum */
+ break;
+
+ case G_UNICODE_OTHER_SYMBOL:
+ if (wc >= 0x24B6 && wc <= 0x24E9) /* Other_Alphabetic */
+ goto Alphabetic;
+
+ if (G_UNLIKELY(wc >= 0x1F1E6 && wc <= 0x1F1FF))
+ {
+ if (prev_WB_type == WB_RI_Odd)
+ WB_type = WB_RI_Even;
+ else
+ WB_type = WB_RI_Odd;
+ }
+
+ break;
+
+ case G_UNICODE_OTHER_LETTER:
+ case G_UNICODE_LETTER_NUMBER:
+ if (wc == 0x3006 || wc == 0x3007 ||
+ (wc >= 0x3021 && wc <= 0x3029) ||
+ (wc >= 0x3038 && wc <= 0x303A) ||
+ (wc >= 0x3400 && wc <= 0x4DB5) ||
+ (wc >= 0x4E00 && wc <= 0x9FC3) ||
+ (wc >= 0xF900 && wc <= 0xFA2D) ||
+ (wc >= 0xFA30 && wc <= 0xFA6A) ||
+ (wc >= 0xFA70 && wc <= 0xFAD9) ||
+ (wc >= 0x20000 && wc <= 0x2A6D6) ||
+ (wc >= 0x2F800 && wc <= 0x2FA1D))
+ break; /* ALetter exceptions: Ideographic */
+ goto Alphabetic;
+
+ case G_UNICODE_LOWERCASE_LETTER:
+ case G_UNICODE_MODIFIER_LETTER:
+ case G_UNICODE_TITLECASE_LETTER:
+ case G_UNICODE_UPPERCASE_LETTER:
+ Alphabetic:
+ if (break_type != G_UNICODE_BREAK_COMPLEX_CONTEXT && script != G_UNICODE_SCRIPT_HIRAGANA)
+ WB_type = WB_ALetter; /* ALetter */
+ break;
+ default:
+ break;
+ }
+
+ if (WB_type == WB_Other)
+ {
+ if (type == G_UNICODE_SPACE_SEPARATOR &&
+ break_type != G_UNICODE_BREAK_NON_BREAKING_GLUE)
+ WB_type = WB_WSegSpace;
+ }
+
+ /* Word Cluster Boundary Rules */
+
+ /* We apply Rules WB1 and WB2 at the end of the function */
+
+ if (prev_wc == 0x3031 && wc == 0x41)
+ g_debug ("Y %d %d", prev_WB_type, WB_type);
+ if (prev_WB_type == WB_NewlineCRLF && prev_WB_i + 1 == i)
+ {
+ /* The extra check for prev_WB_i is to correctly handle sequences like
+ * Newline ÷ Extend × Extend
+ * since we have not skipped ExtendFormat yet.
+ */
+ is_word_boundary = TRUE; /* Rule WB3a */
+ }
+ else if (WB_type == WB_NewlineCRLF)
+ is_word_boundary = TRUE; /* Rule WB3b */
+ else if (prev_wc == 0x200D && is_Extended_Pictographic)
+ is_word_boundary = FALSE; /* Rule WB3c */
+ else if (prev_WB_type == WB_WSegSpace &&
+ WB_type == WB_WSegSpace && prev_WB_i + 1 == i)
+ is_word_boundary = FALSE; /* Rule WB3d */
+ else if (WB_type == WB_ExtendFormat)
+ is_word_boundary = FALSE; /* Rules WB4? */
+ else if ((prev_WB_type == WB_ALetter ||
+ prev_WB_type == WB_Hebrew_Letter ||
+ prev_WB_type == WB_Numeric) &&
+ (WB_type == WB_ALetter ||
+ WB_type == WB_Hebrew_Letter ||
+ WB_type == WB_Numeric))
+ is_word_boundary = FALSE; /* Rules WB5, WB8, WB9, WB10 */
+ else if (prev_WB_type == WB_Katakana && WB_type == WB_Katakana)
+ is_word_boundary = FALSE; /* Rule WB13 */
+ else if ((prev_WB_type == WB_ALetter ||
+ prev_WB_type == WB_Hebrew_Letter ||
+ prev_WB_type == WB_Numeric ||
+ prev_WB_type == WB_Katakana ||
+ prev_WB_type == WB_ExtendNumLet) &&
+ WB_type == WB_ExtendNumLet)
+ is_word_boundary = FALSE; /* Rule WB13a */
+ else if (prev_WB_type == WB_ExtendNumLet &&
+ (WB_type == WB_ALetter ||
+ WB_type == WB_Hebrew_Letter ||
+ WB_type == WB_Numeric ||
+ WB_type == WB_Katakana))
+ is_word_boundary = FALSE; /* Rule WB13b */
+ else if (((prev_prev_WB_type == WB_ALetter ||
+ prev_prev_WB_type == WB_Hebrew_Letter) &&
+ (WB_type == WB_ALetter ||
+ WB_type == WB_Hebrew_Letter)) &&
+ (prev_WB_type == WB_MidLetter ||
+ prev_WB_type == WB_MidNumLet ||
+ prev_wc == 0x0027))
+ {
+ attrs[prev_WB_i].is_word_boundary = FALSE; /* Rule WB6 */
+ is_word_boundary = FALSE; /* Rule WB7 */
+ }
+ else if (prev_WB_type == WB_Hebrew_Letter && wc == 0x0027)
+ is_word_boundary = FALSE; /* Rule WB7a */
+ else if (prev_prev_WB_type == WB_Hebrew_Letter && prev_wc == 0x0022 &&
+ WB_type == WB_Hebrew_Letter) {
+ attrs[prev_WB_i].is_word_boundary = FALSE; /* Rule WB7b */
+ is_word_boundary = FALSE; /* Rule WB7c */
+ }
+ else if ((prev_prev_WB_type == WB_Numeric && WB_type == WB_Numeric) &&
+ (prev_WB_type == WB_MidNum || prev_WB_type == WB_MidNumLet ||
+ prev_wc == 0x0027))
+ {
+ is_word_boundary = FALSE; /* Rule WB11 */
+ attrs[prev_WB_i].is_word_boundary = FALSE; /* Rule WB12 */
+ }
+ else if (prev_WB_type == WB_RI_Odd && WB_type == WB_RI_Even)
+ is_word_boundary = FALSE; /* Rule WB15 and WB16 */
+ else
+ is_word_boundary = TRUE; /* Rule WB999 */
+
+ if (WB_type != WB_ExtendFormat)
+ {
+ prev_prev_WB_type = prev_WB_type;
+ prev_WB_type = WB_type;
+ prev_WB_i = i;
+ }
+ }
+
+ attrs[i].is_word_boundary = is_word_boundary;
+ }
+
+ /* ---- UAX#29 Sentence Boundaries ---- */
+ {
+ is_sentence_boundary = FALSE;
+ if (is_word_boundary ||
+ wc == '\r' || wc == '\n') /* Rules SB3 and SB5 */
+ {
+ SentenceBreakType SB_type;
+
+ /* Find the SentenceBreakType of wc */
+ SB_type = SB_Other;
+
+ if (break_type == G_UNICODE_BREAK_NUMERIC)
+ SB_type = SB_Numeric; /* Numeric */
+
+ if (SB_type == SB_Other)
+ switch ((int) type)
+ {
+ case G_UNICODE_CONTROL:
+ if (wc == '\r' || wc == '\n')
+ SB_type = SB_ParaSep;
+ else if (wc == 0x0009 || wc == 0x000B || wc == 0x000C)
+ SB_type = SB_Sp;
+ else if (wc == 0x0085)
+ SB_type = SB_ParaSep;
+ break;
+
+ case G_UNICODE_SPACE_SEPARATOR:
+ if (wc == 0x0020 || wc == 0x00A0 || wc == 0x1680 ||
+ (wc >= 0x2000 && wc <= 0x200A) ||
+ wc == 0x202F || wc == 0x205F || wc == 0x3000)
+ SB_type = SB_Sp;
+ break;
+
+ case G_UNICODE_LINE_SEPARATOR:
+ case G_UNICODE_PARAGRAPH_SEPARATOR:
+ SB_type = SB_ParaSep;
+ break;
+
+ case G_UNICODE_FORMAT:
+ case G_UNICODE_SPACING_MARK:
+ case G_UNICODE_ENCLOSING_MARK:
+ case G_UNICODE_NON_SPACING_MARK:
+ SB_type = SB_ExtendFormat; /* Extend, Format */
+ break;
+
+ case G_UNICODE_MODIFIER_LETTER:
+ if (wc >= 0xFF9E && wc <= 0xFF9F)
+ SB_type = SB_ExtendFormat; /* Other_Grapheme_Extend */
+ break;
+
+ case G_UNICODE_TITLECASE_LETTER:
+ SB_type = SB_Upper;
+ break;
+
+ case G_UNICODE_DASH_PUNCTUATION:
+ if (wc == 0x002D ||
+ (wc >= 0x2013 && wc <= 0x2014) ||
+ (wc >= 0xFE31 && wc <= 0xFE32) ||
+ wc == 0xFE58 ||
+ wc == 0xFE63 ||
+ wc == 0xFF0D)
+ SB_type = SB_SContinue;
+ break;
+
+ case G_UNICODE_OTHER_PUNCTUATION:
+ if (wc == 0x05F3)
+ SB_type = SB_OLetter;
+ else if (wc == 0x002E || wc == 0x2024 ||
+ wc == 0xFE52 || wc == 0xFF0E)
+ SB_type = SB_ATerm;
+
+ if (wc == 0x002C ||
+ wc == 0x003A ||
+ wc == 0x055D ||
+ (wc >= 0x060C && wc <= 0x060D) ||
+ wc == 0x07F8 ||
+ wc == 0x1802 ||
+ wc == 0x1808 ||
+ wc == 0x3001 ||
+ (wc >= 0xFE10 && wc <= 0xFE11) ||
+ wc == 0xFE13 ||
+ (wc >= 0xFE50 && wc <= 0xFE51) ||
+ wc == 0xFE55 ||
+ wc == 0xFF0C ||
+ wc == 0xFF1A ||
+ wc == 0xFF64)
+ SB_type = SB_SContinue;
+
+ if (_pango2_is_STerm(wc))
+ SB_type = SB_STerm;
+
+ break;
+
+ default:
+ break;
+ }
+
+ if (SB_type == SB_Other)
+ {
+ if (type == G_UNICODE_LOWERCASE_LETTER)
+ SB_type = SB_Lower;
+ else if (type == G_UNICODE_UPPERCASE_LETTER)
+ SB_type = SB_Upper;
+ else if (type == G_UNICODE_TITLECASE_LETTER ||
+ type == G_UNICODE_MODIFIER_LETTER ||
+ type == G_UNICODE_OTHER_LETTER)
+ SB_type = SB_OLetter;
+
+ if (type == G_UNICODE_OPEN_PUNCTUATION ||
+ type == G_UNICODE_CLOSE_PUNCTUATION ||
+ break_type == G_UNICODE_BREAK_QUOTATION)
+ SB_type = SB_Close;
+ }
+
+ /* Sentence Boundary Rules */
+
+ /* We apply Rules SB1 and SB2 at the end of the function */
+
+#define IS_OTHER_TERM(SB_type) \
+ /* not in (OLetter | Upper | Lower | ParaSep | SATerm) */ \
+ !(SB_type == SB_OLetter || \
+ SB_type == SB_Upper || SB_type == SB_Lower || \
+ SB_type == SB_ParaSep || \
+ SB_type == SB_ATerm || SB_type == SB_STerm || \
+ SB_type == SB_ATerm_Close_Sp || \
+ SB_type == SB_STerm_Close_Sp)
+
+
+ if (wc == '\n' && prev_wc == '\r')
+ is_sentence_boundary = FALSE; /* Rule SB3 */
+ else if (prev_SB_type == SB_ParaSep && prev_SB_i + 1 == i)
+ {
+ /* The extra check for prev_SB_i is to correctly handle sequences like
+ * ParaSep ÷ Extend × Extend
+ * since we have not skipped ExtendFormat yet.
+ */
+
+ is_sentence_boundary = TRUE; /* Rule SB4 */
+ }
+ else if (SB_type == SB_ExtendFormat)
+ is_sentence_boundary = FALSE; /* Rule SB5? */
+ else if (prev_SB_type == SB_ATerm && SB_type == SB_Numeric)
+ is_sentence_boundary = FALSE; /* Rule SB6 */
+ else if ((prev_prev_SB_type == SB_Upper ||
+ prev_prev_SB_type == SB_Lower) &&
+ prev_SB_type == SB_ATerm &&
+ SB_type == SB_Upper)
+ is_sentence_boundary = FALSE; /* Rule SB7 */
+ else if (prev_SB_type == SB_ATerm && SB_type == SB_Close)
+ SB_type = SB_ATerm;
+ else if (prev_SB_type == SB_STerm && SB_type == SB_Close)
+ SB_type = SB_STerm;
+ else if (prev_SB_type == SB_ATerm && SB_type == SB_Sp)
+ SB_type = SB_ATerm_Close_Sp;
+ else if (prev_SB_type == SB_STerm && SB_type == SB_Sp)
+ SB_type = SB_STerm_Close_Sp;
+ /* Rule SB8 */
+ else if ((prev_SB_type == SB_ATerm ||
+ prev_SB_type == SB_ATerm_Close_Sp) &&
+ SB_type == SB_Lower)
+ is_sentence_boundary = FALSE;
+ else if ((prev_prev_SB_type == SB_ATerm ||
+ prev_prev_SB_type == SB_ATerm_Close_Sp) &&
+ IS_OTHER_TERM(prev_SB_type) &&
+ SB_type == SB_Lower)
+ {
+ attrs[prev_SB_i].is_sentence_boundary = FALSE;
+ attrs[prev_SB_i].is_sentence_end = FALSE;
+ last_sentence_start = -1;
+ for (int j = prev_SB_i - 1; j >= 0; j--)
+ {
+ attrs[j].is_sentence_end = FALSE;
+ if (attrs[j].is_sentence_boundary)
+ {
+ last_sentence_start = j;
+ break;
+ }
+ }
+ }
+ else if ((prev_SB_type == SB_ATerm ||
+ prev_SB_type == SB_ATerm_Close_Sp ||
+ prev_SB_type == SB_STerm ||
+ prev_SB_type == SB_STerm_Close_Sp) &&
+ (SB_type == SB_SContinue ||
+ SB_type == SB_ATerm || SB_type == SB_STerm))
+ is_sentence_boundary = FALSE; /* Rule SB8a */
+ else if ((prev_SB_type == SB_ATerm ||
+ prev_SB_type == SB_STerm) &&
+ (SB_type == SB_Close || SB_type == SB_Sp ||
+ SB_type == SB_ParaSep))
+ is_sentence_boundary = FALSE; /* Rule SB9 */
+ else if ((prev_SB_type == SB_ATerm ||
+ prev_SB_type == SB_ATerm_Close_Sp ||
+ prev_SB_type == SB_STerm ||
+ prev_SB_type == SB_STerm_Close_Sp) &&
+ (SB_type == SB_Sp || SB_type == SB_ParaSep))
+ is_sentence_boundary = FALSE; /* Rule SB10 */
+ else if ((prev_SB_type == SB_ATerm ||
+ prev_SB_type == SB_ATerm_Close_Sp ||
+ prev_SB_type == SB_STerm ||
+ prev_SB_type == SB_STerm_Close_Sp) &&
+ SB_type != SB_ParaSep)
+ is_sentence_boundary = TRUE; /* Rule SB11 */
+ else
+ is_sentence_boundary = FALSE; /* Rule SB998 */
+
+ if (SB_type != SB_ExtendFormat &&
+ !((prev_prev_SB_type == SB_ATerm ||
+ prev_prev_SB_type == SB_ATerm_Close_Sp) &&
+ IS_OTHER_TERM(prev_SB_type) &&
+ IS_OTHER_TERM(SB_type)))
+ {
+ prev_prev_SB_type = prev_SB_type;
+ prev_SB_type = SB_type;
+ prev_SB_i = i;
+ }
+
+#undef IS_OTHER_TERM
+
+ }
+
+ if (i == 0 || done)
+ is_sentence_boundary = TRUE; /* Rules SB1 and SB2 */
+
+ attrs[i].is_sentence_boundary = is_sentence_boundary;
+ }
+
+ /* ---- Line breaking ---- */
+
+ break_op = BREAK_ALREADY_HANDLED;
+
+ row_break_type = prev_break_type == G_UNICODE_BREAK_SPACE ?
+ prev_prev_break_type : prev_break_type;
+ g_assert (row_break_type != G_UNICODE_BREAK_SPACE);
+
+ attrs[i].is_char_break = FALSE;
+ attrs[i].is_line_break = FALSE;
+ attrs[i].is_mandatory_break = FALSE;
+
+ /* Rule LB1:
+ assign a line breaking class to each code point of the input. */
+ switch ((int)break_type)
+ {
+ case G_UNICODE_BREAK_AMBIGUOUS:
+ case G_UNICODE_BREAK_SURROGATE:
+ case G_UNICODE_BREAK_UNKNOWN:
+ break_type = G_UNICODE_BREAK_ALPHABETIC;
+ break;
+
+ case G_UNICODE_BREAK_COMPLEX_CONTEXT:
+ if (type == G_UNICODE_NON_SPACING_MARK ||
+ type == G_UNICODE_SPACING_MARK)
+ break_type = G_UNICODE_BREAK_COMBINING_MARK;
+ else
+ break_type = G_UNICODE_BREAK_ALPHABETIC;
+ break;
+
+ case G_UNICODE_BREAK_CONDITIONAL_JAPANESE_STARTER:
+ break_type = G_UNICODE_BREAK_NON_STARTER;
+ break;
+
+ default:
+ break;
+ }
+
+ /* If it's not a grapheme boundary, it's not a line break either */
+ if (attrs[i].is_cursor_position ||
+ break_type == G_UNICODE_BREAK_COMBINING_MARK ||
+ break_type == G_UNICODE_BREAK_ZERO_WIDTH_JOINER ||
+ break_type == G_UNICODE_BREAK_HANGUL_L_JAMO ||
+ break_type == G_UNICODE_BREAK_HANGUL_V_JAMO ||
+ break_type == G_UNICODE_BREAK_HANGUL_T_JAMO ||
+ break_type == G_UNICODE_BREAK_HANGUL_LV_SYLLABLE ||
+ break_type == G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE ||
+ break_type == G_UNICODE_BREAK_EMOJI_MODIFIER ||
+ break_type == G_UNICODE_BREAK_REGIONAL_INDICATOR)
+ {
+ LineBreakType LB_type;
+
+ /* Find the LineBreakType of wc */
+ LB_type = LB_Other;
+
+ if (break_type == G_UNICODE_BREAK_NUMERIC)
+ LB_type = LB_Numeric;
+
+ if (break_type == G_UNICODE_BREAK_SYMBOL ||
+ break_type == G_UNICODE_BREAK_INFIX_SEPARATOR)
+ {
+ if (!(prev_LB_type == LB_Numeric))
+ LB_type = LB_Other;
+ }
+
+ if (break_type == G_UNICODE_BREAK_CLOSE_PUNCTUATION ||
+ break_type == G_UNICODE_BREAK_CLOSE_PARANTHESIS)
+ {
+ if (prev_LB_type == LB_Numeric)
+ LB_type = LB_Numeric_Close;
+ else
+ LB_type = LB_Other;
+ }
+
+ if (break_type == G_UNICODE_BREAK_REGIONAL_INDICATOR)
+ {
+ if (prev_LB_type == LB_RI_Odd)
+ LB_type = LB_RI_Even;
+ else
+ LB_type = LB_RI_Odd;
+ }
+
+ attrs[i].is_line_break = TRUE; /* Rule LB31 */
+ /* Unicode doesn't specify char wrap;
+ we wrap around all chars currently. */
+ if (attrs[i].is_cursor_position)
+ attrs[i].is_char_break = TRUE;
+
+ /* Make any necessary replacements first */
+ if (row_break_type == G_UNICODE_BREAK_UNKNOWN)
+ row_break_type = G_UNICODE_BREAK_ALPHABETIC;
+
+ /* add the line break rules in reverse order to override
+ the lower priority rules. */
+
+ /* Rule LB30 */
+ if ((prev_break_type == G_UNICODE_BREAK_ALPHABETIC ||
+ prev_break_type == G_UNICODE_BREAK_HEBREW_LETTER ||
+ prev_break_type == G_UNICODE_BREAK_NUMERIC) &&
+ break_type == G_UNICODE_BREAK_OPEN_PUNCTUATION &&
+ !_pango2_is_EastAsianWide (wc))
+ break_op = BREAK_PROHIBITED;
+
+ if (prev_break_type == G_UNICODE_BREAK_CLOSE_PARANTHESIS &&
+ !_pango2_is_EastAsianWide (prev_wc)&&
+ (break_type == G_UNICODE_BREAK_ALPHABETIC ||
+ break_type == G_UNICODE_BREAK_HEBREW_LETTER ||
+ break_type == G_UNICODE_BREAK_NUMERIC))
+ break_op = BREAK_PROHIBITED;
+
+ /* Rule LB30a */
+ if (prev_LB_type == LB_RI_Odd && LB_type == LB_RI_Even)
+ break_op = BREAK_PROHIBITED;
+
+ /* Rule LB30b */
+ if (prev_break_type == G_UNICODE_BREAK_EMOJI_BASE &&
+ break_type == G_UNICODE_BREAK_EMOJI_MODIFIER)
+ break_op = BREAK_PROHIBITED;
+
+ if ((_pango2_Is_Emoji_Extended_Pictographic (prev_wc) &&
+ g_unichar_type (prev_wc) == G_UNICODE_UNASSIGNED) &&
+ break_type == G_UNICODE_BREAK_EMOJI_MODIFIER)
+ break_op = BREAK_PROHIBITED;
+
+ /* Rule LB29 */
+ if (prev_break_type == G_UNICODE_BREAK_INFIX_SEPARATOR &&
+ (break_type == G_UNICODE_BREAK_ALPHABETIC ||
+ break_type == G_UNICODE_BREAK_HEBREW_LETTER))
+ break_op = BREAK_PROHIBITED;
+
+ /* Rule LB28 */
+ if ((prev_break_type == G_UNICODE_BREAK_ALPHABETIC ||
+ prev_break_type == G_UNICODE_BREAK_HEBREW_LETTER) &&
+ (break_type == G_UNICODE_BREAK_ALPHABETIC ||
+ break_type == G_UNICODE_BREAK_HEBREW_LETTER))
+ break_op = BREAK_PROHIBITED;
+
+ /* Rule LB27 */
+ if ((prev_break_type == G_UNICODE_BREAK_HANGUL_L_JAMO ||
+ prev_break_type == G_UNICODE_BREAK_HANGUL_V_JAMO ||
+ prev_break_type == G_UNICODE_BREAK_HANGUL_T_JAMO ||
+ prev_break_type == G_UNICODE_BREAK_HANGUL_LV_SYLLABLE ||
+ prev_break_type == G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE) &&
+ break_type == G_UNICODE_BREAK_POSTFIX)
+ break_op = BREAK_PROHIBITED;
+
+ if (prev_break_type == G_UNICODE_BREAK_PREFIX &&
+ (break_type == G_UNICODE_BREAK_HANGUL_L_JAMO ||
+ break_type == G_UNICODE_BREAK_HANGUL_V_JAMO ||
+ break_type == G_UNICODE_BREAK_HANGUL_T_JAMO ||
+ break_type == G_UNICODE_BREAK_HANGUL_LV_SYLLABLE ||
+ break_type == G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE))
+ break_op = BREAK_PROHIBITED;
+
+ /* Rule LB26 */
+ if (prev_break_type == G_UNICODE_BREAK_HANGUL_L_JAMO &&
+ (break_type == G_UNICODE_BREAK_HANGUL_L_JAMO ||
+ break_type == G_UNICODE_BREAK_HANGUL_V_JAMO ||
+ break_type == G_UNICODE_BREAK_HANGUL_LV_SYLLABLE ||
+ break_type == G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE))
+ break_op = BREAK_PROHIBITED;
+
+ if ((prev_break_type == G_UNICODE_BREAK_HANGUL_V_JAMO ||
+ prev_break_type == G_UNICODE_BREAK_HANGUL_LV_SYLLABLE) &&
+ (break_type == G_UNICODE_BREAK_HANGUL_V_JAMO ||
+ break_type == G_UNICODE_BREAK_HANGUL_T_JAMO))
+ break_op = BREAK_PROHIBITED;
+
+ if ((prev_break_type == G_UNICODE_BREAK_HANGUL_T_JAMO ||
+ prev_break_type == G_UNICODE_BREAK_HANGUL_LVT_SYLLABLE) &&
+ break_type == G_UNICODE_BREAK_HANGUL_T_JAMO)
+ break_op = BREAK_PROHIBITED;
+
+ /* Rule LB25 with Example 7 of Customization */
+ if ((prev_break_type == G_UNICODE_BREAK_PREFIX ||
+ prev_break_type == G_UNICODE_BREAK_POSTFIX) &&
+ break_type == G_UNICODE_BREAK_NUMERIC)
+ break_op = BREAK_PROHIBITED;
+
+ if ((prev_break_type == G_UNICODE_BREAK_PREFIX ||
+ prev_break_type == G_UNICODE_BREAK_POSTFIX) &&
+ (break_type == G_UNICODE_BREAK_OPEN_PUNCTUATION ||
+ break_type == G_UNICODE_BREAK_HYPHEN) &&
+ next_break_type == G_UNICODE_BREAK_NUMERIC)
+ break_op = BREAK_PROHIBITED;
+
+ if ((prev_break_type == G_UNICODE_BREAK_OPEN_PUNCTUATION ||
+ prev_break_type == G_UNICODE_BREAK_HYPHEN) &&
+ break_type == G_UNICODE_BREAK_NUMERIC)
+ break_op = BREAK_PROHIBITED;
+
+ if (prev_break_type == G_UNICODE_BREAK_NUMERIC &&
+ (break_type == G_UNICODE_BREAK_NUMERIC ||
+ break_type == G_UNICODE_BREAK_SYMBOL ||
+ break_type == G_UNICODE_BREAK_INFIX_SEPARATOR))
+ break_op = BREAK_PROHIBITED;
+
+ if (prev_LB_type == LB_Numeric &&
+ (break_type == G_UNICODE_BREAK_NUMERIC ||
+ break_type == G_UNICODE_BREAK_SYMBOL ||
+ break_type == G_UNICODE_BREAK_INFIX_SEPARATOR ||
+ break_type == G_UNICODE_BREAK_CLOSE_PUNCTUATION ||
+ break_type == G_UNICODE_BREAK_CLOSE_PARANTHESIS))
+ break_op = BREAK_PROHIBITED;
+
+ if ((prev_LB_type == LB_Numeric ||
+ prev_LB_type == LB_Numeric_Close) &&
+ (break_type == G_UNICODE_BREAK_POSTFIX ||
+ break_type == G_UNICODE_BREAK_PREFIX))
+ break_op = BREAK_PROHIBITED;
+
+ /* Rule LB24 */
+ if ((prev_break_type == G_UNICODE_BREAK_PREFIX ||
+ prev_break_type == G_UNICODE_BREAK_POSTFIX) &&
+ (break_type == G_UNICODE_BREAK_ALPHABETIC ||
+ break_type == G_UNICODE_BREAK_HEBREW_LETTER))
+ break_op = BREAK_PROHIBITED;
+
+ if ((prev_break_type == G_UNICODE_BREAK_ALPHABETIC ||
+ prev_break_type == G_UNICODE_BREAK_HEBREW_LETTER) &&
+ (break_type == G_UNICODE_BREAK_PREFIX ||
+ break_type == G_UNICODE_BREAK_POSTFIX))
+ break_op = BREAK_PROHIBITED;
+
+ /* Rule LB23 */
+ if ((prev_break_type == G_UNICODE_BREAK_ALPHABETIC ||
+ prev_break_type == G_UNICODE_BREAK_HEBREW_LETTER) &&
+ break_type == G_UNICODE_BREAK_NUMERIC)
+ break_op = BREAK_PROHIBITED;
+
+ if (prev_break_type == G_UNICODE_BREAK_NUMERIC &&
+ (break_type == G_UNICODE_BREAK_ALPHABETIC ||
+ break_type == G_UNICODE_BREAK_HEBREW_LETTER))
+ break_op = BREAK_PROHIBITED;
+
+ /* Rule LB23a */
+ if (prev_break_type == G_UNICODE_BREAK_PREFIX &&
+ (break_type == G_UNICODE_BREAK_IDEOGRAPHIC ||
+ break_type == G_UNICODE_BREAK_EMOJI_BASE ||
+ break_type == G_UNICODE_BREAK_EMOJI_MODIFIER))
+ break_op = BREAK_PROHIBITED;
+
+ if ((prev_break_type == G_UNICODE_BREAK_IDEOGRAPHIC ||
+ prev_break_type == G_UNICODE_BREAK_EMOJI_BASE ||
+ prev_break_type == G_UNICODE_BREAK_EMOJI_MODIFIER) &&
+ break_type == G_UNICODE_BREAK_POSTFIX)
+ break_op = BREAK_PROHIBITED;
+
+ /* Rule LB22 */
+ if (break_type == G_UNICODE_BREAK_INSEPARABLE)
+ break_op = BREAK_PROHIBITED;
+
+ if (break_type == G_UNICODE_BREAK_AFTER ||
+ break_type == G_UNICODE_BREAK_HYPHEN ||
+ break_type == G_UNICODE_BREAK_NON_STARTER ||
+ prev_break_type == G_UNICODE_BREAK_BEFORE)
+ break_op = BREAK_PROHIBITED; /* Rule LB21 */
+
+ if (prev_prev_break_type == G_UNICODE_BREAK_HEBREW_LETTER &&
+ (prev_break_type == G_UNICODE_BREAK_HYPHEN ||
+ prev_break_type == G_UNICODE_BREAK_AFTER))
+ break_op = BREAK_PROHIBITED; /* Rule LB21a */
+
+ if (prev_break_type == G_UNICODE_BREAK_SYMBOL &&
+ break_type == G_UNICODE_BREAK_HEBREW_LETTER)
+ break_op = BREAK_PROHIBITED; /* Rule LB21b */
+
+ if (prev_break_type == G_UNICODE_BREAK_CONTINGENT ||
+ break_type == G_UNICODE_BREAK_CONTINGENT)
+ break_op = BREAK_ALLOWED; /* Rule LB20 */
+
+ if (prev_break_type == G_UNICODE_BREAK_QUOTATION ||
+ break_type == G_UNICODE_BREAK_QUOTATION)
+ break_op = BREAK_PROHIBITED; /* Rule LB19 */
+
+ /* handle related rules for Space as state machine here,
+ and override the pair table result. */
+ if (prev_break_type == G_UNICODE_BREAK_SPACE) /* Rule LB18 */
+ break_op = BREAK_ALLOWED;
+
+ if (row_break_type == G_UNICODE_BREAK_BEFORE_AND_AFTER &&
+ break_type == G_UNICODE_BREAK_BEFORE_AND_AFTER)
+ break_op = BREAK_PROHIBITED; /* Rule LB17 */
+
+ if ((row_break_type == G_UNICODE_BREAK_CLOSE_PUNCTUATION ||
+ row_break_type == G_UNICODE_BREAK_CLOSE_PARANTHESIS) &&
+ break_type == G_UNICODE_BREAK_NON_STARTER)
+ break_op = BREAK_PROHIBITED; /* Rule LB16 */
+
+ if (row_break_type == G_UNICODE_BREAK_QUOTATION &&
+ break_type == G_UNICODE_BREAK_OPEN_PUNCTUATION)
+ break_op = BREAK_PROHIBITED; /* Rule LB15 */
+
+ if (row_break_type == G_UNICODE_BREAK_OPEN_PUNCTUATION)
+ break_op = BREAK_PROHIBITED; /* Rule LB14 */
+
+ /* Rule LB13 with Example 7 of Customization */
+ if (break_type == G_UNICODE_BREAK_EXCLAMATION)
+ break_op = BREAK_PROHIBITED;
+
+ if (prev_break_type != G_UNICODE_BREAK_NUMERIC &&
+ (break_type == G_UNICODE_BREAK_CLOSE_PUNCTUATION ||
+ break_type == G_UNICODE_BREAK_CLOSE_PARANTHESIS ||
+ break_type == G_UNICODE_BREAK_INFIX_SEPARATOR ||
+ break_type == G_UNICODE_BREAK_SYMBOL))
+ break_op = BREAK_PROHIBITED;
+
+ if (prev_break_type == G_UNICODE_BREAK_NON_BREAKING_GLUE)
+ break_op = BREAK_PROHIBITED; /* Rule LB12 */
+
+ if (break_type == G_UNICODE_BREAK_NON_BREAKING_GLUE &&
+ (prev_break_type != G_UNICODE_BREAK_SPACE &&
+ prev_break_type != G_UNICODE_BREAK_AFTER &&
+ prev_break_type != G_UNICODE_BREAK_HYPHEN))
+ break_op = BREAK_PROHIBITED; /* Rule LB12a */
+
+ if (prev_break_type == G_UNICODE_BREAK_WORD_JOINER ||
+ break_type == G_UNICODE_BREAK_WORD_JOINER)
+ break_op = BREAK_PROHIBITED; /* Rule LB11 */
+
+
+ /* Rule LB9 */
+ if (break_type == G_UNICODE_BREAK_COMBINING_MARK ||
+ break_type == G_UNICODE_BREAK_ZERO_WIDTH_JOINER)
+ {
+ if (!(prev_break_type == G_UNICODE_BREAK_MANDATORY ||
+ prev_break_type == G_UNICODE_BREAK_CARRIAGE_RETURN ||
+ prev_break_type == G_UNICODE_BREAK_LINE_FEED ||
+ prev_break_type == G_UNICODE_BREAK_NEXT_LINE ||
+ prev_break_type == G_UNICODE_BREAK_SPACE ||
+ prev_break_type == G_UNICODE_BREAK_ZERO_WIDTH_SPACE))
+ break_op = BREAK_PROHIBITED;
+ }
+
+ if (row_break_type == G_UNICODE_BREAK_ZERO_WIDTH_SPACE)
+ break_op = BREAK_ALLOWED; /* Rule LB8 */
+
+ if (prev_wc == 0x200D)
+ break_op = BREAK_PROHIBITED; /* Rule LB8a */
+
+ if (break_type == G_UNICODE_BREAK_SPACE ||
+ break_type == G_UNICODE_BREAK_ZERO_WIDTH_SPACE)
+ break_op = BREAK_PROHIBITED; /* Rule LB7 */
+
+ /* Rule LB6 */
+ if (break_type == G_UNICODE_BREAK_MANDATORY ||
+ break_type == G_UNICODE_BREAK_CARRIAGE_RETURN ||
+ break_type == G_UNICODE_BREAK_LINE_FEED ||
+ break_type == G_UNICODE_BREAK_NEXT_LINE)
+ break_op = BREAK_PROHIBITED;
+
+ /* Rules LB4 and LB5 */
+ if (prev_break_type == G_UNICODE_BREAK_MANDATORY ||
+ (prev_break_type == G_UNICODE_BREAK_CARRIAGE_RETURN &&
+ wc != '\n') ||
+ prev_break_type == G_UNICODE_BREAK_LINE_FEED ||
+ prev_break_type == G_UNICODE_BREAK_NEXT_LINE)
+ {
+ attrs[i].is_mandatory_break = TRUE;
+ break_op = BREAK_ALLOWED;
+ }
+
+ switch (break_op)
+ {
+ case BREAK_PROHIBITED:
+ /* can't break here */
+ attrs[i].is_line_break = FALSE;
+ break;
+
+ case BREAK_IF_SPACES:
+ /* break if prev char was space */
+ if (prev_break_type != G_UNICODE_BREAK_SPACE)
+ attrs[i].is_line_break = FALSE;
+ break;
+
+ case BREAK_ALLOWED:
+ attrs[i].is_line_break = TRUE;
+ break;
+
+ case BREAK_ALREADY_HANDLED:
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ /* Rule LB9 */
+ if (!(break_type == G_UNICODE_BREAK_COMBINING_MARK ||
+ break_type == G_UNICODE_BREAK_ZERO_WIDTH_JOINER))
+ {
+ /* Rule LB25 with Example 7 of Customization */
+ if (break_type == G_UNICODE_BREAK_NUMERIC ||
+ break_type == G_UNICODE_BREAK_SYMBOL ||
+ break_type == G_UNICODE_BREAK_INFIX_SEPARATOR)
+ {
+ if (prev_LB_type != LB_Numeric)
+ prev_LB_type = LB_type;
+ /* else don't change the prev_LB_type */
+ }
+ else
+ {
+ prev_LB_type = LB_type;
+ }
+ }
+ /* else don't change the prev_LB_type for Rule LB9 */
+ }
+
+ if (break_type != G_UNICODE_BREAK_SPACE)
+ {
+ /* Rule LB9 */
+ if (break_type == G_UNICODE_BREAK_COMBINING_MARK ||
+ break_type == G_UNICODE_BREAK_ZERO_WIDTH_JOINER)
+ {
+ if (i == 0 /* start of text */ ||
+ prev_break_type == G_UNICODE_BREAK_MANDATORY ||
+ prev_break_type == G_UNICODE_BREAK_CARRIAGE_RETURN ||
+ prev_break_type == G_UNICODE_BREAK_LINE_FEED ||
+ prev_break_type == G_UNICODE_BREAK_NEXT_LINE ||
+ prev_break_type == G_UNICODE_BREAK_SPACE ||
+ prev_break_type == G_UNICODE_BREAK_ZERO_WIDTH_SPACE)
+ prev_break_type = G_UNICODE_BREAK_ALPHABETIC; /* Rule LB10 */
+ /* else don't change the prev_break_type for Rule LB9 */
+ }
+ else
+ {
+ prev_prev_break_type = prev_break_type;
+ prev_break_type = break_type;
+ }
+
+ prev_jamo = jamo;
+ }
+ else
+ {
+ if (prev_break_type != G_UNICODE_BREAK_SPACE)
+ {
+ prev_prev_break_type = prev_break_type;
+ prev_break_type = break_type;
+ }
+ /* else don't change the prev_break_type */
+ }
+
+ /* ---- Word breaks ---- */
+
+ /* default to not a word start/end */
+ attrs[i].is_word_start = FALSE;
+ attrs[i].is_word_end = FALSE;
+
+ if (current_word_type != WordNone)
+ {
+ /* Check for a word end */
+ switch ((int) type)
+ {
+ case G_UNICODE_SPACING_MARK:
+ case G_UNICODE_ENCLOSING_MARK:
+ case G_UNICODE_NON_SPACING_MARK:
+ case G_UNICODE_FORMAT:
+ /* nothing, we just eat these up as part of the word */
+ break;
+
+ case G_UNICODE_LOWERCASE_LETTER:
+ case G_UNICODE_MODIFIER_LETTER:
+ case G_UNICODE_OTHER_LETTER:
+ case G_UNICODE_TITLECASE_LETTER:
+ case G_UNICODE_UPPERCASE_LETTER:
+ if (current_word_type == WordLetters)
+ {
+ /* Japanese special cases for ending the word */
+ if (JAPANESE (last_word_letter) ||
+ JAPANESE (wc))
+ {
+ if ((HIRAGANA (last_word_letter) &&
+ !HIRAGANA (wc)) ||
+ (KATAKANA (last_word_letter) &&
+ !(KATAKANA (wc) || HIRAGANA (wc))) ||
+ (KANJI (last_word_letter) &&
+ !(HIRAGANA (wc) || KANJI (wc))) ||
+ (JAPANESE (last_word_letter) &&
+ !JAPANESE (wc)) ||
+ (!JAPANESE (last_word_letter) &&
+ JAPANESE (wc)))
+ attrs[i].is_word_end = TRUE;
+ }
+ }
+ last_word_letter = wc;
+ break;
+
+ case G_UNICODE_DECIMAL_NUMBER:
+ case G_UNICODE_LETTER_NUMBER:
+ case G_UNICODE_OTHER_NUMBER:
+ last_word_letter = wc;
+ break;
+
+ default:
+ /* Punctuation, control/format chars, etc. all end a word. */
+ attrs[i].is_word_end = TRUE;
+ current_word_type = WordNone;
+ break;
+ }
+ }
+ else
+ {
+ /* Check for a word start */
+ switch ((int) type)
+ {
+ case G_UNICODE_LOWERCASE_LETTER:
+ case G_UNICODE_MODIFIER_LETTER:
+ case G_UNICODE_OTHER_LETTER:
+ case G_UNICODE_TITLECASE_LETTER:
+ case G_UNICODE_UPPERCASE_LETTER:
+ current_word_type = WordLetters;
+ last_word_letter = wc;
+ attrs[i].is_word_start = TRUE;
+ break;
+
+ case G_UNICODE_DECIMAL_NUMBER:
+ case G_UNICODE_LETTER_NUMBER:
+ case G_UNICODE_OTHER_NUMBER:
+ current_word_type = WordNumbers;
+ last_word_letter = wc;
+ attrs[i].is_word_start = TRUE;
+ break;
+
+ default:
+ /* No word here */
+ break;
+ }
+ }
+
+ /* ---- Sentence breaks ---- */
+ {
+
+ /* default to not a sentence start/end */
+ attrs[i].is_sentence_start = FALSE;
+ attrs[i].is_sentence_end = FALSE;
+
+ /* maybe start sentence */
+ if (last_sentence_start == -1 && !is_sentence_boundary)
+ last_sentence_start = i - 1;
+
+ /* remember last non space character position */
+ if (i > 0 && !attrs[i - 1].is_white)
+ last_non_space = i;
+
+ /* meets sentence end, mark both sentence start and end */
+ if (last_sentence_start != -1 && is_sentence_boundary) {
+ if (last_non_space >= last_sentence_start) {
+ attrs[last_sentence_start].is_sentence_start = TRUE;
+ attrs[last_non_space].is_sentence_end = TRUE;
+ }
+
+ last_sentence_start = -1;
+ last_non_space = -1;
+ }
+
+ /* meets space character, move sentence start */
+ if (last_sentence_start != -1 &&
+ last_sentence_start == i - 1 &&
+ attrs[i - 1].is_white) {
+ last_sentence_start++;
+ }
+ }
+
+ /* --- Hyphens --- */
+
+ {
+ gboolean insert_hyphens;
+ gboolean space_or_hyphen = FALSE;
+
+ attrs[i].break_inserts_hyphen = FALSE;
+ attrs[i].break_removes_preceding = FALSE;
+
+ switch ((int)prev_script)
+ {
+ case G_UNICODE_SCRIPT_COMMON:
+ insert_hyphens = prev_wc == 0x00ad;
+ break;
+ case G_UNICODE_SCRIPT_HAN:
+ case G_UNICODE_SCRIPT_HANGUL:
+ case G_UNICODE_SCRIPT_HIRAGANA:
+ case G_UNICODE_SCRIPT_KATAKANA:
+ insert_hyphens = FALSE;
+ break;
+ default:
+ insert_hyphens = TRUE;
+ break;
+ }
+
+ switch ((int)type)
+ {
+ case G_UNICODE_SPACE_SEPARATOR:
+ case G_UNICODE_LINE_SEPARATOR:
+ case G_UNICODE_PARAGRAPH_SEPARATOR:
+ space_or_hyphen = TRUE;
+ break;
+ case G_UNICODE_CONTROL:
+ if (wc == '\t' || wc == '\n' || wc == '\r' || wc == '\f')
+ space_or_hyphen = TRUE;
+ break;
+ default:
+ if (wc == '-' || /* Hyphen-minus */
+ wc == 0x058a || /* Armenian hyphen */
+ wc == 0x1400 || /* Canadian syllabics hyphen */
+ wc == 0x1806 || /* Mongolian todo hyphen */
+ wc == 0x2010 || /* Hyphen */
+ wc == 0x2e17 || /* Double oblique hyphen */
+ wc == 0x2e40 || /* Double hyphen */
+ wc == 0x30a0 || /* Katakana-Hiragana double hyphen */
+ wc == 0xfe63 || /* Small hyphen-minus */
+ wc == 0xff0d) /* Fullwidth hyphen-minus */
+ space_or_hyphen = TRUE;
+ break;
+ }
+
+ if (prev_wc == 0x2027) /* Hyphenation point */
+ {
+ attrs[i].break_inserts_hyphen = TRUE;
+ attrs[i].break_removes_preceding = TRUE;
+ }
+ else if (attrs[i].is_word_boundary)
+ attrs[i].break_inserts_hyphen = FALSE;
+ else if (prev_space_or_hyphen)
+ attrs[i].break_inserts_hyphen = FALSE;
+ else if (space_or_hyphen)
+ attrs[i].break_inserts_hyphen = FALSE;
+ else
+ attrs[i].break_inserts_hyphen = insert_hyphens;
+
+ prev_space_or_hyphen = space_or_hyphen;
+ }
+
+ prev_wc = wc;
+ prev_script = script;
+
+ /* wc might not be a valid Unicode base character, but really all we
+ * need to know is the last non-combining character */
+ if (type != G_UNICODE_SPACING_MARK &&
+ type != G_UNICODE_ENCLOSING_MARK &&
+ type != G_UNICODE_NON_SPACING_MARK)
+ base_character = wc;
+ }
+
+ i--;
+
+ attrs[0].is_cursor_position = TRUE; /* Rule GB1 */
+ attrs[i].is_cursor_position = TRUE; /* Rule GB2 */
+
+ attrs[0].is_word_boundary = TRUE; /* Rule WB1 */
+ attrs[i].is_word_boundary = TRUE; /* Rule WB2 */
+
+ attrs[0].is_line_break = FALSE; /* Rule LB2 */
+ attrs[i].is_line_break = TRUE; /* Rule LB3 */
+ attrs[i].is_mandatory_break = TRUE; /* Rule LB3 */
+}
+
+/* }}} */
+/* {{{ Tailoring */
+/* {{{ Script-specific tailoring */
+
+#include "break-arabic.c"
+#include "break-indic.c"
+#include "break-thai.c"
+#include "break-latin.c"
+
+static gboolean
+break_script (const char *item_text,
+ unsigned int item_length,
+ const Pango2Analysis *analysis,
+ Pango2LogAttr *attrs,
+ int attrs_len)
+{
+ switch (analysis->script)
+ {
+ case G_UNICODE_SCRIPT_ARABIC:
+ break_arabic (item_text, item_length, analysis, attrs, attrs_len);
+ break;
+
+ case G_UNICODE_SCRIPT_DEVANAGARI:
+ case G_UNICODE_SCRIPT_BENGALI:
+ case G_UNICODE_SCRIPT_GURMUKHI:
+ case G_UNICODE_SCRIPT_GUJARATI:
+ case G_UNICODE_SCRIPT_ORIYA:
+ case G_UNICODE_SCRIPT_TAMIL:
+ case G_UNICODE_SCRIPT_TELUGU:
+ case G_UNICODE_SCRIPT_KANNADA:
+ case G_UNICODE_SCRIPT_MALAYALAM:
+ case G_UNICODE_SCRIPT_SINHALA:
+ break_indic (item_text, item_length, analysis, attrs, attrs_len);
+ break;
+
+ case G_UNICODE_SCRIPT_THAI:
+ break_thai (item_text, item_length, analysis, attrs, attrs_len);
+ break;
+
+ case G_UNICODE_SCRIPT_LATIN:
+ break_latin (item_text, item_length, analysis, attrs, attrs_len);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* }}} */
+/* {{{ Attribute-based customization */
+
+/* We allow customizing log attrs in two ways:
+ *
+ * - You can directly remove breaks from a range, using allow_breaks=false.
+ * We preserve the non-tailorable rules from UAX #14, so mandatory breaks
+ * and breaks after ZWS remain. We also preserve break opportunities after
+ * hyphens and visible word dividers.
+ *
+ * - You can tweak the segmentation by marking ranges as word or sentence.
+ * When doing so, we split adjacent segments to preserve alternating
+ * starts and ends. We add a line break opportunity before each word that
+ * is created in this way, and we remove line break opportunities inside
+ * the word in the same way as for a range marked as allow_breaks=false,
+ * except that we don't remove char break opportunities.
+ *
+ * Note that UAX #14 does not guarantee that words fall neatly into
+ * sentences, so we don't do extra work to enforce that.
+ */
+
+static void
+remove_breaks_from_range (const char *text,
+ int start,
+ Pango2LogAttr *log_attrs,
+ int start_pos,
+ int end_pos)
+{
+ int pos;
+ const char *p;
+ gunichar ch;
+ int bt;
+ gboolean after_zws;
+ gboolean after_hyphen;
+
+ /* Assume our range doesn't start after a hyphen or in a zws sequence */
+ after_zws = FALSE;
+ after_hyphen = FALSE;
+ for (pos = start_pos + 1, p = g_utf8_next_char (text + start);
+ pos < end_pos;
+ pos++, p = g_utf8_next_char (p))
+ {
+ /* Mandatory breaks aren't tailorable */
+ if (!log_attrs[pos].is_mandatory_break)
+ log_attrs[pos].is_line_break = FALSE;
+
+ ch = g_utf8_get_char (p);
+ bt = g_unichar_break_type (ch);
+
+ /* Hyphens and visible word dividers */
+ if (after_hyphen)
+ log_attrs[pos].is_line_break = TRUE;
+
+ after_hyphen = ch == 0x00ad || /* Soft Hyphen */
+ ch == 0x05A0 || ch == 0x2010 || /* Breaking Hyphens */
+ ch == 0x2012 || ch == 0x2013 ||
+ ch == 0x05BE || ch == 0x0F0B || /* Visible word dividers */
+ ch == 0x1361 || ch == 0x17D8 ||
+ ch == 0x17DA || ch == 0x2027 ||
+ ch == 0x007C;
+
+ /* ZWS sequence */
+ if (after_zws && bt != G_UNICODE_BREAK_SPACE)
+ log_attrs[pos].is_line_break = TRUE;
+
+ after_zws = bt == G_UNICODE_BREAK_ZERO_WIDTH_SPACE ||
+ (bt == G_UNICODE_BREAK_SPACE && after_zws);
+ }
+}
+
+static gboolean
+handle_allow_breaks (const char *text,
+ int length,
+ Pango2AttrList *attrs,
+ int offset,
+ Pango2LogAttr *log_attrs,
+ int log_attrs_len)
+{
+ Pango2AttrIterator iter;
+ gboolean tailored = FALSE;
+
+ pango2_attr_list_init_iterator (attrs, &iter);
+
+ do
+ {
+ const Pango2Attribute *attr = pango2_attr_iterator_get (&iter, PANGO2_ATTR_ALLOW_BREAKS);
+
+ if (!attr)
+ continue;
+
+ if (!attr->int_value)
+ {
+ int start, end;
+ int start_pos, end_pos;
+ int pos;
+
+ start = attr->start_index;
+ end = attr->end_index;
+ if (start < offset)
+ start_pos = 0;
+ else
+ start_pos = g_utf8_pointer_to_offset (text, text + start - offset);
+ if (end >= offset + length)
+ end_pos = log_attrs_len;
+ else
+ end_pos = g_utf8_pointer_to_offset (text, text + end - offset);
+
+ for (pos = start_pos + 1; pos < end_pos; pos++)
+ log_attrs[pos].is_char_break = FALSE;
+
+ remove_breaks_from_range (text, MAX (start - offset, 0), log_attrs, start_pos, end_pos);
+
+ tailored = TRUE;
+ }
+ }
+ while (pango2_attr_iterator_next (&iter));
+
+ pango2_attr_iterator_clear (&iter);
+
+ return tailored;
+}
+
+
+static gboolean
+handle_words (const char *text,
+ int length,
+ Pango2AttrList *attrs,
+ int offset,
+ Pango2LogAttr *log_attrs,
+ int log_attrs_len)
+{
+ Pango2AttrIterator iter;
+ gboolean tailored = FALSE;
+
+ pango2_attr_list_init_iterator (attrs, &iter);
+
+ do
+ {
+ const Pango2Attribute *attr = pango2_attr_iterator_get (&iter, PANGO2_ATTR_WORD);
+ int start, end;
+ int start_pos, end_pos;
+ int pos;
+
+ if (!attr)
+ continue;
+
+ start = attr->start_index;
+ end = attr->end_index;
+ if (start < offset)
+ start_pos = 0;
+ else
+ start_pos = g_utf8_pointer_to_offset (text, text + start - offset);
+ if (end >= offset + length)
+ end_pos = log_attrs_len;
+ else
+ end_pos = g_utf8_pointer_to_offset (text, text + end - offset);
+
+ for (pos = start_pos + 1; pos < end_pos; pos++)
+ {
+ log_attrs[pos].is_word_start = FALSE;
+ log_attrs[pos].is_word_end = FALSE;
+ log_attrs[pos].is_word_boundary = FALSE;
+ }
+
+ remove_breaks_from_range (text, MAX (start - offset, 0), log_attrs,
+ start_pos, end_pos);
+
+ if (start >= offset)
+ {
+ gboolean in_word = FALSE;
+ for (pos = start_pos; pos >= 0; pos--)
+ {
+ if (log_attrs[pos].is_word_end)
+ {
+ in_word = pos == start_pos;
+ break;
+ }
+ if (pos < start_pos && log_attrs[pos].is_word_start)
+ {
+ in_word = TRUE;
+ break;
+ }
+ }
+ log_attrs[start_pos].is_word_start = TRUE;
+ log_attrs[start_pos].is_word_end = in_word;
+ log_attrs[start_pos].is_word_boundary = TRUE;
+
+ /* Allow line breaks before words */
+ if (start_pos > 0)
+ log_attrs[start_pos].is_line_break = TRUE;
+
+ tailored = TRUE;
+ }
+
+ if (end < offset + length)
+ {
+ gboolean in_word = FALSE;
+ for (pos = end_pos; pos < log_attrs_len; pos++)
+ {
+ if (log_attrs[pos].is_word_start)
+ {
+ in_word = pos == end_pos;
+ break;
+ }
+ if (pos > end_pos && log_attrs[pos].is_word_end)
+ {
+ in_word = TRUE;
+ break;
+ }
+ }
+ log_attrs[end_pos].is_word_start = in_word;
+ log_attrs[end_pos].is_word_end = TRUE;
+ log_attrs[end_pos].is_word_boundary = TRUE;
+
+ /* Allow line breaks before words */
+ if (in_word)
+ log_attrs[end_pos].is_line_break = TRUE;
+
+ tailored = TRUE;
+ }
+ }
+ while (pango2_attr_iterator_next (&iter));
+
+ pango2_attr_iterator_clear (&iter);
+
+ return tailored;
+}
+
+static gboolean
+handle_sentences (const char *text,
+ int length,
+ Pango2AttrList *attrs,
+ int offset,
+ Pango2LogAttr *log_attrs,
+ int log_attrs_len)
+{
+ Pango2AttrIterator iter;
+ gboolean tailored = FALSE;
+
+ pango2_attr_list_init_iterator (attrs, &iter);
+
+ do
+ {
+ const Pango2Attribute *attr = pango2_attr_iterator_get (&iter, PANGO2_ATTR_SENTENCE);
+ int start, end;
+ int start_pos, end_pos;
+ int pos;
+
+ if (!attr)
+ continue;
+
+ start = attr->start_index;
+ end = attr->end_index;
+ if (start < offset)
+ start_pos = 0;
+ else
+ start_pos = g_utf8_pointer_to_offset (text, text + start - offset);
+ if (end >= offset + length)
+ end_pos = log_attrs_len;
+ else
+ end_pos = g_utf8_pointer_to_offset (text, text + end - offset);
+
+ for (pos = start_pos + 1; pos < end_pos; pos++)
+ {
+ log_attrs[pos].is_sentence_start = FALSE;
+ log_attrs[pos].is_sentence_end = FALSE;
+ log_attrs[pos].is_sentence_boundary = FALSE;
+
+ tailored = TRUE;
+ }
+ if (start >= offset)
+ {
+ gboolean in_sentence = FALSE;
+ for (pos = start_pos - 1; pos >= 0; pos--)
+ {
+ if (log_attrs[pos].is_sentence_end)
+ break;
+ if (log_attrs[pos].is_sentence_start)
+ {
+ in_sentence = TRUE;
+ break;
+ }
+ }
+ log_attrs[start_pos].is_sentence_start = TRUE;
+ log_attrs[start_pos].is_sentence_end = in_sentence;
+ log_attrs[start_pos].is_sentence_boundary = TRUE;
+
+ tailored = TRUE;
+ }
+ if (end < offset + length)
+ {
+ gboolean in_sentence = FALSE;
+ for (pos = end_pos + 1; end_pos < log_attrs_len; pos++)
+ {
+ if (log_attrs[pos].is_sentence_start)
+ break;
+ if (log_attrs[pos].is_sentence_end)
+ {
+ in_sentence = TRUE;
+ break;
+ }
+ }
+ log_attrs[end_pos].is_sentence_start = in_sentence;
+ log_attrs[end_pos].is_sentence_end = TRUE;
+ log_attrs[end_pos].is_sentence_boundary = TRUE;
+
+ tailored = TRUE;
+ }
+ }
+ while (pango2_attr_iterator_next (&iter));
+
+ pango2_attr_iterator_clear (&iter);
+
+ return tailored;
+}
+
+static gboolean
+handle_hyphens (const char *text,
+ int length,
+ Pango2AttrList *attrs,
+ int offset,
+ Pango2LogAttr *log_attrs,
+ int log_attrs_len)
+{
+ Pango2AttrIterator iter;
+ gboolean tailored = FALSE;
+
+ pango2_attr_list_init_iterator (attrs, &iter);
+
+ do {
+ const Pango2Attribute *attr = pango2_attr_iterator_get (&iter, PANGO2_ATTR_INSERT_HYPHENS);
+
+ if (attr && attr->int_value == 0)
+ {
+ int start, end;
+ int start_pos, end_pos;
+ int pos;
+
+ pango2_attr_iterator_range (&iter, &start, &end);
+ if (start < offset)
+ start_pos = 0;
+ else
+ start_pos = g_utf8_pointer_to_offset (text, text + start - offset);
+ if (end >= offset + length)
+ end_pos = log_attrs_len;
+ else
+ end_pos = g_utf8_pointer_to_offset (text, text + end - offset);
+
+ for (pos = start_pos + 1; pos < end_pos; pos++)
+ {
+ if (!log_attrs[pos].break_removes_preceding)
+ {
+ log_attrs[pos].break_inserts_hyphen = FALSE;
+
+ tailored = TRUE;
+ }
+ }
+ }
+ } while (pango2_attr_iterator_next (&iter));
+
+ pango2_attr_iterator_clear (&iter);
+
+ return tailored;
+}
+
+static gboolean
+break_attrs (const char *text,
+ int length,
+ GSList *attributes,
+ int offset,
+ Pango2LogAttr *log_attrs,
+ int log_attrs_len)
+{
+ Pango2AttrList allow_breaks;
+ Pango2AttrList words;
+ Pango2AttrList sentences;
+ Pango2AttrList hyphens;
+ GSList *l;
+ gboolean tailored = FALSE;
+
+ pango2_attr_list_init (&allow_breaks);
+ pango2_attr_list_init (&words);
+ pango2_attr_list_init (&sentences);
+ pango2_attr_list_init (&hyphens);
+
+ for (l = attributes; l; l = l->next)
+ {
+ Pango2Attribute *attr = l->data;
+
+ if (attr->type == PANGO2_ATTR_ALLOW_BREAKS)
+ pango2_attr_list_insert (&allow_breaks, pango2_attribute_copy (attr));
+ else if (attr->type == PANGO2_ATTR_WORD)
+ pango2_attr_list_insert (&words, pango2_attribute_copy (attr));
+ else if (attr->type == PANGO2_ATTR_SENTENCE)
+ pango2_attr_list_insert (&sentences, pango2_attribute_copy (attr));
+ else if (attr->type == PANGO2_ATTR_INSERT_HYPHENS)
+ pango2_attr_list_insert (&hyphens, pango2_attribute_copy (attr));
+ }
+
+ tailored |= handle_words (text, length, &words, offset,
+ log_attrs, log_attrs_len);
+
+ tailored |= handle_sentences (text, length, &words, offset,
+ log_attrs, log_attrs_len);
+
+ tailored |= handle_hyphens (text, length, &hyphens, offset,
+ log_attrs, log_attrs_len);
+
+ tailored |= handle_allow_breaks (text, length, &allow_breaks, offset,
+ log_attrs, log_attrs_len);
+
+ pango2_attr_list_destroy (&allow_breaks);
+ pango2_attr_list_destroy (&words);
+ pango2_attr_list_destroy (&sentences);
+ pango2_attr_list_destroy (&hyphens);
+
+ return tailored;
+}
+
+/* }}} */
+
+static gboolean
+tailor_break (const char *text,
+ int length,
+ Pango2Analysis *analysis,
+ int item_offset,
+ Pango2LogAttr *attrs,
+ int attrs_len)
+{
+ gboolean res;
+
+ if (length < 0)
+ length = strlen (text);
+ else if (text == NULL)
+ text = "";
+
+ res = break_script (text, length, analysis, attrs, attrs_len);
+
+ if (item_offset >= 0 && analysis->extra_attrs)
+ res |= break_attrs (text, length, analysis->extra_attrs, item_offset, attrs, attrs_len);
+
+ return res;
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_default_break:
+ * @text: text to break. Must be valid UTF-8
+ * @length: length of text in bytes (may be -1 if @text is nul-terminated)
+ * @attrs: logical attributes to fill in
+ * @attrs_len: size of the array passed as @attrs
+ *
+ * This is the default break algorithm.
+ *
+ * It applies rules from the [Unicode Line Breaking Algorithm](http://www.unicode.org/unicode/reports/tr14/)
+ * without language-specific tailoring.
+ *
+ * See [func@Pango2.tailor_break] for language-specific breaks.
+ *
+ * See [func@Pango2.attr_break] for attribute-based customization.
+ */
+void
+pango2_default_break (const char *text,
+ int length,
+ Pango2LogAttr *attrs,
+ int attrs_len G_GNUC_UNUSED)
+{
+ Pango2LogAttr before = *attrs;
+
+ default_break (text, length, attrs, attrs_len);
+
+ attrs->is_line_break |= before.is_line_break;
+ attrs->is_mandatory_break |= before.is_mandatory_break;
+ attrs->is_cursor_position |= before.is_cursor_position;
+}
+
+/**
+ * pango2_tailor_break:
+ * @text: text to process. Must be valid UTF-8
+ * @length: length in bytes of @text
+ * @analysis: `Pango2Analysis` for @text
+ * @offset: Byte offset of @text from the beginning of the
+ * paragraph, or -1 to ignore attributes from @analysis
+ * @attrs: (array length=attrs_len): array with one `Pango2LogAttr`
+ * per character in @text, plus one extra, to be filled in
+ * @attrs_len: length of @attrs array
+ *
+ * Apply language-specific tailoring to the breaks in @attrs.
+ *
+ * The line breaks are assumed to have been produced by [func@Pango2.default_break].
+ *
+ * If @offset is not -1, it is used to apply attributes from @analysis that are
+ * relevant to line breaking.
+ *
+ * Note that it is better to pass -1 for @offset and use [func@Pango2.attr_break]
+ * to apply attributes to the whole paragraph.
+ */
+void
+pango2_tailor_break (const char *text,
+ int length,
+ Pango2Analysis *analysis,
+ int offset,
+ Pango2LogAttr *attrs,
+ int attrs_len)
+{
+ Pango2LogAttr *start = attrs;
+ Pango2LogAttr attr_before = *start;
+
+ if (tailor_break (text, length, analysis, offset, attrs, attrs_len))
+ {
+ /* if tailored, we enforce some of the attrs from before
+ * tailoring at the boundary
+ */
+
+ start->backspace_deletes_character = attr_before.backspace_deletes_character;
+
+ start->is_line_break |= attr_before.is_line_break;
+ start->is_mandatory_break |= attr_before.is_mandatory_break;
+ start->is_cursor_position |= attr_before.is_cursor_position;
+ }
+}
+
+/**
+ * pango2_attr_break:
+ * @text: text to break. Must be valid UTF-8
+ * @length: length of text in bytes (may be -1 if @text is nul-terminated)
+ * @attr_list: `Pango2AttrList` to apply
+ * @offset: Byte offset of @text from the beginning of the paragraph
+ * @attrs: (array length=attrs_len): array with one `Pango2LogAttr`
+ * per character in @text, plus one extra, to be filled in
+ * @attrs_len: length of @attrs array
+ *
+ * Apply customization from attributes to the breaks in @attrs.
+ *
+ * The line breaks are assumed to have been produced
+ * by [func@Pango2.default_break] and [func@Pango2.tailor_break].
+ */
+void
+pango2_attr_break (const char *text,
+ int length,
+ Pango2AttrList *attr_list,
+ int offset,
+ Pango2LogAttr *attrs,
+ int attrs_len)
+{
+ Pango2LogAttr *start = attrs;
+ Pango2LogAttr attr_before = *start;
+ GSList *attributes;
+
+ attributes = pango2_attr_list_get_attributes (attr_list);
+ if (break_attrs (text, length, attributes, offset, attrs, attrs_len))
+ {
+ /* if tailored, we enforce some of the attrs from before
+ * tailoring at the boundary
+ */
+
+ start->backspace_deletes_character = attr_before.backspace_deletes_character;
+
+ start->is_line_break |= attr_before.is_line_break;
+ start->is_mandatory_break |= attr_before.is_mandatory_break;
+ start->is_cursor_position |= attr_before.is_cursor_position;
+ }
+
+ g_slist_free_full (attributes, (GDestroyNotify)pango2_attribute_destroy);
+}
+
+/**
+ * pango2_get_log_attrs:
+ * @text: text to process. Must be valid UTF-8
+ * @length: length in bytes of @text
+ * @attr_list: (nullable): `Pango2AttrList` to apply
+ * @level: embedding level, or -1 if unknown
+ * @language: language tag
+ * @attrs: (array length=attrs_len): array with one `Pango2LogAttr`
+ * per character in @text, plus one extra, to be filled in
+ * @attrs_len: length of @attrs array
+ *
+ * Computes a `Pango2LogAttr` for each character in @text.
+ *
+ * The @attrs array must have one `Pango2LogAttr` for
+ * each position in @text; if @text contains N characters,
+ * it has N+1 positions, including the last position at the
+ * end of the text. @text should be an entire paragraph;
+ * logical attributes can't be computed without context
+ * (for example you need to see spaces on either side of
+ * a word to know the word is a word).
+ */
+void
+pango2_get_log_attrs (const char *text,
+ int length,
+ Pango2AttrList *attr_list,
+ int level,
+ Pango2Language *language,
+ Pango2LogAttr *attrs,
+ int attrs_len)
+{
+ int chars_broken;
+ Pango2Analysis analysis = { NULL };
+ Pango2ScriptIter iter;
+
+ g_return_if_fail (length == 0 || text != NULL);
+ g_return_if_fail (attrs != NULL);
+
+ analysis.level = level;
+ analysis.language = language;
+
+ pango2_default_break (text, length, attrs, attrs_len);
+
+ chars_broken = 0;
+
+ _pango2_script_iter_init (&iter, text, length);
+ do
+ {
+ const char *run_start, *run_end;
+ GUnicodeScript script;
+ int chars_in_range;
+
+ pango2_script_iter_get_range (&iter, &run_start, &run_end, &script);
+ analysis.script = script;
+
+ chars_in_range = pango2_utf8_strlen (run_start, run_end - run_start);
+
+ pango2_tailor_break (run_start,
+ run_end - run_start,
+ &analysis,
+ -1,
+ attrs + chars_broken,
+ chars_in_range + 1);
+
+ chars_broken += chars_in_range;
+ }
+ while (pango2_script_iter_next (&iter));
+ _pango2_script_iter_fini (&iter);
+
+ if (attr_list)
+ pango2_attr_break (text, length, attr_list, 0, attrs, attrs_len);
+
+ if (chars_broken + 1 > attrs_len)
+ g_warning ("pango2_get_log_attrs: attrs_len should have been at least %d, but was %d. Expect corrupted memory.",
+ chars_broken + 1,
+ attrs_len);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/ellipsize.c b/pango2/ellipsize.c
new file mode 100644
index 00000000..a5f74131
--- /dev/null
+++ b/pango2/ellipsize.c
@@ -0,0 +1,797 @@
+/* Pango2
+ * ellipsize.c: Routine to ellipsize layout lines
+ *
+ * Copyright (C) 2004 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.
+ */
+
+#include "config.h"
+#include <string.h>
+
+#include "pango-item-private.h"
+#include "pango-glyph-item-private.h"
+#include "pango-glyph-iter-private.h"
+#include "pango-font-private.h"
+#include "pango-attributes-private.h"
+#include "pango-attr-private.h"
+#include "pango-attr-list-private.h"
+#include "pango-attr-iterator-private.h"
+#include "pango-impl-utils.h"
+#include "pango-line-private.h"
+
+typedef struct _EllipsizeState EllipsizeState;
+typedef struct _RunInfo RunInfo;
+typedef struct _LineIter LineIter;
+
+
+/* Overall, the way we ellipsize is we grow a "gap" out from an original
+ * gap center position until:
+ *
+ * line_width - gap_width + ellipsize_width <= goal_width
+ *
+ * Line: [-------------------------------------------]
+ * Runs: [------)[---------------)[------------------]
+ * Gap center: *
+ * Gap: [----------------------]
+ *
+ * The gap center may be at the start or end in which case the gap grows
+ * in only one direction.
+ *
+ * Note the line and last run are logically closed at the end; this allows
+ * us to use a gap position at x=line_width and still have it be part of
+ * of a run.
+ *
+ * We grow the gap out one "span" at a time, where a span is simply a
+ * consecutive run of clusters that we can't interrupt with an ellipsis.
+ *
+ * When choosing whether to grow the gap at the start or the end, we
+ * calculate the next span to remove in both directions and see which
+ * causes the smaller increase in:
+ *
+ * MAX (gap_end - gap_center, gap_start - gap_center)
+ *
+ * All computations are done using logical order; the ellipsization
+ * process occurs before the runs are ordered into visual order.
+ */
+
+/* Keeps information about a single run */
+struct _RunInfo
+{
+ Pango2GlyphItem *run;
+ int start_offset; /* Character offset of run start */
+ int width; /* Width of run in Pango2 units */
+};
+
+/* Iterator to a position within the ellipsized line */
+struct _LineIter
+{
+ Pango2GlyphItemIter run_iter;
+ int run_index;
+};
+
+/* State of ellipsization process */
+struct _EllipsizeState
+{
+ Pango2Context *context;
+ const char *text;
+ Pango2LogAttr *log_attrs;
+ Pango2EllipsizeMode ellipsize;
+ Pango2AttrList *attrs; /* Attributes used for itemization/shaping */
+
+ RunInfo *run_info; /* Array of information about each run */
+ int n_runs;
+
+ int total_width; /* Original width of line in Pango2 units */
+ int gap_center; /* Goal for center of gap */
+
+ Pango2GlyphItem *ellipsis_run; /* Run created to hold ellipsis */
+ int ellipsis_width; /* Width of ellipsis, in Pango2 units */
+ int ellipsis_is_cjk; /* Whether the first character in the ellipsized
+ * is wide; this triggers us to try to use a
+ * mid-line ellipsis instead of a baseline
+ */
+
+ Pango2AttrIterator *line_start_attr; /* Cached Pango2AttrIterator for the start of the run */
+
+ LineIter gap_start_iter; /* Iteratator pointig to the first cluster in gap */
+ int gap_start_x; /* x position of start of gap, in Pango2 units */
+ Pango2AttrIterator *gap_start_attr; /* Attribute iterator pointing to a range containing
+ * the first character in gap */
+
+ LineIter gap_end_iter; /* Iterator pointing to last cluster in gap */
+ int gap_end_x; /* x position of end of gap, in Pango2 units */
+
+ Pango2ShapeFlags shape_flags;
+};
+
+static void
+init_state (EllipsizeState *state,
+ Pango2Context *context,
+ const char *text,
+ int start_index,
+ Pango2LogAttr *log_attrs,
+ Pango2EllipsizeMode ellipsize,
+ GSList *runs,
+ Pango2AttrList *attrs,
+ Pango2ShapeFlags shape_flags)
+{
+ GSList *l;
+ int i;
+ int start_offset;
+
+ state->context = context;
+ state->text = text;
+ state->log_attrs = log_attrs;
+ state->ellipsize = ellipsize;
+
+ if (attrs)
+ state->attrs = pango2_attr_list_ref (attrs);
+ else
+ state->attrs = pango2_attr_list_new ();
+
+ state->shape_flags = shape_flags;
+
+ state->n_runs = g_slist_length (runs);
+ state->run_info = g_new (RunInfo, state->n_runs);
+
+ start_offset = pango2_utf8_strlen (state->text, start_index);
+
+ state->total_width = 0;
+ for (l = runs, i = 0; l; l = l->next, i++)
+ {
+ Pango2GlyphItem *run = l->data;
+ int width = pango2_glyph_string_get_width (run->glyphs);
+ state->run_info[i].run = run;
+ state->run_info[i].width = width;
+ state->run_info[i].start_offset = start_offset;
+ state->total_width += width;
+
+ start_offset += run->item->num_chars;
+ }
+
+ state->ellipsis_run = NULL;
+ state->ellipsis_is_cjk = FALSE;
+ state->line_start_attr = NULL;
+ state->gap_start_attr = NULL;
+}
+
+/* Cleanup memory allocation
+ */
+static void
+free_state (EllipsizeState *state)
+{
+ pango2_attr_list_unref (state->attrs);
+ if (state->line_start_attr)
+ pango2_attr_iterator_destroy (state->line_start_attr);
+ if (state->gap_start_attr)
+ pango2_attr_iterator_destroy (state->gap_start_attr);
+ g_free (state->run_info);
+}
+
+/* Computes the width of a single cluster
+ */
+static int
+get_cluster_width (LineIter *iter)
+{
+ Pango2GlyphItemIter *run_iter = &iter->run_iter;
+ Pango2GlyphString *glyphs = run_iter->glyph_item->glyphs;
+ int width = 0;
+ int i;
+
+ if (run_iter->start_glyph < run_iter->end_glyph) /* LTR */
+ {
+ for (i = run_iter->start_glyph; i < run_iter->end_glyph; i++)
+ width += glyphs->glyphs[i].geometry.width;
+ }
+ else /* RTL */
+ {
+ for (i = run_iter->start_glyph; i > run_iter->end_glyph; i--)
+ width += glyphs->glyphs[i].geometry.width;
+ }
+
+ return width;
+}
+
+/* Move forward one cluster. Returns %FALSE if we were already at the end
+ */
+static gboolean
+line_iter_next_cluster (EllipsizeState *state,
+ LineIter *iter)
+{
+ if (!pango2_glyph_item_iter_next_cluster (&iter->run_iter))
+ {
+ if (iter->run_index == state->n_runs - 1)
+ return FALSE;
+ else
+ {
+ iter->run_index++;
+ pango2_glyph_item_iter_init_start (&iter->run_iter,
+ state->run_info[iter->run_index].run,
+ state->text);
+ }
+ }
+
+ return TRUE;
+}
+
+/* Move backward one cluster. Returns %FALSE if we were already at the end
+ */
+static gboolean
+line_iter_prev_cluster (EllipsizeState *state,
+ LineIter *iter)
+{
+ if (!pango2_glyph_item_iter_prev_cluster (&iter->run_iter))
+ {
+ if (iter->run_index == 0)
+ return FALSE;
+ else
+ {
+ iter->run_index--;
+ pango2_glyph_item_iter_init_end (&iter->run_iter,
+ state->run_info[iter->run_index].run,
+ state->text);
+ }
+ }
+
+ return TRUE;
+}
+
+/*
+ * An ellipsization boundary is defined by two things
+ *
+ * - Starts a cluster - forced by structure of code
+ * - Starts a grapheme - checked here
+ *
+ * In the future we'd also like to add a check for cursive connectivity here.
+ * This should be an addition to `Pango2GlyphVisAttr`
+ *
+ */
+
+/* Checks if there is a ellipsization boundary before the cluster @iter points to
+ */
+static gboolean
+starts_at_ellipsization_boundary (EllipsizeState *state,
+ LineIter *iter)
+{
+ RunInfo *run_info = &state->run_info[iter->run_index];
+
+ if (iter->run_iter.start_char == 0 && iter->run_index == 0)
+ return TRUE;
+
+ return state->log_attrs[run_info->start_offset + iter->run_iter.start_char].is_cursor_position;
+}
+
+/* Checks if there is a ellipsization boundary after the cluster @iter points to
+ */
+static gboolean
+ends_at_ellipsization_boundary (EllipsizeState *state,
+ LineIter *iter)
+{
+ RunInfo *run_info = &state->run_info[iter->run_index];
+
+ if (iter->run_iter.end_char == run_info->run->item->num_chars && iter->run_index == state->n_runs - 1)
+ return TRUE;
+
+ return state->log_attrs[run_info->start_offset + iter->run_iter.end_char + 1].is_cursor_position;
+}
+
+/* Helper function to re-itemize a string of text
+ */
+static Pango2Item *
+itemize_text (EllipsizeState *state,
+ const char *text,
+ Pango2AttrList *attrs)
+{
+ GList *items;
+ Pango2Item *item;
+ Pango2Direction dir;
+
+ dir = pango2_context_get_base_dir (state->context);
+ items = pango2_itemize (state->context, dir, text, 0, strlen (text), attrs);
+ g_assert (g_list_length (items) == 1);
+
+ item = items->data;
+ g_list_free (items);
+
+ return item;
+}
+
+/* Shapes the ellipsis using the font and is_cjk information computed by
+ * update_ellipsis_shape() from the first character in the gap.
+ */
+static void
+shape_ellipsis (EllipsizeState *state)
+{
+ Pango2AttrList attrs;
+ GSList *run_attrs;
+ Pango2Item *item;
+ Pango2GlyphString *glyphs;
+ GSList *l;
+ Pango2Attribute *fallback;
+ const char *ellipsis_text;
+ int len;
+ int i;
+
+ pango2_attr_list_init (&attrs);
+
+ /* Create/reset state->ellipsis_run
+ */
+ if (!state->ellipsis_run)
+ {
+ state->ellipsis_run = g_slice_new0 (Pango2GlyphItem);
+ state->ellipsis_run->glyphs = pango2_glyph_string_new ();
+ }
+
+ if (state->ellipsis_run->item)
+ {
+ pango2_item_free (state->ellipsis_run->item);
+ state->ellipsis_run->item = NULL;
+ }
+
+ /* Create an attribute list
+ */
+ run_attrs = pango2_attr_iterator_get_attrs (state->gap_start_attr);
+ int s, e;
+ pango2_attr_iterator_range (state->gap_start_attr, &s, &e);
+ for (l = run_attrs; l; l = l->next)
+ {
+ Pango2Attribute *attr = l->data;
+ attr->start_index = PANGO2_ATTR_INDEX_FROM_TEXT_BEGINNING;
+ attr->end_index = PANGO2_ATTR_INDEX_TO_TEXT_END;
+
+ if (pango2_attribute_affects_itemization (attr, NULL) ||
+ pango2_attribute_affects_break_or_shape (attr, NULL))
+ pango2_attr_list_insert (&attrs, attr);
+ else
+ pango2_attribute_destroy (attr);
+ }
+
+ g_slist_free (run_attrs);
+
+ fallback = pango2_attr_fallback_new (FALSE);
+ fallback->start_index = 0;
+ fallback->end_index = G_MAXINT;
+ pango2_attr_list_insert (&attrs, fallback);
+
+ /* First try using a specific ellipsis character in the best matching font
+ */
+ if (state->ellipsis_is_cjk)
+ ellipsis_text = "\342\213\257"; /* U+22EF: MIDLINE HORIZONTAL ELLIPSIS, used for CJK */
+ else
+ ellipsis_text = "\342\200\246"; /* U+2026: HORIZONTAL ELLIPSIS */
+
+ item = itemize_text (state, ellipsis_text, &attrs);
+
+ /* If that fails we use "..." in the first matching font
+ */
+ if (!item->analysis.font ||
+ !pango2_font_face_has_char (item->analysis.font->face,
+ g_utf8_get_char (ellipsis_text)))
+ {
+ pango2_item_free (item);
+
+ /* Modify the fallback iter while it is inside the Pango2AttrList; Don't try this at home
+ */
+ fallback->int_value = TRUE;
+
+ ellipsis_text = "...";
+ item = itemize_text (state, ellipsis_text, &attrs);
+ }
+
+ pango2_attr_list_destroy (&attrs);
+
+ state->ellipsis_run->item = item;
+
+ /* Now shape
+ */
+ glyphs = state->ellipsis_run->glyphs;
+
+ len = strlen (ellipsis_text);
+ pango2_shape (ellipsis_text, len,
+ ellipsis_text, len,
+ &item->analysis, glyphs,
+ state->shape_flags);
+
+ state->ellipsis_width = 0;
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ state->ellipsis_width += glyphs->glyphs[i].geometry.width;
+}
+
+/* Helper function to advance a Pango2AttrIterator to a particular
+ * byte index.
+ */
+static void
+advance_iterator_to (Pango2AttrIterator *iter,
+ int new_index)
+{
+ int start, end;
+
+ do
+ {
+ pango2_attr_iterator_range (iter, &start, &end);
+ if (end > new_index)
+ break;
+ }
+ while (pango2_attr_iterator_next (iter));
+}
+
+/* Updates the shaping of the ellipsis if necessary when we move the
+ * position of the start of the gap.
+ *
+ * The shaping of the ellipsis is determined by two things:
+ *
+ * - The font attributes applied to the first character in the gap
+ * - Whether the first character in the gap is wide or not. If the
+ * first character is wide, then we assume that we are ellipsizing
+ * East-Asian text, so prefer a mid-line ellipsizes to a baseline
+ * ellipsis, since that's typical practice for Chinese/Japanese/Korean.
+ */
+static void
+update_ellipsis_shape (EllipsizeState *state)
+{
+ gboolean recompute = FALSE;
+ gunichar start_wc;
+ gboolean is_cjk;
+
+ /* Unfortunately, we can only advance Pango2AttrIterator forward; so each
+ * time we back up we need to go forward to find the new position. To make
+ * this not utterly slow, we cache an iterator at the start of the line
+ */
+ if (!state->line_start_attr)
+ {
+ state->line_start_attr = pango2_attr_list_get_iterator (state->attrs);
+ advance_iterator_to (state->line_start_attr, state->run_info[0].run->item->offset);
+ }
+
+ if (state->gap_start_attr)
+ {
+ /* See if the current attribute range contains the new start position
+ */
+ int start, end;
+
+ pango2_attr_iterator_range (state->gap_start_attr, &start, &end);
+
+ if (state->gap_start_iter.run_iter.start_index < start)
+ {
+ pango2_attr_iterator_destroy (state->gap_start_attr);
+ state->gap_start_attr = NULL;
+ }
+ }
+
+ /* Check whether we need to recompute the ellipsis because of new font attributes
+ */
+ if (!state->gap_start_attr)
+ {
+ state->gap_start_attr = pango2_attr_iterator_copy (state->line_start_attr);
+ advance_iterator_to (state->gap_start_attr,
+ state->run_info[state->gap_start_iter.run_index].run->item->offset);
+
+ recompute = TRUE;
+ }
+
+ /* Check whether we need to recompute the ellipsis because we switch from CJK to not
+ * or vice-versa
+ */
+ start_wc = g_utf8_get_char (state->text + state->gap_start_iter.run_iter.start_index);
+ is_cjk = g_unichar_iswide (start_wc);
+
+ if (is_cjk != state->ellipsis_is_cjk)
+ {
+ state->ellipsis_is_cjk = is_cjk;
+ recompute = TRUE;
+ }
+
+ if (recompute)
+ shape_ellipsis (state);
+}
+
+/* Computes the position of the gap center and finds the smallest span containing it
+ */
+static void
+find_initial_span (EllipsizeState *state)
+{
+ Pango2GlyphItem *glyph_item;
+ Pango2GlyphItemIter *run_iter;
+ gboolean have_cluster;
+ int i;
+ int x;
+ int cluster_width;
+
+ switch (state->ellipsize)
+ {
+ case PANGO2_ELLIPSIZE_NONE:
+ default:
+ g_assert_not_reached ();
+ case PANGO2_ELLIPSIZE_START:
+ state->gap_center = 0;
+ break;
+ case PANGO2_ELLIPSIZE_MIDDLE:
+ state->gap_center = state->total_width / 2;
+ break;
+ case PANGO2_ELLIPSIZE_END:
+ state->gap_center = state->total_width;
+ break;
+ }
+
+ /* Find the run containing the gap center
+ */
+ x = 0;
+ for (i = 0; i < state->n_runs; i++)
+ {
+ if (x + state->run_info[i].width > state->gap_center)
+ break;
+
+ x += state->run_info[i].width;
+ }
+
+ if (i == state->n_runs) /* Last run is a closed interval, so back off one run */
+ {
+ i--;
+ x -= state->run_info[i].width;
+ }
+
+ /* Find the cluster containing the gap center
+ */
+ state->gap_start_iter.run_index = i;
+ run_iter = &state->gap_start_iter.run_iter;
+ glyph_item = state->run_info[i].run;
+
+ cluster_width = 0; /* Quiet GCC, the line must have at least one cluster */
+ for (have_cluster = pango2_glyph_item_iter_init_start (run_iter, glyph_item, state->text);
+ have_cluster;
+ have_cluster = pango2_glyph_item_iter_next_cluster (run_iter))
+ {
+ cluster_width = get_cluster_width (&state->gap_start_iter);
+
+ if (x + cluster_width > state->gap_center)
+ break;
+
+ x += cluster_width;
+ }
+
+ if (!have_cluster) /* Last cluster is a closed interval, so back off one cluster */
+ x -= cluster_width;
+
+ state->gap_end_iter = state->gap_start_iter;
+
+ state->gap_start_x = x;
+ state->gap_end_x = x + cluster_width;
+
+ /* Expand the gap to a full span
+ */
+ while (!starts_at_ellipsization_boundary (state, &state->gap_start_iter))
+ {
+ line_iter_prev_cluster (state, &state->gap_start_iter);
+ state->gap_start_x -= get_cluster_width (&state->gap_start_iter);
+ }
+
+ while (!ends_at_ellipsization_boundary (state, &state->gap_end_iter))
+ {
+ line_iter_next_cluster (state, &state->gap_end_iter);
+ state->gap_end_x += get_cluster_width (&state->gap_end_iter);
+ }
+
+ update_ellipsis_shape (state);
+}
+
+/* Removes one run from the start or end of the gap. Returns FALSE
+ * if there's nothing left to remove in either direction.
+ */
+static gboolean
+remove_one_span (EllipsizeState *state)
+{
+ LineIter new_gap_start_iter;
+ LineIter new_gap_end_iter;
+ int new_gap_start_x;
+ int new_gap_end_x;
+ int width;
+
+ /* Find one span backwards and forward from the gap
+ */
+ new_gap_start_iter = state->gap_start_iter;
+ new_gap_start_x = state->gap_start_x;
+ do
+ {
+ if (!line_iter_prev_cluster (state, &new_gap_start_iter))
+ break;
+ width = get_cluster_width (&new_gap_start_iter);
+ new_gap_start_x -= width;
+ }
+ while (!starts_at_ellipsization_boundary (state, &new_gap_start_iter) ||
+ width == 0);
+
+ new_gap_end_iter = state->gap_end_iter;
+ new_gap_end_x = state->gap_end_x;
+ do
+ {
+ if (!line_iter_next_cluster (state, &new_gap_end_iter))
+ break;
+ width = get_cluster_width (&new_gap_end_iter);
+ new_gap_end_x += width;
+ }
+ while (!ends_at_ellipsization_boundary (state, &new_gap_end_iter) ||
+ width == 0);
+
+ if (state->gap_end_x == new_gap_end_x && state->gap_start_x == new_gap_start_x)
+ return FALSE;
+
+ /* In the case where we could remove a span from either end of the
+ * gap, we look at which causes the smaller increase in the
+ * MAX (gap_end - gap_center, gap_start - gap_center)
+ */
+ if (state->gap_end_x == new_gap_end_x ||
+ (state->gap_start_x != new_gap_start_x &&
+ state->gap_center - new_gap_start_x < new_gap_end_x - state->gap_center))
+ {
+ state->gap_start_iter = new_gap_start_iter;
+ state->gap_start_x = new_gap_start_x;
+
+ update_ellipsis_shape (state);
+ }
+ else
+ {
+ state->gap_end_iter = new_gap_end_iter;
+ state->gap_end_x = new_gap_end_x;
+ }
+
+ return TRUE;
+}
+
+/* Fixes up the properties of the ellipsis run once we've determined the final extents
+ * of the gap
+ */
+static void
+fixup_ellipsis_run (EllipsizeState *state,
+ int extra_width)
+{
+ Pango2GlyphString *glyphs = state->ellipsis_run->glyphs;
+ Pango2Item *item = state->ellipsis_run->item;
+ int level;
+ int i;
+
+ /* Make the entire glyphstring into a single logical cluster */
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ glyphs->log_clusters[i] = 0;
+ glyphs->glyphs[i].attr.is_cluster_start = FALSE;
+ }
+
+ glyphs->glyphs[0].attr.is_cluster_start = TRUE;
+
+ glyphs->glyphs[glyphs->num_glyphs - 1].geometry.width += extra_width;
+
+ /* Fix up the item to point to the entire elided text */
+ item->offset = state->gap_start_iter.run_iter.start_index;
+ item->length = state->gap_end_iter.run_iter.end_index - item->offset;
+ item->num_chars = pango2_utf8_strlen (state->text + item->offset, item->length);
+
+ /* The level for the item is the minimum level of the elided text */
+ level = G_MAXINT;
+ for (i = state->gap_start_iter.run_index; i <= state->gap_end_iter.run_index; i++)
+ level = MIN (level, state->run_info[i].run->item->analysis.level);
+
+ item->analysis.level = level;
+
+ item->analysis.flags |= PANGO2_ANALYSIS_FLAG_IS_ELLIPSIS;
+}
+
+/* Computes the new list of runs for the line
+ */
+static GSList *
+get_run_list (EllipsizeState *state)
+{
+ Pango2GlyphItem *partial_start_run = NULL;
+ Pango2GlyphItem *partial_end_run = NULL;
+ GSList *result = NULL;
+ RunInfo *run_info;
+ Pango2GlyphItemIter *run_iter;
+ int i;
+
+ /* We first cut out the pieces of the starting and ending runs we want to
+ * preserve; we do the end first in case the end and the start are
+ * the same. Doing the start first would disturb the indices for the end.
+ */
+ run_info = &state->run_info[state->gap_end_iter.run_index];
+ run_iter = &state->gap_end_iter.run_iter;
+ if (run_iter->end_char != run_info->run->item->num_chars)
+ {
+ partial_end_run = run_info->run;
+ run_info->run = pango2_glyph_item_split (run_info->run, state->text,
+ run_iter->end_index - run_info->run->item->offset);
+ }
+
+ run_info = &state->run_info[state->gap_start_iter.run_index];
+ run_iter = &state->gap_start_iter.run_iter;
+ if (run_iter->start_char != 0)
+ {
+ partial_start_run = pango2_glyph_item_split (run_info->run, state->text,
+ run_iter->start_index - run_info->run->item->offset);
+ }
+
+ /* Now assemble the new list of runs
+ */
+ for (i = 0; i < state->gap_start_iter.run_index; i++)
+ result = g_slist_prepend (result, state->run_info[i].run);
+
+ if (partial_start_run)
+ result = g_slist_prepend (result, partial_start_run);
+
+ result = g_slist_prepend (result, state->ellipsis_run);
+
+ if (partial_end_run)
+ result = g_slist_prepend (result, partial_end_run);
+
+ for (i = state->gap_end_iter.run_index + 1; i < state->n_runs; i++)
+ result = g_slist_prepend (result, state->run_info[i].run);
+
+ /* And free the ones we didn't use
+ */
+ for (i = state->gap_start_iter.run_index; i <= state->gap_end_iter.run_index; i++)
+ pango2_glyph_item_free (state->run_info[i].run);
+
+ return g_slist_reverse (result);
+}
+
+/* Computes the width of the line as currently ellipsized
+ */
+static int
+current_width (EllipsizeState *state)
+{
+ return state->total_width - (state->gap_end_x - state->gap_start_x) + state->ellipsis_width;
+}
+
+void
+pango2_line_ellipsize (Pango2Line *line,
+ Pango2Context *context,
+ Pango2EllipsizeMode ellipsize,
+ int goal_width)
+{
+ EllipsizeState state;
+ const char *text = line->data->text;
+ Pango2AttrList *attrs = line->data->attrs;
+ Pango2LogAttr *log_attrs = line->data->log_attrs;
+ Pango2ShapeFlags shape_flags;
+
+ g_return_if_fail (ellipsize != PANGO2_ELLIPSIZE_NONE && goal_width >= 0);
+
+ if (pango2_context_get_round_glyph_positions (context))
+ shape_flags = PANGO2_SHAPE_ROUND_POSITIONS;
+ else
+ shape_flags = PANGO2_SHAPE_NONE;
+
+ init_state (&state, context, text, line->start_index, log_attrs, ellipsize, line->runs, attrs, shape_flags);
+
+ if (state.total_width <= goal_width)
+ goto out;
+
+ find_initial_span (&state);
+
+ while (current_width (&state) > goal_width)
+ {
+ if (!remove_one_span (&state))
+ break;
+ }
+
+ fixup_ellipsis_run (&state, MAX (goal_width - current_width (&state), 0));
+
+ g_slist_free (line->runs);
+ line->runs = get_run_list (&state);
+ line->ellipsized = TRUE;
+
+ out:
+ free_state (&state);
+}
diff --git a/pango2/emoji_presentation_scanner.c b/pango2/emoji_presentation_scanner.c
new file mode 100644
index 00000000..e40b9d45
--- /dev/null
+++ b/pango2/emoji_presentation_scanner.c
@@ -0,0 +1,280 @@
+
+#line 1 "emoji_presentation_scanner.rl"
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+#line 9 "emoji_presentation_scanner.c"
+static const char _emoji_presentation_actions[] = {
+ 0, 1, 0, 1, 1, 1, 5, 1,
+ 6, 1, 7, 1, 8, 1, 9, 1,
+ 10, 1, 11, 2, 2, 3, 2, 2,
+ 4
+};
+
+static const char _emoji_presentation_key_offsets[] = {
+ 0, 5, 7, 14, 18, 20, 21, 24,
+ 29, 30, 34, 36
+};
+
+static const unsigned char _emoji_presentation_trans_keys[] = {
+ 3u, 7u, 13u, 0u, 2u, 14u, 15u, 2u,
+ 3u, 6u, 7u, 13u, 0u, 1u, 9u, 10u,
+ 11u, 12u, 10u, 12u, 10u, 4u, 10u, 12u,
+ 4u, 9u, 10u, 11u, 12u, 6u, 9u, 10u,
+ 11u, 12u, 8u, 10u, 9u, 10u, 11u, 12u,
+ 14u, 0
+};
+
+static const char _emoji_presentation_single_lengths[] = {
+ 3, 2, 5, 4, 2, 1, 3, 5,
+ 1, 4, 2, 5
+};
+
+static const char _emoji_presentation_range_lengths[] = {
+ 1, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0
+};
+
+static const char _emoji_presentation_index_offsets[] = {
+ 0, 5, 8, 15, 20, 23, 25, 29,
+ 35, 37, 42, 45
+};
+
+static const char _emoji_presentation_indicies[] = {
+ 2, 1, 1, 1, 0, 4, 5, 3,
+ 7, 8, 10, 11, 12, 6, 9, 5,
+ 13, 14, 15, 0, 13, 15, 16, 13,
+ 16, 15, 13, 15, 16, 15, 5, 13,
+ 14, 15, 16, 5, 17, 5, 13, 14,
+ 18, 17, 5, 13, 16, 5, 13, 14,
+ 15, 4, 16, 0
+};
+
+static const char _emoji_presentation_trans_targs[] = {
+ 2, 4, 6, 2, 1, 2, 3, 3,
+ 7, 2, 8, 9, 11, 0, 2, 5,
+ 2, 2, 10
+};
+
+static const char _emoji_presentation_trans_actions[] = {
+ 17, 19, 19, 15, 0, 7, 22, 19,
+ 19, 9, 0, 22, 19, 0, 5, 19,
+ 11, 13, 19
+};
+
+static const char _emoji_presentation_to_state_actions[] = {
+ 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0
+};
+
+static const char _emoji_presentation_from_state_actions[] = {
+ 0, 0, 3, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0
+};
+
+static const char _emoji_presentation_eof_trans[] = {
+ 1, 4, 0, 1, 17, 17, 17, 17,
+ 18, 18, 17, 17
+};
+
+static const int emoji_presentation_start = 2;
+
+static const int emoji_presentation_en_text_and_emoji_run = 2;
+
+
+#line 9 "emoji_presentation_scanner.rl"
+
+
+
+#line 78 "emoji_presentation_scanner.rl"
+
+
+static emoji_text_iter_t
+scan_emoji_presentation (emoji_text_iter_t p,
+ const emoji_text_iter_t pe,
+ bool* is_emoji)
+{
+ emoji_text_iter_t te;
+ const emoji_text_iter_t eof = pe;
+
+ unsigned act;
+ int cs;
+
+
+#line 107 "emoji_presentation_scanner.c"
+ {
+ cs = emoji_presentation_start;
+ te = 0;
+ act = 0;
+ }
+
+#line 115 "emoji_presentation_scanner.c"
+ {
+ int _klen;
+ unsigned int _trans;
+ const char *_acts;
+ unsigned int _nacts;
+ const unsigned char *_keys;
+
+ if ( p == pe )
+ goto _test_eof;
+_resume:
+ _acts = _emoji_presentation_actions + _emoji_presentation_from_state_actions[cs];
+ _nacts = (unsigned int) *_acts++;
+ while ( _nacts-- > 0 ) {
+ switch ( *_acts++ ) {
+ case 1:
+#line 1 "NONE"
+ break;
+#line 134 "emoji_presentation_scanner.c"
+ }
+ }
+
+ _keys = _emoji_presentation_trans_keys + _emoji_presentation_key_offsets[cs];
+ _trans = _emoji_presentation_index_offsets[cs];
+
+ _klen = _emoji_presentation_single_lengths[cs];
+ if ( _klen > 0 ) {
+ const unsigned char *_lower = _keys;
+ const unsigned char *_mid;
+ const unsigned char *_upper = _keys + _klen - 1;
+ while (1) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( (*p) < *_mid )
+ _upper = _mid - 1;
+ else if ( (*p) > *_mid )
+ _lower = _mid + 1;
+ else {
+ _trans += (unsigned int)(_mid - _keys);
+ goto _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _emoji_presentation_range_lengths[cs];
+ if ( _klen > 0 ) {
+ const unsigned char *_lower = _keys;
+ const unsigned char *_mid;
+ const unsigned char *_upper = _keys + (_klen<<1) - 2;
+ while (1) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( (*p) < _mid[0] )
+ _upper = _mid - 2;
+ else if ( (*p) > _mid[1] )
+ _lower = _mid + 2;
+ else {
+ _trans += (unsigned int)((_mid - _keys)>>1);
+ goto _match;
+ }
+ }
+ _trans += _klen;
+ }
+
+_match:
+ _trans = _emoji_presentation_indicies[_trans];
+_eof_trans:
+ cs = _emoji_presentation_trans_targs[_trans];
+
+ if ( _emoji_presentation_trans_actions[_trans] == 0 )
+ goto _again;
+
+ _acts = _emoji_presentation_actions + _emoji_presentation_trans_actions[_trans];
+ _nacts = (unsigned int) *_acts++;
+ while ( _nacts-- > 0 )
+ {
+ switch ( *_acts++ )
+ {
+ case 2:
+#line 1 "NONE"
+ {te = p+1;}
+ break;
+ case 3:
+#line 74 "emoji_presentation_scanner.rl"
+ {act = 2;}
+ break;
+ case 4:
+#line 75 "emoji_presentation_scanner.rl"
+ {act = 3;}
+ break;
+ case 5:
+#line 73 "emoji_presentation_scanner.rl"
+ {te = p+1;{ *is_emoji = false; return te; }}
+ break;
+ case 6:
+#line 74 "emoji_presentation_scanner.rl"
+ {te = p+1;{ *is_emoji = true; return te; }}
+ break;
+ case 7:
+#line 75 "emoji_presentation_scanner.rl"
+ {te = p+1;{ *is_emoji = false; return te; }}
+ break;
+ case 8:
+#line 74 "emoji_presentation_scanner.rl"
+ {te = p;p--;{ *is_emoji = true; return te; }}
+ break;
+ case 9:
+#line 75 "emoji_presentation_scanner.rl"
+ {te = p;p--;{ *is_emoji = false; return te; }}
+ break;
+ case 10:
+#line 74 "emoji_presentation_scanner.rl"
+ {{p = ((te))-1;}{ *is_emoji = true; return te; }}
+ break;
+ case 11:
+#line 1 "NONE"
+ { switch( act ) {
+ case 2:
+ {{p = ((te))-1;} *is_emoji = true; return te; }
+ break;
+ case 3:
+ {{p = ((te))-1;} *is_emoji = false; return te; }
+ break;
+ }
+ }
+ break;
+#line 248 "emoji_presentation_scanner.c"
+ }
+ }
+
+_again:
+ _acts = _emoji_presentation_actions + _emoji_presentation_to_state_actions[cs];
+ _nacts = (unsigned int) *_acts++;
+ while ( _nacts-- > 0 ) {
+ switch ( *_acts++ ) {
+ case 0:
+#line 1 "NONE"
+ break;
+#line 261 "emoji_presentation_scanner.c"
+ }
+ }
+
+ if ( ++p != pe )
+ goto _resume;
+ _test_eof: {}
+ if ( p == eof )
+ {
+ if ( _emoji_presentation_eof_trans[cs] > 0 ) {
+ _trans = _emoji_presentation_eof_trans[cs] - 1;
+ goto _eof_trans;
+ }
+ }
+
+ }
+
+#line 94 "emoji_presentation_scanner.rl"
+
+
+ /* Should not be reached. */
+ *is_emoji = false;
+ return pe;
+}
diff --git a/pango2/emoji_presentation_scanner.rl b/pango2/emoji_presentation_scanner.rl
new file mode 100644
index 00000000..d9c26919
--- /dev/null
+++ b/pango2/emoji_presentation_scanner.rl
@@ -0,0 +1,99 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+%%{
+ machine emoji_presentation;
+ alphtype unsigned char;
+ write data noerror nofinal noentry;
+}%%
+
+%%{
+
+EMOJI = 0;
+EMOJI_TEXT_PRESENTATION = 1;
+EMOJI_EMOJI_PRESENTATION = 2;
+EMOJI_MODIFIER_BASE = 3;
+EMOJI_MODIFIER = 4;
+EMOJI_VS_BASE = 5;
+REGIONAL_INDICATOR = 6;
+KEYCAP_BASE = 7;
+COMBINING_ENCLOSING_KEYCAP = 8;
+COMBINING_ENCLOSING_CIRCLE_BACKSLASH = 9;
+ZWJ = 10;
+VS15 = 11;
+VS16 = 12;
+TAG_BASE = 13;
+TAG_SEQUENCE = 14;
+TAG_TERM = 15;
+
+any_emoji = EMOJI_TEXT_PRESENTATION | EMOJI_EMOJI_PRESENTATION | KEYCAP_BASE |
+ EMOJI_MODIFIER_BASE | TAG_BASE | EMOJI;
+
+emoji_combining_enclosing_circle_backslash_sequence = any_emoji
+ COMBINING_ENCLOSING_CIRCLE_BACKSLASH;
+
+# This could be sharper than any_emoji by restricting this only to valid
+# variation sequences:
+# https://www.unicode.org/Public/emoji/11.0/emoji-variation-sequences.txt
+# However, implementing
+# https://www.unicode.org/reports/tr51/#def_emoji_presentation_sequence is
+# sufficient for our purposes here.
+emoji_presentation_sequence = any_emoji VS16;
+
+emoji_modifier_sequence = EMOJI_MODIFIER_BASE EMOJI_MODIFIER;
+
+emoji_flag_sequence = REGIONAL_INDICATOR REGIONAL_INDICATOR;
+
+# Here we only allow the valid tag sequences
+# https://www.unicode.org/reports/tr51/#valid-emoji-tag-sequences, instead of
+# all well-formed ones defined in
+# https://www.unicode.org/reports/tr51/#def_emoji_tag_sequence
+emoji_tag_sequence = TAG_BASE TAG_SEQUENCE+ TAG_TERM;
+
+emoji_keycap_sequence = KEYCAP_BASE VS16 COMBINING_ENCLOSING_KEYCAP;
+
+emoji_zwj_element = emoji_presentation_sequence | emoji_modifier_sequence | any_emoji;
+
+emoji_zwj_sequence = emoji_zwj_element ( ZWJ emoji_zwj_element )+;
+
+emoji_presentation = EMOJI_EMOJI_PRESENTATION | TAG_BASE | EMOJI_MODIFIER_BASE |
+ emoji_presentation_sequence | emoji_modifier_sequence | emoji_flag_sequence |
+ emoji_tag_sequence | emoji_keycap_sequence | emoji_zwj_sequence |
+ emoji_combining_enclosing_circle_backslash_sequence;
+
+emoji_run = emoji_presentation;
+
+text_presentation_emoji = any_emoji VS15;
+text_run = any;
+
+text_and_emoji_run := |*
+# In order to give the the VS15 sequences higher priority than detecting
+# emoji sequences they are listed first as scanner token here.
+text_presentation_emoji => { *is_emoji = false; return te; };
+emoji_run => { *is_emoji = true; return te; };
+text_run => { *is_emoji = false; return te; };
+*|;
+
+}%%
+
+static emoji_text_iter_t
+scan_emoji_presentation (emoji_text_iter_t p,
+ const emoji_text_iter_t pe,
+ bool* is_emoji)
+{
+ emoji_text_iter_t ts, te;
+ const emoji_text_iter_t eof = pe;
+
+ unsigned act;
+ int cs;
+
+ %%{
+ write init;
+ write exec;
+ }%%
+
+ /* Should not be reached. */
+ *is_emoji = false;
+ return pe;
+}
diff --git a/pango2/glyphstring.c b/pango2/glyphstring.c
new file mode 100644
index 00000000..9ae86b93
--- /dev/null
+++ b/pango2/glyphstring.c
@@ -0,0 +1,793 @@
+/* Pango2
+ * glyphstring.c:
+ *
+ * Copyright (C) 1999 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.
+ */
+
+#include "config.h"
+#include <glib.h>
+#include "pango-glyph.h"
+#include "pango-font.h"
+#include "pango-item-private.h"
+#include "pango-glyph-item-private.h"
+#include "pango-impl-utils.h"
+
+#include <hb-ot.h>
+
+/**
+ * pango2_glyph_string_new:
+ *
+ * Create a new `Pango2GlyphString`.
+ *
+ * Return value: the newly allocated `Pango2GlyphString`, which
+ * should be freed with [method@Pango2.GlyphString.free].
+ */
+Pango2GlyphString *
+pango2_glyph_string_new (void)
+{
+ Pango2GlyphString *string = g_slice_new (Pango2GlyphString);
+
+ string->num_glyphs = 0;
+ string->space = 0;
+ string->glyphs = NULL;
+ string->log_clusters = NULL;
+
+ return string;
+}
+
+/**
+ * pango2_glyph_string_set_size:
+ * @string: a `Pango2GlyphString`.
+ * @new_len: the new length of the string
+ *
+ * Resize a glyph string to the given length.
+ */
+void
+pango2_glyph_string_set_size (Pango2GlyphString *string,
+ int new_len)
+{
+ g_return_if_fail (new_len >= 0);
+
+ while (new_len > string->space)
+ {
+ if (string->space == 0)
+ {
+ string->space = 4;
+ }
+ else
+ {
+ const guint max_space =
+ MIN (G_MAXINT, G_MAXSIZE / MAX (sizeof(Pango2GlyphInfo), sizeof(int)));
+
+ guint more_space = (guint)string->space * 2;
+
+ if (more_space > max_space)
+ {
+ more_space = max_space;
+
+ if ((guint)new_len > max_space)
+ {
+ g_error ("%s: failed to allocate glyph string of length %i\n",
+ G_STRLOC, new_len);
+ }
+ }
+
+ string->space = more_space;
+ }
+ }
+
+ string->glyphs = g_realloc (string->glyphs, string->space * sizeof (Pango2GlyphInfo));
+ string->log_clusters = g_realloc (string->log_clusters, string->space * sizeof (int));
+ string->num_glyphs = new_len;
+}
+
+G_DEFINE_BOXED_TYPE (Pango2GlyphString, pango2_glyph_string,
+ pango2_glyph_string_copy,
+ pango2_glyph_string_free);
+
+/**
+ * pango2_glyph_string_copy:
+ * @string: (nullable): a `Pango2GlyphString`
+ *
+ * Copy a glyph string and associated storage.
+ *
+ * Return value: (nullable): the newly allocated `Pango2GlyphString`
+ */
+Pango2GlyphString *
+pango2_glyph_string_copy (Pango2GlyphString *string)
+{
+ Pango2GlyphString *new_string;
+
+ if (string == NULL)
+ return NULL;
+
+ new_string = g_slice_new (Pango2GlyphString);
+
+ *new_string = *string;
+
+ new_string->glyphs = g_memdup2 (string->glyphs,
+ string->space * sizeof (Pango2GlyphInfo));
+ new_string->log_clusters = g_memdup2 (string->log_clusters,
+ string->space * sizeof (int));
+
+ return new_string;
+}
+
+/**
+ * pango2_glyph_string_free:
+ * @string: (nullable): a `Pango2GlyphString`, may be %NULL
+ *
+ * Free a glyph string and associated storage.
+ */
+void
+pango2_glyph_string_free (Pango2GlyphString *string)
+{
+ if (string == NULL)
+ return;
+
+ g_free (string->glyphs);
+ g_free (string->log_clusters);
+ g_slice_free (Pango2GlyphString, string);
+}
+
+/**
+ * pango2_glyph_string_extents_range:
+ * @glyphs: a `Pango2GlyphString`
+ * @start: start index
+ * @end: end index (the range is the set of bytes with
+ * indices such that start <= index < end)
+ * @font: a `Pango2Font`
+ * @ink_rect: (out caller-allocates) (optional): rectangle used to
+ * store the extents of the glyph string range as drawn
+ * @logical_rect: (out caller-allocates) (optional): rectangle used to
+ * store the logical extents of the glyph string range
+ *
+ * Computes the extents of a sub-portion of a glyph string.
+ *
+ * The extents are relative to the start of the glyph string range
+ * (the origin of their coordinate system is at the start of the range,
+ * not at the start of the entire glyph string).
+ */
+void
+pango2_glyph_string_extents_range (Pango2GlyphString *glyphs,
+ int start,
+ int end,
+ Pango2Font *font,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ int x_pos = 0;
+ int i;
+
+ /* Note that the handling of empty rectangles for ink
+ * and logical rectangles is different. A zero-height ink
+ * rectangle makes no contribution to the overall ink rect,
+ * while a zero-height logical rect still reserves horizontal
+ * width. Also, we may return zero-width, positive height
+ * logical rectangles, while we'll never do that for the
+ * ink rect.
+ */
+ g_return_if_fail (start <= end);
+
+ if (G_UNLIKELY (!ink_rect && !logical_rect))
+ return;
+
+ 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;
+ }
+
+ for (i = start; i < end; i++)
+ {
+ Pango2Rectangle glyph_ink;
+ Pango2Rectangle glyph_logical;
+
+ Pango2GlyphGeometry *geometry = &glyphs->glyphs[i].geometry;
+
+ pango2_font_get_glyph_extents (font, glyphs->glyphs[i].glyph,
+ ink_rect ? &glyph_ink : NULL,
+ logical_rect ? &glyph_logical : NULL);
+
+ if (ink_rect && glyph_ink.width != 0 && glyph_ink.height != 0)
+ {
+ if (ink_rect->width == 0 || ink_rect->height == 0)
+ {
+ ink_rect->x = x_pos + glyph_ink.x + geometry->x_offset;
+ ink_rect->width = glyph_ink.width;
+ ink_rect->y = glyph_ink.y + geometry->y_offset;
+ ink_rect->height = glyph_ink.height;
+ }
+ else
+ {
+ int new_x, new_y;
+
+ new_x = MIN (ink_rect->x, x_pos + glyph_ink.x + geometry->x_offset);
+ ink_rect->width = MAX (ink_rect->x + ink_rect->width,
+ x_pos + glyph_ink.x + glyph_ink.width + geometry->x_offset) - new_x;
+ ink_rect->x = new_x;
+
+ new_y = MIN (ink_rect->y, glyph_ink.y + geometry->y_offset);
+ ink_rect->height = MAX (ink_rect->y + ink_rect->height,
+ glyph_ink.y + glyph_ink.height + geometry->y_offset) - new_y;
+ ink_rect->y = new_y;
+ }
+ }
+
+ if (logical_rect)
+ {
+ logical_rect->width += geometry->width;
+
+ if (i == start)
+ {
+ logical_rect->y = glyph_logical.y;
+ logical_rect->height = glyph_logical.height;
+ }
+ else
+ {
+ int new_y = MIN (logical_rect->y, glyph_logical.y);
+ logical_rect->height = MAX (logical_rect->y + logical_rect->height,
+ glyph_logical.y + glyph_logical.height) - new_y;
+ logical_rect->y = new_y;
+ }
+ }
+
+ x_pos += geometry->width;
+ }
+}
+
+/**
+ * pango2_glyph_string_extents:
+ * @glyphs: a `Pango2GlyphString`
+ * @font: a `Pango2Font`
+ * @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
+ *
+ * Compute the logical and ink extents of a glyph string.
+ *
+ * See the documentation for [method@Pango2.Font.get_glyph_extents] for details
+ * about the interpretation of the rectangles.
+ *
+ * Examples of logical (red) and ink (green) rects:
+ *
+ * ![](rects1.png) ![](rects2.png)
+ */
+void
+pango2_glyph_string_extents (Pango2GlyphString *glyphs,
+ Pango2Font *font,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ pango2_glyph_string_extents_range (glyphs, 0, glyphs->num_glyphs,
+ font, ink_rect, logical_rect);
+}
+
+/**
+ * pango2_glyph_string_get_width:
+ * @glyphs: a `Pango2GlyphString`
+ *
+ * Computes the logical width of the glyph string.
+ *
+ * This can also be computed using [method@Pango2.GlyphString.extents].
+ * However, since this only computes the width, it's much faster. This
+ * is in fact only a convenience function that computes the sum of
+ * @geometry.width for each glyph in the @glyphs.
+ *
+ * Return value: the logical width of the glyph string.
+ */
+int
+pango2_glyph_string_get_width (Pango2GlyphString *glyphs)
+{
+ int i;
+ int width = 0;
+
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ width += glyphs->glyphs[i].geometry.width;
+
+ return width;
+}
+
+/**
+ * pango2_glyph_string_get_logical_widths:
+ * @glyphs: a `Pango2GlyphString`
+ * @text: the text corresponding to the glyphs
+ * @length: the length of @text, in bytes
+ * @embedding_level: the embedding level of the string
+ * @logical_widths: (array): an array whose length is the number of
+ * characters in text (equal to `g_utf8_strlen (text, length)` unless
+ * text has `NUL` bytes) to be filled in with the resulting character widths.
+ *
+ * Given a `Pango2GlyphString` and corresponding text, determine the width
+ * corresponding to each character.
+ *
+ * When multiple characters compose a single cluster, the width of the
+ * entire cluster is divided equally among the characters.
+ */
+void
+pango2_glyph_string_get_logical_widths (Pango2GlyphString *glyphs,
+ const char *text,
+ int length,
+ int embedding_level,
+ int *logical_widths)
+{
+ /* Build a Pango2GlyphItem and call the other API */
+ Pango2Item item = {0, length, pango2_utf8_strlen (text, length), 0,
+ {NULL, NULL,
+ embedding_level, PANGO2_GRAVITY_AUTO, 0,
+ G_UNICODE_SCRIPT_UNKNOWN, NULL,
+ NULL}};
+ Pango2GlyphItem glyph_item = {&item, glyphs};
+
+ pango2_glyph_item_get_logical_widths (&glyph_item, text, logical_widths);
+}
+
+/* The initial implementation here is script independent,
+ * but it might actually need to be virtualized into the
+ * rendering modules. Otherwise, we probably will end up
+ * enforcing unnatural cursor behavior for some languages.
+ *
+ * The only distinction that Uniscript makes is whether
+ * cursor positioning is allowed within clusters or not.
+ */
+
+/**
+ * pango2_glyph_string_index_to_x:
+ * @glyphs: the glyphs return from [func@shape]
+ * @text: the text for the run
+ * @length: the number of bytes (not characters) in @text.
+ * @analysis: the analysis information return from [func@itemize]
+ * @index_: the byte index within @text
+ * @trailing: whether we should compute the result for the beginning (%FALSE)
+ * or end (%TRUE) of the character.
+ * @x_pos: (out): location to store result
+ *
+ * Converts from character position to x position.
+ *
+ * The X position is measured from the left edge of the run.
+ * Character positions are obtained using font metrics for ligatures
+ * where available, and computed by dividing up each cluster
+ * into equal portions, otherwise.
+ *
+ * <picture>
+ * <source srcset="glyphstring-positions-dark.png" media="(prefers-color-scheme: dark)">
+ * <img alt="Glyph positions" src="glyphstring-positions-light.png">
+ * </picture>
+ */
+void
+pango2_glyph_string_index_to_x (Pango2GlyphString *glyphs,
+ const char *text,
+ int length,
+ const Pango2Analysis *analysis,
+ int index,
+ gboolean trailing,
+ int *x_pos)
+{
+ pango2_glyph_string_index_to_x_full (glyphs,
+ text, length,
+ analysis,
+ NULL,
+ index, trailing,
+ x_pos);
+}
+
+/**
+ * pango2_glyph_string_index_to_x_full:
+ * @glyphs: the glyphs return from [func@shape]
+ * @text: the text for the run
+ * @length: the number of bytes (not characters) in @text.
+ * @analysis: the analysis information return from [func@itemize]
+ * @attrs: (nullable): `Pango2LogAttr` array for @text
+ * @index_: the byte index within @text
+ * @trailing: whether we should compute the result for the beginning (%FALSE)
+ * or end (%TRUE) of the character.
+ * @x_pos: (out): location to store result
+ *
+ * Converts from character position to x position.
+ *
+ * This variant of [method@Pango2.GlyphString.index_to_x] additionally
+ * accepts a `Pango2LogAttr` array. The grapheme boundary information
+ * in it can be used to disambiguate positioning inside some complex
+ * clusters.
+ */
+void
+pango2_glyph_string_index_to_x_full (Pango2GlyphString *glyphs,
+ const char *text,
+ int length,
+ const Pango2Analysis *analysis,
+ Pango2LogAttr *attrs,
+ int index,
+ gboolean trailing,
+ int *x_pos)
+{
+ int i;
+ int start_xpos = 0;
+ int end_xpos = 0;
+ int width = 0;
+
+ int start_index = -1;
+ int end_index = -1;
+
+ int cluster_chars = 0;
+ int cluster_offset = 0;
+ int start_glyph_pos = -1;
+ int end_glyph_pos = -1;
+
+ const char *p;
+
+ g_return_if_fail (glyphs != NULL);
+ g_return_if_fail (length >= 0);
+ g_return_if_fail (length == 0 || text != NULL);
+
+ if (!x_pos) /* Allow the user to do the useless */
+ return;
+
+ if (glyphs->num_glyphs == 0)
+ {
+ *x_pos = 0;
+ return;
+ }
+
+ start_glyph_pos = -1;
+ end_glyph_pos = -1;
+
+ /* Calculate the starting and ending character positions
+ * and x positions for the cluster
+ */
+ if (analysis->level % 2) /* Right to left */
+ {
+ for (i = glyphs->num_glyphs - 1; i >= 0; i--)
+ width += glyphs->glyphs[i].geometry.width;
+
+ for (i = glyphs->num_glyphs - 1; i >= 0; i--)
+ {
+ if (glyphs->log_clusters[i] > index)
+ {
+ end_index = glyphs->log_clusters[i];
+ end_xpos = width;
+ break;
+ }
+
+ if (glyphs->log_clusters[i] != start_index)
+ {
+ start_index = glyphs->log_clusters[i];
+ start_xpos = width;
+ }
+
+ width -= glyphs->glyphs[i].geometry.width;
+ }
+
+ for (i = glyphs->num_glyphs - 1; i >= 0; i--)
+ {
+ if (glyphs->log_clusters[i] == start_index)
+ {
+ if (end_glyph_pos < 0)
+ end_glyph_pos = i;
+ start_glyph_pos = i;
+ }
+ }
+ }
+ else /* Left to right */
+ {
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ if (glyphs->log_clusters[i] > index)
+ {
+ end_index = glyphs->log_clusters[i];
+ end_xpos = width;
+ break;
+ }
+
+ if (glyphs->log_clusters[i] != start_index)
+ {
+ start_index = glyphs->log_clusters[i];
+ start_xpos = width;
+ }
+
+ width += glyphs->glyphs[i].geometry.width;
+ }
+
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ if (glyphs->log_clusters[i] == start_index)
+ {
+ if (start_glyph_pos < 0)
+ start_glyph_pos = i;
+ end_glyph_pos = i;
+ }
+ }
+ }
+
+ if (end_index == -1)
+ {
+ end_index = length;
+ end_xpos = (analysis->level % 2) ? 0 : width;
+ }
+
+ /* Calculate offset of character within cluster.
+ * To come up with accurate answers here, we need to know grapheme
+ * boundaries.
+ */
+ for (p = text + start_index, i = attrs ? g_utf8_pointer_to_offset (text, text + start_index) : 0;
+ p < text + end_index;
+ p = g_utf8_next_char (p), i++)
+ {
+ if (attrs && !attrs[i].is_cursor_position)
+ continue;
+
+ if (p < text + index)
+ cluster_offset++;
+ cluster_chars++;
+ }
+
+ if (trailing)
+ cluster_offset += 1;
+
+ if (G_UNLIKELY (!cluster_chars)) /* pedantic */
+ {
+ *x_pos = start_xpos;
+ return;
+ }
+
+ /* Try to get a ligature caret position for the glyph from the font.
+ * This only makes sense if the cluster contains a single spacing
+ * glyph, so we need to check that all but one of them are marks.
+ */
+ if (cluster_offset > 0 && cluster_offset < cluster_chars)
+ {
+ hb_font_t *hb_font;
+ hb_position_t caret;
+ unsigned int caret_count = 1;
+ int glyph_pos;
+ int num_carets;
+
+ hb_font = pango2_font_get_hb_font (analysis->font);
+
+ if (start_glyph_pos == end_glyph_pos)
+ glyph_pos = start_glyph_pos;
+ else
+ {
+ hb_face_t *hb_face;
+
+ hb_face = hb_font_get_face (hb_font);
+
+ glyph_pos = -1;
+ for (i = start_glyph_pos; i <= end_glyph_pos; i++)
+ {
+ if (hb_ot_layout_get_glyph_class (hb_face, glyphs->glyphs[i].glyph) != HB_OT_LAYOUT_GLYPH_CLASS_MARK)
+ {
+ if (glyph_pos != -1)
+ {
+ /* multiple non-mark glyphs in cluster, giving up */
+ goto fallback;
+ }
+ glyph_pos = i;
+ }
+ }
+ if (glyph_pos == -1)
+ {
+ /* no non-mark glyph in a multi-glyph cluster, giving up */
+ goto fallback;
+ }
+ }
+
+ num_carets = hb_ot_layout_get_ligature_carets (hb_font,
+ (analysis->level % 2) ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
+ glyphs->glyphs[glyph_pos].glyph,
+ cluster_offset - 1, &caret_count, &caret);
+ if (caret_count == 0 || num_carets == 0)
+ {
+ /* no ligature caret information found for this glyph */
+ goto fallback;
+ }
+
+ if (analysis->level % 2) /* Right to left */
+ *x_pos = end_xpos + caret;
+ else
+ *x_pos = start_xpos + caret;
+ *x_pos += glyphs->glyphs[glyph_pos].geometry.x_offset;
+ return;
+ }
+
+fallback:
+
+ *x_pos = ((cluster_chars - cluster_offset) * start_xpos +
+ cluster_offset * end_xpos) / cluster_chars;
+}
+
+/**
+ * pango2_glyph_string_x_to_index:
+ * @glyphs: the glyphs returned from [func@shape]
+ * @text: the text for the run
+ * @length: the number of bytes (not characters) in text.
+ * @analysis: the analysis information return from [func@itemize]
+ * @x_pos: the x offset (in Pango2 units)
+ * @index_: (out): location to store calculated byte index within @text
+ * @trailing: (out): location to store a boolean indicating whether the
+ * user clicked on the leading or trailing edge of the character
+ *
+ * Convert from x offset to character position.
+ *
+ * Character positions are computed by dividing up each cluster into
+ * equal portions. In scripts where positioning within a cluster is
+ * not allowed (such as Thai), the returned value may not be a valid
+ * cursor position; the caller must combine the result with the logical
+ * attributes for the text to compute the valid cursor position.
+ */
+void
+pango2_glyph_string_x_to_index (Pango2GlyphString *glyphs,
+ const char *text,
+ int length,
+ const Pango2Analysis *analysis,
+ int x_pos,
+ int *index,
+ gboolean *trailing)
+{
+ int i;
+ int start_xpos = 0;
+ int end_xpos = 0;
+ int width = 0;
+
+ int start_index = -1;
+ int end_index = -1;
+
+ int cluster_chars = 0;
+ const char *p;
+
+ gboolean found = FALSE;
+
+ /* Find the cluster containing the position */
+
+ width = 0;
+
+ if (analysis->level % 2) /* Right to left */
+ {
+ for (i = glyphs->num_glyphs - 1; i >= 0; i--)
+ width += glyphs->glyphs[i].geometry.width;
+
+ for (i = glyphs->num_glyphs - 1; i >= 0; i--)
+ {
+ if (glyphs->log_clusters[i] != start_index)
+ {
+ if (found)
+ {
+ end_index = glyphs->log_clusters[i];
+ end_xpos = width;
+ break;
+ }
+ else
+ {
+ start_index = glyphs->log_clusters[i];
+ start_xpos = width;
+ }
+ }
+
+ width -= glyphs->glyphs[i].geometry.width;
+
+ if (width <= x_pos && x_pos < width + glyphs->glyphs[i].geometry.width)
+ found = TRUE;
+ }
+ }
+ else /* Left to right */
+ {
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ if (glyphs->log_clusters[i] != start_index)
+ {
+ if (found)
+ {
+ end_index = glyphs->log_clusters[i];
+ end_xpos = width;
+ break;
+ }
+ else
+ {
+ start_index = glyphs->log_clusters[i];
+ start_xpos = width;
+ }
+ }
+
+ if (width <= x_pos && x_pos < width + glyphs->glyphs[i].geometry.width)
+ found = TRUE;
+
+ width += glyphs->glyphs[i].geometry.width;
+ }
+ }
+
+ if (end_index == -1)
+ {
+ end_index = length;
+ end_xpos = (analysis->level % 2) ? 0 : width;
+ }
+
+ /* Calculate number of chars within cluster */
+ p = text + start_index;
+ while (p < text + end_index)
+ {
+ p = g_utf8_next_char (p);
+ cluster_chars++;
+ }
+
+ if (start_xpos == end_xpos)
+ {
+ if (index)
+ *index = start_index;
+ if (trailing)
+ *trailing = FALSE;
+ }
+ else
+ {
+ double cp = ((double)(x_pos - start_xpos) * cluster_chars) / (end_xpos - start_xpos);
+
+ /* LTR and right-to-left have to be handled separately
+ * here because of the edge condition when we are exactly
+ * at a pixel boundary; end_xpos goes with the next
+ * character for LTR, with the previous character for RTL.
+ */
+ if (start_xpos < end_xpos) /* Left-to-right */
+ {
+ if (index)
+ {
+ const char *p = text + start_index;
+ int i = 0;
+
+ while (i + 1 <= cp)
+ {
+ p = g_utf8_next_char (p);
+ i++;
+ }
+
+ *index = (p - text);
+ }
+
+ if (trailing)
+ *trailing = (cp - (int)cp >= 0.5) ? TRUE : FALSE;
+ }
+ else /* Right-to-left */
+ {
+ if (index)
+ {
+ const char *p = text + start_index;
+ int i = 0;
+
+ while (i + 1 < cp)
+ {
+ p = g_utf8_next_char (p);
+ i++;
+ }
+
+ *index = (p - text);
+ }
+
+ if (trailing)
+ {
+ double cp_flip = cluster_chars - cp;
+ *trailing = (cp_flip - (int)cp_flip >= 0.5) ? FALSE : TRUE;
+ }
+ }
+ }
+}
diff --git a/pango2/itemize.c b/pango2/itemize.c
new file mode 100644
index 00000000..1cf4e6ea
--- /dev/null
+++ b/pango2/itemize.c
@@ -0,0 +1,1634 @@
+/* Pango2
+ * itemize.c: Turning text into items
+ *
+ * Copyright (C) 2000, 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.
+ */
+
+#include "config.h"
+#include <string.h>
+#include <stdlib.h>
+
+#include "pango-context-private.h"
+#include "pango-impl-utils.h"
+
+#include "pango-font-private.h"
+#include "pango-fontset.h"
+#include "pango-fontmap-private.h"
+#include "pango-script-private.h"
+#include "pango-emoji-private.h"
+#include "pango-attr-iterator-private.h"
+#include "pango-attr-private.h"
+#include "pango-item-private.h"
+#include "pango-bidi-private.h"
+
+#include <hb-ot.h>
+
+/* {{{ Font cache */
+
+/*
+ * We cache the results of character,fontset => font in a hash table
+ */
+
+typedef struct {
+ GHashTable *hash;
+} FontCache;
+
+typedef struct {
+ Pango2Font *font;
+ int position; /* position of the font in the fontset */
+} FontElement;
+
+static void
+font_cache_destroy (FontCache *cache)
+{
+ g_hash_table_destroy (cache->hash);
+ g_slice_free (FontCache, cache);
+}
+
+static void
+font_element_destroy (FontElement *element)
+{
+ if (element->font)
+ g_object_unref (element->font);
+ g_slice_free (FontElement, element);
+}
+
+static FontCache *
+get_font_cache (Pango2Fontset *fontset)
+{
+ FontCache *cache;
+
+ static GQuark cache_quark = 0; /* MT-safe */
+ if (G_UNLIKELY (!cache_quark))
+ cache_quark = g_quark_from_static_string ("pango-font-cache");
+
+retry:
+ cache = g_object_get_qdata (G_OBJECT (fontset), cache_quark);
+ if (G_UNLIKELY (!cache))
+ {
+ cache = g_slice_new (FontCache);
+ cache->hash = g_hash_table_new_full (g_direct_hash, NULL,
+ NULL, (GDestroyNotify)font_element_destroy);
+ if (!g_object_replace_qdata (G_OBJECT (fontset), cache_quark, NULL,
+ cache, (GDestroyNotify)font_cache_destroy,
+ NULL))
+ {
+ font_cache_destroy (cache);
+ goto retry;
+ }
+ }
+
+ return cache;
+}
+
+static gboolean
+font_cache_get (FontCache *cache,
+ gunichar wc,
+ Pango2Font **font,
+ int *position)
+{
+ FontElement *element;
+
+ element = g_hash_table_lookup (cache->hash, GUINT_TO_POINTER (wc));
+ if (element)
+ {
+ *font = element->font;
+ *position = element->position;
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static void
+font_cache_insert (FontCache *cache,
+ gunichar wc,
+ Pango2Font *font,
+ int position)
+{
+ FontElement *element = g_slice_new (FontElement);
+ element->font = font ? g_object_ref (font) : NULL;
+ element->position = position;
+
+ g_hash_table_insert (cache->hash, GUINT_TO_POINTER (wc), element);
+}
+
+/* }}} */
+/* {{{ Width Iter */
+
+typedef struct _Pango2WidthIter Pango2WidthIter;
+
+struct _Pango2WidthIter
+{
+ const char *text_start;
+ const char *text_end;
+ const char *start;
+ const char *end;
+ gboolean upright;
+};
+
+static gboolean
+width_iter_is_upright (gunichar ch)
+{
+ /* https://www.unicode.org/Public/11.0.0/ucd/VerticalOrientation.txt
+ * VO=U or Tu table generated by tools/gen-vertical-orientation-U-table.py.
+ *
+ * FIXME: In the future, If GLib supports VerticalOrientation, please use it.
+ */
+ static const gunichar upright[][2] = {
+ {0x00A7, 0x00A7}, {0x00A9, 0x00A9}, {0x00AE, 0x00AE}, {0x00B1, 0x00B1},
+ {0x00BC, 0x00BE}, {0x00D7, 0x00D7}, {0x00F7, 0x00F7}, {0x02EA, 0x02EB},
+ {0x1100, 0x11FF}, {0x1401, 0x167F}, {0x18B0, 0x18FF}, {0x2016, 0x2016},
+ {0x2020, 0x2021}, {0x2030, 0x2031}, {0x203B, 0x203C}, {0x2042, 0x2042},
+ {0x2047, 0x2049}, {0x2051, 0x2051}, {0x2065, 0x2065}, {0x20DD, 0x20E0},
+ {0x20E2, 0x20E4}, {0x2100, 0x2101}, {0x2103, 0x2109}, {0x210F, 0x210F},
+ {0x2113, 0x2114}, {0x2116, 0x2117}, {0x211E, 0x2123}, {0x2125, 0x2125},
+ {0x2127, 0x2127}, {0x2129, 0x2129}, {0x212E, 0x212E}, {0x2135, 0x213F},
+ {0x2145, 0x214A}, {0x214C, 0x214D}, {0x214F, 0x2189}, {0x218C, 0x218F},
+ {0x221E, 0x221E}, {0x2234, 0x2235}, {0x2300, 0x2307}, {0x230C, 0x231F},
+ {0x2324, 0x2328}, {0x232B, 0x232B}, {0x237D, 0x239A}, {0x23BE, 0x23CD},
+ {0x23CF, 0x23CF}, {0x23D1, 0x23DB}, {0x23E2, 0x2422}, {0x2424, 0x24FF},
+ {0x25A0, 0x2619}, {0x2620, 0x2767}, {0x2776, 0x2793}, {0x2B12, 0x2B2F},
+ {0x2B50, 0x2B59}, {0x2BB8, 0x2BD1}, {0x2BD3, 0x2BEB}, {0x2BF0, 0x2BFF},
+ {0x2E80, 0x3007}, {0x3012, 0x3013}, {0x3020, 0x302F}, {0x3031, 0x309F},
+ {0x30A1, 0x30FB}, {0x30FD, 0xA4CF}, {0xA960, 0xA97F}, {0xAC00, 0xD7FF},
+ {0xE000, 0xFAFF}, {0xFE10, 0xFE1F}, {0xFE30, 0xFE48}, {0xFE50, 0xFE57},
+ {0xFE5F, 0xFE62}, {0xFE67, 0xFE6F}, {0xFF01, 0xFF07}, {0xFF0A, 0xFF0C},
+ {0xFF0E, 0xFF19}, {0xFF1F, 0xFF3A}, {0xFF3C, 0xFF3C}, {0xFF3E, 0xFF3E},
+ {0xFF40, 0xFF5A}, {0xFFE0, 0xFFE2}, {0xFFE4, 0xFFE7}, {0xFFF0, 0xFFF8},
+ {0xFFFC, 0xFFFD}, {0x10980, 0x1099F}, {0x11580, 0x115FF}, {0x11A00, 0x11AAF},
+ {0x13000, 0x1342F}, {0x14400, 0x1467F}, {0x16FE0, 0x18AFF}, {0x1B000, 0x1B12F},
+ {0x1B170, 0x1B2FF}, {0x1D000, 0x1D1FF}, {0x1D2E0, 0x1D37F}, {0x1D800, 0x1DAAF},
+ {0x1F000, 0x1F7FF}, {0x1F900, 0x1FA6F}, {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
+ {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD}
+ };
+ static const int max = sizeof(upright) / sizeof(upright[0]);
+ int st = 0;
+ int ed = max;
+
+ if (ch < upright[0][0])
+ return FALSE;
+
+ while (st <= ed)
+ {
+ int mid = (st + ed) / 2;
+ if (upright[mid][0] <= ch && ch <= upright[mid][1])
+ return TRUE;
+ else
+ if (upright[mid][0] <= ch)
+ st = mid + 1;
+ else
+ ed = mid - 1;
+ }
+
+ return FALSE;
+}
+
+static void
+width_iter_next (Pango2WidthIter *iter)
+{
+ gboolean met_joiner = FALSE;
+ iter->start = iter->end;
+
+ if (iter->end < iter->text_end)
+ {
+ gunichar ch = g_utf8_get_char (iter->end);
+ iter->upright = width_iter_is_upright (ch);
+ }
+
+ while (iter->end < iter->text_end)
+ {
+ gunichar ch = g_utf8_get_char (iter->end);
+
+ /* for zero width joiner */
+ if (ch == 0x200D)
+ {
+ iter->end = g_utf8_next_char (iter->end);
+ met_joiner = TRUE;
+ continue;
+ }
+
+ /* ignore the upright check if met joiner */
+ if (met_joiner)
+ {
+ iter->end = g_utf8_next_char (iter->end);
+ met_joiner = FALSE;
+ continue;
+ }
+
+ /* for variation selector, tag and emoji modifier. */
+ if (G_UNLIKELY (ch == 0xFE0EU || ch == 0xFE0FU ||
+ (ch >= 0xE0020 && ch <= 0xE007F) ||
+ (ch >= 0x1F3FB && ch <= 0x1F3FF)))
+ {
+ iter->end = g_utf8_next_char (iter->end);
+ continue;
+ }
+
+ if (width_iter_is_upright (ch) != iter->upright)
+ break;
+
+ iter->end = g_utf8_next_char (iter->end);
+ }
+}
+
+static void
+width_iter_init (Pango2WidthIter *iter,
+ const char *text,
+ int length)
+{
+ iter->text_start = text;
+ iter->text_end = text + length;
+ iter->start = iter->end = text;
+
+ width_iter_next (iter);
+}
+
+static void
+width_iter_fini (Pango2WidthIter *iter)
+{
+}
+
+/* }}} */
+ /* {{{ Itemization */
+
+typedef struct _ItemizeState ItemizeState;
+
+
+typedef enum {
+ EMBEDDING_CHANGED = 1 << 0,
+ SCRIPT_CHANGED = 1 << 1,
+ LANG_CHANGED = 1 << 2,
+ FONT_CHANGED = 1 << 3,
+ DERIVED_LANG_CHANGED = 1 << 4,
+ WIDTH_CHANGED = 1 << 5,
+ EMOJI_CHANGED = 1 << 6,
+} ChangedFlags;
+
+
+struct _ItemizeState
+{
+ Pango2Context *context;
+ const char *text;
+ const char *end;
+
+ const char *run_start;
+ const char *run_end;
+
+ GList *result;
+ Pango2Item *item;
+
+ guint8 *embedding_levels;
+ int embedding_end_offset;
+ const char *embedding_end;
+ guint8 embedding;
+
+ Pango2Gravity gravity;
+ Pango2GravityHint gravity_hint;
+ Pango2Gravity resolved_gravity;
+ Pango2Gravity font_desc_gravity;
+ gboolean centered_baseline;
+
+ Pango2AttrIterator *attr_iter;
+ gboolean free_attr_iter;
+ const char *attr_end;
+ Pango2FontDescription *font_desc;
+ Pango2FontDescription *emoji_font_desc;
+ Pango2Language *lang;
+ GSList *extra_attrs;
+ gboolean copy_extra_attrs;
+
+ ChangedFlags changed;
+
+ Pango2ScriptIter script_iter;
+ const char *script_end;
+ GUnicodeScript script;
+
+ Pango2WidthIter width_iter;
+ Pango2EmojiIter emoji_iter;
+
+ Pango2Language *derived_lang;
+
+ Pango2Fontset *current_fonts;
+ FontCache *cache;
+ Pango2Font *base_font;
+ gboolean enable_fallback;
+
+ const char *first_space; /* first of a sequence of spaces we've seen */
+ int font_position; /* position of the current font in the fontset */
+};
+
+static void
+update_embedding_end (ItemizeState *state)
+{
+ state->embedding = state->embedding_levels[state->embedding_end_offset];
+ while (state->embedding_end < state->end &&
+ state->embedding_levels[state->embedding_end_offset] == state->embedding)
+ {
+ state->embedding_end_offset++;
+ state->embedding_end = g_utf8_next_char (state->embedding_end);
+ }
+
+ state->changed |= EMBEDDING_CHANGED;
+}
+
+static Pango2Attribute *
+find_attribute (GSList *attr_list,
+ Pango2AttrType type)
+{
+ GSList *node;
+
+ for (node = attr_list; node; node = node->next)
+ if (((Pango2Attribute *) node->data)->type == type)
+ return (Pango2Attribute *) node->data;
+
+ return NULL;
+}
+
+static void
+update_attr_iterator (ItemizeState *state)
+{
+ Pango2Language *old_lang;
+ Pango2Attribute *attr;
+ int end_index;
+
+ pango2_attr_iterator_range (state->attr_iter, NULL, &end_index);
+ if (end_index < state->end - state->text)
+ state->attr_end = state->text + end_index;
+ else
+ state->attr_end = state->end;
+
+ if (state->emoji_font_desc)
+ {
+ pango2_font_description_free (state->emoji_font_desc);
+ state->emoji_font_desc = NULL;
+ }
+
+ old_lang = state->lang;
+ if (state->font_desc)
+ pango2_font_description_free (state->font_desc);
+ state->font_desc = pango2_font_description_copy_static (state->context->font_desc);
+ pango2_attr_iterator_get_font (state->attr_iter, state->font_desc,
+ &state->lang, &state->extra_attrs);
+ if (pango2_font_description_get_set_fields (state->font_desc) & PANGO2_FONT_MASK_GRAVITY)
+ state->font_desc_gravity = pango2_font_description_get_gravity (state->font_desc);
+ else
+ state->font_desc_gravity = PANGO2_GRAVITY_AUTO;
+
+ state->copy_extra_attrs = FALSE;
+
+ if (!state->lang)
+ state->lang = state->context->language;
+
+ attr = find_attribute (state->extra_attrs, PANGO2_ATTR_FALLBACK);
+ state->enable_fallback = (attr == NULL || attr->int_value);
+
+ attr = find_attribute (state->extra_attrs, PANGO2_ATTR_GRAVITY);
+ state->gravity = attr == NULL ? PANGO2_GRAVITY_AUTO : attr->int_value;
+
+ attr = find_attribute (state->extra_attrs, PANGO2_ATTR_GRAVITY_HINT);
+ state->gravity_hint = attr == NULL ? state->context->gravity_hint : (Pango2GravityHint)attr->int_value;
+
+ state->changed |= FONT_CHANGED;
+ if (state->lang != old_lang)
+ state->changed |= LANG_CHANGED;
+}
+
+static void
+update_end (ItemizeState *state)
+{
+ state->run_end = state->embedding_end;
+ if (state->attr_end < state->run_end)
+ state->run_end = state->attr_end;
+ if (state->script_end < state->run_end)
+ state->run_end = state->script_end;
+ if (state->width_iter.end < state->run_end)
+ state->run_end = state->width_iter.end;
+ if (state->emoji_iter.end < state->run_end)
+ state->run_end = state->emoji_iter.end;
+}
+
+
+static void
+itemize_state_init (ItemizeState *state,
+ Pango2Context *context,
+ const char *text,
+ Pango2Direction base_dir,
+ int start_index,
+ int length,
+ Pango2AttrList *attrs,
+ Pango2AttrIterator *cached_iter,
+ const Pango2FontDescription *desc)
+{
+ state->context = context;
+ state->text = text;
+ state->end = text + start_index + length;
+
+ state->result = NULL;
+ state->item = NULL;
+
+ state->run_start = text + start_index;
+ state->changed = EMBEDDING_CHANGED | SCRIPT_CHANGED | LANG_CHANGED |
+ FONT_CHANGED | WIDTH_CHANGED | EMOJI_CHANGED;
+
+ /* First, apply the bidirectional algorithm to break
+ * the text into directional runs.
+ */
+ state->embedding_levels = pango2_log2vis_get_embedding_levels (text + start_index, length, &base_dir);
+
+ state->embedding_end_offset = 0;
+ state->embedding_end = text + start_index;
+ update_embedding_end (state);
+
+ state->gravity = PANGO2_GRAVITY_AUTO;
+ state->centered_baseline = PANGO2_GRAVITY_IS_VERTICAL (state->context->resolved_gravity);
+ state->gravity_hint = state->context->gravity_hint;
+ state->resolved_gravity = PANGO2_GRAVITY_AUTO;
+
+ /* Initialize the attribute iterator
+ */
+ if (cached_iter)
+ {
+ state->attr_iter = cached_iter;
+ state->free_attr_iter = FALSE;
+ }
+ else if (attrs)
+ {
+ state->attr_iter = pango2_attr_list_get_iterator (attrs);
+ state->free_attr_iter = TRUE;
+ }
+ else
+ {
+ state->attr_iter = NULL;
+ state->free_attr_iter = FALSE;
+ }
+
+ state->emoji_font_desc = NULL;
+ if (state->attr_iter)
+ {
+ state->font_desc = NULL;
+ state->lang = NULL;
+
+ pango2_attr_iterator_advance (state->attr_iter, start_index);
+ update_attr_iterator (state);
+ }
+ else
+ {
+ state->font_desc = pango2_font_description_copy_static (desc ? desc : state->context->font_desc);
+ state->lang = state->context->language;
+ state->extra_attrs = NULL;
+ state->copy_extra_attrs = FALSE;
+
+ state->attr_end = state->end;
+ state->enable_fallback = TRUE;
+ }
+
+ /* Initialize the script iterator
+ */
+ _pango2_script_iter_init (&state->script_iter, text + start_index, length);
+ pango2_script_iter_get_range (&state->script_iter, NULL,
+ &state->script_end, &state->script);
+
+ width_iter_init (&state->width_iter, text + start_index, length);
+ _pango2_emoji_iter_init (&state->emoji_iter, text + start_index, length);
+
+ if (!PANGO2_GRAVITY_IS_VERTICAL (state->context->resolved_gravity))
+ state->width_iter.end = state->end;
+ else if (state->emoji_iter.is_emoji)
+ state->width_iter.end = MAX (state->width_iter.end, state->emoji_iter.end);
+
+ update_end (state);
+
+ if (pango2_font_description_get_set_fields (state->font_desc) & PANGO2_FONT_MASK_GRAVITY)
+ state->font_desc_gravity = pango2_font_description_get_gravity (state->font_desc);
+ else
+ state->font_desc_gravity = PANGO2_GRAVITY_AUTO;
+
+ state->derived_lang = NULL;
+ state->current_fonts = NULL;
+ state->cache = NULL;
+ state->base_font = NULL;
+ state->first_space = NULL;
+ state->font_position = 0xffff;
+}
+
+static gboolean
+itemize_state_next (ItemizeState *state)
+{
+ if (state->run_end == state->end)
+ return FALSE;
+
+ state->changed = 0;
+
+ state->run_start = state->run_end;
+
+ if (state->run_end == state->embedding_end)
+ {
+ update_embedding_end (state);
+ }
+
+ if (state->run_end == state->attr_end)
+ {
+ pango2_attr_iterator_next (state->attr_iter);
+ update_attr_iterator (state);
+ }
+
+ if (state->run_end == state->script_end)
+ {
+ pango2_script_iter_next (&state->script_iter);
+ pango2_script_iter_get_range (&state->script_iter, NULL,
+ &state->script_end, &state->script);
+ state->changed |= SCRIPT_CHANGED;
+ }
+ if (state->run_end == state->emoji_iter.end)
+ {
+ _pango2_emoji_iter_next (&state->emoji_iter);
+ state->changed |= EMOJI_CHANGED;
+
+ if (state->emoji_iter.is_emoji)
+ state->width_iter.end = MAX (state->width_iter.end, state->emoji_iter.end);
+ }
+ if (state->run_end == state->width_iter.end)
+ {
+ width_iter_next (&state->width_iter);
+ state->changed |= WIDTH_CHANGED;
+ }
+
+ update_end (state);
+
+ return TRUE;
+}
+
+static GSList *
+copy_attr_slist (GSList *attr_slist)
+{
+ GSList *new_list = NULL;
+ GSList *l;
+
+ for (l = attr_slist; l; l = l->next)
+ new_list = g_slist_prepend (new_list, pango2_attribute_copy (l->data));
+
+ return g_slist_reverse (new_list);
+}
+
+static void
+itemize_state_fill_font (ItemizeState *state,
+ Pango2Font *font)
+{
+ GList *l;
+
+ for (l = state->result; l; l = l->next)
+ {
+ Pango2Item *item = l->data;
+ if (item->analysis.font)
+ break;
+ if (font)
+ item->analysis.font = g_object_ref (font);
+ }
+}
+
+static void
+itemize_state_add_character (ItemizeState *state,
+ Pango2Font *font,
+ int font_position,
+ gboolean force_break,
+ const char *pos,
+ gboolean is_space)
+{
+ const char *first_space = state->first_space;
+ int n_spaces = 0;
+
+ if (is_space)
+ {
+ if (state->first_space == NULL)
+ state->first_space = pos;
+ }
+ else
+ state->first_space = NULL;
+
+ if (state->item)
+ {
+ if (!state->item->analysis.font && font)
+ {
+ itemize_state_fill_font (state, font);
+ state->font_position = font_position;
+ }
+ else if (state->item->analysis.font && !font)
+ {
+ font = state->item->analysis.font;
+ font_position = state->font_position;
+ }
+
+ if (!force_break &&
+ state->item->analysis.font == font)
+ {
+ state->item->num_chars++;
+ return;
+ }
+
+ /* Font is changing, we are about to end the current item.
+ * If it ended in a sequence of spaces (but wasn't only spaces),
+ * check if we should move those spaces to the new item (since
+ * the font is less "fallback".
+ *
+ * See https://gitlab.gnome.org/GNOME/pango/-/issues/249
+ */
+ if (state->text + state->item->offset < first_space &&
+ font_position < state->font_position)
+ {
+ n_spaces = g_utf8_strlen (first_space, pos - first_space);
+ state->item->num_chars -= n_spaces;
+ pos = first_space;
+ }
+
+ state->item->length = (pos - state->text) - state->item->offset;
+ }
+
+ state->item = pango2_item_new ();
+ state->item->offset = pos - state->text;
+ state->item->length = 0;
+ state->item->num_chars = n_spaces + 1;
+
+ if (font)
+ g_object_ref (font);
+ state->item->analysis.font = font;
+ state->font_position = font_position;
+
+ state->item->analysis.level = state->embedding;
+ state->item->analysis.gravity = state->resolved_gravity;
+
+ /* The level vs. gravity dance:
+ * - If gravity is SOUTH, leave level untouched.
+ * - If gravity is NORTH, step level one up, to
+ * not get mirrored upside-down text.
+ * - If gravity is EAST, step up to an even level, as
+ * it's a clockwise-rotated layout, so the rotated
+ * top is unrotated left.
+ * - If gravity is WEST, step up to an odd level, as
+ * it's a counter-clockwise-rotated layout, so the rotated
+ * top is unrotated right.
+ *
+ * A similar dance is performed in pango-layout.c:
+ * line_set_resolved_dir(). Keep in synch.
+ */
+ switch (state->item->analysis.gravity)
+ {
+ case PANGO2_GRAVITY_SOUTH:
+ default:
+ break;
+ case PANGO2_GRAVITY_NORTH:
+ state->item->analysis.level++;
+ break;
+ case PANGO2_GRAVITY_EAST:
+ state->item->analysis.level += 1;
+ state->item->analysis.level &= ~1;
+ break;
+ case PANGO2_GRAVITY_WEST:
+ state->item->analysis.level |= 1;
+ break;
+ }
+
+ state->item->analysis.flags |= state->centered_baseline ? PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE : 0;
+
+ state->item->analysis.script = state->script;
+ state->item->analysis.language = state->derived_lang;
+
+ if (state->copy_extra_attrs)
+ {
+ state->item->analysis.extra_attrs = copy_attr_slist (state->extra_attrs);
+ }
+ else
+ {
+ state->item->analysis.extra_attrs = state->extra_attrs;
+ state->copy_extra_attrs = TRUE;
+ }
+
+ state->result = g_list_prepend (state->result, state->item);
+}
+
+typedef struct {
+ Pango2Font *font;
+ int position;
+} GetFontInfo;
+
+static gboolean
+get_font_foreach (Pango2Fontset *fontset,
+ Pango2Font *font,
+ gpointer data)
+{
+ GetFontInfo *info = data;
+
+ if (font == info->font)
+ return TRUE;
+
+ info->position++;
+
+ return FALSE;
+}
+
+static Pango2Font *
+get_base_font (ItemizeState *state)
+{
+ if (!state->base_font)
+ state->base_font = pango2_font_map_load_font (state->context->font_map,
+ state->context,
+ state->font_desc);
+ return state->base_font;
+}
+
+static gboolean
+get_font (ItemizeState *state,
+ gunichar wc,
+ Pango2Font **font,
+ int *position)
+{
+ GetFontInfo info;
+
+ /* We'd need a separate cache when fallback is disabled, but since lookup
+ * with fallback disabled is faster anyways, we just skip caching
+ */
+ if (state->enable_fallback && font_cache_get (state->cache, wc, font, position))
+ return TRUE;
+
+ info.font = NULL;
+ info.position = 0;
+
+ if (state->enable_fallback)
+ {
+ info.font = pango2_fontset_get_font (state->current_fonts, wc);
+ if (info.font)
+ g_object_unref (info.font);
+ pango2_fontset_foreach (state->current_fonts, get_font_foreach, &info);
+ }
+
+ if (!info.font)
+ info.font = get_base_font (state);
+
+ *font = info.font;
+ *position = info.position;
+
+ /* skip caching if fallback disabled (see above) */
+ if (state->enable_fallback)
+ font_cache_insert (state->cache, wc, *font, *position);
+
+ return TRUE;
+}
+
+static Pango2Language *
+compute_derived_language (Pango2Language *lang,
+ GUnicodeScript script)
+{
+ Pango2Language *derived_lang;
+
+ /* Make sure the language tag is consistent with the derived
+ * script. There is no point in marking up a section of
+ * Arabic text with the "en" language tag.
+ */
+ if (lang && pango2_language_includes_script (lang, script))
+ derived_lang = lang;
+ else
+ {
+ derived_lang = pango2_script_get_sample_language (script);
+ /* If we don't find a sample language for the script, we
+ * use a language tag that shouldn't actually be used
+ * anywhere. This keeps fontconfig (for the Pango2Fc*
+ * backend) from using the language tag to affect the
+ * sort order. I don't have a reference for 'xx' being
+ * safe here, though Keith Packard claims it is.
+ */
+ if (!derived_lang)
+ derived_lang = pango2_language_from_string ("xx");
+ }
+
+ return derived_lang;
+}
+
+static void
+itemize_state_update_for_new_run (ItemizeState *state)
+{
+ /* This block should be moved to update_attr_iterator, but I'm too lazy to
+ * do it right now */
+ if (state->changed & (FONT_CHANGED | SCRIPT_CHANGED | WIDTH_CHANGED))
+ {
+ /* Font-desc gravity overrides everything */
+ if (state->font_desc_gravity != PANGO2_GRAVITY_AUTO)
+ {
+ state->resolved_gravity = state->font_desc_gravity;
+ }
+ else
+ {
+ Pango2Gravity gravity = state->gravity;
+ Pango2GravityHint gravity_hint = state->gravity_hint;
+
+ if (G_LIKELY (gravity == PANGO2_GRAVITY_AUTO))
+ gravity = state->context->resolved_gravity;
+
+ state->resolved_gravity = pango2_gravity_get_for_script_and_width (state->script,
+ state->width_iter.upright,
+ gravity,
+ gravity_hint);
+ }
+
+ if (state->font_desc_gravity != state->resolved_gravity)
+ {
+ pango2_font_description_set_gravity (state->font_desc, state->resolved_gravity);
+ state->changed |= FONT_CHANGED;
+ }
+ }
+
+ if (state->changed & (SCRIPT_CHANGED | LANG_CHANGED))
+ {
+ Pango2Language *old_derived_lang = state->derived_lang;
+ state->derived_lang = compute_derived_language (state->lang, state->script);
+ if (old_derived_lang != state->derived_lang)
+ state->changed |= DERIVED_LANG_CHANGED;
+ }
+
+ if (state->changed & (EMOJI_CHANGED))
+ {
+ state->changed |= FONT_CHANGED;
+ }
+
+ if (state->changed & (FONT_CHANGED | DERIVED_LANG_CHANGED) &&
+ state->current_fonts)
+ {
+ g_object_unref (state->current_fonts);
+ state->current_fonts = NULL;
+ state->cache = NULL;
+ }
+
+ if (!state->current_fonts)
+ {
+ gboolean is_emoji = state->emoji_iter.is_emoji;
+ if (is_emoji && !state->emoji_font_desc)
+ {
+ state->emoji_font_desc = pango2_font_description_copy_static (state->font_desc);
+ pango2_font_description_set_family_static (state->emoji_font_desc, "emoji");
+ }
+ state->current_fonts = pango2_font_map_load_fontset (state->context->font_map,
+ state->context,
+ is_emoji ? state->emoji_font_desc : state->font_desc,
+ state->derived_lang);
+ state->cache = get_font_cache (state->current_fonts);
+ }
+
+ if ((state->changed & FONT_CHANGED) && state->base_font)
+ {
+ g_object_unref (state->base_font);
+ state->base_font = NULL;
+ }
+}
+
+/* We don't want space characters to affect font selection; in general,
+ * it's always wrong to select a font just to render a space.
+ *
+ * We assume that all fonts have the ASCII space, and for other space
+ * characters if they don't, HarfBuzz will compatibility-decompose them
+ * to ASCII space...
+ * See bugs #355987 and #701652.
+ *
+ * We don't want to change fonts just for variation selectors.
+ * See bug #781123.
+ *
+ * We don't want to change fonts for default ignorables such as Cf chars.
+ * Note that Cf chars in the Arabic block are visible and need to have
+ * a font, so we exclude.
+ *
+ * Finally, don't change fonts for line or paragraph separators.
+ *
+ * Note that we want spaces to use the 'better' font, comparing
+ * the font that is used before and after the space. This is handled
+ * in itemize_state_add_character().
+ */
+static gboolean
+consider_as_space (gunichar wc)
+{
+ GUnicodeType type = g_unichar_type (wc);
+ return type == G_UNICODE_CONTROL ||
+ (type == G_UNICODE_FORMAT && !((wc >= 0x600 && wc <= 0x06ff) || wc == 0x70f || wc == 0x8e2)) ||
+ type == G_UNICODE_SURROGATE ||
+ type == G_UNICODE_LINE_SEPARATOR ||
+ type == G_UNICODE_PARAGRAPH_SEPARATOR ||
+ (type == G_UNICODE_SPACE_SEPARATOR && wc != 0x1680u /* OGHAM SPACE MARK */) ||
+ (wc >= 0xfe00u && wc <= 0xfe0fu) ||
+ (wc >= 0xe0100u && wc <= 0xe01efu);
+}
+
+static void
+itemize_state_process_run (ItemizeState *state)
+{
+ const char *p;
+ gboolean last_was_forced_break = FALSE;
+ gboolean is_space;
+ gunichar prev_wc = 0;
+
+ /* Only one character has type G_UNICODE_LINE_SEPARATOR in Unicode 4.0;
+ * update this if that changes. */
+#define LINE_SEPARATOR 0x2028
+
+ itemize_state_update_for_new_run (state);
+
+ /* We should never get an empty run */
+ g_assert (state->run_end != state->run_start);
+
+ for (p = state->run_start;
+ p < state->run_end;
+ p = g_utf8_next_char (p))
+ {
+ gunichar wc = g_utf8_get_char (p);
+ gboolean is_forced_break = wc == '\t' || wc == '\r' || wc == '\n' ||
+ wc == 0x2028 || wc == 0x2029;
+ Pango2Font *font;
+ int font_position;
+
+ if (consider_as_space (wc))
+ {
+ font = NULL;
+ font_position = 0xffff;
+ is_space = TRUE;
+ }
+ else
+ {
+ get_font (state, wc, &font, &font_position);
+ is_space = FALSE;
+ }
+
+ /* Don't break between \r and \n */
+ if (prev_wc == '\r' && wc == '\n')
+ state->item->num_chars++;
+ else
+ itemize_state_add_character (state, font, font_position,
+ is_forced_break || last_was_forced_break,
+ p,
+ is_space);
+
+ last_was_forced_break = is_forced_break;
+ prev_wc = wc;
+ }
+
+ /* Finish the final item from the current segment */
+ state->item->length = (p - state->text) - state->item->offset;
+ if (!state->item->analysis.font)
+ {
+ Pango2Font *font;
+ int position;
+
+ if (G_UNLIKELY (!get_font (state, ' ', &font, &position)))
+ {
+ /* If no font was found, warn once per fontmap/script pair */
+ Pango2FontMap *fontmap = state->context->font_map;
+ char *script_tag = g_strdup_printf ("g-unicode-script-%d", state->script);
+
+ if (!g_object_get_data (G_OBJECT (fontmap), script_tag))
+ {
+ g_warning ("failed to choose a font, expect ugly output. script='%d'",
+ state->script);
+
+ g_object_set_data_full (G_OBJECT (fontmap), script_tag,
+ GINT_TO_POINTER (1), NULL);
+ }
+
+ g_free (script_tag);
+
+ font = NULL;
+ }
+ itemize_state_fill_font (state, font);
+ }
+ state->item = NULL;
+}
+
+static void
+itemize_state_finish (ItemizeState *state)
+{
+ g_free (state->embedding_levels);
+ if (state->free_attr_iter)
+ pango2_attr_iterator_destroy (state->attr_iter);
+ _pango2_script_iter_fini (&state->script_iter);
+ pango2_font_description_free (state->font_desc);
+ pango2_font_description_free (state->emoji_font_desc);
+ width_iter_fini (&state->width_iter);
+ _pango2_emoji_iter_fini (&state->emoji_iter);
+
+ if (state->current_fonts)
+ g_object_unref (state->current_fonts);
+ if (state->base_font)
+ g_object_unref (state->base_font);
+}
+
+/* }}} */
+ /* {{{ Post-processing */
+
+ /* {{{ Handling font scale */
+
+typedef struct {
+ Pango2Attribute *attr;
+ double scale;
+} ScaleItem;
+
+static gboolean
+collect_font_scale (Pango2Context *context,
+ GList **stack,
+ Pango2Item *item,
+ Pango2Item *prev,
+ double *scale,
+ gboolean *is_small_caps)
+{
+ gboolean retval = FALSE;
+ GList *l;
+
+ for (GSList *l = item->analysis.extra_attrs; l; l = l->next)
+ {
+ Pango2Attribute *attr = l->data;
+
+ if (attr->type == PANGO2_ATTR_FONT_SCALE)
+ {
+ if (attr->start_index == item->offset)
+ {
+ ScaleItem *entry;
+ int y_scale;
+ hb_position_t y_size;
+ hb_position_t cap_height;
+ hb_position_t x_height;
+
+ entry = g_new (ScaleItem, 1);
+ entry->attr = attr;
+ *stack = g_list_prepend (*stack, entry);
+
+ switch (attr->int_value)
+ {
+ case PANGO2_FONT_SCALE_NONE:
+ break;
+ case PANGO2_FONT_SCALE_SUPERSCRIPT:
+ if (prev &&
+ hb_ot_metrics_get_position (pango2_font_get_hb_font (prev->analysis.font),
+ HB_OT_METRICS_TAG_SUPERSCRIPT_EM_Y_SIZE,
+ &y_size))
+ {
+ hb_font_get_scale (pango2_font_get_hb_font (prev->analysis.font), NULL, &y_scale);
+ entry->scale = y_size / (double) y_scale;
+ }
+ else
+ {
+ entry->scale = 1 / 1.2;
+ }
+ break;
+ case PANGO2_FONT_SCALE_SUBSCRIPT:
+ if (prev &&
+ hb_ot_metrics_get_position (pango2_font_get_hb_font (prev->analysis.font),
+ HB_OT_METRICS_TAG_SUBSCRIPT_EM_Y_SIZE,
+ &y_size))
+ {
+ hb_font_get_scale (pango2_font_get_hb_font (prev->analysis.font), NULL, &y_scale);
+ entry->scale = y_size / (double) y_scale;
+ }
+ else
+ {
+ entry->scale = 1 / 1.2;
+ }
+ break;
+ case PANGO2_FONT_SCALE_SMALL_CAPS:
+ if (hb_ot_metrics_get_position (pango2_font_get_hb_font (item->analysis.font),
+ HB_OT_METRICS_TAG_CAP_HEIGHT,
+ &cap_height) &&
+ hb_ot_metrics_get_position (pango2_font_get_hb_font (item->analysis.font),
+ HB_OT_METRICS_TAG_X_HEIGHT,
+ &x_height))
+ {
+ entry->scale = x_height / (double) cap_height;
+ }
+ else
+ {
+ entry->scale = 0.8;
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+ }
+ }
+
+ *scale = 1.0;
+ *is_small_caps = TRUE;
+
+ for (l = *stack; l; l = l->next)
+ {
+ ScaleItem *entry = l->data;
+ *scale *= entry->scale;
+ if (entry->attr->int_value != PANGO2_FONT_SCALE_SMALL_CAPS)
+ *is_small_caps = FALSE;
+ retval = TRUE;
+ }
+
+ l = *stack;
+ while (l)
+ {
+ ScaleItem *entry = l->data;
+ GList *next = l->next;
+
+ if (entry->attr->end_index == item->offset + item->length)
+ {
+ *stack = g_list_delete_link (*stack, l);
+ g_free (entry);
+ }
+
+ l = next;
+ }
+
+ return retval;
+}
+
+static void
+apply_scale_to_item (Pango2Context *context,
+ Pango2Item *item,
+ double scale,
+ gboolean is_small_caps)
+{
+ Pango2FontDescription *desc;
+ double size;
+
+ if (!item->analysis.font)
+ return;
+
+ if (is_small_caps)
+ pango2_analysis_set_size_font (&item->analysis, item->analysis.font);
+
+ desc = pango2_font_describe (item->analysis.font);
+ size = scale * pango2_font_description_get_size (desc);
+
+ if (pango2_font_description_get_size_is_absolute (desc))
+ pango2_font_description_set_absolute_size (desc, size);
+ else
+ pango2_font_description_set_size (desc, size);
+
+ g_object_unref (item->analysis.font);
+ item->analysis.font = pango2_font_map_load_font (context->font_map, context, desc);
+
+ pango2_font_description_free (desc);
+}
+
+static void
+apply_font_scale (Pango2Context *context,
+ GList *items)
+{
+ Pango2Item *prev = NULL;
+ GList *stack = NULL;
+
+ for (GList *l = items; l; l = l->next)
+ {
+ Pango2Item *item = l->data;
+ double scale;
+ gboolean is_small_caps;
+
+ if (collect_font_scale (context, &stack, item, prev, &scale, &is_small_caps))
+ apply_scale_to_item (context, item, scale, is_small_caps);
+
+ prev = item;
+ }
+
+ if (stack != NULL)
+ {
+ g_warning ("Leftover font scales");
+ g_list_free_full (stack, g_free);
+ }
+}
+
+/* }}} */
+/* { {{ Handling Casing variants */
+
+static gboolean
+all_features_supported (Pango2Item *item,
+ hb_tag_t *features,
+ guint n_features)
+{
+ hb_font_t *font = pango2_font_get_hb_font (item->analysis.font);
+ hb_face_t *face = hb_font_get_face (font);
+ hb_script_t script;
+ hb_language_t language;
+ guint script_count = HB_OT_MAX_TAGS_PER_SCRIPT;
+ hb_tag_t script_tags[HB_OT_MAX_TAGS_PER_SCRIPT];
+ hb_tag_t chosen_script;
+ guint language_count = HB_OT_MAX_TAGS_PER_LANGUAGE;
+ hb_tag_t language_tags[HB_OT_MAX_TAGS_PER_LANGUAGE];
+ guint script_index, language_index;
+ guint index;
+
+ script = g_unicode_script_to_iso15924 (item->analysis.script);
+ language = hb_language_from_string (pango2_language_to_string (item->analysis.language), -1);
+
+ hb_ot_tags_from_script_and_language (script, language,
+ &script_count, script_tags,
+ &language_count, language_tags);
+ hb_ot_layout_table_select_script (face, HB_OT_TAG_GSUB,
+ script_count, script_tags,
+ &script_index,
+ &chosen_script);
+ hb_ot_layout_script_select_language (face, HB_OT_TAG_GSUB,
+ script_index,
+ language_count, language_tags,
+ &language_index);
+
+ for (int i = 0; i < n_features; i++)
+ {
+ if (!hb_ot_layout_language_find_feature (face, HB_OT_TAG_GSUB,
+ script_index, language_index,
+ features[i],
+ &index))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+variant_supported (Pango2Item *item,
+ Pango2Variant variant)
+{
+ hb_tag_t features[2];
+ guint num_features = 0;
+
+ switch (variant)
+ {
+ case PANGO2_VARIANT_NORMAL:
+ case PANGO2_VARIANT_TITLE_CAPS:
+ return TRUE;
+ case PANGO2_VARIANT_SMALL_CAPS:
+ features[num_features++] = HB_TAG ('s', 'm', 'c', 'p');
+ break;
+ case PANGO2_VARIANT_ALL_SMALL_CAPS:
+ features[num_features++] = HB_TAG ('s', 'm', 'c', 'p');
+ features[num_features++] = HB_TAG ('c', '2', 's', 'c');
+ break;
+ case PANGO2_VARIANT_PETITE_CAPS:
+ features[num_features++] = HB_TAG ('p', 'c', 'a', 'p');
+ break;
+ case PANGO2_VARIANT_ALL_PETITE_CAPS:
+ features[num_features++] = HB_TAG ('p', 'c', 'a', 'p');
+ features[num_features++] = HB_TAG ('c', '2', 'p', 'c');
+ break;
+ case PANGO2_VARIANT_UNICASE:
+ features[num_features++] = HB_TAG ('u', 'n', 'i', 'c');
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return all_features_supported (item, features, num_features);
+}
+
+static Pango2Variant
+get_font_variant (Pango2Item *item)
+{
+ Pango2FontDescription *desc;
+ Pango2Variant variant = PANGO2_VARIANT_NORMAL;
+
+ if (item->analysis.font)
+ {
+ desc = pango2_font_describe (item->analysis.font);
+ variant = pango2_font_description_get_variant (desc);
+ pango2_font_description_free (desc);
+ }
+
+ return variant;
+}
+
+static Pango2TextTransform
+find_text_transform (const Pango2Analysis *analysis)
+{
+ GSList *l;
+ Pango2TextTransform transform = PANGO2_TEXT_TRANSFORM_NONE;
+
+ for (l = analysis->extra_attrs; l; l = l->next)
+ {
+ Pango2Attribute *attr = l->data;
+
+ if (attr->type == PANGO2_ATTR_TEXT_TRANSFORM)
+ transform = (Pango2TextTransform) attr->int_value;
+ }
+
+ return transform;
+}
+
+/* Split list_item into upper- and lowercase runs, and
+ * add font scale and text transform attributes to make
+ * them be appear according to variant. The log_attrs are
+ * needed for taking text transforms into account when
+ * determining the case of characters int he run.
+ */
+static void
+split_item_for_variant (const char *text,
+ Pango2LogAttr *log_attrs,
+ Pango2Variant variant,
+ GList *list_item)
+{
+ Pango2Item *item = list_item->data;
+ const char *start, *end;
+ const char *p, *p0;
+ gunichar wc;
+ Pango2TextTransform transform = PANGO2_TEXT_TRANSFORM_NONE;
+ Pango2FontScale lowercase_scale = PANGO2_FONT_SCALE_NONE;
+ Pango2FontScale uppercase_scale = PANGO2_FONT_SCALE_NONE;
+ Pango2TextTransform item_transform;
+ gboolean is_word_start;
+ int offset;
+
+ switch (variant)
+ {
+ case PANGO2_VARIANT_ALL_SMALL_CAPS:
+ case PANGO2_VARIANT_ALL_PETITE_CAPS:
+ uppercase_scale = PANGO2_FONT_SCALE_SMALL_CAPS;
+ G_GNUC_FALLTHROUGH;
+ case PANGO2_VARIANT_SMALL_CAPS:
+ case PANGO2_VARIANT_PETITE_CAPS:
+ transform = PANGO2_TEXT_TRANSFORM_UPPERCASE;
+ lowercase_scale = PANGO2_FONT_SCALE_SMALL_CAPS;
+ break;
+ case PANGO2_VARIANT_UNICASE:
+ uppercase_scale = PANGO2_FONT_SCALE_SMALL_CAPS;
+ break;
+ case PANGO2_VARIANT_NORMAL:
+ case PANGO2_VARIANT_TITLE_CAPS:
+ default:
+ g_assert_not_reached ();
+ }
+
+ item_transform = find_text_transform (&item->analysis);
+
+ start = text + item->offset;
+ end = start + item->length;
+ offset = item->char_offset;
+
+ p = start;
+ while (p < end)
+ {
+ p0 = p;
+ wc = g_utf8_get_char (p);
+ is_word_start = log_attrs && log_attrs[offset].is_word_start;
+ while (p < end && (item_transform == PANGO2_TEXT_TRANSFORM_LOWERCASE ||
+ consider_as_space (wc) ||
+ (g_unichar_islower (wc) &&
+ !(item_transform == PANGO2_TEXT_TRANSFORM_UPPERCASE ||
+ (item_transform == PANGO2_TEXT_TRANSFORM_CAPITALIZE && is_word_start)))))
+ {
+ p = g_utf8_next_char (p);
+ wc = g_utf8_get_char (p);
+ offset++;
+ is_word_start = log_attrs && log_attrs[offset].is_word_start;
+ }
+
+ if (p0 < p)
+ {
+ Pango2Item *new_item;
+ Pango2Attribute *attr;
+
+ /* p0 .. p is a lowercase segment */
+ if (p < end)
+ {
+ new_item = pango2_item_split (item, p - p0, g_utf8_strlen (p0, p - p0));
+ list_item->data = new_item;
+ list_item = g_list_insert_before (list_item, list_item->next, item);
+ list_item = list_item->next;
+ }
+ else
+ {
+ new_item = item;
+ }
+
+ if (transform != PANGO2_TEXT_TRANSFORM_NONE)
+ {
+ attr = pango2_attr_text_transform_new (transform);
+ attr->start_index = new_item->offset;
+ attr->end_index = new_item->offset + new_item->length;
+ new_item->analysis.extra_attrs = g_slist_append (new_item->analysis.extra_attrs, attr);
+ }
+
+ if (lowercase_scale != PANGO2_FONT_SCALE_NONE)
+ {
+ attr = pango2_attr_font_scale_new (lowercase_scale);
+ attr->start_index = new_item->offset;
+ attr->end_index = new_item->offset + new_item->length;
+ new_item->analysis.extra_attrs = g_slist_append (new_item->analysis.extra_attrs, attr);
+ }
+ }
+
+ p0 = p;
+ wc = g_utf8_get_char (p);
+ is_word_start = log_attrs && log_attrs[offset].is_word_start;
+ while (p < end && (item_transform == PANGO2_TEXT_TRANSFORM_UPPERCASE ||
+ consider_as_space (wc) ||
+ !(item_transform == PANGO2_TEXT_TRANSFORM_LOWERCASE || g_unichar_islower (wc)) ||
+ (item_transform == PANGO2_TEXT_TRANSFORM_CAPITALIZE && is_word_start)))
+ {
+ p = g_utf8_next_char (p);
+ wc = g_utf8_get_char (p);
+ offset++;
+ is_word_start = log_attrs && log_attrs[offset].is_word_start;
+ }
+
+ if (p0 < p)
+ {
+ Pango2Item *new_item;
+ Pango2Attribute *attr;
+
+ /* p0 .. p is a uppercase segment */
+ if (p < end)
+ {
+ new_item = pango2_item_split (item, p - p0, g_utf8_strlen (p0, p - p0));
+ list_item->data = new_item;
+ list_item = g_list_insert_before (list_item, list_item->next, item);
+ list_item = list_item->next;
+ }
+ else
+ {
+ new_item = item;
+ }
+
+ if (uppercase_scale != PANGO2_FONT_SCALE_NONE)
+ {
+ attr = pango2_attr_font_scale_new (uppercase_scale);
+ attr->start_index = new_item->offset;
+ attr->end_index = new_item->offset + new_item->length;
+ new_item->analysis.extra_attrs = g_slist_append (new_item->analysis.extra_attrs, attr);
+ }
+ }
+ }
+}
+
+static void
+handle_variants_for_item (const char *text,
+ Pango2LogAttr *log_attrs,
+ GList *l)
+{
+ Pango2Item *item = l->data;
+ Pango2Variant variant;
+
+ variant = get_font_variant (item);
+ if (!variant_supported (item, variant))
+ split_item_for_variant (text, log_attrs, variant, l);
+}
+
+static void
+handle_variants (const char *text,
+ Pango2LogAttr *log_attrs,
+ GList *items)
+{
+ GList *next;
+
+ for (GList *l = items; l; l = next)
+ {
+ next = l->next;
+ handle_variants_for_item (text, log_attrs, l);
+ }
+}
+
+/* }}} */
+
+static GList *
+reorder_items (Pango2Context *context,
+ GList *items)
+{
+ int char_offset = 0;
+
+ items = g_list_reverse (items);
+
+ /* Also cmpute the char offset for each item here */
+ for (GList *l = items; l; l = l->next)
+ {
+ Pango2Item *item = l->data;
+ item->char_offset = char_offset;
+ char_offset += item->num_chars;
+ }
+
+ return items;
+}
+
+static GList *
+post_process_items (Pango2Context *context,
+ const char *text,
+ Pango2LogAttr *log_attrs,
+ GList *items)
+{
+ handle_variants (text, log_attrs, items);
+ apply_font_scale (context, items);
+
+ return items;
+}
+
+/* }}} */
+/* {{{ Private API */
+
+/* Like pango2_itemize, but takes a font description.
+ * In contrast to pango2_itemize, this function does
+ * not call pango2_itemize_post_process_items, so you need to do that
+ * separately, after applying attributes that affect segmentation and
+ * computing the log attrs.
+ */
+GList *
+pango2_itemize_with_font (Pango2Context *context,
+ Pango2Direction base_dir,
+ const char *text,
+ int start_index,
+ int length,
+ Pango2AttrList *attrs,
+ Pango2AttrIterator *cached_iter,
+ const Pango2FontDescription *desc)
+{
+ ItemizeState state;
+
+ g_return_val_if_fail (context->font_map != NULL, NULL);
+
+ if (length == 0 || g_utf8_get_char (text + start_index) == '\0')
+ return NULL;
+
+ itemize_state_init (&state, context, text, base_dir, start_index, length,
+ attrs, cached_iter, desc);
+
+ do
+ itemize_state_process_run (&state);
+ while (itemize_state_next (&state));
+
+ itemize_state_finish (&state);
+
+ return reorder_items (context, state.result);
+}
+
+/* Apply post-processing steps that may require log attrs.
+ */
+GList *
+pango2_itemize_post_process_items (Pango2Context *context,
+ const char *text,
+ Pango2LogAttr *log_attrs,
+ GList *items)
+{
+ return post_process_items (context, text, log_attrs, items);
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_itemize:
+ * @context: a structure holding information that affects
+ * the itemization process.
+ * @base_dir: base direction to use for bidirectional processing
+ * @text: the text to itemize.
+ * @start_index: first byte in @text to process
+ * @length: the number of bytes (not characters) to process
+ * after @start_index. This must be >= 0.
+ * @attrs: the set of attributes that apply to @text.
+ *
+ * Breaks a piece of text into segments with consistent directional
+ * level and font.
+ *
+ * Each byte of @text will be contained in exactly one of the items in the
+ * returned list; the generated list of items will be in logical order (the
+ * start offsets of the items are ascending).
+ *
+ * The base direction is used when computing bidirectional levels.
+ * [func@itemize] gets the base direction from the `Pango2Context`
+ * (see [method@Pango2.Context.set_base_dir]).
+ *
+ * Return value: (transfer full) (element-type Pango2.Item): a `GList` of
+ * [struct@Pango2.Item] structures. The items should be freed using
+ * [method@Pango2.Item.free] probably in combination with [func@GLib.List.free_full].
+ */
+GList *
+pango2_itemize (Pango2Context *context,
+ Pango2Direction base_dir,
+ const char *text,
+ int start_index,
+ int length,
+ Pango2AttrList *attrs)
+{
+ GList *items;
+
+ g_return_val_if_fail (context != NULL, NULL);
+ g_return_val_if_fail (start_index >= 0, NULL);
+ g_return_val_if_fail (length >= 0, NULL);
+ g_return_val_if_fail (length == 0 || text != NULL, NULL);
+
+ items = pango2_itemize_with_font (context, base_dir,
+ text, start_index, length,
+ attrs, NULL, NULL);
+
+ return pango2_itemize_post_process_items (context, text, NULL, items);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/json/gtkjsonparser.c b/pango2/json/gtkjsonparser.c
new file mode 100644
index 00000000..0599d8f1
--- /dev/null
+++ b/pango2/json/gtkjsonparser.c
@@ -0,0 +1,1737 @@
+/*
+ * Copyright © 2021 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+
+#include "config.h"
+
+#include "gtkjsonparserprivate.h"
+#include <stdlib.h>
+#include <errno.h>
+
+typedef struct _GtkJsonBlock GtkJsonBlock;
+
+typedef enum {
+ GTK_JSON_BLOCK_TOPLEVEL,
+ GTK_JSON_BLOCK_OBJECT,
+ GTK_JSON_BLOCK_ARRAY,
+} GtkJsonBlockType;
+
+struct _GtkJsonBlock
+{
+ GtkJsonBlockType type;
+ const guchar *value; /* start of current value to be consumed by external code */
+ const guchar *member_name; /* name of current value, only used for object types */
+ gsize index; /* index of the current element */
+};
+
+struct _GtkJsonParser
+{
+ GBytes *bytes;
+ const guchar *reader; /* current read head, pointing as far as we've read */
+ const guchar *start; /* pointer at start of data, after optional BOM */
+ const guchar *end; /* pointer after end of data we're reading */
+
+ GError *error; /* if an error has happened, it's stored here. Errors aren't recoverable. */
+ const guchar *error_start; /* start of error location */
+ const guchar *error_end; /* end of error location */
+
+ GtkJsonBlock *block; /* current block */
+ GtkJsonBlock *blocks; /* blocks array */
+ GtkJsonBlock *blocks_end; /* blocks array */
+ GtkJsonBlock blocks_preallocated[128]; /* preallocated */
+};
+
+typedef enum {
+ WHITESPACE = (1 << 4),
+ NEWLINE = (1 << 5),
+ STRING_ELEMENT = (1 << 6),
+ STRING_MARKER = (1 << 7),
+} JsonCharacterType;
+
+#define JSON_CHARACTER_NODE_MASK ((1 << 4) - 1)
+
+static const guchar json_character_table[256] = {
+ ['\t'] = WHITESPACE,
+ ['\r'] = WHITESPACE | NEWLINE,
+ ['\n'] = WHITESPACE | NEWLINE,
+ [' '] = WHITESPACE | STRING_ELEMENT,
+ ['!'] = STRING_ELEMENT,
+ ['"'] = GTK_JSON_STRING | STRING_MARKER,
+ ['#'] = STRING_ELEMENT,
+ ['$'] = STRING_ELEMENT,
+ ['%'] = STRING_ELEMENT,
+ ['&'] = STRING_ELEMENT,
+ ['\''] = STRING_ELEMENT,
+ ['('] = STRING_ELEMENT,
+ [')'] = STRING_ELEMENT,
+ ['*'] = STRING_ELEMENT,
+ ['+'] = STRING_ELEMENT,
+ [','] = STRING_ELEMENT,
+ ['-'] = GTK_JSON_NUMBER | STRING_ELEMENT,
+ ['.'] = STRING_ELEMENT,
+ ['/'] = STRING_ELEMENT,
+ ['0'] = GTK_JSON_NUMBER | STRING_ELEMENT,
+ ['1'] = GTK_JSON_NUMBER | STRING_ELEMENT,
+ ['2'] = GTK_JSON_NUMBER | STRING_ELEMENT,
+ ['3'] = GTK_JSON_NUMBER | STRING_ELEMENT,
+ ['4'] = GTK_JSON_NUMBER | STRING_ELEMENT,
+ ['5'] = GTK_JSON_NUMBER | STRING_ELEMENT,
+ ['6'] = GTK_JSON_NUMBER | STRING_ELEMENT,
+ ['7'] = GTK_JSON_NUMBER | STRING_ELEMENT,
+ ['8'] = GTK_JSON_NUMBER | STRING_ELEMENT,
+ ['9'] = GTK_JSON_NUMBER | STRING_ELEMENT,
+ [':'] = STRING_ELEMENT,
+ [';'] = STRING_ELEMENT,
+ ['<'] = STRING_ELEMENT,
+ ['='] = STRING_ELEMENT,
+ ['>'] = STRING_ELEMENT,
+ ['?'] = STRING_ELEMENT,
+ ['@'] = STRING_ELEMENT,
+ ['A'] = STRING_ELEMENT,
+ ['B'] = STRING_ELEMENT,
+ ['C'] = STRING_ELEMENT,
+ ['D'] = STRING_ELEMENT,
+ ['E'] = STRING_ELEMENT,
+ ['F'] = STRING_ELEMENT,
+ ['G'] = STRING_ELEMENT,
+ ['H'] = STRING_ELEMENT,
+ ['I'] = STRING_ELEMENT,
+ ['J'] = STRING_ELEMENT,
+ ['K'] = STRING_ELEMENT,
+ ['L'] = STRING_ELEMENT,
+ ['M'] = STRING_ELEMENT,
+ ['N'] = STRING_ELEMENT,
+ ['O'] = STRING_ELEMENT,
+ ['P'] = STRING_ELEMENT,
+ ['Q'] = STRING_ELEMENT,
+ ['R'] = STRING_ELEMENT,
+ ['S'] = STRING_ELEMENT,
+ ['T'] = STRING_ELEMENT,
+ ['U'] = STRING_ELEMENT,
+ ['V'] = STRING_ELEMENT,
+ ['W'] = STRING_ELEMENT,
+ ['X'] = STRING_ELEMENT,
+ ['Y'] = STRING_ELEMENT,
+ ['Z'] = STRING_ELEMENT,
+ ['['] = GTK_JSON_ARRAY | STRING_ELEMENT,
+ ['\\'] = STRING_MARKER,
+ [']'] = STRING_ELEMENT,
+ ['^'] = STRING_ELEMENT,
+ ['_'] = STRING_ELEMENT,
+ ['`'] = STRING_ELEMENT,
+ ['a'] = STRING_ELEMENT,
+ ['b'] = STRING_ELEMENT,
+ ['c'] = STRING_ELEMENT,
+ ['d'] = STRING_ELEMENT,
+ ['e'] = STRING_ELEMENT,
+ ['f'] = GTK_JSON_BOOLEAN | STRING_ELEMENT,
+ ['g'] = STRING_ELEMENT,
+ ['h'] = STRING_ELEMENT,
+ ['i'] = STRING_ELEMENT,
+ ['j'] = STRING_ELEMENT,
+ ['k'] = STRING_ELEMENT,
+ ['l'] = STRING_ELEMENT,
+ ['m'] = STRING_ELEMENT,
+ ['n'] = GTK_JSON_NULL | STRING_ELEMENT,
+ ['o'] = STRING_ELEMENT,
+ ['p'] = STRING_ELEMENT,
+ ['q'] = STRING_ELEMENT,
+ ['r'] = STRING_ELEMENT,
+ ['s'] = STRING_ELEMENT,
+ ['t'] = GTK_JSON_BOOLEAN | STRING_ELEMENT,
+ ['u'] = STRING_ELEMENT,
+ ['v'] = STRING_ELEMENT,
+ ['w'] = STRING_ELEMENT,
+ ['x'] = STRING_ELEMENT,
+ ['y'] = STRING_ELEMENT,
+ ['z'] = STRING_ELEMENT,
+ ['{'] = GTK_JSON_OBJECT | STRING_ELEMENT,
+ ['|'] = STRING_ELEMENT,
+ ['}'] = STRING_ELEMENT,
+ ['~'] = STRING_ELEMENT,
+ [127] = STRING_ELEMENT,
+};
+
+static const guchar *
+json_skip_characters (const guchar *start,
+ const guchar *end,
+ JsonCharacterType type)
+{
+ const guchar *s;
+
+ for (s = start; s < end; s++)
+ {
+ if (!(json_character_table[*s] & type))
+ break;
+ }
+ return s;
+}
+
+static const guchar *
+json_skip_characters_until (const guchar *start,
+ const guchar *end,
+ JsonCharacterType type)
+{
+ const guchar *s;
+
+ for (s = start; s < end; s++)
+ {
+ if (json_character_table[*s] & type)
+ break;
+ }
+ return s;
+}
+
+static const guchar *
+json_find_character (const guchar *start,
+ JsonCharacterType type)
+{
+ const guchar *s;
+
+ for (s = start; ; s++)
+ {
+ if ((json_character_table[*s] & type))
+ break;
+ }
+ return s;
+}
+
+GQuark
+gtk_json_error_quark (void)
+{
+ return g_quark_from_static_string ("gtk-json-error-quark");
+}
+
+static void
+gtk_json_parser_take_error (GtkJsonParser *self,
+ const guchar *start_location,
+ const guchar *end_location,
+ GError *error)
+{
+ g_assert (start_location <= end_location);
+ g_assert (self->start <= start_location);
+ g_assert (end_location <= self->end);
+
+ if (self->error)
+ {
+ g_error_free (error);
+ return;
+ }
+
+ self->error = error;
+ self->error_start = start_location;
+ self->error_end = end_location;
+}
+
+static void
+gtk_json_parser_syntax_error_at (GtkJsonParser *self,
+ const guchar *error_start,
+ const guchar *error_end,
+ const char *format,
+ ...) G_GNUC_PRINTF(4, 5);
+static void
+gtk_json_parser_syntax_error_at (GtkJsonParser *self,
+ const guchar *error_start,
+ const guchar *error_end,
+ const char *format,
+ ...)
+{
+ va_list args;
+
+ if (self->error)
+ return;
+
+ va_start (args, format);
+ gtk_json_parser_take_error (self,
+ error_start,
+ error_end,
+ g_error_new_valist (GTK_JSON_ERROR,
+ GTK_JSON_ERROR_SYNTAX,
+ format, args));
+ va_end (args);
+}
+
+static void
+gtk_json_parser_syntax_error (GtkJsonParser *self,
+ const char *format,
+ ...) G_GNUC_PRINTF(2, 3);
+static void
+gtk_json_parser_syntax_error (GtkJsonParser *self,
+ const char *format,
+ ...)
+{
+ va_list args;
+ const guchar *error_end;
+
+ if (self->error)
+ return;
+
+ va_start (args, format);
+ for (error_end = self->reader;
+ error_end < self->end && g_ascii_isalnum (*error_end);
+ error_end++)
+ ;
+ if (error_end == self->reader &&
+ g_utf8_get_char_validated ((const char *) error_end, self->end - error_end) < (gunichar) -2)
+ {
+ error_end = (const guchar *) g_utf8_next_char (error_end);
+ }
+
+ gtk_json_parser_take_error (self,
+ self->reader,
+ error_end,
+ g_error_new_valist (GTK_JSON_ERROR,
+ GTK_JSON_ERROR_SYNTAX,
+ format, args));
+ va_end (args);
+}
+
+static void
+gtk_json_parser_type_error (GtkJsonParser *self,
+ const char *format,
+ ...) G_GNUC_PRINTF(2, 3);
+static void
+gtk_json_parser_type_error (GtkJsonParser *self,
+ const char *format,
+ ...)
+{
+ const guchar *start_location;
+ va_list args;
+
+ if (self->error)
+ return;
+
+ if (self->block->value)
+ start_location = self->block->value;
+ else if (self->block != self->blocks)
+ start_location = self->block[-1].value;
+ else
+ start_location = self->start;
+
+ va_start (args, format);
+ gtk_json_parser_take_error (self,
+ start_location,
+ self->reader,
+ g_error_new_valist (GTK_JSON_ERROR,
+ GTK_JSON_ERROR_TYPE,
+ format, args));
+ va_end (args);
+}
+
+void
+gtk_json_parser_value_error (GtkJsonParser *self,
+ const char *format,
+ ...)
+{
+ const guchar *start_location;
+ va_list args;
+
+ if (self->error)
+ return;
+
+ if (self->block->value)
+ start_location = self->block->value;
+ else if (self->block != self->blocks)
+ start_location = self->block[-1].value;
+ else
+ start_location = self->start;
+
+ va_start (args, format);
+ gtk_json_parser_take_error (self,
+ start_location,
+ self->reader,
+ g_error_new_valist (GTK_JSON_ERROR,
+ GTK_JSON_ERROR_VALUE,
+ format, args));
+ va_end (args);
+}
+
+void
+gtk_json_parser_schema_error (GtkJsonParser *self,
+ const char *format,
+ ...)
+{
+ const guchar *start_location;
+ va_list args;
+
+ if (self->error)
+ return;
+
+ if (self->block->member_name)
+ start_location = self->block->member_name;
+ if (self->block->value)
+ start_location = self->block->value;
+ else if (self->block != self->blocks)
+ start_location = self->block[-1].value;
+ else
+ start_location = self->start;
+
+ va_start (args, format);
+ gtk_json_parser_take_error (self,
+ start_location,
+ self->reader,
+ g_error_new_valist (GTK_JSON_ERROR,
+ GTK_JSON_ERROR_SCHEMA,
+ format, args));
+ va_end (args);
+}
+
+static gboolean
+gtk_json_parser_is_eof (GtkJsonParser *self)
+{
+ return self->reader >= self->end;
+}
+
+static gsize
+gtk_json_parser_remaining (GtkJsonParser *self)
+{
+ g_return_val_if_fail (self->reader <= self->end, 0);
+
+ return self->end - self->reader;
+}
+
+static void
+gtk_json_parser_skip_bom (GtkJsonParser *self)
+{
+ if (gtk_json_parser_remaining (self) < 3)
+ return;
+
+ if (self->reader[0] == 0xEF &&
+ self->reader[1] == 0xBB &&
+ self->reader[2] == 0xBF)
+ self->reader += 3;
+}
+
+static void
+gtk_json_parser_skip_whitespace (GtkJsonParser *self)
+{
+ self->reader = json_skip_characters (self->reader, self->end, WHITESPACE);
+}
+
+static gboolean
+gtk_json_parser_has_char (GtkJsonParser *self,
+ char c)
+{
+ return gtk_json_parser_remaining (self) && *self->reader == c;
+}
+
+static gboolean
+gtk_json_parser_try_char (GtkJsonParser *self,
+ char c)
+{
+ if (!gtk_json_parser_has_char (self, c))
+ return FALSE;
+
+ self->reader++;
+ return TRUE;
+}
+
+static gboolean
+gtk_json_parser_try_identifier_len (GtkJsonParser *self,
+ const char *ident,
+ gsize len)
+{
+ if (gtk_json_parser_remaining (self) < len)
+ return FALSE;
+
+ if (memcmp (self->reader, ident, len) != 0)
+ return FALSE;
+
+ self->reader += len;
+ return TRUE;
+}
+
+#define gtk_json_parser_try_identifier(parser, ident) gtk_json_parser_try_identifier_len(parser, ident, strlen(ident))
+
+/*
+ * decode_utf16_surrogate_pair:
+ * @first: the first UTF-16 code point
+ * @second: the second UTF-16 code point
+ *
+ * Decodes a surrogate pair of UTF-16 code points into the equivalent
+ * Unicode code point.
+ *
+ * If the code points are not valid, 0 is returned.
+ *
+ * Returns: the Unicode code point equivalent to the surrogate pair
+ */
+static inline gunichar
+decode_utf16_surrogate_pair (gunichar first,
+ gunichar second)
+{
+ if (0xd800 > first || first > 0xdbff ||
+ 0xdc00 > second || second > 0xdfff)
+ return 0;
+
+ return 0x10000
+ | (first & 0x3ff) << 10
+ | (second & 0x3ff);
+}
+
+static gsize
+gtk_json_unescape_char (const guchar *json_escape,
+ char out_data[6],
+ gsize *out_len)
+{
+ switch (json_escape[1])
+ {
+ case '"':
+ case '\\':
+ case '/':
+ out_data[0] = json_escape[1];
+ *out_len = 1;
+ return 2;
+ case 'b':
+ out_data[0] = '\b';
+ *out_len = 1;
+ return 2;
+ case 'f':
+ out_data[0] = '\f';
+ *out_len = 1;
+ return 2;
+ case 'n':
+ out_data[0] = '\n';
+ *out_len = 1;
+ return 2;
+ case 'r':
+ out_data[0] = '\r';
+ *out_len = 1;
+ return 2;
+ case 't':
+ out_data[0] = '\t';
+ *out_len = 1;
+ return 2;
+ case 'u':
+ {
+ gunichar unichar = (g_ascii_xdigit_value (json_escape[2]) << 12) |
+ (g_ascii_xdigit_value (json_escape[3]) << 8) |
+ (g_ascii_xdigit_value (json_escape[4]) << 4) |
+ (g_ascii_xdigit_value (json_escape[5]));
+ gsize result = 6;
+
+ /* resolve UTF-16 surrogates for Unicode characters not in the BMP,
+ * as per ECMA 404, § 9, "String"
+ */
+ if (g_unichar_type (unichar) == G_UNICODE_SURROGATE)
+ {
+ unichar = decode_utf16_surrogate_pair (unichar,
+ (g_ascii_xdigit_value (json_escape[8]) << 12) |
+ (g_ascii_xdigit_value (json_escape[9]) << 8) |
+ (g_ascii_xdigit_value (json_escape[10]) << 4) |
+ (g_ascii_xdigit_value (json_escape[11])));
+ result += 6;
+ }
+ *out_len = g_unichar_to_utf8 (unichar, out_data);
+ return result;
+ }
+ default:
+ g_assert_not_reached ();
+ return 0;
+ }
+}
+
+typedef struct _JsonStringIter JsonStringIter;
+struct _JsonStringIter
+{
+ char buf[6];
+ const guchar *s;
+ const guchar *next;
+};
+
+static gsize
+json_string_iter_next (JsonStringIter *iter)
+{
+ gsize len;
+
+ iter->s = iter->next;
+ iter->next = json_find_character (iter->s, STRING_MARKER);
+ if (iter->next != iter->s)
+ return iter->next - iter->s;
+ if (*iter->next == '"')
+ return 0;
+ iter->next += gtk_json_unescape_char (iter->next, iter->buf, &len);
+ iter->s = (const guchar *) iter->buf;
+ return len;
+}
+
+/* The escaped string MUST be valid json, so it must begin
+ * with " and end with " and must not contain any invalid
+ * escape codes.
+ * This function is meant to be fast
+ */
+static gsize
+json_string_iter_init (JsonStringIter *iter,
+ const guchar *string)
+{
+ g_assert (*string == '"');
+
+ iter->next = string + 1;
+
+ return json_string_iter_next (iter);
+}
+
+static gboolean
+json_string_iter_has_next (JsonStringIter *iter)
+{
+ return *iter->next != '"';
+}
+
+static const char *
+json_string_iter_get (JsonStringIter *iter)
+{
+ return (const char *) iter->s;
+}
+
+/* The escaped string MUST be valid json, so it must begin
+ * with " and end with " and must not contain any invalid
+ * escape codes.
+ * This function is meant to be fast
+ */
+static char *
+gtk_json_unescape_string (const guchar *escaped)
+{
+ JsonStringIter iter;
+ GString *string;
+ gsize len;
+
+ len = json_string_iter_init (&iter, escaped);
+ string = NULL;
+
+ if (!json_string_iter_has_next (&iter))
+ return g_strndup (json_string_iter_get (&iter), len);
+
+ string = g_string_new (NULL);
+
+ do
+ {
+ g_string_append_len (string, json_string_iter_get (&iter), len);
+ }
+ while ((len = json_string_iter_next (&iter)));
+
+ return g_string_free (string, FALSE);
+}
+
+static gboolean
+gtk_json_parser_parse_string (GtkJsonParser *self)
+{
+ const guchar *start;
+
+ start = self->reader;
+
+ if (!gtk_json_parser_try_char (self, '"'))
+ {
+ gtk_json_parser_type_error (self, "Not a string");
+ return FALSE;
+ }
+
+ self->reader = json_skip_characters (self->reader, self->end, STRING_ELEMENT);
+
+ while (gtk_json_parser_remaining (self))
+ {
+ if (*self->reader < 0x20)
+ {
+ if (*self->reader == '\r' || *self->reader == '\n')
+ gtk_json_parser_syntax_error (self, "Newlines in strings are not allowed");
+ else if (*self->reader == '\t')
+ gtk_json_parser_syntax_error (self, "Tabs not allowed in strings");
+ else
+ gtk_json_parser_syntax_error (self, "Disallowed control character in string literal");
+ return FALSE;
+ }
+ else if (*self->reader > 127)
+ {
+ gunichar c = g_utf8_get_char_validated ((const char *) self->reader, gtk_json_parser_remaining (self));
+ if (c == (gunichar) -2 || c == (gunichar) -1)
+ {
+ gtk_json_parser_syntax_error (self, "Invalid UTF-8");
+ return FALSE;
+ }
+ self->reader = (const guchar *) g_utf8_next_char ((const char *) self->reader);
+ }
+ else if (*self->reader == '"')
+ {
+ self->reader++;
+ return TRUE;
+ }
+ else if (*self->reader == '\\')
+ {
+ if (gtk_json_parser_remaining (self) < 2)
+ {
+ self->reader = self->end;
+ goto end;
+ }
+ switch (self->reader[1])
+ {
+ case '"':
+ case '\\':
+ case '/':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ break;
+
+ case 'u':
+ /* lots of work necessary to validate the unicode escapes here */
+ if (gtk_json_parser_remaining (self) < 6 ||
+ !g_ascii_isxdigit (self->reader[2]) ||
+ !g_ascii_isxdigit (self->reader[3]) ||
+ !g_ascii_isxdigit (self->reader[4]) ||
+ !g_ascii_isxdigit (self->reader[5]))
+ {
+ const guchar *end;
+ for (end = self->reader + 2;
+ end < self->reader + 6 && end < self->end;
+ end++)
+ {
+ if (!g_ascii_isxdigit (*end))
+ break;
+ }
+ gtk_json_parser_syntax_error_at (self, self->reader, end, "Invalid Unicode escape sequence");
+ return FALSE;
+ }
+ else
+ {
+ gsize escape_size = 6;
+ gunichar unichar = (g_ascii_xdigit_value (self->reader[2]) << 12) |
+ (g_ascii_xdigit_value (self->reader[3]) << 8) |
+ (g_ascii_xdigit_value (self->reader[4]) << 4) |
+ (g_ascii_xdigit_value (self->reader[5]));
+
+ /* resolve UTF-16 surrogates for Unicode characters not in the BMP,
+ * as per ECMA 404, § 9, "String"
+ */
+ if (g_unichar_type (unichar) == G_UNICODE_SURROGATE)
+ {
+ if (gtk_json_parser_remaining (self) >= 12 &&
+ self->reader[6] == '\\' &&
+ self->reader[7] == 'u' &&
+ g_ascii_isxdigit (self->reader[8]) &&
+ g_ascii_isxdigit (self->reader[9]) &&
+ g_ascii_isxdigit (self->reader[10]) &&
+ g_ascii_isxdigit (self->reader[11]))
+ {
+ unichar = decode_utf16_surrogate_pair (unichar,
+ (g_ascii_xdigit_value (self->reader[8]) << 12) |
+ (g_ascii_xdigit_value (self->reader[9]) << 8) |
+ (g_ascii_xdigit_value (self->reader[10]) << 4) |
+ (g_ascii_xdigit_value (self->reader[11])));
+ escape_size += 6;
+ }
+ else
+ {
+ unichar = 0;
+ }
+
+ if (unichar == 0)
+ {
+ gtk_json_parser_syntax_error_at (self, self->reader, self->reader + escape_size, "Invalid UTF-16 surrogate pair");
+ return FALSE;
+ }
+
+ self->reader += escape_size - 2;
+ }
+ }
+ break;
+ default:
+ if (g_utf8_get_char_validated ((const char *) self->reader + 1, self->end - self->reader - 1) < (gunichar) -2)
+ gtk_json_parser_syntax_error_at (self, self->reader, (const guchar *) g_utf8_next_char (self->reader + 1), "Unknown escape sequence");
+ else
+ gtk_json_parser_syntax_error_at (self, self->reader, self->reader + 1, "Unknown escape sequence");
+ return FALSE;
+ }
+ self->reader += 2;
+ }
+
+ self->reader = json_skip_characters (self->reader, self->end, STRING_ELEMENT);
+ }
+
+end:
+ gtk_json_parser_syntax_error_at (self, start, self->reader, "Unterminated string literal");
+ return FALSE;
+}
+
+static gboolean
+gtk_json_parser_parse_number (GtkJsonParser *self)
+{
+ const guchar *start = self->reader;
+ gboolean have_sign;
+
+ /* sign */
+ have_sign = gtk_json_parser_try_char (self, '-');
+
+ /* integer part */
+ if (gtk_json_parser_try_char (self, '0'))
+ {
+ /* Technically, "01" in the JSON grammar would be 2 numbers:
+ * "0" followed by "1".
+ * Practically, nobody understands that it's 2 numbers, so we
+ * special-purpose an error message for it, because 2 numbers
+ * can never follow each other.
+ */
+ if (!gtk_json_parser_is_eof (self) &&
+ g_ascii_isdigit (*self->reader))
+ {
+ do
+ {
+ self->reader++;
+ }
+ while (!gtk_json_parser_is_eof (self) &&
+ g_ascii_isdigit (*self->reader));
+
+ gtk_json_parser_syntax_error_at (self, start, self->reader, "Numbers may not start with leading 0s");
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (gtk_json_parser_is_eof (self) ||
+ !g_ascii_isdigit (*self->reader))
+ {
+ if (have_sign)
+ gtk_json_parser_syntax_error_at (self, start, self->reader, "Expected a number after '-' character");
+ else
+ gtk_json_parser_type_error (self, "Not a number");
+ return FALSE;
+ }
+
+ self->reader++;
+
+ while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader))
+ self->reader++;
+ }
+
+ /* fractional part */
+ if (gtk_json_parser_try_char (self, '.'))
+ {
+ if (!g_ascii_isdigit (*self->reader))
+ {
+ gtk_json_parser_syntax_error_at (self, start, self->reader, "Expected a digit after '.'");
+ return FALSE;
+ }
+
+ do
+ {
+ self->reader++;
+ }
+ while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader));
+ }
+
+ /* exponent */
+ if (gtk_json_parser_try_char (self, 'e') ||
+ gtk_json_parser_try_char (self, 'E'))
+ {
+ if (!gtk_json_parser_try_char (self, '-'))
+ gtk_json_parser_try_char (self, '+');
+
+ if (!g_ascii_isdigit (*self->reader))
+ {
+ gtk_json_parser_syntax_error_at (self, start, self->reader, "Expected a digit in exponent");
+ return FALSE;
+ }
+
+ do
+ {
+ self->reader++;
+ }
+ while (!gtk_json_parser_is_eof (self) && g_ascii_isdigit (*self->reader));
+ }
+ return TRUE;
+}
+
+static gboolean
+gtk_json_parser_parse_value (GtkJsonParser *self)
+{
+ if (gtk_json_parser_is_eof (self))
+ {
+ gtk_json_parser_syntax_error (self, "Unexpected end of document");
+ return FALSE;
+ }
+
+ switch (json_character_table[*self->block->value] & JSON_CHARACTER_NODE_MASK)
+ {
+ case GTK_JSON_STRING:
+ return gtk_json_parser_parse_string (self);
+
+ case GTK_JSON_NUMBER:
+ return gtk_json_parser_parse_number (self);
+
+ case GTK_JSON_NULL:
+ if (gtk_json_parser_try_identifier (self, "null"))
+ return TRUE;
+ break;
+
+ case GTK_JSON_BOOLEAN:
+ if (gtk_json_parser_try_identifier (self, "true") ||
+ gtk_json_parser_try_identifier (self, "false"))
+ return TRUE;
+ break;
+
+ case GTK_JSON_OBJECT:
+ case GTK_JSON_ARRAY:
+ /* don't preparse objects */
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ if (gtk_json_parser_remaining (self) >= 2 &&
+ (self->block->value[0] == '.' || self->block->value[0] == '+') &&
+ g_ascii_isdigit (self->block->value[1]))
+ {
+ const guchar *end = self->block->value + 2;
+ while (end < self->end && g_ascii_isalnum (*end))
+ end++;
+ gtk_json_parser_syntax_error_at (self, self->block->value, end, "Numbers may not start with '%c'", *self->block->value);
+ }
+ else if (*self->reader == 0)
+ gtk_json_parser_syntax_error (self, "Unexpected nul byte in document");
+ else
+ gtk_json_parser_syntax_error (self, "Expected a value");
+ return FALSE;
+}
+
+static void
+gtk_json_parser_push_block (GtkJsonParser *self,
+ GtkJsonBlockType type)
+{
+ self->block++;
+ if (self->block == self->blocks_end)
+ {
+ gsize old_size = self->blocks_end - self->blocks;
+ gsize new_size = old_size + 128;
+
+ if (self->blocks == self->blocks_preallocated)
+ {
+ self->blocks = g_new (GtkJsonBlock, new_size);
+ memcpy (self->blocks, self->blocks_preallocated, sizeof (GtkJsonBlock) * G_N_ELEMENTS (self->blocks_preallocated));
+ }
+ else
+ {
+ self->blocks = g_renew (GtkJsonBlock, self->blocks, new_size);
+ }
+ self->blocks_end = self->blocks + new_size;
+ self->block = self->blocks + old_size;
+ }
+
+ self->block->type = type;
+ self->block->member_name = 0;
+ self->block->value = 0;
+ self->block->index = 0;
+}
+
+static void
+gtk_json_parser_pop_block (GtkJsonParser *self)
+{
+ g_assert (self->block > self->blocks);
+ self->block--;
+}
+
+GtkJsonParser *
+gtk_json_parser_new_for_string (const char *string,
+ gssize size)
+{
+ GtkJsonParser *self;
+ GBytes *bytes;
+
+ bytes = g_bytes_new (string, size >= 0 ? size : strlen (string));
+
+ self = gtk_json_parser_new_for_bytes (bytes);
+
+ g_bytes_unref (bytes);
+
+ return self;
+}
+
+GtkJsonParser *
+gtk_json_parser_new_for_bytes (GBytes *bytes)
+{
+ GtkJsonParser *self;
+ gsize size;
+
+ g_return_val_if_fail (bytes != NULL, NULL);
+
+ self = g_slice_new0 (GtkJsonParser);
+
+ self->bytes = g_bytes_ref (bytes);
+ self->reader = g_bytes_get_data (bytes, &size);
+ self->end = self->reader + size;
+
+ self->blocks = self->blocks_preallocated;
+ self->blocks_end = self->blocks + G_N_ELEMENTS (self->blocks_preallocated);
+ self->block = self->blocks;
+ self->block->type = GTK_JSON_BLOCK_TOPLEVEL;
+
+ gtk_json_parser_skip_bom (self);
+ self->start = self->reader;
+ gtk_json_parser_rewind (self);
+
+ return self;
+}
+
+void
+gtk_json_parser_free (GtkJsonParser *self)
+{
+ if (self == NULL)
+ return;
+
+ g_bytes_unref (self->bytes);
+
+ if (self->blocks != self->blocks_preallocated)
+ g_free (self->blocks);
+
+ if (self->error)
+ g_error_free (self->error);
+
+ g_slice_free (GtkJsonParser, self);
+}
+
+static gboolean
+gtk_json_parser_skip_block (GtkJsonParser *self)
+{
+ gsize depth;
+
+ if (self->reader != self->block->value)
+ return TRUE;
+
+ depth = gtk_json_parser_get_depth (self);
+ while (TRUE)
+ {
+ if (*self->reader == '{')
+ {
+ if (!gtk_json_parser_start_object (self))
+ return FALSE;
+ }
+ else if (*self->reader == '[')
+ {
+ if (!gtk_json_parser_start_array (self))
+ return FALSE;
+ }
+
+ while (self->reader != self->block->value)
+ {
+ /* This should never be reentrant to this function or we might
+ * loop causing stack overflow */
+ if (!gtk_json_parser_next (self))
+ {
+ if (!gtk_json_parser_end (self))
+ return FALSE;
+ if (depth >= gtk_json_parser_get_depth (self))
+ return TRUE;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+gtk_json_parser_next (GtkJsonParser *self)
+{
+ if (self->error)
+ return FALSE;
+
+ if (self->block->value == NULL)
+ return FALSE;
+
+ if (!gtk_json_parser_skip_block (self))
+ {
+ g_assert (self->error);
+ return FALSE;
+ }
+
+ switch (self->block->type)
+ {
+ case GTK_JSON_BLOCK_TOPLEVEL:
+ gtk_json_parser_skip_whitespace (self);
+ if (gtk_json_parser_is_eof (self))
+ {
+ self->block->value = NULL;
+ }
+ else if (*self->reader == 0)
+ {
+ gtk_json_parser_syntax_error (self, "Unexpected nul byte in document");
+ }
+ else
+ {
+ gtk_json_parser_syntax_error_at (self, self->reader, self->end, "Data at end of document");
+ }
+ return FALSE;
+
+ case GTK_JSON_BLOCK_OBJECT:
+ gtk_json_parser_skip_whitespace (self);
+ if (gtk_json_parser_is_eof (self))
+ {
+ gtk_json_parser_syntax_error_at (self,
+ self->block[-1].value,
+ self->reader,
+ "Unterminated object");
+ self->block->member_name = NULL;
+ self->block->value = NULL;
+ }
+ if (gtk_json_parser_has_char (self, '}'))
+ {
+ self->block->member_name = NULL;
+ self->block->value = NULL;
+ return FALSE;
+ }
+ if (!gtk_json_parser_try_char (self, ','))
+ {
+ gtk_json_parser_syntax_error (self, "Expected a ',' to separate object members");
+ return FALSE;
+ }
+ gtk_json_parser_skip_whitespace (self);
+ if (!gtk_json_parser_has_char (self, '"'))
+ {
+ gtk_json_parser_syntax_error (self, "Expected a string for object member name");
+ return FALSE;
+ }
+ self->block->member_name = self->reader;
+
+ if (!gtk_json_parser_parse_string (self))
+ return FALSE;
+ gtk_json_parser_skip_whitespace (self);
+ if (!gtk_json_parser_try_char (self, ':'))
+ {
+ gtk_json_parser_syntax_error (self, "Missing ':' after member name");
+ return FALSE;
+ }
+
+ gtk_json_parser_skip_whitespace (self);
+ self->block->value = self->reader;
+ if (!gtk_json_parser_parse_value (self))
+ return FALSE;
+ break;
+
+ case GTK_JSON_BLOCK_ARRAY:
+ gtk_json_parser_skip_whitespace (self);
+ if (gtk_json_parser_is_eof (self))
+ {
+ gtk_json_parser_syntax_error_at (self,
+ self->block[-1].value,
+ self->reader,
+ "Unterminated array");
+ self->block->member_name = NULL;
+ self->block->value = NULL;
+ }
+ if (gtk_json_parser_has_char (self, ']'))
+ {
+ self->block->value = NULL;
+ return FALSE;
+ }
+
+ if (!gtk_json_parser_try_char (self, ','))
+ {
+ gtk_json_parser_syntax_error (self, "Expected a ',' to separate array members");
+ return FALSE;
+ }
+
+ gtk_json_parser_skip_whitespace (self);
+ self->block->value = self->reader;
+ if (!gtk_json_parser_parse_value (self))
+ return FALSE;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return TRUE;
+}
+
+void
+gtk_json_parser_rewind (GtkJsonParser *self)
+{
+ if (self->error)
+ return;
+
+ switch (self->block->type)
+ {
+ case GTK_JSON_BLOCK_OBJECT:
+ gtk_json_parser_pop_block (self);
+ self->reader = self->block->value;
+ gtk_json_parser_start_object (self);
+ break;
+
+ case GTK_JSON_BLOCK_ARRAY:
+ gtk_json_parser_pop_block (self);
+ self->reader = self->block->value;
+ gtk_json_parser_start_array (self);
+ break;
+
+ case GTK_JSON_BLOCK_TOPLEVEL:
+ self->reader = self->start;
+ gtk_json_parser_skip_whitespace (self);
+ if (gtk_json_parser_is_eof (self))
+ {
+ gtk_json_parser_syntax_error_at (self, self->start, self->reader, "Empty document");
+ }
+ else
+ {
+ self->block->value = self->reader;
+ gtk_json_parser_parse_value (self);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ return;
+ }
+}
+
+gsize
+gtk_json_parser_get_depth (GtkJsonParser *self)
+{
+ return self->block - self->blocks;
+}
+
+GtkJsonNode
+gtk_json_parser_get_node (GtkJsonParser *self)
+{
+ if (self->error)
+ return GTK_JSON_NONE;
+
+ if (self->block->value == NULL)
+ return GTK_JSON_NONE;
+
+ return (json_character_table[*self->block->value] & JSON_CHARACTER_NODE_MASK);
+}
+
+const GError *
+gtk_json_parser_get_error (GtkJsonParser *self)
+{
+ return self->error;
+}
+
+void
+gtk_json_parser_get_error_offset (GtkJsonParser *self,
+ gsize *start,
+ gsize *end)
+{
+ const guchar *data;
+
+ if (self->error == NULL)
+ {
+ if (start)
+ *start = 0;
+ if (end)
+ *end = 0;
+ return;
+ }
+
+ data = g_bytes_get_data (self->bytes, NULL);
+ if (start)
+ *start = self->error_start - data;
+ if (end)
+ *end = self->error_end - data;
+}
+
+void
+gtk_json_parser_get_error_location (GtkJsonParser *self,
+ gsize *start_line,
+ gsize *start_line_bytes,
+ gsize *end_line,
+ gsize *end_line_bytes)
+{
+ const guchar *s, *line_start;
+ gsize lines;
+
+ if (self->error == NULL)
+ {
+ if (start_line)
+ *start_line = 0;
+ if (start_line_bytes)
+ *start_line_bytes = 0;
+ if (end_line)
+ *end_line = 0;
+ if (end_line_bytes)
+ *end_line_bytes = 0;
+ return;
+ }
+
+ line_start = self->start;
+ lines = 0;
+
+ for (s = json_skip_characters_until (line_start, self->error_start, NEWLINE);
+ s < self->error_start;
+ s = json_skip_characters_until (line_start, self->error_start, NEWLINE))
+ {
+ if (s[0] == '\r' && s + 1 < self->error_start && s[1] == '\n')
+ s++;
+ lines++;
+ line_start = s + 1;
+ }
+
+ if (start_line)
+ *start_line = lines;
+ if (start_line_bytes)
+ *start_line_bytes = s - line_start;
+
+ if (end_line == NULL && end_line_bytes == NULL)
+ return;
+
+ for (s = json_skip_characters_until (s, self->error_end, NEWLINE);
+ s < self->error_end;
+ s = json_skip_characters_until (line_start, self->error_end, NEWLINE))
+ {
+ if (s[0] == '\r' && s + 1 < self->error_start && s[1] == '\n')
+ s++;
+ lines++;
+ line_start = s + 1;
+ }
+
+ if (end_line)
+ *end_line = lines;
+ if (end_line_bytes)
+ *end_line_bytes = s - line_start;
+}
+
+static gboolean
+gtk_json_parser_supports_member (GtkJsonParser *self)
+{
+ if (self->error)
+ return FALSE;
+
+ if (self->block->type != GTK_JSON_BLOCK_OBJECT)
+ return FALSE;
+
+ if (self->block->member_name == NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+char *
+gtk_json_parser_get_member_name (GtkJsonParser *self)
+{
+ if (!gtk_json_parser_supports_member (self))
+ return NULL;
+
+ return gtk_json_unescape_string (self->block->member_name);
+}
+
+gboolean
+gtk_json_parser_has_member (GtkJsonParser *self,
+ const char *name)
+{
+ JsonStringIter iter;
+ gsize found, len;
+
+ if (!gtk_json_parser_supports_member (self))
+ return FALSE;
+
+ found = 0;
+
+ for (len = json_string_iter_init (&iter, self->block->member_name);
+ len > 0;
+ len = json_string_iter_next (&iter))
+ {
+ const char *s = json_string_iter_get (&iter);
+
+ if (strncmp (name + found, s, len) != 0)
+ return FALSE;
+
+ found += len;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gtk_json_parser_find_member (GtkJsonParser *self,
+ const char *name)
+{
+ if (!gtk_json_parser_supports_member (self))
+ {
+ while (gtk_json_parser_next (self));
+ return FALSE;
+ }
+
+ gtk_json_parser_rewind (self);
+
+ do
+ {
+ if (gtk_json_parser_has_member (self, name))
+ return TRUE;
+ }
+ while (gtk_json_parser_next (self));
+
+ return FALSE;
+}
+
+static gssize
+json_string_iter_run_select (const guchar *string_data,
+ const char * const *options)
+{
+ JsonStringIter iter;
+ gssize i, j;
+ gsize found, len;
+
+ if (options == NULL || options[0] == NULL)
+ return -1;
+
+ found = 0;
+ i = 0;
+
+ for (len = json_string_iter_init (&iter, string_data);
+ len > 0;
+ len = json_string_iter_next (&iter))
+ {
+ const char *s = json_string_iter_get (&iter);
+
+ if (strncmp (options[i] + found, s, len) != 0)
+ {
+ for (j = i + 1; options[j]; j++)
+ {
+ if (strncmp (options[j], options[i], found) == 0 &&
+ strncmp (options[j] + found, s, len) == 0)
+ {
+ i = j;
+ break;
+ }
+ }
+ if (j != i)
+ return -1;
+ }
+ found += len;
+ }
+
+ if (options[i][found] == 0)
+ return i;
+
+ for (j = i + 1; options[j]; i++)
+ {
+ if (strncmp (options[j], options[i], found) != 0)
+ continue;
+ if (options[j][found] == 0)
+ return j;
+ }
+
+ return -1;
+}
+
+gssize
+gtk_json_parser_select_member (GtkJsonParser *self,
+ const char * const *options)
+{
+ if (!gtk_json_parser_supports_member (self))
+ return -1;
+
+ return json_string_iter_run_select (self->block->member_name, options);
+}
+
+gboolean
+gtk_json_parser_get_boolean (GtkJsonParser *self)
+{
+ if (self->error)
+ return FALSE;
+
+ if (self->block->value == NULL)
+ return FALSE;
+
+ if (*self->block->value == 't')
+ return TRUE;
+ else if (*self->block->value == 'f')
+ return FALSE;
+
+ gtk_json_parser_type_error (self, "Expected a boolean value");
+ return FALSE;
+}
+
+double
+gtk_json_parser_get_number (GtkJsonParser *self)
+{
+ double result;
+
+ if (self->error)
+ return 0;
+
+ if (self->block->value == NULL)
+ return 0;
+
+ if (!strchr ("-0123456789", *self->block->value))
+ {
+ gtk_json_parser_type_error (self, "Expected a number");
+ return 0;
+ }
+
+ errno = 0;
+ result = g_ascii_strtod ((const char *) self->block->value, NULL);
+
+ if (errno)
+ {
+ if (errno == ERANGE)
+ gtk_json_parser_value_error (self, "Number out of range");
+ else
+ gtk_json_parser_value_error (self, "%s", g_strerror (errno));
+
+ return 0;
+ }
+
+ return result;
+}
+
+int
+gtk_json_parser_get_int (GtkJsonParser *self)
+{
+ long result;
+ char *end;
+
+ if (self->error)
+ return 0;
+
+ if (self->block->value == NULL)
+ return 0;
+
+ if (!strchr ("-0123456789", *self->block->value))
+ {
+ gtk_json_parser_type_error (self, "Expected an intereger");
+ return 0;
+ }
+
+ errno = 0;
+ result = strtol ((const char *) self->block->value, &end, 10);
+ if (*end == '.' || *end == 'e' || *end == 'E')
+ {
+ gtk_json_parser_type_error (self, "Expected an intereger");
+ return 0;
+ }
+
+ if (errno)
+ {
+ if (errno == ERANGE)
+ gtk_json_parser_value_error (self, "Number out of integer range");
+ else
+ gtk_json_parser_value_error (self, "%s", g_strerror (errno));
+
+ return 0;
+ }
+ else if (result > G_MAXINT || result < G_MININT)
+ {
+ gtk_json_parser_value_error (self, "Number out of integer range");
+ return 0;
+ }
+
+ return result;
+}
+
+guint
+gtk_json_parser_get_uint (GtkJsonParser *self)
+{
+ gulong result;
+ char *end;
+
+ if (self->error)
+ return 0;
+
+ if (self->block->value == NULL)
+ return 0;
+
+ if (!strchr ("0123456789", *self->block->value))
+ {
+ gtk_json_parser_type_error (self, "Expected an unsigned intereger");
+ return 0;
+ }
+
+ errno = 0;
+ result = strtoul ((const char *) self->block->value, &end, 10);
+ if (*end == '.' || *end == 'e' || *end == 'E')
+ {
+ gtk_json_parser_type_error (self, "Expected an unsigned intereger");
+ return 0;
+ }
+
+ if (errno)
+ {
+ if (errno == ERANGE)
+ gtk_json_parser_value_error (self, "Number out of unsignedinteger range");
+ else
+ gtk_json_parser_value_error (self, "%s", g_strerror (errno));
+
+ return 0;
+ }
+ else if (result > G_MAXUINT)
+ {
+ gtk_json_parser_value_error (self, "Number out of unsigned integer range");
+ return 0;
+ }
+
+ return result;
+}
+
+char *
+gtk_json_parser_get_string (GtkJsonParser *self)
+{
+ if (self->error)
+ return g_strdup ("");
+
+ if (self->block->value == NULL)
+ return g_strdup ("");
+
+ if (*self->block->value != '"')
+ {
+ gtk_json_parser_type_error (self, "Expected a string");
+ return g_strdup ("");
+ }
+
+ return gtk_json_unescape_string (self->block->value);
+}
+
+gssize
+gtk_json_parser_select_string (GtkJsonParser *self,
+ const char * const *options)
+{
+ if (self->error)
+ return -1;
+
+ if (self->block->value == NULL)
+ return -1;
+
+ if (*self->block->value != '"')
+ {
+ gtk_json_parser_type_error (self, "Expected a string");
+ return -1;
+ }
+
+ return json_string_iter_run_select (self->block->value, options);
+}
+
+gboolean
+gtk_json_parser_start_object (GtkJsonParser *self)
+{
+ if (self->error)
+ return FALSE;
+
+ if (!gtk_json_parser_try_char (self, '{'))
+ {
+ gtk_json_parser_type_error (self, "Expected an object");
+ return FALSE;
+ }
+
+ gtk_json_parser_push_block (self, GTK_JSON_BLOCK_OBJECT);
+
+ gtk_json_parser_skip_whitespace (self);
+ if (gtk_json_parser_is_eof (self))
+ {
+ gtk_json_parser_syntax_error_at (self,
+ self->block[-1].value,
+ self->reader,
+ "Unterminated object");
+ return FALSE;
+ }
+ if (gtk_json_parser_has_char (self, '}'))
+ return TRUE;
+
+ if (!gtk_json_parser_has_char (self, '"'))
+ {
+ gtk_json_parser_syntax_error (self, "Expected a string for object member name");
+ return FALSE;
+ }
+ self->block->member_name = self->reader;
+
+ if (!gtk_json_parser_parse_string (self))
+ return FALSE;
+ gtk_json_parser_skip_whitespace (self);
+ if (!gtk_json_parser_try_char (self, ':'))
+ {
+ gtk_json_parser_syntax_error (self, "Missing ':' after member name");
+ return FALSE;
+ }
+
+ gtk_json_parser_skip_whitespace (self);
+ self->block->value = self->reader;
+ if (!gtk_json_parser_parse_value (self))
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+gtk_json_parser_start_array (GtkJsonParser *self)
+{
+ if (self->error)
+ return FALSE;
+
+ if (!gtk_json_parser_try_char (self, '['))
+ {
+ gtk_json_parser_type_error (self, "Expected an array");
+ return FALSE;
+ }
+
+ gtk_json_parser_push_block (self, GTK_JSON_BLOCK_ARRAY);
+ gtk_json_parser_skip_whitespace (self);
+ if (gtk_json_parser_is_eof (self))
+ {
+ gtk_json_parser_syntax_error_at (self,
+ self->block[-1].value,
+ self->reader,
+ "Unterminated array");
+ return FALSE;
+ }
+ if (gtk_json_parser_has_char (self, ']'))
+ {
+ self->block->value = NULL;
+ return TRUE;
+ }
+ self->block->value = self->reader;
+ if (!gtk_json_parser_parse_value (self))
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+gtk_json_parser_end (GtkJsonParser *self)
+{
+ char bracket;
+
+ g_return_val_if_fail (self != NULL, FALSE);
+
+ while (gtk_json_parser_next (self));
+
+ if (self->error)
+ return FALSE;
+
+ switch (self->block->type)
+ {
+ case GTK_JSON_BLOCK_OBJECT:
+ bracket = '}';
+ break;
+ case GTK_JSON_BLOCK_ARRAY:
+ bracket = ']';
+ break;
+ case GTK_JSON_BLOCK_TOPLEVEL:
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+
+ if (!gtk_json_parser_try_char (self, bracket))
+ {
+ gtk_json_parser_syntax_error (self, "No terminating '%c'", bracket);
+ return FALSE;
+ }
+
+ gtk_json_parser_pop_block (self);
+
+ return TRUE;
+}
+
diff --git a/pango2/json/gtkjsonparserprivate.h b/pango2/json/gtkjsonparserprivate.h
new file mode 100644
index 00000000..83f72374
--- /dev/null
+++ b/pango2/json/gtkjsonparserprivate.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2021 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+
+#ifndef __GTK_JSON_PARSER_H__
+#define __GTK_JSON_PARSER_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GTK_JSON_NONE,
+ GTK_JSON_NULL,
+ GTK_JSON_BOOLEAN,
+ GTK_JSON_NUMBER,
+ GTK_JSON_STRING,
+ GTK_JSON_OBJECT,
+ GTK_JSON_ARRAY
+} GtkJsonNode;
+
+typedef enum {
+ GTK_JSON_ERROR_FAILED,
+ GTK_JSON_ERROR_SYNTAX,
+ GTK_JSON_ERROR_TYPE,
+ GTK_JSON_ERROR_VALUE,
+ GTK_JSON_ERROR_SCHEMA,
+} GtkJsonError;
+
+typedef struct _GtkJsonParser GtkJsonParser;
+
+#define GTK_JSON_ERROR (gtk_json_error_quark ())
+GQuark gtk_json_error_quark (void);
+
+GtkJsonParser * gtk_json_parser_new_for_bytes (GBytes *bytes);
+GtkJsonParser * gtk_json_parser_new_for_string (const char *string,
+ gssize size);
+
+void gtk_json_parser_free (GtkJsonParser *self);
+
+gboolean gtk_json_parser_next (GtkJsonParser *self);
+void gtk_json_parser_rewind (GtkJsonParser *self);
+gsize gtk_json_parser_get_depth (GtkJsonParser *self);
+GtkJsonNode gtk_json_parser_get_node (GtkJsonParser *self);
+char * gtk_json_parser_get_member_name (GtkJsonParser *self);
+gboolean gtk_json_parser_has_member (GtkJsonParser *self,
+ const char *name);
+gboolean gtk_json_parser_find_member (GtkJsonParser *self,
+ const char *name);
+gssize gtk_json_parser_select_member (GtkJsonParser *self,
+ const char * const *options);
+
+gboolean gtk_json_parser_get_boolean (GtkJsonParser *self);
+double gtk_json_parser_get_number (GtkJsonParser *self);
+int gtk_json_parser_get_int (GtkJsonParser *self);
+guint gtk_json_parser_get_uint (GtkJsonParser *self);
+char * gtk_json_parser_get_string (GtkJsonParser *self);
+gssize gtk_json_parser_select_string (GtkJsonParser *self,
+ const char * const *options);
+
+gboolean gtk_json_parser_start_object (GtkJsonParser *self);
+gboolean gtk_json_parser_start_array (GtkJsonParser *self);
+gboolean gtk_json_parser_end (GtkJsonParser *self);
+
+const GError * gtk_json_parser_get_error (GtkJsonParser *self) G_GNUC_PURE;
+void gtk_json_parser_get_error_offset (GtkJsonParser *self,
+ gsize *start,
+ gsize *end);
+void gtk_json_parser_get_error_location (GtkJsonParser *self,
+ gsize *start_line,
+ gsize *start_line_bytes,
+ gsize *end_line,
+ gsize *end_line_bytes);
+void gtk_json_parser_value_error (GtkJsonParser *self,
+ const char *format,
+ ...) G_GNUC_PRINTF(2, 3);
+void gtk_json_parser_schema_error (GtkJsonParser *self,
+ const char *format,
+ ...) G_GNUC_PRINTF(2, 3);
+
+G_END_DECLS
+
+#endif /* __GTK_JSON_PARSER_H__ */
diff --git a/pango2/json/gtkjsonprinter.c b/pango2/json/gtkjsonprinter.c
new file mode 100644
index 00000000..e9ca03a3
--- /dev/null
+++ b/pango2/json/gtkjsonprinter.c
@@ -0,0 +1,405 @@
+/*
+ * Copyright © 2021 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+
+#include "config.h"
+
+#include "gtkjsonprinterprivate.h"
+
+typedef struct _GtkJsonBlock GtkJsonBlock;
+
+typedef enum {
+ GTK_JSON_BLOCK_TOPLEVEL,
+ GTK_JSON_BLOCK_OBJECT,
+ GTK_JSON_BLOCK_ARRAY,
+} GtkJsonBlockType;
+
+struct _GtkJsonBlock
+{
+ GtkJsonBlockType type;
+ gsize n_elements; /* number of elements already written */
+};
+
+struct _GtkJsonPrinter
+{
+ GtkJsonPrinterFlags flags;
+ char *indentation;
+
+ GtkJsonPrinterWriteFunc write_func;
+ gpointer user_data;
+ GDestroyNotify user_destroy;
+
+ GtkJsonBlock *block; /* current block */
+ GtkJsonBlock *blocks; /* blocks array */
+ GtkJsonBlock *blocks_end; /* blocks array */
+ GtkJsonBlock blocks_preallocated[128]; /* preallocated */
+};
+
+static void
+gtk_json_printer_push_block (GtkJsonPrinter *self,
+ GtkJsonBlockType type)
+{
+ self->block++;
+ if (self->block == self->blocks_end)
+ {
+ gsize old_size = self->blocks_end - self->blocks;
+ gsize new_size = old_size + 128;
+
+ if (self->blocks == self->blocks_preallocated)
+ {
+ self->blocks = g_new (GtkJsonBlock, new_size);
+ memcpy (self->blocks, self->blocks_preallocated, sizeof (GtkJsonBlock) * G_N_ELEMENTS (self->blocks_preallocated));
+ }
+ else
+ {
+ self->blocks = g_renew (GtkJsonBlock, self->blocks, new_size);
+ }
+ self->blocks_end = self->blocks + new_size;
+ self->block = self->blocks + old_size;
+ }
+
+ self->block->type = type;
+ self->block->n_elements = 0;
+}
+
+static void
+gtk_json_printer_pop_block (GtkJsonPrinter *self)
+{
+ g_assert (self->block > self->blocks);
+ self->block--;
+}
+
+GtkJsonPrinter *
+gtk_json_printer_new (GtkJsonPrinterWriteFunc write_func,
+ gpointer data,
+ GDestroyNotify destroy)
+{
+ GtkJsonPrinter *self;
+
+ g_return_val_if_fail (write_func, NULL);
+
+ self = g_slice_new0 (GtkJsonPrinter);
+
+ self->flags = 0;
+ self->indentation = g_strdup (" ");
+
+ self->write_func = write_func;
+ self->user_data = data;
+ self->user_destroy = destroy;
+
+ self->blocks = self->blocks_preallocated;
+ self->blocks_end = self->blocks + G_N_ELEMENTS (self->blocks_preallocated);
+ self->block = self->blocks;
+ self->block->type = GTK_JSON_BLOCK_TOPLEVEL;
+
+ return self;
+}
+
+void
+gtk_json_printer_free (GtkJsonPrinter *self)
+{
+ g_return_if_fail (self != NULL);
+
+ g_free (self->indentation);
+
+ if (self->user_destroy)
+ self->user_destroy (self->user_data);
+
+ if (self->blocks != self->blocks_preallocated)
+ g_free (self->blocks);
+
+ g_slice_free (GtkJsonPrinter, self);
+}
+
+static gboolean
+gtk_json_printer_has_flag (GtkJsonPrinter *self,
+ GtkJsonPrinterFlags flag)
+{
+ return (self->flags & flag) ? TRUE : FALSE;
+}
+
+gsize
+gtk_json_printer_get_depth (GtkJsonPrinter *self)
+{
+ return self->block - self->blocks;
+}
+
+gsize
+gtk_json_printer_get_n_elements (GtkJsonPrinter *self)
+{
+ return self->block->n_elements;
+}
+
+void
+gtk_json_printer_set_flags (GtkJsonPrinter *self,
+ GtkJsonPrinterFlags flags)
+{
+ g_return_if_fail (self != NULL);
+
+ self->flags = flags;
+}
+
+GtkJsonPrinterFlags
+gtk_json_printer_get_flags (GtkJsonPrinter *self)
+{
+ g_return_val_if_fail (self != NULL, 0);
+
+ return self->flags;
+}
+
+void
+gtk_json_printer_set_indentation (GtkJsonPrinter *self,
+ gsize amount)
+{
+ g_return_if_fail (self != NULL);
+
+ g_free (self->indentation);
+
+ self->indentation = g_malloc (amount + 1);
+ memset (self->indentation, ' ', amount);
+ self->indentation[amount] = 0;
+}
+
+gsize
+gtk_json_printer_get_indentation (GtkJsonPrinter *self)
+{
+ g_return_val_if_fail (self != NULL, 2);
+
+ return strlen (self->indentation);
+}
+
+static void
+gtk_json_printer_write (GtkJsonPrinter *self,
+ const char *s)
+{
+ self->write_func (self, s, self->user_data);
+}
+
+static char *
+gtk_json_printer_escape_string (GtkJsonPrinter *self,
+ const char *str)
+{
+ GString *string;
+
+ string = g_string_new (NULL);
+ string = g_string_append_c (string, '"');
+
+ for (; *str != '\0'; str = g_utf8_next_char (str))
+ {
+ switch (*str)
+ {
+ case '"':
+ g_string_append (string, "\\\"");
+ break;
+ case '\\':
+ g_string_append (string, "\\\\");
+ break;
+ case '\b':
+ g_string_append (string, "\\b");
+ break;
+ case '\f':
+ g_string_append (string, "\\f");
+ break;
+ case '\n':
+ g_string_append (string, "\\n");
+ break;
+ case '\r':
+ g_string_append (string, "\\r");
+ break;
+ case '\t':
+ g_string_append (string, "\\t");
+ break;
+ default:
+ if ((int) *str < 0x20 || (int) *str >= 0x80)
+ {
+ if ((guint) *str < 0x20 || gtk_json_printer_has_flag (self, GTK_JSON_PRINTER_ASCII))
+ g_string_append_printf (string, "\\u%04x", g_utf8_get_char (str));
+ else
+ g_string_append_unichar (string, g_utf8_get_char (str));
+ }
+ else
+ g_string_append_c (string, *str);
+ }
+ }
+
+ string = g_string_append_c (string, '"');
+ return g_string_free (string, FALSE);
+}
+
+static void
+gtk_json_printer_newline (GtkJsonPrinter *self)
+{
+ gsize depth;
+
+ if (!gtk_json_printer_has_flag (self, GTK_JSON_PRINTER_PRETTY))
+ return;
+
+ gtk_json_printer_write (self, "\n");
+ for (depth = gtk_json_printer_get_depth (self); depth-->0;)
+ gtk_json_printer_write (self, self->indentation);
+}
+
+static void
+gtk_json_printer_begin_member (GtkJsonPrinter *self,
+ const char *name)
+{
+ if (gtk_json_printer_get_n_elements (self) > 0)
+ gtk_json_printer_write (self, ",");
+ if (self->block->type != GTK_JSON_BLOCK_TOPLEVEL || gtk_json_printer_get_n_elements (self) > 0)
+ gtk_json_printer_newline (self);
+
+ self->block->n_elements++;
+
+ if (name)
+ {
+ char *escaped = gtk_json_printer_escape_string (self, name);
+ gtk_json_printer_write (self, escaped);
+ g_free (escaped);
+ if (gtk_json_printer_has_flag (self, GTK_JSON_PRINTER_PRETTY))
+ gtk_json_printer_write (self, " : ");
+ else
+ gtk_json_printer_write (self, ":");
+ }
+}
+
+void
+gtk_json_printer_add_boolean (GtkJsonPrinter *self,
+ const char *name,
+ gboolean value)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+ gtk_json_printer_begin_member (self, name);
+ gtk_json_printer_write (self, value ? "true" : "false");
+}
+
+void
+gtk_json_printer_add_number (GtkJsonPrinter *self,
+ const char *name,
+ double value)
+{
+ char buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+ gtk_json_printer_begin_member (self, name);
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, value);
+ gtk_json_printer_write (self, buf);
+}
+
+void
+gtk_json_printer_add_integer (GtkJsonPrinter *self,
+ const char *name,
+ int value)
+{
+ char buf[128];
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+ gtk_json_printer_begin_member (self, name);
+ g_snprintf (buf, sizeof (buf), "%d", value);
+ gtk_json_printer_write (self, buf);
+}
+
+void
+gtk_json_printer_add_string (GtkJsonPrinter *self,
+ const char *name,
+ const char *s)
+{
+ char *escaped;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+ g_return_if_fail (s != NULL);
+
+ gtk_json_printer_begin_member (self, name);
+ escaped = gtk_json_printer_escape_string (self, s);
+ gtk_json_printer_write (self, escaped);
+ g_free (escaped);
+}
+
+void
+gtk_json_printer_add_null (GtkJsonPrinter *self,
+ const char *name)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+ gtk_json_printer_begin_member (self, name);
+ gtk_json_printer_write (self, "null");
+}
+
+void
+gtk_json_printer_start_object (GtkJsonPrinter *self,
+ const char *name)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+ gtk_json_printer_begin_member (self, name);
+ gtk_json_printer_write (self, "{");
+ gtk_json_printer_push_block (self, GTK_JSON_BLOCK_OBJECT);
+}
+
+void
+gtk_json_printer_start_array (GtkJsonPrinter *self,
+ const char *name)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail ((self->block->type == GTK_JSON_BLOCK_OBJECT) == (name != NULL));
+
+ gtk_json_printer_begin_member (self, name);
+ gtk_json_printer_write (self, "[");
+ gtk_json_printer_push_block (self, GTK_JSON_BLOCK_ARRAY);
+}
+
+void
+gtk_json_printer_end (GtkJsonPrinter *self)
+{
+ const char *bracket;
+ gboolean empty;
+
+ g_return_if_fail (self != NULL);
+
+ switch (self->block->type)
+ {
+ case GTK_JSON_BLOCK_OBJECT:
+ bracket = "}";
+ break;
+ case GTK_JSON_BLOCK_ARRAY:
+ bracket = "]";
+ break;
+ case GTK_JSON_BLOCK_TOPLEVEL:
+ default:
+ g_return_if_reached ();
+ }
+
+ empty = gtk_json_printer_get_n_elements (self) == 0;
+ gtk_json_printer_pop_block (self);
+
+ if (!empty)
+ {
+ gtk_json_printer_newline (self);
+ }
+ gtk_json_printer_write (self, bracket);
+}
+
diff --git a/pango2/json/gtkjsonprinterprivate.h b/pango2/json/gtkjsonprinterprivate.h
new file mode 100644
index 00000000..e25a1b1d
--- /dev/null
+++ b/pango2/json/gtkjsonprinterprivate.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright © 2021 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+
+#ifndef __GTK_JSON_PRINTER_H__
+#define __GTK_JSON_PRINTER_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkJsonPrinter GtkJsonPrinter;
+
+typedef enum {
+ GTK_JSON_PRINTER_PRETTY = (1 << 0),
+ GTK_JSON_PRINTER_ASCII = (1 << 1),
+} GtkJsonPrinterFlags;
+
+typedef void (* GtkJsonPrinterWriteFunc) (GtkJsonPrinter *printer,
+ const char *s,
+ gpointer user_data);
+
+
+GtkJsonPrinter * gtk_json_printer_new (GtkJsonPrinterWriteFunc write_func,
+ gpointer data,
+ GDestroyNotify destroy);
+void gtk_json_printer_free (GtkJsonPrinter *self);
+
+void gtk_json_printer_set_flags (GtkJsonPrinter *self,
+ GtkJsonPrinterFlags flags);
+GtkJsonPrinterFlags gtk_json_printer_get_flags (GtkJsonPrinter *self);
+void gtk_json_printer_set_indentation (GtkJsonPrinter *self,
+ gsize amount);
+gsize gtk_json_printer_get_indentation (GtkJsonPrinter *self);
+
+gsize gtk_json_printer_get_depth (GtkJsonPrinter *self);
+gsize gtk_json_printer_get_n_elements (GtkJsonPrinter *self);
+
+void gtk_json_printer_add_boolean (GtkJsonPrinter *self,
+ const char *name,
+ gboolean value);
+void gtk_json_printer_add_number (GtkJsonPrinter *self,
+ const char *name,
+ double value);
+void gtk_json_printer_add_integer (GtkJsonPrinter *self,
+ const char *name,
+ int value);
+void gtk_json_printer_add_string (GtkJsonPrinter *self,
+ const char *name,
+ const char *s);
+void gtk_json_printer_add_null (GtkJsonPrinter *self,
+ const char *name);
+
+void gtk_json_printer_start_object (GtkJsonPrinter *self,
+ const char *name);
+void gtk_json_printer_start_array (GtkJsonPrinter *self,
+ const char *name);
+void gtk_json_printer_end (GtkJsonPrinter *self);
+
+
+G_END_DECLS
+
+#endif /* __GTK_JSON_PRINTER_H__ */
diff --git a/pango2/meson.build b/pango2/meson.build
new file mode 100644
index 00000000..8dce43d8
--- /dev/null
+++ b/pango2/meson.build
@@ -0,0 +1,298 @@
+pango_sources = [
+ 'break.c',
+ 'ellipsize.c',
+ 'glyphstring.c',
+ 'itemize.c',
+ 'pango-attr.c',
+ 'pango-attr-list.c',
+ 'pango-attr-iterator.c',
+ 'pango-attributes.c',
+ 'pango-bidi.c',
+ 'pango-color.c',
+ 'pango-context.c',
+ 'pango-emoji.c',
+ 'pango-font.c',
+ 'pango-font-description.c',
+ 'pango-font-face.c',
+ 'pango-font-family.c',
+ 'pango-font-metrics.c',
+ 'pango-fontmap.c',
+ 'pango-fontset.c',
+ 'pango-fontset-cached.c',
+ 'pango-glyph-item.c',
+ 'pango-gravity.c',
+ 'pango-item.c',
+ 'pango-language.c',
+ 'pango-language-set.c',
+ 'pango-language-set-simple.c',
+ 'pango-layout.c',
+ 'pango-markup.c',
+ 'pango-matrix.c',
+ 'pango-renderer.c',
+ 'pango-script.c',
+ 'pango-tabs.c',
+ 'pango-trace.c',
+ 'pango-utils.c',
+ 'shape.c',
+ 'serializer.c',
+ 'json/gtkjsonparser.c',
+ 'json/gtkjsonprinter.c',
+ 'pango-line.c',
+ 'pango-run.c',
+ 'pango-line-breaker.c',
+ 'pango-lines.c',
+ 'pango-line-iter.c',
+ 'pango-hbface.c',
+ 'pango-hbfamily.c',
+ 'pango-generic-family.c',
+ 'pango-hbfont.c',
+ 'pango-userface.c',
+ 'pango-userfont.c',
+]
+
+pango_headers = [
+ 'pango.h',
+ 'pango-attr.h',
+ 'pango-attr-list.h',
+ 'pango-attr-iterator.h',
+ 'pango-attributes.h',
+ 'pango-break.h',
+ 'pango-color.h',
+ 'pango-context.h',
+ 'pango-direction.h',
+ 'pango-font.h',
+ 'pango-font-description.h',
+ 'pango-font-face.h',
+ 'pango-font-family.h',
+ 'pango-font-metrics.h',
+ 'pango-fontmap.h',
+ 'pango-fontset.h',
+ 'pango-generic-family.h',
+ 'pango-glyph.h',
+ 'pango-gravity.h',
+ 'pango-item.h',
+ 'pango-language.h',
+ 'pango-line.h',
+ 'pango-run.h',
+ 'pango-line-breaker.h',
+ 'pango-line-iter.h',
+ 'pango-lines.h',
+ 'pango-layout.h',
+ 'pango-matrix.h',
+ 'pango-markup.h',
+ 'pango-renderer.h',
+ 'pango-script.h',
+ 'pango-tabs.h',
+ 'pango-types.h',
+ 'pango-utils.h',
+ 'pango-hbface.h',
+ 'pango-hbfont.h',
+ 'pango-userface.h',
+ 'pango-userfont.h',
+]
+
+pango_gir_includes = [
+ 'HarfBuzz-0.0',
+ 'GObject-2.0',
+ 'Gio-2.0'
+]
+
+if cairo_dep.found()
+ pango_headers += [
+ 'pangocairo.h',
+ 'pangocairo-context.h',
+ 'pangocairo-font.h',
+ 'pangocairo-render.h',
+ ]
+
+ pango_sources += [
+ 'pangocairo-context.c',
+ 'pangocairo-font.c',
+ 'pangocairo-render.c',
+ ]
+
+ pango_gir_includes += [
+ 'cairo-1.0',
+ ]
+endif
+
+if host_system == 'darwin'
+ pango_headers += [
+ 'pangocoretext-fontmap.h',
+ ]
+
+ pango_sources += [
+ 'pangocoretext-fontmap.c',
+ ]
+endif
+
+if host_system == 'linux'
+ pango_headers += [
+ 'pangofc-fontmap.h',
+ ]
+
+ pango_sources += [
+ 'pangofc-fontmap.c',
+ 'pangofc-language-set.c',
+ ]
+
+ pango_gir_includes += [
+ 'fontconfig-2.0',
+ ]
+endif
+
+if host_system == 'windows'
+ pango_headers += [
+ 'pangodwrite-fontmap.h',
+ ]
+
+ pango_sources += [
+ 'pangodwrite-fontmap.cpp',
+ ]
+ if cairo_dep.found()
+ pango_sources += [
+ 'pangocairo-dwrite-font.cpp',
+ ]
+ endif
+
+ pango_deps += [
+ cc.find_library('gdi32'),
+ cc.find_library('dwrite'),
+ ]
+endif
+
+pango_installed_headers = pango_headers + [ 'pango-version-macros.h' ]
+
+install_headers(pango_installed_headers, subdir: pango_api_path)
+
+# Features header
+pango_features_conf = configuration_data()
+pango_features_conf.set('PANGO2_VERSION_MAJOR', pango_major_version)
+pango_features_conf.set('PANGO2_VERSION_MINOR', pango_minor_version)
+pango_features_conf.set('PANGO2_VERSION_MICRO', pango_micro_version)
+pango_features_conf.set('PANGO2_API_VERSION', pango_api_version)
+pango_features_conf.set('PANGO2_CURRENT_MINUS_AGE', '0')
+if cairo_dep.found()
+ pango_features_conf.set('PANGO2_RENDERING_CAIRO', 1)
+endif
+
+pango_features_h = configure_file(
+ input: 'pango-features.h.meson',
+ output: 'pango-features.h',
+ configuration: pango_features_conf,
+ install: true,
+ install_dir: join_paths(pango_includedir, pango_api_path),
+)
+
+# Enumerations for GType
+pango_enums = gnome.mkenums(
+ 'pango-enum-types',
+ sources: pango_headers,
+ identifier_prefix: 'Pango2',
+ symbol_prefix: 'PANGO2',
+ c_template: 'pango-enum-types.c.template',
+ h_template: 'pango-enum-types.h.template',
+ install_dir: join_paths(pango_includedir, pango_api_path),
+ install_header: true,
+)
+pango_enum_h = pango_enums[1]
+
+if host_system == 'windows'
+ pango_rc = configure_file(
+ input: 'pango.rc.in',
+ output: 'pango.rc',
+ configuration: pango_features_conf,
+ )
+ pango_res = import('windows').compile_resources(pango_rc)
+ pango_sources += pango_res
+endif
+
+pango_cflags = [
+ '-DG_LOG_DOMAIN="Pango"',
+ '-DG_LOG_USE_STRUCTURED=1',
+ '-DPANGO_COMPILATION',
+ '-DSYSCONFDIR="@0@"'.format(pango_sysconfdir),
+ '-DLIBDIR="@0@"'.format(pango_libdir),
+]
+
+pango_inc = include_directories('.')
+
+libpango = library(
+ pango_api_name,
+ sources: pango_sources + pango_enums,
+ version: pango_libversion,
+ soversion: pango_soversion,
+ darwin_versions : pango_osxversion,
+ install: true,
+ dependencies: pango_deps,
+ include_directories: [ root_inc, pango_inc ],
+ c_args: common_cflags + pango_debug_cflags + pango_cflags,
+ cpp_args: common_cflags + pango_debug_cflags + pango_cflags,
+ link_args: common_ldflags,
+)
+
+pango_dep_sources = [ pango_enum_h ]
+
+gir = find_program('g-ir-scanner', required : get_option('introspection'))
+build_gir = gir.found() and (not meson.is_cross_build() or get_option('introspection').enabled())
+
+if build_gir
+ gir_args = [
+ '--quiet',
+ ]
+ harfbuzz_gobject_dep = dependency('harfbuzz-gobject',
+ version: harfbuzz_req_version,
+ required: false,
+ fallback: ['harfbuzz', 'libharfbuzz_gobject_dep'])
+
+ if harfbuzz_gobject_dep.found()
+ pango_deps += harfbuzz_gobject_dep
+ endif
+ pango_gir = gnome.generate_gir(
+ libpango,
+ sources: pango_sources + pango_headers + [ pango_enum_h, pango_features_h ],
+ namespace: 'Pango2',
+ nsversion: pango_api_version,
+ identifier_prefix: 'Pango2',
+ symbol_prefix: 'pango2',
+ export_packages: 'pango',
+ dependencies: pango_deps,
+ includes: pango_gir_includes,
+ header: 'pango/pango.h',
+ install: true,
+ extra_args: gir_args,
+ )
+ pango_gir_dep = declare_dependency(sources: pango_gir)
+ pango_dep_sources += pango_gir
+endif
+
+libpango_dep = declare_dependency(
+ link_with: libpango,
+ include_directories: pango_inc,
+ dependencies: pango_deps,
+ sources: pango_dep_sources,
+)
+meson.override_dependency('pango', libpango_dep)
+
+pango_pkg_requires = ['gobject-2.0']
+if harfbuzz_dep.type_name() == 'pkgconfig'
+ pango_pkg_requires += 'harfbuzz'
+endif
+
+pkgconfig.generate(libpango,
+ name: 'Pango2',
+ description: 'Internationalized text handling',
+ requires: pango_pkg_requires,
+ filebase: 'pango2',
+ subdirs: pango_api_name,
+)
+
+if cairo_dep.found()
+ pkgconfig.generate(libpango,
+ name: 'Pango2 Cairo',
+ description: 'Cairo rendering support for Pango2',
+ requires: ['pango2'],
+ filebase: 'pango2cairo',
+ subdirs: pango_api_name,
+ )
+endif
diff --git a/pango2/pango-attr-iterator-private.h b/pango2/pango-attr-iterator-private.h
new file mode 100644
index 00000000..5fead454
--- /dev/null
+++ b/pango2/pango-attr-iterator-private.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-attr-iterator.h"
+
+
+struct _Pango2AttrIterator
+{
+ GPtrArray *attrs; /* From the list */
+ guint n_attrs; /* Copied from the list */
+
+ GPtrArray *attribute_stack;
+
+ guint attr_index;
+ guint start_index;
+ guint end_index;
+};
+
+void pango2_attr_iterator_clear (Pango2AttrIterator *iterator);
+gboolean pango2_attr_iterator_advance (Pango2AttrIterator *iterator,
+ int index);
diff --git a/pango2/pango-attr-iterator.c b/pango2/pango-attr-iterator.c
new file mode 100644
index 00000000..59bb1255
--- /dev/null
+++ b/pango2/pango-attr-iterator.c
@@ -0,0 +1,509 @@
+/* Pango2
+ * pango-attr-iterator.c: Attribute iterator
+ *
+ * Copyright (C) 2000-2002 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.
+ */
+
+#include "config.h"
+#include <string.h>
+
+#include "pango-attr-iterator-private.h"
+#include "pango-attr-list-private.h"
+#include "pango-attr-private.h"
+#include "pango-impl-utils.h"
+
+
+G_DEFINE_BOXED_TYPE (Pango2AttrIterator,
+ pango2_attr_iterator,
+ pango2_attr_iterator_copy,
+ pango2_attr_iterator_destroy)
+
+/* {{{ Private API */
+
+void
+pango2_attr_list_init_iterator (Pango2AttrList *list,
+ Pango2AttrIterator *iterator)
+{
+ iterator->attribute_stack = NULL;
+ iterator->attrs = list->attributes;
+ iterator->n_attrs = iterator->attrs ? iterator->attrs->len : 0;
+
+ iterator->attr_index = 0;
+ iterator->start_index = 0;
+ iterator->end_index = 0;
+
+ if (!pango2_attr_iterator_next (iterator))
+ iterator->end_index = G_MAXUINT;
+}
+
+void
+pango2_attr_iterator_clear (Pango2AttrIterator *iterator)
+{
+ if (iterator->attribute_stack)
+ g_ptr_array_free (iterator->attribute_stack, TRUE);
+}
+
+gboolean
+pango2_attr_iterator_advance (Pango2AttrIterator *iterator,
+ int index)
+{
+ int start_range, end_range;
+
+ pango2_attr_iterator_range (iterator, &start_range, &end_range);
+
+ while (index >= end_range)
+ {
+ if (!pango2_attr_iterator_next (iterator))
+ return FALSE;
+ pango2_attr_iterator_range (iterator, &start_range, &end_range);
+ }
+
+ if (start_range > index)
+ g_warning ("pango2_attr_iterator_advance(): iterator had already "
+ "moved beyond the index");
+
+ return TRUE;
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_attr_list_get_iterator:
+ * @list: a `Pango2AttrList`
+ *
+ * Create a iterator initialized to the beginning of the list.
+ *
+ * @list must not be modified until this iterator is freed.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2AttrIterator`, which should be freed with
+ * [method@Pango2.AttrIterator.destroy]
+ */
+Pango2AttrIterator *
+pango2_attr_list_get_iterator (Pango2AttrList *list)
+{
+ Pango2AttrIterator *iterator;
+
+ g_return_val_if_fail (list != NULL, NULL);
+
+ iterator = g_slice_new (Pango2AttrIterator);
+ pango2_attr_list_init_iterator (list, iterator);
+
+ return iterator;
+}
+
+/**
+ * pango2_attr_iterator_destroy:
+ * @iterator: a `Pango2AttrIterator`
+ *
+ * Destroy a `Pango2AttrIterator` and free all associated memory.
+ */
+void
+pango2_attr_iterator_destroy (Pango2AttrIterator *iterator)
+{
+ g_return_if_fail (iterator != NULL);
+
+ pango2_attr_iterator_clear (iterator);
+ g_slice_free (Pango2AttrIterator, iterator);
+}
+
+/**
+ * pango2_attr_iterator_copy:
+ * @iterator: a `Pango2AttrIterator`
+ *
+ * Copy a `Pango2AttrIterator`.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2AttrIterator`, which should be freed with
+ * [method@Pango2.AttrIterator.destroy]
+ */
+Pango2AttrIterator *
+pango2_attr_iterator_copy (Pango2AttrIterator *iterator)
+{
+ Pango2AttrIterator *copy;
+
+ g_return_val_if_fail (iterator != NULL, NULL);
+
+ copy = g_slice_new (Pango2AttrIterator);
+
+ *copy = *iterator;
+
+ if (iterator->attribute_stack)
+ copy->attribute_stack = g_ptr_array_copy (iterator->attribute_stack, NULL, NULL);
+ else
+ copy->attribute_stack = NULL;
+
+ return copy;
+}
+
+/**
+ * pango2_attr_iterator_range:
+ * @iterator: a Pango2AttrIterator
+ * @start: (out): location to store the start of the range
+ * @end: (out): location to store the end of the range
+ *
+ * Get the range of the current segment.
+ *
+ * Note that the stored return values are signed, not unsigned
+ * like the values in `Pango2Attribute`. To deal with this API
+ * oversight, stored return values that wouldn't fit into
+ * a signed integer are clamped to %G_MAXINT.
+ */
+void
+pango2_attr_iterator_range (Pango2AttrIterator *iterator,
+ int *start,
+ int *end)
+{
+ g_return_if_fail (iterator != NULL);
+
+ if (start)
+ *start = MIN (iterator->start_index, G_MAXINT);
+ if (end)
+ *end = MIN (iterator->end_index, G_MAXINT);
+}
+
+/**
+ * pango2_attr_iterator_next:
+ * @iterator: a `Pango2AttrIterator`
+ *
+ * Advance the iterator until the next change of style.
+ *
+ * Return value: %FALSE if the iterator is at the end
+ * of the list, otherwise %TRUE
+ */
+gboolean
+pango2_attr_iterator_next (Pango2AttrIterator *iterator)
+{
+ int i;
+
+ g_return_val_if_fail (iterator != NULL, FALSE);
+
+ if (iterator->attr_index >= iterator->n_attrs &&
+ (!iterator->attribute_stack || iterator->attribute_stack->len == 0))
+ return FALSE;
+
+ iterator->start_index = iterator->end_index;
+ iterator->end_index = G_MAXUINT;
+
+ if (iterator->attribute_stack)
+ {
+ for (i = iterator->attribute_stack->len - 1; i>= 0; i--)
+ {
+ const Pango2Attribute *attr = g_ptr_array_index (iterator->attribute_stack, i);
+
+ if (attr->end_index == iterator->start_index)
+ g_ptr_array_remove_index (iterator->attribute_stack, i); /* Can't use index_fast :( */
+ else
+ iterator->end_index = MIN (iterator->end_index, attr->end_index);
+ }
+ }
+
+ while (1)
+ {
+ Pango2Attribute *attr;
+
+ if (iterator->attr_index >= iterator->n_attrs)
+ break;
+
+ attr = g_ptr_array_index (iterator->attrs, iterator->attr_index);
+
+ if (attr->start_index != iterator->start_index)
+ break;
+
+ if (attr->end_index > iterator->start_index)
+ {
+ if (G_UNLIKELY (!iterator->attribute_stack))
+ iterator->attribute_stack = g_ptr_array_new ();
+
+ g_ptr_array_add (iterator->attribute_stack, attr);
+
+ iterator->end_index = MIN (iterator->end_index, attr->end_index);
+ }
+
+ iterator->attr_index++; /* NEXT! */
+ }
+
+ if (iterator->attr_index < iterator->n_attrs)
+ {
+ Pango2Attribute *attr = g_ptr_array_index (iterator->attrs, iterator->attr_index);
+
+ iterator->end_index = MIN (iterator->end_index, attr->start_index);
+ }
+
+ return TRUE;
+}
+
+/**
+ * pango2_attr_iterator_get:
+ * @iterator: a `Pango2AttrIterator`
+ * @type: the type of attribute to find
+ *
+ * Find the current attribute of a particular type
+ * at the iterator location.
+ *
+ * When multiple attributes of the same type overlap,
+ * the attribute whose range starts closest to the
+ * current location is used.
+ *
+ * Return value: (nullable) (transfer none): the current
+ * attribute of the given type, or %NULL if no attribute
+ * of that type applies to the current location.
+ */
+Pango2Attribute *
+pango2_attr_iterator_get (Pango2AttrIterator *iterator,
+ guint type)
+{
+ int i;
+
+ g_return_val_if_fail (iterator != NULL, NULL);
+
+ if (!iterator->attribute_stack)
+ return NULL;
+
+ for (i = iterator->attribute_stack->len - 1; i>= 0; i--)
+ {
+ Pango2Attribute *attr = g_ptr_array_index (iterator->attribute_stack, i);
+
+ if (attr->type == type)
+ return attr;
+ }
+
+ return NULL;
+}
+
+/**
+ * pango2_attr_iterator_get_font:
+ * @iterator: a `Pango2AttrIterator`
+ * @desc: (out caller-allocates): a `Pango2FontDescription` to fill in with the current
+ * values. The family name in this structure will be set using
+ * [method@Pango2.FontDescription.set_family_static] using
+ * values from an attribute in the `Pango2AttrList` associated
+ * with the iterator, so if you plan to keep it around, you
+ * must call:
+ * `pango2_font_description_set_family (desc, pango2_font_description_get_family (desc))`.
+ * @language: (out) (optional): location to store language tag
+ * for item, or %NULL if none is found.
+ * @extra_attrs: (out) (optional) (element-type Pango2.Attribute) (transfer full):
+ * location in which to store a list of non-font attributes
+ * at the the current position; only the highest priority
+ * value of each attribute will be added to this list. In
+ * order to free this value, you must call
+ * [method@Pango2.Attribute.destroy] on each member.
+ *
+ * Get the font and other attributes at the current
+ * iterator position.
+ */
+void
+pango2_attr_iterator_get_font (Pango2AttrIterator *iterator,
+ Pango2FontDescription *desc,
+ Pango2Language **language,
+ GSList **extra_attrs)
+{
+ Pango2FontMask mask = 0;
+ gboolean have_language = FALSE;
+ double scale = 0;
+ gboolean have_scale = FALSE;
+ int i;
+
+ g_return_if_fail (iterator != NULL);
+ g_return_if_fail (desc != NULL);
+
+ if (language)
+ *language = NULL;
+
+ if (extra_attrs)
+ *extra_attrs = NULL;
+
+ if (!iterator->attribute_stack)
+ return;
+
+ for (i = iterator->attribute_stack->len - 1; i >= 0; i--)
+ {
+ const Pango2Attribute *attr = g_ptr_array_index (iterator->attribute_stack, i);
+
+ switch ((int) attr->type)
+ {
+ case PANGO2_ATTR_FONT_DESC:
+ {
+ Pango2FontMask new_mask = pango2_font_description_get_set_fields (attr->font_value) & ~mask;
+ mask |= new_mask;
+ pango2_font_description_unset_fields (desc, new_mask);
+ pango2_font_description_merge_static (desc, attr->font_value, FALSE);
+
+ break;
+ }
+ case PANGO2_ATTR_FAMILY:
+ if (!(mask & PANGO2_FONT_MASK_FAMILY))
+ {
+ mask |= PANGO2_FONT_MASK_FAMILY;
+ pango2_font_description_set_family (desc, attr->str_value);
+ }
+ break;
+ case PANGO2_ATTR_STYLE:
+ if (!(mask & PANGO2_FONT_MASK_STYLE))
+ {
+ mask |= PANGO2_FONT_MASK_STYLE;
+ pango2_font_description_set_style (desc, attr->int_value);
+ }
+ break;
+ case PANGO2_ATTR_VARIANT:
+ if (!(mask & PANGO2_FONT_MASK_VARIANT))
+ {
+ mask |= PANGO2_FONT_MASK_VARIANT;
+ pango2_font_description_set_variant (desc, attr->int_value);
+ }
+ break;
+ case PANGO2_ATTR_WEIGHT:
+ if (!(mask & PANGO2_FONT_MASK_WEIGHT))
+ {
+ mask |= PANGO2_FONT_MASK_WEIGHT;
+ pango2_font_description_set_weight (desc, attr->int_value);
+ }
+ break;
+ case PANGO2_ATTR_STRETCH:
+ if (!(mask & PANGO2_FONT_MASK_STRETCH))
+ {
+ mask |= PANGO2_FONT_MASK_STRETCH;
+ pango2_font_description_set_stretch (desc, attr->int_value);
+ }
+ break;
+ case PANGO2_ATTR_SIZE:
+ if (!(mask & PANGO2_FONT_MASK_SIZE))
+ {
+ mask |= PANGO2_FONT_MASK_SIZE;
+ pango2_font_description_set_size (desc, attr->int_value);
+ }
+ break;
+ case PANGO2_ATTR_ABSOLUTE_SIZE:
+ if (!(mask & PANGO2_FONT_MASK_SIZE))
+ {
+ mask |= PANGO2_FONT_MASK_SIZE;
+ pango2_font_description_set_absolute_size (desc, attr->int_value);
+ }
+ break;
+ case PANGO2_ATTR_SCALE:
+ if (!have_scale)
+ {
+ have_scale = TRUE;
+ scale = attr->double_value;
+ }
+ break;
+ case PANGO2_ATTR_LANGUAGE:
+ if (language)
+ {
+ if (!have_language)
+ {
+ have_language = TRUE;
+ *language = attr->lang_value;
+ }
+ }
+ break;
+ default:
+ if (extra_attrs)
+ {
+ gboolean found = FALSE;
+
+ if (PANGO2_ATTR_MERGE (attr) == PANGO2_ATTR_MERGE_OVERRIDES)
+ {
+ GSList *tmp_list = *extra_attrs;
+ while (tmp_list)
+ {
+ Pango2Attribute *old_attr = tmp_list->data;
+ if (attr->type == old_attr->type)
+ {
+ found = TRUE;
+ break;
+ }
+
+ tmp_list = tmp_list->next;
+ }
+ }
+
+ if (!found)
+ *extra_attrs = g_slist_prepend (*extra_attrs, pango2_attribute_copy (attr));
+ }
+ }
+ }
+
+ if (have_scale)
+ {
+ /* We need to use a local variable to ensure that the compiler won't
+ * implicitly cast it to integer while the result is kept in registers,
+ * leading to a wrong approximation in i386 (with 387 FPU)
+ */
+ volatile double size = scale * pango2_font_description_get_size (desc);
+
+ if (pango2_font_description_get_size_is_absolute (desc))
+ pango2_font_description_set_absolute_size (desc, size);
+ else
+ pango2_font_description_set_size (desc, size);
+ }
+}
+
+/**
+ * pango2_attr_iterator_get_attrs:
+ * @iterator: a `Pango2AttrIterator`
+ *
+ * Gets a list of all attributes at the current position of the
+ * iterator.
+ *
+ * Return value: (element-type Pango2.Attribute) (transfer full):
+ * a list of all attributes for the current range. To free
+ * this value, call [method@Pango2.Attribute.destroy] on each
+ * value and [func@GLib.SList.free] on the list.
+ */
+GSList *
+pango2_attr_iterator_get_attrs (Pango2AttrIterator *iterator)
+{
+ GSList *attrs = NULL;
+ int i;
+
+ if (!iterator->attribute_stack ||
+ iterator->attribute_stack->len == 0)
+ return NULL;
+
+ for (i = iterator->attribute_stack->len - 1; i >= 0; i--)
+ {
+ Pango2Attribute *attr = g_ptr_array_index (iterator->attribute_stack, i);
+ GSList *tmp_list2;
+ gboolean found = FALSE;
+
+ if (PANGO2_ATTR_MERGE (attr) == PANGO2_ATTR_MERGE_OVERRIDES)
+ {
+ for (tmp_list2 = attrs; tmp_list2; tmp_list2 = tmp_list2->next)
+ {
+ Pango2Attribute *old_attr = tmp_list2->data;
+ if (attr->type == old_attr->type)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ attrs = g_slist_prepend (attrs, pango2_attribute_copy (attr));
+ }
+
+ return attrs;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-attr-iterator.h b/pango2/pango-attr-iterator.h
new file mode 100644
index 00000000..f22ba499
--- /dev/null
+++ b/pango2/pango-attr-iterator.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-attr-list.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+
+/**
+ * Pango2AttrIterator:
+ *
+ * A `Pango2AttrIterator` is used to iterate through a `Pango2AttrList`.
+ *
+ * A new iterator is created with [method@Pango2.AttrList.get_iterator].
+ * Once the iterator is created, it can be advanced through the style
+ * changes in the text using [method@Pango2.AttrIterator.next]. At each
+ * style change, the range of the current style segment and the attributes
+ * currently in effect can be queried.
+ */
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_attr_iterator_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attr_iterator_range (Pango2AttrIterator *iterator,
+ int *start,
+ int *end);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_attr_iterator_next (Pango2AttrIterator *iterator);
+PANGO2_AVAILABLE_IN_ALL
+Pango2AttrIterator * pango2_attr_iterator_copy (Pango2AttrIterator *iterator);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attr_iterator_destroy (Pango2AttrIterator *iterator);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_iterator_get (Pango2AttrIterator *iterator,
+ guint type);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attr_iterator_get_font (Pango2AttrIterator *iterator,
+ Pango2FontDescription *desc,
+ Pango2Language **language,
+ GSList **extra_attrs);
+PANGO2_AVAILABLE_IN_ALL
+GSList * pango2_attr_iterator_get_attrs (Pango2AttrIterator *iterator);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2AttrIterator, pango2_attr_iterator_destroy)
+
+G_END_DECLS
diff --git a/pango2/pango-attr-list-private.h b/pango2/pango-attr-list-private.h
new file mode 100644
index 00000000..e010febd
--- /dev/null
+++ b/pango2/pango-attr-list-private.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 RedHat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-attr-list.h"
+
+
+struct _Pango2AttrList
+{
+ guint ref_count;
+ GPtrArray *attributes;
+};
+
+void pango2_attr_list_init (Pango2AttrList *list);
+void pango2_attr_list_destroy (Pango2AttrList *list);
+gboolean pango2_attr_list_has_attributes (const Pango2AttrList *list);
+
+void pango2_attr_list_init_iterator (Pango2AttrList *list,
+ Pango2AttrIterator *iterator);
diff --git a/pango2/pango-attr-list.c b/pango2/pango-attr-list.c
new file mode 100644
index 00000000..6f0f47d0
--- /dev/null
+++ b/pango2/pango-attr-list.c
@@ -0,0 +1,1286 @@
+/* Pango2
+ * pango-attr-list.c: Attribute lists
+ *
+ * Copyright (C) 2000-2002 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.
+ */
+
+#include "config.h"
+#include <string.h>
+
+#include "pango-attr-list-private.h"
+#include "pango-attr-private.h"
+#include "pango-impl-utils.h"
+
+
+G_DEFINE_BOXED_TYPE (Pango2AttrList, pango2_attr_list,
+ pango2_attr_list_copy,
+ pango2_attr_list_unref);
+
+/* {{{ Utilities */
+
+static void
+pango2_attr_list_insert_internal (Pango2AttrList *list,
+ Pango2Attribute *attr,
+ gboolean before)
+{
+ const guint start_index = attr->start_index;
+ Pango2Attribute *last_attr;
+
+ if (G_UNLIKELY (!list->attributes))
+ list->attributes = g_ptr_array_new ();
+
+ if (list->attributes->len == 0)
+ {
+ g_ptr_array_add (list->attributes, attr);
+ return;
+ }
+
+ g_assert (list->attributes->len > 0);
+
+ last_attr = g_ptr_array_index (list->attributes, list->attributes->len - 1);
+
+ if (last_attr->start_index < start_index ||
+ (!before && last_attr->start_index == start_index))
+ {
+ g_ptr_array_add (list->attributes, attr);
+ }
+ else
+ {
+ guint i, p;
+
+ for (i = 0, p = list->attributes->len; i < p; i++)
+ {
+ Pango2Attribute *cur = g_ptr_array_index (list->attributes, i);
+
+ if (cur->start_index > start_index ||
+ (before && cur->start_index == start_index))
+ {
+ g_ptr_array_insert (list->attributes, i, attr);
+ break;
+ }
+ }
+ }
+}
+
+/* }}} */
+/* {{{ Private API */
+
+void
+pango2_attr_list_init (Pango2AttrList *list)
+{
+ list->ref_count = 1;
+ list->attributes = NULL;
+}
+
+void
+pango2_attr_list_destroy (Pango2AttrList *list)
+{
+ if (!list->attributes)
+ return;
+
+ g_ptr_array_free (list->attributes, TRUE);
+}
+
+gboolean
+pango2_attr_list_has_attributes (const Pango2AttrList *list)
+{
+ return list && list->attributes != NULL && list->attributes->len > 0;
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_attr_list_new:
+ *
+ * Create a new empty attribute list with a reference
+ * count of one.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2AttrList`, which should be freed with
+ * [method@Pango2.AttrList.unref]
+ */
+Pango2AttrList *
+pango2_attr_list_new (void)
+{
+ Pango2AttrList *list = g_slice_new (Pango2AttrList);
+
+ pango2_attr_list_init (list);
+
+ return list;
+}
+
+/**
+ * pango2_attr_list_ref:
+ * @list: (nullable): a `Pango2AttrList`
+ *
+ * Increase the reference count of the given attribute
+ * list by one.
+ *
+ * Return value: The attribute list passed in
+ */
+Pango2AttrList *
+pango2_attr_list_ref (Pango2AttrList *list)
+{
+ if (list == NULL)
+ return NULL;
+
+ g_atomic_int_inc ((int *) &list->ref_count);
+
+ return list;
+}
+
+/**
+ * pango2_attr_list_unref:
+ * @list: (nullable): a `Pango2AttrList`
+ *
+ * Decrease the reference count of the given attribute
+ * list by one.
+ *
+ * If the result is zero, free the attribute list
+ * and the attributes it contains.
+ */
+void
+pango2_attr_list_unref (Pango2AttrList *list)
+{
+ if (list == NULL)
+ return;
+
+ g_return_if_fail (list->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test ((int *) &list->ref_count))
+ {
+ pango2_attr_list_destroy (list);
+ g_slice_free (Pango2AttrList, list);
+ }
+}
+
+/**
+ * pango2_attr_list_copy:
+ * @list: (nullable): a `Pango2AttrList`
+ *
+ * Copy @list and return an identical new list.
+ *
+ * Return value: (nullable): the newly allocated
+ * `Pango2AttrList`, with a reference count of one,
+ * which should be freed with [method@Pango2.AttrList.unref]
+ */
+Pango2AttrList *
+pango2_attr_list_copy (Pango2AttrList *list)
+{
+ Pango2AttrList *new;
+
+ if (list == NULL)
+ return NULL;
+
+ new = pango2_attr_list_new ();
+ if (!list->attributes || list->attributes->len == 0)
+ return new;
+
+ new->attributes = g_ptr_array_copy (list->attributes, (GCopyFunc)pango2_attribute_copy, NULL);
+
+ return new;
+}
+
+/**
+ * pango2_attr_list_insert:
+ * @list: a `Pango2AttrList`
+ * @attr: (transfer full): the attribute to insert
+ *
+ * Insert the given attribute into the `Pango2AttrList`.
+ *
+ * It will be inserted after all other attributes with a
+ * matching @start_index.
+ */
+void
+pango2_attr_list_insert (Pango2AttrList *list,
+ Pango2Attribute *attr)
+{
+ g_return_if_fail (list != NULL);
+ g_return_if_fail (attr != NULL);
+
+ pango2_attr_list_insert_internal (list, attr, FALSE);
+}
+
+/**
+ * pango2_attr_list_insert_before:
+ * @list: a `Pango2AttrList`
+ * @attr: (transfer full): the attribute to insert
+ *
+ * Insert the given attribute into the `Pango2AttrList`.
+ *
+ * It will be inserted before all other attributes with a
+ * matching @start_index.
+ */
+void
+pango2_attr_list_insert_before (Pango2AttrList *list,
+ Pango2Attribute *attr)
+{
+ g_return_if_fail (list != NULL);
+ g_return_if_fail (attr != NULL);
+
+ pango2_attr_list_insert_internal (list, attr, TRUE);
+}
+
+/**
+ * pango2_attr_list_change:
+ * @list: a `Pango2AttrList`
+ * @attr: (transfer full): the attribute to insert
+ *
+ * Insert the given attribute into the `Pango2AttrList`.
+ *
+ * It will replace any attributes of the same type
+ * on that segment and be merged with any adjoining
+ * attributes that are identical.
+ *
+ * This function is slower than [method@Pango2.AttrList.insert]
+ * for creating an attribute list in order (potentially
+ * much slower for large lists). However,
+ * [method@Pango2.AttrList.insert] is not suitable for
+ * continually changing a set of attributes since it
+ * never removes or combines existing attributes.
+ */
+void
+pango2_attr_list_change (Pango2AttrList *list,
+ Pango2Attribute *attr)
+{
+ guint i, p;
+ guint start_index = attr->start_index;
+ guint end_index = attr->end_index;
+ gboolean inserted;
+
+ g_return_if_fail (list != NULL);
+
+ if (start_index == end_index) /* empty, nothing to do */
+ {
+ pango2_attribute_destroy (attr);
+ return;
+ }
+
+ if (!list->attributes || list->attributes->len == 0)
+ {
+ pango2_attr_list_insert (list, attr);
+ return;
+ }
+
+ inserted = FALSE;
+ for (i = 0, p = list->attributes->len; i < p; i++)
+ {
+ Pango2Attribute *tmp_attr = g_ptr_array_index (list->attributes, i);
+
+ if (tmp_attr->start_index > start_index)
+ {
+ g_ptr_array_insert (list->attributes, i, attr);
+ inserted = TRUE;
+ break;
+ }
+
+ if (tmp_attr->type != attr->type)
+ continue;
+
+ if (tmp_attr->end_index < start_index)
+ continue; /* This attr does not overlap with the new one */
+
+ g_assert (tmp_attr->start_index <= start_index);
+ g_assert (tmp_attr->end_index >= start_index);
+
+ if (pango2_attribute_equal (tmp_attr, attr))
+ {
+ /* We can merge the new attribute with this attribute
+ */
+ if (tmp_attr->end_index >= end_index)
+ {
+ /* We are totally overlapping the previous attribute.
+ * No action is needed.
+ */
+ pango2_attribute_destroy (attr);
+ return;
+ }
+
+ tmp_attr->end_index = end_index;
+ pango2_attribute_destroy (attr);
+
+ attr = tmp_attr;
+ inserted = TRUE;
+ break;
+ }
+ else
+ {
+ /* Split, truncate, or remove the old attribute
+ */
+ if (tmp_attr->end_index > end_index)
+ {
+ Pango2Attribute *end_attr = pango2_attribute_copy (tmp_attr);
+
+ end_attr->start_index = end_index;
+ pango2_attr_list_insert (list, end_attr);
+ }
+
+ if (tmp_attr->start_index == start_index)
+ {
+ pango2_attribute_destroy (tmp_attr);
+ g_ptr_array_remove_index (list->attributes, i);
+ break;
+ }
+ else
+ {
+ tmp_attr->end_index = start_index;
+ }
+ }
+ }
+
+ if (!inserted)
+ /* we didn't insert attr yet */
+ pango2_attr_list_insert (list, attr);
+
+ /* We now have the range inserted into the list one way or the
+ * other. Fix up the remainder
+ */
+ /* Attention: No i = 0 here. */
+ for (i = i + 1, p = list->attributes->len; i < p; i++)
+ {
+ Pango2Attribute *tmp_attr = g_ptr_array_index (list->attributes, i);
+
+ if (tmp_attr->start_index > end_index)
+ break;
+
+ if (tmp_attr->type != attr->type)
+ continue;
+
+ if (tmp_attr == attr)
+ continue;
+
+ if (tmp_attr->end_index <= attr->end_index ||
+ pango2_attribute_equal (tmp_attr, attr))
+ {
+ /* We can merge the new attribute with this attribute. */
+ attr->end_index = MAX (end_index, tmp_attr->end_index);
+ pango2_attribute_destroy (tmp_attr);
+ g_ptr_array_remove_index (list->attributes, i);
+ i--;
+ p--;
+ continue;
+ }
+ else
+ {
+ /* Trim the start of this attribute that it begins at the end
+ * of the new attribute. This may involve moving it in the list
+ * to maintain the required non-decreasing order of start indices.
+ */
+ int k, m;
+
+ tmp_attr->start_index = attr->end_index;
+
+ for (k = i + 1, m = list->attributes->len; k < m; k++)
+ {
+ Pango2Attribute *tmp_attr2 = g_ptr_array_index (list->attributes, k);
+
+ if (tmp_attr2->start_index >= tmp_attr->start_index)
+ break;
+
+ g_ptr_array_index (list->attributes, k - 1) = tmp_attr2;
+ g_ptr_array_index (list->attributes, k) = tmp_attr;
+ }
+ }
+ }
+}
+
+/**
+ * pango2_attr_list_update:
+ * @list: a `Pango2AttrList`
+ * @pos: the position of the change
+ * @remove: the number of removed bytes
+ * @add: the number of added bytes
+ *
+ * Update indices of attributes in @list for a change in the
+ * text they refer to.
+ *
+ * The change that this function applies is removing @remove
+ * bytes at position @pos and inserting @add bytes instead.
+ *
+ * Attributes that fall entirely in the (@pos, @pos + @remove)
+ * range are removed.
+ *
+ * Attributes that start or end inside the (@pos, @pos + @remove)
+ * range are shortened to reflect the removal.
+ *
+ * Attributes start and end positions are updated if they are
+ * behind @pos + @remove.
+ */
+void
+pango2_attr_list_update (Pango2AttrList *list,
+ int pos,
+ int remove,
+ int add)
+{
+ guint i, p;
+
+ g_return_if_fail (pos >= 0);
+ g_return_if_fail (remove >= 0);
+ g_return_if_fail (add >= 0);
+
+ if (list->attributes)
+ for (i = 0, p = list->attributes->len; i < p; i++)
+ {
+ Pango2Attribute *attr = g_ptr_array_index (list->attributes, i);
+
+ if (attr->start_index >= pos &&
+ attr->end_index < pos + remove)
+ {
+ pango2_attribute_destroy (attr);
+ g_ptr_array_remove_index (list->attributes, i);
+ i--; /* Look at this index again */
+ p--;
+ continue;
+ }
+
+ if (attr->start_index != PANGO2_ATTR_INDEX_FROM_TEXT_BEGINNING)
+ {
+ if (attr->start_index >= pos &&
+ attr->start_index < pos + remove)
+ {
+ attr->start_index = pos + add;
+ }
+ else if (attr->start_index >= pos + remove)
+ {
+ attr->start_index += add - remove;
+ }
+ }
+
+ if (attr->end_index != PANGO2_ATTR_INDEX_TO_TEXT_END)
+ {
+ if (attr->end_index >= pos &&
+ attr->end_index < pos + remove)
+ {
+ attr->end_index = pos;
+ }
+ else if (attr->end_index >= pos + remove)
+ {
+ if (add > remove &&
+ G_MAXUINT - attr->end_index < add - remove)
+ attr->end_index = G_MAXUINT;
+ else
+ attr->end_index += add - remove;
+ }
+ }
+ }
+}
+
+/**
+ * pango2_attr_list_splice:
+ * @list: a `Pango2AttrList`
+ * @other: another `Pango2AttrList`
+ * @pos: the position in @list at which to insert @other
+ * @len: the length of the spliced segment. (Note that this
+ * must be specified since the attributes in @other may only
+ * be present at some subsection of this range)
+ *
+ * This function opens up a hole in @list, fills it
+ * in with attributes from the left, and then merges
+ * @other on top of the hole.
+ *
+ * This operation is equivalent to stretching every attribute
+ * that applies at position @pos in @list by an amount @len,
+ * and then calling [method@Pango2.AttrList.change] with a copy
+ * of each attribute in @other in sequence (offset in position
+ * by @pos, and limited in length to @len).
+ *
+ * This operation proves useful for, for instance, inserting
+ * a pre-edit string in the middle of an edit buffer.
+ *
+ * For backwards compatibility, the function behaves differently
+ * when @len is 0. In this case, the attributes from @other are
+ * not imited to @len, and are just overlayed on top of @list.
+ *
+ * This mode is useful for merging two lists of attributes together.
+ */
+void
+pango2_attr_list_splice (Pango2AttrList *list,
+ Pango2AttrList *other,
+ int pos,
+ int len)
+{
+ guint i, p;
+ guint upos, ulen;
+ guint end;
+
+ g_return_if_fail (list != NULL);
+ g_return_if_fail (other != NULL);
+ g_return_if_fail (pos >= 0);
+ g_return_if_fail (len >= 0);
+
+ upos = (guint)pos;
+ ulen = (guint)len;
+
+/* This definition only works when a and b are unsigned; overflow
+ * isn't defined in the C standard for signed integers
+ */
+#define CLAMP_ADD(a,b) (((a) + (b) < (a)) ? G_MAXUINT : (a) + (b))
+
+ end = CLAMP_ADD (upos, ulen);
+
+ if (list->attributes)
+ for (i = 0, p = list->attributes->len; i < p; i++)
+ {
+ Pango2Attribute *attr = g_ptr_array_index (list->attributes, i);;
+
+ if (attr->start_index <= upos)
+ {
+ if (attr->end_index > upos)
+ attr->end_index = CLAMP_ADD (attr->end_index, ulen);
+ }
+ else
+ {
+ /* This could result in a zero length attribute if it
+ * gets squashed up against G_MAXUINT, but deleting such
+ * an element could (in theory) suprise the caller, so
+ * we don't delete it.
+ */
+ attr->start_index = CLAMP_ADD (attr->start_index, ulen);
+ attr->end_index = CLAMP_ADD (attr->end_index, ulen);
+ }
+ }
+
+ if (!other->attributes || other->attributes->len == 0)
+ return;
+
+ for (i = 0, p = other->attributes->len; i < p; i++)
+ {
+ Pango2Attribute *attr = pango2_attribute_copy (g_ptr_array_index (other->attributes, i));
+ if (ulen > 0)
+ {
+ attr->start_index = MIN (CLAMP_ADD (attr->start_index, upos), end);
+ attr->end_index = MIN (CLAMP_ADD (attr->end_index, upos), end);
+ }
+ else
+ {
+ attr->start_index = CLAMP_ADD (attr->start_index, upos);
+ attr->end_index = CLAMP_ADD (attr->end_index, upos);
+ }
+
+ /* Same as above, the attribute could be squashed to zero-length; here
+ * pango2_attr_list_change() will take care of deleting it.
+ */
+ pango2_attr_list_change (list, attr);
+ }
+#undef CLAMP_ADD
+}
+
+/**
+ * pango2_attr_list_get_attributes:
+ * @list: a `Pango2AttrList`
+ *
+ * Gets a list of all attributes in @list.
+ *
+ * Return value: (element-type Pango2.Attribute) (transfer full):
+ * a list of all attributes in @list. To free this value,
+ * call [method@Pango2.Attribute.destroy] on each value and
+ * [func@GLib.SList.free] on the list
+ */
+GSList *
+pango2_attr_list_get_attributes (Pango2AttrList *list)
+{
+ GSList *result = NULL;
+ guint i, p;
+
+ g_return_val_if_fail (list != NULL, NULL);
+
+ if (!list->attributes || list->attributes->len == 0)
+ return NULL;
+
+ for (i = 0, p = list->attributes->len; i < p; i++)
+ {
+ Pango2Attribute *attr = g_ptr_array_index (list->attributes, i);
+
+ result = g_slist_prepend (result, pango2_attribute_copy (attr));
+ }
+
+ return g_slist_reverse (result);
+}
+
+/**
+ * pango2_attr_list_equal:
+ * @list: a `Pango2AttrList`
+ * @other_list: the other `Pango2AttrList`
+ *
+ * Checks whether @list and @other_list contain the same
+ * attributes and whether those attributes apply to the
+ * same ranges.
+ *
+ * Beware that this will return wrong values if any list
+ * contains duplicates.
+ *
+ * Return value: %TRUE if the lists are equal, %FALSE if
+ * they aren't
+ */
+gboolean
+pango2_attr_list_equal (Pango2AttrList *list,
+ Pango2AttrList *other_list)
+{
+ GPtrArray *attrs, *other_attrs;
+ guint64 skip_bitmask = 0;
+ guint i;
+
+ if (list == other_list)
+ return TRUE;
+
+ if (list == NULL || other_list == NULL)
+ return FALSE;
+
+ if (list->attributes == NULL || other_list->attributes == NULL)
+ return list->attributes == other_list->attributes;
+
+ attrs = list->attributes;
+ other_attrs = other_list->attributes;
+
+ if (attrs->len != other_attrs->len)
+ return FALSE;
+
+ for (i = 0; i < attrs->len; i++)
+ {
+ Pango2Attribute *attr = g_ptr_array_index (attrs, i);
+ gboolean attr_equal = FALSE;
+ guint other_attr_index;
+
+ for (other_attr_index = 0; other_attr_index < other_attrs->len; other_attr_index++)
+ {
+ Pango2Attribute *other_attr = g_ptr_array_index (other_attrs, other_attr_index);
+ guint64 other_attr_bitmask = other_attr_index < 64 ? 1 << other_attr_index : 0;
+
+ if ((skip_bitmask & other_attr_bitmask) != 0)
+ continue;
+
+ if (attr->start_index == other_attr->start_index &&
+ attr->end_index == other_attr->end_index &&
+ pango2_attribute_equal (attr, other_attr))
+ {
+ skip_bitmask |= other_attr_bitmask;
+ attr_equal = TRUE;
+ break;
+ }
+
+ }
+
+ if (!attr_equal)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * pango2_attr_list_filter:
+ * @list: a `Pango2AttrList`
+ * @func: (scope call) (closure data): callback function;
+ * returns %TRUE if an attribute should be filtered out
+ * @data: (closure): Data to be passed to @func
+ *
+ * Given a `Pango2AttrList` and callback function, removes
+ * any elements of @list for which @func returns %TRUE and
+ * inserts them into a new list.
+ *
+ * Return value: (transfer full) (nullable): the new
+ * `Pango2AttrList` or %NULL if no attributes of the
+ * given types were found
+ */
+Pango2AttrList *
+pango2_attr_list_filter (Pango2AttrList *list,
+ Pango2AttrFilterFunc func,
+ gpointer data)
+
+{
+ Pango2AttrList *new = NULL;
+ guint i, p;
+
+ g_return_val_if_fail (list != NULL, NULL);
+
+ if (!list->attributes || list->attributes->len == 0)
+ return NULL;
+
+ for (i = 0, p = list->attributes->len; i < p; i++)
+ {
+ Pango2Attribute *tmp_attr = g_ptr_array_index (list->attributes, i);
+
+ if ((*func) (tmp_attr, data))
+ {
+ g_ptr_array_remove_index (list->attributes, i);
+ i--; /* Need to look at this index again */
+ p--;
+
+ if (G_UNLIKELY (!new))
+ {
+ new = pango2_attr_list_new ();
+ new->attributes = g_ptr_array_new ();
+ }
+
+ g_ptr_array_add (new->attributes, tmp_attr);
+ }
+ }
+
+ return new;
+}
+
+/* }}} */
+/* {{{ Serialization */
+
+/* We serialize attribute lists to strings. The format
+ * is a comma-separated list of the attributes in the order
+ * in which they are in the list, with each attribute having
+ * this format:
+ *
+ * START END NICK VALUE
+ *
+ * Values that can contain a comma, such as font descriptions
+ * are quoted with "".
+ */
+
+static GType
+get_attr_value_type (Pango2AttrType type)
+{
+ switch ((int)type)
+ {
+ case PANGO2_ATTR_STYLE: return PANGO2_TYPE_STYLE;
+ case PANGO2_ATTR_WEIGHT: return PANGO2_TYPE_WEIGHT;
+ case PANGO2_ATTR_VARIANT: return PANGO2_TYPE_VARIANT;
+ case PANGO2_ATTR_STRETCH: return PANGO2_TYPE_STRETCH;
+ case PANGO2_ATTR_GRAVITY: return PANGO2_TYPE_GRAVITY;
+ case PANGO2_ATTR_GRAVITY_HINT: return PANGO2_TYPE_GRAVITY_HINT;
+ case PANGO2_ATTR_UNDERLINE: return PANGO2_TYPE_LINE_STYLE;
+ case PANGO2_ATTR_STRIKETHROUGH: return PANGO2_TYPE_LINE_STYLE;
+ case PANGO2_ATTR_OVERLINE: return PANGO2_TYPE_LINE_STYLE;
+ case PANGO2_ATTR_BASELINE_SHIFT: return PANGO2_TYPE_BASELINE_SHIFT;
+ case PANGO2_ATTR_FONT_SCALE: return PANGO2_TYPE_FONT_SCALE;
+ case PANGO2_ATTR_TEXT_TRANSFORM: return PANGO2_TYPE_TEXT_TRANSFORM;
+ default: return G_TYPE_INVALID;
+ }
+}
+
+static void
+append_enum_value (GString *str,
+ GType type,
+ int value)
+{
+ GEnumClass *enum_class;
+ GEnumValue *enum_value;
+
+ enum_class = g_type_class_ref (type);
+ enum_value = g_enum_get_value (enum_class, value);
+ g_type_class_unref (enum_class);
+
+ if (enum_value)
+ g_string_append_printf (str, " %s", enum_value->value_nick);
+ else
+ g_string_append_printf (str, " %d", value);
+}
+
+static void
+attr_print (GString *str,
+ Pango2Attribute *attr)
+{
+ const char *name;
+
+ name = pango2_attr_type_get_name (attr->type);
+ if (!name)
+ return;
+
+ g_string_append_printf (str, "%u %u %s", attr->start_index, attr->end_index, name);
+
+ switch (PANGO2_ATTR_VALUE_TYPE (attr))
+ {
+ case PANGO2_ATTR_VALUE_INT:
+ if (attr->type == PANGO2_ATTR_WEIGHT ||
+ attr->type == PANGO2_ATTR_STYLE ||
+ attr->type == PANGO2_ATTR_STRETCH ||
+ attr->type == PANGO2_ATTR_VARIANT ||
+ attr->type == PANGO2_ATTR_GRAVITY ||
+ attr->type == PANGO2_ATTR_GRAVITY_HINT ||
+ attr->type == PANGO2_ATTR_UNDERLINE ||
+ attr->type == PANGO2_ATTR_OVERLINE ||
+ attr->type == PANGO2_ATTR_BASELINE_SHIFT ||
+ attr->type == PANGO2_ATTR_FONT_SCALE ||
+ attr->type == PANGO2_ATTR_TEXT_TRANSFORM)
+ append_enum_value (str, get_attr_value_type (attr->type), attr->int_value);
+ else
+ g_string_append_printf (str, " %d", attr->int_value);
+ break;
+
+ case PANGO2_ATTR_VALUE_BOOLEAN:
+ g_string_append (str, attr->int_value ? " true" : " false");
+ break;
+
+ case PANGO2_ATTR_VALUE_STRING:
+ g_string_append_printf (str, " \"%s\"", attr->str_value);
+ break;
+
+ case PANGO2_ATTR_VALUE_LANGUAGE:
+ g_string_append_printf (str, " %s", pango2_language_to_string (attr->lang_value));
+ break;
+
+ case PANGO2_ATTR_VALUE_FLOAT:
+ {
+ char buf[20];
+ g_ascii_formatd (buf, 20, "%f", attr->double_value);
+ g_string_append_printf (str, " %s", buf);
+ }
+ break;
+
+ case PANGO2_ATTR_VALUE_FONT_DESC:
+ {
+ char *s = pango2_font_description_to_string (attr->font_value);
+ g_string_append_printf (str, " \"%s\"", s);
+ g_free (s);
+ }
+ break;
+
+ case PANGO2_ATTR_VALUE_COLOR:
+ {
+ char *s = pango2_color_to_string (&attr->color_value);
+ g_string_append_printf (str, " %s", s);
+ g_free (s);
+ }
+ break;
+
+ case PANGO2_ATTR_VALUE_POINTER:
+ {
+ char *s = pango2_attr_value_serialize (attr);
+ g_string_append_printf (str, " %s", s);
+ g_free (s);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/**
+ * pango2_attr_list_to_string:
+ * @list: a `Pango2AttrList`
+ *
+ * Serializes a `Pango2AttrList` to a string.
+ *
+ * No guarantees are made about the format of the string,
+ * it may change between Pango2 versions.
+ *
+ * The intended use of this function is testing and
+ * debugging. The format is not meant as a permanent
+ * storage format.
+ *
+ * Returns: (transfer full): a newly allocated string
+ */
+char *
+pango2_attr_list_to_string (Pango2AttrList *list)
+{
+ GString *s;
+
+ s = g_string_new ("");
+
+ if (list->attributes)
+ for (int i = 0; i < list->attributes->len; i++)
+ {
+ Pango2Attribute *attr = g_ptr_array_index (list->attributes, i);
+
+ if (i > 0)
+ g_string_append (s, "\n");
+ attr_print (s, attr);
+ }
+
+ return g_string_free (s, FALSE);
+}
+
+static Pango2AttrType
+get_attr_type_by_nick (const char *nick,
+ int len)
+{
+ GEnumClass *enum_class;
+
+ enum_class = g_type_class_ref (pango2_attr_type_get_type ());
+ for (GEnumValue *ev = enum_class->values; ev->value_name; ev++)
+ {
+ if (ev->value_nick && strncmp (ev->value_nick, nick, len) == 0)
+ {
+ g_type_class_unref (enum_class);
+ return (Pango2AttrType) ev->value;
+ }
+ }
+
+ g_type_class_unref (enum_class);
+ return PANGO2_ATTR_INVALID;
+}
+
+static int
+get_attr_value (Pango2AttrType type,
+ const char *str,
+ int len)
+{
+ GEnumClass *enum_class;
+ char *endp;
+ int value;
+
+ enum_class = g_type_class_ref (get_attr_value_type (type));
+ for (GEnumValue *ev = enum_class->values; ev->value_name; ev++)
+ {
+ if (ev->value_nick && strncmp (ev->value_nick, str, len) == 0)
+ {
+ g_type_class_unref (enum_class);
+ return ev->value;
+ }
+ }
+ g_type_class_unref (enum_class);
+
+ value = g_ascii_strtoll (str, &endp, 10);
+ if (endp - str == len)
+ return value;
+
+ return -1;
+}
+
+static gboolean
+is_valid_end_char (char c)
+{
+ return c == ',' || c == '\n' || c == '\0';
+}
+
+/**
+ * pango2_attr_list_from_string:
+ * @text: a string
+ *
+ * Deserializes a `Pango2AttrList` from a string.
+ *
+ * This is the counterpart to [method@Pango2.AttrList.to_string].
+ * See that functions for details about the format.
+ *
+ * Returns: (transfer full) (nullable): a new `Pango2AttrList`
+ */
+Pango2AttrList *
+pango2_attr_list_from_string (const char *text)
+{
+ Pango2AttrList *list;
+ const char *p;
+
+ g_return_val_if_fail (text != NULL, NULL);
+
+ list = pango2_attr_list_new ();
+
+ if (*text == '\0')
+ return list;
+
+ list->attributes = g_ptr_array_new ();
+
+ p = text + strspn (text, " \t\n");
+ while (*p)
+ {
+ char *endp;
+ gint64 start_index;
+ gint64 end_index;
+ char *str;
+ Pango2AttrType attr_type;
+ Pango2Attribute *attr;
+ Pango2Language *lang;
+ gint64 integer;
+ Pango2FontDescription *desc;
+ Pango2Color color;
+ double num;
+
+ start_index = g_ascii_strtoll (p, &endp, 10);
+ if (*endp != ' ')
+ goto fail;
+
+ p = endp + strspn (endp, " ");
+ if (!*p)
+ goto fail;
+
+ end_index = g_ascii_strtoll (p, &endp, 10);
+ if (*endp != ' ')
+ goto fail;
+
+ p = endp + strspn (endp, " ");
+
+ endp = (char *)p + strcspn (p, " ");
+ attr_type = get_attr_type_by_nick (p, endp - p);
+
+ p = endp + strspn (endp, " ");
+ if (*p == '\0')
+ goto fail;
+
+#define INT_ATTR(name,type) \
+ integer = g_ascii_strtoll (p, &endp, 10); \
+ if (!is_valid_end_char (*endp)) goto fail; \
+ attr = pango2_attr_##name##_new ((type)integer);
+
+#define BOOLEAN_ATTR(name,type) \
+ if (strncmp (p, "true", strlen ("true")) == 0) \
+ { \
+ integer = 1; \
+ endp = (char *)(p + strlen ("true")); \
+ } \
+ else if (strncmp (p, "false", strlen ("false")) == 0) \
+ { \
+ integer = 0; \
+ endp = (char *)(p + strlen ("false")); \
+ } \
+ else \
+ integer = g_ascii_strtoll (p, &endp, 10); \
+ if (!is_valid_end_char (*endp)) goto fail; \
+ attr = pango2_attr_##name##_new ((type)integer);
+
+#define ENUM_ATTR(name, type, min, max) \
+ endp = (char *)p + strcspn (p, ",\n"); \
+ integer = get_attr_value (attr_type, p, endp - p); \
+ attr = pango2_attr_##name##_new ((type) CLAMP (integer, min, max));
+
+#define FLOAT_ATTR(name) \
+ num = g_ascii_strtod (p, &endp); \
+ if (!is_valid_end_char (*endp)) goto fail; \
+ attr = pango2_attr_##name##_new ((float)num);
+
+#define COLOR_ATTR(name) \
+ endp = (char *)p + strcspn (p, ",\n"); \
+ if (!is_valid_end_char (*endp)) goto fail; \
+ str = g_strndup (p, endp - p); \
+ if (!pango2_color_parse (&color, str)) \
+ { \
+ g_free (str); \
+ goto fail; \
+ } \
+ attr = pango2_attr_##name##_new (&color); \
+ g_free (str);
+
+ switch (attr_type)
+ {
+ case PANGO2_ATTR_INVALID:
+ pango2_attr_list_unref (list);
+ return NULL;
+
+ case PANGO2_ATTR_LANGUAGE:
+ endp = (char *)p + strcspn (p, ",\n");
+ if (!is_valid_end_char (*endp)) goto fail;
+ str = g_strndup (p, endp - p);
+ lang = pango2_language_from_string (str);
+ attr = pango2_attr_language_new (lang);
+ g_free (str);
+ break;
+
+ case PANGO2_ATTR_FAMILY:
+ p++;
+ endp = strchr (p, '"');
+ if (!endp) goto fail;
+ str = g_strndup (p, endp - p);
+ attr = pango2_attr_family_new (str);
+ g_free (str);
+ endp++;
+ if (!is_valid_end_char (*endp)) goto fail;
+ break;
+
+ case PANGO2_ATTR_STYLE:
+ ENUM_ATTR(style, Pango2Style, PANGO2_STYLE_NORMAL, PANGO2_STYLE_ITALIC);
+ break;
+
+ case PANGO2_ATTR_WEIGHT:
+ ENUM_ATTR(weight, Pango2Weight, PANGO2_WEIGHT_THIN, PANGO2_WEIGHT_ULTRAHEAVY);
+ break;
+
+ case PANGO2_ATTR_VARIANT:
+ ENUM_ATTR(variant, Pango2Variant, PANGO2_VARIANT_NORMAL, PANGO2_VARIANT_TITLE_CAPS);
+ break;
+
+ case PANGO2_ATTR_STRETCH:
+ ENUM_ATTR(stretch, Pango2Stretch, PANGO2_STRETCH_ULTRA_CONDENSED, PANGO2_STRETCH_ULTRA_EXPANDED);
+ break;
+
+ case PANGO2_ATTR_SIZE:
+ INT_ATTR(size, int);
+ break;
+
+ case PANGO2_ATTR_FONT_DESC:
+ p++;
+ endp = strchr (p, '"');
+ if (!endp) goto fail;
+ str = g_strndup (p, endp - p);
+ desc = pango2_font_description_from_string (str);
+ attr = pango2_attr_font_desc_new (desc);
+ pango2_font_description_free (desc);
+ g_free (str);
+ endp++;
+ if (!is_valid_end_char (*endp)) goto fail;
+ break;
+
+ case PANGO2_ATTR_FOREGROUND:
+ COLOR_ATTR(foreground);
+ break;
+
+ case PANGO2_ATTR_BACKGROUND:
+ COLOR_ATTR(background);
+ break;
+
+ case PANGO2_ATTR_UNDERLINE:
+ ENUM_ATTR(underline, Pango2LineStyle, PANGO2_LINE_STYLE_NONE, PANGO2_LINE_STYLE_WAVY);
+ break;
+
+ case PANGO2_ATTR_UNDERLINE_POSITION:
+ ENUM_ATTR(underline_position, Pango2UnderlinePosition, PANGO2_UNDERLINE_POSITION_NORMAL, PANGO2_UNDERLINE_POSITION_UNDER);
+ break;
+
+ case PANGO2_ATTR_STRIKETHROUGH:
+ ENUM_ATTR(strikethrough, Pango2LineStyle, PANGO2_LINE_STYLE_NONE, PANGO2_LINE_STYLE_WAVY);
+ break;
+
+ case PANGO2_ATTR_RISE:
+ INT_ATTR(rise, int);
+ break;
+
+ case PANGO2_ATTR_SCALE:
+ FLOAT_ATTR(scale);
+ break;
+
+ case PANGO2_ATTR_FALLBACK:
+ BOOLEAN_ATTR(fallback, gboolean);
+ break;
+
+ case PANGO2_ATTR_LETTER_SPACING:
+ INT_ATTR(letter_spacing, int);
+ break;
+
+ case PANGO2_ATTR_UNDERLINE_COLOR:
+ COLOR_ATTR(underline_color);
+ break;
+
+ case PANGO2_ATTR_STRIKETHROUGH_COLOR:
+ COLOR_ATTR(strikethrough_color);
+ break;
+
+ case PANGO2_ATTR_ABSOLUTE_SIZE:
+ integer = g_ascii_strtoll (p, &endp, 10);
+ if (!is_valid_end_char (*endp)) goto fail;
+ attr = pango2_attr_size_new_absolute (integer);
+ break;
+
+ case PANGO2_ATTR_GRAVITY:
+ ENUM_ATTR(gravity, Pango2Gravity, PANGO2_GRAVITY_SOUTH, PANGO2_GRAVITY_WEST);
+ break;
+
+ case PANGO2_ATTR_FONT_FEATURES:
+ p++;
+ endp = strchr (p, '"');
+ if (!endp) goto fail;
+ str = g_strndup (p, endp - p);
+ attr = pango2_attr_font_features_new (str);
+ g_free (str);
+ endp++;
+ if (!is_valid_end_char (*endp)) goto fail;
+ break;
+
+ case PANGO2_ATTR_GRAVITY_HINT:
+ ENUM_ATTR(gravity_hint, Pango2GravityHint, PANGO2_GRAVITY_HINT_NATURAL, PANGO2_GRAVITY_HINT_LINE);
+ break;
+
+ case PANGO2_ATTR_ALLOW_BREAKS:
+ BOOLEAN_ATTR(allow_breaks, gboolean);
+ break;
+
+ case PANGO2_ATTR_SHOW:
+ INT_ATTR(show, Pango2ShowFlags);
+ break;
+
+ case PANGO2_ATTR_INSERT_HYPHENS:
+ BOOLEAN_ATTR(insert_hyphens, gboolean);
+ break;
+
+ case PANGO2_ATTR_OVERLINE:
+ ENUM_ATTR(overline, Pango2LineStyle, PANGO2_LINE_STYLE_NONE, PANGO2_LINE_STYLE_WAVY);
+ break;
+
+ case PANGO2_ATTR_OVERLINE_COLOR:
+ COLOR_ATTR(overline_color);
+ break;
+
+ case PANGO2_ATTR_LINE_HEIGHT:
+ FLOAT_ATTR(line_height);
+ break;
+
+ case PANGO2_ATTR_ABSOLUTE_LINE_HEIGHT:
+ integer = g_ascii_strtoll (p, &endp, 10);
+ if (!is_valid_end_char (*endp)) goto fail;
+ attr = pango2_attr_line_height_new_absolute (integer);
+ break;
+
+ case PANGO2_ATTR_TEXT_TRANSFORM:
+ ENUM_ATTR(text_transform, Pango2TextTransform, PANGO2_TEXT_TRANSFORM_NONE, PANGO2_TEXT_TRANSFORM_CAPITALIZE);
+ break;
+
+ case PANGO2_ATTR_WORD:
+ integer = g_ascii_strtoll (p, &endp, 10);
+ if (!is_valid_end_char (*endp)) goto fail;
+ attr = pango2_attr_word_new ();
+ break;
+
+ case PANGO2_ATTR_SENTENCE:
+ integer = g_ascii_strtoll (p, &endp, 10);
+ if (!is_valid_end_char (*endp)) goto fail;
+ attr = pango2_attr_sentence_new ();
+ break;
+
+ case PANGO2_ATTR_PARAGRAPH:
+ integer = g_ascii_strtoll (p, &endp, 10);
+ if (!is_valid_end_char (*endp)) goto fail;
+ attr = pango2_attr_paragraph_new ();
+ break;
+
+ case PANGO2_ATTR_BASELINE_SHIFT:
+ ENUM_ATTR(baseline_shift, Pango2BaselineShift, 0, G_MAXINT);
+ break;
+
+ case PANGO2_ATTR_FONT_SCALE:
+ ENUM_ATTR(font_scale, Pango2FontScale, PANGO2_FONT_SCALE_NONE, PANGO2_FONT_SCALE_SMALL_CAPS);
+ break;
+
+ case PANGO2_ATTR_LINE_SPACING:
+ INT_ATTR(line_spacing, int);
+ break;
+
+ case PANGO2_ATTR_SHAPE:
+ default:
+ g_assert_not_reached ();
+ }
+
+ attr->start_index = (guint)start_index;
+ attr->end_index = (guint)end_index;
+ g_ptr_array_add (list->attributes, attr);
+
+ p = endp;
+ if (*p)
+ {
+ if (*p == ',')
+ p++;
+ p += strspn (p, " \n");
+ }
+ }
+
+ goto success;
+
+fail:
+ pango2_attr_list_unref (list);
+ list = NULL;
+
+success:
+ return list;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-attr-list.h b/pango2/pango-attr-list.h
new file mode 100644
index 00000000..e51dc5ff
--- /dev/null
+++ b/pango2/pango-attr-list.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-attributes.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+
+typedef struct _Pango2AttrList Pango2AttrList;
+typedef struct _Pango2AttrIterator Pango2AttrIterator;
+
+#define PANGO2_TYPE_ATTR_LIST pango2_attr_list_get_type ()
+
+/**
+ * Pango2AttrList:
+ *
+ * A `Pango2AttrList` represents a list of attributes that apply to a section
+ * of text.
+ *
+ * The attributes in a `Pango2AttrList` are, in general, allowed to overlap in
+ * an arbitrary fashion. However, if the attributes are manipulated only through
+ * [method@Pango2.AttrList.change], the overlap between properties will meet
+ * stricter criteria.
+ */
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_attr_list_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2AttrList * pango2_attr_list_new (void);
+PANGO2_AVAILABLE_IN_ALL
+Pango2AttrList * pango2_attr_list_ref (Pango2AttrList *list);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attr_list_unref (Pango2AttrList *list);
+PANGO2_AVAILABLE_IN_ALL
+Pango2AttrList * pango2_attr_list_copy (Pango2AttrList *list);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attr_list_insert (Pango2AttrList *list,
+ Pango2Attribute *attr);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attr_list_insert_before (Pango2AttrList *list,
+ Pango2Attribute *attr);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attr_list_change (Pango2AttrList *list,
+ Pango2Attribute *attr);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attr_list_splice (Pango2AttrList *list,
+ Pango2AttrList *other,
+ int pos,
+ int len);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attr_list_update (Pango2AttrList *list,
+ int pos,
+ int remove,
+ int add);
+
+/**
+ * Pango2AttrFilterFunc:
+ * @attribute: a Pango2 attribute
+ * @user_data: user data passed to the function
+ *
+ * Callback to filter a list of attributes.
+ *
+ * Return value: `TRUE` if the attribute should be selected for
+ * filtering, `FALSE` otherwise.
+ */
+typedef gboolean (*Pango2AttrFilterFunc) (Pango2Attribute *attribute,
+ gpointer user_data);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2AttrList * pango2_attr_list_filter (Pango2AttrList *list,
+ Pango2AttrFilterFunc func,
+ gpointer data);
+
+PANGO2_AVAILABLE_IN_ALL
+GSList * pango2_attr_list_get_attributes (Pango2AttrList *list);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_attr_list_equal (Pango2AttrList *list,
+ Pango2AttrList *other_list);
+
+PANGO2_AVAILABLE_IN_ALL
+char * pango2_attr_list_to_string (Pango2AttrList *list);
+PANGO2_AVAILABLE_IN_ALL
+Pango2AttrList * pango2_attr_list_from_string (const char *text);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2AttrIterator * pango2_attr_list_get_iterator (Pango2AttrList *list);
+
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2AttrList, pango2_attr_list_unref)
+
+G_END_DECLS
diff --git a/pango2/pango-attr-private.h b/pango2/pango-attr-private.h
new file mode 100644
index 00000000..f8152f8d
--- /dev/null
+++ b/pango2/pango-attr-private.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-attr.h"
+
+struct _Pango2Attribute
+{
+ guint type;
+ guint start_index;
+ guint end_index;
+ union {
+ char *str_value;
+ int int_value;
+ gboolean boolean_value;
+ double double_value;
+ Pango2Color color_value;
+ Pango2Language *lang_value;
+ Pango2FontDescription *font_value;
+ gpointer pointer_value;
+ };
+};
+
+char * pango2_attr_value_serialize (Pango2Attribute *attr);
diff --git a/pango2/pango-attr.c b/pango2/pango-attr.c
new file mode 100644
index 00000000..5f7876d3
--- /dev/null
+++ b/pango2/pango-attr.c
@@ -0,0 +1,761 @@
+/* Pango2
+ * pango-attr.c: Attributed text
+ *
+ * Copyright (C) 2000-2002 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.
+ */
+
+#include "config.h"
+#include <string.h>
+
+#include "pango-attr-private.h"
+#include "pango-attributes-private.h"
+#include "pango-impl-utils.h"
+
+/**
+ * Pango2Attribute:
+ *
+ * A `Pango2Attribute` structure associates a named attribute with
+ * a certain range of a text.
+ *
+ * Attributes can have basic types, such as int, float, boolean
+ * `Pango2Color`, `Pango2FontDescription or `Pango2Language`. It is
+ * also possible to define new, custom attributes.
+ *
+ * By default, an attribute will have an all-inclusive range of
+ * `[0,G_MAXUINT]`.
+ */
+
+/* {{{ Generic attribute code */
+
+G_LOCK_DEFINE_STATIC (attr_type);
+static GArray *attr_type;
+
+#define MIN_CUSTOM 1000
+
+typedef struct _Pango2AttrClass Pango2AttrClass;
+
+struct _Pango2AttrClass {
+ guint type;
+ const char *name;
+ Pango2AttrDataCopyFunc copy;
+ GDestroyNotify destroy;
+ GEqualFunc equal;
+ Pango2AttrDataSerializeFunc serialize;
+};
+
+static const char *
+get_attr_type_nick (Pango2AttrType type)
+{
+ GEnumClass *enum_class;
+ GEnumValue *enum_value;
+
+ enum_class = g_type_class_ref (pango2_attr_type_get_type ());
+ enum_value = g_enum_get_value (enum_class, type);
+ g_type_class_unref (enum_class);
+
+ return enum_value->value_nick;
+}
+
+static gboolean
+is_valid_attr_type (guint type)
+{
+ switch (type)
+ {
+ case PANGO2_ATTR_INVALID:
+ return FALSE;
+ case PANGO2_ATTR_LANGUAGE:
+ case PANGO2_ATTR_FAMILY:
+ case PANGO2_ATTR_STYLE:
+ case PANGO2_ATTR_WEIGHT:
+ case PANGO2_ATTR_VARIANT:
+ case PANGO2_ATTR_STRETCH:
+ case PANGO2_ATTR_SIZE:
+ case PANGO2_ATTR_FONT_DESC:
+ case PANGO2_ATTR_FOREGROUND:
+ case PANGO2_ATTR_BACKGROUND:
+ case PANGO2_ATTR_UNDERLINE:
+ case PANGO2_ATTR_STRIKETHROUGH:
+ case PANGO2_ATTR_RISE:
+ case PANGO2_ATTR_SCALE:
+ case PANGO2_ATTR_FALLBACK:
+ case PANGO2_ATTR_LETTER_SPACING:
+ case PANGO2_ATTR_UNDERLINE_COLOR:
+ case PANGO2_ATTR_STRIKETHROUGH_COLOR:
+ case PANGO2_ATTR_ABSOLUTE_SIZE:
+ case PANGO2_ATTR_GRAVITY:
+ case PANGO2_ATTR_GRAVITY_HINT:
+ case PANGO2_ATTR_FONT_FEATURES:
+ case PANGO2_ATTR_ALLOW_BREAKS:
+ case PANGO2_ATTR_SHOW:
+ case PANGO2_ATTR_INSERT_HYPHENS:
+ case PANGO2_ATTR_OVERLINE:
+ case PANGO2_ATTR_OVERLINE_COLOR:
+ case PANGO2_ATTR_LINE_HEIGHT:
+ case PANGO2_ATTR_ABSOLUTE_LINE_HEIGHT:
+ case PANGO2_ATTR_TEXT_TRANSFORM:
+ case PANGO2_ATTR_WORD:
+ case PANGO2_ATTR_SENTENCE:
+ case PANGO2_ATTR_BASELINE_SHIFT:
+ case PANGO2_ATTR_FONT_SCALE:
+ case PANGO2_ATTR_SHAPE:
+ return TRUE;
+ default:
+ if (!attr_type)
+ return FALSE;
+ else
+ {
+ gboolean result = FALSE;
+
+ G_LOCK (attr_type);
+
+ for (int i = 0; i < attr_type->len; i++)
+ {
+ Pango2AttrClass *class = &g_array_index (attr_type, Pango2AttrClass, i);
+ if (class->type == type)
+ {
+ result = TRUE;
+ break;
+ }
+ }
+
+ G_UNLOCK (attr_type);
+
+ return result;
+ }
+ }
+}
+
+/**
+ * pango2_attr_type_register:
+ * @name: (nullable): an identifier for the type
+ * @value_type: the `Pango2AttrValueType` for the attribute
+ * @affects: `Pango2AttrAffects` flags for the attribute
+ * @merge: `Pango2AttrMerge` flags for the attribute
+ * @copy: (scope forever) (nullable): function to copy the data of an attribute
+ * of this type
+ * @destroy: (scope forever) (nullable): function to free the data of an attribute
+ * of this type
+ * @equal: (scope forever) (nullable): function to compare the data of two attributes
+ * of this type
+ * @serialize: (scope forever) (nullable): function to serialize the data
+ * of an attribute of this type
+ *
+ * Register a new attribute type.
+ *
+ * The attribute type name can be accessed later
+ * by using [func@Pango2.AttrType.get_name].
+ *
+ * The callbacks are only needed if @type is `PANGO2_ATTR_VALUE_POINTER`,
+ * Pango2 knows how to handle other value types and will only use the
+ * callbacks for generic pointer values.
+ *
+ * If @name and @serialize are provided, they will be used
+ * to serialize attributes of this type.
+ *
+ * To create attributes with the new type, use [ctor@Pango2.Attribute.new].
+ *
+ * Return value: the attribute type ID
+ */
+guint
+pango2_attr_type_register (const char *name,
+ Pango2AttrValueType value_type,
+ Pango2AttrAffects affects,
+ Pango2AttrMerge merge,
+ Pango2AttrDataCopyFunc copy,
+ GDestroyNotify destroy,
+ GEqualFunc equal,
+ Pango2AttrDataSerializeFunc serialize)
+{
+ static guint current_id = MIN_CUSTOM; /* MT-safe */
+ Pango2AttrClass class;
+
+ G_LOCK (attr_type);
+
+ class.type = value_type | (affects << 8) | (merge << 12) | (current_id << 16);
+ current_id++;
+
+ class.copy = copy;
+ class.destroy = destroy;
+ class.equal = equal;
+ class.serialize = serialize;
+
+ if (name)
+ class.name = g_intern_string (name);
+
+ if (attr_type == NULL)
+ attr_type = g_array_new (FALSE, FALSE, sizeof (Pango2AttrClass));
+
+ g_array_append_val (attr_type, class);
+
+ G_UNLOCK (attr_type);
+
+ return class.type;
+}
+
+/**
+ * pango2_attr_type_get_name:
+ * @type: an attribute type ID to fetch the name for
+ *
+ * Fetches the attribute type name.
+ *
+ * The attribute type name is the string passed in
+ * when registering the type using
+ * [func@Pango2.AttrType.register].
+ *
+ * The returned value is an interned string (see
+ * [func@GLib.intern_string] for what that means) that should
+ * not be modified or freed.
+ *
+ * Return value: (nullable): the type ID name (which
+ * may be %NULL), or %NULL if @type is a built-in Pango2
+ * attribute type or invalid.
+ */
+const char *
+pango2_attr_type_get_name (guint type)
+{
+ const char *result = NULL;
+
+ if ((type >> 16) < MIN_CUSTOM)
+ return get_attr_type_nick (type);
+
+ G_LOCK (attr_type);
+
+ for (int i = 0; i < attr_type->len; i++)
+ {
+ Pango2AttrClass *class = &g_array_index (attr_type, Pango2AttrClass, i);
+ if (class->type == type)
+ {
+ result = class->name;
+ break;
+ }
+ }
+
+ G_UNLOCK (attr_type);
+
+ return result;
+}
+
+/**
+ * pango2_attribute_copy:
+ * @attr: a `Pango2Attribute`
+ *
+ * Make a copy of an attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy].
+ */
+Pango2Attribute *
+pango2_attribute_copy (const Pango2Attribute *attr)
+{
+ Pango2Attribute *result;
+
+ g_return_val_if_fail (attr != NULL, NULL);
+
+ result = g_slice_dup (Pango2Attribute, attr);
+
+ switch (PANGO2_ATTR_VALUE_TYPE (attr))
+ {
+ case PANGO2_ATTR_VALUE_STRING:
+ result->str_value = g_strdup (attr->str_value);
+ break;
+
+ case PANGO2_ATTR_VALUE_FONT_DESC:
+ result->font_value = pango2_font_description_copy (attr->font_value);
+ break;
+
+ case PANGO2_ATTR_VALUE_POINTER:
+ if (attr->type == PANGO2_ATTR_SHAPE)
+ {
+ ShapeData *shape_data;
+
+ shape_data = g_memdup2 (attr->pointer_value, sizeof (ShapeData));
+ if (shape_data->copy)
+ shape_data->data = shape_data->copy (shape_data->data);
+
+ result->pointer_value = shape_data;
+ }
+ else
+ {
+ Pango2AttrDataCopyFunc copy = NULL;
+
+ G_LOCK (attr_type);
+
+ g_assert (attr_type != NULL);
+
+ for (int i = 0; i < attr_type->len; i++)
+ {
+ Pango2AttrClass *class = &g_array_index (attr_type, Pango2AttrClass, i);
+ if (class->type == attr->type)
+ {
+ copy = class->copy;
+ break;
+ }
+ }
+
+ G_UNLOCK (attr_type);
+
+ if (copy)
+ result->pointer_value = copy (attr->pointer_value);
+ }
+ break;
+
+ case PANGO2_ATTR_VALUE_INT:
+ case PANGO2_ATTR_VALUE_BOOLEAN:
+ case PANGO2_ATTR_VALUE_FLOAT:
+ case PANGO2_ATTR_VALUE_COLOR:
+ case PANGO2_ATTR_VALUE_LANGUAGE:
+ default: ;
+ }
+
+ return result;
+}
+
+/**
+ * pango2_attribute_destroy:
+ * @attr: a `Pango2Attribute`.
+ *
+ * Destroy a `Pango2Attribute` and free all associated memory.
+ */
+void
+pango2_attribute_destroy (Pango2Attribute *attr)
+{
+ g_return_if_fail (attr != NULL);
+
+ switch (PANGO2_ATTR_VALUE_TYPE (attr))
+ {
+ case PANGO2_ATTR_VALUE_STRING:
+ g_free (attr->str_value);
+ break;
+
+ case PANGO2_ATTR_VALUE_FONT_DESC:
+ pango2_font_description_free (attr->font_value);
+ break;
+
+ case PANGO2_ATTR_VALUE_POINTER:
+ if (attr->type == PANGO2_ATTR_SHAPE)
+ {
+ ShapeData *shape_data = attr->pointer_value;
+
+ if (shape_data->destroy)
+ shape_data->destroy (shape_data->data);
+
+ g_free (shape_data);
+ }
+ else
+ {
+ GDestroyNotify destroy = NULL;
+
+ G_LOCK (attr_type);
+
+ g_assert (attr_type != NULL);
+
+ for (int i = 0; i < attr_type->len; i++)
+ {
+ Pango2AttrClass *class = &g_array_index (attr_type, Pango2AttrClass, i);
+ if (class->type == attr->type)
+ {
+ destroy = class->destroy;
+ break;
+ }
+ }
+
+ G_UNLOCK (attr_type);
+
+ if (destroy)
+ destroy (attr->pointer_value);
+ }
+ break;
+
+ case PANGO2_ATTR_VALUE_INT:
+ case PANGO2_ATTR_VALUE_BOOLEAN:
+ case PANGO2_ATTR_VALUE_FLOAT:
+ case PANGO2_ATTR_VALUE_COLOR:
+ case PANGO2_ATTR_VALUE_LANGUAGE:
+ default: ;
+ }
+
+ g_slice_free (Pango2Attribute, attr);
+}
+
+G_DEFINE_BOXED_TYPE (Pango2Attribute, pango2_attribute,
+ pango2_attribute_copy,
+ pango2_attribute_destroy);
+
+/**
+ * pango2_attribute_equal:
+ * @attr1: a `Pango2Attribute`
+ * @attr2: another `Pango2Attribute`
+ *
+ * Compare two attributes for equality.
+ *
+ * This compares only the actual value of the two
+ * attributes and not the ranges that the attributes
+ * apply to.
+ *
+ * Return value: %TRUE if the two attributes have the same value
+ */
+gboolean
+pango2_attribute_equal (const Pango2Attribute *attr1,
+ const Pango2Attribute *attr2)
+{
+ g_return_val_if_fail (attr1 != NULL, FALSE);
+ g_return_val_if_fail (attr2 != NULL, FALSE);
+
+ if (attr1->type != attr2->type)
+ return FALSE;
+
+ switch (PANGO2_ATTR_VALUE_TYPE (attr1))
+ {
+ case PANGO2_ATTR_VALUE_STRING:
+ return strcmp (attr1->str_value, attr2->str_value) == 0;
+
+ case PANGO2_ATTR_VALUE_INT:
+ return attr1->int_value == attr2->int_value;
+
+ case PANGO2_ATTR_VALUE_BOOLEAN:
+ return attr1->boolean_value == attr2->boolean_value;
+
+ case PANGO2_ATTR_VALUE_FLOAT:
+ return attr1->double_value == attr2->double_value;
+
+ case PANGO2_ATTR_VALUE_COLOR:
+ return memcmp (&attr1->color_value, &attr2->color_value, sizeof (Pango2Color)) == 0;
+
+ case PANGO2_ATTR_VALUE_LANGUAGE:
+ return attr1->lang_value == attr2->lang_value;
+
+ case PANGO2_ATTR_VALUE_FONT_DESC:
+ return pango2_font_description_equal (attr1->font_value, attr2->font_value);
+
+ case PANGO2_ATTR_VALUE_POINTER:
+ {
+ GEqualFunc equal = NULL;
+
+ G_LOCK (attr_type);
+
+ g_assert (attr_type != NULL);
+
+ for (int i = 0; i < attr_type->len; i++)
+ {
+ Pango2AttrClass *class = &g_array_index (attr_type, Pango2AttrClass, i);
+ if (class->type == attr1->type)
+ {
+ equal = class->equal;
+ break;
+ }
+ }
+
+ G_UNLOCK (attr_type);
+
+ if (equal)
+ return equal (attr1->pointer_value, attr2->pointer_value);
+
+ return attr1->pointer_value == attr2->pointer_value;
+ }
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/**
+ * pango2_attribute_new:
+ * @type: the attribute type
+ * @value: pointer to the value to be copied, must be of the right type
+ *
+ * Creates a new attribute for the given type.
+ *
+ * The type must be one of the `Pango2AttrType` values, or
+ * have been registered with [func@Pango2.AttrType.register].
+ *
+ * Pango2 will initialize @start_index and @end_index to an
+ * all-inclusive range of `[0,G_MAXUINT]`. The value is copied.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attribute_new (guint type,
+ gconstpointer value)
+{
+ Pango2Attribute attr;
+
+ g_return_val_if_fail (is_valid_attr_type (type), NULL);
+
+ attr.type = type;
+ attr.start_index = PANGO2_ATTR_INDEX_FROM_TEXT_BEGINNING;
+ attr.end_index = PANGO2_ATTR_INDEX_TO_TEXT_END;
+
+ switch (PANGO2_ATTR_TYPE_VALUE_TYPE (type))
+ {
+ case PANGO2_ATTR_VALUE_STRING:
+ attr.str_value = (char *)value;
+ break;
+ case PANGO2_ATTR_VALUE_INT:
+ attr.int_value = *(int *)value;
+ break;
+ case PANGO2_ATTR_VALUE_BOOLEAN:
+ attr.boolean_value = *(gboolean *)value;
+ break;
+ case PANGO2_ATTR_VALUE_FLOAT:
+ attr.double_value = *(double *)value;
+ break;
+ case PANGO2_ATTR_VALUE_COLOR:
+ attr.color_value = *(Pango2Color *)value;
+ break;
+ case PANGO2_ATTR_VALUE_LANGUAGE:
+ attr.lang_value = (Pango2Language *)value;
+ break;
+ case PANGO2_ATTR_VALUE_FONT_DESC:
+ attr.font_value = (Pango2FontDescription *)value;
+ break;
+ case PANGO2_ATTR_VALUE_POINTER:
+ attr.pointer_value = (gpointer)value;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Copy the value */
+ return pango2_attribute_copy (&attr);
+}
+
+/**
+ * pango2_attribute_type:
+ * @attribute: a `Pango2Attribute`
+ *
+ * Returns the type of @attribute, either a
+ * value from the [enum@Pango2.AttrType] enumeration
+ * or a registered custom type.
+ *
+ * Return value: the type of `Pango2Attribute`
+ */
+guint
+pango2_attribute_type (const Pango2Attribute *attribute)
+{
+ return attribute->type;
+}
+
+/* }}} */
+ /* {{{ Private API */
+
+char *
+pango2_attr_value_serialize (Pango2Attribute *attr)
+{
+ Pango2AttrDataSerializeFunc serialize = NULL;
+
+ G_LOCK (attr_type);
+
+ g_assert (attr_type != NULL);
+
+ for (int i = 0; i < attr_type->len; i++)
+ {
+ Pango2AttrClass *class = &g_array_index (attr_type, Pango2AttrClass, i);
+ if (class->type == attr->type)
+ {
+ serialize = class->serialize;
+ break;
+ }
+ }
+
+ G_UNLOCK (attr_type);
+
+ if (serialize)
+ return serialize (attr->pointer_value);
+
+ return g_strdup_printf ("%p", attr->pointer_value);
+}
+
+/* }}} */
+/* {{{ Binding Helpers */
+
+/**
+ * pango2_attribute_set_range:
+ * @attribute: a `Pango2Attribute`
+ * @start_index: start index
+ * @end_index: end index
+ *
+ * Sets the range of the attribute.
+ */
+void
+pango2_attribute_set_range (Pango2Attribute *attribute,
+ guint start_index,
+ guint end_index)
+{
+ attribute->start_index = start_index;
+ attribute->end_index = end_index;
+}
+
+/**
+ * pango2_attribute_get_range:
+ * @attribute: a `Pango2Attribute`
+ * @start_index: (out caller-allocates): return location for start index
+ * @end_index: (out caller-allocates): return location for end index
+ *
+ * Gets the range of the attribute.
+ */
+void
+pango2_attribute_get_range (Pango2Attribute *attribute,
+ guint *start_index,
+ guint *end_index)
+{
+ *start_index = attribute->start_index;
+ *end_index = attribute->end_index;
+}
+
+/**
+ * pango2_attribute_get_string:
+ * @attribute: a `Pango2Attribute`
+ *
+ * Gets the value of an attribute whose value type is string.
+ *
+ * Returns: (nullable): the string value
+ */
+const char *
+pango2_attribute_get_string (Pango2Attribute *attribute)
+{
+ if (PANGO2_ATTR_VALUE_TYPE (attribute) != PANGO2_ATTR_VALUE_STRING)
+ return NULL;
+
+ return attribute->str_value;
+}
+
+/**
+ * pango2_attribute_get_language:
+ * @attribute: a `Pango2Attribute`
+ *
+ * Gets the value of an attribute whose value type is `Pango2Language`.
+ *
+ * Returns: (nullable): the language value
+ */
+Pango2Language *
+pango2_attribute_get_language (Pango2Attribute *attribute)
+{
+ if (PANGO2_ATTR_VALUE_TYPE (attribute) != PANGO2_ATTR_VALUE_LANGUAGE)
+ return NULL;
+
+ return attribute->lang_value;
+}
+
+/**
+ * pango2_attribute_get_int:
+ * @attribute: a `Pango2Attribute`
+ *
+ * Gets the value of an attribute whose value type is `int`.
+ *
+ * Returns: the integer value, or 0
+ */
+int
+pango2_attribute_get_int (Pango2Attribute *attribute)
+{
+ if (PANGO2_ATTR_VALUE_TYPE (attribute) != PANGO2_ATTR_VALUE_INT)
+ return 0;
+
+ return attribute->int_value;
+}
+
+/**
+ * pango2_attribute_get_boolean:
+ * @attribute: a `Pango2Attribute`
+ *
+ * Gets the value of an attribute whose value type is `gboolean`.
+ *
+ * Returns: the boolean value, or `FALSE`
+ */
+gboolean
+pango2_attribute_get_boolean (Pango2Attribute *attribute)
+{
+ if (PANGO2_ATTR_VALUE_TYPE (attribute) != PANGO2_ATTR_VALUE_BOOLEAN)
+ return FALSE;
+
+ return attribute->boolean_value;
+}
+
+/**
+ * pango2_attribute_get_float:
+ * @attribute: a `Pango2Attribute`
+ *
+ * Gets the value of an attribute whose value type is `double`.
+ *
+ * Returns: the float value, or 0
+ */
+double
+pango2_attribute_get_float (Pango2Attribute *attribute)
+{
+ if (PANGO2_ATTR_VALUE_TYPE (attribute) != PANGO2_ATTR_VALUE_FLOAT)
+ return 0.;
+
+ return attribute->double_value;
+}
+
+/**
+ * pango2_attribute_get_color:
+ * @attribute: a `Pango2Attribute`
+ *
+ * Gets the value of an attribute whose value type is `Pango2Color`.
+ *
+ * Returns: (nullable): pointer to the color value
+ */
+Pango2Color *
+pango2_attribute_get_color (Pango2Attribute *attribute)
+{
+ if (PANGO2_ATTR_VALUE_TYPE (attribute) != PANGO2_ATTR_VALUE_COLOR)
+ return NULL;
+
+ return &attribute->color_value;
+}
+
+/**
+ * pango2_attribute_get_font_desc:
+ * @attribute: a `Pango2Attribute`
+ *
+ * Gets the value of an attribute whose value type is `Pango2FontDescription`.
+ *
+ * Returns: (nullable): the font description
+ */
+Pango2FontDescription *
+pango2_attribute_get_font_desc (Pango2Attribute *attribute)
+{
+ if (PANGO2_ATTR_VALUE_TYPE (attribute) != PANGO2_ATTR_VALUE_FONT_DESC)
+ return NULL;
+
+ return attribute->font_value;
+}
+
+/**
+ * pango2_attribute_get_pointer:
+ * @attribute: a `Pango2Attribute`
+ *
+ * Gets the value of an attribute whose value type is `gpointer`.
+ *
+ * Returns: (nullable): the pointer value
+ */
+gpointer
+pango2_attribute_get_pointer (Pango2Attribute *attribute)
+{
+ if (PANGO2_ATTR_VALUE_TYPE (attribute) != PANGO2_ATTR_VALUE_POINTER)
+ return NULL;
+
+ return attribute->pointer_value;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-attr.h b/pango2/pango-attr.h
new file mode 100644
index 00000000..1dda2924
--- /dev/null
+++ b/pango2/pango-attr.h
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-font.h>
+#include <pango2/pango-color.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+
+typedef struct _Pango2Attribute Pango2Attribute;
+
+/**
+ * Pango2AttrValueType:
+ * @PANGO2_ATTR_VALUE_STRING: A string
+ * @PANGO2_ATTR_VALUE_INT: An integer
+ * @PANGO2_ATTR_VALUE_BOOLEAN: A boolean
+ * @PANGO2_ATTR_VALUE_FLOAT: A floating point number
+ * @PANGO2_ATTR_VALUE_COLOR: A `Pango2Color`
+ * @PANGO2_ATTR_VALUE_LANGUAGE: A `Pango2Language`
+ * @PANGO2_ATTR_VALUE_FONT_DESC: A `Pango2FontDescription`
+ * @PANGO2_ATTR_VALUE_POINTER: A generic pointer
+ *
+ * `Pango2AttrValueType` enumerates the types of values
+ * that a `Pango2Attribute` can contain.
+ *
+ * The `Pango2AttrValueType` of a `Pango2Attribute` is part
+ * of its type, and can be obtained with `PANGO2_ATTR_VALUE_TYPE()`.
+ */
+typedef enum
+{
+ PANGO2_ATTR_VALUE_STRING,
+ PANGO2_ATTR_VALUE_INT,
+ PANGO2_ATTR_VALUE_BOOLEAN,
+ PANGO2_ATTR_VALUE_FLOAT,
+ PANGO2_ATTR_VALUE_COLOR,
+ PANGO2_ATTR_VALUE_LANGUAGE,
+ PANGO2_ATTR_VALUE_FONT_DESC,
+ PANGO2_ATTR_VALUE_POINTER
+} Pango2AttrValueType;
+
+/**
+ * Pango2AttrAffects:
+ * @PANGO2_ATTR_AFFECTS_NONE: The attribute does not affect rendering
+ * @PANGO2_ATTR_AFFECTS_ITEMIZATION: The attribute affecs itemization
+ * @PANGO2_ATTR_AFFECTS_BREAKING: The attribute affects `Pango2LogAttr` determination
+ * @PANGO2_ATTR_AFFECTS_SHAPING: The attribute affects shaping
+ * @PANGO2_ATTR_AFFECTS_RENDERING: The attribute affects rendering
+ *
+ * A `Pango2AttrAffects` value indicates what part of Pango2's processing
+ * pipeline is affected by an attribute.
+ *
+ * Marking an attribute with `PANGO2_ATTR_AFFECTS_ITEMIZATION` ensures
+ * that the attribute values are constant across items.
+ */
+typedef enum
+{
+ PANGO2_ATTR_AFFECTS_NONE = 0,
+ PANGO2_ATTR_AFFECTS_ITEMIZATION = 1 << 0,
+ PANGO2_ATTR_AFFECTS_BREAKING = 1 << 1,
+ PANGO2_ATTR_AFFECTS_SHAPING = 1 << 2,
+ PANGO2_ATTR_AFFECTS_RENDERING = 1 << 3
+} Pango2AttrAffects;
+
+/**
+ * Pango2AttrMerge:
+ * @PANGO2_ATTR_MERGE_OVERRIDES: Only the attribute with the narrowest range is used
+ * @PANGO2_ATTR_MERGE_ACCUMULATES: All attributes with overlapping range are kept
+ *
+ * A `Pango2AttrMerge` value indicates how overlapping attribute values
+ * should be reconciled to determine the effective attribute value.
+ *
+ * These options influence the @extra_attrs returned by
+ * [method@Pango2.AttrIterator.get_font].
+ */
+typedef enum
+{
+ PANGO2_ATTR_MERGE_OVERRIDES,
+ PANGO2_ATTR_MERGE_ACCUMULATES
+} Pango2AttrMerge;
+
+/**
+ * PANGO2_ATTR_TYPE_VALUE_TYPE:
+ * @type: an attribute type
+ *
+ * Extracts the `Pango2AttrValueType` from an attribute type.
+ */
+#define PANGO2_ATTR_TYPE_VALUE_TYPE(type) ((Pango2AttrValueType)((type) & 0xff))
+
+/**
+ * PANGO2_ATTR_TYPE_AFFECTS:
+ * @type: an attribute type
+ *
+ * Extracts the `Pango2AttrAffects` flags from an attribute type.
+ */
+#define PANGO2_ATTR_TYPE_AFFECTS(type) ((Pango2AttrAffects)(((type) >> 8) & 0xf))
+
+/**
+ * PANGO2_ATTR_TYPE_MERGE:
+ * @type: an attribute type
+ *
+ * Extracts the `Pango2AttrMerge` flags from an attribute type.
+ */
+#define PANGO2_ATTR_TYPE_MERGE(type) ((Pango2AttrMerge)(((type) >> 12) & 0xf))
+
+/**
+ * PANGO2_ATTR_VALUE_TYPE:
+ * @attr: a `Pango2Attribute`
+ *
+ * Obtains the `Pango2AttrValueType of a `Pango2Attribute`.
+ */
+#define PANGO2_ATTR_VALUE_TYPE(attr) PANGO2_ATTR_TYPE_VALUE_TYPE (pango2_attribute_type (attr))
+
+/**
+ * PANGO2_ATTR_AFFECTS:
+ * @attr: a `Pango2Attribute`
+ *
+ * Obtains the `Pango2AttrAffects` flags of a `Pango2Attribute`.
+ */
+#define PANGO2_ATTR_AFFECTS(attr) PANGO2_ATTR_TYPE_AFFECTS (pango2_attribute_type (attr))
+
+/**
+ * PANGO2_ATTR_MERGE:
+ * @attr: a `Pango2Attribute`
+ *
+ * Obtains the `Pango2AttrMerge` flags of a `Pango2Attribute`.
+ */
+#define PANGO2_ATTR_MERGE(attr) PANGO2_ATTR_TYPE_MERGE (pango2_attribute_type (attr))
+
+/**
+ * PANGO2_ATTR_INDEX_FROM_TEXT_BEGINNING:
+ *
+ * Value for @start_index in `Pango2Attribute` that indicates
+ * the beginning of the text.
+ */
+#define PANGO2_ATTR_INDEX_FROM_TEXT_BEGINNING ((guint)0)
+
+/**
+ * PANGO2_ATTR_INDEX_TO_TEXT_END: (value 4294967295)
+ *
+ * Value for @end_index in `Pango2Attribute` that indicates
+ * the end of the text.
+ */
+#define PANGO2_ATTR_INDEX_TO_TEXT_END ((guint)(G_MAXUINT + 0))
+
+/**
+ * Pango2AttrDataCopyFunc:
+ * @value: value to copy
+ *
+ * Callback to duplicate the value of an attribute.
+ *
+ * Return value: new copy of @value.
+ **/
+typedef gpointer (*Pango2AttrDataCopyFunc) (gconstpointer value);
+
+/**
+ * Pango2AttrDataSerializeFunc:
+ * @value: value to serialize
+ *
+ * Callback to serialize the value of an attribute.
+ *
+ * Return value: a newly allocated string holding the serialization of @value
+ */
+typedef char * (*Pango2AttrDataSerializeFunc) (gconstpointer value);
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_attribute_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+guint pango2_attr_type_register (const char *name,
+ Pango2AttrValueType value_type,
+ Pango2AttrAffects affects,
+ Pango2AttrMerge merge,
+ Pango2AttrDataCopyFunc copy,
+ GDestroyNotify destroy,
+ GEqualFunc equal,
+ Pango2AttrDataSerializeFunc serialize);
+
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_attr_type_get_name (guint type) G_GNUC_CONST;
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attribute_copy (const Pango2Attribute *attr);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attribute_destroy (Pango2Attribute *attr);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_attribute_equal (const Pango2Attribute *attr1,
+ const Pango2Attribute *attr2) G_GNUC_PURE;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attribute_new (guint type,
+ gconstpointer value);
+
+PANGO2_AVAILABLE_IN_ALL
+guint pango2_attribute_type (const Pango2Attribute *attribute);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attribute_set_range (Pango2Attribute *attribute,
+ guint start_index,
+ guint end_index);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attribute_get_range (Pango2Attribute *attribute,
+ guint *start_index,
+ guint *end_index);
+
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_attribute_get_string (Pango2Attribute *attribute);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Language * pango2_attribute_get_language (Pango2Attribute *attribute);
+PANGO2_AVAILABLE_IN_ALL
+int pango2_attribute_get_int (Pango2Attribute *attribute);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_attribute_get_boolean (Pango2Attribute *attribute);
+PANGO2_AVAILABLE_IN_ALL
+double pango2_attribute_get_float (Pango2Attribute *attribute);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Color * pango2_attribute_get_color (Pango2Attribute *attribute);
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontDescription * pango2_attribute_get_font_desc (Pango2Attribute *attribute);
+
+PANGO2_AVAILABLE_IN_ALL
+gpointer pango2_attribute_get_pointer (Pango2Attribute *attribute);
+
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2Attribute, pango2_attribute_destroy)
+
+G_END_DECLS
diff --git a/pango2/pango-attributes-private.h b/pango2/pango-attributes-private.h
new file mode 100644
index 00000000..ef1ab2c3
--- /dev/null
+++ b/pango2/pango-attributes-private.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-attributes.h"
+
+gboolean pango2_attribute_affects_itemization (Pango2Attribute *attr,
+ gpointer data);
+gboolean pango2_attribute_affects_break_or_shape (Pango2Attribute *attr,
+ gpointer data);
+
+typedef struct {
+ Pango2Rectangle ink_rect;
+ Pango2Rectangle logical_rect;
+ gpointer data;
+ Pango2AttrDataCopyFunc copy;
+ GDestroyNotify destroy;
+} ShapeData;
+
diff --git a/pango2/pango-attributes.c b/pango2/pango-attributes.c
new file mode 100644
index 00000000..ea782a07
--- /dev/null
+++ b/pango2/pango-attributes.c
@@ -0,0 +1,866 @@
+/* Pango2
+ * pango-attributes.c: Attributed text
+ *
+ * Copyright (C) 2000-2002 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.
+ */
+
+#include "config.h"
+#include <string.h>
+
+#include "pango-attributes-private.h"
+#include "pango-attr-private.h"
+#include "pango-impl-utils.h"
+
+/* {{{ Attribute value types */
+
+static inline Pango2Attribute *
+pango2_attr_init (Pango2AttrType type)
+{
+ Pango2Attribute *attr;
+
+ attr = g_slice_new (Pango2Attribute);
+ attr->type = type;
+ attr->start_index = PANGO2_ATTR_INDEX_FROM_TEXT_BEGINNING;
+ attr->end_index = PANGO2_ATTR_INDEX_TO_TEXT_END;
+
+ return attr;
+}
+
+static inline Pango2Attribute *
+pango2_attr_string_new (Pango2AttrType type,
+ const char *value)
+{
+ Pango2Attribute *attr;
+
+ g_return_val_if_fail (PANGO2_ATTR_TYPE_VALUE_TYPE (type) == PANGO2_ATTR_VALUE_STRING, NULL);
+
+ attr = pango2_attr_init (type);
+ attr->str_value = g_strdup (value);
+
+ return attr;
+}
+
+static inline Pango2Attribute *
+pango2_attr_int_new (Pango2AttrType type,
+ int value)
+{
+ Pango2Attribute *attr;
+
+ g_return_val_if_fail (PANGO2_ATTR_TYPE_VALUE_TYPE (type) == PANGO2_ATTR_VALUE_INT, NULL);
+
+ attr = pango2_attr_init (type);
+ attr->int_value = value;
+
+ return attr;
+}
+
+static inline Pango2Attribute *
+pango2_attr_boolean_new (Pango2AttrType type,
+ gboolean value)
+{
+ Pango2Attribute *attr;
+
+ g_return_val_if_fail (PANGO2_ATTR_TYPE_VALUE_TYPE (type) == PANGO2_ATTR_VALUE_BOOLEAN, NULL);
+
+ attr = pango2_attr_init (type);
+ attr->boolean_value = value;
+
+ return attr;
+}
+
+static inline Pango2Attribute *
+pango2_attr_float_new (Pango2AttrType type,
+ double value)
+{
+ Pango2Attribute *attr;
+
+ g_return_val_if_fail (PANGO2_ATTR_TYPE_VALUE_TYPE (type) == PANGO2_ATTR_VALUE_FLOAT, NULL);
+
+ attr = pango2_attr_init (type);
+ attr->double_value = value;
+
+ return attr;
+}
+
+static inline Pango2Attribute *
+pango2_attr_color_new (Pango2AttrType type,
+ Pango2Color *color)
+{
+ Pango2Attribute *attr;
+
+ g_return_val_if_fail (PANGO2_ATTR_TYPE_VALUE_TYPE (type) == PANGO2_ATTR_VALUE_COLOR, NULL);
+
+ attr = pango2_attr_init (type);
+ attr->color_value = *color;
+
+ return attr;
+}
+
+static inline Pango2Attribute *
+pango2_attr_lang_new (Pango2AttrType type,
+ Pango2Language *value)
+{
+ Pango2Attribute *attr;
+
+ g_return_val_if_fail (PANGO2_ATTR_TYPE_VALUE_TYPE (type) == PANGO2_ATTR_VALUE_LANGUAGE, NULL);
+
+ attr = pango2_attr_init (type);
+ attr->lang_value = value;
+
+ return attr;
+}
+
+static inline Pango2Attribute *
+pango2_attr_font_description_new (Pango2AttrType type,
+ const Pango2FontDescription *value)
+{
+ Pango2Attribute *attr;
+
+ g_return_val_if_fail (PANGO2_ATTR_TYPE_VALUE_TYPE (type) == PANGO2_ATTR_VALUE_FONT_DESC, NULL);
+
+ attr = pango2_attr_init (type);
+ attr->font_value = pango2_font_description_copy (value);
+
+ return attr;
+}
+
+/* }}} */
+/* {{{ Attribute types */
+
+/**
+ * pango2_attr_family_new:
+ * @family: the family or comma-separated list of families
+ *
+ * Create a new font family attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_family_new (const char *family)
+{
+ return pango2_attr_string_new (PANGO2_ATTR_FAMILY, family);
+}
+
+/**
+ * pango2_attr_language_new:
+ * @language: language tag
+ *
+ * Create a new language tag attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_language_new (Pango2Language *language)
+{
+ return pango2_attr_lang_new (PANGO2_ATTR_LANGUAGE, language);
+}
+
+/**
+ * pango2_attr_foreground_new:
+ * @color: the color
+ *
+ * Create a new foreground color attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_foreground_new (Pango2Color *color)
+{
+ return pango2_attr_color_new (PANGO2_ATTR_FOREGROUND, color);
+}
+
+/**
+ * pango2_attr_background_new:
+ * @color: the color
+ *
+ * Create a new background color attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_background_new (Pango2Color *color)
+{
+ return pango2_attr_color_new (PANGO2_ATTR_BACKGROUND, color);
+}
+
+/**
+ * pango2_attr_size_new:
+ * @size: the font size, in %PANGO2_SCALE-ths of a point
+ *
+ * Create a new font-size attribute in fractional points.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_size_new (int value)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_SIZE, value);
+}
+
+
+/**
+ * pango2_attr_size_new_absolute:
+ * @size: the font size, in %PANGO2_SCALE-ths of a device unit
+ *
+ * Create a new font-size attribute in device units.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_size_new_absolute (int size)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_ABSOLUTE_SIZE, size);
+}
+
+/**
+ * pango2_attr_style_new:
+ * @style: the slant style
+ *
+ * Create a new font slant style attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_style_new (Pango2Style style)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_STYLE, (int)style);
+}
+
+/**
+ * pango2_attr_weight_new:
+ * @weight: the weight
+ *
+ * Create a new font weight attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_weight_new (Pango2Weight weight)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_WEIGHT, (int)weight);
+}
+
+/**
+ * pango2_attr_variant_new:
+ * @variant: the variant
+ *
+ * Create a new font variant attribute (normal or small caps).
+ *
+ * Return value: (transfer full): the newly allocated `Pango2Attribute`,
+ * which should be freed with [method@Pango2.Attribute.destroy].
+ */
+Pango2Attribute *
+pango2_attr_variant_new (Pango2Variant variant)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_VARIANT, (int)variant);
+}
+
+/**
+ * pango2_attr_stretch_new:
+ * @stretch: the stretch
+ *
+ * Create a new font stretch attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_stretch_new (Pango2Stretch stretch)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_STRETCH, (int)stretch);
+}
+
+/**
+ * pango2_attr_font_desc_new:
+ * @desc: the font description
+ *
+ * Create a new font description attribute.
+ *
+ * This attribute allows setting family, style, weight, variant,
+ * stretch, and size simultaneously.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_font_desc_new (const Pango2FontDescription *desc)
+{
+ return pango2_attr_font_description_new (PANGO2_ATTR_FONT_DESC, desc);
+}
+
+/**
+ * pango2_attr_underline_new:
+ * @style: the line style
+ *
+ * Create a new underline attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_underline_new (Pango2LineStyle style)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_UNDERLINE, (int)style);
+}
+
+/**
+ * pango2_attr_underline_color_new:
+ * @color: the color
+ *
+ * Create a new underline color attribute.
+ *
+ * This attribute modifies the color of underlines.
+ * If not set, underlines will use the foreground color.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_underline_color_new (Pango2Color *color)
+{
+ return pango2_attr_color_new (PANGO2_ATTR_UNDERLINE_COLOR, color);
+}
+
+/**
+ * pango2_attr_underline_position_new:
+ * @position: the underline position
+ *
+ * Create a new underline position attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_underline_position_new (Pango2UnderlinePosition position)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_UNDERLINE_POSITION, (int)position);
+}
+/**
+ * pango2_attr_strikethrough_new:
+ * @style: the line style
+ *
+ * Create a new strike-through attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_strikethrough_new (Pango2LineStyle style)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_STRIKETHROUGH, (int)style);
+}
+
+/**
+ * pango2_attr_strikethrough_color_new:
+ * @color: the color
+ *
+ * Create a new strikethrough color attribute.
+ *
+ * This attribute modifies the color of strikethrough lines.
+ * If not set, strikethrough lines will use the foreground color.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_strikethrough_color_new (Pango2Color *color)
+{
+ return pango2_attr_color_new (PANGO2_ATTR_STRIKETHROUGH_COLOR, color);
+}
+
+/**
+ * pango2_attr_rise_new:
+ * @rise: the amount that the text should be displaced vertically,
+ * in Pango2 units. Positive values displace the text upwards.
+ *
+ * Create a new baseline displacement attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_rise_new (int rise)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_RISE, (int)rise);
+}
+
+/**
+ * pango2_attr_baseline_shift_new:
+ * @shift: either a `Pango2BaselineShift` enumeration value or an absolute value (> 1024)
+ * in Pango2 units, relative to the baseline of the previous run.
+ * Positive values displace the text upwards.
+ *
+ * Create a new baseline displacement attribute.
+ *
+ * The effect of this attribute is to shift the baseline of a run,
+ * relative to the run of preceding run.
+ *
+ * <picture>
+ * <source srcset="baseline-shift-dark.png" media="(prefers-color-scheme: dark)">
+ * <img alt="Baseline Shift" src="baseline-shift-light.png">
+ * </picture>
+
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_baseline_shift_new (int rise)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_BASELINE_SHIFT, (int)rise);
+}
+
+/**
+ * pango2_attr_font_scale_new:
+ * @scale: a `Pango2FontScale` value, which indicates font size change relative
+ * to the size of the previous run.
+ *
+ * Create a new font scale attribute.
+ *
+ * The effect of this attribute is to change the font size of a run,
+ * relative to the size of preceding run.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_font_scale_new (Pango2FontScale scale)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_FONT_SCALE, (int)scale);
+}
+
+/**
+ * pango2_attr_scale_new:
+ * @scale_factor: factor to scale the font
+ *
+ * Create a new font size scale attribute.
+ *
+ * The base font for the affected text will have
+ * its size multiplied by @scale_factor.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute*
+pango2_attr_scale_new (double scale_factor)
+{
+ return pango2_attr_float_new (PANGO2_ATTR_SCALE, scale_factor);
+}
+
+/**
+ * pango2_attr_fallback_new:
+ * @enable_fallback: %TRUE if we should fall back on other fonts
+ * for characters the active font is missing
+ *
+ * Create a new font fallback attribute.
+ *
+ * If fallback is disabled, characters will only be
+ * used from the closest matching font on the system.
+ * No fallback will be done to other fonts on the system
+ * that might contain the characters in the text.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_fallback_new (gboolean enable_fallback)
+{
+ return pango2_attr_boolean_new (PANGO2_ATTR_FALLBACK, enable_fallback);
+}
+
+/**
+ * pango2_attr_letter_spacing_new:
+ * @letter_spacing: amount of extra space to add between
+ * graphemes of the text, in Pango2 units
+ *
+ * Create a new letter-spacing attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_letter_spacing_new (int letter_spacing)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_LETTER_SPACING, letter_spacing);
+}
+
+/**
+ * pango2_attr_gravity_new:
+ * @gravity: the gravity value; should not be %PANGO2_GRAVITY_AUTO
+ *
+ * Create a new gravity attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_gravity_new (Pango2Gravity gravity)
+{
+ g_return_val_if_fail (gravity != PANGO2_GRAVITY_AUTO, NULL);
+
+ return pango2_attr_int_new (PANGO2_ATTR_GRAVITY, (int)gravity);
+}
+
+/**
+ * pango2_attr_gravity_hint_new:
+ * @hint: the gravity hint value
+ *
+ * Create a new gravity hint attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_gravity_hint_new (Pango2GravityHint hint)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_GRAVITY_HINT, (int)hint);
+}
+
+/**
+ * pango2_attr_font_features_new:
+ * @features: a string with OpenType font features, with the syntax of the [CSS
+ * font-feature-settings property](https://www.w3.org/TR/css-fonts-4/#font-rend-desc)
+ *
+ * Create a new font features tag attribute.
+ *
+ * You can use this attribute to select OpenType font features like small-caps,
+ * alternative glyphs, ligatures, etc. for fonts that support them.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_font_features_new (const char *features)
+{
+ g_return_val_if_fail (features != NULL, NULL);
+
+ return pango2_attr_string_new (PANGO2_ATTR_FONT_FEATURES, features);
+}
+
+/**
+ * pango2_attr_allow_breaks_new:
+ * @allow_breaks: %TRUE if we line breaks are allowed
+ *
+ * Create a new allow-breaks attribute.
+ *
+ * If breaks are disabled, the range will be kept in a
+ * single run, as far as possible.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_allow_breaks_new (gboolean allow_breaks)
+{
+ return pango2_attr_boolean_new (PANGO2_ATTR_ALLOW_BREAKS, allow_breaks);
+}
+
+/**
+ * pango2_attr_insert_hyphens_new:
+ * @insert_hyphens: %TRUE if hyphens should be inserted
+ *
+ * Create a new insert-hyphens attribute.
+ *
+ * Pango2 will insert hyphens when breaking lines in
+ * the middle of a word. This attribute can be used
+ * to suppress the hyphen.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_insert_hyphens_new (gboolean insert_hyphens)
+{
+ return pango2_attr_boolean_new (PANGO2_ATTR_INSERT_HYPHENS, insert_hyphens);
+}
+
+/**
+ * pango2_attr_show_new:
+ * @flags: `Pango2ShowFlags` to apply
+ *
+ * Create a new attribute that influences how invisible
+ * characters are rendered.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ **/
+Pango2Attribute *
+pango2_attr_show_new (Pango2ShowFlags flags)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_SHOW, (int)flags);
+}
+
+/**
+ * pango2_attr_word_new:
+ *
+ * Create a new attribute that marks its range
+ * as a single word.
+ *
+ * Note that this may require adjustments to word and
+ * sentence classification around the range.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_word_new (void)
+{
+ return pango2_attr_boolean_new (PANGO2_ATTR_WORD, TRUE);
+}
+
+/**
+ * pango2_attr_sentence_new:
+ *
+ * Create a new attribute that marks its range
+ * as a single sentence.
+ *
+ * Note that this may require adjustments to word and
+ * sentence classification around the range.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_sentence_new (void)
+{
+ return pango2_attr_boolean_new (PANGO2_ATTR_SENTENCE, TRUE);
+}
+
+/**
+ * pango2_attr_paragraph_new:
+ *
+ * Create a new attribute that marks its range as a single paragraph.
+ *
+ * Newlines and similar characters in the range of the attribute
+ * will not be treated as paragraph separators.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_paragraph_new (void)
+{
+ return pango2_attr_boolean_new (PANGO2_ATTR_SENTENCE, TRUE);
+}
+
+/**
+ * pango2_attr_overline_new:
+ * @style: the line style
+ *
+ * Create a new overline-style attribute.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_overline_new (Pango2LineStyle style)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_OVERLINE, (int)style);
+}
+
+/**
+ * pango2_attr_overline_color_new:
+ * @color: the color
+ *
+ * Create a new overline color attribute.
+ *
+ * This attribute modifies the color of overlines.
+ * If not set, overlines will use the foreground color.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_overline_color_new (Pango2Color *color)
+{
+ return pango2_attr_color_new (PANGO2_ATTR_OVERLINE_COLOR, color);
+}
+
+/**
+ * pango2_attr_line_height_new:
+ * @factor: the scaling factor to apply to the logical height
+ *
+ * Create a new attribute that modifies the height
+ * of logical line extents by a factor.
+ *
+ * This affects the values returned by
+ * [method@Pango2.Line.get_extents] and
+ * [method@Pango2.LineIter.get_line_extents].
+ */
+Pango2Attribute *
+pango2_attr_line_height_new (double factor)
+{
+ return pango2_attr_float_new (PANGO2_ATTR_LINE_HEIGHT, factor);
+}
+
+/**
+ * pango2_attr_line_height_new_absolute:
+ * @height: the line height, in %PANGO2_SCALE-ths of a point
+ *
+ * Create a new attribute that overrides the height
+ * of logical line extents to be @height.
+ *
+ * This affects the values returned by
+ * [method@Pango2.Line.get_extents],
+ * [method@Pango2.LineIter.get_line_extents].
+ */
+Pango2Attribute *
+pango2_attr_line_height_new_absolute (int height)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_ABSOLUTE_LINE_HEIGHT, height);
+}
+
+/**
+ * pango2_attr_line_spacing_new:
+ * @spacing: the line spacing, in %PANGO2_SCALE-ths of a point
+ *
+ * Create a new attribute that adds space to the
+ * leading from font metrics, if not overridden
+ * by line spacing attributes.
+ *
+ * This affects the values returned by
+ * [method@Pango2.Line.get_extents],
+ * [method@Pango2.LineIter.get_line_extents].
+ */
+Pango2Attribute *
+pango2_attr_line_spacing_new (int spacing)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_LINE_SPACING, spacing);
+}
+
+/**
+ * pango2_attr_text_transform_new:
+ * @transform: `Pango2TextTransform` to apply
+ *
+ * Create a new attribute that influences how characters
+ * are transformed during shaping.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_text_transform_new (Pango2TextTransform transform)
+{
+ return pango2_attr_int_new (PANGO2_ATTR_TEXT_TRANSFORM, transform);
+}
+
+/**
+ * pango2_attr_shape_new:
+ * @ink_rect: ink rectangle to use for each character
+ * @logical_rect: logical rectangle to use for each character
+ * @data: user data
+ * @copy: (nullable): function to copy @data when the attribute
+ * is copied
+ * @destroy: (nullable): function to free @data when the attribute
+ * is freed
+ *
+ * Creates a new shape attribute.
+ *
+ * Shape attributes override the extents for a glyph and can
+ * trigger custom rendering in a `Pango2Renderer`. This might
+ * be used, for instance, for embedding a picture or a widget
+ * inside a `Pango2Layout`.
+ *
+ * Return value: (transfer full): the newly allocated
+ * `Pango2Attribute`, which should be freed with
+ * [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_shape_new (Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect,
+ gpointer data,
+ Pango2AttrDataCopyFunc copy,
+ GDestroyNotify destroy)
+{
+ Pango2Attribute *attr;
+ ShapeData *shape_data;
+
+ shape_data = g_new0 (ShapeData, 1);
+ shape_data->ink_rect = *ink_rect;
+ shape_data->logical_rect = *logical_rect;
+ shape_data->data = data;
+ shape_data->copy = copy;
+ shape_data->destroy = destroy;
+
+ attr = pango2_attr_init (PANGO2_ATTR_SHAPE);
+ attr->pointer_value = shape_data;
+
+ return attr;
+}
+
+/* }}} */
+/* {{{ Private API */
+
+gboolean
+pango2_attribute_affects_itemization (Pango2Attribute *attr,
+ gpointer data)
+{
+ return (PANGO2_ATTR_AFFECTS (attr) & PANGO2_ATTR_AFFECTS_ITEMIZATION) != 0;
+}
+
+gboolean
+pango2_attribute_affects_break_or_shape (Pango2Attribute *attr,
+ gpointer data)
+{
+ return (PANGO2_ATTR_AFFECTS (attr) & (PANGO2_ATTR_AFFECTS_BREAKING |
+ PANGO2_ATTR_AFFECTS_SHAPING)) != 0;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-attributes.h b/pango2/pango-attributes.h
new file mode 100644
index 00000000..98e89d7b
--- /dev/null
+++ b/pango2/pango-attributes.h
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-font.h>
+#include <pango2/pango-color.h>
+#include <pango2/pango-attr.h>
+
+G_BEGIN_DECLS
+
+#ifndef __GI_SCANNER__
+
+#define PANGO2_ATTR_TYPE(value, affects, merge) (PANGO2_ATTR_VALUE_##value | (PANGO2_ATTR_AFFECTS_##affects << 8) | (PANGO2_ATTR_MERGE_##merge << 12) | (__COUNTER__ << 16))
+
+#endif
+
+/**
+ * Pango2AttrType:
+ * @PANGO2_ATTR_INVALID: does not happen
+ * @PANGO2_ATTR_LANGUAGE: language
+ * @PANGO2_ATTR_FAMILY: font family name
+ * @PANGO2_ATTR_STYLE: font style
+ * @PANGO2_ATTR_WEIGHT: font weight
+ * @PANGO2_ATTR_VARIANT: font variant
+ * @PANGO2_ATTR_STRETCH: font stretch
+ * @PANGO2_ATTR_SIZE: font size in points scaled by `PANGO2_SCALE`
+ * @PANGO2_ATTR_FONT_DESC: font description
+ * @PANGO2_ATTR_FOREGROUND: foreground color
+ * @PANGO2_ATTR_BACKGROUND: background color
+ * @PANGO2_ATTR_UNDERLINE: underline style
+ * @PANGO2_ATTR_UNDERLINE_POSITION: underline position
+ * @PANGO2_ATTR_STRIKETHROUGH: whether the text is struck-through
+ * @PANGO2_ATTR_RISE: baseline displacement
+ * @PANGO2_ATTR_SCALE: font size scale factor
+ * @PANGO2_ATTR_FALLBACK: whether font fallback is enabled
+ * @PANGO2_ATTR_LETTER_SPACING: letter spacing in Pango2 units
+ * @PANGO2_ATTR_UNDERLINE_COLOR: underline color
+ * @PANGO2_ATTR_STRIKETHROUGH_COLOR: strikethrough color
+ * @PANGO2_ATTR_ABSOLUTE_SIZE: font size in pixels scaled by `PANGO2_SCALE`
+ * @PANGO2_ATTR_GRAVITY: base text gravity
+ * @PANGO2_ATTR_GRAVITY_HINT: gravity hint
+ * @PANGO2_ATTR_FONT_FEATURES: OpenType font features
+ * @PANGO2_ATTR_ALLOW_BREAKS: whether line breaks are allowed
+ * @PANGO2_ATTR_SHOW: how to render invisible characters
+ * @PANGO2_ATTR_INSERT_HYPHENS: whether to insert hyphens at intra-word line breaks
+ * @PANGO2_ATTR_OVERLINE: whether the text has an overline
+ * @PANGO2_ATTR_OVERLINE_COLOR: overline color
+ * @PANGO2_ATTR_LINE_HEIGHT: line height factor
+ * @PANGO2_ATTR_ABSOLUTE_LINE_HEIGHT: line height in Pango2 units
+ * @PANGO2_ATTR_WORD: mark the range of the attribute as a single word
+ * @PANGO2_ATTR_SENTENCE: mark the range of the attribute as a single sentence
+ * @PANGO2_ATTR_PARAGRAPH: mark the range of the attribute as a single paragraph
+ * @PANGO2_ATTR_BASELINE_SHIFT: baseline displacement
+ * @PANGO2_ATTR_FONT_SCALE: font-relative size change
+ * @PANGO2_ATTR_LINE_SPACING: space to add to the leading from the
+ * font metrics (if not overridden by a line height attribute)
+ * @PANGO2_ATTR_SHAPE: override glyph shapes (requires renderer support)
+ *
+ * `Pango2AttrType` contains predefined attribute types.
+ *
+ * Along with the predefined values, it is possible to allocate additional
+ * values for custom attributes using [func@AttrType.register]. The predefined
+ * values are given below.
+ */
+typedef enum
+{
+ PANGO2_ATTR_INVALID,
+ PANGO2_ATTR_LANGUAGE = PANGO2_ATTR_TYPE (LANGUAGE, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_FAMILY = PANGO2_ATTR_TYPE (STRING, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_STYLE = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_WEIGHT = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_VARIANT = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_STRETCH = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_SIZE = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_FONT_DESC = PANGO2_ATTR_TYPE (FONT_DESC, ITEMIZATION, ACCUMULATES),
+ PANGO2_ATTR_FOREGROUND = PANGO2_ATTR_TYPE (COLOR, RENDERING, OVERRIDES),
+ PANGO2_ATTR_BACKGROUND = PANGO2_ATTR_TYPE (COLOR, RENDERING, OVERRIDES),
+ PANGO2_ATTR_UNDERLINE = PANGO2_ATTR_TYPE (INT, RENDERING, OVERRIDES),
+ PANGO2_ATTR_UNDERLINE_POSITION = PANGO2_ATTR_TYPE (INT, RENDERING, OVERRIDES),
+ PANGO2_ATTR_STRIKETHROUGH = PANGO2_ATTR_TYPE (INT, RENDERING, OVERRIDES),
+ PANGO2_ATTR_RISE = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_SCALE = PANGO2_ATTR_TYPE (FLOAT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_FALLBACK = PANGO2_ATTR_TYPE (BOOLEAN, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_LETTER_SPACING = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_UNDERLINE_COLOR = PANGO2_ATTR_TYPE (COLOR, RENDERING, OVERRIDES),
+ PANGO2_ATTR_STRIKETHROUGH_COLOR = PANGO2_ATTR_TYPE (COLOR, RENDERING, OVERRIDES),
+ PANGO2_ATTR_ABSOLUTE_SIZE = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_GRAVITY = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_GRAVITY_HINT = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_FONT_FEATURES = PANGO2_ATTR_TYPE (STRING, SHAPING, ACCUMULATES),
+ PANGO2_ATTR_ALLOW_BREAKS = PANGO2_ATTR_TYPE (BOOLEAN, BREAKING, OVERRIDES),
+ PANGO2_ATTR_SHOW = PANGO2_ATTR_TYPE (INT, SHAPING, OVERRIDES),
+ PANGO2_ATTR_INSERT_HYPHENS = PANGO2_ATTR_TYPE (BOOLEAN, SHAPING, OVERRIDES),
+ PANGO2_ATTR_OVERLINE = PANGO2_ATTR_TYPE (INT, RENDERING, OVERRIDES),
+ PANGO2_ATTR_OVERLINE_COLOR = PANGO2_ATTR_TYPE (COLOR, RENDERING, OVERRIDES),
+ PANGO2_ATTR_LINE_HEIGHT = PANGO2_ATTR_TYPE (FLOAT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_ABSOLUTE_LINE_HEIGHT = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_TEXT_TRANSFORM = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_WORD = PANGO2_ATTR_TYPE (BOOLEAN, BREAKING, OVERRIDES),
+ PANGO2_ATTR_SENTENCE = PANGO2_ATTR_TYPE (BOOLEAN, BREAKING, OVERRIDES),
+ PANGO2_ATTR_PARAGRAPH = PANGO2_ATTR_TYPE (BOOLEAN, BREAKING, OVERRIDES),
+ PANGO2_ATTR_BASELINE_SHIFT = PANGO2_ATTR_TYPE (INT, ITEMIZATION, ACCUMULATES),
+ PANGO2_ATTR_FONT_SCALE = PANGO2_ATTR_TYPE (INT, ITEMIZATION, ACCUMULATES),
+ PANGO2_ATTR_LINE_SPACING = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
+ PANGO2_ATTR_SHAPE = PANGO2_ATTR_TYPE (POINTER, ITEMIZATION, OVERRIDES),
+} Pango2AttrType;
+
+#undef PANGO2_ATTR_TYPE
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_language_new (Pango2Language *language);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_family_new (const char *family);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_foreground_new (Pango2Color *color);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_background_new (Pango2Color *color);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_size_new (int size);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_size_new_absolute (int size);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_style_new (Pango2Style style);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_weight_new (Pango2Weight weight);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_variant_new (Pango2Variant variant);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_stretch_new (Pango2Stretch stretch);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_font_desc_new (const Pango2FontDescription *desc);
+
+/**
+ * Pango2LineStyle:
+ * @PANGO2_LINE_STYLE_NONE: No visible line
+ * @PANGO2_LINE_STYLE_SOLID: A single line
+ * @PANGO2_LINE_STYLE_DOUBLE: A double line
+ * @PANGO2_LINE_STYLE_DASHED: A dashed line
+ * @PANGO2_LINE_STYLE_DOTTED: A dotted line
+ * @PANGO2_LINE_STYLE_WAVY: A wavy line
+ *
+ * `Pango2LineStyle specifies how lines should be drawn.
+ */
+typedef enum {
+ PANGO2_LINE_STYLE_NONE,
+ PANGO2_LINE_STYLE_SOLID,
+ PANGO2_LINE_STYLE_DOUBLE,
+ PANGO2_LINE_STYLE_DASHED,
+ PANGO2_LINE_STYLE_DOTTED,
+ PANGO2_LINE_STYLE_WAVY,
+} Pango2LineStyle;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_underline_new (Pango2LineStyle style);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_underline_color_new (Pango2Color *color);
+
+/**
+ * Pango2UnderlinePosition:
+ * @PANGO2_UNDERLINE_POSITION_NORMAL: As specified by font metrics
+ * @PANGO2_UNDERLINE_POSITION_UNDER: Below the ink extents of the run
+ *
+ * `Pango2UnderlinePosition` specifies where underlines should be drawn.
+ */
+typedef enum {
+ PANGO2_UNDERLINE_POSITION_NORMAL,
+ PANGO2_UNDERLINE_POSITION_UNDER
+} Pango2UnderlinePosition;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_underline_position_new (Pango2UnderlinePosition position);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_strikethrough_new (Pango2LineStyle style);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_strikethrough_color_new (Pango2Color *color);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_rise_new (int rise);
+
+/**
+ * Pango2BaselineShift:
+ * @PANGO2_BASELINE_SHIFT_NONE: Leave the baseline unchanged
+ * @PANGO2_BASELINE_SHIFT_SUPERSCRIPT: Shift the baseline to the superscript position,
+ * relative to the previous run
+ * @PANGO2_BASELINE_SHIFT_SUBSCRIPT: Shift the baseline to the subscript position,
+ * relative to the previous run
+ *
+ * `Pango2BaselineShift` influences how baselines are changed between runs.
+ */
+typedef enum {
+ PANGO2_BASELINE_SHIFT_NONE,
+ PANGO2_BASELINE_SHIFT_SUPERSCRIPT,
+ PANGO2_BASELINE_SHIFT_SUBSCRIPT,
+} Pango2BaselineShift;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_baseline_shift_new (int shift);
+
+/**
+ * Pango2FontScale:
+ * @PANGO2_FONT_SCALE_NONE: Leave the font size unchanged
+ * @PANGO2_FONT_SCALE_SUPERSCRIPT: Change the font to a size suitable for superscripts
+ * @PANGO2_FONT_SCALE_SUBSCRIPT: Change the font to a size suitable for subscripts
+ * @PANGO2_FONT_SCALE_SMALL_CAPS: Change the font to a size suitable for Small Caps
+ *
+ * `Pango2FontScale` influences the font size of a run.
+ */
+typedef enum {
+ PANGO2_FONT_SCALE_NONE,
+ PANGO2_FONT_SCALE_SUPERSCRIPT,
+ PANGO2_FONT_SCALE_SUBSCRIPT,
+ PANGO2_FONT_SCALE_SMALL_CAPS,
+} Pango2FontScale;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_font_scale_new (Pango2FontScale scale);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_scale_new (double scale_factor);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_fallback_new (gboolean enable_fallback);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_letter_spacing_new (int letter_spacing);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_gravity_new (Pango2Gravity gravity);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_gravity_hint_new (Pango2GravityHint hint);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_font_features_new (const char *features);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_allow_breaks_new (gboolean allow_breaks);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_word_new (void);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_sentence_new (void);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_paragraph_new (void);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_insert_hyphens_new (gboolean insert_hyphens);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_overline_new (Pango2LineStyle style);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_overline_color_new (Pango2Color *color);
+
+/**
+ * Pango2ShowFlags:
+ * @PANGO2_SHOW_NONE: No special treatment for invisible characters
+ * @PANGO2_SHOW_SPACES: Render spaces, tabs and newlines visibly
+ * @PANGO2_SHOW_LINE_BREAKS: Render line breaks visibly
+ * @PANGO2_SHOW_IGNORABLES: Render default-ignorable Unicode
+ * characters visibly
+ *
+ * `Pango2ShowFlags` affect how Pango2 treats characters that are normally
+ * not visible in the output.
+ */
+typedef enum {
+ PANGO2_SHOW_NONE = 0,
+ PANGO2_SHOW_SPACES = 1 << 0,
+ PANGO2_SHOW_LINE_BREAKS = 1 << 1,
+ PANGO2_SHOW_IGNORABLES = 1 << 2
+} Pango2ShowFlags;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_show_new (Pango2ShowFlags flags);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_line_height_new (double factor);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_line_height_new_absolute (int height);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_line_spacing_new (int spacing);
+
+/**
+ * Pango2TextTransform:
+ * @PANGO2_TEXT_TRANSFORM_NONE: Leave text unchanged
+ * @PANGO2_TEXT_TRANSFORM_LOWERCASE: Display letters and numbers as lowercase
+ * @PANGO2_TEXT_TRANSFORM_UPPERCASE: Display letters and numbers as uppercase
+ * @PANGO2_TEXT_TRANSFORM_CAPITALIZE: Display the first character of a word
+ * in titlecase
+ *
+ * `Pango2TextTransform` determines if Pango2 changes the case of characters
+ * during shaping.
+ */
+typedef enum {
+ PANGO2_TEXT_TRANSFORM_NONE,
+ PANGO2_TEXT_TRANSFORM_LOWERCASE,
+ PANGO2_TEXT_TRANSFORM_UPPERCASE,
+ PANGO2_TEXT_TRANSFORM_CAPITALIZE,
+} Pango2TextTransform;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_text_transform_new (Pango2TextTransform transform);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute * pango2_attr_shape_new (Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect,
+ gpointer data,
+ Pango2AttrDataCopyFunc copy,
+ GDestroyNotify destroy);
+
+G_END_DECLS
diff --git a/pango2/pango-bidi-private.h b/pango2/pango-bidi-private.h
new file mode 100644
index 00000000..3a0b1ab5
--- /dev/null
+++ b/pango2/pango-bidi-private.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <glib.h>
+#include "pango-font.h"
+#include "pango-direction.h"
+
+guint8 * pango2_log2vis_get_embedding_levels (const char *text,
+ int length,
+ Pango2Direction *pbase_dir);
+
+Pango2Direction pango2_find_base_dir (const char *text,
+ int length);
diff --git a/pango2/pango-bidi.c b/pango2/pango-bidi.c
new file mode 100644
index 00000000..1bf1a436
--- /dev/null
+++ b/pango2/pango-bidi.c
@@ -0,0 +1,223 @@
+/* Pango2
+ * pango-bidi-type.c: Bidirectional Character Types
+ *
+ * Copyright (C) 2008 Jürg Billeter <j@bitron.ch>
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <fribidi.h>
+
+#include "pango-bidi-private.h"
+#include "pango-utils.h"
+
+/* Some bidi-related functions */
+
+/*< private >
+ * pango2_log2vis_get_embedding_levels:
+ * @text: the text to itemize.
+ * @length: the number of bytes (not characters) to process, or -1
+ * if @text is nul-terminated and the length should be calculated.
+ * @pbase_dir: input base direction, and output resolved direction.
+ *
+ * Return the bidirectional embedding levels of the input paragraph.
+ *
+ * The bidirectional embedding levels are defined by the [Unicode Bidirectional
+ * Algorithm](http://www.unicode.org/reports/tr9/).
+ *
+ * If the input base direction is a weak direction, the direction of the
+ * characters in the text will determine the final resolved direction.
+ *
+ * Return value: a newly allocated array of embedding levels, one item per
+ * character (not byte), that should be freed using [func@GLib.free].
+ */
+guint8 *
+pango2_log2vis_get_embedding_levels (const char *text,
+ int length,
+ Pango2Direction *pbase_dir)
+{
+ glong n_chars, i;
+ guint8 *embedding_levels_list;
+ const char *p;
+ FriBidiParType fribidi_base_dir;
+ FriBidiCharType *bidi_types;
+ FriBidiBracketType *bracket_types;
+ FriBidiLevel max_level;
+ FriBidiCharType ored_types = 0;
+ FriBidiCharType anded_strongs = FRIBIDI_TYPE_RLE;
+
+ G_STATIC_ASSERT (sizeof (FriBidiLevel) == sizeof (guint8));
+ G_STATIC_ASSERT (sizeof (FriBidiChar) == sizeof (gunichar));
+
+ switch (*pbase_dir)
+ {
+ case PANGO2_DIRECTION_LTR:
+ fribidi_base_dir = FRIBIDI_PAR_LTR;
+ break;
+ case PANGO2_DIRECTION_RTL:
+ fribidi_base_dir = FRIBIDI_PAR_RTL;
+ break;
+ case PANGO2_DIRECTION_WEAK_RTL:
+ fribidi_base_dir = FRIBIDI_PAR_WRTL;
+ break;
+ case PANGO2_DIRECTION_WEAK_LTR:
+ case PANGO2_DIRECTION_NEUTRAL:
+ default:
+ fribidi_base_dir = FRIBIDI_PAR_WLTR;
+ break;
+ }
+
+ if (length < 0)
+ length = strlen (text);
+
+ n_chars = g_utf8_strlen (text, length);
+
+ bidi_types = g_new (FriBidiCharType, n_chars);
+ bracket_types = g_new (FriBidiBracketType, n_chars);
+ embedding_levels_list = g_new (guint8, n_chars);
+
+ for (i = 0, p = text; p < text + length; p = g_utf8_next_char(p), i++)
+ {
+ gunichar ch = g_utf8_get_char (p);
+ FriBidiCharType char_type = fribidi_get_bidi_type (ch);
+
+ if (i == n_chars)
+ break;
+
+ bidi_types[i] = char_type;
+ ored_types |= char_type;
+ if (FRIBIDI_IS_STRONG (char_type))
+ anded_strongs &= char_type;
+ if (G_UNLIKELY(bidi_types[i] == FRIBIDI_TYPE_ON))
+ bracket_types[i] = fribidi_get_bracket (ch);
+ else
+ bracket_types[i] = FRIBIDI_NO_BRACKET;
+ }
+
+ /* Short-circuit (malloc-expensive) FriBidi call for unidirectional
+ * text.
+ *
+ * For details see:
+ * https://bugzilla.gnome.org/show_bug.cgi?id=590183
+ */
+
+ /* The case that all resolved levels will be ltr.
+ * No isolates, all strongs be LTR, there should be no Arabic numbers
+ * (or letters for that matter), and one of the following:
+ *
+ * o base_dir doesn't have an RTL taste.
+ * o there are letters, and base_dir is weak.
+ */
+ if (!FRIBIDI_IS_ISOLATE (ored_types) &&
+ !FRIBIDI_IS_RTL (ored_types) &&
+ !FRIBIDI_IS_ARABIC (ored_types) &&
+ (!FRIBIDI_IS_RTL (fribidi_base_dir) ||
+ (FRIBIDI_IS_WEAK (fribidi_base_dir) &&
+ FRIBIDI_IS_LETTER (ored_types))
+ ))
+ {
+ /* all LTR */
+ fribidi_base_dir = FRIBIDI_PAR_LTR;
+ memset (embedding_levels_list, 0, n_chars);
+ goto resolved;
+ }
+ /* The case that all resolved levels will be RTL is much more complex.
+ * No isolates, no numbers, all strongs are RTL, and one of
+ * the following:
+ *
+ * o base_dir has an RTL taste (may be weak).
+ * o there are letters, and base_dir is weak.
+ */
+ else if (!FRIBIDI_IS_ISOLATE (ored_types) &&
+ !FRIBIDI_IS_NUMBER (ored_types) &&
+ FRIBIDI_IS_RTL (anded_strongs) &&
+ (FRIBIDI_IS_RTL (fribidi_base_dir) ||
+ (FRIBIDI_IS_WEAK (fribidi_base_dir) &&
+ FRIBIDI_IS_LETTER (ored_types))
+ ))
+ {
+ /* all RTL */
+ fribidi_base_dir = FRIBIDI_PAR_RTL;
+ memset (embedding_levels_list, 1, n_chars);
+ goto resolved;
+ }
+
+
+ max_level = fribidi_get_par_embedding_levels_ex (bidi_types, bracket_types, n_chars,
+ &fribidi_base_dir,
+ (FriBidiLevel*)embedding_levels_list);
+
+ if (G_UNLIKELY(max_level == 0))
+ {
+ /* fribidi_get_par_embedding_levels() failed. */
+ memset (embedding_levels_list, 0, length);
+ }
+
+resolved:
+ g_free (bidi_types);
+ g_free (bracket_types);
+
+ *pbase_dir = (fribidi_base_dir == FRIBIDI_PAR_LTR) ? PANGO2_DIRECTION_LTR : PANGO2_DIRECTION_RTL;
+
+ return embedding_levels_list;
+}
+
+static Pango2Direction
+pango2_unichar_direction (gunichar ch)
+{
+ FriBidiCharType fribidi_ch_type;
+
+ G_STATIC_ASSERT (sizeof (FriBidiChar) == sizeof (gunichar));
+
+ fribidi_ch_type = fribidi_get_bidi_type (ch);
+
+ if (!FRIBIDI_IS_STRONG (fribidi_ch_type))
+ return PANGO2_DIRECTION_NEUTRAL;
+ else if (FRIBIDI_IS_RTL (fribidi_ch_type))
+ return PANGO2_DIRECTION_RTL;
+ else
+ return PANGO2_DIRECTION_LTR;
+}
+
+Pango2Direction
+pango2_find_base_dir (const char *text,
+ int length)
+{
+ Pango2Direction dir = PANGO2_DIRECTION_NEUTRAL;
+ const char *p;
+
+ g_return_val_if_fail (text != NULL || length == 0, PANGO2_DIRECTION_NEUTRAL);
+
+ p = text;
+ while ((length < 0 || p < text + length) && *p)
+ {
+ gunichar wc = g_utf8_get_char (p);
+
+
+ dir = pango2_unichar_direction (wc);
+
+ if (dir != PANGO2_DIRECTION_NEUTRAL)
+ break;
+
+ p = g_utf8_next_char (p);
+ }
+
+ return dir;
+}
diff --git a/pango2/pango-break-table.h b/pango2/pango-break-table.h
new file mode 100644
index 00000000..0ee37cdf
--- /dev/null
+++ b/pango2/pango-break-table.h
@@ -0,0 +1,864 @@
+/* == Start of generated table == */
+/*
+ * The following tables are generated by running:
+ *
+ * ./gen-break-table.py SentenceBreakProperty.txt IndicSyllabicCategory.txt EastAsianWidth.txt | indent
+ *
+ * on files with these headers:
+ *
+ * # SentenceBreakProperty-14.0.0.txt
+ * # Date: 2021-08-12, 23:13:21 GMT
+ * # © 2021 Unicode®, Inc.
+ * # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+ * # For terms of use, see http://www.unicode.org/terms_of_use.html
+ * #
+ * # Unicode Character Database
+ * # For documentation, see http://www.unicode.org/reports/tr44/
+ * # IndicSyllabicCategory-14.0.0.txt
+ * # Date: 2021-05-22, 01:01:00 GMT [KW, RP]
+ * # © 2021 Unicode®, Inc.
+ * # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+ * # For terms of use, see http://www.unicode.org/terms_of_use.html
+ * #
+ * # For documentation, see UAX #44: Unicode Character Database,
+ * # at http://www.unicode.org/reports/tr44/
+ * #
+ * # This file defines the following property:
+ * #
+ * # Indic_Syllabic_Category enumerated property
+ * #
+ * # Scope: This property is aimed at two general problem
+ * # areas involving the analysis and processing of Indic scripts:
+ * #
+ * # 1. Specification of syllabic structure.
+ * # 2. Specification of segmentation rules.
+ * #
+ * # Both of these problem areas may benefit from having defined subtypes
+ * # of Indic script characters which are relevant to how Indic
+ * # syllables (or aksaras) are constructed. Note that rules for
+ * # syllabic structure in Indic scripts may differ significantly
+ * # from how phonological syllables are defined.
+ * #
+ * # Format:
+ * # Field 0 Unicode code point value or range of code point values
+ * # Field 1 Indic_Syllabic_Category property value
+ * #
+ * # Field 1 is followed by a comment field, starting with the number sign '#',
+ * # which shows the General_Category property value, the Unicode character name
+ * # or names, and, in lines with ranges of code points, the code point count in
+ * # square brackets.
+ * #
+ * # The scripts assessed as Indic in the structural sense used for the
+ * # Indic_Syllabic_Category are the following:
+ * #
+ * # Ahom, Balinese, Batak, Bengali, Bhaiksuki, Brahmi, Buginese, Buhid,
+ * # Chakma, Cham, Devanagari, Dives Akuru, Dogra, Grantha, Gujarati,
+ * # Gunjala Gondi, Gurmukhi, Hanunoo, Javanese, Kaithi, Kannada,
+ * # Kayah Li, Kharoshthi, Khmer, Khojki, Khudawadi, Lao, Lepcha, Limbu,
+ * # Mahajani, Makasar, Malayalam, Marchen, Masaram Gondi, Meetei Mayek,
+ * # Modi, Multani, Myanmar, Nandinagari, Newa, New Tai Lue, Oriya,
+ * # Phags-pa, Rejang, Saurashtra, Sharada, Siddham, Sinhala, Soyombo,
+ * # Sundanese, Syloti Nagri, Tagalog, Tagbanwa, Tai Le, Tai Tham,
+ * # Tai Viet, Takri, Tamil, Telugu, Thai, Tibetan, Tirhuta, and
+ * # Zanabazar Square.
+ * #
+ * # All characters for all other scripts not in that list
+ * # take the default value for this property, unless they
+ * # are individually listed in this data file.
+ * #
+ * # EastAsianWidth-14.0.0.txt
+ * # Date: 2021-07-06, 09:58:53 GMT [KW, LI]
+ * # © 2021 Unicode®, Inc.
+ * # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+ * # For terms of use, see https://www.unicode.org/terms_of_use.html
+ * #
+ * # Unicode Character Database
+ * # For documentation, see https://www.unicode.org/reports/tr44/
+ * #
+ * # East_Asian_Width Property
+ * #
+ * # This file is a normative contributory data file in the
+ * # Unicode Character Database.
+ * #
+ * # The format is two fields separated by a semicolon.
+ * # Field 0: Unicode code point value or range of code point values
+ * # Field 1: East_Asian_Width property, consisting of one of the following values:
+ * # "A", "F", "H", "N", "Na", "W"
+ * # - All code points, assigned or unassigned, that are not listed
+ * # explicitly are given the value "N".
+ * # - The unassigned code points in the following blocks default to "W":
+ * # CJK Unified Ideographs Extension A: U+3400..U+4DBF
+ * # CJK Unified Ideographs: U+4E00..U+9FFF
+ * # CJK Compatibility Ideographs: U+F900..U+FAFF
+ * # - All undesignated code points in Planes 2 and 3, whether inside or
+ * # outside of allocated blocks, default to "W":
+ * # Plane 2: U+20000..U+2FFFD
+ * # Plane 3: U+30000..U+3FFFD
+ * #
+ * # Character ranges are specified as for other property files in the
+ * # Unicode Character Database.
+ * #
+ * # For legacy reasons, there are no spaces before or after the semicolon
+ * # which separates the two fields. The comments following the number sign
+ * # "#" list the General_Category property value or the L& alias of the
+ * # derived value LC, the Unicode character name or names, and, in lines
+ * # with ranges of code points, the code point count in square brackets.
+ * #
+ * # For more information, see UAX #11: East Asian Width,
+ * # at https://www.unicode.org/reports/tr11/
+ * #
+ * # @missing: 0000..10FFFF; N
+ */
+
+#ifndef PANGO2_BREAK_TABLE_H
+#define PANGO2_BREAK_TABLE_H
+
+#include <glib.h>
+
+
+static inline gboolean
+_pango2_is_STerm (gunichar wc)
+{
+ if ((wc >= 0x0021 && wc <= 0x1945))
+ {
+ if (wc == 0x0021 ||
+ wc == 0x003F || wc == 0x0589 || (wc >= 0x061D && wc <= 0x061F))
+ return TRUE;
+ if ((wc >= 0x06D4 && wc <= 0x0839))
+ {
+ if (wc == 0x06D4)
+ return TRUE;
+ if ((wc >= 0x0700 && wc <= 0x0702))
+ return TRUE;
+ if (wc == 0x07F9)
+ return TRUE;
+ if (wc == 0x0837 || wc == 0x0839)
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x083D && wc <= 0x083E) ||
+ (wc >= 0x0964 && wc <= 0x0965) ||
+ (wc >= 0x104A && wc <= 0x104B) || wc == 0x1362)
+ return TRUE;
+ if ((wc >= 0x1367 && wc <= 0x1945))
+ {
+ if ((wc >= 0x1367 && wc <= 0x1368))
+ return TRUE;
+ if (wc == 0x166E || (wc >= 0x1735 && wc <= 0x1736))
+ return TRUE;
+ if (wc == 0x1803)
+ return TRUE;
+ if (wc == 0x1809 || (wc >= 0x1944 && wc <= 0x1945))
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ if ((wc >= 0x1AA8 && wc <= 0xA92F))
+ {
+ if ((wc >= 0x1AA8 && wc <= 0x1AAB) ||
+ (wc >= 0x1B5A && wc <= 0x1B5B) ||
+ (wc >= 0x1B5E && wc <= 0x1B5F) || (wc >= 0x1B7D && wc <= 0x1B7E))
+ return TRUE;
+ if ((wc >= 0x1C3B && wc <= 0x2E2E))
+ {
+ if ((wc >= 0x1C3B && wc <= 0x1C3C))
+ return TRUE;
+ if ((wc >= 0x1C7E && wc <= 0x1C7F))
+ return TRUE;
+ if ((wc >= 0x203C && wc <= 0x203D))
+ return TRUE;
+ if ((wc >= 0x2047 && wc <= 0x2049) || wc == 0x2E2E)
+ return TRUE;
+ return FALSE;
+ }
+ if (wc == 0x2E3C ||
+ (wc >= 0x2E53 && wc <= 0x2E54) || wc == 0x3002 || wc == 0xA4FF)
+ return TRUE;
+ if ((wc >= 0xA60E && wc <= 0xA92F))
+ {
+ if ((wc >= 0xA60E && wc <= 0xA60F))
+ return TRUE;
+ if (wc == 0xA6F3 || wc == 0xA6F7)
+ return TRUE;
+ if ((wc >= 0xA876 && wc <= 0xA877))
+ return TRUE;
+ if ((wc >= 0xA8CE && wc <= 0xA8CF) || wc == 0xA92F)
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ if ((wc >= 0xA9C8 && wc <= 0x1123C))
+ {
+ if ((wc >= 0xA9C8 && wc <= 0xA9C9) ||
+ (wc >= 0xAA5D && wc <= 0xAA5F) ||
+ (wc >= 0xAAF0 && wc <= 0xAAF1) || wc == 0xABEB)
+ return TRUE;
+ if ((wc >= 0xFE56 && wc <= 0x10A57))
+ {
+ if ((wc >= 0xFE56 && wc <= 0xFE57))
+ return TRUE;
+ if (wc == 0xFF01)
+ return TRUE;
+ if (wc == 0xFF1F)
+ return TRUE;
+ if (wc == 0xFF61 || (wc >= 0x10A56 && wc <= 0x10A57))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x10F55 && wc <= 0x10F59) ||
+ (wc >= 0x10F86 && wc <= 0x10F89) ||
+ (wc >= 0x11047 && wc <= 0x11048) ||
+ (wc >= 0x110BE && wc <= 0x110C1))
+ return TRUE;
+ if ((wc >= 0x11141 && wc <= 0x1123C))
+ {
+ if ((wc >= 0x11141 && wc <= 0x11143))
+ return TRUE;
+ if ((wc >= 0x111C5 && wc <= 0x111C6) || wc == 0x111CD)
+ return TRUE;
+ if ((wc >= 0x111DE && wc <= 0x111DF))
+ return TRUE;
+ if ((wc >= 0x11238 && wc <= 0x11239) ||
+ (wc >= 0x1123B && wc <= 0x1123C))
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ if ((wc >= 0x112A9 && wc <= 0x1DA88))
+ {
+ if (wc == 0x112A9 ||
+ (wc >= 0x1144B && wc <= 0x1144C) ||
+ (wc >= 0x115C2 && wc <= 0x115C3) ||
+ (wc >= 0x115C9 && wc <= 0x115D7))
+ return TRUE;
+ if ((wc >= 0x11641 && wc <= 0x11A43))
+ {
+ if ((wc >= 0x11641 && wc <= 0x11642))
+ return TRUE;
+ if ((wc >= 0x1173C && wc <= 0x1173E))
+ return TRUE;
+ if (wc == 0x11944)
+ return TRUE;
+ if (wc == 0x11946 || (wc >= 0x11A42 && wc <= 0x11A43))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x11A9B && wc <= 0x11A9C) ||
+ (wc >= 0x11C41 && wc <= 0x11C42) ||
+ (wc >= 0x11EF7 && wc <= 0x11EF8) ||
+ (wc >= 0x16A6E && wc <= 0x16A6F))
+ return TRUE;
+ if ((wc >= 0x16AF5 && wc <= 0x1DA88))
+ {
+ if (wc == 0x16AF5)
+ return TRUE;
+ if ((wc >= 0x16B37 && wc <= 0x16B38) || wc == 0x16B44)
+ return TRUE;
+ if (wc == 0x16E98)
+ return TRUE;
+ if (wc == 0x1BC9F || wc == 0x1DA88)
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ return FALSE;
+}
+
+static inline gboolean
+_pango2_is_Virama (gunichar wc)
+{
+ if ((wc >= 0x094D && wc <= 0x0BCD))
+ {
+ if (wc == 0x094D)
+ return TRUE;
+ if (wc == 0x09CD || wc == 0x0A4D)
+ return TRUE;
+ if (wc == 0x0ACD)
+ return TRUE;
+ if (wc == 0x0B4D || wc == 0x0BCD)
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x0C4D && wc <= 0xA8C4))
+ {
+ if (wc == 0x0C4D)
+ return TRUE;
+ if (wc == 0x0CCD || wc == 0x0D4D)
+ return TRUE;
+ if (wc == 0x0DCA)
+ return TRUE;
+ if (wc == 0x1B44 || wc == 0xA806 || wc == 0xA8C4)
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0xA9C0 && wc <= 0x1134D))
+ {
+ if (wc == 0xA9C0)
+ return TRUE;
+ if (wc == 0x11046 || wc == 0x110B9)
+ return TRUE;
+ if (wc == 0x111C0)
+ return TRUE;
+ if (wc == 0x11235 || wc == 0x1134D)
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x11442 && wc <= 0x11C3F))
+ {
+ if (wc == 0x11442 || wc == 0x114C2)
+ return TRUE;
+ if (wc == 0x115BF || wc == 0x1163F)
+ return TRUE;
+ if (wc == 0x116B6 || wc == 0x11839)
+ return TRUE;
+ if (wc == 0x119E0 || wc == 0x11C3F)
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+}
+
+static inline gboolean
+_pango2_is_Vowel_Dependent (gunichar wc)
+{
+ if ((wc >= 0x093A && wc <= 0x0CC8))
+ {
+ if ((wc >= 0x093A && wc <= 0x09CC))
+ {
+ if ((wc >= 0x093A && wc <= 0x093B) ||
+ (wc >= 0x093E && wc <= 0x094C))
+ return TRUE;
+ if ((wc >= 0x094E && wc <= 0x094F) ||
+ (wc >= 0x0955 && wc <= 0x0957))
+ return TRUE;
+ if ((wc >= 0x0962 && wc <= 0x0963) ||
+ (wc >= 0x09BE && wc <= 0x09C4))
+ return TRUE;
+ if ((wc >= 0x09C7 && wc <= 0x09C8) ||
+ (wc >= 0x09CB && wc <= 0x09CC))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x09D7 && wc <= 0x0ACC))
+ {
+ if (wc == 0x09D7 || (wc >= 0x09E2 && wc <= 0x09E3))
+ return TRUE;
+ if ((wc >= 0x0A3E && wc <= 0x0A42) ||
+ (wc >= 0x0A47 && wc <= 0x0A48))
+ return TRUE;
+ if ((wc >= 0x0A4B && wc <= 0x0A4C) ||
+ (wc >= 0x0ABE && wc <= 0x0AC5))
+ return TRUE;
+ if ((wc >= 0x0AC7 && wc <= 0x0AC9) ||
+ (wc >= 0x0ACB && wc <= 0x0ACC))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x0AE2 && wc <= 0x0BC8))
+ {
+ if ((wc >= 0x0AE2 && wc <= 0x0AE3) ||
+ (wc >= 0x0B3E && wc <= 0x0B44))
+ return TRUE;
+ if ((wc >= 0x0B47 && wc <= 0x0B48) ||
+ (wc >= 0x0B4B && wc <= 0x0B4C))
+ return TRUE;
+ if ((wc >= 0x0B55 && wc <= 0x0B57) ||
+ (wc >= 0x0B62 && wc <= 0x0B63))
+ return TRUE;
+ if ((wc >= 0x0BBE && wc <= 0x0BC2) ||
+ (wc >= 0x0BC6 && wc <= 0x0BC8))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x0BCA && wc <= 0x0CC8))
+ {
+ if ((wc >= 0x0BCA && wc <= 0x0BCC) || wc == 0x0BD7)
+ return TRUE;
+ if ((wc >= 0x0C3E && wc <= 0x0C44) ||
+ (wc >= 0x0C46 && wc <= 0x0C48))
+ return TRUE;
+ if ((wc >= 0x0C4A && wc <= 0x0C4C) ||
+ (wc >= 0x0C55 && wc <= 0x0C56))
+ return TRUE;
+ if ((wc >= 0x0C62 && wc <= 0x0C63) ||
+ (wc >= 0x0CBE && wc <= 0x0CC4) ||
+ (wc >= 0x0CC6 && wc <= 0x0CC8))
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ if ((wc >= 0x0CCA && wc <= 0x1928))
+ {
+ if ((wc >= 0x0CCA && wc <= 0x0D63))
+ {
+ if ((wc >= 0x0CCA && wc <= 0x0CCC) ||
+ (wc >= 0x0CD5 && wc <= 0x0CD6))
+ return TRUE;
+ if ((wc >= 0x0CE2 && wc <= 0x0CE3) ||
+ (wc >= 0x0D3E && wc <= 0x0D44))
+ return TRUE;
+ if ((wc >= 0x0D46 && wc <= 0x0D48) ||
+ (wc >= 0x0D4A && wc <= 0x0D4C))
+ return TRUE;
+ if (wc == 0x0D57 || (wc >= 0x0D62 && wc <= 0x0D63))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x0DCF && wc <= 0x0EBB))
+ {
+ if ((wc >= 0x0DCF && wc <= 0x0DD4) || wc == 0x0DD6)
+ return TRUE;
+ if ((wc >= 0x0DD8 && wc <= 0x0DDF) ||
+ (wc >= 0x0DF2 && wc <= 0x0DF3))
+ return TRUE;
+ if ((wc >= 0x0E30 && wc <= 0x0E39) ||
+ (wc >= 0x0E40 && wc <= 0x0E45))
+ return TRUE;
+ if (wc == 0x0E47 || (wc >= 0x0EB0 && wc <= 0x0EB9) || wc == 0x0EBB)
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x0EC0 && wc <= 0x1074))
+ {
+ if ((wc >= 0x0EC0 && wc <= 0x0EC4) ||
+ (wc >= 0x0F71 && wc <= 0x0F7D))
+ return TRUE;
+ if ((wc >= 0x0F80 && wc <= 0x0F81) ||
+ (wc >= 0x102B && wc <= 0x1035))
+ return TRUE;
+ if ((wc >= 0x1056 && wc <= 0x1059) || wc == 0x1062)
+ return TRUE;
+ if ((wc >= 0x1067 && wc <= 0x1068) ||
+ (wc >= 0x1071 && wc <= 0x1074))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x1083 && wc <= 0x1928))
+ {
+ if ((wc >= 0x1083 && wc <= 0x1086) ||
+ (wc >= 0x109C && wc <= 0x109D))
+ return TRUE;
+ if ((wc >= 0x1712 && wc <= 0x1713) ||
+ (wc >= 0x1732 && wc <= 0x1733))
+ return TRUE;
+ if ((wc >= 0x1752 && wc <= 0x1753) ||
+ (wc >= 0x1772 && wc <= 0x1773))
+ return TRUE;
+ if ((wc >= 0x17B6 && wc <= 0x17C5) ||
+ wc == 0x17C8 || (wc >= 0x1920 && wc <= 0x1928))
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ if ((wc >= 0x193A && wc <= 0x112E8))
+ {
+ if ((wc >= 0x193A && wc <= 0x1C2C))
+ {
+ if (wc == 0x193A || (wc >= 0x19B0 && wc <= 0x19C0))
+ return TRUE;
+ if ((wc >= 0x1A17 && wc <= 0x1A1B) ||
+ (wc >= 0x1A61 && wc <= 0x1A73))
+ return TRUE;
+ if ((wc >= 0x1B35 && wc <= 0x1B43) ||
+ (wc >= 0x1BA4 && wc <= 0x1BA9))
+ return TRUE;
+ if ((wc >= 0x1BE7 && wc <= 0x1BEF) ||
+ (wc >= 0x1C26 && wc <= 0x1C2C))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0xA802 && wc <= 0xAA32))
+ {
+ if (wc == 0xA802 || (wc >= 0xA823 && wc <= 0xA827))
+ return TRUE;
+ if ((wc >= 0xA8B5 && wc <= 0xA8C3) || wc == 0xA8FF)
+ return TRUE;
+ if ((wc >= 0xA947 && wc <= 0xA94E) ||
+ (wc >= 0xA9B4 && wc <= 0xA9BC))
+ return TRUE;
+ if (wc == 0xA9E5 || (wc >= 0xAA29 && wc <= 0xAA32))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0xAAB0 && wc <= 0x11074))
+ {
+ if ((wc >= 0xAAB0 && wc <= 0xAABE) ||
+ (wc >= 0xAAEB && wc <= 0xAAEF))
+ return TRUE;
+ if ((wc >= 0xABE3 && wc <= 0xABEA) ||
+ (wc >= 0x10A01 && wc <= 0x10A03))
+ return TRUE;
+ if ((wc >= 0x10A05 && wc <= 0x10A06) ||
+ (wc >= 0x10A0C && wc <= 0x10A0D))
+ return TRUE;
+ if ((wc >= 0x11038 && wc <= 0x11045) ||
+ (wc >= 0x11073 && wc <= 0x11074))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x110B0 && wc <= 0x112E8))
+ {
+ if ((wc >= 0x110B0 && wc <= 0x110B8) || wc == 0x110C2)
+ return TRUE;
+ if ((wc >= 0x11127 && wc <= 0x11132) ||
+ (wc >= 0x11145 && wc <= 0x11146))
+ return TRUE;
+ if ((wc >= 0x111B3 && wc <= 0x111BF) ||
+ (wc >= 0x111CB && wc <= 0x111CC))
+ return TRUE;
+ if (wc == 0x111CE ||
+ (wc >= 0x1122C && wc <= 0x11233) ||
+ (wc >= 0x112E0 && wc <= 0x112E8))
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ if ((wc >= 0x1133E && wc <= 0x11EF6))
+ {
+ if ((wc >= 0x1133E && wc <= 0x115B5))
+ {
+ if ((wc >= 0x1133E && wc <= 0x11344) ||
+ (wc >= 0x11347 && wc <= 0x11348))
+ return TRUE;
+ if ((wc >= 0x1134B && wc <= 0x1134C) || wc == 0x11357)
+ return TRUE;
+ if ((wc >= 0x11362 && wc <= 0x11363) ||
+ (wc >= 0x11435 && wc <= 0x11441))
+ return TRUE;
+ if ((wc >= 0x114B0 && wc <= 0x114BE) ||
+ (wc >= 0x115AF && wc <= 0x115B5))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x115B8 && wc <= 0x11938))
+ {
+ if ((wc >= 0x115B8 && wc <= 0x115BB) ||
+ (wc >= 0x115DC && wc <= 0x115DD))
+ return TRUE;
+ if ((wc >= 0x11630 && wc <= 0x1163C) || wc == 0x11640)
+ return TRUE;
+ if ((wc >= 0x116AD && wc <= 0x116B5) ||
+ (wc >= 0x11720 && wc <= 0x1172A))
+ return TRUE;
+ if ((wc >= 0x1182C && wc <= 0x11836) ||
+ (wc >= 0x11930 && wc <= 0x11935) ||
+ (wc >= 0x11937 && wc <= 0x11938))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x119D1 && wc <= 0x11CB4))
+ {
+ if ((wc >= 0x119D1 && wc <= 0x119D7) ||
+ (wc >= 0x119DA && wc <= 0x119DD))
+ return TRUE;
+ if (wc == 0x119E4 || (wc >= 0x11A01 && wc <= 0x11A0A))
+ return TRUE;
+ if ((wc >= 0x11A51 && wc <= 0x11A5B) ||
+ (wc >= 0x11C2F && wc <= 0x11C36))
+ return TRUE;
+ if ((wc >= 0x11C38 && wc <= 0x11C3B) ||
+ (wc >= 0x11CB0 && wc <= 0x11CB4))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x11D31 && wc <= 0x11EF6))
+ {
+ if ((wc >= 0x11D31 && wc <= 0x11D36) || wc == 0x11D3A)
+ return TRUE;
+ if ((wc >= 0x11D3C && wc <= 0x11D3D) || wc == 0x11D3F)
+ return TRUE;
+ if (wc == 0x11D43 || (wc >= 0x11D8A && wc <= 0x11D8E))
+ return TRUE;
+ if ((wc >= 0x11D90 && wc <= 0x11D91) ||
+ (wc >= 0x11D93 && wc <= 0x11D94) ||
+ (wc >= 0x11EF3 && wc <= 0x11EF6))
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ return FALSE;
+}
+
+static inline gboolean
+_pango2_is_Consonant_Prefixed (gunichar wc)
+{
+ if ((wc >= 0x111C2 && wc <= 0x111C3) ||
+ wc == 0x1193F || wc == 0x11A3A || (wc >= 0x11A84 && wc <= 0x11A89))
+ return TRUE;
+ return FALSE;
+}
+
+static inline gboolean
+_pango2_is_Consonant_Preceding_Repha (gunichar wc)
+{
+ if (wc == 0x0D4E || wc == 0x11941 || wc == 0x11D46)
+ return TRUE;
+ return FALSE;
+}
+
+static inline gboolean
+_pango2_is_EastAsianWide (gunichar wc)
+{
+ if ((wc >= 0x1100 && wc <= 0x27B0))
+ {
+ if ((wc >= 0x1100 && wc <= 0x25FE))
+ {
+ if ((wc >= 0x1100 && wc <= 0x115F) || wc == 0x20A9)
+ return TRUE;
+ if ((wc >= 0x231A && wc <= 0x231B) ||
+ (wc >= 0x2329 && wc <= 0x232A))
+ return TRUE;
+ if ((wc >= 0x23E9 && wc <= 0x23EC) || wc == 0x23F0)
+ return TRUE;
+ if (wc == 0x23F3 || (wc >= 0x25FD && wc <= 0x25FE))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x2614 && wc <= 0x26C5))
+ {
+ if ((wc >= 0x2614 && wc <= 0x2615) ||
+ (wc >= 0x2648 && wc <= 0x2653))
+ return TRUE;
+ if (wc == 0x267F || wc == 0x2693)
+ return TRUE;
+ if (wc == 0x26A1 || (wc >= 0x26AA && wc <= 0x26AB))
+ return TRUE;
+ if ((wc >= 0x26BD && wc <= 0x26BE) ||
+ (wc >= 0x26C4 && wc <= 0x26C5))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x26CE && wc <= 0x2705))
+ {
+ if (wc == 0x26CE || wc == 0x26D4)
+ return TRUE;
+ if (wc == 0x26EA || (wc >= 0x26F2 && wc <= 0x26F3))
+ return TRUE;
+ if (wc == 0x26F5 || wc == 0x26FA)
+ return TRUE;
+ if (wc == 0x26FD || wc == 0x2705)
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x270A && wc <= 0x27B0))
+ {
+ if ((wc >= 0x270A && wc <= 0x270B) || wc == 0x2728)
+ return TRUE;
+ if (wc == 0x274C || wc == 0x274E)
+ return TRUE;
+ if ((wc >= 0x2753 && wc <= 0x2755) || wc == 0x2757)
+ return TRUE;
+ if ((wc >= 0x2795 && wc <= 0x2797) || wc == 0x27B0)
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ if ((wc >= 0x27BF && wc <= 0xFFD7))
+ {
+ if ((wc >= 0x27BF && wc <= 0x2FFB))
+ {
+ if (wc == 0x27BF || (wc >= 0x2B1B && wc <= 0x2B1C))
+ return TRUE;
+ if (wc == 0x2B50 || wc == 0x2B55)
+ return TRUE;
+ if ((wc >= 0x2E80 && wc <= 0x2E99) ||
+ (wc >= 0x2E9B && wc <= 0x2EF3))
+ return TRUE;
+ if ((wc >= 0x2F00 && wc <= 0x2FD5) ||
+ (wc >= 0x2FF0 && wc <= 0x2FFB))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x3000 && wc <= 0x321E))
+ {
+ if (wc == 0x3000 || (wc >= 0x3001 && wc <= 0x303E))
+ return TRUE;
+ if ((wc >= 0x3041 && wc <= 0x3096) ||
+ (wc >= 0x3099 && wc <= 0x30FF))
+ return TRUE;
+ if ((wc >= 0x3105 && wc <= 0x312F) ||
+ (wc >= 0x3131 && wc <= 0x318E))
+ return TRUE;
+ if ((wc >= 0x3190 && wc <= 0x31E3) ||
+ (wc >= 0x31F0 && wc <= 0x321E))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x3220 && wc <= 0xFE19))
+ {
+ if ((wc >= 0x3220 && wc <= 0x3247) ||
+ (wc >= 0x3250 && wc <= 0x4DBF))
+ return TRUE;
+ if ((wc >= 0x4E00 && wc <= 0xA48C) ||
+ (wc >= 0xA490 && wc <= 0xA4C6))
+ return TRUE;
+ if ((wc >= 0xA960 && wc <= 0xA97C) ||
+ (wc >= 0xAC00 && wc <= 0xD7A3))
+ return TRUE;
+ if ((wc >= 0xF900 && wc <= 0xFAFF) ||
+ (wc >= 0xFE10 && wc <= 0xFE19))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0xFE30 && wc <= 0xFFD7))
+ {
+ if ((wc >= 0xFE30 && wc <= 0xFE52) ||
+ (wc >= 0xFE54 && wc <= 0xFE66))
+ return TRUE;
+ if ((wc >= 0xFE68 && wc <= 0xFE6B) ||
+ (wc >= 0xFF01 && wc <= 0xFF60))
+ return TRUE;
+ if ((wc >= 0xFF61 && wc <= 0xFFBE) ||
+ (wc >= 0xFFC2 && wc <= 0xFFC7))
+ return TRUE;
+ if ((wc >= 0xFFCA && wc <= 0xFFCF) ||
+ (wc >= 0xFFD2 && wc <= 0xFFD7))
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ if ((wc >= 0xFFDA && wc <= 0x1F3F4))
+ {
+ if ((wc >= 0xFFDA && wc <= 0x18D08))
+ {
+ if ((wc >= 0xFFDA && wc <= 0xFFDC) ||
+ (wc >= 0xFFE0 && wc <= 0xFFE6))
+ return TRUE;
+ if ((wc >= 0xFFE8 && wc <= 0xFFEE) ||
+ (wc >= 0x16FE0 && wc <= 0x16FE4))
+ return TRUE;
+ if ((wc >= 0x16FF0 && wc <= 0x16FF1) ||
+ (wc >= 0x17000 && wc <= 0x187F7))
+ return TRUE;
+ if ((wc >= 0x18800 && wc <= 0x18CD5) ||
+ (wc >= 0x18D00 && wc <= 0x18D08))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x1AFF0 && wc <= 0x1F004))
+ {
+ if ((wc >= 0x1AFF0 && wc <= 0x1AFF3) ||
+ (wc >= 0x1AFF5 && wc <= 0x1AFFB))
+ return TRUE;
+ if ((wc >= 0x1AFFD && wc <= 0x1AFFE) ||
+ (wc >= 0x1B000 && wc <= 0x1B122))
+ return TRUE;
+ if ((wc >= 0x1B150 && wc <= 0x1B152) ||
+ (wc >= 0x1B164 && wc <= 0x1B167))
+ return TRUE;
+ if ((wc >= 0x1B170 && wc <= 0x1B2FB) || wc == 0x1F004)
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x1F0CF && wc <= 0x1F265))
+ {
+ if (wc == 0x1F0CF || wc == 0x1F18E)
+ return TRUE;
+ if ((wc >= 0x1F191 && wc <= 0x1F19A) ||
+ (wc >= 0x1F200 && wc <= 0x1F202))
+ return TRUE;
+ if ((wc >= 0x1F210 && wc <= 0x1F23B) ||
+ (wc >= 0x1F240 && wc <= 0x1F248))
+ return TRUE;
+ if ((wc >= 0x1F250 && wc <= 0x1F251) ||
+ (wc >= 0x1F260 && wc <= 0x1F265))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x1F300 && wc <= 0x1F3F4))
+ {
+ if ((wc >= 0x1F300 && wc <= 0x1F320) ||
+ (wc >= 0x1F32D && wc <= 0x1F335))
+ return TRUE;
+ if ((wc >= 0x1F337 && wc <= 0x1F37C) ||
+ (wc >= 0x1F37E && wc <= 0x1F393))
+ return TRUE;
+ if ((wc >= 0x1F3A0 && wc <= 0x1F3CA) ||
+ (wc >= 0x1F3CF && wc <= 0x1F3D3))
+ return TRUE;
+ if ((wc >= 0x1F3E0 && wc <= 0x1F3F0) || wc == 0x1F3F4)
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ if ((wc >= 0x1F3F8 && wc <= 0x3FFFD))
+ {
+ if ((wc >= 0x1F3F8 && wc <= 0x1F596))
+ {
+ if ((wc >= 0x1F3F8 && wc <= 0x1F43E) || wc == 0x1F440)
+ return TRUE;
+ if ((wc >= 0x1F442 && wc <= 0x1F4FC) ||
+ (wc >= 0x1F4FF && wc <= 0x1F53D))
+ return TRUE;
+ if ((wc >= 0x1F54B && wc <= 0x1F54E) ||
+ (wc >= 0x1F550 && wc <= 0x1F567))
+ return TRUE;
+ if (wc == 0x1F57A || (wc >= 0x1F595 && wc <= 0x1F596))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x1F5A4 && wc <= 0x1F6EC))
+ {
+ if (wc == 0x1F5A4 || (wc >= 0x1F5FB && wc <= 0x1F64F))
+ return TRUE;
+ if ((wc >= 0x1F680 && wc <= 0x1F6C5) || wc == 0x1F6CC)
+ return TRUE;
+ if ((wc >= 0x1F6D0 && wc <= 0x1F6D2) ||
+ (wc >= 0x1F6D5 && wc <= 0x1F6D7))
+ return TRUE;
+ if ((wc >= 0x1F6DD && wc <= 0x1F6DF) ||
+ (wc >= 0x1F6EB && wc <= 0x1F6EC))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x1F6F4 && wc <= 0x1FA7C))
+ {
+ if ((wc >= 0x1F6F4 && wc <= 0x1F6FC) ||
+ (wc >= 0x1F7E0 && wc <= 0x1F7EB))
+ return TRUE;
+ if (wc == 0x1F7F0 || (wc >= 0x1F90C && wc <= 0x1F93A))
+ return TRUE;
+ if ((wc >= 0x1F93C && wc <= 0x1F945) ||
+ (wc >= 0x1F947 && wc <= 0x1F9FF))
+ return TRUE;
+ if ((wc >= 0x1FA70 && wc <= 0x1FA74) ||
+ (wc >= 0x1FA78 && wc <= 0x1FA7C))
+ return TRUE;
+ return FALSE;
+ }
+ if ((wc >= 0x1FA80 && wc <= 0x3FFFD))
+ {
+ if ((wc >= 0x1FA80 && wc <= 0x1FA86) ||
+ (wc >= 0x1FA90 && wc <= 0x1FAAC))
+ return TRUE;
+ if ((wc >= 0x1FAB0 && wc <= 0x1FABA) ||
+ (wc >= 0x1FAC0 && wc <= 0x1FAC5))
+ return TRUE;
+ if ((wc >= 0x1FAD0 && wc <= 0x1FAD9) ||
+ (wc >= 0x1FAE0 && wc <= 0x1FAE7))
+ return TRUE;
+ if ((wc >= 0x1FAF0 && wc <= 0x1FAF6) ||
+ (wc >= 0x20000 && wc <= 0x2FFFD) ||
+ (wc >= 0x30000 && wc <= 0x3FFFD))
+ return TRUE;
+ return FALSE;
+ }
+ return FALSE;
+ }
+ return FALSE;
+}
+
+#endif /* PANGO2_BREAK_TABLE_H */
+
+/* == End of generated table == */
diff --git a/pango2/pango-break.h b/pango2/pango-break.h
new file mode 100644
index 00000000..e3e479e4
--- /dev/null
+++ b/pango2/pango-break.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 1999 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <pango2/pango-item.h>
+
+G_BEGIN_DECLS
+
+
+/* Logical attributes of a character.
+ */
+/**
+ * Pango2LogAttr:
+ * @is_line_break: if set, can break line in front of character
+ * @is_mandatory_break: if set, must break line in front of character
+ * @is_char_break: if set, can break here when doing character wrapping
+ * @is_white: is whitespace character
+ * @is_cursor_position: if set, cursor can appear in front of character.
+ * i.e. this is a grapheme boundary, or the first character in the text.
+ * This flag implements Unicode's
+ * [Grapheme Cluster Boundaries](http://www.unicode.org/reports/tr29/)
+ * semantics.
+ * @is_word_start: is first character in a word
+ * @is_word_end: is first non-word char after a word
+ * Note that in degenerate cases, you could have both @is_word_start
+ * and @is_word_end set for some character.
+ * @is_sentence_boundary: is a sentence boundary.
+ * There are two ways to divide sentences. The first assigns all
+ * inter-sentence whitespace/control/format chars to some sentence,
+ * so all chars are in some sentence; @is_sentence_boundary denotes
+ * the boundaries there. The second way doesn't assign
+ * between-sentence spaces, etc. to any sentence, so
+ * @is_sentence_start/@is_sentence_end mark the boundaries of those sentences.
+ * @is_sentence_start: is first character in a sentence
+ * @is_sentence_end: is first char after a sentence.
+ * Note that in degenerate cases, you could have both @is_sentence_start
+ * and @is_sentence_end set for some character. (e.g. no space after a
+ * period, so the next sentence starts right away)
+ * @backspace_deletes_character: if set, backspace deletes one character
+ * rather than the entire grapheme cluster. This field is only meaningful
+ * on grapheme boundaries (where @is_cursor_position is set). In some languages,
+ * the full grapheme (e.g. letter + diacritics) is considered a unit, while in
+ * others, each decomposed character in the grapheme is a unit. In the default
+ * implementation of [func@default_break], this bit is set on all grapheme
+ * boundaries except those following Latin, Cyrillic or Greek base characters.
+ * @is_expandable_space: is a whitespace character that can possibly be
+ * expanded for justification purposes.
+ * @is_word_boundary: is a word boundary, as defined by UAX#29.
+ * More specifically, means that this is not a position in the middle of a word.
+ * For example, both sides of a punctuation mark are considered word boundaries.
+ * This flag is particularly useful when selecting text word-by-word. This flag
+ * implements Unicode's [Word Boundaries](http://www.unicode.org/reports/tr29/)
+ * semantics.
+ * @break_inserts_hyphen: when breaking lines before this char, insert a hyphen.
+ * @break_removes_preceding: when breaking lines before this char, remove the
+ * preceding char.
+ *
+ * The `Pango2LogAttr` structure stores information about the attributes of a
+ * single character.
+ */
+struct _Pango2LogAttr
+{
+ guint is_line_break : 1;
+ guint is_mandatory_break : 1;
+ guint is_char_break : 1;
+ guint is_white : 1;
+ guint is_cursor_position : 1;
+ guint is_word_start : 1;
+ guint is_word_end : 1;
+ guint is_sentence_boundary : 1;
+ guint is_sentence_start : 1;
+ guint is_sentence_end : 1;
+ guint backspace_deletes_character : 1;
+ guint is_expandable_space : 1;
+ guint is_word_boundary : 1;
+ guint break_inserts_hyphen : 1;
+ guint break_removes_preceding : 1;
+
+ guint reserved : 17;
+};
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_get_log_attrs (const char *text,
+ int length,
+ Pango2AttrList *attr_list,
+ int level,
+ Pango2Language *language,
+ Pango2LogAttr *attrs,
+ int attrs_len);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_default_break (const char *text,
+ int length,
+ Pango2LogAttr *attrs,
+ int attrs_len);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_tailor_break (const char *text,
+ int length,
+ Pango2Analysis *analysis,
+ int offset,
+ Pango2LogAttr *attrs,
+ int attrs_len);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_attr_break (const char *text,
+ int length,
+ Pango2AttrList *attr_list,
+ int offset,
+ Pango2LogAttr *attrs,
+ int attrs_len);
+
+G_END_DECLS
diff --git a/pango2/pango-color-table.h b/pango2/pango-color-table.h
new file mode 100644
index 00000000..a3b95927
--- /dev/null
+++ b/pango2/pango-color-table.h
@@ -0,0 +1,1349 @@
+/* pango-color-table.h: Generated by gen-color-table.pl from rgb.txt
+ *
+ * Date: Thu May 5 15:35:35 2016
+ *
+ * Do not edit.
+ */
+static const char color_names[] =
+ "AliceBlue\0"
+ "AntiqueWhite\0"
+ "AntiqueWhite1\0"
+ "AntiqueWhite2\0"
+ "AntiqueWhite3\0"
+ "AntiqueWhite4\0"
+ "aqua\0"
+ "aquamarine\0"
+ "aquamarine1\0"
+ "aquamarine2\0"
+ "aquamarine3\0"
+ "aquamarine4\0"
+ "azure\0"
+ "azure1\0"
+ "azure2\0"
+ "azure3\0"
+ "azure4\0"
+ "beige\0"
+ "bisque\0"
+ "bisque1\0"
+ "bisque2\0"
+ "bisque3\0"
+ "bisque4\0"
+ "black\0"
+ "BlanchedAlmond\0"
+ "blue\0"
+ "blue1\0"
+ "blue2\0"
+ "blue3\0"
+ "blue4\0"
+ "BlueViolet\0"
+ "brown\0"
+ "brown1\0"
+ "brown2\0"
+ "brown3\0"
+ "brown4\0"
+ "burlywood\0"
+ "burlywood1\0"
+ "burlywood2\0"
+ "burlywood3\0"
+ "burlywood4\0"
+ "CadetBlue\0"
+ "CadetBlue1\0"
+ "CadetBlue2\0"
+ "CadetBlue3\0"
+ "CadetBlue4\0"
+ "chartreuse\0"
+ "chartreuse1\0"
+ "chartreuse2\0"
+ "chartreuse3\0"
+ "chartreuse4\0"
+ "chocolate\0"
+ "chocolate1\0"
+ "chocolate2\0"
+ "chocolate3\0"
+ "chocolate4\0"
+ "coral\0"
+ "coral1\0"
+ "coral2\0"
+ "coral3\0"
+ "coral4\0"
+ "CornflowerBlue\0"
+ "cornsilk\0"
+ "cornsilk1\0"
+ "cornsilk2\0"
+ "cornsilk3\0"
+ "cornsilk4\0"
+ "crimson\0"
+ "cyan\0"
+ "cyan1\0"
+ "cyan2\0"
+ "cyan3\0"
+ "cyan4\0"
+ "DarkBlue\0"
+ "DarkCyan\0"
+ "DarkGoldenrod\0"
+ "DarkGoldenrod1\0"
+ "DarkGoldenrod2\0"
+ "DarkGoldenrod3\0"
+ "DarkGoldenrod4\0"
+ "DarkGray\0"
+ "DarkGreen\0"
+ "DarkGrey\0"
+ "DarkKhaki\0"
+ "DarkMagenta\0"
+ "DarkOliveGreen\0"
+ "DarkOliveGreen1\0"
+ "DarkOliveGreen2\0"
+ "DarkOliveGreen3\0"
+ "DarkOliveGreen4\0"
+ "DarkOrange\0"
+ "DarkOrange1\0"
+ "DarkOrange2\0"
+ "DarkOrange3\0"
+ "DarkOrange4\0"
+ "DarkOrchid\0"
+ "DarkOrchid1\0"
+ "DarkOrchid2\0"
+ "DarkOrchid3\0"
+ "DarkOrchid4\0"
+ "DarkRed\0"
+ "DarkSalmon\0"
+ "DarkSeaGreen\0"
+ "DarkSeaGreen1\0"
+ "DarkSeaGreen2\0"
+ "DarkSeaGreen3\0"
+ "DarkSeaGreen4\0"
+ "DarkSlateBlue\0"
+ "DarkSlateGray\0"
+ "DarkSlateGray1\0"
+ "DarkSlateGray2\0"
+ "DarkSlateGray3\0"
+ "DarkSlateGray4\0"
+ "DarkSlateGrey\0"
+ "DarkTurquoise\0"
+ "DarkViolet\0"
+ "DeepPink\0"
+ "DeepPink1\0"
+ "DeepPink2\0"
+ "DeepPink3\0"
+ "DeepPink4\0"
+ "DeepSkyBlue\0"
+ "DeepSkyBlue1\0"
+ "DeepSkyBlue2\0"
+ "DeepSkyBlue3\0"
+ "DeepSkyBlue4\0"
+ "DimGray\0"
+ "DimGrey\0"
+ "DodgerBlue\0"
+ "DodgerBlue1\0"
+ "DodgerBlue2\0"
+ "DodgerBlue3\0"
+ "DodgerBlue4\0"
+ "firebrick\0"
+ "firebrick1\0"
+ "firebrick2\0"
+ "firebrick3\0"
+ "firebrick4\0"
+ "FloralWhite\0"
+ "ForestGreen\0"
+ "fuchsia\0"
+ "gainsboro\0"
+ "GhostWhite\0"
+ "gold\0"
+ "gold1\0"
+ "gold2\0"
+ "gold3\0"
+ "gold4\0"
+ "goldenrod\0"
+ "goldenrod1\0"
+ "goldenrod2\0"
+ "goldenrod3\0"
+ "goldenrod4\0"
+ "gray\0"
+ "gray0\0"
+ "gray1\0"
+ "gray10\0"
+ "gray100\0"
+ "gray11\0"
+ "gray12\0"
+ "gray13\0"
+ "gray14\0"
+ "gray15\0"
+ "gray16\0"
+ "gray17\0"
+ "gray18\0"
+ "gray19\0"
+ "gray2\0"
+ "gray20\0"
+ "gray21\0"
+ "gray22\0"
+ "gray23\0"
+ "gray24\0"
+ "gray25\0"
+ "gray26\0"
+ "gray27\0"
+ "gray28\0"
+ "gray29\0"
+ "gray3\0"
+ "gray30\0"
+ "gray31\0"
+ "gray32\0"
+ "gray33\0"
+ "gray34\0"
+ "gray35\0"
+ "gray36\0"
+ "gray37\0"
+ "gray38\0"
+ "gray39\0"
+ "gray4\0"
+ "gray40\0"
+ "gray41\0"
+ "gray42\0"
+ "gray43\0"
+ "gray44\0"
+ "gray45\0"
+ "gray46\0"
+ "gray47\0"
+ "gray48\0"
+ "gray49\0"
+ "gray5\0"
+ "gray50\0"
+ "gray51\0"
+ "gray52\0"
+ "gray53\0"
+ "gray54\0"
+ "gray55\0"
+ "gray56\0"
+ "gray57\0"
+ "gray58\0"
+ "gray59\0"
+ "gray6\0"
+ "gray60\0"
+ "gray61\0"
+ "gray62\0"
+ "gray63\0"
+ "gray64\0"
+ "gray65\0"
+ "gray66\0"
+ "gray67\0"
+ "gray68\0"
+ "gray69\0"
+ "gray7\0"
+ "gray70\0"
+ "gray71\0"
+ "gray72\0"
+ "gray73\0"
+ "gray74\0"
+ "gray75\0"
+ "gray76\0"
+ "gray77\0"
+ "gray78\0"
+ "gray79\0"
+ "gray8\0"
+ "gray80\0"
+ "gray81\0"
+ "gray82\0"
+ "gray83\0"
+ "gray84\0"
+ "gray85\0"
+ "gray86\0"
+ "gray87\0"
+ "gray88\0"
+ "gray89\0"
+ "gray9\0"
+ "gray90\0"
+ "gray91\0"
+ "gray92\0"
+ "gray93\0"
+ "gray94\0"
+ "gray95\0"
+ "gray96\0"
+ "gray97\0"
+ "gray98\0"
+ "gray99\0"
+ "green\0"
+ "green1\0"
+ "green2\0"
+ "green3\0"
+ "green4\0"
+ "GreenYellow\0"
+ "grey\0"
+ "grey0\0"
+ "grey1\0"
+ "grey10\0"
+ "grey100\0"
+ "grey11\0"
+ "grey12\0"
+ "grey13\0"
+ "grey14\0"
+ "grey15\0"
+ "grey16\0"
+ "grey17\0"
+ "grey18\0"
+ "grey19\0"
+ "grey2\0"
+ "grey20\0"
+ "grey21\0"
+ "grey22\0"
+ "grey23\0"
+ "grey24\0"
+ "grey25\0"
+ "grey26\0"
+ "grey27\0"
+ "grey28\0"
+ "grey29\0"
+ "grey3\0"
+ "grey30\0"
+ "grey31\0"
+ "grey32\0"
+ "grey33\0"
+ "grey34\0"
+ "grey35\0"
+ "grey36\0"
+ "grey37\0"
+ "grey38\0"
+ "grey39\0"
+ "grey4\0"
+ "grey40\0"
+ "grey41\0"
+ "grey42\0"
+ "grey43\0"
+ "grey44\0"
+ "grey45\0"
+ "grey46\0"
+ "grey47\0"
+ "grey48\0"
+ "grey49\0"
+ "grey5\0"
+ "grey50\0"
+ "grey51\0"
+ "grey52\0"
+ "grey53\0"
+ "grey54\0"
+ "grey55\0"
+ "grey56\0"
+ "grey57\0"
+ "grey58\0"
+ "grey59\0"
+ "grey6\0"
+ "grey60\0"
+ "grey61\0"
+ "grey62\0"
+ "grey63\0"
+ "grey64\0"
+ "grey65\0"
+ "grey66\0"
+ "grey67\0"
+ "grey68\0"
+ "grey69\0"
+ "grey7\0"
+ "grey70\0"
+ "grey71\0"
+ "grey72\0"
+ "grey73\0"
+ "grey74\0"
+ "grey75\0"
+ "grey76\0"
+ "grey77\0"
+ "grey78\0"
+ "grey79\0"
+ "grey8\0"
+ "grey80\0"
+ "grey81\0"
+ "grey82\0"
+ "grey83\0"
+ "grey84\0"
+ "grey85\0"
+ "grey86\0"
+ "grey87\0"
+ "grey88\0"
+ "grey89\0"
+ "grey9\0"
+ "grey90\0"
+ "grey91\0"
+ "grey92\0"
+ "grey93\0"
+ "grey94\0"
+ "grey95\0"
+ "grey96\0"
+ "grey97\0"
+ "grey98\0"
+ "grey99\0"
+ "honeydew\0"
+ "honeydew1\0"
+ "honeydew2\0"
+ "honeydew3\0"
+ "honeydew4\0"
+ "HotPink\0"
+ "HotPink1\0"
+ "HotPink2\0"
+ "HotPink3\0"
+ "HotPink4\0"
+ "IndianRed\0"
+ "IndianRed1\0"
+ "IndianRed2\0"
+ "IndianRed3\0"
+ "IndianRed4\0"
+ "indigo\0"
+ "ivory\0"
+ "ivory1\0"
+ "ivory2\0"
+ "ivory3\0"
+ "ivory4\0"
+ "khaki\0"
+ "khaki1\0"
+ "khaki2\0"
+ "khaki3\0"
+ "khaki4\0"
+ "lavender\0"
+ "LavenderBlush\0"
+ "LavenderBlush1\0"
+ "LavenderBlush2\0"
+ "LavenderBlush3\0"
+ "LavenderBlush4\0"
+ "LawnGreen\0"
+ "LemonChiffon\0"
+ "LemonChiffon1\0"
+ "LemonChiffon2\0"
+ "LemonChiffon3\0"
+ "LemonChiffon4\0"
+ "LightBlue\0"
+ "LightBlue1\0"
+ "LightBlue2\0"
+ "LightBlue3\0"
+ "LightBlue4\0"
+ "LightCoral\0"
+ "LightCyan\0"
+ "LightCyan1\0"
+ "LightCyan2\0"
+ "LightCyan3\0"
+ "LightCyan4\0"
+ "LightGoldenrod\0"
+ "LightGoldenrod1\0"
+ "LightGoldenrod2\0"
+ "LightGoldenrod3\0"
+ "LightGoldenrod4\0"
+ "LightGoldenrodYellow\0"
+ "LightGray\0"
+ "LightGreen\0"
+ "LightGrey\0"
+ "LightPink\0"
+ "LightPink1\0"
+ "LightPink2\0"
+ "LightPink3\0"
+ "LightPink4\0"
+ "LightSalmon\0"
+ "LightSalmon1\0"
+ "LightSalmon2\0"
+ "LightSalmon3\0"
+ "LightSalmon4\0"
+ "LightSeaGreen\0"
+ "LightSkyBlue\0"
+ "LightSkyBlue1\0"
+ "LightSkyBlue2\0"
+ "LightSkyBlue3\0"
+ "LightSkyBlue4\0"
+ "LightSlateBlue\0"
+ "LightSlateGray\0"
+ "LightSlateGrey\0"
+ "LightSteelBlue\0"
+ "LightSteelBlue1\0"
+ "LightSteelBlue2\0"
+ "LightSteelBlue3\0"
+ "LightSteelBlue4\0"
+ "LightYellow\0"
+ "LightYellow1\0"
+ "LightYellow2\0"
+ "LightYellow3\0"
+ "LightYellow4\0"
+ "lime\0"
+ "LimeGreen\0"
+ "linen\0"
+ "magenta\0"
+ "magenta1\0"
+ "magenta2\0"
+ "magenta3\0"
+ "magenta4\0"
+ "maroon\0"
+ "maroon1\0"
+ "maroon2\0"
+ "maroon3\0"
+ "maroon4\0"
+ "MediumAquamarine\0"
+ "MediumBlue\0"
+ "MediumOrchid\0"
+ "MediumOrchid1\0"
+ "MediumOrchid2\0"
+ "MediumOrchid3\0"
+ "MediumOrchid4\0"
+ "MediumPurple\0"
+ "MediumPurple1\0"
+ "MediumPurple2\0"
+ "MediumPurple3\0"
+ "MediumPurple4\0"
+ "MediumSeaGreen\0"
+ "MediumSlateBlue\0"
+ "MediumSpringGreen\0"
+ "MediumTurquoise\0"
+ "MediumVioletRed\0"
+ "MidnightBlue\0"
+ "MintCream\0"
+ "MistyRose\0"
+ "MistyRose1\0"
+ "MistyRose2\0"
+ "MistyRose3\0"
+ "MistyRose4\0"
+ "moccasin\0"
+ "NavajoWhite\0"
+ "NavajoWhite1\0"
+ "NavajoWhite2\0"
+ "NavajoWhite3\0"
+ "NavajoWhite4\0"
+ "navy\0"
+ "NavyBlue\0"
+ "OldLace\0"
+ "olive\0"
+ "OliveDrab\0"
+ "OliveDrab1\0"
+ "OliveDrab2\0"
+ "OliveDrab3\0"
+ "OliveDrab4\0"
+ "orange\0"
+ "orange1\0"
+ "orange2\0"
+ "orange3\0"
+ "orange4\0"
+ "OrangeRed\0"
+ "OrangeRed1\0"
+ "OrangeRed2\0"
+ "OrangeRed3\0"
+ "OrangeRed4\0"
+ "orchid\0"
+ "orchid1\0"
+ "orchid2\0"
+ "orchid3\0"
+ "orchid4\0"
+ "PaleGoldenrod\0"
+ "PaleGreen\0"
+ "PaleGreen1\0"
+ "PaleGreen2\0"
+ "PaleGreen3\0"
+ "PaleGreen4\0"
+ "PaleTurquoise\0"
+ "PaleTurquoise1\0"
+ "PaleTurquoise2\0"
+ "PaleTurquoise3\0"
+ "PaleTurquoise4\0"
+ "PaleVioletRed\0"
+ "PaleVioletRed1\0"
+ "PaleVioletRed2\0"
+ "PaleVioletRed3\0"
+ "PaleVioletRed4\0"
+ "PapayaWhip\0"
+ "PeachPuff\0"
+ "PeachPuff1\0"
+ "PeachPuff2\0"
+ "PeachPuff3\0"
+ "PeachPuff4\0"
+ "peru\0"
+ "pink\0"
+ "pink1\0"
+ "pink2\0"
+ "pink3\0"
+ "pink4\0"
+ "plum\0"
+ "plum1\0"
+ "plum2\0"
+ "plum3\0"
+ "plum4\0"
+ "PowderBlue\0"
+ "purple\0"
+ "purple1\0"
+ "purple2\0"
+ "purple3\0"
+ "purple4\0"
+ "rebeccapurple\0"
+ "red\0"
+ "red1\0"
+ "red2\0"
+ "red3\0"
+ "red4\0"
+ "RosyBrown\0"
+ "RosyBrown1\0"
+ "RosyBrown2\0"
+ "RosyBrown3\0"
+ "RosyBrown4\0"
+ "RoyalBlue\0"
+ "RoyalBlue1\0"
+ "RoyalBlue2\0"
+ "RoyalBlue3\0"
+ "RoyalBlue4\0"
+ "SaddleBrown\0"
+ "salmon\0"
+ "salmon1\0"
+ "salmon2\0"
+ "salmon3\0"
+ "salmon4\0"
+ "SandyBrown\0"
+ "SeaGreen\0"
+ "SeaGreen1\0"
+ "SeaGreen2\0"
+ "SeaGreen3\0"
+ "SeaGreen4\0"
+ "seashell\0"
+ "seashell1\0"
+ "seashell2\0"
+ "seashell3\0"
+ "seashell4\0"
+ "sienna\0"
+ "sienna1\0"
+ "sienna2\0"
+ "sienna3\0"
+ "sienna4\0"
+ "silver\0"
+ "SkyBlue\0"
+ "SkyBlue1\0"
+ "SkyBlue2\0"
+ "SkyBlue3\0"
+ "SkyBlue4\0"
+ "SlateBlue\0"
+ "SlateBlue1\0"
+ "SlateBlue2\0"
+ "SlateBlue3\0"
+ "SlateBlue4\0"
+ "SlateGray\0"
+ "SlateGray1\0"
+ "SlateGray2\0"
+ "SlateGray3\0"
+ "SlateGray4\0"
+ "SlateGrey\0"
+ "snow\0"
+ "snow1\0"
+ "snow2\0"
+ "snow3\0"
+ "snow4\0"
+ "SpringGreen\0"
+ "SpringGreen1\0"
+ "SpringGreen2\0"
+ "SpringGreen3\0"
+ "SpringGreen4\0"
+ "SteelBlue\0"
+ "SteelBlue1\0"
+ "SteelBlue2\0"
+ "SteelBlue3\0"
+ "SteelBlue4\0"
+ "tan\0"
+ "tan1\0"
+ "tan2\0"
+ "tan3\0"
+ "tan4\0"
+ "teal\0"
+ "thistle\0"
+ "thistle1\0"
+ "thistle2\0"
+ "thistle3\0"
+ "thistle4\0"
+ "tomato\0"
+ "tomato1\0"
+ "tomato2\0"
+ "tomato3\0"
+ "tomato4\0"
+ "turquoise\0"
+ "turquoise1\0"
+ "turquoise2\0"
+ "turquoise3\0"
+ "turquoise4\0"
+ "violet\0"
+ "VioletRed\0"
+ "VioletRed1\0"
+ "VioletRed2\0"
+ "VioletRed3\0"
+ "VioletRed4\0"
+ "wheat\0"
+ "wheat1\0"
+ "wheat2\0"
+ "wheat3\0"
+ "wheat4\0"
+ "white\0"
+ "WhiteSmoke\0"
+ "yellow\0"
+ "yellow1\0"
+ "yellow2\0"
+ "yellow3\0"
+ "yellow4\0"
+ "YellowGreen\0";
+
+typedef struct {
+ guint16 name_offset;
+ guchar red;
+ guchar green;
+ guchar blue;
+} ColorEntry;
+
+static const ColorEntry color_entries[] = {
+ { 0, 240, 248, 255 },
+ { 10, 250, 235, 215 },
+ { 23, 255, 239, 219 },
+ { 37, 238, 223, 204 },
+ { 51, 205, 192, 176 },
+ { 65, 139, 131, 120 },
+ { 79, 0, 255, 255 },
+ { 84, 127, 255, 212 },
+ { 95, 127, 255, 212 },
+ { 107, 118, 238, 198 },
+ { 119, 102, 205, 170 },
+ { 131, 69, 139, 116 },
+ { 143, 240, 255, 255 },
+ { 149, 240, 255, 255 },
+ { 156, 224, 238, 238 },
+ { 163, 193, 205, 205 },
+ { 170, 131, 139, 139 },
+ { 177, 245, 245, 220 },
+ { 183, 255, 228, 196 },
+ { 190, 255, 228, 196 },
+ { 198, 238, 213, 183 },
+ { 206, 205, 183, 158 },
+ { 214, 139, 125, 107 },
+ { 222, 0, 0, 0 },
+ { 228, 255, 235, 205 },
+ { 243, 0, 0, 255 },
+ { 248, 0, 0, 255 },
+ { 254, 0, 0, 238 },
+ { 260, 0, 0, 205 },
+ { 266, 0, 0, 139 },
+ { 272, 138, 43, 226 },
+ { 283, 165, 42, 42 },
+ { 289, 255, 64, 64 },
+ { 296, 238, 59, 59 },
+ { 303, 205, 51, 51 },
+ { 310, 139, 35, 35 },
+ { 317, 222, 184, 135 },
+ { 327, 255, 211, 155 },
+ { 338, 238, 197, 145 },
+ { 349, 205, 170, 125 },
+ { 360, 139, 115, 85 },
+ { 371, 95, 158, 160 },
+ { 381, 152, 245, 255 },
+ { 392, 142, 229, 238 },
+ { 403, 122, 197, 205 },
+ { 414, 83, 134, 139 },
+ { 425, 127, 255, 0 },
+ { 436, 127, 255, 0 },
+ { 448, 118, 238, 0 },
+ { 460, 102, 205, 0 },
+ { 472, 69, 139, 0 },
+ { 484, 210, 105, 30 },
+ { 494, 255, 127, 36 },
+ { 505, 238, 118, 33 },
+ { 516, 205, 102, 29 },
+ { 527, 139, 69, 19 },
+ { 538, 255, 127, 80 },
+ { 544, 255, 114, 86 },
+ { 551, 238, 106, 80 },
+ { 558, 205, 91, 69 },
+ { 565, 139, 62, 47 },
+ { 572, 100, 149, 237 },
+ { 587, 255, 248, 220 },
+ { 596, 255, 248, 220 },
+ { 606, 238, 232, 205 },
+ { 616, 205, 200, 177 },
+ { 626, 139, 136, 120 },
+ { 636, 220, 20, 60 },
+ { 644, 0, 255, 255 },
+ { 649, 0, 255, 255 },
+ { 655, 0, 238, 238 },
+ { 661, 0, 205, 205 },
+ { 667, 0, 139, 139 },
+ { 673, 0, 0, 139 },
+ { 682, 0, 139, 139 },
+ { 691, 184, 134, 11 },
+ { 705, 255, 185, 15 },
+ { 720, 238, 173, 14 },
+ { 735, 205, 149, 12 },
+ { 750, 139, 101, 8 },
+ { 765, 169, 169, 169 },
+ { 774, 0, 100, 0 },
+ { 784, 169, 169, 169 },
+ { 793, 189, 183, 107 },
+ { 803, 139, 0, 139 },
+ { 815, 85, 107, 47 },
+ { 830, 202, 255, 112 },
+ { 846, 188, 238, 104 },
+ { 862, 162, 205, 90 },
+ { 878, 110, 139, 61 },
+ { 894, 255, 140, 0 },
+ { 905, 255, 127, 0 },
+ { 917, 238, 118, 0 },
+ { 929, 205, 102, 0 },
+ { 941, 139, 69, 0 },
+ { 953, 153, 50, 204 },
+ { 964, 191, 62, 255 },
+ { 976, 178, 58, 238 },
+ { 988, 154, 50, 205 },
+ { 1000, 104, 34, 139 },
+ { 1012, 139, 0, 0 },
+ { 1020, 233, 150, 122 },
+ { 1031, 143, 188, 143 },
+ { 1044, 193, 255, 193 },
+ { 1058, 180, 238, 180 },
+ { 1072, 155, 205, 155 },
+ { 1086, 105, 139, 105 },
+ { 1100, 72, 61, 139 },
+ { 1114, 47, 79, 79 },
+ { 1128, 151, 255, 255 },
+ { 1143, 141, 238, 238 },
+ { 1158, 121, 205, 205 },
+ { 1173, 82, 139, 139 },
+ { 1188, 47, 79, 79 },
+ { 1202, 0, 206, 209 },
+ { 1216, 148, 0, 211 },
+ { 1227, 255, 20, 147 },
+ { 1236, 255, 20, 147 },
+ { 1246, 238, 18, 137 },
+ { 1256, 205, 16, 118 },
+ { 1266, 139, 10, 80 },
+ { 1276, 0, 191, 255 },
+ { 1288, 0, 191, 255 },
+ { 1301, 0, 178, 238 },
+ { 1314, 0, 154, 205 },
+ { 1327, 0, 104, 139 },
+ { 1340, 105, 105, 105 },
+ { 1348, 105, 105, 105 },
+ { 1356, 30, 144, 255 },
+ { 1367, 30, 144, 255 },
+ { 1379, 28, 134, 238 },
+ { 1391, 24, 116, 205 },
+ { 1403, 16, 78, 139 },
+ { 1415, 178, 34, 34 },
+ { 1425, 255, 48, 48 },
+ { 1436, 238, 44, 44 },
+ { 1447, 205, 38, 38 },
+ { 1458, 139, 26, 26 },
+ { 1469, 255, 250, 240 },
+ { 1481, 34, 139, 34 },
+ { 1493, 255, 0, 255 },
+ { 1501, 220, 220, 220 },
+ { 1511, 248, 248, 255 },
+ { 1522, 255, 215, 0 },
+ { 1527, 255, 215, 0 },
+ { 1533, 238, 201, 0 },
+ { 1539, 205, 173, 0 },
+ { 1545, 139, 117, 0 },
+ { 1551, 218, 165, 32 },
+ { 1561, 255, 193, 37 },
+ { 1572, 238, 180, 34 },
+ { 1583, 205, 155, 29 },
+ { 1594, 139, 105, 20 },
+ { 1605, 128, 128, 128 },
+ { 1610, 0, 0, 0 },
+ { 1616, 3, 3, 3 },
+ { 1622, 26, 26, 26 },
+ { 1629, 255, 255, 255 },
+ { 1637, 28, 28, 28 },
+ { 1644, 31, 31, 31 },
+ { 1651, 33, 33, 33 },
+ { 1658, 36, 36, 36 },
+ { 1665, 38, 38, 38 },
+ { 1672, 41, 41, 41 },
+ { 1679, 43, 43, 43 },
+ { 1686, 46, 46, 46 },
+ { 1693, 48, 48, 48 },
+ { 1700, 5, 5, 5 },
+ { 1706, 51, 51, 51 },
+ { 1713, 54, 54, 54 },
+ { 1720, 56, 56, 56 },
+ { 1727, 59, 59, 59 },
+ { 1734, 61, 61, 61 },
+ { 1741, 64, 64, 64 },
+ { 1748, 66, 66, 66 },
+ { 1755, 69, 69, 69 },
+ { 1762, 71, 71, 71 },
+ { 1769, 74, 74, 74 },
+ { 1776, 8, 8, 8 },
+ { 1782, 77, 77, 77 },
+ { 1789, 79, 79, 79 },
+ { 1796, 82, 82, 82 },
+ { 1803, 84, 84, 84 },
+ { 1810, 87, 87, 87 },
+ { 1817, 89, 89, 89 },
+ { 1824, 92, 92, 92 },
+ { 1831, 94, 94, 94 },
+ { 1838, 97, 97, 97 },
+ { 1845, 99, 99, 99 },
+ { 1852, 10, 10, 10 },
+ { 1858, 102, 102, 102 },
+ { 1865, 105, 105, 105 },
+ { 1872, 107, 107, 107 },
+ { 1879, 110, 110, 110 },
+ { 1886, 112, 112, 112 },
+ { 1893, 115, 115, 115 },
+ { 1900, 117, 117, 117 },
+ { 1907, 120, 120, 120 },
+ { 1914, 122, 122, 122 },
+ { 1921, 125, 125, 125 },
+ { 1928, 13, 13, 13 },
+ { 1934, 127, 127, 127 },
+ { 1941, 130, 130, 130 },
+ { 1948, 133, 133, 133 },
+ { 1955, 135, 135, 135 },
+ { 1962, 138, 138, 138 },
+ { 1969, 140, 140, 140 },
+ { 1976, 143, 143, 143 },
+ { 1983, 145, 145, 145 },
+ { 1990, 148, 148, 148 },
+ { 1997, 150, 150, 150 },
+ { 2004, 15, 15, 15 },
+ { 2010, 153, 153, 153 },
+ { 2017, 156, 156, 156 },
+ { 2024, 158, 158, 158 },
+ { 2031, 161, 161, 161 },
+ { 2038, 163, 163, 163 },
+ { 2045, 166, 166, 166 },
+ { 2052, 168, 168, 168 },
+ { 2059, 171, 171, 171 },
+ { 2066, 173, 173, 173 },
+ { 2073, 176, 176, 176 },
+ { 2080, 18, 18, 18 },
+ { 2086, 179, 179, 179 },
+ { 2093, 181, 181, 181 },
+ { 2100, 184, 184, 184 },
+ { 2107, 186, 186, 186 },
+ { 2114, 189, 189, 189 },
+ { 2121, 191, 191, 191 },
+ { 2128, 194, 194, 194 },
+ { 2135, 196, 196, 196 },
+ { 2142, 199, 199, 199 },
+ { 2149, 201, 201, 201 },
+ { 2156, 20, 20, 20 },
+ { 2162, 204, 204, 204 },
+ { 2169, 207, 207, 207 },
+ { 2176, 209, 209, 209 },
+ { 2183, 212, 212, 212 },
+ { 2190, 214, 214, 214 },
+ { 2197, 217, 217, 217 },
+ { 2204, 219, 219, 219 },
+ { 2211, 222, 222, 222 },
+ { 2218, 224, 224, 224 },
+ { 2225, 227, 227, 227 },
+ { 2232, 23, 23, 23 },
+ { 2238, 229, 229, 229 },
+ { 2245, 232, 232, 232 },
+ { 2252, 235, 235, 235 },
+ { 2259, 237, 237, 237 },
+ { 2266, 240, 240, 240 },
+ { 2273, 242, 242, 242 },
+ { 2280, 245, 245, 245 },
+ { 2287, 247, 247, 247 },
+ { 2294, 250, 250, 250 },
+ { 2301, 252, 252, 252 },
+ { 2308, 0, 128, 0 },
+ { 2314, 0, 255, 0 },
+ { 2321, 0, 238, 0 },
+ { 2328, 0, 205, 0 },
+ { 2335, 0, 139, 0 },
+ { 2342, 173, 255, 47 },
+ { 2354, 128, 128, 128 },
+ { 2359, 0, 0, 0 },
+ { 2365, 3, 3, 3 },
+ { 2371, 26, 26, 26 },
+ { 2378, 255, 255, 255 },
+ { 2386, 28, 28, 28 },
+ { 2393, 31, 31, 31 },
+ { 2400, 33, 33, 33 },
+ { 2407, 36, 36, 36 },
+ { 2414, 38, 38, 38 },
+ { 2421, 41, 41, 41 },
+ { 2428, 43, 43, 43 },
+ { 2435, 46, 46, 46 },
+ { 2442, 48, 48, 48 },
+ { 2449, 5, 5, 5 },
+ { 2455, 51, 51, 51 },
+ { 2462, 54, 54, 54 },
+ { 2469, 56, 56, 56 },
+ { 2476, 59, 59, 59 },
+ { 2483, 61, 61, 61 },
+ { 2490, 64, 64, 64 },
+ { 2497, 66, 66, 66 },
+ { 2504, 69, 69, 69 },
+ { 2511, 71, 71, 71 },
+ { 2518, 74, 74, 74 },
+ { 2525, 8, 8, 8 },
+ { 2531, 77, 77, 77 },
+ { 2538, 79, 79, 79 },
+ { 2545, 82, 82, 82 },
+ { 2552, 84, 84, 84 },
+ { 2559, 87, 87, 87 },
+ { 2566, 89, 89, 89 },
+ { 2573, 92, 92, 92 },
+ { 2580, 94, 94, 94 },
+ { 2587, 97, 97, 97 },
+ { 2594, 99, 99, 99 },
+ { 2601, 10, 10, 10 },
+ { 2607, 102, 102, 102 },
+ { 2614, 105, 105, 105 },
+ { 2621, 107, 107, 107 },
+ { 2628, 110, 110, 110 },
+ { 2635, 112, 112, 112 },
+ { 2642, 115, 115, 115 },
+ { 2649, 117, 117, 117 },
+ { 2656, 120, 120, 120 },
+ { 2663, 122, 122, 122 },
+ { 2670, 125, 125, 125 },
+ { 2677, 13, 13, 13 },
+ { 2683, 127, 127, 127 },
+ { 2690, 130, 130, 130 },
+ { 2697, 133, 133, 133 },
+ { 2704, 135, 135, 135 },
+ { 2711, 138, 138, 138 },
+ { 2718, 140, 140, 140 },
+ { 2725, 143, 143, 143 },
+ { 2732, 145, 145, 145 },
+ { 2739, 148, 148, 148 },
+ { 2746, 150, 150, 150 },
+ { 2753, 15, 15, 15 },
+ { 2759, 153, 153, 153 },
+ { 2766, 156, 156, 156 },
+ { 2773, 158, 158, 158 },
+ { 2780, 161, 161, 161 },
+ { 2787, 163, 163, 163 },
+ { 2794, 166, 166, 166 },
+ { 2801, 168, 168, 168 },
+ { 2808, 171, 171, 171 },
+ { 2815, 173, 173, 173 },
+ { 2822, 176, 176, 176 },
+ { 2829, 18, 18, 18 },
+ { 2835, 179, 179, 179 },
+ { 2842, 181, 181, 181 },
+ { 2849, 184, 184, 184 },
+ { 2856, 186, 186, 186 },
+ { 2863, 189, 189, 189 },
+ { 2870, 191, 191, 191 },
+ { 2877, 194, 194, 194 },
+ { 2884, 196, 196, 196 },
+ { 2891, 199, 199, 199 },
+ { 2898, 201, 201, 201 },
+ { 2905, 20, 20, 20 },
+ { 2911, 204, 204, 204 },
+ { 2918, 207, 207, 207 },
+ { 2925, 209, 209, 209 },
+ { 2932, 212, 212, 212 },
+ { 2939, 214, 214, 214 },
+ { 2946, 217, 217, 217 },
+ { 2953, 219, 219, 219 },
+ { 2960, 222, 222, 222 },
+ { 2967, 224, 224, 224 },
+ { 2974, 227, 227, 227 },
+ { 2981, 23, 23, 23 },
+ { 2987, 229, 229, 229 },
+ { 2994, 232, 232, 232 },
+ { 3001, 235, 235, 235 },
+ { 3008, 237, 237, 237 },
+ { 3015, 240, 240, 240 },
+ { 3022, 242, 242, 242 },
+ { 3029, 245, 245, 245 },
+ { 3036, 247, 247, 247 },
+ { 3043, 250, 250, 250 },
+ { 3050, 252, 252, 252 },
+ { 3057, 240, 255, 240 },
+ { 3066, 240, 255, 240 },
+ { 3076, 224, 238, 224 },
+ { 3086, 193, 205, 193 },
+ { 3096, 131, 139, 131 },
+ { 3106, 255, 105, 180 },
+ { 3114, 255, 110, 180 },
+ { 3123, 238, 106, 167 },
+ { 3132, 205, 96, 144 },
+ { 3141, 139, 58, 98 },
+ { 3150, 205, 92, 92 },
+ { 3160, 255, 106, 106 },
+ { 3171, 238, 99, 99 },
+ { 3182, 205, 85, 85 },
+ { 3193, 139, 58, 58 },
+ { 3204, 75, 0, 130 },
+ { 3211, 255, 255, 240 },
+ { 3217, 255, 255, 240 },
+ { 3224, 238, 238, 224 },
+ { 3231, 205, 205, 193 },
+ { 3238, 139, 139, 131 },
+ { 3245, 240, 230, 140 },
+ { 3251, 255, 246, 143 },
+ { 3258, 238, 230, 133 },
+ { 3265, 205, 198, 115 },
+ { 3272, 139, 134, 78 },
+ { 3279, 230, 230, 250 },
+ { 3288, 255, 240, 245 },
+ { 3302, 255, 240, 245 },
+ { 3317, 238, 224, 229 },
+ { 3332, 205, 193, 197 },
+ { 3347, 139, 131, 134 },
+ { 3362, 124, 252, 0 },
+ { 3372, 255, 250, 205 },
+ { 3385, 255, 250, 205 },
+ { 3399, 238, 233, 191 },
+ { 3413, 205, 201, 165 },
+ { 3427, 139, 137, 112 },
+ { 3441, 173, 216, 230 },
+ { 3451, 191, 239, 255 },
+ { 3462, 178, 223, 238 },
+ { 3473, 154, 192, 205 },
+ { 3484, 104, 131, 139 },
+ { 3495, 240, 128, 128 },
+ { 3506, 224, 255, 255 },
+ { 3516, 224, 255, 255 },
+ { 3527, 209, 238, 238 },
+ { 3538, 180, 205, 205 },
+ { 3549, 122, 139, 139 },
+ { 3560, 238, 221, 130 },
+ { 3575, 255, 236, 139 },
+ { 3591, 238, 220, 130 },
+ { 3607, 205, 190, 112 },
+ { 3623, 139, 129, 76 },
+ { 3639, 250, 250, 210 },
+ { 3660, 211, 211, 211 },
+ { 3670, 144, 238, 144 },
+ { 3681, 211, 211, 211 },
+ { 3691, 255, 182, 193 },
+ { 3701, 255, 174, 185 },
+ { 3712, 238, 162, 173 },
+ { 3723, 205, 140, 149 },
+ { 3734, 139, 95, 101 },
+ { 3745, 255, 160, 122 },
+ { 3757, 255, 160, 122 },
+ { 3770, 238, 149, 114 },
+ { 3783, 205, 129, 98 },
+ { 3796, 139, 87, 66 },
+ { 3809, 32, 178, 170 },
+ { 3823, 135, 206, 250 },
+ { 3836, 176, 226, 255 },
+ { 3850, 164, 211, 238 },
+ { 3864, 141, 182, 205 },
+ { 3878, 96, 123, 139 },
+ { 3892, 132, 112, 255 },
+ { 3907, 119, 136, 153 },
+ { 3922, 119, 136, 153 },
+ { 3937, 176, 196, 222 },
+ { 3952, 202, 225, 255 },
+ { 3968, 188, 210, 238 },
+ { 3984, 162, 181, 205 },
+ { 4000, 110, 123, 139 },
+ { 4016, 255, 255, 224 },
+ { 4028, 255, 255, 224 },
+ { 4041, 238, 238, 209 },
+ { 4054, 205, 205, 180 },
+ { 4067, 139, 139, 122 },
+ { 4080, 0, 255, 0 },
+ { 4085, 50, 205, 50 },
+ { 4095, 250, 240, 230 },
+ { 4101, 255, 0, 255 },
+ { 4109, 255, 0, 255 },
+ { 4118, 238, 0, 238 },
+ { 4127, 205, 0, 205 },
+ { 4136, 139, 0, 139 },
+ { 4145, 128, 0, 0 },
+ { 4152, 255, 52, 179 },
+ { 4160, 238, 48, 167 },
+ { 4168, 205, 41, 144 },
+ { 4176, 139, 28, 98 },
+ { 4184, 102, 205, 170 },
+ { 4201, 0, 0, 205 },
+ { 4212, 186, 85, 211 },
+ { 4225, 224, 102, 255 },
+ { 4239, 209, 95, 238 },
+ { 4253, 180, 82, 205 },
+ { 4267, 122, 55, 139 },
+ { 4281, 147, 112, 219 },
+ { 4294, 171, 130, 255 },
+ { 4308, 159, 121, 238 },
+ { 4322, 137, 104, 205 },
+ { 4336, 93, 71, 139 },
+ { 4350, 60, 179, 113 },
+ { 4365, 123, 104, 238 },
+ { 4381, 0, 250, 154 },
+ { 4399, 72, 209, 204 },
+ { 4415, 199, 21, 133 },
+ { 4431, 25, 25, 112 },
+ { 4444, 245, 255, 250 },
+ { 4454, 255, 228, 225 },
+ { 4464, 255, 228, 225 },
+ { 4475, 238, 213, 210 },
+ { 4486, 205, 183, 181 },
+ { 4497, 139, 125, 123 },
+ { 4508, 255, 228, 181 },
+ { 4517, 255, 222, 173 },
+ { 4529, 255, 222, 173 },
+ { 4542, 238, 207, 161 },
+ { 4555, 205, 179, 139 },
+ { 4568, 139, 121, 94 },
+ { 4581, 0, 0, 128 },
+ { 4586, 0, 0, 128 },
+ { 4595, 253, 245, 230 },
+ { 4603, 128, 128, 0 },
+ { 4609, 107, 142, 35 },
+ { 4619, 192, 255, 62 },
+ { 4630, 179, 238, 58 },
+ { 4641, 154, 205, 50 },
+ { 4652, 105, 139, 34 },
+ { 4663, 255, 165, 0 },
+ { 4670, 255, 165, 0 },
+ { 4678, 238, 154, 0 },
+ { 4686, 205, 133, 0 },
+ { 4694, 139, 90, 0 },
+ { 4702, 255, 69, 0 },
+ { 4712, 255, 69, 0 },
+ { 4723, 238, 64, 0 },
+ { 4734, 205, 55, 0 },
+ { 4745, 139, 37, 0 },
+ { 4756, 218, 112, 214 },
+ { 4763, 255, 131, 250 },
+ { 4771, 238, 122, 233 },
+ { 4779, 205, 105, 201 },
+ { 4787, 139, 71, 137 },
+ { 4795, 238, 232, 170 },
+ { 4809, 152, 251, 152 },
+ { 4819, 154, 255, 154 },
+ { 4830, 144, 238, 144 },
+ { 4841, 124, 205, 124 },
+ { 4852, 84, 139, 84 },
+ { 4863, 175, 238, 238 },
+ { 4877, 187, 255, 255 },
+ { 4892, 174, 238, 238 },
+ { 4907, 150, 205, 205 },
+ { 4922, 102, 139, 139 },
+ { 4937, 219, 112, 147 },
+ { 4951, 255, 130, 171 },
+ { 4966, 238, 121, 159 },
+ { 4981, 205, 104, 137 },
+ { 4996, 139, 71, 93 },
+ { 5011, 255, 239, 213 },
+ { 5022, 255, 218, 185 },
+ { 5032, 255, 218, 185 },
+ { 5043, 238, 203, 173 },
+ { 5054, 205, 175, 149 },
+ { 5065, 139, 119, 101 },
+ { 5076, 205, 133, 63 },
+ { 5081, 255, 192, 203 },
+ { 5086, 255, 181, 197 },
+ { 5092, 238, 169, 184 },
+ { 5098, 205, 145, 158 },
+ { 5104, 139, 99, 108 },
+ { 5110, 221, 160, 221 },
+ { 5115, 255, 187, 255 },
+ { 5121, 238, 174, 238 },
+ { 5127, 205, 150, 205 },
+ { 5133, 139, 102, 139 },
+ { 5139, 176, 224, 230 },
+ { 5150, 128, 0, 128 },
+ { 5157, 155, 48, 255 },
+ { 5165, 145, 44, 238 },
+ { 5173, 125, 38, 205 },
+ { 5181, 85, 26, 139 },
+ { 5189, 102, 51, 153 },
+ { 5203, 255, 0, 0 },
+ { 5207, 255, 0, 0 },
+ { 5212, 238, 0, 0 },
+ { 5217, 205, 0, 0 },
+ { 5222, 139, 0, 0 },
+ { 5227, 188, 143, 143 },
+ { 5237, 255, 193, 193 },
+ { 5248, 238, 180, 180 },
+ { 5259, 205, 155, 155 },
+ { 5270, 139, 105, 105 },
+ { 5281, 65, 105, 225 },
+ { 5291, 72, 118, 255 },
+ { 5302, 67, 110, 238 },
+ { 5313, 58, 95, 205 },
+ { 5324, 39, 64, 139 },
+ { 5335, 139, 69, 19 },
+ { 5347, 250, 128, 114 },
+ { 5354, 255, 140, 105 },
+ { 5362, 238, 130, 98 },
+ { 5370, 205, 112, 84 },
+ { 5378, 139, 76, 57 },
+ { 5386, 244, 164, 96 },
+ { 5397, 46, 139, 87 },
+ { 5406, 84, 255, 159 },
+ { 5416, 78, 238, 148 },
+ { 5426, 67, 205, 128 },
+ { 5436, 46, 139, 87 },
+ { 5446, 255, 245, 238 },
+ { 5455, 255, 245, 238 },
+ { 5465, 238, 229, 222 },
+ { 5475, 205, 197, 191 },
+ { 5485, 139, 134, 130 },
+ { 5495, 160, 82, 45 },
+ { 5502, 255, 130, 71 },
+ { 5510, 238, 121, 66 },
+ { 5518, 205, 104, 57 },
+ { 5526, 139, 71, 38 },
+ { 5534, 192, 192, 192 },
+ { 5541, 135, 206, 235 },
+ { 5549, 135, 206, 255 },
+ { 5558, 126, 192, 238 },
+ { 5567, 108, 166, 205 },
+ { 5576, 74, 112, 139 },
+ { 5585, 106, 90, 205 },
+ { 5595, 131, 111, 255 },
+ { 5606, 122, 103, 238 },
+ { 5617, 105, 89, 205 },
+ { 5628, 71, 60, 139 },
+ { 5639, 112, 128, 144 },
+ { 5649, 198, 226, 255 },
+ { 5660, 185, 211, 238 },
+ { 5671, 159, 182, 205 },
+ { 5682, 108, 123, 139 },
+ { 5693, 112, 128, 144 },
+ { 5703, 255, 250, 250 },
+ { 5708, 255, 250, 250 },
+ { 5714, 238, 233, 233 },
+ { 5720, 205, 201, 201 },
+ { 5726, 139, 137, 137 },
+ { 5732, 0, 255, 127 },
+ { 5744, 0, 255, 127 },
+ { 5757, 0, 238, 118 },
+ { 5770, 0, 205, 102 },
+ { 5783, 0, 139, 69 },
+ { 5796, 70, 130, 180 },
+ { 5806, 99, 184, 255 },
+ { 5817, 92, 172, 238 },
+ { 5828, 79, 148, 205 },
+ { 5839, 54, 100, 139 },
+ { 5850, 210, 180, 140 },
+ { 5854, 255, 165, 79 },
+ { 5859, 238, 154, 73 },
+ { 5864, 205, 133, 63 },
+ { 5869, 139, 90, 43 },
+ { 5874, 0, 128, 128 },
+ { 5879, 216, 191, 216 },
+ { 5887, 255, 225, 255 },
+ { 5896, 238, 210, 238 },
+ { 5905, 205, 181, 205 },
+ { 5914, 139, 123, 139 },
+ { 5923, 255, 99, 71 },
+ { 5930, 255, 99, 71 },
+ { 5938, 238, 92, 66 },
+ { 5946, 205, 79, 57 },
+ { 5954, 139, 54, 38 },
+ { 5962, 64, 224, 208 },
+ { 5972, 0, 245, 255 },
+ { 5983, 0, 229, 238 },
+ { 5994, 0, 197, 205 },
+ { 6005, 0, 134, 139 },
+ { 6016, 238, 130, 238 },
+ { 6023, 208, 32, 144 },
+ { 6033, 255, 62, 150 },
+ { 6044, 238, 58, 140 },
+ { 6055, 205, 50, 120 },
+ { 6066, 139, 34, 82 },
+ { 6077, 245, 222, 179 },
+ { 6083, 255, 231, 186 },
+ { 6090, 238, 216, 174 },
+ { 6097, 205, 186, 150 },
+ { 6104, 139, 126, 102 },
+ { 6111, 255, 255, 255 },
+ { 6117, 245, 245, 245 },
+ { 6128, 255, 255, 0 },
+ { 6135, 255, 255, 0 },
+ { 6143, 238, 238, 0 },
+ { 6151, 205, 205, 0 },
+ { 6159, 139, 139, 0 },
+ { 6167, 154, 205, 50 }
+};
diff --git a/pango2/pango-color.c b/pango2/pango-color.c
new file mode 100644
index 00000000..adc9a0d1
--- /dev/null
+++ b/pango2/pango-color.c
@@ -0,0 +1,337 @@
+/* pango
+ * pango-color.c: Color handling
+ *
+ * Copyright (C) 2000 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.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pango-attributes.h"
+#include "pango-impl-utils.h"
+
+
+G_DEFINE_BOXED_TYPE (Pango2Color, pango2_color,
+ pango2_color_copy,
+ pango2_color_free);
+
+/**
+ * pango2_color_copy:
+ * @src: (nullable): color to copy
+ *
+ * Creates a copy of @src.
+ *
+ * The copy should be freed with [method@Pango2.Color.free].
+ * Primarily used by language bindings, not that useful
+ * otherwise (since colors can just be copied by assignment
+ * in C).
+ *
+ * Return value: (nullable): the newly allocated `Pango2Color`,
+ * which should be freed with [method@Pango2.Color.free]
+ */
+Pango2Color*
+pango2_color_copy (const Pango2Color *src)
+{
+ Pango2Color *ret;
+
+ if (src == NULL)
+ return NULL;
+
+ ret = g_slice_new (Pango2Color);
+
+ *ret = *src;
+
+ return ret;
+}
+
+/**
+ * pango2_color_free:
+ * @color: (nullable): an allocated `Pango2Color`
+ *
+ * Frees a color allocated by [method@Pango2.Color.copy].
+ */
+void
+pango2_color_free (Pango2Color *color)
+{
+ if (color == NULL)
+ return;
+
+ g_slice_free (Pango2Color, color);
+}
+
+/**
+ * pango2_color_equal:
+ * @color1: (nullable): a `Pango2Color`
+ * @color2: (nullable): another `Pango2Color`
+ *
+ * Compares two colors for quality.
+ *
+ * Returns: `TRUE` if the colors are equal
+ */
+gboolean
+pango2_color_equal (const Pango2Color *color1,
+ const Pango2Color *color2)
+{
+ return color1 == color2 ||
+ (color1 && color2 &&
+ color1->red == color2->red &&
+ color1->green == color2->green &&
+ color1->blue == color2->blue &&
+ color1->alpha == color2->alpha);
+}
+
+/**
+ * pango2_color_to_string:
+ * @color: a `Pango2Color`
+ *
+ * Returns a textual specification of @color.
+ *
+ * The string is in the hexadecimal form `#rrrrggggbbbbaaaa`,
+ * where `r`, `g`, `b` and `a` are hex digits representing the
+ * red, green, blue and alpha components respectively.
+ *
+ * Return value: a newly-allocated text string that must
+ * be freed with [GLib.free]
+ */
+char *
+pango2_color_to_string (const Pango2Color *color)
+{
+ g_return_val_if_fail (color != NULL, NULL);
+
+ return g_strdup_printf ("#%04x%04x%04x%04x",
+ color->red,
+ color->green,
+ color->blue,
+ color->alpha);
+}
+
+/* Color parsing
+ */
+
+/* The following 2 routines (parse_color, find_color) come from Tk, via the Win32
+ * port of GDK. The licensing terms on these (longer than the functions) is:
+ *
+ * This software is copyrighted by the Regents of the University of
+ * California, Sun Microsystems, Inc., and other parties. The following
+ * terms apply to all files associated with the software unless explicitly
+ * disclaimed in individual files.
+ *
+ * The authors hereby grant permission to use, copy, modify, distribute,
+ * and license this software and its documentation for any purpose, provided
+ * that existing copyright notices are retained in all copies and that this
+ * notice is included verbatim in any distributions. No written agreement,
+ * license, or royalty fee is required for any of the authorized uses.
+ * Modifications to this software may be copyrighted by their authors
+ * and need not follow the licensing terms described here, provided that
+ * the new terms are clearly indicated on the first page of each file where
+ * they apply.
+ *
+ * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
+ * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
+ * DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
+ * IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
+ * NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+ * MODIFICATIONS.
+ *
+ * GOVERNMENT USE: If you are acquiring this software on behalf of the
+ * U.S. government, the Government shall have only "Restricted Rights"
+ * in the software and related documentation as defined in the Federal
+ * Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you
+ * are acquiring the software on behalf of the Department of Defense, the
+ * software shall be classified as "Commercial Computer Software" and the
+ * Government shall have only "Restricted Rights" as defined in Clause
+ * 252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the
+ * authors grant the U.S. Government and others acting in its behalf
+ * permission to use and distribute the software in accordance with the
+ * terms specified in this license.
+ */
+
+#include "pango-color-table.h"
+
+#define ISUPPER(c) ((c) >= 'A' && (c) <= 'Z')
+#define TOLOWER(c) (ISUPPER (c) ? (c) - 'A' + 'a' : (c))
+
+static int
+compare_xcolor_entries (const void *a, const void *b)
+{
+ const guchar *s1 = (const guchar *) a;
+ const guchar *s2 = (const guchar *) (color_names + ((const ColorEntry *) b)->name_offset);
+
+ while (*s1 && *s2)
+ {
+ int c1, c2;
+ while (*s1 == ' ') s1++;
+ while (*s2 == ' ') s1++;
+ c1 = (int)(guchar) TOLOWER (*s1);
+ c2 = (int)(guchar) TOLOWER (*s2);
+ if (c1 != c2)
+ return (c1 - c2);
+ s1++; s2++;
+ }
+
+ return ((int) *s1) - ((int) *s2);
+}
+
+static gboolean
+find_color(const char *name,
+ Pango2Color *color)
+{
+ ColorEntry *found;
+
+ found = bsearch (name, color_entries, G_N_ELEMENTS (color_entries),
+ sizeof (ColorEntry),
+ compare_xcolor_entries);
+ if (found == NULL)
+ return FALSE;
+
+ if (color)
+ {
+ color->red = (found->red * 65535) / 255;
+ color->green = (found->green * 65535) / 255;
+ color->blue = (found->blue * 65535) / 255;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+hex (const char *spec,
+ int len,
+ unsigned int *c)
+{
+ const char *end;
+ *c = 0;
+ for (end = spec + len; spec != end; spec++)
+ if (g_ascii_isxdigit (*spec))
+ *c = (*c << 4) | g_ascii_xdigit_value (*spec);
+ else
+ return FALSE;
+ return TRUE;
+}
+
+
+/**
+ * pango2_color_parse:
+ * @color: a `Pango2Color` structure in which to store the result
+ * @spec: a string specifying the new color
+ *
+ * Fill in the fields of a color from a string specification.
+ *
+ * The string can either one of a large set of standard names.
+ * (Taken from the CSS Color [specification](https://www.w3.org/TR/css-color-4/#named-colors),
+ * or it can be a hexadecimal value in the form `#rgb`,
+ * `#rrggbb`, `#rrrgggbbb` or `#rrrrggggbbbb` where `r`, `g`
+ * and `b` are hex digits of the red, green, and blue components
+ * of the color, respectively. (White in the four forms is
+ * `#fff`, `#ffffff`, `#fffffffff` and `#ffffffffffff`.)
+ *
+ * Additionally, parse strings of the form `#rgba`, `#rrggbbaa`,
+ * `#rrrrggggbbbbaaaa`, and set the alpha component of @color
+ * to the value specified by the hex digits for `a`. If no alpha
+ * component is found in @spec, alpha is set to 0xffff (for a
+ * solid color).
+ *
+ * Return value: %TRUE if parsing of the specifier succeeded,
+ * otherwise %FALSE
+ */
+gboolean
+pango2_color_parse (Pango2Color *color,
+ const char *spec)
+{
+ g_return_val_if_fail (spec != NULL, FALSE);
+
+ color->alpha = 0xffff;
+
+ if (spec[0] == '#')
+ {
+ size_t len;
+ unsigned int r, g, b, a;
+ gboolean has_alpha;
+
+ spec++;
+ len = strlen (spec);
+ switch (len)
+ {
+ case 3:
+ case 6:
+ case 9:
+ case 12:
+ len /= 3;
+ has_alpha = FALSE;
+ break;
+ case 4:
+ case 8:
+ case 16:
+ len /= 4;
+ has_alpha = TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!hex (spec, len, &r) ||
+ !hex (spec + len, len, &g) ||
+ !hex (spec + len * 2, len, &b) ||
+ (has_alpha && !hex (spec + len * 3, len, &a)))
+ return FALSE;
+
+ if (color)
+ {
+ int bits = len * 4;
+ r <<= 16 - bits;
+ g <<= 16 - bits;
+ b <<= 16 - bits;
+ while (bits < 16)
+ {
+ r |= (r >> bits);
+ g |= (g >> bits);
+ b |= (b >> bits);
+ bits *= 2;
+ }
+ color->red = r;
+ color->green = g;
+ color->blue = b;
+ }
+
+ if (has_alpha)
+ {
+ int bits = len * 4;
+ a <<= 16 - bits;
+ while (bits < 16)
+ {
+ a |= (a >> bits);
+ bits *= 2;
+ }
+ color->alpha = a;
+ }
+ }
+ else
+ {
+ if (!find_color (spec, color))
+ return FALSE;
+ }
+ return TRUE;
+}
diff --git a/pango2/pango-color.h b/pango2/pango-color.h
new file mode 100644
index 00000000..f62a3dd5
--- /dev/null
+++ b/pango2/pango-color.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+
+typedef struct _Pango2Color Pango2Color;
+
+/**
+ * Pango2Color:
+ * @red: value of the red component
+ * @green: value of the green component
+ * @blue: value of the blue component
+ * @alpha: value of the alpha component
+ *
+ * The `Pango2Color` structure is used to
+ * represent a color in an uncalibrated RGB color-space.
+ */
+struct _Pango2Color
+{
+ guint16 red;
+ guint16 green;
+ guint16 blue;
+ guint16 alpha;
+};
+
+#define PANGO2_TYPE_COLOR (pango2_color_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_color_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Color *pango2_color_copy (const Pango2Color *src);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_color_free (Pango2Color *color);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_color_equal (const Pango2Color *color1,
+ const Pango2Color *color2);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_color_parse (Pango2Color *color,
+ const char *spec);
+
+PANGO2_AVAILABLE_IN_ALL
+char * pango2_color_to_string (const Pango2Color *color);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2Color, pango2_color_free)
+
+G_END_DECLS
diff --git a/pango2/pango-context-private.h b/pango2/pango-context-private.h
new file mode 100644
index 00000000..07b4b506
--- /dev/null
+++ b/pango2/pango-context-private.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2004 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-context.h"
+
+#ifdef HAVE_CAIRO
+#include <cairo.h>
+#endif
+
+struct _Pango2Context
+{
+ GObject parent_instance;
+ guint serial;
+ guint fontmap_serial;
+
+ Pango2Language *set_language;
+ Pango2Language *language;
+ Pango2Direction base_dir;
+ Pango2Gravity base_gravity;
+ Pango2Gravity resolved_gravity;
+ Pango2GravityHint gravity_hint;
+
+ Pango2FontDescription *font_desc;
+
+ Pango2Matrix *matrix;
+
+ Pango2FontMap *font_map;
+
+ Pango2FontMetrics *metrics;
+
+ gboolean round_glyph_positions;
+
+#ifdef HAVE_CAIRO
+ gboolean set_options_explicit;
+
+ cairo_font_options_t *set_options;
+ cairo_font_options_t *surface_options;
+ cairo_font_options_t *merged_options;
+#endif
+};
diff --git a/pango2/pango-context.c b/pango2/pango-context.c
new file mode 100644
index 00000000..58f2719a
--- /dev/null
+++ b/pango2/pango-context.c
@@ -0,0 +1,1068 @@
+/* Pango2
+ * pango-context.c: Contexts for itemization and shaping
+ *
+ * Copyright (C) 2000, 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.
+ */
+
+#include "config.h"
+#include <string.h>
+#include <stdlib.h>
+
+#include <gio/gio.h>
+
+#include "pango-context.h"
+#include "pango-context-private.h"
+#include "pango-impl-utils.h"
+
+#include "pango-font-private.h"
+#include "pango-font-metrics-private.h"
+#include "pango-item-private.h"
+#include "pango-fontset-private.h"
+#include "pango-fontmap-private.h"
+#include "pango-script-private.h"
+#include "pango-emoji-private.h"
+
+/**
+ * Pango2Context:
+ *
+ * A `Pango2Context` stores global information used to control the
+ * itemization process.
+ *
+ * The information stored by `Pango2Context` includes the fontmap used
+ * to look up fonts, and default values such as the default language,
+ * default gravity, or default font.
+ *
+ * To obtain a `Pango2Context`, use [ctor@Pango2.Context.new]
+ * or [func@Pango2.cairo_create_context].
+ */
+
+struct _Pango2ContextClass
+{
+ GObjectClass parent_class;
+
+};
+
+static void pango2_context_finalize (GObject *object);
+static void context_changed (Pango2Context *context);
+
+enum {
+ PROP_FONT_MAP = 1,
+ PROP_FONT_DESCRIPTION,
+ PROP_LANGUAGE,
+ PROP_BASE_DIR,
+ PROP_BASE_GRAVITY,
+ PROP_GRAVITY_HINT,
+ PROP_MATRIX,
+ PROP_ROUND_GLYPH_POSITIONS,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+G_DEFINE_FINAL_TYPE (Pango2Context, pango2_context, G_TYPE_OBJECT)
+
+static void
+pango2_context_init (Pango2Context *context)
+{
+ context->base_dir = PANGO2_DIRECTION_WEAK_LTR;
+ context->resolved_gravity = context->base_gravity = PANGO2_GRAVITY_SOUTH;
+ context->gravity_hint = PANGO2_GRAVITY_HINT_NATURAL;
+
+ context->serial = 1;
+ context->set_language = NULL;
+ context->language = pango2_language_get_default ();
+ context->font_map = NULL;
+ context->round_glyph_positions = TRUE;
+
+ context->font_desc = pango2_font_description_new ();
+ pango2_font_description_set_family_static (context->font_desc, "serif");
+ pango2_font_description_set_style (context->font_desc, PANGO2_STYLE_NORMAL);
+ pango2_font_description_set_variant (context->font_desc, PANGO2_VARIANT_NORMAL);
+ pango2_font_description_set_weight (context->font_desc, PANGO2_WEIGHT_NORMAL);
+ pango2_font_description_set_stretch (context->font_desc, PANGO2_STRETCH_NORMAL);
+ pango2_font_description_set_size (context->font_desc, 12 * PANGO2_SCALE);
+}
+
+static gboolean
+pango2_has_mixed_deps (void)
+{
+ GModule *module;
+ gpointer func;
+ gboolean result = FALSE;
+
+ module = g_module_open (NULL, 0);
+
+ if (g_module_symbol (module, "pango2_ot_info_find_script", &func))
+ result = TRUE;
+
+ g_module_close (module);
+
+ return result;
+}
+
+static void
+pango2_context_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2Context *context = PANGO2_CONTEXT (object);
+
+ switch (property_id)
+ {
+ case PROP_FONT_MAP:
+ pango2_context_set_font_map (context, g_value_get_object (value));
+ break;
+
+ case PROP_FONT_DESCRIPTION:
+ pango2_context_set_font_description (context, g_value_get_boxed (value));
+ break;
+
+ case PROP_LANGUAGE:
+ pango2_context_set_language (context, g_value_get_boxed (value));
+ break;
+
+ case PROP_BASE_DIR:
+ pango2_context_set_base_dir (context, g_value_get_enum (value));
+ break;
+
+ case PROP_BASE_GRAVITY:
+ pango2_context_set_base_gravity (context, g_value_get_enum (value));
+ break;
+
+ case PROP_GRAVITY_HINT:
+ pango2_context_set_gravity_hint (context, g_value_get_enum (value));
+ break;
+
+ case PROP_MATRIX:
+ pango2_context_set_matrix (context, g_value_get_boxed (value));
+ break;
+
+ case PROP_ROUND_GLYPH_POSITIONS:
+ pango2_context_set_round_glyph_positions (context, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+pango2_context_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2Context *context = PANGO2_CONTEXT (object);
+
+ switch (property_id)
+ {
+ case PROP_FONT_MAP:
+ g_value_set_object (value, pango2_context_get_font_map (context));
+ break;
+
+ case PROP_FONT_DESCRIPTION:
+ g_value_set_boxed (value, pango2_context_get_font_description (context));
+ break;
+
+ case PROP_LANGUAGE:
+ g_value_set_boxed (value, pango2_context_get_language (context));
+ break;
+
+ case PROP_BASE_DIR:
+ g_value_set_enum (value, pango2_context_get_base_dir (context));
+ break;
+
+ case PROP_BASE_GRAVITY:
+ g_value_set_enum (value, pango2_context_get_base_gravity (context));
+ break;
+
+ case PROP_GRAVITY_HINT:
+ g_value_set_enum (value, pango2_context_get_gravity_hint (context));
+ break;
+
+ case PROP_MATRIX:
+ g_value_set_boxed (value, pango2_context_get_matrix (context));
+ break;
+
+ case PROP_ROUND_GLYPH_POSITIONS:
+ g_value_set_boolean (value, pango2_context_get_round_glyph_positions (context));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+pango2_context_class_init (Pango2ContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ /* Put the check for mixed linkage here, for lack of a better place */
+ if (pango2_has_mixed_deps ())
+ g_error ("Pango2 1.x symbols detected.\n"
+ "Using Pango2 1.x and 2 in the same process is not supported.");
+
+ object_class->finalize = pango2_context_finalize;
+ object_class->set_property = pango2_context_set_property;
+ object_class->get_property = pango2_context_get_property;
+
+ /**
+ * Pango2Context:font-map: (attributes org.gtk.Property.get=pango2_context_get_font_map org.gtk.Property.set=pango2_context_set_font_map)
+ *
+ * The font map to be searched when fonts are looked up
+ * in this context.
+ */
+ properties[PROP_FONT_MAP] =
+ g_param_spec_object ("font-map", NULL, NULL, PANGO2_TYPE_FONT_MAP,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Context:font-description: (attributes org.gtk.Property.get=pango2_context_get_font_description org.gtk.Property.set=pango2_context_set_font_description)
+ *
+ * The default font description for the context.
+ */
+ properties[PROP_FONT_DESCRIPTION] =
+ g_param_spec_boxed ("font-description", NULL, NULL, PANGO2_TYPE_FONT_DESCRIPTION,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Context:language: (attributes org.gtk.Property.get=pango2_context_get_language org.gtk.Property.set=pango2_context_set_language)
+ *
+ * The global language for the context.
+ */
+ properties[PROP_LANGUAGE] =
+ g_param_spec_boxed ("language", NULL, NULL, PANGO2_TYPE_LANGUAGE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Context:base-direction: (attributes org.gtk.Property.get=pango2_context_get_base_dir org.gtk.Property.set=pango2_context_set_base_dir)
+ *
+ * The base direction for the context.
+ *
+ * The base direction is used in applying the Unicode bidirectional
+ * algorithm; if the @direction is `PANGO2_DIRECTION_LTR` or
+ * `PANGO2_DIRECTION_RTL`, then the value will be used as the paragraph
+ * direction in the Unicode bidirectional algorithm. A value of
+ * `PANGO2_DIRECTION_WEAK_LTR` or `PANGO2_DIRECTION_WEAK_RTL` is used only
+ * for paragraphs that do not contain any strong characters themselves.
+ */
+ properties[PROP_BASE_DIR] =
+ g_param_spec_enum ("base-direction", NULL, NULL, PANGO2_TYPE_DIRECTION,
+ PANGO2_DIRECTION_LTR,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Context:base-gravity: (attributes org.gtk.Property.get=pango2_context_get_base_gravity org.gtk.Property.set=pango2_context_set_base_gravity)
+ *
+ * The base gravity for the context.
+ *
+ * The base gravity is used in laying vertical text out.
+ */
+ properties[PROP_BASE_GRAVITY] =
+ g_param_spec_enum ("base-gravity", NULL, NULL, PANGO2_TYPE_GRAVITY,
+ PANGO2_GRAVITY_SOUTH,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Context:gravity-hint: (attributes org.gtk.Property.get=pango2_context_get_gravity_hint org.gtk.Property.set=pango2_context_set_gravity_hint)
+ *
+ * The gravity hint for the context.
+ *
+ * The gravity hint is used in laying vertical text out, and
+ * is only relevant if gravity of the context as returned by
+ * [method@Pango2.Context.get_gravity] is set to `PANGO2_GRAVITY_EAST`
+ * or `PANGO2_GRAVITY_WEST`.
+ */
+ properties[PROP_GRAVITY_HINT] =
+ g_param_spec_enum ("gravity-hint", NULL, NULL, PANGO2_TYPE_GRAVITY_HINT,
+ PANGO2_GRAVITY_HINT_NATURAL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Context:matrix: (attributes org.gtk.Property.get=pango2_context_get_matrix org.gtk.Property.set=pango2_context_set_matrix)
+ *
+ * The 'user to device' transformation that will be applied when rendering
+ * with this context.
+ *
+ * This matrix is also known as the current transformation matrix, or 'ctm'.
+ *
+ * The transformation is needed in cases where the font rendering applies
+ * hinting that depends on knowing the position of text with respect to
+ * the pixel grid.
+ */
+ properties[PROP_MATRIX] =
+ g_param_spec_boxed ("matrix", NULL, NULL, PANGO2_TYPE_MATRIX,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Context:round-glyph-positions: (attributes org.gtk.Property.get=pango2_context_get_round_glyph_positions org.gtk.Property.set=pango2_context_set_round_glyph_positions)
+ *
+ * Determines whether font rendering with this context should
+ * round glyph positions and widths to integral positions,
+ * in device units.
+ *
+ * This is useful when the renderer can't handle subpixel
+ * positioning of glyphs.
+ */
+ properties[PROP_ROUND_GLYPH_POSITIONS] =
+ g_param_spec_boolean ("round-glyph-positions", NULL, NULL, TRUE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+static void
+pango2_context_finalize (GObject *object)
+{
+ Pango2Context *context;
+
+ context = PANGO2_CONTEXT (object);
+
+ if (context->font_map)
+ g_object_unref (context->font_map);
+
+ pango2_font_description_free (context->font_desc);
+ if (context->matrix)
+ pango2_matrix_free (context->matrix);
+
+ if (context->metrics)
+ pango2_font_metrics_free (context->metrics);
+
+ G_OBJECT_CLASS (pango2_context_parent_class)->finalize (object);
+}
+
+/**
+ * pango2_context_new:
+ *
+ * Creates a new `Pango2Context` initialized to default values.
+ *
+ * If you want to use a specific [class@Pango2.FontMap] other than
+ * the default one, you should use [ctor@Pango2.Context.new_with_font_map]
+ * instead.
+ *
+ * If you are using Pango2 as part of a higher-level system,
+ * that system may have it's own way of create a `Pango2Context`.
+ * Pango2's own cairo support for instance, has [func@Pango2.cairo_create_context],
+ * and the GTK toolkit has, among others, gtk_widget_get_pango2_context().
+ * Use those instead.
+ *
+ * Return value: the newly allocated `Pango2Context`
+ */
+Pango2Context *
+pango2_context_new (void)
+{
+ return g_object_new (PANGO2_TYPE_CONTEXT,
+ "font-map", pango2_font_map_get_default (),
+ NULL);
+}
+
+/**
+ * pango2_context_new_with_font_map:
+ * @font_map: the `Pango2FontMap` to use
+ *
+ * Creates a new `Pango2Context` with the given font map.
+ *
+ * If you are using Pango2 as part of a higher-level system,
+ * that system may have it's own way of create a `Pango2Context`.
+ * Pango2's own cairo support for instance, has [func@Pango2.cairo_create_context],
+ * and the GTK toolkit has, among others, gtk_widget_get_pango2_context().
+ * Use those instead.
+ *
+ * Return value: the newly allocated `Pango2Context`
+ */
+Pango2Context *
+pango2_context_new_with_font_map (Pango2FontMap *font_map)
+{
+ return g_object_new (PANGO2_TYPE_CONTEXT,
+ "font-map", font_map,
+ NULL);
+}
+
+static void
+update_resolved_gravity (Pango2Context *context)
+{
+ if (context->base_gravity == PANGO2_GRAVITY_AUTO)
+ context->resolved_gravity = pango2_gravity_get_for_matrix (context->matrix);
+ else
+ context->resolved_gravity = context->base_gravity;
+}
+
+/**
+ * pango2_context_set_matrix:
+ * @context: a `Pango2Context`
+ * @matrix: (nullable): a `Pango2Matrix`, or %NULL to unset any existing
+ * matrix. (No matrix set is the same as setting the identity matrix.)
+ *
+ * Sets the 'user to device' transformation that will be applied when rendering
+ * with this context.
+ *
+ * This matrix is also known as the current transformation matrix, or 'ctm'.
+ *
+ * The transformation is needed in cases where the font rendering applies
+ * hinting that depends on knowing the position of text with respect to
+ * the pixel grid.
+ *
+ * Note that reported metrics are in the user space coordinates before
+ * the application of the matrix, not device-space coordinates after the
+ * application of the matrix. So, they don't scale with the matrix, though
+ * they may change slightly for different matrices, depending on how the
+ * text is fit to the pixel grid.
+ */
+void
+pango2_context_set_matrix (Pango2Context *context,
+ const Pango2Matrix *matrix)
+{
+ g_return_if_fail (PANGO2_IS_CONTEXT (context));
+
+ if (context->matrix || matrix)
+ context_changed (context);
+
+ if (context->matrix)
+ pango2_matrix_free (context->matrix);
+ if (matrix)
+ context->matrix = pango2_matrix_copy (matrix);
+ else
+ context->matrix = NULL;
+
+ update_resolved_gravity (context);
+ g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_MATRIX]);
+}
+
+/**
+ * pango2_context_get_matrix:
+ * @context: a `Pango2Context`
+ *
+ * Gets the transformation matrix that will be applied when
+ * rendering with this context.
+ *
+ * See [method@Pango2.Context.set_matrix].
+ *
+ * Return value: (nullable): the matrix, or %NULL if no matrix has
+ * been set (which is the same as the identity matrix). The returned
+ * matrix is owned by Pango2 and must not be modified or freed.
+ */
+const Pango2Matrix *
+pango2_context_get_matrix (Pango2Context *context)
+{
+ g_return_val_if_fail (PANGO2_IS_CONTEXT (context), NULL);
+
+ return context->matrix;
+}
+
+/**
+ * pango2_context_set_font_map:
+ * @context: a `Pango2Context`
+ * @font_map: the `Pango2FontMap` to set.
+ *
+ * Sets the font map to be searched when fonts are looked-up
+ * in this context.
+ *
+ * This is only for internal use by Pango2 backends, a `Pango2Context`
+ * obtained via one of the recommended methods should already have a
+ * suitable font map.
+ */
+void
+pango2_context_set_font_map (Pango2Context *context,
+ Pango2FontMap *font_map)
+{
+ g_return_if_fail (PANGO2_IS_CONTEXT (context));
+ g_return_if_fail (!font_map || PANGO2_IS_FONT_MAP (font_map));
+
+ if (font_map == context->font_map)
+ return;
+
+ context_changed (context);
+
+ if (font_map)
+ g_object_ref (font_map);
+
+ if (context->font_map)
+ g_object_unref (context->font_map);
+
+ context->font_map = font_map;
+ context->fontmap_serial = pango2_font_map_get_serial (font_map);
+
+ g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_FONT_MAP]);
+}
+
+/**
+ * pango2_context_get_font_map:
+ * @context: a `Pango2Context`
+ *
+ * Gets the `Pango2FontMap` used to look up fonts for this context.
+ *
+ * Return value: (transfer none): the font map for the `Pango2Context`.
+ * This value is owned by Pango2 and should not be unreferenced.
+ */
+Pango2FontMap *
+pango2_context_get_font_map (Pango2Context *context)
+{
+ g_return_val_if_fail (PANGO2_IS_CONTEXT (context), NULL);
+
+ return context->font_map;
+}
+
+/**
+ * pango2_context_load_font:
+ * @context: a `Pango2Context`
+ * @desc: a `Pango2FontDescription` describing the font to load
+ *
+ * Loads the font in one of the fontmaps in the context
+ * that is the closest match for @desc.
+ *
+ * Returns: (transfer full) (nullable): the newly allocated `Pango2Font`
+ * that was loaded, or %NULL if no font matched.
+ */
+Pango2Font *
+pango2_context_load_font (Pango2Context *context,
+ const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (context != NULL, NULL);
+ g_return_val_if_fail (context->font_map != NULL, NULL);
+
+ return pango2_font_map_load_font (context->font_map, context, desc);
+}
+
+/**
+ * pango2_context_load_fontset:
+ * @context: a `Pango2Context`
+ * @desc: a `Pango2FontDescription` describing the fonts to load
+ * @language: a `Pango2Language` the fonts will be used for
+ *
+ * Load a set of fonts in the context that can be used to render
+ * a font matching @desc.
+ *
+ * Returns: (transfer full) (nullable): the newly allocated
+ * `Pango2Fontset` loaded, or %NULL if no font matched.
+ */
+Pango2Fontset *
+pango2_context_load_fontset (Pango2Context *context,
+ const Pango2FontDescription *desc,
+ Pango2Language *language)
+{
+ g_return_val_if_fail (context != NULL, NULL);
+
+ return pango2_font_map_load_fontset (context->font_map, context, desc, language);
+}
+
+/**
+ * pango2_context_set_font_description:
+ * @context: a `Pango2Context`
+ * @desc: the new pango font description
+ *
+ * Set the default font description for the context
+ */
+void
+pango2_context_set_font_description (Pango2Context *context,
+ const Pango2FontDescription *desc)
+{
+ g_return_if_fail (context != NULL);
+ g_return_if_fail (desc != NULL);
+
+ if (desc != context->font_desc &&
+ (!desc || !context->font_desc || !pango2_font_description_equal(desc, context->font_desc)))
+ {
+ context_changed (context);
+
+ pango2_font_description_free (context->font_desc);
+ context->font_desc = pango2_font_description_copy (desc);
+
+ g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_FONT_DESCRIPTION]);
+ }
+}
+
+/**
+ * pango2_context_get_font_description:
+ * @context: a `Pango2Context`
+ *
+ * Retrieve the default font description for the context.
+ *
+ * Return value: (transfer none): a pointer to the context's default font
+ * description. This value must not be modified or freed.
+ */
+Pango2FontDescription *
+pango2_context_get_font_description (Pango2Context *context)
+{
+ g_return_val_if_fail (context != NULL, NULL);
+
+ return context->font_desc;
+}
+
+/**
+ * pango2_context_set_language:
+ * @context: a `Pango2Context`
+ * @language: the new language tag.
+ *
+ * Sets the global language tag for the context.
+ *
+ * The default language for the locale of the running process
+ * can be found using [func@Pango2.Language.get_default].
+ */
+void
+pango2_context_set_language (Pango2Context *context,
+ Pango2Language *language)
+{
+ g_return_if_fail (context != NULL);
+
+ if (language != context->language)
+ context_changed (context);
+
+ context->set_language = language;
+ if (language)
+ context->language = language;
+ else
+ context->language = pango2_language_get_default ();
+
+ g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_LANGUAGE]);
+}
+
+/**
+ * pango2_context_get_language:
+ * @context: a `Pango2Context`
+ *
+ * Retrieves the global language tag for the context.
+ *
+ * Return value: the global language tag.
+ */
+Pango2Language *
+pango2_context_get_language (Pango2Context *context)
+{
+ g_return_val_if_fail (context != NULL, NULL);
+
+ return context->set_language;
+}
+
+/**
+ * pango2_context_set_base_dir:
+ * @context: a `Pango2Context`
+ * @direction: the new base direction
+ *
+ * Sets the base direction for the context.
+ *
+ * The base direction is used in applying the Unicode bidirectional
+ * algorithm; if the @direction is %PANGO2_DIRECTION_LTR or
+ * %PANGO2_DIRECTION_RTL, then the value will be used as the paragraph
+ * direction in the Unicode bidirectional algorithm. A value of
+ * %PANGO2_DIRECTION_WEAK_LTR or %PANGO2_DIRECTION_WEAK_RTL is used only
+ * for paragraphs that do not contain any strong characters themselves.
+ */
+void
+pango2_context_set_base_dir (Pango2Context *context,
+ Pango2Direction direction)
+{
+ g_return_if_fail (context != NULL);
+
+ if (direction == context->base_dir)
+ return;
+
+ context_changed (context);
+
+ context->base_dir = direction;
+ g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_BASE_DIR]);
+}
+
+/**
+ * pango2_context_get_base_dir:
+ * @context: a `Pango2Context`
+ *
+ * Retrieves the base direction for the context.
+ *
+ * See [method@Pango2.Context.set_base_dir].
+ *
+ * Return value: the base direction for the context.
+ */
+Pango2Direction
+pango2_context_get_base_dir (Pango2Context *context)
+{
+ g_return_val_if_fail (context != NULL, PANGO2_DIRECTION_LTR);
+
+ return context->base_dir;
+}
+
+/**
+ * pango2_context_set_base_gravity:
+ * @context: a `Pango2Context`
+ * @gravity: the new base gravity
+ *
+ * Sets the base gravity for the context.
+ *
+ * The base gravity is used in laying vertical text out.
+ */
+void
+pango2_context_set_base_gravity (Pango2Context *context,
+ Pango2Gravity gravity)
+{
+ g_return_if_fail (context != NULL);
+
+ if (gravity == context->base_gravity)
+ return;
+
+ context_changed (context);
+
+ context->base_gravity = gravity;
+
+ update_resolved_gravity (context);
+ g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_BASE_GRAVITY]);
+}
+
+/**
+ * pango2_context_get_base_gravity:
+ * @context: a `Pango2Context`
+ *
+ * Retrieves the base gravity for the context.
+ *
+ * See [method@Pango2.Context.set_base_gravity].
+ *
+ * Return value: the base gravity for the context.
+ */
+Pango2Gravity
+pango2_context_get_base_gravity (Pango2Context *context)
+{
+ g_return_val_if_fail (context != NULL, PANGO2_GRAVITY_SOUTH);
+
+ return context->base_gravity;
+}
+
+/**
+ * pango2_context_get_gravity:
+ * @context: a `Pango2Context`
+ *
+ * Retrieves the gravity for the context.
+ *
+ * This is similar to [method@Pango2.Context.get_base_gravity],
+ * except for when the base gravity is %PANGO2_GRAVITY_AUTO for
+ * which [func@Pango2.Gravity.get_for_matrix] is used to return the
+ * gravity from the current context matrix.
+ *
+ * Return value: the resolved gravity for the context.
+ */
+Pango2Gravity
+pango2_context_get_gravity (Pango2Context *context)
+{
+ g_return_val_if_fail (context != NULL, PANGO2_GRAVITY_SOUTH);
+
+ return context->resolved_gravity;
+}
+
+/**
+ * pango2_context_set_gravity_hint:
+ * @context: a `Pango2Context`
+ * @hint: the new gravity hint
+ *
+ * Sets the gravity hint for the context.
+ *
+ * The gravity hint is used in laying vertical text out, and
+ * is only relevant if gravity of the context as returned by
+ * [method@Pango2.Context.get_gravity] is set to %PANGO2_GRAVITY_EAST
+ * or %PANGO2_GRAVITY_WEST.
+ */
+void
+pango2_context_set_gravity_hint (Pango2Context *context,
+ Pango2GravityHint hint)
+{
+ g_return_if_fail (context != NULL);
+
+ if (hint == context->gravity_hint)
+ return;
+
+ context_changed (context);
+
+ context->gravity_hint = hint;
+ g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_GRAVITY_HINT]);
+}
+
+/**
+ * pango2_context_get_gravity_hint:
+ * @context: a `Pango2Context`
+ *
+ * Retrieves the gravity hint for the context.
+ *
+ * See [method@Pango2.Context.set_gravity_hint] for details.
+ *
+ * Return value: the gravity hint for the context.
+ */
+Pango2GravityHint
+pango2_context_get_gravity_hint (Pango2Context *context)
+{
+ g_return_val_if_fail (context != NULL, PANGO2_GRAVITY_HINT_NATURAL);
+
+ return context->gravity_hint;
+}
+
+
+static gboolean
+get_first_metrics_foreach (Pango2Fontset *fontset,
+ Pango2Font *font,
+ gpointer data)
+{
+ Pango2FontMetrics **fontset_metrics = data;
+ Pango2Language *language = pango2_fontset_get_language (fontset);
+
+ *fontset_metrics = pango2_font_get_metrics (font, language);
+
+ return TRUE; /* Stops iteration */
+}
+
+static Pango2FontMetrics *
+get_base_metrics (Pango2Fontset *fontset)
+{
+ Pango2FontMetrics *metrics = NULL;
+
+ /* Initialize the metrics from the first font in the fontset */
+ pango2_fontset_foreach (fontset, get_first_metrics_foreach, &metrics);
+
+ return metrics;
+}
+
+static void
+update_metrics_from_items (Pango2FontMetrics *metrics,
+ Pango2Language *language,
+ const char *text,
+ unsigned int text_len,
+ GList *items)
+
+{
+ GHashTable *fonts_seen = g_hash_table_new (NULL, NULL);
+ Pango2GlyphString *glyphs = pango2_glyph_string_new ();
+ GList *l;
+ glong text_width;
+
+ /* This should typically be called with a sample text string. */
+ g_return_if_fail (text_len > 0);
+
+ metrics->approximate_char_width = 0;
+
+ for (l = items; l; l = l->next)
+ {
+ Pango2Item *item = l->data;
+ Pango2Font *font = item->analysis.font;
+
+ if (font != NULL && g_hash_table_lookup (fonts_seen, font) == NULL)
+ {
+ Pango2FontMetrics *raw_metrics = pango2_font_get_metrics (font, language);
+ g_hash_table_insert (fonts_seen, font, font);
+
+ /* metrics will already be initialized from the first font in the fontset */
+ metrics->ascent = MAX (metrics->ascent, raw_metrics->ascent);
+ metrics->descent = MAX (metrics->descent, raw_metrics->descent);
+ metrics->height = MAX (metrics->height, raw_metrics->height);
+ pango2_font_metrics_free (raw_metrics);
+ }
+
+ pango2_shape (text + item->offset, item->length,
+ text, text_len,
+ &item->analysis, glyphs,
+ PANGO2_SHAPE_NONE);
+
+ metrics->approximate_char_width += pango2_glyph_string_get_width (glyphs);
+ }
+
+ pango2_glyph_string_free (glyphs);
+ g_hash_table_destroy (fonts_seen);
+
+ text_width = pango2_utf8_strwidth (text);
+ g_assert (text_width > 0);
+ metrics->approximate_char_width /= text_width;
+}
+
+/**
+ * pango2_context_get_metrics:
+ * @context: a `Pango2Context`
+ * @desc: (nullable): a `Pango2FontDescription` structure. %NULL means that the
+ * font description from the context will be used.
+ * @language: (nullable): language tag used to determine which script to get
+ * the metrics for. %NULL means that the language tag from the context
+ * will be used. If no language tag is set on the context, metrics
+ * for the default language (as determined by [func@Pango2.Language.get_default]
+ * will be returned.
+ *
+ * Get overall metric information for a particular font description.
+ *
+ * Since the metrics may be substantially different for different scripts,
+ * a language tag can be provided to indicate that the metrics should be
+ * retrieved that correspond to the script(s) used by that language.
+ *
+ * The `Pango2FontDescription` is interpreted in the same way as by [func@itemize],
+ * and the family name may be a comma separated list of names. If characters
+ * from multiple of these families would be used to render the string, then
+ * the returned fonts would be a composite of the metrics for the fonts loaded
+ * for the individual families.
+ *
+ * Return value: a `Pango2FontMetrics` object. The caller must call
+ * [method@Pango2.FontMetrics.free] when finished using the object.
+ */
+Pango2FontMetrics *
+pango2_context_get_metrics (Pango2Context *context,
+ const Pango2FontDescription *desc,
+ Pango2Language *language)
+{
+ Pango2Fontset *current_fonts = NULL;
+ Pango2FontMetrics *metrics;
+ const char *sample_str;
+ unsigned int text_len;
+ GList *items;
+
+ g_return_val_if_fail (PANGO2_IS_CONTEXT (context), NULL);
+
+ if (!desc)
+ desc = context->font_desc;
+
+ if (!language)
+ language = context->language;
+
+ if (desc == context->font_desc &&
+ language == context->language &&
+ context->metrics != NULL)
+ return pango2_font_metrics_copy (context->metrics);
+
+ current_fonts = pango2_font_map_load_fontset (context->font_map, context, desc, language);
+ metrics = get_base_metrics (current_fonts);
+
+ sample_str = pango2_language_get_sample_string (language);
+ text_len = strlen (sample_str);
+ items = pango2_itemize_with_font (context, context->base_dir,
+ sample_str, 0, text_len,
+ NULL, NULL,
+ desc);
+
+ update_metrics_from_items (metrics, language, sample_str, text_len, items);
+
+ g_list_foreach (items, (GFunc)pango2_item_free, NULL);
+ g_list_free (items);
+
+ g_object_unref (current_fonts);
+
+ if (desc == context->font_desc &&
+ language == context->language)
+ {
+ pango2_font_metrics_free (context->metrics);
+ context->metrics = pango2_font_metrics_copy (metrics);
+ }
+
+ return metrics;
+}
+
+static void
+context_changed (Pango2Context *context)
+{
+ context->serial++;
+ if (context->serial == 0)
+ context->serial++;
+
+ g_clear_pointer (&context->metrics, pango2_font_metrics_free);
+}
+
+/**
+ * pango2_context_changed:
+ * @context: a `Pango2Context`
+ *
+ * Forces a change in the context, which will cause any `Pango2Layout`
+ * using this context to re-layout.
+ *
+ * This function is only useful when implementing a new backend
+ * for Pango2, something applications won't do. Backends should
+ * call this function if they have attached extra data to the context
+ * and such data is changed.
+ */
+void
+pango2_context_changed (Pango2Context *context)
+{
+ context_changed (context);
+}
+
+static void
+check_fontmap_changed (Pango2Context *context)
+{
+ guint old_serial = context->fontmap_serial;
+
+ if (!context->font_map)
+ return;
+
+ context->fontmap_serial = pango2_font_map_get_serial (context->font_map);
+
+ if (old_serial != context->fontmap_serial)
+ context_changed (context);
+}
+
+/**
+ * pango2_context_get_serial:
+ * @context: a `Pango2Context`
+ *
+ * Returns the current serial number of @context.
+ *
+ * The serial number is initialized to an small number larger than zero
+ * when a new context is created and is increased whenever the context
+ * is changed using any of the setter functions, or the `Pango2FontMap` it
+ * uses to find fonts 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 `Pango2Context`,
+ * and is only useful when implementing objects that need update when their
+ * `Pango2Context` changes, like `Pango2Layout`.
+ *
+ * Return value: The current serial number of @context.
+ */
+guint
+pango2_context_get_serial (Pango2Context *context)
+{
+ check_fontmap_changed (context);
+ return context->serial;
+}
+
+/**
+ * pango2_context_set_round_glyph_positions:
+ * @context: a `Pango2Context`
+ * @round_positions: whether to round glyph positions
+ *
+ * Sets whether font rendering with this context should
+ * round glyph positions and widths to integral positions,
+ * in device units.
+ *
+ * This is useful when the renderer can't handle subpixel
+ * positioning of glyphs.
+ *
+ * The default value is to round glyph positions, to remain
+ * compatible with previous Pango2 behavior.
+ */
+void
+pango2_context_set_round_glyph_positions (Pango2Context *context,
+ gboolean round_positions)
+{
+ if (context->round_glyph_positions == round_positions)
+ return;
+
+ context->round_glyph_positions = round_positions;
+ context_changed (context);
+ g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_ROUND_GLYPH_POSITIONS]);
+}
+
+/**
+ * pango2_context_get_round_glyph_positions:
+ * @context: a `Pango2Context`
+ *
+ * Returns whether font rendering with this context should
+ * round glyph positions and widths.
+ */
+gboolean
+pango2_context_get_round_glyph_positions (Pango2Context *context)
+{
+ return context->round_glyph_positions;
+}
diff --git a/pango2/pango-context.h b/pango2/pango-context.h
new file mode 100644
index 00000000..902e840d
--- /dev/null
+++ b/pango2/pango-context.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-font.h>
+#include <pango2/pango-fontmap.h>
+#include <pango2/pango-attributes.h>
+#include <pango2/pango-direction.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_CONTEXT (pango2_context_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (Pango2Context, pango2_context, PANGO2, CONTEXT, GObject);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Context * pango2_context_new (void);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Context * pango2_context_new_with_font_map (Pango2FontMap *font_map);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_context_changed (Pango2Context *context);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_context_set_font_map (Pango2Context *context,
+ Pango2FontMap *font_map);
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontMap * pango2_context_get_font_map (Pango2Context *context);
+PANGO2_AVAILABLE_IN_ALL
+guint pango2_context_get_serial (Pango2Context *context);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Font * pango2_context_load_font (Pango2Context *context,
+ const Pango2FontDescription *desc);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Fontset * pango2_context_load_fontset (Pango2Context *context,
+ const Pango2FontDescription *desc,
+ Pango2Language *language);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontMetrics * pango2_context_get_metrics (Pango2Context *context,
+ const Pango2FontDescription *desc,
+ Pango2Language *language);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_context_set_font_description (Pango2Context *context,
+ const Pango2FontDescription *desc);
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontDescription * pango2_context_get_font_description (Pango2Context *context);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Language * pango2_context_get_language (Pango2Context *context);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_context_set_language (Pango2Context *context,
+ Pango2Language *language);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_context_set_base_dir (Pango2Context *context,
+ Pango2Direction direction);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Direction pango2_context_get_base_dir (Pango2Context *context);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_context_set_base_gravity (Pango2Context *context,
+ Pango2Gravity gravity);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Gravity pango2_context_get_base_gravity (Pango2Context *context);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Gravity pango2_context_get_gravity (Pango2Context *context);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_context_set_gravity_hint (Pango2Context *context,
+ Pango2GravityHint hint);
+PANGO2_AVAILABLE_IN_ALL
+Pango2GravityHint pango2_context_get_gravity_hint (Pango2Context *context);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_context_set_matrix (Pango2Context *context,
+ const Pango2Matrix *matrix);
+PANGO2_AVAILABLE_IN_ALL
+const Pango2Matrix * pango2_context_get_matrix (Pango2Context *context);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_context_set_round_glyph_positions (Pango2Context *context,
+ gboolean round_positions);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_context_get_round_glyph_positions (Pango2Context *context);
+
+G_END_DECLS
diff --git a/pango2/pango-direction.h b/pango2/pango-direction.h
new file mode 100644
index 00000000..cf32c48a
--- /dev/null
+++ b/pango2/pango-direction.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 Matthias Clasen
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/**
+ * Pango2Direction:
+ * @PANGO2_DIRECTION_LTR: A strong left-to-right direction
+ * @PANGO2_DIRECTION_RTL: A strong right-to-left direction
+ * @PANGO2_DIRECTION_WEAK_LTR: A weak left-to-right direction
+ * @PANGO2_DIRECTION_WEAK_RTL: A weak right-to-left direction
+ * @PANGO2_DIRECTION_NEUTRAL: No direction specified
+ *
+ * `Pango2Direction` represents a direction in the Unicode bidirectional
+ * algorithm.
+ *
+ * Not every value in this enumeration makes sense for every usage of
+ * `Pango2Direction`; for example, the direction of characters cannot be
+ * `PANGO2_DIRECTION_WEAK_LTR` or `PANGO2_DIRECTION_WEAK_RTL`, since every
+ * character is either neutral or has a strong direction; on the other hand
+ * `PANGO2_DIRECTION_NEUTRAL` doesn't make sense to pass to [func@itemize].
+ *
+ * See `Pango2Gravity` for how vertical text is handled in Pango2.
+ *
+ * If you are interested in text direction, you should really use
+ * [fribidi](http://fribidi.org/) directly. `Pango2Direction` is only
+ * retained because it is used in some public apis.
+ */
+typedef enum {
+ PANGO2_DIRECTION_LTR,
+ PANGO2_DIRECTION_RTL,
+ PANGO2_DIRECTION_WEAK_LTR,
+ PANGO2_DIRECTION_WEAK_RTL,
+ PANGO2_DIRECTION_NEUTRAL
+} Pango2Direction;
+
+G_END_DECLS
diff --git a/pango2/pango-emoji-private.h b/pango2/pango-emoji-private.h
new file mode 100644
index 00000000..f7578bf4
--- /dev/null
+++ b/pango2/pango-emoji-private.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 Google, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+gboolean
+_pango2_Is_Emoji_Base_Character (gunichar ch);
+
+gboolean
+_pango2_Is_Emoji_Extended_Pictographic (gunichar ch);
+
+typedef struct _Pango2EmojiIter Pango2EmojiIter;
+
+struct _Pango2EmojiIter
+{
+ const char *text_start;
+ const char *text_end;
+ const char *start;
+ const char *end;
+ gboolean is_emoji;
+
+ unsigned char *types;
+ unsigned int n_chars;
+ unsigned int cursor;
+};
+
+Pango2EmojiIter *
+_pango2_emoji_iter_init (Pango2EmojiIter *iter,
+ const char *text,
+ int length);
+
+gboolean
+_pango2_emoji_iter_next (Pango2EmojiIter *iter);
+
+void
+_pango2_emoji_iter_fini (Pango2EmojiIter *iter);
diff --git a/pango2/pango-emoji-table.h b/pango2/pango-emoji-table.h
new file mode 100644
index 00000000..00f1dd13
--- /dev/null
+++ b/pango2/pango-emoji-table.h
@@ -0,0 +1,407 @@
+/* == Start of generated table == */
+/*
+ * The following tables are generated by running:
+ *
+ * ./gen-emoji-table.py emoji-data.txt
+ *
+ * on file with this header:
+ *
+ * # emoji-data-14.0.0.txt
+ * # Date: 2021-08-26, 17:22:22 GMT
+ * # © 2021 Unicode®, Inc.
+ * # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
+ * # For terms of use, see http://www.unicode.org/terms_of_use.html
+ * #
+ * # Emoji Data for UTS #51
+ * # Used with Emoji Version 14.0 and subsequent minor revisions (if any)
+ * #
+ * # For documentation and usage, see http://www.unicode.org/reports/tr51
+ */
+
+#ifndef PANGO2_EMOJI_TABLE_H
+#define PANGO2_EMOJI_TABLE_H
+
+#include <glib.h>
+
+struct Interval {
+ gunichar start, end;
+};
+
+static const struct Interval _pango2_Emoji_table[] =
+{
+ {0x0023, 0x0023},
+ {0x002A, 0x002A},
+ {0x0030, 0x0039},
+ {0x00A9, 0x00A9},
+ {0x00AE, 0x00AE},
+ {0x203C, 0x203C},
+ {0x2049, 0x2049},
+ {0x2122, 0x2122},
+ {0x2139, 0x2139},
+ {0x2194, 0x2199},
+ {0x21A9, 0x21AA},
+ {0x231A, 0x231B},
+ {0x2328, 0x2328},
+ {0x23CF, 0x23CF},
+ {0x23E9, 0x23F3},
+ {0x23F8, 0x23FA},
+ {0x24C2, 0x24C2},
+ {0x25AA, 0x25AB},
+ {0x25B6, 0x25B6},
+ {0x25C0, 0x25C0},
+ {0x25FB, 0x25FE},
+ {0x2600, 0x2604},
+ {0x260E, 0x260E},
+ {0x2611, 0x2611},
+ {0x2614, 0x2615},
+ {0x2618, 0x2618},
+ {0x261D, 0x261D},
+ {0x2620, 0x2620},
+ {0x2622, 0x2623},
+ {0x2626, 0x2626},
+ {0x262A, 0x262A},
+ {0x262E, 0x262F},
+ {0x2638, 0x263A},
+ {0x2640, 0x2640},
+ {0x2642, 0x2642},
+ {0x2648, 0x2653},
+ {0x265F, 0x2660},
+ {0x2663, 0x2663},
+ {0x2665, 0x2666},
+ {0x2668, 0x2668},
+ {0x267B, 0x267B},
+ {0x267E, 0x267F},
+ {0x2692, 0x2697},
+ {0x2699, 0x2699},
+ {0x269B, 0x269C},
+ {0x26A0, 0x26A1},
+ {0x26A7, 0x26A7},
+ {0x26AA, 0x26AB},
+ {0x26B0, 0x26B1},
+ {0x26BD, 0x26BE},
+ {0x26C4, 0x26C5},
+ {0x26C8, 0x26C8},
+ {0x26CE, 0x26CF},
+ {0x26D1, 0x26D1},
+ {0x26D3, 0x26D4},
+ {0x26E9, 0x26EA},
+ {0x26F0, 0x26F5},
+ {0x26F7, 0x26FA},
+ {0x26FD, 0x26FD},
+ {0x2702, 0x2702},
+ {0x2705, 0x2705},
+ {0x2708, 0x270D},
+ {0x270F, 0x270F},
+ {0x2712, 0x2712},
+ {0x2714, 0x2714},
+ {0x2716, 0x2716},
+ {0x271D, 0x271D},
+ {0x2721, 0x2721},
+ {0x2728, 0x2728},
+ {0x2733, 0x2734},
+ {0x2744, 0x2744},
+ {0x2747, 0x2747},
+ {0x274C, 0x274C},
+ {0x274E, 0x274E},
+ {0x2753, 0x2755},
+ {0x2757, 0x2757},
+ {0x2763, 0x2764},
+ {0x2795, 0x2797},
+ {0x27A1, 0x27A1},
+ {0x27B0, 0x27B0},
+ {0x27BF, 0x27BF},
+ {0x2934, 0x2935},
+ {0x2B05, 0x2B07},
+ {0x2B1B, 0x2B1C},
+ {0x2B50, 0x2B50},
+ {0x2B55, 0x2B55},
+ {0x3030, 0x3030},
+ {0x303D, 0x303D},
+ {0x3297, 0x3297},
+ {0x3299, 0x3299},
+ {0x1F004, 0x1F004},
+ {0x1F0CF, 0x1F0CF},
+ {0x1F170, 0x1F171},
+ {0x1F17E, 0x1F17F},
+ {0x1F18E, 0x1F18E},
+ {0x1F191, 0x1F19A},
+ {0x1F1E6, 0x1F1FF},
+ {0x1F201, 0x1F202},
+ {0x1F21A, 0x1F21A},
+ {0x1F22F, 0x1F22F},
+ {0x1F232, 0x1F23A},
+ {0x1F250, 0x1F251},
+ {0x1F300, 0x1F321},
+ {0x1F324, 0x1F393},
+ {0x1F396, 0x1F397},
+ {0x1F399, 0x1F39B},
+ {0x1F39E, 0x1F3F0},
+ {0x1F3F3, 0x1F3F5},
+ {0x1F3F7, 0x1F4FD},
+ {0x1F4FF, 0x1F53D},
+ {0x1F549, 0x1F54E},
+ {0x1F550, 0x1F567},
+ {0x1F56F, 0x1F570},
+ {0x1F573, 0x1F57A},
+ {0x1F587, 0x1F587},
+ {0x1F58A, 0x1F58D},
+ {0x1F590, 0x1F590},
+ {0x1F595, 0x1F596},
+ {0x1F5A4, 0x1F5A5},
+ {0x1F5A8, 0x1F5A8},
+ {0x1F5B1, 0x1F5B2},
+ {0x1F5BC, 0x1F5BC},
+ {0x1F5C2, 0x1F5C4},
+ {0x1F5D1, 0x1F5D3},
+ {0x1F5DC, 0x1F5DE},
+ {0x1F5E1, 0x1F5E1},
+ {0x1F5E3, 0x1F5E3},
+ {0x1F5E8, 0x1F5E8},
+ {0x1F5EF, 0x1F5EF},
+ {0x1F5F3, 0x1F5F3},
+ {0x1F5FA, 0x1F64F},
+ {0x1F680, 0x1F6C5},
+ {0x1F6CB, 0x1F6D2},
+ {0x1F6D5, 0x1F6D7},
+ {0x1F6DD, 0x1F6E5},
+ {0x1F6E9, 0x1F6E9},
+ {0x1F6EB, 0x1F6EC},
+ {0x1F6F0, 0x1F6F0},
+ {0x1F6F3, 0x1F6FC},
+ {0x1F7E0, 0x1F7EB},
+ {0x1F7F0, 0x1F7F0},
+ {0x1F90C, 0x1F93A},
+ {0x1F93C, 0x1F945},
+ {0x1F947, 0x1F9FF},
+ {0x1FA70, 0x1FA74},
+ {0x1FA78, 0x1FA7C},
+ {0x1FA80, 0x1FA86},
+ {0x1FA90, 0x1FAAC},
+ {0x1FAB0, 0x1FABA},
+ {0x1FAC0, 0x1FAC5},
+ {0x1FAD0, 0x1FAD9},
+ {0x1FAE0, 0x1FAE7},
+ {0x1FAF0, 0x1FAF6},
+};
+
+static const struct Interval _pango2_Emoji_Presentation_table[] =
+{
+ {0x231A, 0x231B},
+ {0x23E9, 0x23EC},
+ {0x23F0, 0x23F0},
+ {0x23F3, 0x23F3},
+ {0x25FD, 0x25FE},
+ {0x2614, 0x2615},
+ {0x2648, 0x2653},
+ {0x267F, 0x267F},
+ {0x2693, 0x2693},
+ {0x26A1, 0x26A1},
+ {0x26AA, 0x26AB},
+ {0x26BD, 0x26BE},
+ {0x26C4, 0x26C5},
+ {0x26CE, 0x26CE},
+ {0x26D4, 0x26D4},
+ {0x26EA, 0x26EA},
+ {0x26F2, 0x26F3},
+ {0x26F5, 0x26F5},
+ {0x26FA, 0x26FA},
+ {0x26FD, 0x26FD},
+ {0x2705, 0x2705},
+ {0x270A, 0x270B},
+ {0x2728, 0x2728},
+ {0x274C, 0x274C},
+ {0x274E, 0x274E},
+ {0x2753, 0x2755},
+ {0x2757, 0x2757},
+ {0x2795, 0x2797},
+ {0x27B0, 0x27B0},
+ {0x27BF, 0x27BF},
+ {0x2B1B, 0x2B1C},
+ {0x2B50, 0x2B50},
+ {0x2B55, 0x2B55},
+ {0x1F004, 0x1F004},
+ {0x1F0CF, 0x1F0CF},
+ {0x1F18E, 0x1F18E},
+ {0x1F191, 0x1F19A},
+ {0x1F1E6, 0x1F1FF},
+ {0x1F201, 0x1F201},
+ {0x1F21A, 0x1F21A},
+ {0x1F22F, 0x1F22F},
+ {0x1F232, 0x1F236},
+ {0x1F238, 0x1F23A},
+ {0x1F250, 0x1F251},
+ {0x1F300, 0x1F320},
+ {0x1F32D, 0x1F335},
+ {0x1F337, 0x1F37C},
+ {0x1F37E, 0x1F393},
+ {0x1F3A0, 0x1F3CA},
+ {0x1F3CF, 0x1F3D3},
+ {0x1F3E0, 0x1F3F0},
+ {0x1F3F4, 0x1F3F4},
+ {0x1F3F8, 0x1F43E},
+ {0x1F440, 0x1F440},
+ {0x1F442, 0x1F4FC},
+ {0x1F4FF, 0x1F53D},
+ {0x1F54B, 0x1F54E},
+ {0x1F550, 0x1F567},
+ {0x1F57A, 0x1F57A},
+ {0x1F595, 0x1F596},
+ {0x1F5A4, 0x1F5A4},
+ {0x1F5FB, 0x1F64F},
+ {0x1F680, 0x1F6C5},
+ {0x1F6CC, 0x1F6CC},
+ {0x1F6D0, 0x1F6D2},
+ {0x1F6D5, 0x1F6D7},
+ {0x1F6DD, 0x1F6DF},
+ {0x1F6EB, 0x1F6EC},
+ {0x1F6F4, 0x1F6FC},
+ {0x1F7E0, 0x1F7EB},
+ {0x1F7F0, 0x1F7F0},
+ {0x1F90C, 0x1F93A},
+ {0x1F93C, 0x1F945},
+ {0x1F947, 0x1F9FF},
+ {0x1FA70, 0x1FA74},
+ {0x1FA78, 0x1FA7C},
+ {0x1FA80, 0x1FA86},
+ {0x1FA90, 0x1FAAC},
+ {0x1FAB0, 0x1FABA},
+ {0x1FAC0, 0x1FAC5},
+ {0x1FAD0, 0x1FAD9},
+ {0x1FAE0, 0x1FAE7},
+ {0x1FAF0, 0x1FAF6},
+};
+
+static const struct Interval _pango2_Emoji_Modifier_table[] =
+{
+ {0x1F3FB, 0x1F3FF},
+};
+
+static const struct Interval _pango2_Emoji_Modifier_Base_table[] =
+{
+ {0x261D, 0x261D},
+ {0x26F9, 0x26F9},
+ {0x270A, 0x270D},
+ {0x1F385, 0x1F385},
+ {0x1F3C2, 0x1F3C4},
+ {0x1F3C7, 0x1F3C7},
+ {0x1F3CA, 0x1F3CC},
+ {0x1F442, 0x1F443},
+ {0x1F446, 0x1F450},
+ {0x1F466, 0x1F478},
+ {0x1F47C, 0x1F47C},
+ {0x1F481, 0x1F483},
+ {0x1F485, 0x1F487},
+ {0x1F48F, 0x1F48F},
+ {0x1F491, 0x1F491},
+ {0x1F4AA, 0x1F4AA},
+ {0x1F574, 0x1F575},
+ {0x1F57A, 0x1F57A},
+ {0x1F590, 0x1F590},
+ {0x1F595, 0x1F596},
+ {0x1F645, 0x1F647},
+ {0x1F64B, 0x1F64F},
+ {0x1F6A3, 0x1F6A3},
+ {0x1F6B4, 0x1F6B6},
+ {0x1F6C0, 0x1F6C0},
+ {0x1F6CC, 0x1F6CC},
+ {0x1F90C, 0x1F90C},
+ {0x1F90F, 0x1F90F},
+ {0x1F918, 0x1F91F},
+ {0x1F926, 0x1F926},
+ {0x1F930, 0x1F939},
+ {0x1F93C, 0x1F93E},
+ {0x1F977, 0x1F977},
+ {0x1F9B5, 0x1F9B6},
+ {0x1F9B8, 0x1F9B9},
+ {0x1F9BB, 0x1F9BB},
+ {0x1F9CD, 0x1F9CF},
+ {0x1F9D1, 0x1F9DD},
+ {0x1FAC3, 0x1FAC5},
+ {0x1FAF0, 0x1FAF6},
+};
+
+static const struct Interval _pango2_Extended_Pictographic_table[] =
+{
+ {0x00A9, 0x00A9},
+ {0x00AE, 0x00AE},
+ {0x203C, 0x203C},
+ {0x2049, 0x2049},
+ {0x2122, 0x2122},
+ {0x2139, 0x2139},
+ {0x2194, 0x2199},
+ {0x21A9, 0x21AA},
+ {0x231A, 0x231B},
+ {0x2328, 0x2328},
+ {0x2388, 0x2388},
+ {0x23CF, 0x23CF},
+ {0x23E9, 0x23F3},
+ {0x23F8, 0x23FA},
+ {0x24C2, 0x24C2},
+ {0x25AA, 0x25AB},
+ {0x25B6, 0x25B6},
+ {0x25C0, 0x25C0},
+ {0x25FB, 0x25FE},
+ {0x2600, 0x2605},
+ {0x2607, 0x2612},
+ {0x2614, 0x2685},
+ {0x2690, 0x2705},
+ {0x2708, 0x2712},
+ {0x2714, 0x2714},
+ {0x2716, 0x2716},
+ {0x271D, 0x271D},
+ {0x2721, 0x2721},
+ {0x2728, 0x2728},
+ {0x2733, 0x2734},
+ {0x2744, 0x2744},
+ {0x2747, 0x2747},
+ {0x274C, 0x274C},
+ {0x274E, 0x274E},
+ {0x2753, 0x2755},
+ {0x2757, 0x2757},
+ {0x2763, 0x2767},
+ {0x2795, 0x2797},
+ {0x27A1, 0x27A1},
+ {0x27B0, 0x27B0},
+ {0x27BF, 0x27BF},
+ {0x2934, 0x2935},
+ {0x2B05, 0x2B07},
+ {0x2B1B, 0x2B1C},
+ {0x2B50, 0x2B50},
+ {0x2B55, 0x2B55},
+ {0x3030, 0x3030},
+ {0x303D, 0x303D},
+ {0x3297, 0x3297},
+ {0x3299, 0x3299},
+ {0x1F000, 0x1F0FF},
+ {0x1F10D, 0x1F10F},
+ {0x1F12F, 0x1F12F},
+ {0x1F16C, 0x1F171},
+ {0x1F17E, 0x1F17F},
+ {0x1F18E, 0x1F18E},
+ {0x1F191, 0x1F19A},
+ {0x1F1AD, 0x1F1E5},
+ {0x1F201, 0x1F20F},
+ {0x1F21A, 0x1F21A},
+ {0x1F22F, 0x1F22F},
+ {0x1F232, 0x1F23A},
+ {0x1F23C, 0x1F23F},
+ {0x1F249, 0x1F3FA},
+ {0x1F400, 0x1F53D},
+ {0x1F546, 0x1F64F},
+ {0x1F680, 0x1F6FF},
+ {0x1F774, 0x1F77F},
+ {0x1F7D5, 0x1F7FF},
+ {0x1F80C, 0x1F80F},
+ {0x1F848, 0x1F84F},
+ {0x1F85A, 0x1F85F},
+ {0x1F888, 0x1F88F},
+ {0x1F8AE, 0x1F8FF},
+ {0x1F90C, 0x1F93A},
+ {0x1F93C, 0x1F945},
+ {0x1F947, 0x1FAFF},
+ {0x1FC00, 0x1FFFD},
+};
+
+#endif /* PANGO2_EMOJI_TABLE_H */
+
+/* == End of generated table == */
diff --git a/pango2/pango-emoji.c b/pango2/pango-emoji.c
new file mode 100644
index 00000000..aa82e9ec
--- /dev/null
+++ b/pango2/pango-emoji.c
@@ -0,0 +1,290 @@
+/* Pango2
+ * pango-emoji.c: Emoji handling
+ *
+ * Copyright (C) 2017 Google, Inc.
+ *
+ * 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.
+ *
+ * Implementation of pango2_emoji_iter is based on Chromium's Ragel-based
+ * parser:
+ *
+ * https://chromium-review.googlesource.com/c/chromium/src/+/1264577
+ *
+ * The grammar file emoji_presentation_scanner.rl was just modified to
+ * adapt the function signature and variables to our usecase. The
+ * grammar itself was NOT modified:
+ *
+ * https://chromium-review.googlesource.com/c/chromium/src/+/1264577/3/third_party/blink/renderer/platform/fonts/emoji_presentation_scanner.rl
+ *
+ * The emoji_presentation_scanner.c is generated from .rl file by
+ * running ragel on it.
+ *
+ * The categorization is also based on:
+ *
+ * https://chromium-review.googlesource.com/c/chromium/src/+/1264577/3/third_party/blink/renderer/platform/fonts/utf16_ragel_iterator.h
+ *
+ * The iterator next() is based on:
+ *
+ * https://chromium-review.googlesource.com/c/chromium/src/+/1264577/3/third_party/blink/renderer/platform/fonts/symbols_iterator.cc
+ *
+ * // Copyright 2015 The Chromium Authors. All rights reserved.
+ * // Use of this source code is governed by a BSD-style license that can be
+ * // found in the LICENSE file.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+
+#include "pango-emoji-private.h"
+#include "pango-emoji-table.h"
+
+static inline gboolean
+bsearch_interval (gunichar c,
+ const struct Interval table[],
+ guint n)
+{
+ guint lower = 0;
+ guint upper = n - 1;
+
+ while (lower <= upper)
+ {
+ int mid = (lower + upper) / 2;
+
+ if (c < table[mid].start)
+ upper = mid - 1;
+ else if (c > table[mid].end)
+ lower = mid + 1;
+ else
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+#define DEFINE_pango2_Is_(name) \
+static inline gboolean \
+_pango2_Is_##name (gunichar ch) \
+{ \
+ return ch >= _pango2_##name##_table[0].start && \
+ bsearch_interval (ch, \
+ _pango2_##name##_table, \
+ G_N_ELEMENTS (_pango2_##name##_table)); \
+}
+
+DEFINE_pango2_Is_(Emoji)
+DEFINE_pango2_Is_(Emoji_Presentation)
+DEFINE_pango2_Is_(Emoji_Modifier)
+DEFINE_pango2_Is_(Emoji_Modifier_Base)
+DEFINE_pango2_Is_(Extended_Pictographic)
+
+gboolean
+_pango2_Is_Emoji_Base_Character (gunichar ch)
+{
+ return _pango2_Is_Emoji (ch);
+}
+
+gboolean
+_pango2_Is_Emoji_Extended_Pictographic (gunichar ch)
+{
+ return _pango2_Is_Extended_Pictographic (ch);
+}
+
+static inline gboolean
+_pango2_Is_Emoji_Emoji_Default (gunichar ch)
+{
+ return _pango2_Is_Emoji_Presentation (ch);
+}
+
+static inline gboolean
+_pango2_Is_Emoji_Keycap_Base (gunichar ch)
+{
+ return (ch >= '0' && ch <= '9') || ch == '#' || ch == '*';
+}
+
+static inline gboolean
+_pango2_Is_Regional_Indicator (gunichar ch)
+{
+ return (ch >= 0x1F1E6 && ch <= 0x1F1FF);
+}
+
+
+#define kCombiningEnclosingCircleBackslashCharacter 0x20E0
+#define kCombiningEnclosingKeycapCharacter 0x20E3
+#define kVariationSelector15Character 0xFE0E
+#define kVariationSelector16Character 0xFE0F
+#define kZeroWidthJoinerCharacter 0x200D
+
+enum Pango2EmojiScannerCategory {
+ EMOJI = 0,
+ EMOJI_TEXT_PRESENTATION = 1,
+ EMOJI_EMOJI_PRESENTATION = 2,
+ EMOJI_MODIFIER_BASE = 3,
+ EMOJI_MODIFIER = 4,
+ EMOJI_VS_BASE = 5,
+ REGIONAL_INDICATOR = 6,
+ KEYCAP_BASE = 7,
+ COMBINING_ENCLOSING_KEYCAP = 8,
+ COMBINING_ENCLOSING_CIRCLE_BACKSLASH = 9,
+ ZWJ = 10,
+ VS15 = 11,
+ VS16 = 12,
+ TAG_BASE = 13,
+ TAG_SEQUENCE = 14,
+ TAG_TERM = 15,
+ kMaxEmojiScannerCategory = 16
+};
+
+static inline unsigned char
+_pango2_EmojiSegmentationCategory (gunichar codepoint)
+{
+ /* Specific ones first. */
+ if (('a' <= codepoint && codepoint <= 'z') ||
+ ('A' <= codepoint && codepoint <= 'Z') ||
+ codepoint == ' ')
+ return kMaxEmojiScannerCategory;
+
+ if ('0' <= codepoint && codepoint <= '9')
+ return KEYCAP_BASE;
+
+ switch (codepoint)
+ {
+ case kCombiningEnclosingKeycapCharacter:
+ return COMBINING_ENCLOSING_KEYCAP;
+ case kCombiningEnclosingCircleBackslashCharacter:
+ return COMBINING_ENCLOSING_CIRCLE_BACKSLASH;
+ case kZeroWidthJoinerCharacter:
+ return ZWJ;
+ case kVariationSelector15Character:
+ return VS15;
+ case kVariationSelector16Character:
+ return VS16;
+ case 0x1F3F4:
+ return TAG_BASE;
+ case 0xE007F:
+ return TAG_TERM;
+ default: ;
+ }
+
+ if ((0xE0030 <= codepoint && codepoint <= 0xE0039) ||
+ (0xE0061 <= codepoint && codepoint <= 0xE007A))
+ return TAG_SEQUENCE;
+
+ if (_pango2_Is_Emoji_Modifier_Base (codepoint))
+ return EMOJI_MODIFIER_BASE;
+ if (_pango2_Is_Emoji_Modifier (codepoint))
+ return EMOJI_MODIFIER;
+ if (_pango2_Is_Regional_Indicator (codepoint))
+ return REGIONAL_INDICATOR;
+ if (_pango2_Is_Emoji_Keycap_Base (codepoint))
+ return KEYCAP_BASE;
+ if (_pango2_Is_Emoji_Emoji_Default (codepoint))
+ return EMOJI_EMOJI_PRESENTATION;
+ if (_pango2_Is_Emoji (codepoint))
+ return EMOJI_TEXT_PRESENTATION;
+
+ /* Ragel state machine will interpret unknown category as "any". */
+ return kMaxEmojiScannerCategory;
+}
+
+
+typedef gboolean bool;
+enum { false = FALSE, true = TRUE };
+typedef unsigned char *emoji_text_iter_t;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wswitch-default"
+#include "emoji_presentation_scanner.c"
+#pragma GCC diagnostic pop
+
+
+Pango2EmojiIter *
+_pango2_emoji_iter_init (Pango2EmojiIter *iter,
+ const char *text,
+ int length)
+{
+ unsigned int n_chars = g_utf8_strlen (text, length);
+ unsigned char *types = g_malloc (n_chars);
+ unsigned int i;
+ const char *p;
+
+ p = text;
+ for (i = 0; i < n_chars; i++)
+ {
+ types[i] = _pango2_EmojiSegmentationCategory (g_utf8_get_char (p));
+ p = g_utf8_next_char (p);
+ }
+
+ iter->text_start = iter->start = iter->end = text;
+ if (length >= 0)
+ iter->text_end = text + length;
+ else
+ iter->text_end = text + strlen (text);
+ iter->is_emoji = FALSE;
+
+ iter->types = types;
+ iter->n_chars = n_chars;
+ iter->cursor = 0;
+
+ _pango2_emoji_iter_next (iter);
+
+ return iter;
+}
+
+void
+_pango2_emoji_iter_fini (Pango2EmojiIter *iter)
+{
+ g_free (iter->types);
+}
+
+gboolean
+_pango2_emoji_iter_next (Pango2EmojiIter *iter)
+{
+ unsigned int old_cursor, cursor;
+ gboolean is_emoji;
+
+ if (iter->end >= iter->text_end)
+ return FALSE;
+
+ iter->start = iter->end;
+
+ old_cursor = cursor = iter->cursor;
+ cursor = scan_emoji_presentation (iter->types + cursor,
+ iter->types + iter->n_chars,
+ &is_emoji) - iter->types;
+ do
+ {
+ iter->cursor = cursor;
+ iter->is_emoji = is_emoji;
+
+ if (cursor == iter->n_chars)
+ break;
+
+ cursor = scan_emoji_presentation (iter->types + cursor,
+ iter->types + iter->n_chars,
+ &is_emoji) - iter->types;
+ }
+ while (iter->is_emoji == is_emoji);
+
+ iter->end = g_utf8_offset_to_pointer (iter->start, iter->cursor - old_cursor);
+
+ return TRUE;
+}
+
+
+/**********************************************************
+ * End of code from Chromium
+ **********************************************************/
diff --git a/pango2/pango-enum-types.c.template b/pango2/pango-enum-types.c.template
new file mode 100644
index 00000000..b71b0a39
--- /dev/null
+++ b/pango2/pango-enum-types.c.template
@@ -0,0 +1,38 @@
+/*** BEGIN file-header ***/
+#include "config.h"
+
+#include <pango.h>
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@basename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+ static gsize g_define_type_id__volatile = 0;
+
+ if (g_once_init_enter (&g_define_type_id__volatile))
+ {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+ GType g_define_type_id =
+ g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+ g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+ }
+
+ return g_define_type_id__volatile;
+}
+
+/*** END value-tail ***/
diff --git a/pango2/pango-enum-types.h.template b/pango2/pango-enum-types.h.template
new file mode 100644
index 00000000..ff369b96
--- /dev/null
+++ b/pango2/pango-enum-types.h.template
@@ -0,0 +1,25 @@
+/*** BEGIN file-header ***/
+#pragma once
+
+#include <glib-object.h>
+
+#include <pango2/pango-version-macros.h>
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@basename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+PANGO2_AVAILABLE_IN_ALL
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+/*** END file-tail ***/
diff --git a/pango2/pango-features.h.meson b/pango2/pango-features.h.meson
new file mode 100644
index 00000000..d89f5ad8
--- /dev/null
+++ b/pango2/pango-features.h.meson
@@ -0,0 +1,9 @@
+#pragma once
+
+#mesondefine PANGO2_VERSION_MAJOR
+#mesondefine PANGO2_VERSION_MINOR
+#mesondefine PANGO2_VERSION_MICRO
+
+#define PANGO2_VERSION_STRING "@PANGO2_VERSION_MAJOR@.@PANGO2_VERSION_MINOR@.@PANGO2_VERSION_MICRO@"
+#
+#mesondefine PANGO2_RENDERING_CAIRO
diff --git a/pango2/pango-font-description-private.h b/pango2/pango-font-description-private.h
new file mode 100644
index 00000000..11b09d00
--- /dev/null
+++ b/pango2/pango-font-description-private.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-font-description.h"
+
+
+gboolean pango2_font_description_is_similar (const Pango2FontDescription *a,
+ const Pango2FontDescription *b);
+
+int pango2_font_description_compute_distance (const Pango2FontDescription *a,
+ const Pango2FontDescription *b);
+
+gboolean pango2_parse_style (const char *str,
+ Pango2Style *style,
+ gboolean warn);
+gboolean pango2_parse_variant (const char *str,
+ Pango2Variant *variant,
+ gboolean warn);
+gboolean pango2_parse_weight (const char *str,
+ Pango2Weight *weight,
+ gboolean warn);
+gboolean pango2_parse_stretch (const char *str,
+ Pango2Stretch *stretch,
+ gboolean warn);
+
diff --git a/pango2/pango-font-description.c b/pango2/pango-font-description.c
new file mode 100644
index 00000000..db22853b
--- /dev/null
+++ b/pango2/pango-font-description.c
@@ -0,0 +1,1743 @@
+/* Pango2
+ * pango-font-description.c:
+ *
+ * Copyright (C) 1999 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.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+
+#include "pango-font-description-private.h"
+
+struct _Pango2FontDescription
+{
+ char *family_name;
+
+ Pango2Style style;
+ Pango2Variant variant;
+ Pango2Weight weight;
+ Pango2Stretch stretch;
+ Pango2Gravity gravity;
+
+ int size;
+
+ char *variations;
+ char *faceid;
+
+ guint16 mask;
+ guint static_family : 1;
+ guint static_variations : 1;
+ guint static_faceid : 1;
+ guint size_is_absolute : 1;
+};
+
+G_DEFINE_BOXED_TYPE (Pango2FontDescription, pango2_font_description,
+ pango2_font_description_copy,
+ pango2_font_description_free);
+
+static const Pango2FontDescription pfd_defaults = {
+ NULL, /* family_name */
+
+ PANGO2_STYLE_NORMAL, /* style */
+ PANGO2_VARIANT_NORMAL, /* variant */
+ PANGO2_WEIGHT_NORMAL, /* weight */
+ PANGO2_STRETCH_NORMAL, /* stretch */
+ PANGO2_GRAVITY_SOUTH, /* gravity */
+ 0, /* size */
+ NULL, /* variations */
+ NULL, /* faceid */
+
+ 0, /* mask */
+ 0, /* static_family */
+ 0, /* static_variations */
+ 0, /* static_faceid */
+ 0, /* size_is_absolute */
+};
+
+/**
+ * pango2_font_description_new:
+ *
+ * Creates a new font description structure with all fields unset.
+ *
+ * Return value: the newly allocated `Pango2FontDescription`, which
+ * should be freed using [method@Pango2.FontDescription.free]
+ */
+Pango2FontDescription *
+pango2_font_description_new (void)
+{
+ Pango2FontDescription *desc = g_slice_new (Pango2FontDescription);
+
+ *desc = pfd_defaults;
+
+ return desc;
+}
+
+/**
+ * pango2_font_description_set_family:
+ * @desc: a `Pango2FontDescription`.
+ * @family: a string representing the family name.
+ *
+ * Sets the family name field of a font description.
+ *
+ * The family
+ * name represents a family of related font styles, and will
+ * resolve to a particular `Pango2FontFamily`. In some uses of
+ * `Pango2FontDescription`, it is also possible to use a comma
+ * separated list of family names for this field.
+ */
+void
+pango2_font_description_set_family (Pango2FontDescription *desc,
+ const char *family)
+{
+ g_return_if_fail (desc != NULL);
+
+ pango2_font_description_set_family_static (desc, family ? g_strdup (family) : NULL);
+ if (family)
+ desc->static_family = FALSE;
+}
+
+/**
+ * pango2_font_description_set_family_static:
+ * @desc: a `Pango2FontDescription`
+ * @family: a string representing the family name
+ *
+ * Sets the family name field of a font description, without copying the string.
+ *
+ * This is like [method@Pango2.FontDescription.set_family], except that no
+ * copy of @family is made. The caller must make sure that the
+ * string passed in stays around until @desc has been freed or the
+ * name is set again. This function can be used if @family is a static
+ * string such as a C string literal, or if @desc is only needed temporarily.
+ */
+void
+pango2_font_description_set_family_static (Pango2FontDescription *desc,
+ const char *family)
+{
+ g_return_if_fail (desc != NULL);
+
+ if (desc->family_name == family)
+ return;
+
+ if (desc->family_name && !desc->static_family)
+ g_free (desc->family_name);
+
+ if (family)
+ {
+ desc->family_name = (char *)family;
+ desc->static_family = TRUE;
+ desc->mask |= PANGO2_FONT_MASK_FAMILY;
+ }
+ else
+ {
+ desc->family_name = pfd_defaults.family_name;
+ desc->static_family = pfd_defaults.static_family;
+ desc->mask &= ~PANGO2_FONT_MASK_FAMILY;
+ }
+}
+
+/**
+ * pango2_font_description_get_family:
+ * @desc: a `Pango2FontDescription`.
+ *
+ * Gets the family name field of a font description.
+ *
+ * See [method@Pango2.FontDescription.set_family].
+ *
+ * Return value: (nullable): the family name field for the font
+ * description, or %NULL if not previously set. This has the same
+ * life-time as the font description itself and should not be freed.
+ */
+const char *
+pango2_font_description_get_family (const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (desc != NULL, NULL);
+
+ return desc->family_name;
+}
+
+/**
+ * pango2_font_description_set_style:
+ * @desc: a `Pango2FontDescription`
+ * @style: the style for the font description
+ *
+ * Sets the style field of a `Pango2FontDescription`.
+ *
+ * The [enum@Pango2.Style] enumeration describes whether the font is
+ * slanted and the manner in which it is slanted; it can be either
+ * %PANGO2_STYLE_NORMAL, %PANGO2_STYLE_ITALIC, or %PANGO2_STYLE_OBLIQUE.
+ *
+ * Most fonts will either have a italic style or an oblique style,
+ * but not both, and font matching in Pango2 will match italic
+ * specifications with oblique fonts and vice-versa if an exact
+ * match is not found.
+ */
+void
+pango2_font_description_set_style (Pango2FontDescription *desc,
+ Pango2Style style)
+{
+ g_return_if_fail (desc != NULL);
+
+ desc->style = style;
+ desc->mask |= PANGO2_FONT_MASK_STYLE;
+}
+
+/**
+ * pango2_font_description_get_style:
+ * @desc: a `Pango2FontDescription`
+ *
+ * Gets the style field of a `Pango2FontDescription`.
+ *
+ * See [method@Pango2.FontDescription.set_style].
+ *
+ * Return value: the style field for the font description.
+ * Use [method@Pango2.FontDescription.get_set_fields] to
+ * find out if the field was explicitly set or not.
+ */
+Pango2Style
+pango2_font_description_get_style (const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (desc != NULL, pfd_defaults.style);
+
+ return desc->style;
+}
+
+/**
+ * pango2_font_description_set_variant:
+ * @desc: a `Pango2FontDescription`
+ * @variant: the variant type for the font description.
+ *
+ * Sets the variant field of a font description.
+ *
+ * The [enum@Pango2.Variant] can either be %PANGO2_VARIANT_NORMAL
+ * or %PANGO2_VARIANT_SMALL_CAPS.
+ */
+void
+pango2_font_description_set_variant (Pango2FontDescription *desc,
+ Pango2Variant variant)
+{
+ g_return_if_fail (desc != NULL);
+
+ desc->variant = variant;
+ desc->mask |= PANGO2_FONT_MASK_VARIANT;
+}
+
+/**
+ * pango2_font_description_get_variant:
+ * @desc: a `Pango2FontDescription`.
+ *
+ * Gets the variant field of a `Pango2FontDescription`.
+ *
+ * See [method@Pango2.FontDescription.set_variant].
+ *
+ * Return value: the variant field for the font description.
+ * Use [method@Pango2.FontDescription.get_set_fields] to find
+ * out if the field was explicitly set or not.
+ */
+Pango2Variant
+pango2_font_description_get_variant (const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (desc != NULL, pfd_defaults.variant);
+
+ return desc->variant;
+}
+
+/**
+ * pango2_font_description_set_weight:
+ * @desc: a `Pango2FontDescription`
+ * @weight: the weight for the font description.
+ *
+ * Sets the weight field of a font description.
+ *
+ * The weight field
+ * specifies how bold or light the font should be. In addition
+ * to the values of the [enum@Pango2.Weight] enumeration, other
+ * intermediate numeric values are possible.
+ */
+void
+pango2_font_description_set_weight (Pango2FontDescription *desc,
+ Pango2Weight weight)
+{
+ g_return_if_fail (desc != NULL);
+
+ desc->weight = weight;
+ desc->mask |= PANGO2_FONT_MASK_WEIGHT;
+}
+
+/**
+ * pango2_font_description_get_weight:
+ * @desc: a `Pango2FontDescription`
+ *
+ * Gets the weight field of a font description.
+ *
+ * See [method@Pango2.FontDescription.set_weight].
+ *
+ * Return value: the weight field for the font description.
+ * Use [method@Pango2.FontDescription.get_set_fields] to find
+ * out if the field was explicitly set or not.
+ */
+Pango2Weight
+pango2_font_description_get_weight (const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (desc != NULL, pfd_defaults.weight);
+
+ return desc->weight;
+}
+
+/**
+ * pango2_font_description_set_stretch:
+ * @desc: a `Pango2FontDescription`
+ * @stretch: the stretch for the font description
+ *
+ * Sets the stretch field of a font description.
+ *
+ * The [enum@Pango2.Stretch] field specifies how narrow or
+ * wide the font should be.
+ */
+void
+pango2_font_description_set_stretch (Pango2FontDescription *desc,
+ Pango2Stretch stretch)
+{
+ g_return_if_fail (desc != NULL);
+
+ desc->stretch = stretch;
+ desc->mask |= PANGO2_FONT_MASK_STRETCH;
+}
+
+/**
+ * pango2_font_description_get_stretch:
+ * @desc: a `Pango2FontDescription`.
+ *
+ * Gets the stretch field of a font description.
+ *
+ * See [method@Pango2.FontDescription.set_stretch].
+ *
+ * Return value: the stretch field for the font description.
+ * Use [method@Pango2.FontDescription.get_set_fields] to find
+ * out if the field was explicitly set or not.
+ */
+Pango2Stretch
+pango2_font_description_get_stretch (const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (desc != NULL, pfd_defaults.stretch);
+
+ return desc->stretch;
+}
+
+/**
+ * pango2_font_description_set_size:
+ * @desc: a `Pango2FontDescription`
+ * @size: the size of the font in points, scaled by %PANGO2_SCALE.
+ * (That is, a @size value of 10 * PANGO2_SCALE is a 10 point font.
+ * The conversion factor between points and device units depends on
+ * system configuration and the output device. For screen display, a
+ * logical DPI of 96 is common, in which case a 10 point font corresponds
+ * to a 10 * (96 / 72) = 13.3 pixel font.
+ * Use [method@Pango2.FontDescription.set_absolute_size] if you need
+ * a particular size in device units.
+ *
+ * Sets the size field of a font description in fractional points.
+ *
+ * This is mutually exclusive with
+ * [method@Pango2.FontDescription.set_absolute_size].
+ */
+void
+pango2_font_description_set_size (Pango2FontDescription *desc,
+ int size)
+{
+ g_return_if_fail (desc != NULL);
+ g_return_if_fail (size >= 0);
+
+ desc->size = size;
+ desc->size_is_absolute = FALSE;
+ desc->mask |= PANGO2_FONT_MASK_SIZE;
+}
+
+/**
+ * pango2_font_description_get_size:
+ * @desc: a `Pango2FontDescription`
+ *
+ * Gets the size field of a font description.
+ *
+ * See [method@Pango2.FontDescription.set_size].
+ *
+ * Return value: the size field for the font description in points
+ * or device units. You must call
+ * [method@Pango2.FontDescription.get_size_is_absolute] to find out
+ * which is the case. Returns 0 if the size field has not previously
+ * been set or it has been set to 0 explicitly.
+ * Use [method@Pango2.FontDescription.get_set_fields] to find out
+ * if the field was explicitly set or not.
+ */
+int
+pango2_font_description_get_size (const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (desc != NULL, pfd_defaults.size);
+
+ return desc->size;
+}
+
+/**
+ * pango2_font_description_set_absolute_size:
+ * @desc: a `Pango2FontDescription`
+ * @size: the new size, in Pango2 units. There are %PANGO2_SCALE Pango2 units
+ * in one device unit. For an output backend where a device unit is a pixel,
+ * a @size value of 10 * PANGO2_SCALE gives a 10 pixel font.
+ *
+ * Sets the size field of a font description, in device units.
+ *
+ * This is mutually exclusive with [method@Pango2.FontDescription.set_size]
+ * which sets the font size in points.
+ */
+void
+pango2_font_description_set_absolute_size (Pango2FontDescription *desc,
+ double size)
+{
+ g_return_if_fail (desc != NULL);
+ g_return_if_fail (size >= 0);
+
+ desc->size = size;
+ desc->size_is_absolute = TRUE;
+ desc->mask |= PANGO2_FONT_MASK_SIZE;
+}
+
+/**
+ * pango2_font_description_get_size_is_absolute:
+ * @desc: a `Pango2FontDescription`
+ *
+ * Determines whether the size of the font is in points (not absolute)
+ * or device units (absolute).
+ *
+ * See [method@Pango2.FontDescription.set_size]
+ * and [method@Pango2.FontDescription.set_absolute_size].
+ *
+ * Return value: whether the size for the font description is in
+ * points or device units. Use [method@Pango2.FontDescription.get_set_fields]
+ * to find out if the size field of the font description was explicitly
+ * set or not.
+ */
+gboolean
+pango2_font_description_get_size_is_absolute (const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (desc != NULL, pfd_defaults.size_is_absolute);
+
+ return desc->size_is_absolute;
+}
+
+/**
+ * pango2_font_description_set_gravity:
+ * @desc: a `Pango2FontDescription`
+ * @gravity: the gravity for the font description.
+ *
+ * Sets the gravity field of a font description.
+ *
+ * The gravity field
+ * specifies how the glyphs should be rotated. If @gravity is
+ * %PANGO2_GRAVITY_AUTO, this actually unsets the gravity mask on
+ * the font description.
+ *
+ * This function is seldom useful to the user. Gravity should normally
+ * be set on a `Pango2Context`.
+ */
+void
+pango2_font_description_set_gravity (Pango2FontDescription *desc,
+ Pango2Gravity gravity)
+{
+ g_return_if_fail (desc != NULL);
+
+ if (gravity == PANGO2_GRAVITY_AUTO)
+ {
+ pango2_font_description_unset_fields (desc, PANGO2_FONT_MASK_GRAVITY);
+ return;
+ }
+
+ desc->gravity = gravity;
+ desc->mask |= PANGO2_FONT_MASK_GRAVITY;
+}
+
+/**
+ * pango2_font_description_get_gravity:
+ * @desc: a `Pango2FontDescription`
+ *
+ * Gets the gravity field of a font description.
+ *
+ * See [method@Pango2.FontDescription.set_gravity].
+ *
+ * Return value: the gravity field for the font description.
+ * Use [method@Pango2.FontDescription.get_set_fields] to find out
+ * if the field was explicitly set or not.
+ */
+Pango2Gravity
+pango2_font_description_get_gravity (const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (desc != NULL, pfd_defaults.gravity);
+
+ return desc->gravity;
+}
+
+/**
+ * pango2_font_description_set_variations_static:
+ * @desc: a `Pango2FontDescription`
+ * @variations: a string representing the variations
+ *
+ * Sets the variations field of a font description.
+ *
+ * This is like [method@Pango2.FontDescription.set_variations], except
+ * that no copy of @variations is made. The caller must make sure that
+ * the string passed in stays around until @desc has been freed
+ * or the name is set again. This function can be used if
+ * @variations is a static string such as a C string literal,
+ * or if @desc is only needed temporarily.
+ */
+void
+pango2_font_description_set_variations_static (Pango2FontDescription *desc,
+ const char *variations)
+{
+ g_return_if_fail (desc != NULL);
+
+ if (desc->variations == variations)
+ return;
+
+ if (desc->variations && !desc->static_variations)
+ g_free (desc->variations);
+
+ if (variations)
+ {
+ desc->variations = (char *)variations;
+ desc->static_variations = TRUE;
+ desc->mask |= PANGO2_FONT_MASK_VARIATIONS;
+ }
+ else
+ {
+ desc->variations = pfd_defaults.variations;
+ desc->static_variations = pfd_defaults.static_variations;
+ desc->mask &= ~PANGO2_FONT_MASK_VARIATIONS;
+ }
+}
+
+/**
+ * pango2_font_description_set_variations:
+ * @desc: a `Pango2FontDescription`.
+ * @variations: (nullable): a string representing the variations
+ *
+ * Sets the variations field of a font description.
+ *
+ * OpenType font variations allow to select a font instance by
+ * specifying values for a number of axes, such as width or weight.
+ *
+ * The format of the variations string is
+ *
+ * AXIS1=VALUE,AXIS2=VALUE...
+ *
+ * with each AXIS a 4 character tag that identifies a font axis,
+ * and each VALUE a floating point number. Unknown axes are ignored,
+ * and values are clamped to their allowed range.
+ *
+ * Pango2 does not currently have a way to find supported axes of
+ * a font. Both harfbuzz and freetype have API for this. See
+ * for example [hb_ot_var_get_axis_infos](https://harfbuzz.github.io/harfbuzz-hb-ot-var.html#hb-ot-var-get-axis-infos).
+ */
+void
+pango2_font_description_set_variations (Pango2FontDescription *desc,
+ const char *variations)
+{
+ g_return_if_fail (desc != NULL);
+
+ pango2_font_description_set_variations_static (desc, g_strdup (variations));
+ if (variations)
+ desc->static_variations = FALSE;
+}
+
+/**
+ * pango2_font_description_get_variations:
+ * @desc: a `Pango2FontDescription`
+ *
+ * Gets the variations field of a font description.
+ *
+ * See [method@Pango2.FontDescription.set_variations].
+ *
+ * Return value: (nullable): the variations field for the font
+ * description, or %NULL if not previously set. This has the same
+ * life-time as the font description itself and should not be freed.
+ */
+const char *
+pango2_font_description_get_variations (const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (desc != NULL, NULL);
+
+ return desc->variations;
+}
+
+/**
+ * pango2_font_description_get_set_fields:
+ * @desc: a `Pango2FontDescription`
+ *
+ * Determines which fields in a font description have been set.
+ *
+ * Return value: a bitmask with bits set corresponding to the
+ * fields in @desc that have been set.
+ */
+Pango2FontMask
+pango2_font_description_get_set_fields (const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (desc != NULL, pfd_defaults.mask);
+
+ return desc->mask;
+}
+
+/**
+ * pango2_font_description_unset_fields:
+ * @desc: a `Pango2FontDescription`
+ * @to_unset: bitmask of fields in the @desc to unset.
+ *
+ * Unsets some of the fields in a `Pango2FontDescription`.
+ *
+ * The unset fields will get back to their default values.
+ */
+void
+pango2_font_description_unset_fields (Pango2FontDescription *desc,
+ Pango2FontMask to_unset)
+{
+ Pango2FontDescription unset_desc;
+
+ g_return_if_fail (desc != NULL);
+
+ unset_desc = pfd_defaults;
+ unset_desc.mask = to_unset;
+
+ pango2_font_description_merge_static (desc, &unset_desc, TRUE);
+
+ desc->mask &= ~to_unset;
+}
+
+/**
+ * pango2_font_description_merge:
+ * @desc: a `Pango2FontDescription`
+ * @desc_to_merge: (nullable): the `Pango2FontDescription` to merge from,
+ * or %NULL
+ * @replace_existing: if %TRUE, replace fields in @desc with the
+ * corresponding values from @desc_to_merge, even if they
+ * are already exist.
+ *
+ * Merges the fields that are set in @desc_to_merge into the fields in
+ * @desc.
+ *
+ * If @replace_existing is %FALSE, only fields in @desc that
+ * are not already set are affected. If %TRUE, then fields that are
+ * already set will be replaced as well.
+ *
+ * If @desc_to_merge is %NULL, this function performs nothing.
+ */
+void
+pango2_font_description_merge (Pango2FontDescription *desc,
+ const Pango2FontDescription *desc_to_merge,
+ gboolean replace_existing)
+{
+ gboolean family_merged;
+ gboolean variations_merged;
+ gboolean faceid_merged;
+
+ g_return_if_fail (desc != NULL);
+
+ if (desc_to_merge == NULL)
+ return;
+
+ family_merged = desc_to_merge->family_name && (replace_existing || !desc->family_name);
+ variations_merged = desc_to_merge->variations && (replace_existing || !desc->variations);
+ faceid_merged = desc_to_merge->faceid && (replace_existing || !desc->faceid);
+
+ pango2_font_description_merge_static (desc, desc_to_merge, replace_existing);
+
+ if (family_merged)
+ {
+ desc->family_name = g_strdup (desc->family_name);
+ desc->static_family = FALSE;
+ }
+
+ if (variations_merged)
+ {
+ desc->variations = g_strdup (desc->variations);
+ desc->static_variations = FALSE;
+ }
+
+ if (faceid_merged)
+ {
+ desc->faceid = g_strdup (desc->faceid);
+ desc->static_faceid = FALSE;
+ }
+}
+
+/**
+ * pango2_font_description_merge_static:
+ * @desc: a `Pango2FontDescription`
+ * @desc_to_merge: the `Pango2FontDescription` to merge from
+ * @replace_existing: if %TRUE, replace fields in @desc with the
+ * corresponding values from @desc_to_merge, even if they
+ * are already exist.
+ *
+ * Merges the fields that are set in @desc_to_merge into the fields in
+ * @desc, without copying allocated fields.
+ *
+ * This is like [method@Pango2.FontDescription.merge], but only a shallow copy
+ * is made of the family name and other allocated fields. @desc can only
+ * be used until @desc_to_merge is modified or freed. This is meant to
+ * be used when the merged font description is only needed temporarily.
+ */
+void
+pango2_font_description_merge_static (Pango2FontDescription *desc,
+ const Pango2FontDescription *desc_to_merge,
+ gboolean replace_existing)
+{
+ Pango2FontMask new_mask;
+
+ g_return_if_fail (desc != NULL);
+ g_return_if_fail (desc_to_merge != NULL);
+
+ if (replace_existing)
+ new_mask = desc_to_merge->mask;
+ else
+ new_mask = desc_to_merge->mask & ~desc->mask;
+
+ if (new_mask & PANGO2_FONT_MASK_FAMILY)
+ pango2_font_description_set_family_static (desc, desc_to_merge->family_name);
+ if (new_mask & PANGO2_FONT_MASK_STYLE)
+ desc->style = desc_to_merge->style;
+ if (new_mask & PANGO2_FONT_MASK_VARIANT)
+ desc->variant = desc_to_merge->variant;
+ if (new_mask & PANGO2_FONT_MASK_WEIGHT)
+ desc->weight = desc_to_merge->weight;
+ if (new_mask & PANGO2_FONT_MASK_STRETCH)
+ desc->stretch = desc_to_merge->stretch;
+ if (new_mask & PANGO2_FONT_MASK_SIZE)
+ {
+ desc->size = desc_to_merge->size;
+ desc->size_is_absolute = desc_to_merge->size_is_absolute;
+ }
+ if (new_mask & PANGO2_FONT_MASK_GRAVITY)
+ desc->gravity = desc_to_merge->gravity;
+ if (new_mask & PANGO2_FONT_MASK_VARIATIONS)
+ pango2_font_description_set_variations_static (desc, desc_to_merge->variations);
+ if (new_mask & PANGO2_FONT_MASK_FACEID)
+ pango2_font_description_set_faceid_static (desc, desc_to_merge->faceid);
+
+ desc->mask |= new_mask;
+}
+
+gboolean
+pango2_font_description_is_similar (const Pango2FontDescription *a,
+ const Pango2FontDescription *b)
+{
+ return a->variant == b->variant &&
+ a->gravity == b->gravity;
+}
+
+int
+pango2_font_description_compute_distance (const Pango2FontDescription *a,
+ const Pango2FontDescription *b)
+{
+ if (a->style == b->style)
+ {
+ return abs ((int)(a->weight) - (int)(b->weight)) +
+ abs ((int)(a->stretch) - (int)(b->stretch));
+ }
+ else if (a->style != PANGO2_STYLE_NORMAL &&
+ b->style != PANGO2_STYLE_NORMAL)
+ {
+ /* Equate oblique and italic, but with a big penalty
+ */
+ return 1000000 + abs ((int)(a->weight) - (int)(b->weight))
+ + abs ((int)(a->stretch) - (int)(b->stretch));
+ }
+ else
+ return G_MAXINT;
+}
+
+/**
+ * pango2_font_description_copy:
+ * @desc: (nullable): a `Pango2FontDescription`, may be %NULL
+ *
+ * Make a copy of a `Pango2FontDescription`.
+ *
+ * Return value: (nullable): the newly allocated `Pango2FontDescription`,
+ * which should be freed with [method@Pango2.FontDescription.free]
+ */
+Pango2FontDescription *
+pango2_font_description_copy (const Pango2FontDescription *desc)
+{
+ Pango2FontDescription *result;
+
+ if (desc == NULL)
+ return NULL;
+
+ result = g_slice_new (Pango2FontDescription);
+
+ *result = *desc;
+
+ if (result->family_name)
+ {
+ result->family_name = g_strdup (result->family_name);
+ result->static_family = FALSE;
+ }
+
+ result->variations = g_strdup (result->variations);
+ result->static_variations = FALSE;
+
+ result->faceid = g_strdup (result->faceid);
+ result->static_faceid = FALSE;
+
+ return result;
+}
+
+/**
+ * pango2_font_description_copy_static:
+ * @desc: (nullable): a `Pango2FontDescription`, may be %NULL
+ *
+ * Make a copy of a `Pango2FontDescription`, but don't duplicate
+ * allocated fields.
+ *
+ * This is like [method@Pango2.FontDescription.copy], but only a shallow
+ * copy is made of the family name and other allocated fields. The result
+ * can only be used until @desc is modified or freed. This is meant
+ * to be used when the copy is only needed temporarily.
+ *
+ * Return value: (nullable): the newly allocated `Pango2FontDescription`,
+ * which should be freed with [method@Pango2.FontDescription.free]
+ */
+Pango2FontDescription *
+pango2_font_description_copy_static (const Pango2FontDescription *desc)
+{
+ Pango2FontDescription *result;
+
+ if (desc == NULL)
+ return NULL;
+
+ result = g_slice_new (Pango2FontDescription);
+
+ *result = *desc;
+ if (result->family_name)
+ result->static_family = TRUE;
+
+ if (result->variations)
+ result->static_variations = TRUE;
+
+ if (result->faceid)
+ result->static_faceid = TRUE;
+
+ return result;
+}
+
+/**
+ * pango2_font_description_equal:
+ * @desc1: a `Pango2FontDescription`
+ * @desc2: another `Pango2FontDescription`
+ *
+ * Compares two font descriptions for equality.
+ *
+ * Two font descriptions are considered equal if the fonts they describe
+ * are provably identical. This means that their masks do not have to match,
+ * as long as other fields are all the same. (Two font descriptions may
+ * result in identical fonts being loaded, but still compare %FALSE.)
+ *
+ * Return value: %TRUE if the two font descriptions are identical,
+ * %FALSE otherwise.
+ */
+gboolean
+pango2_font_description_equal (const Pango2FontDescription *desc1,
+ const Pango2FontDescription *desc2)
+{
+ g_return_val_if_fail (desc1 != NULL, FALSE);
+ g_return_val_if_fail (desc2 != NULL, FALSE);
+
+ return desc1->style == desc2->style &&
+ desc1->variant == desc2->variant &&
+ desc1->weight == desc2->weight &&
+ desc1->stretch == desc2->stretch &&
+ desc1->size == desc2->size &&
+ desc1->size_is_absolute == desc2->size_is_absolute &&
+ desc1->gravity == desc2->gravity &&
+ (desc1->family_name == desc2->family_name ||
+ (desc1->family_name && desc2->family_name && g_ascii_strcasecmp (desc1->family_name, desc2->family_name) == 0)) &&
+ (g_strcmp0 (desc1->variations, desc2->variations) == 0) &&
+ (g_strcmp0 (desc1->faceid, desc2->faceid) == 0);
+}
+
+#define TOLOWER(c) \
+ (((c) >= 'A' && (c) <= 'Z') ? (c) - 'A' + 'a' : (c))
+
+static guint
+case_insensitive_hash (const char *key)
+{
+ const char *p = key;
+ guint h = TOLOWER (*p);
+
+ if (h)
+ {
+ for (p += 1; *p != '\0'; p++)
+ h = (h << 5) - h + TOLOWER (*p);
+ }
+
+ return h;
+}
+
+/**
+ * pango2_font_description_hash:
+ * @desc: a `Pango2FontDescription`
+ *
+ * Computes a hash of a `Pango2FontDescription` structure.
+ *
+ * This is suitable to be used, for example, as an argument
+ * to [GLib.HashTable.new]. The hash value is independent of @desc->mask.
+ *
+ * Return value: the hash value.
+ */
+guint
+pango2_font_description_hash (const Pango2FontDescription *desc)
+{
+ guint hash = 0;
+
+ g_return_val_if_fail (desc != NULL, 0);
+
+ if (desc->family_name)
+ hash = case_insensitive_hash (desc->family_name);
+ if (desc->variations)
+ hash ^= g_str_hash (desc->variations);
+ if (desc->faceid)
+ hash ^= g_str_hash (desc->faceid);
+ hash ^= desc->size;
+ hash ^= desc->size_is_absolute ? 0xc33ca55a : 0;
+ hash ^= desc->style << 16;
+ hash ^= desc->variant << 18;
+ hash ^= desc->weight << 16;
+ hash ^= desc->stretch << 26;
+ hash ^= desc->gravity << 28;
+
+ return hash;
+}
+
+/**
+ * pango2_font_description_free:
+ * @desc: (nullable): a `Pango2FontDescription`, may be %NULL
+ *
+ * Frees a font description.
+ */
+void
+pango2_font_description_free (Pango2FontDescription *desc)
+{
+ if (desc == NULL)
+ return;
+
+ if (desc->family_name && !desc->static_family)
+ g_free (desc->family_name);
+
+ if (desc->variations && !desc->static_variations)
+ g_free (desc->variations);
+
+ if (desc->faceid && !desc->static_faceid)
+ g_free (desc->faceid);
+
+ g_slice_free (Pango2FontDescription, desc);
+}
+
+typedef struct
+{
+ int value;
+ const char str[16];
+} FieldMap;
+
+static const FieldMap style_map[] = {
+ { PANGO2_STYLE_NORMAL, "" },
+ { PANGO2_STYLE_NORMAL, "Roman" },
+ { PANGO2_STYLE_OBLIQUE, "Oblique" },
+ { PANGO2_STYLE_ITALIC, "Italic" }
+};
+
+static const FieldMap variant_map[] = {
+ { PANGO2_VARIANT_NORMAL, "" },
+ { PANGO2_VARIANT_SMALL_CAPS, "Small-Caps" },
+ { PANGO2_VARIANT_ALL_SMALL_CAPS, "All-Small-Caps" },
+ { PANGO2_VARIANT_PETITE_CAPS, "Petite-Caps" },
+ { PANGO2_VARIANT_ALL_PETITE_CAPS, "All-Petite-Caps" },
+ { PANGO2_VARIANT_UNICASE, "Unicase" },
+ { PANGO2_VARIANT_TITLE_CAPS, "Title-Caps" }
+};
+
+static const FieldMap weight_map[] = {
+ { PANGO2_WEIGHT_THIN, "Thin" },
+ { PANGO2_WEIGHT_ULTRALIGHT, "Ultra-Light" },
+ { PANGO2_WEIGHT_ULTRALIGHT, "Extra-Light" },
+ { PANGO2_WEIGHT_LIGHT, "Light" },
+ { PANGO2_WEIGHT_SEMILIGHT, "Semi-Light" },
+ { PANGO2_WEIGHT_SEMILIGHT, "Demi-Light" },
+ { PANGO2_WEIGHT_BOOK, "Book" },
+ { PANGO2_WEIGHT_NORMAL, "" },
+ { PANGO2_WEIGHT_NORMAL, "Regular" },
+ { PANGO2_WEIGHT_MEDIUM, "Medium" },
+ { PANGO2_WEIGHT_SEMIBOLD, "Semi-Bold" },
+ { PANGO2_WEIGHT_SEMIBOLD, "Demi-Bold" },
+ { PANGO2_WEIGHT_BOLD, "Bold" },
+ { PANGO2_WEIGHT_ULTRABOLD, "Ultra-Bold" },
+ { PANGO2_WEIGHT_ULTRABOLD, "Extra-Bold" },
+ { PANGO2_WEIGHT_HEAVY, "Heavy" },
+ { PANGO2_WEIGHT_HEAVY, "Black" },
+ { PANGO2_WEIGHT_ULTRAHEAVY, "Ultra-Heavy" },
+ { PANGO2_WEIGHT_ULTRAHEAVY, "Extra-Heavy" },
+ { PANGO2_WEIGHT_ULTRAHEAVY, "Ultra-Black" },
+ { PANGO2_WEIGHT_ULTRAHEAVY, "Extra-Black" }
+};
+
+static const FieldMap stretch_map[] = {
+ { PANGO2_STRETCH_ULTRA_CONDENSED, "Ultra-Condensed" },
+ { PANGO2_STRETCH_EXTRA_CONDENSED, "Extra-Condensed" },
+ { PANGO2_STRETCH_CONDENSED, "Condensed" },
+ { PANGO2_STRETCH_SEMI_CONDENSED, "Semi-Condensed" },
+ { PANGO2_STRETCH_NORMAL, "" },
+ { PANGO2_STRETCH_SEMI_EXPANDED, "Semi-Expanded" },
+ { PANGO2_STRETCH_EXPANDED, "Expanded" },
+ { PANGO2_STRETCH_EXTRA_EXPANDED, "Extra-Expanded" },
+ { PANGO2_STRETCH_ULTRA_EXPANDED, "Ultra-Expanded" }
+};
+
+static const FieldMap gravity_map[] = {
+ { PANGO2_GRAVITY_SOUTH, "Not-Rotated" },
+ { PANGO2_GRAVITY_SOUTH, "South" },
+ { PANGO2_GRAVITY_NORTH, "Upside-Down" },
+ { PANGO2_GRAVITY_NORTH, "North" },
+ { PANGO2_GRAVITY_EAST, "Rotated-Left" },
+ { PANGO2_GRAVITY_EAST, "East" },
+ { PANGO2_GRAVITY_WEST, "Rotated-Right" },
+ { PANGO2_GRAVITY_WEST, "West" }
+};
+
+static gboolean
+field_matches (const char *s1,
+ const char *s2,
+ gsize n)
+{
+ int c1, c2;
+
+ g_return_val_if_fail (s1 != NULL, 0);
+ g_return_val_if_fail (s2 != NULL, 0);
+
+ while (n && *s1 && *s2)
+ {
+ c1 = (int)(guchar) TOLOWER (*s1);
+ c2 = (int)(guchar) TOLOWER (*s2);
+ if (c1 != c2) {
+ if (c1 == '-') {
+ s1++;
+ continue;
+ }
+ return FALSE;
+ }
+ s1++; s2++;
+ n--;
+ }
+
+ return n == 0 && *s1 == '\0';
+}
+
+static gboolean
+parse_int (const char *word,
+ size_t wordlen,
+ int *out)
+{
+ char *end;
+ long val = strtol (word, &end, 10);
+ int i = val;
+
+ if (end != word && (end == word + wordlen) && val >= 0 && val == i)
+ {
+ if (out)
+ *out = i;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+find_field (const char *what,
+ const FieldMap *map,
+ int n_elements,
+ const char *str,
+ int len,
+ int *val)
+{
+ int i;
+ gboolean had_prefix = FALSE;
+
+ if (what)
+ {
+ i = strlen (what);
+ if (len > i && 0 == strncmp (what, str, i) && str[i] == '=')
+ {
+ str += i + 1;
+ len -= i + 1;
+ had_prefix = TRUE;
+ }
+ }
+
+ for (i=0; i<n_elements; i++)
+ {
+ if (map[i].str[0] && field_matches (map[i].str, str, len))
+ {
+ if (val)
+ *val = map[i].value;
+ return TRUE;
+ }
+ }
+
+ if (!what || had_prefix)
+ return parse_int (str, len, val);
+
+ return FALSE;
+}
+
+static gboolean
+find_field_any (const char *str,
+ int len,
+ Pango2FontDescription *desc)
+{
+ if (field_matches ("Normal", str, len))
+ return TRUE;
+
+#define FIELD(NAME, MASK) \
+ G_STMT_START { \
+ if (find_field (G_STRINGIFY (NAME), NAME##_map, G_N_ELEMENTS (NAME##_map), str, len, \
+ desc ? (int *)(void *)&desc->NAME : NULL)) \
+ { \
+ if (desc) \
+ desc->mask |= MASK; \
+ return TRUE; \
+ } \
+ } G_STMT_END
+
+ FIELD (weight, PANGO2_FONT_MASK_WEIGHT);
+ FIELD (style, PANGO2_FONT_MASK_STYLE);
+ FIELD (stretch, PANGO2_FONT_MASK_STRETCH);
+ FIELD (variant, PANGO2_FONT_MASK_VARIANT);
+ FIELD (gravity, PANGO2_FONT_MASK_GRAVITY);
+
+#undef FIELD
+
+ return FALSE;
+}
+
+static const char *
+getword (const char *str,
+ const char *last,
+ size_t *wordlen,
+ const char *stop)
+{
+ const char *result;
+
+ while (last > str && g_ascii_isspace (*(last - 1)))
+ last--;
+
+ result = last;
+ while (result > str && !g_ascii_isspace (*(result - 1)) && !strchr (stop, *(result - 1)))
+ result--;
+
+ *wordlen = last - result;
+
+ return result;
+}
+
+static gboolean
+parse_size (const char *word,
+ size_t wordlen,
+ int *pango2_size,
+ gboolean *size_is_absolute)
+{
+ char *end;
+ double size = g_ascii_strtod (word, &end);
+
+ if (end != word &&
+ (end == word + wordlen ||
+ (end + 2 == word + wordlen && !strncmp (end, "px", 2))
+ ) && size >= 0 && size <= 1000000) /* word is a valid float */
+ {
+ if (pango2_size)
+ *pango2_size = (int)(size * PANGO2_SCALE + 0.5);
+
+ if (size_is_absolute)
+ *size_is_absolute = end < word + wordlen;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+parse_variations (const char *word,
+ size_t wordlen,
+ char **variations)
+{
+ if (word[0] != '@')
+ {
+ *variations = NULL;
+ return FALSE;
+ }
+
+ /* XXX: actually validate here */
+ *variations = g_strndup (word + 1, wordlen - 1);
+
+ return TRUE;
+}
+
+static void
+faceid_from_variations (Pango2FontDescription *desc)
+{
+ const char *p, *q;
+
+ p = desc->variations;
+
+ if (g_str_has_prefix (p, "faceid="))
+ {
+ p += strlen ("faceid=");
+ q = strchr (p, ',');
+ if (q)
+ {
+ desc->faceid = g_strndup (p, q - p);
+ p = q + 1;
+ }
+ else
+ {
+ desc->faceid = g_strdup (p);
+ p = NULL;
+ }
+ desc->mask |= PANGO2_FONT_MASK_FACEID;
+ }
+
+ if (p != desc->variations)
+ {
+ char *variations = g_strdup (p);
+ g_free (desc->variations);
+ desc->variations = variations;
+ if (variations == NULL || *variations == '\0')
+ desc->mask &= ~PANGO2_FONT_MASK_VARIATIONS;
+ }
+}
+
+/**
+ * pango2_font_description_from_string:
+ * @str: string representation of a font description.
+ *
+ * Creates a new font description from a string representation.
+ *
+ * The string must have the form
+ *
+ * "\[FAMILY-LIST] \[STYLE-OPTIONS] \[SIZE] \[VARIATIONS]",
+ *
+ * where FAMILY-LIST is a comma-separated list of families optionally
+ * terminated by a comma, STYLE_OPTIONS is a whitespace-separated list
+ * of words where each word describes one of style, variant, weight,
+ * stretch, or gravity, and SIZE is a decimal number (size in points)
+ * or optionally followed by the unit modifier "px" for absolute size.
+ * VARIATIONS is a comma-separated list of font variation
+ * specifications of the form "\@axis=value" (the = sign is optional).
+ *
+ * The following words are understood as styles:
+ * "Normal", "Roman", "Oblique", "Italic".
+ *
+ * The following words are understood as variants:
+ * "Small-Caps", "All-Small-Caps", "Petite-Caps", "All-Petite-Caps",
+ * "Unicase", "Title-Caps".
+ *
+ * The following words are understood as weights:
+ * "Thin", "Ultra-Light", "Extra-Light", "Light", "Semi-Light",
+ * "Demi-Light", "Book", "Regular", "Medium", "Semi-Bold", "Demi-Bold",
+ * "Bold", "Ultra-Bold", "Extra-Bold", "Heavy", "Black", "Ultra-Black",
+ * "Extra-Black".
+ *
+ * The following words are understood as stretch values:
+ * "Ultra-Condensed", "Extra-Condensed", "Condensed", "Semi-Condensed",
+ * "Semi-Expanded", "Expanded", "Extra-Expanded", "Ultra-Expanded".
+ *
+ * The following words are understood as gravity values:
+ * "Not-Rotated", "South", "Upside-Down", "North", "Rotated-Left",
+ * "East", "Rotated-Right", "West".
+ *
+ * Any one of the options may be absent. If FAMILY-LIST is absent, then
+ * the family_name field of the resulting font description will be
+ * initialized to %NULL. If STYLE-OPTIONS is missing, then all style
+ * options will be set to the default values. If SIZE is missing, the
+ * size in the resulting font description will be set to 0.
+ *
+ * A typical example:
+ *
+ * "Cantarell Italic Light 15 \@wght=200"
+ *
+ * Return value: a new `Pango2FontDescription`.
+ */
+Pango2FontDescription *
+pango2_font_description_from_string (const char *str)
+{
+ Pango2FontDescription *desc;
+ const char *p, *last;
+ size_t len, wordlen;
+
+ g_return_val_if_fail (str != NULL, NULL);
+
+ desc = pango2_font_description_new ();
+
+ desc->mask = PANGO2_FONT_MASK_STYLE |
+ PANGO2_FONT_MASK_WEIGHT |
+ PANGO2_FONT_MASK_VARIANT |
+ PANGO2_FONT_MASK_STRETCH;
+
+ len = strlen (str);
+ last = str + len;
+ p = getword (str, last, &wordlen, "");
+ /* Look for variations at the end of the string */
+ if (wordlen != 0)
+ {
+ if (parse_variations (p, wordlen, &desc->variations))
+ {
+ desc->mask |= PANGO2_FONT_MASK_VARIATIONS;
+ last = p;
+
+ faceid_from_variations (desc);
+ }
+ }
+
+ p = getword (str, last, &wordlen, ",");
+ /* Look for a size */
+ if (wordlen != 0)
+ {
+ gboolean size_is_absolute;
+ if (parse_size (p, wordlen, &desc->size, &size_is_absolute))
+ {
+ desc->size_is_absolute = size_is_absolute;
+ desc->mask |= PANGO2_FONT_MASK_SIZE;
+ last = p;
+ }
+ }
+
+ /* Now parse style words
+ */
+ p = getword (str, last, &wordlen, ",");
+ while (wordlen != 0)
+ {
+ if (!find_field_any (p, wordlen, desc))
+ break;
+ else
+ {
+ last = p;
+ p = getword (str, last, &wordlen, ",");
+ }
+ }
+
+ /* Remainder (str => p) is family list. Trim off trailing commas and leading and trailing white space
+ */
+
+ while (last > str && g_ascii_isspace (*(last - 1)))
+ last--;
+
+ if (last > str && *(last - 1) == ',')
+ last--;
+
+ while (last > str && g_ascii_isspace (*(last - 1)))
+ last--;
+
+ while (last > str && g_ascii_isspace (*str))
+ str++;
+
+ if (str != last)
+ {
+ int i;
+ char **families;
+
+ desc->family_name = g_strndup (str, last - str);
+
+ /* Now sanitize it to trim space from around individual family names.
+ * bug #499624 */
+
+ families = g_strsplit (desc->family_name, ",", -1);
+
+ for (i = 0; families[i]; i++)
+ g_strstrip (families[i]);
+
+ g_free (desc->family_name);
+ desc->family_name = g_strjoinv (",", families);
+ g_strfreev (families);
+
+ desc->mask |= PANGO2_FONT_MASK_FAMILY;
+ }
+
+ return desc;
+}
+
+static void
+append_field (GString *str,
+ const char *what,
+ const FieldMap *map,
+ int n_elements,
+ int val)
+{
+ int i;
+ for (i=0; i<n_elements; i++)
+ {
+ if (map[i].value != val)
+ continue;
+
+ if (G_LIKELY (map[i].str[0]))
+ {
+ if (G_LIKELY (str->len > 0 && str->str[str->len -1] != ' '))
+ g_string_append_c (str, ' ');
+ g_string_append (str, map[i].str);
+ }
+ return;
+ }
+
+ if (G_LIKELY (str->len > 0 || str->str[str->len -1] != ' '))
+ g_string_append_c (str, ' ');
+ g_string_append_printf (str, "%s=%d", what, val);
+}
+
+/**
+ * pango2_font_description_to_string:
+ * @desc: a `Pango2FontDescription`
+ *
+ * Creates a string representation of a font description.
+ *
+ * See [func@Pango2.FontDescription.from_string] for a description
+ * of the format of the string representation. The family list in
+ * the string description will only have a terminating comma if
+ * the last word of the list is a valid style option.
+ *
+ * Return value: a newly allocated string
+ */
+char *
+pango2_font_description_to_string (const Pango2FontDescription *desc)
+{
+ GString *result;
+ gboolean in_variations = FALSE;
+
+ g_return_val_if_fail (desc != NULL, NULL);
+
+ result = g_string_new (NULL);
+
+ if (G_LIKELY (desc->family_name && desc->mask & PANGO2_FONT_MASK_FAMILY))
+ {
+ const char *p;
+ size_t wordlen;
+
+ g_string_append (result, desc->family_name);
+
+ /* We need to add a trailing comma if the family name ends
+ * in a keyword like "Bold", or if the family name ends in
+ * a number and no keywords will be added.
+ */
+ p = getword (desc->family_name, desc->family_name + strlen(desc->family_name), &wordlen, ",");
+ if (wordlen != 0 &&
+ (find_field_any (p, wordlen, NULL) ||
+ (parse_size (p, wordlen, NULL, NULL) &&
+ desc->weight == PANGO2_WEIGHT_NORMAL &&
+ desc->style == PANGO2_STYLE_NORMAL &&
+ desc->stretch == PANGO2_STRETCH_NORMAL &&
+ desc->variant == PANGO2_VARIANT_NORMAL &&
+ (desc->mask & (PANGO2_FONT_MASK_GRAVITY | PANGO2_FONT_MASK_SIZE)) == 0)))
+ g_string_append_c (result, ',');
+ }
+
+#define FIELD(NAME, MASK) \
+ append_field (result, G_STRINGIFY (NAME), NAME##_map, G_N_ELEMENTS (NAME##_map), desc->NAME)
+
+ FIELD (weight, PANGO2_FONT_MASK_WEIGHT);
+ FIELD (style, PANGO2_FONT_MASK_STYLE);
+ FIELD (stretch, PANGO2_FONT_MASK_STRETCH);
+ FIELD (variant, PANGO2_FONT_MASK_VARIANT);
+ if (desc->mask & PANGO2_FONT_MASK_GRAVITY)
+ FIELD (gravity, PANGO2_FONT_MASK_GRAVITY);
+
+#undef FIELD
+
+ if (result->len == 0)
+ g_string_append (result, "Normal");
+
+ if (desc->mask & PANGO2_FONT_MASK_SIZE)
+ {
+ char buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+ if (result->len > 0 || result->str[result->len -1] != ' ')
+ g_string_append_c (result, ' ');
+
+ g_ascii_dtostr (buf, sizeof (buf), (double)desc->size / PANGO2_SCALE);
+ g_string_append (result, buf);
+
+ if (desc->size_is_absolute)
+ g_string_append (result, "px");
+ }
+
+ if (desc->mask & PANGO2_FONT_MASK_FACEID)
+ {
+ in_variations = TRUE;
+ g_string_append (result, " @");
+ g_string_append_printf (result, "faceid=%s", desc->faceid);
+ }
+
+ if ((desc->variations && desc->mask & PANGO2_FONT_MASK_VARIATIONS) &&
+ desc->variations[0] != '\0')
+ {
+ if (!in_variations)
+ g_string_append (result, " @");
+ else
+ g_string_append (result, ",");
+ g_string_append (result, desc->variations);
+ }
+
+ return g_string_free (result, FALSE);
+}
+
+static gboolean
+parse_field (const char *what,
+ const FieldMap *map,
+ int n_elements,
+ const char *str,
+ int *val,
+ gboolean warn)
+{
+ gboolean found;
+ int len = strlen (str);
+
+ if (G_UNLIKELY (*str == '\0'))
+ return FALSE;
+
+ if (field_matches ("Normal", str, len))
+ {
+ /* find the map entry with empty string */
+ int i;
+
+ for (i = 0; i < n_elements; i++)
+ if (map[i].str[0] == '\0')
+ {
+ *val = map[i].value;
+ return TRUE;
+ }
+
+ *val = 0;
+ return TRUE;
+ }
+
+ found = find_field (NULL, map, n_elements, str, len, val);
+
+ if (!found && warn)
+ {
+ int i;
+ GString *s = g_string_new (NULL);
+
+ for (i = 0; i < n_elements; i++)
+ {
+ if (i)
+ g_string_append_c (s, '/');
+ g_string_append (s, map[i].str[0] == '\0' ? "Normal" : map[i].str);
+ }
+
+ g_warning ("%s must be one of %s or a number",
+ what,
+ s->str);
+
+ g_string_free (s, TRUE);
+ }
+
+ return found;
+}
+
+#define FIELD(NAME, MASK) \
+ parse_field (G_STRINGIFY (NAME), NAME##_map, G_N_ELEMENTS (NAME##_map), str, (int *)(void *)NAME, warn)
+
+/*< private >
+ * pango2_parse_style:
+ * @str: a string to parse.
+ * @style: (out): a `Pango2Style` to store the result in.
+ * @warn: if %TRUE, issue a g_warning() on bad input.
+ *
+ * Parses a font style.
+ *
+ * The allowed values are "normal", "italic" and "oblique", case
+ * variations being
+ * ignored.
+ *
+ * Return value: %TRUE if @str was successfully parsed.
+ */
+gboolean
+pango2_parse_style (const char *str,
+ Pango2Style *style,
+ gboolean warn)
+{
+ return FIELD (style, PANGO2_FONT_MASK_STYLE);
+}
+
+/*< private >
+ * pango2_parse_variant:
+ * @str: a string to parse.
+ * @variant: (out): a `Pango2Variant` to store the result in.
+ * @warn: if %TRUE, issue a g_warning() on bad input.
+ *
+ * Parses a font variant.
+ *
+ * The allowed values are "normal", "small-caps", "all-small-caps",
+ * "petite-caps", "all-petite-caps", "unicase" and "title-caps",
+ * case variations being ignored.
+ *
+ * Return value: %TRUE if @str was successfully parsed.
+ */
+gboolean
+pango2_parse_variant (const char *str,
+ Pango2Variant *variant,
+ gboolean warn)
+{
+ return FIELD (variant, PANGO2_FONT_MASK_VARIANT);
+}
+
+/*< private >
+ * pango2_parse_weight:
+ * @str: a string to parse.
+ * @weight: (out): a `Pango2Weight` to store the result in.
+ * @warn: if %TRUE, issue a g_warning() on bad input.
+ *
+ * Parses a font weight.
+ *
+ * The allowed values are "heavy",
+ * "ultrabold", "bold", "normal", "light", "ultraleight"
+ * and integers. Case variations are ignored.
+ *
+ * Return value: %TRUE if @str was successfully parsed.
+ */
+gboolean
+pango2_parse_weight (const char *str,
+ Pango2Weight *weight,
+ gboolean warn)
+{
+ return FIELD (weight, PANGO2_FONT_MASK_WEIGHT);
+}
+
+/*< private >
+ * pango2_parse_stretch:
+ * @str: a string to parse.
+ * @stretch: (out): a `Pango2Stretch` to store the result in.
+ * @warn: if %TRUE, issue a g_warning() on bad input.
+ *
+ * Parses a font stretch.
+ *
+ * The allowed values are
+ * "ultra_condensed", "extra_condensed", "condensed",
+ * "semi_condensed", "normal", "semi_expanded", "expanded",
+ * "extra_expanded" and "ultra_expanded". Case variations are
+ * ignored and the '_' characters may be omitted.
+ *
+ * Return value: %TRUE if @str was successfully parsed.
+ */
+gboolean
+pango2_parse_stretch (const char *str,
+ Pango2Stretch *stretch,
+ gboolean warn)
+{
+ return FIELD (stretch, PANGO2_FONT_MASK_STRETCH);
+}
+
+/**
+ * pango2_font_description_set_faceid_static:
+ * @desc: a `Pango2FontDescription`
+ * @faceid: the faceid string
+ *
+ * Sets the faceid field of a font description.
+ *
+ * This is like [method@Pango2.FontDescription.set_faceid], except
+ * that no copy of @faceid is made. The caller must make sure that
+ * the string passed in stays around until @desc has been freed
+ * or the name is set again. This function can be used if
+ * @faceid is a static string such as a C string literal,
+ * or if @desc is only needed temporarily.
+ */
+void
+pango2_font_description_set_faceid_static (Pango2FontDescription *desc,
+ const char *faceid)
+{
+ g_return_if_fail (desc != NULL);
+
+ if (desc->faceid == faceid)
+ return;
+
+ if (desc->faceid && !desc->static_faceid)
+ g_free (desc->faceid);
+
+ if (faceid)
+ {
+ desc->faceid = (char *)faceid;
+ desc->static_faceid = TRUE;
+ desc->mask |= PANGO2_FONT_MASK_FACEID;
+ }
+ else
+ {
+ desc->faceid = pfd_defaults.faceid;
+ desc->static_faceid = pfd_defaults.static_faceid;
+ desc->mask &= ~PANGO2_FONT_MASK_FACEID;
+ }
+}
+
+/**
+ * pango2_font_description_set_faceid:
+ * @desc: a `Pango2FontDescription`.
+ * @faceid: (nullable): the faceid string
+ *
+ * Sets the faceid field of a font description.
+ *
+ * The faceid is mainly for internal use by Pango2, to ensure
+ * that font -> description -> font roundtrips end up with
+ * the same font they started with, if possible.
+ *
+ * Font descriptions originating from [method@Pango2.FontFace.describe]
+ * should ideally include a faceid. Pango2 takes the faceid
+ * into account when looking for the best matching face while
+ * loading a fontset or font.
+ *
+ * The format of this string is not guaranteed.
+ */
+void
+pango2_font_description_set_faceid (Pango2FontDescription *desc,
+ const char *faceid)
+{
+ g_return_if_fail (desc != NULL);
+
+ pango2_font_description_set_faceid_static (desc, g_strdup (faceid));
+ if (faceid)
+ desc->static_faceid = FALSE;
+}
+
+/**
+ * pango2_font_description_get_faceid:
+ * @desc: a `Pango2FontDescription`
+ *
+ * Gets the faceid field of a font description.
+ *
+ * See [method@Pango2.FontDescription.set_faceid].
+ *
+ * Return value: (nullable): the faceid field for the font
+ * description, or %NULL if not previously set. This has the same
+ * life-time as the font description itself and should not be freed.
+ */
+const char *
+pango2_font_description_get_faceid (const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (desc != NULL, NULL);
+
+ return desc->faceid;
+}
diff --git a/pango2/pango-font-description.h b/pango2/pango-font-description.h
new file mode 100644
index 00000000..160ee50d
--- /dev/null
+++ b/pango2/pango-font-description.h
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+
+#include <glib-object.h>
+#include <hb.h>
+
+G_BEGIN_DECLS
+
+/**
+ * Pango2FontDescription:
+ *
+ * A `Pango2FontDescription` describes a font in an implementation-independent
+ * manner.
+ *
+ * `Pango2FontDescription` structures are used both to list what fonts are
+ * available on the system and also for specifying the characteristics of
+ * a font to load.
+ */
+
+/**
+ * Pango2Style:
+ * @PANGO2_STYLE_NORMAL: the font is upright.
+ * @PANGO2_STYLE_OBLIQUE: the font is slanted, but in a roman style.
+ * @PANGO2_STYLE_ITALIC: the font is slanted in an italic style.
+ *
+ * An enumeration specifying the various slant styles possible for a font.
+ **/
+typedef enum {
+ PANGO2_STYLE_NORMAL,
+ PANGO2_STYLE_OBLIQUE,
+ PANGO2_STYLE_ITALIC
+} Pango2Style;
+
+/**
+ * Pango2Variant:
+ * @PANGO2_VARIANT_NORMAL: A normal font.
+ * @PANGO2_VARIANT_SMALL_CAPS: A font with the lower case characters
+ * replaced by smaller variants of the capital characters.
+ * @PANGO2_VARIANT_ALL_SMALL_CAPS: A font with all characters
+ * replaced by smaller variants of the capital characters.
+ * @PANGO2_VARIANT_PETITE_CAPS: A font with the lower case characters
+ * replaced by smaller variants of the capital characters.
+ * Petite Caps can be even smaller than Small Caps.
+ * @PANGO2_VARIANT_ALL_PETITE_CAPS: A font with all characters
+ * replaced by smaller variants of the capital characters.
+ * Petite Caps can be even smaller than Small Caps.
+ * @PANGO2_VARIANT_UNICASE: A font with the upper case characters
+ * replaced by smaller variants of the capital letters.
+ * @PANGO2_VARIANT_TITLE_CAPS: A font with capital letters that
+ * are more suitable for all-uppercase titles.
+ *
+ * An enumeration specifying capitalization variant of the font.
+ */
+typedef enum {
+ PANGO2_VARIANT_NORMAL,
+ PANGO2_VARIANT_SMALL_CAPS,
+ PANGO2_VARIANT_ALL_SMALL_CAPS,
+ PANGO2_VARIANT_PETITE_CAPS,
+ PANGO2_VARIANT_ALL_PETITE_CAPS,
+ PANGO2_VARIANT_UNICASE,
+ PANGO2_VARIANT_TITLE_CAPS
+} Pango2Variant;
+
+/**
+ * Pango2Weight:
+ * @PANGO2_WEIGHT_THIN: the thin weight (= 100)
+ * @PANGO2_WEIGHT_ULTRALIGHT: the ultralight weight (= 200)
+ * @PANGO2_WEIGHT_LIGHT: the light weight (= 300)
+ * @PANGO2_WEIGHT_SEMILIGHT: the semilight weight (= 350)
+ * @PANGO2_WEIGHT_BOOK: the book weight (= 380)
+ * @PANGO2_WEIGHT_NORMAL: the default weight (= 400)
+ * @PANGO2_WEIGHT_MEDIUM: the normal weight (= 500)
+ * @PANGO2_WEIGHT_SEMIBOLD: the semibold weight (= 600)
+ * @PANGO2_WEIGHT_BOLD: the bold weight (= 700)
+ * @PANGO2_WEIGHT_ULTRABOLD: the ultrabold weight (= 800)
+ * @PANGO2_WEIGHT_HEAVY: the heavy weight (= 900)
+ * @PANGO2_WEIGHT_ULTRAHEAVY: the ultraheavy weight (= 1000)
+ *
+ * A `Pango2Weight` specifes the weight (boldness) of a font.
+ *
+ * Weight is specified as a numeric value ranging from 100 to 1000.
+ * This enumeration simply provides some common, predefined values.
+ */
+typedef enum {
+ PANGO2_WEIGHT_THIN = 100,
+ PANGO2_WEIGHT_ULTRALIGHT = 200,
+ PANGO2_WEIGHT_LIGHT = 300,
+ PANGO2_WEIGHT_SEMILIGHT = 350,
+ PANGO2_WEIGHT_BOOK = 380,
+ PANGO2_WEIGHT_NORMAL = 400,
+ PANGO2_WEIGHT_MEDIUM = 500,
+ PANGO2_WEIGHT_SEMIBOLD = 600,
+ PANGO2_WEIGHT_BOLD = 700,
+ PANGO2_WEIGHT_ULTRABOLD = 800,
+ PANGO2_WEIGHT_HEAVY = 900,
+ PANGO2_WEIGHT_ULTRAHEAVY = 1000
+} Pango2Weight;
+
+/**
+ * Pango2Stretch:
+ * @PANGO2_STRETCH_ULTRA_CONDENSED: ultra-condensed width (= 500)
+ * @PANGO2_STRETCH_EXTRA_CONDENSED: extra-condensed width (= 625)
+ * @PANGO2_STRETCH_CONDENSED: condensed width (= 750)
+ * @PANGO2_STRETCH_SEMI_CONDENSED: semi-condensed width (= 875)
+ * @PANGO2_STRETCH_NORMAL: the normal width (= 1000)
+ * @PANGO2_STRETCH_SEMI_EXPANDED: semi-expanded width (= 1125)
+ * @PANGO2_STRETCH_EXPANDED: expanded width (= 1250)
+ * @PANGO2_STRETCH_EXTRA_EXPANDED: extra-expanded width (= 1500)
+ * @PANGO2_STRETCH_ULTRA_EXPANDED: ultra-expanded width (= 2000)
+ *
+ * A `Pango2Stretch` specifes the width of the font relative
+ * to other designs within a family.
+ *
+ * Stretch is specified as a numeric value ranging from 500 to 2000.
+ * This enumeration simply provides some common, predefined values.
+ */
+typedef enum {
+ PANGO2_STRETCH_ULTRA_CONDENSED = 500,
+ PANGO2_STRETCH_EXTRA_CONDENSED = 625,
+ PANGO2_STRETCH_CONDENSED = 750,
+ PANGO2_STRETCH_SEMI_CONDENSED = 875,
+ PANGO2_STRETCH_NORMAL = 1000,
+ PANGO2_STRETCH_SEMI_EXPANDED = 1125,
+ PANGO2_STRETCH_EXPANDED = 1250,
+ PANGO2_STRETCH_EXTRA_EXPANDED = 1500,
+ PANGO2_STRETCH_ULTRA_EXPANDED = 2000
+} Pango2Stretch;
+
+/**
+ * Pango2FontMask:
+ * @PANGO2_FONT_MASK_FAMILY: the font family is specified.
+ * @PANGO2_FONT_MASK_STYLE: the font style is specified.
+ * @PANGO2_FONT_MASK_VARIANT: the font variant is specified.
+ * @PANGO2_FONT_MASK_WEIGHT: the font weight is specified.
+ * @PANGO2_FONT_MASK_STRETCH: the font stretch is specified.
+ * @PANGO2_FONT_MASK_SIZE: the font size is specified.
+ * @PANGO2_FONT_MASK_GRAVITY: the font gravity is specified
+ * @PANGO2_FONT_MASK_VARIATIONS: OpenType font variations are specified
+ * @PANGO2_FONT_MASK_FACEID: the face ID is specified
+ *
+ * The bits in a `Pango2FontMask` correspond to the set fields in a
+ * `Pango2FontDescription`.
+ */
+typedef enum {
+ PANGO2_FONT_MASK_FAMILY = 1 << 0,
+ PANGO2_FONT_MASK_STYLE = 1 << 1,
+ PANGO2_FONT_MASK_VARIANT = 1 << 2,
+ PANGO2_FONT_MASK_WEIGHT = 1 << 3,
+ PANGO2_FONT_MASK_STRETCH = 1 << 4,
+ PANGO2_FONT_MASK_SIZE = 1 << 5,
+ PANGO2_FONT_MASK_GRAVITY = 1 << 6,
+ PANGO2_FONT_MASK_VARIATIONS = 1 << 7,
+ PANGO2_FONT_MASK_FACEID = 1 << 8,
+} Pango2FontMask;
+
+/* CSS scale factors (1.2 factor between each size) */
+/**
+ * PANGO2_SCALE_XX_SMALL:
+ *
+ * The scale factor for three shrinking steps (1 / (1.2 * 1.2 * 1.2)).
+ */
+/**
+ * PANGO2_SCALE_X_SMALL:
+ *
+ * The scale factor for two shrinking steps (1 / (1.2 * 1.2)).
+ */
+/**
+ * PANGO2_SCALE_SMALL:
+ *
+ * The scale factor for one shrinking step (1 / 1.2).
+ */
+/**
+ * PANGO2_SCALE_MEDIUM:
+ *
+ * The scale factor for normal size (1.0).
+ */
+/**
+ * PANGO2_SCALE_LARGE:
+ *
+ * The scale factor for one magnification step (1.2).
+ */
+/**
+ * PANGO2_SCALE_X_LARGE:
+ *
+ * The scale factor for two magnification steps (1.2 * 1.2).
+ */
+/**
+ * PANGO2_SCALE_XX_LARGE:
+ *
+ * The scale factor for three magnification steps (1.2 * 1.2 * 1.2).
+ */
+#define PANGO2_SCALE_XX_SMALL ((double)0.5787037037037)
+#define PANGO2_SCALE_X_SMALL ((double)0.6944444444444)
+#define PANGO2_SCALE_SMALL ((double)0.8333333333333)
+#define PANGO2_SCALE_MEDIUM ((double)1.0)
+#define PANGO2_SCALE_LARGE ((double)1.2)
+#define PANGO2_SCALE_X_LARGE ((double)1.44)
+#define PANGO2_SCALE_XX_LARGE ((double)1.728)
+
+
+#define PANGO2_TYPE_FONT_DESCRIPTION (pango2_font_description_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_font_description_get_type (void) G_GNUC_CONST;
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontDescription * pango2_font_description_new (void);
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontDescription * pango2_font_description_copy (const Pango2FontDescription *desc);
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontDescription * pango2_font_description_copy_static (const Pango2FontDescription *desc);
+PANGO2_AVAILABLE_IN_ALL
+guint pango2_font_description_hash (const Pango2FontDescription *desc) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_font_description_equal (const Pango2FontDescription *desc1,
+ const Pango2FontDescription *desc2) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_free (Pango2FontDescription *desc);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_family (Pango2FontDescription *desc,
+ const char *family);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_family_static (Pango2FontDescription *desc,
+ const char *family);
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_font_description_get_family (const Pango2FontDescription *desc) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_style (Pango2FontDescription *desc,
+ Pango2Style style);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Style pango2_font_description_get_style (const Pango2FontDescription *desc) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_variant (Pango2FontDescription *desc,
+ Pango2Variant variant);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Variant pango2_font_description_get_variant (const Pango2FontDescription *desc) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_weight (Pango2FontDescription *desc,
+ Pango2Weight weight);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Weight pango2_font_description_get_weight (const Pango2FontDescription *desc) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_stretch (Pango2FontDescription *desc,
+ Pango2Stretch stretch);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Stretch pango2_font_description_get_stretch (const Pango2FontDescription *desc) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_size (Pango2FontDescription *desc,
+ int size);
+PANGO2_AVAILABLE_IN_ALL
+int pango2_font_description_get_size (const Pango2FontDescription *desc) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_absolute_size (Pango2FontDescription *desc,
+ double size);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_font_description_get_size_is_absolute (const Pango2FontDescription *desc) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_gravity (Pango2FontDescription *desc,
+ Pango2Gravity gravity);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Gravity pango2_font_description_get_gravity (const Pango2FontDescription *desc) G_GNUC_PURE;
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_variations_static (Pango2FontDescription *desc,
+ const char *variations);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_variations (Pango2FontDescription *desc,
+ const char *variations);
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_font_description_get_variations (const Pango2FontDescription *desc) G_GNUC_PURE;
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_faceid (Pango2FontDescription *desc,
+ const char *faceid);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_set_faceid_static (Pango2FontDescription *desc,
+ const char *faceid);
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_font_description_get_faceid (const Pango2FontDescription *desc) G_GNUC_PURE;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontMask pango2_font_description_get_set_fields (const Pango2FontDescription *desc) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_unset_fields (Pango2FontDescription *desc,
+ Pango2FontMask to_unset);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_merge (Pango2FontDescription *desc,
+ const Pango2FontDescription *desc_to_merge,
+ gboolean replace_existing);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_description_merge_static (Pango2FontDescription *desc,
+ const Pango2FontDescription *desc_to_merge,
+ gboolean replace_existing);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontDescription * pango2_font_description_from_string (const char *str);
+PANGO2_AVAILABLE_IN_ALL
+char * pango2_font_description_to_string (const Pango2FontDescription *desc);
+
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2FontDescription, pango2_font_description_free)
+
+G_END_DECLS
diff --git a/pango2/pango-font-face-private.h b/pango2/pango-font-face-private.h
new file mode 100644
index 00000000..b7031a26
--- /dev/null
+++ b/pango2/pango-font-face-private.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-font-face.h"
+#include "pango-font-description.h"
+
+
+struct _Pango2FontFace
+{
+ GObject parent_instance;
+
+ Pango2FontFamily *family;
+ Pango2FontDescription *description;
+ char *name;
+ char *faceid;
+};
+
+typedef struct _Pango2FontFaceClass Pango2FontFaceClass;
+
+struct _Pango2FontFaceClass
+{
+ GObjectClass parent_class;
+
+ gboolean (* is_synthesized) (Pango2FontFace *face);
+ gboolean (* is_monospace) (Pango2FontFace *face);
+ gboolean (* is_variable) (Pango2FontFace *face);
+ gboolean (* supports_language) (Pango2FontFace *face,
+ Pango2Language *language);
+ Pango2Language ** (* get_languages) (Pango2FontFace *face);
+ gboolean (* has_char) (Pango2FontFace *face,
+ gunichar wc);
+ const char * (* get_faceid) (Pango2FontFace *face);
+ Pango2Font * (* create_font) (Pango2FontFace *face,
+ const Pango2FontDescription *desc,
+ float dpi,
+ const Pango2Matrix *ctm);
+};
+
+#define PANGO2_FONT_FACE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO2_TYPE_FONT_FACE, Pango2FontFaceClass))
+#define PANGO2_FONT_FACE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO2_TYPE_FONT_FACE, Pango2FontFaceClass))
+
+const char * pango2_font_face_get_faceid (Pango2FontFace *face);
+Pango2Font * pango2_font_face_create_font (Pango2FontFace *face,
+ const Pango2FontDescription *desc,
+ float dpi,
+ const Pango2Matrix *ctm);
+
+static inline void
+pango2_font_face_set_name (Pango2FontFace *face,
+ const char *name)
+{
+ face->name = g_strdup (name);
+}
+
+static inline void
+pango2_font_face_set_description (Pango2FontFace *face,
+ const Pango2FontDescription *description)
+{
+ face->description = pango2_font_description_copy (description);
+}
+
+static inline void
+pango2_font_face_set_family (Pango2FontFace *face,
+ Pango2FontFamily *family)
+{
+ face->family = family;
+}
diff --git a/pango2/pango-font-face.c b/pango2/pango-font-face.c
new file mode 100644
index 00000000..475cb871
--- /dev/null
+++ b/pango2/pango-font-face.c
@@ -0,0 +1,543 @@
+/* Pango2
+ *
+ * Copyright (C) 1999 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.
+ */
+
+#include "config.h"
+
+#include "pango-font-face-private.h"
+#include "pango-font-family.h"
+
+#ifdef __linux__
+#include <execinfo.h>
+
+static inline char *
+get_backtrace (void)
+{
+ void *buffer[1024];
+ int size;
+ char **symbols;
+ GString *s;
+
+ size = backtrace (buffer, 1024);
+ symbols = backtrace_symbols (buffer, size);
+
+ s = g_string_new ("");
+
+ for (int i = 0; i < size; i++)
+ {
+ g_string_append (s, symbols[i]);
+ g_string_append_c (s, '\n');
+ }
+
+ free (symbols);
+
+ return g_string_free (s, FALSE);
+}
+#endif
+
+/**
+ * Pango2FontFace:
+ *
+ * A `Pango2FontFace` is used to represent a group of fonts with
+ * the same family, slant, weight, and width, but varying sizes.
+ *
+ * `Pango2FontFace` provides APIs to determine coverage information,
+ * such as [method@Pango2.FontFace.has_char] and
+ * [method@Pango2.FontFace.supports_language], as well as general
+ * information about the font face like [method@Pango2.FontFace.is_monospace]
+ * or [method@Pango2.FontFace.is_variable].
+ */
+
+/* {{{ Pango2FontFace implementation */
+
+enum {
+ PROP_NAME = 1,
+ PROP_DESCRIPTION,
+ PROP_FAMILY,
+ PROP_SYNTHESIZED,
+ PROP_MONOSPACE,
+ PROP_VARIABLE,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+G_DEFINE_ABSTRACT_TYPE (Pango2FontFace, pango2_font_face, G_TYPE_OBJECT)
+
+static gboolean
+pango2_font_face_default_is_monospace (Pango2FontFace *face)
+{
+ return FALSE;
+}
+
+static gboolean
+pango2_font_face_default_is_variable (Pango2FontFace *face)
+{
+ return FALSE;
+}
+
+static gboolean
+pango2_font_face_default_supports_language (Pango2FontFace *face,
+ Pango2Language *language)
+{
+ return TRUE;
+}
+
+static Pango2Language **
+pango2_font_face_default_get_languages (Pango2FontFace *face)
+{
+ return NULL;
+}
+
+static gboolean
+pango2_font_face_default_has_char (Pango2FontFace *face,
+ gunichar wc)
+{
+ return FALSE;
+}
+
+static const char *
+pango2_font_face_default_get_faceid (Pango2FontFace *face)
+{
+ return "";
+}
+
+static Pango2Font *
+pango2_font_face_default_create_font (Pango2FontFace *face,
+ const Pango2FontDescription *desc,
+ float dpi,
+ const Pango2Matrix *ctm)
+{
+ return NULL;
+}
+
+static void
+pango2_font_face_finalize (GObject *object)
+{
+ Pango2FontFace *face = PANGO2_FONT_FACE (object);
+
+ pango2_font_description_free (face->description);
+ g_free (face->name);
+
+ G_OBJECT_CLASS (pango2_font_face_parent_class)->finalize (object);
+}
+
+static void
+pango2_font_face_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2FontFace *face = PANGO2_FONT_FACE (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, face->name);
+ break;
+
+ case PROP_DESCRIPTION:
+ if ((pango2_font_description_get_set_fields (face->description) & PANGO2_FONT_MASK_FACEID) == 0)
+ pango2_font_description_set_faceid (face->description,
+ pango2_font_face_get_faceid (face));
+
+ g_value_set_boxed (value, face->description);
+ break;
+
+ case PROP_FAMILY:
+ g_value_set_object (value, face->family);
+ break;
+
+ case PROP_SYNTHESIZED:
+ g_value_set_boolean (value, pango2_font_face_is_synthesized (face));
+ break;
+
+ case PROP_MONOSPACE:
+ g_value_set_boolean (value, pango2_font_face_is_monospace (face));
+ break;
+
+ case PROP_VARIABLE:
+ g_value_set_boolean (value, pango2_font_face_is_variable (face));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+pango2_font_face_class_init (Pango2FontFaceClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = pango2_font_face_finalize;
+ object_class->get_property = pango2_font_face_get_property;
+
+ class->is_monospace = pango2_font_face_default_is_monospace;
+ class->is_variable = pango2_font_face_default_is_variable;
+ class->get_languages = pango2_font_face_default_get_languages;
+ class->supports_language = pango2_font_face_default_supports_language;
+ class->has_char = pango2_font_face_default_has_char;
+ class->get_faceid = pango2_font_face_default_get_faceid;
+ class->create_font = pango2_font_face_default_create_font;
+
+ /**
+ * Pango2FontFace:name: (attributes org.gtk.Property.get=pango2_font_face_get_name)
+ *
+ * A name representing the style of this face.
+ */
+ properties[PROP_NAME] =
+ g_param_spec_string ("name", NULL, NULL, NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2FontFace:description: (attributes org.gtk.Property.get=pango2_font_face_describe)
+ *
+ * A font description that matches the face.
+ *
+ * The font description will have the family, style,
+ * variant, weight and stretch of the face, but its size field
+ * will be unset.
+ */
+ properties[PROP_DESCRIPTION] =
+ g_param_spec_boxed ("description", NULL, NULL,
+ PANGO2_TYPE_FONT_DESCRIPTION,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2FontFace:family: (attributes org.gtk.Property.get=pango2_font_face_get_family)
+ *
+ * The `Pango2FontFamily` that the face belongs to.
+ */
+ properties[PROP_FAMILY] =
+ g_param_spec_object ("family", NULL, NULL,
+ PANGO2_TYPE_FONT_FAMILY,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2FontFace:synthesized: (attributes org.gtk.Property.get=pango2_font_face_is_synthesized)
+ *
+ * `TRUE` if the face is synthesized.
+ *
+ * This will be the case if the underlying font rendering engine
+ * creates this face from another face, by shearing, emboldening,
+ * lightening or modifying it in some other way.
+ */
+ properties[PROP_SYNTHESIZED] =
+ g_param_spec_boolean ("synthesized", NULL, NULL, FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2FontFace:monospace: (attributes org.gtk.Property.get=pango2_font_face_is_monospace)
+ *
+ * `TRUE` if the face is designed for text display where the the
+ * characters form a regular grid.
+ */
+ properties[PROP_MONOSPACE] =
+ g_param_spec_boolean ("monospace", NULL, NULL, FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2FontFace:variable: (attributes org.gtk.Property.get=pango2_font_face_is_variable)
+ *
+ * `TRUE` if the face has axes that can be modified
+ * to produce variations.
+ */
+ properties[PROP_VARIABLE] =
+ g_param_spec_boolean ("variable", NULL, NULL, FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+static void
+pango2_font_face_init (Pango2FontFace *face G_GNUC_UNUSED)
+{
+}
+
+/* }}} */
+/* {{{ Private API */
+
+Pango2Font *
+pango2_font_face_create_font (Pango2FontFace *face,
+ const Pango2FontDescription *desc,
+ float dpi,
+ const Pango2Matrix *ctm)
+{
+ if (!PANGO2_IS_FONT_FACE (face))
+ {
+ char *s = pango2_font_description_to_string (desc);
+#ifdef __linux__
+ char *bs = get_backtrace ();
+#else
+ char *bs = g_strdup ("");
+#endif
+ g_critical ("pango2_font_face_create_font called without a face for %s\n%s", s, bs);
+ g_free (s);
+ g_free (bs);
+ }
+
+ g_return_val_if_fail (PANGO2_IS_FONT_FACE (face), NULL);
+
+ return PANGO2_FONT_FACE_GET_CLASS (face)->create_font (face, desc, dpi, ctm);
+}
+
+/*< private >
+ * pango2_font_face_get_faceid:
+ * @self: a `Pango2HbFace`
+ *
+ * Returns the faceid of the face.
+ *
+ * The faceid is meant to uniquely identify the face among the
+ * faces of its family. It includes an identifier for the font
+ * file that is used (currently, we use the PostScript name for
+ * this), the face index, the instance ID, as well as synthetic
+ * tweaks such as emboldening and transforms and variations.
+ *
+ * [method@Pango2.FontFace.describe] adds the faceid to the font
+ * description that it produces.
+ *
+ * See pango2_hb_family_find_face() for the code that takes the
+ * faceid into account when searching for a face. It is careful
+ * to fall back to approximate matching if an exact match for
+ * the faceid isn't found. That should make it safe to preserve
+ * faceids when saving font descriptions in configuration or
+ * other data.
+ *
+ * There are no guarantees about the format of the string that
+ * this function produces, except for that it does not contain
+ * ' ', ',' or '=', so it can be safely embedded in the '@' part
+ * of a serialized font description.
+ *
+ * Returns: (transfer none): the faceid
+ */
+const char *
+pango2_font_face_get_faceid (Pango2FontFace *face)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FACE (face), "");
+
+ return PANGO2_FONT_FACE_GET_CLASS (face)->get_faceid (face);
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_font_face_describe:
+ * @face: a `Pango2FontFace`
+ *
+ * Returns a font description that matches the face.
+ *
+ * The resulting font description will have the family, style,
+ * variant, weight and stretch of the face, but its size field
+ * will be unset.
+ *
+ * Return value: a newly-created `Pango2FontDescription` structure
+ * holding the description of the face. Use [method@Pango2.FontDescription.free]
+ * to free the result.
+ */
+Pango2FontDescription *
+pango2_font_face_describe (Pango2FontFace *face)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FACE (face), NULL);
+
+ if ((pango2_font_description_get_set_fields (face->description) & PANGO2_FONT_MASK_FACEID) == 0)
+ pango2_font_description_set_faceid (face->description,
+ pango2_font_face_get_faceid (face));
+
+ return pango2_font_description_copy (face->description);
+}
+
+/**
+ * pango2_font_face_is_synthesized:
+ * @face: a `Pango2FontFace`
+ *
+ * Returns whether a `Pango2FontFace` is synthesized.
+ *
+ * This will be the case if the underlying font rendering engine
+ * creates this face from another face, by shearing, emboldening,
+ * lightening or modifying it in some other way.
+ *
+ * Return value: whether @face is synthesized
+ */
+gboolean
+pango2_font_face_is_synthesized (Pango2FontFace *face)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FACE (face), FALSE);
+
+ if (PANGO2_FONT_FACE_GET_CLASS (face)->is_synthesized != NULL)
+ return PANGO2_FONT_FACE_GET_CLASS (face)->is_synthesized (face);
+ else
+ return FALSE;
+}
+
+/**
+ * pango2_font_face_get_name:
+ * @face: a `Pango2FontFace`.
+ *
+ * Gets a name representing the style of this face.
+ *
+ * Note that a font family may contain multiple faces
+ * with the same name (e.g. a variable and a non-variable
+ * face for the same style).
+ *
+ * Return value: the name for the face. This string is
+ * owned by the face object and must not be modified or freed.
+ */
+const char *
+pango2_font_face_get_name (Pango2FontFace *face)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FACE (face), NULL);
+
+ return face->name;
+}
+
+/**
+ * pango2_font_face_get_family:
+ * @face: a `Pango2FontFace`
+ *
+ * Gets the `Pango2FontFamily` that the face belongs to.
+ *
+ * Returns: (transfer none): the `Pango2FontFamily`
+ */
+Pango2FontFamily *
+pango2_font_face_get_family (Pango2FontFace *face)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FACE (face), NULL);
+
+ return face->family;
+}
+
+/**
+ * pango2_font_face_is_monospace:
+ * @face: a `Pango2FontFace`
+ *
+ * Returns whether this font face is monospace.
+ *
+ * A monospace font is a font designed for text display where the the
+ * characters form a regular grid.
+ *
+ * For Western languages this would mean that the advance width of all
+ * characters are the same, but this categorization also includes Asian
+ * fonts which include double-width characters: characters that occupy
+ * two grid cells. [func@GLib.unichar_iswide] returns a result that
+ * indicates whether a character is typically double-width in a monospace
+ * font.
+ *
+ * The best way to find out the grid-cell size is to call
+ * [method@Pango2.FontMetrics.get_approximate_digit_width], since the
+ * results of [method@Pango2.FontMetrics.get_approximate_char_width] may
+ * be affected by double-width characters.
+ *
+ * Returns: `TRUE` if @face is monospace
+ */
+gboolean
+pango2_font_face_is_monospace (Pango2FontFace *face)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FACE (face), FALSE);
+
+ return PANGO2_FONT_FACE_GET_CLASS (face)->is_monospace (face);
+}
+
+/**
+ * pango2_font_face_is_variable:
+ * @face: a `Pango2FontFace`
+ *
+ * Returns whether this font face is variable.
+ *
+ * A variable font is a font which has axes that can be modified
+ * to produce variations.
+ *
+ * Such axes are also known as _variations_; see
+ * [method@Pango2.FontDescription.set_variations] for more information.
+ *
+ * Returns: `TRUE` if @face is variable
+ */
+gboolean
+pango2_font_face_is_variable (Pango2FontFace *face)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FACE (face), FALSE);
+
+ return PANGO2_FONT_FACE_GET_CLASS (face)->is_variable (face);
+}
+
+/**
+ * pango2_font_face_supports_language:
+ * @face: a `Pango2FontFace`
+ * @language: a `Pango2Language`
+ *
+ * Returns whether the face has all the glyphs necessary to
+ * support this language.
+ *
+ * Returns: `TRUE` if @face supports @language
+ */
+gboolean
+pango2_font_face_supports_language (Pango2FontFace *face,
+ Pango2Language *language)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FACE (face), FALSE);
+
+ return PANGO2_FONT_FACE_GET_CLASS (face)->supports_language (face, language);
+}
+
+/**
+ * pango2_font_face_get_languages:
+ * @face: a `Pango2FontFace`
+ *
+ * Returns the languages that are supported by the face.
+ *
+ * If the font backend does not provide this information,
+ * `NULL` is returned. For the fontconfig backend, this
+ * corresponds to the `FC_LANG` member of the `FcPattern`.
+ *
+ * The returned array is only valid as long as the face
+ * and its fontmap are valid.
+ *
+ * Returns: (transfer none) (nullable) (array zero-terminated=1) (element-type Pango2Language):
+ * an array of `Pango2Language`
+ */
+Pango2Language **
+pango2_font_face_get_languages (Pango2FontFace *face)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FACE (face), FALSE);
+
+ return PANGO2_FONT_FACE_GET_CLASS (face)->get_languages (face);
+}
+
+/**
+ * pango2_font_face_has_char:
+ * @face: a `Pango2FontFace`
+ * @wc: a Unicode character
+ *
+ * Returns whether the face provides a glyph for this character.
+ *
+ * Returns: `TRUE` if @font can render @wc
+ */
+gboolean
+pango2_font_face_has_char (Pango2FontFace *face,
+ gunichar wc)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FACE (face), FALSE);
+
+ return PANGO2_FONT_FACE_GET_CLASS (face)->has_char (face, wc);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-font-face.h b/pango2/pango-font-face.h
new file mode 100644
index 00000000..dea2b6df
--- /dev/null
+++ b/pango2/pango-font-face.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+
+#define PANGO2_TYPE_FONT_FACE (pango2_font_face_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2FontFace, pango2_font_face, PANGO2, FONT_FACE, GObject)
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontDescription * pango2_font_face_describe (Pango2FontFace *face);
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_font_face_get_name (Pango2FontFace *face) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_font_face_is_synthesized (Pango2FontFace *face) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_font_face_is_monospace (Pango2FontFace *face);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_font_face_is_variable (Pango2FontFace *face);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontFamily * pango2_font_face_get_family (Pango2FontFace *face);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_font_face_supports_language
+ (Pango2FontFace *face,
+ Pango2Language *language);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Language ** pango2_font_face_get_languages (Pango2FontFace *face);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_font_face_has_char (Pango2FontFace *face,
+ gunichar wc);
+
+
+G_END_DECLS
diff --git a/pango2/pango-font-family-private.h b/pango2/pango-font-family-private.h
new file mode 100644
index 00000000..2c2c28fa
--- /dev/null
+++ b/pango2/pango-font-family-private.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-font-family.h"
+
+
+typedef struct _Pango2FontFamilyClass Pango2FontFamilyClass;
+
+struct _Pango2FontFamily
+{
+ GObject parent_instance;
+
+ Pango2FontMap *map;
+ char *name;
+};
+
+struct _Pango2FontFamilyClass
+{
+ GObjectClass parent_class;
+
+ Pango2FontFace * (* get_face) (Pango2FontFamily *family,
+ const char *name);
+};
+
+#define PANGO2_FONT_FAMILY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO2_TYPE_FONT_FAMILY, Pango2FontFamilyClass))
+#define PANGO2_FONT_FAMILY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO2_TYPE_FONT_FAMILY, Pango2FontFamilyClass))
+
+static inline void
+pango2_font_family_set_name (Pango2FontFamily *family,
+ const char *name)
+{
+ family->name = g_strdup (name);
+}
+
+static inline void
+pango2_font_family_set_font_map (Pango2FontFamily *family,
+ Pango2FontMap *map)
+{
+ if (family->map)
+ g_object_remove_weak_pointer (G_OBJECT (family->map),
+ (gpointer *)&family->map);
+
+ family->map = map;
+
+ if (family->map)
+ g_object_add_weak_pointer (G_OBJECT (family->map),
+ (gpointer *)&family->map);
+}
diff --git a/pango2/pango-font-family.c b/pango2/pango-font-family.c
new file mode 100644
index 00000000..1042c7d3
--- /dev/null
+++ b/pango2/pango-font-family.c
@@ -0,0 +1,267 @@
+/* Pango2
+ *
+ * Copyright (C) 1999 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.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "pango-font-family-private.h"
+#include "pango-font-face.h"
+#include "pango-font.h"
+
+/**
+ * Pango2FontFamily:
+ *
+ * A `Pango2FontFamily` is used to represent a family of related
+ * font faces.
+ *
+ * The font faces in a family share a common design, but differ in
+ * slant, weight, width or other aspects.
+ *
+ * `Pango2FontFamily` implements the [iface@Gio.ListModel] interface,
+ * to provide a list of font faces.
+ */
+
+/* {{{ GListModel implementation */
+
+static GType
+pango2_font_family_get_item_type (GListModel *list)
+{
+ return PANGO2_TYPE_FONT_FACE;
+}
+
+static guint
+pango2_font_family_get_n_items (GListModel *list)
+{
+ g_assert_not_reached ();
+ return 0;
+}
+
+static gpointer
+pango2_font_family_get_item (GListModel *list,
+ guint position)
+{
+ g_assert_not_reached ();
+ return NULL;
+}
+
+static void
+pango2_font_family_list_model_init (GListModelInterface *iface)
+{
+ iface->get_item_type = pango2_font_family_get_item_type;
+ iface->get_n_items = pango2_font_family_get_n_items;
+ iface->get_item = pango2_font_family_get_item;
+}
+
+/* }}} */
+/* {{{ Pango2FontFamily implementation */
+
+enum {
+ PROP_NAME = 1,
+ PROP_ITEM_TYPE,
+ PROP_N_ITEMS,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (Pango2FontFamily, pango2_font_family, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, pango2_font_family_list_model_init))
+
+static Pango2FontFace *
+pango2_font_family_real_get_face (Pango2FontFamily *family,
+ const char *name)
+{
+ Pango2FontFace *face;
+
+ face = NULL;
+
+ for (int i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (family)); i++)
+ {
+ Pango2FontFace *f = g_list_model_get_item (G_LIST_MODEL (family), i);
+ g_object_unref (f);
+ if (name == NULL ||
+ strcmp (name, pango2_font_face_get_name (f)) == 0)
+ {
+ face = f;
+ break;
+ }
+ }
+
+ return face;
+}
+
+static void
+pango2_font_family_finalize (GObject *object)
+{
+ Pango2FontFamily *family = PANGO2_FONT_FAMILY (object);
+
+ g_free (family->name);
+ if (family->map)
+ g_object_remove_weak_pointer (G_OBJECT (family->map), (gpointer *)&family->map);
+
+ G_OBJECT_CLASS (pango2_font_family_parent_class)->finalize (object);
+}
+
+static void
+pango2_font_family_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2FontFamily *family = PANGO2_FONT_FAMILY (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, family->name);
+ break;
+
+ case PROP_ITEM_TYPE:
+ g_value_set_gtype (value, PANGO2_TYPE_FONT);
+ break;
+
+ case PROP_N_ITEMS:
+ g_value_set_uint (value, pango2_font_family_get_n_items (G_LIST_MODEL (object)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+pango2_font_family_class_init (Pango2FontFamilyClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = pango2_font_family_finalize;
+ object_class->get_property = pango2_font_family_get_property;
+
+ class->get_face = pango2_font_family_real_get_face;
+
+ /**
+ * Pango2FontFamily:name: (attributes org.gtk.Property.get=pango2_font_family_get_name)
+ *
+ * The name of the family.
+ */
+ properties[PROP_NAME] =
+ g_param_spec_string ("name", NULL, NULL, NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2FontFamily:item-type:
+ *
+ * The type of objects that the family contains.
+ */
+ properties[PROP_ITEM_TYPE] =
+ g_param_spec_gtype ("item-type", NULL, NULL, PANGO2_TYPE_FONT_FACE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2FontFamily:n-items:
+ *
+ * The number of faces contained in the family.
+ */
+ properties[PROP_N_ITEMS] =
+ g_param_spec_uint ("n-items", "", "", 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+static void
+pango2_font_family_init (Pango2FontFamily *family G_GNUC_UNUSED)
+{
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_font_family_get_font_map:
+ * @family: a `Pango2FontFamily`
+ *
+ * Returns the `Pango2FontMap that @family belongs to.
+ *
+ * Note that the family maintains a *weak* reference to
+ * the font map, so if all references to font map are
+ * dropped, the font map will be finalized even if there
+ * are fonts created with the font map that are still alive.
+ * In that case this function will return %NULL.
+ *
+ * It is the responsibility of the user to ensure that the
+ * font map is kept alive. In most uses this is not an issue
+ * as a `Pango2Context` holds a reference to the font map.
+
+ *
+ * Return value: (transfer none) (nullable): the `Pango2FontMap
+ */
+Pango2FontMap *
+pango2_font_family_get_font_map (Pango2FontFamily *family)
+{
+ return family->map;
+}
+
+/**
+ * pango2_font_family_get_name:
+ * @family: a `Pango2FontFamily`
+ *
+ * Gets the name of the family.
+ *
+ * The name is unique among all fonts for the font backend and can
+ * be used in a `Pango2FontDescription` to specify that a face from
+ * this family is desired.
+ *
+ * Return value: the name of the family. This string is owned
+ * by the family object and must not be modified or freed.
+ */
+const char *
+pango2_font_family_get_name (Pango2FontFamily *family)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FAMILY (family), NULL);
+
+ return family->name;
+}
+
+/**
+ * pango2_font_family_get_face:
+ * @family: a `Pango2FontFamily`
+ * @name: (nullable): the name of a face. If the name is `NULL`,
+ * the family's default face (fontconfig calls it "Regular")
+ * will be returned.
+ *
+ * Gets the `Pango2FontFace` of the family with the given name.
+ *
+ * Returns: (transfer none) (nullable): the `Pango2FontFace`,
+ * or `NULL` if no face with the given name exists.
+ */
+Pango2FontFace *
+pango2_font_family_get_face (Pango2FontFamily *family,
+ const char *name)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_FAMILY (family), NULL);
+
+ return PANGO2_FONT_FAMILY_GET_CLASS (family)->get_face (family, name);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-font-family.h b/pango2/pango-font-family.h
new file mode 100644
index 00000000..f6896530
--- /dev/null
+++ b/pango2/pango-font-family.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+
+#define PANGO2_TYPE_FONT_FAMILY (pango2_font_family_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2FontFamily, pango2_font_family, PANGO2, FONT_FAMILY, GObject)
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontMap * pango2_font_family_get_font_map (Pango2FontFamily *family);
+
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_font_family_get_name (Pango2FontFamily *family) G_GNUC_PURE;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontFace * pango2_font_family_get_face (Pango2FontFamily *family,
+ const char *name);
+
+G_END_DECLS
diff --git a/pango2/pango-font-metrics-private.h b/pango2/pango-font-metrics-private.h
new file mode 100644
index 00000000..3ad27a3a
--- /dev/null
+++ b/pango2/pango-font-metrics-private.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-font-metrics.h"
+
+struct _Pango2FontMetrics
+{
+ /* <private> */
+ guint ref_count;
+
+ int ascent;
+ int descent;
+ int height;
+ int approximate_char_width;
+ int approximate_digit_width;
+ int underline_position;
+ int underline_thickness;
+ int strikethrough_position;
+ int strikethrough_thickness;
+};
+
+Pango2FontMetrics *pango2_font_metrics_new (void);
diff --git a/pango2/pango-font-metrics.c b/pango2/pango-font-metrics.c
new file mode 100644
index 00000000..d2b61d44
--- /dev/null
+++ b/pango2/pango-font-metrics.c
@@ -0,0 +1,249 @@
+/* Pango2
+ * pango-font-metrics.c:
+ *
+ * Copyright (C) 1999 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.
+ */
+
+#include "config.h"
+
+#include "pango-font-metrics-private.h"
+
+
+G_DEFINE_BOXED_TYPE (Pango2FontMetrics, pango2_font_metrics,
+ pango2_font_metrics_copy,
+ pango2_font_metrics_free);
+
+/**
+ * pango2_font_metrics_new:
+ *
+ * Creates a new `Pango2FontMetrics` structure.
+ *
+ * This is only for internal use by Pango2 backends and there is
+ * no public way to set the fields of the structure.
+ *
+ * Return value: a newly-created `Pango2FontMetrics` structure
+ */
+Pango2FontMetrics *
+pango2_font_metrics_new (void)
+{
+ Pango2FontMetrics *metrics = g_slice_new0 (Pango2FontMetrics);
+
+ return metrics;
+}
+
+/**
+ * pango2_font_metrics_copy:
+ * @metrics: (nullable): a `Pango2FontMetrics` structure, may be %NULL
+ *
+ * Create a copy of @metrics.
+ *
+ * Return value: (nullable): @metrics
+ */
+Pango2FontMetrics *
+pango2_font_metrics_copy (Pango2FontMetrics *metrics)
+{
+ return g_slice_dup (Pango2FontMetrics, metrics);
+}
+
+/**
+ * pango2_font_metrics_free:
+ * @metrics: (nullable): a `Pango2FontMetrics` structure, may be %NULL
+ *
+ * Free the @metrics.
+ */
+void
+pango2_font_metrics_free (Pango2FontMetrics *metrics)
+{
+ g_slice_free (Pango2FontMetrics, metrics);
+}
+
+/**
+ * pango2_font_metrics_get_ascent:
+ * @metrics: a `Pango2FontMetrics` structure
+ *
+ * Gets the ascent from a font metrics structure.
+ *
+ * The ascent is the distance from the baseline to the logical top
+ * of a line of text. (The logical top may be above or below the top
+ * of the actual drawn ink. It is necessary to lay out the text to
+ * figure where the ink will be.)
+ *
+ * Return value: the ascent, in Pango2 units.
+ */
+int
+pango2_font_metrics_get_ascent (Pango2FontMetrics *metrics)
+{
+ g_return_val_if_fail (metrics != NULL, 0);
+
+ return metrics->ascent;
+}
+
+/**
+ * pango2_font_metrics_get_descent:
+ * @metrics: a `Pango2FontMetrics` structure
+ *
+ * Gets the descent from a font metrics structure.
+ *
+ * The descent is the distance from the baseline to the logical bottom
+ * of a line of text. (The logical bottom may be above or below the
+ * bottom of the actual drawn ink. It is necessary to lay out the text
+ * to figure where the ink will be.)
+ *
+ * Return value: the descent, in Pango2 units.
+ */
+int
+pango2_font_metrics_get_descent (Pango2FontMetrics *metrics)
+{
+ g_return_val_if_fail (metrics != NULL, 0);
+
+ return metrics->descent;
+}
+
+/**
+ * pango2_font_metrics_get_height:
+ * @metrics: a `Pango2FontMetrics` structure
+ *
+ * Gets the line height from a font metrics structure.
+ *
+ * The line height is the recommended distance between successive
+ * baselines in wrapped text using this font.
+ *
+ * If the line height is not available, 0 is returned.
+ *
+ * Return value: the height, in Pango2 units
+ */
+int
+pango2_font_metrics_get_height (Pango2FontMetrics *metrics)
+{
+ g_return_val_if_fail (metrics != NULL, 0);
+
+ return metrics->height;
+}
+
+/**
+ * pango2_font_metrics_get_approximate_char_width:
+ * @metrics: a `Pango2FontMetrics` structure
+ *
+ * Gets the approximate character width for a font metrics structure.
+ *
+ * This is merely a representative value useful, for example, for
+ * determining the initial size for a window. Actual characters in
+ * text will be wider and narrower than this.
+ *
+ * Return value: the character width, in Pango2 units.
+ */
+int
+pango2_font_metrics_get_approximate_char_width (Pango2FontMetrics *metrics)
+{
+ g_return_val_if_fail (metrics != NULL, 0);
+
+ return metrics->approximate_char_width;
+}
+
+/**
+ * pango2_font_metrics_get_approximate_digit_width:
+ * @metrics: a `Pango2FontMetrics` structure
+ *
+ * Gets the approximate digit width for a font metrics structure.
+ *
+ * This is merely a representative value useful, for example, for
+ * determining the initial size for a window. Actual digits in
+ * text can be wider or narrower than this, though this value
+ * is generally somewhat more accurate than the result of
+ * pango2_font_metrics_get_approximate_char_width() for digits.
+ *
+ * Return value: the digit width, in Pango2 units.
+ */
+int
+pango2_font_metrics_get_approximate_digit_width (Pango2FontMetrics *metrics)
+{
+ g_return_val_if_fail (metrics != NULL, 0);
+
+ return metrics->approximate_digit_width;
+}
+
+/**
+ * pango2_font_metrics_get_underline_position:
+ * @metrics: a `Pango2FontMetrics` structure
+ *
+ * Gets the suggested position to draw the underline.
+ *
+ * The value returned is the distance *above* the baseline of the top
+ * of the underline. Since most fonts have underline positions beneath
+ * the baseline, this value is typically negative.
+ *
+ * Return value: the suggested underline position, in Pango2 units.
+ */
+int
+pango2_font_metrics_get_underline_position (Pango2FontMetrics *metrics)
+{
+ g_return_val_if_fail (metrics != NULL, 0);
+
+ return metrics->underline_position;
+}
+
+/**
+ * pango2_font_metrics_get_underline_thickness:
+ * @metrics: a `Pango2FontMetrics` structure
+ *
+ * Gets the suggested thickness to draw for the underline.
+ *
+ * Return value: the suggested underline thickness, in Pango2 units.
+ */
+int
+pango2_font_metrics_get_underline_thickness (Pango2FontMetrics *metrics)
+{
+ g_return_val_if_fail (metrics != NULL, 0);
+
+ return metrics->underline_thickness;
+}
+
+/**
+ * pango2_font_metrics_get_strikethrough_position:
+ * @metrics: a `Pango2FontMetrics` structure
+ *
+ * Gets the suggested position to draw the strikethrough.
+ *
+ * The value returned is the distance *above* the
+ * baseline of the top of the strikethrough.
+ *
+ * Return value: the suggested strikethrough position, in Pango2 units.
+ */
+int
+pango2_font_metrics_get_strikethrough_position (Pango2FontMetrics *metrics)
+{
+ g_return_val_if_fail (metrics != NULL, 0);
+
+ return metrics->strikethrough_position;
+}
+
+/**
+ * pango2_font_metrics_get_strikethrough_thickness:
+ * @metrics: a `Pango2FontMetrics` structure
+ *
+ * Gets the suggested thickness to draw for the strikethrough.
+ *
+ * Return value: the suggested strikethrough thickness, in Pango2 units.
+ */
+int
+pango2_font_metrics_get_strikethrough_thickness (Pango2FontMetrics *metrics)
+{
+ g_return_val_if_fail (metrics != NULL, 0);
+
+ return metrics->strikethrough_thickness;
+}
diff --git a/pango2/pango-font-metrics.h b/pango2/pango-font-metrics.h
new file mode 100644
index 00000000..e138be7d
--- /dev/null
+++ b/pango2/pango-font-metrics.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/**
+ * Pango2FontMetrics:
+ *
+ * A `Pango2FontMetrics` structure holds the overall metric information
+ * for a font.
+ *
+ * The information in a `Pango2FontMetrics` structure may be restricted
+ * to a script. The fields of this structure are private to implementations
+ * of a font backend. See the documentation of the corresponding getters
+ * for documentation of their meaning.
+ *
+ * For an overview of the most important metrics, see:
+ *
+ * <picture>
+ * <source srcset="fontmetrics-dark.png" media="(prefers-color-scheme: dark)">
+ * <img alt="Font metrics" src="fontmetrics-light.png">
+ * </picture>
+
+ */
+typedef struct _Pango2FontMetrics Pango2FontMetrics;
+
+#define PANGO2_TYPE_FONT_METRICS (pango2_font_metrics_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_font_metrics_get_type (void) G_GNUC_CONST;
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontMetrics *pango2_font_metrics_copy (Pango2FontMetrics *metrics);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_metrics_free (Pango2FontMetrics *metrics);
+PANGO2_AVAILABLE_IN_ALL
+int pango2_font_metrics_get_ascent (Pango2FontMetrics *metrics) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+int pango2_font_metrics_get_descent (Pango2FontMetrics *metrics) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+int pango2_font_metrics_get_height (Pango2FontMetrics *metrics) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+int pango2_font_metrics_get_approximate_char_width (Pango2FontMetrics *metrics) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+int pango2_font_metrics_get_approximate_digit_width (Pango2FontMetrics *metrics) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+int pango2_font_metrics_get_underline_position (Pango2FontMetrics *metrics) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+int pango2_font_metrics_get_underline_thickness (Pango2FontMetrics *metrics) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+int pango2_font_metrics_get_strikethrough_position (Pango2FontMetrics *metrics) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+int pango2_font_metrics_get_strikethrough_thickness (Pango2FontMetrics *metrics) G_GNUC_PURE;
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2FontMetrics, pango2_font_metrics_free)
+
+G_END_DECLS
diff --git a/pango2/pango-font-private.h b/pango2/pango-font-private.h
new file mode 100644
index 00000000..ca8d3a77
--- /dev/null
+++ b/pango2/pango-font-private.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-font-family.h"
+#include "pango-font.h"
+#include "pango-types.h"
+
+#include <glib-object.h>
+
+#ifdef HAVE_CAIRO
+#include <cairo.h>
+#endif
+
+struct _Pango2Font
+{
+ GObject parent_instance;
+
+ Pango2FontFace *face;
+
+ hb_font_t *hb_font;
+
+ int size; /* point size, scaled by PANGO2_SCALE */
+ float dpi;
+ Pango2Gravity gravity;
+ Pango2Matrix ctm;
+
+#ifdef HAVE_CAIRO
+ cairo_font_options_t *options;
+#endif
+};
+
+typedef struct _Pango2FontClass Pango2FontClass;
+struct _Pango2FontClass
+{
+ GObjectClass parent_class;
+
+ Pango2FontDescription * (* describe) (Pango2Font *font);
+ void (* get_glyph_extents) (Pango2Font *font,
+ Pango2Glyph glyph,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect);
+ Pango2FontMetrics * (* get_metrics) (Pango2Font *font,
+ Pango2Language *language);
+ hb_font_t * (* create_hb_font) (Pango2Font *font);
+ gboolean (* is_hinted) (Pango2Font *font);
+ void (* get_scale_factors) (Pango2Font *font,
+ double *x_scale,
+ double *y_scale);
+ void (* get_transform) (Pango2Font *font,
+ Pango2Matrix *matrix);
+};
+
+#define PANGO2_FONT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO2_TYPE_FONT, Pango2FontClass))
+#define PANGO2_FONT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO2_TYPE_FONT, Pango2FontClass))
+
+static inline void
+pango2_font_set_face (Pango2Font *font,
+ Pango2FontFace *face)
+{
+ font->face = (Pango2FontFace *) g_object_ref (face);
+}
+
+static inline void
+pango2_font_set_size (Pango2Font *font,
+ int size)
+{
+ font->size = size;
+}
+
+static inline void
+pango2_font_set_dpi (Pango2Font *font,
+ float dpi)
+{
+ font->dpi = dpi;
+}
+
+static inline void
+pango2_font_set_gravity (Pango2Font *font,
+ Pango2Gravity gravity)
+{
+ font->gravity = gravity;
+}
+
+static inline void
+pango2_font_set_ctm (Pango2Font *font,
+ const Pango2Matrix *ctm)
+{
+ const Pango2Matrix matrix_init = PANGO2_MATRIX_INIT;
+ font->ctm = ctm ? *ctm : matrix_init;
+}
+
+gboolean pango2_font_is_hinted (Pango2Font *font);
+void pango2_font_get_scale_factors (Pango2Font *font,
+ double *x_scale,
+ double *y_scale);
+void pango2_font_get_transform (Pango2Font *font,
+ Pango2Matrix *matrix);
+
+gboolean pango2_font_description_is_similar (const Pango2FontDescription *a,
+ const Pango2FontDescription *b);
+
+int pango2_font_description_compute_distance (const Pango2FontDescription *a,
+ const Pango2FontDescription *b);
+
+/* We use these values in a few places as a fallback size for an
+ * unknown glyph, if we have no better information.
+ */
+
+#define PANGO2_UNKNOWN_GLYPH_WIDTH 10
+#define PANGO2_UNKNOWN_GLYPH_HEIGHT 14
diff --git a/pango2/pango-font.c b/pango2/pango-font.c
new file mode 100644
index 00000000..cdf2fc42
--- /dev/null
+++ b/pango2/pango-font.c
@@ -0,0 +1,478 @@
+/* Pango2
+ * fonts.c:
+ *
+ * Copyright (C) 1999 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.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+#include <gio/gio.h>
+
+#include "pango-types.h"
+#include "pango-font-private.h"
+#include "pango-font-metrics-private.h"
+#include "pango-fontmap-private.h"
+#include "pango-impl-utils.h"
+
+#include <hb-gobject.h>
+
+/**
+ * Pango2Font:
+ *
+ * A `Pango2Font` is used to represent a font in a
+ * rendering-system-independent manner.
+ */
+
+/* }}} */
+/* {{{ Pango2Font implementation */
+
+enum {
+ PROP_FACE = 1,
+ PROP_HB_FONT,
+ PROP_SIZE,
+ PROP_DPI,
+ PROP_GRAVITY,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+G_DEFINE_ABSTRACT_TYPE (Pango2Font, pango2_font, G_TYPE_OBJECT)
+
+static void
+pango2_font_finalize (GObject *object)
+{
+ Pango2Font *font = PANGO2_FONT (object);
+
+ g_object_unref (font->face);
+ hb_font_destroy (font->hb_font);
+
+ G_OBJECT_CLASS (pango2_font_parent_class)->finalize (object);
+}
+
+static void
+pango2_font_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2Font *font = PANGO2_FONT (object);
+
+ switch (property_id)
+ {
+ case PROP_FACE:
+ g_value_set_object (value, font->face);
+ break;
+
+ case PROP_HB_FONT:
+ g_value_set_boxed (value, pango2_font_get_hb_font (font));
+ break;
+
+ case PROP_SIZE:
+ g_value_set_int (value, font->size);
+ break;
+
+ case PROP_DPI:
+ g_value_set_float (value, font->dpi);
+ break;
+
+ case PROP_GRAVITY:
+ g_value_set_enum (value, font->gravity);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static gboolean
+pango2_font_default_is_hinted (Pango2Font *font)
+{
+ return FALSE;
+}
+
+static void
+pango2_font_default_get_scale_factors (Pango2Font *font,
+ double *x_scale,
+ double *y_scale)
+{
+ *x_scale = *y_scale = 1.0;
+}
+
+static void
+pango2_font_default_get_transform (Pango2Font *font,
+ Pango2Matrix *matrix)
+{
+ *matrix = (Pango2Matrix) PANGO2_MATRIX_INIT;
+}
+
+static void
+pango2_font_class_init (Pango2FontClass *class G_GNUC_UNUSED)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = pango2_font_finalize;
+ object_class->get_property = pango2_font_get_property;
+
+ class->is_hinted = pango2_font_default_is_hinted;
+ class->get_scale_factors = pango2_font_default_get_scale_factors;
+ class->get_transform = pango2_font_default_get_transform;
+
+ /**
+ * Pango2Font:face: (attributes org.gtk.Property.get=pango2_font_get_face)
+ *
+ * The face to which the font belongs.
+ */
+ properties[PROP_FACE] =
+ g_param_spec_object ("face", NULL, NULL, PANGO2_TYPE_FONT_FACE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Font:hb-font: (attributes org.gtk.Property.get=pango2_font_get_hb_font)
+ *
+ * A `hb_font_t` object backing this font.
+ */
+ properties[PROP_HB_FONT] =
+ g_param_spec_boxed ("hb-font", NULL, NULL, HB_GOBJECT_TYPE_FONT,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Font:size: (attributes org.gtk.Property.get=pango2_font_get_size)
+ *
+ * The size of the font, scaled by `PANGO2_SCALE`.
+ */
+ properties[PROP_SIZE] =
+ g_param_spec_int ("size", NULL, NULL, 0, G_MAXINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Font:dpi:
+ *
+ * The resolution at which the font is rendered.
+ *
+ * The pixel size of the font is computed as
+ *
+ * size * dpi / 72.
+ */
+ properties[PROP_DPI] =
+ g_param_spec_float ("dpi", NULL, NULL, 0, G_MAXFLOAT, 96.0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Font:gravity: (attributes org.gtk.Property.get=pango2_font_get_gravity)
+ *
+ * The gravity of the font.
+ */
+ properties[PROP_GRAVITY] =
+ g_param_spec_enum ("gravity", NULL, NULL, PANGO2_TYPE_GRAVITY,
+ PANGO2_GRAVITY_AUTO,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+static void
+pango2_font_init (Pango2Font *font)
+{
+ font->gravity = PANGO2_GRAVITY_AUTO;
+ font->ctm = (Pango2Matrix) PANGO2_MATRIX_INIT;
+}
+
+/* }}} */
+/* {{{ Private API */
+
+/*< private >
+ * pango2_font_get_transform:
+ * @font: a `Pango2Font`
+ * @matrix: the matrix to fill in
+ *
+ * Gets the matrix for the transformation from 'font space' to 'user space'.
+ */
+void
+pango2_font_get_transform (Pango2Font *font,
+ Pango2Matrix *matrix)
+{
+ PANGO2_FONT_GET_CLASS (font)->get_transform (font, matrix);
+}
+
+/*< private >
+ * pango2_font_is_hinted:
+ * @font: a `Pango2Font`
+ *
+ * Gets whether this font is hinted.
+ *
+ * Returns: %TRUE if @font is hinted
+ */
+gboolean
+pango2_font_is_hinted (Pango2Font *font)
+{
+ return PANGO2_FONT_GET_CLASS (font)->is_hinted (font);
+}
+
+/*< private >
+ * pango2_font_get_scale_factors:
+ * @font: a `Pango2Font`
+ * @x_scale: return location for X scale
+ * @y_scale: return location for Y scale
+ *
+ * Gets the font scale factors of the ctm for this font.
+ *
+ * The ctm is the matrix set on the context that this font was
+ * loaded for.
+ */
+void
+pango2_font_get_scale_factors (Pango2Font *font,
+ double *x_scale,
+ double *y_scale)
+{
+ PANGO2_FONT_GET_CLASS (font)->get_scale_factors (font, x_scale, y_scale);
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_font_describe:
+ * @font: a `Pango2Font`
+ *
+ * Returns a description of the font, with font size set in points.
+ *
+ * Use [method@Pango2.Font.describe_with_absolute_size] if you want
+ * the font size in device units.
+ *
+ * Return value: a newly-allocated `Pango2FontDescription` object.
+ */
+Pango2FontDescription *
+pango2_font_describe (Pango2Font *font)
+{
+ g_return_val_if_fail (font != NULL, NULL);
+
+ return PANGO2_FONT_GET_CLASS (font)->describe (font);
+}
+
+/**
+ * pango2_font_describe_with_absolute_size:
+ * @font: a `Pango2Font`
+ *
+ * Returns a description of the font, with absolute font size set
+ * in device units.
+ *
+ * Use [method@Pango2.Font.describe] if you want the font size in points.
+ *
+ * Return value: a newly-allocated `Pango2FontDescription` object.
+ */
+Pango2FontDescription *
+pango2_font_describe_with_absolute_size (Pango2Font *font)
+{
+ Pango2FontDescription *desc;
+
+ g_return_val_if_fail (font != NULL, NULL);
+
+ desc = pango2_font_describe (font);
+ pango2_font_description_set_absolute_size (desc, font->size * font->dpi / 72.);
+
+ return desc;
+}
+
+/**
+ * pango2_font_get_glyph_extents:
+ * @font: (nullable): a `Pango2Font`
+ * @glyph: the glyph index
+ * @ink_rect: (out) (optional): rectangle used to store the extents of the glyph as drawn
+ * @logical_rect: (out) (optional): rectangle used to store the logical extents of the glyph
+ *
+ * Gets the logical and ink extents of a glyph within a font.
+ *
+ * The coordinate system for each rectangle has its origin at the
+ * base line and horizontal origin of the character with increasing
+ * coordinates extending to the right and down. The macros PANGO2_ASCENT(),
+ * PANGO2_DESCENT(), PANGO2_LBEARING(), and PANGO2_RBEARING() can be used to convert
+ * from the extents rectangle to more traditional font metrics. The units
+ * of the rectangles are in 1/PANGO2_SCALE of a device unit.
+ *
+ * If @font is %NULL, this function gracefully sets some sane values in the
+ * output variables and returns.
+ */
+void
+pango2_font_get_glyph_extents (Pango2Font *font,
+ Pango2Glyph glyph,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ if (G_UNLIKELY (!font))
+ {
+ if (ink_rect)
+ {
+ ink_rect->x = PANGO2_SCALE;
+ ink_rect->y = - (PANGO2_UNKNOWN_GLYPH_HEIGHT - 1) * PANGO2_SCALE;
+ ink_rect->height = (PANGO2_UNKNOWN_GLYPH_HEIGHT - 2) * PANGO2_SCALE;
+ ink_rect->width = (PANGO2_UNKNOWN_GLYPH_WIDTH - 2) * PANGO2_SCALE;
+ }
+ if (logical_rect)
+ {
+ logical_rect->x = 0;
+ logical_rect->y = - PANGO2_UNKNOWN_GLYPH_HEIGHT * PANGO2_SCALE;
+ logical_rect->height = PANGO2_UNKNOWN_GLYPH_HEIGHT * PANGO2_SCALE;
+ logical_rect->width = PANGO2_UNKNOWN_GLYPH_WIDTH * PANGO2_SCALE;
+ }
+ return;
+ }
+
+ PANGO2_FONT_GET_CLASS (font)->get_glyph_extents (font, glyph, ink_rect, logical_rect);
+}
+
+/**
+ * pango2_font_get_metrics:
+ * @font: (nullable): a `Pango2Font`
+ * @language: (nullable): language tag used to determine which script
+ * to get the metrics for, or %NULL to indicate to get the metrics for
+ * the entire font.
+ *
+ * Gets overall metric information for a font.
+ *
+ * Since the metrics may be substantially different for different scripts,
+ * a language tag can be provided to indicate that the metrics should be
+ * retrieved that correspond to the script(s) used by that language.
+ *
+ * If @font is %NULL, this function gracefully sets some sane values in the
+ * output variables and returns.
+ *
+ * Return value: a `Pango2FontMetrics` object. The caller must call
+ * [method@Pango2.FontMetrics.free] when finished using the object.
+ */
+Pango2FontMetrics *
+pango2_font_get_metrics (Pango2Font *font,
+ Pango2Language *language)
+{
+ if (G_UNLIKELY (!font))
+ {
+ Pango2FontMetrics *metrics = pango2_font_metrics_new ();
+
+ metrics->ascent = PANGO2_SCALE * PANGO2_UNKNOWN_GLYPH_HEIGHT;
+ metrics->descent = 0;
+ metrics->height = 0;
+ metrics->approximate_char_width = PANGO2_SCALE * PANGO2_UNKNOWN_GLYPH_WIDTH;
+ metrics->approximate_digit_width = PANGO2_SCALE * PANGO2_UNKNOWN_GLYPH_WIDTH;
+ metrics->underline_position = -PANGO2_SCALE;
+ metrics->underline_thickness = PANGO2_SCALE;
+ metrics->strikethrough_position = PANGO2_SCALE * PANGO2_UNKNOWN_GLYPH_HEIGHT / 2;
+ metrics->strikethrough_thickness = PANGO2_SCALE;
+
+ return metrics;
+ }
+
+ return PANGO2_FONT_GET_CLASS (font)->get_metrics (font, language);
+}
+
+/**
+ * pango2_font_get_face:
+ * @font: a `Pango2Font`
+ *
+ * Gets the `Pango2FontFace` to which the font belongs.
+ *
+ * Returns: (transfer none): the `Pango2FontFace`
+ */
+Pango2FontFace *
+pango2_font_get_face (Pango2Font *font)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT (font), NULL);
+
+ return font->face;
+}
+
+/**
+ * pango2_font_get_hb_font:
+ * @font: a `Pango2Font`
+ *
+ * Get a `hb_font_t` object backing this font.
+ *
+ * Note that the objects returned by this function are cached
+ * and immutable. If you need to make changes to the `hb_font_t`,
+ * use [hb_font_create_sub_font()](https://harfbuzz.github.io/harfbuzz-hb-font.html#hb-font-create-sub-font).
+ *
+ * Returns: (transfer none) (nullable): the `hb_font_t` object
+ * backing the font
+ */
+hb_font_t *
+pango2_font_get_hb_font (Pango2Font *font)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT (font), NULL);
+
+ if (!font->hb_font)
+ {
+ font->hb_font = PANGO2_FONT_GET_CLASS (font)->create_hb_font (font);
+ hb_font_make_immutable (font->hb_font);
+ }
+
+ return font->hb_font;
+}
+
+/**
+ * pango2_font_get_size:
+ * @font: a `Pango2Font`
+ *
+ * Returns the size of the font, scaled by `PANGO2_SCALE`.
+ *
+ * Return value: the size of the font
+ */
+int
+pango2_font_get_size (Pango2Font *font)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT (font), 0);
+
+ return font->size;
+}
+
+/**
+ * pango2_font_get_absolute_size:
+ * @font: a `Pango2Font`
+ *
+ * Returns the pixel size of the font, scaled by `PANGO2_SCALE`.
+ *
+ * Return value: the pixel size of the font
+ */
+double
+pango2_font_get_absolute_size (Pango2Font *font)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT (font), 0.);
+
+ return font->size * font->dpi / 72.;
+}
+
+/**
+ * pango2_font_get_gravity:
+ * @font: a `Pango2Font`
+ *
+ * Returns the gravity of the font.
+ *
+ * Return value: the gravity of the font
+ */
+Pango2Gravity
+pango2_font_get_gravity (Pango2Font *font)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT (font), PANGO2_GRAVITY_SOUTH);
+
+ return font->gravity;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-font.h b/pango2/pango-font.h
new file mode 100644
index 00000000..63e98351
--- /dev/null
+++ b/pango2/pango-font.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-font-description.h>
+#include <pango2/pango-font-metrics.h>
+#include <pango2/pango-font-family.h>
+
+#include <glib-object.h>
+#include <hb.h>
+
+G_BEGIN_DECLS
+
+
+#define PANGO2_TYPE_FONT (pango2_font_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2Font, pango2_font, PANGO2, FONT, GObject)
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontDescription * pango2_font_describe (Pango2Font *font);
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontDescription * pango2_font_describe_with_absolute_size (Pango2Font *font);
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontMetrics * pango2_font_get_metrics (Pango2Font *font,
+ Pango2Language *language);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_get_glyph_extents (Pango2Font *font,
+ Pango2Glyph glyph,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontFace * pango2_font_get_face (Pango2Font *font);
+
+PANGO2_AVAILABLE_IN_ALL
+hb_font_t * pango2_font_get_hb_font (Pango2Font *font);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_font_get_size (Pango2Font *font);
+
+PANGO2_AVAILABLE_IN_ALL
+double pango2_font_get_absolute_size (Pango2Font *font);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Gravity pango2_font_get_gravity (Pango2Font *font);
+
+PANGO2_AVAILABLE_IN_ALL
+GBytes * pango2_font_serialize (Pango2Font *font);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Font * pango2_font_deserialize (Pango2Context *context,
+ GBytes *bytes,
+ GError **error);
+
+G_END_DECLS
diff --git a/pango2/pango-fontmap-private.h b/pango2/pango-fontmap-private.h
new file mode 100644
index 00000000..b8388836
--- /dev/null
+++ b/pango2/pango-fontmap-private.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-fontmap.h"
+#include "pango-hbfamily-private.h"
+#include "pango-fontmap-private.h"
+
+
+G_BEGIN_DECLS
+
+struct _Pango2FontMap
+{
+ GObject parent_instance;
+
+ GPtrArray *added_faces;
+ GPtrArray *added_families;
+ GHashTable *families_hash;
+ GPtrArray *families;
+ GHashTable *fontsets;
+ GQueue fontset_cache;
+ Pango2FontMap *fallback;
+
+ float dpi;
+ gboolean in_populate;
+ guint serial;
+};
+
+/**
+ * Pango2FontMapClass:
+ * @populate: Subclasses should call pango2_font_map_add_face to populate
+ * the map with faces and families in this vfunc.
+ */
+struct _Pango2FontMapClass
+{
+ GObjectClass parent_class;
+
+ Pango2Font * (* load_font) (Pango2FontMap *self,
+ Pango2Context *context,
+ const Pango2FontDescription *desc);
+ Pango2Fontset * (* load_fontset) (Pango2FontMap *self,
+ Pango2Context *context,
+ const Pango2FontDescription *desc,
+ Pango2Language *language);
+ guint (* get_serial) (Pango2FontMap *self);
+ void (* changed) (Pango2FontMap *self);
+ Pango2FontFamily * (* get_family) (Pango2FontMap *self,
+ const char *name);
+ void (* populate) (Pango2FontMap *self);
+};
+
+void pango2_font_map_repopulate (Pango2FontMap *self,
+ gboolean add_synthetic);
+
+void pango2_font_map_changed (Pango2FontMap *self);
+
+G_END_DECLS
diff --git a/pango2/pango-fontmap.c b/pango2/pango-fontmap.c
new file mode 100644
index 00000000..fd87c9d0
--- /dev/null
+++ b/pango2/pango-fontmap.c
@@ -0,0 +1,1294 @@
+/* Pango2
+ *
+ * Copyright (C) 2021 Matthias Clasen
+ *
+ * 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.
+ */
+
+#include "config.h"
+#include <math.h>
+
+#include <gio/gio.h>
+
+#include "pango-fontmap-private.h"
+#include "pango-hbfamily-private.h"
+#include "pango-generic-family-private.h"
+#include "pango-hbface-private.h"
+#include "pango-hbfont-private.h"
+#include "pango-fontset-cached-private.h"
+#include "pango-userface-private.h"
+#include "pango-userfont-private.h"
+#include "pango-fontset.h"
+#include "pango-font-face-private.h"
+#include "pango-trace-private.h"
+#include "pango-context.h"
+
+#ifdef HAVE_CORE_TEXT
+#include "pangocoretext-fontmap.h"
+#endif
+
+#ifdef HAVE_DIRECT_WRITE
+#include "pangodwrite-fontmap.h"
+#endif
+
+#ifdef HAVE_FONTCONFIG
+#include "pangofc-fontmap.h"
+#endif
+
+#ifdef HAVE_CAIRO
+#include <cairo.h>
+#include <pangocairo-private.h>
+#endif
+
+#include <hb-ot.h>
+
+
+/**
+ * Pango2FontMap:
+ *
+ * `Pango2FontMap` is the base class for font enumeration.
+ * It also handles caching and lookup of faces and fonts.
+ * To obtain fonts from a `Pango2FontMap`, use [method@Pango2.FontMap.load_font]
+ * or [method@Pango2.FontMap.load_fontset].
+ *
+ * Subclasses populate the fontmap using backend-specific APIs
+ * to enumerate the available fonts on the sytem, but it is
+ * also possible to create an instance of `Pango2FontMap` and
+ * populate it manually using [method@Pango2.FontMap.add_file]
+ * and [method@Pango2.FontMap.add_face].
+ *
+ * Fontmaps can be combined using [method@Pango2.FontMap.set_fallback].
+ * This can be useful to add custom fonts to the default fonts
+ * without making them available to every user of the default
+ * fontmap.
+ *
+ * Note that to be fully functional, a fontmap needs to provide
+ * generic families for monospace and sans-serif. These can
+ * be added using [method@Pango2.FontMap.add_family] and
+ * [ctor@Pango2.GenericFamily.new].
+ *
+ * `Pango2FontMap` implements the [iface@Gio.ListModel] interface,
+ * to provide a list of font families.
+ */
+
+
+/* The central api is load_fontset, which takes a font description
+ * and language, finds the matching faces, and creates a Pango2Fontset
+ * for them. To speed this operation up, the font map maintains a
+ * cache of fontsets (in the form of a GQueue) and has a hash table
+ * for looking up existing fontsets.
+ *
+ * The Pango2FontsetCached object is the fontset subclass that is used
+ * here, and it contains the necessary data for the cashing and hashing.
+ * Pango2FontsetCached also caches the character-to-font mapping that is
+ * used when itemizing text.
+ */
+
+
+/* {{{ GListModel implementation */
+
+static GType
+pango2_font_map_get_item_type (GListModel *list)
+{
+ return PANGO2_TYPE_FONT_FAMILY;
+}
+
+static guint
+pango2_font_map_get_n_items (GListModel *list)
+{
+ Pango2FontMap *self = PANGO2_FONT_MAP (list);
+
+ if (self->fallback)
+ return self->families->len + g_list_model_get_n_items (G_LIST_MODEL (self->fallback));
+ else
+ return self->families->len;
+}
+
+static gpointer
+pango2_font_map_get_item (GListModel *list,
+ guint position)
+{
+ Pango2FontMap *self = PANGO2_FONT_MAP (list);
+
+ if (position < self->families->len)
+ return g_object_ref (g_ptr_array_index (self->families, position));
+ else if (self->fallback)
+ return g_list_model_get_item (G_LIST_MODEL (self->fallback), position);
+
+ return NULL;
+}
+
+static void
+pango2_font_map_list_model_init (GListModelInterface *iface)
+{
+ iface->get_item_type = pango2_font_map_get_item_type;
+ iface->get_n_items = pango2_font_map_get_n_items;
+ iface->get_item = pango2_font_map_get_item;
+}
+
+/* }}} */
+/* {{{ Fontset caching */
+
+/* The number of fontsets we keep in the fontset cache */
+#define FONTSET_CACHE_SIZE 256
+
+#define FNV_32_PRIME ((guint32)0x01000193)
+#define FNV1_32_INIT ((guint32)0x811c9dc5)
+
+static guint32
+hash_bytes_fnv (unsigned char *buffer,
+ int len,
+ guint32 hval)
+{
+ while (len--)
+ {
+ hval *= FNV_32_PRIME;
+ hval ^= *buffer++;
+ }
+
+ return hval;
+}
+
+static guint
+pango2_fontset_cached_hash (const Pango2FontsetCached *fontset)
+{
+ guint32 hash = FNV1_32_INIT;
+
+ if (fontset->ctm)
+ hash = hash_bytes_fnv ((unsigned char *)(fontset->ctm), sizeof (double) * 4, hash);
+
+ return (hash ^
+ GPOINTER_TO_UINT (fontset->language) ^
+#ifdef HAVE_CAIRO
+ cairo_font_options_hash (fontset->font_options) ^
+#endif
+ pango2_font_description_hash (fontset->description));
+}
+
+static gboolean
+pango2_fontset_cached_equal (const Pango2FontsetCached *a,
+ const Pango2FontsetCached *b)
+{
+ return a->language == b->language &&
+#ifdef HAVE_CAIRO
+ cairo_font_options_equal (a->font_options, b->font_options) &&
+#endif
+ (a->ctm == b->ctm ||
+ (a->ctm && b->ctm && memcmp (a->ctm, b->ctm, 4 * sizeof (double)) == 0)) &&
+ pango2_font_description_equal (a->description, b->description);
+}
+
+static void
+pango2_fontset_cache (Pango2FontsetCached *fontset,
+ Pango2FontMap *self)
+{
+ GQueue *cache = &self->fontset_cache;
+ GList *link = &fontset->cache_link;
+
+ if (link->data == fontset)
+ {
+ /* Already in cache, move to head */
+ if (link == cache->head)
+ return;
+
+ g_queue_unlink (cache, link);
+ }
+ else
+ {
+ /* Not in cache yet. Make room... */
+ if (cache->length == FONTSET_CACHE_SIZE)
+ {
+ GList *old = g_queue_pop_tail_link (cache);
+ g_hash_table_remove (self->fontsets, old->data);
+ old->data = NULL;
+ }
+ }
+
+ link->data = fontset;
+ link->prev = NULL;
+ link->next = NULL;
+ g_queue_push_head_link (cache, link);
+}
+
+/* }}} */
+/* {{{ Utilities */
+
+static guint
+pango2_family_hash (const Pango2FontFamily *key)
+{
+ const char *p;
+ guint32 h = 5381;
+
+ for (p = (const char *)key->name; *p != '\0'; p++)
+ h = (h << 5) + h + g_ascii_tolower (*p);
+
+ return h;
+}
+
+static gboolean
+pango2_family_equal (const Pango2FontFamily *a,
+ const Pango2FontFamily *b)
+{
+ return g_ascii_strcasecmp (a->name, b->name) == 0;
+}
+
+static Pango2FontFamily *
+find_family (Pango2FontMap *self,
+ const char *family_name)
+{
+ Pango2FontFamily lookup;
+ Pango2FontFamily *family;
+
+ lookup.name = (char *)family_name;
+
+ family = PANGO2_FONT_FAMILY (g_hash_table_lookup (self->families_hash, &lookup));
+
+ return family;
+}
+
+static void
+clear_caches (Pango2FontMap *self)
+{
+ g_hash_table_remove_all (self->fontsets);
+ g_queue_init (&self->fontset_cache);
+}
+
+static void
+add_style_variation (Pango2HbFamily *family,
+ Pango2HbFace *face,
+ Pango2Style style,
+ Pango2Weight weight)
+{
+ Pango2Matrix italic_matrix = { 1, 0.2, 0, 1, 0, 0 };
+ Pango2FontDescription *desc;
+ Pango2HbFace *variation;
+
+ desc = pango2_font_description_new ();
+ pango2_font_description_set_family (desc, pango2_font_family_get_name (PANGO2_FONT_FAMILY (family)));
+ pango2_font_description_set_style (desc, style);
+ pango2_font_description_set_weight (desc, weight);
+
+ variation = pango2_hb_face_new_synthetic (face,
+ style == PANGO2_STYLE_ITALIC ? &italic_matrix : NULL,
+ weight == PANGO2_WEIGHT_BOLD,
+ NULL,
+ desc);
+ pango2_hb_family_add_face (family, PANGO2_FONT_FACE (variation));
+
+ pango2_font_description_free (desc);
+}
+
+static void
+synthesize_bold_and_italic_faces (Pango2FontMap *map)
+{
+ for (int i = 0; i < map->families->len; i++)
+ {
+ Pango2FontFamily *family = g_ptr_array_index (map->families, i);
+ Pango2FontFace *regular_face = NULL;
+ int regular_dist = G_MAXINT;
+ int bold_dist = G_MAXINT;
+ gboolean has_italic = FALSE;
+ gboolean has_bold = FALSE;
+ gboolean has_bold_italic = FALSE;
+
+ if (PANGO2_IS_GENERIC_FAMILY (family))
+ continue;
+
+ for (int j = 0; j < g_list_model_get_n_items (G_LIST_MODEL (family)); j++)
+ {
+ Pango2FontFace *face = g_list_model_get_item (G_LIST_MODEL (family), j);
+ int weight;
+ Pango2Style style;
+ int dist;
+
+ if (!PANGO2_IS_HB_FACE (face))
+ continue;
+
+ weight = pango2_font_description_get_weight (face->description);
+ style = pango2_font_description_get_style (face->description);
+
+ if (style == PANGO2_STYLE_NORMAL)
+ {
+ dist = abs (weight - (int)PANGO2_WEIGHT_NORMAL);
+
+ if (dist < regular_dist)
+ {
+ regular_dist = dist;
+ if (dist < 150)
+ regular_face = face;
+ }
+
+ dist = abs (weight - (int)PANGO2_WEIGHT_BOLD);
+
+ if (dist < bold_dist)
+ {
+ bold_dist = dist;
+ has_bold = dist < 150;
+ }
+ }
+ else
+ {
+ if (weight < PANGO2_WEIGHT_SEMIBOLD)
+ has_italic = TRUE;
+ else
+ has_bold_italic = TRUE;
+ }
+
+ g_object_unref (face);
+ }
+
+ if (regular_face)
+ {
+ if (!has_italic)
+ add_style_variation (PANGO2_HB_FAMILY (family),
+ PANGO2_HB_FACE (regular_face),
+ PANGO2_STYLE_ITALIC,
+ PANGO2_WEIGHT_NORMAL);
+
+ if (!has_bold)
+ add_style_variation (PANGO2_HB_FAMILY (family),
+ PANGO2_HB_FACE (regular_face),
+ PANGO2_STYLE_NORMAL,
+ PANGO2_WEIGHT_BOLD);
+
+ if (!has_bold_italic)
+ add_style_variation (PANGO2_HB_FAMILY (family),
+ PANGO2_HB_FACE (regular_face),
+ PANGO2_STYLE_ITALIC,
+ PANGO2_WEIGHT_BOLD);
+ }
+ }
+}
+
+/* }}} */
+/* {{{ Pango2FontMap implementation */
+
+enum {
+ PROP_RESOLUTION = 1,
+ PROP_FALLBACK,
+ PROP_ITEM_TYPE,
+ PROP_N_ITEMS,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL };
+
+G_DEFINE_TYPE_WITH_CODE (Pango2FontMap, pango2_font_map, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, pango2_font_map_list_model_init))
+
+
+static void
+pango2_font_map_init (Pango2FontMap *self)
+{
+ self->added_faces = g_ptr_array_new_with_free_func (g_object_unref);
+ self->added_families = g_ptr_array_new_with_free_func (g_object_unref);
+ self->families = g_ptr_array_new_with_free_func (g_object_unref);
+ self->families_hash = g_hash_table_new_full ((GHashFunc) pango2_family_hash,
+ (GEqualFunc) pango2_family_equal,
+ NULL,
+ NULL);
+ self->fontsets = g_hash_table_new_full ((GHashFunc) pango2_fontset_cached_hash,
+ (GEqualFunc) pango2_fontset_cached_equal,
+ NULL,
+ (GDestroyNotify) g_object_unref);
+ g_queue_init (&self->fontset_cache);
+ self->dpi = 96.;
+}
+
+static void
+pango2_font_map_finalize (GObject *object)
+{
+ Pango2FontMap *self = PANGO2_FONT_MAP (object);
+
+ g_clear_object (&self->fallback);
+ g_ptr_array_unref (self->added_faces);
+ g_ptr_array_unref (self->added_families);
+ g_hash_table_unref (self->families_hash);
+ g_ptr_array_unref (self->families);
+ g_hash_table_unref (self->fontsets);
+
+ G_OBJECT_CLASS (pango2_font_map_parent_class)->finalize (object);
+}
+
+static void
+pango2_font_map_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2FontMap *map = PANGO2_FONT_MAP (object);
+
+ switch (property_id)
+ {
+ case PROP_RESOLUTION:
+ pango2_font_map_set_resolution (map, g_value_get_float (value));
+ break;
+
+ case PROP_FALLBACK:
+ pango2_font_map_set_fallback (map, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+pango2_font_map_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2FontMap *map = PANGO2_FONT_MAP (object);
+
+ switch (property_id)
+ {
+ case PROP_RESOLUTION:
+ g_value_set_float (value, map->dpi);
+ break;
+
+ case PROP_FALLBACK:
+ g_value_set_object (value, map->fallback);
+ break;
+
+ case PROP_ITEM_TYPE:
+ g_value_set_gtype (value, PANGO2_TYPE_FONT_FAMILY);
+ break;
+
+ case PROP_N_ITEMS:
+ g_value_set_uint (value, pango2_font_map_get_n_items (G_LIST_MODEL (object)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+/* Load a font from the first matching family */
+static Pango2Font *
+pango2_font_map_default_load_font (Pango2FontMap *self,
+ Pango2Context *context,
+ const Pango2FontDescription *description)
+{
+ Pango2FontsetCached *fontset;
+ Pango2Language *language;
+ Pango2Font *font = NULL;
+
+ if (self->families->len == 0)
+ return NULL;
+
+ if (context)
+ language = pango2_context_get_language (context);
+ else
+ language = NULL;
+
+ fontset = (Pango2FontsetCached *)pango2_font_map_load_fontset (self, context, description, language);
+ if (pango2_fontset_cached_size (fontset) > 0)
+ font = pango2_fontset_cached_get_first_font (fontset);
+ g_object_unref (fontset);
+
+ return font;
+}
+
+/* Add one font for each family we find */
+static Pango2Fontset *
+pango2_font_map_default_load_fontset (Pango2FontMap *self,
+ Pango2Context *context,
+ const Pango2FontDescription *description,
+ Pango2Language *language)
+{
+ Pango2FontsetCached lookup;
+ Pango2FontsetCached *fontset;
+ const Pango2Matrix *ctm;
+ const char *family_name;
+ char **families;
+ Pango2FontDescription *copy;
+ Pango2FontFamily *family;
+ Pango2FontFace *face;
+ gboolean has_generic = FALSE;
+ Pango2Fontset *fallback;
+ gint64 before G_GNUC_UNUSED;
+
+ before = PANGO2_TRACE_CURRENT_TIME;
+
+ if (!language)
+ language = pango2_context_get_language (context);
+
+ family_name = pango2_font_description_get_family (description);
+ ctm = pango2_context_get_matrix (context);
+
+ lookup.language = language;
+ lookup.description = (Pango2FontDescription *)description;
+ lookup.ctm = ctm;
+#ifdef HAVE_CAIRO
+ lookup.font_options = (cairo_font_options_t *)pango2_cairo_context_get_merged_font_options (context);
+#endif
+ fontset = g_hash_table_lookup (self->fontsets, &lookup);
+
+ if (fontset)
+ goto done;
+
+ fontset = pango2_fontset_cached_new (description, language, self->dpi, ctm);
+#ifdef HAVE_CAIRO
+ fontset->font_options = cairo_font_options_copy (pango2_cairo_context_get_merged_font_options (context));
+#endif
+
+ if (self->families->len == 0)
+ {
+ if (self->fallback)
+ goto add_fallback;
+ else
+ {
+ g_warning ("Font map contains no fonts!!!!");
+ goto done_no_cache;
+ }
+ }
+
+ families = g_strsplit (family_name ? family_name : "", ",", -1);
+
+ /* Unset gravity and variant since Pango2HbFace does not have these fields */
+ copy = pango2_font_description_copy_static (description);
+ pango2_font_description_unset_fields (copy, PANGO2_FONT_MASK_VARIATIONS |
+ PANGO2_FONT_MASK_GRAVITY |
+ PANGO2_FONT_MASK_VARIANT);
+
+ for (int i = 0; families[i]; i++)
+ {
+ family = find_family (self, families[i]);
+ if (!family)
+ continue;
+
+ if (PANGO2_IS_GENERIC_FAMILY (family))
+ {
+ pango2_fontset_cached_add_family (fontset, PANGO2_GENERIC_FAMILY (family));
+ has_generic = TRUE;
+ }
+ else
+ {
+ face = pango2_hb_family_find_face (PANGO2_HB_FAMILY (family), copy, language, 0);
+ if (face)
+ pango2_fontset_cached_add_face (fontset, face);
+ }
+ }
+
+ g_strfreev (families);
+
+ pango2_font_description_free (copy);
+
+ /* Returning an empty fontset leads to bad outcomes.
+ *
+ * We always include a generic family in order
+ * to produce fontsets with good coverage.
+ *
+ * If we have a fallback fontmap, this is where we bring
+ * it in and just add its results to ours.
+ */
+ if (!has_generic)
+ {
+ family = find_family (self, "sans-serif");
+ if (PANGO2_IS_GENERIC_FAMILY (family))
+ pango2_fontset_cached_add_family (fontset, PANGO2_GENERIC_FAMILY (family));
+ else if (self->fallback)
+ {
+add_fallback:
+ fallback = pango2_font_map_load_fontset (self->fallback, context, description, language);
+ pango2_fontset_cached_append (fontset, PANGO2_FONTSET_CACHED (fallback));
+ g_object_unref (fallback);
+ }
+ }
+
+ g_hash_table_add (self->fontsets, fontset);
+
+done:
+ pango2_fontset_cache (fontset, self);
+
+ if (pango2_fontset_cached_size (fontset) == 0)
+ {
+ char *s = pango2_font_description_to_string (description);
+ g_warning ("All font fallbacks failed for \"%s\", in %s !!!!", s, pango2_language_to_string (language));
+ g_free (s);
+ }
+
+done_no_cache:
+ pango2_trace_mark (before, "pango2_fontmap_load_fontset", "%s", family_name);
+
+ return g_object_ref (PANGO2_FONTSET (fontset));
+}
+
+static Pango2FontFamily *
+pango2_font_map_default_get_family (Pango2FontMap *self,
+ const char *name)
+{
+ return PANGO2_FONT_FAMILY (find_family (self, name));
+}
+
+static void
+pango2_font_map_default_populate (Pango2FontMap *self)
+{
+}
+
+static guint
+pango2_font_map_default_get_serial (Pango2FontMap *self)
+{
+ return self->serial;
+}
+
+static void
+pango2_font_map_default_changed (Pango2FontMap *self)
+{
+ self->serial++;
+ if (self->serial == 0)
+ self->serial++;
+}
+
+static void
+pango2_font_map_class_init (Pango2FontMapClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = pango2_font_map_finalize;
+ object_class->set_property = pango2_font_map_set_property;
+ object_class->get_property = pango2_font_map_get_property;
+
+ class->load_font = pango2_font_map_default_load_font;
+ class->load_fontset = pango2_font_map_default_load_fontset;
+ class->get_serial = pango2_font_map_default_get_serial;
+ class->changed = pango2_font_map_default_changed;
+ class->get_family = pango2_font_map_default_get_family;
+ class->populate = pango2_font_map_default_populate;
+
+ /**
+ * Pango2FontMap:resolution: (attributes org.gtk.Property.get=pango2_font_map_get_resolution org.gtk.Property.set=pango2_font_map_set_resolution)
+ *
+ * The resolution for the fontmap.
+ *
+ * This is a scale factor between points specified in a
+ * `Pango2FontDescription` and Cairo units. The default value
+ * is 96, meaning that a 10 point font will be 13 units high.
+ * (10 * 96. / 72. = 13.3).
+ */
+ properties[PROP_RESOLUTION] =
+ g_param_spec_float ("resolution", NULL, NULL, 0, G_MAXFLOAT, 96.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2FontMap:fallback: (attributes org.gtk.Property.get=pango2_font_map_get_fallback org.gtk.Property.set=pango2_font_map_set_fallback)
+ *
+ * The fallback fontmap is used to look up fonts that
+ * this map does not have itself.
+ */
+ properties[PROP_FALLBACK] =
+ g_param_spec_object ("fallback", NULL, NULL, PANGO2_TYPE_FONT_MAP,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2FontMap:item-type:
+ *
+ * The type of items contained in this list.
+ */
+ properties[PROP_ITEM_TYPE] =
+ g_param_spec_gtype ("item-type", "", "", G_TYPE_OBJECT,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2FontMap:n-items:
+ *
+ * The number of items contained in this list.
+ */
+ properties[PROP_N_ITEMS] =
+ g_param_spec_uint ("n-items", "", "", 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+/* }}} */
+/* {{{ Private API */
+
+/*< private >
+ * pango2_font_map_repopulate:
+ * @self: a `Pango2FontMap`
+ * @add_synthetic: if `TRUE`, missing bold and italic faces will be synthesized
+ *
+ * Clear all cached information and repopulate the fontmap with
+ * families and faces.
+ *
+ * Subclasses should call this when their configuration changes
+ * in a way that requires recreating families and faces.
+ */
+void
+pango2_font_map_repopulate (Pango2FontMap *self,
+ gboolean add_synthetic)
+{
+ int removed, added;
+
+ clear_caches (self);
+
+ removed = self->families->len;
+
+ g_hash_table_remove_all (self->families_hash);
+ g_ptr_array_set_size (self->families, 0);
+
+ self->in_populate = TRUE;
+
+ PANGO2_FONT_MAP_GET_CLASS (self)->populate (self);
+
+ if (add_synthetic)
+ synthesize_bold_and_italic_faces (self);
+
+ for (int i = 0; i < self->added_faces->len; i++)
+ {
+ Pango2FontFace *face = g_ptr_array_index (self->added_faces, i);
+ pango2_font_map_add_face (self, face);
+ }
+
+ for (int i = 0; i < self->added_families->len; i++)
+ {
+ Pango2FontFamily *family = PANGO2_FONT_FAMILY (g_ptr_array_index (self->added_families, i));
+ pango2_font_map_add_family (self, family);
+ }
+
+ self->in_populate = FALSE;
+
+ added = self->families->len;
+
+ g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_ITEMS]);
+ pango2_font_map_changed (self);
+}
+
+/*< private >
+ * pango2_font_map_changed:
+ * @self: a `Pango2FontMap`
+ *
+ * Forces a change in the context, which will cause any `Pango2Context`
+ * using this fontmap to change.
+ *
+ * This function is only useful when implementing a new backend
+ * for Pango2, something applications won't do. Backends should
+ * call this function if they have attached extra data to the
+ * context and such data is changed.
+ */
+void
+pango2_font_map_changed (Pango2FontMap *self)
+{
+ g_return_if_fail (PANGO2_IS_FONT_MAP (self));
+
+ PANGO2_FONT_MAP_GET_CLASS (self)->changed (self);
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_font_map_load_font:
+ * @self: a `Pango2FontMap`
+ * @context: the `Pango2Context` the font will be used with
+ * @desc: a `Pango2FontDescription` describing the font to load
+ *
+ * Load the font in the fontmap that is the closest match for
+ * a font description.
+ *
+ * Returns: (transfer full) (nullable): the `Pango2Font` that
+ * was found
+ */
+Pango2Font *
+pango2_font_map_load_font (Pango2FontMap *self,
+ Pango2Context *context,
+ const Pango2FontDescription *desc)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_MAP (self), NULL);
+ g_return_val_if_fail (PANGO2_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (desc != NULL, NULL);
+
+ return PANGO2_FONT_MAP_GET_CLASS (self)->load_font (self, context, desc);
+}
+
+/**
+ * pango2_font_map_load_fontset:
+ * @self: a `Pango2FontMap`
+ * @context: the `Pango2Context` the font will be used with
+ * @desc: a `Pango2FontDescription` describing the font to load
+ * @language: (nullable): a `Pango2Language` the fonts will be used for,
+ * or `NULL` to use the language of @context
+ *
+ * Load a set of fonts in the fontmap that can be used to render
+ * a font matching a font description.
+ *
+ * Returns: (transfer full) (nullable): the `Pango2Fontset` containing
+ * the fonts that were found
+ */
+Pango2Fontset *
+pango2_font_map_load_fontset (Pango2FontMap *self,
+ Pango2Context *context,
+ const Pango2FontDescription *desc,
+ Pango2Language *language)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_MAP (self), NULL);
+ g_return_val_if_fail (PANGO2_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (desc != NULL, NULL);
+
+ return PANGO2_FONT_MAP_GET_CLASS (self)->load_fontset (self, context, desc, language);
+}
+
+/**
+ * pango2_font_map_get_serial:
+ * @self: a `Pango2FontMap`
+ *
+ * Returns the current serial number of the fontmap.
+ *
+ * The serial number is initialized to an small number larger than zero
+ * when a new fontmap is created and is increased whenever the fontmap
+ * is changed. It may wrap, but will never have the value 0. Since it can
+ * wrap, never compare it with "less than", always use "not equals".
+ *
+ * The fontmap can only be changed using backend-specific API, like changing
+ * fontmap resolution.
+ *
+ * This can be used to automatically detect changes to a `Pango2FontMap`,
+ * like in `Pango2Context`.
+ *
+ * Return value: The current serial number of @fontmap.
+ */
+guint
+pango2_font_map_get_serial (Pango2FontMap *self)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_MAP (self), 0);
+
+ return PANGO2_FONT_MAP_GET_CLASS (self)->get_serial (self);
+}
+
+/**
+ * pango2_font_map_get_family:
+ * @self: a `Pango2FontMap`
+ * @name: a family name
+ *
+ * Gets a font family by name.
+ *
+ * Returns: (transfer none): the `Pango2FontFamily`
+ */
+Pango2FontFamily *
+pango2_font_map_get_family (Pango2FontMap *self,
+ const char *name)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_MAP (self), NULL);
+
+ return PANGO2_FONT_MAP_GET_CLASS (self)->get_family (self, name);
+}
+
+/**
+ * pango2_font_map_new:
+ *
+ * Creates a new `Pango2FontMap`.
+ *
+ * A fontmap is used to cache information about available fonts,
+ * and holds certain global parameters such as the resolution.
+ *
+ * Returns: A newly created `Pango2FontMap
+ */
+Pango2FontMap *
+pango2_font_map_new (void)
+{
+ return g_object_new (PANGO2_TYPE_FONT_MAP, NULL);
+}
+
+/**
+ * pango2_font_map_add_face:
+ * @self: a `Pango2FontMap`
+ * @face: (transfer full): a `Pango2FontFace`
+ *
+ * Adds a face to the fontmap.
+ *
+ * This is most useful for creating transformed faces or aliases.
+ * See [method@Pango2.HbFace.new_synthetic] and [method@Pango2.HbFace.new_instance].
+ */
+void
+pango2_font_map_add_face (Pango2FontMap *self,
+ Pango2FontFace *face)
+{
+ const char *family_name;
+ Pango2HbFamily *family;
+ const Pango2FontDescription *description;
+
+ g_return_if_fail (PANGO2_IS_FONT_MAP (self));
+ g_return_if_fail (PANGO2_IS_HB_FACE (face) || PANGO2_IS_USER_FACE (face));
+
+ description = face->description;
+
+ if (pango2_font_description_get_set_fields (description) & PANGO2_FONT_MASK_GRAVITY)
+ g_warning ("Font description for Pango2FontFace includes things that it shouldn't");
+
+ if (!self->in_populate)
+ g_ptr_array_add (self->added_faces, g_object_ref (face));
+
+ family_name = pango2_font_description_get_family (description);
+ family = PANGO2_HB_FAMILY (pango2_font_map_get_family (self, family_name));
+ if (!family)
+ {
+ family = pango2_hb_family_new (family_name);
+ pango2_font_map_add_family (self, PANGO2_FONT_FAMILY (family));
+ }
+
+ pango2_hb_family_add_face (family, face);
+
+ pango2_font_map_changed (self);
+
+ clear_caches (self);
+}
+
+/**
+ * pango2_font_map_remove_face:
+ * @self: a `Pango2FontMap`
+ * @face: a `Pango2FontFace` that belongs to @map
+ *
+ * Removes @face from the fontmap.
+ *
+ * @face must have been added with [method@Pango2.FontMap.add_face].
+ */
+void
+pango2_font_map_remove_face (Pango2FontMap *self,
+ Pango2FontFace *face)
+{
+ Pango2HbFamily *family;
+ unsigned int position;
+
+ g_return_if_fail (PANGO2_IS_FONT_MAP (self));
+ g_return_if_fail (PANGO2_IS_HB_FACE (face) || PANGO2_IS_USER_FACE (face));
+
+ if (!g_ptr_array_find (self->added_faces, face, &position))
+ return;
+
+ family = PANGO2_HB_FAMILY (pango2_font_face_get_family (face));
+
+ pango2_hb_family_remove_face (family, face);
+
+ if (family->faces->len == 0)
+ pango2_font_map_remove_family (self, PANGO2_FONT_FAMILY (family));
+
+ pango2_font_map_changed (self);
+
+ clear_caches (self);
+
+ g_ptr_array_remove_index (self->added_faces, position);
+}
+
+/**
+ * pango2_font_map_add_file:
+ * @self: a `Pango2FontMap`
+ * @file: font filename
+ *
+ * Creates a new `Pango2HbFace` and adds it.
+ *
+ * If you need to specify an instance id or other
+ * parameters, use [ctor@Pango2.HbFace.new_from_file].
+ */
+void
+pango2_font_map_add_file (Pango2FontMap *self,
+ const char *file)
+{
+ Pango2HbFace *face;
+
+ face = pango2_hb_face_new_from_file (file, 0, -1, NULL, NULL);
+ pango2_font_map_add_face (self, PANGO2_FONT_FACE (face));
+}
+
+/**
+ * pango2_font_map_add_family:
+ * @self: a `Pango2FontMap`
+ * @family: (transfer full): a `Pango2FontFamily`
+ *
+ * Adds a family to the `Pango2FontMap`.
+ *
+ * The fontmap must not contain a family with the
+ * same name as @family yet.
+ *
+ * This is mostly useful for adding generic families
+ * using [ctor@Pango2.GenericFamily.new].
+ */
+void
+pango2_font_map_add_family (Pango2FontMap *self,
+ Pango2FontFamily *family)
+{
+ const char *name;
+ int position;
+
+ g_return_if_fail (PANGO2_IS_FONT_MAP (self));
+ g_return_if_fail (PANGO2_IS_HB_FAMILY (family) || PANGO2_IS_GENERIC_FAMILY (family));
+ g_return_if_fail (family->map == NULL);
+
+ if (!self->in_populate)
+ g_ptr_array_add (self->added_families, g_object_ref (family));
+
+ name = family->name;
+
+ position = 0;
+ while (position < self->families->len)
+ {
+ Pango2FontFamily *f = g_ptr_array_index (self->families, position);
+ if (g_ascii_strcasecmp (name, f->name) < 0)
+ break;
+ position++;
+ }
+
+ pango2_font_family_set_font_map (family, self);
+
+ g_ptr_array_insert (self->families, position, family);
+ g_hash_table_add (self->families_hash, family);
+
+ if (!self->in_populate)
+ {
+ g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_ITEMS]);
+ }
+
+ pango2_font_map_changed (self);
+}
+
+/**
+ * pango2_font_map_remove_family:
+ * @self: a `Pango2FontMap`
+ * @family: a `Pango2FontFamily` that belongs to @self
+ *
+ * Removes a family from the fontmap.
+ */
+void
+pango2_font_map_remove_family (Pango2FontMap *self,
+ Pango2FontFamily *family)
+{
+ unsigned int position;
+
+ g_return_if_fail (PANGO2_IS_FONT_MAP (self));
+ g_return_if_fail (PANGO2_IS_HB_FAMILY (family) || PANGO2_IS_GENERIC_FAMILY (family));
+ g_return_if_fail (family->map == self);
+
+ if (!g_ptr_array_find (self->added_families, family, &position))
+ return;
+
+ g_hash_table_remove (self->families_hash, family);
+ g_ptr_array_remove_index (self->families, position);
+
+ pango2_font_family_set_font_map (family, NULL);
+
+ if (!self->in_populate)
+ {
+ g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_ITEMS]);
+ }
+
+ pango2_font_map_changed (self);
+
+ g_ptr_array_remove_index (self->added_families, position);
+}
+
+/**
+ * pango2_font_map_set_fallback:
+ * @self: a `Pango2FontMap`
+ * @fallback: (nullable): the `Pango2FontMap` to use as fallback
+ *
+ * Sets the fontmap to use as fallback when a font isn't found.
+ *
+ * This can be used to make a custom font available only via a
+ * special fontmap, while still having all the regular fonts
+ * from the fallback fontmap.
+ *
+ * Note that families are *not* merged. If you are iterating
+ * the families, you will first get all the families in @self,
+ * followed by all the families in the @fallback.
+ */
+void
+pango2_font_map_set_fallback (Pango2FontMap *self,
+ Pango2FontMap *fallback)
+{
+ g_return_if_fail (PANGO2_IS_FONT_MAP (self));
+ g_return_if_fail (fallback == NULL || PANGO2_IS_FONT_MAP (fallback));
+
+ if (!g_set_object (&self->fallback, fallback))
+ return;
+
+ clear_caches (self);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FALLBACK]);
+}
+
+/**
+ * pango2_font_map_get_fallback:
+ * @self: a `Pango2FontMap`
+ *
+ * Returns the fallback fontmap of the fontmap.
+ *
+ * See [method@Pango2.FontMap.set_fallback].
+ *
+ * Returns: (nullable) (transfer none): the fallback fontmap
+ */
+Pango2FontMap *
+pango2_font_map_get_fallback (Pango2FontMap *self)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_MAP (self), NULL);
+
+ return self->fallback;
+}
+
+/**
+ * pango2_font_map_set_resolution:
+ * @self: a `Pango2FontMap`
+ * @dpi: the new resolution, in "dots per inch"
+ *
+ * Sets the resolution for the fontmap.
+ *
+ * This is a scale factor between points specified in a
+ * `Pango2FontDescription` and Cairo units. The default value
+ * is 96, meaning that a 10 point font will be 13 units high.
+ * (10 * 96. / 72. = 13.3).
+ */
+void
+pango2_font_map_set_resolution (Pango2FontMap *self,
+ float dpi)
+{
+ g_return_if_fail (PANGO2_IS_FONT_MAP (self));
+ g_return_if_fail (dpi > 0);
+
+ if (self->dpi == dpi)
+ return;
+
+ self->dpi = dpi;
+
+ clear_caches (self);
+ pango2_font_map_changed (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_RESOLUTION]);
+}
+
+/**
+ * pango2_font_map_get_resolution:
+ * @self: a `Pango2FontMap`
+ *
+ * Returns the resolution for the fontmap.
+ *
+ * See [method@Pango2.FontMap.set_resolution].
+ *
+ * Return value: the resolution for the fontmap
+ */
+float
+pango2_font_map_get_resolution (Pango2FontMap *self)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT_MAP (self), 0);
+
+ return self->dpi;
+}
+
+/* }}} */
+/* {{{ Default handling */
+
+static GPrivate default_font_map = G_PRIVATE_INIT (g_object_unref); /* MT-safe */
+
+/**
+ * pango2_font_map_new_default:
+ *
+ * Creates a new `Pango2FontMap` object.
+ *
+ * Note that the type of the returned object will depend
+ * on the platform that Pango2 is used on. If you want to
+ * explicitly create an instance of `Pango2FontMap` itself
+ * (and not a platform-specific subclass), see [ctor@Pango2.FontMap.new].
+ *
+ * In most cases, you should use [func@Pango2.FontMap.get_default],
+ * which will automatically create a new default fontmap,
+ * if one has not been created for the current thread yet.
+ *
+ * Return value: (transfer full): the newly allocated `Pango2FontMap`
+ */
+Pango2FontMap *
+pango2_font_map_new_default (void)
+{
+/* If we ever go back to having multiple backends built
+ * at the same time, bring back a PANGO2_BACKEND env var
+ * here.
+ */
+#if defined (HAVE_CORE_TEXT)
+ return PANGO2_FONT_MAP (pango2_core_text_font_map_new ());
+#elif defined (HAVE_DIRECT_WRITE)
+ return PANGO2_FONT_MAP (pango2_direct_write_font_map_new ());
+#elif defined (HAVE_FONTCONFIG)
+ return PANGO2_FONT_MAP (pango2_fc_font_map_new ());
+#else
+ return NULL;
+#endif
+}
+
+/**
+ * pango2_font_map_get_default:
+ *
+ * Gets the default `Pango2FontMap`.
+ *
+ * The type of the returned object will depend on the
+ * platform that Pango2 is used on.
+ *
+ * Note that the default fontmap is per-thread. Each thread gets
+ * its own default fontmap. In this way, Pango2 can be used safely
+ * from multiple threads.
+ *
+ * The default fontmap can be changed by using
+ * [method@Pango2.FontMap.set_default].
+ *
+ * Return value: (transfer none): the default fontmap
+ * for the current thread. This object is owned by Pango2 and must
+ * not be freed.
+ */
+Pango2FontMap *
+pango2_font_map_get_default (void)
+{
+ Pango2FontMap *fontmap = g_private_get (&default_font_map);
+
+ if (G_UNLIKELY (!fontmap))
+ {
+ fontmap = pango2_font_map_new_default ();
+ g_private_replace (&default_font_map, fontmap);
+ }
+
+ return fontmap;
+}
+
+/**
+ * pango2_font_map_set_default:
+ * @fontmap: (nullable): The new default font map
+ *
+ * Sets a default `Pango2FontMap`.
+ *
+ * The old default font map is unreffed and the new font map referenced.
+ *
+ * Note that the default fontmap is per-thread.
+ * This function only changes the default fontmap for
+ * the current thread. Default fontmaps of existing threads
+ * are not changed. Default fontmaps of any new threads will
+ * still be created using [ctor@Pango2.FontMap.new_default].
+ *
+ * A value of %NULL for @fontmap will cause the current default
+ * font map to be released and a new default font map to be created
+ * on demand, using [ctor@Pango2.FontMap.new_default].
+ */
+void
+pango2_font_map_set_default (Pango2FontMap *fontmap)
+{
+ g_return_if_fail (fontmap == NULL || PANGO2_IS_FONT_MAP (fontmap));
+
+ if (fontmap)
+ g_object_ref (fontmap);
+
+ g_private_replace (&default_font_map, fontmap);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-fontmap.h b/pango2/pango-fontmap.h
new file mode 100644
index 00000000..a8e9a778
--- /dev/null
+++ b/pango2/pango-fontmap.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 Matthias Clasen
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-fontset.h>
+#include <pango2/pango-hbface.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_FONT_MAP (pango2_font_map_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2FontMap, pango2_font_map, PANGO2, FONT_MAP, GObject)
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Font * pango2_font_map_load_font (Pango2FontMap *self,
+ Pango2Context *context,
+ const Pango2FontDescription *desc);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Fontset * pango2_font_map_load_fontset (Pango2FontMap *self,
+ Pango2Context *context,
+ const Pango2FontDescription *desc,
+ Pango2Language *language);
+PANGO2_AVAILABLE_IN_ALL
+guint pango2_font_map_get_serial (Pango2FontMap *self);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontFamily * pango2_font_map_get_family (Pango2FontMap *self,
+ const char *name);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontMap * pango2_font_map_new (void);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_map_add_file (Pango2FontMap *self,
+ const char *file);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_map_add_face (Pango2FontMap *self,
+ Pango2FontFace *face);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_map_remove_face (Pango2FontMap *self,
+ Pango2FontFace *face);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_map_add_family (Pango2FontMap *self,
+ Pango2FontFamily *family);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_map_remove_family (Pango2FontMap *self,
+ Pango2FontFamily *family);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_map_set_fallback (Pango2FontMap *self,
+ Pango2FontMap *fallback);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontMap * pango2_font_map_get_fallback (Pango2FontMap *self);
+
+PANGO2_AVAILABLE_IN_ALL
+float pango2_font_map_get_resolution (Pango2FontMap *self);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_map_set_resolution (Pango2FontMap *self,
+ float dpi);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontMap * pango2_font_map_new_default (void);
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontMap * pango2_font_map_get_default (void);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_font_map_set_default (Pango2FontMap *fontmap);
+
+G_END_DECLS
diff --git a/pango2/pango-fontset-cached-private.h b/pango2/pango-fontset-cached-private.h
new file mode 100644
index 00000000..95059a79
--- /dev/null
+++ b/pango2/pango-fontset-cached-private.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-types.h"
+#include "pango-fontset-private.h"
+#include "pango-generic-family.h"
+#include <glib-object.h>
+
+#ifdef HAVE_CAIRO
+#include <cairo.h>
+#endif
+
+#define PANGO2_TYPE_FONTSET_CACHED (pango2_fontset_cached_get_type ())
+
+G_DECLARE_FINAL_TYPE (Pango2FontsetCached, pango2_fontset_cached, PANGO2, FONTSET_CACHED, Pango2Fontset)
+
+struct _Pango2FontsetCached
+{
+ Pango2Fontset parent_instance;
+
+ GPtrArray *items; /* contains Pango2HbFont or Pango2GenericFamily */
+ Pango2Language *language;
+ Pango2FontDescription *description;
+ float dpi;
+ const Pango2Matrix *ctm;
+ GList cache_link;
+ GHashTable *cache;
+
+#ifdef HAVE_CAIRO
+ cairo_font_options_t *font_options;
+#endif
+};
+
+Pango2FontsetCached * pango2_fontset_cached_new (const Pango2FontDescription *description,
+ Pango2Language *language,
+ float dpi,
+ const Pango2Matrix *ctm);
+
+void pango2_fontset_cached_add_face (Pango2FontsetCached *self,
+ Pango2FontFace *face);
+void pango2_fontset_cached_add_family (Pango2FontsetCached *self,
+ Pango2GenericFamily *family);
+int pango2_fontset_cached_size (Pango2FontsetCached *self);
+Pango2Font * pango2_fontset_cached_get_first_font (Pango2FontsetCached *self);
+
+void pango2_fontset_cached_append (Pango2FontsetCached *self,
+ Pango2FontsetCached *other);
diff --git a/pango2/pango-fontset-cached.c b/pango2/pango-fontset-cached.c
new file mode 100644
index 00000000..9feaca79
--- /dev/null
+++ b/pango2/pango-fontset-cached.c
@@ -0,0 +1,347 @@
+/* Pango2
+ *
+ * Copyright (C) 2021 Matthias Clasen
+ *
+ * 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.
+ */
+
+#include "config.h"
+#include <math.h>
+
+#include <gio/gio.h>
+
+#include "pango-fontset-cached-private.h"
+#include "pango-font-private.h"
+#include "pango-font-face-private.h"
+#include "pango-generic-family-private.h"
+
+#ifdef HAVE_CAIRO
+#include "pangocairo-font.h"
+#endif
+
+G_DEFINE_FINAL_TYPE (Pango2FontsetCached, pango2_fontset_cached, PANGO2_TYPE_FONTSET);
+
+static void
+pango2_fontset_cached_init (Pango2FontsetCached *fontset)
+{
+ fontset->items = g_ptr_array_new_with_free_func (g_object_unref);
+ fontset->cache = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
+ fontset->language = NULL;
+#ifdef HAVE_CAIRO
+ fontset->font_options = NULL;
+#endif
+}
+
+static void
+pango2_fontset_cached_finalize (GObject *object)
+{
+ Pango2FontsetCached *self = (Pango2FontsetCached *)object;
+
+ g_ptr_array_free (self->items, TRUE);
+ g_hash_table_unref (self->cache);
+ pango2_font_description_free (self->description);
+#ifdef HAVE_CAIRO
+ if (self->font_options)
+ cairo_font_options_destroy (self->font_options);
+#endif
+
+ G_OBJECT_CLASS (pango2_fontset_cached_parent_class)->finalize (object);
+}
+
+static Pango2Font *
+find_font_for_face (Pango2FontsetCached *self,
+ Pango2FontFace *face)
+{
+ GHashTableIter iter;
+ Pango2Font *font;
+
+ g_hash_table_iter_init (&iter, self->cache);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&font))
+ {
+ if (pango2_font_get_face (font) == face)
+ {
+ return font;
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+static Pango2Font *
+pango2_fontset_cached_get_font (Pango2Fontset *fontset,
+ guint wc)
+{
+ Pango2FontsetCached *self = (Pango2FontsetCached *)fontset;
+ Pango2Font *retval;
+ int i;
+
+ retval = g_hash_table_lookup (self->cache, GUINT_TO_POINTER (wc));
+ if (retval)
+ return g_object_ref (retval);
+
+ for (i = 0; i < self->items->len; i++)
+ {
+ gpointer item = g_ptr_array_index (self->items, i);
+
+ if (PANGO2_IS_FONT (item))
+ {
+ Pango2Font *font = PANGO2_FONT (item);
+ if (pango2_font_face_has_char (font->face, wc))
+ {
+ retval = g_object_ref (font);
+ break;
+ }
+ }
+ else if (PANGO2_IS_GENERIC_FAMILY (item))
+ {
+ Pango2GenericFamily *family = PANGO2_GENERIC_FAMILY (item);
+ Pango2FontDescription *copy;
+ Pango2FontFace *face;
+
+ /* Here is where we implement delayed picking for generic families.
+ * If a face does not cover the character and its family is generic,
+ * choose a different face from the same family and create a font to use.
+ */
+ copy = pango2_font_description_copy_static (self->description);
+ pango2_font_description_unset_fields (copy, PANGO2_FONT_MASK_VARIATIONS |
+ PANGO2_FONT_MASK_GRAVITY |
+ PANGO2_FONT_MASK_VARIANT);
+ face = pango2_generic_family_find_face (family, copy, self->language, wc);
+ pango2_font_description_free (copy);
+
+ if (face)
+ {
+ Pango2Font *font;
+
+ font = find_font_for_face (self, face);
+ if (font)
+ {
+ retval = g_object_ref (font);
+ }
+ else
+ {
+ retval = pango2_font_face_create_font (face,
+ self->description,
+ self->dpi,
+ self->ctm);
+#ifdef HAVE_CAIRO
+ pango2_cairo_font_set_font_options (retval, self->font_options);
+#endif
+ }
+ break;
+ }
+ }
+ }
+
+ if (retval)
+ g_hash_table_insert (self->cache, GUINT_TO_POINTER (wc), g_object_ref (retval));
+
+ return retval;
+}
+
+Pango2Font *
+pango2_fontset_cached_get_first_font (Pango2FontsetCached *self)
+{
+ gpointer item;
+
+ item = g_ptr_array_index (self->items, 0);
+
+ if (PANGO2_IS_FONT (item))
+ return g_object_ref (PANGO2_FONT (item));
+ else if (PANGO2_IS_GENERIC_FAMILY (item))
+ {
+ Pango2FontFace *face;
+ Pango2Font *font;
+
+ face = pango2_generic_family_find_face (PANGO2_GENERIC_FAMILY (item),
+ self->description,
+ self->language,
+ 0);
+ font = find_font_for_face (self, face);
+ if (font)
+ g_object_ref (font);
+ else
+ {
+ font = pango2_font_face_create_font (face,
+ self->description,
+ self->dpi,
+ self->ctm);
+#ifdef HAVE_CAIRO
+ pango2_cairo_font_set_font_options (font, self->font_options);
+#endif
+ }
+
+ return font;
+ }
+
+ return NULL;
+}
+
+static Pango2FontMetrics *
+pango2_fontset_cached_get_metrics (Pango2Fontset *fontset)
+{
+ Pango2FontsetCached *self = (Pango2FontsetCached *)fontset;
+
+ if (self->items->len == 1)
+ {
+ Pango2Font *font;
+ Pango2FontMetrics *ret;
+
+ font = pango2_fontset_cached_get_first_font (self);
+ ret = pango2_font_get_metrics (font, self->language);
+ g_object_unref (font);
+
+ return ret;
+ }
+
+ return PANGO2_FONTSET_CLASS (pango2_fontset_cached_parent_class)->get_metrics (fontset);
+}
+
+static Pango2Language *
+pango2_fontset_cached_get_language (Pango2Fontset *fontset)
+{
+ Pango2FontsetCached *self = (Pango2FontsetCached *)fontset;
+
+ return self->language;
+}
+
+static void
+pango2_fontset_cached_foreach (Pango2Fontset *fontset,
+ Pango2FontsetForeachFunc func,
+ gpointer data)
+{
+ Pango2FontsetCached *self = (Pango2FontsetCached *)fontset;
+ unsigned int i;
+
+ for (i = 0; i < self->items->len; i++)
+ {
+ gpointer item = g_ptr_array_index (self->items, i);
+ Pango2Font *font = NULL;
+
+ if (PANGO2_IS_FONT (item))
+ {
+ font = g_object_ref (PANGO2_FONT (item));
+ }
+ else if (PANGO2_IS_GENERIC_FAMILY (item))
+ {
+ Pango2FontFace *face;
+
+ face = pango2_generic_family_find_face (PANGO2_GENERIC_FAMILY (item),
+ self->description,
+ self->language,
+ 0);
+
+ font = find_font_for_face (self, face);
+ if (font)
+ g_object_ref (font);
+ else
+ {
+ font = pango2_font_face_create_font (face,
+ self->description,
+ self->dpi,
+ self->ctm);
+#ifdef HAVE_CAIRO
+ pango2_cairo_font_set_font_options (font, self->font_options);
+#endif
+ }
+ }
+
+ if ((*func) (fontset, font, data))
+ {
+ g_object_unref (font);
+ return;
+ }
+ g_object_unref (font);
+ }
+}
+
+static void
+pango2_fontset_cached_class_init (Pango2FontsetCachedClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ Pango2FontsetClass *fontset_class = PANGO2_FONTSET_CLASS (class);
+
+ object_class->finalize = pango2_fontset_cached_finalize;
+
+ fontset_class->get_font = pango2_fontset_cached_get_font;
+ fontset_class->get_metrics = pango2_fontset_cached_get_metrics;
+ fontset_class->get_language = pango2_fontset_cached_get_language;
+ fontset_class->foreach = pango2_fontset_cached_foreach;
+}
+
+Pango2FontsetCached *
+pango2_fontset_cached_new (const Pango2FontDescription *description,
+ Pango2Language *language,
+ float dpi,
+ const Pango2Matrix *ctm)
+{
+ Pango2FontsetCached *self;
+
+ self = g_object_new (pango2_fontset_cached_get_type (), NULL);
+ self->language = language;
+ self->description = pango2_font_description_copy (description);
+ self->dpi = dpi;
+ self->ctm = ctm;
+
+ return self;
+}
+
+void
+pango2_fontset_cached_add_face (Pango2FontsetCached *self,
+ Pango2FontFace *face)
+{
+ Pango2Font *font;
+
+ font = pango2_font_face_create_font (face,
+ self->description,
+ self->dpi,
+ self->ctm);
+#ifdef HAVE_CAIRO
+ pango2_cairo_font_set_font_options (font, self->font_options);
+#endif
+ g_ptr_array_add (self->items, font);
+}
+
+void
+pango2_fontset_cached_add_family (Pango2FontsetCached *self,
+ Pango2GenericFamily *family)
+{
+ g_ptr_array_add (self->items, g_object_ref (family));
+}
+
+int
+pango2_fontset_cached_size (Pango2FontsetCached *self)
+{
+ return self->items->len;
+}
+
+void
+pango2_fontset_cached_append (Pango2FontsetCached *self,
+ Pango2FontsetCached *other)
+{
+ for (int i = 0; i < other->items->len; i++)
+ {
+ gpointer item = g_ptr_array_index (other->items, i);
+
+ if (PANGO2_IS_FONT (item))
+ pango2_fontset_cached_add_face (self, pango2_font_get_face (PANGO2_FONT (item)));
+ else if (PANGO2_IS_GENERIC_FAMILY (item))
+ pango2_fontset_cached_add_family (self, PANGO2_GENERIC_FAMILY (item));
+ }
+}
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-fontset-private.h b/pango2/pango-fontset-private.h
new file mode 100644
index 00000000..06e0e7c0
--- /dev/null
+++ b/pango2/pango-fontset-private.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-fontset.h"
+
+
+#define PANGO2_FONTSET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO2_TYPE_FONTSET, Pango2FontsetClass))
+#define PANGO2_FONTSET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO2_TYPE_FONTSET, Pango2FontsetClass))
+
+
+typedef struct _Pango2FontsetClass Pango2FontsetClass;
+
+struct _Pango2Fontset
+{
+ GObject parent_instance;
+};
+
+struct _Pango2FontsetClass
+{
+ GObjectClass parent_class;
+
+ Pango2Font * (* get_font) (Pango2Fontset *fontset,
+ guint wc);
+
+ Pango2FontMetrics * (* get_metrics) (Pango2Fontset *fontset);
+ Pango2Language * (* get_language) (Pango2Fontset *fontset);
+ void (* foreach) (Pango2Fontset *fontset,
+ Pango2FontsetForeachFunc func,
+ gpointer data);
+};
+
+Pango2Language * pango2_fontset_get_language (Pango2Fontset *fontset);
+
diff --git a/pango2/pango-fontset.c b/pango2/pango-fontset.c
new file mode 100644
index 00000000..b3ef1e8e
--- /dev/null
+++ b/pango2/pango-fontset.c
@@ -0,0 +1,212 @@
+/* Pango2
+ * pango-fontset.c:
+ *
+ * Copyright (C) 2001 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.
+ */
+
+#include "config.h"
+
+/**
+ * Pango2Fontset:
+ *
+ * A `Pango2Fontset` represents a set of `Pango2Font` to use when rendering text.
+ *
+ * A `Pango2Fontset` is the result of resolving a `Pango2FontDescription`
+ * against a particular `Pango2Context`. It has operations for finding the
+ * component font for a particular Unicode character, and for finding a
+ * composite set of metrics for the entire fontset.
+ *
+ * To obtain a `Pango2Fontset`, use [method@Pango2.Context.load_fontset] or
+ * [method@Pango2.FontMap.load_fontset].
+ */
+
+#include "pango-fontset-private.h"
+#include "pango-types.h"
+#include "pango-font-metrics-private.h"
+#include "pango-impl-utils.h"
+
+static Pango2FontMetrics *pango2_fontset_real_get_metrics (Pango2Fontset *fontset);
+
+G_DEFINE_ABSTRACT_TYPE (Pango2Fontset, pango2_fontset, G_TYPE_OBJECT);
+
+static void
+pango2_fontset_init (Pango2Fontset *self)
+{
+}
+
+static void
+pango2_fontset_class_init (Pango2FontsetClass *class)
+{
+ class->get_metrics = pango2_fontset_real_get_metrics;
+}
+
+
+/**
+ * pango2_fontset_get_font:
+ * @fontset: a `Pango2Fontset`
+ * @wc: a Unicode character
+ *
+ * Returns the font in the fontset that contains the best
+ * glyph for a Unicode character.
+ *
+ * Return value: (transfer full): a `Pango2Font`
+ */
+Pango2Font *
+pango2_fontset_get_font (Pango2Fontset *fontset,
+ guint wc)
+{
+
+ g_return_val_if_fail (PANGO2_IS_FONTSET (fontset), NULL);
+
+ return PANGO2_FONTSET_GET_CLASS (fontset)->get_font (fontset, wc);
+}
+
+/**
+ * pango2_fontset_get_metrics:
+ * @fontset: a `Pango2Fontset`
+ *
+ * Get overall metric information for the fonts in the fontset.
+ *
+ * Return value: a `Pango2FontMetrics` object
+ */
+Pango2FontMetrics *
+pango2_fontset_get_metrics (Pango2Fontset *fontset)
+{
+ g_return_val_if_fail (PANGO2_IS_FONTSET (fontset), NULL);
+
+ return PANGO2_FONTSET_GET_CLASS (fontset)->get_metrics (fontset);
+}
+
+/*< private >
+ * pango2_fontset_get_language:
+ * @fontset: a `Pango2Fontset`
+ *
+ * Gets the language that the fontset was created for.
+ *
+ * Returns: the language that @fontset was created for
+ */
+Pango2Language *
+pango2_fontset_get_language (Pango2Fontset *fontset)
+{
+ g_return_val_if_fail (PANGO2_IS_FONTSET (fontset), NULL);
+
+ return PANGO2_FONTSET_GET_CLASS (fontset)->get_language (fontset);
+}
+
+/**
+ * pango2_fontset_foreach:
+ * @fontset: a `Pango2Fontset`
+ * @func: (closure data) (scope call): Callback function
+ * @data: (closure): data to pass to the callback function
+ *
+ * Iterates through all the fonts in a fontset, calling @func for
+ * each one.
+ *
+ * If @func returns %TRUE, that stops the iteration.
+ */
+void
+pango2_fontset_foreach (Pango2Fontset *fontset,
+ Pango2FontsetForeachFunc func,
+ gpointer data)
+{
+ g_return_if_fail (PANGO2_IS_FONTSET (fontset));
+ g_return_if_fail (func != NULL);
+
+ PANGO2_FONTSET_GET_CLASS (fontset)->foreach (fontset, func, data);
+}
+
+static gboolean
+get_first_metrics_foreach (Pango2Fontset *fontset,
+ Pango2Font *font,
+ gpointer data)
+{
+ Pango2FontMetrics **fontset_metrics = data;
+ Pango2Language *language = pango2_fontset_get_language (fontset);
+
+ *fontset_metrics = pango2_font_get_metrics (font, language);
+
+ return TRUE; /* Stops iteration */
+}
+
+static Pango2FontMetrics *
+pango2_fontset_real_get_metrics (Pango2Fontset *fontset)
+{
+ Pango2FontMetrics *metrics, *raw_metrics;
+ const char *sample_str;
+ const char *p;
+ int count;
+ GHashTable *fonts_seen;
+ Pango2Font *font;
+ Pango2Language *language;
+
+ language = pango2_fontset_get_language (fontset);
+ sample_str = pango2_language_get_sample_string (language);
+
+ count = 0;
+ metrics = NULL;
+ fonts_seen = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
+
+ /* Initialize the metrics from the first font in the fontset */
+ pango2_fontset_foreach (fontset, get_first_metrics_foreach, &metrics);
+
+ p = sample_str;
+ while (*p)
+ {
+ gunichar wc = g_utf8_get_char (p);
+ font = pango2_fontset_get_font (fontset, wc);
+ if (font)
+ {
+ if (g_hash_table_lookup (fonts_seen, font) == NULL)
+ {
+ raw_metrics = pango2_font_get_metrics (font, language);
+ g_hash_table_insert (fonts_seen, font, font);
+
+ if (count == 0)
+ {
+ metrics->ascent = raw_metrics->ascent;
+ metrics->descent = raw_metrics->descent;
+ metrics->approximate_char_width = raw_metrics->approximate_char_width;
+ metrics->approximate_digit_width = raw_metrics->approximate_digit_width;
+ }
+ else
+ {
+ metrics->ascent = MAX (metrics->ascent, raw_metrics->ascent);
+ metrics->descent = MAX (metrics->descent, raw_metrics->descent);
+ metrics->approximate_char_width += raw_metrics->approximate_char_width;
+ metrics->approximate_digit_width += raw_metrics->approximate_digit_width;
+ }
+ count++;
+ pango2_font_metrics_free (raw_metrics);
+ }
+ else
+ g_object_unref (font);
+ }
+
+ p = g_utf8_next_char (p);
+ }
+
+ g_hash_table_destroy (fonts_seen);
+
+ if (count)
+ {
+ metrics->approximate_char_width /= count;
+ metrics->approximate_digit_width /= count;
+ }
+
+ return metrics;
+}
diff --git a/pango2/pango-fontset.h b/pango2/pango-fontset.h
new file mode 100644
index 00000000..5e6ed716
--- /dev/null
+++ b/pango2/pango-fontset.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2001 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-font-metrics.h>
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_FONTSET (pango2_fontset_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2Fontset, pango2_fontset, PANGO2, FONTSET, GObject)
+
+/**
+ * Pango2FontsetForeachFunc:
+ * @fontset: a `Pango2Fontset`
+ * @font: a font from @fontset
+ * @user_data: callback data
+ *
+ * Callback used when enumerating fonts in a fontset.
+ *
+ * See [method@Pango2.Fontset.foreach].
+ *
+ * Returns: if %TRUE, stop iteration and return immediately.
+ */
+typedef gboolean (*Pango2FontsetForeachFunc) (Pango2Fontset *fontset,
+ Pango2Font *font,
+ gpointer user_data);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Font * pango2_fontset_get_font (Pango2Fontset *fontset,
+ guint wc);
+PANGO2_AVAILABLE_IN_ALL
+Pango2FontMetrics * pango2_fontset_get_metrics (Pango2Fontset *fontset);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_fontset_foreach (Pango2Fontset *fontset,
+ Pango2FontsetForeachFunc func,
+ gpointer data);
+
+G_END_DECLS
diff --git a/pango2/pango-generic-family-private.h b/pango2/pango-generic-family-private.h
new file mode 100644
index 00000000..8c406d81
--- /dev/null
+++ b/pango2/pango-generic-family-private.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 Matthias Clasen
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-font.h"
+#include "pango-generic-family.h"
+#include "pango-font-family-private.h"
+
+
+struct _Pango2GenericFamily
+{
+ Pango2FontFamily parent_instance;
+
+ GPtrArray *families;
+};
+
+
+Pango2FontFace * pango2_generic_family_find_face (Pango2GenericFamily *self,
+ Pango2FontDescription *description,
+ Pango2Language *language,
+ gunichar wc);
diff --git a/pango2/pango-generic-family.c b/pango2/pango-generic-family.c
new file mode 100644
index 00000000..c0697f00
--- /dev/null
+++ b/pango2/pango-generic-family.c
@@ -0,0 +1,226 @@
+/* Pango2
+ *
+ * Copyright (C) 2022 Matthias Clasen
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "pango-generic-family-private.h"
+#include "pango-hbfamily-private.h"
+#include "pango-impl-utils.h"
+#include "pango-hbface-private.h"
+#include "pango-font-private.h"
+
+/**
+ * Pango2GenericFamily:
+ *
+ * An implementation of `Pango2FontFamily`
+ * that provides faces from other families.
+ *
+ * `Pango2GenericFamily can be used to e.g. assemble a
+ * generic 'sans-serif' family from a number of other
+ * font families.
+ */
+
+
+/* {{{ GListModel implementation */
+
+static GType
+pango2_generic_family_get_item_type (GListModel *list)
+{
+ return PANGO2_TYPE_FONT_FACE;
+}
+
+static guint
+pango2_generic_family_get_n_items (GListModel *list)
+{
+ Pango2GenericFamily *self = PANGO2_GENERIC_FAMILY (list);
+ guint n;
+
+ n = 0;
+ for (int i = 0; i < self->families->len; i++)
+ {
+ Pango2HbFamily *family = g_ptr_array_index (self->families, i);
+
+ n += g_list_model_get_n_items (G_LIST_MODEL (family));
+ }
+
+ return n;
+}
+
+static gpointer
+pango2_generic_family_get_item (GListModel *list,
+ guint position)
+{
+ Pango2GenericFamily *self = PANGO2_GENERIC_FAMILY (list);
+ guint pos;
+
+ pos = position;
+ for (int i = 0; i < self->families->len; i++)
+ {
+ Pango2HbFamily *family = g_ptr_array_index (self->families, i);
+
+ if (pos < g_list_model_get_n_items (G_LIST_MODEL (family)))
+ return g_list_model_get_item (G_LIST_MODEL (family), pos);
+
+ pos -= g_list_model_get_n_items (G_LIST_MODEL (family));
+ }
+
+ return NULL;
+}
+
+static void
+pango2_generic_family_list_model_init (GListModelInterface *iface)
+{
+ iface->get_item_type = pango2_generic_family_get_item_type;
+ iface->get_n_items = pango2_generic_family_get_n_items;
+ iface->get_item = pango2_generic_family_get_item;
+}
+
+/* }}} */
+/* {{{ Pango2FontFamily implementation */
+
+struct _Pango2GenericFamilyClass
+{
+ Pango2FontFamilyClass parent_class;
+};
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (Pango2GenericFamily, pango2_generic_family, PANGO2_TYPE_FONT_FAMILY,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, pango2_generic_family_list_model_init))
+
+static void
+pango2_generic_family_init (Pango2GenericFamily *self)
+{
+ self->families = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+static void
+pango2_generic_family_finalize (GObject *object)
+{
+ Pango2GenericFamily *self = PANGO2_GENERIC_FAMILY (object);
+
+ g_ptr_array_unref (self->families);
+
+ G_OBJECT_CLASS (pango2_generic_family_parent_class)->finalize (object);
+}
+
+static void
+pango2_generic_family_class_init (Pango2GenericFamilyClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = pango2_generic_family_finalize;
+}
+
+/* }}} */
+/* {{{ Private API */
+
+/*< private >
+ * pango2_generic_family_find_face:
+ * @family: a `Pango2GenericFamily`
+ * @description: `Pango2FontDescription` to match
+ * @language: (nullable): `Pango2Language` to support
+ * @wc: a Unicode character, or 0 to ignore
+ *
+ * Finds the face that best matches the font description while
+ * also supporting the given language and character, from the first
+ * family in @family that has any matching faces.
+ *
+ * Returns: (transfer none) (nullable): the face
+ */
+Pango2FontFace *
+pango2_generic_family_find_face (Pango2GenericFamily *self,
+ Pango2FontDescription *description,
+ Pango2Language *language,
+ gunichar wc)
+{
+ Pango2FontFace *face = NULL;
+
+ for (int i = 0; i < self->families->len; i++)
+ {
+ Pango2HbFamily *family = g_ptr_array_index (self->families, i);
+
+ face = pango2_hb_family_find_face (family, description, language, wc);
+ if (face)
+ break;
+ }
+
+ /* last resort */
+ if (!face && self->families->len > 0)
+ {
+ Pango2HbFamily *family = g_ptr_array_index (self->families, 0);
+ face = g_list_model_get_item (G_LIST_MODEL (family), 0);
+ g_object_unref (face);
+ }
+
+ return face;
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_generic_family_new:
+ * @name: the family name
+ *
+ * Creates a new `Pango2GenericFamily`.
+ *
+ * A generic family does not contain faces, but will return
+ * faces from other families that match a given query.
+ *
+ * Returns: a newly created `Pango2GenericFamily`
+ */
+Pango2GenericFamily *
+pango2_generic_family_new (const char *name)
+{
+ Pango2GenericFamily *self;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ self = g_object_new (PANGO2_TYPE_GENERIC_FAMILY, NULL);
+
+ pango2_font_family_set_name (PANGO2_FONT_FAMILY (self), name);
+
+ return self;
+}
+
+/**
+ * pango2_generic_family_add_family:
+ * @self: a `Pango2GenericFamily`
+ * @family: (transfer none): a `Pango2FontFamily` to add
+ *
+ * Adds a `Pango2FontFamily` to a `Pango2GenericFamily`.
+ *
+ * It is an error to call this function more than
+ * once for the same family.
+ */
+void
+pango2_generic_family_add_family (Pango2GenericFamily *self,
+ Pango2FontFamily *family)
+{
+ g_return_if_fail (PANGO2_IS_GENERIC_FAMILY (self));
+ g_return_if_fail (PANGO2_IS_FONT_FAMILY (family));
+
+ g_ptr_array_add (self->families, g_object_ref (family));
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-generic-family.h b/pango2/pango-generic-family.h
new file mode 100644
index 00000000..ab1d0fd2
--- /dev/null
+++ b/pango2/pango-generic-family.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 Matthias Clasen
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-font.h"
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_GENERIC_FAMILY (pango2_generic_family_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2GenericFamily, pango2_generic_family, PANGO2, GENERIC_FAMILY, Pango2FontFamily)
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2GenericFamily * pango2_generic_family_new (const char *name);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_generic_family_add_family (Pango2GenericFamily *self,
+ Pango2FontFamily *family);
+
+G_END_DECLS
diff --git a/pango2/pango-glyph-item-private.h b/pango2/pango-glyph-item-private.h
new file mode 100644
index 00000000..1fa80871
--- /dev/null
+++ b/pango2/pango-glyph-item-private.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2002 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-attributes.h"
+#include "pango-break.h"
+#include "pango-item.h"
+#include "pango-glyph.h"
+
+G_BEGIN_DECLS
+
+/**
+ * Pango2GlyphItem:
+ * @item: corresponding `Pango2Item`
+ * @glyphs: corresponding `Pango2GlyphString`
+ * @y_offset: shift of the baseline, relative to the baseline
+ * of the containing line. Positive values shift upwards
+ * @start_x_offset: horizontal displacement to apply before the
+ * glyph item. Positive values shift right
+ * @end_x_offset: horizontal displacement to apply after th
+ * glyph item. Positive values shift right
+ *
+ * A `Pango2GlyphItem` is a pair of a `Pango2Item` and the glyphs
+ * resulting from shaping the items text.
+ *
+ * As an example of the usage of `Pango2GlyphItem`, the results
+ * of shaping text with `Pango2Layout` is a list of `Pango2Line`,
+ * each of which contains a list of `Pango2GlyphItem`.
+ */
+typedef struct _Pango2GlyphItem Pango2GlyphItem;
+
+struct _Pango2GlyphItem
+{
+ Pango2Item *item;
+ Pango2GlyphString *glyphs;
+ int y_offset;
+ int start_x_offset;
+ int end_x_offset;
+};
+
+#define PANGO2_TYPE_GLYPH_ITEM (pango2_glyph_item_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_glyph_item_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2GlyphItem * pango2_glyph_item_split (Pango2GlyphItem *orig,
+ const char *text,
+ int split_index);
+PANGO2_AVAILABLE_IN_ALL
+Pango2GlyphItem * pango2_glyph_item_copy (Pango2GlyphItem *orig);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_item_free (Pango2GlyphItem *glyph_item);
+PANGO2_AVAILABLE_IN_ALL
+GSList * pango2_glyph_item_apply_attrs (Pango2GlyphItem *glyph_item,
+ const char *text,
+ Pango2AttrList *list);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_item_letter_space (Pango2GlyphItem *glyph_item,
+ const char *text,
+ Pango2LogAttr *log_attrs,
+ int letter_spacing);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_item_get_logical_widths (Pango2GlyphItem *glyph_item,
+ const char *text,
+ int *logical_widths);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2GlyphItem, pango2_glyph_item_free)
+
+
+G_END_DECLS
diff --git a/pango2/pango-glyph-item.c b/pango2/pango-glyph-item.c
new file mode 100644
index 00000000..6ba75961
--- /dev/null
+++ b/pango2/pango-glyph-item.c
@@ -0,0 +1,837 @@
+/* Pango2
+ * pango-glyph-item.c: Pair of Pango2Item and a glyph string
+ *
+ * Copyright (C) 2002 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.
+ */
+
+#include "config.h"
+#include <string.h>
+
+#include "pango-glyph-item-private.h"
+#include "pango-glyph-iter-private.h"
+#include "pango-item-private.h"
+#include "pango-impl-utils.h"
+#include "pango-attr-list-private.h"
+#include "pango-attr-iterator-private.h"
+
+#define LTR(glyph_item) (((glyph_item)->item->analysis.level % 2) == 0)
+
+/**
+ * pango2_glyph_item_split:
+ * @orig: a `Pango2Item`
+ * @text: text to which positions in @orig apply
+ * @split_index: byte index of position to split item, relative to the
+ * start of the item
+ *
+ * Modifies @orig to cover only the text after @split_index, and
+ * returns a new item that covers the text before @split_index that
+ * used to be in @orig.
+ *
+ * You can think of @split_index as the length of the returned item.
+ * @split_index may not be 0, and it may not be greater than or equal
+ * to the length of @orig (that is, there must be at least one byte
+ * assigned to each item, you can't create a zero-length item).
+ *
+ * Return value: the newly allocated item representing text before
+ * @split_index, which should be freed with [method@Pango2.GlyphItem.free]
+ */
+Pango2GlyphItem *
+pango2_glyph_item_split (Pango2GlyphItem *orig,
+ const char *text,
+ int split_index)
+{
+ Pango2GlyphItem *new;
+ int i;
+ int num_glyphs;
+ int num_remaining;
+ int split_offset;
+
+ g_return_val_if_fail (orig != NULL, NULL);
+ g_return_val_if_fail (orig->item->length > 0, NULL);
+ g_return_val_if_fail (split_index > 0, NULL);
+ g_return_val_if_fail (split_index < orig->item->length, NULL);
+
+ if (LTR (orig))
+ {
+ for (i = 0; i < orig->glyphs->num_glyphs; i++)
+ {
+ if (orig->glyphs->log_clusters[i] >= split_index)
+ break;
+ }
+
+ if (i == orig->glyphs->num_glyphs) /* No splitting necessary */
+ return NULL;
+
+ split_index = orig->glyphs->log_clusters[i];
+ num_glyphs = i;
+ }
+ else
+ {
+ for (i = orig->glyphs->num_glyphs - 1; i >= 0; i--)
+ {
+ if (orig->glyphs->log_clusters[i] >= split_index)
+ break;
+ }
+
+ if (i < 0) /* No splitting necessary */
+ return NULL;
+
+ split_index = orig->glyphs->log_clusters[i];
+ num_glyphs = orig->glyphs->num_glyphs - 1 - i;
+ }
+
+ num_remaining = orig->glyphs->num_glyphs - num_glyphs;
+
+ new = g_slice_new (Pango2GlyphItem);
+ split_offset = g_utf8_pointer_to_offset (text + orig->item->offset,
+ text + orig->item->offset + split_index);
+ new->item = pango2_item_split (orig->item, split_index, split_offset);
+
+ new->glyphs = pango2_glyph_string_new ();
+ pango2_glyph_string_set_size (new->glyphs, num_glyphs);
+
+ if (LTR (orig))
+ {
+ memcpy (new->glyphs->glyphs, orig->glyphs->glyphs, num_glyphs * sizeof (Pango2GlyphInfo));
+ memcpy (new->glyphs->log_clusters, orig->glyphs->log_clusters, num_glyphs * sizeof (int));
+
+ memmove (orig->glyphs->glyphs, orig->glyphs->glyphs + num_glyphs,
+ num_remaining * sizeof (Pango2GlyphInfo));
+ for (i = num_glyphs; i < orig->glyphs->num_glyphs; i++)
+ orig->glyphs->log_clusters[i - num_glyphs] = orig->glyphs->log_clusters[i] - split_index;
+ }
+ else
+ {
+ memcpy (new->glyphs->glyphs, orig->glyphs->glyphs + num_remaining, num_glyphs * sizeof (Pango2GlyphInfo));
+ memcpy (new->glyphs->log_clusters, orig->glyphs->log_clusters + num_remaining, num_glyphs * sizeof (int));
+
+ for (i = 0; i < num_remaining; i++)
+ orig->glyphs->log_clusters[i] = orig->glyphs->log_clusters[i] - split_index;
+ }
+
+ pango2_glyph_string_set_size (orig->glyphs, orig->glyphs->num_glyphs - num_glyphs);
+
+ new->y_offset = orig->y_offset;
+ new->start_x_offset = orig->start_x_offset;
+ new->end_x_offset = -orig->start_x_offset;
+
+ return new;
+}
+
+/**
+ * pango2_glyph_item_copy:
+ * @orig: (nullable): a `Pango2GlyphItem`
+ *
+ * Make a deep copy of an existing `Pango2GlyphItem` structure.
+ *
+ * Return value: (nullable): the newly allocated `Pango2GlyphItem`
+ */
+Pango2GlyphItem *
+pango2_glyph_item_copy (Pango2GlyphItem *orig)
+{
+ Pango2GlyphItem *result;
+
+ if (orig == NULL)
+ return NULL;
+
+ result = g_slice_new (Pango2GlyphItem);
+
+ result->item = pango2_item_copy (orig->item);
+ result->glyphs = pango2_glyph_string_copy (orig->glyphs);
+ result->y_offset = orig->y_offset;
+ result->start_x_offset = orig->start_x_offset;
+ result->end_x_offset = orig->end_x_offset;
+
+ return result;
+}
+
+/**
+ * pango2_glyph_item_free:
+ * @glyph_item: (nullable): a `Pango2GlyphItem`
+ *
+ * Frees a `Pango2GlyphItem` and resources to which it points.
+ */
+void
+pango2_glyph_item_free (Pango2GlyphItem *glyph_item)
+{
+ if (glyph_item == NULL)
+ return;
+
+ if (glyph_item->item)
+ pango2_item_free (glyph_item->item);
+ if (glyph_item->glyphs)
+ pango2_glyph_string_free (glyph_item->glyphs);
+
+ g_slice_free (Pango2GlyphItem, glyph_item);
+}
+
+G_DEFINE_BOXED_TYPE (Pango2GlyphItem, pango2_glyph_item,
+ pango2_glyph_item_copy,
+ pango2_glyph_item_free);
+
+
+/**
+ * pango2_glyph_item_iter_copy:
+ * @orig: (nullable): a `Pango2GlyphItem`Iter
+ *
+ * Make a shallow copy of an existing `Pango2GlyphItemIter` structure.
+ *
+ * Return value: (nullable): the newly allocated `Pango2GlyphItemIter`
+ */
+Pango2GlyphItemIter *
+pango2_glyph_item_iter_copy (Pango2GlyphItemIter *orig)
+{
+ Pango2GlyphItemIter *result;
+
+ if (orig == NULL)
+ return NULL;
+
+ result = g_slice_new (Pango2GlyphItemIter);
+
+ *result = *orig;
+
+ return result;
+}
+
+/**
+ * pango2_glyph_item_iter_free:
+ * @iter: (nullable): a `Pango2GlyphItemIter`
+ *
+ * Frees a `Pango2GlyphItemIter`.
+ */
+void
+pango2_glyph_item_iter_free (Pango2GlyphItemIter *iter)
+{
+ if (iter == NULL)
+ return;
+
+ g_slice_free (Pango2GlyphItemIter, iter);
+}
+
+G_DEFINE_BOXED_TYPE (Pango2GlyphItemIter, pango2_glyph_item_iter,
+ pango2_glyph_item_iter_copy,
+ pango2_glyph_item_iter_free)
+
+/**
+ * pango2_glyph_item_iter_next_cluster:
+ * @iter: a `Pango2GlyphItemIter`
+ *
+ * Advances the iterator to the next cluster in the glyph item.
+ *
+ * See `Pango2GlyphItemIter` for details of cluster orders.
+ *
+ * Return value: %TRUE if the iterator was advanced,
+ * %FALSE if we were already on the last cluster.
+ */
+gboolean
+pango2_glyph_item_iter_next_cluster (Pango2GlyphItemIter *iter)
+{
+ int glyph_index = iter->end_glyph;
+ Pango2GlyphString *glyphs = iter->glyph_item->glyphs;
+ int cluster;
+ Pango2Item *item = iter->glyph_item->item;
+
+ if (LTR (iter->glyph_item))
+ {
+ if (glyph_index == glyphs->num_glyphs)
+ return FALSE;
+ }
+ else
+ {
+ if (glyph_index < 0)
+ return FALSE;
+ }
+
+ iter->start_glyph = iter->end_glyph;
+ iter->start_index = iter->end_index;
+ iter->start_char = iter->end_char;
+
+ if (LTR (iter->glyph_item))
+ {
+ cluster = glyphs->log_clusters[glyph_index];
+ while (TRUE)
+ {
+ glyph_index++;
+
+ if (glyph_index == glyphs->num_glyphs)
+ {
+ iter->end_index = item->offset + item->length;
+ iter->end_char = item->num_chars;
+ break;
+ }
+
+ if (glyphs->log_clusters[glyph_index] > cluster)
+ {
+ iter->end_index = item->offset + glyphs->log_clusters[glyph_index];
+ iter->end_char += pango2_utf8_strlen (iter->text + iter->start_index,
+ iter->end_index - iter->start_index);
+ break;
+ }
+ }
+ }
+ else /* RTL */
+ {
+ cluster = glyphs->log_clusters[glyph_index];
+ while (TRUE)
+ {
+ glyph_index--;
+
+ if (glyph_index < 0)
+ {
+ iter->end_index = item->offset + item->length;
+ iter->end_char = item->num_chars;
+ break;
+ }
+
+ if (glyphs->log_clusters[glyph_index] > cluster)
+ {
+ iter->end_index = item->offset + glyphs->log_clusters[glyph_index];
+ iter->end_char += pango2_utf8_strlen (iter->text + iter->start_index,
+ iter->end_index - iter->start_index);
+ break;
+ }
+ }
+ }
+
+ iter->end_glyph = glyph_index;
+
+ g_assert (iter->start_char <= iter->end_char);
+ g_assert (iter->end_char <= item->num_chars);
+
+ return TRUE;
+}
+
+/**
+ * pango2_glyph_item_iter_prev_cluster:
+ * @iter: a `Pango2GlyphItemIter`
+ *
+ * Moves the iterator to the preceding cluster in the glyph item.
+ * See `Pango2GlyphItemIter` for details of cluster orders.
+ *
+ * Return value: %TRUE if the iterator was moved,
+ * %FALSE if we were already on the first cluster.
+ */
+gboolean
+pango2_glyph_item_iter_prev_cluster (Pango2GlyphItemIter *iter)
+{
+ int glyph_index = iter->start_glyph;
+ Pango2GlyphString *glyphs = iter->glyph_item->glyphs;
+ int cluster;
+ Pango2Item *item = iter->glyph_item->item;
+
+ if (LTR (iter->glyph_item))
+ {
+ if (glyph_index == 0)
+ return FALSE;
+ }
+ else
+ {
+ if (glyph_index == glyphs->num_glyphs - 1)
+ return FALSE;
+
+ }
+
+ iter->end_glyph = iter->start_glyph;
+ iter->end_index = iter->start_index;
+ iter->end_char = iter->start_char;
+
+ if (LTR (iter->glyph_item))
+ {
+ cluster = glyphs->log_clusters[glyph_index - 1];
+ while (TRUE)
+ {
+ if (glyph_index == 0)
+ {
+ iter->start_index = item->offset;
+ iter->start_char = 0;
+ break;
+ }
+
+ glyph_index--;
+
+ if (glyphs->log_clusters[glyph_index] < cluster)
+ {
+ glyph_index++;
+ iter->start_index = item->offset + glyphs->log_clusters[glyph_index];
+ iter->start_char -= pango2_utf8_strlen (iter->text + iter->start_index,
+ iter->end_index - iter->start_index);
+ break;
+ }
+ }
+ }
+ else /* RTL */
+ {
+ cluster = glyphs->log_clusters[glyph_index + 1];
+ while (TRUE)
+ {
+ if (glyph_index == glyphs->num_glyphs - 1)
+ {
+ iter->start_index = item->offset;
+ iter->start_char = 0;
+ break;
+ }
+
+ glyph_index++;
+
+ if (glyphs->log_clusters[glyph_index] < cluster)
+ {
+ glyph_index--;
+ iter->start_index = item->offset + glyphs->log_clusters[glyph_index];
+ iter->start_char -= pango2_utf8_strlen (iter->text + iter->start_index,
+ iter->end_index - iter->start_index);
+ break;
+ }
+ }
+ }
+
+ iter->start_glyph = glyph_index;
+
+ g_assert (iter->start_char <= iter->end_char);
+ g_assert (0 <= iter->start_char);
+
+ return TRUE;
+}
+
+/**
+ * pango2_glyph_item_iter_init_start:
+ * @iter: a `Pango2GlyphItemIter`
+ * @glyph_item: the glyph item to iterate over
+ * @text: text corresponding to the glyph item
+ *
+ * Initializes a `Pango2GlyphItemIter` structure to point to the
+ * first cluster in a glyph item.
+ *
+ * See `Pango2GlyphItemIter` for details of cluster orders.
+ *
+ * Return value: %FALSE if there are no clusters in the glyph item
+ */
+gboolean
+pango2_glyph_item_iter_init_start (Pango2GlyphItemIter *iter,
+ Pango2GlyphItem *glyph_item,
+ const char *text)
+{
+ iter->glyph_item = glyph_item;
+ iter->text = text;
+
+ if (LTR (glyph_item))
+ iter->end_glyph = 0;
+ else
+ iter->end_glyph = glyph_item->glyphs->num_glyphs - 1;
+
+ iter->end_index = glyph_item->item->offset;
+ iter->end_char = 0;
+
+ iter->start_glyph = iter->end_glyph;
+ iter->start_index = iter->end_index;
+ iter->start_char = iter->end_char;
+
+ /* Advance onto the first cluster of the glyph item */
+ return pango2_glyph_item_iter_next_cluster (iter);
+}
+
+/**
+ * pango2_glyph_item_iter_init_end:
+ * @iter: a `Pango2GlyphItemIter`
+ * @glyph_item: the glyph item to iterate over
+ * @text: text corresponding to the glyph item
+ *
+ * Initializes a `Pango2GlyphItemIter` structure to point to the
+ * last cluster in a glyph item.
+ *
+ * See `Pango2GlyphItemIter` for details of cluster orders.
+ *
+ * Return value: %FALSE if there are no clusters in the glyph item
+ */
+gboolean
+pango2_glyph_item_iter_init_end (Pango2GlyphItemIter *iter,
+ Pango2GlyphItem *glyph_item,
+ const char *text)
+{
+ iter->glyph_item = glyph_item;
+ iter->text = text;
+
+ if (LTR (glyph_item))
+ iter->start_glyph = glyph_item->glyphs->num_glyphs;
+ else
+ iter->start_glyph = -1;
+
+ iter->start_index = glyph_item->item->offset + glyph_item->item->length;
+ iter->start_char = glyph_item->item->num_chars;
+
+ iter->end_glyph = iter->start_glyph;
+ iter->end_index = iter->start_index;
+ iter->end_char = iter->start_char;
+
+ /* Advance onto the first cluster of the glyph item */
+ return pango2_glyph_item_iter_prev_cluster (iter);
+}
+
+typedef struct
+{
+ Pango2GlyphItemIter iter;
+
+ GSList *segment_attrs;
+} ApplyAttrsState;
+
+/* Tack @attrs onto the attributes of glyph_item
+ */
+static void
+append_attrs (Pango2GlyphItem *glyph_item,
+ GSList *attrs)
+{
+ glyph_item->item->analysis.extra_attrs =
+ g_slist_concat (glyph_item->item->analysis.extra_attrs, attrs);
+}
+
+/* Make a deep copy of a GSList of Pango2Attribute
+ */
+static GSList *
+attr_slist_copy (GSList *attrs)
+{
+ GSList *tmp_list;
+ GSList *new_attrs;
+
+ new_attrs = g_slist_copy (attrs);
+
+ for (tmp_list = new_attrs; tmp_list; tmp_list = tmp_list->next)
+ tmp_list->data = pango2_attribute_copy (tmp_list->data);
+
+ return new_attrs;
+}
+
+/* Split the glyph item at the start of the current cluster
+ */
+static Pango2GlyphItem *
+split_before_cluster_start (ApplyAttrsState *state)
+{
+ Pango2GlyphItem *split_item;
+ int split_len = state->iter.start_index - state->iter.glyph_item->item->offset;
+
+ split_item = pango2_glyph_item_split (state->iter.glyph_item, state->iter.text, split_len);
+ append_attrs (split_item, state->segment_attrs);
+
+ /* Adjust iteration to account for the split
+ */
+ if (LTR (state->iter.glyph_item))
+ {
+ state->iter.start_glyph -= split_item->glyphs->num_glyphs;
+ state->iter.end_glyph -= split_item->glyphs->num_glyphs;
+ }
+
+ state->iter.start_char -= split_item->item->num_chars;
+ state->iter.end_char -= split_item->item->num_chars;
+
+ return split_item;
+}
+
+/**
+ * pango2_glyph_item_apply_attrs:
+ * @glyph_item: a shaped item
+ * @text: text that @list applies to
+ * @list: a `Pango2AttrList`
+ *
+ * Splits a shaped item (`Pango2GlyphItem`) into multiple items based
+ * on an attribute list.
+ *
+ * The idea is that if you have attributes that don't affect shaping,
+ * such as color or underline, to avoid affecting shaping, you filter
+ * them out ([method@Pango2.AttrList.filter]), apply the shaping process
+ * and then reapply them to the result using this function.
+ *
+ * All attributes that start or end inside a cluster are applied
+ * to that cluster; for instance, if half of a cluster is underlined
+ * and the other-half strikethrough, then the cluster will end
+ * up with both underline and strikethrough attributes. In these
+ * cases, it may happen that @item->extra_attrs for some of the
+ * result items can have multiple attributes of the same type.
+ *
+ * This function takes ownership of @glyph_item; it will be reused
+ * as one of the elements in the list.
+ *
+ * Return value: (transfer full) (element-type Pango2.GlyphItem): a
+ * list of glyph items resulting from splitting @glyph_item. Free
+ * the elements using [method@Pango2.GlyphItem.free], the list using
+ * [func@GLib.SList.free]
+ */
+GSList *
+pango2_glyph_item_apply_attrs (Pango2GlyphItem *glyph_item,
+ const char *text,
+ Pango2AttrList *list)
+{
+ Pango2AttrIterator iter;
+ GSList *result = NULL;
+ ApplyAttrsState state;
+ gboolean start_new_segment = FALSE;
+ gboolean have_cluster;
+ int range_start, range_end;
+ gboolean is_ellipsis;
+
+ /* This routine works by iterating through the item cluster by
+ * cluster; we accumulate the attributes that we need to
+ * add to the next output item, and decide when to split
+ * off an output item based on two criteria:
+ *
+ * A) If start_index < attribute_start < end_index
+ * (attribute starts within cluster) then we need
+ * to split between the last cluster and this cluster.
+ * B) If start_index < attribute_end <= end_index,
+ * (attribute ends within cluster) then we need to
+ * split between this cluster and the next one.
+ */
+
+ /* Advance the attr iterator to the start of the item
+ */
+ pango2_attr_list_init_iterator (list, &iter);
+ do
+ {
+ pango2_attr_iterator_range (&iter, &range_start, &range_end);
+ if (range_end > glyph_item->item->offset)
+ break;
+ }
+ while (pango2_attr_iterator_next (&iter));
+
+ state.segment_attrs = pango2_attr_iterator_get_attrs (&iter);
+
+ is_ellipsis = (glyph_item->item->analysis.flags & PANGO2_ANALYSIS_FLAG_IS_ELLIPSIS) != 0;
+
+ /* Short circuit the case when we don't actually need to
+ * split the item
+ */
+ if (is_ellipsis ||
+ (range_start <= glyph_item->item->offset &&
+ range_end >= glyph_item->item->offset + glyph_item->item->length))
+ goto out;
+
+ for (have_cluster = pango2_glyph_item_iter_init_start (&state.iter, glyph_item, text);
+ have_cluster;
+ have_cluster = pango2_glyph_item_iter_next_cluster (&state.iter))
+ {
+ gboolean have_next;
+
+ /* [range_start,range_end] is the first range that intersects
+ * the current cluster.
+ */
+
+ /* Split item into two, if this cluster isn't a continuation
+ * of the last cluster
+ */
+ if (start_new_segment)
+ {
+ result = g_slist_prepend (result,
+ split_before_cluster_start (&state));
+ state.segment_attrs = pango2_attr_iterator_get_attrs (&iter);
+ }
+
+ start_new_segment = FALSE;
+
+ /* Loop over all ranges that intersect this cluster; exiting
+ * leaving [range_start,range_end] being the first range that
+ * intersects the next cluster.
+ */
+ do
+ {
+ if (range_end > state.iter.end_index) /* Range intersects next cluster */
+ break;
+
+ /* Since ranges end in this cluster, the next cluster goes into a
+ * separate segment
+ */
+ start_new_segment = TRUE;
+
+ have_next = pango2_attr_iterator_next (&iter);
+ pango2_attr_iterator_range (&iter, &range_start, &range_end);
+
+ if (range_start >= state.iter.end_index) /* New range doesn't intersect this cluster */
+ {
+ /* No gap between ranges, so previous range must of ended
+ * at cluster boundary.
+ */
+ g_assert (range_start == state.iter.end_index && start_new_segment);
+ break;
+ }
+
+ /* If any ranges start *inside* this cluster, then we need
+ * to split the previous cluster into a separate segment
+ */
+ if (range_start > state.iter.start_index &&
+ state.iter.start_index != glyph_item->item->offset)
+ {
+ GSList *new_attrs = attr_slist_copy (state.segment_attrs);
+ result = g_slist_prepend (result,
+ split_before_cluster_start (&state));
+ state.segment_attrs = new_attrs;
+ }
+
+ state.segment_attrs = g_slist_concat (state.segment_attrs,
+ pango2_attr_iterator_get_attrs (&iter));
+ }
+ while (have_next);
+ }
+
+ out:
+ /* What's left in glyph_item is the remaining portion
+ */
+ append_attrs (glyph_item, state.segment_attrs);
+ result = g_slist_prepend (result, glyph_item);
+
+ if (LTR (glyph_item))
+ result = g_slist_reverse (result);
+
+ pango2_attr_iterator_clear (&iter);
+
+ return result;
+}
+
+/**
+ * pango2_glyph_item_letter_space:
+ * @glyph_item: a `Pango2GlyphItem`
+ * @text: text that @glyph_item corresponds to
+ * (glyph_item->item->offset is an offset from the
+ * start of @text)
+ * @log_attrs: (array): logical attributes for the item
+ * (the first logical attribute refers to the position
+ * before the first character in the item)
+ * @letter_spacing: amount of letter spacing to add
+ * in Pango2 units. May be negative, though too large
+ * negative values will give ugly results.
+ *
+ * Adds spacing between the graphemes of @glyph_item to
+ * give the effect of typographic letter spacing.
+ */
+void
+pango2_glyph_item_letter_space (Pango2GlyphItem *glyph_item,
+ const char *text,
+ Pango2LogAttr *log_attrs,
+ int letter_spacing)
+{
+ Pango2GlyphItemIter iter;
+ Pango2GlyphInfo *glyphs = glyph_item->glyphs->glyphs;
+ gboolean have_cluster;
+ int space_left, space_right;
+
+ space_left = letter_spacing / 2;
+
+ /* hinting */
+ if ((letter_spacing & (PANGO2_SCALE - 1)) == 0)
+ {
+ space_left = PANGO2_UNITS_ROUND (space_left);
+ }
+
+ space_right = letter_spacing - space_left;
+
+ for (have_cluster = pango2_glyph_item_iter_init_start (&iter, glyph_item, text);
+ have_cluster;
+ have_cluster = pango2_glyph_item_iter_next_cluster (&iter))
+ {
+ if (!log_attrs[iter.start_char].is_cursor_position)
+ {
+ if (glyphs[iter.start_glyph].geometry.width == 0)
+ {
+ if (iter.start_glyph < iter.end_glyph) /* LTR */
+ glyphs[iter.start_glyph].geometry.x_offset -= space_right;
+ else
+ glyphs[iter.start_glyph].geometry.x_offset += space_left;
+ }
+ continue;
+ }
+
+ if (iter.start_glyph < iter.end_glyph) /* LTR */
+ {
+ if (iter.start_char > 0)
+ {
+ glyphs[iter.start_glyph].geometry.width += space_left ;
+ glyphs[iter.start_glyph].geometry.x_offset += space_left ;
+ }
+ if (iter.end_char < glyph_item->item->num_chars)
+ {
+ glyphs[iter.end_glyph-1].geometry.width += space_right;
+ }
+ }
+ else /* RTL */
+ {
+ if (iter.start_char > 0)
+ {
+ glyphs[iter.start_glyph].geometry.width += space_right;
+ }
+ if (iter.end_char < glyph_item->item->num_chars)
+ {
+ glyphs[iter.end_glyph+1].geometry.x_offset += space_left ;
+ glyphs[iter.end_glyph+1].geometry.width += space_left ;
+ }
+ }
+ }
+}
+
+/**
+ * pango2_glyph_item_get_logical_widths:
+ * @glyph_item: a `Pango2GlyphItem`
+ * @text: text that @glyph_item corresponds to
+ * (glyph_item->item->offset is an offset from the
+ * start of @text)
+ * @logical_widths: (array): an array whose length is the number of
+ * characters in glyph_item (equal to glyph_item->item->num_chars)
+ * to be filled in with the resulting character widths.
+ *
+ * Given a `Pango2GlyphItem` and the corresponding text, determine the
+ * width corresponding to each character.
+ *
+ * When multiple characters compose a single cluster, the width of the
+ * entire cluster is divided equally among the characters.
+ *
+ * See also [method@Pango2.GlyphString.get_logical_widths].
+ */
+void
+pango2_glyph_item_get_logical_widths (Pango2GlyphItem *glyph_item,
+ const char *text,
+ int *logical_widths)
+{
+ Pango2GlyphItemIter iter;
+ gboolean has_cluster;
+ int dir;
+
+ dir = glyph_item->item->analysis.level % 2 == 0 ? +1 : -1;
+ for (has_cluster = pango2_glyph_item_iter_init_start (&iter, glyph_item, text);
+ has_cluster;
+ has_cluster = pango2_glyph_item_iter_next_cluster (&iter))
+ {
+ int glyph_index, char_index, num_chars, cluster_width = 0, char_width;
+
+ for (glyph_index = iter.start_glyph;
+ glyph_index != iter.end_glyph;
+ glyph_index += dir)
+ {
+ cluster_width += glyph_item->glyphs->glyphs[glyph_index].geometry.width;
+ }
+
+ num_chars = iter.end_char - iter.start_char;
+ if (num_chars) /* pedantic */
+ {
+ char_width = cluster_width / num_chars;
+
+ for (char_index = iter.start_char;
+ char_index < iter.end_char;
+ char_index++)
+ {
+ logical_widths[char_index] = char_width;
+ }
+
+ /* add any residues to the first char */
+ logical_widths[iter.start_char] += cluster_width - (char_width * num_chars);
+ }
+ }
+}
diff --git a/pango2/pango-glyph-iter-private.h b/pango2/pango-glyph-iter-private.h
new file mode 100644
index 00000000..5321b33d
--- /dev/null
+++ b/pango2/pango-glyph-iter-private.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2002 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-glyph-item-private.h"
+
+G_BEGIN_DECLS
+
+/**
+ * Pango2GlyphItemIter:
+ *
+ * A `Pango2GlyphItemIter` is an iterator over the clusters in a
+ * `Pango2GlyphItem`.
+ *
+ * The *forward direction* of the iterator is the logical direction of text.
+ * That is, with increasing @start_index and @start_char values. If @glyph_item
+ * is right-to-left (that is, if `glyph_item->item->analysis.level` is odd),
+ * then @start_glyph decreases as the iterator moves forward. Moreover,
+ * in right-to-left cases, @start_glyph is greater than @end_glyph.
+ *
+ * An iterator should be initialized using either
+ * [method@Pango2.GlyphItemIter.init_start] or
+ * [method@Pango2.GlyphItemIter.init_end], for forward and backward iteration
+ * respectively, and walked over using any desired mixture of
+ * [method@Pango2.GlyphItemIter.next_cluster] and
+ * [method@Pango2.GlyphItemIter.prev_cluster].
+ *
+ * A common idiom for doing a forward iteration over the clusters is:
+ *
+ * ```
+ * Pango2GlyphItemIter cluster_iter;
+ * gboolean have_cluster;
+ *
+ * for (have_cluster = pango2_glyph_item_iter_init_start (&cluster_iter,
+ * glyph_item, text);
+ * have_cluster;
+ * have_cluster = pango2_glyph_item_iter_next_cluster (&cluster_iter))
+ * {
+ * ...
+ * }
+ * ```
+ *
+ * Note that @text is the start of the text for layout, which is then
+ * indexed by `glyph_item->item->offset` to get to the text of @glyph_item.
+ * The @start_index and @end_index values can directly index into @text. The
+ * @start_glyph, @end_glyph, @start_char, and @end_char values however are
+ * zero-based for the @glyph_item. For each cluster, the item pointed at by
+ * the start variables is included in the cluster while the one pointed at by
+ * end variables is not.
+ *
+ * None of the members of a `Pango2GlyphItemIter` should be modified manually.
+ */
+typedef struct _Pango2GlyphItemIter Pango2GlyphItemIter;
+
+struct _Pango2GlyphItemIter
+{
+ Pango2GlyphItem *glyph_item;
+ const char *text;
+
+ int start_glyph;
+ int start_index;
+ int start_char;
+
+ int end_glyph;
+ int end_index;
+ int end_char;
+};
+
+#define PANGO2_TYPE_GLYPH_ITEM_ITER (pango2_glyph_item_iter_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_glyph_item_iter_get_type (void) G_GNUC_CONST;
+PANGO2_AVAILABLE_IN_ALL
+Pango2GlyphItemIter * pango2_glyph_item_iter_copy (Pango2GlyphItemIter *orig);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_item_iter_free (Pango2GlyphItemIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_glyph_item_iter_init_start (Pango2GlyphItemIter *iter,
+ Pango2GlyphItem *glyph_item,
+ const char *text);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_glyph_item_iter_init_end (Pango2GlyphItemIter *iter,
+ Pango2GlyphItem *glyph_item,
+ const char *text);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_glyph_item_iter_next_cluster (Pango2GlyphItemIter *iter);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_glyph_item_iter_prev_cluster (Pango2GlyphItemIter *iter);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2GlyphItemIter, pango2_glyph_item_iter_free)
+
+G_END_DECLS
diff --git a/pango2/pango-glyph.h b/pango2/pango-glyph.h
new file mode 100644
index 00000000..7cc014a2
--- /dev/null
+++ b/pango2/pango-glyph.h
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-item.h>
+#include <pango2/pango-break.h>
+
+G_BEGIN_DECLS
+
+typedef struct _Pango2GlyphGeometry Pango2GlyphGeometry;
+typedef struct _Pango2GlyphVisAttr Pango2GlyphVisAttr;
+typedef struct _Pango2GlyphInfo Pango2GlyphInfo;
+typedef struct _Pango2GlyphString Pango2GlyphString;
+
+/* 1024ths of a device unit */
+/**
+ * Pango2GlyphUnit:
+ *
+ * The `Pango2GlyphUnit` type is used to store dimensions within
+ * Pango2.
+ *
+ * Dimensions are stored in 1/PANGO2_SCALE of a device unit.
+ * (A device unit might be a pixel for screen display, or
+ * a point on a printer.) PANGO2_SCALE is currently 1024, and
+ * may change in the future (unlikely though), but you should not
+ * depend on its exact value.
+ *
+ * The PANGO2_PIXELS() macro can be used to convert from glyph units
+ * into device units with correct rounding.
+ */
+typedef gint32 Pango2GlyphUnit;
+
+/* Positioning information about a glyph
+ */
+/**
+ * Pango2GlyphGeometry:
+ * @width: the logical width to use for the the character.
+ * @x_offset: horizontal offset from nominal character position.
+ * @y_offset: vertical offset from nominal character position.
+ *
+ * The `Pango2GlyphGeometry` structure contains width and positioning
+ * information for a single glyph.
+ *
+ * Note that @width is not guaranteed to be the same as the glyph
+ * extents. Kerning and other positioning applied during shaping will
+ * affect both the @width and the @x_offset for the glyphs in the
+ * glyph string that results from shaping.
+ *
+ * The information in this struct is intended for rendering the glyphs,
+ * as follows:
+ *
+ * 1. Assume the current point is (x, y)
+ * 2. Render the current glyph at (x + x_offset, y + y_offset),
+ * 3. Advance the current point to (x + width, y)
+ * 4. Render the next glyph
+ */
+struct _Pango2GlyphGeometry
+{
+ Pango2GlyphUnit width;
+ Pango2GlyphUnit x_offset;
+ Pango2GlyphUnit y_offset;
+};
+
+/* Visual attributes of a glyph
+ */
+/**
+ * Pango2GlyphVisAttr:
+ * @is_cluster_start: set for the first logical glyph in each cluster.
+ * @is_color: set if the the font will render this glyph with color. Since 1.50
+ *
+ * A `Pango2GlyphVisAttr` structure communicates information between
+ * the shaping and rendering phases.
+ *
+ * Currently, it contains cluster start and color information.
+ * More attributes may be added in the future.
+ *
+ * Clusters are stored in visual order, within the cluster, glyphs
+ * are always ordered in logical order, since visual order is meaningless;
+ * that is, in Arabic text, accent glyphs follow the glyphs for the
+ * base character.
+ */
+struct _Pango2GlyphVisAttr
+{
+ guint is_cluster_start : 1;
+ guint is_color : 1;
+};
+
+/* A single glyph
+ */
+/**
+ * Pango2GlyphInfo:
+ * @glyph: the glyph itself.
+ * @geometry: the positional information about the glyph.
+ * @attr: the visual attributes of the glyph.
+ *
+ * A `Pango2GlyphInfo` structure represents a single glyph with
+ * positioning information and visual attributes.
+ */
+struct _Pango2GlyphInfo
+{
+ Pango2Glyph glyph;
+ Pango2GlyphGeometry geometry;
+ Pango2GlyphVisAttr attr;
+};
+
+/**
+ * Pango2GlyphString:
+ * @num_glyphs: number of glyphs in this glyph string
+ * @glyphs: (array length=num_glyphs): array of glyph information
+ * @log_clusters: logical cluster info, indexed by the byte index
+ * within the text corresponding to the glyph string
+ *
+ * A `Pango2GlyphString` is used to store strings of glyphs with geometry
+ * and visual attribute information.
+ *
+ * The storage for the glyph information is owned by the structure
+ * which simplifies memory management.
+ */
+struct _Pango2GlyphString {
+ int num_glyphs;
+
+ Pango2GlyphInfo *glyphs;
+ int *log_clusters;
+
+ /*< private >*/
+ int space;
+};
+
+#define PANGO2_TYPE_GLYPH_STRING (pango2_glyph_string_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_glyph_string_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2GlyphString * pango2_glyph_string_new (void);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_string_set_size (Pango2GlyphString *string,
+ int new_len);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2GlyphString * pango2_glyph_string_copy (Pango2GlyphString *string);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_string_free (Pango2GlyphString *string);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_string_extents (Pango2GlyphString *glyphs,
+ Pango2Font *font,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect);
+PANGO2_AVAILABLE_IN_ALL
+int pango2_glyph_string_get_width (Pango2GlyphString *glyphs);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_string_extents_range (Pango2GlyphString *glyphs,
+ int start,
+ int end,
+ Pango2Font *font,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_string_get_logical_widths (Pango2GlyphString *glyphs,
+ const char *text,
+ int length,
+ int embedding_level,
+ int *logical_widths);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_string_index_to_x (Pango2GlyphString *glyphs,
+ const char *text,
+ int length,
+ const Pango2Analysis *analysis,
+ int index_,
+ gboolean trailing,
+ int *x_pos);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_string_x_to_index (Pango2GlyphString *glyphs,
+ const char *text,
+ int length,
+ const Pango2Analysis *analysis,
+ int x_pos,
+ int *index_,
+ int *trailing);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_glyph_string_index_to_x_full (Pango2GlyphString *glyphs,
+ const char *text,
+ int length,
+ const Pango2Analysis *analysis,
+ Pango2LogAttr *attrs,
+ int index_,
+ gboolean trailing,
+ int *x_pos);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2GlyphString, pango2_glyph_string_free)
+
+
+/* Shaping */
+
+/**
+ * Pango2ShapeFlags:
+ * @PANGO2_SHAPE_NONE: Default value
+ * @PANGO2_SHAPE_ROUND_POSITIONS: Round glyph positions and widths to whole device units
+ * This option should be set if the target renderer can't do subpixel positioning of glyphs
+ *
+ * `Pango2ShapeFlags` influence the shaping process.
+ *
+ * These flags can be passed to [func@Pango2.shape].
+ */
+typedef enum {
+ PANGO2_SHAPE_NONE = 0,
+ PANGO2_SHAPE_ROUND_POSITIONS = 1 << 0,
+} Pango2ShapeFlags;
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_shape (const char *item_text,
+ int item_length,
+ const char *paragraph_text,
+ int paragraph_length,
+ const Pango2Analysis *analysis,
+ Pango2GlyphString *glyphs,
+ Pango2ShapeFlags flags);
+
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_shape_item (Pango2Item *item,
+ const char *paragraph_text,
+ int paragraph_length,
+ Pango2LogAttr *log_attrs,
+ Pango2GlyphString *glyphs,
+ Pango2ShapeFlags flags);
+
+/**
+ * PANGO2_GLYPH_EMPTY:
+ *
+ * A `Pango2Glyph` value that indicates a zero-width empty glpyh.
+ *
+ * This is useful for example in shaper modules, to use as the glyph for
+ * various zero-width Unicode characters (those passing [func@is_zero_width]).
+ */
+#define PANGO2_GLYPH_EMPTY ((Pango2Glyph)0x0FFFFFFF)
+
+/**
+ * PANGO2_GLYPH_INVALID_INPUT:
+ *
+ * A `Pango2Glyph` value for invalid input.
+ *
+ * `Pango2Layout` produces one such glyph per invalid input UTF-8 byte and such
+ * a glyph is rendered as a crossed box.
+ *
+ * Note that this value is defined such that it has the %PANGO2_GLYPH_UNKNOWN_FLAG
+ * set.
+ */
+#define PANGO2_GLYPH_INVALID_INPUT ((Pango2Glyph)0xFFFFFFFF)
+
+/**
+ * PANGO2_GLYPH_UNKNOWN_FLAG:
+ *
+ * Flag used in `Pango2Glyph` to turn a `gunichar` value of a valid Unicode
+ * character into an unknown-character glyph for that `gunichar`.
+ *
+ * Such unknown-character glyphs may be rendered as a 'hex box'.
+ */
+#define PANGO2_GLYPH_UNKNOWN_FLAG ((Pango2Glyph)0x10000000)
+
+/**
+ * PANGO2_GET_UNKNOWN_GLYPH:
+ * @wc: a Unicode character
+ *
+ * The way this unknown glyphs are rendered is backend specific. For example,
+ * a box with the hexadecimal Unicode code-point of the character written in it
+ * is what is done in the most common backends.
+ *
+ * Returns: a `Pango2Glyph` value that means no glyph was found for @wc.
+ */
+#define PANGO2_GET_UNKNOWN_GLYPH(wc) ((Pango2Glyph)(wc)|PANGO2_GLYPH_UNKNOWN_FLAG)
+
+
+G_END_DECLS
diff --git a/pango2/pango-gravity.c b/pango2/pango-gravity.c
new file mode 100644
index 00000000..0e9b4299
--- /dev/null
+++ b/pango2/pango-gravity.c
@@ -0,0 +1,457 @@
+/* Pango2
+ * pango-gravity.c: Gravity routines
+ *
+ * Copyright (C) 2006, 2007 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.
+ */
+
+#include "config.h"
+
+#include "pango-gravity.h"
+
+#include <math.h>
+
+/**
+ * pango2_gravity_to_rotation:
+ * @gravity: gravity to query, should not be %PANGO2_GRAVITY_AUTO
+ *
+ * Converts a `Pango2Gravity` value to its natural rotation in radians.
+ *
+ * Note that [method@Pango2.Matrix.rotate] takes angle in degrees, not radians.
+ * So, to call [method@Pango2.Matrix,rotate] with the output of this function
+ * you should multiply it by (180. / G_PI).
+ *
+ * Return value: the rotation value corresponding to @gravity.
+ */
+double
+pango2_gravity_to_rotation (Pango2Gravity gravity)
+{
+ double rotation;
+
+ g_return_val_if_fail (gravity != PANGO2_GRAVITY_AUTO, 0);
+
+ switch (gravity)
+ {
+ default:
+ case PANGO2_GRAVITY_AUTO: /* shut gcc up */
+ case PANGO2_GRAVITY_SOUTH:
+ rotation = 0;
+ break;
+ case PANGO2_GRAVITY_NORTH:
+ rotation = G_PI;
+ break;
+ case PANGO2_GRAVITY_EAST:
+ rotation = -G_PI_2;
+ break;
+ case PANGO2_GRAVITY_WEST:
+ rotation = +G_PI_2;
+ break;
+ }
+
+ return rotation;
+}
+
+/**
+ * pango2_gravity_get_for_matrix:
+ * @matrix: (nullable): a `Pango2Matrix`
+ *
+ * Finds the gravity that best matches the rotation component
+ * in a `Pango2Matrix`.
+ *
+ * Return value: the gravity of @matrix, which will never be
+ * %PANGO2_GRAVITY_AUTO, or %PANGO2_GRAVITY_SOUTH if @matrix is %NULL
+ */
+Pango2Gravity
+pango2_gravity_get_for_matrix (const Pango2Matrix *matrix)
+{
+ Pango2Gravity gravity;
+ double x;
+ double y;
+
+ if (!matrix)
+ return PANGO2_GRAVITY_SOUTH;
+
+ x = matrix->xy;
+ y = matrix->yy;
+
+ if (fabs (x) > fabs (y))
+ gravity = x > 0 ? PANGO2_GRAVITY_WEST : PANGO2_GRAVITY_EAST;
+ else
+ gravity = y < 0 ? PANGO2_GRAVITY_NORTH : PANGO2_GRAVITY_SOUTH;
+
+ return gravity;
+}
+
+
+
+typedef enum
+{
+ PANGO2_VERTICAL_DIRECTION_NONE,
+ PANGO2_VERTICAL_DIRECTION_TTB,
+ PANGO2_VERTICAL_DIRECTION_BTT
+} Pango2VerticalDirection;
+
+typedef struct {
+ /* Pango2Direction */
+ guint8 horiz_dir; /* Orientation in horizontal context */
+
+ /* Pango2VerticalDirection */
+ guint8 vert_dir; /* Orientation in vertical context */
+
+ /* Pango2Gravity */
+ guint8 preferred_gravity; /* Preferred context gravity */
+
+ /* gboolean */
+ guint8 wide; /* Whether script is mostly wide.
+ * Wide characters are upright (ie.
+ * not rotated) in foreign context */
+} Pango2ScriptProperties;
+
+#define NONE PANGO2_VERTICAL_DIRECTION_NONE
+#define TTB PANGO2_VERTICAL_DIRECTION_TTB
+#define BTT PANGO2_VERTICAL_DIRECTION_BTT
+
+#define LTR PANGO2_DIRECTION_LTR
+#define RTL PANGO2_DIRECTION_RTL
+#define WEAK PANGO2_DIRECTION_WEAK_LTR
+
+#define S PANGO2_GRAVITY_SOUTH
+#define E PANGO2_GRAVITY_EAST
+#define W PANGO2_GRAVITY_WEST
+
+const Pango2ScriptProperties script_properties[] =
+ { /* ISO 15924 code */
+ {LTR, NONE, S, FALSE}, /* Zyyy */
+ {LTR, NONE, S, FALSE}, /* Qaai */
+ {RTL, NONE, S, FALSE}, /* Arab */
+ {LTR, NONE, S, FALSE}, /* Armn */
+ {LTR, NONE, S, FALSE}, /* Beng */
+ {LTR, TTB, E, TRUE }, /* Bopo */
+ {LTR, NONE, S, FALSE}, /* Cher */
+ {LTR, NONE, S, FALSE}, /* Qaac */
+ {LTR, NONE, S, FALSE}, /* Cyrl (Cyrs) */
+ {LTR, NONE, S, FALSE}, /* Dsrt */
+ {LTR, NONE, S, FALSE}, /* Deva */
+ {LTR, NONE, S, FALSE}, /* Ethi */
+ {LTR, NONE, S, FALSE}, /* Geor (Geon, Geoa) */
+ {LTR, NONE, S, FALSE}, /* Goth */
+ {LTR, NONE, S, FALSE}, /* Grek */
+ {LTR, NONE, S, FALSE}, /* Gujr */
+ {LTR, NONE, S, FALSE}, /* Guru */
+ {LTR, TTB, E, TRUE }, /* Hani */
+ {LTR, TTB, E, TRUE }, /* Hang */
+ {RTL, NONE, S, FALSE}, /* Hebr */
+ {LTR, TTB, E, TRUE }, /* Hira */
+ {LTR, NONE, S, FALSE}, /* Knda */
+ {LTR, TTB, E, TRUE }, /* Kana */
+ {LTR, NONE, S, FALSE}, /* Khmr */
+ {LTR, NONE, S, FALSE}, /* Laoo */
+ {LTR, NONE, S, FALSE}, /* Latn (Latf, Latg) */
+ {LTR, NONE, S, FALSE}, /* Mlym */
+ {WEAK,TTB, W, FALSE}, /* Mong */
+ {LTR, NONE, S, FALSE}, /* Mymr */
+ {LTR, BTT, W, FALSE}, /* Ogam */
+ {LTR, NONE, S, FALSE}, /* Ital */
+ {LTR, NONE, S, FALSE}, /* Orya */
+ {LTR, NONE, S, FALSE}, /* Runr */
+ {LTR, NONE, S, FALSE}, /* Sinh */
+ {RTL, NONE, S, FALSE}, /* Syrc (Syrj, Syrn, Syre) */
+ {LTR, NONE, S, FALSE}, /* Taml */
+ {LTR, NONE, S, FALSE}, /* Telu */
+ {RTL, NONE, S, FALSE}, /* Thaa */
+ {LTR, NONE, S, FALSE}, /* Thai */
+ {LTR, NONE, S, FALSE}, /* Tibt */
+ {LTR, NONE, S, FALSE}, /* Cans */
+ {LTR, TTB, S, TRUE }, /* Yiii */
+ {LTR, NONE, S, FALSE}, /* Tglg */
+ {LTR, NONE, S, FALSE}, /* Hano */
+ {LTR, NONE, S, FALSE}, /* Buhd */
+ {LTR, NONE, S, FALSE}, /* Tagb */
+
+ /* Unicode-4.0 additions */
+ {LTR, NONE, S, FALSE}, /* Brai */
+ {RTL, NONE, S, FALSE}, /* Cprt */
+ {LTR, NONE, S, FALSE}, /* Limb */
+ {LTR, NONE, S, FALSE}, /* Osma */
+ {LTR, NONE, S, FALSE}, /* Shaw */
+ {LTR, NONE, S, FALSE}, /* Linb */
+ {LTR, NONE, S, FALSE}, /* Tale */
+ {LTR, NONE, S, FALSE}, /* Ugar */
+
+ /* Unicode-4.1 additions */
+ {LTR, NONE, S, FALSE}, /* Talu */
+ {LTR, NONE, S, FALSE}, /* Bugi */
+ {LTR, NONE, S, FALSE}, /* Glag */
+ {LTR, NONE, S, FALSE}, /* Tfng */
+ {LTR, NONE, S, FALSE}, /* Sylo */
+ {LTR, NONE, S, FALSE}, /* Xpeo */
+ {RTL, NONE, S, FALSE}, /* Khar */
+
+ /* Unicode-5.0 additions */
+ {LTR, NONE, S, FALSE}, /* Zzzz */
+ {LTR, NONE, S, FALSE}, /* Bali */
+ {LTR, NONE, S, FALSE}, /* Xsux */
+ {RTL, NONE, S, FALSE}, /* Phnx */
+ {LTR, NONE, S, FALSE}, /* Phag */
+ {RTL, NONE, S, FALSE}, /* Nkoo */
+
+ /* Unicode-5.1 additions */
+ {LTR, NONE, S, FALSE}, /* Kali */
+ {LTR, NONE, S, FALSE}, /* Lepc */
+ {LTR, NONE, S, FALSE}, /* Rjng */
+ {LTR, NONE, S, FALSE}, /* Sund */
+ {LTR, NONE, S, FALSE}, /* Saur */
+ {LTR, NONE, S, FALSE}, /* Cham */
+ {LTR, NONE, S, FALSE}, /* Olck */
+ {LTR, NONE, S, FALSE}, /* Vaii */
+ {LTR, NONE, S, FALSE}, /* Cari */
+ {LTR, NONE, S, FALSE}, /* Lyci */
+ {RTL, NONE, S, FALSE}, /* Lydi */
+
+ /* Unicode-5.2 additions */
+ {RTL, NONE, S, FALSE}, /* Avst */
+ {LTR, NONE, S, FALSE}, /* Bamu */
+ {LTR, NONE, S, FALSE}, /* Egyp */
+ {RTL, NONE, S, FALSE}, /* Armi */
+ {RTL, NONE, S, FALSE}, /* Phli */
+ {RTL, NONE, S, FALSE}, /* Prti */
+ {LTR, NONE, S, FALSE}, /* Java */
+ {LTR, NONE, S, FALSE}, /* Kthi */
+ {LTR, NONE, S, FALSE}, /* Lisu */
+ {LTR, NONE, S, FALSE}, /* Mtei */
+ {RTL, NONE, S, FALSE}, /* Sarb */
+ {RTL, NONE, S, FALSE}, /* Orkh */
+ {RTL, TTB, S, FALSE}, /* Samr */
+ {LTR, NONE, S, FALSE}, /* Lana */
+ {LTR, NONE, S, FALSE}, /* Tavt */
+
+ /* Unicode-6.0 additions */
+ {LTR, NONE, S, FALSE}, /* Batk */
+ {LTR, NONE, S, FALSE}, /* Brah */
+ {RTL, NONE, S, FALSE}, /* Mand */
+
+ /* Unicode-6.1 additions */
+ {LTR, NONE, S, FALSE}, /* Cakm */
+ {RTL, NONE, S, FALSE}, /* Merc */
+ {RTL, NONE, S, FALSE}, /* Mero */
+ {LTR, NONE, S, FALSE}, /* Plrd */
+ {LTR, NONE, S, FALSE}, /* Shrd */
+ {LTR, NONE, S, FALSE}, /* Sora */
+ {LTR, NONE, S, FALSE}, /* Takr */
+
+ /* Unicode-7.0 additions */
+ {LTR, NONE, S, FALSE}, /* Bass */
+ {LTR, NONE, S, FALSE}, /* Aghb */
+ {LTR, NONE, S, FALSE}, /* Dupl */
+ {LTR, NONE, S, FALSE}, /* Elba */
+ {LTR, NONE, S, FALSE}, /* Gran */
+ {LTR, NONE, S, FALSE}, /* Khoj */
+ {LTR, NONE, S, FALSE}, /* Sind */
+ {LTR, NONE, S, FALSE}, /* Lina */
+ {LTR, NONE, S, FALSE}, /* Mahj */
+ {RTL, NONE, S, FALSE}, /* Mani */
+ {RTL, NONE, S, FALSE}, /* Mend */
+ {LTR, NONE, S, FALSE}, /* Modi */
+ {LTR, NONE, S, FALSE}, /* Mroo */
+ {RTL, NONE, S, FALSE}, /* Nbat */
+ {RTL, NONE, S, FALSE}, /* Narb */
+ {LTR, NONE, S, FALSE}, /* Perm */
+ {LTR, NONE, S, FALSE}, /* Hmng */
+ {RTL, NONE, S, FALSE}, /* Palm */
+ {LTR, NONE, S, FALSE}, /* Pauc */
+ {RTL, NONE, S, FALSE}, /* Phlp */
+ {LTR, NONE, S, FALSE}, /* Sidd */
+ {LTR, NONE, S, FALSE}, /* Tirh */
+ {LTR, NONE, S, FALSE}, /* Wara */
+
+ /* Unicode-8.0 additions */
+ {LTR, NONE, S, FALSE}, /* Ahom */
+ {LTR, NONE, S, FALSE}, /* Hluw */
+ {RTL, NONE, S, FALSE}, /* Hatr */
+ {LTR, NONE, S, FALSE}, /* Mult */
+ {LTR, NONE, S, FALSE}, /* Hung */
+ {LTR, NONE, S, FALSE}, /* Sgnw */
+
+ /* Unicode-9.0 additions */
+ {RTL, NONE, S, FALSE}, /* Adlm */
+ {LTR, NONE, S, FALSE}, /* Bhks */
+ {LTR, NONE, S, FALSE}, /* Marc */
+ {LTR, NONE, S, FALSE}, /* Newa */
+ {LTR, NONE, S, FALSE}, /* Osge */
+ {LTR, NONE, S, FALSE}, /* Tang */
+
+ /* Unicode-10.0 additions */
+ {LTR, NONE, S, FALSE}, /* Gonm */
+ {LTR, NONE, S, FALSE}, /* Nshu */
+ {LTR, NONE, S, FALSE}, /* Soyo */
+ {LTR, NONE, S, FALSE}, /* Zanb */
+
+ /* Unicode-11.0 additions */
+ {LTR, NONE, S, FALSE}, /* Dogr */
+ {LTR, NONE, S, FALSE}, /* Gong */
+ {RTL, NONE, S, FALSE}, /* Rohg */
+ {LTR, NONE, S, FALSE}, /* Maka */
+ {LTR, NONE, S, FALSE}, /* Medf */
+ {RTL, NONE, S, FALSE}, /* Sogo */
+ {RTL, NONE, S, FALSE}, /* Sogd */
+
+ /* Unicode-12.0 additions */
+ {RTL, NONE, S, FALSE}, /* Elym */
+ {LTR, NONE, S, FALSE}, /* Nand */
+ {LTR, NONE, S, FALSE}, /* Rohg */
+ {LTR, NONE, S, FALSE}, /* Wcho */
+
+ /* Unicode-13.0 additions */
+ {RTL, NONE, S, FALSE}, /* Chrs */
+ {LTR, NONE, S, FALSE}, /* Diak */
+ {LTR, NONE, S, FALSE}, /* Kits */
+ {RTL, NONE, S, FALSE}, /* Yezi */
+
+ {LTR, NONE, S, FALSE}, /* Cpmn */
+ {RTL, NONE, S, FALSE}, /* Ougr */
+ {LTR, NONE, S, FALSE}, /* Tnsa */
+ {LTR, NONE, S, FALSE}, /* Toto */
+ {LTR, NONE, S, FALSE}, /* Vith */
+};
+
+#undef NONE
+#undef TTB
+#undef BTT
+
+#undef LTR
+#undef RTL
+#undef WEAK
+
+#undef S
+#undef E
+#undef N
+#undef W
+
+static Pango2ScriptProperties
+get_script_properties (GUnicodeScript script)
+{
+ g_return_val_if_fail (script >= 0, script_properties[0]);
+
+ if ((guint)script >= G_N_ELEMENTS (script_properties))
+ return script_properties[0];
+
+ return script_properties[script];
+}
+
+/**
+ * pango2_gravity_get_for_script:
+ * @script: `Pango2Script` to query
+ * @base_gravity: base gravity of the paragraph
+ * @hint: orientation hint
+ *
+ * Returns the gravity to use in laying out a `Pango2Item`.
+ *
+ * The gravity is determined based on the script, base gravity, and hint.
+ *
+ * If @base_gravity is %PANGO2_GRAVITY_AUTO, it is first replaced with the
+ * preferred gravity of @script. To get the preferred gravity of a script,
+ * pass %PANGO2_GRAVITY_AUTO and %PANGO2_GRAVITY_HINT_STRONG in.
+ *
+ * Return value: resolved gravity suitable to use for a run of text
+ * with @script
+ */
+Pango2Gravity
+pango2_gravity_get_for_script (GUnicodeScript script,
+ Pango2Gravity base_gravity,
+ Pango2GravityHint hint)
+{
+ Pango2ScriptProperties props = get_script_properties (script);
+
+ return pango2_gravity_get_for_script_and_width (script, props.wide,
+ base_gravity, hint);
+}
+
+/**
+ * pango2_gravity_get_for_script_and_width:
+ * @script: `Pango2Script` to query
+ * @wide: %TRUE for wide characters as returned by g_unichar_iswide()
+ * @base_gravity: base gravity of the paragraph
+ * @hint: orientation hint
+ *
+ * Returns the gravity to use in laying out a single character
+ * or `Pango2Item`.
+ *
+ * The gravity is determined based on the script, East Asian width,
+ * base gravity, and hint,
+ *
+ * This function is similar to [func@Pango2.Gravity.get_for_script] except
+ * that this function makes a distinction between narrow/half-width and
+ * wide/full-width characters also. Wide/full-width characters always
+ * stand *upright*, that is, they always take the base gravity,
+ * whereas narrow/full-width characters are always rotated in vertical
+ * context.
+ *
+ * If @base_gravity is %PANGO2_GRAVITY_AUTO, it is first replaced with the
+ * preferred gravity of @script.
+ *
+ * Return value: resolved gravity suitable to use for a run of text
+ * with @script and @wide.
+ */
+Pango2Gravity
+pango2_gravity_get_for_script_and_width (GUnicodeScript script,
+ gboolean wide,
+ Pango2Gravity base_gravity,
+ Pango2GravityHint hint)
+{
+ Pango2ScriptProperties props = get_script_properties (script);
+ gboolean vertical;
+
+ if (G_UNLIKELY (base_gravity == PANGO2_GRAVITY_AUTO))
+ base_gravity = props.preferred_gravity;
+
+ vertical = PANGO2_GRAVITY_IS_VERTICAL (base_gravity);
+
+ /* Everything is designed such that a system with no vertical support
+ * renders everything correctly horizontally. So, if not in a vertical
+ * gravity, base and resolved gravities are always the same.
+ *
+ * Wide characters are always upright.
+ */
+ if (G_LIKELY (!vertical || wide))
+ return base_gravity;
+
+ /* If here, we have a narrow character in a vertical gravity setting.
+ * Resolve depending on the hint.
+ */
+ switch (hint)
+ {
+ default:
+ case PANGO2_GRAVITY_HINT_NATURAL:
+ if (props.vert_dir == PANGO2_VERTICAL_DIRECTION_NONE)
+ return PANGO2_GRAVITY_SOUTH;
+ if ((base_gravity == PANGO2_GRAVITY_EAST) ^
+ (props.vert_dir == PANGO2_VERTICAL_DIRECTION_BTT))
+ return PANGO2_GRAVITY_SOUTH;
+ else
+ return PANGO2_GRAVITY_NORTH;
+
+ case PANGO2_GRAVITY_HINT_STRONG:
+ return base_gravity;
+
+ case PANGO2_GRAVITY_HINT_LINE:
+ if ((base_gravity == PANGO2_GRAVITY_EAST) ^
+ (props.horiz_dir == PANGO2_DIRECTION_RTL))
+ return PANGO2_GRAVITY_SOUTH;
+ else
+ return PANGO2_GRAVITY_NORTH;
+ }
+}
diff --git a/pango2/pango-gravity.h b/pango2/pango-gravity.h
new file mode 100644
index 00000000..b3e79d3d
--- /dev/null
+++ b/pango2/pango-gravity.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2006, 2007 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+
+G_BEGIN_DECLS
+
+/**
+ * Pango2Gravity:
+ * @PANGO2_GRAVITY_SOUTH: Glyphs stand upright (default) <img align="right" valign="center" src="m-south.png">
+ * @PANGO2_GRAVITY_EAST: Glyphs are rotated 90 degrees counter-clockwise. <img align="right" valign="center" src="m-east.png">
+ * @PANGO2_GRAVITY_NORTH: Glyphs are upside-down. <img align="right" valign="cener" src="m-north.png">
+ * @PANGO2_GRAVITY_WEST: Glyphs are rotated 90 degrees clockwise. <img align="right" valign="center" src="m-west.png">
+ * @PANGO2_GRAVITY_AUTO: Gravity is resolved from the context matrix
+ *
+ * `Pango2Gravity` represents the orientation of glyphs in a segment
+ * of text.
+ *
+ * This is useful when rendering vertical text layouts. In those situations,
+ * the layout is rotated using a non-identity [struct@Pango2.Matrix], and then
+ * glyph orientation is controlled using `Pango2Gravity`.
+ *
+ * Not every value in this enumeration makes sense for every usage of
+ * `Pango2Gravity`; for example, %PANGO2_GRAVITY_AUTO only can be passed to
+ * [method@Pango2.Context.set_base_gravity] and can only be returned by
+ * [method@Pango2.Context.get_base_gravity].
+ *
+ * See also: [enum@Pango2.GravityHint]
+ */
+typedef enum {
+ PANGO2_GRAVITY_SOUTH,
+ PANGO2_GRAVITY_EAST,
+ PANGO2_GRAVITY_NORTH,
+ PANGO2_GRAVITY_WEST,
+ PANGO2_GRAVITY_AUTO
+} Pango2Gravity;
+
+/**
+ * Pango2GravityHint:
+ * @PANGO2_GRAVITY_HINT_NATURAL: scripts will take their natural gravity based
+ * on the base gravity and the script. This is the default.
+ * @PANGO2_GRAVITY_HINT_STRONG: always use the base gravity set, regardless of
+ * the script.
+ * @PANGO2_GRAVITY_HINT_LINE: for scripts not in their natural direction (eg.
+ * Latin in East gravity), choose per-script gravity such that every script
+ * respects the line progression. This means, Latin and Arabic will take
+ * opposite gravities and both flow top-to-bottom for example.
+ *
+ * `Pango2GravityHint` defines how horizontal scripts should behave in a
+ * vertical context.
+ *
+ * That is, English excerpts in a vertical paragraph for example.
+ *
+ * See also [enum@Pango2.Gravity]
+ */
+typedef enum {
+ PANGO2_GRAVITY_HINT_NATURAL,
+ PANGO2_GRAVITY_HINT_STRONG,
+ PANGO2_GRAVITY_HINT_LINE
+} Pango2GravityHint;
+
+/**
+ * PANGO2_GRAVITY_IS_VERTICAL:
+ * @gravity: the `Pango2Gravity` to check
+ *
+ * Whether a `Pango2Gravity` represents vertical writing directions.
+ *
+ * Returns: %TRUE if @gravity is %PANGO2_GRAVITY_EAST or %PANGO2_GRAVITY_WEST,
+ * %FALSE otherwise.
+ */
+#define PANGO2_GRAVITY_IS_VERTICAL(gravity) \
+ ((gravity) == PANGO2_GRAVITY_EAST || (gravity) == PANGO2_GRAVITY_WEST)
+
+/**
+ * PANGO2_GRAVITY_IS_IMPROPER:
+ * @gravity: the `Pango2Gravity` to check
+ *
+ * Whether a `Pango2Gravity` represents a gravity that results in reversal
+ * of text direction.
+ *
+ * Returns: %TRUE if @gravity is %PANGO2_GRAVITY_WEST or %PANGO2_GRAVITY_NORTH,
+ * %FALSE otherwise.
+ */
+#define PANGO2_GRAVITY_IS_IMPROPER(gravity) \
+ ((gravity) == PANGO2_GRAVITY_WEST || (gravity) == PANGO2_GRAVITY_NORTH)
+
+#include <pango2/pango-matrix.h>
+#include <pango2/pango-script.h>
+
+PANGO2_AVAILABLE_IN_ALL
+double pango2_gravity_to_rotation (Pango2Gravity gravity) G_GNUC_CONST;
+PANGO2_AVAILABLE_IN_ALL
+Pango2Gravity pango2_gravity_get_for_matrix (const Pango2Matrix *matrix) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+Pango2Gravity pango2_gravity_get_for_script (GUnicodeScript script,
+ Pango2Gravity base_gravity,
+ Pango2GravityHint hint) G_GNUC_CONST;
+PANGO2_AVAILABLE_IN_ALL
+Pango2Gravity pango2_gravity_get_for_script_and_width
+ (GUnicodeScript script,
+ gboolean wide,
+ Pango2Gravity base_gravity,
+ Pango2GravityHint hint) G_GNUC_CONST;
+
+
+G_END_DECLS
diff --git a/pango2/pango-hbface-private.h b/pango2/pango-hbface-private.h
new file mode 100644
index 00000000..e2920388
--- /dev/null
+++ b/pango2/pango-hbface-private.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-hbface.h"
+#include "pango-font-face-private.h"
+#include "pango-fontmap.h"
+#include "pango-language-set-private.h"
+#include <hb.h>
+
+struct _Pango2HbFace
+{
+ Pango2FontFace parent_instance;
+
+ char *faceid;
+ unsigned int index;
+ int instance_id;
+ char *file;
+ hb_face_t *face;
+ hb_variation_t *variations;
+ unsigned int n_variations;
+ Pango2Matrix *transform;
+ double x_scale, y_scale;
+ Pango2LanguageSet *languages;
+ gboolean embolden;
+ gboolean synthetic;
+};
+
+Pango2LanguageSet * pango2_hb_face_get_language_set (Pango2HbFace *self);
+
+void pango2_hb_face_set_language_set (Pango2HbFace *self,
+ Pango2LanguageSet *languages);
+
+void pango2_hb_face_set_matrix (Pango2HbFace *self,
+ const Pango2Matrix *matrix);
diff --git a/pango2/pango-hbface.c b/pango2/pango-hbface.c
new file mode 100644
index 00000000..f549b41a
--- /dev/null
+++ b/pango2/pango-hbface.c
@@ -0,0 +1,1087 @@
+/* Pango2
+ *
+ * Copyright (C) 2021 Matthias Clasen
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "pango-font-private.h"
+#include "pango-hbface-private.h"
+#include "pango-hbfont.h"
+
+#include "pango-language-set-simple-private.h"
+
+#include <string.h>
+#include <hb-ot.h>
+#include <hb-gobject.h>
+
+/**
+ * Pango2HbFace:
+ *
+ * `Pango2HbFace` is a `Pango2FontFace` implementation that wraps
+ * a `hb_face_t` object and implements all of the `Pango2FontFace`
+ * functionality using HarfBuzz.
+ *
+ * In addition to making a `hb_face_t` available for rendering
+ * glyphs with Pango2, `Pango2HbFace` allows some tweaks to the
+ * rendering, such as artificial slant (using a transformation
+ * matrix) or artificial emboldening.
+ *
+ * To get a font instance at a specific size from a `Pango2HbFace`,
+ * use [ctor@Pango2.HbFont.new].
+ */
+
+ /* {{{ Utilities */
+
+static void
+get_name_from_hb_face (hb_face_t *face,
+ hb_ot_name_id_t name_id,
+ hb_ot_name_id_t fallback_id,
+ char *buf,
+ unsigned int len)
+{
+ unsigned int size = len;
+
+ if (hb_ot_name_get_utf8 (face, name_id, HB_LANGUAGE_INVALID, &size, buf) > 0)
+ return;
+
+ if (fallback_id != HB_OT_NAME_ID_INVALID)
+ {
+ size = len;
+
+ if (hb_ot_name_get_utf8 (face, fallback_id, HB_LANGUAGE_INVALID, &size, buf) > 0)
+ return;
+ }
+
+ strncpy (buf, "Unnamed", len);
+ buf[len - 1] = '\0';
+}
+
+static void
+ensure_hb_face (Pango2HbFace *self)
+{
+ hb_blob_t *blob;
+
+ if (self->face)
+ return;
+
+ blob = hb_blob_create_from_file (self->file);
+
+ if (blob == hb_blob_get_empty ())
+ g_warning ("Failed to load %s", self->file);
+
+ if (self->index >= hb_face_count (blob))
+ g_warning ("Face index %d out of range for %s", self->index, self->file);
+
+ self->face = hb_face_create (blob, self->index);
+
+ if (self->instance_id >= (int)hb_ot_var_get_named_instance_count (self->face))
+ g_warning ("Instance ID %d out of range for %s", self->instance_id, self->file);
+
+ hb_blob_destroy (blob);
+ hb_face_make_immutable (self->face);
+}
+
+static char *
+variations_to_string (const hb_variation_t *variations,
+ unsigned int n_variations,
+ const char *equals,
+ const char *separator)
+{
+ GString *s;
+ char buf[128] = { 0, };
+
+ s = g_string_new ("");
+
+ for (unsigned int i = 0; i < n_variations; i++)
+ {
+ if (s->len > 0)
+ g_string_append (s, separator);
+
+ hb_tag_to_string (variations[i].tag, buf);
+ g_string_append (s, buf);
+ g_string_append (s, equals);
+ g_ascii_formatd (buf, sizeof (buf), "%g", variations[i].value);
+ g_string_append (s, buf);
+ }
+
+ return g_string_free (s, FALSE);
+}
+
+static void
+set_name_and_description (Pango2HbFace *self,
+ const char *name,
+ const Pango2FontDescription *description)
+{
+ Pango2FontFace *face = PANGO2_FONT_FACE (self);
+
+ if (name)
+ {
+ pango2_font_face_set_name (face, name);
+ }
+ else
+ {
+ hb_ot_name_id_t name_id;
+ char face_name[256] = { 0, };
+
+ ensure_hb_face (self);
+
+ if (self->instance_id >= 0)
+ name_id = hb_ot_var_named_instance_get_subfamily_name_id (self->face, self->instance_id);
+ else
+ name_id = HB_OT_NAME_ID_TYPOGRAPHIC_SUBFAMILY;
+
+ get_name_from_hb_face (self->face,
+ name_id,
+ HB_OT_NAME_ID_FONT_SUBFAMILY,
+ face_name, sizeof (face_name));
+
+ pango2_font_face_set_name (face, face_name);
+ }
+
+ if (description)
+ {
+ face->description = pango2_font_description_copy (description);
+ }
+ else
+ {
+ char family[256] = { 0, };
+ char *fullname;
+
+ ensure_hb_face (self);
+
+ get_name_from_hb_face (self->face,
+ HB_OT_NAME_ID_TYPOGRAPHIC_FAMILY,
+ HB_OT_NAME_ID_FONT_FAMILY,
+ family, sizeof (family));
+ fullname = g_strconcat (family, " ", face->name, NULL);
+
+ /* This is an attempt to parse style/weight/variant information
+ * out of the face name. FIXME: we should look at variation
+ * coordinates too, here, instead of these guessing games.
+ */
+ face->description = pango2_font_description_from_string (fullname);
+ pango2_font_description_unset_fields (face->description,
+ PANGO2_FONT_MASK_VARIATIONS |
+ PANGO2_FONT_MASK_GRAVITY);
+
+ /* Make sure we don't leave any leftovers misinterpreted
+ * as part of the family name.
+ */
+ pango2_font_description_set_family (face->description, family);
+
+ g_free (fullname);
+ }
+
+ if (self->n_variations > 0)
+ {
+ char *str = variations_to_string (self->variations, self->n_variations, "=", ",");
+ pango2_font_description_set_variations (face->description, str);
+ g_free (str);
+ }
+}
+
+typedef struct {
+ guint16 major;
+ guint16 minor;
+ gint32 italicAngle;
+ gint16 underlinePosition;
+ gint16 underlineThickness;
+ guint8 isFixedPitch[4];
+} PostTable;
+
+static gboolean
+hb_face_is_monospace (hb_face_t *face)
+{
+ hb_blob_t *post_blob;
+ guint8 *raw_post;
+ unsigned int post_len;
+ gboolean res = FALSE;
+
+ post_blob = hb_face_reference_table (face, HB_TAG ('p', 'o', 's', 't'));
+ raw_post = (guint8 *) hb_blob_get_data (post_blob, &post_len);
+
+ if (post_len >= sizeof (PostTable))
+ {
+ PostTable *post = (PostTable *)raw_post;
+
+ res = post->isFixedPitch[0] != 0 ||
+ post->isFixedPitch[1] != 0 ||
+ post->isFixedPitch[2] != 0 ||
+ post->isFixedPitch[3] != 0;
+ }
+
+ hb_blob_destroy (post_blob);
+
+ return res;
+}
+
+static void
+ensure_faceid (Pango2HbFace *self)
+{
+ double slant;
+ char buf0[32], buf1[32], buf2[32];
+ char *str = NULL;
+ char psname[256] = { 0, };
+ char *p;
+
+ if (self->faceid)
+ return;
+
+ ensure_hb_face (self);
+
+ get_name_from_hb_face (self->face,
+ HB_OT_NAME_ID_POSTSCRIPT_NAME,
+ HB_OT_NAME_ID_INVALID,
+ psname, sizeof (psname));
+
+ /* PostScript name should not contain problematic chars, but just in case,
+ * make sure we don't have any ' ', '=' or ',' that would give us parsing
+ * problems.
+ */
+ p = psname;
+ while ((p = strpbrk (p, " =,")) != NULL)
+ *p = '?';
+
+ if (self->transform)
+ slant = pango2_matrix_get_slant_ratio (self->transform);
+ else
+ slant = 0.;
+
+ if (self->n_variations > 0)
+ str = variations_to_string (self->variations, self->n_variations, "_", ":");
+
+ self->faceid = g_strdup_printf ("hb:%s:%u:%d:%d:%s:%s:%s%s%s",
+ psname,
+ self->index,
+ self->instance_id,
+ self->embolden,
+ g_ascii_formatd (buf0, sizeof (buf0), "%g", self->x_scale),
+ g_ascii_formatd (buf1, sizeof (buf1), "%g", self->y_scale),
+ g_ascii_formatd (buf2, sizeof (buf2), "%g", slant),
+ self->n_variations > 0 ? ":" : "",
+ self->n_variations > 0 ? str : "");
+ g_free (str);
+}
+
+static const char *
+style_from_font_description (const Pango2FontDescription *desc)
+{
+ Pango2Style style = pango2_font_description_get_style (desc);
+ Pango2Weight weight = pango2_font_description_get_weight (desc);
+
+ switch (style)
+ {
+ case PANGO2_STYLE_ITALIC:
+ if (weight == PANGO2_WEIGHT_BOLD)
+ return "Bold Italic";
+ else
+ return "Italic";
+ break;
+ case PANGO2_STYLE_OBLIQUE:
+ if (weight == PANGO2_WEIGHT_BOLD)
+ return "Bold Oblique";
+ else
+ return "Oblique";
+ break;
+ case PANGO2_STYLE_NORMAL:
+ if (weight == PANGO2_WEIGHT_BOLD)
+ return "Bold";
+ else
+ return "Regular";
+ break;
+ default: ;
+ }
+
+ return NULL;
+}
+
+/* }}} */
+/* {{{ Pango2FontFace implementation */
+
+struct _Pango2HbFaceClass
+{
+ Pango2FontFaceClass parent_class;
+};
+
+enum {
+ PROP_HB_FACE = 1,
+ PROP_FILE,
+ PROP_FACE_INDEX,
+ PROP_INSTANCE_ID,
+ PROP_VARIATIONS,
+ PROP_EMBOLDEN,
+ PROP_TRANSFORM,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+G_DEFINE_FINAL_TYPE (Pango2HbFace, pango2_hb_face, PANGO2_TYPE_FONT_FACE)
+
+static void
+pango2_hb_face_init (Pango2HbFace *self)
+{
+ self->transform = NULL;
+ self->x_scale = self->y_scale = 1.;
+}
+
+static void
+pango2_hb_face_finalize (GObject *object)
+{
+ Pango2HbFace *self = PANGO2_HB_FACE (object);
+
+ g_free (self->faceid);
+ if (self->face)
+ hb_face_destroy (self->face);
+ g_free (self->file);
+ if (self->languages)
+ g_object_unref (self->languages);
+ g_free (self->variations);
+ if (self->transform)
+ g_free (self->transform);
+
+ G_OBJECT_CLASS (pango2_hb_face_parent_class)->finalize (object);
+}
+
+static gboolean
+pango2_hb_face_is_synthesized (Pango2FontFace *face)
+{
+ Pango2HbFace *self = PANGO2_HB_FACE (face);
+
+ return self->synthetic;
+}
+
+static gboolean
+pango2_hb_face_is_monospace (Pango2FontFace *face)
+{
+ Pango2HbFace *self = PANGO2_HB_FACE (face);
+
+ ensure_hb_face (self);
+
+ return hb_face_is_monospace (self->face);
+}
+
+static gboolean
+pango2_hb_face_is_variable (Pango2FontFace *face)
+{
+ Pango2HbFace *self = PANGO2_HB_FACE (face);
+
+ /* We don't consider named instances as variable, i.e.
+ * a font chooser UI should not expose axes for them.
+ *
+ * In theory, there could be multi-axis fonts where the
+ * variations only pin some of the axes, but we are not
+ * going to worry about possibility here.
+ */
+ if (self->instance_id >= -1 || self->n_variations)
+ return FALSE;
+
+ ensure_hb_face (self);
+
+ return hb_ot_var_get_axis_count (self->face) > 0;
+}
+
+static gboolean
+pango2_hb_face_supports_language (Pango2FontFace *face,
+ Pango2Language *language)
+{
+ Pango2HbFace *self = PANGO2_HB_FACE (face);
+ Pango2LanguageSet *set = pango2_hb_face_get_language_set (self);
+
+ if (set)
+ return pango2_language_set_matches_language (set, language);
+
+ return TRUE;
+}
+
+static Pango2Language **
+pango2_hb_face_get_languages (Pango2FontFace *face)
+{
+ Pango2HbFace *self = PANGO2_HB_FACE (face);
+ Pango2LanguageSet *set = pango2_hb_face_get_language_set (self);
+
+ if (set)
+ return pango2_language_set_get_languages (set);
+
+ return NULL;
+}
+
+static gboolean
+pango2_hb_face_has_char (Pango2FontFace *face,
+ gunichar wc)
+{
+ Pango2HbFace *self = PANGO2_HB_FACE (face);
+ hb_font_t *hb_font;
+ hb_codepoint_t glyph;
+ gboolean ret;
+
+ ensure_hb_face (self);
+
+ hb_font = hb_font_create (self->face);
+ ret = hb_font_get_nominal_glyph (hb_font, wc, &glyph);
+ hb_font_destroy (hb_font);
+
+ return ret;
+}
+
+static const char *
+pango2_hb_face_get_faceid (Pango2FontFace *face)
+{
+ Pango2HbFace *self = PANGO2_HB_FACE (face);
+
+ ensure_faceid (self);
+
+ return self->faceid;
+}
+
+static Pango2Font *
+pango2_hb_face_create_font (Pango2FontFace *face,
+ const Pango2FontDescription *desc,
+ float dpi,
+ const Pango2Matrix *ctm)
+{
+ Pango2HbFace *self = PANGO2_HB_FACE (face);
+
+ return PANGO2_FONT (pango2_hb_font_new_for_description (self, desc, dpi, ctm));
+}
+
+static void
+pango2_hb_face_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2HbFace *self = PANGO2_HB_FACE (object);
+
+ switch (property_id)
+ {
+ case PROP_HB_FACE:
+ g_value_set_boxed (value, pango2_hb_face_get_hb_face (self));
+ break;
+
+ case PROP_FILE:
+ g_value_set_string (value, pango2_hb_face_get_file (self));
+ break;
+
+ case PROP_FACE_INDEX:
+ g_value_set_uint (value, pango2_hb_face_get_face_index (self));
+ break;
+
+ case PROP_INSTANCE_ID:
+ g_value_set_int (value, pango2_hb_face_get_instance_id (self));
+ break;
+
+ case PROP_VARIATIONS:
+ {
+ char *str = NULL;
+ if (self->variations)
+ str = variations_to_string (self->variations, self->n_variations, "=", ",");
+ g_value_take_string (value, str);
+ }
+ break;
+
+ case PROP_EMBOLDEN:
+ g_value_set_boolean (value, pango2_hb_face_get_embolden (self));
+ break;
+
+ case PROP_TRANSFORM:
+ g_value_set_boxed (value, pango2_hb_face_get_transform (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+pango2_hb_face_class_init (Pango2HbFaceClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ Pango2FontFaceClass *face_class = PANGO2_FONT_FACE_CLASS (class);
+
+ object_class->finalize = pango2_hb_face_finalize;
+ object_class->get_property = pango2_hb_face_get_property;
+
+ face_class->is_synthesized = pango2_hb_face_is_synthesized;
+ face_class->is_monospace = pango2_hb_face_is_monospace;
+ face_class->is_variable = pango2_hb_face_is_variable;
+ face_class->supports_language = pango2_hb_face_supports_language;
+ face_class->get_languages = pango2_hb_face_get_languages;
+ face_class->has_char = pango2_hb_face_has_char;
+ face_class->get_faceid = pango2_hb_face_get_faceid;
+ face_class->create_font = pango2_hb_face_create_font;
+
+ /**
+ * Pango2HbFace:hb-face: (attributes org.gtk.Property.get=pango2_hb_face_get_hb_face)
+ *
+ * A `hb_face_t` object backing this face.
+ */
+ properties[PROP_HB_FACE] =
+ g_param_spec_boxed ("hb-face", NULL, NULL, HB_GOBJECT_TYPE_FACE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2HbFace:file: (attributes org.gtk.Property.get=pango2_hb_face_get_file)
+ *
+ * The file that this face was created from, if any.
+ */
+ properties[PROP_FILE] =
+ g_param_spec_string ("file", NULL, NULL, NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2HbFace:face-index: (attributes org.gtk.Property.get=pango2_hb_face_get_face_index)
+ *
+ * The index of the face, in case that it was created
+ * from a file containing data for multiple faces.
+ */
+ properties[PROP_FACE_INDEX] =
+ g_param_spec_uint ("face-index", NULL, NULL, 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2HbFace:instance-id: (attributes org.gtk.Property.get=pango2_hb_face_get_instance_id)
+ *
+ * The ID of the named instance of this face.
+ *
+ * -1 represents the default instance, and
+ * -2 represents no instance, meaning that the face will
+ * be variable.
+ */
+ properties[PROP_INSTANCE_ID] =
+ g_param_spec_int ("instance-id", NULL, NULL, -2, G_MAXINT, -1,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2HbFace:variations: (attributes org.gtk.Property.get=pango2_hb_face_get_variations)
+ *
+ * The variations that are applied for this face.
+ *
+ * This property contains a string representation of the variations.
+ */
+ properties[PROP_VARIATIONS] =
+ g_param_spec_string ("variations", NULL, NULL, NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2HbFace:embolden: (attributes org.gtk.Property.get=pango2_hb_face_get_embolden)
+ *
+ * `TRUE` if the face is using synthetic emboldening.
+ */
+ properties[PROP_EMBOLDEN] =
+ g_param_spec_boolean ("embolden", NULL, NULL, FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2HbFace:transform: (attributes org.gtk.Property.get=pango2_hb_face_get_transform)
+ *
+ * The transform from 'font space' to 'user space' that
+ * this face uses.
+ *
+ * This is the 'font matrix' which is used for
+ * sythetic italics and width variations.
+ */
+ properties[PROP_TRANSFORM] =
+ g_param_spec_boxed ("transform", NULL, NULL, PANGO2_TYPE_MATRIX,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+/* }}} */
+/* {{{ Private API */
+
+/*< private >
+ * pango2_hb_face_get_language_set:
+ * @face: a `Pango2HbFace`
+ *
+ * Returns the languages supported by @face.
+ *
+ * Returns: (transfer none): a `Pango2LanguageSet`
+ */
+Pango2LanguageSet *
+pango2_hb_face_get_language_set (Pango2HbFace *face)
+{
+ return face->languages;
+}
+
+/*< private >
+ * pango2_hb_face_set_language_set:
+ * @self: a `Pango2HbFace`
+ * @languages: a `Pango2LanguageSet`
+ *
+ * Sets the languages that are supported by @face.
+ *
+ * This should only be called by fontmap implementations.
+ */
+void
+pango2_hb_face_set_language_set (Pango2HbFace *self,
+ Pango2LanguageSet *languages)
+{
+ g_set_object (&self->languages, languages);
+}
+
+/*< private >
+ * pango2_hb_face_set_matrix:
+ * @self: a `Pango2HbFace`
+ * @matrix: the `Pango2Matrix`
+ *
+ * Sets the font matrix for @self.
+ *
+ * This should only be called by fontmap implementations.
+ */
+void
+pango2_hb_face_set_matrix (Pango2HbFace *self,
+ const Pango2Matrix *matrix)
+{
+ if (!self->transform)
+ self->transform = g_new (Pango2Matrix, 1);
+
+ *self->transform = *matrix;
+
+ pango2_matrix_get_font_scale_factors (self->transform, &self->x_scale, &self->y_scale);
+ pango2_matrix_scale (self->transform, 1./self->x_scale, 1./self->y_scale);
+}
+
+/* }}} */
+ /* {{{ Public API */
+
+/**
+ * pango2_hb_face_new_from_hb_face:
+ * @face: an immutable `hb_face_t`
+ * @instance_id: named instance id, or -1 for the default instance
+ * or -2 for no instance
+ * @name: (nullable): name for the face
+ * @description: (nullable): `Pango2FontDescription` for the font
+ *
+ * Creates a new `Pango2HbFace` by wrapping an existing `hb_face_t`.
+ *
+ * The @instance_id can be used to pick one of the available named
+ * instances in a variable font. See hb_ot_var_get_named_instance_count()
+ * to learn about the available named instances.
+ *
+ * If @instance_id is -2 and @face has variation axes, then
+ * [method@Pango2.FontFace.is_variable] will return `TRUE` for
+ * the returned `Pango2HbFace`.
+ *
+ * If @name is provided, it is used as the name for the face.
+ * Otherwise, Pango2 will use the named instance subfamily name
+ * or `HB_OT_NAME_ID_TYPOGRAPHIC_SUBFAMILY`.
+ *
+ * If @description is provided, it is used as the font description
+ * for the face. Otherwise, Pango2 creates a description using
+ * `HB_OT_NAME_ID_TYPOGRAPHIC_FAMILY` and the name of the face.
+ *
+ * Returns: a newly created `Pango2HbFace`
+ */
+Pango2HbFace *
+pango2_hb_face_new_from_hb_face (hb_face_t *face,
+ int instance_id,
+ const char *name,
+ const Pango2FontDescription *description)
+{
+ Pango2HbFace *self;
+
+ g_return_val_if_fail (face != NULL, NULL);
+ g_return_val_if_fail (hb_face_is_immutable (face), NULL);
+ g_return_val_if_fail (instance_id >= -2, NULL);
+ g_return_val_if_fail (description == NULL ||
+ (pango2_font_description_get_set_fields (description) &
+ (PANGO2_FONT_MASK_SIZE|PANGO2_FONT_MASK_GRAVITY)) == 0, NULL);
+
+ self = g_object_new (PANGO2_TYPE_HB_FACE, NULL);
+
+ self->face = hb_face_reference (face);
+ self->index = hb_face_get_index (face) & 0xffff;
+ self->instance_id = instance_id;
+
+ if (instance_id >= (int)hb_ot_var_get_named_instance_count (face))
+ g_warning ("Instance ID %d out of range", instance_id);
+
+ set_name_and_description (self, name, description);
+
+ return self;
+}
+
+/**
+ * pango2_hb_face_new_from_file:
+ * @file: font filename
+ * @index: face index
+ * @instance_id: named instance id, or -1 for the default instance
+ * or -2 for no instance
+ * @name: (nullable): name for the face
+ * @description: (nullable): `Pango2FontDescription` for the font
+ *
+ * Creates a new `Pango2HbFace` from a font file.
+ *
+ * The @index can be used to pick a face from a file containing
+ * multiple faces, such as TTC or DFont.
+ *
+ * The @instance_id can be used to pick one of the available named
+ * instances in a variable font. See hb_ot_var_get_named_instance_count()
+ * to learn about the available named instances.
+ *
+ * If @instance_id is -2 and @face has variation axes, then
+ * [method@Pango2.FontFace.is_variable] will return `TRUE` for
+ * the returned `Pango2HbFace`.
+ *
+ * If @name is provided, it is used as the name for the face.
+ * Otherwise, Pango2 will use the named instance subfamily name
+ * or `HB_OT_NAME_ID_TYPOGRAPHIC_SUBFAMILY`.
+ *
+ * If @description is provided, it is used as the font description
+ * for the face. Otherwise, Pango2 creates a description using
+ * `HB_OT_NAME_ID_TYPOGRAPHIC_FAMILY` and the name of the face.
+ *
+ * If @desc and @name are provided, then the returned `Pango2HbFace`
+ * object will be lazily initialized as needed.
+ *
+ * Returns: a newly created `Pango2HbFace`
+ */
+Pango2HbFace *
+pango2_hb_face_new_from_file (const char *file,
+ unsigned int index,
+ int instance_id,
+ const char *name,
+ const Pango2FontDescription *description)
+{
+ Pango2HbFace *self;
+
+ g_return_val_if_fail (file!= NULL, NULL);
+ g_return_val_if_fail (instance_id >= -2, NULL);
+ g_return_val_if_fail (description == NULL ||
+ (pango2_font_description_get_set_fields (description) &
+ (PANGO2_FONT_MASK_VARIANT|
+ PANGO2_FONT_MASK_SIZE|
+ PANGO2_FONT_MASK_GRAVITY)) == 0, NULL);
+
+ self = g_object_new (PANGO2_TYPE_HB_FACE, NULL);
+
+ self->file = g_strdup (file);
+ self->index = index;
+ self->instance_id = instance_id;
+
+ set_name_and_description (self, name, description);
+
+ return self;
+}
+
+/**
+ * pango2_hb_face_new_synthetic:
+ * @face: a `Pango2HbFace`
+ * @transform: (nullable): the transform to apply
+ * @embolden: `TRUE` to render the font bolder
+ * @name: (nullable): name for the face
+ * @description: a `Pango2FontDescription` to override fields from @face's description
+ *
+ * Creates a new `Pango2HbFace` that is a synthetic variant of @face.
+ *
+ * Here, 'synthetic' means that the variant is implemented by rendering
+ * the glyphs differently, not by using data from the original @face.
+ * See [method@Pango2.HbFace.new_instance] for that.
+ *
+ * @transform can be used to specify a non-trivial font matrix for creating
+ * synthetic italics or synthetic condensed variants of an existing face.
+ *
+ * If @embolden is `TRUE`, Pango2 will render the glyphs bolder, creating
+ * a synthetic bold variant of the face.
+ *
+ * If a @name is not specified, the name for the face will be derived
+ * from the @description.
+ *
+ * Apart from setting the style that this face will be used for,
+ * @description can provide an alternative family name. This can
+ * be used to create generic aliases such as 'sans' or 'monospace'.
+ *
+ * Note that only the following fields in @description should be set:
+ *
+ * + style or stretch, to indicate a transformed style
+ * + weight, to indicate a bolder weight
+ * + family, to provide an alternative family name
+ *
+ * [method@Pango2.FontFace.is_synthesized] will return `TRUE` for objects
+ * created by this function.
+ *
+ * Returns: (transfer full): a newly created `Pango2HbFace`
+ */
+Pango2HbFace *
+pango2_hb_face_new_synthetic (Pango2HbFace *face,
+ const Pango2Matrix *transform,
+ gboolean embolden,
+ const char *name,
+ const Pango2FontDescription *description)
+{
+ Pango2HbFace *self;
+ Pango2FontDescription *desc;
+
+ g_return_val_if_fail (PANGO2_IS_HB_FACE (face), NULL);
+ g_return_val_if_fail (description != NULL, NULL);
+ g_return_val_if_fail ((pango2_font_description_get_set_fields (description) &
+ ~(PANGO2_FONT_MASK_FAMILY|
+ PANGO2_FONT_MASK_STYLE|
+ PANGO2_FONT_MASK_STRETCH|
+ PANGO2_FONT_MASK_WEIGHT)) == 0, NULL);
+
+ self = g_object_new (PANGO2_TYPE_HB_FACE, NULL);
+
+ self->file = g_strdup (face->file);
+ if (face->face)
+ self->face = hb_face_reference (face->face);
+
+ self->index = face->index;
+ self->instance_id = face->instance_id;
+ self->variations = g_memdup2 (face->variations, sizeof (hb_variation_t) * face->n_variations);
+ self->n_variations = face->n_variations;
+
+ if (transform)
+ pango2_hb_face_set_matrix (self, transform);
+
+ self->embolden = embolden;
+ self->synthetic = self->embolden || (self->transform != NULL);
+
+ desc = pango2_font_description_copy (PANGO2_FONT_FACE (face)->description);
+ pango2_font_description_merge (desc, description, TRUE);
+
+ if (!name)
+ name = style_from_font_description (desc);
+
+ set_name_and_description (self, name, desc);
+
+ pango2_hb_face_set_language_set (self, face->languages);
+
+ pango2_font_description_free (desc);
+
+ return self;
+}
+
+/**
+ * pango2_hb_face_new_instance:
+ * @face: a `Pango2HbFace`
+ * @variations: (nullable) (array length=n_variations): font variations to apply
+ * @n_variations: length of @variations
+ * @name: (nullable): name for the face
+ * @description: a `Pango2FontDescription` to override fields from @face's description
+ *
+ * Creates a new `Pango2HbFace` that is a variant of @face.
+ *
+ * The @variations provide values for variation axes of @face. Axes that
+ * are not included in @variations will keep the values they have in @face.
+ * @variations that refer to axes that the face does not have are ignored.
+ *
+ * Conceptually, this is similar to a named instance of the face, except
+ * that the mapping of the name to a set of coordinates on the variation
+ * axes is provided externally, and not by the face itself.
+ *
+ * If a @name is not specified, the name for the face will be derived
+ * from the @description.
+ *
+ * Apart from setting the style that this face will be used for,
+ * @description can provide an alternative family name. This can
+ * be used to create generic aliases such as 'sans' or 'monospace'.
+ *
+ * Note that only the following fields in @description should be set:
+ * - style or stretch, to indicate a transformed style
+ * - weight, to indicate a bolder weight
+ * - family, to provide an alternative family name
+ *
+ * Returns: (transfer full): a newly created `Pango2HbFace`
+ */
+Pango2HbFace *
+pango2_hb_face_new_instance (Pango2HbFace *face,
+ const hb_variation_t *variations,
+ unsigned int n_variations,
+ const char *name,
+ const Pango2FontDescription *description)
+{
+ Pango2HbFace *self;
+ Pango2FontDescription *desc;
+
+ g_return_val_if_fail (PANGO2_IS_HB_FACE (face), NULL);
+ g_return_val_if_fail (description != NULL, NULL);
+ g_return_val_if_fail ((pango2_font_description_get_set_fields (description) &
+ ~(PANGO2_FONT_MASK_FAMILY|
+ PANGO2_FONT_MASK_STYLE|
+ PANGO2_FONT_MASK_STRETCH|
+ PANGO2_FONT_MASK_WEIGHT)) == 0, NULL);
+
+ self = g_object_new (PANGO2_TYPE_HB_FACE, NULL);
+
+ self->file = g_strdup (face->file);
+ if (face->face)
+ self->face = hb_face_reference (face->face);
+
+ self->index = face->index;
+ self->instance_id = face->instance_id;
+
+ if (face->transform)
+ {
+ self->transform = g_memdup2 (face->transform, sizeof (Pango2Matrix));
+ self->x_scale = face->x_scale;
+ self->y_scale = face->y_scale;
+ }
+
+ self->embolden = face->embolden;
+ self->synthetic = self->embolden || (self->transform != NULL);
+
+ self->variations = g_memdup2 (variations, sizeof (hb_variation_t) * n_variations);
+ self->n_variations = n_variations;
+
+ desc = pango2_font_description_copy (PANGO2_FONT_FACE (face)->description);
+ pango2_font_description_merge (desc, description, TRUE);
+
+ if (!name)
+ name = style_from_font_description (desc);
+
+ set_name_and_description (self, name, desc);
+
+ pango2_font_description_free (desc);
+
+ return self;
+}
+
+/**
+ * pango2_hb_face_get_hb_face:
+ * @self: a `Pango2HbFace`
+ *
+ * Gets the `hb_face_t` object backing this face.
+ *
+ * Note that the objects returned by this function are cached
+ * and immutable, and may be shared between `Pango2HbFace` objects.
+ *
+ * Returns: (transfer none): the `hb_face_t` object
+ * backing the face
+ */
+hb_face_t *
+pango2_hb_face_get_hb_face (Pango2HbFace *self)
+{
+ g_return_val_if_fail (PANGO2_IS_HB_FACE (self), NULL);
+
+ ensure_hb_face (self);
+
+ return self->face;
+}
+
+/**
+ * pango2_hb_face_get_file:
+ * @self: a `Pango2HbFace`
+ *
+ * Gets the file that backs the face.
+ *
+ * Returns: (transfer none) (nullable): the file backing the face
+ */
+const char *
+pango2_hb_face_get_file (Pango2HbFace *self)
+{
+ g_return_val_if_fail (PANGO2_IS_HB_FACE (self), NULL);
+
+ return self->file;
+}
+
+/**
+ * pango2_hb_face_get_face_index:
+ * @self: a `Pango2HbFace`
+ *
+ * Gets the face index of the face.
+ *
+ * Returns: the face indexx
+ */
+unsigned int
+pango2_hb_face_get_face_index (Pango2HbFace *self)
+{
+ g_return_val_if_fail (PANGO2_IS_HB_FACE (self), 0);
+
+ return self->index;
+}
+
+/**
+ * pango2_hb_face_get_instance_id:
+ * @self: a `Pango2HbFace`
+ *
+ * Gets the instance id of the face.
+ *
+ * Returns: the instance id
+ */
+int
+pango2_hb_face_get_instance_id (Pango2HbFace *self)
+{
+ g_return_val_if_fail (PANGO2_IS_HB_FACE (self), -1);
+
+ return self->instance_id;
+}
+
+/**
+ * pango2_hb_face_get_variations:
+ * @self: a `Pango2HbFace`
+ * @n_variations: (nullable) (out caller-allocates): return location for
+ * the length of the returned array
+ *
+ * Gets the variations of the face.
+ *
+ * Returns: (nullable) (transfer none): the variations
+ */
+const hb_variation_t *
+pango2_hb_face_get_variations (Pango2HbFace *self,
+ unsigned int *n_variations)
+{
+ g_return_val_if_fail (PANGO2_IS_HB_FACE (self), NULL);
+
+ if (n_variations)
+ *n_variations = self->n_variations;
+
+ return self->variations;
+}
+
+/**
+ * pango2_hb_face_get_embolden:
+ * @self: a `Pango2HbFace`
+ *
+ * Gets whether face is using synthetic emboldening.
+ *
+ * Returns: `TRUE` if the face is using synthetic embolding
+ */
+gboolean
+pango2_hb_face_get_embolden (Pango2HbFace *self)
+{
+ g_return_val_if_fail (PANGO2_IS_HB_FACE (self), FALSE);
+
+ return self->embolden;
+}
+
+/**
+ * pango2_hb_face_get_transform:
+ * @self: a `Pango2HbFace`
+ *
+ * Gets the transform from 'font space' to 'user space' that this face uses.
+ *
+ * This is the 'font matrix' which is used for
+ * sythetic italics and width variations.
+ *
+ * Returns: (nullable) (transfer none): the transform of face
+ */
+const Pango2Matrix *
+pango2_hb_face_get_transform (Pango2HbFace *self)
+{
+ g_return_val_if_fail (PANGO2_IS_HB_FACE (self), NULL);
+
+ return self->transform;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-hbface.h b/pango2/pango-hbface.h
new file mode 100644
index 00000000..2d4662d7
--- /dev/null
+++ b/pango2/pango-hbface.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 Matthias Clasen
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-font-face.h>
+
+#include <hb.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_HB_FACE (pango2_hb_face_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2HbFace, pango2_hb_face, PANGO2, HB_FACE, Pango2FontFace)
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2HbFace * pango2_hb_face_new_from_hb_face (hb_face_t *face,
+ int instance_id,
+ const char *name,
+ const Pango2FontDescription *description);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2HbFace * pango2_hb_face_new_from_file (const char *file,
+ unsigned int index,
+ int instance_id,
+ const char *name,
+ const Pango2FontDescription *description);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2HbFace * pango2_hb_face_new_synthetic (Pango2HbFace *face,
+ const Pango2Matrix *transform,
+ gboolean embolden,
+ const char *name,
+ const Pango2FontDescription *description);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2HbFace * pango2_hb_face_new_instance (Pango2HbFace *face,
+ const hb_variation_t *variations,
+ unsigned int n_variations,
+ const char *name,
+ const Pango2FontDescription *description);
+
+PANGO2_AVAILABLE_IN_ALL
+hb_face_t * pango2_hb_face_get_hb_face (Pango2HbFace *self);
+
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_hb_face_get_file (Pango2HbFace *self);
+
+PANGO2_AVAILABLE_IN_ALL
+unsigned int pango2_hb_face_get_face_index (Pango2HbFace *self);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_hb_face_get_instance_id (Pango2HbFace *self);
+
+PANGO2_AVAILABLE_IN_ALL
+const hb_variation_t * pango2_hb_face_get_variations (Pango2HbFace *self,
+ unsigned int *n_variations);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_hb_face_get_embolden (Pango2HbFace *self);
+
+PANGO2_AVAILABLE_IN_ALL
+const Pango2Matrix * pango2_hb_face_get_transform (Pango2HbFace *self);
+
+G_END_DECLS
diff --git a/pango2/pango-hbfamily-private.h b/pango2/pango-hbfamily-private.h
new file mode 100644
index 00000000..fb92cfb6
--- /dev/null
+++ b/pango2/pango-hbfamily-private.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-font-family-private.h"
+#include "pango-hbface.h"
+
+
+#define PANGO2_TYPE_HB_FAMILY (pango2_hb_family_get_type ())
+
+G_DECLARE_FINAL_TYPE (Pango2HbFamily, pango2_hb_family, PANGO2, HB_FAMILY, Pango2FontFamily)
+
+struct _Pango2HbFamily
+{
+ Pango2FontFamily parent_instance;
+
+ GPtrArray *faces;
+};
+
+Pango2HbFamily * pango2_hb_family_new (const char *name);
+
+void pango2_hb_family_add_face (Pango2HbFamily *self,
+ Pango2FontFace *face);
+
+void pango2_hb_family_remove_face (Pango2HbFamily *self,
+ Pango2FontFace *face);
+
+Pango2FontFace * pango2_hb_family_find_face (Pango2HbFamily *self,
+ Pango2FontDescription *description,
+ Pango2Language *language,
+ gunichar wc);
diff --git a/pango2/pango-hbfamily.c b/pango2/pango-hbfamily.c
new file mode 100644
index 00000000..dc990683
--- /dev/null
+++ b/pango2/pango-hbfamily.c
@@ -0,0 +1,332 @@
+/* Pango2
+ *
+ * Copyright (C) 2021 Matthias Clasen
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "pango-hbfamily-private.h"
+#include "pango-impl-utils.h"
+#include "pango-hbface-private.h"
+#include "pango-font-face-private.h"
+#include "pango-font-description-private.h"
+#include "pango-userface-private.h"
+
+/* {{{ GListModel implementation */
+
+static GType
+pango2_hb_family_get_item_type (GListModel *list)
+{
+ return PANGO2_TYPE_FONT_FACE;
+}
+
+static guint
+pango2_hb_family_get_n_items (GListModel *list)
+{
+ Pango2HbFamily *self = PANGO2_HB_FAMILY (list);
+
+ return self->faces->len;
+}
+
+static gpointer
+pango2_hb_family_get_item (GListModel *list,
+ guint position)
+{
+ Pango2HbFamily *self = PANGO2_HB_FAMILY (list);
+
+ if (position < self->faces->len)
+ return g_object_ref (g_ptr_array_index (self->faces, position));
+
+ return NULL;
+}
+
+static void
+pango2_hb_family_list_model_init (GListModelInterface *iface)
+{
+ iface->get_item_type = pango2_hb_family_get_item_type;
+ iface->get_n_items = pango2_hb_family_get_n_items;
+ iface->get_item = pango2_hb_family_get_item;
+}
+
+/* }}} */
+/* {{{ Utilities */
+
+static int
+sort_face_func (Pango2FontFace *face1,
+ Pango2FontFace *face2)
+{
+ int a, b;
+
+ a = pango2_font_description_get_style (face1->description);
+ b = pango2_font_description_get_style (face2->description);
+ if (a != b)
+ return a - b;
+
+ a = pango2_font_description_get_weight (face1->description);
+ b = pango2_font_description_get_weight (face2->description);
+ if (a != b)
+ return a - b;
+
+ a = pango2_font_description_get_stretch (face1->description);
+ b = pango2_font_description_get_stretch (face2->description);
+ if (a != b)
+ return a - b;
+
+ return strcmp (face1->name, face2->name);
+}
+
+/* return 2 if face is a named instance,
+ * 1 if it is variable
+ * 0 otherwise
+ */
+static int
+face_get_variableness (Pango2FontFace *face)
+{
+ if (pango2_font_face_is_variable (PANGO2_FONT_FACE (face)))
+ {
+ if (PANGO2_HB_FACE (face)->instance_id != -1)
+ return 2;
+ else
+ return 1;
+ }
+ return 0;
+}
+
+/* }}} */
+/* {{{ Pango2FontFamily implementation */
+
+struct _Pango2HbFamilyClass
+{
+ Pango2FontFamilyClass parent_class;
+};
+
+G_DEFINE_TYPE_WITH_CODE (Pango2HbFamily, pango2_hb_family, PANGO2_TYPE_FONT_FAMILY,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, pango2_hb_family_list_model_init))
+
+static void
+pango2_hb_family_init (Pango2HbFamily *self)
+{
+ self->faces = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+static void
+pango2_hb_family_finalize (GObject *object)
+{
+ Pango2HbFamily *self = PANGO2_HB_FAMILY (object);
+
+ for (int i = 0; i < self->faces->len; i++)
+ {
+ Pango2FontFace *face = g_ptr_array_index (self->faces, i);
+ face->family = NULL;
+ }
+
+ g_ptr_array_unref (self->faces);
+
+ G_OBJECT_CLASS (pango2_hb_family_parent_class)->finalize (object);
+}
+
+static Pango2FontFace *
+pango2_hb_family_get_face (Pango2FontFamily *family,
+ const char *name)
+{
+ Pango2HbFamily *self = PANGO2_HB_FAMILY (family);
+
+ for (int i = 0; i < self->faces->len; i++)
+ {
+ Pango2FontFace *face = g_ptr_array_index (self->faces, i);
+
+ if (name == NULL || strcmp (name, pango2_font_face_get_name (face)) == 0)
+ return face;
+ }
+
+ return NULL;
+}
+
+static void
+pango2_hb_family_class_init (Pango2HbFamilyClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ Pango2FontFamilyClass *family_class = PANGO2_FONT_FAMILY_CLASS (class);
+
+ object_class->finalize = pango2_hb_family_finalize;
+
+ family_class->get_face = pango2_hb_family_get_face;
+}
+
+/* }}} */
+/* {{{ Private API */
+
+/*< private >
+ * pango2_hb_family_new:
+ * @name: the family name
+ *
+ * Creates a new `Pango2HbFamily`.
+ *
+ * Returns: a newly created `Pango2HbFamily`
+ */
+Pango2HbFamily *
+pango2_hb_family_new (const char *name)
+{
+ Pango2HbFamily *self;
+
+ self = g_object_new (PANGO2_TYPE_HB_FAMILY, NULL);
+
+ pango2_font_family_set_name (PANGO2_FONT_FAMILY (self), name);
+
+ return self;
+}
+
+/*< private >
+ * pango2_hb_family_add_face:
+ * @self: a `Pango2HbFamily`
+ * @face: (transfer full): a `Pango2FontFace` to add
+ *
+ * Adds a `Pango2FontFace` to a `Pango2HbFamily`.
+ *
+ * The family takes ownership of the added face.
+ *
+ * It is an error to call this function more than
+ * once for the same face.
+ */
+void
+pango2_hb_family_add_face (Pango2HbFamily *self,
+ Pango2FontFace *face)
+{
+ int position;
+
+ g_return_if_fail (PANGO2_IS_HB_FACE (face) || PANGO2_IS_USER_FACE (face));
+
+ position = 0;
+ while (position < self->faces->len)
+ {
+ Pango2FontFace *f = g_ptr_array_index (self->faces, position);
+ if (sort_face_func (face, f) < 0)
+ break;
+ position++;
+ }
+
+ g_ptr_array_insert (self->faces, position, face);
+
+ pango2_font_face_set_family (face, PANGO2_FONT_FAMILY (self));
+
+ g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
+ g_object_notify (G_OBJECT (self), "n-items");
+}
+
+/*< private >
+ * pango2_hb_family_remove_face:
+ * @self: a `Pango2HbFamily`
+ * @face: a `Pango2FontFace`
+ *
+ * Remove a `Pango2FontFace` from a `Pango2HbFamily`.
+ */
+void
+pango2_hb_family_remove_face (Pango2HbFamily *self,
+ Pango2FontFace *face)
+{
+ unsigned int position;
+
+ g_return_if_fail (PANGO2_IS_HB_FACE (face) || PANGO2_IS_USER_FACE (face));
+
+ if (!g_ptr_array_find (self->faces, face, &position))
+ return;
+
+ pango2_font_face_set_family (face, NULL);
+
+ g_ptr_array_remove_index (self->faces, position);
+
+ g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
+ g_object_notify (G_OBJECT (self), "n-items");
+}
+
+/*< private >
+ * pango2_hb_family_find_face:
+ * @family: a `Pango2HbFamily`
+ * @description: `Pango2FontDescription` to match
+ * @language: (nullable): `Pango2Language` to support
+ * @wc: a Unicode character, or 0 to ignore
+ *
+ * Finds the face in @family that best matches the font description while
+ * also supporting the given language and character.
+ *
+ * Amongst equally matching faces, a variable face is preferred over a
+ * non-variable one, and a named instance is preferred over a bare variable
+ * face.
+ *
+ * Returns: (transfer none) (nullable): the face
+ */
+Pango2FontFace *
+pango2_hb_family_find_face (Pango2HbFamily *family,
+ Pango2FontDescription *description,
+ Pango2Language *language,
+ gunichar wc)
+{
+ Pango2FontFace *face = NULL;
+ int best_distance = G_MAXINT;
+ int best_variableness = 0;
+
+ /* First look for an exact match if the description has a faceid */
+ if (pango2_font_description_get_set_fields (description) & PANGO2_FONT_MASK_FACEID)
+ {
+ const char *faceid = pango2_font_description_get_faceid (description);
+
+ for (int i = 0; i < family->faces->len; i++)
+ {
+ Pango2FontFace *face2 = g_ptr_array_index (family->faces, i);
+ const char *faceid2 = pango2_font_face_get_faceid (face2);
+
+ if (g_strcmp0 (faceid, faceid2) == 0)
+ return face2;
+ }
+ }
+
+ for (int i = 0; i < family->faces->len; i++)
+ {
+ Pango2FontFace *face2 = g_ptr_array_index (family->faces, i);
+ int distance;
+ int variableness;
+
+ if (language && !pango2_font_face_supports_language (face2, language))
+ continue;
+
+ if (wc && !pango2_font_face_has_char (face2, wc))
+ continue;
+
+ if (!pango2_font_description_is_similar (description, face2->description))
+ continue;
+
+ distance = pango2_font_description_compute_distance (description, face2->description);
+
+ variableness = face_get_variableness (PANGO2_FONT_FACE (face2));
+ if (distance < best_distance ||
+ (distance == best_distance && variableness > best_variableness))
+ {
+ face = face2;
+ best_distance = distance;
+ best_variableness = variableness;
+ }
+ }
+
+ return face;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-hbfont-private.h b/pango2/pango-hbfont-private.h
new file mode 100644
index 00000000..6794357f
--- /dev/null
+++ b/pango2/pango-hbfont-private.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-hbfont.h"
+#include "pango-hbface.h"
+#include "pango-font-private.h"
+
+#include <hb.h>
+
+
+typedef struct _HexBoxInfo HexBoxInfo;
+struct _HexBoxInfo
+{
+ Pango2Font *font;
+ int rows;
+ double digit_width;
+ double digit_height;
+ double pad_x;
+ double pad_y;
+ double line_width;
+ double box_descent;
+ double box_height;
+};
+
+struct _Pango2HbFont
+{
+ Pango2Font parent_instance;
+
+ hb_feature_t *features;
+ unsigned int n_features;
+ hb_variation_t *variations;
+ unsigned int n_variations;
+
+ HexBoxInfo *hex_box_info;
+ Pango2Language *approximate_char_lang;
+ int approximate_char_width;
+ int approximate_digit_width;
+};
diff --git a/pango2/pango-hbfont.c b/pango2/pango-hbfont.c
new file mode 100644
index 00000000..a8876224
--- /dev/null
+++ b/pango2/pango-hbfont.c
@@ -0,0 +1,1097 @@
+/* Pango2
+ *
+ * Copyright (C) 2021 Matthias Clasen
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "pango-hbfont-private.h"
+
+#include "pango-font-private.h"
+#include "pango-font-metrics-private.h"
+#include "pango-hbface-private.h"
+#include "pango-hbfamily-private.h"
+#include "pango-impl-utils.h"
+#include "pango-language-set-private.h"
+
+#include <hb-ot.h>
+
+/**
+ * Pango2HbFont:
+ *
+ * `Pango2HbFont` is a `Pango2Font` implementation that wraps
+ * a `hb_font_t` object and implements all of the `Pango2Font`
+ * functionality using HarfBuzz.
+ *
+ * In addition to a `Pango2HbFace` and a size, a number of optional
+ * parameters can be tweaked when creating a `Pango2HbFont`. First
+ * there are OpenType font features, which can be used to e.g.
+ * select Small Caps. If the face has variation axes, then
+ * coordinates for these axes can be provided. Finally, there are
+ * rendering parameters such as the dpi and the global transformation
+ * matrix.
+ */
+
+/* {{{ Utilities */
+
+static int
+get_average_char_width (Pango2Font *font,
+ const char *text)
+{
+ hb_font_t *hb_font = pango2_font_get_hb_font (font);
+ int width = 0;
+
+ for (const char *p = text; *p; p = g_utf8_next_char (p))
+ {
+ gunichar wc;
+ hb_codepoint_t glyph;
+ Pango2Rectangle extents;
+
+ wc = g_utf8_get_char (p);
+ if (!hb_font_get_nominal_glyph (hb_font, wc, &glyph))
+ continue;
+
+ pango2_font_get_glyph_extents (font, glyph, &extents, NULL);
+
+ width += extents.x + extents.width;
+ }
+
+ return width / pango2_utf8_strwidth (text);
+}
+
+static void
+get_max_char_size (Pango2Font *font,
+ const char *text,
+ int *width,
+ int *height)
+{
+ hb_font_t *hb_font = pango2_font_get_hb_font (font);
+ int w = 0;
+ int h = 0;
+
+ for (const char *p = text; *p; p = g_utf8_next_char (p))
+ {
+ gunichar wc;
+ hb_codepoint_t glyph;
+ Pango2Rectangle extents;
+
+ wc = g_utf8_get_char (p);
+ if (!hb_font_get_nominal_glyph (hb_font, wc, &glyph))
+ continue;
+
+ pango2_font_get_glyph_extents (font, glyph, &extents, NULL);
+
+ w = MAX (w, extents.x + extents.width);
+ h = MAX (h, extents.height);
+ }
+
+ if (width)
+ *width = w;
+
+ if (height)
+ *height = h;
+}
+
+static Pango2Variant
+pango2_variant_from_features (hb_feature_t *features,
+ unsigned int n_features)
+{
+ Pango2Variant variant = PANGO2_VARIANT_NORMAL;
+ gboolean all_caps = FALSE;
+
+ for (int i = 0; i < n_features; i++)
+ {
+ if (features[i].value != 1)
+ continue;
+
+ switch (features[i].tag)
+ {
+ case HB_TAG('s','m','c','p'):
+ if (all_caps)
+ variant = PANGO2_VARIANT_ALL_SMALL_CAPS;
+ else
+ variant = PANGO2_VARIANT_SMALL_CAPS;
+ break;
+ case HB_TAG('c','2','s','c'):
+ if (variant == PANGO2_VARIANT_SMALL_CAPS)
+ variant = PANGO2_VARIANT_ALL_SMALL_CAPS;
+ else
+ all_caps = TRUE;
+ break;
+ case HB_TAG('p','c','a','p'):
+ if (all_caps)
+ variant = PANGO2_VARIANT_ALL_PETITE_CAPS;
+ else
+ variant = PANGO2_VARIANT_PETITE_CAPS;
+ break;
+ case HB_TAG('c','2','p','c'):
+ if (variant == PANGO2_VARIANT_PETITE_CAPS)
+ variant = PANGO2_VARIANT_ALL_PETITE_CAPS;
+ else
+ all_caps = TRUE;
+ break;
+ case HB_TAG('u','n','i','c'):
+ variant = PANGO2_VARIANT_UNICASE;
+ break;
+ case HB_TAG('t','i','t','l'):
+ variant = PANGO2_VARIANT_TITLE_CAPS;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return variant;
+}
+
+static void
+font_description_get_features (const Pango2FontDescription *description,
+ hb_feature_t *features,
+ unsigned int length,
+ unsigned int *n_features)
+{
+
+#define ADD_FEATURE(name) \
+ features[*n_features].tag = HB_TAG (name[0], name[1], name[2], name[3]); \
+ features[*n_features].value = 1; \
+ features[*n_features].start = 0; \
+ features[*n_features].end = (unsigned int) -1; \
+ (*n_features)++
+
+ g_assert (length >= 2);
+
+ *n_features = 0;
+ switch (pango2_font_description_get_variant (description))
+ {
+ case PANGO2_VARIANT_SMALL_CAPS:
+ ADD_FEATURE ("smcp");
+ break;
+ case PANGO2_VARIANT_ALL_SMALL_CAPS:
+ ADD_FEATURE ("smcp");
+ ADD_FEATURE ("c2sc");
+ break;
+ case PANGO2_VARIANT_PETITE_CAPS:
+ ADD_FEATURE ("pcap");
+ break;
+ case PANGO2_VARIANT_ALL_PETITE_CAPS:
+ ADD_FEATURE ("pcap");
+ ADD_FEATURE ("c2pc");
+ break;
+ case PANGO2_VARIANT_UNICASE:
+ ADD_FEATURE ("unic");
+ break;
+ case PANGO2_VARIANT_TITLE_CAPS:
+ ADD_FEATURE ("titl");
+ break;
+ case PANGO2_VARIANT_NORMAL:
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+#undef ADD_FEATURE
+}
+
+static unsigned int
+count_variations (const char *string)
+{
+ unsigned int n;
+ const char *p;
+
+ n = 1;
+ p = string;
+ while ((p = strchr (p, ',')) != NULL)
+ n++;
+
+ return n;
+}
+
+static void
+parse_variations (const char *str,
+ hb_variation_t *variations,
+ unsigned int length,
+ unsigned int *n_variations)
+{
+ const char *p;
+
+ *n_variations = 0;
+
+ p = str;
+ while (p && *p && *n_variations < length)
+ {
+ const char *end = strchr (p, ',');
+ if (hb_variation_from_string (p, end ? end - p: -1, &variations[*n_variations]))
+ (*n_variations)++;
+ p = end ? end + 1 : NULL;
+ }
+}
+
+static char *
+variations_to_string (hb_variation_t *variations,
+ unsigned int n_variations)
+{
+ GString *s;
+ char buf[128];
+
+ s = g_string_new ("");
+
+ for (unsigned int i = 0; i < n_variations; i++)
+ {
+ hb_variation_to_string (&variations[i], buf, sizeof (buf));
+ if (s->len > 0)
+ g_string_append_c (s, ',');
+ g_string_append (s, buf);
+ }
+
+ return g_string_free (s, FALSE);
+}
+
+static inline void
+replace_variation (hb_variation_t *values,
+ unsigned int *len,
+ const hb_variation_t *v)
+{
+ for (int i = 0; i < *len; i++)
+ {
+ if (values[i].tag == v->tag)
+ {
+ values[i].value = v->value;
+ return;
+ }
+ }
+
+ values[(*len)++] = *v;
+}
+
+static unsigned int
+merge_variations (const hb_variation_t *v1,
+ unsigned int l1,
+ const hb_variation_t *v2,
+ unsigned int l2,
+ hb_variation_t *v,
+ unsigned int l)
+{
+ unsigned int len = 0;
+
+ for (int i = 0; i < l1; i++)
+ replace_variation (v, &len, &v1[i]);
+
+ for (int i = 0; i < l2; i++)
+ replace_variation (v, &len, &v2[i]);
+
+ return len;
+}
+
+static inline void
+collect_variation (hb_variation_t *variation,
+ unsigned int n_axes,
+ hb_ot_var_axis_info_t *axes,
+ float *coords)
+{
+ for (int j = 0; j < n_axes; j++)
+ {
+ if (axes[j].tag == variation->tag)
+ {
+ coords[axes[j].axis_index] = variation->value;
+ break;
+ }
+ }
+}
+
+static inline void
+collect_variations (hb_variation_t *variations,
+ unsigned int n_variations,
+ unsigned int n_axes,
+ hb_ot_var_axis_info_t *axes,
+ float *coords)
+{
+ for (int i = 0; i < n_variations; i++)
+ collect_variation (&variations[i], n_axes, axes, coords);
+}
+
+/* }}} */
+/* {{{ hex box sizing */
+
+/* This code needs to stay in sync with the hexbox rendering code in pangocairo-render.c */
+static HexBoxInfo *
+create_hex_box_info (Pango2HbFont *self)
+{
+ Pango2Font *font = PANGO2_FONT (self);
+ const char hexdigits[] = "0123456789ABCDEF";
+ hb_font_t *hb_font;
+ Pango2Font *mini_font;
+ HexBoxInfo *hbi;
+ int rows;
+ double pad;
+ int width = 0;
+ int height = 0;
+ hb_font_extents_t font_extents;
+ double font_ascent, font_descent;
+ double mini_size;
+ Pango2FontDescription *desc;
+ Pango2Context *context;
+ Pango2FontMap *map;
+
+ if (!PANGO2_FONT_FACE (font->face)->family)
+ return NULL;
+
+ map = PANGO2_FONT_FACE (font->face)->family->map;
+
+ if (!map)
+ return NULL;
+
+ desc = pango2_font_describe_with_absolute_size (font);
+ hb_font = pango2_font_get_hb_font (font);
+
+ /* Create mini_font description */
+
+ /* We inherit most font properties for the mini font.
+ * Just change family and size, so you get bold
+ * hex digits in the hexbox for a bold font.
+ */
+
+ /* We should rotate the box, not glyphs */
+ pango2_font_description_unset_fields (desc, PANGO2_FONT_MASK_GRAVITY);
+
+ pango2_font_description_set_family_static (desc, "monospace");
+
+ rows = 2;
+ mini_size = font->size / 2.2;
+
+ if (mini_size < 6.0)
+ {
+ rows = 1;
+ mini_size = MIN (MAX (font->size - 1, 0), 6.0);
+ }
+
+ pango2_font_description_set_size (desc, mini_size);
+
+ /* Load mini_font */
+ context = pango2_context_new_with_font_map (map);
+ pango2_context_set_matrix (context, &font->ctm);
+ pango2_context_set_language (context, pango2_script_get_sample_language (G_UNICODE_SCRIPT_LATIN));
+
+ mini_font = pango2_font_map_load_font (map, context, desc);
+
+ g_object_unref (context);
+
+ pango2_font_description_free (desc);
+
+ get_max_char_size (mini_font, hexdigits, &width, &height);
+
+ hb_font_get_extents_for_direction (hb_font, HB_DIRECTION_LTR, &font_extents);
+ font_ascent = font_extents.ascender / (double) PANGO2_SCALE;
+ font_descent = - font_extents.descender / (double) PANGO2_SCALE;
+
+ pad = (font_ascent + font_descent) / 43.;
+ pad = MIN (pad, mini_size / (double) PANGO2_SCALE);
+
+ hbi = g_new (HexBoxInfo, 1);
+ hbi->font = mini_font;
+ hbi->rows = rows;
+
+ hbi->digit_width = width / (double) PANGO2_SCALE;
+ hbi->digit_height = height / (double) PANGO2_SCALE;
+
+ hbi->pad_x = pad;
+ hbi->pad_y = pad;
+
+ hbi->line_width = MIN (hbi->pad_x, hbi->pad_y);
+
+ hbi->box_height = 3 * hbi->pad_y + rows * (hbi->pad_y + hbi->digit_height);
+
+ if (rows == 1 || hbi->box_height <= font_ascent)
+ hbi->box_descent = 2 * hbi->pad_y;
+ else if (hbi->box_height <= font_ascent + font_descent - 2 * hbi->pad_y)
+ hbi->box_descent = 2 * hbi->pad_y + hbi->box_height - font_ascent;
+ else
+ hbi->box_descent = font_descent * hbi->box_height / (font_ascent + font_descent);
+
+ return hbi;
+}
+
+static void
+get_space_extents (Pango2HbFont *self,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ Pango2Font *font = PANGO2_FONT (self);
+ hb_font_t *hb_font = pango2_font_get_hb_font (font);
+ int width;
+ hb_direction_t direction;
+ hb_font_extents_t font_extents;
+
+ direction = PANGO2_GRAVITY_IS_VERTICAL (font->gravity)
+ ? HB_DIRECTION_TTB
+ : HB_DIRECTION_LTR;
+
+ hb_font_get_extents_for_direction (hb_font, direction, &font_extents);
+
+ /* See https://docs.microsoft.com/en-us/typography/develop/character-design-standards/whitespace */
+
+ width = font->size / 4;
+
+ if (ink_rect)
+ {
+ ink_rect->x = ink_rect->y = ink_rect->height = 0;
+ ink_rect->width = width;
+ }
+
+ if (logical_rect)
+ {
+ logical_rect->x = 0;
+ logical_rect->y = - font_extents.ascender;
+ logical_rect->height = font_extents.ascender - font_extents.descender;
+ logical_rect->width = width;
+ }
+}
+
+static void
+get_glyph_extents_missing (Pango2HbFont *self,
+ Pango2Glyph glyph,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ gunichar ch;
+ int rows, cols;
+ HexBoxInfo *hbi;
+
+ ch = glyph & ~PANGO2_GLYPH_UNKNOWN_FLAG;
+
+ if (!self->hex_box_info)
+ self->hex_box_info = create_hex_box_info (self);
+
+ if (ch == 0x20 || ch == 0x2423)
+ {
+ get_space_extents (self, ink_rect, logical_rect);
+ return;
+ }
+
+ hbi = self->hex_box_info;
+
+ if (G_UNLIKELY (glyph == PANGO2_GLYPH_INVALID_INPUT || ch > 0x10FFFF))
+ {
+ rows = hbi->rows;
+ cols = 1;
+ }
+ else if (pango2_get_ignorable_size (ch, &rows, &cols))
+ {
+ /* We special-case ignorables when rendering hex boxes */
+ }
+ else
+ {
+ rows = hbi->rows;
+ cols = (ch > 0xffff ? 6 : 4) / rows;
+ }
+
+ if (ink_rect)
+ {
+ ink_rect->x = PANGO2_SCALE * hbi->pad_x;
+ ink_rect->y = PANGO2_SCALE * (hbi->box_descent - hbi->box_height);
+ ink_rect->width = PANGO2_SCALE * (3 * hbi->pad_x + cols * (hbi->digit_width + hbi->pad_x));
+ ink_rect->height = PANGO2_SCALE * hbi->box_height;
+ }
+
+ if (logical_rect)
+ {
+ logical_rect->x = 0;
+ logical_rect->y = PANGO2_SCALE * (hbi->box_descent - (hbi->box_height + hbi->pad_y));
+ logical_rect->width = PANGO2_SCALE * (5 * hbi->pad_x + cols * (hbi->digit_width + hbi->pad_x));
+ logical_rect->height = PANGO2_SCALE * (hbi->box_height + 2 * hbi->pad_y);
+ }
+}
+
+ /* }}} */
+/* {{{ Pango2Font implementation */
+
+struct _Pango2HbFontClass
+{
+ Pango2FontClass parent_class;
+};
+
+enum {
+ PROP_VARIATIONS = 1,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+G_DEFINE_FINAL_TYPE (Pango2HbFont, pango2_hb_font, PANGO2_TYPE_FONT)
+
+static void
+pango2_hb_font_init (Pango2HbFont *self)
+{
+}
+
+static void
+hex_box_info_destroy (HexBoxInfo *hex_box_info)
+{
+ g_object_unref (hex_box_info->font);
+ g_free (hex_box_info);
+}
+
+static void
+pango2_hb_font_finalize (GObject *object)
+{
+ Pango2HbFont *self = PANGO2_HB_FONT (object);
+
+ g_free (self->variations);
+ g_clear_pointer (&self->hex_box_info, hex_box_info_destroy);
+
+ G_OBJECT_CLASS (pango2_hb_font_parent_class)->finalize (object);
+}
+
+
+static void
+pango2_hb_font_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2HbFont *self = PANGO2_HB_FONT (object);
+
+ switch (property_id)
+ {
+ case PROP_VARIATIONS:
+ {
+ char *str = NULL;
+ if (self->variations)
+ str = variations_to_string (self->variations, self->n_variations);
+ g_value_take_string (value, str);
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static Pango2FontDescription *
+pango2_hb_font_describe (Pango2Font *font)
+{
+ Pango2HbFont *self = PANGO2_HB_FONT (font);
+ Pango2HbFace *face = PANGO2_HB_FACE (font->face);
+ Pango2FontDescription *desc;
+ Pango2Variant variant;
+
+ desc = pango2_font_face_describe (font->face);
+ pango2_font_description_set_gravity (desc, font->gravity);
+ pango2_font_description_set_size (desc, font->size);
+
+ variant = pango2_variant_from_features (self->features, self->n_features);
+ if (variant != PANGO2_VARIANT_NORMAL)
+ pango2_font_description_set_variant (desc, variant);
+ if (self->n_variations > 0 || face->n_variations > 0)
+ {
+ hb_variation_t *variations;
+ unsigned int n_variations;
+ char *str;
+
+ if (face->n_variations > 0)
+ {
+ variations = g_alloca (sizeof (hb_variation_t) * (self->n_variations + face->n_variations));
+ n_variations = merge_variations (face->variations, face->n_variations,
+ self->variations, self->n_variations,
+ variations, self->n_variations + face->n_variations);
+ }
+ else
+ {
+ variations = self->variations;
+ n_variations = self->n_variations;
+ }
+
+ str = variations_to_string (variations, n_variations);
+ pango2_font_description_set_variations (desc, str);
+ g_free (str);
+ }
+
+ return desc;
+}
+
+static void
+pango2_hb_font_get_glyph_extents (Pango2Font *font,
+ Pango2Glyph glyph,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ Pango2HbFont *self = PANGO2_HB_FONT (font);
+ Pango2HbFace *face = PANGO2_HB_FACE (font->face);
+ hb_font_t *hb_font = pango2_font_get_hb_font (font);
+ hb_glyph_extents_t extents;
+ hb_direction_t direction;
+ hb_font_extents_t font_extents;
+
+ direction = PANGO2_GRAVITY_IS_VERTICAL (font->gravity)
+ ? HB_DIRECTION_TTB
+ : HB_DIRECTION_LTR;
+
+ hb_font_get_extents_for_direction (hb_font, direction, &font_extents);
+
+ if (glyph == PANGO2_GLYPH_EMPTY)
+ {
+ if (ink_rect)
+ ink_rect->x = ink_rect->y = ink_rect->width = ink_rect->height = 0;
+
+ if (logical_rect)
+ {
+ logical_rect->x = logical_rect->width = 0;
+ logical_rect->y = - font_extents.ascender;
+ logical_rect->height = font_extents.ascender - font_extents.descender;
+ }
+
+ return;
+ }
+ else if (glyph & PANGO2_GLYPH_UNKNOWN_FLAG)
+ {
+ get_glyph_extents_missing (self, glyph, ink_rect, logical_rect);
+
+ return;
+ }
+
+ hb_font_get_glyph_extents (hb_font, glyph, &extents);
+
+ if (ink_rect)
+ {
+ Pango2Rectangle r;
+ Pango2Matrix m = PANGO2_MATRIX_INIT;
+
+ r.x = extents.x_bearing;
+ r.y = - extents.y_bearing;
+ r.width = extents.width;
+ r.height = - extents.height;
+
+ if (face->transform)
+ {
+ m.xx = face->transform->xx;
+ m.yx = - face->transform->yx;
+ m.xy = - face->transform->xy;
+ m.yy = face->transform->yy;
+
+ pango2_matrix_transform_rectangle (&m, &r);
+ }
+
+ switch (font->gravity)
+ {
+ case PANGO2_GRAVITY_AUTO:
+ case PANGO2_GRAVITY_SOUTH:
+ ink_rect->x = r.x;
+ ink_rect->y = r.y;
+ ink_rect->width = r.width;
+ ink_rect->height = r.height;
+ break;
+ case PANGO2_GRAVITY_NORTH:
+ ink_rect->x = - r.x;
+ ink_rect->y = - r.y;
+ ink_rect->width = - r.width;
+ ink_rect->height = - r.height;
+ break;
+ case PANGO2_GRAVITY_EAST:
+ ink_rect->x = r.y;
+ ink_rect->y = - r.x - r.width;
+ ink_rect->width = r.height;
+ ink_rect->height = r.width;
+ break;
+ case PANGO2_GRAVITY_WEST:
+ ink_rect->x = - r.y - r.height;
+ ink_rect->y = r.x;
+ ink_rect->width = r.height;
+ ink_rect->height = r.width;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (PANGO2_GRAVITY_IS_IMPROPER (font->gravity))
+ {
+ Pango2Matrix matrix = (Pango2Matrix) PANGO2_MATRIX_INIT;
+ pango2_matrix_scale (&matrix, -1, -1);
+ pango2_matrix_transform_rectangle (&matrix, ink_rect);
+ }
+ }
+
+ if (logical_rect)
+ {
+ hb_position_t h_advance;
+ hb_font_extents_t extents;
+
+ h_advance = hb_font_get_glyph_h_advance (hb_font, glyph);
+ hb_font_get_h_extents (hb_font, &extents);
+
+ logical_rect->x = 0;
+ logical_rect->height = extents.ascender - extents.descender;
+
+ switch (font->gravity)
+ {
+ case PANGO2_GRAVITY_AUTO:
+ case PANGO2_GRAVITY_SOUTH:
+ logical_rect->y = - extents.ascender;
+ logical_rect->width = h_advance;
+ break;
+ case PANGO2_GRAVITY_NORTH:
+ logical_rect->y = extents.descender;
+ logical_rect->width = h_advance;
+ break;
+ case PANGO2_GRAVITY_EAST:
+ logical_rect->y = - logical_rect->height / 2;
+ logical_rect->width = logical_rect->height;
+ break;
+ case PANGO2_GRAVITY_WEST:
+ logical_rect->y = - logical_rect->height / 2;
+ logical_rect->width = - logical_rect->height;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (PANGO2_GRAVITY_IS_IMPROPER (font->gravity))
+ {
+ logical_rect->height = - logical_rect->height;
+ logical_rect->y = - logical_rect->y;
+ }
+ }
+}
+
+static Pango2FontMetrics *
+pango2_hb_font_get_metrics (Pango2Font *font,
+ Pango2Language *language)
+{
+ Pango2HbFont *self = PANGO2_HB_FONT (font);
+ hb_font_t *hb_font = pango2_font_get_hb_font (font);
+ Pango2FontMetrics *metrics;
+ hb_font_extents_t extents;
+ hb_position_t position;
+
+ metrics = pango2_font_metrics_new ();
+
+ hb_font_get_extents_for_direction (hb_font, HB_DIRECTION_LTR, &extents);
+
+ metrics->descent = - extents.descender;
+ metrics->ascent = extents.ascender;
+ metrics->height = extents.ascender - extents.descender + extents.line_gap;
+
+ if (hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_UNDERLINE_SIZE, &position) && position != 0)
+ metrics->underline_thickness = position;
+ else
+ metrics->underline_thickness = PANGO2_SCALE;
+
+ if (hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_UNDERLINE_OFFSET, &position) && position != 0)
+ metrics->underline_position = position;
+ else
+ metrics->underline_position = - PANGO2_SCALE;
+
+ if (hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_STRIKEOUT_SIZE, &position) && position != 0)
+ metrics->strikethrough_thickness = position;
+ else
+ metrics->strikethrough_thickness = PANGO2_SCALE;
+
+ if (hb_ot_metrics_get_position (hb_font, HB_OT_METRICS_TAG_STRIKEOUT_OFFSET, &position) && position != 0)
+ metrics->strikethrough_position = position;
+ else
+ metrics->strikethrough_position = metrics->ascent / 2;
+
+ if (self->approximate_char_width == 0 || self->approximate_char_lang != language)
+ {
+ self->approximate_char_width = get_average_char_width (font, pango2_language_get_sample_string (language));
+ self->approximate_char_lang = language;
+ }
+
+ if (self->approximate_digit_width == 0)
+ get_max_char_size (font, "0123456789", &self->approximate_digit_width, NULL);
+
+ metrics->approximate_char_width = self->approximate_char_width;
+ metrics->approximate_digit_width = self->approximate_digit_width;
+
+ return metrics;
+}
+
+static hb_font_t *
+pango2_hb_font_create_hb_font (Pango2Font *font)
+{
+ Pango2HbFont *self = PANGO2_HB_FONT (font);
+ Pango2HbFace *face = PANGO2_HB_FACE (font->face);
+ hb_font_t *hb_font;
+ double x_scale, y_scale;
+ unsigned int n_axes;
+ hb_ot_var_axis_info_t *axes;
+ float *coords;
+ int size;
+
+ hb_font = hb_font_create (pango2_hb_face_get_hb_face (face));
+
+ size = font->size * font->dpi / 72.f;
+ x_scale = face->x_scale;
+ y_scale = face->y_scale;
+
+ if (PANGO2_GRAVITY_IS_IMPROPER (font->gravity))
+ {
+ x_scale = - x_scale;
+ y_scale = - y_scale;
+ }
+
+ hb_font_set_scale (hb_font, size * x_scale, size * y_scale);
+ hb_font_set_ptem (hb_font, font->size / PANGO2_SCALE);
+
+#if HB_VERSION_ATLEAST (3, 3, 0)
+ hb_font_set_synthetic_slant (hb_font, pango2_matrix_get_slant_ratio (face->transform));
+#endif
+
+ if (face->instance_id >= 0)
+ hb_font_set_var_named_instance (hb_font, face->instance_id);
+
+ if (self->n_variations > 0 || face->n_variations > 0)
+ {
+ n_axes = hb_ot_var_get_axis_count (face->face);
+ axes = g_alloca (sizeof (hb_ot_var_axis_info_t) * n_axes);
+ coords = g_alloca (sizeof (float) * n_axes);
+
+ hb_ot_var_get_axis_infos (face->face, 0, &n_axes, axes);
+
+ if (face->instance_id >= 0)
+ hb_ot_var_named_instance_get_design_coords (face->face, face->instance_id, &n_axes, coords);
+ else
+ {
+ for (int i = 0; i < n_axes; i++)
+ coords[axes[i].axis_index] = axes[i].default_value;
+ }
+
+ collect_variations (face->variations, face->n_variations, n_axes, axes, coords);
+ collect_variations (self->variations, self->n_variations, n_axes, axes, coords);
+
+ hb_font_set_var_coords_design (hb_font, coords, n_axes);
+ }
+
+ return hb_font;
+}
+
+static void
+pango2_hb_font_get_transform (Pango2Font *font,
+ Pango2Matrix *matrix)
+{
+ Pango2HbFace *face = PANGO2_HB_FACE (font->face);
+
+ if (face->transform)
+ {
+ *matrix = *face->transform;
+ pango2_matrix_scale (matrix, face->x_scale, face->y_scale);
+ }
+ else
+ *matrix = (Pango2Matrix) PANGO2_MATRIX_INIT;
+}
+
+static void
+pango2_hb_font_class_init (Pango2HbFontClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ Pango2FontClass *font_class = PANGO2_FONT_CLASS (class);
+
+ object_class->finalize = pango2_hb_font_finalize;
+ object_class->get_property = pango2_hb_font_get_property;
+
+ font_class->describe = pango2_hb_font_describe;
+ font_class->get_glyph_extents = pango2_hb_font_get_glyph_extents;
+ font_class->get_metrics = pango2_hb_font_get_metrics;
+ font_class->create_hb_font = pango2_hb_font_create_hb_font;
+ font_class->get_transform = pango2_hb_font_get_transform;
+
+ /**
+ * Pango2HbFont:variations: (attributes org.gtk.Property.get=pango2_hb_font_get_variations)
+ *
+ * The variations that are applied for this font.
+ *
+ * This property contains a string representation of the variations.
+ */
+ properties[PROP_VARIATIONS] =
+ g_param_spec_string ("variations", NULL, NULL, NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+/* }}} */
+ /* {{{ Public API */
+
+/**
+ * pango2_hb_font_new:
+ * @face: the `Pango2HbFace` to use
+ * @size: the desired size in points, scaled by `PANGO2_SCALE`
+ * @features: (nullable) (array length=n_features): OpenType font features to use for this font
+ * @n_features: length of @features
+ * @variations: (nullable) (array length=n_variations): font variations to apply
+ * @n_variations: length of @variations
+ * @gravity: the gravity to use when rendering
+ * @dpi: the dpi used when rendering
+ * @ctm: (nullable): transformation matrix to use when rendering
+ *
+ * Creates a new `Pango2HbFont`.
+ *
+ * Returns: a newly created `Pango2HbFont`
+ */
+Pango2HbFont *
+pango2_hb_font_new (Pango2HbFace *face,
+ int size,
+ hb_feature_t *features,
+ unsigned int n_features,
+ hb_variation_t *variations,
+ unsigned int n_variations,
+ Pango2Gravity gravity,
+ float dpi,
+ const Pango2Matrix *ctm)
+{
+ Pango2HbFont *self;
+ Pango2Font *font;
+
+ g_return_val_if_fail (PANGO2_IS_HB_FACE (face), NULL);
+ g_return_val_if_fail (size > 0, NULL);
+ g_return_val_if_fail (dpi > 0, NULL);
+
+ self = g_object_new (PANGO2_TYPE_HB_FONT, NULL);
+
+ font = PANGO2_FONT (self);
+
+ pango2_font_set_face (font, PANGO2_FONT_FACE (face));
+ pango2_font_set_size (font, size);
+ pango2_font_set_dpi (font, dpi);
+ pango2_font_set_gravity (font, gravity);
+ pango2_font_set_ctm (font, ctm);
+
+ self->features = g_memdup2 (features, sizeof (hb_feature_t) * n_features);
+ self->n_features = n_features;
+ self->variations = g_memdup2 (variations, sizeof (hb_variation_t) * n_variations);
+ self->n_variations = n_variations;
+
+ return self;
+}
+
+/**
+ * pango2_hb_font_new_for_description:
+ * @face: the `Pango2HbFace` to use
+ * @description: a `Pango2FontDescription`
+ * @dpi: the dpi used when rendering
+ * @ctm: (nullable): transformation matrix to use when rendering
+ *
+ * Creates a new `Pango2HbFont` with size, features, variations and
+ * gravity taken from a font description.
+ *
+ * Returns: a newly created `Pango2HbFont`
+ */
+Pango2HbFont *
+pango2_hb_font_new_for_description (Pango2HbFace *face,
+ const Pango2FontDescription *description,
+ float dpi,
+ const Pango2Matrix *ctm)
+{
+ int size;
+ hb_feature_t features[10];
+ unsigned int n_features;
+ hb_variation_t *variations;
+ unsigned int n_variations;
+ unsigned int length;
+ Pango2Gravity gravity;
+ const char *str;
+
+ g_return_val_if_fail (PANGO2_IS_HB_FACE (face), NULL);
+ g_return_val_if_fail (description != NULL, NULL);
+ g_return_val_if_fail (dpi > 0, NULL);
+
+ if ((pango2_font_description_get_set_fields (description) & PANGO2_FONT_MASK_SIZE) == 0)
+ size = 10 * PANGO2_SCALE;
+ else if (pango2_font_description_get_size_is_absolute (description))
+ size = pango2_font_description_get_size (description) * 72. / dpi;
+ else
+ size = pango2_font_description_get_size (description);
+
+ g_return_val_if_fail (size > 0, NULL);
+
+ font_description_get_features (description, features, G_N_ELEMENTS (features), &n_features);
+
+ if ((pango2_font_description_get_set_fields (description) & PANGO2_FONT_MASK_VARIATIONS) != 0)
+ {
+ str = pango2_font_description_get_variations (description);
+ length = count_variations (str);
+ variations = g_alloca (sizeof (hb_variation_t) * length);
+ parse_variations (str, variations, length, &n_variations);
+ }
+ else
+ {
+ variations = NULL;
+ n_variations = 0;
+ }
+
+ if ((pango2_font_description_get_set_fields (description) & PANGO2_FONT_MASK_GRAVITY) != 0 &&
+ pango2_font_description_get_gravity (description) != PANGO2_GRAVITY_SOUTH)
+ gravity = pango2_font_description_get_gravity (description);
+ else
+ gravity = PANGO2_GRAVITY_AUTO;
+
+ return pango2_hb_font_new (face, size, features, n_features, variations, n_variations, gravity, dpi, ctm);
+}
+
+/**
+ * pango2_hb_font_get_features:
+ * @self: a `Pango2Font`
+ * @n_features: (nullable) (out caller-allocates): return location for
+ * the length of the returned array
+ *
+ * Obtain the OpenType features that are provided by the font.
+ *
+ * These are passed to the rendering system, together with features
+ * that have been explicitly set via attributes.
+ *
+ * Note that this does not include OpenType features which the
+ * rendering system enables by default.
+ *
+ * Returns: (nullable) (transfer none): the features
+ */
+const hb_feature_t *
+pango2_hb_font_get_features (Pango2HbFont *self,
+ unsigned int *n_features)
+{
+ g_return_val_if_fail (PANGO2_IS_HB_FONT (self), NULL);
+
+ if (n_features)
+ *n_features = self->n_features;
+
+ return self->features;
+}
+
+
+/**
+ * pango2_hb_font_get_variations:
+ * @self: a `Pango2HbFont`
+ * @n_variations: (nullable) (out caller-allocates): return location for
+ * the length of the returned array
+ *
+ * Gets the variations of the font.
+ *
+ * Returns: (nullable) (transfer none): the variations
+ */
+const hb_variation_t *
+pango2_hb_font_get_variations (Pango2HbFont *self,
+ unsigned int *n_variations)
+{
+ g_return_val_if_fail (PANGO2_IS_HB_FONT (self), NULL);
+
+ if (n_variations)
+ *n_variations = self->n_variations;
+
+ return self->variations;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-hbfont.h b/pango2/pango-hbfont.h
new file mode 100644
index 00000000..1b98cd49
--- /dev/null
+++ b/pango2/pango-hbfont.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 Matthias Clasen
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-font.h>
+#include <pango2/pango-hbface.h>
+#include <hb.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_HB_FONT (pango2_hb_font_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2HbFont, pango2_hb_font, PANGO2, HB_FONT, Pango2Font)
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2HbFont * pango2_hb_font_new (Pango2HbFace *face,
+ int size,
+ hb_feature_t *features,
+ unsigned int n_features,
+ hb_variation_t *variations,
+ unsigned int n_variations,
+ Pango2Gravity gravity,
+ float dpi,
+ const Pango2Matrix *ctm);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2HbFont * pango2_hb_font_new_for_description (Pango2HbFace *face,
+ const Pango2FontDescription *description,
+ float dpi,
+ const Pango2Matrix *ctm);
+
+PANGO2_AVAILABLE_IN_ALL
+const hb_feature_t * pango2_hb_font_get_features (Pango2HbFont *self,
+ unsigned int *n_features);
+
+PANGO2_AVAILABLE_IN_ALL
+const hb_variation_t * pango2_hb_font_get_variations (Pango2HbFont *self,
+ unsigned int *n_variations);
+
+G_END_DECLS
diff --git a/pango2/pango-impl-utils.h b/pango2/pango-impl-utils.h
new file mode 100644
index 00000000..cc21bc0b
--- /dev/null
+++ b/pango2/pango-impl-utils.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2003 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+#include <pango2/pango.h>
+
+G_BEGIN_DECLS
+
+
+/* String interning for static strings */
+#define I_(string) g_intern_static_string (string)
+
+/* We define these functions static here because we don't want to add public API
+ * for them (if anything, it belongs to glib, but glib found it trivial enough
+ * not to add API for). At some point metrics calculations will be
+ * centralized and this mess can be minimized. Or so I hope.
+ */
+
+static inline G_GNUC_UNUSED int
+pango2_unichar_width (gunichar c)
+{
+ return G_UNLIKELY (g_unichar_iszerowidth (c)) ? 0 :
+ G_UNLIKELY (g_unichar_iswide (c)) ? 2 : 1;
+}
+
+static G_GNUC_UNUSED glong
+pango2_utf8_strwidth (const char *p)
+{
+ glong len = 0;
+ g_return_val_if_fail (p != NULL, 0);
+
+ while (*p)
+ {
+ len += pango2_unichar_width (g_utf8_get_char (p));
+ p = g_utf8_next_char (p);
+ }
+
+ return len;
+}
+
+/* Glib's g_utf8_strlen() is broken and stops at embedded NUL's.
+ * Wrap it here. */
+static G_GNUC_UNUSED glong
+pango2_utf8_strlen (const char *p,
+ gssize max)
+{
+ glong len = 0;
+ const char *start = p;
+ g_return_val_if_fail (p != NULL || max == 0, 0);
+
+ if (max <= 0)
+ return g_utf8_strlen (p, max);
+
+ p = g_utf8_next_char (p);
+ while (p - start < max)
+ {
+ ++len;
+ p = g_utf8_next_char (p);
+ }
+
+ /* only do the last len increment if we got a complete
+ * char (don't count partial chars)
+ */
+ if (p - start <= max)
+ ++len;
+
+ return len;
+}
+
+
+/* To be made public at some point */
+
+static G_GNUC_UNUSED void
+pango2_glyph_string_reverse_range (Pango2GlyphString *glyphs,
+ int start,
+ int end)
+{
+ int i, j;
+
+ for (i = start, j = end - 1; i < j; i++, j--)
+ {
+ Pango2GlyphInfo glyph_info;
+ int log_cluster;
+
+ glyph_info = glyphs->glyphs[i];
+ glyphs->glyphs[i] = glyphs->glyphs[j];
+ glyphs->glyphs[j] = glyph_info;
+
+ log_cluster = glyphs->log_clusters[i];
+ glyphs->log_clusters[i] = glyphs->log_clusters[j];
+ glyphs->log_clusters[j] = log_cluster;
+ }
+}
+
+static inline gboolean
+pango2_is_default_ignorable (gunichar ch)
+{
+ int plane = ch >> 16;
+
+ if (G_LIKELY (plane == 0))
+ {
+ int page = ch >> 8;
+ switch (page)
+ {
+ case 0x00: return ch == 0x00ad;
+ case 0x03: return ch == 0x034f;
+ case 0x06: return ch == 0x061c;
+ case 0x17: return (0x17b4 <= ch && ch <= 0x17b5);
+ case 0x18: return (0x180b <= ch && ch <= 0x180e);
+ case 0x20: return (0x200b <= ch && ch <= 0x200f) ||
+ (0x202a <= ch && ch <= 0x202e) ||
+ (0x2060 <= ch && ch <= 0x206f);
+ case 0xfe: return (0xfe00 <= ch && ch <= 0xfe0f) || ch == 0xfeff;
+ case 0xff: return (0xfff0 <= ch && ch <= 0xfff8);
+ default: return FALSE;
+ }
+ }
+ else
+ {
+ /* Other planes */
+ switch (plane)
+ {
+ case 0x01: return (0x1d173 <= ch && ch <= 0x1d17a);
+ case 0x0e: return (0xe0000 <= ch && ch <= 0xe0fff);
+ default: return FALSE;
+ }
+ }
+}
+
+/* These are the default ignorables that we render as hexboxes
+ * with nicks if PANGO2_SHOW_IGNORABLES is used.
+ *
+ * The cairo hexbox drawing code assumes that these nicks are
+ * 1-6 ASCII chars
+ */
+static struct {
+ gunichar ch;
+ const char *nick;
+} ignorables[] = {
+ { 0x00ad, "SHY" }, /* SOFT HYPHEN */
+ { 0x034f, "CGJ" }, /* COMBINING GRAPHEME JOINER */
+ { 0x061c, "ALM" }, /* ARABIC LETTER MARK */
+ { 0x200b, "ZWS" }, /* ZERO WIDTH SPACE */
+ { 0x200c, "ZWNJ" }, /* ZERO WIDTH NON-JOINER */
+ { 0x200d, "ZWJ" }, /* ZERO WIDTH JOINER */
+ { 0x200e, "LRM" }, /* LEFT-TO-RIGHT MARK */
+ { 0x200f, "RLM" }, /* RIGHT-TO-LEFT MARK */
+ { 0x2028, "LS" }, /* LINE SEPARATOR */
+ { 0x2029, "PS" }, /* PARAGRAPH SEPARATOR */
+ { 0x202a, "LRE" }, /* LEFT-TO-RIGHT EMBEDDING */
+ { 0x202b, "RLE" }, /* RIGHT-TO-LEFT EMBEDDING */
+ { 0x202c, "PDF" }, /* POP DIRECTIONAL FORMATTING */
+ { 0x202d, "LRO" }, /* LEFT-TO-RIGHT OVERRIDE */
+ { 0x202e, "RLO" }, /* RIGHT-TO-LEFT OVERRIDE */
+ { 0x2060, "WJ" }, /* WORD JOINER */
+ { 0x2061, "FA" }, /* FUNCTION APPLICATION */
+ { 0x2062, "IT" }, /* INVISIBLE TIMES */
+ { 0x2063, "IS" }, /* INVISIBLE SEPARATOR */
+ { 0x2066, "LRI" }, /* LEFT-TO-RIGHT ISOLATE */
+ { 0x2067, "RLI" }, /* RIGHT-TO-LEFT ISOLATE */
+ { 0x2068, "FSI" }, /* FIRST STRONG ISOLATE */
+ { 0x2069, "PDI" }, /* POP DIRECTIONAL ISOLATE */
+ { 0xfeff, "ZWNBS" }, /* ZERO WIDTH NO-BREAK SPACE */
+};
+
+static inline G_GNUC_UNUSED const char *
+pango2_get_ignorable (gunichar ch)
+{
+ for (guint i = 0; i < G_N_ELEMENTS (ignorables); i++)
+ {
+ if (ch < ignorables[i].ch)
+ return NULL;
+
+ if (ch == ignorables[i].ch)
+ return ignorables[i].nick;
+ }
+
+ return NULL;
+}
+
+static inline G_GNUC_UNUSED const char *
+pango2_get_ignorable_size (gunichar ch,
+ int *rows,
+ int *cols)
+{
+ const char *nick;
+ int len;
+
+ nick = pango2_get_ignorable (ch);
+ if (nick)
+ {
+ len = strlen (nick);
+ if (len < 4)
+ {
+ *rows = 1;
+ *cols = len;
+ }
+ else if (len > 4)
+ {
+ *rows = 2;
+ *cols = 3;
+ }
+ else
+ {
+ *rows = 2;
+ *cols = 2;
+ }
+ }
+
+ return nick;
+}
+
+/* Backward compatibility shim, to avoid bumping up the minimum
+ * required version of GLib; most of our uses of g_memdup() are
+ * safe, and those that aren't have been fixed
+ */
+#if !GLIB_CHECK_VERSION (2, 67, 3)
+# define g_memdup2(mem,size) g_memdup((mem),(size))
+#endif
+
+G_END_DECLS
diff --git a/pango2/pango-item-private.h b/pango2/pango-item-private.h
new file mode 100644
index 00000000..13925b32
--- /dev/null
+++ b/pango2/pango-item-private.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-item.h"
+#include "pango-break.h"
+
+/*< private >
+ * Pango2Analysis:
+ * @size_font: font to use for determining line height
+ * @font: the font for this segment
+ * @level: the bidirectional level for this segment.
+ * @gravity: the glyph orientation for this segment (A `Pango2Gravity`).
+ * @flags: boolean flags for this segment
+ * @script: the detected script for this segment (A `Pango2Script`)
+ * @language: the detected language for this segment.
+ * @extra_attrs: extra attributes for this segment.
+ *
+ * The `Pango2Analysis` structure stores information about
+ * the properties of a segment of text.
+ */
+struct _Pango2Analysis
+{
+ Pango2Font *size_font;
+ Pango2Font *font;
+
+ guint8 level;
+ guint8 gravity;
+ guint8 flags;
+
+ guint8 script;
+ Pango2Language *language;
+
+ GSList *extra_attrs;
+};
+
+/*< private>
+ * Pango2Item:
+ * @offset: byte offset of the start of this item in text.
+ * @length: length of this item in bytes.
+ * @num_chars: number of Unicode characters in the item.
+ * @char_offset: character offset of the start of this item in text. Since 1.50
+ * @analysis: analysis results for the item.
+ *
+ * The `Pango2Item` structure stores information about a segment of text.
+ *
+ * You typically obtain `Pango2Items` by itemizing a piece of text
+ * with [func@itemize].
+ */
+struct _Pango2Item
+{
+ int offset;
+ int length;
+ int num_chars;
+ int char_offset;
+ Pango2Analysis analysis;
+};
+
+
+void pango2_analysis_collect_features (const Pango2Analysis *analysis,
+ hb_feature_t *features,
+ guint length,
+ guint *num_features);
+
+void pango2_analysis_set_size_font (Pango2Analysis *analysis,
+ Pango2Font *font);
+Pango2Font * pango2_analysis_get_size_font (const Pango2Analysis *analysis);
+
+GList * pango2_itemize_with_font (Pango2Context *context,
+ Pango2Direction base_dir,
+ const char *text,
+ int start_index,
+ int length,
+ Pango2AttrList *attrs,
+ Pango2AttrIterator *cached_iter,
+ const Pango2FontDescription *desc);
+
+GList * pango2_itemize_post_process_items (Pango2Context *context,
+ const char *text,
+ Pango2LogAttr *log_attrs,
+ GList *items);
+
+Pango2Item * pango2_item_new (void);
+Pango2Item * pango2_item_split (Pango2Item *orig,
+ int split_index,
+ int split_offset);
+void pango2_item_unsplit (Pango2Item *orig,
+ int split_index,
+ int split_offset);
+void pango2_item_apply_attrs (Pango2Item *item,
+ Pango2AttrIterator *iter);
+
+
+typedef struct _ItemProperties ItemProperties;
+struct _ItemProperties
+{
+ Pango2LineStyle uline_style;
+ Pango2UnderlinePosition uline_position;
+ Pango2LineStyle strikethrough_style;
+ Pango2LineStyle oline_style;
+ guint oline_single : 1;
+ guint showing_space : 1;
+ guint no_paragraph_break : 1;
+ int letter_spacing;
+ int line_spacing;
+ int absolute_line_height;
+ double line_height;
+ Pango2Attribute *shape;
+};
+
+void pango2_item_get_properties (Pango2Item *item,
+ ItemProperties *properties);
diff --git a/pango2/pango-item.c b/pango2/pango-item.c
new file mode 100644
index 00000000..97b0a2b9
--- /dev/null
+++ b/pango2/pango-item.c
@@ -0,0 +1,652 @@
+/* Pango2
+ * pango-item.c: Single run handling
+ *
+ * Copyright (C) 2000 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.
+ */
+
+#include "config.h"
+#include "pango-attributes.h"
+#include "pango-attr-private.h"
+#include "pango-item-private.h"
+#include "pango-impl-utils.h"
+
+/**
+ * Pango2Analysis:
+ *
+ * The `Pango2Analysis` structure stores information about
+ * the properties of a segment of text.
+ */
+
+/**
+ * Pango2Item:
+ *
+ * The `Pango2Item` structure stores information about
+ * a segment of text.
+ *
+ * You typically obtain `Pango2Items` by itemizing a piece
+ * of text with [func@Pango2.itemize].
+ */
+
+/**
+ * pango2_item_new:
+ *
+ * Creates a new `Pango2Item` structure initialized to default values.
+ *
+ * Return value: the newly allocated `Pango2Item`, which should
+ * be freed with [method@Pango2.Item.free].
+ */
+Pango2Item *
+pango2_item_new (void)
+{
+ Pango2Item *result = g_slice_new0 (Pango2Item);
+
+ return (Pango2Item *)result;
+}
+
+/**
+ * pango2_item_copy:
+ * @item: (nullable): a `Pango2Item`
+ *
+ * Copy an existing `Pango2Item` structure.
+ *
+ * Return value: (nullable): the newly allocated `Pango2Item`
+ */
+Pango2Item *
+pango2_item_copy (Pango2Item *item)
+{
+ GSList *extra_attrs, *tmp_list;
+ Pango2Item *result;
+
+ if (item == NULL)
+ return NULL;
+
+ result = pango2_item_new ();
+
+ result->offset = item->offset;
+ result->length = item->length;
+ result->num_chars = item->num_chars;
+ result->char_offset = item->char_offset;
+
+ result->analysis = item->analysis;
+ if (result->analysis.size_font)
+ g_object_ref (result->analysis.size_font);
+
+ if (result->analysis.font)
+ g_object_ref (result->analysis.font);
+
+ extra_attrs = NULL;
+ tmp_list = item->analysis.extra_attrs;
+ while (tmp_list)
+ {
+ extra_attrs = g_slist_prepend (extra_attrs, pango2_attribute_copy (tmp_list->data));
+ tmp_list = tmp_list->next;
+ }
+
+ result->analysis.extra_attrs = g_slist_reverse (extra_attrs);
+
+ return result;
+}
+
+/**
+ * pango2_item_free:
+ * @item: (nullable): a `Pango2Item`, may be %NULL
+ *
+ * Free a `Pango2Item` and all associated memory.
+ **/
+void
+pango2_item_free (Pango2Item *item)
+{
+ if (item == NULL)
+ return;
+
+ if (item->analysis.extra_attrs)
+ {
+ g_slist_foreach (item->analysis.extra_attrs, (GFunc)pango2_attribute_destroy, NULL);
+ g_slist_free (item->analysis.extra_attrs);
+ }
+
+ if (item->analysis.size_font)
+ g_object_unref (item->analysis.size_font);
+
+ if (item->analysis.font)
+ g_object_unref (item->analysis.font);
+
+ g_slice_free (Pango2Item, item);
+}
+
+G_DEFINE_BOXED_TYPE (Pango2Item, pango2_item,
+ pango2_item_copy,
+ pango2_item_free);
+
+/**
+ * pango2_item_split:
+ * @orig: a `Pango2Item`
+ * @split_index: byte index of position to split item, relative to the
+ * start of the item
+ * @split_offset: number of chars between start of @orig and @split_index
+ *
+ * Modifies @orig to cover only the text after @split_index, and
+ * returns a new item that covers the text before @split_index that
+ * used to be in @orig.
+ *
+ * You can think of @split_index as the length of the returned item.
+ * @split_index may not be 0, and it may not be greater than or equal
+ * to the length of @orig (that is, there must be at least one byte
+ * assigned to each item, you can't create a zero-length item).
+ * @split_offset is the length of the first item in chars, and must be
+ * provided because the text used to generate the item isn't available,
+ * so `pango2_item_split()` can't count the char length of the split items
+ * itself.
+ *
+ * Return value: new item representing text before @split_index, which
+ * should be freed with [method@Pango2.Item.free].
+ */
+Pango2Item *
+pango2_item_split (Pango2Item *orig,
+ int split_index,
+ int split_offset)
+{
+ Pango2Item *new_item;
+
+ g_return_val_if_fail (orig != NULL, NULL);
+ g_return_val_if_fail (split_index > 0, NULL);
+ g_return_val_if_fail (split_index < orig->length, NULL);
+ g_return_val_if_fail (split_offset > 0, NULL);
+ g_return_val_if_fail (split_offset < orig->num_chars, NULL);
+
+ new_item = pango2_item_copy (orig);
+ new_item->length = split_index;
+ new_item->num_chars = split_offset;
+
+ orig->offset += split_index;
+ orig->length -= split_index;
+ orig->num_chars -= split_offset;
+ orig->char_offset += split_offset;
+
+ return new_item;
+}
+
+/*< private >
+ * pango2_item_unsplit:
+ * @orig: the item to unsplit
+ * @split_index: value passed to pango2_item_split()
+ * @split_offset: value passed to pango2_item_split()
+ *
+ * Undoes the effect of a pango2_item_split() call with
+ * the same arguments.
+ *
+ * You are expected to free the new item that was returned
+ * by pango2_item_split() yourself.
+ */
+void
+pango2_item_unsplit (Pango2Item *orig,
+ int split_index,
+ int split_offset)
+{
+ orig->offset -= split_index;
+ orig->length += split_index;
+ orig->num_chars += split_offset;
+ orig->char_offset -= split_offset;
+}
+
+static int
+compare_attr (gconstpointer p1, gconstpointer p2)
+{
+ const Pango2Attribute *a1 = p1;
+ const Pango2Attribute *a2 = p2;
+ if (pango2_attribute_equal (a1, a2) &&
+ a1->start_index == a2->start_index &&
+ a1->end_index == a2->end_index)
+ return 0;
+
+ return 1;
+}
+
+/**
+ * pango2_item_apply_attrs:
+ * @item: a `Pango2Item`
+ * @iter: a `Pango2AttrIterator`
+ *
+ * Add attributes to a `Pango2Item`.
+ *
+ * The idea is that you have attributes that don't affect itemization,
+ * such as font features, so you filter them out using
+ * [method@Pango2.AttrList.filter], itemize your text, then reapply the
+ * attributes to the resulting items using this function.
+ *
+ * The @iter should be positioned before the range of the item,
+ * and will be advanced past it. This function is meant to be called
+ * in a loop over the items resulting from itemization, while passing
+ * the iter to each call.
+ */
+void
+pango2_item_apply_attrs (Pango2Item *item,
+ Pango2AttrIterator *iter)
+{
+ int start, end;
+ GSList *attrs = NULL;
+
+ do
+ {
+ pango2_attr_iterator_range (iter, &start, &end);
+
+ if (start >= item->offset + item->length)
+ break;
+
+ if (end >= item->offset)
+ {
+ GSList *list, *l;
+
+ list = pango2_attr_iterator_get_attrs (iter);
+ for (l = list; l; l = l->next)
+ {
+ if (!g_slist_find_custom (attrs, l->data, compare_attr))
+
+ attrs = g_slist_prepend (attrs, pango2_attribute_copy (l->data));
+ }
+ g_slist_free_full (list, (GDestroyNotify)pango2_attribute_destroy);
+ }
+
+ if (end >= item->offset + item->length)
+ break;
+ }
+ while (pango2_attr_iterator_next (iter));
+
+ item->analysis.extra_attrs = g_slist_concat (item->analysis.extra_attrs, attrs);
+}
+
+void
+pango2_analysis_collect_features (const Pango2Analysis *analysis,
+ hb_feature_t *features,
+ guint length,
+ guint *num_features)
+{
+ GSList *l;
+
+ if (PANGO2_IS_HB_FONT (analysis->font))
+ {
+ const hb_feature_t *font_features;
+ guint n_font_features;
+
+ font_features = pango2_hb_font_get_features (PANGO2_HB_FONT (analysis->font),
+ &n_font_features);
+ *num_features = MIN (length, n_font_features);
+ memcpy (features, font_features, sizeof (hb_feature_t) * *num_features);
+ }
+
+ for (l = analysis->extra_attrs; l && *num_features < length; l = l->next)
+ {
+ Pango2Attribute *attr = l->data;
+ if (attr->type == PANGO2_ATTR_FONT_FEATURES)
+ {
+ const char *feat;
+ const char *end;
+ int len;
+
+ feat = attr->str_value;
+
+ while (feat != NULL && *num_features < length)
+ {
+ end = strchr (feat, ',');
+ if (end)
+ len = end - feat;
+ else
+ len = -1;
+ if (hb_feature_from_string (feat, len, &features[*num_features]))
+ {
+ features[*num_features].start = attr->start_index;
+ features[*num_features].end = attr->end_index;
+ (*num_features)++;
+ }
+
+ if (end == NULL)
+ break;
+
+ feat = end + 1;
+ }
+ }
+ }
+
+ /* Turn off ligatures when letterspacing */
+ for (l = analysis->extra_attrs; l && *num_features < length; l = l->next)
+ {
+ Pango2Attribute *attr = l->data;
+ if (attr->type == PANGO2_ATTR_LETTER_SPACING)
+ {
+ hb_tag_t tags[] = {
+ HB_TAG('l','i','g','a'),
+ HB_TAG('c','l','i','g'),
+ HB_TAG('d','l','i','g'),
+ HB_TAG('h','l','i','g'),
+ };
+ int i;
+ for (i = 0; i < G_N_ELEMENTS (tags); i++)
+ {
+ features[*num_features].tag = tags[i];
+ features[*num_features].value = 0;
+ features[*num_features].start = attr->start_index;
+ features[*num_features].end = attr->end_index;
+ (*num_features)++;
+ }
+ }
+ }
+}
+
+/*< private >
+ * pango2_analysis_set_size_font:
+ * @analysis: a `Pango2Analysis`
+ * @font: a `Pango2Font`
+ *
+ * Sets the font to use for determining the line height.
+ *
+ * This is used when scaling fonts for emulated Small Caps,
+ * to preserve the original line height.
+ */
+void
+pango2_analysis_set_size_font (Pango2Analysis *analysis,
+ Pango2Font *font)
+{
+ if (analysis->size_font)
+ g_object_unref (analysis->size_font);
+ analysis->size_font = font;
+ if (analysis->size_font)
+ g_object_ref (analysis->size_font);
+}
+
+/*< private >
+ * pango2_analysis_get_size_font:
+ * @analysis: a `Pango2Analysis`
+ *
+ * Gets the font to use for determining line height.
+ *
+ * If this returns `NULL`, use analysis->font.
+ *
+ * Returns: (nullable) (transfer none): the font
+ */
+Pango2Font *
+pango2_analysis_get_size_font (const Pango2Analysis *analysis)
+{
+ return analysis->size_font;
+}
+
+/*< private >
+ * pango2_item_get_properties:
+ * @item: a `Pango2Item`
+ * @properties: `ItemProperties` struct to populate
+ *
+ * Extract useful information from the @item's attributes.
+ *
+ * Note that letter-spacing and shape are required to be constant
+ * across items. But underline and strikethrough can vary across
+ * an item, so we collect all the values that we find.
+ */
+void
+pango2_item_get_properties (Pango2Item *item,
+ ItemProperties *properties)
+{
+ GSList *tmp_list = item->analysis.extra_attrs;
+
+ properties->uline_style = PANGO2_LINE_STYLE_NONE;
+ properties->uline_position = PANGO2_UNDERLINE_POSITION_NORMAL;
+ properties->oline_style = PANGO2_LINE_STYLE_NONE;
+ properties->strikethrough_style = PANGO2_LINE_STYLE_NONE;
+ properties->showing_space = FALSE;
+ properties->no_paragraph_break = FALSE;
+ properties->letter_spacing = 0;
+ properties->line_height = 0.0;
+ properties->absolute_line_height = 0;
+ properties->line_spacing = 0;
+ properties->shape = NULL;
+
+ while (tmp_list)
+ {
+ Pango2Attribute *attr = tmp_list->data;
+
+ switch ((int) attr->type)
+ {
+ case PANGO2_ATTR_UNDERLINE:
+ properties->uline_style = attr->int_value;
+ break;
+
+ case PANGO2_ATTR_UNDERLINE_POSITION:
+ properties->uline_position = attr->int_value;
+ break;
+
+ case PANGO2_ATTR_OVERLINE:
+ properties->oline_style = attr->int_value;
+ break;
+
+ case PANGO2_ATTR_STRIKETHROUGH:
+ properties->strikethrough_style = attr->int_value;
+ break;
+
+ case PANGO2_ATTR_LETTER_SPACING:
+ properties->letter_spacing = attr->int_value;
+ break;
+
+ case PANGO2_ATTR_LINE_HEIGHT:
+ properties->line_height = attr->double_value;
+ break;
+
+ case PANGO2_ATTR_ABSOLUTE_LINE_HEIGHT:
+ properties->absolute_line_height = attr->int_value;
+ break;
+
+ case PANGO2_ATTR_LINE_SPACING:
+ properties->line_spacing = attr->int_value;
+ break;
+
+ case PANGO2_ATTR_SHOW:
+ properties->showing_space = (attr->int_value & PANGO2_SHOW_SPACES) != 0;
+ break;
+
+ case PANGO2_ATTR_PARAGRAPH:
+ properties->no_paragraph_break = TRUE;
+ break;
+
+ case PANGO2_ATTR_SHAPE:
+ properties->shape = attr;
+ break;
+
+ default:
+ break;
+ }
+ tmp_list = tmp_list->next;
+ }
+}
+
+/**
+ * pango2_analysis_get_font:
+ * @analysis: a `Pango2Analysis`
+ *
+ * Returns the font that will be used for text
+ * with this `Pango2Analysis`.
+ *
+ * Return value: (transfer none): the `Pango2Font`
+ */
+Pango2Font *
+pango2_analysis_get_font (const Pango2Analysis *analysis)
+{
+ return analysis->font;
+}
+
+/**
+ * pango2_analysis_get_bidi_level:
+ * @analysis: a `Pango2Analysis`
+ *
+ * Returns the bidi embedding level for text
+ * with this `Pango2Analysis`.
+ *
+ * Return value: the bidi embedding level
+ */
+int
+pango2_analysis_get_bidi_level (const Pango2Analysis *analysis)
+{
+ return analysis->level;
+}
+
+/**
+ * pango2_analysis_get_gravity:
+ * @analysis: a `Pango2Analysis`
+ *
+ * Returns the gravity for text with this `Pango2Analysis`.
+ *
+ * Return value: the gravity
+ */
+Pango2Gravity
+pango2_analysis_get_gravity (const Pango2Analysis *analysis)
+{
+ return (Pango2Gravity) analysis->gravity;
+}
+
+/**
+ * pango2_analysis_get_flags:
+ * @analysis: a `Pango2Analysis`
+ *
+ * Returns flags for this `Pango2Analysis`.
+ *
+ * Possible flag values are
+ * `PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE`,
+ * `PANGO2_ANALYSIS_FLAG_IS_ELLIPSIS` and
+ * `PANGO2_ANALYSIS_FLAG_NEED_HYPHEN`.
+ *
+ * Return value: the flags
+ */
+guint
+pango2_analysis_get_flags (const Pango2Analysis *analysis)
+{
+ return analysis->flags;
+}
+
+/**
+ * pango2_analysis_get_script:
+ * @analysis: a `Pango2Analysis`
+ *
+ * Returns the script for text with this `Pango2Analysis`.
+ *
+ * Return value: the script
+ */
+GUnicodeScript
+pango2_analysis_get_script (const Pango2Analysis *analysis)
+{
+ return (GUnicodeScript) analysis->script;
+}
+
+/**
+ * pango2_analysis_get_language:
+ * @analysis: a `Pango2Analysis`
+ *
+ * Returns the language for text with this `Pango2Analysis`.
+ *
+ * Return value: the script
+ */
+Pango2Language *
+pango2_analysis_get_language (const Pango2Analysis *analysis)
+{
+ return analysis->language;
+}
+
+/**
+ * pango2_analysis_get_extra_attributes:
+ * @analysis: a `Pango2Analysis`
+ *
+ * Returns attributes to apply to text with this
+ * `Pango2Analysis`.
+ *
+ * Return value: (transfer none) (element-type Pango2Attribute):
+ * a `GSList` with `Pango2Attribute` values
+ */
+GSList *
+pango2_analysis_get_extra_attributes (const Pango2Analysis *analysis)
+{
+ return analysis->extra_attrs;
+}
+
+/**
+ * pango2_item_get_analysis:
+ * @item: a `Pango2Item`
+ *
+ * Returns the `Pango2Analysis` of @item.
+ *
+ * Return value: (transfer none): a `Pango2Analysis`
+ */
+const Pango2Analysis *
+pango2_item_get_analysis (Pango2Item *item)
+{
+ return &item->analysis;
+}
+
+/**
+ * pango2_item_get_byte_offset:
+ * @item: a `Pango2Item`
+ *
+ * Returns the byte offset of this items
+ * text in the overall paragraph text.
+ *
+ * Return value: the byte offset
+ */
+int
+pango2_item_get_byte_offset (Pango2Item *item)
+{
+ return item->offset;
+}
+
+/**
+ * pango2_item_get_byte_length:
+ * @item: a `Pango2Item`
+ *
+ * Returns the length of this items
+ * text in bytes.
+ *
+ * Return value: the length of @item
+ */
+int
+pango2_item_get_byte_length (Pango2Item *item)
+{
+ return item->length;
+}
+
+/**
+ * pango2_item_get_char_offset:
+ * @item: a `Pango2Item`
+ *
+ * Returns the offset of this items text
+ * in the overall paragraph text, in characters.
+ *
+ * Returns value: the character offset
+ */
+int
+pango2_item_get_char_offset (Pango2Item *item)
+{
+ return item->char_offset;
+}
+
+/**
+ * pango2_item_get_char_length:
+ * @item: a `Pango2Item`
+ *
+ * Returns the number of characters in this
+ * items text.
+ *
+ * Return value: the number of characters in @item
+ */
+int
+pango2_item_get_char_length (Pango2Item *item)
+{
+ return item->num_chars;
+}
diff --git a/pango2/pango-item.h b/pango2/pango-item.h
new file mode 100644
index 00000000..c5dccfd2
--- /dev/null
+++ b/pango2/pango-item.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-attr-list.h>
+#include <pango2/pango-attr-list.h>
+
+G_BEGIN_DECLS
+
+typedef struct _Pango2Analysis Pango2Analysis;
+typedef struct _Pango2Item Pango2Item;
+
+/**
+ * PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE:
+ *
+ * Whether the segment should be shifted to center around the baseline.
+ *
+ * This is mainly used in vertical writing directions.
+ */
+#define PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE (1 << 0)
+
+/**
+ * PANGO2_ANALYSIS_FLAG_IS_ELLIPSIS:
+ *
+ * Whether this run holds ellipsized text.
+ */
+#define PANGO2_ANALYSIS_FLAG_IS_ELLIPSIS (1 << 1)
+
+/**
+ * PANGO2_ANALYSIS_FLAG_NEED_HYPHEN:
+ *
+ * Whether to add a hyphen at the end of the run during shaping.
+ */
+#define PANGO2_ANALYSIS_FLAG_NEED_HYPHEN (1 << 2)
+
+#define PANGO2_TYPE_ITEM (pango2_item_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_item_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Item * pango2_item_copy (Pango2Item *item);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_item_free (Pango2Item *item);
+
+PANGO2_AVAILABLE_IN_ALL
+GList * pango2_itemize (Pango2Context *context,
+ Pango2Direction base_dir,
+ const char *text,
+ int start_index,
+ int length,
+ Pango2AttrList *attrs);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Font * pango2_analysis_get_font (const Pango2Analysis *analysis);
+PANGO2_AVAILABLE_IN_ALL
+int pango2_analysis_get_bidi_level (const Pango2Analysis *analysis);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Gravity pango2_analysis_get_gravity (const Pango2Analysis *analysis);
+PANGO2_AVAILABLE_IN_ALL
+guint pango2_analysis_get_flags (const Pango2Analysis *analysis);
+PANGO2_AVAILABLE_IN_ALL
+GUnicodeScript pango2_analysis_get_script (const Pango2Analysis *analysis);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Language * pango2_analysis_get_language (const Pango2Analysis *analysis);
+PANGO2_AVAILABLE_IN_ALL
+GSList * pango2_analysis_get_extra_attributes (const Pango2Analysis *analysis);
+PANGO2_AVAILABLE_IN_ALL
+const Pango2Analysis * pango2_item_get_analysis (Pango2Item *item);
+PANGO2_AVAILABLE_IN_ALL
+int pango2_item_get_byte_offset (Pango2Item *item);
+PANGO2_AVAILABLE_IN_ALL
+int pango2_item_get_byte_length (Pango2Item *item);
+PANGO2_AVAILABLE_IN_ALL
+int pango2_item_get_char_offset (Pango2Item *item);
+PANGO2_AVAILABLE_IN_ALL
+int pango2_item_get_char_length (Pango2Item *item);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2Item, pango2_item_free)
+
+G_END_DECLS
diff --git a/pango2/pango-language-sample-table.h b/pango2/pango-language-sample-table.h
new file mode 100644
index 00000000..59513209
--- /dev/null
+++ b/pango2/pango-language-sample-table.h
@@ -0,0 +1,675 @@
+/* Pango2 Language Sample Table
+ *
+ * Each entry is of the form:
+ *
+ * LANGUAGE(
+ * code |* Name *|,
+ * SOURCE,
+ * "Sample text for the language."
+ * |* Translation of the sample text to English *|
+ * )
+ *
+ * Where code is the ISO639-1, ISO639-2, or ISO639-3 code for the language,
+ * the first one that exists. Name is the name of the language in English.
+ *
+ * Source is where the sample text comes from. One of:
+ *
+ * WP-PANG
+ * Wikipedia's List of Pangrams in Other Languages
+ * http://en.wikipedia.org/wiki/List_of_pangrams#Other_languages
+ * Fetched on 2008-08-19
+ *
+ * WP-SFD
+ * Wikipedia's Sample Font Displays in Other Languages
+ * http://en.wikipedia.org/wiki/Sample_Font_Displays_In_Other_Languages
+ * Fetched on 2008-08-19
+ *
+ * WP
+ * Wikipedia, Article about the language
+ * Fetched on 2020-09-08
+ *
+ * GLASS
+ * Kermit project's "I Can Eat Glass" list, also available in pango-view/
+ * http://www.columbia.edu/kermit/utf8.html#glass
+ * Fetched on 2008-08-19, updates on 2020-09-08
+ *
+ * KERMIT
+ * Kermit project's Quick-Brown-Fox equivalents for other languages
+ * http://www.columbia.edu/kermit/utf8.html#quickbrownfox
+ * Fetched on 2008-08-19
+ *
+ * GSPECI
+ * gnome-specimen's translations
+ * http://svn.gnome.org/viewvc/gnome-specimen/trunk/po/
+ * Fetched on 2008-08-19
+ *
+ * MISC
+ * Miscellaneous
+ *
+ * The sample text may be a pangram, but is not necessarily. It is chosen to
+ * be demonstrative of normal text in the language, as well as exposing font
+ * feature requirements unique to the language. It should be suitable for use
+ * as sample text in a font selection dialog.
+ *
+ * Needless to say, the list MUST be sorted on the language code.
+ */
+/* Sacrificial define to make introspection happy. */
+#ifndef LANGUAGE
+#define LANGUAGE(x, y, z)
+#endif
+LANGUAGE(
+ af /* Afrikaans */,
+ GLASS,
+ "Ek kan glas eet, maar dit doen my nie skade nie."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ ar /* Arabic */,
+ WP-PANG,
+ "نص حكيم له سر قاطع وذو شأن عظيم مكتوب على ثوب أخضر ومغلف بجلد أزرق."
+ /* A wise text which has an absolute secret and great importance, written on a green tissue and covered with blue leather. */
+ )
+LANGUAGE(
+ arn /* Mapudungun */,
+ WP-PANG,
+ "Gvxam mincetu apocikvyeh: ñizol ce mamvj ka raq kuse bafkeh mew."
+ /* Tale under the full moon: the chief chemamull and the clay old woman at the lake/sea. */
+ )
+LANGUAGE(
+ bar /* Bavarian */,
+ GLASS,
+ "I koh Glos esa, und es duard ma ned wei."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ bg /* Bulgarian */,
+ WP-SFD,
+ "Под южно дърво, цъфтящо в синьо, бягаше малко пухкаво зайче."
+ /* A little fluffy young rabbit ran under a southern tree blooming in blue */
+ )
+LANGUAGE(
+ bi /* Bislama */,
+ GLASS,
+ "Mi save kakae glas, hemi no save katem mi."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ bn /* Bengali */,
+ GLASS,
+ "আমি কাঁচ খেতে পারি, তাতে আমার কোনো ক্ষতি হয় না।"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ bo /* Tibetan */,
+ GLASS,
+ "ཤེལ་སྒོ་ཟ་ནས་ང་ན་གི་མ་རེད།"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ bs /* Bosnian */,
+ WP-PANG,
+ "Fin džip, gluh jež i čvrst konjić dođoše bez moljca."
+ /* A nice jeep, a deaf hedgehog and a tough horse came without a moth. */
+ )
+LANGUAGE(
+ ca /* Catalan */,
+ WP-PANG,
+ "Jove xef, porti whisky amb quinze glaçons d'hidrogen, coi!"
+ /* Young chef, bring whisky with fifteen hydrogen ice cubes, damn! */
+ )
+LANGUAGE(
+ ch /* Chamorro */,
+ GLASS,
+ "Siña yo' chumocho krestat, ti ha na'lalamen yo'."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ cs /* Czech */,
+ WP-SFD,
+ "Příliš žluťoučký kůň úpěl ďábelské ódy."
+ /* A too yellow horse moaned devil odes. */
+ )
+LANGUAGE(
+ cy /* Welsh */,
+ GLASS,
+ "Dw i'n gallu bwyta gwydr, 'dyw e ddim yn gwneud dolur i mi."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ da /* Danish */,
+ WP-SFD,
+ "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon."
+ /* The quiz contestants ate strawberries with cream while Walther the clown was playing the xylophone. */
+ )
+LANGUAGE(
+ de /* German */,
+ WP-SFD,
+ "Zwölf Boxkämpfer jagen Viktor quer über den großen Sylter Deich."
+ /* Twelve boxing fighters drive Viktor over the great. */
+ )
+LANGUAGE(
+ dv /* Maldivian */,
+ WP,
+ "މާއްދާ 1 – ހުރިހާ އިންސާނުން ވެސް އުފަންވަނީ، ދަރަޖަ އާއި ޙައްޤު ތަކުގައި މިނިވަންކަމާއި ހަމަހަމަކަން ލިބިގެންވާ ބައެއްގެ ގޮތުގައެވެ."
+ /* Beginning of UDHR */
+ )
+LANGUAGE(
+ el /* Greek */,
+ WP-SFD,
+ "Θέλει αρετή και τόλμη η ελευθερία. (Ανδρέας Κάλβος)"
+ /* Liberty requires virtue and mettle. (Andreas Kalvos) */
+ )
+LANGUAGE(
+ en /* English */,
+ GSPECI,
+ "The quick brown fox jumps over the lazy dog."
+ )
+LANGUAGE(
+ enm /* Middle English */,
+ GLASS,
+ "Ich canne glas eten and hit hirtiþ me nouȝt."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ eo /* Esperanto */,
+ WP-SFD,
+ "Eĥoŝanĝo ĉiuĵaŭde."
+ /* Change of echo every Thursday. */
+ )
+LANGUAGE(
+ es /* Spanish */,
+ WP-PANG,
+ "Jovencillo emponzoñado de whisky: ¡qué figurota exhibe!"
+ /* Whisky-intoxicated youngster — what a figure he's showing! */
+ )
+LANGUAGE(
+ et /* Estonian */,
+ WP-SFD,
+ "See väike mölder jõuab rongile hüpata."
+ /* This small miller is able to jump on the train. */
+ )
+LANGUAGE(
+ eu /* Basque */,
+ GLASS,
+ "Kristala jan dezaket, ez dit minik ematen."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ fa /* Persian */,
+ MISC /* Behdad Esfahbod (#548730) */,
+ "«الا یا اَیُّها السّاقی! اَدِرْ کَأساً وَ ناوِلْها!» که عشق آسان نمود اوّل، ولی افتاد مشکل‌ها!"
+ )
+LANGUAGE(
+ fi /* Finnish */,
+ WP-SFD,
+ "Viekas kettu punaturkki laiskan koiran takaa kurkki."
+ /* The cunning red-coated fox peeped from behind the lazy dog. */
+ )
+LANGUAGE(
+ fr /* French */,
+ MISC /* Vincent Untz (#549520) http://fr.wikipedia.org/wiki/Pangramme */,
+ "Voix ambiguë d'un cœur qui, au zéphyr, préfère les jattes de kiwis."
+ /* Ambiguous voice of a heart that, in the wind, prefers bowls of kiwis. */
+ )
+LANGUAGE(
+ fro /* Old French */,
+ GLASS,
+ "Je puis mangier del voirre. Ne me nuit."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ ga /* Irish */,
+ WP-PANG,
+ "Chuaigh bé mhórshách le dlúthspád fíorfhinn trí hata mo dhea-phorcáin bhig."
+ /* A maiden of large appetite with an intensely white, dense spade went through the hat of my good little porker. */
+ )
+LANGUAGE(
+ gd /* Scottish Gaelic */,
+ GLASS,
+ "S urrainn dhomh gloinne ithe; cha ghoirtich i mi."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ gl /* Galician */,
+ GLASS,
+ "Eu podo xantar cristais e non cortarme."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ got /* Gothic */,
+ GLASS,
+ "𐌼𐌰𐌲 𐌲𐌻𐌴𐍃 𐌹̈𐍄𐌰𐌽, 𐌽𐌹 𐌼𐌹𐍃 𐍅𐌿 𐌽𐌳𐌰𐌽 𐌱𐍂𐌹𐌲𐌲𐌹𐌸."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ gu /* Gujarati */,
+ GLASS,
+ "હું કાચ ખાઇ શકુ છુ અને તેનાથી મને દર્દ નથી થતુ."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ gv /* Manx Gaelic */,
+ GLASS,
+ "Foddym gee glonney agh cha jean eh gortaghey mee."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ haw /* Hawaiian */,
+ GLASS,
+ "Hiki iaʻu ke ʻai i ke aniani; ʻaʻole nō lā au e ʻeha."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ he /* Hebrew */,
+ WP-SFD,
+ "דג סקרן שט לו בים זך אך לפתע פגש חבורה נחמדה שצצה כך."
+ /* A curious fish sailed a clear sea, and suddenly found nice company that just popped up. */
+ )
+LANGUAGE(
+ hi /* Hindi */,
+ MISC /* G Karunakar (#549532) */,
+ "नहीं नजर किसी की बुरी नहीं किसी का मुँह काला जो करे सो उपर वाला"
+ /* its not in the sight or the face, but its all in god's grace. */
+ )
+LANGUAGE(
+ hr /* Croatian */,
+ MISC,
+ "Deblji krojač: zgužvah smeđ filc u tanjušni džepić."
+ /* A fatter taylor: I’ve crumpled a brown felt in a slim pocket. */
+ )
+LANGUAGE(
+ hu /* Hungarian */,
+ WP-SFD,
+ "Egy hűtlen vejét fülöncsípő, dühös mexikói úr Wesselényinél mázol Quitóban."
+ /* An angry Mexican man, who caught his faithless son-in-law, is painting Wesselényi's house in Quito. */
+ )
+LANGUAGE(
+ hy /* Armenian */,
+ GLASS,
+ "Կրնամ ապակի ուտել և ինծի անհանգիստ չըներ։"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ is /* Icelandic */,
+ WP-PANG,
+ "Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa"
+ /* If a new axe were here, thieves would feel increasing deterrence and punishment. */
+ )
+LANGUAGE(
+ it /* Italian */,
+ WP-SFD,
+ "Ma la volpe, col suo balzo, ha raggiunto il quieto Fido."
+ /* But the fox, with its jump, reached the calm dog */
+ )
+LANGUAGE(
+ ja /* Japanese */,
+ KERMIT,
+ "いろはにほへと ちりぬるを 色は匂へど 散りぬるを"
+ )
+LANGUAGE(
+ jam /* Jamaican Creole English */,
+ KERMIT,
+ "Chruu, a kwik di kwik brong fox a jomp huova di liezi daag de, yu no siit?"
+ )
+LANGUAGE(
+ jbo /* Lojban */,
+ WP-PANG,
+ ".o'i mu xagji sofybakni cu zvati le purdi"
+ /* Watch out, five hungry Soviet-cows are in the garden! */
+ )
+LANGUAGE(
+ jv /* Javanese */,
+ GLASS,
+ "Aku isa mangan beling tanpa lara."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ ka /* Georgian */,
+ GLASS,
+ "მინას ვჭამ და არა მტკივა."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ km /* Khmer */,
+ GLASS,
+ "ខ្ញុំអាចញុំកញ្ចក់បាន ដោយគ្មានបញ្ហារ"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ kn /* Kannada */,
+ GLASS,
+ "ನಾನು ಗಾಜನ್ನು ತಿನ್ನಬಲ್ಲೆ ಮತ್ತು ಅದರಿಂದ ನನಗೆ ನೋವಾಗುವುದಿಲ್ಲ."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ ko /* Korean */,
+ WP-SFD,
+ "다람쥐 헌 쳇바퀴에 타고파"
+ /* I Wanna ride on the chipmunk's old hamster wheel. */
+ )
+LANGUAGE(
+ kw /* Cornish */,
+ GLASS,
+ "Mý a yl dybry gwéder hag éf ny wra ow ankenya."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ la /* Latin */,
+ WP-PANG,
+ "Sic surgens, dux, zelotypos quam karus haberis"
+ )
+LANGUAGE(
+ lo /* Lao */,
+ GLASS,
+ "ຂອ້ຍກິນແກ້ວໄດ້ໂດຍທີ່ມັນບໍ່ໄດ້ເຮັດໃຫ້ຂອ້ຍເຈັບ"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ lt /* Lithuanian */,
+ WP-PANG,
+ "Įlinkdama fechtuotojo špaga sublykčiojusi pragręžė apvalų arbūzą."
+ /* Incurving fencer sword sparkled and perforated a round watermelon. */
+ )
+LANGUAGE(
+ lv /* Latvian */,
+ WP-SFD,
+ "Sarkanās jūrascūciņas peld pa jūru."
+ /* Red seapigs swim in the sea. */
+ )
+LANGUAGE(
+ map /* Marquesan */,
+ GLASS,
+ "E koʻana e kai i te karahi, mea ʻā, ʻaʻe hauhau."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ mk /* Macedonian */,
+ GLASS,
+ "Можам да јадам стакло, а не ме штета."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ ml /* Malayalam */,
+ GLASS,
+ "വേദനയില്ലാതെ കുപ്പിചില്ലു് എനിയ്ക്കു് കഴിയ്ക്കാം."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ mn /* Mongolian */,
+ GLASS,
+ "ᠪᠢ ᠰᠢᠯᠢ ᠢᠳᠡᠶᠦ ᠴᠢᠳᠠᠨᠠ ᠂ ᠨᠠᠳᠤᠷ ᠬᠣᠤᠷᠠᠳᠠᠢ ᠪᠢᠰᠢ"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ mr /* Marathi */,
+ GLASS,
+ "मी काच खाऊ शकतो, मला ते दुखत नाही."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ ms /* Malay */,
+ GLASS,
+ "Saya boleh makan kaca dan ia tidak mencederakan saya."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ my /* Burmese */,
+ WP,
+ "ဘာသာပြန်နှင့် စာပေပြုစုရေး ကော်မရှင်"
+ /* Literary and Translation Commission */
+ )
+LANGUAGE(
+ nap /* Neapolitan */,
+ GLASS,
+ "M' pozz magna' o'vetr, e nun m' fa mal."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ nb /* Norwegian Bokmål */,
+ GSPECI,
+ "Vår sære Zulu fra badeøya spilte jo whist og quickstep i min taxi."
+ )
+LANGUAGE(
+ nl /* Dutch */,
+ WP-SFD,
+ "Pa's wijze lynx bezag vroom het fikse aquaduct."
+ /* Dad's wise lynx piously regarded the substantial aqueduct. */
+ )
+LANGUAGE(
+ nn /* Norwegian Nynorsk */,
+ GLASS,
+ "Eg kan eta glas utan å skada meg."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ no /* Norwegian Bokmål */,
+ GSPECI,
+ "Vår sære Zulu fra badeøya spilte jo whist og quickstep i min taxi."
+ )
+LANGUAGE(
+ nv /* Navajo */,
+ GLASS,
+ "Tsésǫʼ yishą́ągo bííníshghah dóó doo shił neezgai da."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ oc /* Occitan */,
+ GLASS,
+ "Pòdi manjar de veire, me nafrariá pas."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ or /* Oriya */,
+ GLASS,
+ "ମୁଁ କାଚ ଖାଇପାରେ ଏବଂ ତାହା ମୋର କ୍ଷତି କରିନଥାଏ।."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ pa /* Punjabi */,
+ GLASS,
+ "ਮੈਂ ਗਲਾਸ ਖਾ ਸਕਦਾ ਹਾਂ ਅਤੇ ਇਸ ਨਾਲ ਮੈਨੂੰ ਕੋਈ ਤਕਲੀਫ ਨਹੀਂ."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ pcd /* Picard */,
+ GLASS,
+ "Ch'peux mingi du verre, cha m'foé mie n'ma."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ pl /* Polish */,
+ WP-SFD,
+ "Pchnąć w tę łódź jeża lub ośm skrzyń fig."
+ /* Push into this boat a hedgehog or eight boxes of figs. */
+ )
+LANGUAGE(
+ pt /* Portuguese */,
+ WP-SFD,
+ "Vejam a bruxa da raposa Salta-Pocinhas e o cão feliz que dorme regalado."
+ /* Watch the witch of the Jump-Puddles fox and the happy dog that sleeps delighted. */
+ )
+LANGUAGE(
+ pt-br /* Brazilian Portuguese */,
+ WP-PANG,
+ "À noite, vovô Kowalsky vê o ímã cair no pé do pingüim queixoso e vovó põe açúcar no chá de tâmaras do jabuti feliz."
+ /* At night, grandpa Kowalsky sees the magnet falling in the complaining penguin's foot and grandma puts sugar in the happy tortoise's date tea.*/
+ )
+LANGUAGE(
+ ro /* Romanian */,
+ MISC /* Misu Moldovan (#552993) */,
+ "Fumegând hipnotic sașiul azvârle mreje în bălți."
+ /* Hypnotically smoking, the cross-eyed man throws fishing nets into ponds. */
+ )
+LANGUAGE(
+ ru /* Russian */,
+ WP-PANG,
+ "В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!"
+ /* Would a citrus live in the bushes of the south? Yes, but only a fake one! */
+ )
+LANGUAGE(
+ sa /* Sanskrit */,
+ GLASS,
+ "काचं शक्नोम्यत्तुम् । नोपहिनस्ति माम् ॥"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ scn /* Sicilian */,
+ GLASS,
+ "Puotsu mangiari u vitru, nun mi fa mali."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ si /* Sinhalese */,
+ WP,
+ "මනොපුබ්‌බඞ්‌ගමා ධම්‌මා, මනොසෙට්‌ඨා මනොමයා; මනසා චෙ පදුට්‌ඨෙන, භාසති වා කරොති වා; තතො නං දුක්‌ඛමන්‌වෙති, චක්‌කංව වහතො පදං."
+ )
+LANGUAGE(
+ sk /* Slovak */,
+ KERMIT,
+ "Starý kôň na hŕbe kníh žuje tíško povädnuté ruže, na stĺpe sa ďateľ učí kvákať novú ódu o živote."
+ )
+LANGUAGE(
+ sl /* Slovenian */,
+ WP-PANG,
+ "Šerif bo za vajo spet kuhal domače žgance."
+ /* For an exercise, sheriff will again make home-made mush. */
+ )
+LANGUAGE(
+ sq /* Albanian */,
+ GLASS,
+ "Unë mund të ha qelq dhe nuk më gjen gjë."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ sr /* Serbian (Cyrillic) */,
+ WP-SFD,
+ "Чешће цeђење мрeжастим џаком побољшава фертилизацију генских хибрида."
+ /* More frequent filtering through the reticular bag improves fertilization of genetic hybrids. */
+ )
+#if 0
+LANGUAGE(
+ sr-sr@latin /* Serbian (Latin) */,
+ WP-SFD,
+ "Češće ceđenje mrežastim džakom poboljšava fertilizaciju genskih hibrida."
+ /* More frequent filtering through the reticular bag improves fertilization of genetic hybrids. */
+ )
+#endif
+LANGUAGE(
+ sv /* Swedish */,
+ WP-SFD,
+ "Flygande bäckasiner söka strax hwila på mjuka tuvor."
+ /* Flying snipes soon look to rest on soft grass beds. */
+ )
+LANGUAGE(
+ swg /* Swabian */,
+ GLASS,
+ "I kå Glas frässa, ond des macht mr nix!"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ ta /* Tamil */,
+ GLASS,
+ "நான் கண்ணாடி சாப்பிடுவேன், அதனால் எனக்கு ஒரு கேடும் வராது."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ te /* Telugu */,
+ GLASS,
+ "నేను గాజు తినగలను అయినా నాకు యేమీ కాదు."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ th /* Thai */,
+ WP-SFD,
+ "เป็นมนุษย์สุดประเสริฐเลิศคุณค่า - กว่าบรรดาฝูงสัตว์เดรัจฉาน - จงฝ่าฟันพัฒนาวิชาการ อย่าล้างผลาญฤๅเข่นฆ่าบีฑาใคร - ไม่ถือโทษโกรธแช่งซัดฮึดฮัดด่า - หัดอภัยเหมือนกีฬาอัชฌาสัย - ปฏิบัติประพฤติกฎกำหนดใจ - พูดจาให้จ๊ะ ๆ จ๋า ๆ น่าฟังเอยฯ"
+ /* Being a man is worthy - Beyond senseless animal - Begin educate thyself - Begone from killing and trouble - Bear not thy grudge, damn and, curse - Bestow forgiving and sporting - Befit with rules - Benign speech speak thou */
+ )
+LANGUAGE(
+ tl /* Tagalog */,
+ GLASS,
+ "Kaya kong kumain nang bubog at hindi ako masaktan."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ tr /* Turkish */,
+ WP-PANG,
+ "Pijamalı hasta yağız şoföre çabucak güvendi."
+ /* The patient in pajamas trusted the swarthy driver quickly. */
+ )
+LANGUAGE(
+ tw /* Twi */,
+ GLASS,
+ "Metumi awe tumpan, ɜnyɜ me hwee."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ uk /* Ukrainian */,
+ WP-PANG,
+ "Чуєш їх, доцю, га? Кумедна ж ти, прощайся без ґольфів!"
+ /* Daughter, do you hear them, eh? Oh, you are funny! Say good-bye without knee-length socks. */
+ )
+LANGUAGE(
+ ur /* Urdu */,
+ GLASS,
+ "میں کانچ کھا سکتا ہوں اور مجھے تکلیف نہیں ہوتی ۔"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ vec /* Venetian */,
+ GLASS,
+ "Mi posso magnare el vetro, no'l me fa mae."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ vi /* Vietnamese */,
+ GSPECI,
+ "Con sói nâu nhảy qua con chó lười."
+ )
+LANGUAGE(
+ wa /* Walloon */,
+ GLASS,
+ "Dji pou magnî do vêre, çoula m' freut nén må."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ yi /* Yiddish */,
+ GLASS,
+ "איך קען עסן גלאָז און עס טוט מיר נישט װײ."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ yo /* Yoruba */,
+ GLASS,
+ "Mo lè je̩ dígí, kò ní pa mí lára."
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ zh-cn /* Chinese Simplified */,
+ GLASS,
+ "我能吞下玻璃而不伤身体。"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ zh-mo /* Chinese Traditional */,
+ GLASS,
+ "我能吞下玻璃而不傷身體。"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ zh-sg /* Chinese Simplified */,
+ GLASS,
+ "我能吞下玻璃而不伤身体。"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ zh-tw /* Chinese Traditional */,
+ GLASS,
+ "我能吞下玻璃而不傷身體。"
+ /* I can eat glass and it doesn't hurt me. */
+ )
+LANGUAGE(
+ zlm /* Malay */,
+ GLASS,
+ "Saya boleh makan kaca dan ia tidak mencederakan saya."
+ /* I can eat glass and it doesn't hurt me. */
+ )
diff --git a/pango2/pango-language-set-private.h b/pango2/pango-language-set-private.h
new file mode 100644
index 00000000..53aac1cc
--- /dev/null
+++ b/pango2/pango-language-set-private.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-language.h"
+
+
+#define PANGO2_TYPE_LANGUAGE_SET (pango2_language_set_get_type ())
+
+G_DECLARE_DERIVABLE_TYPE (Pango2LanguageSet, pango2_language_set, PANGO2, LANGUAGE_SET, GObject)
+
+struct _Pango2LanguageSetClass
+{
+ GObjectClass parent_class;
+
+ gboolean (* matches_language) (Pango2LanguageSet *set,
+ Pango2Language *language);
+
+ Pango2Language ** (* get_languages) (Pango2LanguageSet *set);
+};
+
+gboolean pango2_language_set_matches_language (Pango2LanguageSet *set,
+ Pango2Language *language);
+
+Pango2Language ** pango2_language_set_get_languages (Pango2LanguageSet *set);
+
+char * pango2_language_set_to_string (Pango2LanguageSet *set);
diff --git a/pango2/pango-language-set-simple-private.h b/pango2/pango-language-set-simple-private.h
new file mode 100644
index 00000000..c473d01e
--- /dev/null
+++ b/pango2/pango-language-set-simple-private.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-language-set-private.h"
+
+
+#define PANGO2_TYPE_LANGUAGE_SET_SIMPLE (pango2_language_set_simple_get_type ())
+
+G_DECLARE_FINAL_TYPE (Pango2LanguageSetSimple, pango2_language_set_simple, PANGO2, LANGUAGE_SET_SIMPLE, Pango2LanguageSet)
+
+Pango2LanguageSetSimple *pango2_language_set_simple_new (void);
+
+void pango2_language_set_simple_add_language (Pango2LanguageSetSimple *self,
+ Pango2Language *language);
diff --git a/pango2/pango-language-set-simple.c b/pango2/pango-language-set-simple.c
new file mode 100644
index 00000000..d117a0c1
--- /dev/null
+++ b/pango2/pango-language-set-simple.c
@@ -0,0 +1,125 @@
+/* Pango2
+ *
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "pango-language-set-simple-private.h"
+
+struct _Pango2LanguageSetSimple
+{
+ Pango2LanguageSet parent_instance;
+
+ GHashTable *languages;
+ Pango2Language **list;
+};
+
+G_DEFINE_TYPE (Pango2LanguageSetSimple, pango2_language_set_simple, PANGO2_TYPE_LANGUAGE_SET)
+
+static void
+pango2_language_set_simple_init (Pango2LanguageSetSimple *self)
+{
+ self->languages = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+pango2_language_set_simple_finalize (GObject *object)
+{
+ Pango2LanguageSetSimple *self = PANGO2_LANGUAGE_SET_SIMPLE (object);
+
+ g_hash_table_unref (self->languages);
+ g_free (self->list);
+
+ G_OBJECT_CLASS (pango2_language_set_simple_parent_class)->finalize (object);
+}
+
+static gboolean
+pango2_language_set_simple_matches_language (Pango2LanguageSet *set,
+ Pango2Language *language)
+{
+ Pango2LanguageSetSimple *self = PANGO2_LANGUAGE_SET_SIMPLE (set);
+ const char *s;
+
+ if (g_hash_table_contains (self->languages, language))
+ return TRUE;
+
+ if (language == pango2_language_from_string ("c"))
+ return TRUE;
+
+ s = pango2_language_to_string (language);
+ if (strchr (s, '-'))
+ {
+ char buf[10];
+
+ for (int i = 0; i < 10; i++)
+ {
+ buf[i] = s[i];
+ if (buf[i] == '-')
+ {
+ buf[i] = '\0';
+ break;
+ }
+ }
+ buf[9] = '\0';
+
+ if (g_hash_table_contains (self->languages, pango2_language_from_string (buf)))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static Pango2Language **
+pango2_language_set_simple_get_languages (Pango2LanguageSet *set)
+{
+ Pango2LanguageSetSimple *self = PANGO2_LANGUAGE_SET_SIMPLE (set);
+
+ if (!self->list)
+ self->list = (Pango2Language **) g_hash_table_get_keys_as_array (self->languages, NULL);
+
+ return self->list;
+}
+
+static void
+pango2_language_set_simple_class_init (Pango2LanguageSetSimpleClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ Pango2LanguageSetClass *language_set_class = PANGO2_LANGUAGE_SET_CLASS (class);
+
+ object_class->finalize = pango2_language_set_simple_finalize;
+ language_set_class->matches_language = pango2_language_set_simple_matches_language;
+ language_set_class->get_languages = pango2_language_set_simple_get_languages;
+}
+
+Pango2LanguageSetSimple *
+pango2_language_set_simple_new (void)
+{
+ return g_object_new (PANGO2_TYPE_LANGUAGE_SET_SIMPLE, NULL);
+}
+
+void
+pango2_language_set_simple_add_language (Pango2LanguageSetSimple *self,
+ Pango2Language *language)
+{
+ g_return_if_fail (self->list == NULL);
+
+ g_hash_table_add (self->languages, language);
+}
diff --git a/pango2/pango-language-set.c b/pango2/pango-language-set.c
new file mode 100644
index 00000000..a96ba929
--- /dev/null
+++ b/pango2/pango-language-set.c
@@ -0,0 +1,87 @@
+/* Pango2
+ *
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "pango-language-set-private.h"
+
+
+G_DEFINE_ABSTRACT_TYPE (Pango2LanguageSet, pango2_language_set, G_TYPE_OBJECT)
+
+static void
+pango2_language_set_init (Pango2LanguageSet *set)
+{
+}
+
+static gboolean
+pango2_language_set_default_matches_language (Pango2LanguageSet *set,
+ Pango2Language *language)
+{
+ return FALSE;
+}
+
+static Pango2Language **
+pango2_language_set_default_get_languages (Pango2LanguageSet *set)
+{
+ return NULL;
+}
+
+static void
+pango2_language_set_class_init (Pango2LanguageSetClass *class)
+{
+ Pango2LanguageSetClass *language_set_class = PANGO2_LANGUAGE_SET_CLASS (class);
+
+ language_set_class->matches_language = pango2_language_set_default_matches_language;
+ language_set_class->get_languages = pango2_language_set_default_get_languages;
+}
+
+gboolean
+pango2_language_set_matches_language (Pango2LanguageSet *set,
+ Pango2Language *language)
+{
+ return PANGO2_LANGUAGE_SET_GET_CLASS (set)->matches_language (set, language);
+}
+
+Pango2Language **
+pango2_language_set_get_languages (Pango2LanguageSet *set)
+{
+ return PANGO2_LANGUAGE_SET_GET_CLASS (set)->get_languages (set);
+}
+
+char *
+pango2_language_set_to_string (Pango2LanguageSet *set)
+{
+ Pango2Language **languages;
+ GString *s;
+
+ s = g_string_new ("");
+
+ languages = pango2_language_set_get_languages (set);
+ for (int i = 0; languages[i]; i++)
+ {
+ if (s->len > 0)
+ g_string_append (s, "|");
+ g_string_append (s, pango2_language_to_string (languages[i]));
+ }
+
+ return g_string_free (s, FALSE);
+}
diff --git a/pango2/pango-language.c b/pango2/pango-language.c
new file mode 100644
index 00000000..71af5a83
--- /dev/null
+++ b/pango2/pango-language.c
@@ -0,0 +1,1029 @@
+/* Pango2
+ * pango-language.c: Language handling routines
+ *
+ * Copyright (C) 2000 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.
+ */
+
+#include "config.h"
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <locale.h>
+
+#include "pango-language.h"
+#include "pango-impl-utils.h"
+
+#ifdef HAVE_CORE_TEXT
+#include <CoreFoundation/CoreFoundation.h>
+#endif /* HAVE_CORE_TEXT */
+
+
+/* We embed a private struct right *before* a where a Pango2Language *
+ * points to.
+ */
+
+typedef struct {
+ gconstpointer lang_info;
+ gconstpointer script_for_lang;
+
+ int magic; /* Used for verification */
+} Pango2LanguagePrivate;
+
+#define PANGO2_LANGUAGE_PRIVATE_MAGIC 0x0BE4DAD0
+
+static void
+pango2_language_private_init (Pango2LanguagePrivate *priv)
+{
+ priv->magic = PANGO2_LANGUAGE_PRIVATE_MAGIC;
+
+ priv->lang_info = (gconstpointer) -1;
+ priv->script_for_lang = (gconstpointer) -1;
+}
+
+static Pango2LanguagePrivate * pango2_language_get_private (Pango2Language *language) G_GNUC_CONST;
+
+static Pango2LanguagePrivate *
+pango2_language_get_private (Pango2Language *language)
+{
+ Pango2LanguagePrivate *priv;
+
+ if (!language)
+ return NULL;
+
+ priv = (Pango2LanguagePrivate *)(void *)((char *)language - sizeof (Pango2LanguagePrivate));
+
+ if (G_UNLIKELY (priv->magic != PANGO2_LANGUAGE_PRIVATE_MAGIC))
+ {
+ g_critical ("Invalid Pango2Language. Did you pass in a straight string instead of calling pango2_language_from_string()?");
+ return NULL;
+ }
+
+ return priv;
+}
+
+
+
+#define LANGUAGE_SEPARATORS ";:, \t"
+
+static const char canon_map[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0,
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0,
+ '-', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, '-',
+ 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, 0
+};
+
+static gboolean
+lang_equal (gconstpointer v1,
+ gconstpointer v2)
+{
+ const guchar *p1 = v1;
+ const guchar *p2 = v2;
+
+ while (canon_map[*p1] && canon_map[*p1] == canon_map[*p2])
+ {
+ p1++, p2++;
+ }
+
+ return (canon_map[*p1] == canon_map[*p2]);
+}
+
+static guint
+lang_hash (gconstpointer key)
+{
+ const guchar *p = key;
+ guint h = 0;
+ while (canon_map[*p])
+ {
+ h = (h << 5) - h + canon_map[*p];
+ p++;
+ }
+
+ return h;
+}
+
+static Pango2Language *
+pango2_language_copy (Pango2Language *language)
+{
+ return language; /* language tags are const */
+}
+
+static void
+pango2_language_free (Pango2Language *language G_GNUC_UNUSED)
+{
+ return; /* nothing */
+}
+
+/**
+ * Pango2Language:
+ *
+ * The `Pango2Language` structure is used to
+ * represent a language.
+ *
+ * `Pango2Language` pointers can be efficiently
+ * copied and compared with each other.
+ */
+G_DEFINE_BOXED_TYPE (Pango2Language, pango2_language,
+ pango2_language_copy,
+ pango2_language_free);
+
+/**
+ * _pango2_get_lc_ctype:
+ *
+ * Return the Unix-style locale string for the language currently in
+ * effect. On Unix systems, this is the return value from
+ * `setlocale (LC_CTYPE, NULL)`, and the user can affect this through
+ * the environment variables LC_ALL, LC_CTYPE or LANG (checked
+ * in that order). The locale strings typically is in the form lang_COUNTRY,
+ * where lang is an ISO-639 language code, and COUNTRY is an ISO-3166 country
+ * code. For instance, sv_FI for Swedish as written in Finland or pt_BR for
+ * Portuguese as written in Brazil.
+ *
+ * On Windows, the C library doesn't use any such environment
+ * variables, and setting them won't affect the behavior of functions
+ * like ctime(). The user sets the locale through the Regional Options
+ * in the Control Panel. The C library (in the setlocale() function)
+ * does not use country and language codes, but country and language
+ * names spelled out in English.
+ * However, this function does check the above environment
+ * variables, and does return a Unix-style locale string based on
+ * either said environment variables or the thread's current locale.
+ *
+ * Return value: a dynamically allocated string, free with [GLib.free]
+ */
+static char *
+_pango2_get_lc_ctype (void)
+{
+#ifdef G_OS_WIN32
+ /* Somebody might try to set the locale for this process using the
+ * LANG or LC_ environment variables. The Microsoft C library
+ * doesn't know anything about them. You set the locale in the
+ * Control Panel. Setting these env vars won't have any affect on
+ * locale-dependent C library functions like ctime(). But just for
+ * kicks, do obey LC_ALL, LC_CTYPE and LANG in Pango2. (This also makes
+ * it easier to test GTK and Pango2 in various default languages, you
+ * don't have to clickety-click in the Control Panel, you can simply
+ * start the program with LC_ALL=something on the command line.)
+ */
+
+ char *p;
+
+ p = getenv ("LC_ALL");
+ if (p != NULL)
+ return g_strdup (p);
+
+ p = getenv ("LC_CTYPE");
+ if (p != NULL)
+ return g_strdup (p);
+
+ p = getenv ("LANG");
+ if (p != NULL)
+ return g_strdup (p);
+
+ return g_win32_getlocale ();
+#elif defined(HAVE_CORE_TEXT)
+ CFArrayRef languages;
+ CFStringRef language;
+ char ret[16];
+ char *p;
+
+ /* Take the same approach as done for Windows above. First we check
+ * if somebody tried to set the locale through environment variables.
+ */
+ p = getenv ("LC_ALL");
+ if (p != NULL)
+ return g_strdup (p);
+
+ p = getenv ("LC_CTYPE");
+ if (p != NULL)
+ return g_strdup (p);
+
+ p = getenv ("LANG");
+ if (p != NULL)
+ return g_strdup (p);
+
+ /* If the environment variables are not set, determine the locale
+ * through the platform-native API.
+ */
+ languages = CFLocaleCopyPreferredLanguages ();
+ language = CFArrayGetValueAtIndex (languages, 0);
+
+ if (!CFStringGetCString (language, ret, 16, kCFStringEncodingUTF8))
+ {
+ CFRelease (languages);
+ return g_strdup (setlocale (LC_CTYPE, NULL));
+ }
+
+ CFRelease (languages);
+
+ return g_strdup (ret);
+#else
+ {
+ char *lc_ctype = setlocale (LC_CTYPE, NULL);
+
+ if (lc_ctype)
+ return g_strdup (lc_ctype);
+ else
+ return g_strdup ("C");
+ }
+#endif
+}
+
+/**
+ * pango2_language_get_default:
+ *
+ * Returns the `Pango2Language` for the current locale of the process.
+ *
+ * On Unix systems, this is the return value is derived from
+ * `setlocale (LC_CTYPE, NULL)`, and the user can
+ * affect this through the environment variables LC_ALL, LC_CTYPE or
+ * LANG (checked in that order). The locale string typically is in
+ * the form lang_COUNTRY, where lang is an ISO-639 language code, and
+ * COUNTRY is an ISO-3166 country code. For instance, sv_FI for
+ * Swedish as written in Finland or pt_BR for Portuguese as written in
+ * Brazil.
+ *
+ * On Windows, the C library does not use any such environment
+ * variables, and setting them won't affect the behavior of functions
+ * like ctime(). The user sets the locale through the Regional Options
+ * in the Control Panel. The C library (in the setlocale() function)
+ * does not use country and language codes, but country and language
+ * names spelled out in English.
+ * However, this function does check the above environment
+ * variables, and does return a Unix-style locale string based on
+ * either said environment variables or the thread's current locale.
+ *
+ * Your application should call `setlocale(LC_ALL, "")` for the user
+ * settings to take effect. GTK does this in its initialization
+ * functions automatically (by calling gtk_set_locale()).
+ * See the setlocale() manpage for more details.
+ *
+ * Note that the default language can change over the life of an application.
+ *
+ * Also note that this function will not do the right thing if you
+ * use per-thread locales with uselocale(). In that case, you should
+ * just call [func@Pango2.Language.from_string] yourself.
+ *
+ * Return value: (transfer none): the default language as a `Pango2Language`
+ **/
+Pango2Language *
+pango2_language_get_default (void)
+{
+ static Pango2Language *result = NULL; /* MT-safe */
+
+ if (g_once_init_enter (&result))
+ {
+ char *lc_ctype;
+ Pango2Language *lang;
+
+ lc_ctype = _pango2_get_lc_ctype ();
+ lang = pango2_language_from_string (lc_ctype);
+ g_free (lc_ctype);
+
+ g_once_init_leave (&result, lang);
+ }
+
+ return result;
+}
+
+/**
+ * pango2_language_from_string:
+ * @language: (nullable): a string representing a language tag
+ *
+ * Convert a language tag to a `Pango2Language`.
+ *
+ * The language tag must be in a RFC-3066 format. `Pango2Language` pointers
+ * can be efficiently copied (copy the pointer) and compared with other
+ * language tags (compare the pointer.)
+ *
+ * This function first canonicalizes the string by converting it to
+ * lowercase, mapping '_' to '-', and stripping all characters other
+ * than letters and '-'.
+ *
+ * Use [func@Pango2.Language.get_default] if you want to get the
+ * `Pango2Language` for the current locale of the process.
+ *
+ * Return value: (transfer none) (nullable): a `Pango2Language`
+ */
+Pango2Language *
+pango2_language_from_string (const char *language)
+{
+ G_LOCK_DEFINE_STATIC (lang_from_string);
+ static GHashTable *hash = NULL; /* MT-safe */
+ Pango2LanguagePrivate *priv;
+ char *result;
+ int len;
+ char *p;
+
+ if (language == NULL)
+ return NULL;
+
+ G_LOCK (lang_from_string);
+
+ if (G_UNLIKELY (!hash))
+ hash = g_hash_table_new (lang_hash, lang_equal);
+ else
+ {
+ result = g_hash_table_lookup (hash, language);
+ if (result)
+ goto out;
+ }
+
+ len = strlen (language);
+ priv = g_malloc0 (sizeof (Pango2LanguagePrivate) + len + 1);
+ g_assert (priv);
+
+ result = (char *)priv;
+ result += sizeof (Pango2LanguagePrivate);
+
+ pango2_language_private_init (priv);
+
+ p = result;
+ while ((*(p++) = canon_map[*(guchar *)language++]))
+ ;
+
+ g_hash_table_insert (hash, result, result);
+
+out:
+ G_UNLOCK (lang_from_string);
+
+ return (Pango2Language *)result;
+}
+
+/**
+ * pango2_language_to_string:
+ * @language: a language tag.
+ *
+ * Gets the RFC-3066 format string representing the given language tag.
+ *
+ * Returns (transfer none): a string representing the language tag
+ */
+const char *
+(pango2_language_to_string) (Pango2Language *language)
+{
+ return pango2_language_to_string (language);
+}
+
+/**
+ * pango2_language_matches:
+ * @language: (nullable): a language tag (see [func@Pango2.Language.from_string]),
+ * %NULL is allowed and matches nothing but '*'
+ * @range_list: a list of language ranges, separated by ';', ':',
+ * ',', or space characters.
+ * Each element must either be '*', or a RFC 3066 language range
+ * canonicalized as by [func@Pango2.Language.from_string]
+ *
+ * Checks if a language tag matches one of the elements in a list of
+ * language ranges.
+ *
+ * A language tag is considered to match a range in the list if the
+ * range is '*', the range is exactly the tag, or the range is a prefix
+ * of the tag, and the character after it in the tag is '-'.
+ *
+ * Return value: %TRUE if a match was found
+ */
+gboolean
+pango2_language_matches (Pango2Language *language,
+ const char *range_list)
+{
+ const char *lang_str = pango2_language_to_string (language);
+ const char *p = range_list;
+ gboolean done = FALSE;
+
+ while (!done)
+ {
+ const char *end = strpbrk (p, LANGUAGE_SEPARATORS);
+ if (!end)
+ {
+ end = p + strlen (p);
+ done = TRUE;
+ }
+
+ if (strncmp (p, "*", 1) == 0 ||
+ (lang_str && strncmp (lang_str, p, end - p) == 0 &&
+ (lang_str[end - p] == '\0' || lang_str[end - p] == '-')))
+ return TRUE;
+
+ if (!done)
+ p = end + 1;
+ }
+
+ return FALSE;
+}
+
+static int
+lang_compare_first_component (gconstpointer pa,
+ gconstpointer pb)
+{
+ const char *a = pa, *b = pb;
+ unsigned int da, db;
+ const char *p;
+
+ p = strstr (a, "-");
+ da = p ? (unsigned int) (p - a) : strlen (a);
+
+ p = strstr (b, "-");
+ db = p ? (unsigned int) (p - b) : strlen (b);
+
+ return strncmp (a, b, MAX (da, db));
+}
+
+/* Finds the best record for @language in an array of records.
+ * Each record should start with the string representation of the language
+ * code for the record (embedded, not a pointer), and the records must be
+ * sorted on language code.
+ */
+static gconstpointer
+find_best_lang_match (Pango2Language *language,
+ gconstpointer records,
+ guint num_records,
+ guint record_size)
+{
+ const char *lang_str;
+ const char *record, *start, *end;
+
+ if (language == NULL)
+ return NULL;
+
+ lang_str = pango2_language_to_string (language);
+
+ record = bsearch (lang_str,
+ records, num_records, record_size,
+ lang_compare_first_component);
+ if (!record)
+ return NULL;
+
+ start = (const char *) records;
+ end = start + num_records * record_size;
+
+ /* find the best match among all those that have the same first-component */
+
+ /* go to the final one matching in the first component */
+ while (record < end - record_size &&
+ lang_compare_first_component (lang_str, record + record_size) == 0)
+ record += record_size;
+
+ /* go back, find which one matches completely */
+ while (start <= record &&
+ lang_compare_first_component (lang_str, record) == 0)
+ {
+ if (pango2_language_matches (language, record))
+ return record;
+
+ record -= record_size;
+ }
+
+ return NULL;
+}
+
+static gconstpointer
+find_best_lang_match_cached (Pango2Language *language,
+ gconstpointer *cache,
+ gconstpointer records,
+ guint num_records,
+ guint record_size)
+{
+ gconstpointer result;
+
+ if (G_LIKELY (cache && *cache != (gconstpointer) -1))
+ return *cache;
+
+ result = find_best_lang_match (language,
+ records,
+ num_records,
+ record_size);
+
+ if (cache)
+ *cache = result;
+
+ return result;
+}
+
+#define FIND_BEST_LANG_MATCH_CACHED(language, cache_key, records) \
+ find_best_lang_match_cached ((language), \
+ pango2_language_get_private (language) ? \
+ &(pango2_language_get_private (language)->cache_key) : NULL, \
+ records, \
+ G_N_ELEMENTS (records), \
+ sizeof (*records));
+
+typedef struct {
+ char lang[6];
+ guint16 offset;
+} LangInfo;
+
+/* Pure black magic, based on appendix of dsohowto.pdf */
+#define POOLSTRFIELD(line) POOLSTRFIELD1(line)
+#define POOLSTRFIELD1(line) str##line
+struct _LangPoolStruct {
+ char str0[1];
+#define LANGUAGE(id, source, sample) char POOLSTRFIELD(__LINE__)[sizeof(sample)];
+#include "pango-language-sample-table.h"
+#undef LANGUAGE
+};
+
+static const union _LangPool {
+ struct _LangPoolStruct lang_pool_struct;
+ const char str[1];
+} lang_pool = { {
+ "",
+#define LANGUAGE(id, source, sample) sample,
+#include "pango-language-sample-table.h"
+#undef LANGUAGE
+} };
+static const LangInfo lang_texts[] = {
+#define LANGUAGE(id, source, sample) {G_STRINGIFY(id), G_STRUCT_OFFSET(struct _LangPoolStruct, POOLSTRFIELD(__LINE__))},
+#include "pango-language-sample-table.h"
+#undef LANGUAGE
+ /* One extra entry with no final comma, to make it C89-happy */
+ {"~~", 0}
+};
+
+/**
+ * pango2_language_get_sample_string:
+ * @language: (nullable): a `Pango2Language`
+ *
+ * Get a string that is representative of the characters needed to
+ * render a particular language.
+ *
+ * The sample text may be a pangram, but is not necessarily. It is chosen
+ * to be demonstrative of normal text in the language, as well as exposing
+ * font feature requirements unique to the language. It is suitable for use
+ * as sample text in a font selection dialog.
+ *
+ * If @language is %NULL, the default language as found by
+ * [func@Pango2.Language.get_default] is used.
+ *
+ * If Pango2 does not have a sample string for @language, the classic
+ * "The quick brown fox..." is returned. This can be detected by
+ * comparing the returned pointer value to that returned for (non-existent)
+ * language code "xx". That is, compare to:
+ *
+ * ```
+ * pango2_language_get_sample_string (pango2_language_from_string ("xx"))
+ * ```
+ *
+ * Return value: (transfer none): the sample string
+ */
+const char *
+pango2_language_get_sample_string (Pango2Language *language)
+{
+ const LangInfo *lang_info;
+
+ if (!language)
+ language = pango2_language_get_default ();
+
+ lang_info = FIND_BEST_LANG_MATCH_CACHED (language, lang_info, lang_texts);
+
+ if (lang_info)
+ return lang_pool.str + lang_info->offset;
+
+ return "The quick brown fox jumps over the lazy dog.";
+}
+
+
+
+
+/*
+ * From language to script
+ */
+
+
+#include "pango-script-lang-table.h"
+
+/**
+ * pango2_language_get_scripts:
+ * @language: (nullable): a `Pango2Language`
+ * @num_scripts: (out caller-allocates) (optional): location to
+ * return number of scripts
+ *
+ * Determines the scripts used to to write @language.
+ *
+ * If nothing is known about the language tag @language,
+ * or if @language is %NULL, then %NULL is returned.
+ * The list of scripts returned starts with the script that the
+ * language uses most and continues to the one it uses least.
+ *
+ * The value @num_script points at will be set to the number
+ * of scripts in the returned array (or zero if %NULL is returned).
+ *
+ * Most languages use only one script for writing, but there are
+ * some that use two (Latin and Cyrillic for example), and a few
+ * use three (Japanese for example). Applications should not make
+ * any assumptions on the maximum number of scripts returned
+ * though, except that it is positive if the return value is not
+ * %NULL, and it is a small number.
+ *
+ * The [method@Pango2.Language.includes_script] function uses this
+ * function internally.
+ *
+ * Return value: (transfer none) (array length=num_scripts) (nullable):
+ * An array of `GUnicodeScript` values, with the number of entries in
+ * the array stored in @num_scripts, or %NULL if Pango2 does not have
+ * any information about this particular language tag (also the case
+ * if @language is %NULL).
+ */
+const GUnicodeScript *
+pango2_language_get_scripts (Pango2Language *language,
+ int *num_scripts)
+{
+ const Pango2ScriptForLang *script_for_lang;
+ unsigned int j;
+
+ script_for_lang = FIND_BEST_LANG_MATCH_CACHED (language,
+ script_for_lang,
+ pango2_script_for_lang);
+
+ if (!script_for_lang || script_for_lang->scripts[0] == 0)
+ {
+ if (num_scripts)
+ *num_scripts = 0;
+
+ return NULL;
+ }
+
+ if (num_scripts)
+ {
+ for (j = 0; j < G_N_ELEMENTS (script_for_lang->scripts); j++)
+ if (script_for_lang->scripts[j] == 0)
+ break;
+
+ g_assert (j > 0);
+
+ *num_scripts = j;
+ }
+
+ return (const GUnicodeScript *) script_for_lang->scripts;
+}
+
+/**
+ * pango2_language_includes_script:
+ * @language: (nullable): a `Pango2Language`
+ * @script: a `GUnicodeScript`
+ *
+ * Determines if @script is one of the scripts used to
+ * write @language.
+ *
+ * The returned value is conservative; if nothing is known about
+ * the language tag @language, %TRUE will be returned, since, as
+ * far as Pango2 knows, @script might be used to write @language.
+ *
+ * This routine is used in Pango2's itemization process when
+ * determining if a supplied language tag is relevant to
+ * a particular section of text. It probably is not useful
+ * for applications in most circumstances.
+ *
+ * This function uses [method@Pango2.Language.get_scripts] internally.
+ *
+ * Return value: %TRUE if @script is one of the scripts used
+ * to write @language or if nothing is known about @language
+ * (including the case that @language is %NULL), %FALSE otherwise.
+ */
+gboolean
+pango2_language_includes_script (Pango2Language *language,
+ GUnicodeScript script)
+{
+ const GUnicodeScript *scripts;
+ int num_scripts, j;
+
+/* copied from the one in pango-script.c */
+#define REAL_SCRIPT(script) \
+ ((script) > G_UNICODE_SCRIPT_INHERITED && (script) != G_UNICODE_SCRIPT_UNKNOWN)
+
+ if (!REAL_SCRIPT (script))
+ return TRUE;
+
+#undef REAL_SCRIPT
+
+ scripts = pango2_language_get_scripts (language, &num_scripts);
+ if (!scripts)
+ return TRUE;
+
+ for (j = 0; j < num_scripts; j++)
+ if (scripts[j] == script)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+
+
+/*
+ * From script to language
+ */
+
+
+static Pango2Language **
+parse_default_languages (void)
+{
+ char *p, *p_copy;
+ gboolean done = FALSE;
+ GPtrArray *langs;
+
+ p = getenv ("PANGO2_LANGUAGE");
+
+ if (p == NULL)
+ p = getenv ("LANGUAGE");
+
+ if (p == NULL)
+ return NULL;
+
+ p_copy = p = g_strdup (p);
+
+ langs = g_ptr_array_new ();
+
+ while (!done)
+ {
+ char *end = strpbrk (p, LANGUAGE_SEPARATORS);
+ if (!end)
+ {
+ end = p + strlen (p);
+ done = TRUE;
+ }
+ else
+ *end = '\0';
+
+ /* skip empty languages, and skip the language 'C' */
+ if (p != end && !(p + 1 == end && *p == 'C'))
+ {
+ Pango2Language *l = pango2_language_from_string (p);
+
+ g_ptr_array_add (langs, l);
+ }
+
+ if (!done)
+ p = end + 1;
+ }
+
+ g_ptr_array_add (langs, NULL);
+
+ g_free (p_copy);
+
+ return (Pango2Language **) g_ptr_array_free (langs, FALSE);
+}
+
+G_LOCK_DEFINE_STATIC (languages);
+static gboolean initialized = FALSE; /* MT-safe */
+static Pango2Language * const * languages = NULL; /* MT-safe */
+static GHashTable *hash = NULL; /* MT-safe */
+
+static Pango2Language *
+_pango2_script_get_default_language (GUnicodeScript script)
+{
+ Pango2Language *result, * const * p;
+
+ G_LOCK (languages);
+
+ if (G_UNLIKELY (!initialized))
+ {
+ languages = parse_default_languages ();
+
+ if (languages)
+ hash = g_hash_table_new (NULL, NULL);
+
+ initialized = TRUE;
+ }
+
+ if (!languages)
+ {
+ result = NULL;
+ goto out;
+ }
+
+ if (g_hash_table_lookup_extended (hash, GINT_TO_POINTER (script), NULL, (gpointer *) (gpointer) &result))
+ goto out;
+
+ for (p = languages; *p; p++)
+ if (pango2_language_includes_script (*p, script))
+ break;
+ result = *p;
+
+ g_hash_table_insert (hash, GINT_TO_POINTER (script), result);
+
+out:
+ G_UNLOCK (languages);
+
+ return result;
+}
+
+/**
+ * pango2_language_get_preferred:
+ *
+ * Returns the list of languages that the user prefers.
+ *
+ * The list is specified by the `PANGO2_LANGUAGE` or `LANGUAGE`
+ * environment variables, in order of preference. Note that this
+ * list does not necessarily include the language returned by
+ * [func@Pango2.Language.get_default].
+ *
+ * When choosing language-specific resources, such as the sample
+ * text returned by [method@Pango2.Language.get_sample_string],
+ * you should first try the default language, followed by the
+ * languages returned by this function.
+ *
+ * Returns: (transfer none) (nullable): a %NULL-terminated array
+ * of `Pango2Language`*
+ */
+Pango2Language **
+pango2_language_get_preferred (void)
+{
+ /* We call this just for its side-effect of initializing languages */
+ _pango2_script_get_default_language (G_UNICODE_SCRIPT_COMMON);
+
+ return (Pango2Language **) languages;
+}
+
+/**
+ * pango2_script_get_sample_language:
+ * @script: a `GUnicodeScript`
+ *
+ * Finds a language tag that is reasonably representative of @script.
+ *
+ * The language will usually be the most widely spoken or used language
+ * written in that script: for instance, the sample language for
+ * %G_UNICODE_SCRIPT_CYRILLIC is ru (Russian), the sample language for
+ * %G_UNICODE_SCRIPT_ARABIC is ar.
+ *
+ * For some scripts, no sample language will be returned because
+ * there is no language that is sufficiently representative. The
+ * best example of this is %G_UNICODE_SCRIPT_HAN, where various different
+ * variants of written Chinese, Japanese, and Korean all use
+ * significantly different sets of Han characters and forms
+ * of shared characters. No sample language can be provided
+ * for many historical scripts as well.
+ *
+ * As of 1.18, this function checks the environment variables
+ * `PANGO2_LANGUAGE` and `LANGUAGE` (checked in that order) first.
+ * If one of them is set, it is parsed as a list of language tags
+ * separated by colons or other separators. This function
+ * will return the first language in the parsed list that Pango2
+ * believes may use @script for writing. This last predicate
+ * is tested using [method@Pango2.Language.includes_script]. This can
+ * be used to control Pango2's font selection for non-primary
+ * languages. For example, a `PANGO2_LANGUAGE` enviroment variable
+ * set to "en:fa" makes Pango2 choose fonts suitable for Persian (fa)
+ * instead of Arabic (ar) when a segment of Arabic text is found
+ * in an otherwise non-Arabic text. The same trick can be used to
+ * choose a default language for %G_UNICODE_SCRIPT_HAN when setting
+ * context language is not feasible.
+ *
+ * Return value: (nullable): a `Pango2Language` that is representative
+ * of the script
+ */
+Pango2Language *
+pango2_script_get_sample_language (GUnicodeScript script)
+{
+ /* Note that in the following, we want
+ * pango2_language_includes_script() for the sample language
+ * to include the script, so alternate orthographies
+ * (Shavian for English, Osmanya for Somali, etc), typically
+ * have no sample language
+ */
+ static const char sample_languages[][4] = {
+ "", /* G_UNICODE_SCRIPT_COMMON */
+ "", /* G_UNICODE_SCRIPT_INHERITED */
+ "ar", /* G_UNICODE_SCRIPT_ARABIC */
+ "hy", /* G_UNICODE_SCRIPT_ARMENIAN */
+ "bn", /* G_UNICODE_SCRIPT_BENGALI */
+ /* Used primarily in Taiwan, but not part of the standard
+ * zh-tw orthography */
+ "", /* G_UNICODE_SCRIPT_BOPOMOFO */
+ "chr", /* G_UNICODE_SCRIPT_CHEROKEE */
+ "cop", /* G_UNICODE_SCRIPT_COPTIC */
+ "ru", /* G_UNICODE_SCRIPT_CYRILLIC */
+ /* Deseret was used to write English */
+ "", /* G_UNICODE_SCRIPT_DESERET */
+ "hi", /* G_UNICODE_SCRIPT_DEVANAGARI */
+ "am", /* G_UNICODE_SCRIPT_ETHIOPIC */
+ "ka", /* G_UNICODE_SCRIPT_GEORGIAN */
+ "", /* G_UNICODE_SCRIPT_GOTHIC */
+ "el", /* G_UNICODE_SCRIPT_GREEK */
+ "gu", /* G_UNICODE_SCRIPT_GUJARATI */
+ "pa", /* G_UNICODE_SCRIPT_GURMUKHI */
+ "", /* G_UNICODE_SCRIPT_HAN */
+ "ko", /* G_UNICODE_SCRIPT_HANGUL */
+ "he", /* G_UNICODE_SCRIPT_HEBREW */
+ "ja", /* G_UNICODE_SCRIPT_HIRAGANA */
+ "kn", /* G_UNICODE_SCRIPT_KANNADA */
+ "ja", /* G_UNICODE_SCRIPT_KATAKANA */
+ "km", /* G_UNICODE_SCRIPT_KHMER */
+ "lo", /* G_UNICODE_SCRIPT_LAO */
+ "en", /* G_UNICODE_SCRIPT_LATIN */
+ "ml", /* G_UNICODE_SCRIPT_MALAYALAM */
+ "mn", /* G_UNICODE_SCRIPT_MONGOLIAN */
+ "my", /* G_UNICODE_SCRIPT_MYANMAR */
+ /* Ogham was used to write old Irish */
+ "", /* G_UNICODE_SCRIPT_OGHAM */
+ "", /* G_UNICODE_SCRIPT_OLD_ITALIC */
+ "or", /* G_UNICODE_SCRIPT_ORIYA */
+ "", /* G_UNICODE_SCRIPT_RUNIC */
+ "si", /* G_UNICODE_SCRIPT_SINHALA */
+ "syr", /* G_UNICODE_SCRIPT_SYRIAC */
+ "ta", /* G_UNICODE_SCRIPT_TAMIL */
+ "te", /* G_UNICODE_SCRIPT_TELUGU */
+ "dv", /* G_UNICODE_SCRIPT_THAANA */
+ "th", /* G_UNICODE_SCRIPT_THAI */
+ "bo", /* G_UNICODE_SCRIPT_TIBETAN */
+ "iu", /* G_UNICODE_SCRIPT_CANADIAN_ABORIGINAL */
+ "", /* G_UNICODE_SCRIPT_YI */
+ "tl", /* G_UNICODE_SCRIPT_TAGALOG */
+ /* Phillipino languages/scripts */
+ "hnn", /* G_UNICODE_SCRIPT_HANUNOO */
+ "bku", /* G_UNICODE_SCRIPT_BUHID */
+ "tbw", /* G_UNICODE_SCRIPT_TAGBANWA */
+
+ "", /* G_UNICODE_SCRIPT_BRAILLE */
+ "", /* G_UNICODE_SCRIPT_CYPRIOT */
+ "", /* G_UNICODE_SCRIPT_LIMBU */
+ /* Used for Somali (so) in the past */
+ "", /* G_UNICODE_SCRIPT_OSMANYA */
+ /* The Shavian alphabet was designed for English */
+ "", /* G_UNICODE_SCRIPT_SHAVIAN */
+ "", /* G_UNICODE_SCRIPT_LINEAR_B */
+ "", /* G_UNICODE_SCRIPT_TAI_LE */
+ "uga", /* G_UNICODE_SCRIPT_UGARITIC */
+
+ "", /* G_UNICODE_SCRIPT_NEW_TAI_LUE */
+ "bug", /* G_UNICODE_SCRIPT_BUGINESE */
+ /* The original script for Old Church Slavonic (chu), later
+ * written with Cyrillic */
+ "", /* G_UNICODE_SCRIPT_GLAGOLITIC */
+ /* Used for for Berber (ber), but Arabic script is more common */
+ "", /* G_UNICODE_SCRIPT_TIFINAGH */
+ "syl", /* G_UNICODE_SCRIPT_SYLOTI_NAGRI */
+ "peo", /* G_UNICODE_SCRIPT_OLD_PERSIAN */
+ "", /* G_UNICODE_SCRIPT_KHAROSHTHI */
+
+ "", /* G_UNICODE_SCRIPT_UNKNOWN */
+ "", /* G_UNICODE_SCRIPT_BALINESE */
+ "", /* G_UNICODE_SCRIPT_CUNEIFORM */
+ "", /* G_UNICODE_SCRIPT_PHOENICIAN */
+ "", /* G_UNICODE_SCRIPT_PHAGS_PA */
+ "nqo", /* G_UNICODE_SCRIPT_NKO */
+
+ /* Unicode-5.1 additions */
+ "", /* G_UNICODE_SCRIPT_KAYAH_LI */
+ "", /* G_UNICODE_SCRIPT_LEPCHA */
+ "", /* G_UNICODE_SCRIPT_REJANG */
+ "", /* G_UNICODE_SCRIPT_SUNDANESE */
+ "", /* G_UNICODE_SCRIPT_SAURASHTRA */
+ "", /* G_UNICODE_SCRIPT_CHAM */
+ "", /* G_UNICODE_SCRIPT_OL_CHIKI */
+ "", /* G_UNICODE_SCRIPT_VAI */
+ "", /* G_UNICODE_SCRIPT_CARIAN */
+ "", /* G_UNICODE_SCRIPT_LYCIAN */
+ "", /* G_UNICODE_SCRIPT_LYDIAN */
+
+ /* Unicode-6.0 additions */
+ "", /* G_UNICODE_SCRIPT_BATAK */
+ "", /* G_UNICODE_SCRIPT_BRAHMI */
+ "", /* G_UNICODE_SCRIPT_MANDAIC */
+
+ /* Unicode-6.1 additions */
+ "", /* G_UNICODE_SCRIPT_CHAKMA */
+ "", /* G_UNICODE_SCRIPT_MEROITIC_CURSIVE */
+ "", /* G_UNICODE_SCRIPT_MEROITIC_HIEROGLYPHS */
+ "", /* G_UNICODE_SCRIPT_MIAO */
+ "", /* G_UNICODE_SCRIPT_SHARADA */
+ "", /* G_UNICODE_SCRIPT_SORA_SOMPENG */
+ "", /* G_UNICODE_SCRIPT_TAKRI */
+ };
+ const char *sample_language;
+ Pango2Language *result;
+
+ g_return_val_if_fail (script >= 0, NULL);
+
+ if ((guint)script >= G_N_ELEMENTS (sample_languages))
+ return NULL;
+
+ result = _pango2_script_get_default_language (script);
+ if (result)
+ return result;
+
+ sample_language = sample_languages[script];
+
+ if (!sample_language[0])
+ return NULL;
+ else
+ return pango2_language_from_string (sample_language);
+}
diff --git a/pango2/pango-language.h b/pango2/pango-language.h
new file mode 100644
index 00000000..f4484fd9
--- /dev/null
+++ b/pango2/pango-language.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 1999 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-version-macros.h>
+#include <pango2/pango-script.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_LANGUAGE (pango2_language_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_language_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Language * pango2_language_get_default (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Language ** pango2_language_get_preferred (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Language * pango2_language_from_string (const char *language);
+
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_language_to_string (Pango2Language *language) G_GNUC_CONST;
+
+/* For back compat. Will have to keep indefinitely. */
+#define pango2_language_to_string(language) ((const char *)language)
+
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_language_get_sample_string (Pango2Language *language) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_language_matches (Pango2Language *language,
+ const char *range_list) G_GNUC_PURE;
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_language_includes_script (Pango2Language *language,
+ GUnicodeScript script) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+const GUnicodeScript * pango2_language_get_scripts (Pango2Language *language,
+ int *num_scripts);
+
+G_END_DECLS
diff --git a/pango2/pango-layout.c b/pango2/pango-layout.c
new file mode 100644
index 00000000..9a01b490
--- /dev/null
+++ b/pango2/pango-layout.c
@@ -0,0 +1,1734 @@
+#include "config.h"
+
+#include "pango-layout.h"
+#include "pango-line-breaker.h"
+#include "pango-line-private.h"
+#include "pango-enum-types.h"
+#include "pango-markup.h"
+#include "pango-context.h"
+
+/**
+ * Pango2Layout:
+ *
+ * A `Pango2Layout` structure represents an entire paragraph of text.
+ *
+ * While complete access to the layout capabilities of Pango2 is provided
+ * using the detailed interfaces for itemization, segmentation and shaping,
+ * using that functionality directly involves writing a fairly large amount
+ * of code. `Pango2Layout` 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 `Pango2Layout` is initialized with a `Pango2Context`, a UTF-8 string
+ * and set of attributes for that string. Once that is done, the set of
+ * formatted lines can be extracted in the form of a [class@Pango2.Lines]
+ * 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.
+ *
+ * The most convenient way to access the visual extents and components
+ * of a formatted layout is via a [struct@Pango2.LineIter] iterator.
+ *
+ * There are a number of parameters to adjust the formatting of a
+ * `Pango2Layout`. 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="Pango2 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=center](align-center.png) | ![align=right](align-right.png) |
+ * | ![align=justify](align-left-justify.png) | | |
+ *
+ * It is possible, as well, to ignore the 2-D setup, and simply treat the
+ * resulting `Pango2Lines` object as a list of lines.
+ *
+ * If you have more complex line breaking needs, such as shaping text
+ * to flow around images, or multi-column layout, the [class@Pango2.LineBreaker]
+ * makes the underlying line-breaking functionality available outside of
+ * `Pango2Layout`.
+ */
+
+
+/* {{{ Pango2Layout implementation */
+
+struct _Pango2Layout
+{
+ GObject parent_instance;
+
+ Pango2Context *context;
+ char *text;
+ int length;
+ Pango2AttrList *attrs;
+ Pango2FontDescription *font_desc;
+ float line_height;
+ int spacing;
+ int width;
+ int height;
+ Pango2TabArray *tabs;
+ gboolean single_paragraph;
+ Pango2WrapMode wrap;
+ int indent;
+ guint serial;
+ guint context_serial;
+ Pango2Alignment alignment;
+ Pango2EllipsizeMode ellipsize;
+ gboolean auto_dir;
+
+ Pango2Lines *lines;
+};
+
+struct _Pango2LayoutClass
+{
+ GObjectClass parent_class;
+};
+
+enum
+{
+ PROP_CONTEXT = 1,
+ PROP_TEXT,
+ PROP_ATTRIBUTES,
+ PROP_FONT_DESCRIPTION,
+ PROP_LINE_HEIGHT,
+ PROP_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 GParamSpec *props[NUM_PROPERTIES] = { NULL, };
+
+G_DEFINE_FINAL_TYPE (Pango2Layout, pango2_layout, G_TYPE_OBJECT)
+
+static void
+pango2_layout_init (Pango2Layout *layout)
+{
+ layout->serial = 1;
+ layout->width = -1;
+ layout->height = -1;
+ layout->indent = 0;
+ layout->wrap = PANGO2_WRAP_WORD;
+ layout->alignment = PANGO2_ALIGN_NATURAL;
+ layout->ellipsize = PANGO2_ELLIPSIZE_NONE;
+ layout->line_height = 0.0;
+ layout->spacing = 0;
+ layout->auto_dir = TRUE;
+ layout->text = g_strdup ("");
+ layout->length = 0;
+}
+
+static void
+pango2_layout_finalize (GObject *object)
+{
+ Pango2Layout *layout = PANGO2_LAYOUT (object);
+
+ g_clear_pointer (&layout->font_desc, pango2_font_description_free);
+ g_object_unref (layout->context);
+ g_free (layout->text);
+ g_clear_pointer (&layout->attrs, pango2_attr_list_unref);
+ g_clear_pointer (&layout->tabs, pango2_tab_array_free);
+ g_clear_object (&layout->lines);
+
+ G_OBJECT_CLASS (pango2_layout_parent_class)->finalize (object);
+}
+
+static void
+pango2_layout_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2Layout *layout = PANGO2_LAYOUT (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ layout->context = g_value_dup_object (value);
+ layout->context_serial = pango2_context_get_serial (layout->context);
+ break;
+
+ case PROP_TEXT:
+ pango2_layout_set_text (layout, g_value_get_string (value), -1);
+ break;
+
+ case PROP_ATTRIBUTES:
+ pango2_layout_set_attributes (layout, g_value_get_boxed (value));
+ break;
+
+ case PROP_FONT_DESCRIPTION:
+ pango2_layout_set_font_description (layout, g_value_get_boxed (value));
+ break;
+
+ case PROP_LINE_HEIGHT:
+ pango2_layout_set_line_height (layout, g_value_get_float (value));
+ break;
+
+ case PROP_SPACING:
+ pango2_layout_set_spacing (layout, g_value_get_int (value));
+ break;
+
+ case PROP_WIDTH:
+ pango2_layout_set_width (layout, g_value_get_int (value));
+ break;
+
+ case PROP_HEIGHT:
+ pango2_layout_set_height (layout, g_value_get_int (value));
+ break;
+
+ case PROP_TABS:
+ pango2_layout_set_tabs (layout, g_value_get_boxed (value));
+ break;
+
+ case PROP_SINGLE_PARAGRAPH:
+ pango2_layout_set_single_paragraph (layout, g_value_get_boolean (value));
+ break;
+
+ case PROP_WRAP:
+ pango2_layout_set_wrap (layout, g_value_get_enum (value));
+ break;
+
+ case PROP_INDENT:
+ pango2_layout_set_indent (layout, g_value_get_int (value));
+ break;
+
+ case PROP_ALIGNMENT:
+ pango2_layout_set_alignment (layout, g_value_get_enum (value));
+ break;
+
+ case PROP_ELLIPSIZE:
+ pango2_layout_set_ellipsize (layout, g_value_get_enum (value));
+ break;
+
+ case PROP_AUTO_DIR:
+ pango2_layout_set_auto_dir (layout, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pango2_layout_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2Layout *layout = PANGO2_LAYOUT (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ g_value_set_object (value, layout->context);
+ break;
+
+ case PROP_TEXT:
+ g_value_set_string (value, layout->text);
+ break;
+
+ case PROP_ATTRIBUTES:
+ g_value_set_boxed (value, layout->attrs);
+ break;
+
+ case PROP_FONT_DESCRIPTION:
+ g_value_set_boxed (value, layout->font_desc);
+ break;
+
+ case PROP_LINE_HEIGHT:
+ g_value_set_float (value, layout->line_height);
+ break;
+
+ case PROP_SPACING:
+ g_value_set_int (value, layout->spacing);
+ break;
+
+ case PROP_WIDTH:
+ g_value_set_int (value, layout->width);
+ break;
+
+ case PROP_HEIGHT:
+ g_value_set_int (value, layout->height);
+ break;
+
+ case PROP_TABS:
+ g_value_set_boxed (value, layout->tabs);
+ break;
+
+ case PROP_SINGLE_PARAGRAPH:
+ g_value_set_boolean (value, layout->single_paragraph);
+ break;
+
+ case PROP_WRAP:
+ g_value_set_enum (value, layout->wrap);
+ break;
+
+ case PROP_INDENT:
+ g_value_set_int (value, layout->indent);
+ break;
+
+ case PROP_ALIGNMENT:
+ g_value_set_enum (value, layout->alignment);
+ break;
+
+ case PROP_ELLIPSIZE:
+ g_value_set_enum (value, layout->ellipsize);
+ break;
+
+ case PROP_AUTO_DIR:
+ g_value_set_boolean (value, layout->auto_dir);
+ break;
+
+ case PROP_LINES:
+ g_value_set_object (value, pango2_layout_get_lines (layout));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pango2_layout_class_init (Pango2LayoutClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = pango2_layout_finalize;
+ object_class->set_property = pango2_layout_set_property;
+ object_class->get_property = pango2_layout_get_property;
+
+ /**
+ * Pango2Layout:context: (attributes org.gtk.Property.get=pango2_layout_get_context)
+ *
+ * The context for the `Pango2Layout`.
+ */
+ props[PROP_CONTEXT] = g_param_spec_object ("context", "context", "context",
+ PANGO2_TYPE_CONTEXT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ /**
+ * Pango2Layout:text: (attributes org.gtk.Property.get=pango2_layout_get_text org.gtk.Property.set=pango2_layout_set_text)
+ *
+ * The text of the `Pango2Layout`.
+ */
+ props[PROP_TEXT] = g_param_spec_string ("text", "text", "text",
+ "",
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * Pango2Layout:attributes: (attributes org.gtk.Property.get=pango2_layout_get_attributes org.gtk.Property.set=pango2_layout_set_attributes)
+ *
+ * The attributes of the `Pango2Layout`.
+ *
+ * Attributes can affect how the text is formatted.
+ */
+ props[PROP_ATTRIBUTES] = g_param_spec_boxed ("attributes", "attributes", "attributes",
+ PANGO2_TYPE_ATTR_LIST,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * Pango2Layout:font-description: (attributes org.gtk.Property.get=pango2_layout_get_font_description org.gtk.Property.set=pango2_layout_set_font_description)
+ *
+ * The font description of the `Pango2Layout`.
+ */
+ props[PROP_FONT_DESCRIPTION] = g_param_spec_boxed ("font-description", "font-description", "font-description",
+ PANGO2_TYPE_FONT_DESCRIPTION,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * Pango2Layout:line-height: (attributes org.gtk.Property.get=pango2_layout_get_line_height org.gtk.Property.set=pango2_layout_set_line_height)
+ *
+ * The line height factor of the `Pango2Layout`.
+ *
+ * If non-zero, the line height is multiplied by this factor to determine
+ * the logical extents of the text.
+ *
+ * The default value is 0.
+ */
+ props[PROP_LINE_HEIGHT] = g_param_spec_float ("line-height", "line-height", "line-height",
+ 0., G_MAXFLOAT, 0.,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * Pango2Layout:spacing: (attributes org.gtk.Property.get=pango2_layout_get_spacing org.gtk.Property.set=pango2_layout_set_spacing)
+ *
+ * Spacing to add between the lines of the `Pango2Layout`.
+ *
+ * The spacing is specified in Pango2 units.
+ *
+ * The default value is 0.
+ */
+ props[PROP_SPACING] = g_param_spec_int ("spacing", "spacing", "spacing",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * Pango2Layout:width: (attributes org.gtk.Property.get=pango2_layout_get_width org.gtk.Property.set=pango2_layout_set_width)
+ *
+ * The width to which the text of `Pango2Layout` will be broken.
+ *
+ * The width is specified in Pango2 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);
+
+ /**
+ * Pango2Layout:height: (attributes org.gtk.Property.get=pango2_layout_get_height org.gtk.Property.set=pango2_layout_set_height)
+ *
+ * The height to which the `Pango2Layout` 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 `PANGO2_ELLIPSIZE_NONE`.
+ * The behavior is undefined if a height other than -1 is set and
+ * ellipsization mode is set to `PANGO2_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);
+
+ /**
+ * Pango2Layout:tabs: (attributes org.gtk.Property.get=pango2_layout_get_tabs org.gtk.Property.set=pango2_layout_set_tabs)
+ *
+ * The tabs to use when formatting the text of `Pango2Layout`.
+ *
+ * `Pango2Layout` 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",
+ PANGO2_TYPE_TAB_ARRAY,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * Pango2Layout:single-paragraph: (attributes org.gtk.Property.get=pango2_layout_get_single_paragraph org.gtk.Property.set=pango2_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);
+
+ /**
+ * Pango2Layout:wrap: (attributes org.gtk.Property.get=pango2_layout_get_wrap org.gtk.Property.set=pango2_layout_set_wrap)
+ *
+ * The wrap mode of this `Pango2Layout`.
+ *
+ * The wrap mode influences how Pango2 chooses line breaks
+ * when text needs to be wrapped.
+ *
+ * The default value is `PANGO2_WRAP_WORD`.
+ */
+ props[PROP_WRAP] = g_param_spec_enum ("wrap", "wrap", "wrap",
+ PANGO2_TYPE_WRAP_MODE,
+ PANGO2_WRAP_WORD,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * Pango2Layout:indent: (attributes org.gtk.Property.get=pango2_layout_get_indent org.gtk.Property.set=pango2_layout_set_indent)
+ *
+ * The indent of this `Pango2Layout`.
+ *
+ * The indent is specified in Pango2 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);
+
+ /**
+ * Pango2Layout:alignment: (attributes org.gtk.Property.get=pango2_layout_get_alignment org.gtk.Property.set=pango2_layout_set_alignment)
+ *
+ * The alignment mode of this `Pango2Layout`.
+ *
+ * The default value is `PANGO2_ALIGN_NATURAL`.
+ */
+ props[PROP_ALIGNMENT] = g_param_spec_enum ("alignment", "alignment", "alignment",
+ PANGO2_TYPE_ALIGNMENT,
+ PANGO2_ALIGN_NATURAL,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * Pango2Layout:ellipsize: (attributes org.gtk.Property.get=pango2_layout_get_ellipsize org.gtk.Property.set=pango2_layout_set_ellipsize)
+ *
+ * The ellipsization mode of this `Pango2Layout`.
+ *
+ * The default value is `PANGO2_ELLIPSIZE_NONE`.
+ */
+ props[PROP_ELLIPSIZE] = g_param_spec_enum ("ellipsize", "ellipsize", "ellipsize",
+ PANGO2_TYPE_ELLIPSIZE_MODE,
+ PANGO2_ELLIPSIZE_NONE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * Pango2Layout:auto-dir: (attributes org.gtk.Property.get=pango2_layout_get_auto_dir org.gtk.Property.set=pango2_layout_set_auto_dir)
+ *
+ * Whether this `Pango2Layout` 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);
+
+ /**
+ * Pango2Layout:lines: (attributes org.gtk.Property.get=pango2_layout_get_lines)
+ *
+ * The `Pango2Lines` object holding the formatted lines.
+ */
+ props[PROP_LINES] = g_param_spec_object ("lines", "lines", "lines",
+ PANGO2_TYPE_LINES,
+ G_PARAM_READABLE);
+
+ g_object_class_install_properties (object_class, NUM_PROPERTIES, props);
+}
+
+/* }}} */
+/* {{{ Utilities */
+
+static void
+layout_changed (Pango2Layout *layout)
+{
+ layout->serial++;
+ if (layout->serial == 0)
+ layout->serial++;
+
+ g_clear_object (&layout->lines);
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_LINES]);
+}
+
+static void
+check_context_changed (Pango2Layout *layout)
+{
+ guint old_serial = layout->context_serial;
+
+ layout->context_serial = pango2_context_get_serial (layout->context);
+
+ if (old_serial != layout->context_serial)
+ pango2_layout_context_changed (layout);
+}
+
+static Pango2AttrList *
+ensure_attrs (Pango2Layout *layout,
+ Pango2AttrList *attrs)
+{
+ if (attrs)
+ return attrs;
+ else if (layout->attrs)
+ return pango2_attr_list_copy (layout->attrs);
+ else
+ return pango2_attr_list_new ();
+}
+
+static Pango2AttrList *
+get_effective_attributes (Pango2Layout *layout)
+{
+ Pango2AttrList *attrs = NULL;
+
+ if (layout->font_desc)
+ {
+ attrs = ensure_attrs (layout, attrs);
+ pango2_attr_list_insert_before (attrs,
+ pango2_attr_font_desc_new (layout->font_desc));
+ }
+
+ if (layout->line_height != 0.0)
+ {
+ attrs = ensure_attrs (layout, attrs);
+ pango2_attr_list_insert_before (attrs,
+ pango2_attr_line_height_new (layout->line_height));
+ }
+
+ if (layout->spacing != 0)
+ {
+ attrs = ensure_attrs (layout, attrs);
+ pango2_attr_list_insert_before (attrs,
+ pango2_attr_line_spacing_new (layout->spacing));
+ }
+
+ if (layout->single_paragraph)
+ {
+ attrs = ensure_attrs (layout, attrs);
+ pango2_attr_list_insert_before (attrs,
+ pango2_attr_paragraph_new ());
+ }
+
+ if (attrs)
+ return attrs;
+
+ return pango2_attr_list_ref (layout->attrs);
+}
+
+static gboolean
+ends_with_paragraph_separator (Pango2Layout *layout)
+{
+ if (layout->single_paragraph)
+ return FALSE;
+
+ 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, "
");
+}
+
+static void
+ensure_lines (Pango2Layout *layout)
+{
+ Pango2LineBreaker *breaker;
+ Pango2AttrList *attrs;
+ int x, y, width;
+ int line_no;
+
+ check_context_changed (layout);
+
+ if (layout->lines)
+ return;
+
+ breaker = pango2_line_breaker_new (layout->context);
+
+ pango2_line_breaker_set_tabs (breaker, layout->tabs);
+ pango2_line_breaker_set_base_dir (breaker,
+ layout->auto_dir
+ ? PANGO2_DIRECTION_NEUTRAL
+ : pango2_context_get_base_dir (layout->context));
+
+ attrs = get_effective_attributes (layout);
+ pango2_line_breaker_add_text (breaker, layout->text ? layout->text : "", -1, attrs);
+ if (attrs)
+ pango2_attr_list_unref (attrs);
+
+ layout->lines = pango2_lines_new ();
+
+ x = y = 0;
+ line_no = 0;
+ while (pango2_line_breaker_has_line (breaker))
+ {
+ Pango2Line *line;
+ Pango2Rectangle ext;
+ int offset;
+ Pango2EllipsizeMode ellipsize = PANGO2_ELLIPSIZE_NONE;
+ Pango2LeadingTrim trim = PANGO2_LEADING_TRIM_NONE;
+
+ if ((line_no == 0) == (layout->indent > 0))
+ {
+ x = abs (layout->indent);
+ width = layout->width - x;
+ }
+ else
+ {
+ x = 0;
+ width = layout->width;
+ }
+
+ if (layout->height < 0 && line_no + 1 == - layout->height)
+ ellipsize = layout->ellipsize;
+
+retry:
+ line = pango2_line_breaker_next_line (breaker, x, width, layout->wrap, ellipsize);
+
+ if (line->starts_paragraph)
+ trim |= PANGO2_LEADING_TRIM_START;
+ if (line->ends_paragraph)
+ trim |= PANGO2_LEADING_TRIM_END;
+
+ pango2_line_get_trimmed_extents (line, trim, &ext);
+
+ if (layout->height >= 0 && y + 2 * ext.height >= layout->height &&
+ ellipsize != layout->ellipsize)
+ {
+ if (pango2_line_breaker_undo_line (breaker, line))
+ {
+ g_clear_pointer (&line, pango2_line_free);
+ ellipsize = layout->ellipsize;
+ goto retry;
+ }
+ }
+
+ /* Handle alignment and justification */
+ offset = 0;
+ switch (layout->alignment)
+ {
+
+ case PANGO2_ALIGN_LEFT:
+ break;
+
+ case PANGO2_ALIGN_CENTER:
+ if (ext.width < width)
+ offset = (width - ext.width) / 2;
+ break;
+
+ case PANGO2_ALIGN_JUSTIFY:
+ if (!pango2_line_is_paragraph_end (line))
+ {
+ line = pango2_line_justify (line, width);
+ break;
+ }
+ G_GNUC_FALLTHROUGH;
+
+ case PANGO2_ALIGN_NATURAL:
+ {
+ Pango2Line *first_line;
+ if (pango2_lines_get_line_count (layout->lines) > 0)
+ first_line = pango2_lines_get_lines (layout->lines)[0];
+ else
+ first_line = line;
+ if (pango2_line_get_resolved_direction (first_line) == PANGO2_DIRECTION_LTR)
+ break;
+ }
+ G_GNUC_FALLTHROUGH;
+
+ case PANGO2_ALIGN_RIGHT:
+ if (ext.width < width)
+ offset = width - ext.width;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ pango2_lines_add_line (layout->lines, line, x + offset, y - ext.y);
+
+ y += ext.height;
+ line_no++;
+ }
+
+ /* Append an empty line if we end with a newline.
+ * And always provide at least one line
+ */
+ if (pango2_lines_get_line_count (layout->lines) == 0 ||
+ ends_with_paragraph_separator (layout))
+ {
+ LineData *data;
+ int start_index;
+ int start_offset;
+ int offset;
+ Pango2Line *line;
+ Pango2Rectangle ext;
+
+ if (pango2_lines_get_line_count (layout->lines) > 0)
+ {
+ Pango2Line *last;
+
+ last = pango2_lines_get_lines (layout->lines)[pango2_lines_get_line_count (layout->lines) - 1];
+ data = line_data_ref (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 (Pango2LogAttr, 1);
+ data->log_attrs[0].is_cursor_position = TRUE;
+ start_index = 0;
+ start_offset = 0;
+ offset = 0;
+ }
+
+ line = pango2_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;
+
+ pango2_line_get_extents (line, NULL, &ext);
+
+ pango2_lines_add_line (layout->lines, line, x + offset, y - ext.y);
+
+ line_data_unref (data);
+ }
+
+ g_object_unref (breaker);
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_layout_new:
+ * @context: a `Pango2Context`
+ *
+ * Creates a new `Pango2Layout` with attribute initialized to
+ * default values for a particular `Pango2Context`
+ *
+ * Return value: a newly allocated `Pango2Layout`
+ */
+Pango2Layout *
+pango2_layout_new (Pango2Context *context)
+{
+ g_return_val_if_fail (PANGO2_IS_CONTEXT (context), NULL);
+
+ return g_object_new (PANGO2_TYPE_LAYOUT, "context", context, NULL);
+}
+
+/**
+ * pango2_layout_copy:
+ * @layout: a `Pango2Layout`
+ *
+ * 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 `Pango2Layout`
+ */
+Pango2Layout *
+pango2_layout_copy (Pango2Layout *layout)
+{
+ Pango2Layout *copy;
+
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), NULL);
+
+ copy = pango2_layout_new (layout->context);
+
+ copy->text = g_strdup (layout->text);
+ copy->length = layout->length;
+ if (layout->attrs)
+ copy->attrs = pango2_attr_list_copy (layout->attrs);
+ if (layout->font_desc)
+ copy->font_desc = pango2_font_description_copy (layout->font_desc);
+ copy->line_height = layout->line_height;
+ copy->spacing = layout->spacing;
+ copy->width = layout->width;
+ copy->height = layout->height;
+ if (layout->tabs)
+ copy->tabs = pango2_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;
+
+ return copy;
+}
+
+/**
+ * pango2_layout_get_serial:
+ * @layout: a `Pango2Layout`
+ *
+ * Returns the current serial number of the 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 `Pango2Context` 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".
+ *
+ * This can be used to automatically detect changes to a `Pango2Layout`,
+ * and is useful for example to decide whether a layout needs redrawing.
+ *
+ * Return value: The current serial number of @layout
+ */
+guint
+pango2_layout_get_serial (Pango2Layout *layout)
+{
+ check_context_changed (layout);
+
+ return layout->serial;
+}
+
+/**
+ * pango2_layout_context_changed:
+ * @layout: a `Pango2Layout`
+ *
+ * Forces recomputation of any state in the layout 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
+pango2_layout_context_changed (Pango2Layout *layout)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ layout_changed (layout);
+}
+
+/* {{{ Property getters and setters */
+
+/**
+ * pango2_layout_get_context:
+ * @layout: a `Pango2Layout`
+ *
+ * Retrieves the `Pango2Context` used for this layout.
+ *
+ * Return value: (transfer none): the `Pango2Context` for the layout
+ */
+Pango2Context *
+pango2_layout_get_context (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), NULL);
+
+ return layout->context;
+}
+
+/**
+ * pango2_layout_set_text:
+ * @layout: a `Pango2Layout`
+ * @text: the text
+ * @length: maximum length of @text, in bytes. -1 indicates that
+ * the string is nul-terminated
+ *
+ * Sets the text of the layout.
+ */
+void
+pango2_layout_set_text (Pango2Layout *layout,
+ const char *text,
+ int length)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (length < 0)
+ length = strlen (text);
+
+ g_free (layout->text);
+ layout->text = g_strndup (text, length);
+ layout->length = length;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_TEXT]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_text:
+ * @layout: a `Pango2Layout`
+ *
+ * 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 *
+pango2_layout_get_text (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), NULL);
+
+ return layout->text;
+}
+
+/**
+ * pango2_layout_set_attributes:
+ * @layout: a `Pango2Layout`
+ * @attrs: (nullable) (transfer none): a `Pango2AttrList`
+ *
+ * Sets the attributes for a layout object.
+ *
+ * References @attrs, so the caller can unref its reference.
+ */
+void
+pango2_layout_set_attributes (Pango2Layout *layout,
+ Pango2AttrList *attrs)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ g_clear_pointer (&layout->attrs, pango2_attr_list_unref);
+ layout->attrs = attrs;
+ if (layout->attrs)
+ pango2_attr_list_ref (layout->attrs);
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_ATTRIBUTES]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_attributes:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets the attribute list for the layout, if any.
+ *
+ * Return value: (transfer none) (nullable): a `Pango2AttrList`
+ */
+Pango2AttrList *
+pango2_layout_get_attributes (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), NULL);
+
+ return layout->attrs;
+}
+
+/**
+ * pango2_layout_set_font_description:
+ * @layout: a `Pango2Layout`
+ * @desc: (nullable): the new `Pango2FontDescription`
+ *
+ * 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.
+ *
+ * This method is a shorthand for adding a font-desc attribute.
+ */
+void
+pango2_layout_set_font_description (Pango2Layout *layout,
+ const Pango2FontDescription *desc)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (desc != layout->font_desc &&
+ (!desc || !layout->font_desc || !pango2_font_description_equal (desc, layout->font_desc)))
+ {
+ if (layout->font_desc)
+ pango2_font_description_free (layout->font_desc);
+
+ layout->font_desc = desc ? pango2_font_description_copy (desc) : NULL;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_FONT_DESCRIPTION]);
+ layout_changed (layout);
+ }
+}
+
+/**
+ * pango2_layout_get_font_description:
+ * @layout: a `Pango2Layout`
+ *
+ * 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.
+ */
+const Pango2FontDescription *
+pango2_layout_get_font_description (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), NULL);
+
+ return layout->font_desc;
+}
+
+/**
+ * pango2_layout_set_line_height:
+ * @layout: a `Pango2Layout`
+ * @line_height: the new line height factor
+ *
+ * Sets a factor for line height.
+ *
+ * Typical values are: 0, 1, 1.5, 2. The default values is 0.
+ *
+ * If @line_height 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).
+ *
+ * This method is a shorthand for adding a line-height attribute.
+ */
+void
+pango2_layout_set_line_height (Pango2Layout *layout,
+ float line_height)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (layout->line_height == line_height)
+ return;
+
+ layout->line_height = line_height;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_LINE_HEIGHT]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_line_height:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets the line height factor of the layout.
+ *
+ * See [method@Pango2.Layout.set_line_height].
+ */
+float
+pango2_layout_get_line_height (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), 0.0);
+
+ return layout->line_height;
+}
+
+/**
+ * pango2_layout_set_spacing:
+ * @layout: a `Pango2Layout`
+ * @spacing: the amount of spacing, in Pango2 units
+ *
+ * Sets the amount of spacing between the lines of the layout.
+ *
+ * When placing lines with spacing, Pango2 arranges things so that
+ *
+ * line2.top = line1.bottom + spacing
+ *
+ * The default value is 0.
+ *
+ * Spacing only takes effect if the line height is not
+ * overridden via [method@Pango2.Layout.set_line_height].
+ */
+void
+pango2_layout_set_spacing (Pango2Layout *layout,
+ int spacing)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (layout->spacing == spacing)
+ return;
+
+ layout->spacing = spacing;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_SPACING]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_spacing:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets the amount of spacing between the lines of the layout.
+ *
+ * Return value: the spacing in Pango2 units
+ */
+int
+pango2_layout_get_spacing (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), 0);
+
+ return layout->spacing;
+}
+
+/**
+ * pango2_layout_set_width:
+ * @layout: a `Pango2Layout`.
+ * @width: the desired width in Pango2 units, or -1 to indicate that no
+ * wrapping or ellipsization should be performed
+ *
+ * Sets the width to which the lines of the layout should
+ * be wrapped or ellipsized.
+ *
+ * The default value is -1: no width set.
+ */
+void
+pango2_layout_set_width (Pango2Layout *layout,
+ int width)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (width < -1)
+ width = -1;
+
+ if (layout->width == width)
+ return;
+
+ layout->width = width;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_WIDTH]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_width:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets the width to which the lines of the layout should wrap.
+ *
+ * Return value: the width in Pango2 units, or -1 if no width set.
+ */
+int
+pango2_layout_get_width (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), -1);
+
+ return layout->width;
+}
+
+/**
+ * pango2_layout_set_height:
+ * @layout: a `Pango2Layout`.
+ * @height: the desired height
+ *
+ * Sets the height to which the `Pango2Layout` should be ellipsized at.
+ *
+ * There are two different behaviors, based on whether @height is positive
+ * or negative.
+ *
+ * See [property@Pango2.Layout:height] for details.
+ */
+void
+pango2_layout_set_height (Pango2Layout *layout,
+ int height)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (layout->height == height)
+ return;
+
+ layout->height = height;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_HEIGHT]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_height:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets the height to which the lines of the layout should ellipsize.
+ *
+ * See [property@Pango2.Layout:height] for details.
+ *
+ * Return value: the height
+ */
+int
+pango2_layout_get_height (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), -1);
+
+ return layout->height;
+}
+
+/**
+ * pango2_layout_set_tabs:
+ * @layout: a `Pango2Layout`
+ * @tabs: (nullable): a `Pango2TabArray`
+ *
+ * Sets the tabs to use for the layout, overriding the
+ * default tabs.
+ *
+ * Setting the tabs to `NULL` reinstates the default
+ * tabs.
+ *
+ * See [method@Pango2.LineBreaker.set_tabs] for details.
+ */
+void
+pango2_layout_set_tabs (Pango2Layout *layout,
+ Pango2TabArray *tabs)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (layout->tabs == tabs)
+ return;
+
+ g_clear_pointer (&layout->tabs, pango2_tab_array_free);
+ if (tabs)
+ layout->tabs = pango2_tab_array_copy (tabs);
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_TABS]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_tabs:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets the current `Pango2TabArray` used by this layout.
+ *
+ * If no `Pango2TabArray` has been set, then the default tabs are
+ * in use and %NULL is returned. Default tabs are every 8 spaces.
+ *
+ * Return value: (transfer none) (nullable): the tabs for this layout
+ */
+Pango2TabArray *
+pango2_layout_get_tabs (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), NULL);
+
+ return layout->tabs;
+}
+
+/**
+ * pango2_layout_set_single_paragraph:
+ * @layout: a `Pango2Layout`
+ * @single_paragraph: the new setting
+ *
+ * Sets the single paragraph mode of the layout.
+ *
+ * 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.
+ *
+ * Used when you want to allow editing of newlines on a single text line.
+ *
+ * The default value is %FALSE.
+ */
+void
+pango2_layout_set_single_paragraph (Pango2Layout *layout,
+ gboolean single_paragraph)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (layout->single_paragraph == single_paragraph)
+ return;
+
+ layout->single_paragraph = single_paragraph;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_SINGLE_PARAGRAPH]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_single_paragraph:
+ * @layout: a `Pango2Layout`
+ *
+ * Obtains whether this layout is in single paragraph mode.
+ *
+ * See [method@Pango2.Layout.set_single_paragraph].
+ *
+ * Return value: `TRUE` if the layout does not break paragraphs
+ * at paragraph separator characters, %FALSE otherwise
+ */
+gboolean
+pango2_layout_get_single_paragraph (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), FALSE);
+
+ return layout->single_paragraph;
+}
+
+/**
+ * pango2_layout_set_wrap:
+ * @layout: a `Pango2Layout`
+ * @wrap: the wrap mode
+ *
+ * Sets the wrap mode.
+ *
+ * The wrap mode only has effect if a width is set on the layout
+ * with [method@Pango2.Layout.set_width]. To turn off wrapping,
+ * set the width to -1.
+ *
+ * The default value is %PANGO2_WRAP_WORD.
+ */
+void
+pango2_layout_set_wrap (Pango2Layout *layout,
+ Pango2WrapMode wrap)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (layout->wrap == wrap)
+ return;
+
+ layout->wrap = wrap;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_WRAP]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_wrap:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets the wrap mode for the layout.
+ *
+ * Return value: active wrap mode.
+ */
+Pango2WrapMode
+pango2_layout_get_wrap (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), PANGO2_WRAP_WORD);
+
+ return layout->wrap;
+}
+
+/**
+ * pango2_layout_set_indent:
+ * @layout: a `Pango2Layout`
+ * @indent: the amount by which to indent
+ *
+ * Sets the width in Pango2 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 default value is 0.
+ */
+void
+pango2_layout_set_indent (Pango2Layout *layout,
+ int indent)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (layout->indent == indent)
+ return;
+
+ layout->indent = indent;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_INDENT]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_indent:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets the paragraph indent width in Pango2 units.
+ *
+ * A negative value indicates a hanging indentation.
+ *
+ * Return value: the indent in Pango2 units
+ */
+int
+pango2_layout_get_indent (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), 0);
+
+ return layout->indent;
+}
+
+/**
+ * pango2_layout_set_alignment:
+ * @layout: a `Pango2Layout`
+ * @alignment: the alignment
+ *
+ * Sets the alignment for the layout.
+ *
+ * The alignment determines how short lines are
+ * positioned within the available horizontal space.
+ *
+ * The default alignment is `PANGO2_ALIGN_NATURAL`.
+ */
+void
+pango2_layout_set_alignment (Pango2Layout *layout,
+ Pango2Alignment alignment)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (layout->alignment == alignment)
+ return;
+
+ layout->alignment = alignment;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_ALIGNMENT]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_alignment:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets the alignment for the layout.
+ *
+ * The alignment determines how short lines are
+ * positioned within the available horizontal space.
+ *
+ * Return value: the alignment
+ */
+Pango2Alignment
+pango2_layout_get_alignment (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), PANGO2_ALIGN_NATURAL);
+
+ return layout->alignment;
+}
+
+/**
+ * pango2_layout_set_ellipsize:
+ * @layout: a `Pango2Layout`
+ * @ellipsize: the new ellipsization mode for @layout
+ *
+ * Sets the type of ellipsization to use for this layout.
+ *
+ * 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@Pango2.Layout.set_width].
+ *
+ * The default value is `PANGO2_ELLIPSIZE_NONE`.
+ */
+void
+pango2_layout_set_ellipsize (Pango2Layout *layout,
+ Pango2EllipsizeMode ellipsize)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (layout->ellipsize == ellipsize)
+ return;
+
+ layout->ellipsize = ellipsize;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_ELLIPSIZE]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_ellipsize:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets the type of ellipsization being performed for the layout.
+ *
+ * See [method@Pango2.Layout.set_ellipsize].
+ *
+ * Return value: the current ellipsization mode for @layout
+ */
+Pango2EllipsizeMode
+pango2_layout_get_ellipsize (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), PANGO2_ELLIPSIZE_NONE);
+
+ return layout->ellipsize;
+}
+
+/**
+ * pango2_layout_set_auto_dir:
+ * @layout: a `Pango2Layout`
+ * @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
+ * `Pango2Context`. (See [method@Pango2.Context.set_base_dir]).
+ *
+ * When the auto-computed direction of a paragraph differs
+ * from the base direction of the context, the interpretation
+ * of `PANGO2_ALIGN_LEFT` and `PANGO2_ALIGN_RIGHT` are swapped.
+ */
+void
+pango2_layout_set_auto_dir (Pango2Layout *layout,
+ gboolean auto_dir)
+{
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ if (auto_dir == layout->auto_dir)
+ return;
+
+ layout->auto_dir = auto_dir;
+
+ g_object_notify_by_pspec (G_OBJECT (layout), props[PROP_AUTO_DIR]);
+ layout_changed (layout);
+}
+
+/**
+ * pango2_layout_get_auto_dir:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets whether to calculate the base direction for the layout
+ * according to its contents.
+ *
+ * See [method@Pango2.Layout.set_auto_dir].
+ *
+ * Return value: `TRUE` if the bidirectional base direction
+ * is computed from the layout's contents, `FALSE` otherwise
+ */
+gboolean
+pango2_layout_get_auto_dir (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), TRUE);
+
+ return layout->auto_dir;
+}
+
+/* }}} */
+/* {{{ Miscellaneous */
+
+/**
+ * pango2_layout_set_markup:
+ * @layout: a `Pango2Layout`
+ * @markup: marked-up text
+ * @length: length of @markup in bytes, or -1 if it is `NUL`-terminated
+ *
+ * Sets the layout text and attribute list from marked-up text.
+ *
+ * See [Pango2 Markup](pango2_markup.html)).
+ *
+ * Replaces the current text and attributes.
+ */
+void
+pango2_layout_set_markup (Pango2Layout *layout,
+ const char *markup,
+ int length)
+{
+ Pango2AttrList *attrs;
+ char *text;
+ GError *error = NULL;
+
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+ g_return_if_fail (markup != NULL);
+
+ if (!pango2_parse_markup (markup, length, 0, &attrs, &text, NULL, &error))
+ {
+ g_warning ("pango2_layout_set_markup_with_accel: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_free (layout->text);
+ layout->text = text;
+ layout->length = strlen (text);
+
+ g_clear_pointer (&layout->attrs, pango2_attr_list_unref);
+ layout->attrs = attrs;
+
+ 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);
+}
+
+/**
+ * pango2_layout_get_character_count:
+ * @layout: a `Pango2Layout`
+ *
+ * Returns the number of Unicode characters in the
+ * the text of the layout.
+ *
+ * Return value: the number of Unicode characters in @layout
+ */
+int
+pango2_layout_get_character_count (Pango2Layout *layout)
+{
+ Pango2Line *line;
+
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), 0);
+
+ ensure_lines (layout);
+
+ line = pango2_lines_get_lines (layout->lines)[0];
+
+ return line->data->n_chars;
+}
+
+/* }}} */
+/* {{{ Output getters */
+
+/**
+ * pango2_layout_get_lines:
+ * @layout: a `Pango2Layout`
+ *
+ * Gets the lines of the layout.
+ *
+ * The returned object will become invalid when any
+ * property of @layout is changed. Take a reference
+ * to keep it.
+ *
+ * Return value: (transfer none): a `Pango2Lines` object
+ * with the lines of @layout
+ */
+Pango2Lines *
+pango2_layout_get_lines (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), NULL);
+
+ ensure_lines (layout);
+
+ return layout->lines;
+}
+
+/**
+ * pango2_layout_get_log_attrs:
+ * @layout: a `Pango2Layout`
+ * @n_attrs: (out): return location for the length of the array
+ *
+ * Gets the `Pango2LogAttr` array for the content of layout.
+ *
+ * The returned array becomes invalid when
+ * any properties of @layout change. Make a
+ * copy if you want to keep it.
+ *
+ * Returns: (transfer none): the `Pango2LogAttr` array
+ */
+const Pango2LogAttr *
+pango2_layout_get_log_attrs (Pango2Layout *layout,
+ int *n_attrs)
+{
+ Pango2Line *line;
+
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), NULL);
+
+ ensure_lines (layout);
+
+ line = pango2_lines_get_lines (layout->lines)[0];
+
+ if (n_attrs)
+ *n_attrs = line->data->n_chars + 1;
+
+ return line->data->log_attrs;
+}
+
+/**
+ * pango2_layout_get_iter:
+ * @layout: a `Pango2Layout`
+ *
+ * Returns an iterator to iterate over the visual extents
+ * of the layout.
+ *
+ * This is a convenience wrapper for [method@Pango2.Lines.get_iter].
+ *
+ * Returns: the new `Pango2LineIter`
+ */
+Pango2LineIter *
+pango2_layout_get_iter (Pango2Layout *layout)
+{
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), NULL);
+
+ ensure_lines (layout);
+
+ return pango2_lines_get_iter (layout->lines);
+}
+
+/* }}} */
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-layout.h b/pango2/pango-layout.h
new file mode 100644
index 00000000..f3ad61dd
--- /dev/null
+++ b/pango2/pango-layout.h
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <pango2/pango-types.h>
+#include <pango2/pango-attributes.h>
+#include <pango2/pango-lines.h>
+#include <pango2/pango-tabs.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_LAYOUT pango2_layout_get_type ()
+
+PANGO2_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (Pango2Layout, pango2_layout, PANGO2, LAYOUT, GObject);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Layout * pango2_layout_new (Pango2Context *context);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Layout * pango2_layout_copy (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+guint pango2_layout_get_serial (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Context * pango2_layout_get_context (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_context_changed (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_text (Pango2Layout *layout,
+ const char *text,
+ int length);
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_layout_get_text (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_markup (Pango2Layout *layout,
+ const char *markup,
+ int length);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_layout_get_character_count
+ (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_attributes (Pango2Layout *layout,
+ Pango2AttrList *attrs);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2AttrList * pango2_layout_get_attributes (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_font_description
+ (Pango2Layout *layout,
+ const Pango2FontDescription *desc);
+
+PANGO2_AVAILABLE_IN_ALL
+const Pango2FontDescription *
+ pango2_layout_get_font_description
+ (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_line_height
+ (Pango2Layout *layout,
+ float line_height);
+
+PANGO2_AVAILABLE_IN_ALL
+float pango2_layout_get_line_height
+ (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_spacing (Pango2Layout *layout,
+ int spacing);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_layout_get_spacing (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_width (Pango2Layout *layout,
+ int width);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_layout_get_width (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_height (Pango2Layout *layout,
+ int height);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_layout_get_height (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_tabs (Pango2Layout *layout,
+ Pango2TabArray *tabs);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2TabArray * pango2_layout_get_tabs (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_single_paragraph
+ (Pango2Layout *layout,
+ gboolean single_paragraph);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_layout_get_single_paragraph
+ (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_wrap (Pango2Layout *layout,
+ Pango2WrapMode wrap);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2WrapMode pango2_layout_get_wrap (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_indent (Pango2Layout *layout,
+ int indent);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_layout_get_indent (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_alignment (Pango2Layout *layout,
+ Pango2Alignment alignment);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Alignment pango2_layout_get_alignment (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_ellipsize (Pango2Layout *layout,
+ Pango2EllipsizeMode ellipsize);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2EllipsizeMode pango2_layout_get_ellipsize (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_layout_set_auto_dir (Pango2Layout *layout,
+ gboolean auto_dir);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_layout_get_auto_dir (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Lines * pango2_layout_get_lines (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2LineIter * pango2_layout_get_iter (Pango2Layout *layout);
+
+PANGO2_AVAILABLE_IN_ALL
+const Pango2LogAttr * pango2_layout_get_log_attrs (Pango2Layout *layout,
+ int *n_attrs);
+
+typedef enum {
+ PANGO2_LAYOUT_SERIALIZE_DEFAULT = 0,
+ PANGO2_LAYOUT_SERIALIZE_CONTEXT = 1 << 0,
+ PANGO2_LAYOUT_SERIALIZE_OUTPUT = 1 << 1,
+} Pango2LayoutSerializeFlags;
+
+PANGO2_AVAILABLE_IN_ALL
+GBytes * pango2_layout_serialize (Pango2Layout *layout,
+ Pango2LayoutSerializeFlags flags);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_layout_write_to_file (Pango2Layout *layout,
+ const char *filename);
+
+#define PANGO2_LAYOUT_DESERIALIZE_ERROR (pango2_layout_deserialize_error_quark ())
+
+/**
+ * Pango2LayoutDeserializeError:
+ * @PANGO2_LAYOUT_DESERIALIZE_INVALID: Unspecified error
+ * @PANGO2_LAYOUT_DESERIALIZE_INVALID_VALUE: A JSon value could not be
+ * interpreted
+ * @PANGO2_LAYOUT_DESERIALIZE_MISSING_VALUE: A required JSon member was
+ * not found
+ *
+ * Errors that can be returned by [func@Pango2.Layout.deserialize].
+ */
+typedef enum {
+ PANGO2_LAYOUT_DESERIALIZE_INVALID,
+ PANGO2_LAYOUT_DESERIALIZE_INVALID_VALUE,
+ PANGO2_LAYOUT_DESERIALIZE_MISSING_VALUE,
+} Pango2LayoutDeserializeError;
+
+typedef enum {
+ PANGO2_LAYOUT_DESERIALIZE_DEFAULT = 0,
+ PANGO2_LAYOUT_DESERIALIZE_CONTEXT = 1 << 0,
+} Pango2LayoutDeserializeFlags;
+
+PANGO2_AVAILABLE_IN_ALL
+GQuark pango2_layout_deserialize_error_quark (void);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Layout * pango2_layout_deserialize (Pango2Context *context,
+ GBytes *bytes,
+ Pango2LayoutDeserializeFlags flags,
+ GError **error);
+
+G_END_DECLS
diff --git a/pango2/pango-line-breaker.c b/pango2/pango-line-breaker.c
new file mode 100644
index 00000000..1049fe5e
--- /dev/null
+++ b/pango2/pango-line-breaker.c
@@ -0,0 +1,2663 @@
+#include "config.h"
+
+#include "pango-line-breaker.h"
+#include "pango-line-private.h"
+
+#include "pango-tabs.h"
+#include "pango-impl-utils.h"
+#include "pango-attributes-private.h"
+#include "pango-attr-private.h"
+#include "pango-attr-list-private.h"
+#include "pango-attr-iterator-private.h"
+#include "pango-item-private.h"
+#include "pango-bidi-private.h"
+
+#include <locale.h>
+
+#include <hb-ot.h>
+
+#if 0
+# define DEBUG1(...) g_debug (__VA_ARGS__)
+#else
+# define DEBUG1(...) do { } while (0)
+#endif
+
+/**
+ * Pango2LineBreaker:
+ *
+ * A `Pango2LineBreaker` breaks text into lines.
+ *
+ * To use a `Pango2LineBreaker`, you must call [method@Pango2.LineBreaker.add_text]
+ * to provide text that you want to break into lines, plus possibly attributes
+ * to influence the formatting.
+ *
+ * Then you can call [method@Pango2.LineBreaker.next_line] repeatedly to obtain
+ * `Pango2Line` objects for the text, one by one.
+ *
+ * `Pango2LineBreaker` is meant to enable use cases like flowing text around images,
+ * or shaped paragraphs. For simple formatting needs, [class@Pango2.Layout]
+ * is probably more convenient to use.
+ */
+
+typedef struct _LastTabState LastTabState;
+struct _LastTabState
+{
+ Pango2GlyphString *glyphs;
+ int index;
+ int width;
+ int pos;
+ Pango2TabAlign align;
+ gunichar decimal;
+};
+
+struct _Pango2LineBreaker
+{
+ GObject parent_instance;
+
+ /* Properties */
+ Pango2Context *context;
+ Pango2Direction base_dir;
+ Pango2TabArray *tabs;
+
+ /* Data that we're building lines from, shared among all the lines */
+ GSList *datas; /* Queued up LineData */
+ LineData *data; /* The LineData we're currently processing */
+ GList *data_items; /* Original items for data (only used for undoing) */
+ GList *items; /* The remaining unprocessed items for data */
+ Pango2AttrList *render_attrs; /* Attributes to be re-added after line breaking */
+
+ /* Arguments to next_line, for use while processing the next line */
+ Pango2WrapMode line_wrap;
+ Pango2EllipsizeMode line_ellipsize;
+
+ int tab_width; /* Cached width of a tab. -1 == not yet calculated */
+ int hyphen_width; /* Cached width of a hyphen. -1 == not yet calculated */
+ gunichar decimal; /* Cached decimal point. 0 == not yet calculated */
+
+ /* State for line breaking */
+ int n_lines; /* Line count, starting from 0 */
+ Pango2GlyphString *glyphs; /* Glyphs for the first item in self->items */
+ int start_offset; /* Character offset of first item in self->items in self->data->text */
+ ItemProperties properties; /* Properties of the first item in self->items */
+ int *log_widths; /* Logical widths for th efirst item in self->items */
+ int num_log_widths; /* Length o fo log_widths */
+ int log_widths_offset; /* Offset into log_widths to the point corresponding to
+ * the remaining portion of the fist item
+ */
+ int line_start_index; /* Byte offset of line in self->data->text */
+ int line_start_offset; /* Character offset of line in self->data->text */
+
+ int line_x; /* X offset for current line */
+ int line_width; /* Goal width of current line; < 0 for unlimited */
+ int remaining_width; /* Amount of spac remaining on line; < 0 for unlimited */
+
+ gboolean at_paragraph_start; /* TRUE if the next line starts a new paragraph */
+
+ GList *baseline_shifts;
+
+ LastTabState last_tab;
+};
+
+struct _Pango2LineBreakerClass
+{
+ GObjectClass parent_class;
+};
+
+/* {{{ Utilities */
+
+static LineData *
+make_line_data (Pango2LineBreaker *self,
+ const char *text,
+ int length,
+ Pango2AttrList *attrs)
+{
+ LineData *data;
+
+ if (length < 0)
+ length = strlen (text);
+
+ data = line_data_new ();
+
+ if (self->base_dir == PANGO2_DIRECTION_NEUTRAL)
+ {
+ data->direction = pango2_find_base_dir (text, length);
+
+ if (data->direction == PANGO2_DIRECTION_NEUTRAL)
+ data->direction = pango2_context_get_base_dir (self->context);
+ }
+ else
+ data->direction = self->base_dir;
+
+ data->text = g_strndup (text, length);
+ data->length = length;
+ data->n_chars = g_utf8_strlen (text, length);
+ if (attrs)
+ data->attrs = pango2_attr_list_copy (attrs);
+
+ return data;
+}
+
+static gboolean
+item_is_paragraph_separator (Pango2LineBreaker *self,
+ Pango2Item *item)
+{
+ gunichar ch;
+
+ if (self->properties.no_paragraph_break)
+ return FALSE;
+
+ ch = g_utf8_get_char (self->data->text + item->offset);
+
+ return ch == '\r' || ch == '\n' || ch == 0x2029;
+}
+
+static void
+apply_attributes_to_items (GList *items,
+ Pango2AttrList *attrs)
+{
+ GList *l;
+ Pango2AttrIterator iter;
+
+ if (!attrs)
+ return;
+
+ pango2_attr_list_init_iterator (attrs, &iter);
+
+ for (l = items; l; l = l->next)
+ {
+ Pango2Item *item = l->data;
+ pango2_item_apply_attrs (item, &iter);
+ }
+
+ pango2_attr_iterator_clear (&iter);
+}
+
+static Pango2LogAttr *
+get_log_attrs (LineData *data,
+ GList *items)
+{
+ Pango2LogAttr *log_attrs;
+ int offset;
+
+ log_attrs = g_new0 (Pango2LogAttr, (data->n_chars + 1));
+
+ pango2_default_break (data->text,
+ data->length,
+ log_attrs,
+ data->n_chars + 1);
+
+ offset = 0;
+ for (GList *l = items; l; l = l->next)
+ {
+ Pango2Item *item = l->data;
+
+ pango2_tailor_break (data->text + item->offset,
+ item->length,
+ &item->analysis,
+ item->offset,
+ log_attrs + offset,
+ item->num_chars + 1);
+
+ offset += item->num_chars;
+ }
+
+ if (data->attrs)
+ pango2_attr_break (data->text,
+ data->length,
+ data->attrs,
+ 0,
+ log_attrs,
+ data->n_chars + 1);
+
+ return log_attrs;
+}
+
+static void
+ensure_items (Pango2LineBreaker *self)
+{
+ Pango2AttrList *itemize_attrs = NULL;
+ Pango2AttrList *shape_attrs = NULL;
+
+ if (self->items)
+ return;
+
+ if (!self->data && self->datas)
+ {
+ self->data = self->datas->data;
+ self->datas = g_slist_remove (self->datas, self->data);
+ }
+
+ if (!self->data)
+ return;
+
+ self->render_attrs = pango2_attr_list_copy (self->data->attrs);
+ if (self->render_attrs)
+ {
+ itemize_attrs = pango2_attr_list_filter (self->render_attrs, pango2_attribute_affects_itemization, NULL);
+ shape_attrs = pango2_attr_list_filter (self->render_attrs, pango2_attribute_affects_break_or_shape, NULL);
+ }
+
+ self->items = pango2_itemize_with_font (self->context,
+ self->data->direction,
+ self->data->text,
+ 0,
+ self->data->length,
+ itemize_attrs,
+ NULL,
+ NULL);
+
+ apply_attributes_to_items (self->items, shape_attrs);
+
+ pango2_attr_list_unref (itemize_attrs);
+ pango2_attr_list_unref (shape_attrs);
+
+ self->data->log_attrs = get_log_attrs (self->data, self->items);
+
+ self->items = pango2_itemize_post_process_items (self->context,
+ self->data->text,
+ self->data->log_attrs,
+ self->items);
+
+ g_assert (self->data_items == NULL);
+ self->data_items = g_list_copy_deep (self->items, (GCopyFunc) pango2_item_copy, NULL);
+
+ self->hyphen_width = -1;
+ self->tab_width = -1;
+
+ self->start_offset = 0;
+ self->line_start_offset = 0;
+ self->line_start_index = 0;
+
+ g_list_free_full (self->baseline_shifts, g_free);
+ self->baseline_shifts = NULL;
+ g_clear_pointer (&self->glyphs, pango2_glyph_string_free);
+ g_clear_pointer (&self->log_widths, g_free);
+ self->num_log_widths = 0;
+ self->log_widths_offset = 0;
+
+ self->remaining_width = -1;
+ self->at_paragraph_start = TRUE;
+}
+
+/* The resolved direction for the line is always one
+ * of LTR/RTL; not a week or neutral directions
+ */
+static Pango2Direction
+get_resolved_dir (Pango2LineBreaker *self)
+{
+ Pango2Direction dir;
+
+ ensure_items (self);
+
+ if (!self->data)
+ return PANGO2_DIRECTION_NEUTRAL;
+
+ switch (self->data->direction)
+ {
+ default:
+ case PANGO2_DIRECTION_LTR:
+ case PANGO2_DIRECTION_WEAK_LTR:
+ case PANGO2_DIRECTION_NEUTRAL:
+ dir = PANGO2_DIRECTION_LTR;
+ break;
+ case PANGO2_DIRECTION_RTL:
+ case PANGO2_DIRECTION_WEAK_RTL:
+ dir = PANGO2_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 (pango2_context_get_gravity (self->context))
+ {
+ default:
+ case PANGO2_GRAVITY_AUTO:
+ case PANGO2_GRAVITY_SOUTH:
+ break;
+ case PANGO2_GRAVITY_NORTH:
+ dir = PANGO2_DIRECTION_LTR + PANGO2_DIRECTION_RTL - dir;
+ break;
+ case PANGO2_GRAVITY_EAST:
+ dir = PANGO2_DIRECTION_LTR;
+ break;
+ case PANGO2_GRAVITY_WEST:
+ dir = PANGO2_DIRECTION_RTL;
+ break;
+ }
+
+ return dir;
+}
+
+static gboolean
+should_ellipsize_current_line (Pango2LineBreaker *self,
+ Pango2Line *line)
+{
+ return self->line_ellipsize != PANGO2_ELLIPSIZE_NONE && self->line_width >= 0;
+}
+
+static void
+get_decimal_prefix_width (Pango2Item *item,
+ Pango2GlyphString *glyphs,
+ const char *text,
+ gunichar decimal,
+ int *width,
+ gboolean *found)
+{
+ Pango2GlyphItem glyph_item = { item, glyphs, 0, 0, 0 };
+ int *log_widths;
+ int i;
+ const char *p;
+
+ log_widths = g_new (int, item->num_chars);
+
+ pango2_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
+pango2_line_compute_width (Pango2Line *line)
+{
+ int width = 0;
+
+ /* Compute the width of the line currently - inefficient, but easier
+ * than keeping the current width of the line up to date everywhere
+ */
+ for (GSList *l = line->runs; l; l = l->next)
+ {
+ Pango2GlyphItem *run = l->data;
+ width += pango2_glyph_string_get_width (run->glyphs);
+ }
+
+ return width;
+}
+
+static inline int
+get_line_width (Pango2LineBreaker *self,
+ Pango2Line *line)
+{
+ if (self->remaining_width > -1)
+ return self->line_width - self->remaining_width;
+
+ return pango2_line_compute_width (line);
+}
+
+static inline void
+ensure_decimal (Pango2LineBreaker *self)
+{
+ if (self->decimal == 0)
+ self->decimal = g_utf8_get_char (localeconv ()->decimal_point);
+}
+
+static void
+ensure_tab_width (Pango2LineBreaker *self)
+{
+ if (self->tab_width == -1)
+ {
+ /* Find out how wide 8 spaces are in the context's default
+ * font. Utter performance killer. :-(
+ */
+ Pango2GlyphString *glyphs = pango2_glyph_string_new ();
+ Pango2Item *item;
+ GList *items;
+ Pango2Attribute *attr;
+ Pango2AttrList *attrs;
+ Pango2AttrList tmp_attrs;
+ Pango2FontDescription *font_desc = pango2_font_description_copy_static (pango2_context_get_font_description (self->context));
+ Pango2Language *language = NULL;
+ Pango2ShapeFlags shape_flags = PANGO2_SHAPE_NONE;
+ Pango2Direction dir;
+
+ if (pango2_context_get_round_glyph_positions (self->context))
+ shape_flags |= PANGO2_SHAPE_ROUND_POSITIONS;
+
+ attrs = self->data->attrs;
+ if (attrs)
+ {
+ Pango2AttrIterator iter;
+
+ pango2_attr_list_init_iterator (attrs, &iter);
+ pango2_attr_iterator_get_font (&iter, font_desc, &language, NULL);
+ pango2_attr_iterator_clear (&iter);
+ }
+
+ pango2_attr_list_init (&tmp_attrs);
+ attr = pango2_attr_font_desc_new (font_desc);
+ pango2_font_description_free (font_desc);
+ pango2_attr_list_insert_before (&tmp_attrs, attr);
+
+ if (language)
+ {
+ attr = pango2_attr_language_new (language);
+ pango2_attr_list_insert_before (&tmp_attrs, attr);
+ }
+
+ dir = pango2_context_get_base_dir (self->context);
+ items = pango2_itemize (self->context, dir, " ", 0, 1, &tmp_attrs);
+
+ if (attrs != self->data->attrs)
+ {
+ pango2_attr_list_unref (attrs);
+ attrs = NULL;
+ }
+
+ pango2_attr_list_destroy (&tmp_attrs);
+
+ item = items->data;
+ pango2_shape (" ", 8, " ", 8, &item->analysis, glyphs, shape_flags);
+
+ pango2_item_free (item);
+ g_list_free (items);
+
+ self->tab_width = pango2_glyph_string_get_width (glyphs);
+
+ pango2_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 (self->tab_width <= 0)
+ self->tab_width = 50 * PANGO2_SCALE; /* pretty much arbitrary */
+ }
+}
+
+static int
+get_item_letter_spacing (Pango2Item *item)
+{
+ ItemProperties properties;
+
+ pango2_item_get_properties (item, &properties);
+
+ return properties.letter_spacing;
+}
+
+static void
+pad_glyphstring_right (Pango2LineBreaker *self,
+ Pango2GlyphString *glyphs,
+ int adjustment)
+{
+ int glyph = glyphs->num_glyphs - 1;
+
+ while (glyph >= 0 && glyphs->glyphs[glyph].geometry.width == 0)
+ glyph--;
+
+ if (glyph < 0)
+ return;
+
+ self->remaining_width -= adjustment;
+
+ glyphs->glyphs[glyph].geometry.width += adjustment;
+ if (glyphs->glyphs[glyph].geometry.width < 0)
+ {
+ self->remaining_width += glyphs->glyphs[glyph].geometry.width;
+ glyphs->glyphs[glyph].geometry.width = 0;
+ }
+}
+
+static void
+pad_glyphstring_left (Pango2LineBreaker *self,
+ Pango2GlyphString *glyphs,
+ int adjustment)
+{
+ int glyph = 0;
+
+ while (glyph < glyphs->num_glyphs && glyphs->glyphs[glyph].geometry.width == 0)
+ glyph++;
+
+ if (glyph == glyphs->num_glyphs)
+ return;
+
+ self->remaining_width -= adjustment;
+
+ glyphs->glyphs[glyph].geometry.width += adjustment;
+ glyphs->glyphs[glyph].geometry.x_offset += adjustment;
+}
+
+static gboolean
+is_tab_run (Pango2Line *line,
+ Pango2GlyphItem *run)
+{
+ return line->data->text[run->item->offset] == '\t';
+}
+
+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++)
+ {
+ Pango2GlyphItem *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++)
+ {
+ Pango2GlyphItem *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
+pango2_line_reorder (Pango2Line *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)
+ {
+ Pango2GlyphItem *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
+compute_n_chars (Pango2Line *line)
+{
+ int n_chars = 0;
+
+ for (GSList *l = line->runs; l; l = l->next)
+ {
+ Pango2GlyphItem *run = l->data;
+ n_chars += run->item->num_chars;
+ }
+
+ return n_chars;
+}
+
+/* }}} */
+/* {{{ Line Breaking */
+
+static void
+get_tab_pos (Pango2LineBreaker *self,
+ Pango2Line *line,
+ int index,
+ int *tab_pos,
+ Pango2TabAlign *alignment,
+ gunichar *decimal,
+ gboolean *is_default)
+{
+ int n_tabs;
+ gboolean in_pixels;
+ int offset = 0;
+
+ offset = self->line_x;
+
+ if (self->tabs)
+ {
+ n_tabs = pango2_tab_array_get_size (self->tabs);
+ in_pixels = pango2_tab_array_get_positions_in_pixels (self->tabs);
+ *is_default = FALSE;
+ }
+ else
+ {
+ n_tabs = 0;
+ in_pixels = FALSE;
+ *is_default = TRUE;
+ }
+
+ if (index < n_tabs)
+ {
+ pango2_tab_array_get_tab (self->tabs, index, alignment, tab_pos);
+
+ if (in_pixels)
+ *tab_pos *= PANGO2_SCALE;
+
+ *decimal = pango2_tab_array_get_decimal_point (self->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;
+
+ pango2_tab_array_get_tab (self->tabs, n_tabs - 1, alignment, &last_pos);
+ *decimal = pango2_tab_array_get_decimal_point (self->tabs, n_tabs - 1);
+
+ if (n_tabs > 1)
+ pango2_tab_array_get_tab (self->tabs, n_tabs - 2, NULL, &next_to_last_pos);
+ else
+ next_to_last_pos = 0;
+
+ if (in_pixels)
+ {
+ next_to_last_pos *= PANGO2_SCALE;
+ last_pos *= PANGO2_SCALE;
+ }
+
+ if (last_pos > next_to_last_pos)
+ tab_width = last_pos - next_to_last_pos;
+ else
+ tab_width = self->tab_width;
+
+ *tab_pos = last_pos + tab_width * (index - n_tabs + 1);
+ }
+ else
+ {
+ /* No tab array set, so use default tab width */
+ *tab_pos = self->tab_width * index;
+ *alignment = PANGO2_TAB_LEFT;
+ *decimal = 0;
+ }
+
+ *tab_pos -= offset;
+}
+
+static void
+shape_tab (Pango2LineBreaker *self,
+ Pango2Line *line,
+ int current_width,
+ Pango2Item *item,
+ Pango2GlyphString *glyphs)
+{
+ int i, space_width;
+ int tab_pos;
+ Pango2TabAlign tab_align;
+ gunichar tab_decimal;
+
+ pango2_glyph_string_set_size (glyphs, 1);
+
+ if (self->properties.showing_space)
+ glyphs->glyphs[0].glyph = PANGO2_GET_UNKNOWN_GLYPH ('\t');
+ else
+ glyphs->glyphs[0].glyph = PANGO2_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 (self);
+ space_width = self->tab_width / 8;
+
+ for (i = self->last_tab.index; ; i++)
+ {
+ gboolean is_default;
+
+ get_tab_pos (self, 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 line breaker, 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 (self);
+ tab_decimal = self->decimal;
+ }
+
+ self->last_tab.glyphs = glyphs;
+ self->last_tab.index = i;
+ self->last_tab.width = current_width;
+ self->last_tab.pos = tab_pos;
+ self->last_tab.align = tab_align;
+ self->last_tab.decimal = tab_decimal;
+}
+
+static inline gboolean
+can_break_at (Pango2LineBreaker *self,
+ int offset,
+ Pango2WrapMode wrap)
+{
+ if (offset == self->data->n_chars)
+ return TRUE;
+ else if (wrap == PANGO2_WRAP_CHAR)
+ return self->data->log_attrs[offset].is_char_break;
+ else
+ return self->data->log_attrs[offset].is_line_break;
+}
+
+static inline gboolean
+can_break_in (Pango2LineBreaker *self,
+ int start_offset,
+ int num_chars,
+ gboolean allow_break_at_start)
+{
+ for (int i = allow_break_at_start ? 0 : 1; i < num_chars; i++)
+ {
+ if (can_break_at (self, start_offset + i, self->line_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 & (PANGO2_SCALE - 1)) == 0)
+ *space_left = PANGO2_UNITS_ROUND (*space_left);
+ *space_right = letter_spacing - *space_left;
+}
+
+static void
+pango2_shape_shape (const char *text,
+ unsigned int n_chars,
+ ShapeData *shape,
+ Pango2GlyphString *glyphs)
+{
+ unsigned int i;
+ const char *p;
+
+ pango2_glyph_string_set_size (glyphs, n_chars);
+
+ for (i = 0, p = text; i < n_chars; i++, p = g_utf8_next_char (p))
+ {
+ glyphs->glyphs[i].glyph = PANGO2_GLYPH_EMPTY;
+ glyphs->glyphs[i].geometry.x_offset = 0;
+ glyphs->glyphs[i].geometry.y_offset = 0;
+ glyphs->glyphs[i].geometry.width = shape->logical_rect.width;
+ glyphs->glyphs[i].attr.is_cluster_start = 1;
+
+ glyphs->log_clusters[i] = p - text;
+ }
+}
+
+static Pango2GlyphString *
+shape_run (Pango2LineBreaker *self,
+ Pango2Line *line,
+ Pango2Item *item)
+{
+ Pango2GlyphString *glyphs = pango2_glyph_string_new ();
+
+ if (self->data->text[item->offset] == '\t')
+ shape_tab (self, line, get_line_width (self, line), item, glyphs);
+ else
+ {
+ Pango2ShapeFlags shape_flags = PANGO2_SHAPE_NONE;
+
+ if (pango2_context_get_round_glyph_positions (self->context))
+ shape_flags |= PANGO2_SHAPE_ROUND_POSITIONS;
+
+ if (self->properties.shape)
+ pango2_shape_shape (self->data->text + item->offset, item->num_chars,
+ (ShapeData *)self->properties.shape->pointer_value,
+ glyphs);
+ else
+ pango2_shape_item (item,
+ self->data->text, self->data->length,
+ self->data->log_attrs + self->start_offset,
+ glyphs,
+ shape_flags);
+
+ if (self->properties.letter_spacing)
+ {
+ Pango2GlyphItem glyph_item;
+ int space_left, space_right;
+
+ glyph_item.item = item;
+ glyph_item.glyphs = glyphs;
+
+ pango2_glyph_item_letter_space (&glyph_item,
+ self->data->text,
+ self->data->log_attrs + self->start_offset,
+ self->properties.letter_spacing);
+
+ distribute_letter_spacing (self->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 (self->last_tab.glyphs != NULL)
+ {
+ int w;
+
+ g_assert (self->last_tab.glyphs->num_glyphs == 1);
+
+ /* Update the width of the current tab to position this run properly */
+
+ w = self->last_tab.pos - self->last_tab.width;
+
+ if (self->last_tab.align == PANGO2_TAB_RIGHT)
+ w -= pango2_glyph_string_get_width (glyphs);
+ else if (self->last_tab.align == PANGO2_TAB_CENTER)
+ w -= pango2_glyph_string_get_width (glyphs) / 2;
+ else if (self->last_tab.align == PANGO2_TAB_DECIMAL)
+ {
+ int width;
+ gboolean found;
+
+ get_decimal_prefix_width (item, glyphs, self->data->text, self->last_tab.decimal, &width, &found);
+
+ w -= width;
+ }
+
+ self->last_tab.glyphs->glyphs[0].geometry.width = MAX (w, 0);
+ }
+ }
+
+ return glyphs;
+}
+
+static void
+free_run (Pango2GlyphItem *run,
+ gpointer data)
+{
+ gboolean free_item = data != NULL;
+ if (free_item)
+ pango2_item_free (run->item);
+
+ pango2_glyph_string_free (run->glyphs);
+ g_slice_free (Pango2GlyphItem, run);
+}
+
+static Pango2Item *
+uninsert_run (Pango2Line *line)
+{
+ Pango2GlyphItem *run;
+ Pango2Item *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, NULL);
+
+ return item;
+}
+
+static void
+insert_run (Pango2LineBreaker *self,
+ Pango2Line *line,
+ Pango2Item *run_item,
+ Pango2GlyphString *glyphs,
+ gboolean last_run)
+{
+ Pango2GlyphItem *run = g_slice_new (Pango2GlyphItem);
+
+ run->item = run_item;
+
+ if (glyphs)
+ run->glyphs = glyphs;
+ else if (last_run &&
+ self->log_widths_offset == 0 &&
+ !(run_item->analysis.flags & PANGO2_ANALYSIS_FLAG_NEED_HYPHEN))
+ {
+ run->glyphs = self->glyphs;
+ self->glyphs = NULL;
+ }
+ else
+ run->glyphs = shape_run (self, line, run_item);
+
+ if (last_run && self->glyphs)
+ {
+ pango2_glyph_string_free (self->glyphs);
+ self->glyphs = NULL;
+ }
+
+ line->runs = g_slist_prepend (line->runs, run);
+ line->length += run_item->length;
+
+ if (self->last_tab.glyphs && run->glyphs != self->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 (self->last_tab.align == PANGO2_TAB_RIGHT)
+ self->last_tab.width += pango2_glyph_string_get_width (run->glyphs);
+ else if (self->last_tab.align == PANGO2_TAB_CENTER)
+ self->last_tab.width += pango2_glyph_string_get_width (run->glyphs) / 2;
+ else if (self->last_tab.align == PANGO2_TAB_DECIMAL)
+ {
+ int width;
+
+ get_decimal_prefix_width (run->item, run->glyphs, line->data->text, self->last_tab.decimal, &width, &found_decimal);
+
+ self->last_tab.width += width;
+ }
+
+ width = MAX (self->last_tab.pos - self->last_tab.width, 0);
+ self->last_tab.glyphs->glyphs[0].geometry.width = width;
+
+ if (found_decimal || width == 0)
+ self->last_tab.glyphs = NULL;
+ }
+}
+
+static gboolean
+break_needs_hyphen (Pango2LineBreaker *self,
+ int pos)
+{
+ return self->data->log_attrs[self->start_offset + pos].break_inserts_hyphen ||
+ self->data->log_attrs[self->start_offset + pos].break_removes_preceding;
+}
+
+
+static int
+find_hyphen_width (Pango2Item *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 = pango2_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 (Pango2LineBreaker *self)
+{
+ if (self ->hyphen_width < 0)
+ {
+ Pango2Item *item = self->items->data;
+ self->hyphen_width = find_hyphen_width (item);
+ }
+}
+
+static int
+find_break_extra_width (Pango2LineBreaker *self,
+ 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 (self->data->log_attrs[self->start_offset + pos].break_inserts_hyphen)
+ {
+ ensure_hyphen_width (self);
+
+ if (self->data->log_attrs[self->start_offset + pos].break_removes_preceding && pos > 0)
+ return self->hyphen_width - self->log_widths[self->log_widths_offset + pos - 1];
+ else
+ return self->hyphen_width;
+ }
+ else if (pos > 0 &&
+ self->data->log_attrs[self->start_offset + pos - 1].is_white)
+ {
+ return - self->log_widths[self->log_widths_offset + pos - 1];
+ }
+
+ return 0;
+}
+
+static inline void
+compute_log_widths (Pango2LineBreaker *self)
+{
+ Pango2Item *item = self->items->data;
+ Pango2GlyphItem glyph_item = { item, self->glyphs };
+
+ if (item->num_chars > self->num_log_widths)
+ {
+ self->log_widths = g_renew (int, self->log_widths, item->num_chars);
+ self->num_log_widths = item->num_chars;
+ }
+
+ g_assert (self->log_widths_offset == 0);
+ pango2_glyph_item_get_logical_widths (&glyph_item, self->data->text, self->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 (Pango2LineBreaker *self)
+{
+ if (self->last_tab.glyphs)
+ return self->last_tab.glyphs->glyphs[0].geometry.width - (self->last_tab.pos - self->last_tab.width);
+
+ return 0;
+}
+
+typedef enum
+{
+ BREAK_NONE_FIT,
+ BREAK_SOME_FIT,
+ BREAK_ALL_FIT,
+ BREAK_EMPTY_FIT,
+ BREAK_LINE_SEPARATOR,
+ BREAK_PARAGRAPH_SEPARATOR
+} BreakResult;
+
+/* Tries to insert as much as possible of the item at the head of
+ * self->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.
+ * %BREAK_PARAGRAPH_SEPARATOR: Item begins with a paragraph 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
+ *
+ * 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
+ *
+ * if bc does not fit and we can loosen break conditions
+ * loosen break conditions and retry break
+ *
+ * return bc
+ */
+
+static BreakResult
+process_item (Pango2LineBreaker *self,
+ Pango2Line *line,
+ gboolean force_fit,
+ gboolean no_break_at_end,
+ gboolean is_last_item)
+{
+ Pango2Item *item = self->items->data;
+ int width;
+ int extra_width;
+ int orig_extra_width;
+ int length;
+ int i;
+ int processing_new_item;
+ int num_chars;
+ int orig_width;
+ Pango2WrapMode wrap;
+ int break_num_chars;
+ int break_width;
+ int break_extra_width;
+ Pango2GlyphString *break_glyphs;
+ Pango2FontMetrics *metrics;
+ int safe_distance;
+
+ DEBUG1 ("process item '%.*s'. Remaining width %d",
+ item->length, self->data->text + item->offset,
+ self->remaining_width);
+
+ /* We don't want to shape more than necessary, so we keep the results
+ * of shaping a new item in self->glyphs, self->log_widths. Once
+ * we break off initial parts of the item, we update self->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 self->log_widths_offset != 0 to detect if we are dealing
+ * with the original item, or one that has been chopped off.
+ */
+ if (!self->glyphs)
+ {
+ pango2_item_get_properties (item, &self->properties);
+ self->glyphs = shape_run (self, line, item);
+ self->log_widths_offset = 0;
+ processing_new_item = TRUE;
+ }
+ else
+ processing_new_item = FALSE;
+
+ if (item_is_paragraph_separator (self, item))
+ {
+ g_clear_pointer (&self->glyphs, pango2_glyph_string_free);
+ return BREAK_PARAGRAPH_SEPARATOR;
+ }
+
+ /* Only one character has type G_UNICODE_LINE_SEPARATOR in Unicode 5.0;
+ * update this if that changes.
+ */
+#define LINE_SEPARATOR 0x2028
+
+ if (g_utf8_get_char (self->data->text + item->offset) == LINE_SEPARATOR &&
+ !should_ellipsize_current_line (self, line))
+ {
+ insert_run (self, line, item, NULL, TRUE);
+ self->log_widths_offset += item->num_chars;
+
+ return BREAK_LINE_SEPARATOR;
+ }
+
+ if (self->remaining_width < 0 && !no_break_at_end) /* Wrapping off */
+ {
+ insert_run (self, line, item, NULL, TRUE);
+ DEBUG1 ("no wrapping, all-fit");
+ return BREAK_ALL_FIT;
+ }
+
+ if (processing_new_item)
+ {
+ compute_log_widths (self);
+ processing_new_item = FALSE;
+ }
+
+ width = 0;
+ g_assert (self->log_widths_offset + item->num_chars <= self->num_log_widths);
+ for (i = 0; i < item->num_chars; i++)
+ width += self->log_widths[self->log_widths_offset + i];
+
+ if (self->data->text[item->offset] == '\t')
+ {
+ insert_run (self, line, item, NULL, TRUE);
+ self->remaining_width -= width;
+ self->remaining_width = MAX (self->remaining_width, 0);
+
+ DEBUG1 ("tab run, all-fit");
+ return BREAK_ALL_FIT;
+ }
+
+ wrap = self->line_wrap;
+ if (!no_break_at_end &&
+ can_break_at (self, self->start_offset + item->num_chars, wrap))
+ {
+ extra_width = find_break_extra_width (self, item->num_chars);
+ }
+ else
+ extra_width = 0;
+
+ if ((width + extra_width <= self->remaining_width || (item->num_chars == 1 && !line->runs) ||
+ (self->last_tab.glyphs && self->last_tab.align != PANGO2_TAB_LEFT)) &&
+ !no_break_at_end)
+ {
+ Pango2GlyphString *glyphs;
+
+ DEBUG1 ("%d + %d <= %d", width, extra_width, self->remaining_width);
+ glyphs = shape_run (self, line, item);
+
+ width = pango2_glyph_string_get_width (glyphs) + tab_width_change (self);
+
+ if (width + extra_width <= self->remaining_width || (item->num_chars == 1 && !line->runs))
+ {
+ insert_run (self, line, item, glyphs, TRUE);
+
+ self->remaining_width -= width;
+ self->remaining_width = MAX (self->remaining_width, 0);
+
+ DEBUG1 ("early accept '%.*s', all-fit, remaining %d",
+ item->length, self->data->text + item->offset,
+ self->remaining_width);
+ return BREAK_ALL_FIT;
+ }
+
+ /* if it doesn't fit after shaping, discard and proceed to break the item */
+ pango2_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 = self->line_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 = pango2_font_get_metrics (item->analysis.font, item->analysis.language);
+ safe_distance = pango2_font_metrics_get_approximate_char_width (metrics) * 3;
+ pango2_font_metrics_free (metrics);
+
+ if (processing_new_item)
+ {
+ compute_log_widths (self);
+ 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 (self, 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) > self->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, self->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 (self, self->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 < self->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;
+ Pango2Item *new_item;
+ Pango2GlyphString *glyphs;
+
+ length = g_utf8_offset_to_pointer (self->data->text + item->offset, num_chars) - (self->data->text + item->offset);
+
+ if (num_chars < item->num_chars)
+ {
+ new_item = pango2_item_split (item, length, num_chars);
+
+ if (break_needs_hyphen (self, num_chars))
+ new_item->analysis.flags |= PANGO2_ANALYSIS_FLAG_NEED_HYPHEN;
+ else
+ new_item->analysis.flags &= ~PANGO2_ANALYSIS_FLAG_NEED_HYPHEN;
+ }
+ else
+ new_item = item;
+
+ glyphs = shape_run (self, line, new_item);
+
+ new_break_width = pango2_glyph_string_get_width (glyphs) + tab_width_change (self);
+
+ if (num_chars > 0 &&
+ (item != new_item || !is_last_item) && /* We don't collapse space at the very end */
+ self->data->log_attrs[self->start_offset + num_chars - 1].is_white)
+ extra_width = - self->log_widths[self->log_widths_offset + num_chars - 1];
+ else if (item == new_item && !is_last_item &&
+ break_needs_hyphen (self, num_chars))
+ extra_width = self->hyphen_width;
+ else
+ extra_width = 0;
+
+ DEBUG1 ("measured breakpoint %d: %d, extra %d", num_chars, new_break_width, extra_width);
+
+ if (new_item != item)
+ {
+ pango2_item_free (new_item);
+ pango2_item_unsplit (item, length, num_chars);
+ }
+
+ if (break_num_chars == item->num_chars ||
+ new_break_width + extra_width <= self->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)
+ pango2_glyph_string_free (break_glyphs);
+ break_glyphs = glyphs;
+ }
+ else
+ {
+ DEBUG1 ("ignore breakpoint %d", num_chars);
+ pango2_glyph_string_free (glyphs);
+ }
+ }
+ }
+
+ DEBUG1 ("bp now %d", break_num_chars);
+ if (num_chars < item->num_chars)
+ width += self->log_widths[self->log_widths_offset + num_chars];
+ }
+
+ if (wrap == PANGO2_WRAP_WORD_CHAR &&
+ force_fit &&
+ break_width + break_extra_width > self->remaining_width)
+ {
+ /* Try again, with looser conditions */
+ DEBUG1 ("does not fit, try again with wrap-char");
+ wrap = PANGO2_WRAP_CHAR;
+ break_num_chars = item->num_chars;
+ break_width = orig_width;
+ break_extra_width = orig_extra_width;
+ if (break_glyphs)
+ pango2_glyph_string_free (break_glyphs);
+ break_glyphs = NULL;
+ goto retry_break;
+ }
+
+ if (force_fit || break_width + break_extra_width <= self->remaining_width) /* Successfully broke the item */
+ {
+ if (self->remaining_width >= 0)
+ {
+ self->remaining_width -= break_width + break_extra_width;
+ self->remaining_width = MAX (self->remaining_width, 0);
+ }
+
+ if (break_num_chars == item->num_chars)
+ {
+ if (can_break_at (self, self->start_offset + break_num_chars, wrap) &&
+ break_needs_hyphen (self, break_num_chars))
+ item->analysis.flags |= PANGO2_ANALYSIS_FLAG_NEED_HYPHEN;
+
+ insert_run (self, line, item, NULL, TRUE);
+
+ if (break_glyphs)
+ pango2_glyph_string_free (break_glyphs);
+
+ DEBUG1 ("all-fit '%.*s', remaining %d",
+ item->length, self->data->text + item->offset,
+ self->remaining_width);
+ return BREAK_ALL_FIT;
+ }
+ else if (break_num_chars == 0)
+ {
+ if (break_glyphs)
+ pango2_glyph_string_free (break_glyphs);
+
+ DEBUG1 ("empty-fit, remaining %d", self->remaining_width);
+ return BREAK_EMPTY_FIT;
+ }
+ else
+ {
+ Pango2Item *new_item;
+
+ length = g_utf8_offset_to_pointer (self->data->text + item->offset, break_num_chars) - (self->data->text + item->offset);
+
+ new_item = pango2_item_split (item, length, break_num_chars);
+
+ insert_run (self, line, new_item, break_glyphs, FALSE);
+
+ self->log_widths_offset += break_num_chars;
+
+ DEBUG1 ("some-fit '%.*s', remaining %d",
+ new_item->length, self->data->text + new_item->offset,
+ self->remaining_width);
+ return BREAK_SOME_FIT;
+ }
+ }
+ else
+ {
+ pango2_glyph_string_free (self->glyphs);
+ self->glyphs = NULL;
+
+ if (break_glyphs)
+ pango2_glyph_string_free (break_glyphs);
+
+ DEBUG1 ("none-fit, remaining %d", self->remaining_width);
+ return BREAK_NONE_FIT;
+ }
+}
+
+static void
+process_line (Pango2LineBreaker *self,
+ Pango2Line *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;
+
+ while (self->items)
+ {
+ Pango2Item *item = self->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 = self->remaining_width;
+ first_item_in_line = line->runs == NULL;
+ last_item_in_line = self->items->next == NULL;
+
+ result = process_item (self, line, !have_break, FALSE, last_item_in_line);
+
+ switch (result)
+ {
+ case BREAK_ALL_FIT:
+ if (self->data->text[item->offset] != '\t' &&
+ can_break_in (self, self->start_offset, old_num_chars, !first_item_in_line))
+ {
+ have_break = TRUE;
+ break_remaining_width = old_remaining_width;
+ break_start_offset = self->start_offset;
+ break_link = line->runs->next;
+ }
+
+ self->items = g_list_delete_link (self->items, self->items);
+ self->start_offset += old_num_chars;
+ break;
+
+ case BREAK_EMPTY_FIT:
+ wrapped = TRUE;
+ goto done;
+
+ case BREAK_SOME_FIT:
+ self->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)
+ {
+ Pango2GlyphItem *run = line->runs->data;
+
+ /* Reset tab stat if we uninsert the current tab run */
+ if (run->glyphs == self->last_tab.glyphs)
+ {
+ self->last_tab.glyphs = NULL;
+ self->last_tab.index = 0;
+ self->last_tab.align = PANGO2_TAB_LEFT;
+ }
+
+ self->items = g_list_prepend (self->items, uninsert_run (line));
+ }
+
+ self->start_offset = break_start_offset;
+ self->remaining_width = break_remaining_width;
+ last_item_in_line = self->items->next == NULL;
+
+ /* Reshape run to break */
+ item = self->items->data;
+
+ old_num_chars = item->num_chars;
+ result = process_item (self, line, TRUE, TRUE, last_item_in_line);
+ g_assert (result == BREAK_SOME_FIT || result == BREAK_EMPTY_FIT);
+
+ self->start_offset += old_num_chars - item->num_chars;
+
+ wrapped = TRUE;
+ goto done;
+
+ case BREAK_LINE_SEPARATOR:
+ self->items = g_list_delete_link (self->items, self->items);
+ self->start_offset += old_num_chars;
+ /* A line-separate is just a forced break. Set wrapped, so we justify */
+ wrapped = TRUE;
+ goto done;
+
+ case BREAK_PARAGRAPH_SEPARATOR:
+ /* We don't add the item as a run, so don't add to
+ * line->length or line->n_chars here.
+ * But we still need the next line to start after
+ * the terminators, so add to self->line_start_index
+ */
+ line->ends_paragraph = TRUE;
+ self->line_start_index += item->length;
+ self->start_offset += item->num_chars;
+ self->items = g_list_delete_link (self->items, self->items);
+ pango2_item_free (item);
+ goto done;
+
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+done:
+ line->wrapped = wrapped;
+}
+
+/* {{{ Post-processing */
+
+static void
+add_missing_hyphen (Pango2LineBreaker *self,
+ Pango2Line *line)
+{
+ Pango2GlyphItem *run;
+ Pango2Item *item;
+
+ if (!line->runs)
+ return;
+
+ run = line->runs->data;
+ item = run->item;
+
+ if (self->data->log_attrs[self->line_start_offset + line->n_chars].break_inserts_hyphen &&
+ !(item->analysis.flags & PANGO2_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 = pango2_glyph_string_get_width (run->glyphs);
+
+ /* Ugly, shape_run uses self->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 = self->start_offset;
+ self->start_offset = self->line_start_offset + line->n_chars - item->num_chars;
+
+ pango2_glyph_string_free (run->glyphs);
+ item->analysis.flags |= PANGO2_ANALYSIS_FLAG_NEED_HYPHEN;
+ run->glyphs = shape_run (self, line, item);
+
+ self->start_offset = start_offset;
+
+ self->remaining_width += pango2_glyph_string_get_width (run->glyphs) - width;
+ }
+
+ line->hyphenated = (item->analysis.flags & PANGO2_ANALYSIS_FLAG_NEED_HYPHEN) != 0;
+}
+
+static void
+zero_line_final_space (Pango2LineBreaker *self,
+ Pango2Line *line)
+{
+ Pango2GlyphItem *run;
+ Pango2Item *item;
+ Pango2GlyphString *glyphs;
+ int glyph;
+
+ if (!line->runs)
+ return;
+
+ run = line->runs->data;
+ item = run->item;
+
+ glyphs = run->glyphs;
+ glyph = item->analysis.level % 2 ? 0 : glyphs->num_glyphs - 1;
+
+ if (glyphs->glyphs[glyph].glyph == PANGO2_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 || self->start_offset == 0 ||
+ !self->data->log_attrs[self->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 = PANGO2_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.
+ *
+ * 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.
+ */
+static void
+adjust_line_letter_spacing (Pango2LineBreaker *self,
+ Pango2Line *line)
+{
+ gboolean reversed;
+ Pango2GlyphItem *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->direction == PANGO2_DIRECTION_RTL)
+ {
+ for (l = line->runs; l; l = l->next)
+ if (is_tab_run (line, 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)
+ {
+ Pango2GlyphItem *run = l->data;
+ Pango2GlyphItem *next_run = l->next ? l->next->data : NULL;
+
+ if (is_tab_run (line, run))
+ {
+ pad_glyphstring_right (self, run->glyphs, tab_adjustment);
+ tab_adjustment = 0;
+ }
+ else
+ {
+ Pango2GlyphItem *visual_next_run = reversed ? last_run : next_run;
+ Pango2GlyphItem *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 (self, run->glyphs, - space_left);
+ }
+ else if (!visual_last_run || is_tab_run (line, visual_last_run))
+ {
+ pad_glyphstring_left (self, run->glyphs, - 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 (self, run->glyphs, - space_right);
+ }
+
+ else if (!visual_next_run || is_tab_run (line, visual_next_run))
+ {
+ pad_glyphstring_right (self, run->glyphs, - space_right);
+ tab_adjustment += space_right;
+ }
+ }
+
+ last_run = run;
+ }
+
+ if (reversed)
+ line->runs = g_slist_reverse (line->runs);
+}
+
+typedef struct {
+ Pango2Attribute *attr;
+ int x_offset;
+ int y_offset;
+} BaselineItem;
+
+static void
+collect_baseline_shift (Pango2LineBreaker *self,
+ Pango2Item *item,
+ Pango2Item *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)
+ {
+ Pango2Attribute *attr = l->data;
+
+ if (attr->type == PANGO2_ATTR_RISE)
+ {
+ int value = attr->int_value;
+
+ *start_y_offset += value;
+ *end_y_offset -= value;
+ }
+ else if (attr->type == PANGO2_ATTR_BASELINE_SHIFT)
+ {
+ if (attr->start_index == item->offset)
+ {
+ BaselineItem *entry;
+ int value;
+
+ entry = g_new0 (BaselineItem, 1);
+ entry->attr = attr;
+ self->baseline_shifts = g_list_prepend (self->baseline_shifts, entry);
+
+ value = attr->int_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 = pango2_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 PANGO2_BASELINE_SHIFT_NONE:
+ entry->x_offset = 0;
+ entry->y_offset = 0;
+ break;
+ case PANGO2_BASELINE_SHIFT_SUPERSCRIPT:
+ entry->x_offset = superscript_x_offset;
+ entry->y_offset = superscript_y_offset;
+ break;
+ case PANGO2_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 = self->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 &&
+ attr->int_value == entry->attr->int_value)
+ {
+ *end_x_offset -= entry->x_offset;
+ *end_y_offset -= entry->y_offset;
+ }
+
+ self->baseline_shifts = g_list_remove (self->baseline_shifts, entry);
+ g_free (entry);
+ break;
+ }
+ if (t == NULL)
+ g_warning ("Baseline attributes mismatch\n");
+ }
+ }
+ }
+}
+
+static void
+apply_baseline_shift (Pango2LineBreaker *self,
+ Pango2Line *line)
+{
+ int y_offset = 0;
+ Pango2Item *prev = NULL;
+ hb_position_t baseline_adjustment = 0;
+#if HB_VERSION_ATLEAST(4,0,0)
+ hb_ot_layout_baseline_tag_t baseline_tag = 0;
+ hb_position_t baseline;
+ hb_position_t run_baseline;
+#endif
+
+ for (GSList *l = line->runs; l; l = l->next)
+ {
+ Pango2GlyphItem *run = l->data;
+ Pango2Item *item = run->item;
+ int start_x_offset, end_x_offset;
+ int start_y_offset, end_y_offset;
+#if HB_VERSION_ATLEAST(4,0,0)
+ hb_font_t *hb_font;
+ hb_script_t script;
+ hb_language_t language;
+ hb_direction_t direction;
+ hb_tag_t script_tags[HB_OT_MAX_TAGS_PER_SCRIPT];
+ hb_tag_t lang_tags[HB_OT_MAX_TAGS_PER_LANGUAGE];
+ unsigned int script_count = HB_OT_MAX_TAGS_PER_SCRIPT;
+ unsigned int lang_count = HB_OT_MAX_TAGS_PER_LANGUAGE;
+#endif
+
+ if (item->analysis.font == NULL)
+ continue;
+
+#if HB_VERSION_ATLEAST(4,0,0)
+ hb_font = pango2_font_get_hb_font (item->analysis.font);
+
+ script = (hb_script_t) g_unicode_script_to_iso15924 (item->analysis.script);
+ language = hb_language_from_string (pango2_language_to_string (item->analysis.language), -1);
+ hb_ot_tags_from_script_and_language (script, language,
+ &script_count, script_tags,
+ &lang_count, lang_tags);
+
+ if (item->analysis.flags & PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE)
+ direction = HB_DIRECTION_TTB;
+ else
+ direction = HB_DIRECTION_LTR;
+
+ if (baseline_tag == 0)
+ {
+ if (item->analysis.flags & PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE)
+ baseline_tag = HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_CENTRAL;
+ else
+ baseline_tag = hb_ot_layout_get_horizontal_baseline_tag_for_script (script);
+ hb_ot_layout_get_baseline_with_fallback (hb_font,
+ baseline_tag,
+ direction,
+ script_tags[script_count - 1],
+ lang_count ? lang_tags[lang_count - 1] : HB_TAG_NONE,
+ &run_baseline);
+ baseline = run_baseline;
+ }
+ else
+ {
+ hb_ot_layout_get_baseline_with_fallback (hb_font,
+ baseline_tag,
+ direction,
+ script_tags[script_count - 1],
+ lang_count ? lang_tags[lang_count - 1] : HB_TAG_NONE,
+ &run_baseline);
+ }
+
+ /* Don't do baseline adjustment in vertical, since the renderer
+ * is still doing its own baseline shifting there
+ */
+ if (item->analysis.flags & PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE)
+ baseline_adjustment = 0;
+ else
+ baseline_adjustment = baseline - run_baseline;
+#endif
+
+ collect_baseline_shift (self, item, prev, &start_x_offset, &start_y_offset, &end_x_offset, &end_y_offset);
+
+ y_offset += start_y_offset + baseline_adjustment;
+
+ run->y_offset = y_offset;
+ run->start_x_offset = start_x_offset;
+ run->end_x_offset = end_x_offset;
+
+ y_offset += end_y_offset - baseline_adjustment;
+
+ prev = item;
+ }
+}
+
+static void
+apply_render_attributes (Pango2LineBreaker *self,
+ Pango2Line *line)
+{
+ GSList *runs;
+
+ if (!self->render_attrs)
+ return;
+
+ runs = g_slist_reverse (line->runs);
+ line->runs = NULL;
+
+ for (GSList *l = runs; l; l = l->next)
+ {
+ Pango2GlyphItem *glyph_item = l->data;
+ GSList *new_runs;
+
+ new_runs = pango2_glyph_item_apply_attrs (glyph_item,
+ line->data->text,
+ self->render_attrs);
+
+ line->runs = g_slist_concat (new_runs, line->runs);
+ }
+
+ g_slist_free (runs);
+}
+
+static void
+postprocess_line (Pango2LineBreaker *self,
+ Pango2Line *line)
+{
+ add_missing_hyphen (self, line);
+
+ /* Truncate the logical-final whitespace in the line if we broke the line at it */
+ if (line->wrapped)
+ zero_line_final_space (self, line);
+
+ line->runs = g_slist_reverse (line->runs);
+
+ apply_baseline_shift (self, line);
+
+ if (should_ellipsize_current_line (self, line))
+ pango2_line_ellipsize (line, self->context, self->line_ellipsize, self->line_width);
+
+ /* Now convert logical to visual order */
+ pango2_line_reorder (line);
+
+ /* Fixup letter spacing between runs */
+ adjust_line_letter_spacing (self, line);
+
+ apply_render_attributes (self, line);
+}
+
+/* }}} */
+/* }}} */
+/* {{{ Pango2LineBreaker implementation */
+
+G_DEFINE_FINAL_TYPE (Pango2LineBreaker, pango2_line_breaker, G_TYPE_OBJECT)
+
+enum {
+ PROP_CONTEXT = 1,
+ PROP_BASE_DIR,
+ PROP_TABS,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES];
+
+static void
+pango2_line_breaker_init (Pango2LineBreaker *self)
+{
+ self->tabs = NULL;
+ self->tab_width = -1;
+ self->decimal = 0;
+ self->n_lines = 0;
+}
+
+static void
+pango2_line_breaker_finalize (GObject *object)
+{
+ Pango2LineBreaker *self = PANGO2_LINE_BREAKER (object);
+
+ g_list_free_full (self->baseline_shifts, g_free);
+ g_clear_pointer (&self->glyphs, pango2_glyph_string_free);
+ g_clear_pointer (&self->log_widths, g_free);
+ g_list_free_full (self->items, (GDestroyNotify) pango2_item_free);
+ g_clear_pointer (&self->data, line_data_unref);
+ g_list_free_full (self->data_items, (GDestroyNotify) pango2_item_free);
+ g_clear_pointer (&self->render_attrs, pango2_attr_list_unref);
+ g_slist_free_full (self->datas, (GDestroyNotify) line_data_unref);
+ g_clear_pointer (&self->tabs, pango2_tab_array_free);
+ g_object_unref (self->context);
+
+ G_OBJECT_CLASS (pango2_line_breaker_parent_class)->finalize (object);
+}
+
+static void
+pango2_line_breaker_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2LineBreaker *self = PANGO2_LINE_BREAKER (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ g_assert (self->context == NULL);
+ self->context = g_value_dup_object (value);
+ break;
+
+ case PROP_BASE_DIR:
+ pango2_line_breaker_set_base_dir (self, g_value_get_enum (value));
+ break;
+
+ case PROP_TABS:
+ pango2_line_breaker_set_tabs (self, g_value_get_boxed (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pango2_line_breaker_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2LineBreaker *self = PANGO2_LINE_BREAKER (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ g_value_set_object (value, self->context);
+ break;
+
+ case PROP_BASE_DIR:
+ g_value_set_enum (value, pango2_line_breaker_get_base_dir (self));
+ break;
+
+ case PROP_TABS:
+ g_value_set_boxed (value, pango2_line_breaker_get_tabs (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pango2_line_breaker_class_init (Pango2LineBreakerClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = pango2_line_breaker_finalize;
+ object_class->set_property = pango2_line_breaker_set_property;
+ object_class->get_property = pango2_line_breaker_get_property;
+
+ /**
+ * Pango2LineBreaker:context: (attributes org.gtk.Property.get=pango2_line_breaker_get_context)
+ *
+ * The context for the `Pango2LineBreaker`.
+ */
+ properties[PROP_CONTEXT] =
+ g_param_spec_object ("context", "context", "context",
+ PANGO2_TYPE_CONTEXT,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * Pango2LineBreaker:base-dir: (attributes org.gtk.Property.get=pango2_line_breaker_get_base_dir org.gtk.Property.set=pango2_line_breaker_set_base_dir)
+ *
+ * The base direction for the `Pango2LineBreaker`.
+ *
+ * The default value is `PANGO2_DIRECTION_NEUTRAL`.
+ */
+ properties[PROP_BASE_DIR] =
+ g_param_spec_enum ("base-dir", "base-dir", "base-dir",
+ PANGO2_TYPE_DIRECTION,
+ PANGO2_DIRECTION_NEUTRAL,
+ G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * Pango2LineBreaker:tabs: (attributes org.gtk.Property.get=pango2_line_breaker_get_tabs org.gtk.Property.set=pango2_line_breaker_set_tabs)
+ *
+ * The tabs to use when formatting the next line of the `Pango2LineBreaker`.
+ *
+ * `Pango2LineBreaker` will place content at the next tab position
+ * whenever it meets a Tab character (U+0009).
+ */
+ properties[PROP_TABS] =
+ g_param_spec_boxed ("tabs", "tabs", "tabs",
+ PANGO2_TYPE_TAB_ARRAY,
+ G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_line_breaker_new:
+ * @context: a `Pango2Context`
+ *
+ * Creates a new `Pango2LineBreaker`.
+ *
+ * Returns: a newly created `Pango2LineBreaker`
+ */
+Pango2LineBreaker *
+pango2_line_breaker_new (Pango2Context *context)
+{
+ g_return_val_if_fail (PANGO2_IS_CONTEXT (context), NULL);
+
+ return g_object_new (PANGO2_TYPE_LINE_BREAKER, "context", context, NULL);
+}
+
+/**
+ * pango2_line_breaker_get_context:
+ * @self: a `Pango2LineBreaker`
+ *
+ * Retrieves the context used for the `Pango2LineBreaker`.
+ *
+ * Returns: (transfer none): the `Pango2Context` for @self
+ */
+Pango2Context *
+pango2_line_breaker_get_context (Pango2LineBreaker *self)
+{
+ g_return_val_if_fail (PANGO2_IS_LINE_BREAKER (self), NULL);
+
+ return self->context;
+}
+
+/**
+ * pango2_line_breaker_set_tabs:
+ * @self: a `Pango2LineBreaker`
+ * @tabs: (nullable): a `Pango2TabArray`
+ *
+ * Sets the tab positions to use for lines.
+ *
+ * `Pango2LineBreaker` 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 by @self, 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
+ * %PANGO2_ALIGNMENT_LEFT.
+ */
+void
+pango2_line_breaker_set_tabs (Pango2LineBreaker *self,
+ Pango2TabArray *tabs)
+{
+ g_return_if_fail (PANGO2_IS_LINE_BREAKER (self));
+
+ if (self->tabs)
+ {
+ pango2_tab_array_free (self->tabs);
+ self->tabs = NULL;
+ }
+
+ if (tabs)
+ {
+ self->tabs = pango2_tab_array_copy (tabs);
+ pango2_tab_array_sort (self->tabs);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TABS]);
+}
+
+/**
+ * pango2_line_breaker_get_tabs:
+ * @self: a `Pango2LineBreaker`
+ *
+ * Gets the current `Pango2TabArray` used by the `Pango2LineBreaker`.
+ *
+ * If no `Pango2TabArray` has been set, then the default tabs are
+ * in use and %NULL is returned. Default tabs are every 8 spaces.
+ *
+ * Return value: (transfer none) (nullable): the tabs for @self
+ */
+Pango2TabArray *
+pango2_line_breaker_get_tabs (Pango2LineBreaker *self)
+{
+ g_return_val_if_fail (PANGO2_IS_LINE_BREAKER (self), NULL);
+
+ if (self->tabs)
+ return self->tabs;
+ else
+ return NULL;
+}
+
+/**
+ * pango2_line_breaker_set_base_dir:
+ * @self: a `Pango2LineBreaker`
+ * @direction: the direction
+ *
+ * Sets the base direction for lines produced by the `Pango2LineBreaker`.
+ *
+ * If @direction is `PANGO2_DIRECTION_NEUTRAL`, the direction is determined
+ * from the content. This is the default behavior.
+ */
+void
+pango2_line_breaker_set_base_dir (Pango2LineBreaker *self,
+ Pango2Direction direction)
+{
+ g_return_if_fail (PANGO2_IS_LINE_BREAKER (self));
+
+ if (self->base_dir == direction)
+ return;
+
+ self->base_dir = direction;
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BASE_DIR]);
+}
+
+/**
+ * pango2_line_breaker_get_base_dir:
+ * @self: a `Pango2LineBreaker`
+ *
+ * Gets the base direction for lines produced by the `Pango2LineBreaker`.
+ *
+ * See [method@Pango2.LineBreaker.set_base_dir].
+ */
+Pango2Direction
+pango2_line_breaker_get_base_dir (Pango2LineBreaker *self)
+{
+ g_return_val_if_fail (PANGO2_IS_LINE_BREAKER (self), PANGO2_DIRECTION_NEUTRAL);
+
+ return self->base_dir;
+}
+
+/**
+ * pango2_line_breaker_add_text:
+ * @text: the text to break into lines
+ * @length: length of @text in bytes, or -1 if @text is nul-terminated
+ * @attrs: (nullable): a `Pango2AttrList` with attributes for @text, or `NULL`
+ *
+ * Provides input that the `Pango2LineBreaker` should break into lines.
+ *
+ * It is possible to call this function repeatedly to add more
+ * input to an existing `Pango2LineBreaker`.
+ *
+ * The end of @text is treated as a paragraph break.
+ */
+void
+pango2_line_breaker_add_text (Pango2LineBreaker *self,
+ const char *text,
+ int length,
+ Pango2AttrList *attrs)
+{
+ g_return_if_fail (PANGO2_IS_LINE_BREAKER (self));
+ g_return_if_fail (text != NULL);
+
+ self->datas = g_slist_append (self->datas, make_line_data (self, text, length, attrs));
+}
+
+/**
+ * pango2_line_breaker_get_direction:
+ * @self: a `Pango2LineBreaker`
+ *
+ * Obtains the resolved direction for the next line.
+ *
+ * If the `Pango2LineBreaker` has no more input, then
+ * `PANGO2_DIRECTION_NEUTRAL` is returned.
+ *
+ * Returns: the resolved direction of the next line.
+ */
+Pango2Direction
+pango2_line_breaker_get_direction (Pango2LineBreaker *self)
+{
+ g_return_val_if_fail (PANGO2_IS_LINE_BREAKER (self), PANGO2_DIRECTION_NEUTRAL);
+
+ return get_resolved_dir (self);
+}
+
+/**
+ * pango2_line_breaker_has_line:
+ * @self: a `Pango2LineBreaker`
+ *
+ * Returns whether the `Pango2LineBreaker` has any text left to process.
+ *
+ * Returns: TRUE if there are more lines.
+ */
+gboolean
+pango2_line_breaker_has_line (Pango2LineBreaker *self)
+{
+ g_return_val_if_fail (PANGO2_IS_LINE_BREAKER (self), FALSE);
+
+ ensure_items (self);
+
+ return self->items != NULL;
+}
+
+/**
+ * pango2_line_breaker_next_line:
+ * @self: a `Pango2LineBreaker`
+ * @x: the X position for the line, in Pango2 units
+ * @width: the width for the line, or -1 for no limit, in Pango2 units
+ * @wrap: how to wrap the text
+ * @ellipsize: whether to ellipsize the text
+ *
+ * Gets the next line.
+ *
+ * The `Pango2LineBreaker` will use as much of its unprocessed text
+ * as will fit into @width. The @x position is used to determine
+ * where tabs are located are.
+ *
+ * If @ellipsize is not `PANGO2_ELLIPSIZE_NONE`, then all unprocessed
+ * text will be made to fit by ellipsizing.
+ *
+ * Note that the line is not positioned - the leftmost point of its baseline
+ * is at 0, 0. See [class@Pango2.Lines] for a way to hold a list of positioned
+ * `Pango2Line` objects.
+ *
+ * line = pango2_line_breaker_next_line (breaker,
+ * x, width,
+ * PANGO2_WRAP_MODE,
+ * PANGO2_ELLIPSIZE_NONE);
+ * pango2_line_get_extents (line, &ext);
+ * line = pango2_line_justify (line, width);
+ * pango2_lines_add_line (lines, line, x, y - ext.y);
+ *
+ * Returns: (transfer full) (nullable): the next line, or `NULL`
+ * if @self has no more input
+ */
+Pango2Line *
+pango2_line_breaker_next_line (Pango2LineBreaker *self,
+ int x,
+ int width,
+ Pango2WrapMode wrap,
+ Pango2EllipsizeMode ellipsize)
+{
+ Pango2Line *line;
+
+ g_return_val_if_fail (PANGO2_IS_LINE_BREAKER (self), NULL);
+
+ ensure_items (self);
+
+ if (!self->items)
+ return NULL;
+
+ line = pango2_line_new (self->context, self->data);
+
+ line->start_index = self->line_start_index;
+ line->start_offset = self->line_start_offset;
+ line->starts_paragraph = self->at_paragraph_start;
+ line->direction = get_resolved_dir (self);
+
+ self->line_x = x;
+ self->line_width = width;
+ self->line_wrap = wrap;
+ self->line_ellipsize = ellipsize;
+
+ self->last_tab.glyphs = NULL;
+ self->last_tab.index = 0;
+ self->last_tab.align = PANGO2_TAB_LEFT;
+
+ if (should_ellipsize_current_line (self, line))
+ self->remaining_width = -1;
+ else
+ self->remaining_width = width;
+
+ process_line (self, line);
+
+ line->n_chars = compute_n_chars (line);
+
+ postprocess_line (self, line);
+
+ if (!self->items)
+ line->ends_paragraph = TRUE;
+
+ self->at_paragraph_start = line->ends_paragraph;
+ self->n_lines++;
+ self->line_start_index += line->length;
+ self->line_start_offset = self->start_offset;
+
+ if (self->items == NULL)
+ {
+ g_clear_pointer (&self->data, line_data_unref);
+ g_list_free_full (self->data_items, (GDestroyNotify) pango2_item_free);
+ self->data_items = NULL;
+ g_clear_pointer (&self->render_attrs, pango2_attr_list_unref);
+ }
+
+ pango2_line_check_invariants (line);
+
+ return line;
+}
+
+/**
+ * pango2_line_breaker_undo_line:
+ * @self: a `Pango2LineBreaker`
+ * @line: (transfer none): the most recent line produced by @self
+ *
+ * Re-adds the content of a line to the unprocessed
+ * input of the `Pango2LineBreaker`.
+ *
+ * This can be used to try this line again with
+ * different parameters passed to
+ * [method@Pango2.LineBreaker.next_line].
+ *
+ * When undoing multiple lines, they have to be
+ * undone in the reverse order in which they
+ * were produced.
+ *
+ * Returns: `TRUE` on success, `FALSE` if Pango2
+ * determines that the line can't be undone
+ */
+gboolean
+pango2_line_breaker_undo_line (Pango2LineBreaker *self,
+ Pango2Line *line)
+{
+ if (self->data == NULL &&
+ line->start_index == 0 && line->length == line->data->length)
+ {
+ g_assert (self->items == NULL);
+ self->datas = g_slist_prepend (self->datas, line_data_ref (line->data));
+
+ self->n_lines--;
+
+ /* ensure_items will set up everything else */
+
+ g_clear_pointer (&self->glyphs, pango2_glyph_string_free);
+
+ return TRUE;
+ }
+
+ if (self->data == line->data &&
+ self->line_start_index == line->start_index + line->length)
+ {
+ GList *items = NULL;
+
+ /* recover the original items */
+ for (GList *l = self->data_items; l; l = l->next)
+ {
+ Pango2Item *item = l->data;
+
+ if (item->offset + item->length < line->start_index)
+ continue;
+
+ if (item->offset > self->line_start_index)
+ break;
+
+ item = pango2_item_copy (item);
+
+ if (item->offset < line->start_index)
+ {
+ Pango2Item *new_item;
+ int n_chars;
+
+ n_chars = g_utf8_strlen (self->data->text + item->offset, line->start_index - item->offset);
+ new_item = pango2_item_split (item, line->start_index - item->offset, n_chars);
+ pango2_item_free (new_item);
+ }
+
+ if (item->offset + item->length > self->line_start_index)
+ {
+ Pango2Item *new_item;
+ int n_chars;
+
+ n_chars = g_utf8_strlen (self->data->text + item->offset, self->line_start_index - item->offset);
+ new_item = pango2_item_split (item, self->line_start_index - item->offset, n_chars);
+ pango2_item_free (item);
+ item = new_item;
+ }
+
+ items = g_list_prepend (items, item);
+ }
+
+ self->items = g_list_concat (g_list_reverse (items), self->items);
+
+ self->n_lines--;
+
+ self->at_paragraph_start = line->starts_paragraph;
+ self->line_start_index = line->start_index;
+ self->line_start_offset = line->start_offset;
+
+ g_clear_pointer (&self->glyphs, pango2_glyph_string_free);
+ self->start_offset = line->start_offset;
+ self->log_widths_offset = 0;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-line-breaker.h b/pango2/pango-line-breaker.h
new file mode 100644
index 00000000..666f67f5
--- /dev/null
+++ b/pango2/pango-line-breaker.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <pango2/pango-types.h>
+#include <pango2/pango-break.h>
+#include <pango2/pango-layout.h>
+#include <pango2/pango-line.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_LINE_BREAKER pango2_line_breaker_get_type ()
+
+PANGO2_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (Pango2LineBreaker, pango2_line_breaker, PANGO2, LINE_BREAKER, GObject);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2LineBreaker * pango2_line_breaker_new (Pango2Context *context);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Context * pango2_line_breaker_get_context (Pango2LineBreaker *self);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_breaker_set_tabs (Pango2LineBreaker *self,
+ Pango2TabArray *tabs);
+PANGO2_AVAILABLE_IN_ALL
+Pango2TabArray * pango2_line_breaker_get_tabs (Pango2LineBreaker *self);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_breaker_set_base_dir (Pango2LineBreaker *self,
+ Pango2Direction direction);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Direction pango2_line_breaker_get_base_dir (Pango2LineBreaker *self);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_breaker_add_text (Pango2LineBreaker *self,
+ const char *text,
+ int length,
+ Pango2AttrList *attrs);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Direction pango2_line_breaker_get_direction (Pango2LineBreaker *self);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_breaker_has_line (Pango2LineBreaker *self);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Line * pango2_line_breaker_next_line (Pango2LineBreaker *self,
+ int x,
+ int width,
+ Pango2WrapMode wrap,
+ Pango2EllipsizeMode ellipsize);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_breaker_undo_line (Pango2LineBreaker *self,
+ Pango2Line *line);
+
+G_END_DECLS
diff --git a/pango2/pango-line-iter-private.h b/pango2/pango-line-iter-private.h
new file mode 100644
index 00000000..1aba2956
--- /dev/null
+++ b/pango2/pango-line-iter-private.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-line-iter.h"
+
+
+Pango2LineIter * pango2_line_iter_new (Pango2Lines *lines);
diff --git a/pango2/pango-line-iter.c b/pango2/pango-line-iter.c
new file mode 100644
index 00000000..8139e4ef
--- /dev/null
+++ b/pango2/pango-line-iter.c
@@ -0,0 +1,881 @@
+#include "config.h"
+
+#include "pango-line-iter-private.h"
+#include "pango-lines-private.h"
+#include "pango-line-private.h"
+#include "pango-run-private.h"
+
+/**
+ * Pango2LineIter:
+ *
+ * A `Pango2LineIter` can be used to iterate over the visual
+ * extents of a `Pango2Layout` or `Pango2Lines`.
+ *
+ * To obtain a `Pango2LineIter`, use [method@Pango2.Layout.get_iter]
+ * or [method@Pango2.Lines.get_iter].
+ *
+ * The `Pango2LineIter` structure is opaque, and has no user-visible
+ * fields.
+ */
+
+
+/* {{{ Pango2LineIter implementation */
+
+struct _Pango2LineIter
+{
+ Pango2Lines *lines;
+ guint serial;
+
+ int line_no;
+ Position line_pos;
+ Pango2Line *line;
+ GSList *run_link;
+ Pango2Run *run;
+ int index;
+
+ /* run handling */
+ int run_x;
+ int run_width;
+ int end_x_offset;
+ gboolean ltr;
+
+ /* cluster handling */
+ int cluster_x;
+ int cluster_width;
+ int cluster_start;
+ int next_cluster_glyph;
+ int cluster_num_chars;
+
+ int character_position;
+};
+
+G_DEFINE_BOXED_TYPE (Pango2LineIter, pango2_line_iter,
+ pango2_line_iter_copy, pango2_line_iter_free);
+
+
+/* }}} */
+/* {{{ Utilities */
+
+#define ITER_IS_VALID(iter) ((iter)->serial == (iter)->lines->serial)
+
+static gboolean
+line_is_terminated (Pango2LineIter *iter)
+{
+ if (iter->line_no + 1 < pango2_lines_get_line_count (iter->lines))
+ return pango2_line_is_paragraph_end (iter->line);
+
+ return FALSE;
+
+}
+
+static int
+next_cluster_start (Pango2GlyphString *glyphs,
+ int cluster_start)
+{
+ int i;
+
+ i = cluster_start + 1;
+ while (i < glyphs->num_glyphs)
+ {
+ if (glyphs->glyphs[i].attr.is_cluster_start)
+ return i;
+
+ i++;
+ }
+
+ return glyphs->num_glyphs;
+}
+
+static int
+cluster_width (Pango2GlyphString *glyphs,
+ int cluster_start)
+{
+ int i;
+ int width;
+
+ width = glyphs->glyphs[cluster_start].geometry.width;
+ i = cluster_start + 1;
+ while (i < glyphs->num_glyphs)
+ {
+ if (glyphs->glyphs[i].attr.is_cluster_start)
+ break;
+
+ width += glyphs->glyphs[i].geometry.width;
+ i++;
+ }
+
+ return width;
+}
+
+/* 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 (Pango2LineIter *iter,
+ int cluster_start_index)
+{
+ Pango2GlyphItem *glyph_item;
+ char *cluster_text;
+ int cluster_length;
+ Pango2Run *run = iter->run_link->data;
+
+ glyph_item = pango2_run_get_glyph_item (run);
+
+ iter->character_position = 0;
+
+ iter->cluster_width = cluster_width (glyph_item->glyphs, iter->cluster_start);
+ iter->next_cluster_glyph = next_cluster_start (glyph_item->glyphs, 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 < glyph_item->glyphs->num_glyphs)
+ cluster_length = glyph_item->glyphs->log_clusters[iter->next_cluster_glyph] - cluster_start_index;
+ else
+ cluster_length = glyph_item->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 && glyph_item->glyphs->log_clusters[i - 1] == cluster_start_index)
+ i--;
+
+ if (i == 0)
+ cluster_length = glyph_item->item->length - cluster_start_index;
+ else
+ cluster_length = glyph_item->glyphs->log_clusters[i - 1] - cluster_start_index;
+ }
+
+ cluster_text = iter->line->data->text + glyph_item->item->offset + cluster_start_index;
+ iter->cluster_num_chars = g_utf8_strlen (cluster_text, cluster_length);
+
+ if (iter->ltr)
+ iter->index = cluster_text - iter->line->data->text;
+ else
+ iter->index = g_utf8_prev_char (cluster_text + cluster_length) - iter->line->data->text;
+}
+
+/* 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 (Pango2LineIter *iter,
+ gboolean include_terminators)
+{
+ gboolean result;
+
+ while (TRUE)
+ {
+ result = pango2_line_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 (Pango2LineIter *iter,
+ gboolean include_terminators)
+{
+ gboolean result;
+
+ while (TRUE)
+ {
+ result = pango2_line_iter_next_run (iter);
+ if (!result)
+ break;
+
+ if (iter->run)
+ break;
+
+ if (include_terminators && line_is_terminated (iter))
+ break;
+ }
+
+ return result;
+}
+
+/* Like pango2_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 (Pango2LineIter *iter,
+ gboolean include_terminators)
+{
+ Pango2GlyphItem *glyph_item;
+ Pango2Run *run;
+
+ if (iter->run_link == NULL)
+ return next_nonempty_line (iter, include_terminators);
+
+ run = iter->run_link->data;
+ glyph_item = pango2_run_get_glyph_item (run);
+
+ if (iter->next_cluster_glyph == glyph_item->glyphs->num_glyphs)
+ {
+ return next_nonempty_run (iter, include_terminators);
+ }
+ else
+ {
+ iter->cluster_start = iter->next_cluster_glyph;
+ iter->cluster_x += iter->cluster_width;
+ update_cluster (iter, glyph_item->glyphs->log_clusters[iter->cluster_start]);
+
+ return TRUE;
+ }
+}
+
+static void
+update_run (Pango2LineIter *iter,
+ int start_index)
+{
+ if (iter->run)
+ {
+ Pango2GlyphItem *glyph_item = pango2_run_get_glyph_item (iter->run);
+
+ if (iter->run_link == iter->line->runs)
+ iter->run_x = 0;
+ else
+ iter->run_x += iter->run_width + iter->end_x_offset + glyph_item->start_x_offset;
+
+ iter->run_width = pango2_glyph_string_get_width (glyph_item->glyphs);
+ iter->end_x_offset = glyph_item->end_x_offset;
+ iter->ltr = (glyph_item->item->analysis.level % 2) == 0;
+ iter->cluster_start = 0;
+ iter->cluster_x = iter->run_x;
+ update_cluster (iter, glyph_item->glyphs->log_clusters[0]);
+ }
+ else
+ {
+ /* The empty run at the end of a line */
+ iter->run_x = 0;
+
+ iter->run_width = 0;
+ iter->end_x_offset = 0;
+ iter->ltr = TRUE;
+ iter->cluster_start = 0;
+ iter->cluster_x = iter->run_x;
+ iter->cluster_width = 0;
+ iter->character_position = 0;
+ iter->cluster_num_chars = 0;
+ iter->index = start_index;
+ }
+}
+
+static inline void
+offset_line (Pango2LineIter *iter,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ if (ink_rect)
+ {
+ ink_rect->x += iter->line_pos.x;
+ ink_rect->y += iter->line_pos.y;
+ }
+ if (logical_rect)
+ {
+ logical_rect->x += iter->line_pos.x;
+ logical_rect->y += iter->line_pos.y;
+ }
+}
+
+static inline void
+offset_run (Pango2LineIter *iter,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ if (ink_rect)
+ ink_rect->x += iter->run_x;
+ if (logical_rect)
+ logical_rect->x += iter->run_x;
+}
+
+/* }}} */
+/* {{{ Private API */
+
+Pango2LineIter *
+pango2_line_iter_new (Pango2Lines *lines)
+{
+ Pango2LineIter *iter;
+ int run_start_index;
+
+ g_return_val_if_fail (PANGO2_IS_LINES (lines), NULL);
+
+ iter = g_new0 (Pango2LineIter, 1);
+
+ iter->lines = g_object_ref (lines);
+ iter->serial = pango2_lines_get_serial (lines);
+
+ iter->line_no = 0;
+ iter->line = g_ptr_array_index (lines->lines, 0);
+ iter->line_pos = g_array_index (lines->positions, Position, 0);
+ iter->run_link = iter->line->runs;
+ if (iter->run_link)
+ {
+ iter->run = iter->run_link->data;
+ run_start_index = pango2_run_get_glyph_item (iter->run)->item->offset;
+ }
+ else
+ {
+ iter->run = NULL;
+ run_start_index = 0;
+ }
+
+ update_run (iter, run_start_index);
+
+ return iter;
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_line_iter_copy:
+ * @iter: (nullable): a `Pango2LineIter`
+ *
+ * Copies a `Pango2LineIter`.
+ *
+ * Return value: (nullable): the newly allocated `Pango2LineIter`
+ */
+Pango2LineIter *
+pango2_line_iter_copy (Pango2LineIter *iter)
+{
+ Pango2LineIter *copy;
+
+ if (iter == NULL)
+ return NULL;
+
+ copy = g_new0 (Pango2LineIter, 1);
+ memcpy (iter, copy, sizeof (Pango2LineIter));
+ g_object_ref (copy->lines);
+
+ return copy;
+}
+
+/**
+ * pango2_line_iter_free:
+ * @iter: (nullable): a `Pango2LineIter`
+ *
+ * Frees an iterator that's no longer in use.
+ */
+void
+pango2_line_iter_free (Pango2LineIter *iter)
+{
+ if (iter == NULL)
+ return;
+
+ g_object_unref (iter->lines);
+ g_free (iter);
+}
+
+/**
+ * pango2_line_iter_get_lines:
+ * @iter: a `Pango2LineIter`
+ *
+ * Gets the `Pango2Lines` object associated with a `Pango2LineIter`.
+ *
+ * Return value: (transfer none): the lines associated with @iter
+ */
+Pango2Lines *
+pango2_line_iter_get_lines (Pango2LineIter *iter)
+{
+ return iter->lines;
+}
+
+/**
+ * pango2_line_iter_get_line:
+ * @iter: a `Pango2LineIter`
+ *
+ * Gets the current line.
+ *
+ * Return value: (transfer none): the current line
+ */
+Pango2Line *
+pango2_line_iter_get_line (Pango2LineIter *iter)
+{
+ g_return_val_if_fail (ITER_IS_VALID (iter), NULL);
+
+ return iter->line;
+}
+
+/**
+ * pango2_line_iter_at_last_line:
+ * @iter: a `Pango2LineIter`
+ *
+ * Determines whether @iter is on the last line.
+ *
+ * Return value: %TRUE if @iter is on the last line
+ */
+gboolean
+pango2_line_iter_at_last_line (Pango2LineIter *iter)
+{
+ g_return_val_if_fail (ITER_IS_VALID (iter), FALSE);
+
+ return iter->line_no + 1 == pango2_lines_get_line_count (iter->lines);
+}
+
+/**
+ * pango2_line_iter_get_run:
+ * @iter: a `Pango2LineIter`
+ *
+ * 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.
+ *
+ * Return value: (transfer none) (nullable): the current run
+ */
+Pango2Run *
+pango2_line_iter_get_run (Pango2LineIter *iter)
+{
+ g_return_val_if_fail (ITER_IS_VALID (iter), NULL);
+
+ return iter->run;
+}
+
+/**
+ * pango2_line_iter_get_index:
+ * @iter: a `Pango2LineIter`
+ *
+ * Gets the current byte index.
+ *
+ * The byte index is relative to the text backing the current
+ * line.
+ *
+ * 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@Pango2.LineIter.get_run]).
+ *
+ * Return value: current byte index
+ */
+int
+pango2_line_iter_get_index (Pango2LineIter *iter)
+{
+ g_return_val_if_fail (ITER_IS_VALID (iter), 0);
+
+ return iter->index;
+}
+
+/**
+ * pango2_line_iter_next_line:
+ * @iter: a `Pango2LineIter`
+ *
+ * Moves @iter forward to the start of the next line.
+ *
+ * If @iter is already on the last line, returns %FALSE.
+ *
+ * Return value: whether motion was possible
+ */
+gboolean
+pango2_line_iter_next_line (Pango2LineIter *iter)
+{
+ g_return_val_if_fail (ITER_IS_VALID (iter), FALSE);
+
+ iter->line_no++;
+ if (iter->line_no == iter->lines->lines->len)
+ return FALSE;
+
+ iter->line = g_ptr_array_index (iter->lines->lines, iter->line_no);
+ iter->line_pos = g_array_index (iter->lines->positions, Position, iter->line_no);
+
+ iter->run_link = iter->line->runs;
+ if (iter->run_link)
+ iter->run = iter->run_link->data;
+ else
+ iter->run = NULL;
+
+ update_run (iter, iter->line->start_index);
+
+ return TRUE;
+}
+
+/**
+ * pango2_line_iter_next_run:
+ * @iter: a `Pango2LineIter`
+ *
+ * Moves @iter forward to the next run in visual order.
+ *
+ * If @iter was already at the end, returns %FALSE.
+ *
+ * Return value: whether motion was possible
+ */
+gboolean
+pango2_line_iter_next_run (Pango2LineIter *iter)
+{
+ int run_start_index;
+
+ g_return_val_if_fail (ITER_IS_VALID (iter), FALSE);
+
+ if (iter->run == NULL)
+ return pango2_line_iter_next_line (iter);
+
+ iter->run_link = iter->run_link->next;
+ if (iter->run_link == NULL)
+ {
+ Pango2Item *item = pango2_run_get_glyph_item (iter->run)->item;
+ run_start_index = item->offset + item->length;
+ iter->run = NULL;
+ }
+ else
+ {
+ iter->run = iter->run_link->data;
+ run_start_index = pango2_run_get_glyph_item (iter->run)->item->offset;
+ }
+
+ update_run (iter, run_start_index);
+
+ return TRUE;
+}
+
+/**
+ * pango2_line_iter_next_cluster:
+ * @iter: a `Pango2LineIter`
+ *
+ * Moves @iter forward to the next cluster in visual order.
+ *
+ * If @iter was already at the end, returns %FALSE.
+ *
+ * Return value: whether motion was possible
+ */
+gboolean
+pango2_line_iter_next_cluster (Pango2LineIter *iter)
+{
+ g_return_val_if_fail (ITER_IS_VALID (iter), FALSE);
+
+ return next_cluster_internal (iter, FALSE);
+}
+
+/**
+ * pango2_line_iter_next_char:
+ * @iter: a `Pango2LineIter`
+ *
+ * Moves @iter forward to the next character in visual order.
+ *
+ * If @iter was already at the end, returns %FALSE.
+ *
+ * Return value: whether motion was possible
+ */
+gboolean
+pango2_line_iter_next_char (Pango2LineIter *iter)
+{
+ const char *text;
+
+ g_return_val_if_fail (ITER_IS_VALID (iter), 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->line->data->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->line->data->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;
+
+ return TRUE;
+}
+
+/**
+ * pango2_line_iter_get_layout_extents:
+ * @iter: a `Pango2LineIter`
+ * @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 `Pango2Lines` being iterated over.
+ */
+void
+pango2_line_iter_get_layout_extents (Pango2LineIter *iter,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ g_return_if_fail (ITER_IS_VALID (iter));
+
+ pango2_lines_get_extents (iter->lines, ink_rect, logical_rect);
+}
+
+/**
+ * pango2_line_iter_get_line_extents:
+ * @iter: a `Pango2LineIter`
+ * @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 current line.
+ *
+ * Extents are in layout coordinates (origin is the top-left corner of the
+ * entire `Pango2Lines`). 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@Pango2.Line.get_extents].
+ *
+ * The logical extents returned by this function always have their leading
+ * trimmed according to paragraph boundaries: if the line starts a paragraph,
+ * it has its start leading trimmed; if it ends a paragraph, it has its end
+ * leading trimmed. If you need other trimming, use
+ * [method@Pango2.Line.get_trimmed_extents].
+ */
+void
+pango2_line_iter_get_line_extents (Pango2LineIter *iter,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ g_return_if_fail (ITER_IS_VALID (iter));
+
+ pango2_line_get_extents (iter->line, ink_rect, logical_rect);
+ offset_line (iter, ink_rect, logical_rect);
+}
+
+void
+pango2_line_iter_get_trimmed_line_extents (Pango2LineIter *iter,
+ Pango2LeadingTrim trim,
+ Pango2Rectangle *logical_rect)
+{
+ g_return_if_fail (ITER_IS_VALID (iter));
+
+ pango2_line_get_trimmed_extents (iter->line, trim, logical_rect);
+ offset_line (iter, NULL, logical_rect);
+}
+
+/**
+ * pango2_line_iter_get_run_extents:
+ * @iter: a `Pango2LineIter`
+ * @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 run in layout coordinates.
+ *
+ * Layout coordinates have the origin at the top left of the entire `Pango2Lines`.
+ *
+ * The logical extents returned by this function always have their leading
+ * trimmed off. If you need extents that include leading, use
+ * [method@Pango2.Run.get_extents].
+ */
+void
+pango2_line_iter_get_run_extents (Pango2LineIter *iter,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ g_return_if_fail (ITER_IS_VALID (iter));
+
+ if (iter->run)
+ {
+ pango2_run_get_extents (iter->run, PANGO2_LEADING_TRIM_BOTH, ink_rect, logical_rect);
+ }
+ else
+ {
+ GSList *runs = iter->line->runs;
+ if (runs)
+ {
+ /* Virtual run at the end of a nonempty line */
+ Pango2Run *run = g_slist_last (runs)->data;
+
+ pango2_run_get_extents (run, PANGO2_LEADING_TRIM_BOTH, ink_rect, logical_rect);
+ if (ink_rect)
+ {
+ ink_rect->x += ink_rect->width;
+ ink_rect->width = 0;
+ }
+ if (logical_rect)
+ {
+ logical_rect->x += logical_rect->width;
+ logical_rect->width = 0;
+ }
+ }
+ else
+ {
+ /* Empty line */
+ Pango2Rectangle r;
+
+ pango2_line_get_empty_extents (iter->line, PANGO2_LEADING_TRIM_BOTH, &r);
+
+ if (ink_rect)
+ *ink_rect = r;
+
+ if (logical_rect)
+ *logical_rect = r;
+ }
+ }
+
+ offset_line (iter, ink_rect, logical_rect);
+ offset_run (iter, ink_rect, logical_rect);
+}
+
+/**
+ * pango2_line_iter_get_cluster_extents:
+ * @iter: a `Pango2LineIter`
+ * @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 `Pango2Lines`.
+ */
+void
+pango2_line_iter_get_cluster_extents (Pango2LineIter *iter,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ Pango2GlyphItem *glyph_item;
+
+ g_return_if_fail (ITER_IS_VALID (iter));
+
+ if (iter->run == NULL)
+ {
+ /* When on the NULL run, all extents are the same */
+ pango2_line_iter_get_run_extents (iter, ink_rect, logical_rect);
+ return;
+ }
+
+ glyph_item = pango2_run_get_glyph_item (iter->run);
+
+ pango2_glyph_string_extents_range (glyph_item->glyphs,
+ iter->cluster_start,
+ iter->next_cluster_glyph,
+ glyph_item->item->analysis.font,
+ ink_rect,
+ logical_rect);
+
+ offset_line (iter, ink_rect, logical_rect);
+ if (ink_rect)
+ {
+ ink_rect->x += iter->cluster_x + glyph_item->start_x_offset;
+ ink_rect->y -= glyph_item->y_offset;
+ }
+
+ if (logical_rect)
+ {
+ g_assert (logical_rect->width == iter->cluster_width);
+ logical_rect->x += iter->cluster_x + glyph_item->start_x_offset;
+ logical_rect->y -= glyph_item->y_offset;
+ }
+}
+
+/**
+ * pango2_line_iter_get_char_extents:
+ * @iter: a `Pango2LineIter`
+ * @logical_rect: (out caller-allocates): rectangle to fill with logical extents
+ *
+ * Gets the extents of the current character, in layout coordinates.
+ *
+ * Layout coordinates have the origin at the top left of the entire `Pango2Lines`.
+ *
+ * Only logical extents can sensibly be obtained for characters;
+ * ink extents make sense only down to the level of clusters.
+ */
+void
+pango2_line_iter_get_char_extents (Pango2LineIter *iter,
+ Pango2Rectangle *logical_rect)
+{
+ Pango2Rectangle cluster_rect;
+ int x0, x1;
+
+ g_return_if_fail (ITER_IS_VALID (iter));
+
+ if (logical_rect == NULL)
+ return;
+
+ pango2_line_iter_get_cluster_extents (iter, NULL, &cluster_rect);
+
+ if (iter->run == NULL)
+ {
+ /* When on the NULL run, all extents are the same */
+ *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;
+}
+
+/**
+ * pango2_line_iter_get_line_baseline:
+ * @iter: a `Pango2LineIter`
+ *
+ * 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 `Pango2Lines`.
+ *
+ * Return value: baseline of current line
+ */
+int
+pango2_line_iter_get_line_baseline (Pango2LineIter *iter)
+{
+ g_return_val_if_fail (ITER_IS_VALID (iter), 0);
+
+ return iter->line_pos.y;
+}
+
+/**
+ * pango2_line_iter_get_run_baseline:
+ * @iter: a `Pango2LineIter`
+ *
+ * Gets the Y position of the current run's baseline, in layout
+ * coordinates.
+ *
+ * Layout coordinates have the origin at the top left of the entire `Pango2Lines`.
+ *
+ * The run baseline can be different from the line baseline, for
+ * example due to superscript or subscript positioning.
+ */
+int
+pango2_line_iter_get_run_baseline (Pango2LineIter *iter)
+{
+ g_return_val_if_fail (ITER_IS_VALID (iter), 0);
+
+ if (iter->run)
+ return pango2_line_iter_get_line_baseline (iter) - pango2_run_get_glyph_item (iter->run)->y_offset;
+ else
+ return pango2_line_iter_get_line_baseline (iter);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-line-iter.h b/pango2/pango-line-iter.h
new file mode 100644
index 00000000..59a4f5c2
--- /dev/null
+++ b/pango2/pango-line-iter.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-lines.h>
+
+G_BEGIN_DECLS
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_line_iter_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2LineIter * pango2_line_iter_copy (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_iter_free (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Lines * pango2_line_iter_get_lines (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Line * pango2_line_iter_get_line (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_iter_at_last_line (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Run * pango2_line_iter_get_run (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_line_iter_get_index (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_iter_next_line (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_iter_next_run (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_iter_next_cluster (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_iter_next_char (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_iter_get_layout_extents (Pango2LineIter *iter,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_iter_get_line_extents (Pango2LineIter *iter,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_iter_get_trimmed_line_extents (Pango2LineIter *iter,
+ Pango2LeadingTrim trim,
+ Pango2Rectangle *logical_rect);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_iter_get_run_extents (Pango2LineIter *iter,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_iter_get_cluster_extents (Pango2LineIter *iter,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_iter_get_char_extents (Pango2LineIter *iter,
+ Pango2Rectangle *logical_rect);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_line_iter_get_line_baseline (Pango2LineIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_line_iter_get_run_baseline (Pango2LineIter *iter);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2LineIter, pango2_line_iter_free)
+
+G_END_DECLS
diff --git a/pango2/pango-line-private.h b/pango2/pango-line-private.h
new file mode 100644
index 00000000..963674c2
--- /dev/null
+++ b/pango2/pango-line-private.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-line.h"
+#include "pango-break.h"
+#include "pango-attributes.h"
+#include "pango-glyph-item-private.h"
+
+
+typedef struct _LineData LineData;
+struct _LineData {
+ char *text;
+ int length;
+ int n_chars;
+ Pango2Direction direction;
+
+ Pango2AttrList *attrs;
+ Pango2LogAttr *log_attrs;
+};
+
+LineData * line_data_new (void);
+LineData * line_data_ref (LineData *data);
+void line_data_unref (LineData *data);
+void line_data_clear (LineData *data);
+
+struct _Pango2Line
+{
+ Pango2Context *context;
+ LineData *data;
+
+ int start_index;
+ int length;
+ int start_offset;
+ int n_chars;
+ GSList *runs;
+ Pango2Run **run_array;
+ int n_runs;
+
+ guint wrapped : 1;
+ guint ellipsized : 1;
+ guint hyphenated : 1;
+ guint justified : 1;
+ guint starts_paragraph : 1;
+ guint ends_paragraph : 1;
+ guint has_extents : 1;
+
+ Pango2Direction direction;
+
+ Pango2Rectangle ink_rect;
+ Pango2Rectangle logical_rect;
+};
+
+Pango2Line * pango2_line_new (Pango2Context *context,
+ LineData *data);
+
+void pango2_line_ellipsize (Pango2Line *line,
+ Pango2Context *context,
+ Pango2EllipsizeMode ellipsize,
+ int goal_width);
+
+void pango2_line_index_to_run (Pango2Line *line,
+ int idx,
+ Pango2Run **run);
+
+void pango2_line_get_empty_extents (Pango2Line *line,
+ Pango2LeadingTrim trim,
+ Pango2Rectangle *logical_rect);
+
+void pango2_line_check_invariants (Pango2Line *line);
diff --git a/pango2/pango-line.c b/pango2/pango-line.c
new file mode 100644
index 00000000..992a29fe
--- /dev/null
+++ b/pango2/pango-line.c
@@ -0,0 +1,1603 @@
+#include "config.h"
+
+#include "pango-line-private.h"
+
+#include "pango-tabs.h"
+#include "pango-impl-utils.h"
+#include "pango-attributes-private.h"
+#include "pango-attr-private.h"
+#include "pango-attr-list-private.h"
+#include "pango-attr-iterator-private.h"
+#include "pango-item-private.h"
+#include "pango-glyph-iter-private.h"
+#include "pango-run-private.h"
+#include "pango-font-metrics-private.h"
+
+#include <math.h>
+#include <hb-ot.h>
+
+/**
+ * Pango2Line:
+ *
+ * A `Pango2Line` is an immutable object which represents a line resulting
+ * from laying out text via `Pango2Layout` or `Pango2LineBreaker`.
+ *
+ * A line consists of a number of runs (i.e. ranges of text with uniform
+ * script, font and attributes that are shaped as a unit). Runs are
+ * represented as [struct@Pango2.Run] objects.
+ *
+ * A `Pango2Line` always has its origin at the leftmost point of its
+ * baseline. To position lines in an entire paragraph of text (i.e. in layout
+ * coordinates), the `Pango2Lines` object stores X and Y coordinates to
+ * offset each line to.
+ *
+ * The most convenient way to access the visual extents and components
+ * of a `Pango2Line` is via a [struct@Pango2.LineIter] iterator.
+ */
+
+/* {{{ LineData */
+
+void
+line_data_clear (LineData *data)
+{
+ g_free (data->text);
+ g_clear_pointer (&data->attrs, pango2_attr_list_unref);
+ g_free (data->log_attrs);
+}
+
+LineData *
+line_data_new (void)
+{
+ return g_rc_box_new0 (LineData);
+}
+
+LineData *
+line_data_ref (LineData *data)
+{
+ return g_rc_box_acquire (data);
+}
+
+void
+line_data_unref (LineData *data)
+{
+ g_rc_box_release_full (data, (GDestroyNotify) line_data_clear);
+}
+
+/* }}} */
+/* {{{ Pango2Line implementation */
+
+G_DEFINE_BOXED_TYPE (Pango2Line, pango2_line,
+ pango2_line_copy, pango2_line_free);
+
+/* }}} */
+/* {{{ Justification */
+
+static inline void
+distribute_letter_spacing (int letter_spacing,
+ int *space_left,
+ int *space_right)
+{
+ *space_left = letter_spacing / 2;
+
+ /* hinting */
+ if ((letter_spacing & (PANGO2_SCALE - 1)) == 0)
+ *space_left = PANGO2_UNITS_ROUND (*space_left);
+ *space_right = letter_spacing - *space_left;
+}
+
+static int
+pango2_line_compute_width (Pango2Line *line)
+{
+ int width = 0;
+
+ /* Compute the width of the line currently - inefficient, but easier
+ * than keeping the current width of the line up to date everywhere
+ */
+ for (GSList *l = line->runs; l; l = l->next)
+ {
+ Pango2GlyphItem *run = l->data;
+ width += pango2_glyph_string_get_width (run->glyphs);
+ }
+
+ return width;
+}
+
+static void
+justify_clusters (Pango2Line *line,
+ int *remaining_width)
+{
+ 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 = *remaining_width;
+ if (total_remaining_width <= 0)
+ return;
+
+ /* hint to full pixel if total remaining width was so */
+ is_hinted = (total_remaining_width & (PANGO2_SCALE - 1)) == 0;
+
+ for (mode = MEASURE; mode <= ADJUST; mode++)
+ {
+ gboolean leftedge = TRUE;
+ Pango2GlyphString *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)
+ {
+ Pango2GlyphItem *run = run_iter->data;
+ Pango2GlyphString *glyphs = run->glyphs;
+ Pango2GlyphItemIter cluster_iter;
+ gboolean have_cluster;
+ int dir;
+ int offset;
+
+ dir = run->item->analysis.level % 2 == 0 ? +1 : -1;
+ offset = run->item->char_offset;
+ for (have_cluster = dir > 0 ?
+ pango2_glyph_item_iter_init_start (&cluster_iter, run, line->data->text) :
+ pango2_glyph_item_iter_init_end (&cluster_iter, run, line->data->text);
+ have_cluster;
+ have_cluster = dir > 0 ?
+ pango2_glyph_item_iter_next_cluster (&cluster_iter) :
+ pango2_glyph_item_iter_prev_cluster (&cluster_iter))
+ {
+ int i;
+ int width = 0;
+
+ /* don't expand in the middle of graphemes */
+ if (!line->data->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 = PANGO2_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;
+ }
+ }
+ }
+
+ *remaining_width -= added_so_far;
+}
+
+static void
+justify_words (Pango2Line *line,
+ int *remaining_width)
+{
+ 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 = *remaining_width;
+ if (total_remaining_width <= 0)
+ return;
+
+ /* hint to full pixel if total remaining width was so */
+ is_hinted = (total_remaining_width & (PANGO2_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)
+ {
+ Pango2GlyphItem *run = run_iter->data;
+ Pango2GlyphString *glyphs = run->glyphs;
+ Pango2GlyphItemIter cluster_iter;
+ gboolean have_cluster;
+ int offset;
+
+ offset = run->item->char_offset;
+ for (have_cluster = pango2_glyph_item_iter_init_start (&cluster_iter, run, line->data->text);
+ have_cluster;
+ have_cluster = pango2_glyph_item_iter_next_cluster (&cluster_iter))
+ {
+ int i;
+ int dir;
+
+ if (!line->data->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 = PANGO2_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, remaining_width);
+ return;
+ }
+ }
+ }
+
+ *remaining_width -= added_so_far;
+}
+
+/* }}} */
+/* {{{ Extents */
+
+static void
+compute_extents (Pango2Line *line,
+ Pango2LeadingTrim trim,
+ Pango2Rectangle *ink,
+ Pango2Rectangle *logical)
+{
+ int x_pos = 0;
+
+ if (!line->runs)
+ {
+ memset (ink, 0, sizeof (Pango2Rectangle));
+ pango2_line_get_empty_extents (line, trim, logical);
+ return;
+ }
+
+ for (GSList *l = line->runs; l; l = l->next)
+ {
+ Pango2Run *run = l->data;
+ Pango2Rectangle run_ink;
+ Pango2Rectangle run_logical;
+ int new_pos;
+
+ pango2_run_get_extents (run, trim, &run_ink, &run_logical);
+
+ if (ink->width == 0 || ink->height == 0)
+ {
+ *ink = run_ink;
+ ink->x += x_pos;
+ }
+ else if (run_ink.width != 0 && run_ink.height != 0)
+ {
+ new_pos = MIN (ink->x, x_pos + run_ink.x);
+ ink->width = MAX (ink->x + ink->width,
+ x_pos + run_ink.x + run_ink.width) - new_pos;
+ ink->x = new_pos;
+
+ new_pos = MIN (ink->y, run_ink.y);
+ ink->height = MAX (ink->y + ink->height,
+ run_ink.y + run_ink.height) - new_pos;
+ ink->y = new_pos;
+ }
+
+ if (l == line->runs)
+ {
+ *logical = run_logical;
+ logical->x += x_pos;
+ }
+ else
+ {
+ new_pos = MIN (logical->x, x_pos + run_logical.x);
+ logical->width = MAX (logical->x + logical->width,
+ x_pos + run_logical.x + run_logical.width) - new_pos;
+ logical->x = new_pos;
+
+ new_pos = MIN (logical->y, run_logical.y);
+ logical->height = MAX (logical->y + logical->height,
+ run_logical.y + run_logical.height) - new_pos;
+ logical->y = new_pos;
+ }
+
+ x_pos += run_logical.width;
+ }
+}
+
+/* }}} */
+/* {{{ Private API */
+
+void
+pango2_line_check_invariants (Pango2Line *line)
+{
+ /* Check that byte and char positions agree */
+ g_assert (g_utf8_strlen (line->data->text + line->start_index, line->length) == line->n_chars);
+ g_assert (g_utf8_offset_to_pointer (line->data->text + line->start_index, line->n_chars) == line->data->text + line->start_index + line->length);
+
+ /* Check that runs are sane */
+ if (line->runs)
+ {
+ int run_min, run_max;
+ int n_chars;
+
+ run_min = G_MAXINT;
+ run_max = 0;
+ n_chars = 0;
+ for (GSList *l = line->runs; l; l = l->next)
+ {
+ Pango2GlyphItem *run = l->data;
+
+ run_min = MIN (run_min, run->item->offset);
+ run_max = MAX (run_max, run->item->offset + run->item->length);
+ n_chars += run->item->num_chars;
+ }
+
+ g_assert (run_min == line->start_index);
+ g_assert (run_max == line->start_index + line->length);
+ g_assert (n_chars == line->n_chars);
+ }
+}
+
+void
+pango2_line_get_empty_extents (Pango2Line *line,
+ Pango2LeadingTrim trim,
+ Pango2Rectangle *logical_rect)
+{
+ Pango2FontDescription *font_desc = NULL;
+ gboolean free_font_desc = FALSE;
+ double line_height_factor = 0.0;
+ int absolute_line_height = 0;
+ Pango2Font *font;
+
+ font_desc = pango2_context_get_font_description (line->context);
+
+ if (line->data->attrs)
+ {
+ Pango2AttrIterator iter;
+ int start, end;
+
+ pango2_attr_list_init_iterator (line->data->attrs, &iter);
+
+ do
+ {
+ pango2_attr_iterator_range (&iter, &start, &end);
+
+ if (start <= line->start_index && line->start_index < end)
+ {
+ Pango2Attribute *attr;
+
+ if (!free_font_desc)
+ {
+ font_desc = pango2_font_description_copy_static (font_desc);
+ free_font_desc = TRUE;
+ }
+
+ pango2_attr_iterator_get_font (&iter, font_desc, NULL, NULL);
+
+ attr = pango2_attr_iterator_get (&iter, PANGO2_ATTR_LINE_HEIGHT);
+ if (attr)
+ line_height_factor = attr->double_value;
+
+ attr = pango2_attr_iterator_get (&iter, PANGO2_ATTR_ABSOLUTE_LINE_HEIGHT);
+ if (attr)
+ absolute_line_height = attr->int_value;
+
+ break;
+ }
+ }
+ while (pango2_attr_iterator_next (&iter));
+
+ pango2_attr_iterator_clear (&iter);
+ }
+
+ memset (logical_rect, 0, sizeof (Pango2Rectangle));
+
+ font = pango2_context_load_font (line->context, font_desc);
+ if (font)
+ {
+ Pango2FontMetrics *metrics;
+
+ metrics = pango2_font_get_metrics (font, pango2_context_get_language (line->context));
+ if (metrics)
+ {
+ logical_rect->y = - pango2_font_metrics_get_ascent (metrics);
+ logical_rect->height = - logical_rect->y + pango2_font_metrics_get_descent (metrics);
+
+ if (trim != PANGO2_LEADING_TRIM_BOTH)
+ {
+ int leading;
+
+ if (absolute_line_height != 0 || line_height_factor != 0.0)
+ {
+ int line_height;
+
+ line_height = MAX (absolute_line_height, ceilf (line_height_factor * logical_rect->height));
+
+ leading = line_height - logical_rect->height;
+ }
+ else
+ {
+ leading = MAX (metrics->height - (metrics->ascent + metrics->descent), 0);
+ }
+
+ if ((trim & PANGO2_LEADING_TRIM_START) == 0)
+ logical_rect->y -= leading / 2;
+ if (trim == PANGO2_LEADING_TRIM_NONE)
+ logical_rect->height += leading;
+ else
+ logical_rect->height += (leading - leading / 2);
+ }
+
+ pango2_font_metrics_free (metrics);
+ }
+
+ g_object_unref (font);
+ }
+
+ if (free_font_desc)
+ pango2_font_description_free (font_desc);
+}
+
+/*< private >
+ * pango2_line_new:
+ * @context: the `Pango2Context`
+ * @data: the `LineData`
+ *
+ * Creates a new `Pango2Line`.
+ *
+ * The line shares the immutable `LineData` with other lines.
+ *
+ * The @context is needed for shape rendering.
+ *
+ * Returns: new `Pango2Line`
+ */
+Pango2Line *
+pango2_line_new (Pango2Context *context,
+ LineData *data)
+{
+ Pango2Line *line;
+
+ line = g_new0 (Pango2Line, 1);
+ line->context = g_object_ref (context);
+ line->data = line_data_ref (data);
+
+ return line;
+}
+
+/*< private >
+ * pango2_line_index_to_run:
+ * @line: a `Pango2Line`
+ * @idx: a byte offset in the line
+ * @run: (out): return location for the run
+ *
+ * Finds the run in @line which contains @idx.
+ */
+void
+pango2_line_index_to_run (Pango2Line *line,
+ int idx,
+ Pango2Run **run)
+{
+ *run = NULL;
+
+ for (GSList *l = line->runs; l; l = l->next)
+ {
+ Pango2Run *r = l->data;
+ Pango2Item *item;
+
+ item = pango2_run_get_glyph_item (r)->item;
+ if (item->offset <= idx && idx < item->offset + item->length)
+ {
+ *run = r;
+ break;
+ }
+ }
+}
+
+/* }}} */
+/* {{{ Public API */
+
+Pango2Line *
+pango2_line_copy (Pango2Line *line)
+{
+ Pango2Line *copy;
+
+ if (line == NULL)
+ return NULL;
+
+ copy = g_new0 (Pango2Line, 1);
+ copy->context = g_object_ref (line->context);
+ copy->data = line_data_ref (line->data);
+ copy->start_index = line->start_index;
+ copy->length = line->length;
+ copy->start_offset = line->start_offset;
+ copy->n_chars = line->n_chars;
+ copy->wrapped = line->wrapped;
+ copy->ellipsized = line->ellipsized;
+ copy->hyphenated = line->hyphenated;
+ copy->justified = TRUE;
+ copy->starts_paragraph = line->starts_paragraph;
+ copy->ends_paragraph = line->ends_paragraph;
+ copy->has_extents = FALSE;
+ copy->direction = line->direction;
+ copy->runs = g_slist_copy_deep (line->runs, (GCopyFunc) pango2_glyph_item_copy, NULL);
+ copy->n_runs = line->n_runs;
+
+ return copy;
+}
+
+void
+pango2_line_free (Pango2Line *line)
+{
+ g_object_unref (line->context);
+ line_data_unref (line->data);
+ g_slist_free_full (line->runs, (GDestroyNotify)pango2_glyph_item_free);
+ g_free (line->run_array);
+ g_free (line);
+}
+
+/* {{{ Simple getters */
+
+/**
+ * pango2_line_get_run_count:
+ * @line: a `Pango2Line`
+ *
+ * Gets the number of runs in the line.
+ *
+ * Returns: the number of runs
+ */
+int
+pango2_line_get_run_count (Pango2Line *line)
+{
+ g_return_val_if_fail (line != NULL, 0);
+
+ pango2_line_get_runs (line);
+
+ return line->n_runs;
+}
+
+/**
+ * pango2_line_get_runs:
+ * @line: a `Pango2Line`
+ *
+ * Gets the runs of the line.
+ *
+ * Note that the returned list and its contents
+ * are owned by Pango2 and must not be modified.
+ *
+ * The length of the returned array can be obtained
+ * with [method@Pango2.Line.get_run_count].
+ *
+ * Returns: (transfer none): an array of `Pango2Run`
+ */
+Pango2Run **
+pango2_line_get_runs (Pango2Line *line)
+{
+ g_return_val_if_fail (line != NULL, NULL);
+
+ if (!line->run_array)
+ {
+ GSList *l;
+ int i;
+
+ line->n_runs = g_slist_length (line->runs);
+
+ line->run_array = g_new (Pango2Run *, line->n_runs);
+ for (l = line->runs, i = 0; l; l = l->next, i++)
+ line->run_array[i] = l->data;
+ }
+
+ return line->run_array;
+}
+
+/**
+ * pango2_line_get_text:
+ * @line: a `Pango2Line`
+ * @start_index: the byte index of the first byte of @line
+ * @length: the number of bytes in @line
+ *
+ * Gets the text that @line presents.
+ *
+ * The `Pango2Line` represents the slice from @start_index
+ * to @start_index + @length of the returned string.
+ *
+ * The returned string is owned by @line and must not
+ * be modified.
+ *
+ * Returns: the text
+ */
+const char *
+pango2_line_get_text (Pango2Line *line,
+ int *start_index,
+ int *length)
+{
+ g_return_val_if_fail (line != NULL, NULL);
+ g_return_val_if_fail (start_index != NULL, NULL);
+ g_return_val_if_fail (length != NULL, NULL);
+
+ *start_index = line->start_index;
+ *length = line->length;
+
+ return line->data->text;
+}
+
+/**
+ * pango2_line_get_start_index:
+ * @line: a `Pango2Line`
+ *
+ * Returns the start index of the line, as byte index
+ * into the text of the layout.
+ *
+ * Returns: the start index of the line
+ */
+int
+pango2_line_get_start_index (Pango2Line *line)
+{
+ g_return_val_if_fail (line != NULL, 0);
+
+ return line->start_index;
+}
+
+/**
+ * pango2_line_get_length:
+ * @line: a `Pango2Line`
+ *
+ * Returns the length of the line, in bytes.
+ *
+ * Returns: the length of the line
+ */
+int
+pango2_line_get_length (Pango2Line *line)
+{
+ g_return_val_if_fail (line != NULL, 0);
+
+ return line->length;
+}
+
+/**
+ * pango2_line_get_log_attrs:
+ * @line: a `Pango2Line`
+ * @start_offset: the character offset of the first character of @line
+ * @n_attrs: the number of attributes that apply to @line
+ *
+ * Gets the `Pango2LogAttr` array for the line.
+ *
+ * The `Pango2LogAttrs` for @line are the slice from @start_offset
+ * to @start_offset+@n_attrs of the returned array. @n_attrs is
+ * be the number of characters plus one.
+ *
+ * The returned array is owned by @line and must not be modified.
+ *
+ * Returns: the `Pango2LogAttr` array
+ */
+const Pango2LogAttr *
+pango2_line_get_log_attrs (Pango2Line *line,
+ int *start_offset,
+ int *n_attrs)
+{
+ g_return_val_if_fail (line != NULL, NULL);
+ g_return_val_if_fail (start_offset != NULL, NULL);
+ g_return_val_if_fail (n_attrs != NULL, NULL);
+
+ *start_offset = line->start_offset;
+ *n_attrs = line->n_chars + 1;
+
+ return line->data->log_attrs;
+}
+
+/**
+ * pango2_line_is_wrapped:
+ * @line: a `Pango2Line`
+ *
+ * Gets whether the line is wrapped.
+ *
+ * Returns: `TRUE` if @line has been wrapped
+ */
+gboolean
+pango2_line_is_wrapped (Pango2Line *line)
+{
+ g_return_val_if_fail (line != NULL, FALSE);
+
+ return line->wrapped;
+}
+
+/**
+ * pango2_line_is_ellipsized:
+ * @line: a `Pango2Line`
+ *
+ * Gets whether the line is ellipsized.
+ *
+ * Returns: `TRUE` if @line has been ellipsized
+ */
+gboolean
+pango2_line_is_ellipsized (Pango2Line *line)
+{
+ g_return_val_if_fail (line != NULL, FALSE);
+
+ return line->ellipsized;
+}
+
+/**
+ * pango2_line_is_hyphenated:
+ * @line: a `Pango2Line`
+ *
+ * Gets whether the line is hyphenated.
+ *
+ * Returns: `TRUE` if @line has been hyphenated
+ */
+gboolean
+pango2_line_is_hyphenated (Pango2Line *line)
+{
+ g_return_val_if_fail (line != NULL, FALSE);
+
+ return line->hyphenated;
+}
+
+/**
+ * pango2_line_is_justified:
+ * @line: a `Pango2Line`
+ *
+ * Gets whether the line is justified.
+ *
+ * See [method@Pango2.Line.justify].
+ *
+ * Returns: `TRUE` if @line has been justified
+ */
+gboolean
+pango2_line_is_justified (Pango2Line *line)
+{
+ g_return_val_if_fail (line != NULL, FALSE);
+
+ return line->justified;
+}
+
+/**
+ * pango2_line_is_paragraph_start:
+ * @line: a `Pango2Line`
+ *
+ * Gets whether the line is the first of a paragraph.
+ *
+ * Returns: `TRUE` if @line starts a paragraph
+ */
+gboolean
+pango2_line_is_paragraph_start (Pango2Line *line)
+{
+ g_return_val_if_fail (line != NULL, FALSE);
+
+ return line->starts_paragraph;
+}
+
+/**
+ * pango2_line_is_paragraph_end:
+ * @line: a `Pango2Line`
+ *
+ * Gets whether the line is the last of a paragraph.
+ *
+ * Returns: `TRUE` if @line ends a paragraph
+ */
+gboolean
+pango2_line_is_paragraph_end (Pango2Line *line)
+{
+ g_return_val_if_fail (line != NULL, FALSE);
+
+ return line->ends_paragraph;
+}
+
+/**
+ * pango2_line_get_resolved_direction:
+ * @line: a `Pango2Line`
+ *
+ * Gets the resolved direction of the line.
+ *
+ * Returns: the resolved direction of @line
+ */
+Pango2Direction
+pango2_line_get_resolved_direction (Pango2Line *line)
+{
+ g_return_val_if_fail (line != NULL, PANGO2_DIRECTION_LTR);
+
+ return line->direction;
+}
+
+/* }}} */
+/* {{{ Justification */
+
+/**
+ * pango2_line_justify:
+ * @line: (transfer full): a `Pango2Line`
+ * @width: the width to justify @line to
+ *
+ * Creates a new `Pango2Line` that is justified
+ * copy of @line.
+ *
+ * The content of the returned line is justified
+ * to fill the given width, by modifying inter-word
+ * spaces (and possibly intra-word spaces too).
+ *
+ * Note that this function consumes @line.
+ *
+ * Returns: (transfer full): a new `Pango2Line`
+ */
+Pango2Line *
+pango2_line_justify (Pango2Line *line,
+ int width)
+{
+ int remaining_width;
+ Pango2Line *copy;
+
+ g_return_val_if_fail (line != NULL, NULL);
+
+ remaining_width = width - pango2_line_compute_width (line);
+ if (remaining_width <= 0)
+ return line;
+
+ copy = pango2_line_new (line->context, line->data);
+ copy->start_index = line->start_index;
+ copy->length = line->length;
+ copy->start_offset = line->start_offset;
+ copy->n_chars = line->n_chars;
+ copy->wrapped = line->wrapped;
+ copy->ellipsized = line->ellipsized;
+ copy->hyphenated = line->hyphenated;
+ copy->justified = TRUE;
+ copy->starts_paragraph = line->starts_paragraph;
+ copy->ends_paragraph = line->ends_paragraph;
+ copy->has_extents = FALSE;
+ copy->direction = line->direction;
+ copy->runs = line->runs;
+ line->runs = NULL;
+
+ justify_words (copy, &remaining_width);
+
+ pango2_line_free (line);
+
+ return copy;
+}
+
+/* }}} */
+/* {{{ Extents */
+
+/**
+ * pango2_line_get_extents:
+ * @line: a `Pango2Line`
+ * @ink_rect: (out) (optional): rectangle that will be filled with ink extents
+ * @logical_rect: (out) (optional): rectangle that will be filled with the logical extents
+ *
+ * Gets the extents of the line.
+ *
+ * The logical extents returned by this function always include leading.
+ * If you need extents with trimmed leading, use [method@Pango2.Line.get_trimmed_extents].
+ *
+ * Note that the origin is at the left end of the baseline.
+ *
+ * Pango2 is following CSS in splitting the external leading, and giving one half of it
+ * to the line above, and the other half the the line below. Unless the line height is set
+ * via attributes, the external leading is determined as the difference between the
+ * height and ascent + descent in font metrics:
+ *
+ * <picture>
+ * <source srcset="line-height1-dark.png" media="(prefers-color-scheme: dark)">
+ * <img alt="Pango2 Font Metrics" src="line-height1-light.png">
+ * </picture>
+ *
+ * If spacing is set, it also gets split, for the purpose of determining the
+ * logical extents.
+ *
+ * <picture>
+ * <source srcset="line-height2-dark.png" media="(prefers-color-scheme: dark)">
+ * <img alt="Pango2 Extents and Spacing" src="line-height2-light.png">
+ * </picture>
+ *
+ * If line height is set, it determines the logical extents.
+ *
+ * <picture>
+ * <source srcset="line-height3-dark.png" media="(prefers-color-scheme: dark)">
+ * <img alt="Pango2 Extents and Line Height" src="line-height3-light.png">
+ * </picture>
+ */
+void
+pango2_line_get_extents (Pango2Line *line,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ Pango2Rectangle ink = { 0, };
+ Pango2Rectangle logical = { 0, };
+
+ if (line->has_extents)
+ goto cached;
+
+ compute_extents (line, PANGO2_LEADING_TRIM_NONE, &ink, &logical);
+
+ line->ink_rect = ink;
+ line->logical_rect = logical;
+ line->has_extents = TRUE;
+
+cached:
+ if (ink_rect)
+ *ink_rect = line->ink_rect;
+ if (logical_rect)
+ *logical_rect = line->logical_rect;
+}
+
+/**
+ * pango2_line_get_trimmed_extents:
+ * @line: a `Pango2Line`
+ * @trim: `Pango2LeadingTrim` flags
+ * @logical_rect: (out): rectangle that will be filled with the logical extents
+ *
+ * Gets trimmed logical extents of the line.
+ *
+ * The @trim flags specify if line-height attributes are taken
+ * into consideration for determining the logical height. See the
+ * [CSS inline layout](https://www.w3.org/TR/css-inline-3/#inline-height)
+ * specification for details.
+ *
+ * Note that the origin is at the left end of the baseline.
+ */
+void
+pango2_line_get_trimmed_extents (Pango2Line *line,
+ Pango2LeadingTrim trim,
+ Pango2Rectangle *logical_rect)
+{
+ Pango2Rectangle ink = { 0, };
+
+ if (line->has_extents && trim == PANGO2_LEADING_TRIM_NONE)
+ {
+ *logical_rect = line->logical_rect;
+ return;
+ }
+
+ compute_extents (line, trim, &ink, logical_rect);
+}
+
+/* }}} */
+/* {{{ Editing API */
+
+/**
+ * pango2_line_layout_index_to_pos:
+ * @line: a `Pango2Line`
+ * @idx: byte index within @line
+ * @pos: (out): rectangle in which to store the position of the grapheme
+ *
+ * Converts from an index within a `Pango2Line` to the
+ * position corresponding to the grapheme at that index.
+ *
+ * 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.
+ *
+ * Note that @idx is allowed to be @line->start_index + @line->length.
+ */
+void
+pango2_line_index_to_pos (Pango2Line *line,
+ int idx,
+ Pango2Rectangle *pos)
+{
+ Pango2Rectangle run_logical;
+ Pango2Rectangle line_logical;
+ Pango2Run *run = NULL;
+ int x_pos;
+
+ pango2_line_get_extents (line, NULL, &line_logical);
+
+ if (!line->runs)
+ {
+ *pos = line_logical;
+ return;
+ }
+
+ if (idx == line->start_index + line->length)
+ run = g_slist_last (line->runs)->data;
+ else
+ pango2_line_index_to_run (line, idx, &run);
+
+ pango2_run_get_extents (run, PANGO2_LEADING_TRIM_BOTH, NULL, &run_logical);
+
+ pos->y = run_logical.y;
+ pos->height = run_logical.height;
+
+ /* FIXME: avoid iterating through the runs multiple times */
+
+ pango2_line_index_to_x (line, idx, 0, &x_pos);
+ pos->x = line_logical.x + x_pos;
+
+ if (idx < line->start_index + line->length)
+ {
+ pango2_line_index_to_x (line, idx, 1, &x_pos);
+ pos->width = (line_logical.x + x_pos) - pos->x;
+ }
+ else
+ pos->width = 0;
+}
+
+/**
+ * pango2_line_index_to_x:
+ * @line: a `Pango2Line`
+ * @idx: byte index within @line
+ * @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 Pango2 units)
+ *
+ * Converts an index within a `Pango2Line` to a X position.
+ *
+ * Note that @idx is allowed to be @line->start_index + @line->length.
+ */
+void
+pango2_line_index_to_x (Pango2Line *line,
+ int index,
+ int trailing,
+ int *x_pos)
+{
+ GSList *run_list = line->runs;
+ int width = 0;
+
+ while (run_list)
+ {
+ Pango2GlyphItem *run = run_list->data;
+
+ if (run->item->offset <= index && run->item->offset + run->item->length > index)
+ {
+ int offset = g_utf8_pointer_to_offset (line->data->text, line->data->text + index);
+ int attr_offset;
+
+ if (trailing)
+ {
+ while (index < line->start_index + line->length &&
+ offset + 1 < line->data->n_chars &&
+ !line->data->log_attrs[offset + 1].is_cursor_position)
+ {
+ offset++;
+ index = g_utf8_next_char (line->data->text + index) - line->data->text;
+ }
+ }
+ else
+ {
+ while (index > line->start_index &&
+ !line->data->log_attrs[offset].is_cursor_position)
+ {
+ offset--;
+ index = g_utf8_prev_char (line->data->text + index) - line->data->text;
+ }
+ }
+
+ attr_offset = run->item->char_offset;
+ pango2_glyph_string_index_to_x_full (run->glyphs,
+ line->data->text + run->item->offset,
+ run->item->length,
+ &run->item->analysis,
+ line->data->log_attrs + attr_offset,
+ index - run->item->offset, trailing, x_pos);
+ if (x_pos)
+ *x_pos += width;
+
+ return;
+ }
+
+ width += pango2_glyph_string_get_width (run->glyphs);
+
+ run_list = run_list->next;
+ }
+
+ if (x_pos)
+ *x_pos = width;
+}
+
+/**
+ * pango2_line_x_to_index:
+ * @line: a `Pango2Line`
+ * @x: the X offset (in Pango2 units) from the left edge of the line
+ * @idx: (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.
+ *
+ * Converts from x offset to the byte index of the corresponding character
+ * within the text of the line.
+ *
+ * If @x is outside the line, @idx 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 @idx and @trailing. An X position to the
+ * left of the line results in @idx 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.
+ *
+ * Return value: %FALSE if @x_pos was outside the line, %TRUE if inside
+ */
+gboolean
+pango2_line_x_to_index (Pango2Line *line,
+ int x_pos,
+ int *index,
+ int *trailing)
+{
+ GSList *tmp_list;
+ int start_pos = 0;
+ int first_index = 0; /* line->start_index */
+ int first_offset;
+ int last_index; /* start of last grapheme in line */
+ int last_offset;
+ int end_index; /* end iterator for line */
+ int end_offset; /* end iterator for line */
+ int last_trailing;
+ gboolean suppress_last_trailing;
+
+ /* 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 (line->data->text, line->data->text + line->start_index);
+
+ end_index = first_index + line->length;
+ end_offset = first_offset + g_utf8_pointer_to_offset (line->data->text + first_index, line->data->text + end_index);
+
+ last_index = end_index;
+ last_offset = end_offset;
+ last_trailing = 0;
+ do
+ {
+ last_index = g_utf8_prev_char (line->data->text + last_index) - line->data->text;
+ last_offset--;
+ last_trailing++;
+ }
+ while (last_offset > first_offset && !line->data->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.
+ */
+ if (line->wrapped)
+ suppress_last_trailing = TRUE;
+ else
+ suppress_last_trailing = FALSE;
+
+ if (x_pos < 0)
+ {
+ /* pick the leftmost char */
+ if (index)
+ *index = (line->direction == PANGO2_DIRECTION_LTR) ? first_index : last_index;
+ /* and its leftmost edge */
+ if (trailing)
+ *trailing = (line->direction == PANGO2_DIRECTION_LTR || suppress_last_trailing) ? 0 : last_trailing;
+
+ return FALSE;
+ }
+
+ tmp_list = line->runs;
+ while (tmp_list)
+ {
+ Pango2GlyphItem *run = tmp_list->data;
+ int logical_width;
+
+ logical_width = pango2_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;
+
+ pango2_glyph_string_x_to_index (run->glyphs,
+ line->data->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 (line->data->text, line->data->text + char_index);
+
+ grapheme_start_offset = offset;
+ grapheme_start_index = char_index;
+ while (grapheme_start_offset > first_offset &&
+ !line->data->log_attrs[grapheme_start_offset].is_cursor_position)
+ {
+ grapheme_start_index = g_utf8_prev_char (line->data->text + grapheme_start_index) - line->data->text;
+ grapheme_start_offset--;
+ }
+
+ grapheme_end_offset = offset;
+ do
+ {
+ grapheme_end_offset++;
+ }
+
+ while (grapheme_end_offset < end_offset &&
+ !line->data->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->direction == PANGO2_DIRECTION_LTR) ? last_index : first_index;
+
+ /* and its rightmost edge */
+ if (trailing)
+ *trailing = (line->direction == PANGO2_DIRECTION_LTR && !suppress_last_trailing) ? last_trailing : 0;
+
+ return FALSE;
+}
+
+/* }}} */
+/* {{{ Cursor positioning */
+
+/**
+ * pango2_line_get_cursor_pos:
+ * @line: a `Pango2Line`
+ * @idx: 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
+ *
+ * Given an index within @line, determines the positions that of the
+ * strong and weak cursors if the insertion point is at that index.
+ *
+ * Note that @idx is allowed to be @line->start_index + @line->length.
+ *
+ * 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>
+ *
+ * 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
+pango2_line_get_cursor_pos (Pango2Line *line,
+ int idx,
+ Pango2Rectangle *strong_pos,
+ Pango2Rectangle *weak_pos)
+{
+ Pango2Rectangle line_rect = { 666, };
+ Pango2Rectangle run_rect = { 666, };
+ Pango2Direction dir1, dir2;
+ int level1, level2;
+ Pango2Run *run = NULL;
+ int x1_trailing;
+ int x2;
+
+ if (idx >= line->start_index + line->length)
+ {
+ if (line->runs)
+ run = g_slist_last (line->runs)->data;
+ }
+ else
+ pango2_line_index_to_run (line, idx, &run);
+
+ pango2_line_get_extents (line, NULL, &line_rect);
+ if (run)
+ pango2_run_get_extents (run, PANGO2_LEADING_TRIM_BOTH, NULL, &run_rect);
+ else
+ {
+ run_rect = line_rect;
+ x1_trailing = x2 = line_rect.width;
+ goto done;
+ }
+
+ /* Examine the trailing edge of the character before the cursor */
+ if (idx == line->start_index)
+ {
+ dir1 = line->direction;
+ level1 = dir1 == PANGO2_DIRECTION_LTR ? 0 : 1;
+ if (line->direction == PANGO2_DIRECTION_LTR)
+ x1_trailing = 0;
+ else
+ x1_trailing = line_rect.width;
+ }
+ else
+ {
+ int prev_index = g_utf8_prev_char (line->data->text + idx) - line->data->text;
+
+ if (prev_index >= line->start_index + line->length)
+ {
+ dir1 = line->direction;
+ level1 = dir1 == PANGO2_DIRECTION_LTR ? 0 : 1;
+ x1_trailing = line_rect.width;
+ }
+ else
+ {
+ Pango2Run *prev_run;
+
+ pango2_line_index_to_run (line, prev_index, &prev_run);
+ level1 = pango2_run_get_glyph_item (prev_run)->item->analysis.level;
+ dir1 = level1 % 2 ? PANGO2_DIRECTION_RTL : PANGO2_DIRECTION_LTR;
+ pango2_line_index_to_x (line, prev_index, TRUE, &x1_trailing);
+ }
+ }
+
+ /* Examine the leading edge of the character after the cursor */
+ if (idx >= line->start_index + line->length)
+ {
+ dir2 = line->direction;
+ level2 = dir2 == PANGO2_DIRECTION_LTR ? 0 : 1;
+ if (line->direction == PANGO2_DIRECTION_LTR)
+ x2 = line_rect.width;
+ else
+ x2 = 0;
+ }
+ else
+ {
+ pango2_line_index_to_x (line, idx, FALSE, &x2);
+ level2 = pango2_run_get_glyph_item (run)->item->analysis.level;
+ dir2 = level2 % 2 ? PANGO2_DIRECTION_RTL : PANGO2_DIRECTION_LTR;
+ }
+
+done:
+ if (strong_pos)
+ {
+ strong_pos->x = line_rect.x;
+
+ if (dir1 == line->direction &&
+ (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 == line->direction &&
+ (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;
+ }
+}
+
+/**
+ * pango2_line_get_caret_pos:
+ * @line: a `Pango2Line`
+ * @idx: 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
+ *
+ * Given an index within @line, determines the positions of the
+ * strong and weak cursors if the insertion point is at that index.
+ *
+ * Note that @idx is allowed to be @line->start_index + @line->length.
+ *
+ * This is a variant of [method@Pango2.Line.get_cursor_pos] that applies
+ * font metric information about caret slope and offset to the positions
+ * it returns.
+ *
+ * <picture>
+ * <source srcset="caret-metrics-dark.png" media="(prefers-color-scheme: dark)">
+ * <img alt="Caret metrics" src="caret-metrics-light.png">
+ * </picture>
+ */
+void
+pango2_line_get_caret_pos (Pango2Line *line,
+ int idx,
+ Pango2Rectangle *strong_pos,
+ Pango2Rectangle *weak_pos)
+{
+ Pango2Run *run = NULL;
+ Pango2GlyphItem *glyph_item;
+ hb_font_t *hb_font;
+ hb_position_t caret_offset, caret_run, caret_rise, descender;
+
+ pango2_line_get_cursor_pos (line, idx, strong_pos, weak_pos);
+
+ if (idx >= line->start_index + line->length)
+ {
+ if (line->runs)
+ run = g_slist_last (line->runs)->data;
+ }
+ else
+ pango2_line_index_to_run (line, idx, &run);
+
+ if (!run)
+ return;
+
+ glyph_item = pango2_run_get_glyph_item (run);
+ hb_font = pango2_font_get_hb_font (glyph_item->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 / (double) caret_rise) * (y_scale / (double) 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;
+ }
+ }
+}
+
+/* }}} */
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-line.h b/pango2/pango-line.h
new file mode 100644
index 00000000..31458d41
--- /dev/null
+++ b/pango2/pango-line.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-run.h>
+
+G_BEGIN_DECLS
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_line_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Line * pango2_line_copy (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_free (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Line * pango2_line_justify (Pango2Line *line,
+ int width);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_line_get_run_count (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Run ** pango2_line_get_runs (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_line_get_text (Pango2Line *line,
+ int *start_index,
+ int *length);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_line_get_start_index (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_line_get_length (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+const Pango2LogAttr * pango2_line_get_log_attrs (Pango2Line *line,
+ int *start_offset,
+ int *n_attrs);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_is_wrapped (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_is_ellipsized (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_is_hyphenated (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_is_justified (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_is_paragraph_start (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_is_paragraph_end (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Direction pango2_line_get_resolved_direction (Pango2Line *line);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_get_extents (Pango2Line *line,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_get_trimmed_extents (Pango2Line *line,
+ Pango2LeadingTrim trim,
+ Pango2Rectangle *logical_rect);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_index_to_pos (Pango2Line *line,
+ int idx,
+ Pango2Rectangle *pos);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_index_to_x (Pango2Line *line,
+ int idx,
+ int trailing,
+ int *x_pos);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_line_x_to_index (Pango2Line *line,
+ int x,
+ int *idx,
+ int *trailing);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_get_cursor_pos (Pango2Line *line,
+ int idx,
+ Pango2Rectangle *strong_pos,
+ Pango2Rectangle *weak_pos);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_line_get_caret_pos (Pango2Line *line,
+ int idx,
+ Pango2Rectangle *strong_pos,
+ Pango2Rectangle *weak_pos);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2Line, pango2_line_free)
+
+G_END_DECLS
diff --git a/pango2/pango-lines-private.h b/pango2/pango-lines-private.h
new file mode 100644
index 00000000..8832fba0
--- /dev/null
+++ b/pango2/pango-lines-private.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-lines.h"
+
+
+typedef struct _Position Position;
+struct _Position
+{
+ int x, y;
+};
+
+struct _Pango2Lines
+{
+ GObject parent_instance;
+
+ GPtrArray *lines;
+ GArray *positions;
+ guint serial;
+};
diff --git a/pango2/pango-lines.c b/pango2/pango-lines.c
new file mode 100644
index 00000000..e528ec05
--- /dev/null
+++ b/pango2/pango-lines.c
@@ -0,0 +1,1332 @@
+#include "config.h"
+
+#include "pango-lines-private.h"
+#include "pango-line-private.h"
+#include "pango-item-private.h"
+#include "pango-run-private.h"
+#include "pango-line-iter-private.h"
+
+/**
+ * Pango2Lines:
+ *
+ * A `Pango2Lines` object represents the result of formatting an
+ * entire paragraph (or more) of text.
+ *
+ * A `Pango2Lines` object contains a list of `Pango2Line` objects,
+ * together with information about where to position each line
+ * in layout coordinates.
+ *
+ * `Pango2Lines` has APIs to query collective information about
+ * its lines (such as ellipsization or unknown glyphs), to translate
+ * between logical character positions within the text and the physical
+ * position of the resulting glyphs, and to determine cursor positions
+ * for editing the text.
+ *
+ * One way to obtain a `Pango2Lines` object is to use a [class@Pango2.Layout].
+ * But it is also possible to populate a `Pango2Lines` manually with lines
+ * produced by a [class@Pango2.LineBreaker] object.
+ *
+ * Note that the lines that make up a `Pango2Lines` object don't have to
+ * share the same underlying text. Therefore, using byte indexes to refer
+ * to positions within the `Pango2Lines` is, in general, ambiguous. All the
+ * `Pango2Lines` APIs that take a byte index as argument or return one have
+ * a `Pango2Line*` companion argument to handle this situation. When all
+ * the lines in the `Pango2Lines` share the same text (such as when they
+ * originate from the same `Pango2Layout`), it is safe to always pass `NULL`
+ * for the `Pango2Lines*`.
+ *
+ * The most convenient way to access the visual extents and components
+ * of a `Pango2Lines` is via a [struct@Pango2.LineIter] iterator.
+ */
+
+/* {{{ Pango2Lines implementation */
+
+struct _Pango2LinesClass
+{
+ GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (Pango2Lines, pango2_lines, G_TYPE_OBJECT)
+
+enum {
+ PROP_UNKNOWN_GLYPHS_COUNT = 1,
+ PROP_WRAPPED,
+ PROP_ELLIPSIZED,
+ PROP_HYPHENATED,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+static void
+pango2_lines_init (Pango2Lines *lines)
+{
+ lines->serial = 1;
+ lines->lines = g_ptr_array_new_with_free_func ((GDestroyNotify) pango2_line_free);
+ lines->positions = g_array_new (FALSE, FALSE, sizeof (Position));
+}
+
+static void
+pango2_lines_finalize (GObject *object)
+{
+ Pango2Lines *lines = PANGO2_LINES (object);
+
+ g_ptr_array_unref (lines->lines);
+ g_array_unref (lines->positions);
+
+ G_OBJECT_CLASS (pango2_lines_parent_class)->finalize (object);
+}
+
+static void
+pango2_lines_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Pango2Lines *self = PANGO2_LINES (object);
+
+ switch (prop_id)
+ {
+ case PROP_UNKNOWN_GLYPHS_COUNT:
+ g_value_set_int (value, pango2_lines_get_unknown_glyphs_count (self));
+ break;
+
+ case PROP_WRAPPED:
+ g_value_set_boolean (value, pango2_lines_is_wrapped (self));
+ break;
+
+ case PROP_ELLIPSIZED:
+ g_value_set_boolean (value, pango2_lines_is_ellipsized (self));
+ break;
+
+ case PROP_HYPHENATED:
+ g_value_set_boolean (value, pango2_lines_is_hyphenated (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+pango2_lines_class_init (Pango2LinesClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = pango2_lines_finalize;
+ object_class->get_property = pango2_lines_get_property;
+
+ /**
+ * Pango2Lines:unknown-glyphs-count: (attributes org.gtk.Property.get=pango2_lines_get_unknown_glyphs_count)
+ *
+ * The number of unknown glyphs in the `Pango2Lines`.
+ */
+ properties[PROP_UNKNOWN_GLYPHS_COUNT] =
+ g_param_spec_uint ("unknown-glyphs-count", NULL, NULL, 0, G_MAXUINT, 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Lines:wrapped: (attributes org.gtk.Property.get=pango2_lines_is_wrapped)
+ *
+ * `TRUE` if the `Pango2Lines` contains any wrapped lines.
+ */
+ properties[PROP_WRAPPED] =
+ g_param_spec_boolean ("wrapped", NULL, NULL, FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Lines:ellipsized: (attributes org.gtk.Property.get=pango2_lines_is_ellipsized)
+ *
+ * `TRUE` if the `Pango2Lines` contains any ellipsized lines.
+ */
+ properties[PROP_ELLIPSIZED] =
+ g_param_spec_boolean ("ellipsized", NULL, NULL, FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * Pango2Lines:hyphenated: (attributes org.gtk.Property.get=pango2_lines_is_hyphenated)
+ *
+ * `TRUE` if the `Pango2Lines` contains any hyphenated lines.
+ */
+ properties[PROP_HYPHENATED] =
+ g_param_spec_boolean ("hyphenated", NULL, NULL, FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+/* }}} */
+/* {{{ Utilities*/
+
+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
+pango2_line_get_cursors (Pango2Lines *lines,
+ Pango2Line *line,
+ gboolean strong,
+ GArray *cursors)
+{
+ const char *start, *end;
+ int start_offset;
+ int j;
+ const char *p;
+ Pango2Rectangle pos;
+ Position offset;
+
+ g_assert (g_array_get_element_size (cursors) == sizeof (CursorPos));
+ g_assert (cursors->len == 0);
+
+ for (int i = 0; i < lines->lines->len; i++)
+ {
+ if (line == g_ptr_array_index (lines->lines, i))
+ {
+ offset = g_array_index (lines->positions, Position, i);
+ break;
+ }
+ }
+
+ start = line->data->text + line->start_index;
+ end = start + line->length;
+ start_offset = line->start_offset;
+
+ if (line->ends_paragraph)
+ end++;
+
+ for (j = start_offset, p = start; p < end; j++, p = g_utf8_next_char (p))
+ {
+ int idx = p - line->data->text;
+
+ if (line->data->log_attrs[j].is_cursor_position)
+ {
+ CursorPos cursor;
+
+ pango2_line_get_cursor_pos (line, idx,
+ strong ? &pos : NULL,
+ strong ? NULL : &pos);
+
+ cursor.x = pos.x + offset.x;
+ cursor.pos = idx;
+ g_array_append_val (cursors, cursor);
+ }
+ }
+
+ g_array_sort (cursors, compare_cursor);
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_lines_new:
+ *
+ * Creates an empty `Pango2Lines` object.
+ *
+ * Returns: a newly allocated `Pango2Lines`
+ */
+Pango2Lines *
+pango2_lines_new (void)
+{
+ return g_object_new (PANGO2_TYPE_LINES, NULL);
+}
+
+/**
+ * pango2_lines_get_serial:
+ * @lines: a `Pango2Lines`
+ *
+ * Returns the current serial number of @lines.
+ *
+ * The serial number is initialized to an small number larger than zero
+ * when a new layout is created and is increased whenever the @lines
+ * object is changed (i.e. more lines are added).
+ *
+ * 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 `Pango2Lines`,
+ * and is useful for example to decide whether a layout needs redrawing.
+ *
+ * Return value: The current serial number of @lines
+ */
+guint
+pango2_lines_get_serial (Pango2Lines *lines)
+{
+ return lines->serial;
+}
+
+/**
+ * pango2_lines_add_line:
+ * @lines: a `Pango2Lines`
+ * @line: (transfer full): the `Pango2Line` to add
+ * @line_x: X coordinate of the position
+ * @line_y: Y coordinate of the position
+ *
+ * Adds a line to the `Pango2Lines`.
+ *
+ * The coordinates are the position
+ * at which @line is placed.
+ *
+ * Note that this function takes ownership of the line.
+ */
+void
+pango2_lines_add_line (Pango2Lines *lines,
+ Pango2Line *line,
+ int x_line,
+ int y_line)
+{
+ Position pos = { x_line, y_line };
+
+ g_ptr_array_add (lines->lines, line);
+ g_array_append_val (lines->positions, pos);
+
+ lines->serial++;
+ if (lines->serial == 0)
+ lines->serial++;
+
+ g_object_notify_by_pspec (G_OBJECT (lines), properties[PROP_UNKNOWN_GLYPHS_COUNT]);
+ g_object_notify_by_pspec (G_OBJECT (lines), properties[PROP_WRAPPED]);
+ g_object_notify_by_pspec (G_OBJECT (lines), properties[PROP_ELLIPSIZED]);
+ g_object_notify_by_pspec (G_OBJECT (lines), properties[PROP_HYPHENATED]);
+}
+
+/**
+ * pango2_lines_get_iter:
+ * @lines: a `Pango2Lines`
+ *
+ * Returns an iterator to iterate over the visual extents of the lines.
+ *
+ * The returned iterator will be invaliated when more
+ * lines are added to @lines, and can't be used anymore
+ * after that point.
+ *
+ * Note that the iter holds a reference to @lines.
+ *
+ * Return value: the new `Pango2LineIter`
+ */
+Pango2LineIter *
+pango2_lines_get_iter (Pango2Lines *lines)
+{
+ return pango2_line_iter_new (lines);
+}
+
+/**
+ * pango2_lines_get_line_count:
+ * @lines: a `Pango2Lines`
+ *
+ * Gets the number of lines in @lines.
+ *
+ * Returns: the number of lines'
+ */
+int
+pango2_lines_get_line_count (Pango2Lines *lines)
+{
+ return lines->lines->len;
+}
+
+/**
+ * pango2_lines_get_lines:
+ * @lines: a `Pango2Lines`
+ *
+ * Gets the lines.
+ *
+ * The length of the returned array can be obtained with
+ * [method@Pango2.Lines.get_line_count].
+ *
+ * Returns: (transfer none): the array of lines
+ */
+Pango2Line **
+pango2_lines_get_lines (Pango2Lines *lines)
+{
+ g_return_val_if_fail (PANGO2_IS_LINES (lines), NULL);
+
+ return (Pango2Line **)lines->lines->pdata;
+}
+
+/**
+ * pango2_lines_get_line_position:
+ * @lines: a `Pango2Lines`
+ * @num: the index of the line to get the position for
+ * @line_x: (out) (optional): return location for the X coordinate
+ * @line_y: (out) (optional): return location for the Y coordinate
+ *
+ * Gets the position of a line.
+ */
+void
+pango2_lines_get_line_position (Pango2Lines *lines,
+ int num,
+ int *line_x,
+ int *line_y)
+{
+ Position *pos;
+
+ g_return_if_fail (PANGO2_IS_LINES (lines));
+
+ if (num >= lines->lines->len)
+ return;
+
+ pos = &g_array_index (lines->positions, Position, num);
+
+ if (line_x)
+ *line_x = pos->x;
+ if (line_y)
+ *line_y = pos->y;
+}
+
+/* {{{ Miscellaneous */
+
+/**
+ * pango2_lines_get_unknown_glyphs_count:
+ * @lines: a `Pango2Lines` object
+ *
+ * Counts the number of unknown glyphs in the `Pango2Lines`
+ *
+ * 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 `PANGO2_ATTR_FALLBACK`, to check if a
+ * certain font supports all the characters in the string.
+ *
+ * Return value: The number of unknown glyphs in @lines
+ */
+int
+pango2_lines_get_unknown_glyphs_count (Pango2Lines *lines)
+{
+ int count = 0;
+
+ for (int i = 0; i < lines->lines->len; i++)
+ {
+ Pango2Line *line = g_ptr_array_index (lines->lines, i);
+
+ for (GSList *l = line->runs; l; l = l->next)
+ {
+ Pango2GlyphItem *run = l->data;
+
+ for (int j = 0; j < run->glyphs->num_glyphs; j++)
+ {
+ if (run->glyphs->glyphs[j].glyph & PANGO2_GLYPH_UNKNOWN_FLAG)
+ count++;
+ }
+ }
+ }
+
+ return count;
+}
+
+/**
+ * pango2_lines_is_wrapped:
+ * @lines: a `Pango2Lines` object
+ *
+ * Returns whether any of the lines is wrapped.
+ *
+ * Returns: `TRUE` if @lines is wrapped
+ */
+gboolean
+pango2_lines_is_wrapped (Pango2Lines *lines)
+{
+ for (int i = 0; i < lines->lines->len; i++)
+ {
+ Pango2Line *line = g_ptr_array_index (lines->lines, i);
+ if (pango2_line_is_wrapped (line))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * pango2_lines_is_ellipsized:
+ * @lines: a `Pango2Lines` object
+ *
+ * Returns whether any of the lines is ellipsized.
+ *
+ * Returns: `TRUE` if @lines is ellipsized
+ */
+gboolean
+pango2_lines_is_ellipsized (Pango2Lines *lines)
+{
+ for (int i = 0; i < lines->lines->len; i++)
+ {
+ Pango2Line *line = g_ptr_array_index (lines->lines, i);
+ if (pango2_line_is_ellipsized (line))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * pango2_lines_is_hyphenated:
+ * @lines: a `Pango2Lines` object
+ *
+ * Returns whether any of the lines is hyphenated.
+ *
+ * Returns: `TRUE` if @lines is hyphenated
+ */
+gboolean
+pango2_lines_is_hyphenated (Pango2Lines *lines)
+{
+ for (int i = 0; i < lines->lines->len; i++)
+ {
+ Pango2Line *line = g_ptr_array_index (lines->lines, i);
+ if (pango2_line_is_hyphenated (line))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* }}} */
+/* {{{ Extents */
+
+/**
+ * pango2_lines_get_extents:
+ * @lines: a `Pango2Lines` object
+ * @ink_rect: (out) (optional): return location for the ink extents
+ * @logical_rect: (out) (optional): return location for the logical extents
+ *
+ * Computes the extents of the lines.
+ *
+ * Logical extents are usually what you want for positioning things. Note
+ * that the 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.
+ *
+ * The extents are given in layout coordinates and in Pango2 units; layout
+ * coordinates begin at the top left corner.
+ */
+void
+pango2_lines_get_extents (Pango2Lines *lines,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ for (int i = 0; i < lines->lines->len; i++)
+ {
+ Pango2Line *line = g_ptr_array_index (lines->lines, i);
+ Position *pos = &g_array_index (lines->positions, Position, i);
+ Pango2Rectangle line_ink;
+ Pango2Rectangle line_logical;
+ Pango2LeadingTrim trim = PANGO2_LEADING_TRIM_NONE;
+
+ if (line->starts_paragraph)
+ trim |= PANGO2_LEADING_TRIM_START;
+ if (line->ends_paragraph)
+ trim |= PANGO2_LEADING_TRIM_END;
+
+ pango2_line_get_extents (line, &line_ink, NULL);
+ pango2_line_get_trimmed_extents (line, trim, &line_logical);
+
+ line_ink.x += pos->x;
+ line_ink.y += pos->y;
+ line_logical.x += pos->x;
+ line_logical.y += pos->y;
+
+ if (i == 0)
+ {
+ if (ink_rect)
+ *ink_rect = line_ink;
+ if (logical_rect)
+ *logical_rect = line_logical;
+ }
+ else
+ {
+ int new_pos;
+
+ if (ink_rect)
+ {
+ new_pos = MIN (ink_rect->x, line_ink.x);
+ ink_rect->width = MAX (ink_rect->x + ink_rect->width, line_ink.x + line_ink.width) - new_pos;
+ ink_rect->x = new_pos;
+
+ new_pos = MIN (ink_rect->y, line_ink.y);
+ ink_rect->height = MAX (ink_rect->y + ink_rect->height, line_ink.y + line_ink.height) - new_pos;
+ ink_rect->y = new_pos;
+ }
+ if (logical_rect)
+ {
+ new_pos = MIN (logical_rect->x, line_logical.x);
+ logical_rect->width = MAX (logical_rect->x + logical_rect->width, line_logical.x + line_logical.width) - new_pos;
+ logical_rect->x = new_pos;
+
+ new_pos = MIN (logical_rect->y, line_logical.y);
+ logical_rect->height = MAX (logical_rect->y + logical_rect->height, line_logical.y + line_logical.height) - new_pos;
+ logical_rect->y = new_pos;
+ }
+ }
+ }
+}
+
+/**
+ * pango2_lines_get_size:
+ * @lines: a `Pango2Lines`
+ * @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 `Pango2Lines`
+ * in Pango2 units.
+ *
+ * This is simply a convenience function around
+ * [method@Pango2.Lines.get_extents].
+ */
+void
+pango2_lines_get_size (Pango2Lines *lines,
+ int *width,
+ int *height)
+{
+ Pango2Rectangle ext;
+
+ pango2_lines_get_extents (lines, NULL, &ext);
+
+ if (width)
+ *width = ext.width;
+ if (height)
+ *height = ext.height;
+}
+
+/**
+ * pango2_lines_get_baseline:
+ * @lines: a `Pango2Lines` object
+ *
+ * Gets the Y position of baseline of the first line.
+ *
+ * Return value: baseline of first line
+ */
+int
+pango2_lines_get_baseline (Pango2Lines *lines)
+{
+ Position *pos;
+
+ g_return_val_if_fail (PANGO2_IS_LINES (lines), 0);
+
+ if (lines->lines->len == 0)
+ return 0;
+
+ pos = &g_array_index (lines->positions, Position, 0);
+
+ return pos->y;
+}
+
+/**
+ * pango2_lines_get_x_ranges:
+ * @lines: a `Pango2Lines` object
+ * @line: the `Pango2Line` in @lines whose x ranges will be reported
+ * @start_line: (nullable): `Pango2Line` wrt to which @start_index is
+ * interpreted or `NULL` for the first matching line
+ * @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_line: (nullable): `Pango2Line` wrt to which @end_index is
+ * interpreted or `NULL` for the first matching line
+ * @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 [GLib.free]. The coordinates are relative to the layout and are in
+ * Pango2 units.
+ * @n_ranges: The number of ranges stored in @ranges
+ *
+ * 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.
+ */
+void
+pango2_lines_get_x_ranges (Pango2Lines *lines,
+ Pango2Line *line,
+ Pango2Line *start_line,
+ int start_index,
+ Pango2Line *end_line,
+ int end_index,
+ int **ranges,
+ int *n_ranges)
+{
+ int x_offset;
+ int line_no, start_line_no, end_line_no;
+ Pango2Direction dir;
+ int range_count;
+ int width;
+ Pango2Rectangle ext;
+ int accumulated_width;
+
+ g_return_if_fail (PANGO2_IS_LINES (lines));
+ g_return_if_fail (ranges != NULL);
+ g_return_if_fail (n_ranges != NULL);
+
+ pango2_lines_index_to_line (lines, line->start_index, &line, &line_no, &x_offset, NULL);
+ g_return_if_fail (line != NULL);
+
+ pango2_lines_index_to_line (lines, start_index, &start_line, &start_line_no, NULL, NULL);
+ g_return_if_fail (start_line != NULL);
+ g_return_if_fail (start_line_no <= line_no);
+
+ pango2_lines_index_to_line (lines, end_index, &end_line, &end_line_no, NULL, NULL);
+ g_return_if_fail (end_line != NULL);
+ g_return_if_fail (end_line_no >= line_no);
+
+ /* clamp start_index and end_index to be inside line, we'll use
+ * the line numbers to determine overshoot.
+ */
+ if (start_line_no < line_no || line_no < end_line_no)
+ {
+ if (start_line_no < line_no)
+ start_index = line->start_index;
+ if (end_line_no > line_no)
+ end_index = line->start_index + line->length;
+ }
+
+ *ranges = g_new (int, 2 * (2 + g_slist_length (line->runs)));
+ range_count = 0;
+
+ dir = pango2_line_get_resolved_direction (line);
+
+ if (x_offset > 0 &&
+ ((dir == PANGO2_DIRECTION_LTR && start_line_no < line_no) ||
+ (dir == PANGO2_DIRECTION_RTL && end_line_no > line_no)))
+ {
+ /* add range from left edge of layout to line start */
+ (*ranges)[2*range_count] = 0;
+ (*ranges)[2*range_count + 1] = x_offset;
+ range_count++;
+ }
+
+ accumulated_width = 0;
+ for (int i = 0; i < pango2_line_get_run_count (line); i++)
+ {
+ Pango2GlyphItem *run = pango2_run_get_glyph_item (pango2_line_get_runs (line)[i]);
+
+ if ((start_index < run->item->offset + run->item->length &&
+ end_index > run->item->offset))
+ {
+ 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->data->text + run_end_index) - line->data->text;
+
+ attr_offset = run->item->char_offset;
+
+ pango2_glyph_string_index_to_x_full (run->glyphs,
+ line->data->text + run->item->offset,
+ run->item->length,
+ &run->item->analysis,
+ line->data->log_attrs + attr_offset,
+ run_start_index - run->item->offset, FALSE,
+ &run_start_x);
+ pango2_glyph_string_index_to_x_full (run->glyphs,
+ line->data->text + run->item->offset,
+ run->item->length,
+ &run->item->analysis,
+ line->data->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 (i + 1 < pango2_line_get_run_count (line))
+ accumulated_width += pango2_glyph_string_get_width (run->glyphs);
+ }
+
+ pango2_line_get_extents (line, NULL, &ext);
+ pango2_lines_get_size (lines, &width, NULL);
+
+ if (x_offset + ext.width < width &&
+ ((dir == PANGO2_DIRECTION_LTR && start_line_no < line_no) ||
+ (dir == PANGO2_DIRECTION_RTL && end_line_no > line_no)))
+ {
+ /* add range from line end to rigth edge of layout */
+ (*ranges)[2*range_count] = x_offset + ext.width;
+ (*ranges)[2*range_count + 1] = width;
+ range_count ++;
+ }
+
+ *n_ranges = range_count;
+}
+
+/* }}} */
+ /* { {{ Editing API */
+
+/**
+ * pango2_lines_index_to_line:
+ * @lines: a `Pango2Lines`
+ * @idx: index in @lines
+ * @line: (inout): if *@line is `NULL`, the found line will be returned here
+ * @line_no: (out) (optional): return location for line number
+ * @x_offset: (out) (optional): return location for X offset of line
+ * @y_offset: (out) (optional): return location for Y offset of line
+ *
+ * Given an index (and possibly line), determine the line number,
+ * and offset for the line.
+ *
+ * @idx may refer to any byte position inside @lines, as well as
+ * the position before the first or after the last character (i.e.
+ * line->start_index + line->length, for the last line).
+ *
+ * If @lines contains lines with different backing data (i.e.
+ * more than one line with line->start_index of zero), then
+ * *@line must be specified to disambiguate. If all lines
+ * are backed by the same data, it is save to pass `NULL`
+ * as *@line and use this function to find the line at @idx.
+ */
+void
+pango2_lines_index_to_line (Pango2Lines *lines,
+ int idx,
+ Pango2Line **line,
+ int *line_no,
+ int *x_offset,
+ int *y_offset)
+{
+ Pango2Line *found = NULL;
+ int num;
+ int i;
+ Position pos;
+
+ g_return_if_fail (PANGO2_IS_LINES (lines));
+
+ for (i = 0; i < lines->lines->len; i++)
+ {
+ Pango2Line *l = g_ptr_array_index (lines->lines, i);
+
+ if (l->start_index > idx && found)
+ break;
+
+ found = l;
+ pos = g_array_index (lines->positions, Position, i);
+ num = i;
+
+ if (*line && *line == found)
+ break;
+
+ if (l->start_index + l->length > idx)
+ break;
+ }
+
+ if (found)
+ {
+ *line = found;
+ if (line_no)
+ *line_no = num;
+ if (x_offset)
+ *x_offset = pos.x;
+ if (y_offset)
+ *y_offset = pos.y;
+
+ return;
+ }
+
+ *line = NULL;
+}
+
+/**
+ * pango2_lines_pos_to_line:
+ * @lines: a `Pango2Lines` object
+ * @x: the X position (in Pango2 units)
+ * @y: the Y position (in Pango2 units)
+ * @line_x: (out) (optional): return location for the X offset of the line
+ * @line_y: (out) (optional): return location for the Y offset of the line
+ *
+ * Finds the line at the given position.
+ *
+ * If either the X or Y positions were not inside the layout, then the
+ * function returns `NULL`; on an exact hit, it returns the found line.
+
+ * Returns: (transfer none) (nullable): the line that was found
+ */
+Pango2Line *
+pango2_lines_pos_to_line (Pango2Lines *lines,
+ int x,
+ int y,
+ int *line_x,
+ int *line_y)
+{
+ g_return_val_if_fail (PANGO2_IS_LINES (lines), FALSE);
+
+ for (int i = 0; i < lines->lines->len; i++)
+ {
+ Pango2Line *line = g_ptr_array_index (lines->lines, i);
+ Position pos = g_array_index (lines->positions, Position, i);
+ Pango2Rectangle ext;
+
+ pango2_line_get_extents (line, NULL, &ext);
+
+ ext.x += pos.x;
+ ext.y += pos.y;
+
+ if (ext.x <= x && x <= ext.x + ext.width &&
+ ext.y <= y && y <= ext.y + ext.height)
+ {
+ if (line_x)
+ *line_x = pos.x;
+ if (line_y)
+ *line_y = pos.y;
+
+ return line;
+ }
+ }
+
+ if (line_x)
+ *line_x = 0;
+ if (line_y)
+ *line_y = 0;
+
+ return NULL;
+}
+
+/**
+ * pango2_lines_index_to_pos:
+ * @lines: a `Pango2Lines` object
+ * @line: (nullable): `Pango2Line` wrt to which @idx is interpreted
+ * or `NULL` for the first matching line
+ * @idx: byte index within @line
+ * @pos: (out): rectangle in which to store the position of the grapheme
+ *
+ * Converts from an index within a `Pango2Line` to the
+ * position corresponding to the grapheme at that index.
+ *
+ * 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.
+ *
+ * Note that @idx is allowed to be @line->start_index + @line->length
+ * for the position off the end of the last line.
+ */
+void
+pango2_lines_index_to_pos (Pango2Lines *lines,
+ Pango2Line *line,
+ int idx,
+ Pango2Rectangle *pos)
+{
+ int x_offset, y_offset;
+
+ g_return_if_fail (PANGO2_IS_LINES (lines));
+ g_return_if_fail (idx >= 0);
+ g_return_if_fail (pos != NULL);
+
+ pango2_lines_index_to_line (lines, idx, &line, NULL, &x_offset, &y_offset);
+
+ g_return_if_fail (line != NULL);
+
+ pango2_line_index_to_pos (line, idx, pos);
+ pos->x += x_offset;
+ pos->y += y_offset;
+}
+
+/**
+ * pango2_lines_pos_to_index:
+ * @lines: a `Pango2Lines` object
+ * @x: the X offset (in Pango2 units) from the left edge of the layout
+ * @y: the Y offset (in Pango2 units) from the top edge of the layout
+ * @idx: (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 lines to the byte index of the
+ * character at that position.
+ *
+ * Returns: (transfer none) (nullable): the line that was found
+ */
+Pango2Line *
+pango2_lines_pos_to_index (Pango2Lines *lines,
+ int x,
+ int y,
+ int *idx,
+ int *trailing)
+{
+ Pango2Line *line;
+ int x_offset;
+
+ g_return_val_if_fail (PANGO2_IS_LINES (lines), FALSE);
+ g_return_val_if_fail (idx != NULL, FALSE);
+ g_return_val_if_fail (trailing != NULL, FALSE);
+
+ line = pango2_lines_pos_to_line (lines, x, y, &x_offset, NULL);
+ if (line)
+ pango2_line_x_to_index (line, x - x_offset, idx, trailing);
+
+ return line;
+}
+
+/* }}} */
+/* {{{ Cursor positioning */
+
+/**
+ * pango2_lines_get_cursor_pos:
+ * @lines: a `Pango2Lines` object
+ * @line: (nullable): `Pango2Line` wrt to which @idx is interpreted
+ * or `NULL` for the first matching line
+ * @idx: 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
+ *
+ * Given an index within lines, determines the positions that of the
+ * strong and weak cursors if the insertion point is at that index.
+ *
+ * Note that @idx is allowed to be @line->start_index + @line->length
+ * for the position off the end of the last line.
+ *
+ * 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>
+ *
+ * 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
+pango2_lines_get_cursor_pos (Pango2Lines *lines,
+ Pango2Line *line,
+ int idx,
+ Pango2Rectangle *strong_pos,
+ Pango2Rectangle *weak_pos)
+{
+ int x_offset, y_offset;
+ Pango2Line *l;
+
+ g_return_if_fail (PANGO2_IS_LINES (lines));
+
+ l = line;
+ pango2_lines_index_to_line (lines, idx, &l, NULL, &x_offset, &y_offset);
+
+ g_return_if_fail (l != NULL);
+ g_return_if_fail (line == NULL || l == line);
+
+ line = l;
+
+ pango2_line_get_cursor_pos (line, idx, strong_pos, weak_pos);
+
+ if (strong_pos)
+ {
+ strong_pos->x += x_offset;
+ strong_pos->y += y_offset;
+ }
+ if (weak_pos)
+ {
+ weak_pos->x += x_offset;
+ weak_pos->y += y_offset;
+ }
+}
+
+/**
+ * pango2_lines_get_caret_pos:
+ * @lines: a `Pango2Lines` object
+ * @line: (nullable): `Pango2Line` wrt to which @idx is interpreted
+ * or `NULL` for the first matching line
+ * @idx: 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
+ *
+ * Given an index within a layout, determines the positions of the
+ * strong and weak cursors if the insertion point is at that index.
+ *
+ * Note that @idx is allowed to be @line->start_index + @line->length
+ * for the position off the end of the last line.
+ *
+ * This is a variant of [method@Pango2.Lines.get_cursor_pos] that applies
+ * font metric information about caret slope and offset to the positions
+ * it returns.
+ *
+ * <picture>
+ * <source srcset="caret-metrics-dark.png" media="(prefers-color-scheme: dark)">
+ * <img alt="Caret metrics" src="caret-metrics-light.png">
+ * </picture>
+ */
+void
+pango2_lines_get_caret_pos (Pango2Lines *lines,
+ Pango2Line *line,
+ int idx,
+ Pango2Rectangle *strong_pos,
+ Pango2Rectangle *weak_pos)
+{
+ int x_offset, y_offset;
+
+ g_return_if_fail (PANGO2_IS_LINES (lines));
+
+ pango2_lines_index_to_line (lines, idx, &line, NULL, &x_offset, &y_offset);
+
+ g_return_if_fail (line != NULL);
+
+ pango2_line_get_caret_pos (line, idx, strong_pos, weak_pos);
+
+ if (strong_pos)
+ {
+ strong_pos->x += x_offset;
+ strong_pos->y += y_offset;
+ }
+ if (weak_pos)
+ {
+ weak_pos->x += x_offset;
+ weak_pos->y += y_offset;
+ }
+}
+
+/**
+ * pango2_lines_move_cursor:
+ * @lines: a `Pango2Lines` object
+ * @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.
+ * @line: (nullable): `Pango2Line` wrt to which @idx is interpreted
+ * or `NULL` for the first matching line
+ * @idx: the byte index of the current cursor position
+ * @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_line: (nullable): `Pango2Line` wrt to which @new_idx is interpreted
+ * @new_idx: (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_idx 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_idx 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.
+ *
+ * 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.
+ */
+void
+pango2_lines_move_cursor (Pango2Lines *lines,
+ gboolean strong,
+ Pango2Line *line,
+ int idx,
+ int trailing,
+ int direction,
+ Pango2Line **new_line,
+ int *new_idx,
+ int *new_trailing)
+{
+ int line_no;
+ GArray *cursors;
+ int n_vis;
+ int vis_pos;
+ int start_offset;
+ gboolean off_start = FALSE;
+ gboolean off_end = FALSE;
+ Pango2Rectangle pos;
+ int j;
+
+ g_return_if_fail (PANGO2_IS_LINES (lines));
+ g_return_if_fail (idx >= 0);
+ g_return_if_fail (trailing >= 0);
+ g_return_if_fail (new_idx != NULL);
+ g_return_if_fail (new_trailing != NULL);
+
+ direction = (direction >= 0 ? 1 : -1);
+
+ pango2_lines_index_to_line (lines, idx, &line, &line_no, NULL, NULL);
+
+ g_return_if_fail (line != NULL);
+
+ while (trailing--)
+ idx = g_utf8_next_char (line->data->text + idx) - line->data->text;
+
+ /* Clamp old_index to fit on the line */
+ if (idx > (line->start_index + line->length))
+ idx = line->start_index + line->length;
+
+ cursors = g_array_new (FALSE, FALSE, sizeof (CursorPos));
+ pango2_line_get_cursors (lines, line, strong, cursors);
+
+ pango2_lines_get_cursor_pos (lines, line, idx, 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 &&
+ idx == line->start_index + line->length)
+ {
+ if (line->direction == PANGO2_DIRECTION_LTR)
+ vis_pos = cursors->len;
+ else
+ vis_pos = 0;
+ }
+
+ /* Handling movement between lines */
+ if (line->direction == PANGO2_DIRECTION_LTR)
+ {
+ if (idx == line->start_index && direction < 0)
+ off_start = TRUE;
+ if (idx == line->start_index + line->length && direction > 0)
+ off_end = TRUE;
+ }
+ else
+ {
+ if (idx == line->start_index + line->length && direction < 0)
+ off_start = TRUE;
+ if (idx == 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 (line_no == 0)
+ {
+ if (new_line)
+ *new_line = NULL;
+ *new_idx = -1;
+ *new_trailing = 0;
+ g_array_unref (cursors);
+ return;
+ }
+
+ line_no--;
+ line = g_ptr_array_index (lines->lines, line_no);
+ paragraph_boundary = (line->start_index + line->length != idx);
+ }
+ else
+ {
+ if (line_no == lines->lines->len - 1)
+ {
+ if (new_line)
+ *new_line = NULL;
+ *new_idx = G_MAXINT;
+ *new_trailing = 0;
+ g_array_unref (cursors);
+ return;
+ }
+
+ line_no++;
+ line = g_ptr_array_index (lines->lines, line_no);
+ paragraph_boundary = (line->start_index != idx);
+ }
+
+ g_array_set_size (cursors, 0);
+ pango2_line_get_cursors (lines, 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_idx = g_array_index (cursors, CursorPos, vis_pos).pos;
+ else if (vis_pos >= cursors->len - 1)
+ *new_idx = line->start_index + line->length;
+
+ *new_trailing = 0;
+
+ if (*new_idx == line->start_index + line->length && line->length > 0)
+ {
+ int log_pos;
+
+ start_offset = g_utf8_pointer_to_offset (line->data->text, line->data->text + line->start_index);
+ log_pos = start_offset + g_utf8_strlen (line->data->text + line->start_index, line->length);
+ do
+ {
+ log_pos--;
+ *new_idx = g_utf8_prev_char (line->data->text + *new_idx) - line->data->text;
+ (*new_trailing)++;
+ }
+ while (log_pos > start_offset && !line->data->log_attrs[log_pos].is_cursor_position);
+ }
+
+ if (new_line)
+ *new_line = line;
+
+ g_array_unref (cursors);
+}
+
+/* }}} */
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-lines.h b/pango2/pango-lines.h
new file mode 100644
index 00000000..f2a89f43
--- /dev/null
+++ b/pango2/pango-lines.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-line.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_LINES pango2_lines_get_type ()
+
+PANGO2_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (Pango2Lines, pango2_lines, PANGO2, LINES, GObject);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Lines * pango2_lines_new (void);
+
+PANGO2_AVAILABLE_IN_ALL
+guint pango2_lines_get_serial (Pango2Lines *lines);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_lines_add_line (Pango2Lines *lines,
+ Pango2Line *line,
+ int line_x,
+ int line_y);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_lines_get_line_count (Pango2Lines *lines);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Line ** pango2_lines_get_lines (Pango2Lines *lines);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_lines_get_line_position
+ (Pango2Lines *lines,
+ int num,
+ int *line_x,
+ int *line_y);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2LineIter * pango2_lines_get_iter (Pango2Lines *lines);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_lines_get_extents (Pango2Lines *lines,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_lines_get_size (Pango2Lines *lines,
+ int *width,
+ int *height);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_lines_get_baseline (Pango2Lines *lines);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_lines_get_x_ranges (Pango2Lines *lines,
+ Pango2Line *line,
+ Pango2Line *start_line,
+ int start_index,
+ Pango2Line *end_line,
+ int end_index,
+ int **ranges,
+ int *n_ranges);
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_lines_get_unknown_glyphs_count
+ (Pango2Lines *lines);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_lines_is_wrapped (Pango2Lines *lines);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_lines_is_ellipsized (Pango2Lines *lines);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_lines_is_hyphenated (Pango2Lines *lines);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_lines_index_to_line (Pango2Lines *lines,
+ int idx,
+ Pango2Line **line,
+ int *line_no,
+ int *x_offset,
+ int *y_offset);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Line * pango2_lines_pos_to_line (Pango2Lines *lines,
+ int x,
+ int y,
+ int *line_x,
+ int *line_y);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_lines_index_to_pos (Pango2Lines *lines,
+ Pango2Line *line,
+ int idx,
+ Pango2Rectangle *pos);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Line * pango2_lines_pos_to_index (Pango2Lines *lines,
+ int x,
+ int y,
+ int *idx,
+ int *trailing);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_lines_get_cursor_pos (Pango2Lines *lines,
+ Pango2Line *line,
+ int idx,
+ Pango2Rectangle *strong_pos,
+ Pango2Rectangle *weak_pos);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_lines_get_caret_pos (Pango2Lines *lines,
+ Pango2Line *line,
+ int idx,
+ Pango2Rectangle *strong_pos,
+ Pango2Rectangle *weak_pos);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_lines_move_cursor (Pango2Lines *lines,
+ gboolean strong,
+ Pango2Line *line,
+ int idx,
+ int trailing,
+ int direction,
+ Pango2Line **new_line,
+ int *new_idx,
+ int *new_trailing);
+
+PANGO2_AVAILABLE_IN_ALL
+GBytes * pango2_lines_serialize (Pango2Lines *lines);
+
+G_END_DECLS
diff --git a/pango2/pango-markup.c b/pango2/pango-markup.c
new file mode 100644
index 00000000..c03196e8
--- /dev/null
+++ b/pango2/pango-markup.c
@@ -0,0 +1,2016 @@
+/* Pango2
+ * pango-markup.c: Parse markup into attributed text
+ *
+ * Copyright (C) 2000 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.
+ */
+
+#include "config.h"
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "pango-markup.h"
+
+#include "pango-attributes.h"
+#include "pango-attr-private.h"
+#include "pango-font.h"
+#include "pango-enum-types.h"
+#include "pango-impl-utils.h"
+#include "pango-font-description-private.h"
+
+/* CSS size levels */
+typedef enum
+{
+ XXSmall = -3,
+ XSmall = -2,
+ Small = -1,
+ Medium = 0,
+ Large = 1,
+ XLarge = 2,
+ XXLarge = 3
+} SizeLevel;
+
+typedef struct _MarkupData MarkupData;
+
+struct _MarkupData
+{
+ Pango2AttrList *attr_list;
+ GString *text;
+ GSList *tag_stack;
+ gsize index;
+ GSList *to_apply;
+ gunichar accel_marker;
+ gunichar accel_char;
+};
+
+typedef struct _OpenTag OpenTag;
+
+struct _OpenTag
+{
+ GSList *attrs;
+ gsize start_index;
+ /* Current total scale level; reset whenever
+ * an absolute size is set.
+ * Each "larger" ups it 1, each "smaller" decrements it 1
+ */
+ int scale_level;
+ /* Our impact on scale_level, so we know whether we
+ * need to create an attribute ourselves on close
+ */
+ int scale_level_delta;
+ /* Base scale factor currently in effect
+ * or size that this tag
+ * forces, or parent's scale factor or size.
+ */
+ double base_scale_factor;
+ int base_font_size;
+ guint has_base_font_size : 1;
+};
+
+typedef gboolean (*TagParseFunc) (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+
+static gboolean b_parse_func (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+static gboolean big_parse_func (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+static gboolean span_parse_func (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+static gboolean i_parse_func (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+static gboolean markup_parse_func (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+static gboolean s_parse_func (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+static gboolean sub_parse_func (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+static gboolean sup_parse_func (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+static gboolean small_parse_func (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+static gboolean tt_parse_func (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+static gboolean u_parse_func (MarkupData *md,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error);
+
+static gboolean
+_pango2_scan_int (const char **pos, int *out)
+{
+ char *end;
+ long temp;
+
+ errno = 0;
+ temp = strtol (*pos, &end, 10);
+ if (errno == ERANGE)
+ {
+ errno = 0;
+ return FALSE;
+ }
+
+ *out = (int)temp;
+ if ((long)(*out) != temp)
+ {
+ return FALSE;
+ }
+
+ *pos = end;
+
+ return TRUE;
+}
+
+static gboolean
+parse_int (const char *word,
+ int *out)
+{
+ char *end;
+ long val;
+ int i;
+
+ if (word == NULL)
+ return FALSE;
+
+ val = strtol (word, &end, 10);
+ i = val;
+
+ if (end != word && *end == '\0' && val >= 0 && val == i)
+ {
+ if (out)
+ *out = i;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+_pango2_parse_enum (GType type,
+ const char *str,
+ int *value,
+ gboolean warn,
+ char **possible_values)
+{
+ GEnumClass *class = NULL;
+ gboolean ret = TRUE;
+ GEnumValue *v = NULL;
+
+ class = g_type_class_ref (type);
+
+ if (G_LIKELY (str))
+ v = g_enum_get_value_by_nick (class, str);
+
+ if (v)
+ {
+ if (G_LIKELY (value))
+ *value = v->value;
+ }
+ else if (!parse_int (str, value))
+ {
+ ret = FALSE;
+ if (G_LIKELY (warn || possible_values))
+ {
+ int i;
+ GString *s = g_string_new (NULL);
+
+ for (i = 0, v = g_enum_get_value (class, i); v;
+ i++ , v = g_enum_get_value (class, i))
+ {
+ if (i)
+ g_string_append_c (s, '/');
+ g_string_append (s, v->value_nick);
+ }
+
+ if (warn)
+ g_warning ("%s must be one of %s",
+ G_ENUM_CLASS_TYPE_NAME(class),
+ s->str);
+
+ if (possible_values)
+ *possible_values = s->str;
+
+ g_string_free (s, possible_values ? FALSE : TRUE);
+ }
+ }
+
+ g_type_class_unref (class);
+
+ return ret;
+}
+
+static gboolean
+pango2_parse_flags (GType type,
+ const char *str,
+ int *value,
+ char **possible_values)
+{
+ GFlagsClass *class = NULL;
+ gboolean ret = TRUE;
+ GFlagsValue *v = NULL;
+
+ class = g_type_class_ref (type);
+
+ v = g_flags_get_value_by_nick (class, str);
+
+ if (v)
+ {
+ *value = v->value;
+ }
+ else if (!parse_int (str, value))
+ {
+ char **strv = g_strsplit (str, "|", 0);
+ int i;
+
+ *value = 0;
+
+ for (i = 0; strv[i]; i++)
+ {
+ strv[i] = g_strstrip (strv[i]);
+ v = g_flags_get_value_by_nick (class, strv[i]);
+ if (!v)
+ {
+ ret = FALSE;
+ break;
+ }
+ *value |= v->value;
+ }
+ g_strfreev (strv);
+
+ if (!ret && possible_values)
+ {
+ int i;
+ GString *s = g_string_new (NULL);
+
+ for (i = 0; i < class->n_values; i++)
+ {
+ v = &class->values[i];
+ if (i)
+ g_string_append_c (s, '/');
+ g_string_append (s, v->value_nick);
+ }
+
+ *possible_values = s->str;
+
+ g_string_free (s, FALSE);
+ }
+ }
+
+ g_type_class_unref (class);
+
+ return ret;
+}
+
+static double
+scale_factor (int scale_level, double base)
+{
+ double factor = base;
+ int i;
+
+ /* 1.2 is the CSS scale factor between sizes */
+
+ if (scale_level > 0)
+ {
+ i = 0;
+ while (i < scale_level)
+ {
+ factor *= 1.2;
+
+ ++i;
+ }
+ }
+ else if (scale_level < 0)
+ {
+ i = scale_level;
+ while (i < 0)
+ {
+ factor /= 1.2;
+
+ ++i;
+ }
+ }
+
+ return factor;
+}
+
+static void
+open_tag_free (OpenTag *ot)
+{
+ g_slist_foreach (ot->attrs, (GFunc) pango2_attribute_destroy, NULL);
+ g_slist_free (ot->attrs);
+ g_slice_free (OpenTag, ot);
+}
+
+static void
+open_tag_set_absolute_font_size (OpenTag *ot,
+ int font_size)
+{
+ ot->base_font_size = font_size;
+ ot->has_base_font_size = TRUE;
+ ot->scale_level = 0;
+ ot->scale_level_delta = 0;
+}
+
+static void
+open_tag_set_absolute_font_scale (OpenTag *ot,
+ double scale)
+{
+ ot->base_scale_factor = scale;
+ ot->has_base_font_size = FALSE;
+ ot->scale_level = 0;
+ ot->scale_level_delta = 0;
+}
+
+static OpenTag*
+markup_data_open_tag (MarkupData *md)
+{
+ OpenTag *ot;
+ OpenTag *parent = NULL;
+
+ if (md->attr_list == NULL)
+ return NULL;
+
+ if (md->tag_stack)
+ parent = md->tag_stack->data;
+
+ ot = g_slice_new (OpenTag);
+ ot->attrs = NULL;
+ ot->start_index = md->index;
+ ot->scale_level_delta = 0;
+
+ if (parent == NULL)
+ {
+ ot->base_scale_factor = 1.0;
+ ot->base_font_size = 0;
+ ot->has_base_font_size = FALSE;
+ ot->scale_level = 0;
+ }
+ else
+ {
+ ot->base_scale_factor = parent->base_scale_factor;
+ ot->base_font_size = parent->base_font_size;
+ ot->has_base_font_size = parent->has_base_font_size;
+ ot->scale_level = parent->scale_level;
+ }
+
+ md->tag_stack = g_slist_prepend (md->tag_stack, ot);
+
+ return ot;
+}
+
+static void
+markup_data_close_tag (MarkupData *md)
+{
+ OpenTag *ot;
+ GSList *tmp_list;
+
+ if (md->attr_list == NULL)
+ return;
+
+ /* pop the stack */
+ ot = md->tag_stack->data;
+ md->tag_stack = g_slist_delete_link (md->tag_stack,
+ md->tag_stack);
+
+ /* Adjust end indexes, and push each attr onto the front of the
+ * to_apply list. This means that outermost tags are on the front of
+ * that list; if we apply the list in order, then the innermost
+ * tags will "win" which is correct.
+ */
+ tmp_list = ot->attrs;
+ while (tmp_list != NULL)
+ {
+ Pango2Attribute *a = tmp_list->data;
+
+ a->start_index = ot->start_index;
+ a->end_index = md->index;
+
+ md->to_apply = g_slist_prepend (md->to_apply, a);
+
+ tmp_list = g_slist_next (tmp_list);
+ }
+
+ if (ot->scale_level_delta != 0)
+ {
+ /* We affected relative font size; create an appropriate
+ * attribute and reverse our effects on the current level
+ */
+ Pango2Attribute *a;
+
+ if (ot->has_base_font_size)
+ {
+ /* Create a font using the absolute point size as the base size
+ * to be scaled from.
+ * We need to use a local variable to ensure that the compiler won't
+ * implicitly cast it to integer while the result is kept in registers,
+ * leading to a wrong approximation in i386 (with 387 FPU)
+ */
+ volatile double size;
+
+ size = scale_factor (ot->scale_level, 1.0) * ot->base_font_size;
+ a = pango2_attr_size_new (size);
+ }
+ else
+ {
+ /* Create a font using the current scale factor
+ * as the base size to be scaled from
+ */
+ a = pango2_attr_scale_new (scale_factor (ot->scale_level,
+ ot->base_scale_factor));
+ }
+
+ a->start_index = ot->start_index;
+ a->end_index = md->index;
+
+ md->to_apply = g_slist_prepend (md->to_apply, a);
+ }
+
+ g_slist_free (ot->attrs);
+ g_slice_free (OpenTag, ot);
+}
+
+static void
+start_element_handler (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ TagParseFunc parse_func = NULL;
+ OpenTag *ot;
+
+ switch (*element_name)
+ {
+ case 'b':
+ if (strcmp ("b", element_name) == 0)
+ parse_func = b_parse_func;
+ else if (strcmp ("big", element_name) == 0)
+ parse_func = big_parse_func;
+ break;
+
+ case 'i':
+ if (strcmp ("i", element_name) == 0)
+ parse_func = i_parse_func;
+ break;
+
+ case 'm':
+ if (strcmp ("markup", element_name) == 0)
+ parse_func = markup_parse_func;
+ break;
+
+ case 's':
+ if (strcmp ("span", element_name) == 0)
+ parse_func = span_parse_func;
+ else if (strcmp ("s", element_name) == 0)
+ parse_func = s_parse_func;
+ else if (strcmp ("sub", element_name) == 0)
+ parse_func = sub_parse_func;
+ else if (strcmp ("sup", element_name) == 0)
+ parse_func = sup_parse_func;
+ else if (strcmp ("small", element_name) == 0)
+ parse_func = small_parse_func;
+ break;
+
+ case 't':
+ if (strcmp ("tt", element_name) == 0)
+ parse_func = tt_parse_func;
+ break;
+
+ case 'u':
+ if (strcmp ("u", element_name) == 0)
+ parse_func = u_parse_func;
+ break;
+
+ default:
+ break;
+ }
+
+ if (parse_func == NULL)
+ {
+ int line_number, char_number;
+
+ g_markup_parse_context_get_position (context,
+ &line_number, &char_number);
+
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ "Unknown tag '%s' on line %d char %d",
+ element_name,
+ line_number, char_number);
+
+ return;
+ }
+
+ ot = markup_data_open_tag (user_data);
+
+ /* note ot may be NULL if the user didn't want the attribute list */
+
+ if (!(*parse_func) (user_data, ot,
+ attribute_names, attribute_values,
+ context, error))
+ {
+ /* there's nothing to do; we return an error, and end up
+ * freeing ot off the tag stack later.
+ */
+ }
+}
+
+static void
+end_element_handler (GMarkupParseContext *context G_GNUC_UNUSED,
+ const char *element_name G_GNUC_UNUSED,
+ gpointer user_data,
+ GError **error G_GNUC_UNUSED)
+{
+ markup_data_close_tag (user_data);
+}
+
+static void
+text_handler (GMarkupParseContext *context G_GNUC_UNUSED,
+ const char *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error G_GNUC_UNUSED)
+{
+ MarkupData *md = user_data;
+
+ if (md->accel_marker == 0)
+ {
+ /* Just append all the text */
+
+ md->index += text_len;
+
+ g_string_append_len (md->text, text, text_len);
+ }
+ else
+ {
+ /* Parse the accelerator */
+ const char *p;
+ const char *end;
+ const char *range_start;
+ const char *range_end;
+ gssize uline_index = -1;
+ gsize uline_len = 0; /* Quiet GCC */
+
+ range_end = NULL;
+ range_start = text;
+ p = text;
+ end = text + text_len;
+
+ while (p != end)
+ {
+ gunichar c;
+
+ c = g_utf8_get_char (p);
+
+ if (range_end)
+ {
+ if (c == md->accel_marker)
+ {
+ /* escaped accel marker; move range_end
+ * past the accel marker that came before,
+ * append the whole thing
+ */
+ range_end = g_utf8_next_char (range_end);
+ g_string_append_len (md->text,
+ range_start,
+ range_end - range_start);
+ md->index += range_end - range_start;
+
+ /* set next range_start, skipping accel marker */
+ range_start = g_utf8_next_char (p);
+ }
+ else
+ {
+ /* Don't append the accel marker (leave range_end
+ * alone); set the accel char to c; record location for
+ * underline attribute
+ */
+ if (md->accel_char == 0)
+ md->accel_char = c;
+
+ g_string_append_len (md->text,
+ range_start,
+ range_end - range_start);
+ md->index += range_end - range_start;
+
+ /* The underline should go underneath the char
+ * we're setting as the next range_start
+ */
+ if (md->attr_list != NULL)
+ {
+ /* Add the underline indicating the accelerator */
+ Pango2Attribute *attr;
+
+ attr = pango2_attr_underline_new (PANGO2_LINE_STYLE_SOLID);
+
+ uline_index = md->index;
+ uline_len = g_utf8_next_char (p) - p;
+
+ attr->start_index = uline_index;
+ attr->end_index = uline_index + uline_len;
+
+ pango2_attr_list_change (md->attr_list, attr);
+
+ attr = pango2_attr_underline_position_new (PANGO2_UNDERLINE_POSITION_UNDER);
+
+ attr->start_index = uline_index;
+ attr->end_index = uline_index + uline_len;
+
+ pango2_attr_list_change (md->attr_list, attr);
+ }
+
+ /* set next range_start to include this char */
+ range_start = p;
+ }
+
+ /* reset range_end */
+ range_end = NULL;
+ }
+ else if (c == md->accel_marker)
+ {
+ range_end = p;
+ }
+
+ p = g_utf8_next_char (p);
+ }
+
+ g_string_append_len (md->text,
+ range_start,
+ end - range_start);
+ md->index += end - range_start;
+ }
+}
+
+static gboolean
+xml_isspace (char c)
+{
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+static const GMarkupParser pango2_markup_parser = {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ NULL,
+ NULL
+};
+
+static void
+destroy_markup_data (MarkupData *md)
+{
+ g_slist_free_full (md->tag_stack, (GDestroyNotify) open_tag_free);
+ g_slist_free_full (md->to_apply, (GDestroyNotify) pango2_attribute_destroy);
+ if (md->text)
+ g_string_free (md->text, TRUE);
+
+ if (md->attr_list)
+ pango2_attr_list_unref (md->attr_list);
+
+ g_slice_free (MarkupData, md);
+}
+
+static GMarkupParseContext *
+pango2_markup_parser_new_internal (char accel_marker,
+ GError **error,
+ gboolean want_attr_list)
+{
+ MarkupData *md;
+ GMarkupParseContext *context;
+
+ md = g_slice_new (MarkupData);
+
+ /* Don't bother creating these if they weren't requested;
+ * might be useful e.g. if you just want to validate
+ * some markup.
+ */
+ if (want_attr_list)
+ md->attr_list = pango2_attr_list_new ();
+ else
+ md->attr_list = NULL;
+
+ md->text = g_string_new (NULL);
+
+ md->accel_marker = accel_marker;
+ md->accel_char = 0;
+
+ md->index = 0;
+ md->tag_stack = NULL;
+ md->to_apply = NULL;
+
+ context = g_markup_parse_context_new (&pango2_markup_parser,
+ 0, md,
+ (GDestroyNotify)destroy_markup_data);
+
+ if (!g_markup_parse_context_parse (context, "<markup>", -1, error))
+ g_clear_pointer (&context, g_markup_parse_context_free);
+
+ return context;
+}
+
+/**
+ * pango2_parse_markup:
+ * @markup_text: markup to parse (see the [Pango2 Markup](pango2_markup.html) docs)
+ * @length: length of @markup_text, or -1 if nul-terminated
+ * @accel_marker: character that precedes an accelerator, or 0 for none
+ * @attr_list: (out) (optional): address of return location for a `Pango2AttrList`
+ * @text: (out) (optional): address of return location for text with tags stripped
+ * @accel_char: (out) (optional): address of return location for accelerator char
+ * @error: address of return location for errors
+ *
+ * Parses marked-up text to create a plain-text string and an attribute list.
+ *
+ * See the [Pango2 Markup](pango2_markup.html) docs for details about the
+ * supported markup.
+ *
+ * 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 %PANGO2_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.
+ *
+ * To parse a stream of pango markup incrementally, use [func@markup_parser_new].
+ *
+ * If any error happens, none of the output arguments are touched except
+ * for @error.
+ *
+ * Return value: %FALSE if @error is set, otherwise %TRUE
+ **/
+gboolean
+pango2_parse_markup (const char *markup_text,
+ int length,
+ gunichar accel_marker,
+ Pango2AttrList **attr_list,
+ char **text,
+ gunichar *accel_char,
+ GError **error)
+{
+ GMarkupParseContext *context = NULL;
+ gboolean ret = FALSE;
+ const char *p;
+ const char *end;
+
+ g_return_val_if_fail (markup_text != NULL, FALSE);
+
+ if (length < 0)
+ length = strlen (markup_text);
+
+ p = markup_text;
+ end = markup_text + length;
+ while (p != end && xml_isspace (*p))
+ ++p;
+
+ context = pango2_markup_parser_new_internal (accel_marker,
+ error,
+ (attr_list != NULL));
+
+ if (!g_markup_parse_context_parse (context,
+ markup_text,
+ length,
+ error))
+ goto out;
+
+ if (!pango2_markup_parser_finish (context,
+ attr_list,
+ text,
+ accel_char,
+ error))
+ goto out;
+
+ ret = TRUE;
+
+ out:
+ if (context != NULL)
+ g_markup_parse_context_free (context);
+ return ret;
+}
+
+/**
+ * pango2_markup_parser_new:
+ * @accel_marker: character that precedes an accelerator, or 0 for none
+ *
+ * Incrementally parses marked-up text to create a plain-text string
+ * and an attribute list.
+ *
+ * See the [Pango2 Markup](pango2_markup.html) docs for details about the
+ * supported markup.
+ *
+ * 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 %PANGO2_UNDERLINE_LOW attribute,
+ * and the first character so marked will be returned in @accel_char,
+ * when calling [func@markup_parser_finish]. Two @accel_marker characters
+ * following each other produce a single literal @accel_marker character.
+ *
+ * To feed markup to the parser, use [method@GLib.MarkupParseContext.parse]
+ * on the returned [struct@GLib.MarkupParseContext]. When done with feeding markup
+ * to the parser, use [func@markup_parser_finish] to get the data out
+ * of it, and then use [method@GLib.MarkupParseContext.free] to free it.
+ *
+ * This function is designed for applications that read Pango2 markup
+ * from streams. To simply parse a string containing Pango2 markup,
+ * the [func@Pango2.parse_markup] API is recommended instead.
+ *
+ * Return value: (transfer none): a `GMarkupParseContext` that should be
+ * destroyed with [method@GLib.MarkupParseContext.free].
+ **/
+GMarkupParseContext *
+pango2_markup_parser_new (gunichar accel_marker)
+{
+ return pango2_markup_parser_new_internal (accel_marker, NULL, TRUE);
+}
+
+/**
+ * pango2_markup_parser_finish:
+ * @context: A valid parse context that was returned from [func@markup_parser_new]
+ * @attr_list: (out) (optional): address of return location for a `Pango2AttrList`
+ * @text: (out) (optional): address of return location for text with tags stripped
+ * @accel_char: (out) (optional): address of return location for accelerator char
+ * @error: address of return location for errors
+ *
+ * Finishes parsing markup.
+ *
+ * After feeding a Pango2 markup parser some data with [method@GLib.MarkupParseContext.parse],
+ * use this function to get the list of attributes and text out of the
+ * markup. This function will not free @context, use [method@GLib.MarkupParseContext.free]
+ * to do so.
+ *
+ * Return value: %FALSE if @error is set, otherwise %TRUE
+ */
+gboolean
+pango2_markup_parser_finish (GMarkupParseContext *context,
+ Pango2AttrList **attr_list,
+ char **text,
+ gunichar *accel_char,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ MarkupData *md = g_markup_parse_context_get_user_data (context);
+ GSList *tmp_list;
+
+ if (!g_markup_parse_context_parse (context,
+ "</markup>",
+ -1,
+ error))
+ goto out;
+
+ if (!g_markup_parse_context_end_parse (context, error))
+ goto out;
+
+ if (md->attr_list)
+ {
+ /* The apply list has the most-recently-closed tags first;
+ * we want to apply the least-recently-closed tag last.
+ */
+ tmp_list = md->to_apply;
+ while (tmp_list != NULL)
+ {
+ Pango2Attribute *attr = tmp_list->data;
+
+ /* Innermost tags before outermost */
+ pango2_attr_list_insert (md->attr_list, attr);
+
+ tmp_list = g_slist_next (tmp_list);
+ }
+ g_slist_free (md->to_apply);
+ md->to_apply = NULL;
+ }
+
+ if (attr_list)
+ {
+ *attr_list = md->attr_list;
+ md->attr_list = NULL;
+ }
+
+ if (text)
+ {
+ *text = g_string_free (md->text, FALSE);
+ md->text = NULL;
+ }
+
+ if (accel_char)
+ *accel_char = md->accel_char;
+
+ g_assert (md->tag_stack == NULL);
+ ret = TRUE;
+
+ out:
+ return ret;
+}
+
+static void
+set_bad_attribute (GError **error,
+ GMarkupParseContext *context,
+ const char *element_name,
+ const char *attribute_name)
+{
+ int line_number, char_number;
+
+ g_markup_parse_context_get_position (context,
+ &line_number, &char_number);
+
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "Tag '%s' does not support attribute '%s' on line %d char %d",
+ element_name,
+ attribute_name,
+ line_number, char_number);
+}
+
+static void
+add_attribute (OpenTag *ot,
+ Pango2Attribute *attr)
+{
+ if (ot == NULL)
+ pango2_attribute_destroy (attr);
+ else
+ ot->attrs = g_slist_prepend (ot->attrs, attr);
+}
+
+#define CHECK_NO_ATTRS(elem) G_STMT_START { \
+ if (*names != NULL) { \
+ set_bad_attribute (error, context, (elem), *names); \
+ return FALSE; \
+ } }G_STMT_END
+
+static gboolean
+b_parse_func (MarkupData *md G_GNUC_UNUSED,
+ OpenTag *tag,
+ const char **names,
+ const char **values G_GNUC_UNUSED,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ CHECK_NO_ATTRS("b");
+ add_attribute (tag, pango2_attr_weight_new (PANGO2_WEIGHT_BOLD));
+ return TRUE;
+}
+
+static gboolean
+big_parse_func (MarkupData *md G_GNUC_UNUSED,
+ OpenTag *tag,
+ const char **names,
+ const char **values G_GNUC_UNUSED,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ CHECK_NO_ATTRS("big");
+
+ /* Grow text one level */
+ if (tag)
+ {
+ tag->scale_level_delta += 1;
+ tag->scale_level += 1;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+parse_percentage (const char *input,
+ double *val)
+{
+ double v;
+ char *end;
+
+ v = g_ascii_strtod (input, &end);
+ if (errno == 0 && strcmp (end, "%") == 0 && v > 0)
+ {
+ *val = v;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+parse_absolute_size (OpenTag *tag,
+ const char *size)
+{
+ SizeLevel level = Medium;
+ double val;
+ double factor;
+
+ if (strcmp (size, "xx-small") == 0)
+ level = XXSmall;
+ else if (strcmp (size, "x-small") == 0)
+ level = XSmall;
+ else if (strcmp (size, "small") == 0)
+ level = Small;
+ else if (strcmp (size, "medium") == 0)
+ level = Medium;
+ else if (strcmp (size, "large") == 0)
+ level = Large;
+ else if (strcmp (size, "x-large") == 0)
+ level = XLarge;
+ else if (strcmp (size, "xx-large") == 0)
+ level = XXLarge;
+ else if (parse_percentage (size, &val))
+ {
+ factor = val / 100.0;
+ goto done;
+ }
+ else
+ return FALSE;
+
+ /* This is "absolute" in that it's relative to the base font,
+ * but not to sizes created by any other tags
+ */
+ factor = scale_factor (level, 1.0);
+
+done:
+ add_attribute (tag, pango2_attr_scale_new (factor));
+ if (tag)
+ open_tag_set_absolute_font_scale (tag, factor);
+
+ return TRUE;
+}
+
+/* a string compare func that ignores '-' vs '_' differences */
+static int
+attr_strcmp (gconstpointer pa,
+ gconstpointer pb)
+{
+ const char *a = pa;
+ const char *b = pb;
+
+ int ca;
+ int cb;
+
+ while (*a && *b)
+ {
+ ca = *a++;
+ cb = *b++;
+
+ if (ca == cb)
+ continue;
+
+ ca = ca == '_' ? '-' : ca;
+ cb = cb == '_' ? '-' : cb;
+
+ if (ca != cb)
+ return cb - ca;
+ }
+
+ ca = *a;
+ cb = *b;
+
+ return cb - ca;
+}
+
+static gboolean
+span_parse_int (const char *attr_name,
+ const char *attr_val,
+ int *val,
+ int line_number,
+ GError **error)
+{
+ const char *end = attr_val;
+
+ if (!_pango2_scan_int (&end, val) || *end != '\0')
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Value of '%s' attribute on <span> tag "
+ "on line %d could not be parsed; "
+ "should be an integer, not '%s'",
+ attr_name, line_number, attr_val);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+span_parse_float (const char *attr_name,
+ const char *attr_val,
+ double *val,
+ int line_number,
+ GError **error)
+{
+ *val = g_ascii_strtod (attr_val, NULL);
+ if (errno != 0)
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Value of '%s' attribute on <span> tag "
+ "on line %d could not be parsed; "
+ "should be a number, not '%s'",
+ attr_name, line_number, attr_val);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+span_parse_boolean (const char *attr_name,
+ const char *attr_val,
+ gboolean *val,
+ int line_number,
+ GError **error)
+{
+ if (strcmp (attr_val, "true") == 0 ||
+ strcmp (attr_val, "yes") == 0 ||
+ strcmp (attr_val, "t") == 0 ||
+ strcmp (attr_val, "y") == 0)
+ *val = TRUE;
+ else if (strcmp (attr_val, "false") == 0 ||
+ strcmp (attr_val, "no") == 0 ||
+ strcmp (attr_val, "f") == 0 ||
+ strcmp (attr_val, "n") == 0)
+ *val = FALSE;
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Value of '%s' attribute on <span> tag "
+ "line %d should have one of "
+ "'true/yes/t/y' or 'false/no/f/n': '%s' is not valid",
+ attr_name, line_number, attr_val);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+span_parse_color (const char *attr_name,
+ const char *attr_val,
+ Pango2Color *color,
+ int line_number,
+ GError **error)
+{
+ if (!pango2_color_parse (color, attr_val))
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Value of '%s' attribute on <span> tag "
+ "on line %d could not be parsed; "
+ "should be a color specification, not '%s'",
+ attr_name, line_number, attr_val);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+span_parse_enum (const char *attr_name,
+ const char *attr_val,
+ GType type,
+ int *val,
+ int line_number,
+ GError **error)
+{
+ char *possible_values = NULL;
+
+ if (!_pango2_parse_enum (type, attr_val, val, FALSE, &possible_values))
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "'%s' is not a valid value for the '%s' "
+ "attribute on <span> tag, line %d; valid "
+ "values are %s",
+ attr_val, attr_name, line_number, possible_values);
+ g_free (possible_values);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+span_parse_flags (const char *attr_name,
+ const char *attr_val,
+ GType type,
+ int *val,
+ int line_number,
+ GError **error)
+{
+ char *possible_values = NULL;
+
+ if (!pango2_parse_flags (type, attr_val, val, &possible_values))
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "'%s' is not a valid value for the '%s' "
+ "attribute on <span> tag, line %d; valid "
+ "values are %s or combinations with |",
+ attr_val, attr_name, line_number, possible_values);
+ g_free (possible_values);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+parse_length (const char *attr_val,
+ int *result)
+{
+ const char *attr;
+ int n;
+
+ attr = attr_val;
+ if (_pango2_scan_int (&attr, &n) && *attr == '\0')
+ {
+ *result = n;
+ return TRUE;
+ }
+ else
+ {
+ double val;
+ char *end;
+
+ val = g_ascii_strtod (attr_val, &end);
+ if (errno == 0 && strcmp (end, "pt") == 0)
+ {
+ *result = val * PANGO2_SCALE;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+span_parse_func (MarkupData *md G_GNUC_UNUSED,
+ OpenTag *tag,
+ const char **names,
+ const char **values,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ int line_number, char_number;
+ int i;
+
+ const char *family = NULL;
+ const char *size = NULL;
+ const char *style = NULL;
+ const char *weight = NULL;
+ const char *variant = NULL;
+ const char *stretch = NULL;
+ const char *desc = NULL;
+ const char *foreground = NULL;
+ const char *background = NULL;
+ const char *underline = NULL;
+ const char *underline_position = NULL;
+ const char *underline_color = NULL;
+ const char *overline = NULL;
+ const char *overline_color = NULL;
+ 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;
+ const char *gravity = NULL;
+ const char *gravity_hint = NULL;
+ const char *font_features = NULL;
+ const char *allow_breaks = NULL;
+ const char *insert_hyphens = NULL;
+ const char *show = NULL;
+ const char *line_height = NULL;
+ const char *text_transform = NULL;
+ const char *segment = NULL;
+ const char *font_scale = NULL;
+
+ g_markup_parse_context_get_position (context,
+ &line_number, &char_number);
+
+#define CHECK_DUPLICATE(var) G_STMT_START{ \
+ if ((var) != NULL) { \
+ g_set_error (error, G_MARKUP_ERROR, \
+ G_MARKUP_ERROR_INVALID_CONTENT, \
+ "Attribute '%s' occurs twice on <span> tag " \
+ "on line %d char %d, may only occur once", \
+ names[i], line_number, char_number); \
+ return FALSE; \
+ }}G_STMT_END
+#define CHECK_ATTRIBUTE2(var, name) \
+ if (attr_strcmp (names[i], (name)) == 0) { \
+ CHECK_DUPLICATE (var); \
+ (var) = values[i]; \
+ found = TRUE; \
+ break; \
+ }
+#define CHECK_ATTRIBUTE(var) CHECK_ATTRIBUTE2 (var, G_STRINGIFY (var))
+
+ i = 0;
+ while (names[i])
+ {
+ gboolean found = FALSE;
+
+ switch (names[i][0]) {
+ case 'a':
+ CHECK_ATTRIBUTE (allow_breaks);
+ break;
+ case 'b':
+ CHECK_ATTRIBUTE (background);
+ CHECK_ATTRIBUTE2 (background, "bgcolor");
+ CHECK_ATTRIBUTE (baseline_shift);
+ break;
+ case 'c':
+ CHECK_ATTRIBUTE2 (foreground, "color");
+ break;
+ case 'f':
+ CHECK_ATTRIBUTE (fallback);
+ CHECK_ATTRIBUTE2 (desc, "font");
+ CHECK_ATTRIBUTE2 (desc, "font_desc");
+ CHECK_ATTRIBUTE2 (family, "face");
+
+ CHECK_ATTRIBUTE2 (family, "font_family");
+ CHECK_ATTRIBUTE2 (size, "font_size");
+ CHECK_ATTRIBUTE2 (stretch, "font_stretch");
+ CHECK_ATTRIBUTE2 (style, "font_style");
+ CHECK_ATTRIBUTE2 (variant, "font_variant");
+ CHECK_ATTRIBUTE2 (weight, "font_weight");
+ CHECK_ATTRIBUTE (font_scale);
+
+ CHECK_ATTRIBUTE (foreground);
+ CHECK_ATTRIBUTE2 (foreground, "fgcolor");
+
+ CHECK_ATTRIBUTE (font_features);
+ break;
+ case 's':
+ CHECK_ATTRIBUTE (show);
+ CHECK_ATTRIBUTE (size);
+ CHECK_ATTRIBUTE (stretch);
+ CHECK_ATTRIBUTE (strikethrough);
+ CHECK_ATTRIBUTE (strikethrough_color);
+ CHECK_ATTRIBUTE (style);
+ CHECK_ATTRIBUTE (segment);
+ break;
+ case 't':
+ CHECK_ATTRIBUTE (text_transform);
+ break;
+ case 'g':
+ CHECK_ATTRIBUTE (gravity);
+ CHECK_ATTRIBUTE (gravity_hint);
+ break;
+ case 'i':
+ CHECK_ATTRIBUTE (insert_hyphens);
+ break;
+ case 'l':
+ CHECK_ATTRIBUTE (lang);
+ CHECK_ATTRIBUTE (letter_spacing);
+ CHECK_ATTRIBUTE (line_height);
+ break;
+ case 'o':
+ CHECK_ATTRIBUTE (overline);
+ CHECK_ATTRIBUTE (overline_color);
+ break;
+ case 'u':
+ CHECK_ATTRIBUTE (underline);
+ CHECK_ATTRIBUTE (underline_position);
+ CHECK_ATTRIBUTE (underline_color);
+ break;
+ case 'r':
+ CHECK_ATTRIBUTE (rise);
+ break;
+ case 'v':
+ CHECK_ATTRIBUTE (variant);
+ break;
+ case 'w':
+ CHECK_ATTRIBUTE (weight);
+ break;
+ default:;
+ }
+
+ if (!found)
+ {
+ g_set_error (error, G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ "Attribute '%s' is not allowed on the <span> tag "
+ "on line %d char %d",
+ names[i], line_number, char_number);
+ return FALSE;
+ }
+
+ ++i;
+ }
+
+ /* Parse desc first, then modify it with other font-related attributes. */
+ if (G_UNLIKELY (desc))
+ {
+ Pango2FontDescription *parsed;
+
+ parsed = pango2_font_description_from_string (desc);
+ if (parsed)
+ {
+ add_attribute (tag, pango2_attr_font_desc_new (parsed));
+ if (tag)
+ open_tag_set_absolute_font_size (tag, pango2_font_description_get_size (parsed));
+ pango2_font_description_free (parsed);
+ }
+ }
+
+ if (G_UNLIKELY (family))
+ {
+ add_attribute (tag, pango2_attr_family_new (family));
+ }
+
+ if (G_UNLIKELY (size))
+ {
+ int n;
+
+ if (parse_length (size, &n) && n > 0)
+ {
+ add_attribute (tag, pango2_attr_size_new (n));
+ if (tag)
+ open_tag_set_absolute_font_size (tag, n);
+ }
+ else if (strcmp (size, "smaller") == 0)
+ {
+ if (tag)
+ {
+ tag->scale_level_delta -= 1;
+ tag->scale_level -= 1;
+ }
+ }
+ else if (strcmp (size, "larger") == 0)
+ {
+ if (tag)
+ {
+ tag->scale_level_delta += 1;
+ tag->scale_level += 1;
+ }
+ }
+ else if (parse_absolute_size (tag, size))
+ ; /* nothing */
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Value of 'size' attribute on <span> tag on line %d "
+ "could not be parsed; should be an integer, or a "
+ "string such as 'small', not '%s'",
+ line_number, size);
+ goto error;
+ }
+ }
+
+ if (G_UNLIKELY (style))
+ {
+ Pango2Style pango2_style;
+
+ if (pango2_parse_style (style, &pango2_style, FALSE))
+ add_attribute (tag, pango2_attr_style_new (pango2_style));
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "'%s' is not a valid value for the 'style' attribute "
+ "on <span> tag, line %d; valid values are "
+ "'normal', 'oblique', 'italic'",
+ style, line_number);
+ goto error;
+ }
+ }
+
+ if (G_UNLIKELY (weight))
+ {
+ Pango2Weight pango2_weight;
+
+ if (pango2_parse_weight (weight, &pango2_weight, FALSE))
+ add_attribute (tag,
+ pango2_attr_weight_new (pango2_weight));
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "'%s' is not a valid value for the 'weight' "
+ "attribute on <span> tag, line %d; valid "
+ "values are for example 'light', 'ultrabold' or a number",
+ weight, line_number);
+ goto error;
+ }
+ }
+
+ if (G_UNLIKELY (variant))
+ {
+ Pango2Variant pango2_variant;
+
+ if (pango2_parse_variant (variant, &pango2_variant, FALSE))
+ add_attribute (tag, pango2_attr_variant_new (pango2_variant));
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "'%s' is not a valid value for the 'variant' "
+ "attribute on <span> tag, line %d; valid values are "
+ "'normal', 'smallcaps'",
+ variant, line_number);
+ goto error;
+ }
+ }
+
+ if (G_UNLIKELY (stretch))
+ {
+ Pango2Stretch pango2_stretch;
+
+ if (pango2_parse_stretch (stretch, &pango2_stretch, FALSE))
+ add_attribute (tag, pango2_attr_stretch_new (pango2_stretch));
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "'%s' is not a valid value for the 'stretch' "
+ "attribute on <span> tag, line %d; valid "
+ "values are for example 'condensed', "
+ "'ultraexpanded', 'normal'",
+ stretch, line_number);
+ goto error;
+ }
+ }
+
+ if (G_UNLIKELY (foreground))
+ {
+ Pango2Color color;
+
+ if (!span_parse_color ("foreground", foreground, &color, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_foreground_new (&color));
+ }
+
+ if (G_UNLIKELY (background))
+ {
+ Pango2Color color;
+
+ if (!span_parse_color ("background", background, &color, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_background_new (&color));
+ }
+
+ if (G_UNLIKELY (underline))
+ {
+ Pango2LineStyle style = PANGO2_LINE_STYLE_NONE;
+
+ if (!span_parse_enum ("underline", underline, PANGO2_TYPE_LINE_STYLE, (int*)(void*)&style, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_underline_new (style));
+ }
+
+ if (G_UNLIKELY (underline_position))
+ {
+ Pango2UnderlinePosition pos = PANGO2_UNDERLINE_POSITION_NORMAL;
+
+ if (!span_parse_enum ("underline_position", underline_position, PANGO2_TYPE_UNDERLINE_POSITION, (int*)(void*)&pos, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_underline_position_new (pos));
+ }
+
+ if (G_UNLIKELY (underline_color))
+ {
+ Pango2Color color;
+
+ if (!span_parse_color ("underline_color", underline_color, &color, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_underline_color_new (&color));
+ }
+
+ if (G_UNLIKELY (overline))
+ {
+ Pango2LineStyle style = PANGO2_LINE_STYLE_NONE;
+
+ if (!span_parse_enum ("overline", overline, PANGO2_TYPE_LINE_STYLE, (int*)(void*)&style, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_overline_new (style));
+ }
+
+ if (G_UNLIKELY (overline_color))
+ {
+ Pango2Color color;
+
+ if (!span_parse_color ("overline_color", overline_color, &color, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_overline_color_new (&color));
+ }
+
+ if (G_UNLIKELY (gravity))
+ {
+ Pango2Gravity gr = PANGO2_GRAVITY_SOUTH;
+
+ if (!span_parse_enum ("gravity", gravity, PANGO2_TYPE_GRAVITY, (int*)(void*)&gr, line_number, error))
+ goto error;
+
+ if (gr == PANGO2_GRAVITY_AUTO)
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "'%s' is not a valid value for the 'gravity' "
+ "attribute on <span> tag, line %d; valid "
+ "values are for example 'south', 'east', "
+ "'north', 'west'",
+ gravity, line_number);
+ goto error;
+ }
+
+ add_attribute (tag, pango2_attr_gravity_new (gr));
+ }
+
+ if (G_UNLIKELY (gravity_hint))
+ {
+ Pango2GravityHint hint = PANGO2_GRAVITY_HINT_NATURAL;
+
+ if (!span_parse_enum ("gravity_hint", gravity_hint, PANGO2_TYPE_GRAVITY_HINT, (int*)(void*)&hint, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_gravity_hint_new (hint));
+ }
+
+ if (G_UNLIKELY (strikethrough))
+ {
+ Pango2LineStyle style = PANGO2_LINE_STYLE_NONE;
+
+ if (!span_parse_enum ("strikethrough", strikethrough, PANGO2_TYPE_LINE_STYLE, (int*)(void*)&style, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_strikethrough_new (style));
+ }
+
+ if (G_UNLIKELY (strikethrough_color))
+ {
+ Pango2Color color;
+
+ if (!span_parse_color ("strikethrough_color", strikethrough_color, &color, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_strikethrough_color_new (&color));
+ }
+
+ if (G_UNLIKELY (fallback))
+ {
+ gboolean b = FALSE;
+
+ if (!span_parse_boolean ("fallback", fallback, &b, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_fallback_new (b));
+ }
+
+ if (G_UNLIKELY (show))
+ {
+ Pango2ShowFlags flags;
+
+ if (!span_parse_flags ("show", show, PANGO2_TYPE_SHOW_FLAGS, (int*)(void*)&flags, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_show_new (flags));
+ }
+
+ if (G_UNLIKELY (text_transform))
+ {
+ Pango2TextTransform tf;
+
+ if (!span_parse_enum ("text_transform", text_transform, PANGO2_TYPE_TEXT_TRANSFORM, (int*)(void*)&tf, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_text_transform_new (tf));
+ }
+
+ if (G_UNLIKELY (rise))
+ {
+ int n = 0;
+
+ if (!parse_length (rise, &n))
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Value of 'rise' attribute on <span> tag on line %d "
+ "could not be parsed; should be an integer, or a "
+ "string such as '5.5pt', not '%s'",
+ line_number, rise);
+ goto error;
+ }
+
+ add_attribute (tag, pango2_attr_rise_new (n));
+ }
+
+ if (G_UNLIKELY (baseline_shift))
+ {
+ int shift = 0;
+
+ if (span_parse_enum ("baseline_shift", baseline_shift, PANGO2_TYPE_BASELINE_SHIFT, (int*)(void*)&shift, line_number, NULL))
+ add_attribute (tag, pango2_attr_baseline_shift_new (shift));
+ else if (parse_length (baseline_shift, &shift) && (shift > 1024 || shift < -1024))
+ add_attribute (tag, pango2_attr_baseline_shift_new (shift));
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Value of 'baseline_shift' attribute on <span> 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 (font_scale))
+ {
+ Pango2FontScale scale;
+
+ if (!span_parse_enum ("font_scale", font_scale, PANGO2_TYPE_FONT_SCALE, (int*)(void*)&scale, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_font_scale_new (scale));
+ }
+
+ if (G_UNLIKELY (letter_spacing))
+ {
+ int n = 0;
+
+ if (!span_parse_int ("letter_spacing", letter_spacing, &n, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_letter_spacing_new (n));
+ }
+
+ if (G_UNLIKELY (line_height))
+ {
+ double f = 0;
+
+ if (!span_parse_float ("line_height", line_height, &f, line_number, error))
+ goto error;
+
+ if (f > 1024.0 && strchr (line_height, '.') == 0)
+ add_attribute (tag, pango2_attr_line_height_new_absolute ((int)f));
+ else
+ add_attribute (tag, pango2_attr_line_height_new (f));
+ }
+
+ if (G_UNLIKELY (lang))
+ {
+ add_attribute (tag,
+ pango2_attr_language_new (pango2_language_from_string (lang)));
+ }
+
+ if (G_UNLIKELY (font_features))
+ {
+ add_attribute (tag, pango2_attr_font_features_new (font_features));
+ }
+
+ if (G_UNLIKELY (allow_breaks))
+ {
+ gboolean b = FALSE;
+
+ if (!span_parse_boolean ("allow_breaks", allow_breaks, &b, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_allow_breaks_new (b));
+ }
+
+ if (G_UNLIKELY (insert_hyphens))
+ {
+ gboolean b = FALSE;
+
+ if (!span_parse_boolean ("insert_hyphens", insert_hyphens, &b, line_number, error))
+ goto error;
+
+ add_attribute (tag, pango2_attr_insert_hyphens_new (b));
+ }
+
+ if (G_UNLIKELY (segment))
+ {
+ if (strcmp (segment, "word") == 0)
+ add_attribute (tag, pango2_attr_word_new ());
+ else if (strcmp (segment, "sentence") == 0)
+ add_attribute (tag, pango2_attr_sentence_new ());
+ else if (strcmp (segment, "paragraph") == 0)
+ add_attribute (tag, pango2_attr_paragraph_new ());
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_INVALID_CONTENT,
+ "Value of 'segment' attribute on <span> tag on line %d "
+ "could not be parsed; should be one of 'word', 'sentence' "
+ "or 'paragraph', not '%s'",
+ line_number, segment);
+ goto error;
+ }
+ }
+
+ return TRUE;
+
+ error:
+
+ return FALSE;
+}
+
+static gboolean
+i_parse_func (MarkupData *md G_GNUC_UNUSED,
+ OpenTag *tag,
+ const char **names,
+ const char **values G_GNUC_UNUSED,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ CHECK_NO_ATTRS ("i");
+
+ add_attribute (tag, pango2_attr_style_new (PANGO2_STYLE_ITALIC));
+
+ return TRUE;
+}
+
+static gboolean
+markup_parse_func (MarkupData *md G_GNUC_UNUSED,
+ OpenTag *tag G_GNUC_UNUSED,
+ const char **names G_GNUC_UNUSED,
+ const char **values G_GNUC_UNUSED,
+ GMarkupParseContext *context G_GNUC_UNUSED,
+ GError **error G_GNUC_UNUSED)
+{
+ /* We don't do anything with this tag at the moment. */
+ CHECK_NO_ATTRS ("markup");
+
+ return TRUE;
+}
+
+static gboolean
+s_parse_func (MarkupData *md G_GNUC_UNUSED,
+ OpenTag *tag,
+ const char **names,
+ const char **values G_GNUC_UNUSED,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ CHECK_NO_ATTRS ("s");
+
+ add_attribute (tag, pango2_attr_strikethrough_new (TRUE));
+
+ return TRUE;
+}
+
+static gboolean
+sub_parse_func (MarkupData *md G_GNUC_UNUSED,
+ OpenTag *tag,
+ const char **names,
+ const char **values G_GNUC_UNUSED,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ CHECK_NO_ATTRS ("sub");
+
+ add_attribute (tag, pango2_attr_font_scale_new (PANGO2_FONT_SCALE_SUBSCRIPT));
+ add_attribute (tag, pango2_attr_baseline_shift_new (PANGO2_BASELINE_SHIFT_SUBSCRIPT));
+
+ return TRUE;
+}
+
+static gboolean
+sup_parse_func (MarkupData *md G_GNUC_UNUSED,
+ OpenTag *tag,
+ const char **names,
+ const char **values G_GNUC_UNUSED,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ CHECK_NO_ATTRS ("sup");
+
+ add_attribute (tag, pango2_attr_font_scale_new (PANGO2_FONT_SCALE_SUPERSCRIPT));
+ add_attribute (tag, pango2_attr_baseline_shift_new (PANGO2_BASELINE_SHIFT_SUPERSCRIPT));
+
+ return TRUE;
+}
+
+static gboolean
+small_parse_func (MarkupData *md G_GNUC_UNUSED,
+ OpenTag *tag,
+ const char **names,
+ const char **values G_GNUC_UNUSED,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ CHECK_NO_ATTRS ("small");
+
+ /* Shrink text one level */
+ if (tag)
+ {
+ tag->scale_level_delta -= 1;
+ tag->scale_level -= 1;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+tt_parse_func (MarkupData *md G_GNUC_UNUSED,
+ OpenTag *tag,
+ const char **names,
+ const char **values G_GNUC_UNUSED,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ CHECK_NO_ATTRS ("tt");
+
+ add_attribute (tag, pango2_attr_family_new ("Monospace"));
+
+ return TRUE;
+}
+
+static gboolean
+u_parse_func (MarkupData *md G_GNUC_UNUSED,
+ OpenTag *tag,
+ const char **names,
+ const char **values G_GNUC_UNUSED,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ CHECK_NO_ATTRS ("u");
+
+ add_attribute (tag, pango2_attr_underline_new (PANGO2_LINE_STYLE_SOLID));
+
+ return TRUE;
+}
diff --git a/pango2/pango-markup.h b/pango2/pango-markup.h
new file mode 100644
index 00000000..8bc37af7
--- /dev/null
+++ b/pango2/pango-markup.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-attr-list.h>
+
+G_BEGIN_DECLS
+
+
+PANGO2_AVAILABLE_IN_ALL
+GMarkupParseContext * pango2_markup_parser_new (gunichar accel_marker);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_markup_parser_finish (GMarkupParseContext *context,
+ Pango2AttrList **attr_list,
+ char **text,
+ gunichar *accel_char,
+ GError **error);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_parse_markup (const char *markup_text,
+ int length,
+ gunichar accel_marker,
+ Pango2AttrList **attr_list,
+ char **text,
+ gunichar *accel_char,
+ GError **error);
+
+
+G_END_DECLS
diff --git a/pango2/pango-matrix.c b/pango2/pango-matrix.c
new file mode 100644
index 00000000..9381ffcd
--- /dev/null
+++ b/pango2/pango-matrix.c
@@ -0,0 +1,528 @@
+/* Pango2
+ * pango-matrix.c: Matrix manipulation routines
+ *
+ * Copyright (C) 2000, 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.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <math.h>
+
+#include "pango-matrix.h"
+#include "pango-impl-utils.h"
+
+G_DEFINE_BOXED_TYPE (Pango2Matrix, pango2_matrix,
+ pango2_matrix_copy,
+ pango2_matrix_free);
+
+/**
+ * pango2_matrix_copy:
+ * @matrix: (nullable): a `Pango2Matrix`
+ *
+ * Copies a `Pango2Matrix`.
+ *
+ * Return value: (nullable): the newly allocated `Pango2Matrix`
+ */
+Pango2Matrix *
+pango2_matrix_copy (const Pango2Matrix *matrix)
+{
+ Pango2Matrix *new_matrix;
+
+ if (matrix == NULL)
+ return NULL;
+
+ new_matrix = g_slice_new (Pango2Matrix);
+
+ *new_matrix = *matrix;
+
+ return new_matrix;
+}
+
+/**
+ * pango2_matrix_free:
+ * @matrix: (nullable): a `Pango2Matrix`, may be %NULL
+ *
+ * Free a `Pango2Matrix`.
+ */
+void
+pango2_matrix_free (Pango2Matrix *matrix)
+{
+ if (matrix == NULL)
+ return;
+
+ g_slice_free (Pango2Matrix, matrix);
+}
+
+/**
+ * pango2_matrix_translate:
+ * @matrix: a `Pango2Matrix`
+ * @tx: amount to translate in the X direction
+ * @ty: amount to translate in the Y direction
+ *
+ * Changes the transformation represented by @matrix to be the
+ * transformation given by first translating by (@tx, @ty)
+ * then applying the original transformation.
+ */
+void
+pango2_matrix_translate (Pango2Matrix *matrix,
+ double tx,
+ double ty)
+{
+ g_return_if_fail (matrix != NULL);
+
+ matrix->x0 = matrix->xx * tx + matrix->xy * ty + matrix->x0;
+ matrix->y0 = matrix->yx * tx + matrix->yy * ty + matrix->y0;
+}
+
+/**
+ * pango2_matrix_scale:
+ * @matrix: a `Pango2Matrix`
+ * @scale_x: amount to scale by in X direction
+ * @scale_y: amount to scale by in Y direction
+ *
+ * Changes the transformation represented by @matrix to be the
+ * transformation given by first scaling by @sx in the X direction
+ * and @sy in the Y direction then applying the original
+ * transformation.
+ */
+void
+pango2_matrix_scale (Pango2Matrix *matrix,
+ double scale_x,
+ double scale_y)
+{
+ g_return_if_fail (matrix != NULL);
+
+ matrix->xx *= scale_x;
+ matrix->xy *= scale_y;
+ matrix->yx *= scale_x;
+ matrix->yy *= scale_y;
+}
+
+/**
+ * pango2_matrix_rotate:
+ * @matrix: a `Pango2Matrix`
+ * @degrees: degrees to rotate counter-clockwise
+ *
+ * Changes the transformation represented by @matrix to be the
+ * transformation given by first rotating by @degrees degrees
+ * counter-clockwise then applying the original transformation.
+ */
+void
+pango2_matrix_rotate (Pango2Matrix *matrix,
+ double degrees)
+{
+ Pango2Matrix tmp;
+ double r, s, c;
+
+ g_return_if_fail (matrix != NULL);
+
+ r = degrees * (G_PI / 180.);
+ s = sin (r);
+ c = cos (r);
+
+ tmp.xx = c;
+ tmp.xy = s;
+ tmp.yx = -s;
+ tmp.yy = c;
+ tmp.x0 = 0;
+ tmp.y0 = 0;
+
+ pango2_matrix_concat (matrix, &tmp);
+}
+
+/**
+ * pango2_matrix_concat:
+ * @matrix: a `Pango2Matrix`
+ * @new_matrix: a `Pango2Matrix`
+ *
+ * Changes the transformation represented by @matrix to be the
+ * transformation given by first applying transformation
+ * given by @new_matrix then applying the original transformation.
+ */
+void
+pango2_matrix_concat (Pango2Matrix *matrix,
+ const Pango2Matrix *new_matrix)
+{
+ Pango2Matrix tmp;
+
+ g_return_if_fail (matrix != NULL);
+
+ tmp = *matrix;
+
+ matrix->xx = tmp.xx * new_matrix->xx + tmp.xy * new_matrix->yx;
+ matrix->xy = tmp.xx * new_matrix->xy + tmp.xy * new_matrix->yy;
+ matrix->yx = tmp.yx * new_matrix->xx + tmp.yy * new_matrix->yx;
+ matrix->yy = tmp.yx * new_matrix->xy + tmp.yy * new_matrix->yy;
+ matrix->x0 = tmp.xx * new_matrix->x0 + tmp.xy * new_matrix->y0 + tmp.x0;
+ matrix->y0 = tmp.yx * new_matrix->x0 + tmp.yy * new_matrix->y0 + tmp.y0;
+}
+
+/**
+ * pango2_matrix_get_font_scale_factor:
+ * @matrix: (nullable): a `Pango2Matrix`, may be %NULL
+ *
+ * Returns the scale factor of a matrix on the height of the font.
+ *
+ * That is, the scale factor in the direction perpendicular to the
+ * vector that the X coordinate is mapped to. If the scale in the X
+ * coordinate is needed as well, use [method@Pango2.Matrix.get_font_scale_factors].
+ *
+ * Return value: the scale factor of @matrix on the height of the font,
+ * or 1.0 if @matrix is %NULL.
+ */
+double
+pango2_matrix_get_font_scale_factor (const Pango2Matrix *matrix)
+{
+ double yscale;
+ pango2_matrix_get_font_scale_factors (matrix, NULL, &yscale);
+ return yscale;
+}
+
+/**
+ * pango2_matrix_get_font_scale_factors:
+ * @matrix: (nullable): a `Pango2Matrix`
+ * @xscale: (out) (optional): output scale factor in the x direction
+ * @yscale: (out) (optional): output scale factor perpendicular to the x direction
+ *
+ * Calculates the scale factor of a matrix on the width and height of the font.
+ *
+ * That is, @xscale is the scale factor in the direction of the X coordinate,
+ * and @yscale is the scale factor in the direction perpendicular to the
+ * vector that the X coordinate is mapped to.
+ *
+ * Note that output numbers will always be non-negative.
+ **/
+void
+pango2_matrix_get_font_scale_factors (const Pango2Matrix *matrix,
+ double *xscale,
+ double *yscale)
+{
+/*
+ * Based on cairo-matrix.c:_cairo_matrix_compute_scale_factors()
+ *
+ * Copyright 2005, Keith Packard
+ */
+ double major = 1., minor = 1.;
+
+ if (matrix)
+ {
+ double x = matrix->xx;
+ double y = matrix->yx;
+ major = sqrt (x*x + y*y);
+
+ if (major)
+ {
+ double det = matrix->xx * matrix->yy - matrix->yx * matrix->xy;
+
+ /*
+ * ignore mirroring
+ */
+ if (det < 0)
+ det = - det;
+
+ minor = det / major;
+ }
+ else
+ minor = 0.;
+ }
+
+ if (xscale)
+ *xscale = major;
+ if (yscale)
+ *yscale = minor;
+}
+
+#define RAD_TO_DEG(x) ((x)/G_PI * 180)
+
+/**
+ * pango2_matrix_get_rotation:
+ * @matrix: a `Pango2Matrix`
+ *
+ * Returns the angle (in degrees) that this
+ * matrix rotates the X axis by.
+ *
+ * For font matrices, this is typically zero.
+ *
+ * Returns: the rotation of @matrix
+ */
+double
+pango2_matrix_get_rotation (const Pango2Matrix *matrix)
+{
+ double x, y;
+
+ x = 1;
+ y = 0;
+
+ pango2_matrix_transform_distance (matrix, &x, &y);
+
+ return RAD_TO_DEG (acos (CLAMP (x / sqrtf (x*x + y*y), -1., 1.)));
+}
+
+/**
+ * pango2_matrix_get_slant_ratio:
+ * @matrix: a `Pango2Matrix`
+ *
+ * Gets the slant ratio of a matrix.
+ *
+ * For a simple shear matrix in the form:
+ *
+ * 1 λ
+ * 0 1
+ *
+ * this is simply λ.
+ *
+ * Returns: the slant ratio of @matrix
+ */
+double
+pango2_matrix_get_slant_ratio (const Pango2Matrix *matrix)
+{
+ if (matrix)
+ {
+ double a = matrix->xx;
+ double b = matrix->xy;
+ double c = matrix->yx;
+ double d = matrix->yy;
+
+ if (c != 0 || d != 0)
+ {
+ double s = sqrtf (c * c + d * d);
+ return (a*c + b*d) / (s*s);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * pango2_matrix_transform_distance:
+ * @matrix: (nullable): a `Pango2Matrix`
+ * @dx: (inout): in/out X component of a distance vector
+ * @dy: (inout): in/out Y component of a distance vector
+ *
+ * Transforms the distance vector (@dx,@dy) by @matrix.
+ *
+ * This is similar to [method@Pango2.Matrix.transform_point],
+ * except that the translation components of the transformation
+ * are ignored. The calculation of the returned vector is as follows:
+ *
+ * ```
+ * dx2 = dx1 * xx + dy1 * xy;
+ * dy2 = dx1 * yx + dy1 * yy;
+ * ```
+ *
+ * Affine transformations are position invariant, so the same vector
+ * always transforms to the same vector. If (@x1,@y1) transforms
+ * to (@x2,@y2) then (@x1+@dx1,@y1+@dy1) will transform to
+ * (@x1+@dx2,@y1+@dy2) for all values of @x1 and @x2.
+ */
+void
+pango2_matrix_transform_distance (const Pango2Matrix *matrix,
+ double *dx,
+ double *dy)
+{
+ if (matrix)
+ {
+ double new_x, new_y;
+
+ new_x = (matrix->xx * *dx + matrix->xy * *dy);
+ new_y = (matrix->yx * *dx + matrix->yy * *dy);
+
+ *dx = new_x;
+ *dy = new_y;
+ }
+}
+
+/**
+ * pango2_matrix_transform_point:
+ * @matrix: (nullable): a `Pango2Matrix`
+ * @x: (inout): in/out X position
+ * @y: (inout): in/out Y position
+ *
+ * Transforms the point (@x, @y) by @matrix.
+ */
+void
+pango2_matrix_transform_point (const Pango2Matrix *matrix,
+ double *x,
+ double *y)
+{
+ if (matrix)
+ {
+ pango2_matrix_transform_distance (matrix, x, y);
+
+ *x += matrix->x0;
+ *y += matrix->y0;
+ }
+}
+
+/**
+ * pango2_matrix_transform_rectangle:
+ * @matrix: (nullable): a `Pango2Matrix`
+ * @rect: (inout) (optional): in/out bounding box in Pango2 units
+ *
+ * First transforms @rect using @matrix, then calculates the bounding box
+ * of the transformed rectangle.
+ *
+ * This function is useful for example when you want to draw a rotated
+ * @Pango2Layout to an image buffer, and want to know how large the image
+ * should be and how much you should shift the layout when rendering.
+ *
+ * If you have a rectangle in device units (pixels), use
+ * [method@Pango2.Matrix.transform_pixel_rectangle].
+ *
+ * If you have the rectangle in Pango2 units and want to convert to
+ * transformed pixel bounding box, it is more accurate to transform it first
+ * (using this function) and pass the result to pango2_extents_to_pixels(),
+ * first argument, for an inclusive rounded rectangle.
+ * However, there are valid reasons that you may want to convert
+ * to pixels first and then transform, for example when the transformed
+ * coordinates may overflow in Pango2 units (large matrix translation for
+ * example).
+ */
+void
+pango2_matrix_transform_rectangle (const Pango2Matrix *matrix,
+ Pango2Rectangle *rect)
+{
+ int i;
+ double quad_x[4], quad_y[4];
+ double dx1, dy1;
+ double dx2, dy2;
+ double min_x, max_x;
+ double min_y, max_y;
+
+ if (!rect || !matrix)
+ return;
+
+ quad_x[0] = pango2_units_to_double (rect->x);
+ quad_y[0] = pango2_units_to_double (rect->y);
+ pango2_matrix_transform_point (matrix, &quad_x[0], &quad_y[0]);
+
+ dx1 = pango2_units_to_double (rect->width);
+ dy1 = 0;
+ pango2_matrix_transform_distance (matrix, &dx1, &dy1);
+ quad_x[1] = quad_x[0] + dx1;
+ quad_y[1] = quad_y[0] + dy1;
+
+ dx2 = 0;
+ dy2 = pango2_units_to_double (rect->height);
+ pango2_matrix_transform_distance (matrix, &dx2, &dy2);
+ quad_x[2] = quad_x[0] + dx2;
+ quad_y[2] = quad_y[0] + dy2;
+
+ quad_x[3] = quad_x[0] + dx1 + dx2;
+ quad_y[3] = quad_y[0] + dy1 + dy2;
+
+ min_x = max_x = quad_x[0];
+ min_y = max_y = quad_y[0];
+
+ for (i=1; i < 4; i++) {
+ if (quad_x[i] < min_x)
+ min_x = quad_x[i];
+ else if (quad_x[i] > max_x)
+ max_x = quad_x[i];
+
+ if (quad_y[i] < min_y)
+ min_y = quad_y[i];
+ else if (quad_y[i] > max_y)
+ max_y = quad_y[i];
+ }
+
+ rect->x = pango2_units_from_double (min_x);
+ rect->y = pango2_units_from_double (min_y);
+ rect->width = pango2_units_from_double (max_x) - rect->x;
+ rect->height = pango2_units_from_double (max_y) - rect->y;
+}
+
+/**
+ * pango2_matrix_transform_pixel_rectangle:
+ * @matrix: (nullable): a `Pango2Matrix`
+ * @rect: (inout) (optional): in/out bounding box in device units
+ *
+ * First transforms the @rect using @matrix, then calculates the bounding box
+ * of the transformed rectangle.
+ *
+ * This function is useful for example when you want to draw a rotated
+ * @Pango2Layout to an image buffer, and want to know how large the image
+ * should be and how much you should shift the layout when rendering.
+ *
+ * For better accuracy, you should use [method@Pango2.Matrix.transform_rectangle]
+ * on original rectangle in Pango2 units and convert to pixels afterward
+ * using [func@extents_to_pixels]'s first argument.
+ */
+void
+pango2_matrix_transform_pixel_rectangle (const Pango2Matrix *matrix,
+ Pango2Rectangle *rect)
+{
+ int i;
+ double quad_x[4], quad_y[4];
+ double dx1, dy1;
+ double dx2, dy2;
+ double min_x, max_x;
+ double min_y, max_y;
+
+ if (!rect || !matrix)
+ return;
+
+ quad_x[0] = rect->x;
+ quad_y[0] = rect->y;
+ pango2_matrix_transform_point (matrix, &quad_x[0], &quad_y[0]);
+
+ dx1 = rect->width;
+ dy1 = 0;
+ pango2_matrix_transform_distance (matrix, &dx1, &dy1);
+ quad_x[1] = quad_x[0] + dx1;
+ quad_y[1] = quad_y[0] + dy1;
+
+ dx2 = 0;
+ dy2 = rect->height;
+ pango2_matrix_transform_distance (matrix, &dx2, &dy2);
+ quad_x[2] = quad_x[0] + dx2;
+ quad_y[2] = quad_y[0] + dy2;
+
+ quad_x[3] = quad_x[0] + dx1 + dx2;
+ quad_y[3] = quad_y[0] + dy1 + dy2;
+
+ min_x = max_x = quad_x[0];
+ min_y = max_y = quad_y[0];
+
+ for (i=1; i < 4; i++)
+ {
+ if (quad_x[i] < min_x)
+ min_x = quad_x[i];
+ else if (quad_x[i] > max_x)
+ max_x = quad_x[i];
+
+ if (quad_y[i] < min_y)
+ min_y = quad_y[i];
+ else if (quad_y[i] > max_y)
+ max_y = quad_y[i];
+ }
+
+ rect->x = floor (min_x);
+ rect->y = floor (min_y);
+ rect->width = ceil (max_x - rect->x);
+ rect->height = ceil (max_y - rect->y);
+}
+
+gboolean
+pango2_matrix_equal (const Pango2Matrix *m1,
+ const Pango2Matrix *m2)
+{
+ return m1->xx == m2->xx && m1->xy == m2->xy &&
+ m1->yx == m2->yx && m1->yy == m2->yy &&
+ m1->x0 == m2->x0 && m1->y0 == m2->y0;
+}
diff --git a/pango2/pango-matrix.h b/pango2/pango-matrix.h
new file mode 100644
index 00000000..0456a4fc
--- /dev/null
+++ b/pango2/pango-matrix.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2002, 2006 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _Pango2Matrix Pango2Matrix;
+
+/**
+ * Pango2Matrix:
+ * @xx: 1st component of the transformation matrix
+ * @xy: 2nd component of the transformation matrix
+ * @yx: 3rd component of the transformation matrix
+ * @yy: 4th component of the transformation matrix
+ * @x0: x translation
+ * @y0: y translation
+ *
+ * A `Pango2Matrix` specifies a transformation between user-space
+ * and device coordinates.
+ *
+ * The transformation is given by
+ *
+ * ```
+ * x_device = x_user * matrix->xx + y_user * matrix->xy + matrix->x0;
+ * y_device = x_user * matrix->yx + y_user * matrix->yy + matrix->y0;
+ * ```
+ */
+struct _Pango2Matrix
+{
+ double xx;
+ double xy;
+ double yx;
+ double yy;
+ double x0;
+ double y0;
+};
+
+#define PANGO2_TYPE_MATRIX (pango2_matrix_get_type ())
+
+/**
+ * PANGO2_MATRIX_INIT:
+ *
+ * Constant that can be used to initialize a `Pango2Matrix` to
+ * the identity transform.
+ *
+ * ```
+ * Pango2Matrix matrix = PANGO2_MATRIX_INIT;
+ * pango2_matrix_rotate (&matrix, 45.);
+ * ```
+ */
+#define PANGO2_MATRIX_INIT { 1., 0., 0., 1., 0., 0. }
+
+#include <pango2/pango-types.h>
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_matrix_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Matrix * pango2_matrix_copy (const Pango2Matrix *matrix);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_matrix_free (Pango2Matrix *matrix);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_matrix_equal (const Pango2Matrix *m1,
+ const Pango2Matrix *m2);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_matrix_translate (Pango2Matrix *matrix,
+ double tx,
+ double ty);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_matrix_scale (Pango2Matrix *matrix,
+ double scale_x,
+ double scale_y);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_matrix_rotate (Pango2Matrix *matrix,
+ double degrees);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_matrix_concat (Pango2Matrix *matrix,
+ const Pango2Matrix *new_matrix);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_matrix_transform_point (const Pango2Matrix *matrix,
+ double *x,
+ double *y);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_matrix_transform_distance (const Pango2Matrix *matrix,
+ double *dx,
+ double *dy);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_matrix_transform_rectangle (const Pango2Matrix *matrix,
+ Pango2Rectangle *rect);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_matrix_transform_pixel_rectangle (const Pango2Matrix *matrix,
+ Pango2Rectangle *rect);
+PANGO2_AVAILABLE_IN_ALL
+double pango2_matrix_get_font_scale_factor (const Pango2Matrix *matrix) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+void pango2_matrix_get_font_scale_factors (const Pango2Matrix *matrix,
+ double *xscale,
+ double *yscale);
+PANGO2_AVAILABLE_IN_ALL
+double pango2_matrix_get_rotation (const Pango2Matrix *matrix) G_GNUC_PURE;
+PANGO2_AVAILABLE_IN_ALL
+double pango2_matrix_get_slant_ratio (const Pango2Matrix *matrix) G_GNUC_PURE;
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2Matrix, pango2_matrix_free)
+
+
+G_END_DECLS
diff --git a/pango2/pango-renderer.c b/pango2/pango-renderer.c
new file mode 100644
index 00000000..5f1d3de6
--- /dev/null
+++ b/pango2/pango-renderer.c
@@ -0,0 +1,1731 @@
+/* Pango2
+ * pango-renderer.h: Base class for rendering
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+
+#include "pango-renderer.h"
+#include "pango-impl-utils.h"
+#include "pango-layout.h"
+#include "pango-attr-private.h"
+#include "pango-run-private.h"
+#include "pango-line-private.h"
+#include "pango-attributes-private.h"
+#include "pango-glyph-item-private.h"
+
+#define N_RENDER_PARTS 5
+
+#define IS_VALID_PART(part) ((guint)part < N_RENDER_PARTS)
+
+typedef struct _LineState LineState;
+typedef struct _Point Point;
+typedef struct _Pango2RendererPrivate Pango2RendererPrivate;
+
+struct _Point
+{
+ double x, y;
+};
+
+struct _LineState
+{
+ Pango2LineStyle underline;
+ Pango2UnderlinePosition underline_position;
+ Pango2Rectangle underline_rect;
+
+ Pango2LineStyle strikethrough;
+ Pango2Rectangle strikethrough_rect;
+ int strikethrough_glyphs;
+
+ Pango2LineStyle overline;
+ Pango2Rectangle overline_rect;
+
+ int logical_rect_end;
+};
+
+struct _Pango2RendererPrivate
+{
+ Pango2LineStyle underline;
+ Pango2UnderlinePosition underline_position;
+ Pango2LineStyle strikethrough;
+ Pango2LineStyle overline;
+ int active_count;
+
+ Pango2Matrix *matrix;
+ Pango2Context *context;
+
+ Pango2Color color[N_RENDER_PARTS];
+ gboolean color_set[N_RENDER_PARTS];
+
+ Pango2Lines *lines;
+ Pango2Line *line;
+ LineState *line_state;
+};
+
+static void pango2_renderer_finalize (GObject *gobject);
+static void pango2_renderer_default_draw_glyphs (Pango2Renderer *renderer,
+ Pango2Font *font,
+ Pango2GlyphString *glyphs,
+ int x,
+ int y);
+static void pango2_renderer_default_draw_run (Pango2Renderer *renderer,
+ const char *text,
+ Pango2Run *run,
+ int x,
+ int y);
+static void pango2_renderer_default_draw_rectangle (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ int x,
+ int y,
+ int width,
+ int height);
+static void pango2_renderer_default_draw_styled_line (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ Pango2LineStyle style,
+ int x,
+ int y,
+ int width,
+ int height);
+static void pango2_renderer_default_prepare_run (Pango2Renderer *renderer,
+ Pango2Run *run);
+static void pango2_renderer_prepare_run (Pango2Renderer *renderer,
+ Pango2Run *run);
+
+static void
+to_device (Pango2Matrix *matrix,
+ double x,
+ double y,
+ Point *result)
+{
+ if (matrix)
+ {
+ result->x = (x * matrix->xx + y * matrix->xy) / PANGO2_SCALE + matrix->x0;
+ result->y = (x * matrix->yx + y * matrix->yy) / PANGO2_SCALE + matrix->y0;
+ }
+ else
+ {
+ result->x = x / PANGO2_SCALE;
+ result->y = y / PANGO2_SCALE;
+ }
+}
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (Pango2Renderer, pango2_renderer, G_TYPE_OBJECT,
+ G_ADD_PRIVATE (Pango2Renderer))
+
+static void
+pango2_renderer_class_init (Pango2RendererClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ klass->draw_glyphs = pango2_renderer_default_draw_glyphs;
+ klass->draw_run = pango2_renderer_default_draw_run;
+ klass->draw_rectangle = pango2_renderer_default_draw_rectangle;
+ klass->draw_styled_line = pango2_renderer_default_draw_styled_line;
+ klass->prepare_run = pango2_renderer_default_prepare_run;
+
+ gobject_class->finalize = pango2_renderer_finalize;
+}
+
+static void
+pango2_renderer_init (Pango2Renderer *renderer)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+ priv->matrix = NULL;
+}
+
+static void
+pango2_renderer_finalize (GObject *gobject)
+{
+ Pango2Renderer *renderer = PANGO2_RENDERER (gobject);
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ if (priv->matrix)
+ pango2_matrix_free (priv->matrix);
+
+ G_OBJECT_CLASS (pango2_renderer_parent_class)->finalize (gobject);
+}
+
+static void
+pango2_renderer_activate_with_context (Pango2Renderer *renderer,
+ Pango2Context *context)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ /* We only change the matrix if the renderer isn't already active. */
+ if (!priv->active_count)
+ {
+ pango2_renderer_set_matrix (renderer, context ? pango2_context_get_matrix (context) : NULL);
+ priv->context = context;
+ }
+
+ pango2_renderer_activate (renderer);
+}
+
+static void
+draw_underline (Pango2Renderer *renderer,
+ LineState *state)
+{
+ Pango2Rectangle *rect = &state->underline_rect;
+ Pango2LineStyle underline = state->underline;
+
+ state->underline = PANGO2_LINE_STYLE_NONE;
+ state->underline_position = PANGO2_UNDERLINE_POSITION_NORMAL;
+
+ switch (underline)
+ {
+ case PANGO2_LINE_STYLE_NONE:
+ break;
+ case PANGO2_LINE_STYLE_DOUBLE:
+ pango2_renderer_draw_rectangle (renderer,
+ PANGO2_RENDER_PART_UNDERLINE,
+ rect->x,
+ rect->y + 2 * rect->height,
+ rect->width,
+ rect->height);
+ G_GNUC_FALLTHROUGH;
+ case PANGO2_LINE_STYLE_SOLID:
+ pango2_renderer_draw_rectangle (renderer,
+ PANGO2_RENDER_PART_UNDERLINE,
+ rect->x,
+ rect->y,
+ rect->width,
+ rect->height);
+ break;
+ case PANGO2_LINE_STYLE_DOTTED:
+ case PANGO2_LINE_STYLE_DASHED:
+ pango2_renderer_draw_styled_line (renderer,
+ PANGO2_RENDER_PART_UNDERLINE,
+ underline,
+ rect->x,
+ rect->y,
+ rect->width,
+ rect->height);
+ break;
+ case PANGO2_LINE_STYLE_WAVY:
+ pango2_renderer_draw_styled_line (renderer,
+ PANGO2_RENDER_PART_UNDERLINE,
+ underline,
+ rect->x,
+ rect->y,
+ rect->width,
+ 3 * rect->height);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+draw_overline (Pango2Renderer *renderer,
+ LineState *state)
+{
+ Pango2Rectangle *rect = &state->overline_rect;
+ Pango2LineStyle overline = state->overline;
+
+ state->overline = PANGO2_LINE_STYLE_NONE;
+
+ switch (overline)
+ {
+ case PANGO2_LINE_STYLE_NONE:
+ break;
+ case PANGO2_LINE_STYLE_DOUBLE:
+ pango2_renderer_draw_rectangle (renderer,
+ PANGO2_RENDER_PART_OVERLINE,
+ rect->x,
+ rect->y - 2 * rect->height,
+ rect->width,
+ rect->height);
+ G_GNUC_FALLTHROUGH;
+ case PANGO2_LINE_STYLE_SOLID:
+ pango2_renderer_draw_rectangle (renderer,
+ PANGO2_RENDER_PART_OVERLINE,
+ rect->x,
+ rect->y,
+ rect->width,
+ rect->height);
+ break;
+ case PANGO2_LINE_STYLE_DOTTED:
+ case PANGO2_LINE_STYLE_DASHED:
+ pango2_renderer_draw_styled_line (renderer,
+ PANGO2_RENDER_PART_OVERLINE,
+ overline,
+ rect->x,
+ rect->y,
+ rect->width,
+ rect->height);
+ break;
+ case PANGO2_LINE_STYLE_WAVY:
+ pango2_renderer_draw_styled_line (renderer,
+ PANGO2_RENDER_PART_OVERLINE,
+ overline,
+ rect->x,
+ rect->y,
+ rect->width,
+ 3 * rect->height);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+draw_strikethrough (Pango2Renderer *renderer,
+ LineState *state)
+{
+ Pango2Rectangle *rect = &state->strikethrough_rect;
+
+ if (state->strikethrough_glyphs > 0)
+ {
+ rect->y /= state->strikethrough_glyphs;
+ rect->height /= state->strikethrough_glyphs;
+
+ switch (state->strikethrough)
+ {
+ case PANGO2_LINE_STYLE_NONE:
+ break;
+ case PANGO2_LINE_STYLE_DOUBLE:
+ pango2_renderer_draw_rectangle (renderer,
+ PANGO2_RENDER_PART_STRIKETHROUGH,
+ rect->x,
+ rect->y - rect->height,
+ rect->width,
+ rect->height);
+ rect->y += rect->height;
+ G_GNUC_FALLTHROUGH;
+ case PANGO2_LINE_STYLE_SOLID:
+ pango2_renderer_draw_rectangle (renderer,
+ PANGO2_RENDER_PART_STRIKETHROUGH,
+ rect->x,
+ rect->y,
+ rect->width,
+ rect->height);
+ break;
+ case PANGO2_LINE_STYLE_DOTTED:
+ case PANGO2_LINE_STYLE_DASHED:
+ pango2_renderer_draw_styled_line (renderer,
+ PANGO2_RENDER_PART_STRIKETHROUGH,
+ state->strikethrough,
+ rect->x,
+ rect->y,
+ rect->width,
+ rect->height);
+ break;
+ case PANGO2_LINE_STYLE_WAVY:
+ pango2_renderer_draw_styled_line (renderer,
+ PANGO2_RENDER_PART_STRIKETHROUGH,
+ state->strikethrough,
+ rect->x,
+ rect->y,
+ rect->width,
+ 3 * rect->height);
+ break;
+ default:
+ break;
+ }
+ }
+
+ state->strikethrough = PANGO2_LINE_STYLE_NONE;
+ state->strikethrough_glyphs = 0;
+ rect->x += rect->width;
+ rect->width = 0;
+ rect->y = 0;
+ rect->height = 0;
+}
+
+static void
+handle_line_state_change (Pango2Renderer *renderer,
+ Pango2RenderPart part)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+ LineState *state = priv->line_state;
+ if (!state)
+ return;
+
+ if (part == PANGO2_RENDER_PART_UNDERLINE &&
+ state->underline != PANGO2_LINE_STYLE_NONE)
+ {
+ Pango2Rectangle *rect = &state->underline_rect;
+
+ rect->width = state->logical_rect_end - rect->x;
+ draw_underline (renderer, state);
+ state->underline = priv->underline;
+ state->underline_position = priv->underline_position;
+ rect->x = state->logical_rect_end;
+ rect->width = 0;
+ }
+
+ if (part == PANGO2_RENDER_PART_OVERLINE &&
+ state->overline != PANGO2_LINE_STYLE_NONE)
+ {
+ Pango2Rectangle *rect = &state->overline_rect;
+
+ rect->width = state->logical_rect_end - rect->x;
+ draw_overline (renderer, state);
+ state->overline = priv->overline;
+ rect->x = state->logical_rect_end;
+ rect->width = 0;
+ }
+
+ if (part == PANGO2_RENDER_PART_STRIKETHROUGH &&
+ state->strikethrough != PANGO2_LINE_STYLE_NONE)
+ {
+ Pango2Rectangle *rect = &state->strikethrough_rect;
+
+ rect->width = state->logical_rect_end - rect->x;
+ draw_strikethrough (renderer, state);
+ state->strikethrough = priv->strikethrough;
+ }
+}
+
+static void
+add_underline (Pango2Renderer *renderer,
+ LineState *state,
+ Pango2FontMetrics *metrics,
+ int base_x,
+ int base_y,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+ Pango2Rectangle *current_rect = &state->underline_rect;
+ Pango2Rectangle new_rect;
+
+ int underline_thickness = pango2_font_metrics_get_underline_thickness (metrics);
+ int underline_position = pango2_font_metrics_get_underline_position (metrics);
+
+ new_rect.x = base_x + MIN (ink_rect->x, logical_rect->x);
+ new_rect.width = MAX (ink_rect->width, logical_rect->width);
+ new_rect.height = underline_thickness;
+ new_rect.y = base_y;
+
+ switch (priv->underline)
+ {
+ case PANGO2_LINE_STYLE_NONE:
+ g_assert_not_reached ();
+ break;
+ case PANGO2_LINE_STYLE_SOLID:
+ case PANGO2_LINE_STYLE_DASHED:
+ case PANGO2_LINE_STYLE_DOTTED:
+ if (priv->underline_position == PANGO2_UNDERLINE_POSITION_UNDER)
+ {
+ new_rect.y += ink_rect->y + ink_rect->height + underline_thickness;
+ break;
+ }
+ G_GNUC_FALLTHROUGH;
+ case PANGO2_LINE_STYLE_DOUBLE:
+ case PANGO2_LINE_STYLE_WAVY:
+ new_rect.y -= underline_position;
+ if (state->underline == priv->underline)
+ {
+ new_rect.y = MAX (current_rect->y, new_rect.y);
+ new_rect.height = MAX (current_rect->height, new_rect.height);
+ current_rect->y = new_rect.y;
+ current_rect->height = new_rect.height;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (priv->underline == state->underline &&
+ priv->underline_position == state->underline_position &&
+ new_rect.y == current_rect->y &&
+ new_rect.height == current_rect->height)
+ {
+ current_rect->width = new_rect.x + new_rect.width - current_rect->x;
+ }
+ else
+ {
+ draw_underline (renderer, state);
+
+ *current_rect = new_rect;
+ state->underline = priv->underline;
+ state->underline_position = priv->underline_position;
+ }
+}
+
+static void
+add_overline (Pango2Renderer *renderer,
+ LineState *state,
+ Pango2FontMetrics *metrics,
+ int base_x,
+ int base_y,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+ Pango2Rectangle *current_rect = &state->overline_rect;
+ Pango2Rectangle new_rect;
+ int underline_thickness = pango2_font_metrics_get_underline_thickness (metrics);
+ int ascent = pango2_font_metrics_get_ascent (metrics);
+
+ new_rect.x = base_x + ink_rect->x;
+ new_rect.width = ink_rect->width;
+ new_rect.height = underline_thickness;
+ new_rect.y = base_y;
+
+ switch (priv->overline)
+ {
+ case PANGO2_LINE_STYLE_NONE:
+ g_assert_not_reached ();
+ break;
+ case PANGO2_LINE_STYLE_SOLID:
+ case PANGO2_LINE_STYLE_DOUBLE:
+ case PANGO2_LINE_STYLE_DASHED:
+ case PANGO2_LINE_STYLE_DOTTED:
+ case PANGO2_LINE_STYLE_WAVY:
+ new_rect.y -= ascent;
+ if (state->overline == priv->overline)
+ {
+ new_rect.y = MIN (current_rect->y, new_rect.y);
+ new_rect.height = MAX (current_rect->height, new_rect.height);
+ current_rect->y = new_rect.y;
+ current_rect->height = new_rect.height;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (priv->overline == state->overline &&
+ new_rect.y == current_rect->y &&
+ new_rect.height == current_rect->height)
+ {
+ current_rect->width = new_rect.x + new_rect.width - current_rect->x;
+ }
+ else
+ {
+ draw_overline (renderer, state);
+
+ *current_rect = new_rect;
+ state->overline = priv->overline;
+ }
+}
+
+static void
+add_strikethrough (Pango2Renderer *renderer,
+ LineState *state,
+ Pango2FontMetrics *metrics,
+ int base_x,
+ int base_y,
+ Pango2Rectangle *ink_rect G_GNUC_UNUSED,
+ Pango2Rectangle *logical_rect,
+ int num_glyphs)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+ Pango2Rectangle *current_rect = &state->strikethrough_rect;
+ Pango2Rectangle new_rect;
+
+ int strikethrough_thickness = pango2_font_metrics_get_strikethrough_thickness (metrics);
+ int strikethrough_position = pango2_font_metrics_get_strikethrough_position (metrics);
+
+ new_rect.x = base_x + ink_rect->x;
+ new_rect.width = ink_rect->width;
+ new_rect.y = (base_y - strikethrough_position) * num_glyphs;
+ new_rect.height = strikethrough_thickness * num_glyphs;
+
+ if (state->strikethrough == priv->strikethrough)
+ {
+ current_rect->width = new_rect.x + new_rect.width - current_rect->x;
+ current_rect->y += new_rect.y;
+ current_rect->height += new_rect.height;
+ state->strikethrough_glyphs += num_glyphs;
+ }
+ else
+ {
+ draw_strikethrough (renderer, state);
+
+ *current_rect = new_rect;
+ state->strikethrough = priv->strikethrough;
+ state->strikethrough_glyphs = num_glyphs;
+ }
+}
+
+static void pango2_renderer_draw_runs (Pango2Renderer *renderer,
+ Pango2Line *line,
+ GSList *runs,
+ const char *text,
+ int x,
+ int y);
+
+static void
+draw_shaped_glyphs (Pango2Renderer *renderer,
+ Pango2GlyphString *glyphs,
+ Pango2Attribute *attr,
+ int x,
+ int y)
+{
+ Pango2RendererClass *class = PANGO2_RENDERER_GET_CLASS (renderer);
+ int i;
+
+ if (!class->draw_shape)
+ return;
+
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ Pango2GlyphInfo *gi = &glyphs->glyphs[i];
+ ShapeData *data = (ShapeData *)attr->pointer_value;
+
+ class->draw_shape (renderer,
+ &data->ink_rect,
+ &data->logical_rect,
+ data->data,
+ x, y);
+
+ x += gi->geometry.width;
+ }
+}
+
+/**
+ * pango2_renderer_draw_line:
+ * @renderer: a `Pango2Renderer`
+ * @line: a `Pango2Line`
+ * @x: X position of left edge of baseline, in user space coordinates
+ * in Pango2 units.
+ * @y: Y position of left edge of baseline, in user space coordinates
+ * in Pango2 units.
+ *
+ * Draws @line with the specified `Pango2Renderer`.
+ *
+ * This draws the glyph items that make up the line, as well as
+ * shapes, backgrounds and lines that are specified by the attributes
+ * of those items.
+ */
+void
+pango2_renderer_draw_line (Pango2Renderer *renderer,
+ Pango2Line *line,
+ int x,
+ int y)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+ LineState state = { 0, };
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+
+ pango2_renderer_activate_with_context (renderer, line->context);
+
+ priv->line = line;
+ priv->line_state = &state;
+
+ state.underline = PANGO2_LINE_STYLE_NONE;
+ state.underline_position = PANGO2_UNDERLINE_POSITION_NORMAL;
+ state.overline = PANGO2_LINE_STYLE_NONE;
+ state.strikethrough = PANGO2_LINE_STYLE_NONE;
+
+ pango2_renderer_draw_runs (renderer, line, line->runs, line->data->text, x, y);
+
+ /* Finish off any remaining underlines */
+ draw_underline (renderer, &state);
+ draw_overline (renderer, &state);
+ draw_strikethrough (renderer, &state);
+
+ priv->line = NULL;
+ priv->line_state = NULL;
+ priv->line = NULL;
+
+ pango2_renderer_deactivate (renderer);
+}
+
+/**
+ * pango2_renderer_draw_lines:
+ * @renderer: a `Pango2Renderer`
+ * @lines: a `Pango2Lines` object
+ * @x: X position of left edge of baseline, in user space coordinates
+ * in Pango2 units.
+ * @y: Y position of left edge of baseline, in user space coordinates
+ * in Pango2 units.
+ *
+ * Draws @lines with the specified `Pango2Renderer`.
+ */
+void
+pango2_renderer_draw_lines (Pango2Renderer *renderer,
+ Pango2Lines *lines,
+ int x,
+ int y)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+ int n;
+ Pango2Line *line;
+ int line_x, line_y;
+ Pango2Line **l;
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+
+ priv->lines = lines;
+
+ l = pango2_lines_get_lines (lines);
+ for (n = 0; n < pango2_lines_get_line_count (lines); n++)
+ {
+ line = l[n];
+ pango2_lines_get_line_position (lines, n, &line_x, &line_y);
+
+ if (n == 0)
+ pango2_renderer_activate_with_context (renderer, line->context);
+
+ pango2_renderer_draw_line (renderer, line, x + line_x, y + line_y);
+ }
+
+ if (n > 0)
+ pango2_renderer_deactivate (renderer);
+
+ priv->lines = NULL;
+}
+
+static void
+pango2_shape_get_extents (int n_chars,
+ Pango2Rectangle *shape_ink,
+ Pango2Rectangle *shape_logical,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ if (n_chars > 0)
+ {
+ ink_rect->x = MIN (shape_ink->x, shape_ink->x + shape_logical->width * (n_chars - 1));
+ ink_rect->width = MAX (shape_ink->width, shape_ink->width + shape_logical->width * (n_chars - 1));
+ ink_rect->y = shape_ink->y;
+ ink_rect->height = shape_ink->height;
+
+ logical_rect->x = MIN (shape_logical->x, shape_logical->x + shape_logical->width * (n_chars - 1));
+ logical_rect->width = MAX (shape_logical->width, shape_logical->width + shape_logical->width * (n_chars - 1));
+ logical_rect->y = shape_logical->y;
+ logical_rect->height = shape_logical->height;
+ }
+ else
+ {
+ ink_rect->x = 0;
+ ink_rect->y = 0;
+ ink_rect->width = 0;
+ ink_rect->height = 0;
+
+ logical_rect->x = 0;
+ logical_rect->y = 0;
+ logical_rect->width = 0;
+ logical_rect->height = 0;
+ }
+}
+
+
+
+static void
+pango2_renderer_draw_runs (Pango2Renderer *renderer,
+ Pango2Line *line,
+ GSList *runs,
+ const char *text,
+ int x,
+ int y)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+ GSList *l;
+ int x_off = 0;
+ int glyph_string_width;
+ gboolean got_overall = FALSE;
+ Pango2Rectangle overall_rect;
+
+ for (l = runs; l; l = l->next)
+ {
+ Pango2FontMetrics *metrics;
+ Pango2Run *run = l->data;
+ Pango2GlyphItem *glyph_item = pango2_run_get_glyph_item (run);
+ Pango2Item *item = pango2_run_get_item (run);
+ Pango2GlyphString *glyphs = pango2_run_get_glyphs (run);
+ Pango2Rectangle ink_rect, *ink = NULL;
+ Pango2Rectangle logical_rect, *logical = NULL;
+ ItemProperties properties;
+ int y_off;
+
+ if (item->analysis.flags & PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE)
+ logical = &logical_rect;
+
+ pango2_renderer_prepare_run (renderer, run);
+
+ pango2_item_get_properties (item, &properties);
+
+ if (properties.shape)
+ {
+ ShapeData *data = (ShapeData *)properties.shape->pointer_value;
+
+ ink = &ink_rect;
+ logical = &logical_rect;
+ pango2_shape_get_extents (glyphs->num_glyphs,
+ &data->ink_rect,
+ &data->logical_rect,
+ ink,
+ logical);
+
+ glyph_string_width = logical->width;
+ }
+ else
+ {
+ if (priv->underline != PANGO2_LINE_STYLE_NONE ||
+ priv->overline != PANGO2_LINE_STYLE_NONE ||
+ priv->strikethrough != PANGO2_LINE_STYLE_NONE)
+ {
+ ink = &ink_rect;
+ logical = &logical_rect;
+ }
+ if (G_UNLIKELY (ink || logical))
+ pango2_glyph_string_extents (glyphs, item->analysis.font, ink, logical);
+ if (logical)
+ glyph_string_width = logical_rect.width;
+ else
+ glyph_string_width = pango2_glyph_string_get_width (glyphs);
+ }
+
+ priv->line_state->logical_rect_end = x + x_off + glyph_string_width;
+
+ x_off += glyph_item->start_x_offset;
+ y_off = glyph_item->y_offset;
+
+ if (item->analysis.flags & PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE)
+ {
+ gboolean is_hinted = ((logical_rect.y | logical_rect.height) & (PANGO2_SCALE - 1)) == 0;
+ int adjustment = logical_rect.y + logical_rect.height / 2;
+
+ if (is_hinted)
+ adjustment = PANGO2_UNITS_ROUND (adjustment);
+
+ y_off += adjustment;
+ }
+
+ if (priv->color_set[PANGO2_RENDER_PART_BACKGROUND])
+ {
+ if (!got_overall)
+ {
+ pango2_line_get_extents (line, NULL, &overall_rect);
+ got_overall = TRUE;
+ }
+
+ pango2_renderer_draw_rectangle (renderer,
+ PANGO2_RENDER_PART_BACKGROUND,
+ x + x_off,
+ y + overall_rect.y,
+ glyph_string_width,
+ overall_rect.height);
+ }
+
+ if (properties.shape)
+ draw_shaped_glyphs (renderer, glyphs, properties.shape, x + x_off, y - y_off);
+ else
+ pango2_renderer_draw_run (renderer, text, run, x + x_off, y - y_off);
+
+ if (priv->underline != PANGO2_LINE_STYLE_NONE ||
+ priv->overline != PANGO2_LINE_STYLE_NONE ||
+ priv->strikethrough != PANGO2_LINE_STYLE_NONE)
+ {
+ metrics = pango2_font_get_metrics (item->analysis.font,
+ item->analysis.language);
+
+ if (priv->underline != PANGO2_LINE_STYLE_NONE)
+ add_underline (renderer, priv->line_state, metrics,
+ x + x_off, y - y_off,
+ ink, logical);
+
+ if (priv->overline != PANGO2_LINE_STYLE_NONE)
+ add_overline (renderer, priv->line_state, metrics,
+ x + x_off, y - y_off,
+ ink, logical);
+
+ if (priv->strikethrough != PANGO2_LINE_STYLE_NONE)
+ add_strikethrough (renderer, priv->line_state, metrics,
+ x + x_off, y - y_off,
+ ink, logical, glyphs->num_glyphs);
+
+ pango2_font_metrics_free (metrics);
+ }
+
+ if (priv->underline == PANGO2_LINE_STYLE_NONE &&
+ priv->line_state->underline != PANGO2_LINE_STYLE_NONE)
+ draw_underline (renderer, priv->line_state);
+
+ if (priv->overline == PANGO2_LINE_STYLE_NONE &&
+ priv->line_state->overline != PANGO2_LINE_STYLE_NONE)
+ draw_overline (renderer, priv->line_state);
+
+ if (priv->strikethrough == PANGO2_LINE_STYLE_NONE &&
+ priv->line_state->strikethrough != PANGO2_LINE_STYLE_NONE)
+ draw_strikethrough (renderer, priv->line_state);
+
+ x_off += glyph_string_width;
+ x_off += glyph_item->end_x_offset;
+ }
+}
+
+/**
+ * pango2_renderer_draw_glyphs:
+ * @renderer: a `Pango2Renderer`
+ * @font: a `Pango2Font`
+ * @glyphs: a `Pango2GlyphString`
+ * @x: X position of left edge of baseline, in user space coordinates
+ * in Pango2 units.
+ * @y: Y position of left edge of baseline, in user space coordinates
+ * in Pango2 units.
+ *
+ * Draws the glyphs in @glyphs with the specified `Pango2Renderer`.
+ */
+void
+pango2_renderer_draw_glyphs (Pango2Renderer *renderer,
+ Pango2Font *font,
+ Pango2GlyphString *glyphs,
+ int x,
+ int y)
+{
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+
+ pango2_renderer_activate (renderer);
+
+ PANGO2_RENDERER_GET_CLASS (renderer)->draw_glyphs (renderer, font, glyphs, x, y);
+
+ pango2_renderer_deactivate (renderer);
+}
+
+static void
+pango2_renderer_default_draw_glyphs (Pango2Renderer *renderer,
+ Pango2Font *font,
+ Pango2GlyphString *glyphs,
+ int x,
+ int y)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+ int i;
+ int x_position = 0;
+
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ Pango2GlyphInfo *gi = &glyphs->glyphs[i];
+ Point p;
+
+ to_device (priv->matrix,
+ x + x_position + gi->geometry.x_offset,
+ y + gi->geometry.y_offset,
+ &p);
+
+ pango2_renderer_draw_glyph (renderer, font, gi->glyph, p.x, p.y);
+
+ x_position += gi->geometry.width;
+ }
+}
+
+/**
+ * pango2_renderer_draw_run:
+ * @renderer: a `Pango2Renderer`
+ * @text: (nullable): the UTF-8 text that @glyph_item refers to
+ * @run: a `Pango2Run`
+ * @x: X position of left edge of baseline, in user space coordinates
+ * in Pango2 units
+ * @y: Y position of left edge of baseline, in user space coordinates
+ * in Pango2 units
+ *
+ * Draws the glyphs in @run with the specified `Pango2Renderer`,
+ * embedding the text associated with the glyphs in the output if the
+ * output format supports it.
+ *
+ * This is useful for rendering text in PDF.
+ *
+ * Note that this method does not handle attributes in @run.
+ * If you want colors, shapes and lines handled automatically according
+ * to those attributes, you need to use [method@Pango2.Renderer.draw_line]
+ * or [method@Pango2.Renderer.draw_lines].
+ *
+ * Note that @text is the start of the text for layout, which is then
+ * indexed by `run->item->offset`.
+ *
+ * If @text is %NULL, this simply calls [method@Pango2.Renderer.draw_glyphs].
+ *
+ * The default implementation of this method simply falls back to
+ * [method@Pango2.Renderer.draw_glyphs].
+ */
+void
+pango2_renderer_draw_run (Pango2Renderer *renderer,
+ const char *text,
+ Pango2Run *run,
+ int x,
+ int y)
+{
+ if (!text)
+ {
+ Pango2Item *item = pango2_run_get_item (run);
+ Pango2GlyphString *glyphs = pango2_run_get_glyphs (run);
+
+ pango2_renderer_draw_glyphs (renderer, item->analysis.font, glyphs, x, y);
+ return;
+ }
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+
+ pango2_renderer_activate (renderer);
+
+ PANGO2_RENDERER_GET_CLASS (renderer)->draw_run (renderer, text, run, x, y);
+
+ pango2_renderer_deactivate (renderer);
+}
+
+static void
+pango2_renderer_default_draw_run (Pango2Renderer *renderer,
+ const char *text G_GNUC_UNUSED,
+ Pango2Run *run,
+ int x,
+ int y)
+{
+ Pango2Item *item = pango2_run_get_item (run);
+ Pango2GlyphString *glyphs = pango2_run_get_glyphs (run);
+
+ pango2_renderer_draw_glyphs (renderer, item->analysis.font, glyphs, x, y);
+}
+
+/**
+ * pango2_renderer_draw_rectangle:
+ * @renderer: a `Pango2Renderer`
+ * @part: type of object this rectangle is part of
+ * @x: X position at which to draw rectangle, in user space coordinates
+ * in Pango2 units
+ * @y: Y position at which to draw rectangle, in user space coordinates
+ * in Pango2 units
+ * @width: width of rectangle in Pango2 units
+ * @height: height of rectangle in Pango2 units
+ *
+ * Draws an axis-aligned rectangle in user space coordinates with the
+ * specified `Pango2Renderer`.
+ *
+ * This should be called while @renderer is already active.
+ * Use [method@Pango2.Renderer.activate] to activate a renderer.
+ */
+void
+pango2_renderer_draw_rectangle (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+ g_return_if_fail (IS_VALID_PART (part));
+ g_return_if_fail (priv->active_count > 0);
+
+ PANGO2_RENDERER_GET_CLASS (renderer)->draw_rectangle (renderer, part, x, y, width, height);
+}
+
+static int
+compare_points (const void *a,
+ const void *b)
+{
+ const Point *pa = a;
+ const Point *pb = b;
+
+ if (pa->y < pb->y)
+ return -1;
+ else if (pa->y > pb->y)
+ return 1;
+ else if (pa->x < pb->x)
+ return -1;
+ else if (pa->x > pb->x)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+draw_rectangle (Pango2Renderer *renderer,
+ Pango2Matrix *matrix,
+ Pango2RenderPart part,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ Point points[4];
+
+ /* Convert the points to device coordinates, and sort
+ * in ascending Y order. (Ordering by X for ties)
+ */
+ to_device (matrix, x, y, &points[0]);
+ to_device (matrix, x + width, y, &points[1]);
+ to_device (matrix, x, y + height, &points[2]);
+ to_device (matrix, x + width, y + height, &points[3]);
+
+ qsort (points, 4, sizeof (Point), compare_points);
+
+ /* There are essentially three cases. (There is a fourth
+ * case where trapezoid B is degenerate and we just have
+ * two triangles, but we don't need to handle it separately.)
+ *
+ * 1 2 3
+ *
+ * ______ /\ /\
+ * / / /A \ /A \
+ * / B / /____\ /____\
+ * /_____/ / B / \ B \
+ * /_____/ \_____\
+ * \ C / \ C /
+ * \ / \ /
+ * \/ \/
+ */
+ if (points[0].y == points[1].y)
+ {
+ /* Case 1 (pure shear) */
+ pango2_renderer_draw_trapezoid (renderer, part, /* B */
+ points[0].y, points[0].x, points[1].x,
+ points[2].y, points[2].x, points[3].x);
+ }
+ else if (points[1].x < points[2].x)
+ {
+ /* Case 2 */
+ double tmp_width = ((points[2].x - points[0].x) * (points[1].y - points[0].y)) / (points[2].y - points[0].y);
+ double base_width = tmp_width + points[0].x - points[1].x;
+
+ pango2_renderer_draw_trapezoid (renderer, part, /* A */
+ points[0].y, points[0].x, points[0].x,
+ points[1].y, points[1].x, points[1].x + base_width);
+ pango2_renderer_draw_trapezoid (renderer, part, /* B */
+ points[1].y, points[1].x, points[1].x + base_width,
+ points[2].y, points[2].x - base_width, points[2].x);
+ pango2_renderer_draw_trapezoid (renderer, part, /* C */
+ points[2].y, points[2].x - base_width, points[2].x,
+ points[3].y, points[3].x, points[3].x);
+ }
+ else
+ {
+ /* case 3 */
+ double tmp_width = ((points[0].x - points[2].x) * (points[1].y - points[0].y)) / (points[2].y - points[0].y);
+ double base_width = tmp_width + points[1].x - points[0].x;
+
+ pango2_renderer_draw_trapezoid (renderer, part, /* A */
+ points[0].y, points[0].x, points[0].x,
+ points[1].y, points[1].x - base_width, points[1].x);
+ pango2_renderer_draw_trapezoid (renderer, part, /* B */
+ points[1].y, points[1].x - base_width, points[1].x,
+ points[2].y, points[2].x, points[2].x + base_width);
+ pango2_renderer_draw_trapezoid (renderer, part, /* C */
+ points[2].y, points[2].x, points[2].x + base_width,
+ points[3].y, points[3].x, points[3].x);
+ }
+}
+
+static void
+pango2_renderer_default_draw_rectangle (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ draw_rectangle (renderer, priv->matrix, part, x, y, width, height);
+}
+
+/**
+ * pango2_renderer_draw_styled_line:
+ * @renderer: a `Pango2Renderer`
+ * @part: type of object this rectangle is part of
+ * @style: the line style
+ * @x: X coordinate of line, in Pango2 units in user coordinate system
+ * @y: Y coordinate of line, in Pango2 units in user coordinate system
+ * @width: width of line, in Pango2 units in user coordinate system
+ * @height: height of line, in Pango2 units in user coordinate system
+ *
+ * Draw a line in the given style.
+ *
+ * For `PANGO2_LINE_STYLE_WAVY`, this should draw a squiggly line that
+ * approximately covers the given rectangle in the style of an underline
+ * used to indicate a spelling error.
+ *
+ * The width of the underline is rounded to an integer number
+ * of up/down segments and the resulting rectangle is centered
+ * in the original rectangle.
+ *
+ * This should be called while @renderer is already active.
+ * Use [method@Pango2.Renderer.activate] to activate a renderer.
+ */
+void
+pango2_renderer_draw_styled_line (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ Pango2LineStyle style,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+ g_return_if_fail (IS_VALID_PART (part));
+ g_return_if_fail (priv->active_count > 0);
+
+ PANGO2_RENDERER_GET_CLASS (renderer)->draw_styled_line (renderer, part, style, x, y, width, height);
+}
+
+/* We are drawing an error underline that looks like one of:
+ *
+ * /\ /\ /\ /\ /\ -
+ * / \ / \ / \ / \ / \ |
+ * \ \ /\ \ / / \ \ /\ \ |
+ * \ \/B \ \/ C / \ \/B \ \ | height = HEIGHT_SQUARES * square
+ * \ A \ /\ A \ / \ A \ /\ A \ |
+ * \ \/ \ \/ \ \/ \ \ |
+ * \ / \ / \ / \ / |
+ * \/ \/ \/ \/ -
+ * |---|
+ * unit_width = (HEIGHT_SQUARES - 1) * square
+ *
+ * To do this conveniently, we work in a coordinate system where A,B,C
+ * are axis aligned rectangles. (If fonts were square, the diagrams
+ * would be clearer)
+ *
+ * (0,0)
+ * /\ /\
+ * / \ / \
+ * /\ /\ /\ /
+ * / \/ \/ \/
+ * / \ /\ /
+ * Y axis \/ \/
+ * \ /\
+ * \/ \
+ * \ X axis
+ *
+ * Note that the long side in this coordinate system is HEIGHT_SQUARES + 1
+ * units long
+ *
+ * The diagrams above are shown with HEIGHT_SQUARES an integer, but
+ * that is actually incidental; the value 2.5 below seems better than
+ * either HEIGHT_SQUARES=3 (a little long and skinny) or
+ * HEIGHT_SQUARES=2 (a bit short and stubby)
+ */
+
+#define HEIGHT_SQUARES 2.5
+
+static void
+get_total_matrix (Pango2Matrix *total,
+ const Pango2Matrix *global,
+ int x,
+ int y,
+ int square)
+{
+ Pango2Matrix local;
+ double scale = 0.5 * square;
+
+ /* The local matrix translates from the axis aligned coordinate system
+ * to the original user space coordinate system.
+ */
+ local.xx = scale;
+ local.xy = - scale;
+ local.yx = scale;
+ local.yy = scale;
+ local.x0 = 0;
+ local.y0 = 0;
+
+ *total = *global;
+ pango2_matrix_concat (total, &local);
+
+ total->x0 = (global->xx * x + global->xy * y) / PANGO2_SCALE + global->x0;
+ total->y0 = (global->yx * x + global->yy * y) / PANGO2_SCALE + global->y0;
+}
+
+static void
+pango2_renderer_default_draw_styled_line (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ Pango2LineStyle style,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+ int square;
+ int unit_width;
+ int width_units;
+ const Pango2Matrix identity = PANGO2_MATRIX_INIT;
+ const Pango2Matrix *matrix;
+ double dx, dx0, dy0;
+ Pango2Matrix total;
+ int i;
+
+ if (width <= 0 || height <= 0)
+ return;
+
+ if (style != PANGO2_LINE_STYLE_WAVY)
+ {
+ /* subclasses can do better */
+ pango2_renderer_draw_rectangle (renderer, part, x, y, width, height);
+ return;
+ }
+
+ square = height / HEIGHT_SQUARES;
+ unit_width = (HEIGHT_SQUARES - 1) * square;
+ width_units = (width + unit_width / 2) / unit_width;
+
+ x += (width - width_units * unit_width) / 2;
+
+ if (priv->matrix)
+ matrix = priv->matrix;
+ else
+ matrix = &identity;
+
+ get_total_matrix (&total, matrix, x, y, square);
+ dx = unit_width * 2;
+ dx0 = (matrix->xx * dx) / PANGO2_SCALE;
+ dy0 = (matrix->yx * dx) / PANGO2_SCALE;
+
+ i = (width_units - 1) / 2;
+ while (TRUE)
+ {
+ draw_rectangle (renderer, &total, PANGO2_RENDER_PART_UNDERLINE, /* A */
+ 0, 0,
+ HEIGHT_SQUARES * 2 - 1, 1);
+
+ if (i <= 0)
+ break;
+ i--;
+
+ draw_rectangle (renderer, &total, PANGO2_RENDER_PART_UNDERLINE, /* B */
+ HEIGHT_SQUARES * 2 - 2, - (HEIGHT_SQUARES * 2 - 3),
+ 1, HEIGHT_SQUARES * 2 - 3);
+
+ total.x0 += dx0;
+ total.y0 += dy0;
+ }
+ if (width_units % 2 == 0)
+ {
+ draw_rectangle (renderer, &total, PANGO2_RENDER_PART_UNDERLINE, /* C */
+ HEIGHT_SQUARES * 2 - 2, - (HEIGHT_SQUARES * 2 - 2),
+ 1, HEIGHT_SQUARES * 2 - 2);
+ }
+}
+
+/**
+ * pango2_renderer_draw_trapezoid:
+ * @renderer: a `Pango2Renderer`
+ * @part: type of object this trapezoid is part of
+ * @y1_: Y coordinate of top of trapezoid
+ * @x11: X coordinate of left end of top of trapezoid
+ * @x21: X coordinate of right end of top of trapezoid
+ * @y2: Y coordinate of bottom of trapezoid
+ * @x12: X coordinate of left end of bottom of trapezoid
+ * @x22: X coordinate of right end of bottom of trapezoid
+ *
+ * Draws a trapezoid with the parallel sides aligned with the X axis
+ * using the given `Pango2Renderer`; coordinates are in device space.
+ */
+void
+pango2_renderer_draw_trapezoid (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ double y1_,
+ double x11,
+ double x21,
+ double y2,
+ double x12,
+ double x22)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+ g_return_if_fail (priv->active_count > 0);
+
+ if (PANGO2_RENDERER_GET_CLASS (renderer)->draw_trapezoid)
+ PANGO2_RENDERER_GET_CLASS (renderer)->draw_trapezoid (renderer, part,
+ y1_, x11, x21,
+ y2, x12, x22);
+}
+
+/**
+ * pango2_renderer_draw_glyph:
+ * @renderer: a `Pango2Renderer`
+ * @font: a `Pango2Font`
+ * @glyph: the glyph index of a single glyph
+ * @x: X coordinate of left edge of baseline of glyph
+ * @y: Y coordinate of left edge of baseline of glyph
+ *
+ * Draws a single glyph with coordinates in device space.
+ */
+void
+pango2_renderer_draw_glyph (Pango2Renderer *renderer,
+ Pango2Font *font,
+ Pango2Glyph glyph,
+ double x,
+ double y)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+ g_return_if_fail (priv->active_count > 0);
+
+ if (glyph == PANGO2_GLYPH_EMPTY) /* glyph PANGO2_GLYPH_EMPTY never renders */
+ return;
+
+ if (PANGO2_RENDERER_GET_CLASS (renderer)->draw_glyph)
+ PANGO2_RENDERER_GET_CLASS (renderer)->draw_glyph (renderer, font, glyph, x, y);
+}
+
+/**
+ * pango2_renderer_activate:
+ * @renderer: a `Pango2Renderer`
+ *
+ * Does initial setup before rendering operations on @renderer.
+ *
+ * [method@Pango2.Renderer.deactivate] should be called when done drawing.
+ * Calls such as [method@Pango2.Renderer.draw_lines] automatically
+ * activate the layout before drawing on it.
+ *
+ * Calls to [method@Pango2.Renderer.activate] and
+ * [method@Pango2.Renderer.deactivate] can be nested and the
+ * renderer will only be initialized and deinitialized once.
+ */
+void
+pango2_renderer_activate (Pango2Renderer *renderer)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+
+ priv->active_count++;
+ if (priv->active_count == 1)
+ {
+ if (PANGO2_RENDERER_GET_CLASS (renderer)->begin)
+ PANGO2_RENDERER_GET_CLASS (renderer)->begin (renderer);
+ }
+}
+
+/**
+ * pango2_renderer_deactivate:
+ * @renderer: a `Pango2Renderer`
+ *
+ * Cleans up after rendering operations on @renderer.
+ *
+ * See docs for [method@Pango2.Renderer.activate].
+ */
+void
+pango2_renderer_deactivate (Pango2Renderer *renderer)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+ g_return_if_fail (priv->active_count > 0);
+
+ if (priv->active_count == 1)
+ {
+ if (PANGO2_RENDERER_GET_CLASS (renderer)->end)
+ PANGO2_RENDERER_GET_CLASS (renderer)->end (renderer);
+ }
+ priv->active_count--;
+}
+
+/**
+ * pango2_renderer_set_color:
+ * @renderer: a `Pango2Renderer`
+ * @part: the part to change the color of
+ * @color: (nullable): the new color or %NULL to unset the current color
+ *
+ * Sets the color for part of the rendering.
+ */
+void
+pango2_renderer_set_color (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ const Pango2Color *color)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+ g_return_if_fail (IS_VALID_PART (part));
+
+ if ((!color && !priv->color_set[part]) ||
+ (color && priv->color_set[part] &&
+ pango2_color_equal (color, &priv->color[part])))
+ return;
+
+ pango2_renderer_part_changed (renderer, part);
+
+ if (color)
+ {
+ priv->color_set[part] = TRUE;
+ priv->color[part] = *color;
+ }
+ else
+ {
+ priv->color_set[part] = FALSE;
+ }
+}
+
+/**
+ * pango2_renderer_get_color:
+ * @renderer: a `Pango2Renderer`
+ * @part: the part to get the color for
+ *
+ * Gets the current rendering color for the specified part.
+ *
+ * Return value: (transfer none) (nullable): the color for the
+ * specified part, or %NULL if it hasn't been set and should be
+ * inherited from the environment.
+ */
+Pango2Color *
+pango2_renderer_get_color (Pango2Renderer *renderer,
+ Pango2RenderPart part)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ g_return_val_if_fail (PANGO2_IS_RENDERER (renderer), NULL);
+ g_return_val_if_fail (IS_VALID_PART (part), NULL);
+
+ if (priv->color_set[part])
+ return &priv->color[part];
+ else
+ return NULL;
+}
+
+/**
+ * pango2_renderer_part_changed:
+ * @renderer: a `Pango2Renderer`
+ * @part: the part for which rendering has changed.
+ *
+ * Informs Pango2 that the way that the rendering is done
+ * for @part has changed.
+ *
+ * This should be called if the rendering changes in a way that would
+ * prevent multiple pieces being joined together into one drawing call.
+ * For instance, if a subclass of `Pango2Renderer` was to add a stipple
+ * option for drawing underlines, it needs to call
+ *
+ * ```
+ * pango2_renderer_part_changed (render, PANGO2_RENDER_PART_UNDERLINE);
+ * ```
+ *
+ * When the stipple changes or underlines with different stipples
+ * might be joined together. Pango2 automatically calls this for
+ * changes to colors. (See [method@Pango2.Renderer.set_color])
+ */
+void
+pango2_renderer_part_changed (Pango2Renderer *renderer,
+ Pango2RenderPart part)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+ g_return_if_fail (IS_VALID_PART (part));
+ g_return_if_fail (priv->active_count > 0);
+
+ handle_line_state_change (renderer, part);
+
+ if (PANGO2_RENDERER_GET_CLASS (renderer)->part_changed)
+ PANGO2_RENDERER_GET_CLASS (renderer)->part_changed (renderer, part);
+}
+
+/**
+ * pango2_renderer_prepare_run:
+ * @renderer: a `Pango2Renderer`
+ * @run: a `Pango2Run`
+ *
+ * Set up the state of the `Pango2Renderer` for rendering @run.
+ */
+static void
+pango2_renderer_prepare_run (Pango2Renderer *renderer,
+ Pango2Run *run)
+{
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+
+ PANGO2_RENDERER_GET_CLASS (renderer)->prepare_run (renderer, run);
+}
+
+static void
+pango2_renderer_default_prepare_run (Pango2Renderer *renderer,
+ Pango2Run *run)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+ Pango2Color *fg_color = NULL;
+ Pango2Color *bg_color = NULL;
+ Pango2Color *underline_color = NULL;
+ Pango2Color *overline_color = NULL;
+ Pango2Color *strikethrough_color = NULL;
+ GSList *l;
+ Pango2GlyphItem *glyph_item;
+
+ glyph_item = pango2_run_get_glyph_item (run);
+
+ priv->underline = PANGO2_LINE_STYLE_NONE;
+ priv->underline_position = PANGO2_UNDERLINE_POSITION_NORMAL;
+ priv->overline = PANGO2_LINE_STYLE_NONE;
+ priv->strikethrough = PANGO2_LINE_STYLE_NONE;
+
+ for (l = glyph_item->item->analysis.extra_attrs; l; l = l->next)
+ {
+ Pango2Attribute *attr = l->data;
+
+ switch ((int) attr->type)
+ {
+ case PANGO2_ATTR_UNDERLINE:
+ priv->underline = attr->int_value;
+ break;
+
+ case PANGO2_ATTR_UNDERLINE_POSITION:
+ priv->underline_position = attr->int_value;
+ break;
+
+ case PANGO2_ATTR_OVERLINE:
+ priv->overline = attr->int_value;
+ break;
+
+ case PANGO2_ATTR_STRIKETHROUGH:
+ priv->strikethrough = attr->int_value;
+ break;
+
+ case PANGO2_ATTR_FOREGROUND:
+ fg_color = &attr->color_value;
+ break;
+
+ case PANGO2_ATTR_BACKGROUND:
+ bg_color = &attr->color_value;
+ break;
+
+ case PANGO2_ATTR_UNDERLINE_COLOR:
+ underline_color = &attr->color_value;
+ break;
+
+ case PANGO2_ATTR_OVERLINE_COLOR:
+ overline_color = &attr->color_value;
+ break;
+
+ case PANGO2_ATTR_STRIKETHROUGH_COLOR:
+ strikethrough_color = &attr->color_value;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (!underline_color)
+ underline_color = fg_color;
+
+ if (!overline_color)
+ overline_color = fg_color;
+
+ if (!strikethrough_color)
+ strikethrough_color = fg_color;
+
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_FOREGROUND, fg_color);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_BACKGROUND, bg_color);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_UNDERLINE, underline_color);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_STRIKETHROUGH, strikethrough_color);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_OVERLINE, overline_color);
+}
+
+/**
+ * pango2_renderer_set_matrix:
+ * @renderer: a `Pango2Renderer`
+ * @matrix: (nullable): a `Pango2Matrix`, or %NULL to unset any existing matrix
+ * (No matrix set is the same as setting the identity matrix.)
+ *
+ * Sets the transformation matrix that will be applied when rendering.
+ */
+void
+pango2_renderer_set_matrix (Pango2Renderer *renderer,
+ const Pango2Matrix *matrix)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ g_return_if_fail (PANGO2_IS_RENDERER (renderer));
+
+ pango2_matrix_free (priv->matrix);
+ priv->matrix = pango2_matrix_copy (matrix);
+}
+
+/**
+ * pango2_renderer_get_matrix:
+ * @renderer: a `Pango2Renderer`
+ *
+ * Gets the transformation matrix that will be applied when
+ * rendering.
+ *
+ * See [method@Pango2.Renderer.set_matrix].
+ *
+ * Return value: (nullable): the matrix, or %NULL if no matrix has
+ * been set (which is the same as the identity matrix). The returned
+ * matrix is owned by Pango2 and must not be modified or freed.
+ */
+const Pango2Matrix *
+pango2_renderer_get_matrix (Pango2Renderer *renderer)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ g_return_val_if_fail (PANGO2_IS_RENDERER (renderer), NULL);
+
+ return priv->matrix;
+}
+
+/**
+ * pango2_renderer_get_lines:
+ * @renderer: a `Pango2Renderer`
+ *
+ * Gets the `Pango2Lines` currently being rendered using @renderer.
+ *
+ * Calling this function only makes sense from inside a subclass's
+ * methods, like in its draw_shape vfunc, for example.
+ *
+ * The returned layout should not be modified while still being
+ * rendered.
+ *
+ * Return value: (transfer none) (nullable): the `Pango2Lines`, or
+ * %NULL if no layout is being rendered using @renderer at this time.
+ */
+Pango2Lines *
+pango2_renderer_get_lines (Pango2Renderer *renderer)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ return priv->lines;
+}
+
+/**
+ * pango2_renderer_get_layout_line:
+ * @renderer: a `Pango2Renderer`
+ *
+ * Gets the line currently being rendered using @renderer.
+ *
+ * Calling this function only makes sense from inside a subclass's
+ * methods, like in its draw_shape vfunc, for example.
+ *
+ * The returned line should not be modified while still being
+ * rendered.
+ *
+ * Return value: (transfer none) (nullable): the line, or %NULL
+ * if no layout line is being rendered using @renderer at this time.
+ */
+Pango2Line *
+pango2_renderer_get_layout_line (Pango2Renderer *renderer)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ return priv->line;
+}
+
+/**
+ * pango2_renderer_get_context:
+ * @renderer: a `Pango2Renderer`
+ *
+ * Gets the current context in which @renderer operates.
+ *
+ * Returns: (nullable) (transfer none): the `Pango2Context`
+ */
+Pango2Context *
+pango2_renderer_get_context (Pango2Renderer *renderer)
+{
+ Pango2RendererPrivate *priv = pango2_renderer_get_instance_private (renderer);
+
+ return priv->context;
+}
diff --git a/pango2/pango-renderer.h b/pango2/pango-renderer.h
new file mode 100644
index 00000000..e641bb3e
--- /dev/null
+++ b/pango2/pango-renderer.h
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2004, Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-layout.h>
+#include <pango2/pango-lines.h>
+#include <pango2/pango-glyph.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_RENDERER (pango2_renderer_get_type())
+#define PANGO2_RENDERER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), PANGO2_TYPE_RENDERER, Pango2Renderer))
+#define PANGO2_IS_RENDERER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), PANGO2_TYPE_RENDERER))
+#define PANGO2_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO2_TYPE_RENDERER, Pango2RendererClass))
+#define PANGO2_IS_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO2_TYPE_RENDERER))
+#define PANGO2_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO2_TYPE_RENDERER, Pango2RendererClass))
+
+typedef struct _Pango2Renderer Pango2Renderer;
+typedef struct _Pango2RendererClass Pango2RendererClass;
+
+/**
+ * Pango2RenderPart:
+ * @PANGO2_RENDER_PART_FOREGROUND: the text itself
+ * @PANGO2_RENDER_PART_BACKGROUND: the area behind the text
+ * @PANGO2_RENDER_PART_UNDERLINE: underlines
+ * @PANGO2_RENDER_PART_STRIKETHROUGH: strikethrough lines
+ * @PANGO2_RENDER_PART_OVERLINE: overlines
+ *
+ * `Pango2RenderPart` defines different items to render for such
+ * purposes as setting colors.
+ */
+/* When extending, note N_RENDER_PARTS #define in pango-renderer.c */
+typedef enum
+{
+ PANGO2_RENDER_PART_FOREGROUND,
+ PANGO2_RENDER_PART_BACKGROUND,
+ PANGO2_RENDER_PART_UNDERLINE,
+ PANGO2_RENDER_PART_STRIKETHROUGH,
+ PANGO2_RENDER_PART_OVERLINE
+} Pango2RenderPart;
+
+/**
+ * Pango2Renderer:
+ *
+ * `Pango2Renderer` is a base class for objects that can render text
+ * provided as `Pango2GlyphString` or `Pango2Layout`.
+ *
+ * By subclassing `Pango2Renderer` and overriding operations such as
+ * @draw_glyphs and @draw_rectangle, renderers for particular font
+ * backends and destinations can be created.
+ */
+struct _Pango2Renderer
+{
+ GObject parent_instance;
+};
+
+/**
+ * Pango2RendererClass:
+ * @draw_glyphs: draws a `Pango2GlyphString`
+ * @draw_rectangle: draws a rectangle
+ * @draw_line: draws a line in the given style that approximately
+ * covers the given rectangle
+ * @draw_trapezoid: draws a trapezoidal filled area
+ * @draw_glyph: draws a single glyph
+ * @part_changed: do renderer specific processing when rendering
+ * attributes change
+ * @begin: Do renderer-specific initialization before drawing
+ * @end: Do renderer-specific cleanup after drawing
+ * @prepare_run: updates the renderer for a new run
+ * @draw_glyph_item: draws a `Pango2GlyphItem`
+ *
+ * Class structure for `Pango2Renderer`.
+ *
+ * The following vfuncs take user space coordinates in Pango2 units
+ * and have default implementations:
+ * - draw_glyphs
+ * - draw_rectangle
+ * - draw_styled_line
+ * - draw_shape
+ * - draw_glyph_item
+ *
+ * The default draw_shape implementation draws nothing.
+ *
+ * The following vfuncs take device space coordinates as doubles
+ * and must be implemented:
+ * - draw_trapezoid
+ * - draw_glyph
+ */
+struct _Pango2RendererClass
+{
+ /*< private >*/
+ GObjectClass parent_class;
+
+ /*< public >*/
+
+ void (* draw_glyphs) (Pango2Renderer *renderer,
+ Pango2Font *font,
+ Pango2GlyphString *glyphs,
+ int x,
+ int y);
+ void (* draw_rectangle) (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ int x,
+ int y,
+ int width,
+ int height);
+ void (* draw_styled_line) (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ Pango2LineStyle style,
+ int x,
+ int y,
+ int width,
+ int height);
+ void (* draw_line) (Pango2Renderer *renderer,
+ Pango2LineStyle style,
+ int x,
+ int y,
+ int width,
+ int height);
+ void (* draw_shape) (Pango2Renderer *renderer,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect,
+ gpointer data,
+ int x,
+ int y);
+ void (* draw_trapezoid) (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ double y1_,
+ double x11,
+ double x21,
+ double y2,
+ double x12,
+ double x22);
+ void (* draw_glyph) (Pango2Renderer *renderer,
+ Pango2Font *font,
+ Pango2Glyph glyph,
+ double x,
+ double y);
+
+ void (* part_changed) (Pango2Renderer *renderer,
+ Pango2RenderPart part);
+
+ void (* begin) (Pango2Renderer *renderer);
+ void (* end) (Pango2Renderer *renderer);
+
+ void (* prepare_run) (Pango2Renderer *renderer,
+ Pango2Run *run);
+
+ void (* draw_run) (Pango2Renderer *renderer,
+ const char *text,
+ Pango2Run *run,
+ int x,
+ int y);
+
+ /*< private >*/
+
+ /* Padding for future expansion */
+ gpointer _pango2_reserved[8];
+};
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_renderer_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_draw_lines (Pango2Renderer *renderer,
+ Pango2Lines *lines,
+ int x,
+ int y);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_draw_line (Pango2Renderer *renderer,
+ Pango2Line *line,
+ int x,
+ int y);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_draw_glyphs (Pango2Renderer *renderer,
+ Pango2Font *font,
+ Pango2GlyphString *glyphs,
+ int x,
+ int y);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_draw_run (Pango2Renderer *renderer,
+ const char *text,
+ Pango2Run *run,
+ int x,
+ int y);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_draw_rectangle (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ int x,
+ int y,
+ int width,
+ int height);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_draw_styled_line (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ Pango2LineStyle style,
+ int x,
+ int y,
+ int width,
+ int height);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_draw_error_underline (Pango2Renderer *renderer,
+ int x,
+ int y,
+ int width,
+ int height);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_draw_trapezoid (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ double y1_,
+ double x11,
+ double x21,
+ double y2,
+ double x12,
+ double x22);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_draw_glyph (Pango2Renderer *renderer,
+ Pango2Font *font,
+ Pango2Glyph glyph,
+ double x,
+ double y);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_activate (Pango2Renderer *renderer);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_deactivate (Pango2Renderer *renderer);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_part_changed (Pango2Renderer *renderer,
+ Pango2RenderPart part);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_set_color (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ const Pango2Color *color);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Color * pango2_renderer_get_color (Pango2Renderer *renderer,
+ Pango2RenderPart part);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_renderer_set_matrix (Pango2Renderer *renderer,
+ const Pango2Matrix *matrix);
+PANGO2_AVAILABLE_IN_ALL
+const Pango2Matrix * pango2_renderer_get_matrix (Pango2Renderer *renderer);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Lines * pango2_renderer_get_lines (Pango2Renderer *renderer);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Line * pango2_renderer_get_layout_line (Pango2Renderer *renderer);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Context * pango2_renderer_get_context (Pango2Renderer *renderer);
+
+G_END_DECLS
diff --git a/pango2/pango-run-private.h b/pango2/pango-run-private.h
new file mode 100644
index 00000000..2e84bdfe
--- /dev/null
+++ b/pango2/pango-run-private.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "config.h"
+
+#include "pango-run.h"
+#include "pango-glyph-item-private.h"
+#include "pango-item-private.h"
+
+
+struct _Pango2Run
+{
+ Pango2GlyphItem glyph_item;
+};
+
+static inline Pango2GlyphItem *
+pango2_run_get_glyph_item (Pango2Run *run)
+{
+ return &run->glyph_item;
+}
diff --git a/pango2/pango-run.c b/pango2/pango-run.c
new file mode 100644
index 00000000..8c36f138
--- /dev/null
+++ b/pango2/pango-run.c
@@ -0,0 +1,271 @@
+#include "config.h"
+
+#include "pango-run-private.h"
+#include "pango-attr-private.h"
+#include "pango-item-private.h"
+#include "pango-impl-utils.h"
+#include "pango-font-metrics-private.h"
+#include "pango-attributes-private.h"
+
+#include <math.h>
+
+/**
+ * Pango2Run:
+ *
+ * A `Pango2Run` represents a single run within a `Pango2Line`.
+ *
+ * A run is a range of text with uniform script, font and attributes that
+ * is shaped as a unit.
+ *
+ * Script, font and attributes of a run can be accessed via
+ * [method@Pango2.Run.get_item]. The glyphs that result from shaping
+ * the text of the run can be obtained via [method@Pango2.Run.get_glyphs].
+ */
+
+/**
+ * pango2_run_get_item:
+ * @run: a `Pango2Run`
+ *
+ * Gets the `Pango2Item` for the run.
+ *
+ * Returns: (transfer none): the `Pango2Item` of @run
+ */
+Pango2Item *
+pango2_run_get_item (Pango2Run *run)
+{
+ return run->glyph_item.item;
+}
+
+/**
+ * pango2_run_get_glyphs:
+ * @run: a `Pango2Run`
+ *
+ * Gets the `Pango2GlyphString` for the run.
+ *
+ * Returns: (transfer none): the `Pango2GlyphString` of @run
+ */
+Pango2GlyphString *
+pango2_run_get_glyphs (Pango2Run *run)
+{
+ return run->glyph_item.glyphs;
+}
+
+static void
+pango2_shape_get_extents (int n_chars,
+ Pango2Attribute *attr,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ if (n_chars > 0)
+ {
+ ShapeData *data = (ShapeData *)attr->pointer_value;
+ Pango2Rectangle *shape_ink = &data->ink_rect;
+ Pango2Rectangle *shape_logical = &data->logical_rect;
+
+ if (ink_rect)
+ {
+ ink_rect->x = MIN (shape_ink->x, shape_ink->x + shape_logical->width * (n_chars - 1));
+ ink_rect->width = MAX (shape_ink->width, shape_ink->width + shape_logical->width * (n_chars - 1));
+ ink_rect->y = shape_ink->y;
+ ink_rect->height = shape_ink->height;
+ }
+
+ if (logical_rect)
+ {
+ logical_rect->x = MIN (shape_logical->x, shape_logical->x + shape_logical->width * (n_chars - 1));
+ logical_rect->width = MAX (shape_logical->width, shape_logical->width + shape_logical->width * (n_chars - 1));
+ logical_rect->y = shape_logical->y;
+ logical_rect->height = shape_logical->height;
+ }
+ }
+ else
+ {
+ 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;
+ }
+ }
+}
+
+/**
+ * pango2_run_get_extents:
+ * @run: a `Pango2Run`
+ * @trim: `Pango2LeadingTrim` flags
+ * @ink_rect: (out caller-allocates) (optional): return location
+ * for the ink extents
+ * @logical_rect: (out caller-allocates) (optional): return location
+ * for the logical extents
+ *
+ * Gets the extents of a `Pango2Run`.
+ *
+ * The @trim flags specify if line-height attributes are taken
+ * into consideration for determining the logical height. See the
+ * [CSS inline layout](https://www.w3.org/TR/css-inline-3/#inline-height)
+ * specification for details.
+ */
+void
+pango2_run_get_extents (Pango2Run *run,
+ Pango2LeadingTrim trim,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ Pango2GlyphItem *glyph_item = &run->glyph_item;
+ ItemProperties properties;
+ gboolean has_underline;
+ gboolean has_overline;
+ gboolean has_strikethrough;
+ Pango2Rectangle logical;
+ Pango2FontMetrics *metrics = NULL;
+ int y_offset;
+
+ pango2_item_get_properties (glyph_item->item, &properties);
+
+ has_underline = properties.uline_style != PANGO2_LINE_STYLE_NONE;
+ has_overline = properties.oline_style != PANGO2_LINE_STYLE_NONE;
+ has_strikethrough = properties.strikethrough_style != PANGO2_LINE_STYLE_NONE;
+
+ if (!logical_rect && (glyph_item->item->analysis.flags & PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE))
+ logical_rect = &logical;
+
+ if (!logical_rect && (has_underline || has_overline || has_strikethrough))
+ logical_rect = &logical;
+
+ if (properties.shape)
+ pango2_shape_get_extents (glyph_item->item->num_chars, properties.shape,
+ ink_rect, logical_rect);
+ else
+ pango2_glyph_string_extents (glyph_item->glyphs, glyph_item->item->analysis.font,
+ ink_rect, logical_rect);
+
+ if (ink_rect && (has_underline || has_overline || has_strikethrough))
+ {
+ int underline_thickness;
+ int underline_position;
+ int strikethrough_thickness;
+ int strikethrough_position;
+ int new_pos;
+
+ if (!metrics)
+ metrics = pango2_font_get_metrics (glyph_item->item->analysis.font,
+ glyph_item->item->analysis.language);
+
+ underline_thickness = pango2_font_metrics_get_underline_thickness (metrics);
+ underline_position = pango2_font_metrics_get_underline_position (metrics);
+ strikethrough_thickness = pango2_font_metrics_get_strikethrough_thickness (metrics);
+ strikethrough_position = pango2_font_metrics_get_strikethrough_position (metrics);
+
+ /* the underline/strikethrough takes x, width of logical_rect.
+ * Reflect that into ink_rect.
+ */
+ new_pos = MIN (ink_rect->x, logical_rect->x);
+ ink_rect->width = MAX (ink_rect->x + ink_rect->width, logical_rect->x + logical_rect->width) - new_pos;
+ ink_rect->x = new_pos;
+
+ /* We should better handle the case of height==0 in the following cases.
+ * If ink_rect->height == 0, we should adjust ink_rect->y appropriately.
+ */
+
+ if (has_strikethrough)
+ {
+ if (ink_rect->height == 0)
+ {
+ ink_rect->height = strikethrough_thickness;
+ ink_rect->y = -strikethrough_position;
+ }
+ }
+
+ if (properties.oline_style == PANGO2_LINE_STYLE_SOLID ||
+ properties.oline_style == PANGO2_LINE_STYLE_DASHED ||
+ properties.oline_style == PANGO2_LINE_STYLE_DOTTED)
+ {
+ ink_rect->y -= underline_thickness;
+ ink_rect->height += underline_thickness;
+ }
+ else if (properties.oline_style == PANGO2_LINE_STYLE_DOUBLE ||
+ properties.oline_style == PANGO2_LINE_STYLE_WAVY)
+ {
+ ink_rect->y -= 3 * underline_thickness;
+ ink_rect->height += 3 * underline_thickness;
+ }
+
+ if (properties.uline_style == PANGO2_LINE_STYLE_SOLID ||
+ properties.uline_style == PANGO2_LINE_STYLE_DASHED ||
+ properties.uline_style == PANGO2_LINE_STYLE_DOTTED)
+ {
+ if (properties.uline_position == PANGO2_UNDERLINE_POSITION_UNDER)
+ ink_rect->height += 2 * underline_thickness;
+ else
+ ink_rect->height = MAX (ink_rect->height,
+ underline_thickness - underline_position - ink_rect->y);
+ }
+ else if (properties.uline_style == PANGO2_LINE_STYLE_DOUBLE ||
+ properties.uline_style == PANGO2_LINE_STYLE_WAVY)
+ {
+ if (properties.uline_position == PANGO2_UNDERLINE_POSITION_UNDER)
+ ink_rect->height += 4 * underline_thickness;
+ else
+ ink_rect->height = MAX (ink_rect->height,
+ 3 * underline_thickness - underline_position - ink_rect->y);
+ }
+ }
+
+ y_offset = glyph_item->y_offset;
+
+ if (glyph_item->item->analysis.flags & PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE)
+ {
+ gboolean is_hinted = (logical_rect->y & logical_rect->height & (PANGO2_SCALE - 1)) == 0;
+ int adjustment = logical_rect->y + logical_rect->height / 2;
+
+ if (is_hinted)
+ adjustment = PANGO2_UNITS_ROUND (adjustment);
+
+ y_offset += adjustment;
+ }
+
+ if (ink_rect)
+ ink_rect->y -= y_offset;
+
+ if (logical_rect)
+ logical_rect->y -= y_offset;
+
+ if (logical_rect && trim != PANGO2_LEADING_TRIM_BOTH)
+ {
+ int leading;
+
+ if (properties.absolute_line_height != 0 || properties.line_height != 0.0)
+ {
+ int line_height;
+
+ line_height = MAX (properties.absolute_line_height, ceilf (properties.line_height * logical_rect->height));
+ leading = (line_height - logical_rect->height);
+ }
+ else
+ {
+ /* line-height 'normal' in the CSS inline layout spec */
+ if (!metrics)
+ metrics = pango2_font_get_metrics (glyph_item->item->analysis.font,
+ glyph_item->item->analysis.language);
+ leading = MAX (metrics->height - (metrics->ascent + metrics->descent) + properties.line_spacing, 0);
+ }
+ if ((trim & PANGO2_LEADING_TRIM_START) == 0)
+ logical_rect->y -= leading / 2;
+ if (trim == PANGO2_LEADING_TRIM_NONE)
+ logical_rect->height += leading;
+ else
+ logical_rect->height += (leading - leading / 2);
+ }
+
+ if (metrics)
+ pango2_font_metrics_free (metrics);
+}
diff --git a/pango2/pango-run.h b/pango2/pango-run.h
new file mode 100644
index 00000000..26760cb3
--- /dev/null
+++ b/pango2/pango-run.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <pango2/pango-types.h>
+#include <pango2/pango-item.h>
+#include <pango2/pango-glyph.h>
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Item * pango2_run_get_item (Pango2Run *run);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2GlyphString * pango2_run_get_glyphs (Pango2Run *run);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_run_get_extents (Pango2Run *run,
+ Pango2LeadingTrim trim,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect);
diff --git a/pango2/pango-script-lang-table.h b/pango2/pango-script-lang-table.h
new file mode 100644
index 00000000..1332b2d6
--- /dev/null
+++ b/pango2/pango-script-lang-table.h
@@ -0,0 +1,261 @@
+/* pango-script-lang-table.h:
+ *
+ * Generated by ../tools/gen-script-for-lang.c
+ * Date: 2019-06-06
+ * Source: fontconfig-2.13.1
+ *
+ * Do not edit.
+ */
+typedef struct _Pango2ScriptForLang {
+ const char lang[9];
+ GUnicodeScript scripts[3];
+} Pango2ScriptForLang;
+
+static const Pango2ScriptForLang pango2_script_for_lang[] = {
+ { "aa", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "ab", { G_UNICODE_SCRIPT_CYRILLIC/*90*/ } },
+ { "af", { G_UNICODE_SCRIPT_LATIN/*69*/ } },
+ { "ak", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "am", { G_UNICODE_SCRIPT_ETHIOPIC/*264*/ } },
+ { "an", { G_UNICODE_SCRIPT_LATIN/*66*/ } },
+ { "ar", { G_UNICODE_SCRIPT_ARABIC/*36*/ } },
+ { "as", { G_UNICODE_SCRIPT_BENGALI/*64*/ } },
+ { "ast", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "av", { G_UNICODE_SCRIPT_CYRILLIC/*67*/ } },
+ { "ay", { G_UNICODE_SCRIPT_LATIN/*60*/ } },
+ { "az-az", { G_UNICODE_SCRIPT_LATIN/*66*/ } },
+ { "az-ir", { G_UNICODE_SCRIPT_ARABIC/*38*/ } },
+ { "ba", { G_UNICODE_SCRIPT_CYRILLIC/*82*/ } },
+ { "be", { G_UNICODE_SCRIPT_CYRILLIC/*68*/ } },
+ { "ber-dz", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "ber-ma", { G_UNICODE_SCRIPT_TIFINAGH/*32*/ } },
+ { "bg", { G_UNICODE_SCRIPT_CYRILLIC/*60*/ } },
+ { "bh", { G_UNICODE_SCRIPT_DEVANAGARI/*68*/ } },
+ { "bho", { G_UNICODE_SCRIPT_DEVANAGARI/*68*/ } },
+ { "bi", { G_UNICODE_SCRIPT_LATIN/*58*/ } },
+ { "bin", { G_UNICODE_SCRIPT_LATIN/*76*/ } },
+ { "bm", { G_UNICODE_SCRIPT_LATIN/*60*/ } },
+ { "bn", { G_UNICODE_SCRIPT_BENGALI/*63*/ } },
+ { "bo", { G_UNICODE_SCRIPT_TIBETAN/*95*/ } },
+ { "br", { G_UNICODE_SCRIPT_LATIN/*64*/ } },
+ { "brx", { G_UNICODE_SCRIPT_DEVANAGARI/*78*/ } },
+ { "bs", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "bua", { G_UNICODE_SCRIPT_CYRILLIC/*70*/ } },
+ { "byn", { G_UNICODE_SCRIPT_ETHIOPIC/*255*/ } },
+ { "ca", { G_UNICODE_SCRIPT_LATIN/*74*/ } },
+ { "ce", { G_UNICODE_SCRIPT_CYRILLIC/*67*/ } },
+ { "ch", { G_UNICODE_SCRIPT_LATIN/*58*/ } },
+ { "chm", { G_UNICODE_SCRIPT_CYRILLIC/*76*/ } },
+ { "chr", { G_UNICODE_SCRIPT_CHEROKEE/*85*/ } },
+ { "co", { G_UNICODE_SCRIPT_LATIN/*84*/ } },
+ { "crh", { G_UNICODE_SCRIPT_LATIN/*68*/ } },
+ { "cs", { G_UNICODE_SCRIPT_LATIN/*82*/ } },
+ { "csb", { G_UNICODE_SCRIPT_LATIN/*74*/ } },
+ { "cu", { G_UNICODE_SCRIPT_CYRILLIC/*103*/ } },
+ { "cv", { G_UNICODE_SCRIPT_CYRILLIC/*72*/, G_UNICODE_SCRIPT_LATIN/*2*/ } },
+ { "cy", { G_UNICODE_SCRIPT_LATIN/*78*/ } },
+ { "da", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "de", { G_UNICODE_SCRIPT_LATIN/*59*/ } },
+ { "doi", { G_UNICODE_SCRIPT_DEVANAGARI/*81*/ } },
+ { "dv", { G_UNICODE_SCRIPT_THAANA/*49*/ } },
+ { "dz", { G_UNICODE_SCRIPT_TIBETAN/*95*/ } },
+ { "ee", { G_UNICODE_SCRIPT_LATIN/*96*/ } },
+ { "el", { G_UNICODE_SCRIPT_GREEK/*69*/ } },
+ { "en", { G_UNICODE_SCRIPT_LATIN/*72*/ } },
+ { "eo", { G_UNICODE_SCRIPT_LATIN/*64*/ } },
+ { "es", { G_UNICODE_SCRIPT_LATIN/*66*/ } },
+ { "et", { G_UNICODE_SCRIPT_LATIN/*64*/ } },
+ { "eu", { G_UNICODE_SCRIPT_LATIN/*56*/ } },
+ { "fa", { G_UNICODE_SCRIPT_ARABIC/*38*/ } },
+ { "fat", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "ff", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "fi", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "fil", { G_UNICODE_SCRIPT_LATIN/*84*/ } },
+ { "fj", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "fo", { G_UNICODE_SCRIPT_LATIN/*68*/ } },
+ { "fr", { G_UNICODE_SCRIPT_LATIN/*84*/ } },
+ { "fur", { G_UNICODE_SCRIPT_LATIN/*66*/ } },
+ { "fy", { G_UNICODE_SCRIPT_LATIN/*75*/ } },
+ { "ga", { G_UNICODE_SCRIPT_LATIN/*80*/ } },
+ { "gd", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "gez", { G_UNICODE_SCRIPT_ETHIOPIC/*218*/ } },
+ { "gl", { G_UNICODE_SCRIPT_LATIN/*66*/ } },
+ { "gn", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "gu", { G_UNICODE_SCRIPT_GUJARATI/*68*/ } },
+ { "gv", { G_UNICODE_SCRIPT_LATIN/*54*/ } },
+ { "ha", { G_UNICODE_SCRIPT_LATIN/*60*/ } },
+ { "haw", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "he", { G_UNICODE_SCRIPT_HEBREW/*27*/ } },
+ { "hi", { G_UNICODE_SCRIPT_DEVANAGARI/*68*/ } },
+ { "hne", { G_UNICODE_SCRIPT_DEVANAGARI/*68*/ } },
+ { "ho", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "hr", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "hsb", { G_UNICODE_SCRIPT_LATIN/*72*/ } },
+ { "ht", { G_UNICODE_SCRIPT_LATIN/*56*/ } },
+ { "hu", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "hy", { G_UNICODE_SCRIPT_ARMENIAN/*77*/ } },
+ { "hz", { G_UNICODE_SCRIPT_LATIN/*56*/ } },
+ { "ia", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "id", { G_UNICODE_SCRIPT_LATIN/*54*/ } },
+ { "ie", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "ig", { G_UNICODE_SCRIPT_LATIN/*58*/ } },
+ { "ii", { G_UNICODE_SCRIPT_YI/*1165*/ } },
+ { "ik", { G_UNICODE_SCRIPT_CYRILLIC/*68*/ } },
+ { "io", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "is", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "it", { G_UNICODE_SCRIPT_LATIN/*72*/ } },
+ { "iu", { G_UNICODE_SCRIPT_CANADIAN_ABORIGINAL/*161*/ } },
+ { "ja", { G_UNICODE_SCRIPT_HAN/*2134*/, G_UNICODE_SCRIPT_KATAKANA/*88*/, G_UNICODE_SCRIPT_HIRAGANA/*85*/ } },
+ { "jv", { G_UNICODE_SCRIPT_LATIN/*56*/ } },
+ { "ka", { G_UNICODE_SCRIPT_GEORGIAN/*33*/ } },
+ { "kaa", { G_UNICODE_SCRIPT_CYRILLIC/*78*/ } },
+ { "kab", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "ki", { G_UNICODE_SCRIPT_LATIN/*56*/ } },
+ { "kj", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "kk", { G_UNICODE_SCRIPT_CYRILLIC/*77*/ } },
+ { "kl", { G_UNICODE_SCRIPT_LATIN/*81*/ } },
+ { "km", { G_UNICODE_SCRIPT_KHMER/*63*/ } },
+ { "kn", { G_UNICODE_SCRIPT_KANNADA/*70*/ } },
+ { "ko", { G_UNICODE_SCRIPT_HANGUL/*2442*/ } },
+ { "kok", { G_UNICODE_SCRIPT_DEVANAGARI/*68*/ } },
+ { "kr", { G_UNICODE_SCRIPT_LATIN/*56*/ } },
+ { "ks", { G_UNICODE_SCRIPT_ARABIC/*33*/ } },
+ { "ku-am", { G_UNICODE_SCRIPT_CYRILLIC/*64*/ } },
+ { "ku-iq", { G_UNICODE_SCRIPT_ARABIC/*32*/ } },
+ { "ku-ir", { G_UNICODE_SCRIPT_ARABIC/*32*/ } },
+ { "ku-tr", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "kum", { G_UNICODE_SCRIPT_CYRILLIC/*66*/ } },
+ { "kv", { G_UNICODE_SCRIPT_CYRILLIC/*70*/ } },
+ { "kw", { G_UNICODE_SCRIPT_LATIN/*64*/ } },
+ { "kwm", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "ky", { G_UNICODE_SCRIPT_CYRILLIC/*70*/ } },
+ { "la", { G_UNICODE_SCRIPT_LATIN/*68*/ } },
+ { "lah", { G_UNICODE_SCRIPT_ARABIC/*27*/ } },
+ { "lb", { G_UNICODE_SCRIPT_LATIN/*75*/ } },
+ { "lez", { G_UNICODE_SCRIPT_CYRILLIC/*67*/ } },
+ { "lg", { G_UNICODE_SCRIPT_LATIN/*54*/ } },
+ { "li", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "ln", { G_UNICODE_SCRIPT_LATIN/*78*/ } },
+ { "lo", { G_UNICODE_SCRIPT_LAO/*55*/ } },
+ { "lt", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "lv", { G_UNICODE_SCRIPT_LATIN/*78*/ } },
+ { "mai", { G_UNICODE_SCRIPT_DEVANAGARI/*68*/ } },
+ { "mg", { G_UNICODE_SCRIPT_LATIN/*56*/ } },
+ { "mh", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "mi", { G_UNICODE_SCRIPT_LATIN/*64*/ } },
+ { "mk", { G_UNICODE_SCRIPT_CYRILLIC/*42*/ } },
+ { "ml", { G_UNICODE_SCRIPT_MALAYALAM/*68*/ } },
+ { "mn-cn", { G_UNICODE_SCRIPT_MONGOLIAN/*130*/ } },
+ { "mn-mn", { G_UNICODE_SCRIPT_CYRILLIC/*70*/ } },
+ { "mni", { G_UNICODE_SCRIPT_BENGALI/*75*/ } },
+ { "mo", { G_UNICODE_SCRIPT_CYRILLIC/*66*/, G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "mr", { G_UNICODE_SCRIPT_DEVANAGARI/*68*/ } },
+ { "ms", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "mt", { G_UNICODE_SCRIPT_LATIN/*72*/ } },
+ { "my", { G_UNICODE_SCRIPT_MYANMAR/*48*/ } },
+ { "na", { G_UNICODE_SCRIPT_LATIN/*60*/ } },
+ { "nb", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "nds", { G_UNICODE_SCRIPT_LATIN/*59*/ } },
+ { "ne", { G_UNICODE_SCRIPT_DEVANAGARI/*70*/ } },
+ { "ng", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "nl", { G_UNICODE_SCRIPT_LATIN/*82*/ } },
+ { "nn", { G_UNICODE_SCRIPT_LATIN/*76*/ } },
+ { "no", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "nqo", { G_UNICODE_SCRIPT_NKO/*59*/ } },
+ { "nr", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "nso", { G_UNICODE_SCRIPT_LATIN/*58*/ } },
+ { "nv", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "ny", { G_UNICODE_SCRIPT_LATIN/*54*/ } },
+ { "oc", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "om", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "or", { G_UNICODE_SCRIPT_ORIYA/*68*/ } },
+ { "os", { G_UNICODE_SCRIPT_CYRILLIC/*66*/ } },
+ { "ota", { G_UNICODE_SCRIPT_ARABIC/*37*/ } },
+ { "pa", { G_UNICODE_SCRIPT_GURMUKHI/*63*/ } },
+ { "pa-pk", { G_UNICODE_SCRIPT_ARABIC/*27*/ } },
+ { "pap-an", { G_UNICODE_SCRIPT_LATIN/*72*/ } },
+ { "pap-aw", { G_UNICODE_SCRIPT_LATIN/*54*/ } },
+ { "pl", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "ps-af", { G_UNICODE_SCRIPT_ARABIC/*49*/ } },
+ { "ps-pk", { G_UNICODE_SCRIPT_ARABIC/*49*/ } },
+ { "pt", { G_UNICODE_SCRIPT_LATIN/*82*/ } },
+ { "qu", { G_UNICODE_SCRIPT_LATIN/*54*/ } },
+ { "quz", { G_UNICODE_SCRIPT_LATIN/*54*/ } },
+ { "rm", { G_UNICODE_SCRIPT_LATIN/*66*/ } },
+ { "rn", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "ro", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "ru", { G_UNICODE_SCRIPT_CYRILLIC/*66*/ } },
+ { "rw", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "sa", { G_UNICODE_SCRIPT_DEVANAGARI/*68*/ } },
+ { "sah", { G_UNICODE_SCRIPT_CYRILLIC/*76*/ } },
+ { "sat", { G_UNICODE_SCRIPT_DEVANAGARI/*68*/ } },
+ { "sc", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "sco", { G_UNICODE_SCRIPT_LATIN/*56*/ } },
+ { "sd", { G_UNICODE_SCRIPT_ARABIC/*54*/ } },
+ { "se", { G_UNICODE_SCRIPT_LATIN/*66*/ } },
+ { "sel", { G_UNICODE_SCRIPT_CYRILLIC/*66*/ } },
+ { "sg", { G_UNICODE_SCRIPT_LATIN/*72*/ } },
+ { "sh", { G_UNICODE_SCRIPT_CYRILLIC/*94*/, G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "shs", { G_UNICODE_SCRIPT_LATIN/*46*/ } },
+ { "si", { G_UNICODE_SCRIPT_SINHALA/*73*/ } },
+ { "sid", { G_UNICODE_SCRIPT_ETHIOPIC/*281*/ } },
+ { "sk", { G_UNICODE_SCRIPT_LATIN/*86*/ } },
+ { "sl", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "sm", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "sma", { G_UNICODE_SCRIPT_LATIN/*60*/ } },
+ { "smj", { G_UNICODE_SCRIPT_LATIN/*60*/ } },
+ { "smn", { G_UNICODE_SCRIPT_LATIN/*68*/ } },
+ { "sms", { G_UNICODE_SCRIPT_LATIN/*80*/ } },
+ { "sn", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "so", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "sq", { G_UNICODE_SCRIPT_LATIN/*56*/ } },
+ { "sr", { G_UNICODE_SCRIPT_CYRILLIC/*60*/ } },
+ { "ss", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "st", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "su", { G_UNICODE_SCRIPT_LATIN/*54*/ } },
+ { "sv", { G_UNICODE_SCRIPT_LATIN/*68*/ } },
+ { "sw", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "syr", { G_UNICODE_SCRIPT_SYRIAC/*45*/ } },
+ { "ta", { G_UNICODE_SCRIPT_TAMIL/*48*/ } },
+ { "te", { G_UNICODE_SCRIPT_TELUGU/*70*/ } },
+ { "tg", { G_UNICODE_SCRIPT_CYRILLIC/*78*/ } },
+ { "th", { G_UNICODE_SCRIPT_THAI/*73*/ } },
+ { "ti-er", { G_UNICODE_SCRIPT_ETHIOPIC/*255*/ } },
+ { "ti-et", { G_UNICODE_SCRIPT_ETHIOPIC/*281*/ } },
+ { "tig", { G_UNICODE_SCRIPT_ETHIOPIC/*221*/ } },
+ { "tk", { G_UNICODE_SCRIPT_LATIN/*68*/ } },
+ { "tl", { G_UNICODE_SCRIPT_LATIN/*84*/ } },
+ { "tn", { G_UNICODE_SCRIPT_LATIN/*58*/ } },
+ { "to", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "tr", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "ts", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "tt", { G_UNICODE_SCRIPT_CYRILLIC/*76*/ } },
+ { "tw", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "ty", { G_UNICODE_SCRIPT_LATIN/*64*/ } },
+ { "tyv", { G_UNICODE_SCRIPT_CYRILLIC/*70*/ } },
+ { "ug", { G_UNICODE_SCRIPT_ARABIC/*33*/ } },
+ { "uk", { G_UNICODE_SCRIPT_CYRILLIC/*72*/ } },
+ { "und-zmth", { G_UNICODE_SCRIPT_LATIN/*53*/, G_UNICODE_SCRIPT_GREEK/*51*/ } },
+ { "und-zsye", { 0 } },
+ { "ur", { G_UNICODE_SCRIPT_ARABIC/*27*/ } },
+ { "uz", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "ve", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "vi", { G_UNICODE_SCRIPT_LATIN/*186*/ } },
+ { "vo", { G_UNICODE_SCRIPT_LATIN/*54*/ } },
+ { "vot", { G_UNICODE_SCRIPT_LATIN/*62*/ } },
+ { "wa", { G_UNICODE_SCRIPT_LATIN/*70*/ } },
+ { "wal", { G_UNICODE_SCRIPT_ETHIOPIC/*281*/ } },
+ { "wen", { G_UNICODE_SCRIPT_LATIN/*76*/ } },
+ { "wo", { G_UNICODE_SCRIPT_LATIN/*66*/ } },
+ { "xh", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "yap", { G_UNICODE_SCRIPT_LATIN/*58*/ } },
+ { "yi", { G_UNICODE_SCRIPT_HEBREW/*27*/ } },
+ { "yo", { G_UNICODE_SCRIPT_LATIN/*114*/ } },
+ { "za", { G_UNICODE_SCRIPT_LATIN/*52*/ } },
+ { "zh-cn", { G_UNICODE_SCRIPT_HAN/*6763*/ } },
+ { "zh-hk", { G_UNICODE_SCRIPT_HAN/*1083*/ } },
+ { "zh-mo", { G_UNICODE_SCRIPT_HAN/*1083*/ } },
+ { "zh-sg", { G_UNICODE_SCRIPT_HAN/*6763*/ } },
+ { "zh-tw", { G_UNICODE_SCRIPT_HAN/*13063*/ } },
+ { "zu", { G_UNICODE_SCRIPT_LATIN/*52*/ } }
+};
diff --git a/pango2/pango-script-private.h b/pango2/pango-script-private.h
new file mode 100644
index 00000000..06c84b65
--- /dev/null
+++ b/pango2/pango-script-private.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2002 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+
+#define PAREN_STACK_DEPTH 128
+
+typedef struct _ParenStackEntry ParenStackEntry;
+
+struct _ParenStackEntry
+{
+ int pair_index;
+ GUnicodeScript script_code;
+};
+
+struct _Pango2ScriptIter
+{
+ const char *text_start;
+ const char *text_end;
+
+ const char *script_start;
+ const char *script_end;
+ GUnicodeScript script_code;
+
+ ParenStackEntry paren_stack[PAREN_STACK_DEPTH];
+ int paren_sp;
+};
+
+Pango2ScriptIter *
+_pango2_script_iter_init (Pango2ScriptIter *iter,
+ const char *text,
+ int length);
+
+void
+_pango2_script_iter_fini (Pango2ScriptIter *iter);
diff --git a/pango2/pango-script.c b/pango2/pango-script.c
new file mode 100644
index 00000000..e65c6265
--- /dev/null
+++ b/pango2/pango-script.c
@@ -0,0 +1,373 @@
+/* Pango2
+ * pango-script.c: Script tag handling
+ *
+ * Copyright (C) 2002 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.
+ *
+ * Implementation of pango2_script_iter is derived from ICU:
+ *
+ * icu/sources/common/usc_impl.c
+ *
+ **********************************************************************
+ * Copyright (C) 1999-2002, International Business Machines
+ * Corporation and others. All Rights Reserved.
+ **********************************************************************
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, and/or sell copies of the Software, and to permit persons
+ * to whom the Software is furnished to do so, provided that the above
+ * copyright notice(s) and this permission notice appear in all copies of
+ * the Software and that both the above copyright notice(s) and this
+ * permission notice appear in supporting documentation.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+ * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
+ * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
+ * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+ * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Except as contained in this notice, the name of a copyright holder
+ * shall not be used in advertising or otherwise to promote the sale, use
+ * or other dealings in this Software without prior written authorization
+ * of the copyright holder.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+
+#include "pango-script.h"
+#include "pango-script-private.h"
+
+
+/**********************************************************************/
+
+static Pango2ScriptIter *pango2_script_iter_copy (Pango2ScriptIter *iter);
+
+G_DEFINE_BOXED_TYPE (Pango2ScriptIter,
+ pango2_script_iter,
+ pango2_script_iter_copy,
+ pango2_script_iter_free)
+
+Pango2ScriptIter *
+_pango2_script_iter_init (Pango2ScriptIter *iter,
+ const char *text,
+ int length)
+{
+ iter->text_start = text;
+ if (length >= 0)
+ iter->text_end = text + length;
+ else
+ iter->text_end = text + strlen (text);
+
+ iter->script_start = text;
+ iter->script_end = text;
+ iter->script_code = G_UNICODE_SCRIPT_COMMON;
+
+ iter->paren_sp = -1;
+
+ pango2_script_iter_next (iter);
+
+ return iter;
+}
+
+/**
+ * pango2_script_iter_new:
+ * @text: a UTF-8 string
+ * @length: length of @text, or -1 if @text is nul-terminated
+ *
+ * Create a new `Pango2ScriptIter`, used to break a string of
+ * Unicode text into runs by Unicode script.
+ *
+ * No copy is made of @text, so the caller needs to make
+ * sure it remains valid until the iterator is freed with
+ * [method@Pango2.ScriptIter.free].
+ *
+ * Return value: the new script iterator, initialized
+ * to point at the first range in the text, which should be
+ * freed with [method@Pango2.ScriptIter.free]. If the string is
+ * empty, it will point at an empty range.
+ */
+Pango2ScriptIter *
+pango2_script_iter_new (const char *text,
+ int length)
+{
+ return _pango2_script_iter_init (g_slice_new (Pango2ScriptIter), text, length);
+}
+
+static Pango2ScriptIter *
+pango2_script_iter_copy (Pango2ScriptIter *iter)
+{
+ return g_slice_dup (Pango2ScriptIter, iter);
+}
+
+void
+_pango2_script_iter_fini (Pango2ScriptIter *iter)
+{
+}
+
+/**
+ * pango2_script_iter_free:
+ * @iter: a `Pango2ScriptIter`
+ *
+ * Frees a `Pango2ScriptIter`.
+ */
+void
+pango2_script_iter_free (Pango2ScriptIter *iter)
+{
+ _pango2_script_iter_fini (iter);
+ g_slice_free (Pango2ScriptIter, iter);
+}
+
+/**
+ * pango2_script_iter_get_range:
+ * @iter: a `Pango2ScriptIter`
+ * @start: (out) (optional): location to store start position of the range
+ * @end: (out) (optional): location to store end position of the range
+ * @script: (out) (optional): location to store script for range
+ *
+ * Gets information about the range to which @iter currently points.
+ *
+ * The range is the set of locations p where *start <= p < *end.
+ * (That is, it doesn't include the character stored at *end)
+ */
+void
+pango2_script_iter_get_range (Pango2ScriptIter *iter,
+ const char **start,
+ const char **end,
+ GUnicodeScript *script)
+{
+ if (start)
+ *start = iter->script_start;
+ if (end)
+ *end = iter->script_end;
+ if (script)
+ *script = iter->script_code;
+}
+
+static const gunichar paired_chars[] = {
+ 0x0028, 0x0029, /* ascii paired punctuation */
+ 0x003c, 0x003e,
+ 0x005b, 0x005d,
+ 0x007b, 0x007d,
+ 0x00ab, 0x00bb, /* guillemets */
+ 0x0f3a, 0x0f3b, /* tibetan */
+ 0x0f3c, 0x0f3d,
+ 0x169b, 0x169c, /* ogham */
+ 0x2018, 0x2019, /* general punctuation */
+ 0x201c, 0x201d,
+ 0x2039, 0x203a,
+ 0x2045, 0x2046,
+ 0x207d, 0x207e,
+ 0x208d, 0x208e,
+ 0x27e6, 0x27e7, /* math */
+ 0x27e8, 0x27e9,
+ 0x27ea, 0x27eb,
+ 0x27ec, 0x27ed,
+ 0x27ee, 0x27ef,
+ 0x2983, 0x2984,
+ 0x2985, 0x2986,
+ 0x2987, 0x2988,
+ 0x2989, 0x298a,
+ 0x298b, 0x298c,
+ 0x298d, 0x298e,
+ 0x298f, 0x2990,
+ 0x2991, 0x2992,
+ 0x2993, 0x2994,
+ 0x2995, 0x2996,
+ 0x2997, 0x2998,
+ 0x29fc, 0x29fd,
+ 0x2e02, 0x2e03,
+ 0x2e04, 0x2e05,
+ 0x2e09, 0x2e0a,
+ 0x2e0c, 0x2e0d,
+ 0x2e1c, 0x2e1d,
+ 0x2e20, 0x2e21,
+ 0x2e22, 0x2e23,
+ 0x2e24, 0x2e25,
+ 0x2e26, 0x2e27,
+ 0x2e28, 0x2e29,
+ 0x3008, 0x3009, /* chinese paired punctuation */
+ 0x300a, 0x300b,
+ 0x300c, 0x300d,
+ 0x300e, 0x300f,
+ 0x3010, 0x3011,
+ 0x3014, 0x3015,
+ 0x3016, 0x3017,
+ 0x3018, 0x3019,
+ 0x301a, 0x301b,
+ 0xfe59, 0xfe5a,
+ 0xfe5b, 0xfe5c,
+ 0xfe5d, 0xfe5e,
+ 0xff08, 0xff09,
+ 0xff3b, 0xff3d,
+ 0xff5b, 0xff5d,
+ 0xff5f, 0xff60,
+ 0xff62, 0xff63
+};
+
+static int
+get_pair_index (gunichar ch)
+{
+ int lower = 0;
+ int upper = G_N_ELEMENTS (paired_chars) - 1;
+
+ while (lower <= upper)
+ {
+ int mid = (lower + upper) / 2;
+
+ if (ch < paired_chars[mid])
+ upper = mid - 1;
+ else if (ch > paired_chars[mid])
+ lower = mid + 1;
+ else
+ return mid;
+ }
+
+ return -1;
+}
+
+/* duplicated in pango-language.c */
+#define REAL_SCRIPT(script) \
+ ((script) > G_UNICODE_SCRIPT_INHERITED && (script) != G_UNICODE_SCRIPT_UNKNOWN)
+
+#define SAME_SCRIPT(script1, script2) \
+ (!REAL_SCRIPT (script1) || !REAL_SCRIPT (script2) || (script1) == (script2))
+
+#define IS_OPEN(pair_index) (((pair_index) & 1) == 0)
+
+/**
+ * pango2_script_iter_next:
+ * @iter: a `Pango2ScriptIter`
+ *
+ * Advances a `Pango2ScriptIter` to the next range.
+ *
+ * If @iter is already at the end, it is left unchanged
+ * and %FALSE is returned.
+ *
+ * Return value: %TRUE if @iter was successfully advanced
+ */
+gboolean
+pango2_script_iter_next (Pango2ScriptIter *iter)
+{
+ int start_sp;
+
+ if (iter->script_end == iter->text_end)
+ return FALSE;
+
+ start_sp = iter->paren_sp;
+ iter->script_code = G_UNICODE_SCRIPT_COMMON;
+ iter->script_start = iter->script_end;
+
+ for (; iter->script_end < iter->text_end; iter->script_end = g_utf8_next_char (iter->script_end))
+ {
+ gunichar ch = g_utf8_get_char (iter->script_end);
+ GUnicodeScript sc;
+ int pair_index;
+
+ sc = g_unichar_get_script (ch);
+ if (sc != G_UNICODE_SCRIPT_COMMON)
+ pair_index = -1;
+ else
+ pair_index = get_pair_index (ch);
+
+ /*
+ * Paired character handling:
+ *
+ * if it's an open character, push it onto the stack.
+ * if it's a close character, find the matching open on the
+ * stack, and use that script code. Any non-matching open
+ * characters above it on the stack will be poped.
+ */
+ if (pair_index >= 0)
+ {
+ if (IS_OPEN (pair_index))
+ {
+ /*
+ * If the paren stack is full, empty it. This
+ * means that deeply nested paired punctuation
+ * characters will be ignored, but that's an unusual
+ * case, and it's better to ignore them than to
+ * write off the end of the stack...
+ */
+ if (++iter->paren_sp >= PAREN_STACK_DEPTH)
+ iter->paren_sp = 0;
+
+ iter->paren_stack[iter->paren_sp].pair_index = pair_index;
+ iter->paren_stack[iter->paren_sp].script_code = iter->script_code;
+ }
+ else if (iter->paren_sp >= 0)
+ {
+ int pi = pair_index & ~1;
+
+ while (iter->paren_sp >= 0 && iter->paren_stack[iter->paren_sp].pair_index != pi)
+ iter->paren_sp--;
+
+ if (iter->paren_sp < start_sp)
+ start_sp = iter->paren_sp;
+
+ if (iter->paren_sp >= 0)
+ sc = iter->paren_stack[iter->paren_sp].script_code;
+ }
+ }
+
+ if (SAME_SCRIPT (iter->script_code, sc))
+ {
+ if (!REAL_SCRIPT (iter->script_code) && REAL_SCRIPT (sc))
+ {
+ iter->script_code = sc;
+
+ /*
+ * now that we have a final script code, fix any open
+ * characters we pushed before we knew the script code.
+ */
+ while (start_sp < iter->paren_sp)
+ iter->paren_stack[++start_sp].script_code = iter->script_code;
+ }
+
+ /*
+ * if this character is a close paired character,
+ * pop it from the stack
+ */
+ if (pair_index >= 0 && !IS_OPEN (pair_index) && iter->paren_sp >= 0)
+ {
+ iter->paren_sp--;
+
+ if (iter->paren_sp < start_sp)
+ start_sp = iter->paren_sp;
+ }
+ }
+ else
+ {
+ /* Different script, we're done */
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+/**********************************************************
+ * End of code from ICU
+ **********************************************************/
diff --git a/pango2/pango-script.h b/pango2/pango-script.h
new file mode 100644
index 00000000..abe822c8
--- /dev/null
+++ b/pango2/pango-script.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2002 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include <pango2/pango-language.h>
+#include <pango2/pango-version-macros.h>
+
+G_BEGIN_DECLS
+
+/**
+ * Pango2ScriptIter:
+ *
+ * A `Pango2ScriptIter` is used to iterate through a string
+ * and identify ranges in different scripts.
+ **/
+typedef struct _Pango2ScriptIter Pango2ScriptIter;
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_script_iter_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2ScriptIter * pango2_script_iter_new (const char *text,
+ int length);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_script_iter_get_range (Pango2ScriptIter *iter,
+ const char **start,
+ const char **end,
+ GUnicodeScript *script);
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_script_iter_next (Pango2ScriptIter *iter);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_script_iter_free (Pango2ScriptIter *iter);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Language * pango2_script_get_sample_language (GUnicodeScript script) G_GNUC_PURE;
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2ScriptIter, pango2_script_iter_free)
+
+G_END_DECLS
diff --git a/pango2/pango-tabs.c b/pango2/pango-tabs.c
new file mode 100644
index 00000000..71fdfe2f
--- /dev/null
+++ b/pango2/pango-tabs.c
@@ -0,0 +1,617 @@
+/* Pango2
+ * pango-tabs.c: Tab-related stuff
+ *
+ * Copyright (C) 2000 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.
+ */
+
+#include "config.h"
+#include "pango-tabs.h"
+#include "pango-impl-utils.h"
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct _Pango2Tab Pango2Tab;
+
+struct _Pango2Tab
+{
+ int location;
+ Pango2TabAlign alignment;
+ gunichar decimal_point;
+};
+
+/**
+ * Pango2TabArray:
+ *
+ * A `Pango2TabArray` contains an array of tab stops.
+ *
+ * `Pango2TabArray` can be used to set tab stops in a `Pango2Layout`.
+ * Each tab stop has an alignment, a position, and optionally
+ * a character to use as decimal point.
+ */
+struct _Pango2TabArray
+{
+ int size;
+ int allocated;
+ gboolean positions_in_pixels;
+ Pango2Tab *tabs;
+};
+
+static void
+init_tabs (Pango2TabArray *array, int start, int end)
+{
+ while (start < end)
+ {
+ array->tabs[start].location = 0;
+ array->tabs[start].alignment = PANGO2_TAB_LEFT;
+ array->tabs[start].decimal_point = 0;
+ ++start;
+ }
+}
+
+/**
+ * pango2_tab_array_new:
+ * @initial_size: Initial number of tab stops to allocate, can be 0
+ * @positions_in_pixels: whether positions are in pixel units
+ *
+ * Creates an array of @initial_size tab stops.
+ *
+ * Tab stops are specified in pixel units if @positions_in_pixels is %TRUE,
+ * otherwise in Pango2 units. All stops are initially at position 0.
+ *
+ * Return value: the newly allocated `Pango2TabArray`, which should
+ * be freed with [method@Pango2.TabArray.free].
+ */
+Pango2TabArray*
+pango2_tab_array_new (int initial_size,
+ gboolean positions_in_pixels)
+{
+ Pango2TabArray *array;
+
+ g_return_val_if_fail (initial_size >= 0, NULL);
+
+ /* alloc enough to treat array->tabs as an array of length
+ * size, though it's declared as an array of length 1.
+ * If we allowed tab array resizing we'd need to drop this
+ * optimization.
+ */
+ array = g_slice_new (Pango2TabArray);
+ array->size = initial_size; array->allocated = initial_size;
+
+ if (array->allocated > 0)
+ {
+ array->tabs = g_new (Pango2Tab, array->allocated);
+ init_tabs (array, 0, array->allocated);
+ }
+ else
+ array->tabs = NULL;
+
+ array->positions_in_pixels = positions_in_pixels;
+
+ return array;
+}
+
+/**
+ * pango2_tab_array_new_with_positions:
+ * @size: number of tab stops in the array
+ * @positions_in_pixels: whether positions are in pixel units
+ * @first_alignment: alignment of first tab stop
+ * @first_position: position of first tab stop
+ * @...: additional alignment/position pairs
+ *
+ * Creates a `Pango2TabArray` and allows you to specify the alignment
+ * and position of each tab stop.
+ *
+ * You **must** provide an alignment and position for @size tab stops.
+ *
+ * Return value: the newly allocated `Pango2TabArray`, which should
+ * be freed with [method@Pango2.TabArray.free].
+ */
+Pango2TabArray *
+pango2_tab_array_new_with_positions (int size,
+ gboolean positions_in_pixels,
+ Pango2TabAlign first_alignment,
+ int first_position,
+ ...)
+{
+ Pango2TabArray *array;
+ va_list args;
+ int i;
+
+ g_return_val_if_fail (size >= 0, NULL);
+
+ array = pango2_tab_array_new (size, positions_in_pixels);
+
+ if (size == 0)
+ return array;
+
+ array->tabs[0].alignment = first_alignment;
+ array->tabs[0].location = first_position;
+ array->tabs[0].decimal_point = 0;
+
+ if (size == 1)
+ return array;
+
+ va_start (args, first_position);
+
+ i = 1;
+ while (i < size)
+ {
+ Pango2TabAlign align = va_arg (args, Pango2TabAlign);
+ int pos = va_arg (args, int);
+
+ array->tabs[i].alignment = align;
+ array->tabs[i].location = pos;
+ array->tabs[i].decimal_point = 0;
+
+ ++i;
+ }
+
+ va_end (args);
+
+ return array;
+}
+
+G_DEFINE_BOXED_TYPE (Pango2TabArray, pango2_tab_array,
+ pango2_tab_array_copy,
+ pango2_tab_array_free);
+
+/**
+ * pango2_tab_array_copy:
+ * @src: `Pango2TabArray` to copy
+ *
+ * Copies a `Pango2TabArray`.
+ *
+ * Return value: the newly allocated `Pango2TabArray`, which should
+ * be freed with [method@Pango2.TabArray.free].
+ */
+Pango2TabArray*
+pango2_tab_array_copy (Pango2TabArray *src)
+{
+ Pango2TabArray *copy;
+
+ g_return_val_if_fail (src != NULL, NULL);
+
+ copy = pango2_tab_array_new (src->size, src->positions_in_pixels);
+
+ if (copy->tabs)
+ memcpy (copy->tabs, src->tabs, sizeof(Pango2Tab) * src->size);
+
+ return copy;
+}
+
+/**
+ * pango2_tab_array_free:
+ * @tab_array: a `Pango2TabArray`
+ *
+ * Frees a tab array and associated resources.
+ */
+void
+pango2_tab_array_free (Pango2TabArray *tab_array)
+{
+ g_return_if_fail (tab_array != NULL);
+
+ g_free (tab_array->tabs);
+
+ g_slice_free (Pango2TabArray, tab_array);
+}
+
+/**
+ * pango2_tab_array_get_size:
+ * @tab_array: a `Pango2TabArray`
+ *
+ * Gets the number of tab stops in @tab_array.
+ *
+ * Return value: the number of tab stops in the array.
+ */
+int
+pango2_tab_array_get_size (Pango2TabArray *tab_array)
+{
+ g_return_val_if_fail (tab_array != NULL, 0);
+
+ return tab_array->size;
+}
+
+/**
+ * pango2_tab_array_resize:
+ * @tab_array: a `Pango2TabArray`
+ * @new_size: new size of the array
+ *
+ * Resizes a tab array.
+ *
+ * You must subsequently initialize any tabs
+ * that were added as a result of growing the array.
+ */
+void
+pango2_tab_array_resize (Pango2TabArray *tab_array,
+ int new_size)
+{
+ if (new_size > tab_array->allocated)
+ {
+ int current_end = tab_array->allocated;
+
+ /* Ratchet allocated size up above the index. */
+ if (tab_array->allocated == 0)
+ tab_array->allocated = 2;
+
+ while (new_size > tab_array->allocated)
+ tab_array->allocated = tab_array->allocated * 2;
+
+ tab_array->tabs = g_renew (Pango2Tab, tab_array->tabs,
+ tab_array->allocated);
+
+ init_tabs (tab_array, current_end, tab_array->allocated);
+ }
+
+ tab_array->size = new_size;
+}
+
+/**
+ * pango2_tab_array_set_tab:
+ * @tab_array: a `Pango2TabArray`
+ * @tab_index: the index of a tab stop
+ * @alignment: tab alignment
+ * @location: tab location in Pango2 units
+ *
+ * Sets the alignment and location of a tab stop.
+ */
+void
+pango2_tab_array_set_tab (Pango2TabArray *tab_array,
+ int tab_index,
+ Pango2TabAlign alignment,
+ int location)
+{
+ g_return_if_fail (tab_array != NULL);
+ g_return_if_fail (tab_index >= 0);
+ g_return_if_fail (location >= 0);
+
+ if (tab_index >= tab_array->size)
+ pango2_tab_array_resize (tab_array, tab_index + 1);
+
+ tab_array->tabs[tab_index].alignment = alignment;
+ tab_array->tabs[tab_index].location = location;
+}
+
+/**
+ * pango2_tab_array_get_tab:
+ * @tab_array: a `Pango2TabArray`
+ * @tab_index: tab stop index
+ * @alignment: (out) (optional): location to store alignment
+ * @location: (out) (optional): location to store tab position
+ *
+ * Gets the alignment and position of a tab stop.
+ */
+void
+pango2_tab_array_get_tab (Pango2TabArray *tab_array,
+ int tab_index,
+ Pango2TabAlign *alignment,
+ int *location)
+{
+ g_return_if_fail (tab_array != NULL);
+ g_return_if_fail (tab_index < tab_array->size);
+ g_return_if_fail (tab_index >= 0);
+
+ if (alignment)
+ *alignment = tab_array->tabs[tab_index].alignment;
+
+ if (location)
+ *location = tab_array->tabs[tab_index].location;
+}
+
+/**
+ * pango2_tab_array_get_tabs:
+ * @tab_array: a `Pango2TabArray`
+ * @alignments: (out) (optional): location to store an array of tab
+ * stop alignments
+ * @locations: (out) (optional) (array): location to store an array
+ * of tab positions
+ *
+ * If non-%NULL, @alignments and @locations are filled with allocated
+ * arrays.
+ *
+ * The arrays are of length [method@Pango2.TabArray.get_size].
+ * You must free the returned array.
+ */
+void
+pango2_tab_array_get_tabs (Pango2TabArray *tab_array,
+ Pango2TabAlign **alignments,
+ int **locations)
+{
+ int i;
+
+ g_return_if_fail (tab_array != NULL);
+
+ if (alignments)
+ *alignments = g_new (Pango2TabAlign, tab_array->size);
+
+ if (locations)
+ *locations = g_new (int, tab_array->size);
+
+ i = 0;
+ while (i < tab_array->size)
+ {
+ if (alignments)
+ (*alignments)[i] = tab_array->tabs[i].alignment;
+ if (locations)
+ (*locations)[i] = tab_array->tabs[i].location;
+
+ ++i;
+ }
+}
+
+/**
+ * pango2_tab_array_get_positions_in_pixels:
+ * @tab_array: a `Pango2TabArray`
+ *
+ * Returns %TRUE if the tab positions are in pixels,
+ * %FALSE if they are in Pango2 units.
+ *
+ * Return value: whether positions are in pixels.
+ */
+gboolean
+pango2_tab_array_get_positions_in_pixels (Pango2TabArray *tab_array)
+{
+ g_return_val_if_fail (tab_array != NULL, FALSE);
+
+ return tab_array->positions_in_pixels;
+}
+
+/**
+ * pango2_tab_array_set_positions_in_pixels:
+ * @tab_array: a `Pango2TabArray`
+ * @positions_in_pixels: whether positions are in pixels
+ *
+ * Sets whether positions in this array are specified in
+ * pixels.
+ */
+void
+pango2_tab_array_set_positions_in_pixels (Pango2TabArray *tab_array,
+ gboolean positions_in_pixels)
+{
+ g_return_if_fail (tab_array != NULL);
+
+ tab_array->positions_in_pixels = positions_in_pixels;
+}
+
+/**
+ * pango2_tab_array_to_string:
+ * @tab_array: a `Pango2TabArray`
+ *
+ * Serializes a `Pango2TabArray` to a string.
+ *
+ * No guarantees are made about the format of the string,
+ * it may change between Pango2 versions.
+ *
+ * The intended use of this function is testing and
+ * debugging. The format is not meant as a permanent
+ * storage format.
+ *
+ * Returns: (transfer full): a newly allocated string
+ */
+char *
+pango2_tab_array_to_string (Pango2TabArray *tab_array)
+{
+ GString *s;
+
+ s = g_string_new ("");
+
+ for (int i = 0; i < tab_array->size; i++)
+ {
+ if (i > 0)
+ g_string_append_c (s, '\n');
+
+ if (tab_array->tabs[i].alignment == PANGO2_TAB_RIGHT)
+ g_string_append (s, "right:");
+ else if (tab_array->tabs[i].alignment == PANGO2_TAB_CENTER)
+ g_string_append (s, "center:");
+ else if (tab_array->tabs[i].alignment == PANGO2_TAB_DECIMAL)
+ g_string_append (s, "decimal:");
+
+ g_string_append_printf (s, "%d", tab_array->tabs[i].location);
+ if (tab_array->positions_in_pixels)
+ g_string_append (s, "px");
+
+ if (tab_array->tabs[i].decimal_point != 0)
+ g_string_append_printf (s, ":%d", tab_array->tabs[i].decimal_point);
+ }
+
+ return g_string_free (s, FALSE);
+}
+
+static const char *
+skip_whitespace (const char *p)
+{
+ while (g_ascii_isspace (*p))
+ p++;
+ return p;
+}
+
+/**
+ * pango2_tab_array_from_string:
+ * @text: a string
+ *
+ * Deserializes a `Pango2TabArray` from a string.
+ *
+ * This is the counterpart to [method@Pango2.TabArray.to_string].
+ * See that functions for details about the format.
+ *
+ * Returns: (transfer full) (nullable): a new `Pango2TabArray`
+ */
+Pango2TabArray *
+pango2_tab_array_from_string (const char *text)
+{
+ Pango2TabArray *array;
+ gboolean pixels;
+ const char *p;
+ int i;
+
+ pixels = strstr (text, "px") != NULL;
+
+ array = pango2_tab_array_new (0, pixels);
+
+ p = skip_whitespace (text);
+
+ i = 0;
+ while (*p)
+ {
+ char *endp;
+ gint64 pos;
+ Pango2TabAlign align;
+
+ if (g_str_has_prefix (p, "left:"))
+ {
+ align = PANGO2_TAB_LEFT;
+ p += strlen ("left:");
+ }
+ else if (g_str_has_prefix (p, "right:"))
+ {
+ align = PANGO2_TAB_RIGHT;
+ p += strlen ("right:");
+ }
+ else if (g_str_has_prefix (p, "center:"))
+ {
+ align = PANGO2_TAB_CENTER;
+ p += strlen ("center:");
+ }
+ else if (g_str_has_prefix (p, "decimal:"))
+ {
+ align = PANGO2_TAB_DECIMAL;
+ p += strlen ("decimal:");
+ }
+ else
+ {
+ align = PANGO2_TAB_LEFT;
+ }
+
+ pos = g_ascii_strtoll (p, &endp, 10);
+ if (pos < 0 ||
+ (pixels && *endp != 'p') ||
+ (!pixels && !g_ascii_isspace (*endp) && *endp != ':' && *endp != '\0')) goto fail;
+
+ pango2_tab_array_set_tab (array, i, align, pos);
+
+ p = (const char *)endp;
+ if (pixels)
+ {
+ if (p[0] != 'p' || p[1] != 'x') goto fail;
+ p += 2;
+ }
+
+ if (p[0] == ':')
+ {
+ gunichar ch;
+
+ p++;
+ ch = g_ascii_strtoll (p, &endp, 10);
+ if (!g_ascii_isspace (*endp) && *endp != '\0') goto fail;
+
+ pango2_tab_array_set_decimal_point (array, i, ch);
+
+ p = (const char *)endp;
+ }
+
+ p = skip_whitespace (p);
+
+ i++;
+ }
+
+ goto success;
+
+fail:
+ pango2_tab_array_free (array);
+ array = NULL;
+
+success:
+ return array;
+}
+
+/**
+ * pango2_tab_array_set_decimal_point:
+ * @tab_array: a `Pango2TabArray`
+ * @tab_index: the index of a tab stop
+ * @decimal_point: the decimal point to use
+ *
+ * Sets the Unicode character to use as decimal point.
+ *
+ * This is only relevant for tabs with %PANGO2_TAB_DECIMAL alignment,
+ * which align content at the first occurrence of the decimal point
+ * character.
+ *
+ * By default, Pango2 uses the decimal point according
+ * to the current locale.
+ */
+void
+pango2_tab_array_set_decimal_point (Pango2TabArray *tab_array,
+ int tab_index,
+ gunichar decimal_point)
+{
+ g_return_if_fail (tab_array != NULL);
+ g_return_if_fail (tab_index >= 0);
+
+ if (tab_index >= tab_array->size)
+ pango2_tab_array_resize (tab_array, tab_index + 1);
+
+ tab_array->tabs[tab_index].decimal_point = decimal_point;
+}
+
+/**
+ * pango2_tab_array_get_decimal_point:
+ * @tab_array: a `Pango2TabArray`
+ * @tab_index: the index of a tab stop
+ *
+ * Gets the Unicode character to use as decimal point.
+ *
+ * This is only relevant for tabs with %PANGO2_TAB_DECIMAL alignment,
+ * which align content at the first occurrence of the decimal point
+ * character.
+ *
+ * The default value of 0 means that Pango2 will use the
+ * decimal point according to the current locale.
+ */
+gunichar
+pango2_tab_array_get_decimal_point (Pango2TabArray *tab_array,
+ int tab_index)
+{
+ g_return_val_if_fail (tab_array != NULL, 0);
+ g_return_val_if_fail (tab_index < tab_array->size, 0);
+ g_return_val_if_fail (tab_index >= 0, 0);
+
+ return tab_array->tabs[tab_index].decimal_point;
+}
+
+static int
+compare_tabs (const void *p1, const void *p2)
+{
+ const Pango2Tab *t1 = p1;
+ const Pango2Tab *t2 = p2;
+
+ return t1->location - t2->location;
+}
+
+/**
+ * pango2_tab_array_sort:
+ * @tab_array: a `Pango2TabArray`
+ *
+ * Utility function to ensure that the tab stops are in increasing order.
+ */
+void
+pango2_tab_array_sort (Pango2TabArray *tab_array)
+{
+ g_return_if_fail (tab_array != NULL);
+
+ qsort (tab_array->tabs, tab_array->size, sizeof (Pango2Tab), compare_tabs);
+}
diff --git a/pango2/pango-tabs.h b/pango2/pango-tabs.h
new file mode 100644
index 00000000..b5303789
--- /dev/null
+++ b/pango2/pango-tabs.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+
+G_BEGIN_DECLS
+
+typedef struct _Pango2TabArray Pango2TabArray;
+
+/**
+ * Pango2TabAlign:
+ * @PANGO2_TAB_LEFT: the text appears to the right of the tab stop position
+ * @PANGO2_TAB_RIGHT: the text appears to the left of the tab stop position
+ * until the available space is filled.
+ * @PANGO2_TAB_CENTER: the text is centered at the tab stop position
+ * until the available space is filled.
+ * @PANGO2_TAB_DECIMAL: text before the first occurrence of the decimal point
+ * character appears to the left of the tab stop position (until the available
+ * space is filled), the rest to the right.
+ *
+ * `Pango2TabAlign` specifies where the text appears relative to the tab stop
+ * position.
+ */
+typedef enum
+{
+ PANGO2_TAB_LEFT,
+ PANGO2_TAB_RIGHT,
+ PANGO2_TAB_CENTER,
+ PANGO2_TAB_DECIMAL
+} Pango2TabAlign;
+
+#define PANGO2_TYPE_TAB_ARRAY (pango2_tab_array_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+GType pango2_tab_array_get_type (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2TabArray * pango2_tab_array_new (int initial_size,
+ gboolean positions_in_pixels);
+PANGO2_AVAILABLE_IN_ALL
+Pango2TabArray * pango2_tab_array_new_with_positions (int size,
+ gboolean positions_in_pixels,
+ Pango2TabAlign first_alignment,
+ int first_position,
+ ...);
+PANGO2_AVAILABLE_IN_ALL
+Pango2TabArray * pango2_tab_array_copy (Pango2TabArray *src);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_tab_array_free (Pango2TabArray *tab_array);
+PANGO2_AVAILABLE_IN_ALL
+int pango2_tab_array_get_size (Pango2TabArray *tab_array);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_tab_array_resize (Pango2TabArray *tab_array,
+ int new_size);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_tab_array_set_tab (Pango2TabArray *tab_array,
+ int tab_index,
+ Pango2TabAlign alignment,
+ int location);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_tab_array_get_tab (Pango2TabArray *tab_array,
+ int tab_index,
+ Pango2TabAlign *alignment,
+ int *location);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_tab_array_get_tabs (Pango2TabArray *tab_array,
+ Pango2TabAlign **alignments,
+ int **locations);
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_tab_array_get_positions_in_pixels (Pango2TabArray *tab_array);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_tab_array_set_positions_in_pixels (Pango2TabArray *tab_array,
+ gboolean positions_in_pixels);
+
+PANGO2_AVAILABLE_IN_ALL
+char * pango2_tab_array_to_string (Pango2TabArray *tab_array);
+PANGO2_AVAILABLE_IN_ALL
+Pango2TabArray * pango2_tab_array_from_string (const char *text);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_tab_array_set_decimal_point (Pango2TabArray *tab_array,
+ int tab_index,
+ gunichar decimal_point);
+PANGO2_AVAILABLE_IN_ALL
+gunichar pango2_tab_array_get_decimal_point (Pango2TabArray *tab_array,
+ int tab_index);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_tab_array_sort (Pango2TabArray *tab_array);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(Pango2TabArray, pango2_tab_array_free)
+
+G_END_DECLS
diff --git a/pango2/pango-trace-private.h b/pango2/pango-trace-private.h
new file mode 100644
index 00000000..5ef2a5ff
--- /dev/null
+++ b/pango2/pango-trace-private.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#ifdef HAVE_SYSPROF
+#include <sysprof-capture.h>
+#endif
+
+#include <glib.h>
+
+
+#ifdef HAVE_SYSPROF
+#define PANGO2_TRACE_CURRENT_TIME SYSPROF_CAPTURE_CURRENT_TIME
+#else
+#define PANGO2_TRACE_CURRENT_TIME 0
+#endif
+
+void pango2_trace_mark (gint64 begin_time,
+ const char *name,
+ const char *message_format,
+ ...) G_GNUC_PRINTF (3, 4);
+
+#ifndef HAVE_SYSPROF
+/* Optimise the whole call out */
+#if defined(G_HAVE_ISO_VARARGS)
+#define pango2_trace_mark(b, n, m, ...) G_STMT_START { } G_STMT_END
+#elif defined(G_HAVE_GNUC_VARARGS)
+#define pango2_trace_mark(b, n, m...) G_STMT_START { } G_STMT_END
+#else
+/* no varargs macro support; the call will have to be optimised out by the compiler */
+#endif
+#endif
diff --git a/pango2/pango-trace.c b/pango2/pango-trace.c
new file mode 100644
index 00000000..99417c45
--- /dev/null
+++ b/pango2/pango-trace.c
@@ -0,0 +1,40 @@
+/* Pango2
+ * pango-trace.c:
+ *
+ * Copyright (C) 2020 Red Hat, Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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 Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "pango-trace-private.h"
+
+#include <stdarg.h>
+
+void
+(pango2_trace_mark) (gint64 begin_time,
+ const char *name,
+ const char *message_format,
+ ...)
+{
+#ifdef HAVE_SYSPROF
+ gint64 end_time = PANGO2_TRACE_CURRENT_TIME;
+ va_list args;
+
+ va_start (args, message_format);
+ sysprof_collector_mark_vprintf (begin_time, end_time - begin_time, "Pango2", name, message_format, args);
+ va_end (args);
+#endif /* HAVE_SYSPROF */
+}
diff --git a/pango2/pango-types.h b/pango2/pango-types.h
new file mode 100644
index 00000000..003dec8e
--- /dev/null
+++ b/pango2/pango-types.h
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 1999 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <pango2/pango-version-macros.h>
+
+G_BEGIN_DECLS
+
+typedef struct _Pango2LogAttr Pango2LogAttr;
+
+typedef struct _Pango2FontDescription Pango2FontDescription;
+
+typedef struct _Pango2Font Pango2Font;
+typedef struct _Pango2FontFace Pango2FontFace;
+typedef struct _Pango2FontFamily Pango2FontFamily;
+
+typedef struct _Pango2FontMap Pango2FontMap;
+
+typedef struct _Pango2Rectangle Pango2Rectangle;
+
+typedef struct _Pango2Context Pango2Context;
+
+typedef struct _Pango2Language Pango2Language;
+
+/* A index of a glyph into a font. Rendering system dependent */
+/**
+ * Pango2Glyph:
+ *
+ * A `Pango2Glyph` represents a single glyph in the output form of a string.
+ */
+typedef guint32 Pango2Glyph;
+
+typedef struct _Pango2Lines Pango2Lines;
+typedef struct _Pango2Run Pango2Run;
+typedef struct _Pango2Line Pango2Line;
+typedef struct _Pango2LineIter Pango2LineIter;
+
+/**
+ * PANGO2_SCALE:
+ *
+ * The scale between dimensions used for Pango2 distances and device units.
+ *
+ * The definition of device units is dependent on the output device; it will
+ * typically be pixels for a screen, and points for a printer. %PANGO2_SCALE is
+ * currently 1024, but this may be changed in the future.
+ *
+ * When setting font sizes, device units are always considered to be
+ * points (as in "12 point font"), rather than pixels.
+ */
+/**
+ * PANGO2_PIXELS:
+ * @d: a dimension in Pango2 units.
+ *
+ * Converts a dimension to device units by rounding.
+ *
+ * Return value: rounded dimension in device units.
+ */
+/**
+ * PANGO2_PIXELS_FLOOR:
+ * @d: a dimension in Pango2 units.
+ *
+ * Converts a dimension to device units by flooring.
+ *
+ * Return value: floored dimension in device units.
+ */
+/**
+ * PANGO2_PIXELS_CEIL:
+ * @d: a dimension in Pango2 units.
+ *
+ * Converts a dimension to device units by ceiling.
+ *
+ * Return value: ceiled dimension in device units.
+ */
+#define PANGO2_SCALE 1024
+#define PANGO2_PIXELS(d) (((int)(d) + 512) >> 10)
+#define PANGO2_PIXELS_FLOOR(d) (((int)(d)) >> 10)
+#define PANGO2_PIXELS_CEIL(d) (((int)(d) + 1023) >> 10)
+/* The above expressions are just slightly wrong for floating point d;
+ * For example we'd expect PANGO2_PIXELS(-512.5) => -1 but instead we get 0.
+ * That's unlikely to matter for practical use and the expression is much
+ * more compact and faster than alternatives that work exactly for both
+ * integers and floating point.
+ *
+ * PANGO2_PIXELS also behaves differently for +512 and -512.
+ */
+
+/**
+ * PANGO2_UNITS_FLOOR:
+ * @d: a dimension in Pango2 units.
+ *
+ * Rounds a dimension down to whole device units, but does not
+ * convert it to device units.
+ *
+ * Return value: rounded down dimension in Pango2 units.
+ */
+#define PANGO2_UNITS_FLOOR(d) \
+ ((d) & ~(PANGO2_SCALE - 1))
+
+/**
+ * PANGO2_UNITS_CEIL:
+ * @d: a dimension in Pango2 units.
+ *
+ * Rounds a dimension up to whole device units, but does not
+ * convert it to device units.
+ *
+ * Return value: rounded up dimension in Pango2 units.
+ */
+#define PANGO2_UNITS_CEIL(d) \
+ (((d) + (PANGO2_SCALE - 1)) & ~(PANGO2_SCALE - 1))
+
+/**
+ * PANGO2_UNITS_ROUND:
+ * @d: a dimension in Pango2 units.
+ *
+ * Rounds a dimension to whole device units, but does not
+ * convert it to device units.
+ *
+ * Return value: rounded dimension in Pango2 units.
+ */
+#define PANGO2_UNITS_ROUND(d) \
+ (((d) + (PANGO2_SCALE >> 1)) & ~(PANGO2_SCALE - 1))
+
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_units_from_double (double d) G_GNUC_CONST;
+PANGO2_AVAILABLE_IN_ALL
+double pango2_units_to_double (int i) G_GNUC_CONST;
+
+
+
+/**
+ * Pango2Rectangle:
+ * @x: X coordinate of the left side of the rectangle.
+ * @y: Y coordinate of the the top side of the rectangle.
+ * @width: width of the rectangle.
+ * @height: height of the rectangle.
+ *
+ * The `Pango2Rectangle` structure represents a rectangle.
+ *
+ * `Pango2Rectangle` is frequently used to represent the logical or ink
+ * extents of a single glyph or section of text. (See, for instance,
+ * [method@Pango2.Font.get_glyph_extents].)
+ */
+struct _Pango2Rectangle
+{
+ int x;
+ int y;
+ int width;
+ int height;
+};
+
+/* Macros to translate from extents rectangles to ascent/descent/lbearing/rbearing
+ */
+/**
+ * PANGO2_ASCENT:
+ * @rect: a `Pango2Rectangle`
+ *
+ * Extracts the *ascent* from a `Pango2Rectangle`
+ * representing glyph extents.
+ *
+ * The ascent is the distance from the baseline to the
+ * highest point of the character. This is positive if the
+ * glyph ascends above the baseline.
+ */
+/**
+ * PANGO2_DESCENT:
+ * @rect: a `Pango2Rectangle`
+ *
+ * Extracts the *descent* from a `Pango2Rectangle`
+ * representing glyph extents.
+ *
+ * The descent is the distance from the baseline to the
+ * lowest point of the character. This is positive if the
+ * glyph descends below the baseline.
+ */
+/**
+ * PANGO2_LBEARING:
+ * @rect: a `Pango2Rectangle`
+ *
+ * Extracts the *left bearing* from a `Pango2Rectangle`
+ * representing glyph extents.
+ *
+ * The left bearing is the distance from the horizontal
+ * origin to the farthest left point of the character.
+ * This is positive for characters drawn completely to
+ * the right of the glyph origin.
+ */
+/**
+ * PANGO2_RBEARING:
+ * @rect: a `Pango2Rectangle`
+ *
+ * Extracts the *right bearing* from a `Pango2Rectangle`
+ * representing glyph extents.
+ *
+ * The right bearing is the distance from the horizontal
+ * origin to the farthest right point of the character.
+ * This is positive except for characters drawn completely
+ * to the left of the horizontal origin.
+ */
+#define PANGO2_ASCENT(rect) (-(rect).y)
+#define PANGO2_DESCENT(rect) ((rect).y + (rect).height)
+#define PANGO2_LBEARING(rect) ((rect).x)
+#define PANGO2_RBEARING(rect) ((rect).x + (rect).width)
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_extents_to_pixels (Pango2Rectangle *inclusive,
+ Pango2Rectangle *nearest);
+
+
+#include <pango2/pango-direction.h>
+#include <pango2/pango-gravity.h>
+#include <pango2/pango-language.h>
+#include <pango2/pango-matrix.h>
+#include <pango2/pango-script.h>
+
+/**
+ * Pango2Alignment:
+ * @PANGO2_ALIGN_LEFT: Put all available space on the right
+ * @PANGO2_ALIGN_CENTER: Center the line within the available space
+ * @PANGO2_ALIGN_RIGHT: Put all available space on the left
+ * @PANGO2_ALIGN_NATURAL: Use left or right alignment, depending
+ * on the text direction of the paragraph
+ * @PANGO2_ALIGN_JUSTIFY: Justify the content to fill the available space
+ *
+ * `Pango2Alignment` describes how to align the lines of a `Pango2Layout`
+ * within the available space.
+ */
+typedef enum
+{
+ PANGO2_ALIGN_LEFT,
+ PANGO2_ALIGN_CENTER,
+ PANGO2_ALIGN_RIGHT,
+ PANGO2_ALIGN_NATURAL,
+ PANGO2_ALIGN_JUSTIFY
+} Pango2Alignment;
+
+/**
+ * Pango2WrapMode:
+ * @PANGO2_WRAP_WORD: wrap lines at word boundaries.
+ * @PANGO2_WRAP_CHAR: wrap lines at character boundaries.
+ * @PANGO2_WRAP_WORD_CHAR: wrap lines at word boundaries, but fall back to
+ * character boundaries if there is not enough space for a full word.
+ *
+ * `Pango2WrapMode` describes how to wrap the lines of a `Pango2Layout`
+ * to the desired width.
+ *
+ * For @PANGO2_WRAP_WORD, Pango2 uses break opportunities that are determined
+ * by the Unicode line breaking algorithm. For @PANGO2_WRAP_CHAR, Pango2 allows
+ * breaking at grapheme boundaries that are determined by the Unicode text
+ * segmentation algorithm.
+ */
+typedef enum {
+ PANGO2_WRAP_WORD,
+ PANGO2_WRAP_CHAR,
+ PANGO2_WRAP_WORD_CHAR
+} Pango2WrapMode;
+
+/**
+ * Pango2EllipsizeMode:
+ * @PANGO2_ELLIPSIZE_NONE: No ellipsization
+ * @PANGO2_ELLIPSIZE_START: Omit characters at the start of the text
+ * @PANGO2_ELLIPSIZE_MIDDLE: Omit characters in the middle of the text
+ * @PANGO2_ELLIPSIZE_END: Omit characters at the end of the text
+ *
+ * `Pango2EllipsizeMode` describes what sort of ellipsization
+ * should be applied to text.
+ *
+ * In the ellipsization process characters are removed from the
+ * text in order to make it fit to a given width and replaced
+ * with an ellipsis.
+ */
+typedef enum {
+ PANGO2_ELLIPSIZE_NONE,
+ PANGO2_ELLIPSIZE_START,
+ PANGO2_ELLIPSIZE_MIDDLE,
+ PANGO2_ELLIPSIZE_END
+} Pango2EllipsizeMode;
+
+/**
+* Pango2LeadingTrim:
+ * @PANGO2_LEADING_TRIM_NONE: No trimming
+ * @PANGO2_LEADING_TRIM_START: Trim leading at the top
+ * @PANGO2_LEADING_TRIM_END: Trim leading at the bottom
+ *
+ * The `Pango2LeadingTrim` flags control how the line height affects
+ * the extents of runs and lines.
+ */
+typedef enum
+{
+ PANGO2_LEADING_TRIM_NONE = 0,
+ PANGO2_LEADING_TRIM_START = 1 << 0,
+ PANGO2_LEADING_TRIM_END = 1 << 1,
+} Pango2LeadingTrim;
+
+/**
+ * PANGO2_LEADING_TRIM_BOTH:
+ *
+ * Shorthand for `PANGO2_LEADING_TRIM_START|PANGO2_LEADING_TRIM_END`.
+ */
+#define PANGO2_LEADING_TRIM_BOTH (PANGO2_LEADING_TRIM_START|PANGO2_LEADING_TRIM_END)
+
+/*
+ * PANGO2_DECLARE_INTERNAL_TYPE:
+ * @ModuleObjName: The name of the new type, in camel case (like GtkWidget)
+ * @module_obj_name: The name of the new type in lowercase, with words
+ * separated by '_' (like 'gtk_widget')
+ * @MODULE: The name of the module, in all caps (like 'GTK')
+ * @OBJ_NAME: The bare name of the type, in all caps (like 'WIDGET')
+ * @ParentName: the name of the parent type, in camel case (like GtkWidget)
+ *
+ * A convenience macro for emitting the usual declarations in the
+ * header file for a type which is intended to be subclassed only
+ * by internal consumers.
+ *
+ * This macro differs from %G_DECLARE_DERIVABLE_TYPE and %G_DECLARE_FINAL_TYPE
+ * by declaring a type that is only derivable internally. Internal users can
+ * derive this type, assuming they have access to the instance and class
+ * structures; external users will not be able to subclass this type.
+ */
+#define PANGO2_DECLARE_INTERNAL_TYPE(ModuleObjName, module_obj_name, MODULE, OBJ_NAME, ParentName) \
+ GType module_obj_name##_get_type (void); \
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
+ typedef struct _##ModuleObjName ModuleObjName; \
+ typedef struct _##ModuleObjName##Class ModuleObjName##Class; \
+ \
+ _GLIB_DEFINE_AUTOPTR_CHAINUP (ModuleObjName, ParentName) \
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC (ModuleObjName##Class, g_type_class_unref) \
+ \
+ G_GNUC_UNUSED static inline ModuleObjName * MODULE##_##OBJ_NAME (gpointer ptr) { \
+ return G_TYPE_CHECK_INSTANCE_CAST (ptr, module_obj_name##_get_type (), ModuleObjName); } \
+ G_GNUC_UNUSED static inline ModuleObjName##Class * MODULE##_##OBJ_NAME##_CLASS (gpointer ptr) { \
+ return G_TYPE_CHECK_CLASS_CAST (ptr, module_obj_name##_get_type (), ModuleObjName##Class); } \
+ G_GNUC_UNUSED static inline gboolean MODULE##_IS_##OBJ_NAME (gpointer ptr) { \
+ return G_TYPE_CHECK_INSTANCE_TYPE (ptr, module_obj_name##_get_type ()); } \
+ G_GNUC_UNUSED static inline gboolean MODULE##_IS_##OBJ_NAME##_CLASS (gpointer ptr) { \
+ return G_TYPE_CHECK_CLASS_TYPE (ptr, module_obj_name##_get_type ()); } \
+ G_GNUC_UNUSED static inline ModuleObjName##Class * MODULE##_##OBJ_NAME##_GET_CLASS (gpointer ptr) { \
+ return G_TYPE_INSTANCE_GET_CLASS (ptr, module_obj_name##_get_type (), ModuleObjName##Class); } \
+ G_GNUC_END_IGNORE_DEPRECATIONS
+
+G_END_DECLS
diff --git a/pango2/pango-userface-private.h b/pango2/pango-userface-private.h
new file mode 100644
index 00000000..a8a564fc
--- /dev/null
+++ b/pango2/pango-userface-private.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-userface.h"
+#include "pango-fontmap.h"
+#include "pango-font-face-private.h"
+
+
+struct _Pango2UserFace
+{
+ Pango2FontFace parent_instance;
+
+ char *faceid;
+
+ Pango2UserFaceGetFontInfoFunc font_info_func;
+ Pango2UserFaceUnicodeToGlyphFunc glyph_func;
+ Pango2UserFaceGetGlyphInfoFunc glyph_info_func;
+ Pango2UserFaceTextToGlyphFunc shape_func;
+ Pango2UserFaceRenderGlyphFunc render_func;
+ gpointer user_data;
+ GDestroyNotify destroy;
+};
diff --git a/pango2/pango-userface.c b/pango2/pango-userface.c
new file mode 100644
index 00000000..d4cb60f5
--- /dev/null
+++ b/pango2/pango-userface.c
@@ -0,0 +1,481 @@
+/* Pango2
+ *
+ * Copyright (C) 2022 Matthias Clasen
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "pango-font-face-private.h"
+#include "pango-userface-private.h"
+#include "pango-userfont-private.h"
+#include "pango-utils.h"
+#include "pango-item-private.h"
+#include "pango-impl-utils.h"
+
+#include <string.h>
+#include <hb-ot.h>
+
+/**
+ * Pango2UserFace:
+ *
+ * `Pango2UserFace` is a `Pango2FontFace` implementation that uses callbacks.
+ *
+ * It allows to draw the glyphs in a font using custom code. This can
+ * be used to implement fonts in non-standard formats, but can also be
+ * used by games and other application to draw "funky" fonts.
+ *
+ * To get a font instance at a specific size from a `Pango2UserFace`,
+ * use [ctor@Pango2.UserFont.new].
+ */
+
+/* {{{ Utilities */
+
+static void
+ensure_faceid (Pango2UserFace *self)
+{
+ Pango2FontFace *face = PANGO2_FONT_FACE (self);
+ char *psname;
+ char *p;
+
+ if (self->faceid)
+ return;
+
+ psname = g_strconcat (pango2_font_description_get_family (face->description), "_", face->name, NULL);
+
+ /* PostScript name should not contain problematic chars, but just in case,
+ * make sure we don't have any ' ', '=' or ',' that would give us parsing
+ * problems.
+ */
+ p = psname;
+ while ((p = strpbrk (p, " =,")) != NULL)
+ *p = '?';
+
+ self->faceid = g_strconcat ("user:", psname, NULL);
+
+ g_free (psname);
+}
+
+static const char *
+style_from_font_description (const Pango2FontDescription *desc)
+{
+ Pango2Style style = pango2_font_description_get_style (desc);
+ Pango2Weight weight = pango2_font_description_get_weight (desc);
+
+ switch (style)
+ {
+ case PANGO2_STYLE_ITALIC:
+ if (weight == PANGO2_WEIGHT_BOLD)
+ return "Bold Italic";
+ else
+ return "Italic";
+ break;
+ case PANGO2_STYLE_OBLIQUE:
+ if (weight == PANGO2_WEIGHT_BOLD)
+ return "Bold Oblique";
+ else
+ return "Oblique";
+ break;
+ case PANGO2_STYLE_NORMAL:
+ if (weight == PANGO2_WEIGHT_BOLD)
+ return "Bold";
+ else
+ return "Regular";
+ break;
+ default: ;
+ }
+
+ return NULL;
+}
+
+static gboolean
+default_shape_func (Pango2UserFace *face,
+ int size,
+ const char *text,
+ int length,
+ const Pango2Analysis *analysis,
+ Pango2GlyphString *glyphs,
+ Pango2ShapeFlags flags,
+ gpointer user_data)
+{
+ int n_chars;
+ const char *p;
+ int cluster = 0;
+ int i;
+ int last_cluster;
+ gboolean is_color;
+ hb_glyph_extents_t ext;
+ hb_position_t dummy;
+ hb_font_t *hb_font;
+ hb_font_extents_t font_extents;
+
+ n_chars = g_utf8_strlen (text, length);
+
+ pango2_glyph_string_set_size (glyphs, n_chars);
+
+ last_cluster = -1;
+
+ hb_font = pango2_font_get_hb_font (analysis->font);
+ hb_font_get_h_extents (hb_font, &font_extents);
+
+ p = text;
+ for (i = 0; i < n_chars; i++)
+ {
+ gunichar wc;
+ Pango2Glyph glyph;
+ Pango2Rectangle ink_rect;
+ Pango2Rectangle logical_rect;
+
+ wc = g_utf8_get_char (p);
+
+ if (g_unichar_type (wc) != G_UNICODE_NON_SPACING_MARK)
+ cluster = p - text;
+
+ if (pango2_is_zero_width (wc))
+ glyph = PANGO2_GLYPH_EMPTY;
+ else if (!face->glyph_func (face, wc, &glyph, face->user_data))
+ glyph = PANGO2_GET_UNKNOWN_GLYPH (wc);
+
+ face->glyph_info_func (face, size, glyph, &ext, &dummy, &dummy, &is_color, face->user_data);
+ pango2_font_get_glyph_extents (analysis->font, glyph, &ink_rect, &logical_rect);
+
+ glyphs->glyphs[i].glyph = glyph;
+
+ glyphs->glyphs[i].attr.is_cluster_start = cluster != last_cluster;
+ glyphs->glyphs[i].attr.is_color = is_color;
+
+ if (analysis->gravity == PANGO2_GRAVITY_EAST)
+ {
+ glyphs->glyphs[i].geometry.x_offset = font_extents.ascender;
+ glyphs->glyphs[i].geometry.y_offset = - logical_rect.y - (logical_rect.height - ink_rect.height) / 2;
+ glyphs->glyphs[i].geometry.width = logical_rect.width;
+ }
+ else if (analysis->gravity == PANGO2_GRAVITY_WEST)
+ {
+ glyphs->glyphs[i].geometry.x_offset = font_extents.descender;
+ glyphs->glyphs[i].geometry.y_offset = logical_rect.y + (logical_rect.height - ink_rect.height) / 2;
+ glyphs->glyphs[i].geometry.width = logical_rect.width;
+ }
+ else if (analysis->gravity == PANGO2_GRAVITY_SOUTH)
+ {
+ glyphs->glyphs[i].geometry.x_offset = 0;
+ glyphs->glyphs[i].geometry.y_offset = 0;
+ glyphs->glyphs[i].geometry.width = logical_rect.width;
+ }
+ else if (analysis->gravity == PANGO2_GRAVITY_NORTH)
+ {
+ glyphs->glyphs[i].geometry.x_offset = 0;
+ glyphs->glyphs[i].geometry.y_offset = 0;
+ glyphs->glyphs[i].geometry.width = - logical_rect.width;
+ }
+
+ glyphs->log_clusters[i] = cluster;
+ last_cluster = cluster;
+
+ p = g_utf8_next_char (p);
+ }
+
+ if (analysis->level & 1)
+ pango2_glyph_string_reverse_range (glyphs, 0, glyphs->num_glyphs);
+
+ return TRUE;
+}
+
+static gboolean
+default_render_func (Pango2UserFace *face,
+ int size,
+ hb_codepoint_t glyph,
+ gpointer user_data,
+ const char *backend_id,
+ gpointer backend_data)
+{
+ /* Draw nothing... not very exciting */
+ return TRUE;
+}
+
+/* }}} */
+/* {{{ Pango2FontFace implementation */
+
+struct _Pango2UserFaceClass
+{
+ Pango2FontFaceClass parent_class;
+};
+
+G_DEFINE_FINAL_TYPE (Pango2UserFace, pango2_user_face, PANGO2_TYPE_FONT_FACE)
+
+static void
+pango2_user_face_init (Pango2UserFace *self)
+{
+}
+
+static void
+pango2_user_face_finalize (GObject *object)
+{
+ Pango2UserFace *self = PANGO2_USER_FACE (object);
+
+ g_free (self->faceid);
+ if (self->destroy)
+ self->destroy (self->user_data);
+
+ G_OBJECT_CLASS (pango2_user_face_parent_class)->finalize (object);
+}
+
+static gboolean
+pango2_user_face_is_synthesized (Pango2FontFace *face)
+{
+ return TRUE;
+}
+
+static gboolean
+pango2_user_face_is_monospace (Pango2FontFace *face)
+{
+ return FALSE;
+}
+
+static gboolean
+pango2_user_face_is_variable (Pango2FontFace *face)
+{
+ return FALSE;
+}
+
+static gboolean
+pango2_user_face_has_char (Pango2FontFace *face,
+ gunichar wc)
+{
+ Pango2UserFace *self = PANGO2_USER_FACE (face);
+ hb_codepoint_t glyph;
+
+ return self->glyph_func (self, wc, &glyph, self->user_data);
+}
+
+static const char *
+pango2_user_face_get_faceid (Pango2FontFace *face)
+{
+ Pango2UserFace *self = PANGO2_USER_FACE (face);
+
+ ensure_faceid (self);
+
+ return self->faceid;
+}
+
+static Pango2Font *
+pango2_user_face_create_font (Pango2FontFace *face,
+ const Pango2FontDescription *desc,
+ float dpi,
+ const Pango2Matrix *ctm)
+{
+ Pango2UserFace *self = PANGO2_USER_FACE (face);
+
+ return PANGO2_FONT (pango2_user_font_new_for_description (self, desc, dpi, ctm));
+}
+
+static void
+pango2_user_face_class_init (Pango2UserFaceClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ Pango2FontFaceClass *face_class = PANGO2_FONT_FACE_CLASS (class);
+
+ object_class->finalize = pango2_user_face_finalize;
+
+ face_class->is_synthesized = pango2_user_face_is_synthesized;
+ face_class->is_monospace = pango2_user_face_is_monospace;
+ face_class->is_variable = pango2_user_face_is_variable;
+ face_class->has_char = pango2_user_face_has_char;
+ face_class->get_faceid = pango2_user_face_get_faceid;
+ face_class->create_font = pango2_user_face_create_font;
+}
+
+/* }}} */
+ /* {{{ Public API */
+
+/**
+ * Pango2UserFaceGetFontInfoFunc:
+ * @face: the `Pango2UserFace`
+ * @size: the size of the font that is being created
+ * @extents: (out caller-allocates): return location for font extents
+ * user_data: user data that was passed to [ctor@Pango2.UserFace.new]
+ *
+ * Callback to obtain font extents for user fonts.
+ *
+ * Typically, the `ascender` and `descender` fields in the @extents
+ * struct should both the filled with non-negative values that add
+ * up to @size.
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * Pango2UserFaceUnicodeToGlyphFunc:
+ * @face: the `Pango2UserFace`
+ * @unicode: the Unicode character
+ * @glyph: (out caller-allocates): return location for the glyph that
+ * @user_data: user data that was passed to [ctor@Pango2.UserFace.new]
+ *
+ * Callback to determine if a user font can render a character,
+ * and what glyph it will use.
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * Pango2UserFaceGetGlyphInfoFunc:
+ * @face: the `Pango2UserFace`
+ * @size: the size of the font that is queried
+ * @glyph: the glyph that is being queried
+ * @extents: (out caller-allocates): return location for the glyphs ink rectangle
+ * @h_advance: (out caller-allocates): return location for the h advance
+ * @v_advance: (out caller-allocates): return location for the v advance
+ * @is_color_glyph: (out caller-allocates): return location for information about
+ * whether @glyph has color
+ * @user_data: user data that was passed to [ctor@Pango2.UserFace.new]
+ *
+ * Callback to obtain information about a glyph in a user font.
+ *
+ * The @extents, @h_advance and @v_advance arguments should be filled with
+ * values that are scaled according to @size. Note that @y_bearing will typically
+ * be positive, and @height negative.
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * Pango2UserFaceTextToGlyphFunc:
+ * @face: the `Pango2UserFace`
+ * @size: the size of the font that is used
+ * @text: the text to shape
+ * @length: the length of @text
+ * @analysis: `Pango2Analysis` for @text
+ * @glyphs: (out caller-allocates): the `Pango2GlyphString` to populate
+ * @flags: `Pango2ShapeFlags` to use
+ * @user_data: user data that was pased to [ctor@Pango2.UserFace.new]
+ *
+ * Callback to shape a segment of text with a user font.
+ *
+ * This callback is optional when creating a user font. If it isn't
+ * provided, Pango2 will rely on the `Pango2UserFaceUnicodeToGlyphFunc`
+ * and the `Pango2UserFaceGetGlyphInfo` callback to translate Unicode
+ * characters to glyphs 1-1, and position the glyphs according to their
+ * advance widths.
+ *
+ * If this callback is provided, it replaces all of Pango2's own shaping.
+ * The function can implement ligatures, reordering, and other features
+ * that turn the text-to-glyph mapping into an m-n relationship. The
+ * function is responsible for filling not just the glyphs and their
+ * positions, but also cluster information and glyph attributes in
+ * [struct@Pango2.GlyphVisAttr].
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * Pango2UserFaceRenderGlyphFunc:
+ * @face: the `Pango2UserFace`
+ * @size: the size of the font that is used
+ * @glyph: the glyph that is being queried
+ * @user_data: user data that was pased to [ctor@Pango2.UserFace.new]
+ * @backend_id: a string identifying the [class@Pango2.Renderer] in use
+ * @backend_data: backend-specific data
+ *
+ * Callback to render a glyph with a user font.
+ *
+ * This callback is optional when creating a user font. If it isn't
+ * provided, the font will not produce any visible output.
+ *
+ * The @backend_id identifies the [class@Pango2.Renderer] in use.
+ * Implementations should return `FALSE` for unsupported backends.
+ *
+ * The cairo backend uses the string "cairo" as @backend_id, and
+ * provides a `cairo_t` as @backend_data. The context is set up
+ * to render in `font space`, i.e. The transformation is set up
+ * to map the unit square to @size x @size. If supported, Pango2
+ * uses `cairo_user_font_face_set_render_color_glyph_func` to
+ * allow glyphs to be rendered with colors. For more information,
+ * see the cairo documentation about user fonts.
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * pango2_user_face_new:
+ * @font_info_func: (scope notified): a `Pango2UserFaceGetFontInfoFunc`
+ * @glyph_func: (scope notified): a `Pango2UserFaceUnicodeToGlyphFunc`
+ * @glyph_info_func: (scope notified): a `Pango2UserFaceGetGlyphInfoFunc`
+ * @shape_func: (scope notified) (nullable): a `Pango2UserFaceTextToGlyphFunc`
+ * @render_func: (scope notified) (nullable): a `Pango2UserFaceRenderGlyphFunc`
+ * @user_data: user data that will be assed to the callbacks
+ * @destroy: destroy notify for @user_data
+ * @name: name for the face
+ * @description: `Pango2FontDescription` for the font
+ *
+ * Creates a new user font face.
+ *
+ * A user font face does not rely on font data from a font file,
+ * but instead uses callbacks to determine glyph extents, positions
+ * and rendering.
+ *
+ * If @shape_func is `NULL`, Pango2 will rely on @glyph_func and
+ * @glyph_info_func to find and position a glyph for each character.
+ *
+ * If @render_func is `NULL`, the font will not produce any visible
+ * glyphs.
+ *
+ * Returns: (transfer full): a newly created `Pango2UserFace`
+ */
+Pango2UserFace *
+pango2_user_face_new (Pango2UserFaceGetFontInfoFunc font_info_func,
+ Pango2UserFaceUnicodeToGlyphFunc glyph_func,
+ Pango2UserFaceGetGlyphInfoFunc glyph_info_func,
+ Pango2UserFaceTextToGlyphFunc shape_func,
+ Pango2UserFaceRenderGlyphFunc render_func,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ const char *name,
+ const Pango2FontDescription *description)
+{
+ Pango2UserFace *self;
+ Pango2FontFace *face;
+
+ g_return_val_if_fail (font_info_func != NULL, NULL);
+ g_return_val_if_fail (glyph_func != NULL, NULL);
+ g_return_val_if_fail (glyph_info_func != NULL, NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (description != NULL, NULL);
+
+ self = g_object_new (PANGO2_TYPE_USER_FACE, NULL);
+
+ self->font_info_func = font_info_func;
+ self->glyph_func = glyph_func;
+ self->glyph_info_func = glyph_info_func;
+ self->shape_func = shape_func ? shape_func : default_shape_func;
+ self->render_func = render_func ? render_func : default_render_func;
+ self->user_data = user_data;
+ self->destroy = destroy;
+
+ if (!name)
+ name = style_from_font_description (description);
+
+ face = PANGO2_FONT_FACE (self);
+
+ face->name = g_strdup (name);
+ face->description = pango2_font_description_copy (description);
+
+ return self;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-userface.h b/pango2/pango-userface.h
new file mode 100644
index 00000000..359ded72
--- /dev/null
+++ b/pango2/pango-userface.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-glyph.h>
+#include <pango2/pango-font-face.h>
+
+G_BEGIN_DECLS
+
+typedef struct _Pango2UserFont Pango2UserFont;
+
+#define PANGO2_TYPE_USER_FACE (pango2_user_face_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2UserFace, pango2_user_face, PANGO2, USER_FACE, Pango2FontFace)
+
+typedef gboolean (* Pango2UserFaceGetFontInfoFunc) (Pango2UserFace *face,
+ int size,
+ hb_font_extents_t *extents,
+ gpointer user_data);
+
+typedef gboolean (* Pango2UserFaceUnicodeToGlyphFunc) (Pango2UserFace *face,
+ hb_codepoint_t unicode,
+ hb_codepoint_t *glyph,
+ gpointer user_data);
+
+typedef gboolean (* Pango2UserFaceGetGlyphInfoFunc) (Pango2UserFace *face,
+ int size,
+ hb_codepoint_t glyph,
+ hb_glyph_extents_t *extents,
+ hb_position_t *h_advance,
+ hb_position_t *v_advance,
+ gboolean *is_color_glyph,
+ gpointer user_data);
+
+typedef gboolean (* Pango2UserFaceTextToGlyphFunc) (Pango2UserFace *face,
+ int size,
+ const char *text,
+ int length,
+ const Pango2Analysis *analysis,
+ Pango2GlyphString *glyphs,
+ Pango2ShapeFlags flags,
+ gpointer user_data);
+
+typedef gboolean (* Pango2UserFaceRenderGlyphFunc) (Pango2UserFace *face,
+ int size,
+ hb_codepoint_t glyph,
+ gpointer user_data,
+ const char *backend_id,
+ gpointer backend_data);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2UserFace * pango2_user_face_new (Pango2UserFaceGetFontInfoFunc font_info_func,
+ Pango2UserFaceUnicodeToGlyphFunc glyph_func,
+ Pango2UserFaceGetGlyphInfoFunc glyph_info_func,
+ Pango2UserFaceTextToGlyphFunc shape_func,
+ Pango2UserFaceRenderGlyphFunc render_func,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ const char *name,
+ const Pango2FontDescription *description);
+
+G_END_DECLS
diff --git a/pango2/pango-userfont-private.h b/pango2/pango-userfont-private.h
new file mode 100644
index 00000000..94be5a18
--- /dev/null
+++ b/pango2/pango-userfont-private.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-userfont.h"
+#include "pango-userface.h"
+#include "pango-font-private.h"
+
+
+struct _Pango2UserFont
+{
+ Pango2Font parent_instance;
+};
diff --git a/pango2/pango-userfont.c b/pango2/pango-userfont.c
new file mode 100644
index 00000000..6bd4b20c
--- /dev/null
+++ b/pango2/pango-userfont.c
@@ -0,0 +1,442 @@
+/* Pango2
+ *
+ * Copyright (C) 2021 Matthias Clasen
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "pango-userfont-private.h"
+
+#include "pango-font-private.h"
+#include "pango-font-metrics-private.h"
+#include "pango-userface-private.h"
+#include "pango-hbfamily-private.h"
+#include "pango-impl-utils.h"
+#include "pango-language-set-private.h"
+
+#include <hb-ot.h>
+
+/**
+ * Pango2UserFont:
+ *
+ * `Pango2UserFont` is a `Pango2Font` implementation that uses callbacks.
+ */
+
+/* {{{ Pango2Font implementation */
+
+struct _Pango2UserFontClass
+{
+ Pango2FontClass parent_class;
+};
+
+G_DEFINE_FINAL_TYPE (Pango2UserFont, pango2_user_font, PANGO2_TYPE_FONT)
+
+static void
+pango2_user_font_init (Pango2UserFont *self G_GNUC_UNUSED)
+{
+}
+
+static void
+pango2_user_font_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (pango2_user_font_parent_class)->finalize (object);
+}
+
+static Pango2FontDescription *
+pango2_user_font_describe (Pango2Font *font)
+{
+ Pango2FontDescription *desc;
+
+ desc = pango2_font_face_describe (PANGO2_FONT_FACE (font->face));
+ pango2_font_description_set_gravity (desc, font->gravity);
+ pango2_font_description_set_size (desc, font->size);
+
+ return desc;
+}
+
+static void
+pango2_user_font_get_glyph_extents (Pango2Font *font,
+ Pango2Glyph glyph,
+ Pango2Rectangle *ink_rect,
+ Pango2Rectangle *logical_rect)
+{
+ hb_font_t *hb_font = pango2_font_get_hb_font (font);
+ hb_glyph_extents_t extents;
+ hb_direction_t direction;
+ hb_font_extents_t font_extents;
+
+ direction = PANGO2_GRAVITY_IS_VERTICAL (font->gravity)
+ ? HB_DIRECTION_TTB
+ : HB_DIRECTION_LTR;
+
+ hb_font_get_extents_for_direction (hb_font, direction, &font_extents);
+
+ if (glyph == PANGO2_GLYPH_EMPTY || (glyph & PANGO2_GLYPH_UNKNOWN_FLAG))
+ {
+ if (ink_rect)
+ ink_rect->x = ink_rect->y = ink_rect->width = ink_rect->height = 0;
+
+ if (logical_rect)
+ {
+ logical_rect->x = logical_rect->width = 0;
+ logical_rect->y = - font_extents.ascender;
+ logical_rect->height = font_extents.ascender - font_extents.descender;
+ }
+
+ return;
+ }
+
+ hb_font_get_glyph_extents (hb_font, glyph, &extents);
+
+ if (ink_rect)
+ {
+ Pango2Rectangle r;
+
+ r.x = extents.x_bearing;
+ r.y = - extents.y_bearing;
+ r.width = extents.width;
+ r.height = - extents.height;
+
+ switch (font->gravity)
+ {
+ case PANGO2_GRAVITY_AUTO:
+ case PANGO2_GRAVITY_SOUTH:
+ ink_rect->x = r.x;
+ ink_rect->y = r.y;
+ ink_rect->width = r.width;
+ ink_rect->height = r.height;
+ break;
+ case PANGO2_GRAVITY_NORTH:
+ ink_rect->x = - r.x;
+ ink_rect->y = - r.y;
+ ink_rect->width = - r.width;
+ ink_rect->height = - r.height;
+ break;
+ case PANGO2_GRAVITY_EAST:
+ ink_rect->x = r.y;
+ ink_rect->y = - r.x - r.width;
+ ink_rect->width = r.height;
+ ink_rect->height = r.width;
+ break;
+ case PANGO2_GRAVITY_WEST:
+ ink_rect->x = - r.y - r.height;
+ ink_rect->y = r.x;
+ ink_rect->width = r.height;
+ ink_rect->height = r.width;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (PANGO2_GRAVITY_IS_IMPROPER (font->gravity))
+ {
+ Pango2Matrix matrix = (Pango2Matrix) PANGO2_MATRIX_INIT;
+ pango2_matrix_scale (&matrix, -1, -1);
+ pango2_matrix_transform_rectangle (&matrix, ink_rect);
+ }
+ }
+
+ if (logical_rect)
+ {
+ hb_position_t h_advance;
+ hb_font_extents_t extents;
+
+ h_advance = hb_font_get_glyph_h_advance (hb_font, glyph);
+ hb_font_get_h_extents (hb_font, &extents);
+
+ logical_rect->x = 0;
+ logical_rect->height = extents.ascender - extents.descender;
+
+ switch (font->gravity)
+ {
+ case PANGO2_GRAVITY_AUTO:
+ case PANGO2_GRAVITY_SOUTH:
+ logical_rect->y = - extents.ascender;
+ logical_rect->width = h_advance;
+ break;
+ case PANGO2_GRAVITY_NORTH:
+ logical_rect->y = extents.descender;
+ logical_rect->width = h_advance;
+ break;
+ case PANGO2_GRAVITY_EAST:
+ logical_rect->y = - logical_rect->height / 2;
+ logical_rect->width = logical_rect->height;
+ break;
+ case PANGO2_GRAVITY_WEST:
+ logical_rect->y = - logical_rect->height / 2;
+ logical_rect->width = - logical_rect->height;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (PANGO2_GRAVITY_IS_IMPROPER (font->gravity))
+ {
+ logical_rect->height = - logical_rect->height;
+ logical_rect->y = - logical_rect->y;
+ }
+ }
+}
+
+static Pango2FontMetrics *
+pango2_user_font_get_metrics (Pango2Font *font,
+ Pango2Language *language)
+{
+ hb_font_t *hb_font = pango2_font_get_hb_font (font);
+ Pango2FontMetrics *metrics;
+ hb_font_extents_t extents;
+
+ metrics = pango2_font_metrics_new ();
+
+ hb_font_get_extents_for_direction (hb_font, HB_DIRECTION_LTR, &extents);
+
+ metrics->descent = - extents.descender;
+ metrics->ascent = extents.ascender;
+ metrics->height = extents.ascender - extents.descender + extents.line_gap;
+
+ metrics->underline_thickness = PANGO2_SCALE;
+ metrics->underline_position = - PANGO2_SCALE;
+ metrics->strikethrough_thickness = PANGO2_SCALE;
+ metrics->strikethrough_position = metrics->ascent / 2;
+ metrics->approximate_char_width = 0; /* FIXME */
+ metrics->approximate_digit_width = 0;
+
+ return metrics;
+}
+
+static hb_bool_t
+nominal_glyph_func (hb_font_t *hb_font,
+ void *font_data,
+ hb_codepoint_t unicode,
+ hb_codepoint_t *glyph,
+ void *user_data)
+{
+ Pango2Font *font = font_data;
+ Pango2UserFace *face = PANGO2_USER_FACE (font->face);
+
+ return face->glyph_func (face, unicode, glyph, face->user_data);
+}
+
+static hb_position_t
+glyph_h_advance_func (hb_font_t *hb_font,
+ void *font_data,
+ hb_codepoint_t glyph,
+ void *user_data)
+{
+ Pango2Font *font = font_data;
+ Pango2UserFace *face = PANGO2_USER_FACE (font->face);
+ int size = font->size * font->dpi / 72.;
+ hb_position_t h_advance, v_advance;
+ hb_glyph_extents_t glyph_extents;
+ gboolean is_color;
+
+ face->glyph_info_func (face, size, glyph,
+ &glyph_extents,
+ &h_advance, &v_advance,
+ &is_color,
+ face->user_data);
+
+ return h_advance;
+}
+
+static hb_bool_t
+glyph_extents_func (hb_font_t *hb_font,
+ void *font_data,
+ hb_codepoint_t glyph,
+ hb_glyph_extents_t *extents,
+ void *user_data)
+{
+ Pango2Font *font = font_data;
+ Pango2UserFace *face = PANGO2_USER_FACE (font->face);
+ int size = font->size * font->dpi / 72.;
+ hb_position_t h_advance, v_advance;
+ gboolean is_color;
+
+ face->glyph_info_func (face, size, glyph,
+ extents,
+ &h_advance, &v_advance,
+ &is_color,
+ face->user_data);
+
+ if (PANGO2_GRAVITY_IS_IMPROPER (font->gravity))
+ {
+ extents->x_bearing = - extents->x_bearing;
+ extents->y_bearing = - extents->y_bearing;
+ extents->width = - extents->width;
+ extents->height = - extents->height;
+ }
+
+ return TRUE;
+}
+
+static hb_bool_t
+font_extents_func (hb_font_t *hb_font,
+ void *font_data,
+ hb_font_extents_t *extents,
+ void *user_data)
+{
+ Pango2Font *font = font_data;
+ Pango2UserFace *face = PANGO2_USER_FACE (font->face);
+ int size = font->size * font->dpi / 72.;
+ gboolean ret;
+
+ ret = face->font_info_func (face, size, extents, face->user_data);
+
+ if (PANGO2_GRAVITY_IS_IMPROPER (font->gravity))
+ {
+ extents->ascender = - extents->ascender;
+ extents->descender = - extents->descender;
+ }
+
+ return ret;
+}
+
+static hb_font_t *
+pango2_user_font_create_hb_font (Pango2Font *font)
+{
+ double x_scale, y_scale;
+ int size;
+ hb_blob_t *blob;
+ hb_face_t *hb_face;
+ hb_font_t *hb_font;
+ hb_font_funcs_t *funcs;
+
+ blob = hb_blob_create ("", 0, HB_MEMORY_MODE_READONLY, NULL, NULL);
+ hb_face = hb_face_create (blob, 0);
+ hb_font = hb_font_create (hb_face);
+
+ funcs = hb_font_funcs_create ();
+
+ hb_font_funcs_set_nominal_glyph_func (funcs, nominal_glyph_func, NULL, NULL);
+ hb_font_funcs_set_glyph_h_advance_func (funcs, glyph_h_advance_func, NULL, NULL);
+ hb_font_funcs_set_glyph_extents_func (funcs, glyph_extents_func, NULL, NULL);
+ hb_font_funcs_set_font_h_extents_func (funcs, font_extents_func, NULL, NULL);
+ hb_font_funcs_set_font_v_extents_func (funcs, font_extents_func, NULL, NULL);
+
+ hb_font_set_funcs (hb_font, funcs, font, NULL);
+
+ hb_font_funcs_destroy (funcs);
+ hb_face_destroy (hb_face);
+ hb_blob_destroy (blob);
+
+ size = font->size * font->dpi / 72.f;
+ x_scale = y_scale = 1;
+
+ if (PANGO2_GRAVITY_IS_IMPROPER (font->gravity))
+ {
+ x_scale = - x_scale;
+ y_scale = - y_scale;
+ }
+
+ hb_font_set_scale (hb_font, size * x_scale, size * y_scale);
+ hb_font_set_ptem (hb_font, font->size / PANGO2_SCALE);
+
+ return hb_font;
+}
+
+static void
+pango2_user_font_class_init (Pango2UserFontClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ Pango2FontClass *font_class = PANGO2_FONT_CLASS (class);
+
+ object_class->finalize = pango2_user_font_finalize;
+
+ font_class->describe = pango2_user_font_describe;
+ font_class->get_glyph_extents = pango2_user_font_get_glyph_extents;
+ font_class->get_metrics = pango2_user_font_get_metrics;
+ font_class->create_hb_font = pango2_user_font_create_hb_font;
+}
+
+/* }}} */
+ /* {{{ Public API */
+
+/**
+ * pango2_user_font_new:
+ * @face: the `Pango2UserFace` to use
+ * @size: the desired size in points, scaled by `PANGO2_SCALE`
+ * @gravity: the gravity to use when rendering
+ * @dpi: the dpi used when rendering
+ * @ctm: (nullable): transformation matrix to use when rendering
+ *
+ * Creates a new `Pango2UserFont`.
+ *
+ * Returns: a newly created `Pango2UserFont`
+ */
+Pango2UserFont *
+pango2_user_font_new (Pango2UserFace *face,
+ int size,
+ Pango2Gravity gravity,
+ float dpi,
+ const Pango2Matrix *ctm)
+{
+ Pango2UserFont *self;
+ Pango2Font *font;
+
+ self = g_object_new (PANGO2_TYPE_USER_FONT, NULL);
+
+ font = PANGO2_FONT (self);
+
+ pango2_font_set_face (font, PANGO2_FONT_FACE (face));
+ pango2_font_set_size (font, size);
+ pango2_font_set_dpi (font, dpi);
+ pango2_font_set_gravity (font, gravity);
+ pango2_font_set_ctm (font, ctm);
+
+ return self;
+}
+
+/**
+ * pango2_user_font_new_for_description:
+ * @face: the `Pango2UserFace` to use
+ * @description: a `Pango2FontDescription`
+ * @dpi: the dpi used when rendering
+ * @ctm: (nullable): transformation matrix to use when rendering
+ *
+ * Creates a new `Pango2UserFont` with size and gravity taken
+ * from a font description.
+ *
+ * Returns: a newly created `Pango2HbFont`
+ */
+
+Pango2UserFont *
+pango2_user_font_new_for_description (Pango2UserFace *face,
+ const Pango2FontDescription *description,
+ float dpi,
+ const Pango2Matrix *ctm)
+{
+ int size;
+ Pango2Gravity gravity;
+
+ if (pango2_font_description_get_size_is_absolute (description))
+ size = pango2_font_description_get_size (description) * 72. / dpi;
+ else
+ size = pango2_font_description_get_size (description);
+
+ if ((pango2_font_description_get_set_fields (description) & PANGO2_FONT_MASK_GRAVITY) != 0 &&
+ pango2_font_description_get_gravity (description) != PANGO2_GRAVITY_SOUTH)
+ gravity = pango2_font_description_get_gravity (description);
+ else
+ gravity = PANGO2_GRAVITY_AUTO;
+
+ return pango2_user_font_new (face, size, gravity, dpi, ctm);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-userfont.h b/pango2/pango-userfont.h
new file mode 100644
index 00000000..47b23b01
--- /dev/null
+++ b/pango2/pango-userfont.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-types.h>
+#include <pango2/pango-userface.h>
+#include <hb.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_USER_FONT (pango2_user_font_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2UserFont, pango2_user_font, PANGO2, USER_FONT, Pango2Font)
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2UserFont * pango2_user_font_new (Pango2UserFace *face,
+ int size,
+ Pango2Gravity gravity,
+ float dpi,
+ const Pango2Matrix *ctm);
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2UserFont * pango2_user_font_new_for_description (Pango2UserFace *face,
+ const Pango2FontDescription *description,
+ float dpi,
+ const Pango2Matrix *ctm);
+
+G_END_DECLS
diff --git a/pango2/pango-utils.c b/pango2/pango-utils.c
new file mode 100644
index 00000000..89e20dac
--- /dev/null
+++ b/pango2/pango-utils.c
@@ -0,0 +1,377 @@
+/* Pango2
+ * pango-utils.c: Utilities for internal functions and modules
+ *
+ * Copyright (C) 2000 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.
+ */
+
+#include "config.h"
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <locale.h>
+
+#include "pango-font.h"
+#include "pango-features.h"
+#include "pango-impl-utils.h"
+
+#include <glib/gstdio.h>
+
+#ifndef HAVE_FLOCKFILE
+# define flockfile(f) (void)1
+# define funlockfile(f) (void)1
+# define getc_unlocked(f) getc(f)
+#endif /* !HAVE_FLOCKFILE */
+
+#ifdef G_OS_WIN32
+
+#include <sys/types.h>
+
+#define STRICT
+#include <windows.h>
+
+#endif
+
+/**
+ * pango2_version:
+ *
+ * Returns the encoded version of Pango2 available at run-time.
+ *
+ * This is similar to the macro %PANGO2_VERSION except that the macro
+ * returns the encoded version available at compile-time. A version
+ * number can be encoded into an integer using PANGO2_VERSION_ENCODE().
+ *
+ * Returns: The encoded version of Pango2 library available at run time.
+ */
+int
+pango2_version (void)
+{
+ return PANGO2_VERSION;
+}
+
+/**
+ * pango2_version_string:
+ *
+ * Returns the version of Pango2 available at run-time.
+ *
+ * This is similar to the macro %PANGO2_VERSION_STRING except that the
+ * macro returns the version available at compile-time.
+ *
+ * Returns: A string containing the version of Pango2 library available
+ * at run time. The returned string is owned by Pango2 and should not
+ * be modified or freed.
+ */
+const char *
+pango2_version_string (void)
+{
+ return PANGO2_VERSION_STRING;
+}
+
+/**
+ * pango2_version_check:
+ * @required_major: the required major version
+ * @required_minor: the required minor version
+ * @required_micro: the required major version
+ *
+ * Checks that the Pango2 library in use is compatible with the
+ * given version.
+ *
+ * Generally you would pass in the constants %PANGO2_VERSION_MAJOR,
+ * %PANGO2_VERSION_MINOR, %PANGO2_VERSION_MICRO as the three arguments
+ * to this function; that produces a check that the library in use at
+ * run-time is compatible with the version of Pango2 the application or
+ * module was compiled against.
+ *
+ * Compatibility is defined by two things: first the version
+ * of the running library is newer than the version
+ * @required_major.required_minor.@required_micro. Second
+ * the running library must be binary compatible with the
+ * version @required_major.required_minor.@required_micro
+ * (same major version.)
+ *
+ * For compile-time version checking use PANGO2_VERSION_CHECK().
+ *
+ * Return value: (nullable): %NULL if the Pango2 library is compatible
+ * with the given version, or a string describing the version
+ * mismatch. The returned string is owned by Pango2 and should not
+ * be modified or freed.
+ */
+const char *
+pango2_version_check (int required_major,
+ int required_minor,
+ int required_micro)
+{
+ int pango2_effective_micro = 100 * PANGO2_VERSION_MINOR + PANGO2_VERSION_MICRO;
+ int required_effective_micro = 100 * required_minor + required_micro;
+
+ if (required_major > PANGO2_VERSION_MAJOR)
+ return "Pango2 version too old (major mismatch)";
+ if (required_major < PANGO2_VERSION_MAJOR)
+ return "Pango2 version too new (major mismatch)";
+ if (required_effective_micro < pango2_effective_micro - PANGO2_BINARY_AGE)
+ return "Pango2 version too new (micro mismatch)";
+ if (required_effective_micro > pango2_effective_micro)
+ return "Pango2 version too old (micro mismatch)";
+ return NULL;
+}
+
+/**
+ * pango2_is_zero_width:
+ * @ch: a Unicode character
+ *
+ * Checks if a character that should not be normally rendered.
+ *
+ * This includes all Unicode characters with "ZERO WIDTH" in their name,
+ * as well as *bidi* formatting characters, and a few other ones.
+ *
+ * This is totally different from [func@GLib.unichar_iszerowidth] and is at best misnamed.
+ *
+ * Return value: %TRUE if @ch is a zero-width character, %FALSE otherwise
+ */
+gboolean
+pango2_is_zero_width (gunichar ch)
+{
+/* Zero Width characters:
+ *
+ * 00AD SOFT HYPHEN
+ * 034F COMBINING GRAPHEME JOINER
+ *
+ * 200B ZERO WIDTH SPACE
+ * 200C ZERO WIDTH NON-JOINER
+ * 200D ZERO WIDTH JOINER
+ * 200E LEFT-TO-RIGHT MARK
+ * 200F RIGHT-TO-LEFT MARK
+ *
+ * 2028 LINE SEPARATOR
+ *
+ * 2060 WORD JOINER
+ * 2061 FUNCTION APPLICATION
+ * 2062 INVISIBLE TIMES
+ * 2063 INVISIBLE SEPARATOR
+ *
+ * 2066 LEFT-TO-RIGHT ISOLATE
+ * 2067 RIGHT-TO-LEFT ISOLATE
+ * 2068 FIRST STRONG ISOLATE
+ * 2069 POP DIRECTIONAL ISOLATE
+ *
+ * 202A LEFT-TO-RIGHT EMBEDDING
+ * 202B RIGHT-TO-LEFT EMBEDDING
+ * 202C POP DIRECTIONAL FORMATTING
+ * 202D LEFT-TO-RIGHT OVERRIDE
+ * 202E RIGHT-TO-LEFT OVERRIDE
+ *
+ * FEFF ZERO WIDTH NO-BREAK SPACE
+ */
+ return ((ch & ~(gunichar)0x007F) == 0x2000 && (
+ (ch >= 0x200B && ch <= 0x200F) ||
+ (ch >= 0x202A && ch <= 0x202E) ||
+ (ch >= 0x2060 && ch <= 0x2063) ||
+ (ch >= 0x2066 && ch <= 0x2069) ||
+ (ch == 0x2028)
+ )) || G_UNLIKELY (ch == 0x00AD
+ || ch == 0x034F
+ || ch == 0xFEFF);
+}
+
+/**
+ * pango2_units_from_double:
+ * @d: double floating-point value
+ *
+ * Converts a floating-point number to Pango2 units.
+ *
+ * The conversion is done by multiplying @d by %PANGO2_SCALE and
+ * rounding the result to nearest integer.
+ *
+ * Return value: the value in Pango2 units.
+ */
+int
+pango2_units_from_double (double d)
+{
+ return (int)floor (d * PANGO2_SCALE + 0.5);
+}
+
+/**
+ * pango2_units_to_double:
+ * @i: value in Pango2 units
+ *
+ * Converts a number in Pango2 units to floating-point.
+ *
+ * The conversion is done by dividing @i by %PANGO2_SCALE.
+ *
+ * Return value: the double value.
+ */
+double
+pango2_units_to_double (int i)
+{
+ return (double)i / PANGO2_SCALE;
+}
+
+/**
+ * pango2_extents_to_pixels:
+ * @inclusive: (nullable): rectangle to round to pixels inclusively
+ * @nearest: (nullable): rectangle to round to nearest pixels
+ *
+ * Converts extents from Pango2 units to device units.
+ *
+ * The conversion is done by dividing by the %PANGO2_SCALE factor and
+ * performing rounding.
+ *
+ * The @inclusive rectangle is converted by flooring the x/y coordinates
+ * and extending width/height, such that the final rectangle completely
+ * includes the original rectangle.
+ *
+ * The @nearest rectangle is converted by rounding the coordinates
+ * of the rectangle to the nearest device unit (pixel).
+ *
+ * The rule to which argument to use is: if you want the resulting device-space
+ * rectangle to completely contain the original rectangle, pass it in as
+ * @inclusive. If you want two touching-but-not-overlapping rectangles stay
+ * touching-but-not-overlapping after rounding to device units, pass them in
+ * as @nearest.
+ */
+void
+pango2_extents_to_pixels (Pango2Rectangle *inclusive,
+ Pango2Rectangle *nearest)
+{
+ if (inclusive)
+ {
+ int orig_x = inclusive->x;
+ int orig_y = inclusive->y;
+
+ inclusive->x = PANGO2_PIXELS_FLOOR (inclusive->x);
+ inclusive->y = PANGO2_PIXELS_FLOOR (inclusive->y);
+
+ inclusive->width = PANGO2_PIXELS_CEIL (orig_x + inclusive->width ) - inclusive->x;
+ inclusive->height = PANGO2_PIXELS_CEIL (orig_y + inclusive->height) - inclusive->y;
+ }
+
+ if (nearest)
+ {
+ int orig_x = nearest->x;
+ int orig_y = nearest->y;
+
+ nearest->x = PANGO2_PIXELS (nearest->x);
+ nearest->y = PANGO2_PIXELS (nearest->y);
+
+ nearest->width = PANGO2_PIXELS (orig_x + nearest->width ) - nearest->x;
+ nearest->height = PANGO2_PIXELS (orig_y + nearest->height) - nearest->y;
+ }
+}
+
+/**
+ * pango2_find_paragraph_boundary:
+ * @text: UTF-8 text
+ * @length: length of @text in bytes, or -1 if nul-terminated
+ * @paragraph_delimiter_index: (out): return location for index of
+ * delimiter
+ * @next_paragraph_start: (out): return location for start of next
+ * paragraph
+ *
+ * Locates a paragraph boundary in @text.
+ *
+ * A boundary is caused by delimiter characters, such as
+ * a newline, carriage return, carriage return-newline pair,
+ * or Unicode paragraph separator character.
+ *
+ * The index of the run of delimiters is returned in
+ * @paragraph_delimiter_index. The index of the start of the
+ * next paragraph (index after all delimiters) is stored n
+ * @next_paragraph_start.
+ *
+ * If no delimiters are found, both @paragraph_delimiter_index
+ * and @next_paragraph_start are filled with the length of @text
+ * (an index one off the end).
+ */
+void
+pango2_find_paragraph_boundary (const char *text,
+ int length,
+ int *paragraph_delimiter_index,
+ int *next_paragraph_start)
+{
+ const char *p = text;
+ const char *end;
+ const char *start = NULL;
+ const char *delimiter = NULL;
+
+ /* Only one character has type G_UNICODE_PARAGRAPH_SEPARATOR in
+ * Unicode 5.0; update the following code if that changes.
+ */
+
+ /* prev_sep is the first byte of the previous separator. Since
+ * the valid separators are \r, \n, and PARAGRAPH_SEPARATOR, the
+ * first byte is enough to identify it.
+ */
+ char prev_sep;
+
+#define PARAGRAPH_SEPARATOR_STRING "\xE2\x80\xA9"
+
+ if (length < 0)
+ length = strlen (text);
+
+ end = text + length;
+
+ if (paragraph_delimiter_index)
+ *paragraph_delimiter_index = length;
+
+ if (next_paragraph_start)
+ *next_paragraph_start = length;
+
+ if (length == 0)
+ return;
+
+ prev_sep = 0;
+ while (p < end)
+ {
+ if (prev_sep == '\n' ||
+ prev_sep == PARAGRAPH_SEPARATOR_STRING[0])
+ {
+ g_assert (delimiter);
+ start = p;
+ break;
+ }
+ else if (prev_sep == '\r')
+ {
+ /* don't break between \r and \n */
+ if (*p != '\n')
+ {
+ g_assert (delimiter);
+ start = p;
+ break;
+ }
+ }
+
+ if (*p == '\n' ||
+ *p == '\r' ||
+ !strncmp(p, PARAGRAPH_SEPARATOR_STRING, strlen (PARAGRAPH_SEPARATOR_STRING)))
+ {
+ if (delimiter == NULL)
+ delimiter = p;
+ prev_sep = *p;
+ }
+ else
+ prev_sep = 0;
+
+ p = g_utf8_next_char (p);
+ }
+
+ if (delimiter && paragraph_delimiter_index)
+ *paragraph_delimiter_index = delimiter - text;
+
+ if (start && next_paragraph_start)
+ *next_paragraph_start = start - text;
+}
diff --git a/pango2/pango-utils.h b/pango2/pango-utils.h
new file mode 100644
index 00000000..f5187dc9
--- /dev/null
+++ b/pango2/pango-utils.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2000 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <glib.h>
+#include <pango2/pango-font.h>
+
+G_BEGIN_DECLS
+
+PANGO2_AVAILABLE_IN_ALL
+gboolean pango2_is_zero_width (gunichar ch) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_find_paragraph_boundary (const char *text,
+ int length,
+ int *paragraph_delimiter_index,
+ int *next_paragraph_start);
+
+/**
+ * PANGO2_RENDERING_CAIRO:
+ *
+ * Defined to 1 at compile time if Pango2 was built with cairo support.
+ */
+
+/* Encode a Pango2 version as an integer */
+/* Pango2 version checking */
+
+/**
+ * PANGO2_VERSION_ENCODE:
+ * @major: the major component of the version number
+ * @minor: the minor component of the version number
+ * @micro: the micro component of the version number
+ *
+ * This macro encodes the given Pango2 version into an integer. The numbers
+ * returned by %PANGO2_VERSION and pango2_version() are encoded using this macro.
+ * Two encoded version numbers can be compared as integers.
+ */
+#define PANGO2_VERSION_ENCODE(major, minor, micro) ( \
+ ((major) * 10000) \
+ + ((minor) * 100) \
+ + ((micro) * 1))
+
+/* Encoded version of Pango2 at compile-time */
+/**
+ * PANGO2_VERSION:
+ *
+ * The version of Pango2 available at compile-time, encoded using PANGO2_VERSION_ENCODE().
+ */
+/**
+ * PANGO2_VERSION_STRING:
+ *
+ * A string literal containing the version of Pango2 available at compile-time.
+ */
+/**
+ * PANGO2_VERSION_MAJOR:
+ *
+ * The major component of the version of Pango2 available at compile-time.
+ */
+/**
+ * PANGO2_VERSION_MINOR:
+ *
+ * The minor component of the version of Pango2 available at compile-time.
+ */
+/**
+ * PANGO2_VERSION_MICRO:
+ *
+ * The micro component of the version of Pango2 available at compile-time.
+ */
+#define PANGO2_VERSION PANGO2_VERSION_ENCODE( \
+ PANGO2_VERSION_MAJOR, \
+ PANGO2_VERSION_MINOR, \
+ PANGO2_VERSION_MICRO)
+
+/* Check that compile-time Pango2 is as new as required */
+/**
+ * PANGO2_VERSION_CHECK:
+ * @major: the major component of the version number
+ * @minor: the minor component of the version number
+ * @micro: the micro component of the version number
+ *
+ * Checks that the version of Pango2 available at compile-time is not older than
+ * the provided version number.
+ */
+#define PANGO2_VERSION_CHECK(major,minor,micro) \
+ (PANGO2_VERSION >= PANGO2_VERSION_ENCODE(major,minor,micro))
+
+PANGO2_AVAILABLE_IN_ALL
+int pango2_version (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_version_string (void) G_GNUC_CONST;
+
+PANGO2_AVAILABLE_IN_ALL
+const char * pango2_version_check (int required_major,
+ int required_minor,
+ int required_micro) G_GNUC_CONST;
+
+G_END_DECLS
diff --git a/pango2/pango-version-macros.h b/pango2/pango-version-macros.h
new file mode 100644
index 00000000..bce4e314
--- /dev/null
+++ b/pango2/pango-version-macros.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 1999 Red Hat Software
+ * Copyright (C) 2012 Ryan Lortie, Matthias Clasen and Emmanuele Bassi
+ * Copyright (C) 2016 Chun-wei Fan
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * 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 Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-features.h>
+
+#include <glib.h>
+
+#ifndef _PANGO2_EXTERN
+#define _PANGO2_EXTERN extern
+#endif
+
+#define PANGO2_AVAILABLE_IN_ALL _PANGO2_EXTERN
+
+/* XXX: Every new stable minor release bump should add a macro here */
+
+/**
+ * PANGO2_VERSION_0_90
+ *
+ * A macro that evaluates to the 0.90 version of Pango2, in a format
+ * that can be used by the C pre-processor.
+ */
+#define PANGO2_VERSION_0_90 (G_ENCODE_VERSION (0, 90))
+
+/**
+ * PANGO2_VERSION_1_0
+ *
+ * A macro that evaluates to the 1.0 version of Pango2, in a format
+ * that can be used by the C pre-processor.
+ */
+#define PANGO2_VERSION_1_0 (G_ENCODE_VERSION (1, 0))
+
+/* evaluates to the current stable version; for development cycles,
+ * this means the next stable target
+ */
+#if (PANGO2_VERSION_MINOR % 2)
+#define PANGO2_VERSION_CUR_STABLE (G_ENCODE_VERSION (PANGO2_VERSION_MAJOR, PANGO2_VERSION_MINOR + 1))
+#else
+#define PANGO2_VERSION_CUR_STABLE (G_ENCODE_VERSION (PANGO2_VERSION_MAJOR, PANGO2_VERSION_MINOR))
+#endif
+
+/* evaluates to the previous stable version */
+#if (PANGO2_VERSION_MINOR % 2)
+#define PANGO2_VERSION_PREV_STABLE (G_ENCODE_VERSION (PANGO2_VERSION_MAJOR, PANGO2_VERSION_MINOR - 1))
+#else
+#define PANGO2_VERSION_PREV_STABLE (G_ENCODE_VERSION (PANGO2_VERSION_MAJOR, PANGO2_VERSION_MINOR - 2))
+#endif
+
+/**
+ * PANGO2_VERSION_MIN_REQUIRED:
+ *
+ * A macro that should be defined by the user prior to including
+ * the pango.h header.
+ * The definition should be one of the predefined Pango2 version
+ * macros: %PANGO2_VERSION_2_0,…
+ *
+ * This macro defines the earliest version of Pango2 that the package is
+ * required to be able to compile against.
+ *
+ * If the compiler is configured to warn about the use of deprecated
+ * functions, then using functions that were deprecated in version
+ * %PANGO2_VERSION_MIN_REQUIRED or earlier will cause warnings (but
+ * using functions deprecated in later releases will not).
+ */
+/* If the package sets PANGO2_VERSION_MIN_REQUIRED to some future
+ * PANGO2_VERSION_X_Y value that we don't know about, it will compare as
+ * 0 in preprocessor tests.
+ */
+#ifndef PANGO2_VERSION_MIN_REQUIRED
+# define PANGO2_VERSION_MIN_REQUIRED (PANGO2_VERSION_CUR_STABLE)
+#elif PANGO2_VERSION_MIN_REQUIRED == 0
+# undef PANGO2_VERSION_MIN_REQUIRED
+# define PANGO2_VERSION_MIN_REQUIRED (PANGO2_VERSION_CUR_STABLE + 2)
+#endif
+
+/**
+ * PANGO2_VERSION_MAX_ALLOWED:
+ *
+ * A macro that should be defined by the user prior to including
+ * the glib.h header.
+ * The definition should be one of the predefined Pango2 version
+ * macros: %PANGO2_VERSION_2_0,…
+ *
+ * This macro defines the latest version of the Pango2 API that the
+ * package is allowed to make use of.
+ *
+ * If the compiler is configured to warn about the use of deprecated
+ * functions, then using functions added after version
+ * %PANGO2_VERSION_MAX_ALLOWED will cause warnings.
+ *
+ * Unless you are using PANGO2_VERSION_CHECK() or the like to compile
+ * different code depending on the Pango2 version, then this should be
+ * set to the same value as %PANGO2_VERSION_MIN_REQUIRED.
+ */
+#if !defined (PANGO2_VERSION_MAX_ALLOWED) || (PANGO2_VERSION_MAX_ALLOWED == 0)
+# undef PANGO2_VERSION_MAX_ALLOWED
+# define PANGO2_VERSION_MAX_ALLOWED (PANGO2_VERSION_CUR_STABLE)
+#endif
+
+/* sanity checks */
+#if PANGO2_VERSION_MIN_REQUIRED > PANGO2_VERSION_CUR_STABLE
+#error "PANGO2_VERSION_MIN_REQUIRED must be <= PANGO2_VERSION_CUR_STABLE"
+#endif
+#if PANGO2_VERSION_MAX_ALLOWED < PANGO2_VERSION_MIN_REQUIRED
+#error "PANGO2_VERSION_MAX_ALLOWED must be >= PANGO2_VERSION_MIN_REQUIRED"
+#endif
+#if PANGO2_VERSION_MIN_REQUIRED < PANGO2_VERSION_0_90
+#error "PANGO2_VERSION_MIN_REQUIRED must be >= PANGO2_VERSION_0_90"
+#endif
+
+/* These macros are used to mark deprecated functions in Pango2 headers,
+ * and thus have to be exposed in installed headers.
+ */
+#ifdef PANGO2_DISABLE_DEPRECATION_WARNINGS
+# define PANGO2_DEPRECATED _PANGO2_EXTERN
+# define PANGO2_DEPRECATED_FOR(f) _PANGO2_EXTERN
+# define PANGO2_UNAVAILABLE(maj,min) _PANGO2_EXTERN
+#else
+# define PANGO2_DEPRECATED G_DEPRECATED _PANGO2_EXTERN
+# define PANGO2_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) _PANGO2_EXTERN
+# define PANGO2_UNAVAILABLE(maj,min) G_UNAVAILABLE(maj,min) _PANGO2_EXTERN
+#endif
+
+/* XXX: Every new stable minor release should add a set of macros here */
+
+#if PANGO2_VERSION_MIN_REQUIRED >= PANGO2_VERSION_1_0
+# define PANGO2_DEPRECATED_IN_1_0 PANGO2_DEPRECATED
+# define PANGO2_DEPRECATED_IN_1_0_FOR(f) PANGO2_DEPRECATED_FOR(f)
+#else
+# define PANGO2_DEPRECATED_IN_1_0 _PANGO2_EXTERN
+# define PANGO2_DEPRECATED_IN_1_0_FOR(f) _PANGO2_EXTERN
+#endif
+
+#if PANGO2_VERSION_MAX_ALLOWED < PANGO2_VERSION_1_0
+# define PANGO2_AVAILABLE_IN_1_0 PANGO2_UNAVAILABLE(2, 0)
+#else
+# define PANGO2_AVAILABLE_IN_1_0 _PANGO2_EXTERN
+#endif
diff --git a/pango2/pango.h b/pango2/pango.h
new file mode 100644
index 00000000..f3486c4d
--- /dev/null
+++ b/pango2/pango.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 1999 Red Hat Software
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango-attr.h>
+#include <pango2/pango-attr-list.h>
+#include <pango2/pango-attr-iterator.h>
+#include <pango2/pango-attributes.h>
+#include <pango2/pango-break.h>
+#include <pango2/pango-color.h>
+#include <pango2/pango-context.h>
+#include <pango2/pango-direction.h>
+#include <pango2/pango-enum-types.h>
+#include <pango2/pango-features.h>
+#include <pango2/pango-font.h>
+#include <pango2/pango-font-description.h>
+#include <pango2/pango-font-face.h>
+#include <pango2/pango-font-family.h>
+#include <pango2/pango-font-metrics.h>
+#include <pango2/pango-fontmap.h>
+#include <pango2/pango-fontset.h>
+#include <pango2/pango-generic-family.h>
+#include <pango2/pango-glyph.h>
+#include <pango2/pango-gravity.h>
+#include <pango2/pango-hbface.h>
+#include <pango2/pango-hbfont.h>
+#include <pango2/pango-item.h>
+#include <pango2/pango-language.h>
+#include <pango2/pango-layout.h>
+#include <pango2/pango-line.h>
+#include <pango2/pango-line-breaker.h>
+#include <pango2/pango-line-iter.h>
+#include <pango2/pango-lines.h>
+#include <pango2/pango-markup.h>
+#include <pango2/pango-matrix.h>
+#include <pango2/pango-renderer.h>
+#include <pango2/pango-run.h>
+#include <pango2/pango-script.h>
+#include <pango2/pango-tabs.h>
+#include <pango2/pango-types.h>
+#include <pango2/pango-userface.h>
+#include <pango2/pango-userfont.h>
+#include <pango2/pango-utils.h>
+#include <pango2/pango-version-macros.h>
diff --git a/pango2/pango.rc.in b/pango2/pango.rc.in
new file mode 100644
index 00000000..3af81445
--- /dev/null
+++ b/pango2/pango.rc.in
@@ -0,0 +1,30 @@
+#include <winver.h>
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION @PANGO_VERSION_MAJOR@,@PANGO_VERSION_MINOR@,@PANGO_VERSION_MICRO@,0
+ PRODUCTVERSION @PANGO_VERSION_MAJOR@,@PANGO_VERSION_MINOR@,@PANGO_VERSION_MICRO@,0
+ FILEFLAGSMASK 0
+ FILEFLAGS 0
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+ BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904B0"
+ BEGIN
+ VALUE "CompanyName", "Red Hat Software"
+ VALUE "FileDescription", "Pango"
+ VALUE "FileVersion", "@PANGO_VERSION_MAJOR@.@PANGO_VERSION_MINOR@.@PANGO_VERSION_MICRO@.0"
+ VALUE "InternalName", "pango-@PANGO_API_VERSION@-@PANGO_CURRENT_MINUS_AGE@"
+ VALUE "LegalCopyright", "Copyright 1999 Red Hat Software."
+ VALUE "OriginalFilename", "pango-@PANGO_API_VERSION@-@PANGO_CURRENT_MINUS_AGE@.dll"
+ VALUE "ProductName", "Pango"
+ VALUE "ProductVersion", "@PANGO_VERSION_MAJOR@.@PANGO_VERSION_MINOR@.@PANGO_VERSION_MICRO@"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+ END
diff --git a/pango2/pangocairo-context.c b/pango2/pangocairo-context.c
new file mode 100644
index 00000000..2346b635
--- /dev/null
+++ b/pango2/pangocairo-context.c
@@ -0,0 +1,274 @@
+/* Pango2
+ * pangocairo-context.c: Cairo context handling
+ *
+ * Copyright (C) 2000-2005 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.
+ */
+
+#include "config.h"
+
+#include "pangocairo.h"
+#include "pangocairo-private.h"
+#include "pango-impl-utils.h"
+#include "pango-context-private.h"
+
+#include <string.h>
+
+/**
+ * pango2_cairo_update_context:
+ * @cr: a Cairo context
+ * @context: a `Pango2Context`, from a pangocairo font map
+ *
+ * Updates a `Pango2Context` previously created for use with Cairo to
+ * match the current transformation and target surface of a Cairo
+ * context.
+ *
+ * If any layouts have been created for the context, it's necessary
+ * to call [method@Pango2.Layout.context_changed] on those layouts.
+ */
+void
+pango2_cairo_update_context (cairo_t *cr,
+ Pango2Context *context)
+{
+ cairo_matrix_t cairo_matrix;
+ cairo_surface_t *target;
+ Pango2Matrix pango2_matrix;
+ const Pango2Matrix *current_matrix, identity_matrix = PANGO2_MATRIX_INIT;
+ const cairo_font_options_t *merged_options;
+ cairo_font_options_t *old_merged_options;
+ gboolean changed = FALSE;
+
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (PANGO2_IS_CONTEXT (context));
+
+ target = cairo_get_target (cr);
+
+ if (!context->surface_options)
+ context->surface_options = cairo_font_options_create ();
+ cairo_surface_get_font_options (target, context->surface_options);
+ if (!context->set_options_explicit)
+ {
+ if (!context->set_options)
+ context->set_options = cairo_font_options_create ();
+ cairo_get_font_options (cr, context->set_options);
+ }
+
+ old_merged_options = context->merged_options;
+ context->merged_options = NULL;
+
+ merged_options = pango2_cairo_context_get_merged_font_options (context);
+
+ if (old_merged_options)
+ {
+ if (!cairo_font_options_equal (merged_options, old_merged_options))
+ changed = TRUE;
+ cairo_font_options_destroy (old_merged_options);
+ old_merged_options = NULL;
+ }
+ else
+ changed = TRUE;
+
+ cairo_get_matrix (cr, &cairo_matrix);
+ pango2_matrix.xx = cairo_matrix.xx;
+ pango2_matrix.yx = cairo_matrix.yx;
+ pango2_matrix.xy = cairo_matrix.xy;
+ pango2_matrix.yy = cairo_matrix.yy;
+ pango2_matrix.x0 = 0;
+ pango2_matrix.y0 = 0;
+
+ current_matrix = pango2_context_get_matrix (context);
+ if (!current_matrix)
+ current_matrix = &identity_matrix;
+
+ /* layout is matrix-independent if metrics-hinting is off.
+ * also ignore matrix translation offsets
+ */
+ if ((cairo_font_options_get_hint_metrics (merged_options) != CAIRO_HINT_METRICS_OFF) &&
+ (0 != memcmp (&pango2_matrix, current_matrix, sizeof (Pango2Matrix))))
+ changed = TRUE;
+
+ pango2_context_set_matrix (context, &pango2_matrix);
+
+ if (changed)
+ pango2_context_changed (context);
+}
+
+/**
+ * pango2_cairo_context_set_font_options:
+ * @context: a `Pango2Context`, from a pangocairo font map
+ * @options: (nullable): a `cairo_font_options_t`, or %NULL to unset
+ * any previously set options. A copy is made.
+ *
+ * Sets the font options used when rendering text with this context.
+ *
+ * These options override any options that [func@Pango2.cairo_update_context]
+ * derives from the target surface.
+ */
+void
+pango2_cairo_context_set_font_options (Pango2Context *context,
+ const cairo_font_options_t *options)
+{
+ g_return_if_fail (PANGO2_IS_CONTEXT (context));
+
+ if (!context->set_options && !options)
+ return;
+
+ if (context->set_options && options &&
+ cairo_font_options_equal (context->set_options, options))
+ return;
+
+ if (context->set_options || options)
+ pango2_context_changed (context);
+
+ if (context->set_options)
+ cairo_font_options_destroy (context->set_options);
+
+ if (options)
+ {
+ context->set_options = cairo_font_options_copy (options);
+ context->set_options_explicit = TRUE;
+ }
+ else
+ {
+ context->set_options = NULL;
+ context->set_options_explicit = FALSE;
+ }
+
+ if (context->merged_options)
+ {
+ cairo_font_options_destroy (context->merged_options);
+ context->merged_options = NULL;
+ }
+}
+
+/**
+ * pango2_cairo_context_get_font_options:
+ * @context: a `Pango2Context`, from a pangocairo font map
+ *
+ * Retrieves any font rendering options previously set with
+ * [func@Pango2.cairo_context_set_font_options].
+ *
+ * This function does not report options that are derived from
+ * the target surface by [func@Pango2.cairo_update_context].
+ *
+ * Return value: (nullable): the font options previously set on the
+ * context, or %NULL if no options have been set. This value is
+ * owned by the context and must not be modified or freed.
+ */
+const cairo_font_options_t *
+pango2_cairo_context_get_font_options (Pango2Context *context)
+{
+ g_return_val_if_fail (PANGO2_IS_CONTEXT (context), NULL);
+
+ return context->set_options;
+}
+
+const cairo_font_options_t *
+pango2_cairo_context_get_merged_font_options (Pango2Context *context)
+{
+ if (!context->merged_options)
+ {
+ context->merged_options = cairo_font_options_create ();
+
+ if (context->surface_options)
+ cairo_font_options_merge (context->merged_options, context->surface_options);
+ if (context->set_options)
+ cairo_font_options_merge (context->merged_options, context->set_options);
+ }
+
+ return context->merged_options;
+}
+
+/**
+ * pango2_cairo_create_context:
+ * @cr: a Cairo context
+ *
+ * Creates a context object set up to match the current transformation
+ * and target surface of the Cairo context.
+ *
+ * This context can then be
+ * used to create a layout using [ctor@Pango2.Layout.new].
+ *
+ * This function is a convenience function that creates a context
+ * using the default font map, then updates it to @cr.
+ *
+ * Return value: (transfer full): the newly created `Pango2Context`
+ */
+Pango2Context *
+pango2_cairo_create_context (cairo_t *cr)
+{
+ Pango2Context *context;
+
+ g_return_val_if_fail (cr != NULL, NULL);
+
+ context = pango2_context_new ();
+ pango2_cairo_update_context (cr, context);
+
+ return context;
+}
+
+/**
+ * pango2_cairo_update_layout:
+ * @cr: a Cairo context
+ * @layout: a `Pango2Layout`
+ *
+ * Updates the private `Pango2Context` of a `Pango2Layout` to match
+ * the current transformation and target surface of a Cairo context.
+ */
+void
+pango2_cairo_update_layout (cairo_t *cr,
+ Pango2Layout *layout)
+{
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ pango2_cairo_update_context (cr, pango2_layout_get_context (layout));
+}
+
+/**
+ * pango2_cairo_create_layout:
+ * @cr: a Cairo context
+ *
+ * Creates a layout object set up to match the current transformation
+ * and target surface of the Cairo context.
+ *
+ * This layout can then be used for text measurement with functions
+ * like [method@Pango2.Lines.get_size] or drawing with functions like
+ * [func@Pango2.cairo_show_layout]. If you change the transformation or target
+ * surface for @cr, you need to call [func@Pango2.cairo_update_layout].
+ *
+ * This function is the most convenient way to use Cairo with Pango2,
+ * however it is slightly inefficient since it creates a separate
+ * `Pango2Context` object for each layout. This might matter in an
+ * application that was laying out large amounts of text.
+ *
+ * Return value: (transfer full): the newly created `Pango2Layout`
+ */
+Pango2Layout *
+pango2_cairo_create_layout (cairo_t *cr)
+{
+ Pango2Context *context;
+ Pango2Layout *layout;
+
+ g_return_val_if_fail (cr != NULL, NULL);
+
+ context = pango2_cairo_create_context (cr);
+ layout = pango2_layout_new (context);
+ g_object_unref (context);
+
+ return layout;
+}
diff --git a/pango2/pangocairo-context.h b/pango2/pangocairo-context.h
new file mode 100644
index 00000000..5532beab
--- /dev/null
+++ b/pango2/pangocairo-context.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 1999, 2004 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango.h>
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2Context * pango2_cairo_create_context (cairo_t *cr);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_update_context (cairo_t *cr,
+ Pango2Context *context);
+PANGO2_AVAILABLE_IN_ALL
+Pango2Layout * pango2_cairo_create_layout (cairo_t *cr);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_update_layout (cairo_t *cr,
+ Pango2Layout *layout);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_context_set_font_options (Pango2Context *context,
+ const cairo_font_options_t *options);
+PANGO2_AVAILABLE_IN_ALL
+const cairo_font_options_t *
+ pango2_cairo_context_get_font_options (Pango2Context *context);
+
+G_END_DECLS
diff --git a/pango2/pangocairo-dwrite-font.cpp b/pango2/pangocairo-dwrite-font.cpp
new file mode 100644
index 00000000..ed5b4f0c
--- /dev/null
+++ b/pango2/pangocairo-dwrite-font.cpp
@@ -0,0 +1,53 @@
+/* Pango
+ * pangocairo-dwrite-font.cpp: PangoCairo fonts using DirectWrite
+ *
+ * Copyright (C) 2022 the GTK team
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#ifdef HAVE_DIRECT_WRITE
+
+#include <windows.h>
+#include <dwrite.h>
+#include <hb-directwrite.h>
+#include <cairo-win32.h>
+
+#include "pangocairo-private.h"
+
+/* {{{ DirectWrite PangoCairo utilities */
+cairo_font_face_t *
+pango_cairo_create_font_face_for_dwrite_pango_font (PangoFont *font)
+{
+ hb_font_t *hb_font;
+ IDWriteFontFace *dwrite_font_face = NULL;
+ cairo_font_face_t *result;
+
+ hb_font = pango_font_get_hb_font (font);
+ dwrite_font_face = hb_directwrite_face_get_font_face (hb_font_get_face (hb_font));
+
+ result = cairo_dwrite_font_face_create_for_dwrite_fontface (dwrite_font_face);
+
+ return result;
+}
+
+#endif /* HAVE_DIRECT_WRITE */
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pangocairo-font.c b/pango2/pangocairo-font.c
new file mode 100644
index 00000000..3eda3d5a
--- /dev/null
+++ b/pango2/pangocairo-font.c
@@ -0,0 +1,796 @@
+/* Pango2
+ * pangocairo-font.c: Cairo font handling
+ *
+ * Copyright (C) 2000-2005 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.
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <string.h>
+
+#include "pangocairo.h"
+#include "pangocairo-private.h"
+#include "pango-font-private.h"
+#include "pango-font-metrics-private.h"
+#include "pango-impl-utils.h"
+#include "pango-hbfont-private.h"
+#include "pango-hbface-private.h"
+#include "pango-userfont-private.h"
+#include "pango-userface-private.h"
+#include "pango-font-private.h"
+
+#if defined (HAVE_CORE_TEXT)
+
+#include <Carbon/Carbon.h>
+#include <cairo-quartz.h>
+#include <hb-coretext.h>
+
+#elif defined (HAVE_FONTCONFIG)
+
+#include <hb-ot.h>
+#include <cairo-ft.h>
+#include <freetype/ftmm.h>
+
+#endif
+
+static Pango2CairoFontPrivate * _pango2_font_get_cairo_font_private (Pango2Font *font);
+static cairo_scaled_font_t * _pango2_font_get_scaled_font (Pango2Font *font);
+static void _pango2_cairo_font_private_initialize (Pango2CairoFontPrivate *cf_priv,
+ Pango2Font *font,
+ Pango2Gravity gravity,
+ const cairo_font_options_t *font_options,
+ const Pango2Matrix *pango2_ctm,
+ const cairo_matrix_t *font_matrix);
+static void _pango2_cairo_font_private_finalize (Pango2CairoFontPrivate *cf_priv);
+
+
+
+static Pango2CairoFontPrivateScaledFontData *
+_pango2_cairo_font_private_scaled_font_data_create (void)
+{
+ return g_slice_new (Pango2CairoFontPrivateScaledFontData);
+}
+
+static void
+_pango2_cairo_font_private_scaled_font_data_destroy (Pango2CairoFontPrivateScaledFontData *data)
+{
+ if (data)
+ {
+ cairo_font_options_destroy (data->options);
+ g_slice_free (Pango2CairoFontPrivateScaledFontData, data);
+ }
+}
+
+static cairo_user_data_key_t cairo_user_data;
+
+static cairo_status_t
+render_func (cairo_scaled_font_t *scaled_font,
+ unsigned long glyph,
+ cairo_t *cr,
+ cairo_text_extents_t *extents)
+{
+ cairo_font_face_t *font_face;
+ Pango2Font *font;
+ Pango2UserFace *face;
+ hb_glyph_extents_t glyph_extents;
+ hb_position_t h_advance;
+ hb_position_t v_advance;
+ gboolean is_color;
+
+ font_face = cairo_scaled_font_get_font_face (scaled_font);
+ font = cairo_font_face_get_user_data (font_face, &cairo_user_data);
+ face = PANGO2_USER_FACE (font->face);
+
+ extents->x_bearing = 0;
+ extents->y_bearing = 0;
+ extents->width = 0;
+ extents->height = 0;
+ extents->x_advance = 0;
+ extents->y_advance = 0;
+
+ if (!face->glyph_info_func (face, 1024,
+ (hb_codepoint_t)glyph,
+ &glyph_extents,
+ &h_advance, &v_advance,
+ &is_color,
+ face->user_data))
+ {
+ return CAIRO_STATUS_USER_FONT_ERROR;
+ }
+
+ extents->x_bearing = glyph_extents.x_bearing / 1024.;
+ extents->y_bearing = - glyph_extents.y_bearing / 1024.;
+ extents->width = glyph_extents.width / 1024.;
+ extents->height = - glyph_extents.height / 1024.;
+ extents->x_advance = h_advance / 1024.;
+ extents->y_advance = v_advance / 1024.;
+
+ if (!face->render_func (face, font->size,
+ (hb_codepoint_t)glyph,
+ face->user_data,
+ "cairo",
+ cr))
+ {
+ return CAIRO_STATUS_USER_FONT_ERROR;
+ }
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+init_func (cairo_scaled_font_t *scaled_font,
+ cairo_t *cr,
+ cairo_font_extents_t *extents)
+{
+ cairo_font_face_t *cairo_face;
+ Pango2Font *font;
+ Pango2UserFace *face;
+ hb_font_extents_t font_extents;
+
+ cairo_face = cairo_scaled_font_get_font_face (scaled_font);
+ font = cairo_font_face_get_user_data (cairo_face, &cairo_user_data);
+ face = (Pango2UserFace *) pango2_font_get_face (font);
+
+ face->font_info_func (face,
+ pango2_font_get_size (font),
+ &font_extents,
+ face->user_data);
+
+ extents->ascent = font_extents.ascender / (font_extents.ascender + font_extents.descender);
+ extents->descent = font_extents.descender / (font_extents.ascender + font_extents.descender);
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_font_face_t *
+create_cairo_font_face_for_user_font (Pango2Font *font)
+{
+ cairo_font_face_t *cairo_face;
+
+ cairo_face = cairo_user_font_face_create ();
+ cairo_font_face_set_user_data (cairo_face, &cairo_user_data, font, NULL);
+ cairo_user_font_face_set_init_func (cairo_face, init_func);
+ cairo_user_font_face_set_render_color_glyph_func (cairo_face, render_func);
+
+ return cairo_face;
+}
+
+#if defined (HAVE_CORE_TEXT)
+
+static cairo_font_face_t *
+create_cairo_font_face_for_hb_font (Pango2Font *font)
+{
+ hb_font_t *hbfont;
+ CTFontRef ctfont;
+ CGFontRef cgfont;
+ cairo_font_face_t *cairo_face;
+
+ hbfont = pango2_font_get_hb_font (font);
+ ctfont = hb_coretext_font_get_ct_font (hbfont);
+ cgfont = CTFontCopyGraphicsFont (ctfont, NULL);
+
+ cairo_face = cairo_quartz_font_face_create_for_cgfont (cgfont);
+
+ CFRelease (cgfont);
+
+ return cairo_face;
+}
+
+#elif defined (HAVE_DIRECT_WRITE)
+
+static cairo_font_face_t *
+create_cairo_font_face_for_hb_font (Pango2Font *font)
+{
+ return pango2_cairo_create_font_face_for_dwrite_pango2_font (font);
+}
+
+#else
+
+static cairo_font_face_t *
+create_cairo_font_face_for_hb_font (Pango2Font *font)
+{
+ static FT_Library ft_library;
+
+ Pango2HbFace *face = PANGO2_HB_FACE (font->face);
+ hb_blob_t *blob;
+ const char *blob_data;
+ unsigned int blob_length;
+ FT_Face ft_face;
+ hb_font_t *hb_font;
+ unsigned int num_coords;
+ const int *coords;
+ cairo_font_face_t *cairo_face;
+ static const cairo_user_data_key_t key;
+ static const cairo_user_data_key_t key2;
+ FT_Error error;
+
+ if (g_once_init_enter (&ft_library))
+ {
+ FT_Library library;
+ FT_Init_FreeType (&library);
+ g_once_init_leave (&ft_library, library);
+ }
+
+ hb_font = pango2_font_get_hb_font (font);
+ blob = hb_face_reference_blob (hb_font_get_face (hb_font));
+ blob_data = hb_blob_get_data (blob, &blob_length);
+
+ if ((error = FT_New_Memory_Face (ft_library,
+ (const FT_Byte *) blob_data,
+ blob_length,
+ hb_face_get_index (face->face),
+ &ft_face)) != 0)
+ {
+ hb_blob_destroy (blob);
+ g_warning ("FT_New_Memory_Face failed: %d %s", error, FT_Error_String (error));
+ return NULL;
+ }
+
+ coords = hb_font_get_var_coords_normalized (hb_font, &num_coords);
+ if (num_coords > 0)
+ {
+ FT_Fixed *ft_coords = (FT_Fixed *) g_alloca (num_coords * sizeof (FT_Fixed));
+
+ for (unsigned int i = 0; i < num_coords; i++)
+ ft_coords[i] = coords[i] << 2;
+
+ FT_Set_Var_Blend_Coordinates (ft_face, num_coords, ft_coords);
+ }
+
+ cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, FT_LOAD_NO_HINTING | FT_LOAD_COLOR);
+
+ if (face->embolden)
+ cairo_ft_font_face_set_synthesize (cairo_face, CAIRO_FT_SYNTHESIZE_BOLD);
+
+ cairo_font_face_set_user_data (cairo_face, &key,
+ ft_face, (cairo_destroy_func_t) FT_Done_Face);
+ cairo_font_face_set_user_data (cairo_face, &key2,
+ blob, (cairo_destroy_func_t) hb_blob_destroy);
+
+ return cairo_face;
+}
+
+#endif
+
+static cairo_scaled_font_t *
+_pango2_cairo_font_private_get_scaled_font (Pango2CairoFontPrivate *cf_priv)
+{
+ cairo_font_face_t *font_face;
+
+ if (G_LIKELY (cf_priv->scaled_font))
+ return cf_priv->scaled_font;
+
+ /* need to create it */
+
+ if (G_UNLIKELY (cf_priv->data == NULL))
+ {
+ /* we have tried to create and failed before */
+ return NULL;
+ }
+
+ if (PANGO2_IS_HB_FONT (cf_priv->cfont))
+ font_face = create_cairo_font_face_for_hb_font (cf_priv->cfont);
+ else if (PANGO2_IS_USER_FONT (cf_priv->cfont))
+ font_face = create_cairo_font_face_for_user_font (cf_priv->cfont);
+
+ if (G_UNLIKELY (font_face == NULL))
+ goto done;
+
+ cf_priv->scaled_font = cairo_scaled_font_create (font_face,
+ &cf_priv->data->font_matrix,
+ &cf_priv->data->ctm,
+ cf_priv->data->options);
+
+ cairo_font_face_destroy (font_face);
+
+done:
+
+ if (G_UNLIKELY (cf_priv->scaled_font == NULL || cairo_scaled_font_status (cf_priv->scaled_font) != CAIRO_STATUS_SUCCESS))
+ {
+ cairo_scaled_font_t *scaled_font = cf_priv->scaled_font;
+ Pango2Font *font = cf_priv->cfont;
+ static GQuark warned_quark = 0; /* MT-safe */
+ if (!warned_quark)
+ warned_quark = g_quark_from_static_string ("pangocairo-scaledfont-warned");
+
+ if (!g_object_get_qdata (G_OBJECT (font), warned_quark))
+ {
+ Pango2FontDescription *desc;
+ char *s;
+
+ desc = pango2_font_describe (font);
+ s = pango2_font_description_to_string (desc);
+ pango2_font_description_free (desc);
+
+ g_warning ("failed to create cairo %s, expect ugly output. the offending font is '%s'",
+ font_face ? "scaled font" : "font face",
+ s);
+
+ if (!font_face)
+ g_warning ("font_face is NULL");
+ else
+ g_warning ("font_face status is: %s",
+ cairo_status_to_string (cairo_font_face_status (font_face)));
+
+ if (!scaled_font)
+ g_warning ("scaled_font is NULL");
+ else
+ g_warning ("scaled_font status is: %s",
+ cairo_status_to_string (cairo_scaled_font_status (scaled_font)));
+
+ g_free (s);
+
+ g_object_set_qdata_full (G_OBJECT (font), warned_quark,
+ GINT_TO_POINTER (1), NULL);
+ }
+ }
+
+ _pango2_cairo_font_private_scaled_font_data_destroy (cf_priv->data);
+ cf_priv->data = NULL;
+
+ return cf_priv->scaled_font;
+}
+
+cairo_scaled_font_t *
+_pango2_font_get_scaled_font (Pango2Font *font)
+{
+ Pango2CairoFontPrivate *cf_priv;
+
+ cf_priv = _pango2_font_get_cairo_font_private (font);
+
+ if (G_UNLIKELY (!cf_priv))
+ return NULL;
+
+ return _pango2_cairo_font_private_get_scaled_font (cf_priv);
+}
+
+/**
+ * _pango2_cairo_font_install:
+ * @font: a `Pango2CairoFont`
+ * @cr: a #cairo_t
+ *
+ * Makes @font the current font for rendering in the specified
+ * Cairo context.
+ *
+ * Return value: %TRUE if font was installed successfully, %FALSE otherwise.
+ */
+gboolean
+_pango2_cairo_font_install (Pango2Font *font,
+ cairo_t *cr)
+{
+ cairo_scaled_font_t *scaled_font;
+
+ scaled_font = _pango2_font_get_scaled_font (font);
+
+ if (G_UNLIKELY (scaled_font == NULL || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
+ return FALSE;
+
+ cairo_set_scaled_font (cr, scaled_font);
+
+ return TRUE;
+}
+
+static Pango2CairoFontHexBoxInfo *
+_pango2_cairo_font_private_get_hex_box_info (Pango2CairoFontPrivate *cf_priv)
+{
+ const char hexdigits[] = "0123456789ABCDEF";
+ char c[2] = {0, 0};
+ Pango2Font *mini_font;
+ Pango2CairoFontHexBoxInfo *hbi;
+
+ /* for metrics hinting */
+ double scale_x = 1., scale_x_inv = 1., scale_y = 1., scale_y_inv = 1.;
+ gboolean is_hinted;
+
+ int i;
+ int rows;
+ double pad;
+ double width = 0;
+ double height = 0;
+ cairo_font_options_t *font_options;
+ cairo_font_extents_t font_extents;
+ double size, mini_size;
+ Pango2FontDescription *desc;
+ cairo_scaled_font_t *scaled_font, *scaled_mini_font;
+ Pango2Matrix pango2_ctm, pango2_font_matrix;
+ cairo_matrix_t cairo_ctm, cairo_font_matrix;
+ /*Pango2Gravity gravity;*/
+
+ if (!cf_priv)
+ return NULL;
+
+ if (cf_priv->hbi)
+ return cf_priv->hbi;
+
+ scaled_font = _pango2_cairo_font_private_get_scaled_font (cf_priv);
+ if (G_UNLIKELY (scaled_font == NULL || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
+ return NULL;
+
+ is_hinted = cf_priv->is_hinted;
+
+ font_options = cairo_font_options_create ();
+ desc = pango2_font_describe_with_absolute_size (cf_priv->cfont);
+ /*gravity = pango2_font_description_get_gravity (desc);*/
+
+ cairo_scaled_font_get_ctm (scaled_font, &cairo_ctm);
+ cairo_scaled_font_get_font_matrix (scaled_font, &cairo_font_matrix);
+ cairo_scaled_font_get_font_options (scaled_font, font_options);
+ /* I started adding support for vertical hexboxes here, but it's too much
+ * work. Easier to do with cairo user fonts and vertical writing mode
+ * support in cairo.
+ */
+ /*cairo_matrix_rotate (&cairo_ctm, pango2_gravity_to_rotation (gravity));*/
+ pango2_ctm.xx = cairo_ctm.xx;
+ pango2_ctm.yx = cairo_ctm.yx;
+ pango2_ctm.xy = cairo_ctm.xy;
+ pango2_ctm.yy = cairo_ctm.yy;
+ pango2_ctm.x0 = cairo_ctm.x0;
+ pango2_ctm.y0 = cairo_ctm.y0;
+ pango2_font_matrix.xx = cairo_font_matrix.xx;
+ pango2_font_matrix.yx = cairo_font_matrix.yx;
+ pango2_font_matrix.xy = cairo_font_matrix.xy;
+ pango2_font_matrix.yy = cairo_font_matrix.yy;
+ pango2_font_matrix.x0 = cairo_font_matrix.x0;
+ pango2_font_matrix.y0 = cairo_font_matrix.y0;
+
+ size = pango2_matrix_get_font_scale_factor (&pango2_font_matrix) /
+ pango2_matrix_get_font_scale_factor (&pango2_ctm);
+
+ if (is_hinted)
+ {
+ /* prepare for some hinting */
+ double x, y;
+
+ x = 1.; y = 0.;
+ cairo_matrix_transform_distance (&cairo_ctm, &x, &y);
+ scale_x = sqrt (x*x + y*y);
+ scale_x_inv = 1 / scale_x;
+
+ x = 0.; y = 1.;
+ cairo_matrix_transform_distance (&cairo_ctm, &x, &y);
+ scale_y = sqrt (x*x + y*y);
+ scale_y_inv = 1 / scale_y;
+ }
+
+/* we hint to the nearest device units */
+#define HINT(value, scale, scale_inv) (ceil ((value-1e-5) * scale) * scale_inv)
+#define HINT_X(value) HINT ((value), scale_x, scale_x_inv)
+#define HINT_Y(value) HINT ((value), scale_y, scale_y_inv)
+
+ /* create mini_font description */
+ {
+ Pango2FontFace *face;
+ Pango2FontFamily *family;
+ Pango2FontMap *fontmap;
+ Pango2Context *context;
+
+ /* XXX this is racy. need a ref'ing getter... */
+ face = pango2_font_get_face (cf_priv->cfont);
+ family = pango2_font_face_get_family (face);
+ if (!family)
+ return NULL;
+ fontmap = pango2_font_family_get_font_map (family);
+ if (!fontmap)
+ return NULL;
+ fontmap = g_object_ref (fontmap);
+
+ /* we inherit most font properties for the mini font. just
+ * change family and size. means, you get bold hex digits
+ * in the hexbox for a bold font.
+ */
+
+ /* We should rotate the box, not glyphs */
+ pango2_font_description_unset_fields (desc, PANGO2_FONT_MASK_GRAVITY);
+
+ pango2_font_description_set_family_static (desc, "monospace");
+
+ rows = 2;
+ mini_size = size / 2.2;
+ if (is_hinted)
+ {
+ mini_size = HINT_Y (mini_size);
+
+ if (mini_size < 6.0)
+ {
+ rows = 1;
+ mini_size = MIN (MAX (size - 1, 0), 6.0);
+ }
+ }
+
+ pango2_font_description_set_absolute_size (desc, pango2_units_from_double (mini_size));
+
+ /* load mini_font */
+
+ context = pango2_context_new_with_font_map (fontmap);
+
+ pango2_context_set_matrix (context, &pango2_ctm);
+ pango2_context_set_language (context, pango2_script_get_sample_language (G_UNICODE_SCRIPT_LATIN));
+ pango2_cairo_context_set_font_options (context, font_options);
+ mini_font = pango2_font_map_load_font (fontmap, context, desc);
+
+ g_object_unref (context);
+ g_object_unref (fontmap);
+ }
+
+ pango2_font_description_free (desc);
+ cairo_font_options_destroy (font_options);
+
+ scaled_mini_font = _pango2_font_get_scaled_font (mini_font);
+ if (G_UNLIKELY (scaled_mini_font == NULL || cairo_scaled_font_status (scaled_mini_font) != CAIRO_STATUS_SUCCESS))
+ return NULL;
+
+ for (i = 0 ; i < 16 ; i++)
+ {
+ cairo_text_extents_t extents;
+
+ c[0] = hexdigits[i];
+ cairo_scaled_font_text_extents (scaled_mini_font, c, &extents);
+ width = MAX (width, extents.width);
+ height = MAX (height, extents.height);
+ }
+
+ cairo_scaled_font_extents (scaled_font, &font_extents);
+ if (font_extents.ascent + font_extents.descent <= 0)
+ {
+ font_extents.ascent = PANGO2_UNKNOWN_GLYPH_HEIGHT;
+ font_extents.descent = 0;
+ }
+
+ pad = (font_extents.ascent + font_extents.descent) / 43;
+ pad = MIN (pad, mini_size);
+
+ hbi = g_slice_new (Pango2CairoFontHexBoxInfo);
+ hbi->font = mini_font;
+ hbi->rows = rows;
+
+ hbi->digit_width = width;
+ hbi->digit_height = height;
+
+ hbi->pad_x = pad;
+ hbi->pad_y = pad;
+
+ if (is_hinted)
+ {
+ hbi->digit_width = HINT_X (hbi->digit_width);
+ hbi->digit_height = HINT_Y (hbi->digit_height);
+ hbi->pad_x = HINT_X (hbi->pad_x);
+ hbi->pad_y = HINT_Y (hbi->pad_y);
+ }
+
+ hbi->line_width = MIN (hbi->pad_x, hbi->pad_y);
+
+ hbi->box_height = 3 * hbi->pad_y + rows * (hbi->pad_y + hbi->digit_height);
+
+ if (rows == 1 || hbi->box_height <= font_extents.ascent)
+ {
+ hbi->box_descent = 2 * hbi->pad_y;
+ }
+ else if (hbi->box_height <= font_extents.ascent + font_extents.descent - 2 * hbi->pad_y)
+ {
+ hbi->box_descent = 2 * hbi->pad_y + hbi->box_height - font_extents.ascent;
+ }
+ else
+ {
+ hbi->box_descent = font_extents.descent * hbi->box_height /
+ (font_extents.ascent + font_extents.descent);
+ }
+ if (is_hinted)
+ {
+ hbi->box_descent = HINT_Y (hbi->box_descent);
+ }
+
+ cf_priv->hbi = hbi;
+ return hbi;
+}
+
+static void
+_pango2_cairo_font_hex_box_info_destroy (Pango2CairoFontHexBoxInfo *hbi)
+{
+ if (hbi)
+ {
+ g_object_unref (hbi->font);
+ g_slice_free (Pango2CairoFontHexBoxInfo, hbi);
+ }
+}
+
+static void
+free_cairo_font_private (gpointer data)
+{
+ Pango2CairoFontPrivate *cf_priv = data;
+ _pango2_cairo_font_private_finalize (cf_priv);
+ g_free (data);
+}
+
+static Pango2CairoFontPrivate *
+_pango2_font_get_cairo_font_private (Pango2Font *font)
+{
+ Pango2CairoFontPrivate *cf_priv;
+
+ cf_priv = g_object_get_data (G_OBJECT (font), "pango-font-cairo_private");
+ if (!cf_priv)
+ {
+ cairo_font_options_t *font_options;
+ cairo_matrix_t font_matrix;
+ double x_scale, y_scale;
+ int size;
+
+ cairo_matrix_init (&font_matrix, 1., 0., 0., 1., 0., 0.);
+ x_scale = y_scale = 1;
+
+ if (PANGO2_IS_HB_FONT (font))
+ {
+ Pango2HbFace *face = PANGO2_HB_FACE (font->face);
+ if (face->transform)
+ cairo_matrix_init (&font_matrix,
+ face->transform->xx,
+ - face->transform->yx,
+ - face->transform->xy,
+ face->transform->yy,
+ 0., 0.);
+
+ x_scale = face->x_scale;
+ y_scale = face->y_scale;
+ }
+
+ size = font->size * font->dpi / 72.;
+
+ cairo_matrix_scale (&font_matrix,
+ x_scale * size / (double)PANGO2_SCALE,
+ y_scale * size / (double)PANGO2_SCALE);
+
+ font_options = (cairo_font_options_t *)pango2_cairo_font_get_font_options (font);
+ if (font_options)
+ font_options = cairo_font_options_copy (font_options);
+ else
+ {
+ font_options = cairo_font_options_create ();
+ cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE);
+ cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF);
+ }
+
+ cf_priv = g_new0 (Pango2CairoFontPrivate, 1);
+ _pango2_cairo_font_private_initialize (cf_priv,
+ font,
+ font->gravity,
+ font_options,
+ &font->ctm,
+ &font_matrix);
+
+ cairo_font_options_destroy (font_options);
+
+ g_object_set_data_full (G_OBJECT (font), "pango-font-cairo_private",
+ cf_priv, free_cairo_font_private);
+ }
+
+ return cf_priv;
+}
+
+Pango2CairoFontHexBoxInfo *
+_pango2_cairo_font_get_hex_box_info (Pango2Font *font)
+{
+ Pango2CairoFontPrivate *cf_priv = _pango2_font_get_cairo_font_private (font);
+
+ return _pango2_cairo_font_private_get_hex_box_info (cf_priv);
+}
+
+void
+_pango2_cairo_font_private_initialize (Pango2CairoFontPrivate *cf_priv,
+ Pango2Font *cfont,
+ Pango2Gravity gravity,
+ const cairo_font_options_t *font_options,
+ const Pango2Matrix *pango2_ctm,
+ const cairo_matrix_t *font_matrix)
+{
+ cairo_matrix_t gravity_matrix;
+
+ cf_priv->cfont = cfont;
+ cf_priv->gravity = gravity != PANGO2_GRAVITY_AUTO ? gravity : PANGO2_GRAVITY_SOUTH;
+
+ cf_priv->data = _pango2_cairo_font_private_scaled_font_data_create ();
+
+ /* first apply gravity rotation, then font_matrix, such that
+ * vertical italic text comes out "correct". we don't do anything
+ * like baseline adjustment etc though. should be specially
+ * handled when we support italic correction.
+ */
+ cairo_matrix_init_rotate (&gravity_matrix,
+ pango2_gravity_to_rotation (cf_priv->gravity));
+ cairo_matrix_multiply (&cf_priv->data->font_matrix,
+ font_matrix,
+ &gravity_matrix);
+
+ if (pango2_ctm)
+ cairo_matrix_init (&cf_priv->data->ctm,
+ pango2_ctm->xx,
+ pango2_ctm->yx,
+ pango2_ctm->xy,
+ pango2_ctm->yy,
+ 0., 0.);
+ else
+ cairo_matrix_init_identity (&cf_priv->data->ctm);
+
+ cf_priv->data->options = cairo_font_options_copy (font_options);
+ cf_priv->is_hinted = cairo_font_options_get_hint_metrics (font_options) != CAIRO_HINT_METRICS_OFF;
+
+ cf_priv->scaled_font = NULL;
+ cf_priv->hbi = NULL;
+}
+
+void
+_pango2_cairo_font_private_finalize (Pango2CairoFontPrivate *cf_priv)
+{
+ _pango2_cairo_font_private_scaled_font_data_destroy (cf_priv->data);
+
+ if (cf_priv->scaled_font)
+ cairo_scaled_font_destroy (cf_priv->scaled_font);
+ cf_priv->scaled_font = NULL;
+
+ _pango2_cairo_font_hex_box_info_destroy (cf_priv->hbi);
+ cf_priv->hbi = NULL;
+}
+
+/**
+ * pango2_cairo_font_set_font_options:
+ * @font: a `Pango2Font`
+ * @options: (nullable): a `cairo_font_options_t`, or %NULL to unset
+ * any previously set options. A copy is made.
+ *
+ * Sets the font options used when rendering text with this font.
+ *
+ * This is rarely needed. Fonts usually get font options from the
+ * `Pango2Context` in which they are loaded.
+ */
+void
+pango2_cairo_font_set_font_options (Pango2Font *font,
+ const cairo_font_options_t *options)
+{
+ g_return_if_fail (PANGO2_IS_FONT (font));
+
+ if (!font->options && !options)
+ return;
+
+ if (font->options && options &&
+ cairo_font_options_equal (font->options, options))
+ return;
+
+ if (font->options)
+ cairo_font_options_destroy (font->options);
+
+ if (options)
+ font->options = cairo_font_options_copy (options);
+ else
+ font->options = NULL;
+}
+
+/**
+ * pango2_cairo_font_get_font_options:
+ * @font: a `Pango2Font`
+ *
+ * Gets font options for the font.
+ *
+ * Returns: (transfer none): font options that are
+ * applied when rendering text with this font
+ */
+const cairo_font_options_t *
+pango2_cairo_font_get_font_options (Pango2Font *font)
+{
+ g_return_val_if_fail (PANGO2_IS_FONT (font), NULL);
+
+ return font->options;
+}
diff --git a/pango2/pangocairo-font.h b/pango2/pangocairo-font.h
new file mode 100644
index 00000000..9c57318d
--- /dev/null
+++ b/pango2/pangocairo-font.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 1999, 2004 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango.h>
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_font_set_font_options (Pango2Font *font,
+ const cairo_font_options_t *options);
+PANGO2_AVAILABLE_IN_ALL
+const cairo_font_options_t *
+ pango2_cairo_font_get_font_options (Pango2Font *font);
+
+G_END_DECLS
diff --git a/pango2/pangocairo-private.h b/pango2/pangocairo-private.h
new file mode 100644
index 00000000..d3ad6994
--- /dev/null
+++ b/pango2/pangocairo-private.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2000, 2004 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pangocairo.h"
+#include "pango-renderer.h"
+
+G_BEGIN_DECLS
+
+typedef struct _Pango2CairoFontPrivate Pango2CairoFontPrivate;
+typedef struct _HexBoxInfo Pango2CairoFontHexBoxInfo;
+typedef struct _Pango2CairoFontPrivateScaledFontData Pango2CairoFontPrivateScaledFontData;
+
+struct _Pango2CairoFontPrivateScaledFontData
+{
+ cairo_matrix_t font_matrix;
+ cairo_matrix_t ctm;
+ cairo_font_options_t *options;
+};
+
+struct _Pango2CairoFontPrivate
+{
+ Pango2Font *cfont;
+
+ Pango2CairoFontPrivateScaledFontData *data;
+
+ cairo_scaled_font_t *scaled_font;
+ Pango2CairoFontHexBoxInfo *hbi;
+
+ gboolean is_hinted;
+ Pango2Gravity gravity;
+
+ Pango2Rectangle font_extents;
+};
+
+gboolean _pango2_cairo_font_install (Pango2Font *font,
+ cairo_t *cr);
+Pango2CairoFontHexBoxInfo *_pango2_cairo_font_get_hex_box_info (Pango2Font *font);
+
+#define PANGO2_TYPE_CAIRO_RENDERER (pango2_cairo_renderer_get_type())
+#define PANGO2_CAIRO_RENDERER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), PANGO2_TYPE_CAIRO_RENDERER, Pango2CairoRenderer))
+#define PANGO2_IS_CAIRO_RENDERER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), PANGO2_TYPE_CAIRO_RENDERER))
+
+typedef struct _Pango2CairoRenderer Pango2CairoRenderer;
+
+_PANGO2_EXTERN
+GType pango2_cairo_renderer_get_type (void) G_GNUC_CONST;
+
+const cairo_font_options_t *
+ pango2_cairo_context_get_merged_font_options (Pango2Context *context);
+
+#ifdef HAVE_DIRECT_WRITE
+cairo_font_face_t *
+pango2_cairo_create_font_face_for_dwrite_pango2_font (Pango2Font *font);
+#endif
+
+G_END_DECLS
diff --git a/pango2/pangocairo-render.c b/pango2/pangocairo-render.c
new file mode 100644
index 00000000..b5189bee
--- /dev/null
+++ b/pango2/pangocairo-render.c
@@ -0,0 +1,1255 @@
+/* Pango2
+ * pangocairo-render.c: Rendering routines to Cairo surfaces
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <math.h>
+
+#include "pango-item-private.h"
+#include "pango-font-private.h"
+#include "pangocairo-private.h"
+#include "pango-glyph-item-private.h"
+#include "pango-glyph-iter-private.h"
+#include "pango-run-private.h"
+#include "pango-impl-utils.h"
+#include "pango-hbfont-private.h"
+
+typedef struct _Pango2CairoRendererClass Pango2CairoRendererClass;
+
+#define PANGO2_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PANGO2_TYPE_CAIRO_RENDERER, Pango2CairoRendererClass))
+#define PANGO2_IS_CAIRO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PANGO2_TYPE_CAIRO_RENDERER))
+#define PANGO2_CAIRO_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PANGO2_TYPE_CAIRO_RENDERER, Pango2CairoRendererClass))
+
+struct _Pango2CairoRenderer
+{
+ Pango2Renderer parent_instance;
+
+ cairo_t *cr;
+ gboolean do_path;
+ gboolean has_show_text_glyphs;
+ double x_offset, y_offset;
+
+ /* house-keeping options */
+ gboolean is_cached_renderer;
+ gboolean cr_had_current_point;
+};
+
+struct _Pango2CairoRendererClass
+{
+ Pango2RendererClass parent_class;
+};
+
+G_DEFINE_TYPE (Pango2CairoRenderer, pango2_cairo_renderer, PANGO2_TYPE_RENDERER)
+
+static void
+set_color (Pango2CairoRenderer *crenderer,
+ Pango2RenderPart part)
+{
+ Pango2Color *color = pango2_renderer_get_color ((Pango2Renderer *) (crenderer), part);
+ double red, green, blue, alpha;
+
+ if (!color)
+ return;
+
+ if (color)
+ {
+ red = color->red / 65535.;
+ green = color->green / 65535.;
+ blue = color->blue / 65535.;
+ alpha = color->alpha / 65535.;
+ }
+ else
+ {
+ cairo_pattern_t *pattern = cairo_get_source (crenderer->cr);
+
+ if (pattern && cairo_pattern_get_type (pattern) == CAIRO_PATTERN_TYPE_SOLID)
+ cairo_pattern_get_rgba (pattern, &red, &green, &blue, &alpha);
+ else
+ {
+ red = 0.;
+ green = 0.;
+ blue = 0.;
+ alpha = 1.;
+ }
+ }
+
+ cairo_set_source_rgba (crenderer->cr, red, green, blue, alpha);
+}
+
+/* note: modifies crenderer->cr without doing cairo_save/restore() */
+static void
+_pango2_cairo_renderer_draw_frame (Pango2CairoRenderer *crenderer,
+ double x,
+ double y,
+ double width,
+ double height,
+ double line_width,
+ gboolean invalid)
+{
+ cairo_t *cr = crenderer->cr;
+
+ if (crenderer->do_path)
+ {
+ double d2 = line_width * .5, d = line_width;
+
+ /* we draw an outer box in one winding direction and an inner one in the
+ * opposite direction. This works for both cairo windings rules.
+ *
+ * what we really want is cairo_stroke_to_path(), but that's not
+ * implemented in cairo yet.
+ */
+
+ /* outer */
+ cairo_rectangle (cr, x-d2, y-d2, width+d, height+d);
+
+ /* inner */
+ if (invalid)
+ {
+ /* delicacies of computing the joint... this is REALLY slow */
+
+ double alpha, tan_alpha2, cos_alpha;
+ double sx, sy;
+
+ alpha = atan2 (height, width);
+
+ tan_alpha2 = tan (alpha * .5);
+ if (tan_alpha2 < 1e-5 || (sx = d2 / tan_alpha2, 2. * sx > width - d))
+ sx = (width - d) * .5;
+
+ cos_alpha = cos (alpha);
+ if (cos_alpha < 1e-5 || (sy = d2 / cos_alpha, 2. * sy > height - d))
+ sy = (height - d) * .5;
+
+ /* top triangle */
+ cairo_new_sub_path (cr);
+ cairo_line_to (cr, x+width-sx, y+d2);
+ cairo_line_to (cr, x+sx, y+d2);
+ cairo_line_to (cr, x+.5*width, y+.5*height-sy);
+ cairo_close_path (cr);
+
+ /* bottom triangle */
+ cairo_new_sub_path (cr);
+ cairo_line_to (cr, x+width-sx, y+height-d2);
+ cairo_line_to (cr, x+.5*width, y+.5*height+sy);
+ cairo_line_to (cr, x+sx, y+height-d2);
+ cairo_close_path (cr);
+
+
+ alpha = G_PI_2 - alpha;
+ tan_alpha2 = tan (alpha * .5);
+ if (tan_alpha2 < 1e-5 || (sy = d2 / tan_alpha2, 2. * sy > height - d))
+ sy = (height - d) * .5;
+
+ cos_alpha = cos (alpha);
+ if (cos_alpha < 1e-5 || (sx = d2 / cos_alpha, 2. * sx > width - d))
+ sx = (width - d) * .5;
+
+ /* left triangle */
+ cairo_new_sub_path (cr);
+ cairo_line_to (cr, x+d2, y+sy);
+ cairo_line_to (cr, x+d2, y+height-sy);
+ cairo_line_to (cr, x+.5*width-sx, y+.5*height);
+ cairo_close_path (cr);
+
+ /* right triangle */
+ cairo_new_sub_path (cr);
+ cairo_line_to (cr, x+width-d2, y+sy);
+ cairo_line_to (cr, x+.5*width+sx, y+.5*height);
+ cairo_line_to (cr, x+width-d2, y+height-sy);
+ cairo_close_path (cr);
+ }
+ else
+ cairo_rectangle (cr, x+width-d2, y+d2, - (width-d), height-d);
+ }
+ else
+ {
+ cairo_rectangle (cr, x, y, width, height);
+
+ if (invalid)
+ {
+ /* draw an X */
+
+ cairo_new_sub_path (cr);
+ cairo_move_to (cr, x, y);
+ cairo_rel_line_to (cr, width, height);
+
+ cairo_new_sub_path (cr);
+ cairo_move_to (cr, x + width, y);
+ cairo_rel_line_to (cr, -width, height);
+
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
+ }
+
+ cairo_set_line_width (cr, line_width);
+ cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
+ cairo_set_miter_limit (cr, 2.);
+ cairo_stroke (cr);
+ }
+}
+
+static void
+_pango2_cairo_renderer_draw_box_glyph (Pango2CairoRenderer *crenderer,
+ Pango2GlyphInfo *gi,
+ double cx,
+ double cy,
+ gboolean invalid)
+{
+ cairo_save (crenderer->cr);
+
+ _pango2_cairo_renderer_draw_frame (crenderer,
+ cx + 1.5,
+ cy + 1.5 - PANGO2_UNKNOWN_GLYPH_HEIGHT,
+ (double)gi->geometry.width / PANGO2_SCALE - 3.0,
+ PANGO2_UNKNOWN_GLYPH_HEIGHT - 3.0,
+ 1.0,
+ invalid);
+
+ cairo_restore (crenderer->cr);
+}
+
+static void
+_pango2_cairo_renderer_draw_unknown_glyph (Pango2CairoRenderer *crenderer,
+ Pango2Font *font,
+ Pango2GlyphInfo *gi,
+ double cx,
+ double cy)
+{
+ char buf[7];
+ double x0, y0;
+ int row, col;
+ int rows, cols;
+ double width, lsb;
+ char hexbox_string[2] = { 0, 0 };
+ Pango2CairoFontHexBoxInfo *hbi;
+ gunichar ch;
+ gboolean invalid_input;
+ const char *p;
+ const char *name;
+
+ cairo_save (crenderer->cr);
+
+ ch = gi->glyph & ~PANGO2_GLYPH_UNKNOWN_FLAG;
+ invalid_input = G_UNLIKELY (gi->glyph == PANGO2_GLYPH_INVALID_INPUT || ch > 0x10FFFF);
+
+ if (PANGO2_IS_HB_FONT (font))
+ hbi = PANGO2_HB_FONT (font)->hex_box_info;
+ else
+ hbi = _pango2_cairo_font_get_hex_box_info (font);
+ if (!hbi || !_pango2_cairo_font_install ((Pango2Font *)(hbi->font), crenderer->cr))
+ {
+ _pango2_cairo_renderer_draw_box_glyph (crenderer, gi, cx, cy, invalid_input);
+ goto done;
+ }
+
+ if (G_UNLIKELY (invalid_input))
+ {
+ rows = hbi->rows;
+ cols = 1;
+ }
+ else if (ch == 0x2423 ||
+ g_unichar_type (ch) == G_UNICODE_SPACE_SEPARATOR)
+ {
+ /* We never want to show a hex box or other drawing for
+ * space. If we want space to be visible, we replace 0x20
+ * by 0x2423 (visible space).
+ *
+ * Since we don't want to rely on glyph availability,
+ * we render a centered dot ourselves.
+ */
+ double x = cx + 0.5 *((double)gi->geometry.width / PANGO2_SCALE);
+ double y = cy + hbi->box_descent - 0.5 * hbi->box_height;
+
+ cairo_new_sub_path (crenderer->cr);
+ cairo_arc (crenderer->cr, x, y, 1.5 * hbi->line_width, 0, 2 * G_PI);
+ cairo_close_path (crenderer->cr);
+ cairo_fill (crenderer->cr);
+ goto done;
+ }
+ else if (ch == '\t')
+ {
+ /* Since we don't want to rely on glyph availability,
+ * we render an arrow like ↦ ourselves.
+ */
+ double y = cy + hbi->box_descent - 0.5 * hbi->box_height;
+ double width = (double)gi->geometry.width / PANGO2_SCALE;
+ double offset = 0.2 * width;
+ double x = cx + offset;
+ double al = width - 2 * offset; /* arrow length */
+ double tl = MIN (hbi->digit_width, 0.75 * al); /* tip length */
+ double tw2 = 2.5 * hbi->line_width; /* tip width / 2 */
+ double lw2 = 0.5 * hbi->line_width; /* line width / 2 */
+
+ cairo_move_to (crenderer->cr, x - lw2, y - tw2);
+ cairo_line_to (crenderer->cr, x + lw2, y - tw2);
+ cairo_line_to (crenderer->cr, x + lw2, y - lw2);
+ cairo_line_to (crenderer->cr, x + al - tl, y - lw2);
+ cairo_line_to (crenderer->cr, x + al - tl, y - tw2);
+ cairo_line_to (crenderer->cr, x + al, y);
+ cairo_line_to (crenderer->cr, x + al - tl, y + tw2);
+ cairo_line_to (crenderer->cr, x + al - tl, y + lw2);
+ cairo_line_to (crenderer->cr, x + lw2, y + lw2);
+ cairo_line_to (crenderer->cr, x + lw2, y + tw2);
+ cairo_line_to (crenderer->cr, x - lw2, y + tw2);
+ cairo_close_path (crenderer->cr);
+ cairo_fill (crenderer->cr);
+ goto done;
+ }
+ else if (ch == '\n' || ch == 0x2028 || ch == 0x2029)
+ {
+ /* Since we don't want to rely on glyph availability,
+ * we render an arrow like ↵ ourselves.
+ */
+ double width = (double)gi->geometry.width / PANGO2_SCALE;
+ double offset = 0.2 * width;
+ double al = width - 2 * offset; /* arrow length */
+ double tl = MIN (hbi->digit_width, 0.75 * al); /* tip length */
+ double ah = al - 0.5 * tl; /* arrow height */
+ double tw2 = 2.5 * hbi->line_width; /* tip width / 2 */
+ double x = cx + offset;
+ double y = cy - (hbi->box_height - al) / 2;
+ double lw2 = 0.5 * hbi->line_width; /* line width / 2 */
+
+ cairo_move_to (crenderer->cr, x, y);
+ cairo_line_to (crenderer->cr, x + tl, y - tw2);
+ cairo_line_to (crenderer->cr, x + tl, y - lw2);
+ cairo_line_to (crenderer->cr, x + al - lw2, y - lw2);
+ cairo_line_to (crenderer->cr, x + al - lw2, y - ah);
+ cairo_line_to (crenderer->cr, x + al + lw2, y - ah);
+ cairo_line_to (crenderer->cr, x + al + lw2, y + lw2);
+ cairo_line_to (crenderer->cr, x + tl, y + lw2);
+ cairo_line_to (crenderer->cr, x + tl, y + tw2);
+ cairo_close_path (crenderer->cr);
+ cairo_fill (crenderer->cr);
+ goto done;
+ }
+ else if ((name = pango2_get_ignorable_size (ch, &rows, &cols)))
+ {
+ /* Nothing else to do, we render 'default ignorable' chars
+ * as hex box with their nick.
+ */
+ }
+ else
+ {
+ /* Everything else gets a traditional hex box. */
+ rows = hbi->rows;
+ cols = (ch > 0xffff ? 6 : 4) / rows;
+ g_snprintf (buf, sizeof(buf), (ch > 0xffff) ? "%06X" : "%04X", ch);
+ name = buf;
+ }
+
+ width = (3 * hbi->pad_x + cols * (hbi->digit_width + hbi->pad_x));
+ lsb = ((double)gi->geometry.width / PANGO2_SCALE - width) * .5;
+ lsb = floor (lsb / hbi->pad_x) * hbi->pad_x;
+
+ _pango2_cairo_renderer_draw_frame (crenderer,
+ cx + lsb + .5 * hbi->pad_x,
+ cy + hbi->box_descent - hbi->box_height + hbi->pad_y * 0.5,
+ width - hbi->pad_x,
+ (hbi->box_height - hbi->pad_y),
+ hbi->line_width,
+ invalid_input);
+
+ if (invalid_input)
+ goto done;
+
+ x0 = cx + lsb + hbi->pad_x * 2;
+ y0 = cy + hbi->box_descent - hbi->pad_y * 2 - ((hbi->rows - rows) * hbi->digit_height / 2);
+
+ for (row = 0, p = name; row < rows; row++)
+ {
+ double y = y0 - (rows - 1 - row) * (hbi->digit_height + hbi->pad_y);
+ for (col = 0; col < cols; col++, p++)
+ {
+ double x = x0 + col * (hbi->digit_width + hbi->pad_x);
+
+ if (!p)
+ goto done;
+
+ cairo_move_to (crenderer->cr, x, y);
+
+ hexbox_string[0] = p[0];
+
+ if (crenderer->do_path)
+ cairo_text_path (crenderer->cr, hexbox_string);
+ else
+ cairo_show_text (crenderer->cr, hexbox_string);
+ }
+ }
+
+done:
+ cairo_restore (crenderer->cr);
+}
+
+#ifndef STACK_BUFFER_SIZE
+#define STACK_BUFFER_SIZE (512 * sizeof (int))
+#endif
+
+#define STACK_ARRAY_LENGTH(T) (STACK_BUFFER_SIZE / sizeof(T))
+
+static void
+pango2_cairo_renderer_show_text_glyphs (Pango2Renderer *renderer,
+ const char *text,
+ int text_len,
+ Pango2GlyphString *glyphs,
+ cairo_text_cluster_t *clusters,
+ int num_clusters,
+ gboolean backward,
+ Pango2Font *font,
+ int x,
+ int y)
+{
+ Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (renderer);
+
+ int i, count;
+ int x_position = 0;
+ cairo_glyph_t *cairo_glyphs;
+ cairo_glyph_t stack_glyphs[STACK_ARRAY_LENGTH (cairo_glyph_t)];
+ double base_x = crenderer->x_offset + (double)x / PANGO2_SCALE;
+ double base_y = crenderer->y_offset + (double)y / PANGO2_SCALE;
+
+ cairo_save (crenderer->cr);
+ if (!crenderer->do_path)
+ set_color (crenderer, PANGO2_RENDER_PART_FOREGROUND);
+
+ if (!_pango2_cairo_font_install (font, crenderer->cr))
+ {
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ Pango2GlyphInfo *gi = &glyphs->glyphs[i];
+
+ if (gi->glyph != PANGO2_GLYPH_EMPTY)
+ {
+ double cx = base_x + (double)(x_position + gi->geometry.x_offset) / PANGO2_SCALE;
+ double cy = gi->geometry.y_offset == 0 ?
+ base_y :
+ base_y + (double)(gi->geometry.y_offset) / PANGO2_SCALE;
+
+ _pango2_cairo_renderer_draw_unknown_glyph (crenderer, font, gi, cx, cy);
+ }
+ x_position += gi->geometry.width;
+ }
+
+ goto done;
+ }
+
+ if (glyphs->num_glyphs > (int) G_N_ELEMENTS (stack_glyphs))
+ cairo_glyphs = g_new (cairo_glyph_t, glyphs->num_glyphs);
+ else
+ cairo_glyphs = stack_glyphs;
+
+ count = 0;
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ Pango2GlyphInfo *gi = &glyphs->glyphs[i];
+
+ if (gi->glyph != PANGO2_GLYPH_EMPTY)
+ {
+ double cx = base_x + (double)(x_position + gi->geometry.x_offset) / PANGO2_SCALE;
+ double cy = gi->geometry.y_offset == 0 ?
+ base_y :
+ base_y + (double)(gi->geometry.y_offset) / PANGO2_SCALE;
+
+ if (gi->glyph & PANGO2_GLYPH_UNKNOWN_FLAG)
+ {
+ if (gi->glyph == (0x20 | PANGO2_GLYPH_UNKNOWN_FLAG))
+ ; /* no hex boxes for space, please */
+ else
+ _pango2_cairo_renderer_draw_unknown_glyph (crenderer, font, gi, cx, cy);
+ }
+ else
+ {
+ cairo_glyphs[count].index = gi->glyph;
+ cairo_glyphs[count].x = cx;
+ cairo_glyphs[count].y = cy;
+ count++;
+ }
+ }
+ x_position += gi->geometry.width;
+ }
+
+ if (G_UNLIKELY (crenderer->do_path))
+ cairo_glyph_path (crenderer->cr, cairo_glyphs, count);
+ else
+ if (G_UNLIKELY (clusters))
+ cairo_show_text_glyphs (crenderer->cr,
+ text, text_len,
+ cairo_glyphs, count,
+ clusters, num_clusters,
+ backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : 0);
+ else
+ cairo_show_glyphs (crenderer->cr, cairo_glyphs, count);
+
+ if (cairo_glyphs != stack_glyphs)
+ g_free (cairo_glyphs);
+
+done:
+ cairo_restore (crenderer->cr);
+}
+
+static void
+pango2_cairo_renderer_draw_glyphs (Pango2Renderer *renderer,
+ Pango2Font *font,
+ Pango2GlyphString *glyphs,
+ int x,
+ int y)
+{
+ pango2_cairo_renderer_show_text_glyphs (renderer, NULL, 0, glyphs, NULL, 0, FALSE, font, x, y);
+}
+
+static void
+pango2_cairo_renderer_draw_run (Pango2Renderer *renderer,
+ const char *text,
+ Pango2Run *run,
+ int x,
+ int y)
+{
+ Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (renderer);
+ Pango2Item *item = pango2_run_get_item (run);
+ Pango2GlyphString *glyphs = pango2_run_get_glyphs (run);
+ Pango2Font *font = item->analysis.font;
+ gboolean backward = (item->analysis.level & 1) != 0;
+ Pango2GlyphItem *glyph_item = pango2_run_get_glyph_item (run);
+ Pango2GlyphItemIter iter;
+ cairo_text_cluster_t *cairo_clusters;
+ cairo_text_cluster_t stack_clusters[STACK_ARRAY_LENGTH (cairo_text_cluster_t)];
+ int num_clusters;
+
+ if (!crenderer->has_show_text_glyphs || crenderer->do_path)
+ {
+ pango2_cairo_renderer_show_text_glyphs (renderer, NULL, 0, glyphs, NULL, 0, FALSE, font, x, y);
+ return;
+ }
+
+ if (glyphs->num_glyphs > (int) G_N_ELEMENTS (stack_clusters))
+ cairo_clusters = g_new (cairo_text_cluster_t, glyphs->num_glyphs);
+ else
+ cairo_clusters = stack_clusters;
+
+ num_clusters = 0;
+ if (pango2_glyph_item_iter_init_start (&iter, glyph_item, text))
+ {
+ do {
+ int num_bytes, num_glyphs, i;
+
+ num_bytes = iter.end_index - iter.start_index;
+ num_glyphs = backward ? iter.start_glyph - iter.end_glyph : iter.end_glyph - iter.start_glyph;
+
+ if (num_bytes < 1)
+ g_warning ("pango2_cairo_renderer_draw_glyph_item: bad cluster has num_bytes %d", num_bytes);
+ if (num_glyphs < 1)
+ g_warning ("pango2_cairo_renderer_draw_glyph_item: bad cluster has num_glyphs %d", num_glyphs);
+
+ /* Discount empty and unknown glyphs */
+ for (i = MIN (iter.start_glyph, iter.end_glyph+1);
+ i < MAX (iter.start_glyph+1, iter.end_glyph);
+ i++)
+ {
+ Pango2GlyphInfo *gi = &glyphs->glyphs[i];
+
+ if (gi->glyph == PANGO2_GLYPH_EMPTY ||
+ gi->glyph & PANGO2_GLYPH_UNKNOWN_FLAG)
+ num_glyphs--;
+ }
+
+ cairo_clusters[num_clusters].num_bytes = num_bytes;
+ cairo_clusters[num_clusters].num_glyphs = num_glyphs;
+ num_clusters++;
+ } while (pango2_glyph_item_iter_next_cluster (&iter));
+ }
+
+ pango2_cairo_renderer_show_text_glyphs (renderer,
+ text + item->offset, item->length,
+ glyphs,
+ cairo_clusters, num_clusters,
+ backward,
+ font,
+ x, y);
+
+ if (cairo_clusters != stack_clusters)
+ g_free (cairo_clusters);
+}
+
+static void
+pango2_cairo_renderer_draw_rectangle (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (renderer);
+
+ if (!crenderer->do_path)
+ {
+ cairo_save (crenderer->cr);
+
+ set_color (crenderer, part);
+ }
+
+ cairo_rectangle (crenderer->cr,
+ crenderer->x_offset + (double)x / PANGO2_SCALE,
+ crenderer->y_offset + (double)y / PANGO2_SCALE,
+ (double)width / PANGO2_SCALE, (double)height / PANGO2_SCALE);
+
+ if (!crenderer->do_path)
+ {
+ cairo_fill (crenderer->cr);
+
+ cairo_restore (crenderer->cr);
+ }
+}
+
+static void
+pango2_cairo_renderer_draw_trapezoid (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ double y1_,
+ double x11,
+ double x21,
+ double y2,
+ double x12,
+ double x22)
+{
+ Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (renderer);
+ cairo_t *cr;
+ double x, y;
+
+ cr = crenderer->cr;
+
+ cairo_save (cr);
+
+ if (!crenderer->do_path)
+ set_color (crenderer, part);
+
+ x = crenderer->x_offset,
+ y = crenderer->y_offset;
+ cairo_user_to_device_distance (cr, &x, &y);
+ cairo_identity_matrix (cr);
+ cairo_translate (cr, x, y);
+
+ cairo_move_to (cr, x11, y1_);
+ cairo_line_to (cr, x21, y1_);
+ cairo_line_to (cr, x22, y2);
+ cairo_line_to (cr, x12, y2);
+ cairo_close_path (cr);
+
+ if (!crenderer->do_path)
+ cairo_fill (cr);
+
+ cairo_restore (cr);
+}
+
+/* Draws an error underline that looks like one of:
+ * H E H
+ * /\ /\ /\ /\ /\ -
+ * A/ \ / \ / \ A/ \ / \ |
+ * \ \ / \ / /D \ \ / \ |
+ * \ \/ C \/ / \ \/ C \ | height = HEIGHT_SQUARES * square
+ * \ /\ F / \ F /\ \ |
+ * \ / \ / \ / \ \G |
+ * \ / \ / \ / \ / |
+ * \/ \/ \/ \/ -
+ * B B
+ * |---|
+ * unit_width = (HEIGHT_SQUARES - 1) * square
+ *
+ * The x, y, width, height passed in give the desired bounding box;
+ * x/width are adjusted to make the underline a integer number of units
+ * wide.
+ */
+#define HEIGHT_SQUARES 2.5
+
+static void
+draw_wavy_line (cairo_t *cr,
+ double x,
+ double y,
+ double width,
+ double height)
+{
+ double square = height / HEIGHT_SQUARES;
+ double unit_width = (HEIGHT_SQUARES - 1) * square;
+ double double_width = 2 * unit_width;
+ int width_units = (width + unit_width / 2) / unit_width;
+ double y_top, y_bottom;
+ double x_left, x_middle, x_right;
+ int i;
+
+ x += (width - width_units * unit_width) / 2;
+
+ y_top = y;
+ y_bottom = y + height;
+
+ /* Bottom of squiggle */
+ x_middle = x + unit_width;
+ x_right = x + double_width;
+ cairo_move_to (cr, x - square / 2, y_top + square / 2); /* A */
+ for (i = 0; i < width_units-2; i += 2)
+ {
+ cairo_line_to (cr, x_middle, y_bottom); /* B */
+ cairo_line_to (cr, x_right, y_top + square); /* C */
+
+ x_middle += double_width;
+ x_right += double_width;
+ }
+ cairo_line_to (cr, x_middle, y_bottom); /* B */
+
+ if (i + 1 == width_units)
+ cairo_line_to (cr, x_middle + square / 2, y_bottom - square / 2); /* G */
+ else if (i + 2 == width_units) {
+ cairo_line_to (cr, x_right + square / 2, y_top + square / 2); /* D */
+ cairo_line_to (cr, x_right, y_top); /* E */
+ }
+
+ /* Top of squiggle */
+ x_left = x_middle - unit_width;
+ for (; i >= 0; i -= 2)
+ {
+ cairo_line_to (cr, x_middle, y_bottom - square); /* F */
+ cairo_line_to (cr, x_left, y_top); /* H */
+
+ x_left -= double_width;
+ x_middle -= double_width;
+ }
+}
+
+static void
+pango2_cairo_renderer_draw_styled_line (Pango2Renderer *renderer,
+ Pango2RenderPart part,
+ Pango2LineStyle style,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (renderer);
+ cairo_t *cr = crenderer->cr;
+
+ if (!crenderer->do_path)
+ {
+ cairo_save (cr);
+
+ set_color (crenderer, part);
+
+ cairo_new_path (cr);
+ }
+
+ switch (style)
+ {
+ case PANGO2_LINE_STYLE_NONE:
+ break;
+
+ case PANGO2_LINE_STYLE_DOTTED:
+ case PANGO2_LINE_STYLE_DASHED:
+ cairo_save (cr);
+ cairo_set_line_width (cr, (double)height / PANGO2_SCALE);
+ if (style == PANGO2_LINE_STYLE_DOTTED)
+ {
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_dash (cr, (const double []){0., (double)(2 * height) / PANGO2_SCALE}, 2, 0.);
+ }
+ else
+ {
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
+ cairo_set_dash (cr, (const double []){(double)(3 * height) / PANGO2_SCALE, (double)(3 * height) / PANGO2_SCALE}, 2, 0.);
+ }
+ cairo_move_to (cr,
+ crenderer->x_offset + (double)x / PANGO2_SCALE + (double)height / (2 * PANGO2_SCALE),
+ crenderer->y_offset + (double)y / PANGO2_SCALE + (double)height / (2 * PANGO2_SCALE));
+ cairo_line_to (cr,
+ crenderer->x_offset + (double)x / PANGO2_SCALE + (double)width / PANGO2_SCALE - (double)height / PANGO2_SCALE,
+ crenderer->y_offset + (double)y / PANGO2_SCALE + (double)height / (2 * PANGO2_SCALE));
+ cairo_stroke (cr);
+ cairo_restore (cr);
+ break;
+
+ case PANGO2_LINE_STYLE_SOLID:
+ cairo_rectangle (cr,
+ crenderer->x_offset + (double)x / PANGO2_SCALE,
+ crenderer->y_offset + (double)y / PANGO2_SCALE,
+ (double)width / PANGO2_SCALE, (double)height / PANGO2_SCALE);
+ break;
+
+ case PANGO2_LINE_STYLE_DOUBLE:
+ cairo_rectangle (cr,
+ crenderer->x_offset + (double)x / PANGO2_SCALE,
+ crenderer->y_offset + (double)y / PANGO2_SCALE,
+ (double)width / PANGO2_SCALE, (double)height / (3 * PANGO2_SCALE));
+ cairo_rectangle (cr,
+ crenderer->x_offset + (double)x / PANGO2_SCALE,
+ crenderer->y_offset + (double)y / PANGO2_SCALE + (double)(2 * height) / (3 * PANGO2_SCALE),
+ (double)width / PANGO2_SCALE, (double)height / (3 * PANGO2_SCALE));
+ break;
+
+
+ case PANGO2_LINE_STYLE_WAVY:
+ draw_wavy_line (cr,
+ crenderer->x_offset + (double)x / PANGO2_SCALE,
+ crenderer->y_offset + (double)y / PANGO2_SCALE,
+ (double)width / PANGO2_SCALE, (double)height / PANGO2_SCALE);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (!crenderer->do_path)
+ {
+ cairo_fill (cr);
+
+ cairo_restore (cr);
+ }
+}
+
+static void
+pango2_cairo_renderer_init (Pango2CairoRenderer *renderer G_GNUC_UNUSED)
+{
+}
+
+static void
+pango2_cairo_renderer_class_init (Pango2CairoRendererClass *klass)
+{
+ Pango2RendererClass *renderer_class = PANGO2_RENDERER_CLASS (klass);
+
+ renderer_class->draw_glyphs = pango2_cairo_renderer_draw_glyphs;
+ renderer_class->draw_run = pango2_cairo_renderer_draw_run;
+ renderer_class->draw_rectangle = pango2_cairo_renderer_draw_rectangle;
+ renderer_class->draw_styled_line = pango2_cairo_renderer_draw_styled_line;
+ renderer_class->draw_trapezoid = pango2_cairo_renderer_draw_trapezoid;
+}
+
+static Pango2CairoRenderer *cached_renderer = NULL; /* MT-safe */
+G_LOCK_DEFINE_STATIC (cached_renderer);
+
+static Pango2CairoRenderer *
+acquire_renderer (void)
+{
+ Pango2CairoRenderer *renderer;
+
+ if (G_LIKELY (G_TRYLOCK (cached_renderer)))
+ {
+ if (G_UNLIKELY (!cached_renderer))
+ {
+ cached_renderer = g_object_new (PANGO2_TYPE_CAIRO_RENDERER, NULL);
+ cached_renderer->is_cached_renderer = TRUE;
+ }
+
+ renderer = cached_renderer;
+ }
+ else
+ {
+ renderer = g_object_new (PANGO2_TYPE_CAIRO_RENDERER, NULL);
+ }
+
+ return renderer;
+}
+
+static void
+release_renderer (Pango2CairoRenderer *renderer)
+{
+ if (G_LIKELY (renderer->is_cached_renderer))
+ {
+ renderer->cr = NULL;
+ renderer->do_path = FALSE;
+ renderer->has_show_text_glyphs = FALSE;
+ renderer->x_offset = 0.;
+ renderer->y_offset = 0.;
+
+ G_UNLOCK (cached_renderer);
+ }
+ else
+ g_object_unref (renderer);
+}
+
+static void
+save_current_point (Pango2CairoRenderer *renderer)
+{
+ renderer->cr_had_current_point = cairo_has_current_point (renderer->cr);
+ cairo_get_current_point (renderer->cr, &renderer->x_offset, &renderer->y_offset);
+
+ /* abuse save_current_point() to cache cairo_has_show_text_glyphs() result */
+ renderer->has_show_text_glyphs = cairo_surface_has_show_text_glyphs (cairo_get_target (renderer->cr));
+}
+
+static void
+restore_current_point (Pango2CairoRenderer *renderer)
+{
+ if (renderer->cr_had_current_point)
+ /* XXX should do cairo_set_current_point() when we have that function */
+ cairo_move_to (renderer->cr, renderer->x_offset, renderer->y_offset);
+ else
+ cairo_new_sub_path (renderer->cr);
+}
+
+
+/* convenience wrappers using the default renderer */
+
+
+static void
+_pango2_cairo_do_glyph_string (cairo_t *cr,
+ Pango2Font *font,
+ Pango2GlyphString *glyphs,
+ gboolean do_path)
+{
+ Pango2CairoRenderer *crenderer = acquire_renderer ();
+ Pango2Renderer *renderer = (Pango2Renderer *) crenderer;
+
+ crenderer->cr = cr;
+ crenderer->do_path = do_path;
+ save_current_point (crenderer);
+
+ if (!do_path)
+ {
+ /* unset all part colors, since when drawing just a glyph string,
+ * prepare_run() isn't called.
+ */
+
+ pango2_renderer_activate (renderer);
+
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_FOREGROUND, NULL);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_BACKGROUND, NULL);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_UNDERLINE, NULL);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_STRIKETHROUGH, NULL);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_OVERLINE, NULL);
+ }
+
+ pango2_renderer_draw_glyphs (renderer, font, glyphs, 0, 0);
+
+ if (!do_path)
+ {
+ pango2_renderer_deactivate (renderer);
+ }
+
+ restore_current_point (crenderer);
+
+ release_renderer (crenderer);
+}
+
+static void
+_pango2_cairo_do_run (cairo_t *cr,
+ const char *text,
+ Pango2Run *run,
+ gboolean do_path)
+{
+ Pango2CairoRenderer *crenderer = acquire_renderer ();
+ Pango2Renderer *renderer = (Pango2Renderer *) crenderer;
+
+ crenderer->cr = cr;
+ crenderer->do_path = do_path;
+ save_current_point (crenderer);
+
+ if (!do_path)
+ {
+ /* unset all part colors, since when drawing just a glyph string,
+ * prepare_run() isn't called.
+ */
+
+ pango2_renderer_activate (renderer);
+
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_FOREGROUND, NULL);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_BACKGROUND, NULL);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_UNDERLINE, NULL);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_STRIKETHROUGH, NULL);
+ pango2_renderer_set_color (renderer, PANGO2_RENDER_PART_OVERLINE, NULL);
+ }
+
+ pango2_renderer_draw_run (renderer, text, run, 0, 0);
+
+ if (!do_path)
+ pango2_renderer_deactivate (renderer);
+
+ restore_current_point (crenderer);
+
+ release_renderer (crenderer);
+}
+
+static void
+_pango2_cairo_do_line (cairo_t *cr,
+ Pango2Line *line,
+ gboolean do_path)
+{
+ Pango2CairoRenderer *crenderer = acquire_renderer ();
+ Pango2Renderer *renderer = (Pango2Renderer *) crenderer;
+
+ crenderer->cr = cr;
+ crenderer->do_path = do_path;
+ save_current_point (crenderer);
+
+ pango2_renderer_draw_line (renderer, line, 0, 0);
+
+ restore_current_point (crenderer);
+
+ release_renderer (crenderer);
+}
+
+static void
+_pango2_cairo_do_lines (cairo_t *cr,
+ Pango2Lines *lines,
+ gboolean do_path)
+{
+ Pango2CairoRenderer *crenderer = acquire_renderer ();
+ Pango2Renderer *renderer = (Pango2Renderer *) crenderer;
+
+ crenderer->cr = cr;
+ crenderer->do_path = do_path;
+ save_current_point (crenderer);
+
+ pango2_renderer_draw_lines (renderer, lines, 0, 0);
+
+ restore_current_point (crenderer);
+
+ release_renderer (crenderer);
+}
+
+static void
+_pango2_cairo_do_layout (cairo_t *cr,
+ Pango2Layout *layout,
+ gboolean do_path)
+{
+ Pango2CairoRenderer *crenderer = acquire_renderer ();
+ Pango2Renderer *renderer = (Pango2Renderer *) crenderer;
+
+ crenderer->cr = cr;
+ crenderer->do_path = do_path;
+ save_current_point (crenderer);
+
+ pango2_renderer_draw_lines (renderer, pango2_layout_get_lines (layout), 0, 0);
+
+ restore_current_point (crenderer);
+
+ release_renderer (crenderer);
+}
+
+/* public wrapper of above to show or append path */
+
+
+/**
+ * pango2_cairo_show_glyph_string:
+ * @cr: a Cairo context
+ * @font: a `Pango2Font` from a `Pango2CairoFontMap`
+ * @glyphs: a `Pango2GlyphString`
+ *
+ * Draws the glyphs in @glyphs in the specified cairo context.
+ *
+ * The origin of the glyphs (the left edge of the baseline) will
+ * be drawn at the current point of the cairo context.
+ */
+void
+pango2_cairo_show_glyph_string (cairo_t *cr,
+ Pango2Font *font,
+ Pango2GlyphString *glyphs)
+{
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (glyphs != NULL);
+
+ _pango2_cairo_do_glyph_string (cr, font, glyphs, FALSE);
+}
+
+/**
+ * pango2_cairo_show_run:
+ * @cr: a Cairo context
+ * @text: the UTF-8 text that @run refers to
+ * @run: a `Pango2Run`
+ *
+ * Draws the glyphs in @run in the specified cairo context,
+ * embedding the text associated with the glyphs in the output if the
+ * output format supports it (PDF for example), otherwise it acts
+ * similar to [func@Pango2.cairo_show_glyph_string].
+ *
+ * The origin of the glyphs (the left edge of the baseline) will
+ * be drawn at the current point of the cairo context.
+ *
+ * Note that @text is the start of the text for layout, which is then
+ * indexed by `run->item->offset`.
+ */
+void
+pango2_cairo_show_run (cairo_t *cr,
+ const char *text,
+ Pango2Run *run)
+{
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (text != NULL);
+ g_return_if_fail (run != NULL);
+
+ _pango2_cairo_do_run (cr, text, run, FALSE);
+}
+
+/**
+ * pango2_cairo_show_line:
+ * @cr: a Cairo context
+ * @line: a `Pango2Line`
+ *
+ * Draws a `Pango2Line` in the specified cairo context.
+ *
+ * The origin of the glyphs (the left edge of the line) will
+ * be drawn at the current point of the cairo context.
+ */
+void
+pango2_cairo_show_line (cairo_t *cr,
+ Pango2Line *line)
+{
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (line != NULL);
+
+ _pango2_cairo_do_line (cr, line, FALSE);
+}
+
+/**
+ * pango2_cairo_show_lines:
+ * @cr: a Cairo context
+ * @lines: a `Pango2Lines` object
+ *
+ * Draws a `Pango2Lines` object in the specified cairo context.
+ *
+ * The top-left corner of the `Pango2Lines` will be drawn
+ * at the current point of the cairo context.
+ */
+void
+pango2_cairo_show_lines (cairo_t *cr,
+ Pango2Lines *lines)
+{
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (lines != NULL);
+
+ _pango2_cairo_do_lines (cr, lines, FALSE);
+}
+
+/**
+ * pango2_cairo_show_layout:
+ * @cr: a Cairo context
+ * @layout: a Pango2 layout
+ *
+ * Draws a `Pango2Layout` in the specified cairo context.
+ *
+ * The top-left corner of the `Pango2Layout` will be drawn
+ * at the current point of the cairo context.
+ */
+void
+pango2_cairo_show_layout (cairo_t *cr,
+ Pango2Layout *layout)
+{
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ _pango2_cairo_do_layout (cr, layout, FALSE);
+}
+
+/**
+ * pango2_cairo_glyph_string_path:
+ * @cr: a Cairo context
+ * @font: a `Pango2Font` from a `Pango2CairoFontMap`
+ * @glyphs: a `Pango2GlyphString`
+ *
+ * Adds the glyphs in @glyphs to the current path in the specified
+ * cairo context.
+ *
+ * The origin of the glyphs (the left edge of the baseline)
+ * will be at the current point of the cairo context.
+ */
+void
+pango2_cairo_glyph_string_path (cairo_t *cr,
+ Pango2Font *font,
+ Pango2GlyphString *glyphs)
+{
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (glyphs != NULL);
+
+ _pango2_cairo_do_glyph_string (cr, font, glyphs, TRUE);
+}
+
+/**
+ * pango2_cairo_run_path:
+ * @cr: a Cairo context
+ * @text: the UTF-8 text that @run refers to
+ * @run: a `Pango2Run`
+ *
+ * Adds the text in `Pango2Run` to the current path in the
+ * specified cairo context.
+ *
+ * The origin of the glyphs (the left edge of the line) will be
+ * at the current point of the cairo context.
+ */
+void
+pango2_cairo_run_path (cairo_t *cr,
+ const char *text,
+ Pango2Run *run)
+{
+ _pango2_cairo_do_run (cr, text, run, TRUE);
+}
+
+/**
+ * pango2_cairo_line_path:
+ * @cr: a Cairo context
+ * @line: a `Pango2Line`
+ *
+ * Adds the text in `Pango2Line` to the current path in the
+ * specified cairo context.
+ *
+ * The origin of the glyphs (the left edge of the line) will be
+ * at the current point of the cairo context.
+ */
+void
+pango2_cairo_line_path (cairo_t *cr,
+ Pango2Line *line)
+{
+ g_return_if_fail (cr != NULL);
+
+ _pango2_cairo_do_line (cr, line, TRUE);
+}
+
+/**
+ * pango2_cairo_layout_path:
+ * @cr: a Cairo context
+ * @layout: a Pango2 layout
+ *
+ * Adds the text in a `Pango2Layout` to the current path in the
+ * specified cairo context.
+ *
+ * The top-left corner of the `Pango2Layout` will be at the
+ * current point of the cairo context.
+ */
+void
+pango2_cairo_layout_path (cairo_t *cr,
+ Pango2Layout *layout)
+{
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (PANGO2_IS_LAYOUT (layout));
+
+ _pango2_cairo_do_layout (cr, layout, TRUE);
+}
+
+/**
+ * pango2_cairo_lines_path:
+ * @cr: a Cairo context
+ * @lines: a `Pango2Lines` object
+ *
+ * Adds the text in a `Pango2Lines` to the current path in the
+ * specified cairo context.
+ *
+ * The top-left corner of the `Pango2Layout` will be at the
+ * current point of the cairo context.
+ */
+void
+pango2_cairo_lines_path (cairo_t *cr,
+ Pango2Lines *lines)
+{
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (PANGO2_IS_LINES (lines));
+
+ _pango2_cairo_do_lines (cr, lines, TRUE);
+}
diff --git a/pango2/pangocairo-render.h b/pango2/pangocairo-render.h
new file mode 100644
index 00000000..fb40fba9
--- /dev/null
+++ b/pango2/pangocairo-render.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 1999, 2004 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango.h>
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_show_glyph_string (cairo_t *cr,
+ Pango2Font *font,
+ Pango2GlyphString *glyphs);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_show_run (cairo_t *cr,
+ const char *text,
+ Pango2Run *run);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_show_line (cairo_t *cr,
+ Pango2Line *line);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_show_lines (cairo_t *cr,
+ Pango2Lines *lines);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_show_layout (cairo_t *cr,
+ Pango2Layout *layout);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_glyph_string_path (cairo_t *cr,
+ Pango2Font *font,
+ Pango2GlyphString *glyphs);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_run_path (cairo_t *cr,
+ const char *text,
+ Pango2Run *run);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_line_path (cairo_t *cr,
+ Pango2Line *line);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_lines_path (cairo_t *cr,
+ Pango2Lines *lines);
+PANGO2_AVAILABLE_IN_ALL
+void pango2_cairo_layout_path (cairo_t *cr,
+ Pango2Layout *layout);
+
+G_END_DECLS
diff --git a/pango2/pangocairo.h b/pango2/pangocairo.h
new file mode 100644
index 00000000..2291fb8e
--- /dev/null
+++ b/pango2/pangocairo.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 1999, 2004 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pangocairo-context.h>
+#include <pango2/pangocairo-font.h>
+#include <pango2/pangocairo-render.h>
diff --git a/pango2/pangocoretext-fontmap.c b/pango2/pangocoretext-fontmap.c
new file mode 100644
index 00000000..3b7cdd47
--- /dev/null
+++ b/pango2/pangocoretext-fontmap.c
@@ -0,0 +1,460 @@
+/* Pango2
+ *
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include "config.h"
+#include <math.h>
+
+#include <gio/gio.h>
+
+#include "pangocoretext-fontmap.h"
+#include "pango-hbfamily-private.h"
+#include "pango-fontmap-private.h"
+#include "pango-hbface-private.h"
+#include "pango-hbfont-private.h"
+#include "pango-context.h"
+#include "pango-impl-utils.h"
+#include "pango-trace-private.h"
+
+#include <Carbon/Carbon.h>
+#include <hb-ot.h>
+#include <hb-coretext.h>
+
+
+/**
+ * Pango2CoreTextFontMap:
+ *
+ * `Pango2CoreTextFontMap` is a subclass of `Pango2FontMap` that uses
+ * CoreText to populate the fontmap with the available fonts.
+ */
+
+
+struct _Pango2CoreTextFontMap
+{
+ Pango2FontMap parent_instance;
+};
+
+struct _Pango2CoreTextFontMapClass
+{
+ Pango2FontMapClass parent_class;
+};
+
+/* {{{ CoreText utilities */
+
+#define ct_weight_min -0.7f
+#define ct_weight_max 0.8f
+
+/* This map is based on empirical data from analyzing a large collection of
+ * fonts and comparing the opentype value with the value that OSX returns.
+ * see: https://bugzilla.gnome.org/show_bug.cgi?id=766148
+ * FIXME: This need recalibrating, values outside these bounds do occur!
+ */
+
+static struct {
+ float ct_weight;
+ Pango2Weight pango2_weight;
+} ct_weight_map[] = {
+ { ct_weight_min, PANGO2_WEIGHT_THIN },
+ { -0.5, PANGO2_WEIGHT_ULTRALIGHT },
+ { -0.23, PANGO2_WEIGHT_LIGHT },
+ { -0.115, PANGO2_WEIGHT_SEMILIGHT },
+ { 0.00, PANGO2_WEIGHT_NORMAL },
+ { 0.2, PANGO2_WEIGHT_MEDIUM },
+ { 0.3, PANGO2_WEIGHT_SEMIBOLD },
+ { 0.4, PANGO2_WEIGHT_BOLD },
+ { 0.6, PANGO2_WEIGHT_ULTRABOLD },
+ { ct_weight_max, PANGO2_WEIGHT_HEAVY }
+};
+
+static int
+lerp (float x, float x1, float x2, int y1, int y2)
+{
+ float dx = x2 - x1;
+ int dy = y2 - y1;
+ return y1 + (dy * (x - x1) + dx / 2) / dx;
+}
+
+static Pango2Weight
+ct_font_descriptor_get_weight (CTFontDescriptorRef desc)
+{
+ CFDictionaryRef dict;
+ CFNumberRef cf_number;
+ CGFloat value;
+ Pango2Weight weight = PANGO2_WEIGHT_NORMAL;
+ guint i;
+
+ dict = CTFontDescriptorCopyAttribute (desc, kCTFontTraitsAttribute);
+ cf_number = (CFNumberRef)CFDictionaryGetValue (dict, kCTFontWeightTrait);
+
+ if (cf_number != NULL && CFNumberGetValue (cf_number, kCFNumberCGFloatType, &value))
+ {
+ if (!(value >= ct_weight_min && value <= ct_weight_max))
+ {
+ i = value > ct_weight_max ? G_N_ELEMENTS (ct_weight_map) - 1 : 0;
+ weight = ct_weight_map[i].ct_weight;
+ }
+ else
+ {
+ for (i = 1; value > ct_weight_map[i].ct_weight; ++i)
+ ;
+
+ weight = lerp (value, ct_weight_map[i-1].ct_weight, ct_weight_map[i].ct_weight,
+ ct_weight_map[i-1].pango2_weight, ct_weight_map[i].pango2_weight);
+
+ }
+ }
+ else
+ weight = PANGO2_WEIGHT_NORMAL;
+
+ CFRelease (dict);
+
+ return weight;
+}
+
+static char *
+gchar_from_cf_string (CFStringRef str)
+{
+ CFIndex len;
+ char *buffer;
+
+ /* GetLength returns the number of UTF-16 pairs, so this number
+ * times 2 should definitely gives us enough space for UTF8.
+ * We add one for the terminating zero.
+ */
+ len = CFStringGetLength (str) * 2 + 1;
+ buffer = g_new0 (char, len);
+ CFStringGetCString (str, buffer, len, kCFStringEncodingUTF8);
+
+ return buffer;
+}
+
+static char *
+ct_font_descriptor_get_family_name (CTFontDescriptorRef desc,
+ gboolean may_fail)
+{
+ CFStringRef cf_str;
+ char *buffer;
+
+ cf_str = CTFontDescriptorCopyAttribute (desc, kCTFontFamilyNameAttribute);
+ if (!cf_str)
+ {
+ int i;
+
+ /* No font family name is set, try to retrieve font name and deduce
+ * the family name from that instead.
+ */
+ cf_str = CTFontDescriptorCopyAttribute (desc, kCTFontNameAttribute);
+ if (!cf_str)
+ {
+ if (may_fail)
+ return NULL;
+
+ /* This font is likely broken, return a default family name ... */
+ return g_strdup ("Sans");
+ }
+
+ buffer = gchar_from_cf_string (cf_str);
+ CFRelease (cf_str);
+
+ for (i = 0; i < strlen (buffer); i++)
+ if (buffer[i] == '-')
+ break;
+
+ if (i < strlen (buffer))
+ {
+ char *ret;
+
+ ret = g_strndup (buffer, i);
+ g_free (buffer);
+
+ return ret;
+ }
+ else
+ return buffer;
+ }
+
+ buffer = gchar_from_cf_string (cf_str);
+ CFRelease (cf_str);
+
+ return buffer;
+}
+
+static char *
+ct_font_descriptor_get_style_name (CTFontDescriptorRef desc)
+{
+ CFStringRef cf_str;
+ char *buffer;
+
+ cf_str = CTFontDescriptorCopyAttribute (desc, kCTFontStyleNameAttribute);
+ if (!cf_str)
+ return NULL;
+
+ buffer = gchar_from_cf_string (cf_str);
+ CFRelease (cf_str);
+
+ return buffer;
+}
+
+static CTFontSymbolicTraits
+ct_font_descriptor_get_traits (CTFontDescriptorRef desc)
+{
+ CFDictionaryRef dict;
+ CFNumberRef cf_number;
+ SInt64 traits;
+
+ /* This is interesting, the value stored is a CTFontSymbolicTraits which
+ * is defined as uint32_t. CFNumber does not have an obvious type which
+ * deals with unsigned values. Upon inspection with CFNumberGetType,
+ * it turns out this value is stored as SInt64, so we use that to
+ * obtain the value from the CFNumber.
+ */
+ dict = CTFontDescriptorCopyAttribute (desc, kCTFontTraitsAttribute);
+ cf_number = (CFNumberRef)CFDictionaryGetValue (dict, kCTFontSymbolicTrait);
+ if (!CFNumberGetValue (cf_number, kCFNumberSInt64Type, &traits))
+ traits = 0;
+ CFRelease (dict);
+
+ return (CTFontSymbolicTraits)traits;
+}
+
+static gboolean
+ct_font_descriptor_is_small_caps (CTFontDescriptorRef desc)
+{
+ CFIndex i, count;
+ CFArrayRef array;
+ CFStringRef str;
+ gboolean retval = FALSE;
+
+ /* See http://stackoverflow.com/a/4811371 for why this works and an
+ * explanation of the magic number "3" used below.
+ */
+ array = CTFontDescriptorCopyAttribute (desc, kCTFontFeaturesAttribute);
+ if (!array)
+ return FALSE;
+
+ str = CFStringCreateWithCString (NULL, "CTFeatureTypeIdentifier",
+ kCFStringEncodingASCII);
+
+ count = CFArrayGetCount (array);
+ for (i = 0; i < count; i++)
+ {
+ CFDictionaryRef dict = CFArrayGetValueAtIndex (array, i);
+ CFNumberRef num;
+
+ num = (CFNumberRef)CFDictionaryGetValue (dict, str);
+ if (num)
+ {
+ int value = 0;
+
+ if (CFNumberGetValue (num, kCFNumberSInt32Type, &value) &&
+ value == 3)
+ {
+ /* This font supports small caps. */
+ retval = TRUE;
+ break;
+ }
+ }
+ }
+
+ CFRelease (str);
+ CFRelease (array);
+
+ return retval;
+}
+
+static gboolean
+pango2_core_text_style_name_is_oblique (const char *style_name)
+{
+ return style_name && strstr (style_name, "Oblique");
+}
+
+static Pango2FontDescription *
+font_description_from_ct_font_descriptor (CTFontDescriptorRef desc)
+{
+ SInt64 font_traits;
+ char *family_name;
+ char *style_name;
+ Pango2FontDescription *font_desc;
+
+ font_desc = pango2_font_description_new ();
+
+ family_name = ct_font_descriptor_get_family_name (desc, FALSE);
+ pango2_font_description_set_family (font_desc, family_name);
+ g_free (family_name);
+
+ pango2_font_description_set_weight (font_desc, ct_font_descriptor_get_weight (desc));
+
+ font_traits = ct_font_descriptor_get_traits (desc);
+ style_name = ct_font_descriptor_get_style_name (desc);
+
+ if ((font_traits & kCTFontItalicTrait) == kCTFontItalicTrait)
+ pango2_font_description_set_style (font_desc, PANGO2_STYLE_ITALIC);
+ else if (pango2_core_text_style_name_is_oblique (style_name))
+ pango2_font_description_set_style (font_desc, PANGO2_STYLE_OBLIQUE);
+ else
+ pango2_font_description_set_style (font_desc, PANGO2_STYLE_NORMAL);
+
+ if ((font_traits & kCTFontCondensedTrait) == kCTFontCondensedTrait)
+ pango2_font_description_set_stretch (font_desc, PANGO2_STRETCH_CONDENSED);
+
+ if (ct_font_descriptor_is_small_caps (desc))
+ pango2_font_description_set_variant (font_desc, PANGO2_VARIANT_SMALL_CAPS);
+ else
+ pango2_font_description_set_variant (font_desc, PANGO2_VARIANT_NORMAL);
+
+ g_free (style_name);
+
+ return font_desc;
+}
+
+static Pango2HbFace *
+face_from_ct_font_descriptor (CTFontDescriptorRef desc)
+{
+ Pango2HbFace *face;
+ CTFontRef ctfont;
+ CGFontRef cgfont;
+ hb_face_t *hb_face;
+ char *name;
+ Pango2FontDescription *description;
+
+ ctfont = CTFontCreateWithFontDescriptor (desc, 0.0, NULL);
+ cgfont = CTFontCopyGraphicsFont (ctfont, NULL);
+
+ hb_face = hb_coretext_face_create (cgfont);
+
+ CFRelease (cgfont);
+ CFRelease (ctfont);
+
+ name = ct_font_descriptor_get_style_name (desc);
+ description = font_description_from_ct_font_descriptor (desc);
+ pango2_font_description_unset_fields (description, PANGO2_FONT_MASK_VARIANT |
+ PANGO2_FONT_MASK_SIZE |
+ PANGO2_FONT_MASK_GRAVITY);
+
+ hb_face_make_immutable (hb_face);
+
+ face = pango2_hb_face_new_from_hb_face (hb_face, -1, name, description);
+ // FIXME: what about languages? see CTFontCopySupportedLanguages
+
+ pango2_font_description_free (description);
+ g_free (name);
+ hb_face_destroy (hb_face);
+
+ return face;
+}
+
+/* }}} */
+/* {{{ Pango2FontMap implementation */
+
+static void
+pango2_core_text_font_map_populate (Pango2FontMap *map)
+{
+ CTFontCollectionRef collection;
+ CFArrayRef ctfaces;
+ CFIndex count;
+
+ /* Add all system fonts */
+ collection = CTFontCollectionCreateFromAvailableFonts (0);
+ ctfaces = CTFontCollectionCreateMatchingFontDescriptors (collection);
+ count = CFArrayGetCount (ctfaces);
+
+ for (int i = 0; i < count; i++)
+ {
+ CTFontDescriptorRef desc = CFArrayGetValueAtIndex (ctfaces, i);
+ Pango2HbFace *face = face_from_ct_font_descriptor (desc);
+ pango2_font_map_add_face (map, PANGO2_FONT_FACE (face));
+ }
+
+ /* Add generic aliases */
+ struct {
+ const char *alias_name;
+ const char *family_name;
+ } aliases[] = {
+ { "Monospace", "Courier" },
+ { "Sans-serif", "Helvetica" },
+ { "Sans", "Helvetica" },
+ { "Serif", "Times" },
+ { "Cursive", "Apple Chancery" },
+ { "Fantasy", "Papyrus", },
+ { "System-ui", ".AppleSystemUIFont" },
+ { "Emoji", "Apple Color Emoji" }
+ };
+
+ for (int i = 0; i < G_N_ELEMENTS (aliases); i++)
+ {
+ Pango2FontFamily *family = pango2_font_map_get_family (map, aliases[i].family_name);
+
+ if (family)
+ {
+ Pango2GenericFamily *alias_family;
+
+ alias_family = pango2_generic_family_new (aliases[i].alias_name);
+ pango2_generic_family_add_family (alias_family, family);
+ pango2_font_map_add_family (map, PANGO2_FONT_FAMILY (alias_family));
+ }
+ }
+}
+
+/* }}} */
+/* {{{ Pango2CoreTextFontMap implementation */
+
+G_DEFINE_FINAL_TYPE (Pango2CoreTextFontMap, pango2_core_text_font_map, PANGO2_TYPE_FONT_MAP)
+
+static void
+pango2_core_text_font_map_init (Pango2CoreTextFontMap *self)
+{
+ pango2_font_map_repopulate (PANGO2_FONT_MAP (self), TRUE);
+}
+
+static void
+pango2_core_text_font_map_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (pango2_core_text_font_map_parent_class)->finalize (object);
+}
+
+static void
+pango2_core_text_font_map_class_init (Pango2CoreTextFontMapClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ Pango2FontMapClass *font_map_class = PANGO2_FONT_MAP_CLASS (class);
+
+ object_class->finalize = pango2_core_text_font_map_finalize;
+
+ font_map_class->populate = pango2_core_text_font_map_populate;
+}
+
+/* }}} */
+ /* {{{ Public API */
+
+/**
+ * pango2_core_text_font_map_new:
+ *
+ * Creates a new `Pango2CoreTextFontMap` object.
+ *
+ * Returns: a new `Pango2CoreTextFontMap`
+ */
+Pango2CoreTextFontMap *
+pango2_core_text_font_map_new (void)
+{
+ return g_object_new (PANGO2_TYPE_CORE_TEXT_FONT_MAP, NULL);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pangocoretext-fontmap.h b/pango2/pangocoretext-fontmap.h
new file mode 100644
index 00000000..ecbe7c51
--- /dev/null
+++ b/pango2/pangocoretext-fontmap.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 Red Hat, Inc
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_CORE_TEXT_FONT_MAP (pango2_core_text_font_map_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2CoreTextFontMap, pango2_core_text_font_map, PANGO2, CORE_TEXT_FONT_MAP, Pango2FontMap)
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2CoreTextFontMap * pango2_core_text_font_map_new (void);
+
+
+G_END_DECLS
diff --git a/pango2/pangodwrite-fontmap.cpp b/pango2/pangodwrite-fontmap.cpp
new file mode 100644
index 00000000..37307a3d
--- /dev/null
+++ b/pango2/pangodwrite-fontmap.cpp
@@ -0,0 +1,392 @@
+/* Pango
+ * pangodwrite-fontmap.cpp: Fontmap using DirectWrite
+ *
+ * Copyright (C) 2022 the GTK team
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include <windows.h>
+#include <initguid.h>
+#include <dwrite.h>
+
+#include <hb-ot.h>
+#include <hb-directwrite.h>
+
+#include "pangodwrite-fontmap.h"
+#include "pango-hbfamily-private.h"
+#include "pango-fontmap-private.h"
+#include "pango-hbface-private.h"
+#include "pango-hbfont-private.h"
+#include "pango-context.h"
+#include "pango-impl-utils.h"
+#include "pango-trace-private.h"
+
+
+/**
+ * PangoDirectWriteFontMap:
+ *
+ * `PangoDirectWriteFontMap` is a subclass of `PangoFontMap` that
+ * uses DirectWrite to populate the fontmap with the available fonts.
+ */
+
+
+struct _PangoDirectWriteFontMap
+{
+ PangoFontMap parent_instance;
+
+ IDWriteFactory *dwrite_factory;
+};
+
+struct _PangoDirectWriteFontMapClass
+{
+ PangoFontMapClass parent_class;
+};
+
+#ifdef _MSC_VER
+# define UUID_OF_IDWriteFactory __uuidof (IDWriteFactory)
+#else
+# define UUID_OF_IDWriteFactory IID_IDWriteFactory
+#endif
+
+/* {{{ DirectWrite utilities */
+
+static PangoStretch
+util_to_pango_stretch (DWRITE_FONT_STRETCH stretch)
+{
+ int value = (int) stretch;
+
+ if G_UNLIKELY (stretch <= DWRITE_FONT_STRETCH_UNDEFINED ||
+ stretch > DWRITE_FONT_STRETCH_ULTRA_EXPANDED)
+ return PANGO_STRETCH_NORMAL;
+
+ return (PangoStretch) --value;
+}
+
+static PangoStyle
+util_to_pango_style (DWRITE_FONT_STYLE style)
+{
+ switch (style)
+ {
+ case DWRITE_FONT_STYLE_NORMAL:
+ return PANGO_STYLE_NORMAL;
+ case DWRITE_FONT_STYLE_OBLIQUE:
+ return PANGO_STYLE_OBLIQUE;
+ case DWRITE_FONT_STYLE_ITALIC:
+ return PANGO_STYLE_ITALIC;
+ default:
+ g_assert_not_reached ();
+ return PANGO_STYLE_NORMAL;
+ }
+}
+
+static int
+util_map_weight (int weight)
+{
+ if G_UNLIKELY (weight < 100)
+ weight = 100;
+
+ if G_UNLIKELY (weight > 1000)
+ weight = 1000;
+
+ return weight;
+}
+
+static PangoWeight
+util_to_pango_weight (DWRITE_FONT_WEIGHT weight)
+{
+ /* DirectWrite weight values range from 1 to 999, Pango values
+ * range from 100 to 1000. */
+
+ return (PangoWeight) util_map_weight (weight);
+}
+
+static PangoFontDescription*
+util_get_pango_font_description (IDWriteFont *font,
+ const char *family_name)
+{
+ DWRITE_FONT_STRETCH stretch = font->GetStretch ();
+ DWRITE_FONT_STYLE style = font->GetStyle ();
+ DWRITE_FONT_WEIGHT weight = font->GetWeight ();
+ PangoFontDescription *description;
+
+ description = pango_font_description_new ();
+ pango_font_description_set_family (description, family_name);
+ pango_font_description_set_stretch (description, util_to_pango_stretch (stretch));
+ pango_font_description_set_style (description, util_to_pango_style (style));
+ pango_font_description_set_weight (description, util_to_pango_weight (weight));
+
+ return description;
+}
+
+static char*
+util_free_to_string (IDWriteLocalizedStrings *strings)
+{
+ char *string = NULL;
+ HRESULT hr;
+
+ if (strings->GetCount() > 0)
+ {
+ UINT32 index = 0;
+ BOOL exists = FALSE;
+ UINT32 length = 0;
+
+ hr = strings->FindLocaleName (L"en-us", &index, &exists);
+ if (FAILED (hr) || !exists || index == UINT32_MAX)
+ index = 0;
+
+ hr = strings->GetStringLength (index, &length);
+ if (SUCCEEDED (hr) && length > 0)
+ {
+ gunichar2 *string_utf16 = g_new (gunichar2, length + 1);
+
+ hr = strings->GetString (index, (wchar_t*) string_utf16, length + 1);
+ if (SUCCEEDED (hr))
+ string = g_utf16_to_utf8 (string_utf16, -1, NULL, NULL, NULL);
+
+ g_free (string_utf16);
+ }
+ }
+
+ strings->Release ();
+
+ return string;
+}
+
+static char*
+util_dwrite_get_font_variant_name (IDWriteFont *font)
+{
+ IDWriteLocalizedStrings *strings = NULL;
+ HRESULT hr;
+
+ hr = font->GetFaceNames (&strings);
+ if (FAILED (hr) || strings == NULL)
+ {
+ g_warning ("IDWriteFont::GetFaceNames failed with error code %x", (unsigned) hr);
+ return NULL;
+ }
+
+ return util_free_to_string (strings);
+}
+
+static char*
+util_dwrite_get_font_family_name (IDWriteFontFamily *family)
+{
+ IDWriteLocalizedStrings *strings = NULL;
+ HRESULT hr;
+
+ hr = family->GetFamilyNames (&strings);
+ if (FAILED (hr) || strings == NULL)
+ {
+ g_warning ("IDWriteFontFamily::GetFamilyNames failed with error code %x", (unsigned) hr);
+ return NULL;
+ }
+
+ return util_free_to_string (strings);
+}
+
+static PangoHbFace*
+util_create_pango_hb_face (IDWriteFontFamily *family,
+ IDWriteFont *font,
+ IDWriteFontFace *face)
+{
+ char *family_name = util_dwrite_get_font_family_name (family);
+ char *variant_name = util_dwrite_get_font_variant_name (font);
+ PangoHbFace *pango_face = NULL;
+
+ if (family_name && variant_name)
+ {
+ PangoFontDescription *description = util_get_pango_font_description (font, family_name);
+ hb_face_t *hb_face = hb_directwrite_face_create (face);
+ char *name = g_strconcat (family_name, " ", variant_name, NULL);
+
+ hb_face_make_immutable (hb_face);
+
+ pango_face = pango_hb_face_new_from_hb_face (hb_face, -1, name, description);
+
+ g_free (name);
+ hb_face_destroy (hb_face);
+ pango_font_description_free (description);
+ }
+
+ g_free (family_name);
+ g_free (variant_name);
+
+ return pango_face;
+}
+
+/* }}} */
+/* {{{ PangoFontMap implementation */
+
+static void
+pango_direct_write_font_map_populate (PangoFontMap *map)
+{
+ PangoDirectWriteFontMap *dwrite_map = PANGO_DIRECT_WRITE_FONT_MAP (map);
+ IDWriteFontCollection *collection = NULL;
+ UINT32 count;
+ HRESULT hr;
+
+ hr = dwrite_map->dwrite_factory->GetSystemFontCollection (&collection, FALSE);
+ if (FAILED (hr) || collection == NULL)
+ g_error ("IDWriteFactory::GetSystemFontCollection failed with error code %x\n", (unsigned)hr);
+
+ count = collection->GetFontFamilyCount ();
+
+ for (UINT32 i = 0; i < count; i++)
+ {
+ IDWriteFontFamily *family = NULL;
+ UINT32 font_count;
+
+ hr = collection->GetFontFamily (i, &family);
+ if G_UNLIKELY (FAILED (hr) || family == NULL)
+ {
+ g_warning ("IDWriteFontCollection::GetFontFamily failed with error code %x\n", (unsigned)hr);
+ continue;
+ }
+
+ font_count = family->GetFontCount ();
+
+ for (UINT32 j = 0; j < font_count; j++)
+ {
+ IDWriteFont *font = NULL;
+ IDWriteFontFace *face = NULL;
+ PangoHbFace *pango_face = NULL;
+
+ hr = family->GetFont (j, &font);
+ if (FAILED (hr) || font == NULL)
+ {
+ g_warning ("IDWriteFontFamily::GetFont failed with error code %x\n", (unsigned)hr);
+ break;
+ }
+
+ hr = font->CreateFontFace (&face);
+ if (FAILED (hr) || face == NULL)
+ {
+ g_warning ("IDWriteFont::CreateFontFace failed with error code %x\n", (unsigned)hr);
+ font->Release ();
+ continue;
+ }
+
+ pango_face = util_create_pango_hb_face (family, font, face);
+ if (pango_face)
+ pango_font_map_add_face (map, PANGO_FONT_FACE (pango_face));
+
+ face->Release ();
+ font->Release ();
+ }
+
+ family->Release ();
+ }
+
+ collection->Release ();
+ collection = NULL;
+
+ /* Add generic aliases */
+ struct {
+ const char *alias_name;
+ const char *family_name;
+ } aliases[] = {
+ { "Monospace", "Consolas" },
+ { "Sans-serif", "Arial" },
+ { "Sans", "Arial" },
+ { "Serif", "Times New Roman" },
+ { "System-ui", "Segoe UI" },
+ { "Emoji", "Segoe UI Emoji" }
+ };
+
+#if 0
+ if (IsWindows11OrLater ())
+ aliases[0].family_name = "Cascadia Mono";
+#endif
+
+ for (gsize i = 0; i < G_N_ELEMENTS (aliases); i++)
+ {
+ PangoFontFamily *family = pango_font_map_get_family (map, aliases[i].family_name);
+
+ if (family)
+ {
+ PangoGenericFamily *alias_family;
+
+ alias_family = pango_generic_family_new (aliases[i].alias_name);
+ pango_generic_family_add_family (alias_family, family);
+ pango_font_map_add_family (map, PANGO_FONT_FAMILY (alias_family));
+ }
+ }
+}
+
+/* }}} */
+/* {{{ PangoDirectWriteFontMap implementation */
+
+G_DEFINE_FINAL_TYPE (PangoDirectWriteFontMap, pango_direct_write_font_map, PANGO_TYPE_FONT_MAP)
+
+static void
+pango_direct_write_font_map_init (PangoDirectWriteFontMap *self)
+{
+ HRESULT hr;
+
+ hr = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
+ UUID_OF_IDWriteFactory,
+ reinterpret_cast<IUnknown**> (&self->dwrite_factory));
+
+ if (FAILED (hr) || !self->dwrite_factory)
+ g_error ("DWriteCreateFactory failed with error code %x", (unsigned)hr);
+
+ pango_font_map_repopulate (PANGO_FONT_MAP (self), TRUE);
+}
+
+static void
+pango_direct_write_font_map_finalize (GObject *object)
+{
+ PangoDirectWriteFontMap *dwrite_map = PANGO_DIRECT_WRITE_FONT_MAP (object);
+
+ dwrite_map->dwrite_factory->Release ();
+ dwrite_map->dwrite_factory = NULL;
+
+ G_OBJECT_CLASS (pango_direct_write_font_map_parent_class)->finalize (object);
+}
+
+static void
+pango_direct_write_font_map_class_init (PangoDirectWriteFontMapClass *class_)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class_);
+ PangoFontMapClass *font_map_class = PANGO_FONT_MAP_CLASS (class_);
+
+ object_class->finalize = pango_direct_write_font_map_finalize;
+
+ font_map_class->populate = pango_direct_write_font_map_populate;
+}
+
+/* }}} */
+ /* {{{ Public API */
+
+/**
+ * pango_direct_write_font_map_new:
+ *
+ * Creates a new `PangoDirectWriteFontMap` object.
+ *
+ * Returns: a new `PangoDirectWriteFontMap`
+ */
+PangoDirectWriteFontMap *
+pango_direct_write_font_map_new (void)
+{
+ return (PangoDirectWriteFontMap *) g_object_new (PANGO_TYPE_DIRECT_WRITE_FONT_MAP, NULL);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pangodwrite-fontmap.h b/pango2/pangodwrite-fontmap.h
new file mode 100644
index 00000000..c873759d
--- /dev/null
+++ b/pango2/pangodwrite-fontmap.h
@@ -0,0 +1,40 @@
+/* Pango2
+ * pangodwrite-fontmap.h: Fontmap using DirectWrite
+ *
+ * Copyright (C) 2022 the GTK team
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <pango2/pango.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_DIRECT_WRITE_FONT_MAP (pango2_direct_write_font_map_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2DirectWriteFontMap,
+ pango2_direct_write_font_map,
+ PANGO2, DIRECT_WRITE_FONT_MAP,
+ Pango2FontMap)
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2DirectWriteFontMap * pango2_direct_write_font_map_new (void);
+
+
+G_END_DECLS
diff --git a/pango2/pangofc-fontmap.c b/pango2/pangofc-fontmap.c
new file mode 100644
index 00000000..c3d026e2
--- /dev/null
+++ b/pango2/pangofc-fontmap.c
@@ -0,0 +1,624 @@
+/* Pango2
+ *
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include "config.h"
+#include <math.h>
+
+#include <gio/gio.h>
+
+#include "pangofc-fontmap.h"
+#include "pango-hbfamily-private.h"
+#include "pango-generic-family-private.h"
+#include "pango-fontmap-private.h"
+#include "pango-hbface-private.h"
+#include "pango-hbfont-private.h"
+#include "pango-context.h"
+#include "pango-impl-utils.h"
+#include "pango-trace-private.h"
+#include "pangofc-language-set-private.h"
+
+#include <fontconfig/fontconfig.h>
+#include <hb-ot.h>
+
+
+/**
+ * Pango2FcFontMap:
+ *
+ * `Pango2FcFontMap` is a subclass of `Pango2FontMap` that uses
+ * fontconfig to populate the fontmap with the available fonts.
+ */
+
+
+struct _Pango2FcFontMap
+{
+ Pango2FontMap parent_instance;
+
+ FcConfig *config;
+};
+
+struct _Pango2FcFontMapClass
+{
+ Pango2FontMapClass parent_class;
+};
+
+/* {{{ Fontconfig utilities */
+
+static gboolean
+is_supported_font_format (FcPattern *pattern)
+{
+ FcResult res;
+ const char *fontformat;
+ const char *file;
+
+ /* Harfbuzz loads woff fonts, but we don't get any glyphs */
+ res = FcPatternGetString (pattern, FC_FILE, 0, (FcChar8 **)(void*)&file);
+ if (res == FcResultMatch &&
+ (g_str_has_suffix (file, ".woff") || g_str_has_suffix (file, ".woff2")))
+ return FALSE;
+
+ res = FcPatternGetString (pattern, FC_FONTFORMAT, 0, (FcChar8 **)(void*)&fontformat);
+ if (res != FcResultMatch)
+ return FALSE;
+
+ /* Harfbuzz supports only SFNT fonts */
+
+ /* FIXME: "CFF" is used for both CFF in OpenType and bare CFF files, but
+ * HarfBuzz does not support the later and FontConfig does not seem
+ * to have a way to tell them apart.
+ */
+ if (g_ascii_strcasecmp (fontformat, "TrueType") == 0 ||
+ g_ascii_strcasecmp (fontformat, "CFF") == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static Pango2Style
+convert_fc_slant_to_pango (int fc_style)
+{
+ switch (fc_style)
+ {
+ case FC_SLANT_ROMAN:
+ return PANGO2_STYLE_NORMAL;
+ case FC_SLANT_ITALIC:
+ return PANGO2_STYLE_ITALIC;
+ case FC_SLANT_OBLIQUE:
+ return PANGO2_STYLE_OBLIQUE;
+ default:
+ return PANGO2_STYLE_NORMAL;
+ }
+}
+
+static Pango2Stretch
+convert_fc_width_to_pango (int fc_stretch)
+{
+ switch (fc_stretch)
+ {
+ case FC_WIDTH_NORMAL:
+ return PANGO2_STRETCH_NORMAL;
+ case FC_WIDTH_ULTRACONDENSED:
+ return PANGO2_STRETCH_ULTRA_CONDENSED;
+ case FC_WIDTH_EXTRACONDENSED:
+ return PANGO2_STRETCH_EXTRA_CONDENSED;
+ case FC_WIDTH_CONDENSED:
+ return PANGO2_STRETCH_CONDENSED;
+ case FC_WIDTH_SEMICONDENSED:
+ return PANGO2_STRETCH_SEMI_CONDENSED;
+ case FC_WIDTH_SEMIEXPANDED:
+ return PANGO2_STRETCH_SEMI_EXPANDED;
+ case FC_WIDTH_EXPANDED:
+ return PANGO2_STRETCH_EXPANDED;
+ case FC_WIDTH_EXTRAEXPANDED:
+ return PANGO2_STRETCH_EXTRA_EXPANDED;
+ case FC_WIDTH_ULTRAEXPANDED:
+ return PANGO2_STRETCH_ULTRA_EXPANDED;
+ default:
+ return PANGO2_STRETCH_NORMAL;
+ }
+}
+
+static Pango2Weight
+convert_fc_weight_to_pango (double fc_weight)
+{
+ return FcWeightToOpenTypeDouble (fc_weight);
+}
+
+#define PANGO2_FC_GRAVITY "gravity"
+
+/* Create font description that matches the pattern.
+ * We explicitly don't want to include variant, gravity,
+ * variations and font features here, since there are
+ * handled on the font level, by Pango2HbFont, not
+ * by Pango2HbFace.
+ */
+static Pango2FontDescription *
+pango2_font_description_from_pattern (FcPattern *pattern)
+{
+ Pango2FontDescription *desc;
+ Pango2Style style;
+ Pango2Weight weight;
+ Pango2Stretch stretch;
+ FcChar8 *s;
+ int i;
+ double d;
+ FcResult res;
+
+ desc = pango2_font_description_new ();
+
+ res = FcPatternGetString (pattern, FC_FAMILY, 0, (FcChar8 **) &s);
+ g_assert (res == FcResultMatch);
+
+ pango2_font_description_set_family (desc, (char *)s);
+
+ if (FcPatternGetInteger (pattern, FC_SLANT, 0, &i) == FcResultMatch)
+ style = convert_fc_slant_to_pango (i);
+ else
+ style = PANGO2_STYLE_NORMAL;
+
+ pango2_font_description_set_style (desc, style);
+
+ if (FcPatternGetDouble (pattern, FC_WEIGHT, 0, &d) == FcResultMatch)
+ weight = convert_fc_weight_to_pango (d);
+ else
+ weight = PANGO2_WEIGHT_NORMAL;
+
+ pango2_font_description_set_weight (desc, weight);
+
+ if (FcPatternGetInteger (pattern, FC_WIDTH, 0, &i) == FcResultMatch)
+ stretch = convert_fc_width_to_pango (i);
+ else
+ stretch = PANGO2_STRETCH_NORMAL;
+
+ pango2_font_description_set_stretch (desc, stretch);
+
+ return desc;
+}
+
+static const char *
+style_name_from_pattern (FcPattern *pattern)
+{
+ const char *font_style;
+ int weight, slant;
+
+ if (FcPatternGetString (pattern, FC_STYLE, 0, (FcChar8 **)(void*)&font_style) == FcResultMatch)
+ return font_style;
+
+ if (FcPatternGetInteger(pattern, FC_WEIGHT, 0, &weight) != FcResultMatch)
+ weight = FC_WEIGHT_MEDIUM;
+
+ if (FcPatternGetInteger(pattern, FC_SLANT, 0, &slant) != FcResultMatch)
+ slant = FC_SLANT_ROMAN;
+
+ if (weight <= FC_WEIGHT_MEDIUM)
+ {
+ if (slant == FC_SLANT_ROMAN)
+ return "Regular";
+ else
+ return "Italic";
+ }
+ else
+ {
+ if (slant == FC_SLANT_ROMAN)
+ return "Bold";
+ else
+ return "Bold Italic";
+ }
+
+ return "Normal";
+}
+
+static gboolean
+font_matrix_from_pattern (FcPattern *pattern,
+ Pango2Matrix *matrix)
+{
+ FcMatrix fc_matrix, *fc_matrix_val;
+ int i;
+
+ FcMatrixInit (&fc_matrix);
+ for (i = 0; FcPatternGetMatrix (pattern, FC_MATRIX, i, &fc_matrix_val) == FcResultMatch; i++)
+ FcMatrixMultiply (&fc_matrix, &fc_matrix, fc_matrix_val);
+ FcMatrixScale (&fc_matrix, 1, -1);
+
+ if (i > 0)
+ {
+ matrix->xx = fc_matrix.xx;
+ matrix->yx = fc_matrix.yx;
+ matrix->xy = fc_matrix.xy;
+ matrix->yy = fc_matrix.yy;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static Pango2HbFace *
+pango2_hb_face_from_pattern (Pango2FcFontMap *self,
+ FcPattern *pattern,
+ GHashTable *lang_hash)
+{
+ const char *family_name, *file;
+ int index;
+ int instance_id;
+ Pango2FontDescription *description;
+ const char *name;
+ Pango2HbFace *face;
+ Pango2Matrix font_matrix;
+ FcLangSet *langs;
+ gboolean variable;
+
+ if (!is_supported_font_format (pattern))
+ return NULL;
+
+ if (FcPatternGetString (pattern, FC_FAMILY, 0, (FcChar8 **)(void*)&family_name) != FcResultMatch)
+ return NULL;
+
+ if (FcPatternGetString (pattern, FC_FILE, 0, (FcChar8 **)(void*)&file) != FcResultMatch)
+ return NULL;
+
+ if (FcPatternGetInteger (pattern, FC_INDEX, 0, &index) != FcResultMatch)
+ index = 0;
+
+ if (FcPatternGetBool (pattern, FC_VARIABLE, 0, &variable) != FcResultMatch)
+ variable = FALSE;
+
+ instance_id = (index >> 16) - 1;
+ index = index & 0xffff;
+
+ if (variable)
+ instance_id = -2;
+
+ description = pango2_font_description_from_pattern (pattern);
+ name = style_name_from_pattern (pattern);
+
+ face = pango2_hb_face_new_from_file (file, index, instance_id, name, description);
+
+ pango2_font_description_free (description);
+
+ if (font_matrix_from_pattern (pattern, &font_matrix))
+ pango2_hb_face_set_matrix (face, &font_matrix);
+
+ if (FcPatternGetLangSet (pattern, FC_LANG, 0, &langs) == FcResultMatch)
+ {
+ Pango2FcLanguageSet lookup;
+ Pango2LanguageSet *languages;
+ FcLangSet *empty = FcLangSetCreate ();
+
+ if (!FcLangSetEqual (langs, empty))
+ {
+ lookup.langs = langs;
+ languages = g_hash_table_lookup (lang_hash, &lookup);
+ if (!languages)
+ {
+ languages = PANGO2_LANGUAGE_SET (pango2_fc_language_set_new (langs));
+ g_hash_table_add (lang_hash, languages);
+ }
+ pango2_hb_face_set_language_set (face, languages);
+ }
+
+ FcLangSetDestroy (empty);
+ }
+
+ return face;
+}
+
+/* }}} */
+/* {{{ Pango2FontMap implementation */
+
+static void
+pango2_fc_font_map_populate (Pango2FontMap *map)
+{
+ Pango2FcFontMap *self = PANGO2_FC_FONT_MAP (map);
+ FcPattern *pat;
+ FcFontSet *fontset;
+ int k;
+ GHashTable *lang_hash;
+ FcObjectSet *os;
+ gint64 before G_GNUC_UNUSED;
+
+ before = PANGO2_TRACE_CURRENT_TIME;
+
+ os = FcObjectSetBuild (FC_FAMILY, FC_SPACING, FC_STYLE, FC_WEIGHT,
+ FC_WIDTH, FC_SLANT, FC_VARIABLE, FC_FONTFORMAT,
+ FC_FILE, FC_INDEX, FC_LANG, NULL);
+
+ pat = FcPatternCreate ();
+ fontset = FcFontList (self->config, pat, os);
+ FcPatternDestroy (pat);
+
+ lang_hash = g_hash_table_new_full ((GHashFunc) pango2_fc_language_set_hash,
+ (GEqualFunc) pango2_fc_language_set_equal,
+ NULL, g_object_unref);
+
+ for (k = 0; k < fontset->nfont; k++)
+ {
+ Pango2HbFace *face = pango2_hb_face_from_pattern (self, fontset->fonts[k], lang_hash);
+ if (!face)
+ continue;
+
+ pango2_font_map_add_face (PANGO2_FONT_MAP (self), PANGO2_FONT_FACE (face));
+ }
+
+ g_hash_table_unref (lang_hash);
+
+ FcFontSetDestroy (fontset);
+
+ /* Add aliases */
+ const char *alias_names[] = {
+ "System-ui",
+ "Emoji"
+ };
+
+ for (int i = 0; i < G_N_ELEMENTS (alias_names); i++)
+ {
+ FcPattern *pattern;
+ FcPattern *ret;
+ FcResult res;
+ const char *family_name;
+
+ if (pango2_font_map_get_family (map, alias_names[i]))
+ continue;
+
+ pattern = FcPatternCreate ();
+ FcPatternAddString (pattern, FC_FAMILY, (FcChar8 *) alias_names[i]);
+
+ FcConfigSubstitute (self->config, pattern, FcMatchPattern);
+ FcDefaultSubstitute (pattern);
+
+ ret = FcFontMatch (self->config, pattern, &res);
+
+ if (FcPatternGetString (ret, FC_FAMILY, 0, (FcChar8 **)(void*)&family_name) == FcResultMatch)
+ {
+ Pango2FontFamily *family = pango2_font_map_get_family (map, family_name);
+ if (family)
+ {
+ Pango2GenericFamily *alias_family;
+
+ alias_family = pango2_generic_family_new (alias_names[i]);
+ pango2_generic_family_add_family (alias_family, family);
+ pango2_font_map_add_family (map, PANGO2_FONT_FAMILY (alias_family));
+ }
+ }
+
+ FcPatternDestroy (ret);
+ FcPatternDestroy (pattern);
+ }
+
+ /* Add generic aliases. Unfortunately, we need both sans-serif and sans,
+ * since the old fontconfig backend had 'Sans', and fontconfig itself
+ * has 'sans-serif'
+ */
+ const char *generic_names[] = {
+ "Monospace",
+ "Sans-serif",
+ "Sans",
+ "Serif",
+ "Cursive",
+ "Fantasy",
+ };
+ FcLangSet *no_langs = FcLangSetCreate ();
+ FcLangSet *emoji_langs = FcLangSetCreate ();
+
+ FcLangSetAdd (emoji_langs, (FcChar8 *)"und-zsye");
+
+ for (int i = 0; i < G_N_ELEMENTS (generic_names); i++)
+ {
+ Pango2GenericFamily *generic_family;
+ GHashTable *families_hash;
+ FcPattern *pattern;
+ FcFontSet *ret;
+ FcResult res;
+
+ if (pango2_font_map_get_family (map, generic_names[i]))
+ continue;
+
+ generic_family = pango2_generic_family_new (generic_names[i]);
+
+ families_hash = g_hash_table_new (NULL, NULL);
+
+ pattern = FcPatternCreate ();
+ FcPatternAddString (pattern, FC_FAMILY, (FcChar8 *) generic_names[i]);
+
+ FcConfigSubstitute (self->config, pattern, FcMatchPattern);
+ FcDefaultSubstitute (pattern);
+
+ ret = FcFontSort (self->config, pattern, TRUE, NULL, &res);
+ for (int j = 0; j < ret->nfont; j++)
+ {
+ Pango2HbFamily *family;
+ const char *file;
+ int index;
+ FcLangSet *langs;
+ int spacing;
+ const char *family_name;
+
+ pat = ret->fonts[j];
+
+ if (!is_supported_font_format (pat))
+ continue;
+
+ if (FcPatternGetLangSet (pat, FC_LANG, 0, &langs) != FcResultMatch)
+ continue;
+
+ if (FcLangSetEqual (langs, no_langs))
+ continue;
+
+ if ((strcmp (generic_names[i], "emoji") == 0) != FcLangSetEqual (langs, emoji_langs))
+ continue;
+
+ if (FcPatternGetInteger (pat, FC_SPACING, 0, &spacing) != FcResultMatch)
+ spacing = FC_PROPORTIONAL;
+
+ if (strcmp (generic_names[i], "monospace") == 0 && spacing != FC_MONO)
+ continue;
+
+ if (FcPatternGetString (pat, FC_FAMILY, 0, (FcChar8 **)(void*)&family_name) != FcResultMatch)
+ continue;
+
+ if (FcPatternGetString (pat, FC_FILE, 0, (FcChar8 **)(void*)&file) != FcResultMatch)
+ continue;
+
+ if (FcPatternGetInteger (pat, FC_INDEX, 0, &index) != FcResultMatch)
+ index = 0;
+
+ index = index & 0xffff;
+
+ family = PANGO2_HB_FAMILY (pango2_font_map_get_family (map, family_name));
+ if (!family)
+ continue;
+
+ if (g_hash_table_contains (families_hash, family))
+ continue;
+
+ pango2_generic_family_add_family (generic_family, PANGO2_FONT_FAMILY (family));
+ g_hash_table_add (families_hash, family);
+ }
+
+ FcFontSetDestroy (ret);
+ FcPatternDestroy (pattern);
+ g_hash_table_unref (families_hash);
+
+ if (g_list_model_get_n_items (G_LIST_MODEL (generic_family)) > 0)
+ pango2_font_map_add_family (map, PANGO2_FONT_FAMILY (generic_family));
+ else
+ g_object_unref (generic_family);
+ }
+
+ FcLangSetDestroy (no_langs);
+ FcLangSetDestroy (emoji_langs);
+
+ FcObjectSetDestroy (os);
+
+ pango2_trace_mark (before, "populate FcFontMap", NULL);
+}
+
+/* }}} */
+/* {{{ Pango2FcFontMap implementation */
+
+G_DEFINE_FINAL_TYPE (Pango2FcFontMap, pango2_fc_font_map, PANGO2_TYPE_FONT_MAP)
+
+static void
+pango2_fc_font_map_init (Pango2FcFontMap *self)
+{
+ pango2_font_map_repopulate (PANGO2_FONT_MAP (self), TRUE);
+}
+
+static void
+pango2_fc_font_map_finalize (GObject *object)
+{
+ Pango2FcFontMap *self = PANGO2_FC_FONT_MAP (object);
+
+ if (self->config)
+ FcConfigDestroy (self->config);
+
+ G_OBJECT_CLASS (pango2_fc_font_map_parent_class)->finalize (object);
+}
+
+static void
+pango2_fc_font_map_class_init (Pango2FcFontMapClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ Pango2FontMapClass *font_map_class = PANGO2_FONT_MAP_CLASS (class);
+ gint64 before G_GNUC_UNUSED;
+
+ before = PANGO2_TRACE_CURRENT_TIME;
+
+ FcInit ();
+
+ pango2_trace_mark (before, "FcInit", NULL);
+
+ object_class->finalize = pango2_fc_font_map_finalize;
+
+ font_map_class->populate = pango2_fc_font_map_populate;
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_fc_font_map_new:
+ *
+ * Creates a new `Pango2FcFontMap` using the default
+ * fontconfig configuration.
+ *
+ * Returns: a new `Pango2FcFontMap`
+ */
+Pango2FcFontMap *
+pango2_fc_font_map_new (void)
+{
+ return g_object_new (PANGO2_TYPE_FC_FONT_MAP, NULL);
+}
+
+/**
+ * pango2_fc_font_map_set_config: (skip)
+ * @self: a `Pango2FcFontMap`
+ * @config: (nullable): the `FcConfig` to use, or `NULL` to use
+ * the default configuration
+ *
+ * Sets the fontconfig configuration to use.
+ *
+ * Note that changing the fontconfig configuration
+ * removes all cached font families, faces and fonts.
+ */
+void
+pango2_fc_font_map_set_config (Pango2FcFontMap *self,
+ FcConfig *config)
+{
+ g_return_if_fail (PANGO2_IS_FC_FONT_MAP (self));
+
+ if (self->config == config && FcConfigUptoDate (config))
+ return;
+
+ if (self->config != config)
+ {
+ if (self->config)
+ FcConfigDestroy (self->config);
+
+ self->config = config;
+
+ if (self->config)
+ FcConfigReference (self->config);
+ }
+
+ pango2_font_map_repopulate (PANGO2_FONT_MAP (self), TRUE);
+}
+
+/**
+ * pango2_fc_font_map_get_config: (skip)
+ * @self: a `Pango2FcFontMap`
+ *
+ * Fetches the fontconfig configuration of the fontmap.
+ *
+ * See also: [method@Pango2Fc.FontMap.set_config].
+ *
+ * Returns: (nullable): the `FcConfig` object attached to
+ * @self, which might be `NULL`. The return value is
+ * owned by Pango2 and should not be freed.
+ */
+FcConfig *
+pango2_fc_font_map_get_config (Pango2FcFontMap *self)
+{
+ g_return_val_if_fail (PANGO2_IS_FC_FONT_MAP (self), NULL);
+
+ return self->config;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pangofc-fontmap.h b/pango2/pangofc-fontmap.h
new file mode 100644
index 00000000..7af8f0bb
--- /dev/null
+++ b/pango2/pangofc-fontmap.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 Red Hat, Inc
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango2/pango.h>
+#include <fontconfig/fontconfig.h>
+
+G_BEGIN_DECLS
+
+#define PANGO2_TYPE_FC_FONT_MAP (pango2_fc_font_map_get_type ())
+
+PANGO2_AVAILABLE_IN_ALL
+PANGO2_DECLARE_INTERNAL_TYPE (Pango2FcFontMap, pango2_fc_font_map, PANGO2, FC_FONT_MAP, Pango2FontMap)
+
+PANGO2_AVAILABLE_IN_ALL
+Pango2FcFontMap * pango2_fc_font_map_new (void);
+
+PANGO2_AVAILABLE_IN_ALL
+void pango2_fc_font_map_set_config (Pango2FcFontMap *self,
+ FcConfig *config);
+
+PANGO2_AVAILABLE_IN_ALL
+FcConfig * pango2_fc_font_map_get_config (Pango2FcFontMap *self);
+
+G_END_DECLS
diff --git a/pango2/pangofc-language-set-private.h b/pango2/pangofc-language-set-private.h
new file mode 100644
index 00000000..e1c48d4c
--- /dev/null
+++ b/pango2/pangofc-language-set-private.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-language-set-private.h"
+#include <fontconfig/fontconfig.h>
+
+
+#define PANGO2_TYPE_FC_LANGUAGE_SET (pango2_fc_language_set_get_type ())
+
+G_DECLARE_FINAL_TYPE (Pango2FcLanguageSet, pango2_fc_language_set, PANGO2, FC_LANGUAGE_SET, Pango2LanguageSet)
+
+struct _Pango2FcLanguageSet
+{
+ Pango2LanguageSet parent_instance;
+
+ FcLangSet *langs;
+};
+
+struct _Pango2FcLanguageSetClass
+{
+ Pango2LanguageSetClass parent_class;
+};
+
+Pango2FcLanguageSet * pango2_fc_language_set_new (FcLangSet *langs);
+
+guint pango2_fc_language_set_hash (const Pango2FcLanguageSet *self);
+gboolean pango2_fc_language_set_equal (const Pango2FcLanguageSet *a,
+ const Pango2FcLanguageSet *b);
diff --git a/pango2/pangofc-language-set.c b/pango2/pangofc-language-set.c
new file mode 100644
index 00000000..1c4534c2
--- /dev/null
+++ b/pango2/pangofc-language-set.c
@@ -0,0 +1,142 @@
+/* Pango2
+ *
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "pangofc-language-set-private.h"
+
+
+G_DEFINE_TYPE (Pango2FcLanguageSet, pango2_fc_language_set, PANGO2_TYPE_LANGUAGE_SET)
+
+static void
+pango2_fc_language_set_init (Pango2FcLanguageSet *self)
+{
+}
+
+static void
+pango2_fc_language_set_finalize (GObject *object)
+{
+ Pango2FcLanguageSet *self = PANGO2_FC_LANGUAGE_SET (object);
+
+ FcLangSetDestroy (self->langs);
+
+ G_OBJECT_CLASS (pango2_fc_language_set_parent_class)->finalize (object);
+}
+
+static gboolean
+pango2_fc_language_set_matches_language (Pango2LanguageSet *set,
+ Pango2Language *language)
+{
+ Pango2FcLanguageSet *self = PANGO2_FC_LANGUAGE_SET (set);
+ const char *s;
+
+ if (language == pango2_language_from_string ("c"))
+ return TRUE;
+
+ if (FcLangSetHasLang (self->langs, (FcChar8 *) pango2_language_to_string (language)) != FcLangDifferentLang)
+ return TRUE;
+
+ s = pango2_language_to_string (language);
+ if (strchr (s, '-'))
+ {
+ char buf[10];
+
+ for (int i = 0; i < 10; i++)
+ {
+ buf[i] = s[i];
+ if (buf[i] == '-')
+ {
+ buf[i] = '\0';
+ break;
+ }
+ }
+ buf[9] = '\0';
+
+ if (FcLangSetHasLang (self->langs, (FcChar8 *) buf) != FcLangDifferentLang)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static Pango2Language **
+pango2_fc_language_set_get_languages (Pango2LanguageSet *set)
+{
+ Pango2FcLanguageSet *self = PANGO2_FC_LANGUAGE_SET (set);
+ FcStrSet *strset;
+ FcStrList *list;
+ FcChar8 *s;
+ GPtrArray *langs;
+
+ langs = g_ptr_array_new ();
+
+ strset = FcLangSetGetLangs (self->langs);
+ list = FcStrListCreate (strset);
+
+ FcStrListFirst (list);
+ while ((s = FcStrListNext (list)))
+ g_ptr_array_add (langs, pango2_language_from_string ((const char *)s));
+
+ FcStrListDone (list);
+ FcStrSetDestroy (strset);
+
+ g_ptr_array_add (langs, NULL);
+
+ return (Pango2Language **) g_ptr_array_free (langs, FALSE);
+}
+
+static void
+pango2_fc_language_set_class_init (Pango2FcLanguageSetClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ Pango2LanguageSetClass *language_set_class = PANGO2_LANGUAGE_SET_CLASS (class);
+
+ object_class->finalize = pango2_fc_language_set_finalize;
+
+ language_set_class->matches_language = pango2_fc_language_set_matches_language;
+ language_set_class->get_languages = pango2_fc_language_set_get_languages;
+}
+
+Pango2FcLanguageSet *
+pango2_fc_language_set_new (FcLangSet *langs)
+{
+ Pango2FcLanguageSet *self;
+
+ self = g_object_new (PANGO2_TYPE_FC_LANGUAGE_SET, NULL);
+
+ self->langs = FcLangSetCopy (langs);
+
+ return self;
+}
+
+guint
+pango2_fc_language_set_hash (const Pango2FcLanguageSet *self)
+{
+ return (guint) FcLangSetHash (self->langs);
+}
+
+gboolean
+pango2_fc_language_set_equal (const Pango2FcLanguageSet *a,
+ const Pango2FcLanguageSet *b)
+{
+ return FcLangSetEqual (a->langs, b->langs);
+}
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/serializer.c b/pango2/serializer.c
new file mode 100644
index 00000000..4e7e8a39
--- /dev/null
+++ b/pango2/serializer.c
@@ -0,0 +1,1803 @@
+/* Pango2
+ * serializer.c: Code to serialize various Pango2 objects
+ *
+ * Copyright (C) 2021 Red Hat, Inc
+ *
+ * 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.
+ */
+
+#include "config.h"
+
+#include "pango-layout.h"
+#include "pango-context-private.h"
+#include "pango-enum-types.h"
+#include "pango-font-private.h"
+#include "pango-line-private.h"
+#include "pango-hbface.h"
+#include "pango-hbfont.h"
+#include "pango-attributes.h"
+#include "pango-attr-private.h"
+#include "pango-item-private.h"
+
+#include <hb-ot.h>
+#include "json/gtkjsonparserprivate.h"
+#include "json/gtkjsonprinterprivate.h"
+
+/* {{{ Error handling */
+
+G_DEFINE_QUARK(pango-layout-deserialize-error-quark, pango2_layout_deserialize_error)
+
+/* }}} */
+/* {{{ Enum names */
+
+static const char *style_names[] = {
+ "normal",
+ "oblique",
+ "italic",
+ NULL
+};
+
+static const char *variant_names[] = {
+ "normal",
+ "small-caps",
+ "all-small-caps",
+ "petite-caps",
+ "all-petite-caps",
+ "unicase",
+ "titlecase",
+ NULL
+};
+
+static const char *stretch_names[] = {
+ "ultra-condensed",
+ "extra-condensed",
+ "condensed",
+ "semi-condensed",
+ "normal",
+ "semi-expanded",
+ "expanded",
+ "extra-expanded",
+ "ultra-expanded",
+ NULL
+};
+
+static const char *line_style_names[] = {
+ "none",
+ "single",
+ "double",
+ "dashed",
+ "dotted",
+ "wavy",
+ NULL
+};
+
+static const char *underline_position_names[] = {
+ "normal",
+ "under",
+ NULL
+};
+
+static const char *gravity_names[] = {
+ "south",
+ "east",
+ "north",
+ "west",
+ "auto",
+ NULL
+};
+
+static const char *gravity_hint_names[] = {
+ "natural",
+ "strong",
+ "line",
+ NULL
+};
+
+static const char *text_transform_names[] = {
+ "none",
+ "lowercase",
+ "uppercase",
+ "capitalize",
+ NULL
+};
+
+static const char *baseline_shift_names[] = {
+ "none",
+ "superscript",
+ "subscript",
+ NULL
+};
+
+static const char *font_scale_names[] = {
+ "none",
+ "superscript",
+ "subscript",
+ "small-caps",
+ NULL
+};
+
+static const char *weight_names[] = {
+ "thin",
+ "ultralight",
+ "light",
+ "semilight",
+ "book",
+ "normal",
+ "medium",
+ "semibold",
+ "bold",
+ "ultrabold",
+ "heavy",
+ "ultraheavy",
+ NULL
+};
+
+static int named_weights[] = { 100, 200, 300, 350, 380, 400, 500, 600, 700, 800, 900, 1000 };
+
+static int
+get_weight (int pos)
+{
+ return named_weights[pos];
+}
+
+static const char *
+get_weight_name (int weight)
+{
+ for (int i = 0; i < G_N_ELEMENTS (named_weights); i++)
+ {
+ if (named_weights[i] == weight)
+ return weight_names[i];
+ }
+
+ return NULL;
+}
+
+static Pango2AttrType
+get_attr_type (const char *nick)
+{
+ GEnumClass *enum_class;
+ GEnumValue *enum_value = NULL;
+
+ enum_class = g_type_class_ref (PANGO2_TYPE_ATTR_TYPE);
+ for (int i = 0; i < enum_class->n_values; i++)
+ {
+ enum_value = &enum_class->values[i];
+ if (strcmp (enum_value->value_nick, nick) == 0)
+ break;
+ enum_value = NULL;
+ }
+ g_type_class_unref (enum_class);
+
+ if (enum_value)
+ return enum_value->value;
+
+ return 0;
+}
+
+static const char *
+get_attr_type_name (Pango2AttrType type)
+{
+ GEnumClass *enum_class;
+ GEnumValue *enum_value = NULL;
+
+ enum_class = g_type_class_ref (PANGO2_TYPE_ATTR_TYPE);
+ for (int i = 0; i < enum_class->n_values; i++)
+ {
+ enum_value = &enum_class->values[i];
+ if (enum_value->value == type)
+ break;
+ enum_value = NULL;
+ }
+ g_type_class_unref (enum_class);
+
+ if (enum_value)
+ return enum_value->value_nick;
+
+ return NULL;
+}
+
+static void
+get_script_name (GUnicodeScript script,
+ char *buf)
+{
+ guint32 tag = g_unicode_script_to_iso15924 (script);
+ hb_tag_to_string (tag, buf);
+}
+
+static const char *tab_align_names[] = {
+ "left",
+ "right",
+ "center",
+ "decimal",
+ NULL
+};
+
+static const char *direction_names[] = {
+ "ltr",
+ "rtl",
+ "ttb-ltr",
+ "ttb-rtl",
+ "weak-ltr",
+ "weak-rtl",
+ "neutral",
+ NULL
+};
+
+static const char *wrap_names[] = {
+ "word",
+ "char",
+ "word-char",
+ NULL
+};
+
+static const char *alignment_names[] = {
+ "left",
+ "center",
+ "right",
+ "natural",
+ "justify",
+ NULL
+};
+
+static const char *ellipsize_names[] = {
+ "none",
+ "start",
+ "middle",
+ "end",
+ NULL
+};
+
+/* }}} */
+/* {{{ Serialization */
+
+static char *
+font_description_to_string (Pango2FontDescription *desc)
+{
+ Pango2FontDescription *copy;
+ char *s;
+
+ /* Leave out the faceid for now, since it would make serialization
+ * backend-dependent.
+ */
+ copy = pango2_font_description_copy_static (desc);
+ pango2_font_description_unset_fields (copy, PANGO2_FONT_MASK_FACEID);
+ s = pango2_font_description_to_string (copy);
+ pango2_font_description_free (copy);
+
+ return s;
+}
+
+static void
+add_attribute (GtkJsonPrinter *printer,
+ Pango2Attribute *attr)
+{
+ char *str;
+
+ gtk_json_printer_start_object (printer, NULL);
+
+ if (attr->start_index != PANGO2_ATTR_INDEX_FROM_TEXT_BEGINNING)
+ gtk_json_printer_add_integer (printer, "start", (int)attr->start_index);
+ if (attr->end_index != PANGO2_ATTR_INDEX_TO_TEXT_END)
+ gtk_json_printer_add_integer (printer, "end", (int)attr->end_index);
+ gtk_json_printer_add_string (printer, "type", get_attr_type_name (attr->type));
+
+ switch (PANGO2_ATTR_VALUE_TYPE (attr))
+ {
+ case PANGO2_ATTR_VALUE_STRING:
+ gtk_json_printer_add_string (printer, "value", attr->str_value);
+ break;
+
+ case PANGO2_ATTR_VALUE_INT:
+ switch ((int)attr->type)
+ {
+ case PANGO2_ATTR_STYLE:
+ gtk_json_printer_add_string (printer, "value", style_names[attr->int_value]);
+ break;
+
+ case PANGO2_ATTR_VARIANT:
+ gtk_json_printer_add_string (printer, "value", variant_names[attr->int_value]);
+ break;
+
+ case PANGO2_ATTR_STRETCH:
+ gtk_json_printer_add_string (printer, "value", stretch_names[attr->int_value]);
+ break;
+
+ case PANGO2_ATTR_UNDERLINE:
+ case PANGO2_ATTR_STRIKETHROUGH:
+ case PANGO2_ATTR_OVERLINE:
+ gtk_json_printer_add_string (printer, "value", line_style_names[attr->int_value]);
+ break;
+
+ case PANGO2_ATTR_UNDERLINE_POSITION:
+ gtk_json_printer_add_string (printer, "value", underline_position_names[attr->int_value]);
+ break;
+
+ case PANGO2_ATTR_GRAVITY:
+ gtk_json_printer_add_string (printer, "value", gravity_names[attr->int_value]);
+ break;
+
+ case PANGO2_ATTR_GRAVITY_HINT:
+ gtk_json_printer_add_string (printer, "value", gravity_hint_names[attr->int_value]);
+ break;
+
+ case PANGO2_ATTR_TEXT_TRANSFORM:
+ gtk_json_printer_add_string (printer, "value", text_transform_names[attr->int_value]);
+ break;
+
+ case PANGO2_ATTR_FONT_SCALE:
+ gtk_json_printer_add_string (printer, "value", font_scale_names[attr->int_value]);
+ break;
+
+ case PANGO2_ATTR_WEIGHT:
+ {
+ const char *name = get_weight_name (attr->int_value);
+ if (name)
+ gtk_json_printer_add_string (printer, "value", name);
+ else
+ gtk_json_printer_add_integer (printer, "value", attr->int_value);
+ }
+ break;
+
+ case PANGO2_ATTR_BASELINE_SHIFT:
+ gtk_json_printer_add_string (printer, "value", baseline_shift_names[attr->int_value]);
+ break;
+
+ default:
+ gtk_json_printer_add_integer (printer, "value", attr->int_value);
+ break;
+ }
+
+ break;
+
+ case PANGO2_ATTR_VALUE_BOOLEAN:
+ gtk_json_printer_add_boolean (printer, "value", attr->boolean_value);
+ break;
+
+ case PANGO2_ATTR_VALUE_LANGUAGE:
+ gtk_json_printer_add_string (printer, "value", pango2_language_to_string (attr->lang_value));
+ break;
+
+ case PANGO2_ATTR_VALUE_FONT_DESC:
+ str = font_description_to_string (attr->font_value);
+ gtk_json_printer_add_string (printer, "value", str);
+ g_free (str);
+ break;
+
+ case PANGO2_ATTR_VALUE_COLOR:
+ str = pango2_color_to_string (&attr->color_value);
+ gtk_json_printer_add_string (printer, "value", str);
+ g_free (str);
+ break;
+
+ case PANGO2_ATTR_VALUE_FLOAT:
+ gtk_json_printer_add_number (printer, "value", attr->double_value);
+ break;
+
+ case PANGO2_ATTR_VALUE_POINTER:
+ str = pango2_attr_value_serialize (attr);
+ gtk_json_printer_add_string (printer, "value", str);
+ g_free (str);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ gtk_json_printer_end (printer);
+}
+
+static void
+add_attr_list (GtkJsonPrinter *printer,
+ Pango2AttrList *attrs)
+{
+ GSList *attributes, *l;
+
+ if (!attrs)
+ return;
+
+ attributes = pango2_attr_list_get_attributes (attrs);
+
+ if (!attributes)
+ return;
+
+ gtk_json_printer_start_array (printer, "attributes");
+
+ for (l = attributes; l; l = l->next)
+ {
+ Pango2Attribute *attr = l->data;
+ add_attribute (printer, attr);
+ }
+ g_slist_free_full (attributes, (GDestroyNotify) pango2_attribute_destroy);
+
+ gtk_json_printer_end (printer);
+}
+
+static void
+add_tab_array (GtkJsonPrinter *printer,
+ Pango2TabArray *tabs)
+{
+ if (!tabs || pango2_tab_array_get_size (tabs) == 0)
+ return;
+
+ gtk_json_printer_start_object (printer, "tabs");
+
+ gtk_json_printer_add_boolean (printer, "positions-in-pixels", pango2_tab_array_get_positions_in_pixels (tabs));
+ gtk_json_printer_start_array (printer, "positions");
+ for (int i = 0; i < pango2_tab_array_get_size (tabs); i++)
+ {
+ Pango2TabAlign align;
+ int pos;
+ pango2_tab_array_get_tab (tabs, i, &align, &pos);
+ gtk_json_printer_start_object (printer, NULL);
+ gtk_json_printer_add_integer (printer, "position", pos);
+ gtk_json_printer_add_string (printer, "alignment", tab_align_names[align]);
+ gtk_json_printer_add_integer (printer, "decimal-point", pango2_tab_array_get_decimal_point (tabs, i));
+ gtk_json_printer_end (printer);
+ }
+ gtk_json_printer_end (printer);
+
+ gtk_json_printer_end (printer);
+}
+
+static void
+add_context (GtkJsonPrinter *printer,
+ Pango2Context *context)
+{
+ char *str;
+ const Pango2Matrix *matrix;
+ Pango2Matrix identity = PANGO2_MATRIX_INIT;
+
+ gtk_json_printer_start_object (printer, "context");
+
+ /* Note: since we don't create the context when deserializing,
+ * we don't strip out default values here to ensure that the
+ * context gets updated as expected.
+ */
+
+ str = font_description_to_string (context->font_desc);
+ gtk_json_printer_add_string (printer, "font", str);
+ g_free (str);
+
+ if (context->set_language)
+ gtk_json_printer_add_string (printer, "language", pango2_language_to_string (context->set_language));
+
+ gtk_json_printer_add_string (printer, "base-gravity", gravity_names[context->base_gravity]);
+ gtk_json_printer_add_string (printer, "gravity-hint", gravity_hint_names[context->gravity_hint]);
+ gtk_json_printer_add_string (printer, "base-dir", direction_names[context->base_dir]);
+ gtk_json_printer_add_boolean (printer, "round-glyph-positions", context->round_glyph_positions);
+
+ matrix = pango2_context_get_matrix (context);
+ if (!matrix)
+ matrix = &identity;
+
+ gtk_json_printer_start_array (printer, "transform");
+ gtk_json_printer_add_number (printer, NULL, matrix->xx);
+ gtk_json_printer_add_number (printer, NULL, matrix->xy);
+ gtk_json_printer_add_number (printer, NULL, matrix->yx);
+ gtk_json_printer_add_number (printer, NULL, matrix->yy);
+ gtk_json_printer_add_number (printer, NULL, matrix->x0);
+ gtk_json_printer_add_number (printer, NULL, matrix->y0);
+ gtk_json_printer_end (printer);
+
+ gtk_json_printer_end (printer);
+}
+
+static void
+add_log_attrs (GtkJsonPrinter *printer,
+ const Pango2LogAttr *log_attrs,
+ int n_attrs)
+{
+ gtk_json_printer_start_array (printer, "log-attrs");
+
+ for (int i = 0; i < n_attrs; i++)
+ {
+ gtk_json_printer_start_object (printer, NULL);
+ if (log_attrs[i].is_line_break)
+ gtk_json_printer_add_boolean (printer, "line-break", TRUE);
+ if (log_attrs[i].is_mandatory_break)
+ gtk_json_printer_add_boolean (printer, "mandatory-break", TRUE);
+ if (log_attrs[i].is_char_break)
+ gtk_json_printer_add_boolean (printer, "char-break", TRUE);
+ if (log_attrs[i].is_white)
+ gtk_json_printer_add_boolean (printer, "white", TRUE);
+ if (log_attrs[i].is_cursor_position)
+ gtk_json_printer_add_boolean (printer, "cursor-position", TRUE);
+ if (log_attrs[i].is_word_start)
+ gtk_json_printer_add_boolean (printer, "word-start", TRUE);
+ if (log_attrs[i].is_word_end)
+ gtk_json_printer_add_boolean (printer, "word-end", TRUE);
+ if (log_attrs[i].is_sentence_boundary)
+ gtk_json_printer_add_boolean (printer, "sentence-boundary", TRUE);
+ if (log_attrs[i].is_sentence_start)
+ gtk_json_printer_add_boolean (printer, "sentence-start", TRUE);
+ if (log_attrs[i].is_sentence_end)
+ gtk_json_printer_add_boolean (printer, "sentence-end", TRUE);
+ if (log_attrs[i].backspace_deletes_character)
+ gtk_json_printer_add_boolean (printer, "backspace-deletes-character", TRUE);
+ if (log_attrs[i].is_expandable_space)
+ gtk_json_printer_add_boolean (printer, "expandable-space", TRUE);
+ if (log_attrs[i].is_word_boundary)
+ gtk_json_printer_add_boolean (printer, "word-boundary", TRUE);
+ if (log_attrs[i].break_inserts_hyphen)
+ gtk_json_printer_add_boolean (printer, "break-inserts-hyphen", TRUE);
+ if (log_attrs[i].break_removes_preceding)
+ gtk_json_printer_add_boolean (printer, "break-removes-preceding", TRUE);
+ gtk_json_printer_end (printer);
+ }
+
+ gtk_json_printer_end (printer);
+}
+
+static void
+add_font (GtkJsonPrinter *printer,
+ const char *member,
+ Pango2Font *font)
+{
+ Pango2FontDescription *desc;
+ char *str;
+ hb_font_t *hb_font;
+ hb_face_t *face;
+ hb_blob_t *blob;
+ const char *data;
+ guint length;
+ const int *coords;
+ const hb_feature_t *features;
+ Pango2Matrix matrix;
+
+ gtk_json_printer_start_object (printer, member);
+
+ desc = pango2_font_describe (font);
+ str = font_description_to_string (desc);
+ gtk_json_printer_add_string (printer, "description", str);
+ g_free (str);
+ pango2_font_description_free (desc);
+
+ hb_font = pango2_font_get_hb_font (font);
+ face = hb_font_get_face (hb_font);
+ blob = hb_face_reference_blob (face);
+
+ data = hb_blob_get_data (blob, &length);
+ str = g_compute_checksum_for_data (G_CHECKSUM_SHA256, (const guchar *)data, length);
+
+ gtk_json_printer_add_string (printer, "checksum", str);
+
+ g_free (str);
+ hb_blob_destroy (blob);
+
+ coords = hb_font_get_var_coords_normalized (hb_font, &length);
+ if (length > 0)
+ {
+ guint count;
+ hb_ot_var_axis_info_t *axes;
+
+ count = hb_ot_var_get_axis_count (face);
+ g_assert (count == length);
+
+ axes = g_alloca (count * sizeof (hb_ot_var_axis_info_t));
+ hb_ot_var_get_axis_infos (face, 0, &count, axes);
+
+ gtk_json_printer_start_object (printer, "variations");
+
+ for (int i = 0; i < length; i++)
+ {
+ char buf[5] = { 0, };
+
+ hb_tag_to_string (axes[i].tag, buf);
+ gtk_json_printer_add_integer (printer, buf, coords[i]);
+ }
+
+ gtk_json_printer_end (printer);
+ }
+
+ length = 0;
+ if (PANGO2_IS_HB_FONT (font))
+ features = pango2_hb_font_get_features (PANGO2_HB_FONT (font), &length);
+ if (length > 0)
+ {
+ gtk_json_printer_start_object (printer, "features");
+
+ for (int i = 0; i < length; i++)
+ {
+ char buf[5] = { 0, };
+
+ hb_tag_to_string (features[i].tag, buf);
+ gtk_json_printer_add_integer (printer, buf, features[i].value);
+ }
+
+ gtk_json_printer_end (printer);
+ }
+
+ pango2_font_get_transform (font, &matrix);
+ if (!pango2_matrix_equal (&matrix, &(const Pango2Matrix)PANGO2_MATRIX_INIT))
+ {
+ gtk_json_printer_start_array (printer, "matrix");
+ gtk_json_printer_add_number (printer, NULL, matrix.xx);
+ gtk_json_printer_add_number (printer, NULL, matrix.xy);
+ gtk_json_printer_add_number (printer, NULL, matrix.yx);
+ gtk_json_printer_add_number (printer, NULL, matrix.yy);
+ gtk_json_printer_add_number (printer, NULL, matrix.x0);
+ gtk_json_printer_add_number (printer, NULL, matrix.y0);
+ gtk_json_printer_end (printer);
+ }
+
+ gtk_json_printer_end (printer);
+}
+
+#define ANALYSIS_FLAGS (PANGO2_ANALYSIS_FLAG_CENTERED_BASELINE | \
+ PANGO2_ANALYSIS_FLAG_IS_ELLIPSIS | \
+ PANGO2_ANALYSIS_FLAG_NEED_HYPHEN)
+
+static void
+add_run (GtkJsonPrinter *printer,
+ const char *text,
+ Pango2GlyphItem *run)
+{
+ char *str;
+ char buf[5] = { 0, };
+
+ gtk_json_printer_start_object (printer, NULL);
+
+ gtk_json_printer_add_integer (printer, "offset", run->item->offset);
+ gtk_json_printer_add_integer (printer, "length", run->item->length);
+
+ str = g_strndup (text + run->item->offset, run->item->length);
+ gtk_json_printer_add_string (printer, "text", str);
+ g_free (str);
+
+ gtk_json_printer_add_integer (printer, "bidi-level", run->item->analysis.level);
+ gtk_json_printer_add_string (printer, "gravity", gravity_names[run->item->analysis.gravity]);
+ gtk_json_printer_add_string (printer, "language", pango2_language_to_string (run->item->analysis.language));
+ get_script_name (run->item->analysis.script, buf);
+ gtk_json_printer_add_string (printer, "script", buf);
+
+ add_font (printer, "font", run->item->analysis.font);
+
+ gtk_json_printer_add_integer (printer, "flags", run->item->analysis.flags & ANALYSIS_FLAGS);
+
+ if (run->item->analysis.extra_attrs)
+ {
+ GSList *l;
+
+ gtk_json_printer_start_array (printer, "extra-attributes");
+ for (l = run->item->analysis.extra_attrs; l; l = l->next)
+ {
+ Pango2Attribute *attr = l->data;
+ add_attribute (printer, attr);
+ }
+ gtk_json_printer_end (printer);
+ }
+
+ gtk_json_printer_add_integer (printer, "y-offset", run->y_offset);
+ gtk_json_printer_add_integer (printer, "start-x-offset", run->start_x_offset);
+ gtk_json_printer_add_integer (printer, "end-x-offset", run->end_x_offset);
+
+ gtk_json_printer_start_array (printer, "glyphs");
+ for (int i = 0; i < run->glyphs->num_glyphs; i++)
+ {
+ gtk_json_printer_start_object (printer, NULL);
+
+ gtk_json_printer_add_integer (printer, "glyph", run->glyphs->glyphs[i].glyph);
+ gtk_json_printer_add_integer (printer, "width", run->glyphs->glyphs[i].geometry.width);
+
+ if (run->glyphs->glyphs[i].geometry.x_offset != 0)
+ gtk_json_printer_add_integer (printer, "x-offset", run->glyphs->glyphs[i].geometry.x_offset);
+
+ if (run->glyphs->glyphs[i].geometry.y_offset != 0)
+ gtk_json_printer_add_integer (printer, "y-offset", run->glyphs->glyphs[i].geometry.y_offset);
+
+ if (run->glyphs->glyphs[i].attr.is_cluster_start)
+ gtk_json_printer_add_boolean (printer, "is-cluster-start", TRUE);
+
+ if (run->glyphs->glyphs[i].attr.is_color)
+ gtk_json_printer_add_boolean (printer, "is-color", TRUE);
+
+ gtk_json_printer_add_integer (printer, "log-cluster", run->glyphs->log_clusters[i]);
+
+ gtk_json_printer_end (printer);
+ }
+
+ gtk_json_printer_end (printer);
+
+ gtk_json_printer_end (printer);
+}
+
+#undef ANALYSIS_FLAGS
+
+static void
+line_to_json (GtkJsonPrinter *printer,
+ Pango2Line *line,
+ int x,
+ int y)
+{
+ gtk_json_printer_start_object (printer, NULL);
+
+ gtk_json_printer_start_array (printer, "position");
+ gtk_json_printer_add_number (printer, NULL, x);
+ gtk_json_printer_add_number (printer, NULL, y);
+ gtk_json_printer_end (printer);
+
+ gtk_json_printer_start_object (printer, "line");
+
+ gtk_json_printer_add_integer (printer, "start-index", line->start_index);
+ gtk_json_printer_add_integer (printer, "length", line->length);
+ gtk_json_printer_add_integer (printer, "start-offset", line->start_offset);
+ gtk_json_printer_add_integer (printer, "n-chars", line->n_chars);
+
+ gtk_json_printer_add_boolean (printer, "wrapped", line->wrapped);
+ gtk_json_printer_add_boolean (printer, "ellipsized", line->ellipsized);
+ gtk_json_printer_add_boolean (printer, "hyphenated", line->hyphenated);
+ gtk_json_printer_add_boolean (printer, "justified", line->justified);
+ gtk_json_printer_add_boolean (printer, "paragraph-start", line->starts_paragraph);
+ gtk_json_printer_add_boolean (printer, "paragraph-end", line->ends_paragraph);
+ gtk_json_printer_add_string (printer, "direction", direction_names[line->direction]);
+
+ gtk_json_printer_start_array (printer, "runs");
+ for (GSList *l = line->runs; l; l = l->next)
+ {
+ Pango2GlyphItem *run = l->data;
+ add_run (printer, line->data->text, run);
+ }
+ gtk_json_printer_end (printer);
+
+ gtk_json_printer_end (printer);
+
+ gtk_json_printer_end (printer);
+}
+
+static void
+lines_to_json (GtkJsonPrinter *printer,
+ Pango2Lines *lines)
+{
+ int width, height;
+ Pango2Line **l;
+
+ gtk_json_printer_start_object (printer, "output");
+
+ gtk_json_printer_add_boolean (printer, "wrapped", pango2_lines_is_wrapped (lines));
+ gtk_json_printer_add_boolean (printer, "ellipsized", pango2_lines_is_ellipsized (lines));
+ gtk_json_printer_add_boolean (printer, "hypenated", pango2_lines_is_hyphenated (lines));
+ gtk_json_printer_add_integer (printer, "unknown-glyphs", pango2_lines_get_unknown_glyphs_count (lines));
+ pango2_lines_get_size (lines, &width, &height);
+ gtk_json_printer_add_integer (printer, "width", width);
+ gtk_json_printer_add_integer (printer, "height", height);
+
+ gtk_json_printer_start_array (printer, "lines");
+
+ l = pango2_lines_get_lines (lines);
+ for (int i = 0; i < pango2_lines_get_line_count (lines); i++)
+ {
+ int x, y;
+ pango2_lines_get_line_position (lines, i, &x, &y);
+ line_to_json (printer, l[i], x, y);
+ }
+
+ gtk_json_printer_end (printer);
+
+ gtk_json_printer_end (printer);
+}
+
+static void
+layout_to_json (GtkJsonPrinter *printer,
+ Pango2Layout *layout,
+ Pango2LayoutSerializeFlags flags)
+{
+ const char *str;
+
+ gtk_json_printer_start_object (printer, NULL);
+
+ if (flags & PANGO2_LAYOUT_SERIALIZE_CONTEXT)
+ add_context (printer, pango2_layout_get_context (layout));
+
+ str = (const char *) g_object_get_data (G_OBJECT (layout), "comment");
+ if (str)
+ gtk_json_printer_add_string (printer, "comment", str);
+
+ gtk_json_printer_add_string (printer, "text", pango2_layout_get_text (layout));
+
+ add_attr_list (printer, pango2_layout_get_attributes (layout));
+
+ if (pango2_layout_get_font_description (layout))
+ {
+ char *str = pango2_font_description_to_string (pango2_layout_get_font_description (layout));
+ gtk_json_printer_add_string (printer, "font", str);
+ g_free (str);
+ }
+
+ add_tab_array (printer, pango2_layout_get_tabs (layout));
+
+ if (!pango2_layout_get_auto_dir (layout))
+ gtk_json_printer_add_boolean (printer, "auto-dir", FALSE);
+
+ if (pango2_layout_get_alignment (layout) != PANGO2_ALIGN_NATURAL)
+ gtk_json_printer_add_string (printer, "alignment", alignment_names[pango2_layout_get_alignment (layout)]);
+
+ if (pango2_layout_get_wrap (layout) != PANGO2_WRAP_WORD)
+ gtk_json_printer_add_string (printer, "wrap", wrap_names[pango2_layout_get_wrap (layout)]);
+
+ if (pango2_layout_get_ellipsize (layout) != PANGO2_ELLIPSIZE_NONE)
+ gtk_json_printer_add_string (printer, "ellipsize", ellipsize_names[pango2_layout_get_ellipsize (layout)]);
+
+ if (pango2_layout_get_width (layout) != -1)
+ gtk_json_printer_add_integer (printer, "width", pango2_layout_get_width (layout));
+
+ if (pango2_layout_get_height (layout) != -1)
+ gtk_json_printer_add_integer (printer, "height", pango2_layout_get_height (layout));
+
+ if (pango2_layout_get_indent (layout) != 0)
+ gtk_json_printer_add_integer (printer, "indent", pango2_layout_get_indent (layout));
+
+ if (pango2_layout_get_line_height (layout) != 0.)
+ gtk_json_printer_add_number (printer, "line-height", pango2_layout_get_line_height (layout));
+
+ if (pango2_layout_get_spacing (layout) != 0)
+ gtk_json_printer_add_integer (printer, "spacing", pango2_layout_get_spacing (layout));
+
+ if (flags & PANGO2_LAYOUT_SERIALIZE_OUTPUT)
+ {
+ const Pango2LogAttr *log_attrs;
+ int n_attrs;
+
+ log_attrs = pango2_layout_get_log_attrs (layout, &n_attrs);
+ add_log_attrs (printer, log_attrs, n_attrs);
+
+ lines_to_json (printer, pango2_layout_get_lines (layout));
+ }
+
+ gtk_json_printer_end (printer);
+}
+
+static void
+gstring_write (GtkJsonPrinter *printer,
+ const char *s,
+ gpointer data)
+{
+ GString *str = data;
+ g_string_append (str, s);
+}
+
+/* }}} */
+/* {{{ Deserialization */
+
+static int
+parser_select_string (GtkJsonParser *parser,
+ const char **options)
+{
+ int value;
+
+ value = gtk_json_parser_select_string (parser, options);
+ if (value == -1)
+ {
+ char *str = gtk_json_parser_get_string (parser);
+ char *opts = g_strjoinv (", ", (char **)options);
+
+ gtk_json_parser_value_error (parser,
+ "Failed to parse string: %s, valid options are: %s",
+ str, opts);
+
+ g_free (opts);
+ g_free (str);
+
+ value = 0;
+ }
+
+ return value;
+}
+
+static Pango2FontDescription *
+parser_get_font_description (GtkJsonParser *parser)
+{
+ char *str = gtk_json_parser_get_string (parser);
+ Pango2FontDescription *desc = pango2_font_description_from_string (str);
+
+ if (!desc)
+ gtk_json_parser_value_error (parser,
+ "Failed to parse font: %s", str);
+ g_free (str);
+
+ return desc;
+}
+
+static void
+parser_get_color (GtkJsonParser *parser,
+ Pango2Color *color)
+{
+ char *str = gtk_json_parser_get_string (parser);
+ if (!pango2_color_parse (color, str))
+ {
+ gtk_json_parser_value_error (parser,
+ "Failed to parse color: %s", str);
+ color->red = color->green = color->blue = 0;
+ }
+
+ g_free (str);
+}
+
+static Pango2Attribute *
+attr_for_type (GtkJsonParser *parser,
+ Pango2AttrType type,
+ int start,
+ int end)
+{
+ Pango2Attribute *attr;
+ Pango2FontDescription *desc;
+ Pango2Color color;
+ char *str;
+
+ switch (type)
+ {
+ case PANGO2_ATTR_SHAPE:
+ default:
+ g_assert_not_reached ();
+
+ case PANGO2_ATTR_INVALID:
+ gtk_json_parser_schema_error (parser, "Missing attribute type");
+ return NULL;
+
+ case PANGO2_ATTR_LANGUAGE:
+ str = gtk_json_parser_get_string (parser);
+ attr = pango2_attr_language_new (pango2_language_from_string (str));
+ g_free (str);
+ break;
+
+ case PANGO2_ATTR_FAMILY:
+ str = gtk_json_parser_get_string (parser);
+ attr = pango2_attr_family_new (str);
+ g_free (str);
+ break;
+
+ case PANGO2_ATTR_STYLE:
+ attr = pango2_attr_style_new ((Pango2Style) parser_select_string (parser, style_names));
+ break;
+
+ case PANGO2_ATTR_WEIGHT:
+ if (gtk_json_parser_get_node (parser) == GTK_JSON_STRING)
+ attr = pango2_attr_weight_new (get_weight (parser_select_string (parser, weight_names)));
+ else
+ attr = pango2_attr_weight_new ((int) gtk_json_parser_get_int (parser));
+ break;
+
+ case PANGO2_ATTR_VARIANT:
+ attr = pango2_attr_variant_new ((Pango2Variant) parser_select_string (parser, variant_names));
+ break;
+
+ case PANGO2_ATTR_STRETCH:
+ attr = pango2_attr_stretch_new ((Pango2Stretch) parser_select_string (parser, stretch_names));
+ break;
+
+ case PANGO2_ATTR_SIZE:
+ attr = pango2_attr_size_new ((int) gtk_json_parser_get_number (parser));
+ break;
+
+ case PANGO2_ATTR_FONT_DESC:
+ desc = parser_get_font_description (parser);
+ attr = pango2_attr_font_desc_new (desc);
+ pango2_font_description_free (desc);
+ break;
+
+ case PANGO2_ATTR_FOREGROUND:
+ parser_get_color (parser, &color);
+ attr = pango2_attr_foreground_new (&color);
+ break;
+
+ case PANGO2_ATTR_BACKGROUND:
+ parser_get_color (parser, &color);
+ attr = pango2_attr_background_new (&color);
+ break;
+
+ case PANGO2_ATTR_UNDERLINE:
+ attr = pango2_attr_underline_new ((Pango2LineStyle) parser_select_string (parser, line_style_names));
+ break;
+
+ case PANGO2_ATTR_UNDERLINE_POSITION:
+ attr = pango2_attr_underline_position_new ((Pango2UnderlinePosition) parser_select_string (parser, underline_position_names));
+ break;
+
+ case PANGO2_ATTR_STRIKETHROUGH:
+ attr = pango2_attr_strikethrough_new ((Pango2LineStyle) parser_select_string (parser, line_style_names));
+ break;
+
+ case PANGO2_ATTR_RISE:
+ attr = pango2_attr_rise_new ((int) gtk_json_parser_get_number (parser));
+ break;
+
+ case PANGO2_ATTR_SCALE:
+ attr = pango2_attr_scale_new (gtk_json_parser_get_number (parser));
+ break;
+
+ case PANGO2_ATTR_FALLBACK:
+ attr = pango2_attr_fallback_new (gtk_json_parser_get_boolean (parser));
+ break;
+
+ case PANGO2_ATTR_LETTER_SPACING:
+ attr = pango2_attr_letter_spacing_new ((int) gtk_json_parser_get_number (parser));
+ break;
+
+ case PANGO2_ATTR_UNDERLINE_COLOR:
+ parser_get_color (parser, &color);
+ attr = pango2_attr_underline_color_new (&color);
+ break;
+
+ case PANGO2_ATTR_STRIKETHROUGH_COLOR:
+ parser_get_color (parser, &color);
+ attr = pango2_attr_strikethrough_color_new (&color);
+ break;
+
+ case PANGO2_ATTR_ABSOLUTE_SIZE:
+ attr = pango2_attr_size_new_absolute ((int) gtk_json_parser_get_number (parser));
+ break;
+
+ case PANGO2_ATTR_GRAVITY:
+ attr = pango2_attr_gravity_new ((Pango2Gravity) parser_select_string (parser, gravity_names));
+ break;
+
+ case PANGO2_ATTR_GRAVITY_HINT:
+ attr = pango2_attr_gravity_hint_new ((Pango2GravityHint) parser_select_string (parser, gravity_hint_names));
+ break;
+
+ case PANGO2_ATTR_FONT_FEATURES:
+ str = gtk_json_parser_get_string (parser);
+ attr = pango2_attr_font_features_new (str);
+ g_free (str);
+ break;
+
+ case PANGO2_ATTR_ALLOW_BREAKS:
+ attr = pango2_attr_allow_breaks_new (gtk_json_parser_get_boolean (parser));
+ break;
+
+ case PANGO2_ATTR_SHOW:
+ attr = pango2_attr_show_new ((int) gtk_json_parser_get_number (parser));
+ break;
+
+ case PANGO2_ATTR_INSERT_HYPHENS:
+ attr = pango2_attr_insert_hyphens_new ((int) gtk_json_parser_get_number (parser));
+ break;
+
+ case PANGO2_ATTR_OVERLINE:
+ attr = pango2_attr_overline_new ((Pango2LineStyle) parser_select_string (parser, line_style_names));
+ break;
+
+ case PANGO2_ATTR_OVERLINE_COLOR:
+ parser_get_color (parser, &color);
+ attr = pango2_attr_overline_color_new (&color);
+ break;
+
+ case PANGO2_ATTR_LINE_HEIGHT:
+ attr = pango2_attr_line_height_new (gtk_json_parser_get_number (parser));
+ break;
+
+ case PANGO2_ATTR_ABSOLUTE_LINE_HEIGHT:
+ attr = pango2_attr_line_height_new_absolute ((int) gtk_json_parser_get_number (parser));
+ break;
+
+ case PANGO2_ATTR_LINE_SPACING:
+ attr = pango2_attr_line_spacing_new ((int) gtk_json_parser_get_number (parser));
+ break;
+
+ case PANGO2_ATTR_TEXT_TRANSFORM:
+ attr = pango2_attr_text_transform_new ((Pango2TextTransform) parser_select_string (parser, text_transform_names));
+ break;
+
+ case PANGO2_ATTR_WORD:
+ attr = pango2_attr_word_new ();
+ break;
+
+ case PANGO2_ATTR_SENTENCE:
+ attr = pango2_attr_sentence_new ();
+ break;
+
+ case PANGO2_ATTR_BASELINE_SHIFT:
+ attr = pango2_attr_baseline_shift_new (parser_select_string (parser, baseline_shift_names));
+ break;
+
+ case PANGO2_ATTR_FONT_SCALE:
+ attr = pango2_attr_font_scale_new ((Pango2FontScale) parser_select_string (parser, font_scale_names));
+ break;
+
+ case PANGO2_ATTR_PARAGRAPH:
+ attr = pango2_attr_paragraph_new ();
+ break;
+
+ }
+
+ attr->start_index = start;
+ attr->end_index = end;
+
+ return attr;
+}
+
+enum {
+ ATTR_START,
+ ATTR_END,
+ ATTR_TYPE,
+ ATTR_VALUE
+};
+
+static const char *attr_members[] = {
+ "start",
+ "end",
+ "type",
+ "value",
+ NULL
+};
+
+static Pango2Attribute *
+json_to_attribute (GtkJsonParser *parser)
+{
+ Pango2Attribute *attr = NULL;
+ Pango2AttrType type = PANGO2_ATTR_INVALID;
+ guint start = PANGO2_ATTR_INDEX_FROM_TEXT_BEGINNING;
+ guint end = PANGO2_ATTR_INDEX_TO_TEXT_END;
+
+ gtk_json_parser_start_object (parser);
+
+ do
+ {
+ switch (gtk_json_parser_select_member (parser, attr_members))
+ {
+ case ATTR_START:
+ start = (int)gtk_json_parser_get_number (parser);
+ break;
+
+ case ATTR_END:
+ end = (int)gtk_json_parser_get_number (parser);
+ break;
+
+ case ATTR_TYPE:
+ type = get_attr_type (gtk_json_parser_get_string (parser));
+ break;
+
+ case ATTR_VALUE:
+ attr = attr_for_type (parser, type, start, end);
+ break;
+
+ default:
+ break;
+ }
+ }
+ while (gtk_json_parser_next (parser));
+
+ if (!attr && !gtk_json_parser_get_error (parser))
+ {
+ if (type == PANGO2_ATTR_INVALID)
+ gtk_json_parser_schema_error (parser, "Invalid attribute \"type\"");
+ else
+ gtk_json_parser_schema_error (parser, "Attribute missing \"value\"");
+ }
+
+ gtk_json_parser_end (parser);
+
+ return attr;
+}
+
+static void
+json_parser_fill_attr_list (GtkJsonParser *parser,
+ Pango2AttrList *attributes)
+{
+ gtk_json_parser_start_array (parser);
+
+ do
+ {
+ Pango2Attribute *attr = json_to_attribute (parser);
+ if (attr)
+ pango2_attr_list_insert (attributes, attr);
+ }
+ while (gtk_json_parser_next (parser));
+
+ gtk_json_parser_end (parser);
+}
+
+enum {
+ TAB_POSITION,
+ TAB_ALIGNMENT,
+ TAB_DECIMAL_POINT
+};
+
+static const char *tab_members[] = {
+ "position",
+ "alignment",
+ "decimal-point",
+ NULL,
+};
+
+
+static void
+json_parser_fill_tabs (GtkJsonParser *parser,
+ Pango2TabArray *tabs)
+{
+ int index;
+
+ gtk_json_parser_start_array (parser);
+
+ index = 0;
+ do
+ {
+ int pos;
+ Pango2TabAlign align = PANGO2_TAB_LEFT;
+ gunichar ch = 0;
+
+ if (gtk_json_parser_get_node (parser) == GTK_JSON_OBJECT)
+ {
+ gtk_json_parser_start_object (parser);
+ do
+ {
+ switch (gtk_json_parser_select_member (parser, tab_members))
+ {
+ case TAB_POSITION:
+ pos = (int) gtk_json_parser_get_number (parser);
+ break;
+
+ case TAB_ALIGNMENT:
+ align = (Pango2TabAlign) parser_select_string (parser, tab_align_names);
+ break;
+
+ case TAB_DECIMAL_POINT:
+ ch = (int) gtk_json_parser_get_number (parser);
+ break;
+
+ default:
+ break;
+ }
+ }
+ while (gtk_json_parser_next (parser));
+
+ gtk_json_parser_end (parser);
+ }
+ else
+ pos = (int) gtk_json_parser_get_number (parser);
+
+ pango2_tab_array_set_tab (tabs, index, align, pos);
+ pango2_tab_array_set_decimal_point (tabs, index, ch);
+ index++;
+ }
+ while (gtk_json_parser_next (parser));
+
+ gtk_json_parser_end (parser);
+}
+
+enum {
+ TABS_POSITIONS_IN_PIXELS,
+ TABS_POSITIONS
+};
+
+static const char *tabs_members[] = {
+ "positions-in-pixels",
+ "positions",
+ NULL
+};
+
+static void
+json_parser_fill_tab_array (GtkJsonParser *parser,
+ Pango2TabArray *tabs)
+{
+ gtk_json_parser_start_object (parser);
+
+ do
+ {
+ switch (gtk_json_parser_select_member (parser, tabs_members))
+ {
+ case TABS_POSITIONS_IN_PIXELS:
+ pango2_tab_array_set_positions_in_pixels (tabs, gtk_json_parser_get_boolean (parser));
+ break;
+
+ case TABS_POSITIONS:
+ json_parser_fill_tabs (parser, tabs);
+ break;
+
+ default:
+ break;
+ }
+ }
+ while (gtk_json_parser_next (parser));
+
+ gtk_json_parser_end (parser);
+}
+
+enum {
+ CONTEXT_LANGUAGE,
+ CONTEXT_FONT,
+ CONTEXT_BASE_GRAVITY,
+ CONTEXT_GRAVITY_HINT,
+ CONTEXT_BASE_DIR,
+ CONTEXT_ROUND_GLYPH_POSITIONS,
+ CONTEXT_TRANSFORM,
+};
+
+static const char *context_members[] = {
+ "language",
+ "font",
+ "base-gravity",
+ "gravity-hint",
+ "base-dir",
+ "round-glyph-positions",
+ "transform",
+ NULL,
+};
+
+static void
+json_parser_fill_context (GtkJsonParser *parser,
+ Pango2Context *context)
+{
+ gtk_json_parser_start_object (parser);
+
+ do
+ {
+ char *str;
+
+ switch (gtk_json_parser_select_member (parser, context_members))
+ {
+ case CONTEXT_LANGUAGE:
+ str = gtk_json_parser_get_string (parser);
+ Pango2Language *language = pango2_language_from_string (str);
+ pango2_context_set_language (context, language);
+ g_free (str);
+ break;
+
+ case CONTEXT_FONT:
+ {
+ Pango2FontDescription *desc = parser_get_font_description (parser);
+ pango2_context_set_font_description (context, desc);
+ pango2_font_description_free (desc);
+ }
+ break;
+
+ case CONTEXT_BASE_GRAVITY:
+ pango2_context_set_base_gravity (context, (Pango2Gravity) parser_select_string (parser, gravity_names));
+ break;
+
+ case CONTEXT_GRAVITY_HINT:
+ pango2_context_set_gravity_hint (context, (Pango2GravityHint) parser_select_string (parser, gravity_hint_names));
+ break;
+
+ case CONTEXT_BASE_DIR:
+ pango2_context_set_base_dir (context, (Pango2Direction) parser_select_string (parser, direction_names));
+ break;
+
+ case CONTEXT_ROUND_GLYPH_POSITIONS:
+ pango2_context_set_round_glyph_positions (context, gtk_json_parser_get_boolean (parser));
+ break;
+
+ case CONTEXT_TRANSFORM:
+ {
+ Pango2Matrix m = PANGO2_MATRIX_INIT;
+
+ gtk_json_parser_start_array (parser);
+ m.xx = gtk_json_parser_get_number (parser);
+ gtk_json_parser_next (parser);
+ m.xy = gtk_json_parser_get_number (parser);
+ gtk_json_parser_next (parser);
+ m.yx = gtk_json_parser_get_number (parser);
+ gtk_json_parser_next (parser);
+ m.yy = gtk_json_parser_get_number (parser);
+ gtk_json_parser_next (parser);
+ m.x0 = gtk_json_parser_get_number (parser);
+ gtk_json_parser_next (parser);
+ m.y0 = gtk_json_parser_get_number (parser);
+ gtk_json_parser_end (parser);
+
+ pango2_context_set_matrix (context, &m);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ while (gtk_json_parser_next (parser));
+
+ gtk_json_parser_end (parser);
+}
+
+enum {
+ LAYOUT_CONTEXT,
+ LAYOUT_COMMENT,
+ LAYOUT_TEXT,
+ LAYOUT_ATTRIBUTES,
+ LAYOUT_FONT,
+ LAYOUT_TABS,
+ LAYOUT_AUTO_DIR,
+ LAYOUT_ALIGNMENT,
+ LAYOUT_WRAP,
+ LAYOUT_ELLIPSIZE,
+ LAYOUT_WIDTH,
+ LAYOUT_HEIGHT,
+ LAYOUT_INDENT,
+ LAYOUT_LINE_HEIGHT,
+ LAYOUT_LINES
+};
+
+static const char *layout_members[] = {
+ "context",
+ "comment",
+ "text",
+ "attributes",
+ "font",
+ "tabs",
+ "auto-dir",
+ "alignment",
+ "wrap",
+ "ellipsize",
+ "width",
+ "height",
+ "indent",
+ "line-height",
+ "lines",
+ NULL
+};
+
+static void
+json_parser_fill_layout (GtkJsonParser *parser,
+ Pango2Layout *layout,
+ Pango2LayoutDeserializeFlags flags)
+{
+ gtk_json_parser_start_object (parser);
+
+ do
+ {
+ char *str;
+
+ switch (gtk_json_parser_select_member (parser, layout_members))
+ {
+ case LAYOUT_CONTEXT:
+ if (flags & PANGO2_LAYOUT_DESERIALIZE_CONTEXT)
+ json_parser_fill_context (parser, pango2_layout_get_context (layout));
+ break;
+
+ case LAYOUT_COMMENT:
+ str = gtk_json_parser_get_string (parser);
+ g_object_set_data_full (G_OBJECT (layout), "comment", str, g_free);
+ break;
+
+ case LAYOUT_TEXT:
+ str = gtk_json_parser_get_string (parser);
+ pango2_layout_set_text (layout, str, -1);
+ g_free (str);
+ break;
+
+ case LAYOUT_ATTRIBUTES:
+ {
+ Pango2AttrList *attributes = pango2_attr_list_new ();
+ json_parser_fill_attr_list (parser, attributes);
+ pango2_layout_set_attributes (layout, attributes);
+ pango2_attr_list_unref (attributes);
+ }
+ break;
+
+ case LAYOUT_FONT:
+ {
+ Pango2FontDescription *desc = parser_get_font_description (parser);;
+ pango2_layout_set_font_description (layout, desc);
+ pango2_font_description_free (desc);
+ }
+ break;
+
+ case LAYOUT_AUTO_DIR:
+ pango2_layout_set_auto_dir (layout, gtk_json_parser_get_boolean (parser));
+ break;
+
+ case LAYOUT_LINE_HEIGHT:
+ pango2_layout_set_line_height (layout, gtk_json_parser_get_number (parser));
+ break;
+
+ case LAYOUT_TABS:
+ {
+ Pango2TabArray *tabs = pango2_tab_array_new (0, FALSE);
+ json_parser_fill_tab_array (parser, tabs);
+ pango2_layout_set_tabs (layout, tabs);
+ pango2_tab_array_free (tabs);
+ }
+ break;
+
+ case LAYOUT_ALIGNMENT:
+ pango2_layout_set_alignment (layout, (Pango2Alignment) parser_select_string (parser, alignment_names));
+ break;
+
+ case LAYOUT_WRAP:
+ pango2_layout_set_wrap (layout, (Pango2WrapMode) parser_select_string (parser, wrap_names));
+ break;
+
+ case LAYOUT_ELLIPSIZE:
+ pango2_layout_set_ellipsize (layout, (Pango2EllipsizeMode) parser_select_string (parser, ellipsize_names));
+ break;
+
+ case LAYOUT_WIDTH:
+ pango2_layout_set_width (layout, (int) gtk_json_parser_get_number (parser));
+ break;
+
+ case LAYOUT_HEIGHT:
+ pango2_layout_set_height (layout, (int) gtk_json_parser_get_number (parser));
+ break;
+
+ case LAYOUT_INDENT:
+ pango2_layout_set_indent (layout, (int) gtk_json_parser_get_number (parser));
+ break;
+
+ case LAYOUT_LINES:
+ break;
+
+ default:
+ break;
+ }
+ }
+ while (gtk_json_parser_next (parser));
+
+ gtk_json_parser_end (parser);
+}
+
+enum {
+ FONT_DESCRIPTION,
+ FONT_CHECKSUM,
+ FONT_VARIATIONS,
+ FONT_FEATURES,
+ FONT_MATRIX
+};
+
+static const char *font_members[] = {
+ "description",
+ "checksum",
+ "variations",
+ "features",
+ "matrix",
+ NULL
+};
+
+static Pango2Font *
+json_parser_load_font (GtkJsonParser *parser,
+ Pango2Context *context,
+ GError **error)
+{
+ Pango2Font *font = NULL;
+
+ gtk_json_parser_start_object (parser);
+
+ switch (gtk_json_parser_select_member (parser, font_members))
+ {
+ case FONT_DESCRIPTION:
+ {
+ Pango2FontDescription *desc = parser_get_font_description (parser);
+ font = pango2_context_load_font (context, desc);
+ pango2_font_description_free (desc);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ gtk_json_parser_end (parser);
+
+ return font;
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_layout_serialize:
+ * @layout: a `Pango2Layout`
+ * @flags: `Pango2LayoutSerializeFlags`
+ *
+ * Serializes the @layout for later deserialization via [func@Pango2.Layout.deserialize].
+ *
+ * There are no guarantees about the format of the output across different
+ * versions of Pango2 and [func@Pango2.Layout.deserialize] will reject data
+ * that it cannot parse.
+ *
+ * The intended use of this function is testing, benchmarking and debugging.
+ * The format is not meant as a permanent storage format.
+ *
+ * Returns: a `GBytes` containing the serialized form of @layout
+ */
+GBytes *
+pango2_layout_serialize (Pango2Layout *layout,
+ Pango2LayoutSerializeFlags flags)
+{
+ GString *str;
+ GtkJsonPrinter *printer;
+ char *data;
+ gsize size;
+
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), NULL);
+
+ str = g_string_new ("");
+
+ printer = gtk_json_printer_new (gstring_write, str, NULL);
+ gtk_json_printer_set_flags (printer, GTK_JSON_PRINTER_PRETTY);
+ layout_to_json (printer, layout, flags);
+ gtk_json_printer_free (printer);
+
+ g_string_append_c (str, '\n');
+
+ size = str->len;
+ data = g_string_free (str, FALSE);
+
+ return g_bytes_new_take (data, size);
+}
+
+/**
+ * pango2_layout_write_to_file:
+ * @layout: a `Pango2Layout`
+ *
+ * A convenience method to serialize a layout to a file.
+ *
+ * It is equivalent to calling [method@Pango2.Layout.serialize]
+ * followed by [func@GLib.file_set_contents].
+ *
+ * See those two functions for details on the arguments.
+ *
+ * It is mostly intended for use inside a debugger to quickly dump
+ * a layout to a file for later inspection.
+ *
+ * Returns: %TRUE if saving was successful
+ */
+gboolean
+pango2_layout_write_to_file (Pango2Layout *layout,
+ const char *filename)
+{
+ GBytes *bytes;
+ gboolean result;
+
+ g_return_val_if_fail (PANGO2_IS_LAYOUT (layout), FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ bytes = pango2_layout_serialize (layout, PANGO2_LAYOUT_SERIALIZE_CONTEXT |
+ PANGO2_LAYOUT_SERIALIZE_OUTPUT);
+
+ result = g_file_set_contents (filename,
+ g_bytes_get_data (bytes, NULL),
+ g_bytes_get_size (bytes),
+ NULL);
+ g_bytes_unref (bytes);
+
+ return result;
+}
+
+/**
+ * pango2_layout_deserialize:
+ * @context: a `Pango2Context`
+ * @flags: `Pango2LayoutDeserializeFlags`
+ * @bytes: the bytes containing the data
+ * @error: return location for an error
+ *
+ * Loads data previously created via [method@Pango2.Layout.serialize].
+ *
+ * For a discussion of the supported format, see that function.
+ *
+ * Note: to verify that the returned layout is identical to
+ * the one that was serialized, you can compare @bytes to the
+ * result of serializing the layout again.
+ *
+ * Returns: (nullable) (transfer full): a new `Pango2Layout`
+ */
+Pango2Layout *
+pango2_layout_deserialize (Pango2Context *context,
+ GBytes *bytes,
+ Pango2LayoutDeserializeFlags flags,
+ GError **error)
+{
+ Pango2Layout *layout;
+ GtkJsonParser *parser;
+ const GError *parser_error;
+
+ g_return_val_if_fail (PANGO2_IS_CONTEXT (context), NULL);
+
+ layout = pango2_layout_new (context);
+
+ parser = gtk_json_parser_new_for_bytes (bytes);
+ json_parser_fill_layout (parser, layout, flags);
+
+ parser_error = gtk_json_parser_get_error (parser);
+
+ if (parser_error)
+ {
+ gsize start, end;
+ int code;
+
+ gtk_json_parser_get_error_offset (parser, &start, &end);
+
+ if (g_error_matches (parser_error, GTK_JSON_ERROR, GTK_JSON_ERROR_VALUE))
+ code = PANGO2_LAYOUT_DESERIALIZE_INVALID_VALUE;
+ else if (g_error_matches (parser_error, GTK_JSON_ERROR, GTK_JSON_ERROR_SCHEMA))
+ code = PANGO2_LAYOUT_DESERIALIZE_MISSING_VALUE;
+ else
+ code = PANGO2_LAYOUT_DESERIALIZE_INVALID;
+
+ g_set_error (error, PANGO2_LAYOUT_DESERIALIZE_ERROR, code,
+ "%" G_GSIZE_FORMAT ":%" G_GSIZE_FORMAT ": %s",
+ start, end, parser_error->message);
+
+ g_clear_object (&layout);
+ }
+
+ gtk_json_parser_free (parser);
+
+ return layout;
+}
+
+/**
+ * pango2_font_serialize:
+ * @font: a `Pango2Font`
+ *
+ * Serializes the @font in a way that can be uniquely identified.
+ *
+ * There are no guarantees about the format of the output across different
+ * versions of Pango2.
+ *
+ * The intended use of this function is testing, benchmarking and debugging.
+ * The format is not meant as a permanent storage format.
+ *
+ * To recreate a font from its serialized form, use [func@Pango2.Font.deserialize].
+ *
+ * Returns: a `GBytes` containing the serialized form of @font
+ */
+GBytes *
+pango2_font_serialize (Pango2Font *font)
+{
+ GString *str;
+ GtkJsonPrinter *printer;
+ char *data;
+ gsize size;
+
+ g_return_val_if_fail (PANGO2_IS_FONT (font), NULL);
+
+ str = g_string_new ("");
+
+ printer = gtk_json_printer_new (gstring_write, str, NULL);
+ gtk_json_printer_set_flags (printer, GTK_JSON_PRINTER_PRETTY);
+ add_font (printer, NULL, font);
+ gtk_json_printer_free (printer);
+
+ size = str->len;
+ data = g_string_free (str, FALSE);
+
+ return g_bytes_new_take (data, size);
+}
+
+/**
+ * pango2_font_deserialize:
+ * @context: a `Pango2Context`
+ * @bytes: the bytes containing the data
+ * @error: return location for an error
+ *
+ * Loads data previously created via [method@Pango2.Font.serialize].
+ *
+ * For a discussion of the supported format, see that function.
+ *
+ * Note: to verify that the returned font is identical to
+ * the one that was serialized, you can compare @bytes to the
+ * result of serializing the font again.
+ *
+ * Returns: (nullable) (transfer full): a new `Pango2Font`
+ */
+Pango2Font *
+pango2_font_deserialize (Pango2Context *context,
+ GBytes *bytes,
+ GError **error)
+{
+ Pango2Font *font;
+ GtkJsonParser *parser;
+
+ g_return_val_if_fail (PANGO2_IS_CONTEXT (context), NULL);
+
+ parser = gtk_json_parser_new_for_bytes (bytes);
+ font = json_parser_load_font (parser, context, error);
+ gtk_json_parser_free (parser);
+
+ return font;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/shape.c b/pango2/shape.c
new file mode 100644
index 00000000..62ea953a
--- /dev/null
+++ b/pango2/shape.c
@@ -0,0 +1,868 @@
+/* Pango2
+ * shape.c: Convert characters into glyphs.
+ *
+ * Copyright (C) 1999 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.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <math.h>
+#include <glib.h>
+
+#include "pango-impl-utils.h"
+#include "pango-glyph.h"
+
+#include "pango-attr-private.h"
+#include "pango-item-private.h"
+#include "pango-font-private.h"
+#include "pango-userfont-private.h"
+#include "pango-userface-private.h"
+
+#include <hb-ot.h>
+
+/* {{{ Harfbuzz shaping */
+/* {{{ Buffer handling */
+
+static hb_buffer_t *cached_buffer = NULL; /* MT-safe */
+G_LOCK_DEFINE_STATIC (cached_buffer);
+
+static hb_buffer_t *
+acquire_buffer (gboolean *free_buffer)
+{
+ hb_buffer_t *buffer;
+
+ if (G_LIKELY (G_TRYLOCK (cached_buffer)))
+ {
+ if (G_UNLIKELY (!cached_buffer))
+ cached_buffer = hb_buffer_create ();
+
+ buffer = cached_buffer;
+ *free_buffer = FALSE;
+ }
+ else
+ {
+ buffer = hb_buffer_create ();
+ *free_buffer = TRUE;
+ }
+
+ return buffer;
+}
+
+static void
+release_buffer (hb_buffer_t *buffer,
+ gboolean free_buffer)
+{
+ if (G_LIKELY (!free_buffer))
+ {
+ hb_buffer_reset (buffer);
+ G_UNLOCK (cached_buffer);
+ }
+ else
+ hb_buffer_destroy (buffer);
+}
+
+/* }}} */
+/* {{{ Use Pango2Font with Harfbuzz */
+
+typedef struct
+{
+ Pango2Font *font;
+ hb_font_t *parent;
+ Pango2ShowFlags show_flags;
+} Pango2HbShapeContext;
+
+static hb_bool_t
+pango2_hb_font_get_nominal_glyph (hb_font_t *font,
+ void *font_data,
+ hb_codepoint_t unicode,
+ hb_codepoint_t *glyph,
+ void *user_data G_GNUC_UNUSED)
+{
+ Pango2HbShapeContext *context = (Pango2HbShapeContext *) font_data;
+
+ if (context->show_flags != 0)
+ {
+ if ((context->show_flags & PANGO2_SHOW_SPACES) != 0 &&
+ g_unichar_type (unicode) == G_UNICODE_SPACE_SEPARATOR)
+ {
+ /* Replace 0x20 by visible space, since we
+ * don't draw a hex box for 0x20
+ */
+ if (unicode == 0x20)
+ unicode = 0x2423;
+ else
+ {
+ *glyph = PANGO2_GET_UNKNOWN_GLYPH (unicode);
+ return TRUE;
+ }
+ }
+
+ if ((context->show_flags & PANGO2_SHOW_IGNORABLES) != 0 &&
+ pango2_is_default_ignorable (unicode))
+ {
+ if (pango2_get_ignorable (unicode))
+ *glyph = PANGO2_GET_UNKNOWN_GLYPH (unicode);
+ else
+ *glyph = PANGO2_GLYPH_EMPTY;
+ return TRUE;
+ }
+
+ if ((context->show_flags & PANGO2_SHOW_LINE_BREAKS) != 0 &&
+ unicode == 0x2028)
+ {
+ /* Always mark LS as unknown. If it ends up at the line end,
+ * the line breaking code takes care of hiding them, and if
+ * they end up in the middle of a line, we are in single
+ * paragraph mode and want to show the LS.
+ */
+ *glyph = PANGO2_GET_UNKNOWN_GLYPH (unicode);
+ return TRUE;
+ }
+ }
+
+ if (hb_font_get_nominal_glyph (context->parent, unicode, glyph))
+ return TRUE;
+
+ /* HarfBuzz knows how to synthesize other spaces from 0x20, so never
+ * replace them with unknown glyphs, just tell HarfBuzz that we don't
+ * have a glyph.
+ *
+ * For 0x20, on the other hand, we need to pretend that we have a glyph
+ * and rely on our glyph extents code to provide a reasonable width for
+ * PANGO2_GET_UNKNOWN_WIDTH (0x20).
+ */
+ if (g_unichar_type (unicode) == G_UNICODE_SPACE_SEPARATOR)
+ {
+ if (unicode == 0x20)
+ {
+ *glyph = PANGO2_GET_UNKNOWN_GLYPH (0x20);
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ *glyph = PANGO2_GET_UNKNOWN_GLYPH (unicode);
+
+ /* We draw our own invalid-Unicode shape, so prevent HarfBuzz
+ * from using REPLACEMENT CHARACTER.
+ */
+ if (unicode > 0x10FFFF)
+ return TRUE;
+
+ return FALSE;
+}
+
+static hb_position_t
+pango2_hb_font_get_glyph_h_advance (hb_font_t *font,
+ void *font_data,
+ hb_codepoint_t glyph,
+ void *user_data G_GNUC_UNUSED)
+{
+ Pango2HbShapeContext *context = (Pango2HbShapeContext *) font_data;
+
+ if (glyph & PANGO2_GLYPH_UNKNOWN_FLAG)
+ {
+ Pango2Rectangle logical;
+
+ pango2_font_get_glyph_extents (context->font, glyph, NULL, &logical);
+ return logical.width;
+ }
+
+ return hb_font_get_glyph_h_advance (context->parent, glyph);
+}
+
+static hb_position_t
+pango2_hb_font_get_glyph_v_advance (hb_font_t *font,
+ void *font_data,
+ hb_codepoint_t glyph,
+ void *user_data G_GNUC_UNUSED)
+{
+ Pango2HbShapeContext *context = (Pango2HbShapeContext *) font_data;
+
+ if (glyph & PANGO2_GLYPH_UNKNOWN_FLAG)
+ {
+ Pango2Rectangle logical;
+
+ pango2_font_get_glyph_extents (context->font, glyph, NULL, &logical);
+ return logical.height;
+ }
+
+ return hb_font_get_glyph_v_advance (context->parent, glyph);
+}
+
+static hb_bool_t
+pango2_hb_font_get_glyph_extents (hb_font_t *font,
+ void *font_data,
+ hb_codepoint_t glyph,
+ hb_glyph_extents_t *extents,
+ void *user_data G_GNUC_UNUSED)
+{
+ Pango2HbShapeContext *context = (Pango2HbShapeContext *) font_data;
+
+ if (glyph & PANGO2_GLYPH_UNKNOWN_FLAG)
+ {
+ Pango2Rectangle ink;
+
+ pango2_font_get_glyph_extents (context->font, glyph, &ink, NULL);
+
+ extents->x_bearing = ink.x;
+ extents->y_bearing = ink.y;
+ extents->width = ink.width;
+ extents->height = ink.height;
+
+ return TRUE;
+ }
+
+ return hb_font_get_glyph_extents (context->parent, glyph, extents);
+}
+
+static hb_font_t *
+pango2_font_get_hb_font_for_context (Pango2Font *font,
+ Pango2HbShapeContext *context)
+{
+ hb_font_t *hb_font;
+ static hb_font_funcs_t *funcs;
+
+ hb_font = pango2_font_get_hb_font (font);
+
+ if (G_UNLIKELY (!funcs))
+ {
+ funcs = hb_font_funcs_create ();
+
+ hb_font_funcs_set_nominal_glyph_func (funcs, pango2_hb_font_get_nominal_glyph, NULL, NULL);
+ hb_font_funcs_set_glyph_h_advance_func (funcs, pango2_hb_font_get_glyph_h_advance, NULL, NULL);
+ hb_font_funcs_set_glyph_v_advance_func (funcs, pango2_hb_font_get_glyph_v_advance, NULL, NULL);
+ hb_font_funcs_set_glyph_extents_func (funcs, pango2_hb_font_get_glyph_extents, NULL, NULL);
+
+ hb_font_funcs_make_immutable (funcs);
+ }
+
+ context->font = font;
+ context->parent = hb_font;
+
+ hb_font = hb_font_create_sub_font (hb_font);
+ hb_font_set_funcs (hb_font, funcs, context, NULL);
+
+ return hb_font;
+}
+
+/* }}} */
+/* {{{ Utilities */
+
+static Pango2ShowFlags
+find_show_flags (const Pango2Analysis *analysis)
+{
+ GSList *l;
+ Pango2ShowFlags flags = 0;
+
+ for (l = analysis->extra_attrs; l; l = l->next)
+ {
+ Pango2Attribute *attr = l->data;
+
+ if (attr->type == PANGO2_ATTR_SHOW)
+ flags |= attr->int_value;
+ }
+
+ return flags;
+}
+
+static Pango2TextTransform
+find_text_transform (const Pango2Analysis *analysis)
+{
+ GSList *l;
+ Pango2TextTransform transform = PANGO2_TEXT_TRANSFORM_NONE;
+
+ for (l = analysis->extra_attrs; l; l = l->next)
+ {
+ Pango2Attribute *attr = l->data;
+
+ if (attr->type == PANGO2_ATTR_TEXT_TRANSFORM)
+ transform = (Pango2TextTransform) attr->int_value;
+ }
+
+ return transform;
+}
+
+static gboolean
+glyph_has_color (hb_font_t *font,
+ hb_codepoint_t glyph)
+{
+ hb_face_t *face;
+ hb_blob_t *blob;
+
+ face = hb_font_get_face (font);
+
+ if (hb_ot_color_glyph_get_layers (face, glyph, 0, NULL, NULL) > 0)
+ return TRUE;
+
+ if (hb_ot_color_has_png (face))
+ {
+ blob = hb_ot_color_glyph_reference_png (font, glyph);
+ if (blob)
+ {
+ guint length = hb_blob_get_length (blob);
+ hb_blob_destroy (blob);
+ if (length > 0)
+ return TRUE;
+ }
+ }
+
+ if (hb_ot_color_has_svg (face))
+ {
+ blob = hb_ot_color_glyph_reference_svg (face, glyph);
+ if (blob)
+ {
+ guint length = hb_blob_get_length (blob);
+ hb_blob_destroy (blob);
+ if (length > 0)
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* }}} */
+
+static void
+pango2_hb_shape (const char *item_text,
+ int item_length,
+ const char *paragraph_text,
+ int paragraph_length,
+ const Pango2Analysis *analysis,
+ Pango2LogAttr *log_attrs,
+ int num_chars,
+ Pango2GlyphString *glyphs,
+ Pango2ShapeFlags flags)
+{
+ Pango2HbShapeContext context = { 0, };
+ hb_buffer_flags_t hb_buffer_flags;
+ hb_font_t *hb_font;
+ hb_buffer_t *hb_buffer;
+ hb_direction_t hb_direction;
+ gboolean free_buffer;
+ hb_glyph_info_t *hb_glyph;
+ hb_glyph_position_t *hb_position;
+ int last_cluster;
+ guint i, num_glyphs;
+ unsigned int item_offset = item_text - paragraph_text;
+ hb_feature_t features[32];
+ unsigned int num_features = 0;
+ Pango2GlyphInfo *infos;
+ Pango2TextTransform transform;
+ int hyphen_index;
+
+ g_return_if_fail (analysis != NULL);
+ g_return_if_fail (analysis->font != NULL);
+
+ context.show_flags = find_show_flags (analysis);
+ hb_font = pango2_font_get_hb_font_for_context (analysis->font, &context);
+ hb_buffer = acquire_buffer (&free_buffer);
+
+ transform = find_text_transform (analysis);
+
+ hb_direction = PANGO2_GRAVITY_IS_VERTICAL (analysis->gravity) ? HB_DIRECTION_TTB : HB_DIRECTION_LTR;
+ if (analysis->level % 2)
+ hb_direction = HB_DIRECTION_REVERSE (hb_direction);
+ if (PANGO2_GRAVITY_IS_IMPROPER (analysis->gravity))
+ hb_direction = HB_DIRECTION_REVERSE (hb_direction);
+
+ hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT;
+
+ if (context.show_flags & PANGO2_SHOW_IGNORABLES)
+ hb_buffer_flags |= HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES;
+
+ /* setup buffer */
+
+ hb_buffer_set_direction (hb_buffer, hb_direction);
+ hb_buffer_set_script (hb_buffer, (hb_script_t) g_unicode_script_to_iso15924 (analysis->script));
+ hb_buffer_set_language (hb_buffer, hb_language_from_string (pango2_language_to_string (analysis->language), -1));
+ hb_buffer_set_cluster_level (hb_buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
+ hb_buffer_set_flags (hb_buffer, hb_buffer_flags);
+ hb_buffer_set_invisible_glyph (hb_buffer, PANGO2_GLYPH_EMPTY);
+
+ if (analysis->flags & PANGO2_ANALYSIS_FLAG_NEED_HYPHEN)
+ {
+ const char *p = paragraph_text + item_offset + item_length;
+ int last_char_len = p - g_utf8_prev_char (p);
+
+ hyphen_index = item_offset + item_length - last_char_len;
+
+ if (log_attrs[num_chars].break_removes_preceding)
+ item_length -= last_char_len;
+ }
+
+ /* Add pre-context */
+ hb_buffer_add_utf8 (hb_buffer, paragraph_text, item_offset, item_offset, 0);
+
+ if (transform == PANGO2_TEXT_TRANSFORM_NONE)
+ {
+ hb_buffer_add_utf8 (hb_buffer, paragraph_text, item_offset + item_length, item_offset, item_length);
+ }
+ else
+ {
+ const char *p;
+ int i;
+
+ /* Transform the item text according to text transform.
+ * Note: we assume text transforms won't cross font boundaries
+ */
+ for (p = paragraph_text + item_offset, i = 0;
+ p < paragraph_text + item_offset + item_length;
+ p = g_utf8_next_char (p), i++)
+ {
+ int index = p - paragraph_text;
+ gunichar ch = g_utf8_get_char (p);
+ char *str = NULL;
+
+ switch (transform)
+ {
+ case PANGO2_TEXT_TRANSFORM_LOWERCASE:
+ if (g_unichar_isalnum (ch))
+ str = g_utf8_strdown (p, g_utf8_next_char (p) - p);
+ break;
+
+ case PANGO2_TEXT_TRANSFORM_UPPERCASE:
+ if (g_unichar_isalnum (ch))
+ str = g_utf8_strup (p, g_utf8_next_char (p) - p);
+ break;
+
+ case PANGO2_TEXT_TRANSFORM_CAPITALIZE:
+ if (log_attrs[i].is_word_start)
+ ch = g_unichar_totitle (ch);
+ break;
+
+ case PANGO2_TEXT_TRANSFORM_NONE:
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (str)
+ {
+ for (const char *q = str; *q; q = g_utf8_next_char (q))
+ {
+ ch = g_utf8_get_char (q);
+ hb_buffer_add (hb_buffer, ch, index);
+ }
+ g_free (str);
+ }
+ else
+ hb_buffer_add (hb_buffer, ch, index);
+ }
+ }
+
+ /* Add post-context */
+ hb_buffer_add_utf8 (hb_buffer, paragraph_text, paragraph_length, item_offset + item_length, 0);
+
+ if (analysis->flags & PANGO2_ANALYSIS_FLAG_NEED_HYPHEN)
+ {
+ /* Insert either a Unicode or ASCII hyphen. We may
+ * want to look for script-specific hyphens here.
+ */
+ hb_codepoint_t glyph;
+
+ /* Note: We rely on hb_buffer_add clearing existing post-context */
+ if (hb_font_get_nominal_glyph (hb_font, 0x2010, &glyph))
+ hb_buffer_add (hb_buffer, 0x2010, hyphen_index);
+ else if (hb_font_get_nominal_glyph (hb_font, '-', &glyph))
+ hb_buffer_add (hb_buffer, '-', hyphen_index);
+ }
+
+ pango2_analysis_collect_features (analysis, features, G_N_ELEMENTS (features), &num_features);
+
+ hb_shape (hb_font, hb_buffer, features, num_features);
+
+ if (PANGO2_GRAVITY_IS_IMPROPER (analysis->gravity))
+ hb_buffer_reverse (hb_buffer);
+
+ /* buffer output */
+ num_glyphs = hb_buffer_get_length (hb_buffer);
+ hb_glyph = hb_buffer_get_glyph_infos (hb_buffer, NULL);
+ pango2_glyph_string_set_size (glyphs, num_glyphs);
+ infos = glyphs->glyphs;
+ last_cluster = -1;
+
+ for (i = 0; i < num_glyphs; i++)
+ {
+ infos[i].glyph = hb_glyph->codepoint;
+ glyphs->log_clusters[i] = hb_glyph->cluster - item_offset;
+ infos[i].attr.is_cluster_start = glyphs->log_clusters[i] != last_cluster;
+ infos[i].attr.is_color = glyph_has_color (hb_font, hb_glyph->codepoint);
+ hb_glyph++;
+ last_cluster = glyphs->log_clusters[i];
+ }
+
+ hb_position = hb_buffer_get_glyph_positions (hb_buffer, NULL);
+ if (PANGO2_GRAVITY_IS_VERTICAL (analysis->gravity))
+ for (i = 0; i < num_glyphs; i++)
+ {
+ /* 90 degrees rotation counter-clockwise. */
+ infos[i].geometry.width = - hb_position->y_advance;
+ infos[i].geometry.x_offset = - hb_position->y_offset;
+ infos[i].geometry.y_offset = - hb_position->x_offset;
+ hb_position++;
+ }
+ else /* horizontal */
+ for (i = 0; i < num_glyphs; i++)
+ {
+ infos[i].geometry.width = hb_position->x_advance;
+ infos[i].geometry.x_offset = hb_position->x_offset;
+ infos[i].geometry.y_offset = - hb_position->y_offset;
+ hb_position++;
+ }
+
+ release_buffer (hb_buffer, free_buffer);
+ hb_font_destroy (hb_font);
+}
+
+/* }}} */
+/* {{{ User shaping */
+
+static void
+pango2_user_shape (const char *text,
+ unsigned int length,
+ const Pango2Analysis *analysis,
+ Pango2GlyphString *glyphs,
+ Pango2ShapeFlags flags)
+{
+ Pango2Font *font = analysis->font;
+ Pango2UserFace *face = PANGO2_USER_FACE (font->face);
+
+ face->shape_func (face, font->size,
+ text, length,
+ analysis,
+ glyphs, flags,
+ face->user_data);
+}
+
+/* }}} */
+/* {{{ Fallback shaping */
+
+/* This is not meant to produce reasonable results */
+
+static void
+fallback_shape (const char *text,
+ unsigned int length,
+ const Pango2Analysis *analysis,
+ Pango2GlyphString *glyphs)
+{
+ int n_chars;
+ const char *p;
+ int cluster = 0;
+ int i;
+
+ n_chars = text ? pango2_utf8_strlen (text, length) : 0;
+
+ pango2_glyph_string_set_size (glyphs, n_chars);
+
+ p = text;
+ for (i = 0; i < n_chars; i++)
+ {
+ gunichar wc;
+ Pango2Glyph glyph;
+ Pango2Rectangle logical_rect;
+
+ wc = g_utf8_get_char (p);
+
+ if (g_unichar_type (wc) != G_UNICODE_NON_SPACING_MARK)
+ cluster = p - text;
+
+ if (pango2_is_zero_width (wc))
+ glyph = PANGO2_GLYPH_EMPTY;
+ else
+ glyph = PANGO2_GET_UNKNOWN_GLYPH (wc);
+
+ pango2_font_get_glyph_extents (analysis->font, glyph, NULL, &logical_rect);
+
+ glyphs->glyphs[i].glyph = glyph;
+
+ glyphs->glyphs[i].geometry.x_offset = 0;
+ glyphs->glyphs[i].geometry.y_offset = 0;
+ glyphs->glyphs[i].geometry.width = logical_rect.width;
+
+ glyphs->log_clusters[i] = cluster;
+
+ p = g_utf8_next_char (p);
+ }
+
+ if (analysis->level & 1)
+ pango2_glyph_string_reverse_range (glyphs, 0, glyphs->num_glyphs);
+}
+
+/* }}} */
+/* {{{ Shaping implementation */
+
+static void
+pango2_shape_internal (const char *item_text,
+ int item_length,
+ const char *paragraph_text,
+ int paragraph_length,
+ const Pango2Analysis *analysis,
+ Pango2LogAttr *log_attrs,
+ int num_chars,
+ Pango2GlyphString *glyphs,
+ Pango2ShapeFlags flags)
+{
+ int i;
+ int last_cluster;
+
+ glyphs->num_glyphs = 0;
+
+ if (item_length == -1)
+ item_length = strlen (item_text);
+
+ if (!paragraph_text)
+ {
+ paragraph_text = item_text;
+ paragraph_length = item_length;
+ }
+ if (paragraph_length == -1)
+ paragraph_length = strlen (paragraph_text);
+
+ g_return_if_fail (paragraph_text <= item_text);
+ g_return_if_fail (paragraph_text + paragraph_length >= item_text + item_length);
+
+ if (PANGO2_IS_USER_FONT (analysis->font))
+ pango2_user_shape (item_text, item_length, analysis, glyphs, flags);
+ else if (analysis->font)
+ pango2_hb_shape (item_text, item_length,
+ paragraph_text, paragraph_length,
+ analysis,
+ log_attrs, num_chars,
+ glyphs, flags);
+ else
+ glyphs->num_glyphs = 0;
+
+ if (analysis->font && glyphs->num_glyphs == 0)
+ {
+ /* If a font has been correctly chosen, but no glyphs are output,
+ * there's probably something wrong with the font.
+ *
+ * Trying to be informative, we print out the font description,
+ * and the text, but to not flood the terminal with
+ * zillions of the message, we set a flag to only err once per
+ * font.
+ */
+ GQuark warned_quark = g_quark_from_static_string ("pango-shape-fail-warned");
+
+ if (!g_object_get_qdata (G_OBJECT (analysis->font), warned_quark))
+ {
+ Pango2FontDescription *desc;
+ char *font_name;
+
+ desc = pango2_font_describe (analysis->font);
+ font_name = pango2_font_description_to_string (desc);
+ pango2_font_description_free (desc);
+
+ g_warning ("shaping failure, expect ugly output. font='%s', text='%.*s'",
+ font_name, item_length, item_text);
+
+ g_free (font_name);
+
+ g_object_set_qdata (G_OBJECT (analysis->font), warned_quark,
+ GINT_TO_POINTER (1));
+ }
+ }
+
+ if (G_UNLIKELY (!glyphs->num_glyphs))
+ {
+ fallback_shape (item_text, item_length, analysis, glyphs);
+ if (G_UNLIKELY (!glyphs->num_glyphs))
+ return;
+ }
+
+ /* make sure last_cluster is invalid */
+ last_cluster = glyphs->log_clusters[0] - 1;
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ /* Set glyphs[i].attr.is_cluster_start based on log_clusters[] */
+ if (glyphs->log_clusters[i] != last_cluster)
+ {
+ glyphs->glyphs[i].attr.is_cluster_start = TRUE;
+ last_cluster = glyphs->log_clusters[i];
+ }
+ else
+ glyphs->glyphs[i].attr.is_cluster_start = FALSE;
+
+
+ /* Shift glyph if width is negative, and negate width.
+ * This is useful for rotated font matrices and shouldn't
+ * harm in normal cases.
+ */
+ if (glyphs->glyphs[i].geometry.width < 0)
+ {
+ glyphs->glyphs[i].geometry.width = -glyphs->glyphs[i].geometry.width;
+ glyphs->glyphs[i].geometry.x_offset += glyphs->glyphs[i].geometry.width;
+ }
+ }
+
+ /* Make sure glyphstring direction conforms to analysis->level */
+ if (G_UNLIKELY ((analysis->level & 1) &&
+ glyphs->log_clusters[0] < glyphs->log_clusters[glyphs->num_glyphs - 1]))
+ {
+ g_warning ("Expected RTL run but got LTR. Fixing.");
+
+ /* *Fix* it so we don't crash later */
+ pango2_glyph_string_reverse_range (glyphs, 0, glyphs->num_glyphs);
+ }
+
+ if (flags & PANGO2_SHAPE_ROUND_POSITIONS)
+ {
+ if (analysis->font && pango2_font_is_hinted (analysis->font))
+ {
+ double x_scale_inv, y_scale_inv;
+ double x_scale, y_scale;
+
+ pango2_font_get_scale_factors (analysis->font, &x_scale_inv, &y_scale_inv);
+
+ if (PANGO2_GRAVITY_IS_IMPROPER (analysis->gravity))
+ {
+ x_scale_inv = -x_scale_inv;
+ y_scale_inv = -y_scale_inv;
+ }
+
+ x_scale = 1.0 / x_scale_inv;
+ y_scale = 1.0 / y_scale_inv;
+
+ if (x_scale == 1.0 && y_scale == 1.0)
+ {
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ glyphs->glyphs[i].geometry.width = PANGO2_UNITS_ROUND (glyphs->glyphs[i].geometry.width);
+ }
+ else
+ {
+ #if 0
+ if (PANGO2_GRAVITY_IS_VERTICAL (analysis->gravity))
+ {
+ /* XXX */
+ double tmp = x_scale;
+ x_scale = y_scale;
+ y_scale = -tmp;
+ }
+ #endif
+ #define HINT(value, scale_inv, scale) (PANGO2_UNITS_ROUND ((int) ((value) * scale)) * scale_inv)
+ #define HINT_X(value) HINT ((value), x_scale, x_scale_inv)
+ #define HINT_Y(value) HINT ((value), y_scale, y_scale_inv)
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ glyphs->glyphs[i].geometry.width = HINT_X (glyphs->glyphs[i].geometry.width);
+ glyphs->glyphs[i].geometry.x_offset = HINT_X (glyphs->glyphs[i].geometry.x_offset);
+ glyphs->glyphs[i].geometry.y_offset = HINT_Y (glyphs->glyphs[i].geometry.y_offset);
+ }
+ #undef HINT_Y
+ #undef HINT_X
+ #undef HINT
+ }
+ }
+ else
+ {
+ for (i = 0; i < glyphs->num_glyphs; i++)
+ {
+ glyphs->glyphs[i].geometry.width =
+ PANGO2_UNITS_ROUND (glyphs->glyphs[i].geometry.width);
+ glyphs->glyphs[i].geometry.x_offset =
+ PANGO2_UNITS_ROUND (glyphs->glyphs[i].geometry.x_offset);
+ glyphs->glyphs[i].geometry.y_offset =
+ PANGO2_UNITS_ROUND (glyphs->glyphs[i].geometry.y_offset);
+ }
+ }
+ }
+}
+
+/* }}} */
+/* {{{ Public API */
+
+/**
+ * pango2_shape:
+ * @item_text: valid UTF-8 text to shape
+ * @item_length: the length (in bytes) of @item_text.
+ * -1 means nul-terminated text.
+ * @paragraph_text: (nullable): text of the paragraph (see details).
+ * @paragraph_length: the length (in bytes) of @paragraph_text.
+ * -1 means nul-terminated text.
+ * @analysis: `Pango2Analysis` structure from [func@Pango2.itemize]
+ * @glyphs: glyph string in which to store results
+ * @flags: flags influencing the shaping process
+ *
+ * Convert the characters in @text into glyphs.
+ *
+ * Given a segment of text and the corresponding `Pango2Analysis` structure
+ * returned from [func@Pango2.itemize], convert the characters into glyphs.
+ * You may also pass in only a substring of the item from [func@Pango2.itemize].
+ *
+ * Note that the extra attributes in the @analyis that is returned from
+ * [func@Pango2.itemize] have indices that are relative to the entire paragraph,
+ * so you do not pass the full paragraph text as @paragraph_text, you need
+ * to subtract the item offset from their indices first.
+ */
+void
+pango2_shape (const char *item_text,
+ int item_length,
+ const char *paragraph_text,
+ int paragraph_length,
+ const Pango2Analysis *analysis,
+ Pango2GlyphString *glyphs,
+ Pango2ShapeFlags flags)
+{
+ pango2_shape_internal (item_text, item_length,
+ paragraph_text, paragraph_length,
+ analysis, NULL, 0,
+ glyphs, flags);
+}
+
+/**
+ * pango2_shape_item:
+ * @item: `Pango2Item` to shape
+ * @paragraph_text: (nullable): text of the paragraph (see details).
+ * @paragraph_length: the length (in bytes) of @paragraph_text.
+ * -1 means nul-terminated text.
+ * @log_attrs: (nullable): array of `Pango2LogAttr` for @item
+ * @glyphs: glyph string in which to store results
+ * @flags: flags influencing the shaping process
+ *
+ * Convert the characters in @item into glyphs.
+ *
+ * This is similar to [func@Pango2.shape], except it takes a `Pango2Item`
+ * instead of separate @item_text and @analysis arguments. It also takes
+ * @log_attrs, which may be used in implementing text transforms.
+ *
+ * Note that the extra attributes in the @analyis that is returned from
+ * [func@Pango2.itemize] have indices that are relative to the entire paragraph,
+ * so you do not pass the full paragraph text as @paragraph_text, you need to
+ * subtract the item offset from their indices before calling [func@Pango2.shape].
+ */
+void
+pango2_shape_item (Pango2Item *item,
+ const char *paragraph_text,
+ int paragraph_length,
+ Pango2LogAttr *log_attrs,
+ Pango2GlyphString *glyphs,
+ Pango2ShapeFlags flags)
+{
+ pango2_shape_internal (paragraph_text + item->offset, item->length,
+ paragraph_text, paragraph_length,
+ &item->analysis,
+ log_attrs, item->num_chars,
+ glyphs, flags);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */