fortplot_tt_tables.f90 Source File


Source Code

module fortplot_tt_tables
    !! TrueType table lookup and font initialization.
    !! Validates font signatures, finds tables in the table directory,
    !! and initializes a font by locating all required tables and the cmap encoding.
    use fortplot_tt_binary, only: tt_byte, tt_ushort, tt_ulong, tt_tag_match
    use, intrinsic :: iso_fortran_env, only: int8
    implicit none

    private
    public :: tt_is_font, tt_find_table
    public :: tt_get_font_offset_for_index, tt_init_font_tables

contains

    pure function tt_is_font(data, offset) result(is_valid)
        !! Check if data at offset contains a valid TrueType/OpenType signature.
        !! Recognizes: 00-01-00-00 (standard), "true", "typ1", "OTTO".
        integer(int8), intent(in) :: data(:)
        integer, intent(in) :: offset
        logical :: is_valid

        is_valid = .false.
        if (offset + 4 > size(data)) return

        ! Standard TrueType: 00 01 00 00
        if (tt_byte(data, offset) == 0 .and. tt_byte(data, offset + 1) == 1 .and. &
            tt_byte(data, offset + 2) == 0 .and. tt_byte(data, offset + 3) == 0) then
            is_valid = .true.
            return
        end if

        ! Apple TrueType
        if (tt_tag_match(data, offset, 'true')) then
            is_valid = .true.
            return
        end if

        ! TrueType with Type1 tables
        if (tt_tag_match(data, offset, 'typ1')) then
            is_valid = .true.
            return
        end if

        ! OpenType with CFF data
        if (tt_tag_match(data, offset, 'OTTO')) then
            is_valid = .true.
            return
        end if
    end function tt_is_font

    pure function tt_find_table(data, fontstart, tag) result(table_offset)
        !! Find a table by its 4-byte tag in the table directory.
        !! Returns the table byte offset, or 0 if not found.
        integer(int8), intent(in) :: data(:)
        integer, intent(in) :: fontstart
        character(len=4), intent(in) :: tag
        integer :: table_offset
        integer :: num_tables, i, record_offset

        table_offset = 0
        num_tables = tt_ushort(data, fontstart + 4)

        do i = 0, num_tables - 1
            record_offset = fontstart + 12 + 16*i
            if (tt_tag_match(data, record_offset, tag)) then
                table_offset = tt_ulong(data, record_offset + 8)
                return
            end if
        end do
    end function tt_find_table

    pure function tt_get_font_offset_for_index(data, index) result(offset)
        !! Get font byte offset for a given index in a TTC collection.
        !! For a standalone font, index 0 returns 0.
        !! Returns -1 if the index is out of range or data is invalid.
        integer(int8), intent(in) :: data(:)
        integer, intent(in) :: index
        integer :: offset
        integer :: version, num_fonts

        offset = -1

        ! Standalone font: only index 0 is valid
        if (tt_is_font(data, 0)) then
            if (index == 0) offset = 0
            return
        end if

        ! TrueType Collection
        if (.not. tt_tag_match(data, 0, 'ttcf')) return

        ! TTC header version is a 16.16 fixed-point number:
        ! 1.0 = 0x00010000 (65536), 2.0 = 0x00020000 (131072).
        version = tt_ulong(data, 4)
        if (version /= 65536 .and. version /= 131072) return

        num_fonts = tt_ulong(data, 8)
        if (index < 0 .or. index >= num_fonts) return

        offset = tt_ulong(data, 12 + index*4)
    end function tt_get_font_offset_for_index

    pure subroutine tt_init_font_tables(data, fontstart, loca, head, glyf, hhea, &
            hmtx, kern, gpos, index_map, index_to_loc_format, &
            num_glyphs, success)
        !! Initialize a TrueType font by locating all required tables and the
        !! cmap encoding subtable. Sets success=.false. if required tables are
        !! missing, no glyf table is present (CFF not supported), or no usable
        !! cmap encoding is found.
        integer(int8), intent(in) :: data(:)
        integer, intent(in) :: fontstart
        integer, intent(out) :: loca, head, glyf, hhea, hmtx
        integer, intent(out) :: kern, gpos
        integer, intent(out) :: index_map, index_to_loc_format, num_glyphs
        logical, intent(out) :: success
        integer :: cmap, maxp_table
        integer :: num_cmap_tables, i, encoding_record, platform_id, encoding_id

        success = .false.

        ! Locate required and optional tables
        cmap = tt_find_table(data, fontstart, 'cmap')
        loca = tt_find_table(data, fontstart, 'loca')
        head = tt_find_table(data, fontstart, 'head')
        glyf = tt_find_table(data, fontstart, 'glyf')
        hhea = tt_find_table(data, fontstart, 'hhea')
        hmtx = tt_find_table(data, fontstart, 'hmtx')
        kern = tt_find_table(data, fontstart, 'kern')
        gpos = tt_find_table(data, fontstart, 'GPOS')

        ! Validate required tables
        if (cmap == 0 .or. head == 0 .or. hhea == 0 .or. hmtx == 0) return
        if (glyf == 0) return
        if (loca == 0) return

        ! Number of glyphs from maxp table
        maxp_table = tt_find_table(data, fontstart, 'maxp')
        if (maxp_table /= 0) then
            num_glyphs = tt_ushort(data, maxp_table + 4)
        else
            num_glyphs = 65535
        end if

        ! Find a usable cmap encoding subtable
        num_cmap_tables = tt_ushort(data, cmap + 2)
        index_map = 0
        do i = 0, num_cmap_tables - 1
            encoding_record = cmap + 4 + 8*i
            platform_id = tt_ushort(data, encoding_record)
            select case (platform_id)
            case (3)  ! Microsoft
                encoding_id = tt_ushort(data, encoding_record + 2)
                if (encoding_id == 1 .or. encoding_id == 10) then
                    index_map = cmap + tt_ulong(data, encoding_record + 4)
                end if
            case (0)  ! Unicode
                index_map = cmap + tt_ulong(data, encoding_record + 4)
            end select
        end do
        if (index_map == 0) return

        ! Loca table entry format from head table offset 50
        index_to_loc_format = tt_ushort(data, head + 50)

        success = .true.
    end subroutine tt_init_font_tables

end module fortplot_tt_tables