Here it is, the actuall code diff for adding skia as a ot-svg renderer hook to
freetype, as an alternative to rsvg/cairo. There is a new file "README.skia". I
fixed the color change and the intermittent crash, so as far as I know the only
thing is that it is occasionally 1-pixel off compared to rsvg/cairo.
This can be somewhat corrected by changing these two lines:
x = bounds.left(); -> floor(bounds.left());
y = bounds.top(); -> floor(boulds.left());
I am not convinced that skia is entirey wrong, actually; the equivalent rsvg
location has some comments on doubts about rounding.
I thought of switching the hooks dynamically (between 3, rsvg_hook, skia_hook,
and a new null_hook), but running init of one hook , switch in the middle and
run the free of another hook is dangerous. So this is still done as a
build-time change. Otherwise it would be great to switch dynamically to
investigate the 1-pixel-difference problem.
For now, if somebody else wants to give this a try, especially a mac user or a
windows user, and/or somebody want to build skia (I am lazy and just downloaded
a not-too-old pre-built binaries), I like to hear about 1st-hand experience on
that too.
The rest, please have a thought how to switch the hook dynamically, without
crashing....
will probably post this up at
https://github.com/HinTak/harfbuzz-python-demos/ under a new directory
"skia-adventure" and write there. Thanks for reading this far :-).
On Wednesday, 12 July 2023 at 07:28:15 BST, Hin-Tak Leung
<ht...@users.sourceforge.net> wrote:
See attached screenshot. Left is skia-based ftview/ftgrid, right is my
system-wide ftview/ftgrid 2.13.0 (based on rsvg/cairo). The default zoom level
for ftgrid seems to have changed between 2.13.0 and 2.13.1 from 4 to 6?
There are 3 issues I see
- the first one is of course the color - seems to be a rgba vs bgra difference?
i.e. the Alpha channel is the same, but red and blue swapped.
- 2nd one is not very noticeable on ftview but more on ftgrid - the glyph is
shifted down by one pixel. Probably a rounding error somewhere.
- I have intermitent crashes from 'include/core/SkRefCnt.h:72: fatal error:
"assert(this->getRefCnt() > 0)" ' . Prove I don't know Skia well enough yet. (
That's one week of learning Skia ...)
On the way also learns a few things - this is based on m110 . I see that even
with main (close to m116), one of the headers I am using have changed location.
I tried updating skia-python 's bundled skia from m87 to m88 - just 1
milestone, or roughly 4 weeks, to get a feel of how easy/hard it is. The main
reason is that m88 is the first where the SVGDOM class is declared
non-experimental. So I expect the SVG related headers to move. But the actual
diff is close to about 1000 lines, took me a whole day. (by comparison, the
fontval diff is a little over 4000 lines, and took 3 years). The amount of
changes in skia per milestone is just alarming. That explains why nobody wants
to ship skia shared libraries, and why skia-python is stuck at m87 for 2 years.
Skia is just contantly changing from milestone to milstone.
That said, I think somebody should update skia-python's bundled skia from m87
to m98 (COLRv1) and perhaps even m103 (OT-SVG). Google folks?
One milestone is about 1000 lines of code difference and a whole day, I reckon
it will take somebody working full time for 2 months to update skia-python to
current-ish. (116 - 87 = 29 working days..., by the time one gets to m116,
m117/m118 would be out...). If google folks are not moving a finger, and
somebody else wants to fund me to tackle this, please feel free to get in touch.
I'll tidy the diff up and send it to freetype-devel at some point. At the
moment it is just replacing all contents of rsvg-port.{c,h}. with
-#ifdef HAVE_LIBRSVG
+#ifdef HAVE_SKIA
...
- SVG_RendererHooks rsvg_hooks = { NULL, NULL, NULL, NULL };
+ SVG_RendererHooks skia_hooks = { NULL, NULL, NULL, NULL };
...
(void)FT_Property_Set( handle->library,
- "ot-svg", "svg-hooks", &rsvg_hooks );
+ "ot-svg", "svg-hooks", &skia_hooks );
...
So potentionally I can add a toggle key to dynamically switch between
rsvg/cairo to skia rendering by just swapping &rsvg_hooks with &skia_hooks on
the fly.
diff --git a/Makefile b/Makefile
index cded506..019e04d 100644
--- a/Makefile
+++ b/Makefile
@@ -61,6 +61,9 @@ ifeq ($(wildcard $(CONFIG_MK)),)
no_config_mk := 1
endif
+# Skia definitely want c++, and clang++ does not like -std=99
+# Why does this line has no effect?
+CC=g++
ifdef no_config_mk
exes:
@@ -136,8 +139,8 @@ else
#
# For the pure `make` call (without using `configure`) we have to add
# all needed cflags manually.
- FT_DEMO_CFLAGS := $(shell pkg-config --cflags librsvg-2.0) \
- -DHAVE_LIBRSVG
+ FT_DEMO_CFLAGS := -I$(TOP_DIR_2)/skia/ \
+ -DHAVE_SKIA
endif
FT_INCLUDES := $(OBJ_BUILD) \
@@ -145,10 +148,13 @@ else
$(TOP_DIR)/include \
$(SRC_DIR)
+ # Skia definitely want c++, and clang++ does not like -std=99
+ CC=g++
COMPILE = $(CC) $(ANSIFLAGS) \
$(INCLUDES:%=$I%) \
$(CFLAGS) \
- $(FT_DEMO_CFLAGS)
+ $(FT_DEMO_CFLAGS) -I$(TOP_DIR_2)/skia/ -DHAVE_SKIA
+
# Enable C99 for gcc to avoid warnings.
# Note that clang++ aborts with an error if we use `-std=C99',
@@ -186,8 +192,10 @@ else
# `FT_DEMO_LDFLAGS` has been set in `unix-cc.mk`, too.
override CC = $(CCraw)
LINK_CMD = $(LIBTOOL) --mode=link $(CC) \
- $(subst /,$(COMPILER_SEP),$(LDFLAGS))
+ -L$(TOP_DIR_2)/skia/out/Release/ -lsvg -lskia -lskshaper -lskunicode -lGL -lfontconfig -lharfbuzz \
+ $(subst /,$(COMPILER_SEP),$(LDFLAGS))
LINK_LIBS = $(subst /,$(COMPILER_SEP),$(FTLIB) $(EFENCE)) \
+ -L$(TOP_DIR_2)/skia/out/Release/ -lsvg -lskia -lskshaper -lskunicode -lGL -lfontconfig -lharfbuzz \
$(FT_DEMO_LDFLAGS)
else
LINK_CMD = $(CC) $(subst /,$(COMPILER_SEP),$(LDFLAGS))
@@ -196,10 +204,10 @@ else
# all needed libraries manually.
LINK_LIBS := $(subst /,$(COMPILER_SEP),$(FTLIB) $(EFENCE)) \
-lm -lrt -lz -lbz2 -lpthread
+ LINK_LIBS += -L$(TOP_DIR_2)/skia/out/Release/ -lsvg -lskia
LINK_LIBS += $(shell pkg-config --libs libpng)
LINK_LIBS += $(shell pkg-config --libs harfbuzz)
LINK_LIBS += $(shell pkg-config --libs libbrotlidec)
- LINK_LIBS += $(shell pkg-config --libs librsvg-2.0)
else
LINK_LIBS = $(subst /,$(COMPILER_SEP),$(FTLIB) $(EFENCE))
endif
@@ -469,9 +477,13 @@ else
$(OBJ_DIR_2)/rsvg-port.$(SO): $(SRC_DIR)/rsvg-port.c $(SRC_DIR)/rsvg-port.h
$(COMPILE) $T$(subst /,$(COMPILER_SEP),$@ $<)
+ $(OBJ_DIR_2)/skia-port.$(SO): $(SRC_DIR)/skia-port.c $(SRC_DIR)/skia-port.h
+ $(COMPILE) $T$(subst /,$(COMPILER_SEP),$@ $<)
+
FTCOMMON_OBJ := $(OBJ_DIR_2)/ftcommon.$(SO) \
$(OBJ_DIR_2)/ftpngout.$(SO) \
- $(OBJ_DIR_2)/rsvg-port.$(SO)
+ $(OBJ_DIR_2)/rsvg-port.$(SO) \
+ $(OBJ_DIR_2)/skia-port.$(SO)
####################################################################
diff --git a/README.skia b/README.skia
new file mode 100644
index 0000000..35c9185
--- /dev/null
+++ b/README.skia
@@ -0,0 +1,59 @@
+Using Skia instead of Rsvg for rendering OT-SVG
+===============================================
+
+Skia has a large code base and a long history, and moving very fast.
+There is no official prebuilt binaries AFAIK. The official
+build procedure, does a 900MB+ clone, then fetches at least another
+1GB of third-party dependencies by clone'ing on the fly. I gave up
+after seeing it downloading 2GB+ without doing anything else (yet)!
+
+So I scouted around for reasonably up-to-date unofficial builds.
+This seems to be close enough for this purpose:
+
+ https://github.com/JetBrains/skia-pack/releases/
+
+At the time of writing, official skia is at m116, they have
+lightly patched m110. Another closely related place,
+seems to have unpatched m109:
+
+ https://github.com/HumbleUI/SkiaBuild
+
+skia-python bundles m87; m98 introduces COLRv1, and m103 introduces
+OT-SVG. For this purpose, m103+ is the minimum.
+
+
+PREPARATION:
+
+ mkdir skia/
+ pushd skia/
+ unzip "<downloaded>"/Skia-m110-d88a7b5-linux-Release-x64.zip
+ popd
+
+# This is just mirroring the layout of a submodule-based self-build
+# using the official procedure.
+
+ ln -s Release-linux-x64 skia/out/Release
+
+# Since we set "-Lskia/out/Release/" in the linker, we want to avoid
+# picking up those same bundled libraries, but let the linker pick
+# up the system versions of these libraries.
+
+ rm skia/out/Release/libharfbuzz.a
+ rm skia/out/Release/libzlib.a
+ rm skia/out/Release/libpng.a
+ rm skia/out/Release/libfreetype2.a
+ rm skia/out/Release/libjpeg.a
+
+
+BUILDING:
+
+Run:
+ CC=c++ ./configure --with-librsvg=no
+
+in freetype2. Using a C++ compiler is necessary (Skia is heavy-duty
+c++ code). Co-existence with librsvg and runtime-switching is
+still being looked at.
+
+Then just build as normal. The main noticeable outcome is that a few
+files in "bin/.libs" are 25MB+ (because linking with libskia.a),
+instead of a few MB in size.
diff --git a/skia/out/Release b/skia/out/Release
new file mode 120000
index 0000000..9f40889
--- /dev/null
+++ b/skia/out/Release
@@ -0,0 +1 @@
+Release-linux-x64
\ No newline at end of file
diff --git a/src/ftcommon.c b/src/ftcommon.c
index 74553aa..7d8a72e 100644
--- a/src/ftcommon.c
+++ b/src/ftcommon.c
@@ -29,6 +29,7 @@
#include "strbuf.h"
#include "ftcommon.h"
#include "rsvg-port.h"
+#include "skia-port.h"
#include <stdio.h>
#include <stdlib.h>
@@ -360,7 +361,7 @@
/* The use of an external SVG rendering library is optional. */
(void)FT_Property_Set( handle->library,
- "ot-svg", "svg-hooks", &rsvg_hooks );
+ "ot-svg", "svg-hooks", &skia_hooks );
error = FTC_Manager_New( handle->library, 0, 0, 0,
my_face_requester, 0, &handle->cache_manager );
diff --git a/src/skia-port.c b/src/skia-port.c
new file mode 100644
index 0000000..961c705
--- /dev/null
+++ b/src/skia-port.c
@@ -0,0 +1,356 @@
+/****************************************************************************
+ *
+ * skia-port.c
+ *
+ *
+ * Copyright (C) 2022-2023 by
+ * Hin-Tak Leung, based on rsvg-port.c
+ *
+ * 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.
+ *
+ */
+
+/*
+ * Main reference, this landed in Skia m103:
+ * https://github.com/google/skia/commit/9cbadcd9280dc139af2f4d41d25a6c9a750e0302.patch
+
+ * From 9cbadcd9280dc139af2f4d41d25a6c9a750e0302 Mon Sep 17 00:00:00 2001
+ * From: Ben Wagner <bunge...@google.com>
+ * Date: Wed, 20 Apr 2022 17:52:50 -0400
+ * Subject: [PATCH] Add optional OT-SVG support to FreeType
+
+ * In particular,
+ src/ports/SkFontHost_FreeType_common.cpp:
+ SkScalerContext_FreeType_Base::drawSVGGlyph()
+ src/ports/SkFontHost_FreeType.cpp:
+ SkScalerContext_FreeType::generateMetrics()
+
+ Unrelated but good-side reading:
+ src/ports/SkFontHost_FreeType_common.cpp:
+ SkScalerContext_FreeType_Base::computeColrV1GlyphBoundingBox()
+*/
+#include <ft2build.h>
+#include <freetype/otsvg.h>
+
+#ifdef HAVE_SKIA
+
+#include "modules/svg/include/SkSVGDOM.h"
+#include "modules/svg/include/SkSVGNode.h"
+#include "modules/svg/include/SkSVGRenderContext.h" // SkSVGPresentationContext
+#include "include/core/SkBitmap.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkStream.h"
+#include "include/private/SkFixed.h" // SkFixedToFloat(x) // relocated between m110 and main to "include/private/base/SkFixed.h"
+#include <stdlib.h>
+#include <math.h>
+
+#include <freetype/freetype.h>
+#include <freetype/ftbbox.h>
+
+#include "skia-port.h"
+
+
+ /*
+ * The init hook is called when the first OT-SVG glyph is rendered. All
+ * we do is to 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
+ skia_port_init( FT_Pointer *state )
+ {
+ /* allocate the memory upon initialization */
+ *state = calloc( sizeof( Skia_Port_StateRec ), 1 ); /* XXX error handling */
+ // Skia pointers are reference-counted, so
+ // malloc seems to be buggy and calloc is needed here.
+ // Unlike rsvg.
+
+ return FT_Err_Ok;
+ }
+
+
+ /*
+ * Deallocate the state structure.
+ */
+ void
+ skia_port_free( FT_Pointer *state )
+ {
+ free( *state );
+ }
+
+
+ /*
+ * The render hook. 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
+ skia_port_render( FT_GlyphSlot slot,
+ FT_Pointer *_state )
+ {
+ FT_Error error = FT_Err_Ok;
+
+ Skia_Port_State state;
+
+ state = *(Skia_Port_State*)_state;
+
+ /* Create a SkBitmap to store the rendered image. However, */
+ /* don't allocate memory; instead use the space already provided in */
+ /* `slot->bitmap.buffer`. */
+ SkBitmap dstBitmap;
+ dstBitmap.setInfo(SkImageInfo::Make(slot->bitmap.width, slot->bitmap.rows,
+ kBGRA_8888_SkColorType, // Not kN32 - FT_Bitmap are platform-neutral
+ kPremul_SkAlphaType),
+ slot->bitmap.pitch);
+ dstBitmap.setPixels(slot->bitmap.buffer);
+
+ SkCanvas canvas(dstBitmap);
+
+ canvas.clear(SK_ColorTRANSPARENT);
+
+ /* Set a translate transform that translates the points in such a way */
+ /* that we get a tight rendering with least redundant white spac. */
+ canvas.translate( -state->x, -state->y );
+
+ /* Replay from state->picture. This saves us from parsing the */
+ /* document again and redoing what was already done in the preset */
+ /* hook. */
+ canvas.drawPicture( state->picture );
+
+ slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+ slot->bitmap.num_grays = 256;
+ slot->format = FT_GLYPH_FORMAT_BITMAP;
+
+ /* Clean up everything. */
+ state->picture = nullptr;
+
+ 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 is the former, when `cache` is true, it is 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
+ skia_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_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;
+
+ SkCanvas *recordingCanvas;
+ SkMatrix m;
+
+ /* Rendering port's state. */
+ Skia_Port_State state;
+ Skia_Port_StateRec state_dummy;
+
+ /* General variables. */
+ double x, y;
+ double width, height;
+
+ float metrics_width, metrics_height;
+ float horiBearingX, horiBearingY;
+ float vertBearingX, vertBearingY;
+ float tmpf;
+
+ /* 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 us from too many 'if' statements. */
+ if ( cache )
+ state = *(Skia_Port_State*)_state;
+ else
+ state = &state_dummy;
+
+ /* Form an `sk_sp<SkSVGDOM>` by loading the SVG document. */
+ SkMemoryStream svgmem(document->svg_document,
+ document->svg_document_length, false /*not copying */);
+ sk_sp<SkSVGDOM> svg = SkSVGDOM::MakeFromStream(svgmem);
+
+ //svg->getRoot()->intrinsicSize();
+ if (svg->containerSize().isEmpty()) {
+ SkSize size = SkSize::Make(units_per_EM, units_per_EM);
+ svg->setContainerSize(size);
+ }
+ // Do we care about the viewBox attribute? It is auto I think, anyway.
+
+ /*
+ * Create a SkPictureRecorder. This is done for two reasons.
+ * Firstly, it is required to get the bounding box of the final drawing
+ * so we can use an 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.
+ */
+ SkPictureRecorder recorder;
+
+ SkRect infiniteRect = SkRect::MakeLTRB(-SK_ScalarInfinity, -SK_ScalarInfinity,
+ SK_ScalarInfinity, SK_ScalarInfinity);
+ sk_sp<SkBBoxHierarchy> bboxh = SkRTreeFactory()();
+
+ recordingCanvas = recorder.beginRecording(infiniteRect, bboxh);
+
+ /*
+ * Borrow heavily from:
+ * src/ports/SkFontHost_FreeType_common.cpp:
+ * SkScalerContext_FreeType_Base::drawSVGGlyph()
+ */
+ SkASSERT(slot->format == FT_GLYPH_FORMAT_SVG);
+
+ /*
+ * 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.
+ */
+ FT_Matrix ftMatrix = document->transform;
+ FT_Vector ftOffset = document->delta;
+
+ /* Skia stores both transformation and translation in one matrix. */
+ m.setAll(
+ SkFixedToFloat(ftMatrix.xx), -SkFixedToFloat(ftMatrix.xy), SkFixedToFloat(ftOffset.x),
+ -SkFixedToFloat(ftMatrix.yx), SkFixedToFloat(ftMatrix.yy), -SkFixedToFloat(ftOffset.y),
+ 0 , 0 , 1 );
+
+ /* Set up a scale transformation to scale up the document to the */
+ /* required output size. */
+ m.postScale(SkFixedToFloat(document->metrics.x_scale) / 64.0f,
+ SkFixedToFloat(document->metrics.y_scale) / 64.0f);
+
+ /* Set up a transformation matrix. */
+ recordingCanvas->concat(m);
+
+ /* If the document contains only one glyph, `start_glyph_id` and */
+ /* `end_glyph_id` have the same value. Otherwise `end_glyph_id` */
+ /* is larger. */
+ if ( start_glyph_id < end_glyph_id )
+ {
+ SkSVGPresentationContext pctx;
+ char id[32];
+ /* Render only the element with its ID equal to `glyph<ID>`. */
+ sprintf( id, "glyph%u", slot->glyph_index );
+
+ /*
+ * Unlike Rsvg, Skia's renderNode() takes an extra
+ * SkSVGPresentationContext argument, which sets foreground
+ * colors, palettes, etc, and does not take NULL to render
+ * whole. In the case of OT-SVG, there is no extra
+ * Context, so leaving it as default is fine.
+ */
+ svg->renderNode(recordingCanvas, pctx, id);
+ }
+ else
+ {
+ /* Render the whole document */
+ svg->render(recordingCanvas);
+ }
+
+ /* Get the bounding box of the drawing. */
+ state->picture = recorder.finishRecordingAsPicture();
+ SkRect bounds = state->picture->cullRect();
+ SkASSERT(bounds.isFinite());
+
+ width = bounds.width();
+ height = bounds.height();
+ x = bounds.left();
+ y = bounds.top();
+
+ /* We store the bounding box's `x` and `y` values 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 = (FT_Int) state->x; /* XXX rounding? */
+ slot->bitmap_top = (FT_Int)-state->y;
+
+ slot->bitmap.rows = ceil( height );
+ slot->bitmap.width = ceil( width );
+
+ slot->bitmap.pitch = (int)slot->bitmap.width * 4;
+
+ slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+
+ /* Compute all the bearings and set them correctly. The outline is */
+ /* scaled already, we just need to use the bounding box. */
+ metrics_width = (float)width;
+ metrics_height = (float)height;
+
+ horiBearingX = (float) state->x;
+ horiBearingY = (float)-state->y;
+
+ vertBearingX = slot->metrics.horiBearingX / 64.0f -
+ slot->metrics.horiAdvance / 64.0f / 2;
+ vertBearingY = ( slot->metrics.vertAdvance / 64.0f -
+ slot->metrics.height / 64.0f ) / 2; /* XXX parentheses correct? */
+
+ /* Do conversion in two steps to avoid 'bad function cast' warning. */
+ tmpf = roundf( metrics_width * 64 );
+ slot->metrics.width = (FT_Pos)tmpf;
+ tmpf = roundf( metrics_height * 64 );
+ slot->metrics.height = (FT_Pos)tmpf;
+
+ slot->metrics.horiBearingX = (FT_Pos)( horiBearingX * 64 ); /* XXX rounding? */
+ slot->metrics.horiBearingY = (FT_Pos)( horiBearingY * 64 );
+ slot->metrics.vertBearingX = (FT_Pos)( vertBearingX * 64 );
+ slot->metrics.vertBearingY = (FT_Pos)( vertBearingY * 64 );
+
+ if ( slot->metrics.vertAdvance == 0 )
+ slot->metrics.vertAdvance = (FT_Pos)( metrics_height * 1.2f * 64 );
+
+ /* If a render call is to follow, just destroy the canvas for the */
+ /* SkPictureRecorder since no more drawing will be done on it. */
+ /* However, keep the Picture itself for use by the render hook. */
+ // TRUE defined in /usr/include/glib-2.0/glib/gmacros.h
+ // TRUE/FALSE not in c++, defined in include/freetype/internal/ftobjs.h (not used)
+ // but defined indirectly in librsvg. Its header to refer to it (from glib).
+ if ( !cache )
+ {
+ /* We don't have to do this; just being pedantic */
+ state->picture = nullptr;
+ }
+
+ return error;
+ }
+
+
+ SVG_RendererHooks skia_hooks = {
+ (SVG_Lib_Init_Func)skia_port_init,
+ (SVG_Lib_Free_Func)skia_port_free,
+ (SVG_Lib_Render_Func)skia_port_render,
+ (SVG_Lib_Preset_Slot_Func)skia_port_preset_slot
+ };
+
+#else /* !HAVE_SKIA */
+
+ SVG_RendererHooks skia_hooks = { NULL, NULL, NULL, NULL };
+
+#endif /* !HAVE_SKIA */
+
+
+/* End */
diff --git a/src/skia-port.h b/src/skia-port.h
new file mode 100644
index 0000000..75c2d5a
--- /dev/null
+++ b/src/skia-port.h
@@ -0,0 +1,71 @@
+/****************************************************************************
+ *
+ * skia-port.h
+ *
+ * Skia based hook functions for OT-SVG rendering in FreeType
+ * (headers).
+ *
+ * Copyright (C) 2022-2023 by
+ * Hin-Tak Leung, based on rsvg-port.h
+ *
+ * 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 SKIA_PORT_H
+#define SKIA_PORT_H
+
+#include <ft2build.h>
+#include <freetype/otsvg.h>
+
+#ifdef HAVE_SKIA
+
+#include "include/core/SkPictureRecorder.h"
+#include <freetype/freetype.h>
+
+
+ /*
+ * Different hook functions can access persisting data by creating a state
+ * structure and putting its address in `library->svg_renderer_state`.
+ * Functions can then store and retrieve data from this structure.
+ */
+ typedef struct Skia_Port_StateRec_
+ {
+ sk_sp<SkPicture> picture;
+
+ double x;
+ double y;
+
+ } Skia_Port_StateRec;
+
+ typedef struct Skia_Port_StateRec_* Skia_Port_State;
+
+
+ FT_Error
+ skia_port_init( FT_Pointer *state );
+
+ void
+ skia_port_free( FT_Pointer *state );
+
+ FT_Error
+ skia_port_render( FT_GlyphSlot slot,
+ FT_Pointer *state );
+
+ FT_Error
+ skia_port_preset_slot( FT_GlyphSlot slot,
+ FT_Bool cache,
+ FT_Pointer *state );
+
+#endif /* HAVE_SKAI */
+
+
+ extern SVG_RendererHooks skia_hooks;
+
+#endif /* SKIA_PORT_H */
+
+
+/* End */