jdk
436 строк · 14.0 Кб
1/*
2* Copyright © 2013 Google, Inc.
3*
4* This is part of HarfBuzz, a text shaping library.
5*
6* Permission is hereby granted, without written agreement and without
7* license or royalty fees, to use, copy, modify, and distribute this
8* software and its documentation for any purpose, provided that the
9* above copyright notice and the following two paragraphs appear in
10* all copies of this software.
11*
12* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16* DAMAGE.
17*
18* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
21* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23*
24* Google Author(s): Behdad Esfahbod
25*/
26
27#include "hb.hh"28
29#ifndef HB_NO_OT_SHAPE30
31#include "hb-ot-shaper.hh"32
33
34/* Hangul shaper */
35
36
37/* Same order as the feature array below */
38enum {39_JMO,40
41LJMO,42VJMO,43TJMO,44
45FIRST_HANGUL_FEATURE = LJMO,46HANGUL_FEATURE_COUNT = TJMO + 147};48
49static const hb_tag_t hangul_features[HANGUL_FEATURE_COUNT] =50{
51HB_TAG_NONE,52HB_TAG('l','j','m','o'),53HB_TAG('v','j','m','o'),54HB_TAG('t','j','m','o')55};56
57static void58collect_features_hangul (hb_ot_shape_planner_t *plan)59{
60hb_ot_map_builder_t *map = &plan->map;61
62for (unsigned int i = FIRST_HANGUL_FEATURE; i < HANGUL_FEATURE_COUNT; i++)63map->add_feature (hangul_features[i]);64}
65
66static void67override_features_hangul (hb_ot_shape_planner_t *plan)68{
69/* Uniscribe does not apply 'calt' for Hangul, and certain fonts70* (Noto Sans CJK, Source Sans Han, etc) apply all of jamo lookups
71* in calt, which is not desirable. */
72plan->map.disable_feature (HB_TAG('c','a','l','t'));73}
74
75struct hangul_shape_plan_t76{
77hb_mask_t mask_array[HANGUL_FEATURE_COUNT];78};79
80static void *81data_create_hangul (const hb_ot_shape_plan_t *plan)82{
83hangul_shape_plan_t *hangul_plan = (hangul_shape_plan_t *) hb_calloc (1, sizeof (hangul_shape_plan_t));84if (unlikely (!hangul_plan))85return nullptr;86
87for (unsigned int i = 0; i < HANGUL_FEATURE_COUNT; i++)88hangul_plan->mask_array[i] = plan->map.get_1_mask (hangul_features[i]);89
90return hangul_plan;91}
92
93static void94data_destroy_hangul (void *data)95{
96hb_free (data);97}
98
99/* Constants for algorithmic hangul syllable [de]composition. */
100#define LBase 0x1100u101#define VBase 0x1161u102#define TBase 0x11A7u103#define LCount 19u104#define VCount 21u105#define TCount 28u106#define SBase 0xAC00u107#define NCount (VCount * TCount)108#define SCount (LCount * NCount)109
110#define isCombiningL(u) (hb_in_range<hb_codepoint_t> ((u), LBase, LBase+LCount-1))111#define isCombiningV(u) (hb_in_range<hb_codepoint_t> ((u), VBase, VBase+VCount-1))112#define isCombiningT(u) (hb_in_range<hb_codepoint_t> ((u), TBase+1, TBase+TCount-1))113#define isCombinedS(u) (hb_in_range<hb_codepoint_t> ((u), SBase, SBase+SCount-1))114
115#define isL(u) (hb_in_ranges<hb_codepoint_t> ((u), 0x1100u, 0x115Fu, 0xA960u, 0xA97Cu))116#define isV(u) (hb_in_ranges<hb_codepoint_t> ((u), 0x1160u, 0x11A7u, 0xD7B0u, 0xD7C6u))117#define isT(u) (hb_in_ranges<hb_codepoint_t> ((u), 0x11A8u, 0x11FFu, 0xD7CBu, 0xD7FBu))118
119#define isHangulTone(u) (hb_in_range<hb_codepoint_t> ((u), 0x302Eu, 0x302Fu))120
121/* buffer var allocations */
122#define hangul_shaping_feature() ot_shaper_var_u8_auxiliary() /* hangul jamo shaping feature */123
124static bool125is_zero_width_char (hb_font_t *font,126hb_codepoint_t unicode)127{
128hb_codepoint_t glyph;129return hb_font_get_glyph (font, unicode, 0, &glyph) && hb_font_get_glyph_h_advance (font, glyph) == 0;130}
131
132static void133preprocess_text_hangul (const hb_ot_shape_plan_t *plan HB_UNUSED,134hb_buffer_t *buffer,135hb_font_t *font)136{
137HB_BUFFER_ALLOCATE_VAR (buffer, hangul_shaping_feature);138
139/* Hangul syllables come in two shapes: LV, and LVT. Of those:140*
141* - LV can be precomposed, or decomposed. Lets call those
142* <LV> and <L,V>,
143* - LVT can be fully precomposed, partially precomposed, or
144* fully decomposed. Ie. <LVT>, <LV,T>, or <L,V,T>.
145*
146* The composition / decomposition is mechanical. However, not
147* all <L,V> sequences compose, and not all <LV,T> sequences
148* compose.
149*
150* Here are the specifics:
151*
152* - <L>: U+1100..115F, U+A960..A97F
153* - <V>: U+1160..11A7, U+D7B0..D7C7
154* - <T>: U+11A8..11FF, U+D7CB..D7FB
155*
156* - Only the <L,V> sequences for some of the U+11xx ranges combine.
157* - Only <LV,T> sequences for some of the Ts in U+11xx range combine.
158*
159* Here is what we want to accomplish in this shaper:
160*
161* - If the whole syllable can be precomposed, do that,
162* - Otherwise, fully decompose and apply ljmo/vjmo/tjmo features.
163* - If a valid syllable is followed by a Hangul tone mark, reorder the tone
164* mark to precede the whole syllable - unless it is a zero-width glyph, in
165* which case we leave it untouched, assuming it's designed to overstrike.
166*
167* That is, of the different possible syllables:
168*
169* <L>
170* <L,V>
171* <L,V,T>
172* <LV>
173* <LVT>
174* <LV, T>
175*
176* - <L> needs no work.
177*
178* - <LV> and <LVT> can stay the way they are if the font supports them, otherwise we
179* should fully decompose them if font supports.
180*
181* - <L,V> and <L,V,T> we should compose if the whole thing can be composed.
182*
183* - <LV,T> we should compose if the whole thing can be composed, otherwise we should
184* decompose.
185*/
186
187buffer->clear_output ();188unsigned int start = 0, end = 0; /* Extent of most recently seen syllable;189* valid only if start < end
190*/
191unsigned int count = buffer->len;192
193for (buffer->idx = 0; buffer->idx < count && buffer->successful;)194{195hb_codepoint_t u = buffer->cur().codepoint;196
197if (isHangulTone (u))198{199/*200* We could cache the width of the tone marks and the existence of dotted-circle,
201* but the use of the Hangul tone mark characters seems to be rare enough that
202* I didn't bother for now.
203*/
204if (start < end && end == buffer->out_len)205{206/* Tone mark follows a valid syllable; move it in front, unless it's zero width. */207buffer->unsafe_to_break_from_outbuffer (start, buffer->idx);208if (unlikely (!buffer->next_glyph ())) break;209if (!is_zero_width_char (font, u))210{211buffer->merge_out_clusters (start, end + 1);212hb_glyph_info_t *info = buffer->out_info;213hb_glyph_info_t tone = info[end];214memmove (&info[start + 1], &info[start], (end - start) * sizeof (hb_glyph_info_t));215info[start] = tone;216}217}218else219{220/* No valid syllable as base for tone mark; try to insert dotted circle. */221if (!(buffer->flags & HB_BUFFER_FLAG_DO_NOT_INSERT_DOTTED_CIRCLE) &&222font->has_glyph (0x25CCu))223{224hb_codepoint_t chars[2];225if (!is_zero_width_char (font, u))226{227chars[0] = u;228chars[1] = 0x25CCu;229} else230{231chars[0] = 0x25CCu;232chars[1] = u;233}234(void) buffer->replace_glyphs (1, 2, chars);235}236else237{238/* No dotted circle available in the font; just leave tone mark untouched. */239(void) buffer->next_glyph ();240}241}242start = end = buffer->out_len;243continue;244}245
246start = buffer->out_len; /* Remember current position as a potential syllable start;247* will only be used if we set end to a later position.
248*/
249
250if (isL (u) && buffer->idx + 1 < count)251{252hb_codepoint_t l = u;253hb_codepoint_t v = buffer->cur(+1).codepoint;254if (isV (v))255{256/* Have <L,V> or <L,V,T>. */257hb_codepoint_t t = 0;258unsigned int tindex = 0;259if (buffer->idx + 2 < count)260{261t = buffer->cur(+2).codepoint;262if (isT (t))263tindex = t - TBase; /* Only used if isCombiningT (t); otherwise invalid. */264else265t = 0; /* The next character was not a trailing jamo. */266}267buffer->unsafe_to_break (buffer->idx, buffer->idx + (t ? 3 : 2));268
269/* We've got a syllable <L,V,T?>; see if it can potentially be composed. */270if (isCombiningL (l) && isCombiningV (v) && (t == 0 || isCombiningT (t)))271{272/* Try to compose; if this succeeds, end is set to start+1. */273hb_codepoint_t s = SBase + (l - LBase) * NCount + (v - VBase) * TCount + tindex;274if (font->has_glyph (s))275{276(void) buffer->replace_glyphs (t ? 3 : 2, 1, &s);277end = start + 1;278continue;279}280}281
282/* We didn't compose, either because it's an Old Hangul syllable without a283* precomposed character in Unicode, or because the font didn't support the
284* necessary precomposed glyph.
285* Set jamo features on the individual glyphs, and advance past them.
286*/
287buffer->cur().hangul_shaping_feature() = LJMO;288(void) buffer->next_glyph ();289buffer->cur().hangul_shaping_feature() = VJMO;290(void) buffer->next_glyph ();291if (t)292{293buffer->cur().hangul_shaping_feature() = TJMO;294(void) buffer->next_glyph ();295end = start + 3;296}297else298end = start + 2;299if (unlikely (!buffer->successful))300break;301if (buffer->cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES)302buffer->merge_out_clusters (start, end);303continue;304}305}306
307else if (isCombinedS (u))308{309/* Have <LV>, <LVT>, or <LV,T> */310hb_codepoint_t s = u;311bool has_glyph = font->has_glyph (s);312unsigned int lindex = (s - SBase) / NCount;313unsigned int nindex = (s - SBase) % NCount;314unsigned int vindex = nindex / TCount;315unsigned int tindex = nindex % TCount;316
317if (!tindex &&318buffer->idx + 1 < count &&319isCombiningT (buffer->cur(+1).codepoint))320{321/* <LV,T>, try to combine. */322unsigned int new_tindex = buffer->cur(+1).codepoint - TBase;323hb_codepoint_t new_s = s + new_tindex;324if (font->has_glyph (new_s))325{326(void) buffer->replace_glyphs (2, 1, &new_s);327end = start + 1;328continue;329}330else331buffer->unsafe_to_break (buffer->idx, buffer->idx + 2); /* Mark unsafe between LV and T. */332}333
334/* Otherwise, decompose if font doesn't support <LV> or <LVT>,335* or if having non-combining <LV,T>. Note that we already handled
336* combining <LV,T> above. */
337if (!has_glyph ||338(!tindex &&339buffer->idx + 1 < count &&340isT (buffer->cur(+1).codepoint)))341{342hb_codepoint_t decomposed[3] = {LBase + lindex,343VBase + vindex,344TBase + tindex};345if (font->has_glyph (decomposed[0]) &&346font->has_glyph (decomposed[1]) &&347(!tindex || font->has_glyph (decomposed[2])))348{349unsigned int s_len = tindex ? 3 : 2;350(void) buffer->replace_glyphs (1, s_len, decomposed);351
352/* If we decomposed an LV because of a non-combining T following,353* we want to include this T in the syllable.
354*/
355if (has_glyph && !tindex)356{357(void) buffer->next_glyph ();358s_len++;359}360if (unlikely (!buffer->successful))361break;362
363/* We decomposed S: apply jamo features to the individual glyphs364* that are now in buffer->out_info.
365*/
366hb_glyph_info_t *info = buffer->out_info;367end = start + s_len;368
369unsigned int i = start;370info[i++].hangul_shaping_feature() = LJMO;371info[i++].hangul_shaping_feature() = VJMO;372if (i < end)373info[i++].hangul_shaping_feature() = TJMO;374
375if (buffer->cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES)376buffer->merge_out_clusters (start, end);377continue;378}379else if ((!tindex && buffer->idx + 1 < count && isT (buffer->cur(+1).codepoint)))380buffer->unsafe_to_break (buffer->idx, buffer->idx + 2); /* Mark unsafe between LV and T. */381}382
383if (has_glyph)384{385/* We didn't decompose the S, so just advance past it and fall through. */386end = start + 1;387}388}389
390/* Didn't find a recognizable syllable, so we leave end <= start;391* this will prevent tone-mark reordering happening.
392*/
393(void) buffer->next_glyph ();394}395buffer->sync ();396}
397
398static void399setup_masks_hangul (const hb_ot_shape_plan_t *plan,400hb_buffer_t *buffer,401hb_font_t *font HB_UNUSED)402{
403const hangul_shape_plan_t *hangul_plan = (const hangul_shape_plan_t *) plan->data;404
405if (likely (hangul_plan))406{407unsigned int count = buffer->len;408hb_glyph_info_t *info = buffer->info;409for (unsigned int i = 0; i < count; i++, info++)410info->mask |= hangul_plan->mask_array[info->hangul_shaping_feature()];411}412
413HB_BUFFER_DEALLOCATE_VAR (buffer, hangul_shaping_feature);414}
415
416
417const hb_ot_shaper_t _hb_ot_shaper_hangul =418{
419collect_features_hangul,420override_features_hangul,421data_create_hangul,422data_destroy_hangul,423preprocess_text_hangul,424nullptr, /* postprocess_glyphs */425nullptr, /* decompose */426nullptr, /* compose */427setup_masks_hangul,428nullptr, /* reorder_marks */429HB_TAG_NONE, /* gpos_tag */430HB_OT_SHAPE_NORMALIZATION_MODE_NONE,431HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE,432false, /* fallback_position */433};434
435
436#endif437