Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDI-compatible rotation handling #702

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions libass/ass_cache_template.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ START(glyph_metrics, glyph_metrics_hash_key)
GENERIC(double, size)
GENERIC(int, face_index)
GENERIC(int, glyph_index)
GENERIC(int, vertical) // @font vertical layout
END(GlyphMetricsHashKey)

// describes an outline glyph
Expand Down
150 changes: 150 additions & 0 deletions libass/ass_font.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <inttypes.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_SFNT_NAMES_H
#include FT_SYNTHESIS_H
#include FT_GLYPH_H
#include FT_TRUETYPE_TABLES_H
Expand All @@ -35,6 +36,7 @@
#include "ass_fontselect.h"
#include "ass_utils.h"
#include "ass_shaper.h"
#include "ass_string.h"

#if FREETYPE_MAJOR == 2 && FREETYPE_MINOR < 6
// The lowercase name is still included (as a macro) but deprecated as of 2.6, so avoid using it if we can
Expand Down Expand Up @@ -401,6 +403,85 @@ FT_Face ass_face_stream(ASS_Library *lib, FT_Library ftlib, const char *name,
return face;
}

/**
* \brief Check if a face needs special GDI compatibility behavior
*/
static bool is_type1_conversion(FT_Face face, bool *is_mincho_gothic)
{
static const char* mincho_gothic_ids[] = {
"Microsoft:MS Mincho:1995",
"Microsoft:MS PMincho:1995",
"Microsoft:MS Gothic:1995",
"Microsoft:MS PGothic:1995",
};

#define TYPE1_VERSION "Converter: Windows Type 1 Installer"

*is_mincho_gothic = false;

bool ret = false;

int num_names = FT_Get_Sfnt_Name_Count(face);

for (int i = 0; i < num_names; i++) {
FT_SfntName name;

if (FT_Get_Sfnt_Name(face, i, &name))
continue;

if (name.platform_id == TT_PLATFORM_MICROSOFT &&
(name.name_id == TT_NAME_ID_UNIQUE_ID ||
name.name_id == TT_NAME_ID_VERSION_STRING)) {
char buf[64];
ass_utf16be_to_utf8(buf, sizeof(buf), (uint8_t *)name.string,
name.string_len);

if (name.name_id == TT_NAME_ID_UNIQUE_ID) {
for (size_t j = 0; j < sizeof(mincho_gothic_ids) / sizeof(mincho_gothic_ids[0]); j++) {
if (!ass_strcasecmp(mincho_gothic_ids[j], buf))
*is_mincho_gothic = true;
}
}

if (name.name_id == TT_NAME_ID_VERSION_STRING) {
// See if it starts with the prefix we expect
if (!strncmp(buf, TYPE1_VERSION, sizeof(TYPE1_VERSION) - 1))
ret = true;
}
}
}

return ret;
}

/**
* \brief Get a codepage bitmask for a face, 0 if unknown or invalid
*/
static uint32_t get_cp_mask(FT_Face face, bool is_mincho_gothic)
{
TT_OS2 *os2 = FT_Get_Sfnt_Table(face, FT_SFNT_OS2);

if (!os2 || os2->version < 1 || !os2->ulUnicodeRange1)
return 0;

uint32_t ranges = os2->ulCodePageRange1;

// Detection for some old MS fonts that GDI special-cases
if (!(ranges & (1 << 17)) &&
face->charmap && face->charmap->encoding == FT_ENCODING_MS_SJIS &&
FT_Get_Char_Index(face, 0xFF71) &&
FT_Get_Char_Index(face, 0xFF72) &&
FT_Get_Char_Index(face, 0xFF73) &&
FT_Get_Char_Index(face, 0xFF74) &&
FT_Get_Char_Index(face, 0xFF75))
return 0;

if ((ranges & (1 << 18)) && is_mincho_gothic)
return 0;

return ranges;
}

