/* Haiku window system support. Hey, Emacs, this is -*- C++ -*-
Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of GNU Emacs.
GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
GNU Emacs 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Emacs. If not, see . */
#include
#include
#include
#include
#include
#include
#include "haiku_support.h"
/* Haiku doesn't expose font language data in BFont objects. Thus, we
select a few representative characters for each supported `:lang'
(currently Chinese, Korean and Japanese,) and test for those
instead. */
static uint32_t language_code_points[MAX_LANGUAGE][4] =
{{20154, 20754, 22996, 0}, /* Chinese. */
{51312, 49440, 44544, 0}, /* Korean. */
{26085, 26412, 12371, 0}, /* Japanese. */};
static void
estimate_font_ascii (BFont *font, int *max_width,
int *min_width, int *avg_width)
{
char ch[2];
bool tems[1];
int total = 0;
int count = 0;
int min = 0;
int max = 0;
std::memset (ch, 0, sizeof ch);
for (ch[0] = 32; ch[0] < 127; ++ch[0])
{
tems[0] = false;
font->GetHasGlyphs (ch, 1, tems);
if (tems[0])
{
int w = font->StringWidth (ch);
++count;
total += w;
if (!min || min > w)
min = w;
if (max < w)
max = w;
}
}
*min_width = min;
*max_width = max;
*avg_width = total / count;
}
void
BFont_close (void *font)
{
if (font != (void *) be_fixed_font &&
font != (void *) be_plain_font &&
font != (void *) be_bold_font)
delete (BFont *) font;
}
void
BFont_dat (void *font, int *px_size, int *min_width, int *max_width,
int *avg_width, int *height, int *space_width, int *ascent,
int *descent, int *underline_position, int *underline_thickness)
{
BFont *ft = (BFont *) font;
struct font_height fheight;
bool have_space_p;
char atem[1];
bool otem[1];
ft->GetHeight (&fheight);
atem[0] = ' ';
otem[0] = false;
ft->GetHasGlyphs (atem, 1, otem);
have_space_p = otem[0];
estimate_font_ascii (ft, max_width, min_width, avg_width);
*ascent = std::lrint (fheight.ascent);
*descent = std::lrint (fheight.descent);
*height = *ascent + *descent;
*space_width = have_space_p ? ft->StringWidth (" ") : 0;
*px_size = std::lrint (ft->Size ());
*underline_position = 0;
*underline_thickness = 0;
}
/* Return non-null if FONT contains CHR, a Unicode code-point. */
int
BFont_have_char_p (void *font, int32_t chr)
{
BFont *ft = (BFont *) font;
return ft->IncludesBlock (chr, chr);
}
/* Return non-null if font contains a block from BEG to END. */
int
BFont_have_char_block (void *font, int32_t beg, int32_t end)
{
BFont *ft = (BFont *) font;
return ft->IncludesBlock (beg, end);
}
/* Compute bounds for MB_STR, a character in multibyte encoding,
used with font. The width (in pixels) is returned in ADVANCE,
the left bearing in LB, and the right bearing in RB. */
void
BFont_char_bounds (void *font, const char *mb_str, int *advance,
int *lb, int *rb)
{
BFont *ft = (BFont *) font;
edge_info edge_info;
float size, escapement;
size = ft->Size ();
ft->GetEdges (mb_str, 1, &edge_info);
ft->GetEscapements (mb_str, 1, &escapement);
*advance = std::lrint (escapement * size);
*lb = std::lrint (edge_info.left * size);
*rb = *advance + std::lrint (edge_info.right * size);
}
/* The same, but for a variable amount of chars. */
void
BFont_nchar_bounds (void *font, const char *mb_str, int *advance,
int *lb, int *rb, int32_t n)
{
BFont *ft = (BFont *) font;
edge_info edge_info[n];
float size;
float escapement[n];
size = ft->Size ();
ft->GetEdges (mb_str, n, edge_info);
ft->GetEscapements (mb_str, n, (float *) escapement);
for (int32_t i = 0; i < n; ++i)
{
advance[i] = std::lrint (escapement[i] * size);
lb[i] = advance[i] - std::lrint (edge_info[i].left * size);
rb[i] = advance[i] + std::lrint (edge_info[i].right * size);
}
}
static void
font_style_to_flags (char *st, struct haiku_font_pattern *pattern)
{
char *style = strdup (st);
char *token;
pattern->weight = -1;
pattern->width = NO_WIDTH;
pattern->slant = NO_SLANT;
int tok = 0;
while ((token = std::strtok (!tok ? style : NULL, " ")) && tok < 3)
{
if (token && !strcmp (token, "Thin"))
pattern->weight = HAIKU_THIN;
else if (token && !strcmp (token, "UltraLight"))
pattern->weight = HAIKU_ULTRALIGHT;
else if (token && !strcmp (token, "ExtraLight"))
pattern->weight = HAIKU_EXTRALIGHT;
else if (token && !strcmp (token, "Light"))
pattern->weight = HAIKU_LIGHT;
else if (token && !strcmp (token, "SemiLight"))
pattern->weight = HAIKU_SEMI_LIGHT;
else if (token && !strcmp (token, "Regular"))
{
if (pattern->slant == NO_SLANT)
pattern->slant = SLANT_REGULAR;
if (pattern->width == NO_WIDTH)
pattern->width = NORMAL_WIDTH;
if (pattern->weight == -1)
pattern->weight = HAIKU_REGULAR;
}
else if (token && !strcmp (token, "SemiBold"))
pattern->weight = HAIKU_SEMI_BOLD;
else if (token && !strcmp (token, "Bold"))
pattern->weight = HAIKU_BOLD;
else if (token && (!strcmp (token, "ExtraBold") ||
/* This has actually been seen in the wild. */
!strcmp (token, "Extrabold")))
pattern->weight = HAIKU_EXTRA_BOLD;
else if (token && !strcmp (token, "UltraBold"))
pattern->weight = HAIKU_ULTRA_BOLD;
else if (token && !strcmp (token, "Book"))
pattern->weight = HAIKU_BOOK;
else if (token && !strcmp (token, "Heavy"))
pattern->weight = HAIKU_HEAVY;
else if (token && !strcmp (token, "UltraHeavy"))
pattern->weight = HAIKU_ULTRA_HEAVY;
else if (token && !strcmp (token, "Black"))
pattern->weight = HAIKU_BLACK;
else if (token && !strcmp (token, "Medium"))
pattern->weight = HAIKU_MEDIUM;
else if (token && !strcmp (token, "Oblique"))
pattern->slant = SLANT_OBLIQUE;
else if (token && !strcmp (token, "Italic"))
pattern->slant = SLANT_ITALIC;
else if (token && !strcmp (token, "UltraCondensed"))
pattern->width = ULTRA_CONDENSED;
else if (token && !strcmp (token, "ExtraCondensed"))
pattern->width = EXTRA_CONDENSED;
else if (token && !strcmp (token, "Condensed"))
pattern->width = CONDENSED;
else if (token && !strcmp (token, "SemiCondensed"))
pattern->width = SEMI_CONDENSED;
else if (token && !strcmp (token, "SemiExpanded"))
pattern->width = SEMI_EXPANDED;
else if (token && !strcmp (token, "Expanded"))
pattern->width = EXPANDED;
else if (token && !strcmp (token, "ExtraExpanded"))
pattern->width = EXTRA_EXPANDED;
else if (token && !strcmp (token, "UltraExpanded"))
pattern->width = ULTRA_EXPANDED;
else
{
tok = 1000;
break;
}
tok++;
}
if (pattern->weight != -1)
pattern->specified |= FSPEC_WEIGHT;
if (pattern->slant != NO_SLANT)
pattern->specified |= FSPEC_SLANT;
if (pattern->width != NO_WIDTH)
pattern->specified |= FSPEC_WIDTH;
if (tok > 3)
{
pattern->specified &= ~FSPEC_SLANT;
pattern->specified &= ~FSPEC_WEIGHT;
pattern->specified &= ~FSPEC_WIDTH;
pattern->specified |= FSPEC_STYLE;
std::strncpy ((char *) &pattern->style, st,
sizeof pattern->style - 1);
}
free (style);
}
static bool
font_check_wanted_chars (struct haiku_font_pattern *pattern, font_family family,
char *style)
{
BFont ft;
if (ft.SetFamilyAndStyle (family, style) != B_OK)
return false;
for (int i = 0; i < pattern->want_chars_len; ++i)
if (!ft.IncludesBlock (pattern->wanted_chars[i],
pattern->wanted_chars[i]))
return false;
return true;
}
static bool
font_check_one_of (struct haiku_font_pattern *pattern, font_family family,
char *style)
{
BFont ft;
if (ft.SetFamilyAndStyle (family, style) != B_OK)
return false;
for (int i = 0; i < pattern->need_one_of_len; ++i)
if (ft.IncludesBlock (pattern->need_one_of[i],
pattern->need_one_of[i]))
return true;
return false;
}
static bool
font_check_language (struct haiku_font_pattern *pattern, font_family family,
char *style)
{
BFont ft;
if (ft.SetFamilyAndStyle (family, style) != B_OK)
return false;
if (pattern->language == MAX_LANGUAGE)
return false;
for (uint32_t *ch = (uint32_t *)
&language_code_points[pattern->language]; *ch; ch++)
if (!ft.IncludesBlock (*ch, *ch))
return false;
return true;
}
static bool
font_family_style_matches_p (font_family family, char *style, uint32_t flags,
struct haiku_font_pattern *pattern,
int ignore_flags_p = 0)
{
struct haiku_font_pattern m;
m.specified = 0;
if (style)
font_style_to_flags (style, &m);
if ((pattern->specified & FSPEC_FAMILY) &&
strcmp ((char *) &pattern->family, family))
return false;
if (!ignore_flags_p && (pattern->specified & FSPEC_SPACING) &&
!(pattern->mono_spacing_p) != !(flags & B_IS_FIXED))
return false;
if (pattern->specified & FSPEC_STYLE)
return style && !strcmp (style, pattern->style);
if ((pattern->specified & FSPEC_WEIGHT)
&& (pattern->weight
!= ((m.specified & FSPEC_WEIGHT) ? m.weight : HAIKU_REGULAR)))
return false;
if ((pattern->specified & FSPEC_SLANT)
&& (pattern->slant
!= ((m.specified & FSPEC_SLANT) ? m.slant : SLANT_REGULAR)))
return false;
if ((pattern->specified & FSPEC_WANTED)
&& !font_check_wanted_chars (pattern, family, style))
return false;
if ((pattern->specified & FSPEC_WIDTH)
&& (pattern->width !=
((m.specified & FSPEC_WIDTH) ? m.width : NORMAL_WIDTH)))
return false;
if ((pattern->specified & FSPEC_NEED_ONE_OF)
&& !font_check_one_of (pattern, family, style))
return false;
if ((pattern->specified & FSPEC_LANGUAGE)
&& !font_check_language (pattern, family, style))
return false;
return true;
}
static void
haiku_font_fill_pattern (struct haiku_font_pattern *pattern,
font_family family, char *style,
uint32_t flags)
{
if (style)
font_style_to_flags (style, pattern);
pattern->specified |= FSPEC_FAMILY;
std::strncpy (pattern->family, family,
sizeof pattern->family - 1);
pattern->specified |= FSPEC_SPACING;
pattern->mono_spacing_p = flags & B_IS_FIXED;
}
/* Delete every element of the font pattern PT. */
void
haiku_font_pattern_free (struct haiku_font_pattern *pt)
{
struct haiku_font_pattern *tem = pt;
while (tem)
{
struct haiku_font_pattern *t = tem;
tem = t->next;
delete t;
}
}
/* Find all fonts matching the font pattern PT. */
struct haiku_font_pattern *
BFont_find (struct haiku_font_pattern *pt)
{
struct haiku_font_pattern *r = NULL;
font_family name;
font_style sname;
uint32 flags;
int sty_count;
int fam_count = count_font_families ();
for (int fi = 0; fi < fam_count; ++fi)
{
if (get_font_family (fi, &name, &flags) == B_OK)
{
sty_count = count_font_styles (name);
if (!sty_count &&
font_family_style_matches_p (name, NULL, flags, pt))
{
struct haiku_font_pattern *p = new struct haiku_font_pattern;
p->specified = 0;
p->oblique_seen_p = 1;
haiku_font_fill_pattern (p, name, NULL, flags);
p->next = r;
if (p->next)
p->next->last = p;
p->last = NULL;
p->next_family = r;
r = p;
}
else if (sty_count)
{
for (int si = 0; si < sty_count; ++si)
{
int oblique_seen_p = 0;
struct haiku_font_pattern *head = r;
struct haiku_font_pattern *p = NULL;
if (get_font_style (name, si, &sname, &flags) == B_OK)
{
if (font_family_style_matches_p (name, (char *) &sname, flags, pt))
{
p = new struct haiku_font_pattern;
p->specified = 0;
haiku_font_fill_pattern (p, name, (char *) &sname, flags);
if (p->specified & FSPEC_SLANT &&
((p->slant == SLANT_OBLIQUE) || (p->slant == SLANT_ITALIC)))
oblique_seen_p = 1;
p->next = r;
if (p->next)
p->next->last = p;
r = p;
p->next_family = head;
}
}
if (p)
p->last = NULL;
for (; head; head = head->last)
{
head->oblique_seen_p = oblique_seen_p;
}
}
}
}
}
/* There's a very good chance that this result will get cached if no
slant is specified. Thus, we look through each font that hasn't
seen an oblique style, and add one. */
if (!(pt->specified & FSPEC_SLANT))
{
/* r->last is invalid from here onwards. */
for (struct haiku_font_pattern *p = r; p;)
{
if (!p->oblique_seen_p)
{
struct haiku_font_pattern *n = new haiku_font_pattern;
*n = *p;
n->slant = SLANT_OBLIQUE;
p->next = n;
p = p->next_family;
}
else
p = p->next_family;
}
}
return r;
}
/* Find and open a font matching the pattern PAT, which must have its
family set. */
int
BFont_open_pattern (struct haiku_font_pattern *pat, void **font, float size)
{
int sty_count;
font_family name;
font_style sname;
uint32 flags = 0;
if (!(pat->specified & FSPEC_FAMILY))
return 1;
strncpy (name, pat->family, sizeof name - 1);
sty_count = count_font_styles (name);
if (!sty_count &&
font_family_style_matches_p (name, NULL, flags, pat, 1))
{
BFont *ft = new BFont;
if (ft->SetFamilyAndStyle (name, NULL) != B_OK)
{
delete ft;
return 1;
}
ft->SetSize (size);
ft->SetEncoding (B_UNICODE_UTF8);
ft->SetSpacing (B_BITMAP_SPACING);
*font = (void *) ft;
return 0;
}
else if (sty_count)
{
for (int si = 0; si < sty_count; ++si)
{
if (get_font_style (name, si, &sname, &flags) == B_OK &&
font_family_style_matches_p (name, (char *) &sname, flags, pat))
{
BFont *ft = new BFont;
if (ft->SetFamilyAndStyle (name, sname) != B_OK)
{
delete ft;
return 1;
}
ft->SetSize (size);
ft->SetEncoding (B_UNICODE_UTF8);
ft->SetSpacing (B_BITMAP_SPACING);
*font = (void *) ft;
return 0;
}
}
}
if (pat->specified & FSPEC_SLANT && pat->slant == SLANT_OBLIQUE)
{
struct haiku_font_pattern copy = *pat;
copy.slant = SLANT_REGULAR;
int code = BFont_open_pattern (©, font, size);
if (code)
return code;
BFont *ft = (BFont *) *font;
/* XXX Font measurements don't respect shear. Haiku bug?
This apparently worked in BeOS.
ft->SetShear (100.0); */
ft->SetFace (B_ITALIC_FACE);
return 0;
}
return 1;
}
/* Query the family of the default fixed font. */
void
BFont_populate_fixed_family (struct haiku_font_pattern *ptn)
{
font_family f;
font_style s;
be_fixed_font->GetFamilyAndStyle (&f, &s);
ptn->specified |= FSPEC_FAMILY;
strncpy (ptn->family, f, sizeof ptn->family - 1);
}
void
BFont_populate_plain_family (struct haiku_font_pattern *ptn)
{
font_family f;
font_style s;
be_plain_font->GetFamilyAndStyle (&f, &s);
ptn->specified |= FSPEC_FAMILY;
strncpy (ptn->family, f, sizeof ptn->family - 1);
}
int
BFont_string_width (void *font, const char *utf8)
{
return ((BFont *) font)->StringWidth (utf8);
}