1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
# skia_ot_svg_module
# Copyright 2023 Hin-Tak Leung
# Typical usage:
#
# from freetype import get_handle, FT_Property_Set
# from skia_ot_svg_module import hooks
# ...
# library = get_handle()
# FT_Property_Set( library, b"ot-svg", b"svg-hooks", byref(hooks) )
#
# After these lines (or equivalent), COLOR OT-SVG font support is enabled.
# You just use freetype-py as usual. Color bitmaps are returned for OT-SVG
# fonts in FT_Load_Char()/FT_Load_Glyph().
from freetype import FT_SVG_Document, FT_Err_Ok, SVG_RendererHooks, \
SVG_Lib_Init_Func, SVG_Lib_Free_Func, SVG_Lib_Render_Func, \
SVG_Lib_Preset_Slot_Func, SizeMetrics, FT_UShort, FT_PIXEL_MODE_BGRA, \
FT_GLYPH_FORMAT_BITMAP
import ctypes
from ctypes import py_object, pythonapi, cast, c_char_p
import skia
from skia import Size, ImageInfo, ColorType, AlphaType, Canvas, \
PictureRecorder, Rect, ScalarNegativeInfinity, ScalarInfinity, RTreeFactory
from packaging import version
assert(version.parse(skia.__version__) > version.parse("116.0b2")), "Needs Skia-Python 116.0b2+"
pythonapi.PyMemoryView_FromMemory.restype = py_object
# include/private/base/SkFixed.h
def SkFixedToFloat(x):
return ((x) * 1.52587890625e-5)
from math import ceil, floor
_state = None
def svg_init(ctx):
global _state
_state = {}
ctx.contents.value = _state
return FT_Err_Ok
def svg_free(ctx):
global _state
_state = None
# "None" is strictly speaking a special pyobject,
# this line does not do what it should, i.e. setting the
# pointer to NULL.
ctx.contents = None
return # void
def svg_render(slot, ctx):
state = ctx.contents.value
dstBitmap = skia.Bitmap()
dstBitmap.setInfo(ImageInfo.Make(slot.contents.bitmap.width, slot.contents.bitmap.rows,
ColorType.kBGRA_8888_ColorType,
AlphaType.kPremul_AlphaType),
slot.contents.bitmap.pitch)
dstBitmap.setPixels(pythonapi.PyMemoryView_FromMemory(cast(slot.contents.bitmap.buffer, c_char_p),
slot.contents.bitmap.rows * slot.contents.bitmap.pitch,
0x200), # Read-Write
)
canvas = Canvas(dstBitmap)
canvas.clear(skia.ColorTRANSPARENT)
canvas.translate( -state['x'], -state['y'] )
canvas.drawPicture( state['picture'] )
slot.contents.bitmap.pixel_mode = FT_PIXEL_MODE_BGRA
slot.contents.bitmap.num_grays = 256
slot.contents.format = FT_GLYPH_FORMAT_BITMAP
state['picture'] = None
return FT_Err_Ok
def svg_preset_slot(slot, cached, ctx):
state = ctx.contents.value
document = ctypes.cast(slot.contents.other, FT_SVG_Document)
metrics = SizeMetrics(document.contents.metrics)
units_per_EM = FT_UShort(document.contents.units_per_EM)
end_glyph_id = FT_UShort(document.contents.end_glyph_id)
start_glyph_id = FT_UShort(document.contents.start_glyph_id)
doc = ctypes.string_at(document.contents.svg_document, # not terminated
size=document.contents.svg_document_length)
data = skia.Data(doc)
svgmem = skia.MemoryStream(data)
svg = skia.SVGDOM.MakeFromStream(svgmem)
if (svg.containerSize().isEmpty()):
size = Size.Make(units_per_EM.value, units_per_EM.value)
svg.setContainerSize(size)
recorder = PictureRecorder()
infiniteRect = Rect.MakeLTRB(ScalarNegativeInfinity, ScalarNegativeInfinity,
ScalarInfinity, ScalarInfinity)
bboxh = RTreeFactory()()
recordingCanvas = recorder.beginRecording(infiniteRect, bboxh)
ftMatrix = document.contents.transform
ftOffset = document.contents.delta
m = skia.Matrix()
m.setAll(
SkFixedToFloat(ftMatrix.xx), -SkFixedToFloat(ftMatrix.xy), SkFixedToFloat(ftOffset.x),
-SkFixedToFloat(ftMatrix.yx), SkFixedToFloat(ftMatrix.yy), -SkFixedToFloat(ftOffset.y),
0 , 0 , 1 )
m.postScale(SkFixedToFloat(document.contents.metrics.x_scale) / 64.0,
SkFixedToFloat(document.contents.metrics.y_scale) / 64.0)
recordingCanvas.concat(m)
if ( start_glyph_id.value < end_glyph_id.value ):
id = "glyph%u" % ( slot.contents.glyph_index )
svg.renderNode(recordingCanvas, id)
else:
svg.render(recordingCanvas)
state['picture'] = recorder.finishRecordingAsPicture()
bounds = state['picture'].cullRect()
width = ceil(bounds.right()) - floor(bounds.left())
height = ceil(bounds.bottom()) - floor(bounds.top())
x = floor(bounds.left())
y = floor(bounds.top())
state['x'] = x
state['y'] = y
slot.contents.bitmap_left = int(state['x']) # float to int conversion
slot.contents.bitmap_top = int(-state['y'])
slot.contents.bitmap.rows = ceil( height ) # float to int
slot.contents.bitmap.width = ceil( width )
slot.contents.bitmap.pitch = slot.contents.bitmap.width * 4
slot.contents.bitmap.pixel_mode = FT_PIXEL_MODE_BGRA
metrics_width = width
metrics_height = height
horiBearingX = state['x']
horiBearingY = -state['y']
vertBearingX = slot.contents.metrics.horiBearingX / 64.0 - slot.contents.metrics.horiAdvance / 64.0 / 2
vertBearingY = ( slot.contents.metrics.vertAdvance / 64.0 - slot.contents.metrics.height / 64.0 ) / 2
slot.contents.metrics.width = int(round( width * 64 ))
slot.contents.metrics.height = int(round( height * 64 ))
slot.contents.metrics.horiBearingX = int( horiBearingX * 64 )
slot.contents.metrics.horiBearingY = int( horiBearingY * 64 )
slot.contents.metrics.vertBearingX = int( vertBearingX * 64 )
slot.contents.metrics.vertBearingY = int( vertBearingY * 64 )
if ( slot.contents.metrics.vertAdvance == 0 ):
slot.contents.metrics.vertAdvance = int( height * 1.2 * 64 )
if ( cached == False ):
state['picture'] = None
state['x'] = 0
state['y'] = 0
return FT_Err_Ok
hooks = SVG_RendererHooks(svg_init=SVG_Lib_Init_Func(svg_init),
svg_free=SVG_Lib_Free_Func(svg_free),
svg_render=SVG_Lib_Render_Func(svg_render),
svg_preset_slot=SVG_Lib_Preset_Slot_Func(svg_preset_slot))
|