/**
* \brief Select a face with the given charcode and add it to ASS_Font
* \return index of the new face in font->faces, -1 if failed
Expand Down Expand Up @@ -444,6 +525,42 @@ static int add_face(ASS_FontSelector *fontsel, ASS_Font *font, uint32_t ch)
ass_charmap_magic(font->library, face);
set_font_metrics(face);

// Default
font->faces_cp[font->n_faces] = FT_ENCODING_ADOBE_LATIN_1;

bool is_mincho_gothic;
if (!is_type1_conversion(face, &is_mincho_gothic)) {
uint32_t cp_mask = get_cp_mask(face, is_mincho_gothic);
if (cp_mask) {
if (cp_mask & (1 << 17))
font->faces_cp[font->n_faces] = FT_ENCODING_MS_SJIS;
else if (cp_mask & (1 << 20))
font->faces_cp[font->n_faces] = FT_ENCODING_MS_GB2312;
else if (cp_mask & (1 << 18))
font->faces_cp[font->n_faces] = FT_ENCODING_MS_BIG5;
else if (cp_mask & (1 << 19))
font->faces_cp[font->n_faces] = FT_ENCODING_MS_WANSUNG;
} else {
if (FT_Get_Char_Index(face, 0xFF71) &&
FT_Get_Char_Index(face, 0xFF72) &&
FT_Get_Char_Index(face, 0xFF73) &&
FT_Get_Char_Index(face, 0xFF74) &&
FT_Get_Char_Index(face, 0xFF75))
font->faces_cp[font->n_faces] = FT_ENCODING_MS_SJIS;
else if (FT_Get_Char_Index(face, 0x61D4) &&
FT_Get_Char_Index(face, 0x9EE2))
font->faces_cp[font->n_faces] = FT_ENCODING_MS_GB2312;
else if (FT_Get_Char_Index(face, 0x9F98) &&
FT_Get_Char_Index(face, 0x9F79))
font->faces_cp[font->n_faces] = FT_ENCODING_MS_BIG5;
else if (FT_Get_Char_Index(face, 0xAC00) &&
FT_Get_Char_Index(face, 0xD558))
font->faces_cp[font->n_faces] = FT_ENCODING_MS_WANSUNG;
else if (FT_Get_Char_Index(face, 0xE000)) // GDI uses the current ANSI code page; we'll guess based on the font's encoding
font->faces_cp[font->n_faces] = (face->charmap && face->charmap->platform_id) ? face->charmap->encoding : FT_ENCODING_NONE;
}
}

font->faces[font->n_faces] = face;
font->faces_uid[font->n_faces++] = uid;
ass_face_set_size(face, font->size);
Expand Down Expand Up @@ -698,6 +815,39 @@ void ass_font_clear(ASS_Font *font)
free((char *) font->desc.family.str);
}

/**
* \brief Check if a code point should be treated as fullwidth in a particular encoding,
* matching GDI behavior when possible.
**/
bool ass_codepoint_is_fullwidth(FT_Encoding encoding, uint32_t symbol)
{
static const struct {
uint32_t low, high;
} ranges[] = {
{ 0x1100, 0x11ff }, // Hangul Jamo
{ 0x2000, 0x206f }, // General Punctuation
{ 0x2100, 0x214f }, // Letterlike Symbols
{ 0x2460, 0x24ff }, // Enclosed Alphanumerics
{ 0x25a0, 0x27ff }, // Geometric shapes, misc symbols
{ 0x3001, 0x319f }, // Various CJK
{ 0x3200, 0x4dff }, // CJK extensions
{ 0x4e00, 0x9fff }, // CJK unified ideographs
{ 0xac00, 0xd7a3 }, // Hangul
{ 0xe000, 0xfaff }, // PUA, CJK compatibility
{ 0xfe30, 0xfe4f }, // CJK Compatibility forms
{ 0xff01, 0xff5e }, // Fullwidth latin + punct
{ 0xffe0, 0xffee } // More fullwidth punctuation
};

for (size_t i = 0; i < sizeof(ranges) / sizeof(ranges[0]); i++) {
if (symbol >= ranges[i].low &&
symbol <= ranges[i].high)
return true;
}

return convert_unicode_to_mb(encoding, symbol) > 0xFF;
}

/**
* \brief Convert glyph into ASS_Outline according to decoration flags
**/
Expand Down
4 changes: 2 additions & 2 deletions libass/ass_font.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ typedef struct ass_font ASS_Font;
#include "ass_cache.h"
#include "ass_outline.h"

#define VERTICAL_LOWER_BOUND 0x02f1

