HarfBuzz API design

View: New views
9 Messages — Rating Filter:   Alert me  

HarfBuzz API design

by Behdad Esfahbod-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

[Warning: long email ahead]

Hi all,

With the rewritten HarfBuzz OpenType Layout engine merged in pango master now,
I've been working on the public API for a few weeks.  I have it mostly
designed, though there are a few open questions still and I appreciate any
feedback.  The current code can be browsed here:

   http://git.gnome.org/cgit/pango/tree/pango/opentype

I will add a separate configure.ac to that directory in the coming days such
that it can be built as a standalone library.  In a couple of weeks I may even
move it back out to its own git repo and use git magic to pull it in pango
until we start using it as a shared library (expect end of year).


When designing HarfBuzz API my reference has been cairo.  That is, usability
has been the top priority.  Other than that, hiding technical details while
still being powerful enough to implement advanced features internally are
other goals of the API.

In this mail I'll only discuss the backend-agnostic API, which is what I
expect most users will use.  This is what will be available by including
"hb.h".  For example, OpenType-specific APIs will be included in "hb-ot.h"
only.  That includes querying list of supported OpenType scripts, language
systems, features, etc.

Finally, the other strict goal of the API is to be fully thread-safe.  That
means, I had to bit the bullet and add refcounting API already.  Object
lifecycle API is like cairo's, that is, each object has: _create(),
_reference(), _destory(), and _get_reference_count().  At some point we may
want to add _[gs]et_user_data() also which is useful for language bindings.

Error handling is also designed somewhat like cairo's, that is, objects keep
track of failure internally (including malloc failures), but unlike cairo,
there's no direct way to query objects for errors.  HarfBuzz simply does its
best to get you the result you wanted.  In case of errors, the output may be
wrong, but there's nothing you can do to improve it.  There's not much point
in reporting that state anyway.  So, no error handling in the API.

Before jumping into the API, lemme introdce a memory management construct I
added first:


Blobs
=====

hb_blob_t is a refcounted container for raw data and is introduced to make
memory management between HarfBuzz and the user easy and flexible.  Blobs can
be created by:

typedef enum {
   HB_MEMORY_MODE_DUPLICATE,
   HB_MEMORY_MODE_READONLY,
   HB_MEMORY_MODE_WRITEABLE,
   HB_MEMORY_MODE_READONLY_NEVER_DUPLICATE,
   HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITEABLE,
} hb_memory_mode_t;

typedef struct _hb_blob_t hb_blob_t;

hb_blob_t *
hb_blob_create (const char        *data,
                 unsigned int       length,
                 hb_memory_mode_t   mode,
                 hb_destroy_func_t  destroy,
                 void              *user_data);

The various mode parameters mean:

   DUPLICATE: copy data right away and own it.

   READONLY: the data passed in can be kept for later use, but should not be
modified.  If modification is needed, the blob will duplicate the data lazily.

   WRITEABLE: data is writeable, use it freely.

   READONLY_NEVER_DUPLICATE: data is readonly and should never be duplicated.
  This disables operations needing write access to data.

   READONLY_MAY_MAKE_WRITEABLE: data is readonly but may be made writeable
using mprotect() or equivalent win32 calls.  It's up to the user to make sure
calling mprotect() or system-specific equivalents on the data is safe.  In
practice, that's never an issue on Linux and (according to Tor) on win32.


One can also create a sub-blob of a blob:

hb_blob_t *
hb_blob_create_sub_blob (hb_blob_t    *parent,
                          unsigned int  offset,
                          unsigned int  length);

Blob's data can be used by locking it:

const char *
hb_blob_lock (hb_blob_t *blob);


One can query whether data is writeable:

hb_bool_t
hb_blob_is_writeable (hb_blob_t *blob);

Can request it to be writeable inplace:

hb_bool_t
hb_blob_try_writeable_inplace (hb_blob_t *blob);

Or can request making data writeable, making a copy if need be:

hb_bool_t
hb_blob_try_writeable (hb_blob_t *blob);

For the latter the blob must not be locked.  The lock is recursive.  The blob
internal stuff is protected using a mutex and hence the structure is threadsafe.

The main use of the blob is to provide font data or table data to HarfBuzz.
More about that later.



Text API
========

Perhaps the biggest difference between the API of the old Qt-based HarfBuzz
shaper API and the new one is that the new API reuses hb-buffer for its
shaping input+output.  So, this is how you will use harfbuzz in three lines:

   - create buffer
   - add text to buffer    ---> buffer contains Unicode text now
   - call hb_shape() on it
   - use output glyphs     ---> buffer contains positioned glyphs now

Within that picture, there are three main objects in HarfBuzz:

   - hb_buffer_t: holds text/glyphs, and is not threadsafe

   - hb_face_t: represents a single SFNT face, fully threadsafe beyond
construction.   Maps to cairo_font_face_t.

   - hb_font_t: represents a face at a certain size with certain hinting
options, fully threadsafe beyond construction.  Maps to cairo_scaled_font_t.


Buffer
======

The buffer's output is two arrays: glyph infos and glyph positions. Eventually
these two items will look like:

typedef struct _hb_glyph_info_t {
   hb_codepoint_t codepoint;
   hb_mask_t      mask;
   uint32_t       cluster;
   uint16_t       component;
   uint16_t       lig_id;
   uint32_t       internal;
} hb_glyph_info_t;

