307 lines
14 KiB
XML
307 lines
14 KiB
XML
<?xml version="1.0"?>
|
|
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
|
|
"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
|
|
<!ENTITY % local.common.attrib "xmlns:xi CDATA #FIXED 'http://www.w3.org/2003/XInclude'">
|
|
<!ENTITY version SYSTEM "version.xml">
|
|
]>
|
|
<chapter id="shaping-and-shape-plans">
|
|
<title>Shaping and shape plans</title>
|
|
<para>
|
|
Once you have your face and font objects configured as desired and
|
|
your input buffer is filled with the characters you need to shape,
|
|
all you need to do is call <function>hb_shape()</function>.
|
|
</para>
|
|
<para>
|
|
HarfBuzz will return the shaped version of the text in the same
|
|
buffer that you provided, but it will be in output mode. At that
|
|
point, you can iterate through the glyphs in the buffer, drawing
|
|
each one at the specified position or handing them off to the
|
|
appropriate graphics library.
|
|
</para>
|
|
<para>
|
|
For the most part, HarfBuzz's shaping step is straightforward from
|
|
the outside. But that doesn't mean there will never be cases where
|
|
you want to look under the hood and see what is happening on the
|
|
inside. HarfBuzz provides facilities for doing that, too.
|
|
</para>
|
|
|
|
<section id="shaping-buffer-output">
|
|
<title>Shaping and buffer output</title>
|
|
<para>
|
|
The <function>hb_shape()</function> function call takes four arguments: the font
|
|
object to use, the buffer of characters to shape, an array of
|
|
user-specified features to apply, and the length of that feature
|
|
array. The feature array can be NULL, so for the sake of
|
|
simplicity we will start with that case.
|
|
</para>
|
|
<para>
|
|
Internally, HarfBuzz looks at the tables of the font file to
|
|
determine where glyph classes, substitutions, and positioning
|
|
are defined, using that information to decide which
|
|
<emphasis>shaper</emphasis> to use (<literal>ot</literal> for
|
|
OpenType fonts, <literal>aat</literal> for Apple Advanced
|
|
Typography fonts, and so on). It also looks at the direction,
|
|
script, and language properties of the segment to figure out
|
|
which script-specific shaping model is needed (at least, in
|
|
shapers that support multiple options).
|
|
</para>
|
|
<para>
|
|
If a font has a GDEF table, then that is used for
|
|
glyph classes; if not, HarfBuzz will fall back to Unicode
|
|
categorization by code point. If a font has an AAT "morx" table,
|
|
then it is used for substitutions; if not, but there is a GSUB
|
|
table, then the GSUB table is used. If the font has an AAT
|
|
"kerx" table, then it is used for positioning; if not, but
|
|
there is a GPOS table, then the GPOS table is used. If neither
|
|
table is found, but there is a "kern" table, then HarfBuzz will
|
|
use the "kern" table. If there is no "kerx", no GPOS, and no
|
|
"kern", HarfBuzz will fall back to positioning marks itself.
|
|
</para>
|
|
<para>
|
|
With a well-behaved OpenType font, you expect GDEF, GSUB, and
|
|
GPOS tables to all be applied. HarfBuzz implements the
|
|
script-specific shaping models in internal functions, rather
|
|
than in the public API.
|
|
</para>
|
|
<para>
|
|
The algorithms
|
|
used for complex scripts can be quite involved; HarfBuzz tries
|
|
to be compatible with the OpenType Layout specification
|
|
and, wherever there is any ambiguity, HarfBuzz attempts to replicate the
|
|
output of Microsoft's Uniscribe engine. See the <ulink
|
|
url="https://docs.microsoft.com/en-us/typography/script-development/standard">Microsoft
|
|
Typography pages</ulink> for more detail.
|
|
</para>
|
|
<para>
|
|
In general, though, all that you need to know is that
|
|
<function>hb_shape()</function> returns the results of shaping
|
|
in the same buffer that you provided. The buffer's content type
|
|
will now be set to
|
|
<literal>HB_BUFFER_CONTENT_TYPE_GLYPHS</literal>, indicating
|
|
that it contains shaped output, rather than input text. You can
|
|
now extract the glyph information and positioning arrays:
|
|
</para>
|
|
<programlisting language="C">
|
|
hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count);
|
|
hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(buf, &glyph_count);
|
|
</programlisting>
|
|
<para>
|
|
The glyph information array holds a <type>hb_glyph_info_t</type>
|
|
for each output glyph, which has two fields:
|
|
<parameter>codepoint</parameter> and
|
|
<parameter>cluster</parameter>. Whereas, in the input buffer,
|
|
the <parameter>codepoint</parameter> field contained the Unicode
|
|
code point, it now contains the glyph ID of the corresponding
|
|
glyph in the font. The <parameter>cluster</parameter> field is
|
|
an integer that you can use to help identify when shaping has
|
|
reordered, split, or combined code points; we will say more
|
|
about that in the next chapter.
|
|
</para>
|
|
<para>
|
|
The glyph positions array holds a corresponding
|
|
<type>hb_glyph_position_t</type> for each output glyph,
|
|
containing four fields: <parameter>x_advance</parameter>,
|
|
<parameter>y_advance</parameter>,
|
|
<parameter>x_offset</parameter>, and
|
|
<parameter>y_offset</parameter>. The advances tell you how far
|
|
you need to move the drawing point after drawing this glyph,
|
|
depending on whether you are setting horizontal text (in which
|
|
case you will have x advances) or vertical text (for which you
|
|
will have y advances). The x and y offsets tell you where to
|
|
move to start drawing the glyph; usually you will have both and
|
|
x and a y offset, regardless of the text direction.
|
|
</para>
|
|
<para>
|
|
Most of the time, you will rely on a font-rendering library or
|
|
other graphics library to do the actual drawing of glyphs, so
|
|
you will need to iterate through the glyphs in the buffer and
|
|
pass the corresponding values off.
|
|
</para>
|
|
</section>
|
|
|
|
<section id="shaping-opentype-features">
|
|
<title>OpenType features</title>
|
|
<para>
|
|
OpenType features enable fonts to include smart behavior,
|
|
implemented as "lookup" rules stored in the GSUB and GPOS
|
|
tables. The OpenType specification defines a long list of
|
|
standard features that fonts can use for these behaviors; each
|
|
feature has a four-character reserved name and a well-defined
|
|
semantic meaning.
|
|
</para>
|
|
<para>
|
|
Some OpenType features are defined for the purpose of supporting
|
|
complex-script shaping, and are automatically activated, but
|
|
only when a buffer's script property is set to a script that the
|
|
feature supports.
|
|
</para>
|
|
<para>
|
|
Other features are more generic and can apply to several (or
|
|
any) script, and shaping engines are expected to implement
|
|
them. By default, HarfBuzz activates several of these features
|
|
on every text run. They include <literal>abvm</literal>,
|
|
<literal>blwm</literal>, <literal>ccmp</literal>,
|
|
<literal>locl</literal>, <literal>mark</literal>,
|
|
<literal>mkmk</literal>, and <literal>rlig</literal>.
|
|
</para>
|
|
<para>
|
|
In addition, if the text direction is horizontal, HarfBuzz
|
|
also applies the <literal>calt</literal>,
|
|
<literal>clig</literal>, <literal>curs</literal>,
|
|
<literal>dist</literal>, <literal>kern</literal>,
|
|
<literal>liga</literal>, <literal>rclt</literal>,
|
|
and <literal>frac</literal> features.
|
|
</para>
|
|
<para>
|
|
If the text direction is vertical, HarfBuzz applies
|
|
the <literal>vert</literal> feature by default.
|
|
</para>
|
|
<para>
|
|
Still other features are designed to be purely optional and left
|
|
up to the application or the end user to enable or disable as desired.
|
|
</para>
|
|
<para>
|
|
You can adjust the set of features that HarfBuzz applies to a
|
|
buffer by supplying an array of <type>hb_feature_t</type>
|
|
features as the third argument to
|
|
<function>hb_shape()</function>. For a simple case, let's just
|
|
enable the <literal>dlig</literal> feature, which turns on any
|
|
"discretionary" ligatures in the font:
|
|
</para>
|
|
<programlisting language="C">
|
|
hb_feature_t userfeatures[1];
|
|
userfeatures[0].tag = HB_TAG('d','l','i','g');
|
|
userfeatures[0].value = 1;
|
|
userfeatures[0].start = HB_FEATURE_GLOBAL_START;
|
|
userfeatures[0].end = HB_FEATURE_GLOBAL_END;
|
|
</programlisting>
|
|
<para>
|
|
<literal>HB_FEATURE_GLOBAL_END</literal> and
|
|
<literal>HB_FEATURE_GLOBAL_END</literal> are macros we can use
|
|
to indicate that the features will be applied to the entire
|
|
buffer. We could also have used a literal <literal>0</literal>
|
|
for the start and a <literal>-1</literal> to indicate the end of
|
|
the buffer (or have selected other start and end positions, if needed).
|
|
</para>
|
|
<para>
|
|
When we pass the <varname>userfeatures</varname> array to
|
|
<function>hb_shape()</function>, any discretionary ligature
|
|
substitutions from our font that match the text in our buffer
|
|
will get performed:
|
|
</para>
|
|
<programlisting language="C">
|
|
hb_shape(font, buf, userfeatures, num_features);
|
|
</programlisting>
|
|
<para>
|
|
Just like we enabled the <literal>dlig</literal> feature by
|
|
setting its <parameter>value</parameter> to
|
|
<literal>1</literal>, you would disable a feature by setting its
|
|
<parameter>value</parameter> to <literal>0</literal>. Some
|
|
features can take other <parameter>value</parameter> settings;
|
|
be sure you read the full specification of each feature tag to
|
|
understand what it does and how to control it.
|
|
</para>
|
|
</section>
|
|
|
|
<section id="shaping-shaper-selection">
|
|
<title>Shaper selection</title>
|
|
<para>
|
|
The basic version of <function>hb_shape()</function> determines
|
|
its shaping strategy based on examining the capabilities of the
|
|
font file. OpenType font tables cause HarfBuzz to try the
|
|
<literal>ot</literal> shaper, while AAT font tables cause HarfBuzz to try the
|
|
<literal>aat</literal> shaper.
|
|
</para>
|
|
<para>
|
|
In the real world, however, a font might include some unusual
|
|
mix of tables, or one of the tables might simply be broken for
|
|
the script you need to shape. So, sometimes, you might not
|
|
want to rely on HarfBuzz's process for deciding what to do, and
|
|
just tell <function>hb_shape()</function> what you want it to try.
|
|
</para>
|
|
<para>
|
|
<function>hb_shape_full()</function> is an alternate shaping
|
|
function that lets you supply a list of shapers for HarfBuzz to
|
|
try, in order, when shaping your buffer. For example, if you
|
|
have determined that HarfBuzz's attempts to work around broken
|
|
tables gives you better results than the AAT shaper itself does,
|
|
you might move the AAT shaper to the end of your list of
|
|
preferences and call <function>hb_shape_full()</function>
|
|
</para>
|
|
<programlisting language="C">
|
|
char *shaperprefs[3] = {"ot", "default", "aat"};
|
|
...
|
|
hb_shape_full(font, buf, userfeatures, num_features, shaperprefs);
|
|
</programlisting>
|
|
<para>
|
|
to get results you are happier with.
|
|
</para>
|
|
<para>
|
|
You may also want to call
|
|
<function>hb_shape_list_shapers()</function> to get a list of
|
|
the shapers that were built at compile time in your copy of HarfBuzz.
|
|
</para>
|
|
</section>
|
|
|
|
<section id="shaping-plans-and-caching">
|
|
<title>Plans and caching</title>
|
|
<para>
|
|
Internally, HarfBuzz uses a structure called a shape plan to
|
|
track its decisions about how to shape the contents of a
|
|
buffer. The <function>hb_shape()</function> function builds up the shape plan by
|
|
examining segment properties and by inspecting the contents of
|
|
the font.
|
|
</para>
|
|
<para>
|
|
This process can involve some decision-making and
|
|
trade-offs — for example, HarfBuzz inspects the GSUB and GPOS
|
|
lookups for the script and language tags set on the segment
|
|
properties, but it falls back on the lookups under the
|
|
<literal>DFLT</literal> tag (and sometimes other common tags)
|
|
if there are actually no lookups for the tag requested.
|
|
</para>
|
|
<para>
|
|
HarfBuzz also includes some work-arounds for
|
|
handling well-known older font conventions that do not follow
|
|
OpenType or Unicode specifications, for buggy system fonts, and for
|
|
peculiarities of Microsoft Uniscribe. All of that means that a
|
|
shape plan, while not something that you should edit directly in
|
|
client code, still might be an object that you want to
|
|
inspect. Furthermore, if resources are tight, you might want to
|
|
cache the shape plan that HarfBuzz builds for your buffer and
|
|
font, so that you do not have to rebuild it for every shaping call.
|
|
</para>
|
|
<para>
|
|
You can create a cacheable shape plan with
|
|
<function>hb_shape_plan_create_cached(face, props,
|
|
user_features, num_user_features, shaper_list)</function>, where
|
|
<parameter>face</parameter> is a face object (not a font object,
|
|
notably), <parameter>props</parameter> is an
|
|
<type>hb_segment_properties_t</type>,
|
|
<parameter>user_features</parameter> is an array of
|
|
<type>hb_feature_t</type>s (with length
|
|
<parameter>num_user_features</parameter>), and
|
|
<parameter>shaper_list</parameter> is a list of shapers to try.
|
|
</para>
|
|
<para>
|
|
Shape plans are objects in HarfBuzz, so there are
|
|
reference-counting functions and user-data attachment functions
|
|
you can
|
|
use. <function>hb_shape_plan_reference(shape_plan)</function>
|
|
increases the reference count on a shape plan, while
|
|
<function>hb_shape_plan_destroy(shape_plan)</function> decreases
|
|
the reference count, destroying the shape plan when the last
|
|
reference is dropped.
|
|
</para>
|
|
<para>
|
|
You can attach user data to a shaper (with a key) using the
|
|
<function>hb_shape_plan_set_user_data(shape_plan,key,data,destroy,replace)</function>
|
|
function, optionally supplying a <function>destroy</function>
|
|
callback to use. You can then fetch the user data attached to a
|
|
shape plan with
|
|
<function>hb_shape_plan_get_user_data(shape_plan, key)</function>.
|
|
</para>
|
|
</section>
|
|
|
|
</chapter>
|