#define ASS_FONT_MAX_FACES 10
#define DECO_UNDERLINE 1
#define DECO_STRIKETHROUGH 2
Expand All @@ -43,6 +41,7 @@ struct ass_font {
ASS_Library *library;
FT_Library ftlibrary;
int faces_uid[ASS_FONT_MAX_FACES];
FT_Encoding faces_cp[ASS_FONT_MAX_FACES];
FT_Face faces[ASS_FONT_MAX_FACES];
ASS_ShaperFontData *shaper_priv;
int n_faces;
Expand All @@ -61,6 +60,7 @@ uint32_t ass_font_index_magic(FT_Face face, uint32_t symbol);
bool ass_font_get_glyph(ASS_Font *font, int face_index, int index,
ASS_Hinting hinting);
void ass_font_clear(ASS_Font *font);
bool ass_codepoint_is_fullwidth(FT_Encoding encoding, uint32_t symbol);

bool ass_get_glyph_outline(ASS_Outline *outline, int32_t *advance,
FT_Face face, unsigned flags);
Expand Down
2 changes: 0 additions & 2 deletions libass/ass_render.c
Original file line number Diff line number Diff line change
Expand Up @@ -2177,8 +2177,6 @@ static bool parse_events(RenderContext *state, ASS_Event *event)
info->bold = state->bold;
info->italic = state->italic;
info->flags = state->flags;
if (info->font->desc.vertical && code >= VERTICAL_LOWER_BOUND)
info->flags |= DECO_ROTATE;
info->frx = state->frx;
info->fry = state->fry;
info->frz = state->frz;
Expand Down
20 changes: 9 additions & 11 deletions libass/ass_shaper.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ struct ass_shaper {
struct ass_shaper_metrics_data {
Cache *metrics_cache;
GlyphMetricsHashKey hash_key;
int vertical;
};

struct ass_shaper_font_data {
Expand Down Expand Up @@ -228,15 +227,8 @@ static FT_Glyph_Metrics *
get_cached_metrics(struct ass_shaper_metrics_data *metrics,
hb_codepoint_t unicode, hb_codepoint_t glyph)
{
bool rotate = false;
// if @font rendering is enabled and the glyph should be rotated,
// make cached_h_advance pick up the right advance later
if (metrics->vertical && unicode >= VERTICAL_LOWER_BOUND)
rotate = true;

metrics->hash_key.glyph_index = glyph;
FT_Glyph_Metrics *val = ass_cache_get(metrics->metrics_cache, &metrics->hash_key,
rotate ? metrics : NULL);
FT_Glyph_Metrics *val = ass_cache_get(metrics->metrics_cache, &metrics->hash_key, NULL);
if (!val)
return NULL;
if (val->width >= 0)
Expand All @@ -261,7 +253,9 @@ size_t ass_glyph_metrics_construct(void *key, void *value, void *priv)

memcpy(v, &face->glyph->metrics, sizeof(FT_Glyph_Metrics));

if (priv) // rotate
// if @font rendering is enabled and the glyph should be rotated,
// make cached_h_advance pick up the right advance later
if (k->vertical)
v->horiAdvance = v->vertAdvance;

return 1;
Expand Down Expand Up @@ -478,7 +472,6 @@ static hb_font_t *get_hb_font(ASS_Shaper *shaper, GlyphInfo *info)
if (!metrics)
return NULL;
metrics->metrics_cache = shaper->metrics_cache;
metrics->vertical = info->font->desc.vertical;

hb_font_funcs_t *funcs = hb_font_funcs_create();
if (!funcs)
Expand Down Expand Up @@ -513,6 +506,7 @@ static hb_font_t *get_hb_font(ASS_Shaper *shaper, GlyphInfo *info)
// update hash key for cached metrics
struct ass_shaper_metrics_data *metrics =
font->shaper_priv->metrics_data[info->face_index];
metrics->hash_key.vertical = !!(info->flags & DECO_ROTATE);
metrics->hash_key.font = info->font;
metrics->hash_key.face_index = info->face_index;
metrics->hash_key.size = info->font_size;
Expand Down Expand Up @@ -894,6 +888,10 @@ void ass_shaper_find_runs(ASS_Shaper *shaper, ASS_Renderer *render_priv,
// get font face and glyph index
ass_font_get_index(render_priv->fontselect, info->font,
info->symbol, &info->face_index, &info->glyph_index);

if (info->font->desc.vertical &&
ass_codepoint_is_fullwidth(info->font->faces_cp[info->face_index], info->symbol))
info->flags |= DECO_ROTATE;
}
if (i > 0) {
GlyphInfo *last = glyphs + i - 1;
Expand Down