typedef struct _hb_glyph_position_t {
   hb_position_t  x_pos;
   hb_position_t  y_pos;
   hb_position_t  x_advance;
   hb_position_t  y_advance;
   uint32_t       internal;
} hb_glyph_position_t;


One nice thing about using hb-buffer for input is that we can now easily add
UTF-8, UTF-16, and UTF-32 APIs to HarfBuzz by simply implementing:

void
hb_buffer_add_utf8 (hb_buffer_t  *buffer,
                     const char   *text,
                     unsigned int  text_length,
                     unsigned int  item_offset,
                     unsigned int  item_length);

void
hb_buffer_add_utf16 (hb_buffer_t    *buffer,
                      const uint16_t *text,
                      unsigned int    text_length,
                      unsigned int    item_offset,
                      unsigned int    item_length);

void
hb_buffer_add_utf32 (hb_buffer_t    *buffer,
                      const uint32_t *text,
                      unsigned int    text_length,
                      unsigned int    item_offset,
                      unsigned int    item_length);

These add individual Unicode characters to the buffer and set the cluster
values respectively.


Face
====

HarfBuzz is build around the SFNT font format.  A Face simply represents a
SFNT face, although this is all transparent to the user: you can pass junk to
HarfBuzz as font data and it will simply ignore it.  There are two main face
constructors:

hb_face_t *
hb_face_create_for_data (hb_blob_t    *blob,
                          unsigned int  index);

typedef hb_blob_t * (*hb_get_table_func_t)  (hb_tag_t tag, void *user_data);

/* calls destory() when not needing user_data anymore */
hb_face_t *
hb_face_create_for_tables (hb_get_table_func_t  get_table,
                            hb_destroy_func_t    destroy,
                            void                *user_data);

The for_tables() version uses a callback to load SFNT tables, whereas the
for_data() version takes a blob which contains the font file data, plus the
face index for TTC collections.


The face is only responsible for the "complex" part of the shaping right now,
that is, OpenType Layout features (GSUB/GPOS...).  In the future we may also
access cmap directly.  Not implemented right now, but old-style 'kern' table
will also be implemented in the same layer.

The reason for introducing the blob machinery is that the new OpenType Layout
engine and any other table work we'll add use the font data directly, instead
of parsing it into separate data structures.  For that reason, we need to
"sanitize" the font data first.  When sanitizing, instead of pass/fail, upon
finding irregularities (say, an offset that points to out of the table), we
may modify the font data to make it clean-enough to pass to the layout code.
In those cases, we first try to make the blob writeable in place, and if that
fails, to make a writeable dup of it.  That is, copy-on-write easy or the hard
way.  For sane fonts, this means zero per-process memory is consumed.  In the
future, we'll cache sanitize() results in fontconfig such that not every
process has to sanitize() clean fonts.



Font
====

Normally I would have made the font constructor take a hb_face_t (like cairo's
does indeed).  A font is a face at a certain size and with certain hinting /
other options afterall.  However, FreeType's lack of refcounting makes this
really hard.  The reason being:  Pango caches hb_face_t on the FT_Face
instance's generic slot.  Whereas a hb_font_t should be attached to a
PangoFont or PangoFcFont.

As everyone knows, FT_Face is not threadsafe, is not refcounted, and is not
just a face, but also includes sizing information for one font at a time.  For
this reasons, whenever a font wants to access a FT_Face, it needs to "lock"
one.  When you lock it though, you don't necessarily get the same object that
you got the last time.  It may be a totally different object, created for the
same font data, depending on who manages your FT_Face pool (cairo in our
case).  Anyway, for this reason, having hb_font_t have a ref to hb_face_t
makes life hard: one either would have to create/destroy hb_font_t between
FT_Face lock/unlock, or risk having a hb_face_t pointing to memory owned by a
FT_Face that may have been freed since.

For the reasons above I opted for not refing a face from hb_font_t and instead
passing both a face and a font around in the API.  Maybe I should use a
different name (hb_font_scale_t?)  I'd rather keep names short, instead of
cairo style hb_font_face_t and hb_scaled_font_t.

Anyway, a font is created easily:

hb_font_t *
hb_font_create (void);

One then needs to set various parameters on it, and after the last change, it
can be used from multiple threads safely.


Shaping
=======

The main hb_shape() API I have written down right now (just a stub) is:

typedef struct _hb_feature_t {
   const char   *name;
   const char   *value;
   unsigned int  start;
   unsigned int  end;
} hb_feature_t;

void
hb_shape (hb_face_t    *face,
           hb_font_t    *font,
           hb_buffer_t  *buffer,
           hb_feature_t *features,
           unsigned int  num_features);

where features are normally empty, but can be used to pass things like:

   "kern"=>"0"         -------> no kerning
   "ot:aalt"=>"2"      -------> use 2nd OpenType glyph alternative
   "ot:mkmk"=>"0"      -------> never apply 'mkmk' OpenType feature

Perhaps:

   "ot:script"=>"math" ------> Force an OpenType script tag
   "ot:langsys"=>"FAR " -----> Force an OpenType language system

Maybe:

   "ot"=>"0"           ------> Disable OpenType engine (prefer AAT, SIL, etc)

Or perhaps even features marking visual edge of the text, etc.





Discussion
==========


Script and language
===================

Normally the shape() call needs a few more pieces of information.  Namely:
text direction, script, and language.  Note that none of those belong on the
face or font objects.  For text direction, I'm convinced that it should be set
on the buffer, and already have that in place.

