def create_columns_component_h(data: GTData) -> str:
    """
    Returns the HTML text fragment for the column/spanner labels.
    """

    # Should the column labels be hidden?
    column_labels_hidden: bool = data._options.column_labels_hidden.value

    if column_labels_hidden:
        return ""

    # Get necessary data objects for composing the column labels and spanners
    stubh = data._stubhead
    boxhead = data._boxhead

    # TODO: The body component of the table is only needed for determining RTL alignment
    # is needed in the corresponding column labels
    # body = data._body

    # Get vector representation of stub layout
    stub_layout = data._stub._get_stub_layout(options=data._options)

    # Determine the finalized number of spanner rows
    spanner_row_count = _get_spanners_matrix_height(data=data, omit_columns_row=True)

    # TODO: Modify alignments for RTL support, skip this for now
    # Detect any RTL script characters within the visible columns;
    # this creates a vector the same length as `col_alignment`
    # rtl_detect = [
    #     any(char in rtl_modern_unicode_charset() for char in str(body[x])) for x in range(len(body))
    # ]
    #
    # For any columns containing characters from RTL scripts; we
    # will transform a 'left' alignment to a 'right' alignment
    # for i in range(len(rtl_detect)):
    #     if rtl_detect[i] and col_alignment[i] != "center":
    #         col_alignment[i] = "right"

    # Get the column headings
    headings_info = boxhead._get_default_columns()

    # Filter list of StyleInfo for the various stubhead and column labels components
    styles_stubhead = [x for x in data._styles if _is_loc(x.locname, loc.LocStubhead)]
    styles_column_labels = [x for x in data._styles if _is_loc(x.locname, loc.LocColumnHeader)]
    styles_spanner_label = [x for x in data._styles if _is_loc(x.locname, loc.LocSpannerLabels)]
    styles_column_label = [x for x in data._styles if _is_loc(x.locname, loc.LocColumnLabels)]

    # If columns are present in the stub, then replace with a set stubhead label or nothing
    if len(stub_layout) > 0 and stubh is not None:
        stub_label = stubh
        stub_var = "::stub"
    else:
        stub_label = ""
        stub_var = None

    # Set a default alignment for the stubhead label
    stubhead_label_alignment = "left"

    # Initialize the column headings list
    table_col_headings = []

    # Extract the table ID to ensure subsequent IDs are unique
    table_id = data._options.table_id.value

    # If there are no spanners, then we have to create the cells for the stubhead label
    # (if present) and for the column headings
    if spanner_row_count == 0:
        # Create the cell for the stubhead label
        if len(stub_layout) > 0:
            table_col_headings.append(
                tags.th(
                    HTML(_process_text(stub_label)),
                    class_=f"gt_col_heading gt_columns_bottom_border gt_{stubhead_label_alignment}",
                    rowspan="1",
                    colspan=len(stub_layout),
                    style=_flatten_styles(styles_stubhead),
                    scope="colgroup" if len(stub_layout) > 1 else "col",
                    id=_create_element_id(table_id, stub_label),
                )
            )

        # Create the headings in the case where there are no spanners at all -------------------------
        for info in headings_info:
            # Filter by column label / id, join with overall column labels style
            styles_i = [x for x in styles_column_label if x.colname == info.var]

            table_col_headings.append(
                tags.th(
                    HTML(_process_text(info.column_label)),
                    class_=f"gt_col_heading gt_columns_bottom_border gt_{info.defaulted_align}",
                    rowspan=1,
                    colspan=1,
                    style=_flatten_styles(styles_column_labels + styles_i),
                    scope="col",
                    id=_create_element_id(table_id, info.var),
                )
            )

        # Join the <th> cells into a string and begin each with a newline
        # th_cells = "\n" + "\n".join(["  " + str(tag) for tag in table_col_headings]) + "\n"

        table_col_headings = tags.tr(*table_col_headings, class_="gt_col_headings")

    #
    # Create the spanners and column labels in the case where there *are* spanners -------------
    #

    if spanner_row_count >= 1:
        spanners, _ = spanners_print_matrix(
            spanners=data._spanners, boxhead=boxhead, include_hidden=False
        )

        spanner_ids, spanner_col_names = spanners_print_matrix(
            spanners=data._spanners, boxhead=boxhead, include_hidden=False, ids=False
        )

        # Last is column labels
        # So take second to last
        level_1_index = -2

        # A list of <th> elements that will go in the first level; this
        # includes spanner labels and column labels for solo columns (don't
        # have spanner labels above them)
        level_1_spanners = []

        # A list of <th> elements that will go in the second row. This is
        # all column labels that DO have spanners above them.
        spanned_column_labels = []

        # Create the cell for the stubhead label
        if len(stub_layout) > 0:
            level_1_spanners.append(
                tags.th(
                    HTML(_process_text(stub_label)),
                    class_=f"gt_col_heading gt_columns_bottom_border gt_{str(stubhead_label_alignment)}",
                    rowspan=2,
                    colspan=len(stub_layout),
                    style=_flatten_styles(styles_stubhead),
                    scope="colgroup" if len(stub_layout) > 1 else "col",
                    id=_create_element_id(table_id, stub_label),
                )
            )

        # NOTE: Run-length encoding treats missing values as distinct from each other; in other
        # words, each missing value starts a new run of length 1

        spanner_ids_level_1_index = list(spanner_ids[level_1_index].values())
        spanners_rle = seq_groups(seq=spanner_ids_level_1_index)

        # `colspans` matches `spanners` in length; each element is the number of columns that the
        # <th> at that position should span; if 0, then skip the <th> at that position
        group_spans = [[x[1]] + [0] * (x[1] - 1) for x in spanners_rle]

        colspans = list(chain(*group_spans))

        for ii, (span_key, h_info) in enumerate(zip(spanner_col_names, headings_info)):
            if spanner_ids[level_1_index][span_key] is None:
                # Filter by column label / id, join with overall column labels style
                styles_i = [x for x in styles_column_label if x.colname == h_info.var]

                # Get the alignment values for the first set of column labels
                first_set_alignment = h_info.defaulted_align

                # Creation of <th> tags for column labels with no spanners above them
                level_1_spanners.append(
                    tags.th(
                        HTML(_process_text(h_info.column_label)),
                        class_=f"gt_col_heading gt_columns_bottom_border gt_{str(first_set_alignment)}",
                        rowspan=2,
                        colspan=1,
                        style=_flatten_styles(styles_column_labels + styles_i),
                        scope="col",
                        id=_create_element_id(table_id, h_info.var),
                    )
                )

            elif spanner_ids[level_1_index][span_key] is not None:
                # If colspans[i] == 0, it means that a previous cell's
                # `colspan` will cover us
                if colspans[ii] > 0:
                    # Filter by column label / id, join with overall column labels style
                    # TODO check this filter logic
                    styles_i = [
                        x
                        for x in styles_spanner_label
                        # TODO: refactor use of set
                        if set(x.grpname) & set([spanner_ids_level_1_index[ii]])
                    ]

                    level_1_spanners.append(
                        tags.th(
                            tags.span(
                                HTML(_process_text(spanner_ids_level_1_index[ii])),
                                class_="gt_column_spanner",
                            ),
                            class_="gt_center gt_columns_top_border gt_column_spanner_outer",
                            rowspan=1,
                            colspan=colspans[ii],
                            style=_flatten_styles(styles_column_labels + styles_i),
                            scope="colgroup" if colspans[ii] > 1 else "col",
                            id=_create_element_id(table_id, spanner_ids_level_1_index[ii]),
                        )
                    )

        remaining_headings = [k for k, v in spanner_ids[level_1_index].items() if v is not None]
        remaining_headings_labels = [
            entry.column_label for entry in boxhead if entry.var in remaining_headings
        ]
        col_alignment = [
            entry.defaulted_align for entry in boxhead if entry.var in remaining_headings
        ]

        if len(remaining_headings) > 0:
            spanned_column_labels = []

            remaining_heading_ids = [
                entry.var for entry in boxhead if entry.var in remaining_headings
            ]

            for j in range(len(remaining_headings)):
                # Filter by column label / id, join with overall column labels style
                # TODO check this filter logic
                styles_i = [x for x in styles_column_label if x.colname == remaining_headings[j]]

                remaining_alignment = boxhead._get_boxhead_get_alignment_by_var(
                    var=remaining_headings[j]
                )

                spanned_column_labels.append(
                    tags.th(
                        HTML(_process_text(remaining_headings_labels[j])),
                        class_=f"gt_col_heading gt_columns_bottom_border gt_{remaining_alignment}",
                        rowspan=1,
                        colspan=1,
                        style=_flatten_styles(styles_column_labels + styles_i),
                        scope="col",
                        id=_create_element_id(table_id, remaining_heading_ids[j]),
                    )
                )

            table_col_headings = TagList(
                tags.tr(level_1_spanners, class_="gt_col_headings gt_spanner_row"),
                tags.tr(spanned_column_labels, class_="gt_col_headings"),
            )

        else:
            # Create the `table_col_headings` HTML component
            table_col_headings = tags.tr(level_1_spanners, class_="gt_col_headings gt_spanner_row")

    if _get_spanners_matrix_height(data=data) > 2:
        # Spanners are listed top to bottom, so we need to work bottom to top
        # We can skip the last (column labels) and second to last (first spanner)
        higher_spanner_rows_idx = range(0, len(spanner_ids) - 2)
        higher_spanner_rows = TagList()

        for i in higher_spanner_rows_idx:
            spanners_row = spanners[i]
            for k, v in spanners_row.items():
                if v is None:
                    spanners_row[k] = ""

            spanner_ids_index = spanners_row.values()
            spanners_rle = seq_groups(seq=spanner_ids_index)
            group_spans = [[x[1]] + [0] * (x[1] - 1) for x in spanners_rle]
            colspans = list(chain(*group_spans))
            level_i_spanners = []

            for colspan, span_label in zip(colspans, spanners_row.values()):
                if colspan > 0:
                    # Filter by column label / id, join with overall column labels style
                    # TODO check this filter logic
                    styles_i = [
                        x
                        for x in styles_column_label
                        # TODO: refactor use of set
                        if set(x.grpname) & set([colspan, span_label])
                    ]

                    if span_label:
                        span = tags.span(
                            HTML(_process_text(span_label)),
                            class_="gt_column_spanner",
                        )
                    else:
                        span = tags.span(HTML("&nbsp;"))

                    level_i_spanners.append(
                        tags.th(
                            span,
                            class_="gt_center gt_columns_bottom_border gt_columns_top_border gt_column_spanner_outer",
                            rowspan=1,
                            colspan=colspan,
                            style=_flatten_styles(styles_column_labels + styles_i),
                            scope="colgroup" if colspan > 1 else "col",
                        )
                    )

            if len(stub_layout) > 0:
                level_i_spanners.insert(
                    0,
                    tags.th(
                        tags.span(HTML("&nbsp")),
                        class_=f"gt_col_heading gt_columns_bottom_border gt_{str(stubhead_label_alignment)}",
                        rowspan=1,
                        colspan=len(stub_layout),
                        scope="colgroup" if len(stub_layout) > 1 else "col",
                        # TODO check if ok to just use base styling?
                        style=_flatten_styles(styles_column_labels),
                    ),
                )

            higher_spanner_rows = TagList(
                higher_spanner_rows,
                TagList(
                    tags.tr(
                        level_i_spanners,
                        class_="gt_col_headings gt_spanner_row",
                        # TODO check if ok to just use base styling?
                        style=_flatten_styles(styles_column_labels),
                    )
                ),
            )

        table_col_headings = TagList(
            higher_spanner_rows,
            table_col_headings,
        )
    return table_col_headings

    Returns the HTML text fragment for the column/spanner labels.
    
great_tables/_utils_render_html.py