freetype-commit
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[freetype2-demos] wl/freetype-demos-support-ot-svg-glyphs-new da09628 1/


From: Werner Lemberg
Subject: [freetype2-demos] wl/freetype-demos-support-ot-svg-glyphs-new da09628 1/5: Prototype SVG support
Date: Mon, 3 Jan 2022 13:30:21 -0500 (EST)

branch: wl/freetype-demos-support-ot-svg-glyphs-new
commit da09628c1b17c5e0fabee471d0fde20021105969
Author: Moazin Khatti <moazinkhatri@gmail.com>
Commit: Werner Lemberg <wl@gnu.org>

    Prototype SVG support
---
 Makefile        |  10 ++
 src/ftcommon.c  |  12 +-
 src/rsvg_port.c | 355 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/rsvg_port.h |  54 +++++++++
 4 files changed, 430 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 2c9c051..9a1bae1 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,7 @@ TOP_DIR   ?= ../freetype
 TOP_DIR_2 ?= .
 OBJ_DIR   ?= $(TOP_DIR)/objs
 
+LIBRSVG_INCLUDES=$(shell pkg-config librsvg-2.0 --cflags)
 
 ######################################################################
 #
@@ -168,6 +169,7 @@ else
     LINK_CMD    = $(LIBTOOL) --mode=link $(CC) \
                   $(subst /,$(COMPILER_SEP),$(LDFLAGS))
     LINK_LIBS   = $(subst /,$(COMPILER_SEP),$(FTLIB) $(EFENCE)) 
$(LIB_CLOCK_GETTIME)
+    LINK_LIBS += $(shell pkg-config --libs librsvg-2.0 cairo)
   else
     LINK_CMD = $(CC) $(subst /,$(COMPILER_SEP),$(LDFLAGS))
     ifeq ($(PLATFORM),unixdev)
@@ -349,12 +351,19 @@ else
   $(OBJ_DIR_2)/mlgetopt.$(SO): $(SRC_DIR)/mlgetopt.c
   COMMON_OBJ := $(OBJ_DIR_2)/common.$(SO) \
                 $(OBJ_DIR_2)/strbuf.$(SO) \
+                $(OBJ_DIR_2)/rsvg_port.$(SO) \
                 $(OBJ_DIR_2)/output.$(SO) \
                 $(OBJ_DIR_2)/md5.$(SO) \
                 $(OBJ_DIR_2)/mlgetopt.$(SO)
 
   $(OBJ_DIR_2)/ftcommon.$(SO): $(SRC_DIR)/ftcommon.c $(SRC_DIR)/ftcommon.h
          $(COMPILE) $(GRAPH_INCLUDES:%=$I%) \
+                                                                               
 $(LIBRSVG_INCLUDES) \
+                     $T$(subst /,$(COMPILER_SEP),$@ $<)
+
+  $(OBJ_DIR_2)/rsvg_port.$(SO): $(SRC_DIR)/rsvg_port.c $(SRC_DIR)/rsvg_port.h
+         $(COMPILE) $(GRAPH_INCLUDES:%=$I%) \
+                                                                               
 $(LIBRSVG_INCLUDES) \
                      $T$(subst /,$(COMPILER_SEP),$@ $<)
 
   $(OBJ_DIR_2)/ftpngout.$(SO): $(SRC_DIR)/ftpngout.c $(SRC_DIR)/ftcommon.h
@@ -417,6 +426,7 @@ else
                              $(SRC_DIR)/ftcommon.h \
                              $(GRAPH_LIB)
          $(COMPILE) $(GRAPH_INCLUDES:%=$I%) \
+                     $(LIBRSVG_INCLUDES) \
                      $T$(subst /,$(COMPILER_SEP),$@ $<)
 
   $(OBJ_DIR_2)/ftsdf.$(SO): $(SRC_DIR)/ftsdf.c \
diff --git a/src/ftcommon.c b/src/ftcommon.c
index bea2d2e..23068b0 100644
--- a/src/ftcommon.c
+++ b/src/ftcommon.c
@@ -27,6 +27,9 @@
 
 #include FT_BITMAP_H
 #include FT_FONT_FORMATS_H