For script and language, it's a bit more delicate.  I'm also convinced that
they belong to the buffer.  With script it's fine, but with language it
introduces a small implementation hassle: that I would have to deal with
copying/interning language tags, something I was trying to avoid.  The other
options are:

   - Extra parameters to hb_shape().  I rather not do this.  Keeping details
like this out of the main API and addings setters where appropriate makes the
API cleaner and more extensible.

   - Use the feature dict for them too.  I'm strictly against this one.  The
feature dict is already too highlevel for my taste.

So, comments here is appreciated.



Unicode callbacks
=================

HarfBuzz itself does not include any Unicode character database tables, but
needs access to a few properties, some of them for fallback shaping only.
Currently I have identified the following properties as being useful at some
point:

typedef hb_codepoint_t
(*hb_unicode_get_mirroring_func_t) (hb_codepoint_t unicode);

Needed to implement character-level mirroring.


typedef hb_category_t
(*hb_unicode_get_general_category_func_t) (hb_codepoint_t unicode);

Used for synthesizing GDEF glyph classes when the face doesn't have them.


typedef hb_script_t
(*hb_unicode_get_script_func_t) (hb_codepoint_t unicode);

Not needed unless we also implement script itemization (which we can do
transparently, say, if user passed SCRIPT_COMMON to the shape() function).


typedef unsigned int
(*hb_unicode_get_combining_class_func_t) (hb_codepoint_t unicode);

Useful for all kinds of mark positioning when GPOS is not available.


typedef unsigned int
(*hb_unicode_get_eastasian_width_func_t) (hb_codepoint_t unicode);

Not sure it will be useful in HarfBuzz layer.  I recently needed to use it
correctly set text in vertical direction in Pango.


I've added an object called hb_unicode_funcs_t that holds all these callbacks.
  It can be ref'ed, as well as copied.  There's also a
hb_unicode_funcs_make_immutable() call, useful for libraries who want to give
out references to a hb_unicode_funcs_t object they own but want to make sure
the user doesn't modify the object by mistake.

The hb-glib.h layer then implements:

hb_unicode_funcs_t *
hb_glib_get_unicode_funcs (void);


The question then is where to pass the unicode funcs to the shape() machinery.
  My current design has it on the face:

void
hb_face_set_unicode_funcs (hb_face_t *face,
                            hb_unicode_funcs_t *unicode_funcs);

However, that is quite arbitrary.  There is nothing in the face alone that
requires Unicode functionality.  Moreover, I want to keep the face very
objective.  Say, you should be able to get the hb_face_t from whoever provides
you with one (pango, ...), and use it without worrying about what settings it
has.  The Unicode funcs, while welldefined, can still come from a variety of
sources: glib, Qt, Python's, your own experiments, ...

I started thinking about moving that to the buffer instead.  That's the only
other place that Unicode comes in (add_utf8/...), and the buffer is the only
object that is not shared by HarfBuzz, so user has full control over it.

One may ask why have the callbacks settable to begin with.  We can hardcode
them at build time: if glib is available, use it, otherwise use our own copy
or something.  While I may make it to fallback to whatever has been available
at compile time, I like being able to let user set the callbacks.  At least
until I write one UCD library to rule them all... /me grins

So that's another question I need feedback about.


Font callbacks
==============

These are the font callbacks (font class, font funcs, ...) that I've
prototyped.  Note that both the font, face, and a user_data parameter are
passed to all of them.  Some of these callbacks technically just need a face,
not font, but since many systems implement these functions on actual fonts not
faces, we implement it this way.  Right now one can set the
hb_font_callbacks_t object on the hb-font and set user_data there
(hb_font_set_funcs()).


typedef hb_codepoint_t
(*hb_font_get_glyph_func_t) (hb_font_t *font, hb_face_t
                              *face, const void *user_data,
                              hb_codepoint_t unicode,
                              hb_codepoint_t variant_selector);

This is the cmap callback.  Note the variant_selector: it supports new cmap14
tables.  For older clients, they can ignore that argument and do the mapping.
  We probably will implement support for Unicode cmaps internally, but chain
to this function for missing-glyphs or if no suitable cmap was found.  That
has three advantages:

   - Pango etc can pass whatever code they want for missing glyphs, to use
later to draw hexbox,

   - Pango, through fontconfig, knows how to handle non-Unicode cmaps, so that
will continue to work,

   - For non-SFNT fonts, HarfBuzz should happily sit back and make things work
still, this is how that will work.


typedef hb_bool_t
(*hb_font_get_contour_point_func_t) (hb_font_t *font, hb_face_t *face,
                                      const void *user_data,
                                      hb_codepoint_t glyph,
                                      hb_position_t *x, hb_position_t *y);

Needed for complex GPOS positioning.  Pango never did this before.  Pretty
straightforward, just need to make it clear the space that the positions are
returned in.  I'll discuss that in the next section.


typedef void
(*hb_font_get_glyph_metrics_func_t) (hb_font_t *font, hb_face_t *face, const
                                      void *user_data, hb_codepoint_t glyph,
                                      hb_glyph_metrics_t *metrics);

This one is a bit more tricky.  Technically we just need the advance width.
The rest of the metrics are only used for fallback mark positioning.  So maybe
I should split this in a get_glyph_advance and a full get_glyph_metrics one.
Current HarfBuzz has a single call to get advance width of multiple glyphs.
If that kind of optimization deems necessary in the future, we can add a
callback to take an entire buffer and set the advances.

There are more issues here though:

   1) The metrics struct most probably should be public.  However, in the