+#include FT_OTSVG_H
+
+#include <rsvg_port.h>
 
   /* error messages */
 #undef FTERRORS_H_
@@ -348,6 +351,13 @@
     if ( error )
       PanicZ( "could not initialize FreeType" );
 
+    SVG_RendererHooks  hooks;
+    hooks.init_svg        = (SVG_Lib_Init_Func)rsvg_port_init;
+    hooks.free_svg        = (SVG_Lib_Free_Func)rsvg_port_free;
+    hooks.render_svg      = (SVG_Lib_Render_Func)rsvg_port_render;
+    hooks.preset_slot     = (SVG_Lib_Preset_Slot_Func)rsvg_port_preset_slot;
+    FT_Property_Set( handle->library, "ot-svg", "svg_hooks", &hooks );
+
     error = FTC_Manager_New( handle->library, 0, 0, 0,
                              my_face_requester, 0, &handle->cache_manager );
     if ( error )
@@ -1331,7 +1341,7 @@
 
     error = FT_Err_Ok;
 
-    if ( glyf->format == FT_GLYPH_FORMAT_OUTLINE )
+    if ( glyf->format == FT_GLYPH_FORMAT_OUTLINE || glyf->format == 
FT_GLYPH_FORMAT_SVG )
     {
       FT_Render_Mode  render_mode;
 
diff --git a/src/rsvg_port.c b/src/rsvg_port.c
new file mode 100644
index 0000000..bb2aa7f
--- /dev/null
+++ b/src/rsvg_port.c
@@ -0,0 +1,355 @@
+/****************************************************************************
+ *
+ * rsvg_port.h
+ *
+ *   Librsvg based hook functions for OT-SVG rendering in FreeType.
+ *   (implementation)
+ *
+ * Copyright (C) 1996-2021 by
+ * David Turner, Robert Wilhelm, Werner Lemberg and Moazin Khatti.
+ *
+ * This file is part of the FreeType project, and may only be used,
+ * modified, and distributed under the terms of the FreeType project
+ * license, LICENSE.TXT.  By continuing to use, modify, or distribute
+ * this file you indicate that you have read the license and
+ * understand and accept it fully.
+ *
+ */
+
+#include <cairo.h>
+#include <librsvg/rsvg.h>
+#include <rsvg_port.h>
+#include <ft2build.h>
+#include <stdlib.h>
+#include <math.h>
+#include <freetype/freetype.h>
+#include <freetype/ftbbox.h>
+#include <freetype/otsvg.h>
+
+
+  /**
+   * The init hook is called when the first OT-SVG glyph is rendered.
+   * All we do is allocate an internal state structure and set the pointer
+   * in `library->svg_renderer_state`. This state structure becomes very
+   * useful to cache some of the results obtained by one hook function that
+   * the other one might use.
+   */
+  FT_Error
+  rsvg_port_init( FT_Pointer  *state )
+  {
+    /* allocate the memory upon initialization */
+    *state = malloc( sizeof( Rsvg_Port_StateRec ) );
+    return FT_Err_Ok;
+  }
+
+
+  /**
+   * Freeing up the state structure.
+   */
+  void
+  rsvg_port_free( FT_Pointer  state)
+  {
+    /* free the memory of the state structure */
+    free( state );
+  }
+
+  /**
+   * The render hook that's called to render. The job of this hook is to
+   * simply render the glyph in the buffer that has been allocated on
+   * the FreeType side. Here we simply use the recording surface by playing
+   * it back against the surface.
+   */
+
+  FT_Error
+  rsvg_port_render( FT_GlyphSlot slot, FT_Pointer  _state )
+  {
+    FT_Error         error = FT_Err_Ok;
+
+    Rsvg_Port_State    state;
+    cairo_status_t     status;
+    cairo_t            *cr;
+    cairo_surface_t    *surface;
+
+    state = (Rsvg_Port_State)_state;
+
+    /* create an image surface to store the rendered image, however,
+     * don't allocate memory, instead use the space already provided
+     * in `slot->bitmap.buffer'.
+     */
+    surface = cairo_image_surface_create_for_data( slot->bitmap.buffer,
+                                                   CAIRO_FORMAT_ARGB32,
+                                                   slot->bitmap.width,
+                                                   slot->bitmap.rows,
+                                                   slot->bitmap.pitch );
+    status = cairo_surface_status( surface );
+
+    if ( status != CAIRO_STATUS_SUCCESS )
+    {
+      if ( status == CAIRO_STATUS_NO_MEMORY )
+        return FT_Err_Out_Of_Memory;
+      else
+        return FT_Err_Invalid_Outline;
+    }
+
+    cr     = cairo_create( surface );
+    status = cairo_status( cr );
+
+    if ( status != CAIRO_STATUS_SUCCESS )
+    {
+      if ( status == CAIRO_STATUS_NO_MEMORY )
+        return FT_Err_Out_Of_Memory;
+      else
+        return FT_Err_Invalid_Outline;
+    }
+
+    /* set a translate transform that translates the points in such
+     * a way that we get a tight rendering with least redundant white
+     * space */
+    cairo_translate( cr, -1 * (state->x), -1 * (state->y) );
+    /* replay from the recorded surface. Saves us from parsing the document
+     * again and redoing this already done in the preset hook. */
+    cairo_set_source_surface( cr, state->rec_surface, 0.0, 0.0 );
+    cairo_paint( cr );
+
+    cairo_surface_flush( surface );
+
+    slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+    slot->bitmap.num_grays  = 256;
+    slot->format = FT_GLYPH_FORMAT_BITMAP;
+
+    /* clean everything */
+    cairo_surface_destroy( surface );
+    cairo_destroy( cr );
+    cairo_surface_destroy( state->rec_surface );
+    return error;
+  }
+
+  /**
+   * This hook is called at two different locations. Firstly, it is called
+   * when presetting the glyphslot when FT_Load_Glyph is called. Secondly, it 
is
+   * called right before the render hook is called. When `cache` is false, it's
+   * the former while when `cache` is true, it's the latter.
+   *
+   * The job of this function is to preset the slot setting the width, height,
+   * pitch, bitmap.left and bitmap.top. These are all necessary for appropriate
+   * memory allocation as well as ultimately compositing the glyph later on by
+   * client applications.
+   */
+  FT_Error
+  rsvg_port_preset_slot( FT_GlyphSlot  slot, FT_Bool  cache, FT_Pointer _state 
)
+  {
+    /* FreeType variables */
+    FT_Error         error          = FT_Err_Ok;
+    FT_SVG_Document  document       = (FT_SVG_Document)slot->other;
+    FT_Size_Metrics  metrics        = document->metrics;
+    FT_UShort        units_per_EM   = document->units_per_EM;
+    FT_UShort        end_glyph_id   = document->end_glyph_id;
+    FT_UShort        start_glyph_id = document->start_glyph_id;
+
+    /* Librsvg variables */
+    GError             *gerror = NULL;
+    gboolean           ret;
+    gboolean           out_has_width;
+    gboolean           out_has_height;
+    gboolean           out_has_viewbox;
+    RsvgHandle         *handle;
+    RsvgLength         out_width;
+    RsvgLength         out_height;
+    RsvgRectangle      out_viewbox;
+    RsvgDimensionData  dimension_svg;
+    cairo_t            *rec_cr;
+    cairo_matrix_t     transform_matrix;
+
+    /* Rendering port's state */
+    Rsvg_Port_State     state;
+    Rsvg_Port_StateRec  state_dummy;
+
+    /* general variables */
+    double  x;
+    double  y;
+    double  xx;
+    double  xy;
+    double  yx;
+    double  yy;
+    double  x0;
+    double  y0;
+    double  width;
+    double  height;
+    double  x_svg_to_out;
+    double  y_svg_to_out;
+
+    /* if cache is `TRUE` we store calculations in the actual port
+     * state variable, otherwise we just create a dummy variable and
+     * store there. This saves from too many if statements.
+     */
+    if ( cache )
+      state = (Rsvg_Port_State)_state;
+    else
+      state = &state_dummy;
+
+    /* form an RsvgHandle by loading the SVG document */
+    handle = rsvg_handle_new_from_data( document->svg_document,
+                                        document->svg_document_length,
+                                        &gerror );
+    if ( handle == NULL )
+    {
+      error = FT_Err_Invalid_SVG_Document;
+      goto CleanLibrsvg;
+    }
+
+    /* get attributes like `viewBox' and `width/height'. */
+    rsvg_handle_get_intrinsic_dimensions( handle,
+                                          &out_has_width,
+                                          &out_width,
+                                          &out_has_height,
+                                          &out_height,
+                                          &out_has_viewbox,
+                                          &out_viewbox );
+
+    /**
+     * Figure out the units in the EM square in the SVG document. This is
+     * specified by the ViewBox or the width/height attributes, if present,
+     * otherwise it should be assumed that the units in the EM square are
+     * the same as in the TTF/CFF outlines.
+     *
+     * TODO: I'm not sure what the standard says about the situation if
+     * the ViewBox as well as width/height are present, however, I've never
+     * seen that situation in real fonts.
+     */
+    if ( out_has_viewbox == TRUE )
+    {
+      dimension_svg.width  = out_viewbox.width;
+      dimension_svg.height = out_viewbox.height;
+    }
+    else if ( ( out_has_width == TRUE ) && ( out_has_height == TRUE ) )
+    {
+      dimension_svg.width  = out_width.length;
+      dimension_svg.height = out_height.length;
+    }
+    else
+    {
+      /* if no `viewBox' and `width/height' are present, the `units_per_EM'
+       * in SVG coordinates must be the same as `units_per_EM' of the TTF/CFF
+       * outlines.
+       */
+      dimension_svg.width  = units_per_EM;
+      dimension_svg.height = units_per_EM;
+    }
+
+    /* scale factors from SVG coordinates to the output size needed */
+    x_svg_to_out = (float)metrics.x_ppem/(float)dimension_svg.width;
+    y_svg_to_out = (float)metrics.y_ppem/(float)dimension_svg.height;
+
+    /* create a cairo recording surface. This is done for two reasons. Firstly,
+     * it is required to get the bounding box of the final drawing so we can
+     * use appropriate translate transform to get a tight rendering. Secondly,
+     * if `cache' is true, we can save this surface and later replay it
+     * against an image surface for the final rendering. This saves us from
+     * loading and parsing the document again.
+     */
+    state->rec_surface = cairo_recording_surface_create( 
CAIRO_CONTENT_COLOR_ALPHA,
+                                                         NULL );
+
+    rec_cr = cairo_create( state->rec_surface );
+
+
+    /* we need to take into account any transformations applied. The end
+     * user who applied the transformation doesn't know the internal details
+     * of the SVG document. Thus, we expect that the end user should just
+     * write the transformation as if the glyph is a traditional one. We then,
+     * do some maths on this to get the equivalent transformation in SVG
+     * coordinates.
+     */
+    xx =  1 * (((double)document->transform.xx)/((double)(1 << 16 )));
+    xy = -1 * (((double)document->transform.xy)/((double)(1 << 16 )));
+    yx = -1 * (((double)document->transform.yx)/((double)(1 << 16 )));
+    yy =  1 * (((double)document->transform.yy)/((double)(1 << 16 )));
+    x0 =  1 * ((double)document->delta.x/(double)(1 << 6)) *
+              ((double)dimension_svg.width/(double)metrics.x_ppem);
+    y0 = -1 * ((double)document->delta.y/(double)(1 << 6)) *
+              ((double)dimension_svg.height/(double)metrics.y_ppem);
+
+    /* cairo stores transformation and translate both in one matrix */
+    transform_matrix.xx = xx;
+    transform_matrix.yx = yx;
+    transform_matrix.xy = xy;
+    transform_matrix.yy = yy;
+    transform_matrix.x0 = x0;
+    transform_matrix.y0 = y0;
+
+    /* set up a scale transfromation to scale up the document to the
+     * required output size */
+    cairo_scale( rec_cr, x_svg_to_out, y_svg_to_out );
+    /* setup a transformation matrix */
+    cairo_transform( rec_cr, &transform_matrix );
+
+    /* if the document contains only one glyph, `start_glyph_id' and
+     * `end_glyph_id' will have the same value, else, `end_glyph_id'
+     * will be higher. */
+    if ( start_glyph_id == end_glyph_id )
+    {
+      /* render the whole document to the recording surface */
+      ret = rsvg_handle_render_cairo ( handle, rec_cr );
+      if ( ret == FALSE )
+      {
+        error = FT_Err_Invalid_SVG_Document;
+        goto CleanCairo;
+      }
+    }
+    else if ( start_glyph_id < end_glyph_id )
+    {
+      int    length;
+      char*  str;
+
+      length = snprintf( NULL, 0, "%u", slot->glyph_index );
+      str    = (char*)malloc( 6 + length + 1 );
+      strcpy( str, "#glyph");
+      snprintf( str + 6, length + 1, "%u", slot->glyph_index );
+      /* render only the element with `id' equal to `glyph<ID>' */
+      ret = rsvg_handle_render_cairo_sub( handle, rec_cr, str );
+      free(str);
+      if ( ret == FALSE )
+      {
+        error = FT_Err_Invalid_SVG_Document;
+        goto CleanCairo;
+      }
+    }
+
+    /* get the bounding box of the drawing */
+    cairo_recording_surface_ink_extents( state->rec_surface, &x, &y,
+                                         &width, &height);
+
+    /* we store the bounding box's `x' and `y' so that the render hook
+     * can apply a translation to get a tight rendering.
+     */
+    state->x            = x;
+    state->y            = y;
+
+    /* preset the values */
+    slot->bitmap_left   = state->x ;
+    slot->bitmap_top    = (state->y) * -1;
+    slot->bitmap.rows   = ceil( height );
+    slot->bitmap.width  = ceil( width );
+    slot->bitmap.pitch  = slot->bitmap.width * 4;
+
+    slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+
+    /* if a render call is to follow, just destroy the context for the
+     * recording surface, since no more drawing will be done on it, but,
+     * keep the surface itself for use by the render hook.
+     */
+    if ( cache == TRUE )
+    {
+      cairo_destroy( rec_cr );
+      goto CleanLibrsvg;
+    }
+
+    /* destroy the recording surface as well as the context */
+  CleanCairo:
+    cairo_surface_destroy( state->rec_surface );
+    cairo_destroy( rec_cr );
+  CleanLibrsvg:
+    /* destroy the handle */
+    g_object_unref( handle );
+    return error;
+  }
diff --git a/src/rsvg_port.h b/src/rsvg_port.h
new file mode 100644
index 0000000..cb10a4b
--- /dev/null
+++ b/src/rsvg_port.h
@@ -0,0 +1,54 @@
+/****************************************************************************
+ *
+ * rsvg_port.h
+ *
+ *   Librsvg based hook functions for OT-SVG rendering in FreeType. (headers)
+ *
+ * Copyright (C) 1996-2021 by
+ * David Turner, Robert Wilhelm, Werner Lemberg and Moazin Khatti.
+ *
+ * This file is part of the FreeType project, and may only be used,
+ * modified, and distributed under the terms of the FreeType project
+ * license, LICENSE.TXT.  By continuing to use, modify, or distribute
+ * this file you indicate that you have read the license and
+ * understand and accept it fully.
+ *
+ */
+
+#ifndef RSVG_PORT_H
+#define RSVG_PORT_H
+
+#include <cairo.h>
+#include <librsvg/rsvg.h>
+#include <ft2build.h>
+#include <freetype/freetype.h>
+
+  /* Different hook functions can persist data by creating a state structure
+   * and putting its address in `library->svg_renderer_state'. Functions can
+   * store and retrieve data from this structure
+   */
+  typedef struct Rsvg_Port_StateRec_
+  {
+    cairo_surface_t    *rec_surface;
+    double             x;
+    double             y;
+  } Rsvg_Port_StateRec;
+
+  typedef struct Rsvg_Port_StateRec_*  Rsvg_Port_State;
+
+  FT_Error
+  rsvg_port_init( FT_Pointer *state);
+
+  void
+  rsvg_port_free( FT_Pointer state );
+
+  FT_Error
+  rsvg_port_render( FT_GlyphSlot slot,
+                    FT_Pointer state );
+
+  FT_Error
+  rsvg_port_preset_slot( FT_GlyphSlot  slot,
+                         FT_Bool  cache,
+                         FT_Pointer state);
+
+#endif



reply via email to

[Prev in Thread] Current Thread [Next in Thread]