future I like to use bearing-deltas to improve positioning.  A transparent
struct doesn't help in those situations.  Not sure what the alternatives are.

   2) It's not exactly clear how to deal with vertical fonts.  One way would
be to assume that if buffer direction is vertical, then the font already knows
that and returns the vertical metrics.  That's not a completely off
assumption, though that may not be how win32 fonts work?


typedef hb_position_t
(*hb_font_get_kerning_func_t) (hb_font_t *font, hb_face_t *face,
                                const void *user_data,
                                hb_codepoint_t first_glyph,
                                hb_codepoint_t second_glyph);

Again, most probably we will read 'kern' table internally anyway, but this can
be used for fallback with non-SFNT fonts.  You can even pass, say, SVG fonts
through HarfBuzz such that the higher level just deals with one API.



Another call that may be useful is a get_font_metrics one.  Again, only useful
in fallback positioning.  In that case, ascent/descent as well as slope come
handy.


Font scale, etc
===============

Currently, based on the old code, the font object has the following setters:

void
hb_font_set_scale (hb_font_t *font,
                    hb_16dot16_t x_scale,
                    hb_16dot16_t y_scale);

void
hb_font_set_ppem (hb_font_t *font,
                   unsigned int x_ppem,
                   unsigned int y_ppem);

The ppem API is well-defined: that's the ppem to use for hinting and
device-dependent positioning.  Old HarfBuzz also had a "device-independent"
setting, which essentially turned hinting off.  I've removed that setting in
favor of passing zero as ppem.  That allows hinting in one direction and not
the other.  Unlike old HarfBuzz, we will do metrics hinting ourselves.

The set_scale() API is modeled after FreeType, but otherwise very awkward to
use.  There are four different spaces relevant in HarfBuzz:

   - Font design space: typically a 1024x1024 box per glyph.  The GPOS and
'kern' values are in this space.  This maps to the EM space by a per-face
value called upem (units per em).

   - EM space: 1em = 1em.

   - Device space: actual pixels.  The ppem maps EM space to this space, if
such a mapping exists.

   - User space: the user expects glyph positions in this space.  This can be
different from device space (it is, for example if you use cairo_scale()).
Current/old pango ignore this distinction and hence kerning doesn't scale
correctly [1].


Now, what the hb_font_set_scale() call accepts right now is a 16.16 pair of
scales mapping from font design space to device space.  I'm not sure, but
getting that number from font systems other than FreeType may actually be
quite hard.  The problem is, upem is an implementation detail of the face, and
the user shouldn't care about it.

So my proposal is to separate upem and make it a face property.  In fact, we
can read upem from OS/2 SFNT table and assume a 1024 upem for non-SFNT fonts
(that's what Type1 did IIRC).  In fact, we wouldn't directly use upem for
non-SFNT fonts right now.

Then the scale would simply need to map EM space to device space.  But notice
how that's the same as the ppem.  But then again, we really just care about
user space for positioning (device space comes in only when hinting).  So,
set_scale should be changed to accept em-to-user-space scale.  Not
surprisingly, that's the same as the font size in the user-space.

Another problem I would need to solve here is, cairo allows a full matrix for
device-to-user space.  That is, glyphs can be rotated in-place for example.
That's what we use to implement vertical text.  I'm inclined to also adding a
full-matrix setter.  The behavior would be:

   - If (1,0) maps to (x,y) with nonzero y, then many kinds of positioning
should be completely disabled,

   - Somehow figure out what to do with vertical.  Not sure right now, but it
should be ok detecting if the font is 90-degree rotated and compensate for that.


In that model however, I wonder how easy/hard would it be for callbacks to
provide requested values (contour point, glyph metrics, etc) in the user
space.  For cairo/pango I know that's actually the easiest thing to do,
anything else would need conversion, but I'm not sure about other systems.  An
alternative would be to let the callbacks choose which space the returned
value is in, so we can map appropriately.



I guess that's it for now.  Let discussion begin.  Thanks for reading!

behdad

[1] http://bugzilla.gnome.org/show_bug.cgi?id=341481
_______________________________________________
gtk-i18n-list mailing list
gtk-i18n-list@...
http://mail.gnome.org/mailman/listinfo/gtk-i18n-list

Parent Message unknown Re: HarfBuzz API design

by Behdad Esfahbod-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 08/19/2009 02:45 PM, Carl Worth wrote:
> Excerpts from Behdad Esfahbod's message of Tue Aug 18 16:23:50 -0700 2009:
>> [Warning: long email ahead]
>
> My reply might bounce to unsubscribed-from lists. Feel free to forward
> if you think there's anything important in here for others to read.

Thanks Carl.

>> _reference(), _destory(), and _get_reference_count().  At some point we may
>> want to add _[gs]et_user_data() also which is useful for language bindings.
>
> That should be "destroy" not "destory", of course. I normally wouldn't
> point out a typo in an email message, but I did see the same error in
> a comment that might be copied from actual code:

Ouch.  Fixed the typo.


>> /* calls destory() when not needing user_data anymore */
>> hb_face_t *
>> hb_face_create_for_tables (hb_get_table_func_t  get_table,
>>                              hb_destroy_func_t    destroy,
>>                              void                *user_data);
>
> So don't forget to spell-check your header files. :-)

Yeah, postponed that to when I add actual docs.

Humm, seems like s/writeable/writable/g is also needed.  Stupid me.  The geek
inside me prefers writeable though.


>> typedef enum {
>>     HB_MEMORY_MODE_DUPLICATE,
>>     HB_MEMORY_MODE_READONLY,
>>     HB_MEMORY_MODE_WRITEABLE,
>>     HB_MEMORY_MODE_READONLY_NEVER_DUPLICATE,
>>     HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITEABLE,
>> } hb_memory_mode_t;
> ...
>>     DUPLICATE: copy data right away and own it.
>>
>>     READONLY: the data passed in can be kept for later use, but should not be
>> modified.  If modification is needed, the blob will duplicate the data lazily.
>>
>>     WRITEABLE: data is writeable, use it freely.
>>
>>     READONLY_NEVER_DUPLICATE: data is readonly and should never be duplicated.
>>    This disables operations needing write access to data.
>>
>>     READONLY_MAY_MAKE_WRITEABLE: data is readonly but may be made writeable
>> using mprotect() or equivalent win32 calls.  It's up to the user to make sure
>> calling mprotect() or system-specific equivalents on the data is safe.  In
>> practice, that's never an issue on Linux and (according to Tor) on win32.
>
> I don't think these names are quite right yet.

That's definitely one of the most arbitrary parts of the code, thanks for
catching!  I do think there's a slight confusion though, which if I clarify
(as the docs will eventually will), the values make more sense.  Look at it
this way, the mode parameter describes the characteristics of the input data
only.  It's not a property of the blob itself, and for that reason there is no
getter for it.

Looking at it that way, these values are accurate:

 >>     HB_MEMORY_MODE_DUPLICATE,
 >>     HB_MEMORY_MODE_READONLY,
 >>     HB_MEMORY_MODE_WRITEABLE,
 >>     HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITEABLE,

This one, not so much:

 >>     HB_MEMORY_MODE_READONLY_NEVER_DUPLICATE,

As it also describe a behavior of the blob itself.  That value however is for
very corner case uses (when you're just interested in knowing whether the data
is sane and you want to avoid the cost of copying).


> I think the thing that strikes me first as wrong is that if you create
> a READONLY blob then it's still perfectly valid to write to it, (such
> that you have to have a separate READONLY_NEVER_DUPLICATE for
> "readonly---and I mean it).

More clear in the above context?

I also did some Hoffman encoding here.  I initially had READONLY and
READONLY_MAY_DUPLICATE, but figured that READONLY_MAY_DUPLICATE is what I want
most users to use, and one should think twice before using READONLY.  So I
gave the short name to the common one and made the corner case use a longer name.


> One difficulty is that you're capturing separate notions here,
> (whether the data buffer is writeable vs. whether the created blob
> should be writeable).

Yes and no indeed.


> And there's some missing orthogonality too. For example, if I have
> data that I don't want the blob to reference, (hence DUPLICATE), might
> I not still want to create a blob in which all operations requiring
> write access are disabled? That combination is not possible to express
> from what I can see.

Initially I had that design in mind, with separate writeable.  But I figured
the extra control is not needed, no.


> So, here's pseudocode trying to capture the decision-tree for choosing
> one of the modes currently:
>
> if (the data cannot be referenced by the blob)
> DUPLICATE
> else if (the data buffer can be written to)
> if (writing requires calling mprotect)
> READONLY_MAY_MAKE_WRITEABLE
> else
> WRITEABLE
> else if (the resulting blob should be readonly)
> READONLY_NEVER_DUPLICATE
> else
> READONLY;

Again, lets forget about READONLY_NEVER_DUPLICATE, my mental model for this was:

if (I don't own the data)
   DUPLICATE;
else if (I malloced it)
   WRITEABLE;
else if (it's mmapped)
   READONLY_MAY_MAKE_WRITEABLE;
else /* ie.  it's shared with someone else, I don't have perms to modify it */
   READONLY;


> So perhaps what's actually desired here is a set of flags to express
> each of those conditions independently? I don't have a good proposal
> for what those flag names might be.

I found the flag approach even more confusing as one would then think about
(and the docs have to explain) all different kind of combinations.


> Although, having a name for the entire behavior is helpful, (rather
> than just a conjunction of flags). So maybe we just need simpler names
> to capture the behaviors of interest:
>
> COPY: Data is immediately copied by the blob. Resulting blob supports
> write operations. [New name for DUPLICATE]
>
> COPY_ON_WRITE: Data is referenced by the blob. Resulting blob support
> write operations which are implemented by lazily copying the
> data, (the original data is never modified). [New name for
> READONLY]
>
> READ_WRITE: Data is referenced by the blob. Resulting blob supports
> write operations which will directly modify original data.
> [New name for WRITEABLE. Of the new names I'm proposing, this
> is the one I like the least.]
>
> READONLY: Data is referenced by the blob. Resulting blob does not
> support write operations. [New name for READONLY_NEVER_DUPLICATE]
>
> MPROTECT: Data is referenced by the blob. Resulting blob supports
> write operations which are implemented by first calling
> mprotect or equivalent. [New name for
> READONLY_MAY_MAKE_WRITEABLE. Obviously this isn't an ideal
> name since it references an operating-system-specific
> feature. Suggestions welcome.]

I wanted to keep MPROTECT out of the name.  Again, please think about it again
without the mode determining whether the blob will be writeable or not.  Just
as describing the access mode of the input data only.  Do you think the
current enum makes sense that way?


> I don't have much to say about the actual font-related parts of the
> proposal. But I do think it's perhaps a bad idea to reuse the same
> buffer for text input and glyph output. Is memory that tight? Might a
> user not want to be able to query the text back out even after
> shaping?

Memory is not tight, no.  Note that this is only about what the input/output
to the hb_shape() function will be.  Users already keep the original text in
some location anyway, and the cluster mapping is what's used to map the glyphs
back.  All I'm doing here essentially is to let the user populate a hb-buffer
and pass that to hb_shape(), instead of hb_shape() doing it.

The benefit of course is having one hb_shape() API, instead of
hb_shape_utf8(), hb_shape_utf16(), and hb_shape_utf32().

Makes more sense?

Thanks again,
behdad



> -Carl
_______________________________________________
gtk-i18n-list mailing list
gtk-i18n-list@...
http://mail.gnome.org/mailman/listinfo/gtk-i18n-list

Parent Message unknown Re: [HarfBuzz] HarfBuzz API design

by Behdad Esfahbod-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 08/19/2009 03:16 PM, Ed Trager wrote:

> Hi, everyone,
>
>>
>> I don't have much to say about the actual font-related parts of the
>> proposal. But I do think it's perhaps a bad idea to reuse the same
>> buffer for text input and glyph output. Is memory that tight? Might a
>> user not want to be able to query the text back out even after
>> shaping?
>>
>
> I was wondering about this too : When selecting or highlighting shaped
> text (to copy/paste/do something else to it) surely a shaper like
> HarfBuzz needs to provide convenient access / mapping back to the
> underlying Unicode text ?

A shaper just converts text to glyphs, giving back the cluster mapping too.
The rest is done by the client.  The client sure has the text somewhere
already.  there's no point in keeping a separate copy by HarfBuzz.

behdad

> - Ed
_______________________________________________
gtk-i18n-list mailing list
gtk-i18n-list@...
http://mail.gnome.org/mailman/listinfo/gtk-i18n-list

Parent Message unknown Re: [HarfBuzz] HarfBuzz API design

by Behdad Esfahbod-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 08/19/2009 02:57 AM, Martin Hosken wrote:
> Dear Behdad,
>
> I feel that this is the core of the API since it specifies what inputs and outputs harfbuzz works with (particularly outputs).

Hi Martin,

Yes, hb_shape() and the hb_glyph_info_t are essentially the core of the API.


>> typedef struct _hb_glyph_info_t {
>>     hb_codepoint_t codepoint;
>>     hb_mask_t      mask;
>>     uint32_t       cluster;
>>     uint16_t       component;
>>     uint16_t       lig_id;
>>     uint32_t       internal;
>> } hb_glyph_info_t;
>
> I may have misinterpretted but mask, lig_id and probably component, feel to be OT specific in that a consumer of the output is unlikely to ever need them.

Yes and no.  Mask is used to mark which user features should be applied to
which glyphs, and I think at least AAT can/will use that too.  For lig_id and
component, they are not inherently OT-specific.  They are implementation
details of how HarfBuzz implements the OT spec.  We may decide to hide them
too, and just have another internal member.  Individual shapers can use the
internal members as they wish then.  That's actually a good idea.  Unless I
find a use for the client having access to those values, it better be hidden.
  I'll make that change now.

I'm thinking about adding a some other fields here though (without changing
the size).  Things like justification points, etc.


> The disadvantage I see with having a single buffer that changes its contents from chars to glyphs is that then you lose the association map between underlying chars and glyphs. I suppose it can be recreated using the component information, but it's going to be problematic when it comes to cursor hit testing.

The decision is only relevant inside the hb_shape() call.  The user has the
original text still.  Please see the last part of my reply to Carl Worth.


>> For script and language, it's a bit more delicate.  I'm also convinced that
>> they belong to the buffer.  With script it's fine, but with language it
>> introduces a small implementation hassle: that I would have to deal with
>> copying/interning language tags, something I was trying to avoid.  The other
>> options are:
>>
>>     - Extra parameters to hb_shape().  I rather not do this.  Keeping details
>> like this out of the main API and addings setters where appropriate makes the
>> API cleaner and more extensible.
>>
>>     - Use the feature dict for them too.  I'm strictly against this one.  The
>> feature dict is already too highlevel for my taste.
>
> Why do you say the feature dict is too high level? It seems just the right place, to me. Or it could be stored in the buffer, since it is buffer specific.

It's just not as efficient and easy to use as I like.  But it's just fine for
user features, yes.


> One question: is a buffer representing a single run for which the language doesn't change or is it potentially multiple runs that are yet to be segmented?

The way I'd recommend using it is for one run.  The API already limits it to
one font anyway.  Doesn't mean we can't add API to do multiple runs in the
future though.

behdad

> Yours,
> Martin
>
_______________________________________________
gtk-i18n-list mailing list
gtk-i18n-list@...
http://mail.gnome.org/mailman/listinfo/gtk-i18n-list

Parent Message unknown Re: [HarfBuzz] HarfBuzz API design

by Behdad Esfahbod-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 08/19/2009 04:04 PM, Adam Twardoch wrote:
> But surely there needs to be a way to trace which glyphs relate to which
> character codes, which can of course be an n:m relationship. When the
> user moves around the rendered text with a text cursor and decides to
> delete some glyphs, select them or change them, the client needs to make
> sure to sync the operations that it does in its own text buffer with the
> rendering -- especially regarding the size of the selection and/or
> position of the cursor?

That mapping is kept in the "cluster" member of the glyph info.  It's the
Uniscribed-like mapping.  The way it works is:

   - User sets any value they want for the cluster member of the input buffer
for each input character,

   - hb_shape() keeps those cluster values as the characters are converted to
glyphs.  Whenever multiple glyphs are merged to produce one or more glyphs,
the cluster value of the first glyph is copied to all the output glyphs.

The user then can query the cluster value of the output glyphs to see which
glyphs correspond to what part of the text.  This is how Pango tracks all
those things for example.

Now, the hb_buffer_add_utfX() APIs use the offset input input the text array
as the cluster value, but if different values are desirable, user can change
them before passing to hb_shape().


> Does HarfBuzz provide access to the cursor position information that can
> optionally be stored in the GDEF table?

Right now there is OpenType-specific API for that in hb-ot-layout.h, yes:

/* Ligature caret positions */
hb_bool_t
hb_ot_layout_get_lig_carets (hb_face_t      *face,
                              hb_font_t      *font,
                              hb_codepoint_t  glyph,
                              unsigned int   *caret_count /* IN/OUT */,
                              int            *caret_array /* OUT */);

I'm waiting to see what other systems provide before adding a backend-agnostic
API for that.


behdad


> Adam
>
>
> Behdad Esfahbod wrote:
>> On 08/19/2009 03:16 PM, Ed Trager wrote:
>>> Hi, everyone,
>>>
>>>> I don't have much to say about the actual font-related parts of the
>>>> proposal. But I do think it's perhaps a bad idea to reuse the same
>>>> buffer for text input and glyph output. Is memory that tight? Might a
>>>> user not want to be able to query the text back out even after
>>>> shaping?
>>>>
>>> I was wondering about this too : When selecting or highlighting shaped
>>> text (to copy/paste/do something else to it) surely a shaper like
>>> HarfBuzz needs to provide convenient access / mapping back to the
>>> underlying Unicode text ?
>>
>> A shaper just converts text to glyphs, giving back the cluster mapping too.
>> The rest is done by the client.  The client sure has the text somewhere
>> already.  there's no point in keeping a separate copy by HarfBuzz.
>>
>> behdad
>>
>>> - Ed
>> _______________________________________________
>> HarfBuzz mailing list
>> HarfBuzz@...
>> http://lists.freedesktop.org/mailman/listinfo/harfbuzz
>>
>>
>
>
_______________________________________________
gtk-i18n-list mailing list
gtk-i18n-list@...
http://mail.gnome.org/mailman/listinfo/gtk-i18n-list

Parent Message unknown Re: HarfBuzz API design

by Behdad Esfahbod-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 08/19/2009 04:30 PM, Carl Worth wrote:

>> I wanted to keep MPROTECT out of the name.  Again, please think about it again
>> without the mode determining whether the blob will be writeable or not.  Just
>> as describing the access mode of the input data only.  Do you think the
>> current enum makes sense that way?
>
> Well, if the READONLY_NEVER_DUPLICATE case didn't exist, then it all
> makes perfect sense, yes.

Ok, I remove that entry!  There are other ways to inhibit duplication (locking
the blob).


> But with that value present, there *is* at least one mode (if rarely
> used) where the resulting blob has write operations disabled. And it
> still seems a little odd that code with:
>
>        hb_blob_create (... HB_MEMORY_MODEL_READONLY ...);
>
> doesn't create such a blob.

Note, it's note "MEMORY_MODEL", it's "MEMORY_MODE".


> No matter. With improved documentation it's likely all just
> fine. You've clearly thought a lot about the names already, and that's
> really all that I can ask. :-)

The WRITEABLE vs WRITABLE bothers me now.  So Maybe I change that to
READWRITE.  That still leaves "is_writable" and family in the API though.

behdad


> -Carl
_______________________________________________
gtk-i18n-list mailing list
gtk-i18n-list@...
http://mail.gnome.org/mailman/listinfo/gtk-i18n-list

Parent Message unknown Re: [HarfBuzz] HarfBuzz API design

by Behdad Esfahbod-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 08/19/2009 04:15 PM, Adam Twardoch wrote:
> I think it would be useful to have a helper function akin to Microsoft's
> ScriptItemizeOpenType()* that breaks a Unicode string into individually
> shapeable items (runs) and provides an array of feature tags for each
> shapeable item for OpenType processing.

Thanks Adam.  So far the focus has been to unify the shaping logic (most
important for Indic).  Itemization, while pretty well defined, is something
everyone does slightly differently.  It requires:

   - Applying Unicode Bidi Algorithm,

   - Script tagging heuristic for Script=Common characters

   - Language tagging heuristic

   - Font assignment

Except for the first item which is well-defined by Unicode, the other steps
are less well-defined and different usecases require slightly different
solutions.  For example, web browsers have very strict font assignment rules
that follow the CSS spec.  Other applications, less so.  It would be harder to
justify using a unified itemizer.  At least initially.  But yes, that's one of
the logical next steps.

behdad


> * http://msdn.microsoft.com/en-us/library/dd368557%28VS.85%29.aspx
>
> Adam
>
> Behdad Esfahbod wrote:
>> On 08/19/2009 02:57 AM, Martin Hosken wrote:
>>> Dear Behdad,
>>>
>>> I feel that this is the core of the API since it specifies what inputs and outputs harfbuzz works with (particularly outputs).
>>
>> Hi Martin,
>>
>> Yes, hb_shape() and the hb_glyph_info_t are essentially the core of the API.
>>
>>
>>>> typedef struct _hb_glyph_info_t {
>>>>      hb_codepoint_t codepoint;
>>>>      hb_mask_t      mask;
>>>>      uint32_t       cluster;
>>>>      uint16_t       component;
>>>>      uint16_t       lig_id;
>>>>      uint32_t       internal;
>>>> } hb_glyph_info_t;
>>> I may have misinterpretted but mask, lig_id and probably component, feel to be OT specific in that a consumer of the output is unlikely to ever need them.
>>
>> Yes and no.  Mask is used to mark which user features should be applied to
>> which glyphs, and I think at least AAT can/will use that too.  For lig_id and
>> component, they are not inherently OT-specific.  They are implementation
>> details of how HarfBuzz implements the OT spec.  We may decide to hide them
>> too, and just have another internal member.  Individual shapers can use the
>> internal members as they wish then.  That's actually a good idea.  Unless I
>> find a use for the client having access to those values, it better be hidden.
>>    I'll make that change now.
>>
>> I'm thinking about adding a some other fields here though (without changing
>> the size).  Things like justification points, etc.
>>
>>
>>> The disadvantage I see with having a single buffer that changes its contents from chars to glyphs is that then you lose the association map between underlying chars and glyphs. I suppose it can be recreated using the component information, but it's going to be problematic when it comes to cursor hit testing.
>>
>> The decision is only relevant inside the hb_shape() call.  The user has the
>> original text still.  Please see the last part of my reply to Carl Worth.
>>
>>
>>>> For script and language, it's a bit more delicate.  I'm also convinced that
>>>> they belong to the buffer.  With script it's fine, but with language it
>>>> introduces a small implementation hassle: that I would have to deal with
>>>> copying/interning language tags, something I was trying to avoid.  The other
>>>> options are:
>>>>
>>>>      - Extra parameters to hb_shape().  I rather not do this.  Keeping details
>>>> like this out of the main API and addings setters where appropriate makes the
>>>> API cleaner and more extensible.
>>>>
>>>>      - Use the feature dict for them too.  I'm strictly against this one.  The
>>>> feature dict is already too highlevel for my taste.
>>> Why do you say the feature dict is too high level? It seems just the right place, to me. Or it could be stored in the buffer, since it is buffer specific.
>>
>> It's just not as efficient and easy to use as I like.  But it's just fine for
>> user features, yes.
>>
>>
>>> One question: is a buffer representing a single run for which the language doesn't change or is it potentially multiple runs that are yet to be segmented?
>>
>> The way I'd recommend using it is for one run.  The API already limits it to
>> one font anyway.  Doesn't mean we can't add API to do multiple runs in the
>> future though.
>>
>> behdad
>>
>>> Yours,
>>> Martin
>>>
>> _______________________________________________
>> HarfBuzz mailing list
>> HarfBuzz@...
>> http://lists.freedesktop.org/mailman/listinfo/harfbuzz
>>
>>
>
>
_______________________________________________
gtk-i18n-list mailing list
gtk-i18n-list@...
http://mail.gnome.org/mailman/listinfo/gtk-i18n-list

Re: HarfBuzz API design

by msevior-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Hi Behdad,
                Thanks very much for this.

One issue that we've talked about is highlighting selected text. Is
there some way to change the font colour/background and draw between
clusters after shaping? The draw should keep the shaped characters at
the offsets they would have had the draw been over the complete set of
text.

We're implementing a work around that involves drawing selected text
twice in AbiWord.

I didn't see selections mentioned in your API but they're an important
part of interactive text manipulation. It would be great if they could
be addressed in a high level API.

Cheers

Martin


On Wed, Aug 19, 2009 at 9:23 AM, Behdad Esfahbod<behdad@...> wrote:
> [Warning: long email ahead]
>

<snip>
_______________________________________________
gtk-i18n-list mailing list
gtk-i18n-list@...
http://mail.gnome.org/mailman/listinfo/gtk-i18n-list

Re: HarfBuzz API design

by Behdad Esfahbod-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 08/19/2009 08:42 PM, Martin Sevior wrote:
> Hi Behdad,
>                  Thanks very much for this.

Hi Martin,

> One issue that we've talked about is highlighting selected text. Is
> there some way to change the font colour/background and draw between
> clusters after shaping? The draw should keep the shaped characters at
> the offsets they would have had the draw been over the complete set of
> text.

The right way to do this indeed is to not reshape the selected text.  The
cluster mapping should give you the clusters you need to highlight.  The main
problem however is what to do when only part of a cluster is selected.  For
now, what one can do (and indeed what GTK+/Pango do) is to distribute the
cluster width evenly among the characters comprising it.  OpenType does
provide some further information about how wide individual components of a
ligature are.  That information is exposed by HarfBuzz using the
OpenType-specific API.  As I was saying in reply to Adam, I'm waiting to see
how other systems handle this before adding a generic API for that.

> We're implementing a work around that involves drawing selected text
> twice in AbiWord.

That's actually not that bad.

> I didn't see selections mentioned in your API but they're an important
> part of interactive text manipulation. It would be great if they could
> be addressed in a high level API.

Yes, that's something I do have in mind.  However, the shaper is way lower
than when actual selection and rendering happens, so it's natural to expect
the higher level to have to walk the clusters manually.  At least for now.

Cheers,
behdad

> Cheers
>
> Martin
_______________________________________________
gtk-i18n-list mailing list
gtk-i18n-list@...
http://mail.gnome.org/mailman/listinfo/gtk-i18n-list