├── README.md ├── README.vertical.md ├── bitmap_to_surface.py ├── gen-images.sh ├── hb-view-ot-svg-skia-GL.py ├── hb-view-ot-svg.py ├── hb-view.py ├── images ├── NotoSansMongolian-Regular-bare.png ├── NotoSansMongolian-Regular-form0.png ├── NotoSansMongolian-Regular-form1.png ├── NotoSansMongolian-Regular-form2.png ├── NotoSansMongolian-Regular-join1.png ├── NotoSansMongolian-Regular-join2.png ├── NotoSansMongolian-Regular-join3.png ├── another-harbuzz.png ├── arabic-boxed.png ├── arabic-cropped.png ├── c++japanese.png ├── japanese-example.png ├── mngltitleotf-bare.png ├── mngltitleotf-form0.png ├── mngltitleotf-form1.png ├── mngltitleotf-join1.png ├── mngltitleotf-join2.png ├── mngltitleotf-join3.png ├── mnglwhiteotf-bare.png ├── mnglwhiteotf-form0.png ├── mnglwhiteotf-form1.png ├── mnglwhiteotf-form2.png ├── mnglwhiteotf-form3.png ├── mnglwhiteotf-join1.png ├── mnglwhiteotf-join2.png ├── mnglwhiteotf-join3.png ├── mnglwritingotf-bare.png ├── mnglwritingotf-form0.png ├── mnglwritingotf-form1.png ├── mnglwritingotf-form2.png ├── mnglwritingotf-form3.png ├── mnglwritingotf-join1.png ├── mnglwritingotf-join2.png ├── mnglwritingotf-join3.png ├── monbaiti-bare.png ├── monbaiti-form0.png ├── monbaiti-form1.png ├── monbaiti-join1.png ├── monbaiti-join2.png ├── monbaiti-join3.png ├── mongolian-example1.png ├── phagspa-example1.png ├── phagspa-example2.png ├── phagspa-long.png ├── sanskrit-ligature1.png └── sanskrit-ligature2.png ├── mongolian-variants.sh ├── otsvg.py ├── skia-adventure ├── COLRv1.md ├── README.md ├── RPM-sizes.txt └── screenshots │ ├── ftgrid-Nabla-rsvg.png │ ├── ftgrid-Nabla-skia.png │ ├── ftgrid-SVG.png │ ├── ftgrid-colrv1.png │ ├── ftgrid-diff.png │ ├── ftgrid-glyf.png │ ├── ftgrid-kAlpha.png │ ├── ftgrid-kGray.png │ ├── ftgrid-palette0-g0.png │ ├── ftgrid-palette0-g1.png │ ├── ftgrid-palette0-g2.png │ ├── ftgrid-palette0-g3.png │ ├── ftgrid-palette0-g4.png │ ├── ftgrid-palette0-g5.png │ ├── ftgrid-palette0-g6.png │ ├── ftgrid-palette0.png │ ├── ftgrid-palette1.png │ ├── ftgrid-palette2.png │ ├── ftgrid-palette3.png │ ├── ftgrid-palette4.png │ ├── ftgrid-palette5.png │ ├── ftgrid-palette6.png │ ├── ftgrid-rsvg.png │ └── ftgrid-skia.png ├── skia_glfw_module.py ├── skia_ot_svg_module.py ├── svg-native ├── README.md ├── ft2-demos-Adobe-SVG-Cairo.diff ├── ft2-demos-SkiaSVG-Adobe-SVG-Native.diff ├── ftgrid-0.png ├── ftgrid-10.png ├── ftgrid-11.png ├── ftgrid-12.png ├── ftgrid-13.png ├── ftgrid-14.png ├── ftgrid-190.png ├── ftgrid-2.png ├── ftgrid-222.png ├── ftgrid-238.png ├── ftgrid-270.png ├── ftgrid-3.png ├── ftgrid-4.png ├── ftgrid-5.png ├── ftgrid-6.png ├── ftgrid-7.png ├── ftgrid-8.png └── ftgrid-9.png └── variation-selectors.md /README.md: -------------------------------------------------------------------------------- 1 | This repository is a Python-based project that re-implements the hb-view tool from the HarfBuzz library. HarfBuzz is a popular text-shaping engine used in typography and font rendering. 2 | 3 | - Purpose: Provides tools and demonstrations related to HarfBuzz, particularly focusing on replicating the functionality of the hb-view tool, which is used for visualizing shaped text. 4 | 5 | # HarfBuzz python demos 6 | 7 | This project currently has one script: `hb-view.py`, a re-implementation of some of the functionalilty of upstream HarfBuzz's C++ 8 | based `hb-view` utilility, with some additional and extra functionality inspired by and taken from `hb-shape`. It uses pygobject and 9 | HarfBuzz's gobject binding, which is maintained within HarfBuzz. 10 | 11 | Specifically, it does ink-box tight-cropping by default, and output PNG images. It also calculates margin adjustments, so that you 12 | can use upstream HarfBuzz's C++ based `hb-view` utilility to generate vector images with tight-cropping. 13 | Upstream HarfBuzz's C++ based `hb-view` utilility uses descender/ascender + advance-width , which could be substantially larger or smaller than 14 | the ink area. 15 | 16 | For example, for the Persian "HarfBuzz" image below, `hb-view.py` would output this before generating the PNG image: 17 | 18 | ``` 19 | default: 693.75 314.375 20 | ink box: 695.25 264.875 21 | margin: -21.25 -1.625 3.75 35.125 22 | ``` 23 | 24 | You can cut-and-paste this and run C++ `hb-view` with `--margin="-21.25 -1.625 3.75 35.125"` to get a vector svg/pdf/ps/eps image of the ink-box area, 25 | if you need a vector image. 26 | 27 | There is an option for `hb-view.py` to use descender/ascender . The drawing code is not a step-by-step translation of C Cairo code to 28 | pycairo Python code, so in both kinds of outputs (ink-box based or descender/ascender+advance-width based), sub-pixel differences are expected. 29 | However, differences should not be beyond fractional pixels. 30 | 31 | See below for output demos, and [vertical layout](README.vertical.md) for vertical-layout examples, [variation selector examples](variation-selectors.md). 32 | 33 | All the images are generated by the [script](gen-images.sh), using either libre fonts or fonts from Windows. 34 | They should be commonly accessible. 35 | 36 | # Requirement 37 | 38 | The descender/ascender code depends on a recent bug fix ( https://github.com/harfbuzz/harfbuzz/pull/1209 ) to harfbuzz from me. 39 | This was merged after HarfBuzz version 1.9.0 and first appeared in 2.0.0 (released on Oct 18, 2018). 40 | Another fix ( https://github.com/harfbuzz/harfbuzz/pull/1363 ) was merged and first appeared in 2.1.2 . 41 | 42 | You need to build and install harfbuzz with introspection (`./configure --with-gobject --enable-introspection`), and have pygobject 43 | (https://wiki.gnome.org/Projects/PyGObject). The latter should be readily available as pre-packaged on many systems. 44 | 45 | The drawing code requires freetype-py and pycairo, and uses PIL to display the PNG image. 46 | 47 | Highly recommended is pgi-docgen, ( see the HarfBuzz example in https://github.com/pygobject/pgi-docgen/pull/172 ) 48 | to generate the HarfBuzz python API reference documentation. API doc 49 | generation from gobject doc tool is at best described as both incorrect and incomplete 50 | ( https://gitlab.gnome.org/GNOME/gobject-introspection/issues/235 ) . 51 | 52 | # Background 53 | 54 | This comes about from a need for generating figures for the purpose of illustrating /demonstrating complex text layout. 55 | 56 | While playing with Sanskrit ligatures in Devanagari (which has hugh ascender/decender and plenty of empty spaces) 57 | and also Arabic (which can have strokes outside and clipped by the ascender/decender area), I decided that I don't like C++ 58 | ( https://github.com/behdad/harfbuzz/issues/79 ) and thought this kind of tasks should be done by a scripting tool 59 | instead of a compiled one. 60 | 61 | Sanskrit ligatures use diacritics both above and below the main shape extensively, so have hugh ascender/decenders. 62 | The C++ tool's default (with `--background="#000000" --foreground="#FFFFFF"` for clarity) shows this, especially with the 63 | default margin of 16: 64 | 65 | ![upstream-sanskrit](images/sanskrit-ligature1.png) 66 | 67 | Here is the ink-box image from the python tool: 68 | 69 | ![py-sanskrit](images/sanskrit-ligature2.png) 70 | 71 | On the other hand, Arabic writing can go beyond the area declared by ascender/descenders and advance width. 72 | ( `--background="#000000" --foreground="#FFFFFF" --margin=0,0,0,0`). The uneven margins on the 4 directions 73 | would also be difficult to set manually. Here is the Persian word for "HarfBuzz", note the clipping below and on the left-side: 74 | 75 | ![upstream-arabic](images/arabic-cropped.png) 76 | 77 | Here is the ink-box image from the python tool: 78 | 79 | ![py-arabic](images/arabic-boxed.png) 80 | 81 | More than two years before the Devanagari/Arabic activities, I first encountered https://github.com/behdad/harfbuzz/issues/79 with CJK fonts. 82 | When one is generating an image of CJK glyphs, one expect equal margins around the square shapes. 83 | I was surprised by the empty space below, though it is understandable that many CJK fonts have Latin glyphs and have a descender area. 84 | 85 | Don't forget to checkout the [vertical layout](README.vertical.md) images too, and [variation selector examples](variation-selectors.md). 86 | 87 | The C++ `hb-view` also has an inconsistency, special-casing `ansi` output: `ansi` drawings skips blank ascender/descender areas above and 88 | below the ink-box. 89 | -------------------------------------------------------------------------------- /README.vertical.md: -------------------------------------------------------------------------------- 1 | # Vertical layout examples 2 | 3 | Unlike `c++ hb-view`, `hb-view.py` defaults to vertical for CJK, and also the two Mongolian scripts. 4 | 5 | Here are three examples: 6 | 7 | ![mongolian](images/mongolian-example1.png) ![japanese](images/japanese-example.png) ![phags pa](images/phagspa-example2.png) 8 | 9 | The below is from `c++ hb-view`. Note that the small kana is aligned differently in vertical layout; and of course, the rotated 10 | quotation marks: 11 | 12 | ![c++ japanese](images/c++japanese.png) 13 | 14 | Here is a break-down of the individual letters in the Phags Pa example: 15 | 16 | ![phags pa](images/phagspa-long.png) 17 | -------------------------------------------------------------------------------- /bitmap_to_surface.py: -------------------------------------------------------------------------------- 1 | # FT_Bitmap to CAIRO_SURFACE_TYPE_IMAGE module 2 | # ============================================ 3 | # 4 | # Copyright 2017 Hin-Tak Leung 5 | # 6 | # FreeType is under FTL (BSD license with an advertising clause) or GPLv2+. 7 | # Cairo is under LGPLv2 or MPLv1.1. 8 | # 9 | # This is a heavily modified copy of a few routines from Lawrence D'Oliveiro[1], 10 | # adjusted for freetype-py, and bugfix/workaround for mono-rendering [2]. 11 | # 12 | # The bugfix/workaround requires libtiff on small-endian platforms. 13 | # 14 | # TODO: Look into using FreeType's FT_Bitmap_Convert() instead. However, libtiff 15 | # is common enough, and probably not important. 16 | # 17 | #[1] https://github.com/ldo/python_freetype 18 | # https://github.com/ldo/python_freetype_examples 19 | # 20 | #[2] https://github.com/ldo/python_freetype/issues/1 21 | # https://github.com/ldo/python_freetype_examples/issues/1 22 | # 23 | ''' 24 | FT_Bitmap to CAIRO_SURFACE_TYPE_IMAGE module 25 | ============================================ 26 | 27 | Converting from Freetype's FT_Bitmap to Cairo's CAIRO_SURFACE_TYPE_IMAGE 28 | 29 | Usage: 30 | from bitmap_to_surface import make_image_surface 31 | 32 | Works with cairocffi too. (Replace "from cairo ..." with "from cairocffi ...") 33 | 34 | Limitation: Surface.create_for_data is not in the "python 3, pycairo < 1.11" combo. 35 | ''' 36 | from freetype import FT_PIXEL_MODE_MONO, FT_PIXEL_MODE_GRAY, FT_PIXEL_MODE_BGRA, FT_Pointer, FT_Bitmap 37 | #from freetype import get_handle, FT_Bitmap_Init, FT_Bitmap_Convert, FT_Exception 38 | 39 | from cairo import ImageSurface, FORMAT_A1, FORMAT_A8, FORMAT_ARGB32 40 | #from cairocffi import ImageSurface, FORMAT_A1, FORMAT_A8, FORMAT_ARGB32 41 | 42 | from array import array 43 | from ctypes import cast, memmove, CDLL, c_void_p, c_int, byref 44 | from sys import byteorder 45 | 46 | def make_image_surface(bitmap, copy = True) : 47 | if ( type(bitmap) == FT_Bitmap ): 48 | # bare FT_Bitmap 49 | content = bitmap 50 | else: 51 | # wrapped Bitmap instance 52 | content = bitmap._FT_Bitmap 53 | "creates a Cairo ImageSurface containing (a copy of) the Bitmap pixels." 54 | if ( False and ( byteorder == 'little' ) and (content.pixel_mode == FT_PIXEL_MODE_MONO ) ): 55 | try: 56 | libtiff = CDLL("not") 57 | except: 58 | library = get_handle( ) 59 | dest = c_void_p() 60 | error = FT_Bitmap_Init( dest ) 61 | if error: raise FT_Exception( error ) 62 | error = FT_Bitmap_Convert( library, byref(content), 63 | dest, 1 ) 64 | if error: raise FT_Exception( error ) 65 | content = dest 66 | if content.pixel_mode == FT_PIXEL_MODE_MONO : 67 | cairo_format = FORMAT_A1 68 | elif content.pixel_mode == FT_PIXEL_MODE_GRAY : 69 | cairo_format = FORMAT_A8 70 | elif content.pixel_mode == FT_PIXEL_MODE_BGRA : # small-endian 71 | cairo_format = FORMAT_ARGB32 # platform native 72 | else : 73 | raise NotImplementedError("unsupported bitmap format %d" % content.pixel_mode) 74 | src_pitch = content.pitch 75 | dst_pitch = ImageSurface.format_stride_for_width(cairo_format, content.width) 76 | if not copy and dst_pitch == src_pitch and content.buffer != None : 77 | pixels = content.buffer 78 | else : 79 | pixels = to_array(content, content.pixel_mode, dst_pitch) 80 | result = ImageSurface.create_for_data( 81 | pixels, cairo_format, 82 | content.width, content.rows, 83 | dst_pitch) 84 | return result 85 | 86 | def to_array(content, pixel_mode, dst_pitch = None) : 87 | "returns a Python array object containing a copy of the Bitmap pixels." 88 | if dst_pitch == None : 89 | dst_pitch = content.pitch 90 | buffer_size = content.rows * dst_pitch 91 | buffer = array("B", b"0" * buffer_size) 92 | dstaddr = buffer.buffer_info()[0] 93 | srcaddr = cast(content.buffer, FT_Pointer).value 94 | src_pitch = content.pitch 95 | if dst_pitch == src_pitch : 96 | memmove(dstaddr, srcaddr, buffer_size) 97 | else : 98 | # have to copy a row at a time 99 | if src_pitch < 0 or dst_pitch < 0 : 100 | raise NotImplementedError("can't cope with negative bitmap pitch") 101 | assert dst_pitch > src_pitch 102 | for i in range(content.rows) : 103 | memmove(dstaddr, srcaddr, src_pitch) 104 | dstaddr += dst_pitch 105 | srcaddr += src_pitch 106 | # pillow/PIL itself requires libtiff so it is assumed to be around. 107 | # swap the bit-order from freetype's (MSB) to cairo's (host order) if needed 108 | if ( ( byteorder == 'little' ) and (pixel_mode == FT_PIXEL_MODE_MONO ) ): 109 | libtiff = CDLL("libtiff.so.5") 110 | libtiff.TIFFReverseBits.restype = None 111 | libtiff.TIFFReverseBits.argtypes = (c_void_p, c_int) 112 | libtiff.TIFFReverseBits(buffer.buffer_info()[0], buffer_size) 113 | return buffer 114 | -------------------------------------------------------------------------------- /gen-images.sh: -------------------------------------------------------------------------------- 1 | ./hb-view.py phagspa.ttf 'ꡏ + ꡡ + ꡃ ꡣ + ꡡ + ꡙ ꡐ + ꡜ + ꡞ = ꡏꡡꡃ ꡣꡡꡙ ꡐꡜꡞ' 2 | mv hb-view.png phagspa-example1.png 3 | ./hb-view.py phagspa.ttf 'ꡏꡡꡃ ꡣꡡꡙ ꡐꡜꡞ' 4 | mv hb-view.png phagspa-example2.png 5 | ./hb-view.py monbaiti.ttf 'ᠴᠢᠩᠭᠢᠰ ᠬᠠᠭᠠᠨ' 6 | mv hb-view.png mongolian-example1.png 7 | ./hb-view.py msmincho.ttc '《日本ょ》' 8 | mv hb-view.png japanese-example.png 9 | ./hb-view.py Lohit-Devanagari.ttf 'द + ् + ध + ् + र + ् + य = द्ध्र्य' 10 | mv hb-view.png sanskrit-ligature2.png 11 | ./hb-view.py KacstFarsi.ttf 'حرف‌باز' 12 | mv hb-view.png arabic-boxed.png 13 | ./hb-view.py LaylaThuluth.ttf "حرف‌باز" 14 | mv hb-view.png another-harbuzz.png 15 | ./hb-view.py phagspa.ttf 'ꡏ + ꡡ + ꡃ ꡣ + ꡡ + ꡙ ꡐ + ꡜ + ꡞ = ꡏꡡꡃ ꡣꡡꡙ ꡐꡜꡞ' 16 | mv hb-view.png phagspa-long.png 17 | -------------------------------------------------------------------------------- /hb-view-ot-svg-skia-GL.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ----------------------------------------------------------------------------- 4 | # 5 | # Copyright 2018 Hin-Tak Leung 6 | # Distributed under the terms of the new BSD license. 7 | # 8 | # 9 | # Significant portion of this code came from harfbuzz:src/sample.py by 10 | # Behdad Esfahbod, and freetype-py:examples/hello-world-cairo.py by 11 | # Hin-Tak Leung. 12 | # 13 | # Usage: 14 | # python hb-view.py fontname.ttf "word phrase" 15 | # 16 | # Note: change "emulate_default" a few lines below to "True" for 17 | # ascender/decender box. 18 | # 19 | # ----------------------------------------------------------------------------- 20 | 21 | from __future__ import print_function, division, absolute_import 22 | 23 | # Change this to True to get the default uptream size 24 | emulate_default = False 25 | 26 | # wantTTB is auto-on for CJK, and wantRotate is on for Mongolian/Phags Pa. 27 | wantTTB = False 28 | wantRotate = False 29 | 30 | import sys 31 | import array 32 | import gi 33 | gi.require_version('HarfBuzz', '0.0') 34 | 35 | from gi.repository import HarfBuzz as hb 36 | from gi.repository import GLib 37 | 38 | # Python 2/3 compatibility 39 | try: 40 | unicode 41 | except NameError: 42 | unicode = str 43 | 44 | def tounicode(s, encoding='utf-8'): 45 | if not isinstance(s, unicode): 46 | return s.decode(encoding) 47 | else: 48 | return s 49 | 50 | if (hb.version_atleast(2,1,2)): 51 | pass 52 | else: 53 | raise RuntimeError('HarfBuzz too old') 54 | 55 | ##################################################################### 56 | ### Derived from harfbuzz:src/sample.py 57 | 58 | fontdata = open (sys.argv[1], 'rb').read () 59 | text = tounicode(sys.argv[2]) 60 | # Need to create GLib.Bytes explicitly until this bug is fixed: 61 | # https://bugzilla.gnome.org/show_bug.cgi?id=729541 62 | blob = hb.glib_blob_create (GLib.Bytes.new (fontdata)) 63 | face = hb.face_create (blob, 0) 64 | del blob 65 | font = hb.font_create (face) 66 | upem = hb.face_get_upem (face) 67 | del face 68 | hb.font_set_scale (font, upem, upem) 69 | # select "ft" or "ot" for get the right margins - see: 70 | # https://github.com/harfbuzz/harfbuzz/issues/1248 71 | # https://github.com/harfbuzz/harfbuzz/issues/1262 72 | # and also 73 | # https://github.com/harfbuzz/harfbuzz/issues/537 74 | hb.ft_font_set_funcs(font) 75 | #hb.ot_font_set_funcs (font) 76 | 77 | font_size = 256 78 | buf = hb.buffer_create () 79 | class Debugger(object): 80 | def message (self, buf, font, msg, data, _x_what_is_this): 81 | print(msg) 82 | return True 83 | debugger = Debugger() 84 | #hb.buffer_set_message_func (buf, debugger.message, 1, 0) 85 | 86 | ## 87 | ## Add text to buffer 88 | ## 89 | # 90 | # See https://github.com/harfbuzz/harfbuzz/pull/271 91 | # 92 | if False: 93 | # If you do not care about cluster values reflecting Python 94 | # string indices, then this is quickest way to add text to 95 | # buffer: 96 | hb.buffer_add_utf8 (buf, text.encode('utf-8'), 0, -1) 97 | # Otherwise, then following handles both narrow and wide 98 | # Python builds (the first item in the array is BOM, so we skip it): 99 | elif sys.maxunicode == 0x10FFFF: 100 | hb.buffer_add_utf32 (buf, array.array('I', text.encode('utf-32'))[1:], 0, -1) 101 | else: 102 | hb.buffer_add_utf16 (buf, array.array('H', text.encode('utf-16'))[1:], 0, -1) 103 | 104 | 105 | hb.buffer_guess_segment_properties (buf) 106 | if ((hb.buffer_get_script(buf) == hb.script_t.MONGOLIAN) or (hb.buffer_get_script(buf) == hb.script_t.PHAGS_PA)): 107 | wantRotate = True 108 | if (hb.buffer_get_script(buf) == hb.script_t.HAN): 109 | hb.buffer_set_direction(buf, hb.direction_t.TTB) 110 | wantTTB = True 111 | 112 | hb.shape (font, buf, []) 113 | font_extents = hb.font_get_extents_for_direction(font, hb.buffer_get_direction(buf)) 114 | font_height = font_extents.ascender - font_extents.descender + font_extents.line_gap 115 | 116 | infos = hb.buffer_get_glyph_infos (buf) 117 | positions = hb.buffer_get_glyph_positions (buf) 118 | 119 | x = 0 120 | y = 0 121 | glyph_extents = list() 122 | min_ix = upem 123 | max_ix = -upem 124 | min_iy = upem 125 | max_iy = -upem 126 | 127 | for info,pos in zip(infos, positions): 128 | gid = info.codepoint 129 | cluster = info.cluster 130 | x_advance = pos.x_advance 131 | y_advance = pos.y_advance 132 | x_offset = pos.x_offset 133 | y_offset = pos.y_offset 134 | 135 | print("gid%d=%d@%d,%d+%d" % (gid, cluster, x_advance, x_offset, y_offset)) 136 | 137 | ### Derived from harfbuzz:src/sample.py ends. 138 | ##################################################################### 139 | 140 | (results, extents) = hb.font_get_glyph_extents(font, info.codepoint) 141 | glyph_extents.append(extents) 142 | if ((extents.width != 0) and (extents.height !=0)): 143 | # don't want invisible glyph to pin the ink box 144 | # https://github.com/harfbuzz/harfbuzz/issues/1208 145 | # https://github.com/harfbuzz/harfbuzz/issues/1216 146 | min_ix = min(min_ix, x + pos.x_offset + extents.x_bearing) 147 | max_ix = max(max_ix, x + pos.x_offset + extents.x_bearing + extents.width) 148 | max_iy = max(max_iy, y + pos.y_offset + extents.y_bearing) 149 | min_iy = min(min_iy, y + pos.y_offset + extents.y_bearing + extents.height) 150 | x += x_advance 151 | y += y_advance 152 | 153 | def sc(value): 154 | return (value * font_size)/upem 155 | 156 | class Margin: 157 | def __init__(self, top, right, bottom, left): 158 | self.top = top 159 | self.right = right 160 | self.bottom = bottom 161 | self.left = left 162 | 163 | if (not wantTTB): 164 | # (top,right,bottom,left) 165 | # default is: font_extents.ascender, x, font_extents.descender, 0 166 | _margin = Margin(sc(max_iy - font_extents.ascender), 167 | sc(max_ix - x), 168 | sc(font_extents.descender - min_iy), 169 | -sc(min_ix)) 170 | 171 | print("default:", sc(x) + 32, sc(font_height) + 32) 172 | else: 173 | # (top,right,bottom,left) 174 | # default is: 0, font_height + positions[0].x_offset, y, positions[0].x_offset 175 | _margin = Margin(sc(max_iy), 176 | sc(max_ix - font_height - positions[0].x_offset), 177 | sc(y - min_iy), 178 | -sc(min_ix - positions[0].x_offset)) 179 | 180 | print("default:", sc(font_height) + 32, sc(-y) + 32) 181 | 182 | print("ink box:", sc(max_ix - min_ix), sc(max_iy - min_iy)) 183 | print("margin:", 184 | _margin.top, 185 | _margin.right, 186 | _margin.bottom, 187 | _margin.left) 188 | margin = "--margin=%f %f %f %f" % (_margin.top, 189 | _margin.right, 190 | _margin.bottom, 191 | _margin.left) 192 | del font 193 | 194 | (width,height) = (sc(max_ix - min_ix), sc(max_iy - min_iy)) 195 | if (emulate_default): 196 | (height,width) = ((font_height * font_size)/upem + 32, (x * font_size)/upem + 32) 197 | 198 | ##################################################################### 199 | ### Derived from freetype-py:examples/hello-world-cairo.py 200 | 201 | from freetype import * 202 | 203 | from skia_glfw_module import glfw_window, skia_surface 204 | 205 | face = Face(sys.argv[1]) 206 | face.set_char_size( font_size*64 ) 207 | 208 | from skia_ot_svg_module import hooks 209 | import skia 210 | from skia import ImageInfo, ColorType, AlphaType 211 | import glfw 212 | 213 | library = get_handle() 214 | FT_Property_Set( library, b"ot-svg", b"svg-hooks", byref(hooks) ) # python 3 only syntax 215 | 216 | if (not wantRotate): 217 | WIDTH, HEIGHT = int(round(width+0.5)), int(round(height+0.5)) 218 | else: 219 | WIDTH, HEIGHT = int(round(height+0.5)), int(round(width+0.5)) 220 | 221 | with glfw_window(WIDTH, HEIGHT) as window: 222 | if (wantRotate): 223 | ctx.set_matrix(Matrix(xx=0.0,xy=-1.0,yx=1.0,yy=0.0,x0=height)) 224 | x, y = -sc(min_ix), height + sc(min_iy) 225 | if (emulate_default): 226 | x = 16 227 | y = sc(font_extents.asscender) + 16 228 | if (wantTTB): 229 | x = sc(max_ix) 230 | y = sc(max_iy) 231 | with skia_surface(window) as surface: 232 | with surface as canvas: 233 | for info,pos,extent in zip(infos, positions, glyph_extents): 234 | face.load_glyph(info.codepoint, FT_LOAD_COLOR | FT_LOAD_RENDER) 235 | x += sc(extent.x_bearing + pos.x_offset) 236 | y -= sc(extent.y_bearing + pos.y_offset) 237 | if (face.glyph.bitmap.width > 0): 238 | glyphBitmap = skia.Bitmap() 239 | bitmap = face.glyph.bitmap 240 | glyphBitmap.setInfo(ImageInfo.Make(bitmap.width, bitmap.rows, 241 | ColorType.kBGRA_8888_ColorType, 242 | AlphaType.kPremul_AlphaType), 243 | bitmap.pitch) 244 | glyphBitmap.setPixels(pythonapi.PyMemoryView_FromMemory(cast(bitmap._FT_Bitmap.buffer, c_char_p), 245 | bitmap.rows * bitmap.pitch, 246 | 0x200), # Read-Write 247 | ) 248 | canvas.drawBitmap(glyphBitmap, x, y) 249 | x += sc(pos.x_advance - extent.x_bearing - pos.x_offset) 250 | y -= sc(pos.y_advance - extent.y_bearing - pos.y_offset) 251 | surface.flushAndSubmit() 252 | image = surface.makeImageSnapshot() 253 | image.save("hb-view-ot-svg-skia-GL.png", skia.kPNG) 254 | glfw.swap_buffers(window) 255 | while (glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS 256 | and not glfw.window_should_close(window)): 257 | glfw.wait_events() 258 | ### Derived from freetype-py:examples/hello-world-cairo.py ends. 259 | ##################################################################### 260 | -------------------------------------------------------------------------------- /hb-view-ot-svg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ----------------------------------------------------------------------------- 4 | # 5 | # Copyright 2018 Hin-Tak Leung 6 | # Distributed under the terms of the new BSD license. 7 | # 8 | # 9 | # Significant portion of this code came from harfbuzz:src/sample.py by 10 | # Behdad Esfahbod, and freetype-py:examples/hello-world-cairo.py by 11 | # Hin-Tak Leung. 12 | # 13 | # Usage: 14 | # python hb-view.py fontname.ttf "word phrase" 15 | # 16 | # Note: change "emulate_default" a few lines below to "True" for 17 | # ascender/decender box. 18 | # 19 | # ----------------------------------------------------------------------------- 20 | 21 | from __future__ import print_function, division, absolute_import 22 | 23 | # Change this to True to get the default uptream size 24 | emulate_default = False 25 | 26 | # wantTTB is auto-on for CJK, and wantRotate is on for Mongolian/Phags Pa. 27 | wantTTB = False 28 | wantRotate = False 29 | 30 | import sys 31 | import array 32 | import gi 33 | gi.require_version('HarfBuzz', '0.0') 34 | 35 | from gi.repository import HarfBuzz as hb 36 | from gi.repository import GLib 37 | 38 | # Python 2/3 compatibility 39 | try: 40 | unicode 41 | except NameError: 42 | unicode = str 43 | 44 | def tounicode(s, encoding='utf-8'): 45 | if not isinstance(s, unicode): 46 | return s.decode(encoding) 47 | else: 48 | return s 49 | 50 | if (hb.version_atleast(2,1,2)): 51 | pass 52 | else: 53 | raise RuntimeError('HarfBuzz too old') 54 | 55 | ##################################################################### 56 | ### Derived from harfbuzz:src/sample.py 57 | 58 | fontdata = open (sys.argv[1], 'rb').read () 59 | text = tounicode(sys.argv[2]) 60 | # Need to create GLib.Bytes explicitly until this bug is fixed: 61 | # https://bugzilla.gnome.org/show_bug.cgi?id=729541 62 | blob = hb.glib_blob_create (GLib.Bytes.new (fontdata)) 63 | face = hb.face_create (blob, 0) 64 | del blob 65 | font = hb.font_create (face) 66 | upem = hb.face_get_upem (face) 67 | del face 68 | hb.font_set_scale (font, upem, upem) 69 | # select "ft" or "ot" for get the right margins - see: 70 | # https://github.com/harfbuzz/harfbuzz/issues/1248 71 | # https://github.com/harfbuzz/harfbuzz/issues/1262 72 | # and also 73 | # https://github.com/harfbuzz/harfbuzz/issues/537 74 | hb.ft_font_set_funcs(font) 75 | #hb.ot_font_set_funcs (font) 76 | 77 | font_size = 256 78 | buf = hb.buffer_create () 79 | class Debugger(object): 80 | def message (self, buf, font, msg, data, _x_what_is_this): 81 | print(msg) 82 | return True 83 | debugger = Debugger() 84 | #hb.buffer_set_message_func (buf, debugger.message, 1, 0) 85 | 86 | ## 87 | ## Add text to buffer 88 | ## 89 | # 90 | # See https://github.com/harfbuzz/harfbuzz/pull/271 91 | # 92 | if False: 93 | # If you do not care about cluster values reflecting Python 94 | # string indices, then this is quickest way to add text to 95 | # buffer: 96 | hb.buffer_add_utf8 (buf, text.encode('utf-8'), 0, -1) 97 | # Otherwise, then following handles both narrow and wide 98 | # Python builds (the first item in the array is BOM, so we skip it): 99 | elif sys.maxunicode == 0x10FFFF: 100 | hb.buffer_add_utf32 (buf, array.array('I', text.encode('utf-32'))[1:], 0, -1) 101 | else: 102 | hb.buffer_add_utf16 (buf, array.array('H', text.encode('utf-16'))[1:], 0, -1) 103 | 104 | 105 | hb.buffer_guess_segment_properties (buf) 106 | if ((hb.buffer_get_script(buf) == hb.script_t.MONGOLIAN) or (hb.buffer_get_script(buf) == hb.script_t.PHAGS_PA)): 107 | wantRotate = True 108 | if (hb.buffer_get_script(buf) == hb.script_t.HAN): 109 | hb.buffer_set_direction(buf, hb.direction_t.TTB) 110 | wantTTB = True 111 | 112 | hb.shape (font, buf, []) 113 | font_extents = hb.font_get_extents_for_direction(font, hb.buffer_get_direction(buf)) 114 | font_height = font_extents.ascender - font_extents.descender + font_extents.line_gap 115 | 116 | infos = hb.buffer_get_glyph_infos (buf) 117 | positions = hb.buffer_get_glyph_positions (buf) 118 | 119 | x = 0 120 | y = 0 121 | glyph_extents = list() 122 | min_ix = upem 123 | max_ix = -upem 124 | min_iy = upem 125 | max_iy = -upem 126 | 127 | for info,pos in zip(infos, positions): 128 | gid = info.codepoint 129 | cluster = info.cluster 130 | x_advance = pos.x_advance 131 | y_advance = pos.y_advance 132 | x_offset = pos.x_offset 133 | y_offset = pos.y_offset 134 | 135 | print("gid%d=%d@%d,%d+%d" % (gid, cluster, x_advance, x_offset, y_offset)) 136 | 137 | ### Derived from harfbuzz:src/sample.py ends. 138 | ##################################################################### 139 | 140 | (results, extents) = hb.font_get_glyph_extents(font, info.codepoint) 141 | glyph_extents.append(extents) 142 | if ((extents.width != 0) and (extents.height !=0)): 143 | # don't want invisible glyph to pin the ink box 144 | # https://github.com/harfbuzz/harfbuzz/issues/1208 145 | # https://github.com/harfbuzz/harfbuzz/issues/1216 146 | min_ix = min(min_ix, x + pos.x_offset + extents.x_bearing) 147 | max_ix = max(max_ix, x + pos.x_offset + extents.x_bearing + extents.width) 148 | max_iy = max(max_iy, y + pos.y_offset + extents.y_bearing) 149 | min_iy = min(min_iy, y + pos.y_offset + extents.y_bearing + extents.height) 150 | x += x_advance 151 | y += y_advance 152 | 153 | def sc(value): 154 | return (value * font_size)/upem 155 | 156 | class Margin: 157 | def __init__(self, top, right, bottom, left): 158 | self.top = top 159 | self.right = right 160 | self.bottom = bottom 161 | self.left = left 162 | 163 | if (not wantTTB): 164 | # (top,right,bottom,left) 165 | # default is: font_extents.ascender, x, font_extents.descender, 0 166 | _margin = Margin(sc(max_iy - font_extents.ascender), 167 | sc(max_ix - x), 168 | sc(font_extents.descender - min_iy), 169 | -sc(min_ix)) 170 | 171 | print("default:", sc(x) + 32, sc(font_height) + 32) 172 | else: 173 | # (top,right,bottom,left) 174 | # default is: 0, font_height + positions[0].x_offset, y, positions[0].x_offset 175 | _margin = Margin(sc(max_iy), 176 | sc(max_ix - font_height - positions[0].x_offset), 177 | sc(y - min_iy), 178 | -sc(min_ix - positions[0].x_offset)) 179 | 180 | print("default:", sc(font_height) + 32, sc(-y) + 32) 181 | 182 | print("ink box:", sc(max_ix - min_ix), sc(max_iy - min_iy)) 183 | print("margin:", 184 | _margin.top, 185 | _margin.right, 186 | _margin.bottom, 187 | _margin.left) 188 | margin = "--margin=%f %f %f %f" % (_margin.top, 189 | _margin.right, 190 | _margin.bottom, 191 | _margin.left) 192 | del font 193 | 194 | (width,height) = (sc(max_ix - min_ix), sc(max_iy - min_iy)) 195 | if (emulate_default): 196 | (height,width) = ((font_height * font_size)/upem + 32, (x * font_size)/upem + 32) 197 | 198 | ##################################################################### 199 | ### Derived from freetype-py:examples/hello-world-cairo.py 200 | 201 | from freetype import * 202 | 203 | # cairo.Matrix shadows freetype.Matrix 204 | from cairo import Context, ImageSurface, FORMAT_ARGB32, Matrix 205 | from bitmap_to_surface import make_image_surface 206 | from PIL import Image 207 | 208 | face = Face(sys.argv[1]) 209 | face.set_char_size( font_size*64 ) 210 | 211 | from otsvg import hooks 212 | 213 | library = get_handle() 214 | FT_Property_Set( library, b"ot-svg", b"svg-hooks", byref(hooks) ) # python 3 only syntax 215 | 216 | if (not wantRotate): 217 | Z = ImageSurface(FORMAT_ARGB32, int(round(width+0.5)), int(round(height+0.5))) 218 | else: 219 | Z = ImageSurface(FORMAT_ARGB32, int(round(height+0.5)), int(round(width+0.5))) 220 | ctx = Context(Z) 221 | 222 | if (wantRotate): 223 | ctx.set_matrix(Matrix(xx=0.0,xy=-1.0,yx=1.0,yy=0.0,x0=height)) 224 | # Second pass for actual rendering 225 | ## x is occationally not "-sc(glyph_extents[0].x_bearing + positions[0].x_offset)": 226 | ## If the first character contains sub-glyphs! 227 | x, y = -sc(min_ix), height + sc(min_iy) 228 | if (emulate_default): 229 | x = 16 230 | y = sc(font_extents.asscender) + 16 231 | if (wantTTB): 232 | x = sc(max_ix) 233 | y = sc(max_iy) 234 | for info,pos,extent in zip(infos, positions, glyph_extents): 235 | face.load_glyph(info.codepoint, FT_LOAD_COLOR | FT_LOAD_RENDER) 236 | x += sc(extent.x_bearing + pos.x_offset) 237 | y -= sc(extent.y_bearing + pos.y_offset) 238 | # cairo does not like zero-width bitmap from the space character! 239 | if (face.glyph.bitmap.width > 0): 240 | glyph_surface = make_image_surface(face.glyph.bitmap) 241 | ctx.set_source_surface(glyph_surface, x, y) 242 | ctx.paint() 243 | x += sc(pos.x_advance - extent.x_bearing - pos.x_offset) 244 | y -= sc(pos.y_advance - extent.y_bearing - pos.y_offset) 245 | Z.flush() 246 | Z.write_to_png("hb-view.png") 247 | Image.open("hb-view.png").show() 248 | 249 | ### Derived from freetype-py:examples/hello-world-cairo.py ends. 250 | ##################################################################### 251 | -------------------------------------------------------------------------------- /hb-view.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ----------------------------------------------------------------------------- 4 | # 5 | # Copyright 2018 Hin-Tak Leung 6 | # Distributed under the terms of the new BSD license. 7 | # 8 | # 9 | # Significant portion of this code came from harfbuzz:src/sample.py by 10 | # Behdad Esfahbod, and freetype-py:examples/hello-world-cairo.py by 11 | # Hin-Tak Leung. 12 | # 13 | # Usage: 14 | # python hb-view.py fontname.ttf "word phrase" 15 | # 16 | # Note: change "emulate_default" a few lines below to "True" for 17 | # ascender/decender box. 18 | # 19 | # ----------------------------------------------------------------------------- 20 | 21 | from __future__ import print_function, division, absolute_import 22 | 23 | # Change this to True to get the default uptream size 24 | emulate_default = False 25 | 26 | # wantTTB is auto-on for CJK, and wantRotate is on for Mongolian/Phags Pa. 27 | wantTTB = False 28 | wantRotate = False 29 | 30 | import sys 31 | import array 32 | import gi 33 | gi.require_version('HarfBuzz', '0.0') 34 | 35 | from gi.repository import HarfBuzz as hb 36 | from gi.repository import GLib 37 | 38 | # Python 2/3 compatibility 39 | try: 40 | unicode 41 | except NameError: 42 | unicode = str 43 | 44 | def tounicode(s, encoding='utf-8'): 45 | if not isinstance(s, unicode): 46 | return s.decode(encoding) 47 | else: 48 | return s 49 | 50 | if (hb.version_atleast(2,1,2)): 51 | pass 52 | else: 53 | raise RuntimeError('HarfBuzz too old') 54 | 55 | ##################################################################### 56 | ### Derived from harfbuzz:src/sample.py 57 | 58 | fontdata = open (sys.argv[1], 'rb').read () 59 | text = tounicode(sys.argv[2]) 60 | # Need to create GLib.Bytes explicitly until this bug is fixed: 61 | # https://bugzilla.gnome.org/show_bug.cgi?id=729541 62 | blob = hb.glib_blob_create (GLib.Bytes.new (fontdata)) 63 | face = hb.face_create (blob, 0) 64 | del blob 65 | font = hb.font_create (face) 66 | upem = hb.face_get_upem (face) 67 | del face 68 | hb.font_set_scale (font, upem, upem) 69 | # select "ft" or "ot" for get the right margins - see: 70 | # https://github.com/harfbuzz/harfbuzz/issues/1248 71 | # https://github.com/harfbuzz/harfbuzz/issues/1262 72 | # and also 73 | # https://github.com/harfbuzz/harfbuzz/issues/537 74 | hb.ft_font_set_funcs(font) 75 | #hb.ot_font_set_funcs (font) 76 | 77 | font_size = 256 78 | buf = hb.buffer_create () 79 | class Debugger(object): 80 | def message (self, buf, font, msg, data, _x_what_is_this): 81 | print(msg) 82 | return True 83 | debugger = Debugger() 84 | #hb.buffer_set_message_func (buf, debugger.message, 1, 0) 85 | 86 | ## 87 | ## Add text to buffer 88 | ## 89 | # 90 | # See https://github.com/harfbuzz/harfbuzz/pull/271 91 | # 92 | if False: 93 | # If you do not care about cluster values reflecting Python 94 | # string indices, then this is quickest way to add text to 95 | # buffer: 96 | hb.buffer_add_utf8 (buf, text.encode('utf-8'), 0, -1) 97 | # Otherwise, then following handles both narrow and wide 98 | # Python builds (the first item in the array is BOM, so we skip it): 99 | elif sys.maxunicode == 0x10FFFF: 100 | hb.buffer_add_utf32 (buf, array.array('I', text.encode('utf-32'))[1:], 0, -1) 101 | else: 102 | hb.buffer_add_utf16 (buf, array.array('H', text.encode('utf-16'))[1:], 0, -1) 103 | 104 | 105 | hb.buffer_guess_segment_properties (buf) 106 | if ((hb.buffer_get_script(buf) == hb.script_t.MONGOLIAN) or (hb.buffer_get_script(buf) == hb.script_t.PHAGS_PA)): 107 | wantRotate = True 108 | if (hb.buffer_get_script(buf) == hb.script_t.HAN): 109 | hb.buffer_set_direction(buf, hb.direction_t.TTB) 110 | wantTTB = True 111 | 112 | hb.shape (font, buf, []) 113 | font_extents = hb.font_get_extents_for_direction(font, hb.buffer_get_direction(buf)) 114 | font_height = font_extents.ascender - font_extents.descender + font_extents.line_gap 115 | 116 | infos = hb.buffer_get_glyph_infos (buf) 117 | positions = hb.buffer_get_glyph_positions (buf) 118 | 119 | x = 0 120 | y = 0 121 | glyph_extents = list() 122 | min_ix = upem 123 | max_ix = -upem 124 | min_iy = upem 125 | max_iy = -upem 126 | 127 | for info,pos in zip(infos, positions): 128 | gid = info.codepoint 129 | cluster = info.cluster 130 | x_advance = pos.x_advance 131 | y_advance = pos.y_advance 132 | x_offset = pos.x_offset 133 | y_offset = pos.y_offset 134 | 135 | print("gid%d=%d@%d,%d+%d" % (gid, cluster, x_advance, x_offset, y_offset)) 136 | 137 | ### Derived from harfbuzz:src/sample.py ends. 138 | ##################################################################### 139 | 140 | (results, extents) = hb.font_get_glyph_extents(font, info.codepoint) 141 | glyph_extents.append(extents) 142 | if ((extents.width != 0) and (extents.height !=0)): 143 | # don't want invisible glyph to pin the ink box 144 | # https://github.com/harfbuzz/harfbuzz/issues/1208 145 | # https://github.com/harfbuzz/harfbuzz/issues/1216 146 | min_ix = min(min_ix, x + pos.x_offset + extents.x_bearing) 147 | max_ix = max(max_ix, x + pos.x_offset + extents.x_bearing + extents.width) 148 | max_iy = max(max_iy, y + pos.y_offset + extents.y_bearing) 149 | min_iy = min(min_iy, y + pos.y_offset + extents.y_bearing + extents.height) 150 | x += x_advance 151 | y += y_advance 152 | 153 | def sc(value): 154 | return (value * font_size)/upem 155 | 156 | class Margin: 157 | def __init__(self, top, right, bottom, left): 158 | self.top = top 159 | self.right = right 160 | self.bottom = bottom 161 | self.left = left 162 | 163 | if (not wantTTB): 164 | # (top,right,bottom,left) 165 | # default is: font_extents.ascender, x, font_extents.descender, 0 166 | _margin = Margin(sc(max_iy - font_extents.ascender), 167 | sc(max_ix - x), 168 | sc(font_extents.descender - min_iy), 169 | -sc(min_ix)) 170 | 171 | print("default:", sc(x) + 32, sc(font_height) + 32) 172 | else: 173 | # (top,right,bottom,left) 174 | # default is: 0, font_height + positions[0].x_offset, y, positions[0].x_offset 175 | _margin = Margin(sc(max_iy), 176 | sc(max_ix - font_height - positions[0].x_offset), 177 | sc(y - min_iy), 178 | -sc(min_ix - positions[0].x_offset)) 179 | 180 | print("default:", sc(font_height) + 32, sc(-y) + 32) 181 | 182 | print("ink box:", sc(max_ix - min_ix), sc(max_iy - min_iy)) 183 | print("margin:", 184 | _margin.top, 185 | _margin.right, 186 | _margin.bottom, 187 | _margin.left) 188 | margin = "--margin=%f %f %f %f" % (_margin.top, 189 | _margin.right, 190 | _margin.bottom, 191 | _margin.left) 192 | del font 193 | 194 | (width,height) = (sc(max_ix - min_ix), sc(max_iy - min_iy)) 195 | if (emulate_default): 196 | (height,width) = ((font_height * font_size)/upem + 32, (x * font_size)/upem + 32) 197 | 198 | ##################################################################### 199 | ### Derived from freetype-py:examples/hello-world-cairo.py 200 | 201 | from freetype import * 202 | 203 | # cairo.Matrix shadows freetype.Matrix 204 | from cairo import Context, ImageSurface, FORMAT_A8, Matrix 205 | from bitmap_to_surface import make_image_surface 206 | from PIL import Image 207 | 208 | face = Face(sys.argv[1]) 209 | face.set_char_size( font_size*64 ) 210 | 211 | if (not wantRotate): 212 | Z = ImageSurface(FORMAT_A8, int(round(width+0.5)), int(round(height+0.5))) 213 | else: 214 | Z = ImageSurface(FORMAT_A8, int(round(height+0.5)), int(round(width+0.5))) 215 | ctx = Context(Z) 216 | 217 | if (wantRotate): 218 | ctx.set_matrix(Matrix(xx=0.0,xy=-1.0,yx=1.0,yy=0.0,x0=height)) 219 | # Second pass for actual rendering 220 | ## x is occationally not "-sc(glyph_extents[0].x_bearing + positions[0].x_offset)": 221 | ## If the first character contains sub-glyphs! 222 | x, y = -sc(min_ix), height + sc(min_iy) 223 | if (emulate_default): 224 | x = 16 225 | y = sc(font_extents.asscender) + 16 226 | if (wantTTB): 227 | x = sc(max_ix) 228 | y = sc(max_iy) 229 | for info,pos,extent in zip(infos, positions, glyph_extents): 230 | face.load_glyph(info.codepoint) 231 | x += sc(extent.x_bearing + pos.x_offset) 232 | y -= sc(extent.y_bearing + pos.y_offset) 233 | # cairo does not like zero-width bitmap from the space character! 234 | if (face.glyph.bitmap.width > 0): 235 | glyph_surface = make_image_surface(face.glyph.bitmap) 236 | ctx.set_source_surface(glyph_surface, x, y) 237 | ctx.paint() 238 | x += sc(pos.x_advance - extent.x_bearing - pos.x_offset) 239 | y -= sc(pos.y_advance - extent.y_bearing - pos.y_offset) 240 | Z.flush() 241 | Z.write_to_png("hb-view.png") 242 | Image.open("hb-view.png").show() 243 | 244 | ### Derived from freetype-py:examples/hello-world-cairo.py ends. 245 | ##################################################################### 246 | -------------------------------------------------------------------------------- /images/NotoSansMongolian-Regular-bare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/NotoSansMongolian-Regular-bare.png -------------------------------------------------------------------------------- /images/NotoSansMongolian-Regular-form0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/NotoSansMongolian-Regular-form0.png -------------------------------------------------------------------------------- /images/NotoSansMongolian-Regular-form1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/NotoSansMongolian-Regular-form1.png -------------------------------------------------------------------------------- /images/NotoSansMongolian-Regular-form2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/NotoSansMongolian-Regular-form2.png -------------------------------------------------------------------------------- /images/NotoSansMongolian-Regular-join1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/NotoSansMongolian-Regular-join1.png -------------------------------------------------------------------------------- /images/NotoSansMongolian-Regular-join2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/NotoSansMongolian-Regular-join2.png -------------------------------------------------------------------------------- /images/NotoSansMongolian-Regular-join3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/NotoSansMongolian-Regular-join3.png -------------------------------------------------------------------------------- /images/another-harbuzz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/another-harbuzz.png -------------------------------------------------------------------------------- /images/arabic-boxed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/arabic-boxed.png -------------------------------------------------------------------------------- /images/arabic-cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/arabic-cropped.png -------------------------------------------------------------------------------- /images/c++japanese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/c++japanese.png -------------------------------------------------------------------------------- /images/japanese-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/japanese-example.png -------------------------------------------------------------------------------- /images/mngltitleotf-bare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mngltitleotf-bare.png -------------------------------------------------------------------------------- /images/mngltitleotf-form0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mngltitleotf-form0.png -------------------------------------------------------------------------------- /images/mngltitleotf-form1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mngltitleotf-form1.png -------------------------------------------------------------------------------- /images/mngltitleotf-join1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mngltitleotf-join1.png -------------------------------------------------------------------------------- /images/mngltitleotf-join2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mngltitleotf-join2.png -------------------------------------------------------------------------------- /images/mngltitleotf-join3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mngltitleotf-join3.png -------------------------------------------------------------------------------- /images/mnglwhiteotf-bare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwhiteotf-bare.png -------------------------------------------------------------------------------- /images/mnglwhiteotf-form0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwhiteotf-form0.png -------------------------------------------------------------------------------- /images/mnglwhiteotf-form1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwhiteotf-form1.png -------------------------------------------------------------------------------- /images/mnglwhiteotf-form2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwhiteotf-form2.png -------------------------------------------------------------------------------- /images/mnglwhiteotf-form3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwhiteotf-form3.png -------------------------------------------------------------------------------- /images/mnglwhiteotf-join1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwhiteotf-join1.png -------------------------------------------------------------------------------- /images/mnglwhiteotf-join2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwhiteotf-join2.png -------------------------------------------------------------------------------- /images/mnglwhiteotf-join3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwhiteotf-join3.png -------------------------------------------------------------------------------- /images/mnglwritingotf-bare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwritingotf-bare.png -------------------------------------------------------------------------------- /images/mnglwritingotf-form0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwritingotf-form0.png -------------------------------------------------------------------------------- /images/mnglwritingotf-form1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwritingotf-form1.png -------------------------------------------------------------------------------- /images/mnglwritingotf-form2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwritingotf-form2.png -------------------------------------------------------------------------------- /images/mnglwritingotf-form3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwritingotf-form3.png -------------------------------------------------------------------------------- /images/mnglwritingotf-join1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwritingotf-join1.png -------------------------------------------------------------------------------- /images/mnglwritingotf-join2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwritingotf-join2.png -------------------------------------------------------------------------------- /images/mnglwritingotf-join3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mnglwritingotf-join3.png -------------------------------------------------------------------------------- /images/monbaiti-bare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/monbaiti-bare.png -------------------------------------------------------------------------------- /images/monbaiti-form0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/monbaiti-form0.png -------------------------------------------------------------------------------- /images/monbaiti-form1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/monbaiti-form1.png -------------------------------------------------------------------------------- /images/monbaiti-join1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/monbaiti-join1.png -------------------------------------------------------------------------------- /images/monbaiti-join2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/monbaiti-join2.png -------------------------------------------------------------------------------- /images/monbaiti-join3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/monbaiti-join3.png -------------------------------------------------------------------------------- /images/mongolian-example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/mongolian-example1.png -------------------------------------------------------------------------------- /images/phagspa-example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/phagspa-example1.png -------------------------------------------------------------------------------- /images/phagspa-example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/phagspa-example2.png -------------------------------------------------------------------------------- /images/phagspa-long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/phagspa-long.png -------------------------------------------------------------------------------- /images/sanskrit-ligature1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/sanskrit-ligature1.png -------------------------------------------------------------------------------- /images/sanskrit-ligature2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/images/sanskrit-ligature2.png -------------------------------------------------------------------------------- /mongolian-variants.sh: -------------------------------------------------------------------------------- 1 | ./hb-view.py ${1}.ttf 'ᠴᠢᠩᠭᠢᠰ ᠬᠠᠭᠠᠨ' 2 | mv hb-view.png ${1}-bare.png 3 | ./hb-view.py ${1}.ttf 'ᠴ ᠢ ᠩ ᠭ ᠢ ᠰ ᠬ ᠠ ᠭ ᠠ ᠨ' 4 | mv hb-view.png ${1}-form0.png 5 | ./hb-view.py ${1}.ttf 'ᠴ᠋ᠢ᠋ᠩ᠋ᠭ᠋ᠢ᠋ᠰ᠋ ᠬ᠋ᠠ᠋ᠭ᠋ᠠ᠋ᠨ᠋' 6 | mv hb-view.png ${1}-join1.png 7 | ./hb-view.py ${1}.ttf 'ᠴ᠋ ᠢ᠋ ᠩ᠋ ᠭ᠋ ᠢ᠋ ᠰ᠋ ᠬ᠋ ᠠ᠋ ᠭ᠋ ᠠ᠋ ᠨ᠋' 8 | mv hb-view.png ${1}-form1.png 9 | ./hb-view.py ${1}.ttf 'ᠴ᠌ᠢ᠌ᠩ᠌ᠭ᠌ᠢ᠌ᠰ᠌ ᠬ᠌ᠠ᠌ᠭ᠌ᠠ᠌ᠨ᠌' 10 | mv hb-view.png ${1}-join2.png 11 | ./hb-view.py ${1}.ttf 'ᠴ᠌ ᠢ᠌ ᠩ᠌ ᠭ᠌ ᠢ᠌ ᠰ᠌ ᠬ᠌ ᠠ᠌ ᠭ᠌ ᠠ᠌ ᠨ᠌' 12 | mv hb-view.png ${1}-form2.png 13 | ./hb-view.py ${1}.ttf 'ᠴ᠍ᠢ᠍ᠩ᠍ᠭ᠍ᠢ᠍ᠰ᠍ ᠬ᠍ᠠ᠍ᠭ᠍ᠠ᠍ᠨ᠍' 14 | mv hb-view.png ${1}-join3.png 15 | ./hb-view.py ${1}.ttf 'ᠴ᠍ ᠢ᠍ ᠩ᠍ ᠭ᠍ ᠢ᠍ ᠰ᠍ ᠬ᠍ ᠠ᠍ ᠭ᠍ ᠠ᠍ ᠨ᠍' 16 | mv hb-view.png ${1}-form3.png 17 | -------------------------------------------------------------------------------- /otsvg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # OT-SVG example 5 | # 6 | # Copyright 2023 Hin-Tak Leung 7 | # Distributed under the terms of the new BSD license. 8 | 9 | # This is largely a python re-write of freetype2-demos:src/rsvg-port.c . 10 | # It is designed to be embeddable as a module in another python script . 11 | # 12 | # To use, rename this to, for example "otsvg.py", then insert 13 | # 14 | # ''' 15 | # from otsvg import hooks 16 | # 17 | # library = get_handle() 18 | # FT_Property_Set( library, b"ot-svg", b"svg-hooks", byref(hooks) 19 | # ''' 20 | # 21 | # in your script, and add "FT_LOAD_COLOR | FT_LOAD_RENDER" 22 | # to load_glyph() or load_char() calls. As in "__main__" below, 23 | # but also in the longer example of hb-view-ot-svg.py 24 | # in https://github.com/HinTak/harfbuzz-python-demos/ . 25 | # 26 | # Limitation: it is necessary to have "_state" as a module-level global 27 | # partially (in svg_init/svg_free, not in svg_render/svg_preset_slot) 28 | # to stop python destroying it when execution is in the c-side. 29 | # 30 | # Note: 31 | # Strictly-speaking, cairo.FORMAT_ARGB32 is host-order, 32 | # while freetype.FT_PIXEL_MODE_BGRA is small-endian. They are 33 | # different on big-endian platforms. The below 34 | # works in all circumstances, only because the bitmap is both 35 | # generated by cairo at the beginning and also consumed 36 | # by cairo in the end. 37 | # 38 | # The use of "pythonapi.PyMemoryView_FromMemory" is flaky - 39 | # specific to CPython 3.3+. 40 | # 41 | # Uses librsvg 2.52+ only. Or deprecation warnings per run. 42 | # 43 | # Not using Rsvg.Handle.render_document() . 44 | # Rsvg.Handle.render_layer(..., None, ...) does the same work. 45 | # 46 | # The original C-code does not check 47 | # start_glyph_id <= glyph_index <= end_glyph_id.value . 48 | 49 | import gi 50 | gi.require_version('Rsvg', '2.0') 51 | 52 | from gi.repository import Rsvg as rsvg 53 | 54 | from freetype import * 55 | from cairo import * # cairo.Matrix shadows freetype.Matrix 56 | 57 | from math import ceil 58 | 59 | _state = None 60 | 61 | def svg_init(ctx): 62 | global _state 63 | _state = {} 64 | ctx.contents.value = _state 65 | return FT_Err_Ok 66 | 67 | def svg_free(ctx): 68 | global _state 69 | _state = None 70 | # "None" is strictly speaking a special pyobject, 71 | # this line does not do what it should, i.e. setting the 72 | # pointer to NULL. 73 | ctx.contents = None 74 | return # void 75 | 76 | def svg_render(slot, ctx): 77 | state = ctx.contents.value 78 | #pythonapi is imported from ctypes 79 | pythonapi.PyMemoryView_FromMemory.argtypes = (c_char_p, c_ssize_t, c_int) 80 | pythonapi.PyMemoryView_FromMemory.restype = py_object 81 | surface = ImageSurface.create_for_data( pythonapi.PyMemoryView_FromMemory(cast(slot.contents.bitmap.buffer, c_char_p), 82 | slot.contents.bitmap.rows * slot.contents.bitmap.pitch, 0x200), 83 | FORMAT_ARGB32, 84 | slot.contents.bitmap.width, 85 | slot.contents.bitmap.rows, 86 | slot.contents.bitmap.pitch ) 87 | cr = Context( surface ) 88 | cr.translate( -state['x'], -state['y'] ) 89 | 90 | cr.set_source_surface( state['rec_surface'] ) # 0,0 is default 91 | cr.paint() 92 | 93 | surface.flush() 94 | 95 | slot.contents.bitmap.pixel_mode = FT_PIXEL_MODE_BGRA 96 | slot.contents.bitmap.num_grays = 256 97 | slot.contents.format = FT_GLYPH_FORMAT_BITMAP 98 | 99 | state['rec_surface'] = None # Let python destroy the surface 100 | 101 | return FT_Err_Ok 102 | 103 | def svg_preset_slot(slot, cached, ctx): 104 | state = ctx.contents.value 105 | 106 | document = ctypes.cast(slot.contents.other, FT_SVG_Document) 107 | 108 | metrics = SizeMetrics(document.contents.metrics) 109 | 110 | units_per_EM = FT_UShort(document.contents.units_per_EM) 111 | end_glyph_id = FT_UShort(document.contents.end_glyph_id) 112 | start_glyph_id = FT_UShort(document.contents.start_glyph_id) 113 | 114 | dimension_svg = rsvg.DimensionData() 115 | 116 | handle = rsvg.Handle.new_from_data( ctypes.string_at(document.contents.svg_document, # not terminated 117 | size=document.contents.svg_document_length) 118 | ) 119 | 120 | (out_has_width, out_width, 121 | out_has_height, out_height, 122 | out_has_viewbox, out_viewbox) = handle.get_intrinsic_dimensions() 123 | 124 | if ( out_has_viewbox == True ): 125 | dimension_svg.width = out_viewbox.width 126 | dimension_svg.height = out_viewbox.height 127 | else: 128 | # "out_has_width" and "out_has_height" are True always 129 | dimension_svg.width = units_per_EM.value 130 | dimension_svg.height = units_per_EM.value 131 | 132 | if (( out_width.length != 1) or (out_height.length != 1 )): 133 | dimension_svg.width = out_width.length 134 | dimension_svg.height = out_height.length 135 | 136 | x_svg_to_out = metrics.x_ppem / dimension_svg.width 137 | y_svg_to_out = metrics.y_ppem / dimension_svg.height 138 | 139 | state['rec_surface'] = RecordingSurface( Content.COLOR_ALPHA, None ) 140 | 141 | rec_cr = Context( state['rec_surface'] ) 142 | 143 | xx = document.contents.transform.xx / ( 1 << 16 ) 144 | xy = -document.contents.transform.xy / ( 1 << 16 ) 145 | yx = -document.contents.transform.yx / ( 1 << 16 ) 146 | yy = document.contents.transform.yy / ( 1 << 16 ) 147 | 148 | x0 = document.contents.delta.x / 64 * dimension_svg.width / metrics.x_ppem 149 | y0 = -document.contents.delta.y / 64 * dimension_svg.height / metrics.y_ppem 150 | 151 | transform_matrix = Matrix(xx, yx, xy, yy, x0, y0) # cairo.Matrix 152 | 153 | rec_cr.scale( x_svg_to_out, y_svg_to_out ) 154 | 155 | rec_cr.transform( transform_matrix ) 156 | 157 | viewport = rsvg.Rectangle() 158 | viewport.x = 0 159 | viewport.y = 0 160 | viewport.width = dimension_svg.width 161 | viewport.height = dimension_svg.height 162 | 163 | str = None # render whole document - not using Handle.render_document() 164 | if ( start_glyph_id.value < end_glyph_id.value ): 165 | str = "#glyph%u" % (slot.contents.glyph_index ) 166 | 167 | handle.render_layer( rec_cr, str, viewport ) 168 | 169 | (state['x'], state['y'], width, height) = state['rec_surface'].ink_extents() 170 | 171 | slot.contents.bitmap_left = int(state['x']) 172 | slot.contents.bitmap_top = int(-state['y']) 173 | 174 | slot.contents.bitmap.rows = ceil( height ) 175 | slot.contents.bitmap.width = ceil( width ) 176 | 177 | slot.contents.bitmap.pitch = slot.contents.bitmap.width * 4 178 | 179 | slot.contents.bitmap.pixel_mode = FT_PIXEL_MODE_BGRA 180 | 181 | metrics_width = width 182 | metrics_height = height 183 | 184 | horiBearingX = state['x'] 185 | horiBearingY = -state['y'] 186 | 187 | vertBearingX = slot.contents.metrics.horiBearingX / 64.0 - slot.contents.metrics.horiAdvance / 64.0 / 2 188 | vertBearingY = ( slot.contents.metrics.vertAdvance / 64.0 - slot.contents.metrics.height / 64.0 ) / 2 189 | 190 | slot.contents.metrics.width = int(round( metrics_width * 64 )) 191 | slot.contents.metrics.height = int(round( metrics_height * 64 )) 192 | 193 | slot.contents.metrics.horiBearingX = int( horiBearingX * 64 ) 194 | slot.contents.metrics.horiBearingY = int( horiBearingY * 64 ) 195 | slot.contents.metrics.vertBearingX = int( vertBearingX * 64 ) 196 | slot.contents.metrics.vertBearingY = int( vertBearingY * 64 ) 197 | 198 | if ( slot.contents.metrics.vertAdvance == 0 ): 199 | slot.contents.metrics.vertAdvance = int( metrics_height * 1.2 * 64 ) 200 | 201 | if ( cached == False ): 202 | state['rec_surface'] = None 203 | state['x'] = 0 204 | state['y'] = 0 205 | 206 | return FT_Err_Ok 207 | 208 | hooks = SVG_RendererHooks(svg_init=SVG_Lib_Init_Func(svg_init), 209 | svg_free=SVG_Lib_Free_Func(svg_free), 210 | svg_render=SVG_Lib_Render_Func(svg_render), 211 | svg_preset_slot=SVG_Lib_Preset_Slot_Func(svg_preset_slot)) 212 | 213 | if __name__ == '__main__': 214 | import sys 215 | execname = sys.argv[0] 216 | 217 | if len(sys.argv) < 2: 218 | print("Example usage: %s TrajanColor-Concept.otf" % execname) 219 | exit(1) 220 | 221 | face = Face(sys.argv[1]) 222 | 223 | face.set_char_size( 160*64 ) 224 | library = get_handle() 225 | FT_Property_Set( library, b"ot-svg", b"svg-hooks", byref(hooks) ) # python 3 only syntax 226 | face.load_char('A', FT_LOAD_COLOR | FT_LOAD_RENDER ) 227 | 228 | bitmap = face.glyph.bitmap 229 | width = face.glyph.bitmap.width 230 | rows = face.glyph.bitmap.rows 231 | 232 | if ( face.glyph.bitmap.pitch != width * 4 ): 233 | raise RuntimeError('pitch != width * 4 for color bitmap: Please report this.') 234 | 235 | I = ImageSurface.create_for_data( pythonapi.PyMemoryView_FromMemory(cast(bitmap._FT_Bitmap.buffer, c_char_p), 236 | bitmap.rows * bitmap.pitch, 237 | 0x200), # Read-Write 238 | FORMAT_ARGB32, 239 | width, rows, 240 | bitmap.pitch ) 241 | surface = ImageSurface(FORMAT_ARGB32, 2*width, rows) 242 | ctx = Context(surface) 243 | 244 | ctx.set_source_surface(I, 0, 0) 245 | ctx.paint() 246 | 247 | ctx.set_source_surface(I, width/2, 0) 248 | ctx.paint() 249 | 250 | ctx.set_source_surface(I, width , 0) 251 | ctx.paint() 252 | 253 | surface.write_to_png("ot-svg-example.png") 254 | 255 | from PIL import Image 256 | Image.open("ot-svg-example.png").show() 257 | -------------------------------------------------------------------------------- /skia-adventure/COLRv1.md: -------------------------------------------------------------------------------- 1 | This is a patch to adds 3 other OT-SVG hooks to FreeType2-demos. There is an extension on top of it, to 2 | add COLRv1 rendering, too. 3 | 4 | The COLRv1 extension currently has a limitation - it works by over-writing 5 | the SVG rendering with a toggle key, so it depends on the font having a SVG table. In one without, it overwrites 6 | the glyh rendering and does gray. (Hope to fix). So it is convenient that both Rsvg and Adobe SVG rendering are flawed. 7 | [Adobe SVG Native](https://github.com/adobe/svg-native-viewer/issues/185) , and [Rsvg issue](https://gitlab.gnome.org/GNOME/librsvg/-/issues/997). 8 | 9 | It is a toggle-key to toggle SVG<->COLRv1 rendering ("z" for "color layered glyphs" as for COLRv0), 10 | and overloads the palette toggle key ("C" for switching palettes for color-blind-friendiness in glyf mode) 11 | to switch CPAL entries. Binaries at the [FontVal binary archive](https://github.com/FontVal-extras/binary-archive/) ). 12 | 13 | ## Skia COLRv1 Rendering 14 | 15 | Skia COLRv1: 16 | 17 | ![Skia COLRv1](screenshots/ftgrid-colrv1.png) 18 | 19 | The glyf data: 20 | 21 | ![Glyph](screenshots/ftgrid-glyf.png) 22 | 23 | Skia COLRv1 to Alpha channel: 24 | 25 | ![Skia to Alpha](screenshots/ftgrid-kAlpha.png) 26 | 27 | Skia COLRv1 to Gray: 28 | 29 | ![Skia to Gray](screenshots/ftgrid-kGray.png) 30 | 31 | ## COLRv1 Glyphs vs SVG Glyphs, both rendered via Skia 32 | 33 | Skia COLRv1: 34 | 35 | ![Skia COLRv1](screenshots/ftgrid-colrv1.png) 36 | 37 | Skia SVG: 38 | 39 | ![Skia COLRv1](screenshots/ftgrid-SVG.png) 40 | 41 | Difficult to tell the difference by the naked eye. Here is the programmatic highlights (with ImageMagick's `compare`): 42 | 43 | ![Skia COLRv1](screenshots/ftgrid-diff.png) 44 | 45 | ## COLRv1 palettes 46 | 47 | index 0: 48 | 49 | ![palette 0](screenshots/ftgrid-palette0.png) 50 | 51 | index 1: 52 | 53 | ![palette 1](screenshots/ftgrid-palette1.png) 54 | 55 | index 2: 56 | 57 | ![palette 2](screenshots/ftgrid-palette2.png) 58 | 59 | index 3: 60 | 61 | ![palette 3](screenshots/ftgrid-palette3.png) 62 | 63 | index 4: 64 | 65 | ![palette 4](screenshots/ftgrid-palette4.png) 66 | 67 | index 5: 68 | 69 | ![palette 5](screenshots/ftgrid-palette5.png) 70 | 71 | index 6: 72 | 73 | ![palette 6](screenshots/ftgrid-palette6.png) 74 | -------------------------------------------------------------------------------- /skia-adventure/README.md: -------------------------------------------------------------------------------- 1 | The skia-enhanced freetype2-demos now has its own [repo](https://github.com/HinTak/freetype2-demos-skia). 2 | There is an extension on top of it, to add COLRv1 rendering, too. This page is mostly historical, except 3 | the svg-native viewer issue. 4 | 5 | ## Librsvg/Cairo SVG Rendering vs SKia SVG Rendering 6 | 7 | See the top of the RSVG rendering. There are a few very pale pixels over the bound box. This difference 8 | is consistent across rendering different glyphs. 9 | 10 | Librsvg: 11 | 12 | ![RSVG rendering](screenshots/ftgrid-rsvg.png) 13 | 14 | Skia: 15 | 16 | ![SKIA rendering](screenshots/ftgrid-skia.png) 17 | 18 | This seems to be a bug in rsvg-based (2.56.2 and 2.56.90-12-g1b589574) SVG rendering: 19 | 20 | Librsvg: 21 | 22 | ![RSVG rendering](screenshots/ftgrid-Nabla-rsvg.png) 23 | 24 | Skia: 25 | 26 | ![SKIA rendering](screenshots/ftgrid-Nabla-skia.png) 27 | 28 | Filed as https://gitlab.gnome.org/GNOME/librsvg/-/issues/997 . Apparently it 29 | is due to the use of CSS `var()` to reference colors. Looks like the 30 | librsvg folks will try to support `var(--foo, #rrggbb)` fallbacks 31 | as a workaround. The bug was fixed in 2.57.1, and verified to be so. 32 | 33 | Interestingly, [SVG Native](https://github.com/adobe/svg-native-viewer) renders it half-way. 34 | ( filed as https://github.com/adobe/svg-native-viewer/issues/185 ) 35 | 36 | ![SVG Native](../svg-native/ftgrid-14.png) 37 | 38 | More screenshots about SVG Native in [the directory above](../svg-native/). 39 | 40 | Inkscape also have problems with this SVG ( https://gitlab.com/inkscape/inbox/-/issues/8857 , moved from 41 | https://gitlab.com/inkscape/inkscape/-/issues/4423 ) 42 | 43 | -------------------------------------------------------------------------------- /skia-adventure/RPM-sizes.txt: -------------------------------------------------------------------------------- 1 | Static Shared : SVG Native 2 | =============================================== 3 | 36680 36680 Jun 27 01:00 /usr/bin/ftbench 4 | 15800 15800 Jun 27 01:00 /usr/bin/ftchkwd 5 | 679368 90944 Jun 27 01:00 /usr/bin/ftdiff 6 | 48696 48696 Jun 27 01:00 /usr/bin/ftdump 7 | 662920 82760 Jun 27 01:00 /usr/bin/ftgamma 8 | 787768 161768 Jun 27 01:00 /usr/bin/ftgrid 9 | 28152 28152 Jun 27 01:00 /usr/bin/ftlint 10 | 15904 15896 Jun 27 01:00 /usr/bin/ftmemchk 11 | 671080 86848 Jun 27 01:00 /usr/bin/ftmulti 12 | 15792 15800 Jun 27 01:00 /usr/bin/ftpatchk 13 | 750968 132912 Jun 27 01:00 /usr/bin/ftstring 14 | 15856 15856 Jun 27 01:00 /usr/bin/fttimer 15 | 24400 24400 Jun 27 01:00 /usr/bin/ftvalid 16 | 771352 161840 Jun 27 01:00 /usr/bin/ftview 17 | 6863136 6863240 Jun 27 01:00 /usr/lib64/libskia.so 18 | 380352 380344 Jun 27 01:00 /usr/lib64/libskottie.so 19 | 23840 23840 Jun 27 01:00 /usr/lib64/libskparagraph.so 20 | 14984 14984 Jun 27 01:00 /usr/lib64/libsksg.so 21 | 58584 58584 Jun 27 01:00 /usr/lib64/libskshaper.so 22 | 15128 15136 Jun 27 01:00 /usr/lib64/libsktext.so 23 | 37056 37056 Jun 27 01:00 /usr/lib64/libskunicode.so 24 | 252840 252840 Jun 27 01:00 /usr/lib64/libsvg.so 25 | - 799712 Jun 27 01:00 /usr/lib64/libSVGNativeViewerLib.so 26 | 27 | Size : 155612541 Build Date : Sat 15 Jul 2023 01:37:36 BST - Skia 28 | Size : 159360325 Build Date : Sat 22 Jul 2023 08:14:06 BST - COLRv1 ( + 4 hooks ) 29 | Size : 12393889 Build Date : Mon 24 Jul 2023 01:21:26 BST - COLRv1 + shared Skia 30 | Size : 9587367 Build Date : Wed 26 Jul 2023 04:44:41 BST - COLRv1 + shared mod'ed Skia and shared SVG Native 31 | -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-Nabla-rsvg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-Nabla-rsvg.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-Nabla-skia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-Nabla-skia.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-SVG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-SVG.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-colrv1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-colrv1.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-diff.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-glyf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-glyf.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-kAlpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-kAlpha.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-kGray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-kGray.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette0-g0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette0-g0.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette0-g1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette0-g1.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette0-g2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette0-g2.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette0-g3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette0-g3.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette0-g4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette0-g4.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette0-g5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette0-g5.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette0-g6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette0-g6.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette0.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette1.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette2.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette3.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette4.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette5.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-palette6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-palette6.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-rsvg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-rsvg.png -------------------------------------------------------------------------------- /skia-adventure/screenshots/ftgrid-skia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/skia-adventure/screenshots/ftgrid-skia.png -------------------------------------------------------------------------------- /skia_glfw_module.py: -------------------------------------------------------------------------------- 1 | # skia_glfw_module 2 | # This file created by 2023 Hin-Tak Leung; but Copyright skia-python project: 3 | 4 | # Adapted from https://kyamagu.github.io/skia-python/tutorial/canvas.html#opengl-window 5 | 6 | # Typical usage: 7 | # 8 | # from skia_glfw_module import glfw_window, skia_surface 9 | # .... 10 | # with glfw_window(WIDTH, HEIGHT) as window: 11 | # ... 12 | # with skia_surface(window) as surface: 13 | # with surface as canvas: 14 | # canvas.drawStuff() 15 | # 16 | # surface.flushAndSubmit() 17 | # glfw.swap_buffers(window) 18 | # 19 | 20 | import contextlib, glfw 21 | import skia 22 | from OpenGL import GL 23 | 24 | @contextlib.contextmanager 25 | def glfw_window(WIDTH, HEIGHT): 26 | if not glfw.init(): 27 | raise RuntimeError('glfw.init() failed') 28 | glfw.window_hint(glfw.STENCIL_BITS, 8) 29 | window = glfw.create_window(WIDTH, HEIGHT, '', None, None) 30 | glfw.make_context_current(window) 31 | yield window 32 | glfw.terminate() 33 | 34 | @contextlib.contextmanager 35 | def skia_surface(window): 36 | context = skia.GrDirectContext.MakeGL() 37 | (fb_width, fb_height) = glfw.get_framebuffer_size(window) 38 | backend_render_target = skia.GrBackendRenderTarget( 39 | fb_width, 40 | fb_height, 41 | 0, # sampleCnt 42 | 0, # stencilBits 43 | skia.GrGLFramebufferInfo(0, GL.GL_RGBA8)) 44 | surface = skia.Surface.MakeFromBackendRenderTarget( 45 | context, backend_render_target, skia.kBottomLeft_GrSurfaceOrigin, 46 | skia.kRGBA_8888_ColorType, skia.ColorSpace.MakeSRGB()) 47 | assert surface is not None 48 | yield surface 49 | context.abandonContext() 50 | -------------------------------------------------------------------------------- /skia_ot_svg_module.py: -------------------------------------------------------------------------------- 1 | # skia_ot_svg_module 2 | # Copyright 2023 Hin-Tak Leung 3 | 4 | # Typical usage: 5 | # 6 | # from freetype import get_handle, FT_Property_Set 7 | # from skia_ot_svg_module import hooks 8 | # ... 9 | # library = get_handle() 10 | # FT_Property_Set( library, b"ot-svg", b"svg-hooks", byref(hooks) ) 11 | # 12 | # After these lines (or equivalent), COLOR OT-SVG font support is enabled. 13 | # You just use freetype-py as usual. Color bitmaps are returned for OT-SVG 14 | # fonts in FT_Load_Char()/FT_Load_Glyph(). 15 | 16 | from freetype import FT_SVG_Document, FT_Err_Ok, SVG_RendererHooks, \ 17 | SVG_Lib_Init_Func, SVG_Lib_Free_Func, SVG_Lib_Render_Func, \ 18 | SVG_Lib_Preset_Slot_Func, SizeMetrics, FT_UShort, FT_PIXEL_MODE_BGRA, \ 19 | FT_GLYPH_FORMAT_BITMAP 20 | import ctypes 21 | from ctypes import py_object, pythonapi, cast, c_char_p 22 | 23 | import skia 24 | from skia import Size, ImageInfo, ColorType, AlphaType, Canvas, \ 25 | PictureRecorder, Rect, ScalarNegativeInfinity, ScalarInfinity, RTreeFactory 26 | 27 | from packaging import version 28 | assert(version.parse(skia.__version__) > version.parse("116.0b2")), "Needs Skia-Python 116.0b2+" 29 | 30 | pythonapi.PyMemoryView_FromMemory.restype = py_object 31 | 32 | # include/private/base/SkFixed.h 33 | def SkFixedToFloat(x): 34 | return ((x) * 1.52587890625e-5) 35 | 36 | from math import ceil, floor 37 | 38 | _state = None 39 | 40 | def svg_init(ctx): 41 | global _state 42 | _state = {} 43 | ctx.contents.value = _state 44 | return FT_Err_Ok 45 | 46 | def svg_free(ctx): 47 | global _state 48 | _state = None 49 | # "None" is strictly speaking a special pyobject, 50 | # this line does not do what it should, i.e. setting the 51 | # pointer to NULL. 52 | ctx.contents = None 53 | return # void 54 | 55 | def svg_render(slot, ctx): 56 | state = ctx.contents.value 57 | dstBitmap = skia.Bitmap() 58 | dstBitmap.setInfo(ImageInfo.Make(slot.contents.bitmap.width, slot.contents.bitmap.rows, 59 | ColorType.kBGRA_8888_ColorType, 60 | AlphaType.kPremul_AlphaType), 61 | slot.contents.bitmap.pitch) 62 | dstBitmap.setPixels(pythonapi.PyMemoryView_FromMemory(cast(slot.contents.bitmap.buffer, c_char_p), 63 | slot.contents.bitmap.rows * slot.contents.bitmap.pitch, 64 | 0x200), # Read-Write 65 | ) 66 | canvas = Canvas(dstBitmap) 67 | 68 | canvas.clear(skia.ColorTRANSPARENT) 69 | 70 | canvas.translate( -state['x'], -state['y'] ) 71 | canvas.drawPicture( state['picture'] ) 72 | 73 | slot.contents.bitmap.pixel_mode = FT_PIXEL_MODE_BGRA 74 | slot.contents.bitmap.num_grays = 256 75 | slot.contents.format = FT_GLYPH_FORMAT_BITMAP 76 | 77 | state['picture'] = None 78 | 79 | return FT_Err_Ok 80 | 81 | def svg_preset_slot(slot, cached, ctx): 82 | state = ctx.contents.value 83 | 84 | document = ctypes.cast(slot.contents.other, FT_SVG_Document) 85 | 86 | metrics = SizeMetrics(document.contents.metrics) 87 | 88 | units_per_EM = FT_UShort(document.contents.units_per_EM) 89 | end_glyph_id = FT_UShort(document.contents.end_glyph_id) 90 | start_glyph_id = FT_UShort(document.contents.start_glyph_id) 91 | 92 | doc = ctypes.string_at(document.contents.svg_document, # not terminated 93 | size=document.contents.svg_document_length) 94 | data = skia.Data(doc) 95 | svgmem = skia.MemoryStream(data) 96 | svg = skia.SVGDOM.MakeFromStream(svgmem) 97 | 98 | if (svg.containerSize().isEmpty()): 99 | size = Size.Make(units_per_EM.value, units_per_EM.value) 100 | svg.setContainerSize(size) 101 | 102 | recorder = PictureRecorder() 103 | 104 | infiniteRect = Rect.MakeLTRB(ScalarNegativeInfinity, ScalarNegativeInfinity, 105 | ScalarInfinity, ScalarInfinity) 106 | bboxh = RTreeFactory()() 107 | 108 | recordingCanvas = recorder.beginRecording(infiniteRect, bboxh) 109 | 110 | ftMatrix = document.contents.transform 111 | ftOffset = document.contents.delta 112 | 113 | m = skia.Matrix() 114 | m.setAll( 115 | SkFixedToFloat(ftMatrix.xx), -SkFixedToFloat(ftMatrix.xy), SkFixedToFloat(ftOffset.x), 116 | -SkFixedToFloat(ftMatrix.yx), SkFixedToFloat(ftMatrix.yy), -SkFixedToFloat(ftOffset.y), 117 | 0 , 0 , 1 ) 118 | 119 | m.postScale(SkFixedToFloat(document.contents.metrics.x_scale) / 64.0, 120 | SkFixedToFloat(document.contents.metrics.y_scale) / 64.0) 121 | 122 | recordingCanvas.concat(m) 123 | 124 | if ( start_glyph_id.value < end_glyph_id.value ): 125 | id = "glyph%u" % ( slot.contents.glyph_index ) 126 | svg.renderNode(recordingCanvas, id) 127 | else: 128 | svg.render(recordingCanvas) 129 | 130 | state['picture'] = recorder.finishRecordingAsPicture() 131 | bounds = state['picture'].cullRect() 132 | 133 | width = ceil(bounds.right()) - floor(bounds.left()) 134 | height = ceil(bounds.bottom()) - floor(bounds.top()) 135 | x = floor(bounds.left()) 136 | y = floor(bounds.top()) 137 | 138 | state['x'] = x 139 | state['y'] = y 140 | 141 | slot.contents.bitmap_left = int(state['x']) # float to int conversion 142 | slot.contents.bitmap_top = int(-state['y']) 143 | 144 | slot.contents.bitmap.rows = ceil( height ) # float to int 145 | slot.contents.bitmap.width = ceil( width ) 146 | 147 | slot.contents.bitmap.pitch = slot.contents.bitmap.width * 4 148 | 149 | slot.contents.bitmap.pixel_mode = FT_PIXEL_MODE_BGRA 150 | 151 | metrics_width = width 152 | metrics_height = height 153 | 154 | horiBearingX = state['x'] 155 | horiBearingY = -state['y'] 156 | 157 | vertBearingX = slot.contents.metrics.horiBearingX / 64.0 - slot.contents.metrics.horiAdvance / 64.0 / 2 158 | vertBearingY = ( slot.contents.metrics.vertAdvance / 64.0 - slot.contents.metrics.height / 64.0 ) / 2 159 | 160 | slot.contents.metrics.width = int(round( width * 64 )) 161 | slot.contents.metrics.height = int(round( height * 64 )) 162 | 163 | slot.contents.metrics.horiBearingX = int( horiBearingX * 64 ) 164 | slot.contents.metrics.horiBearingY = int( horiBearingY * 64 ) 165 | slot.contents.metrics.vertBearingX = int( vertBearingX * 64 ) 166 | slot.contents.metrics.vertBearingY = int( vertBearingY * 64 ) 167 | 168 | if ( slot.contents.metrics.vertAdvance == 0 ): 169 | slot.contents.metrics.vertAdvance = int( height * 1.2 * 64 ) 170 | 171 | if ( cached == False ): 172 | state['picture'] = None 173 | state['x'] = 0 174 | state['y'] = 0 175 | 176 | return FT_Err_Ok 177 | 178 | hooks = SVG_RendererHooks(svg_init=SVG_Lib_Init_Func(svg_init), 179 | svg_free=SVG_Lib_Free_Func(svg_free), 180 | svg_render=SVG_Lib_Render_Func(svg_render), 181 | svg_preset_slot=SVG_Lib_Preset_Slot_Func(svg_preset_slot)) 182 | -------------------------------------------------------------------------------- /svg-native/README.md: -------------------------------------------------------------------------------- 1 | [svg native viewer issue 185](https://github.com/adobe/svg-native-viewer/issues/185): 2 | 3 | ![0](ftgrid-0.png) 4 | 5 | ![2](ftgrid-2.png) 6 | 7 | ![3](ftgrid-3.png) 8 | 9 | ![4](ftgrid-4.png) 10 | 11 | ![5](ftgrid-5.png) 12 | 13 | ![6](ftgrid-6.png) 14 | 15 | ![7](ftgrid-7.png) 16 | 17 | ![8](ftgrid-8.png) 18 | 19 | ![9](ftgrid-9.png) 20 | 21 | ![10](ftgrid-10.png) 22 | 23 | ![11](ftgrid-11.png) 24 | 25 | ![12](ftgrid-12.png) 26 | 27 | ![13](ftgrid-13.png) 28 | 29 | ![14](ftgrid-14.png) 30 | 31 | ![190](ftgrid-190.png) 32 | 33 | ![222](ftgrid-222.png) 34 | 35 | ![238](ftgrid-238.png) 36 | 37 | ![270](ftgrid-270.png) 38 | 39 | [Skia m116 core support](https://github.com/adobe/svg-native-viewer/pull/189), 40 | [This svg from google fonts gets mis-rendered.](https://github.com/adobe/svg-native-viewer/issues/185), 41 | [Missing in svgnative/SVGRenderer.h](https://github.com/adobe/svg-native-viewer/issues/186), 42 | [A few compatibility issues with skia m110](https://github.com/adobe/svg-native-viewer/issues/187), 43 | [A few compatibility issues with recent cairo](https://github.com/adobe/svg-native-viewer/issues/188). 44 | -------------------------------------------------------------------------------- /svg-native/ft2-demos-Adobe-SVG-Cairo.diff: -------------------------------------------------------------------------------- 1 | diff --git a/Makefile b/Makefile 2 | index 5b8325b..b7b744a 100644 3 | --- a/Makefile 4 | +++ b/Makefile 5 | @@ -153,7 +153,7 @@ else 6 | COMPILE = $(CC) $(ANSIFLAGS) \ 7 | $(INCLUDES:%=$I%) \ 8 | $(CFLAGS) \ 9 | - $(FT_DEMO_CFLAGS) 10 | + $(FT_DEMO_CFLAGS) -DHAVE_ADOBESVG -I/usr/include/cairo/ -I$(TOP_DIR_2)/svg-native-viewer/svgnative/include/ 11 | 12 | 13 | # Enable C99 for gcc to avoid warnings. 14 | @@ -192,8 +192,10 @@ else 15 | # `FT_DEMO_LDFLAGS` has been set in `unix-cc.mk`, too. 16 | override CC = $(CCraw) 17 | LINK_CMD = $(LIBTOOL) --mode=link $(CC) \ 18 | + -L$(TOP_DIR_2)/svg-native-viewer/svgnative/build/linux/ -lSVGNativeViewerLib -lcairo -ljpeg -lexpat \ 19 | $(subst /,$(COMPILER_SEP),$(LDFLAGS)) 20 | LINK_LIBS = $(subst /,$(COMPILER_SEP),$(FTLIB) $(EFENCE)) \ 21 | + -L$(TOP_DIR_2)/svg-native-viewer/svgnative/build/linux/ -lSVGNativeViewerLib -lcairo -ljpeg -lexpat \ 22 | $(FT_DEMO_LDFLAGS) 23 | else 24 | LINK_CMD = $(CC) $(subst /,$(COMPILER_SEP),$(LDFLAGS)) 25 | diff --git a/README.Adobe-SVG-Native-Cairo b/README.Adobe-SVG-Native-Cairo 26 | new file mode 100644 27 | index 0000000..9e64a93 28 | --- /dev/null 29 | +++ b/README.Adobe-SVG-Native-Cairo 30 | @@ -0,0 +1,40 @@ 31 | +CC=c++ ./configure --with-librsvg=no 32 | + 33 | +Building svg-native-viewer: 34 | +=========================== 35 | + 36 | +build static library with (without "-DSHARED=ON" on cmake): 37 | + 38 | +git submodule update --init 39 | +cd svgnative/ 40 | + cmake -Bbuild/linux -H. -DLIB_ONLY=ON -DPLATFORM_XML=ON -DCAIRO=ON -DCMAKE_CXX_FLAGS="-I`pwd`/include/svgnative/" 41 | + cd build/linux/ 42 | + make 43 | + 44 | +Apply small change: 45 | + 46 | +diff --git a/svgnative/include/svgnative/SVGRenderer.h b/svgnative/include/svgnative/SVGRenderer.h 47 | +index aa09bb4..1745093 100644 48 | +--- a/svgnative/include/svgnative/SVGRenderer.h 49 | ++++ b/svgnative/include/svgnative/SVGRenderer.h 50 | +@@ -24,6 +24,7 @@ governing permissions and limitations under the License. 51 | + #include 52 | + #include 53 | + #include 54 | ++#include 55 | + 56 | + namespace SVGNative 57 | + { 58 | +--- 59 | + 60 | +This works with: 61 | + 62 | +commit ab9ea1d48b0ff055c2fb063ae4c68edafce5b7c5 (origin/main, origin/HEAD) 63 | +Author: tushar 64 | +Date: Thu Mar 23 17:07:26 2023 +0530 65 | + 66 | + update only boost submodule 67 | + 68 | +--- 69 | + 70 | +Then just build ft2-demo as normal. 71 | diff --git a/src/rsvg-port.c b/src/rsvg-port.c 72 | index 7a8bb9a..3cc58e2 100644 73 | --- a/src/rsvg-port.c 74 | +++ b/src/rsvg-port.c 75 | @@ -19,10 +19,9 @@ 76 | #include 77 | #include 78 | 79 | -#ifdef HAVE_LIBRSVG 80 | +#ifdef HAVE_ADOBESVG 81 | 82 | #include 83 | -#include 84 | #include 85 | #include 86 | 87 | @@ -160,20 +159,6 @@ 88 | FT_UShort end_glyph_id = document->end_glyph_id; 89 | FT_UShort start_glyph_id = document->start_glyph_id; 90 | 91 | - /* Librsvg variables. */ 92 | - GError *gerror = NULL; 93 | - gboolean ret; 94 | - 95 | - gboolean out_has_width; 96 | - gboolean out_has_height; 97 | - gboolean out_has_viewbox; 98 | - 99 | - RsvgHandle *handle; 100 | - RsvgLength out_width; 101 | - RsvgLength out_height; 102 | - RsvgRectangle out_viewbox; 103 | - RsvgDimensionData dimension_svg; 104 | - 105 | cairo_t *rec_cr; 106 | cairo_matrix_t transform_matrix; 107 | 108 | @@ -207,74 +192,16 @@ 109 | state = &state_dummy; 110 | 111 | /* Form an `RsvgHandle` by loading the SVG document. */ 112 | - handle = rsvg_handle_new_from_data( document->svg_document, 113 | - document->svg_document_length, 114 | - &gerror ); 115 | + auto renderer = std::make_shared(); 116 | + char *s = strndup((char *)document->svg_document, 117 | + document->svg_document_length); 118 | + auto handle = std::unique_ptr(SVGNative::SVGDocument::CreateSVGDocument(s, renderer)); 119 | if ( handle == NULL ) 120 | { 121 | error = FT_Err_Invalid_SVG_Document; 122 | goto CleanLibrsvg; 123 | } 124 | 125 | - /* Get attributes like `viewBox` and `width`/`height`. */ 126 | - rsvg_handle_get_intrinsic_dimensions( handle, 127 | - &out_has_width, 128 | - &out_width, 129 | - &out_has_height, 130 | - &out_height, 131 | - &out_has_viewbox, 132 | - &out_viewbox ); 133 | - 134 | - /* 135 | - * Figure out the units in the EM square in the SVG document. This is 136 | - * specified by the `ViewBox` or the `width`/`height` attributes, if 137 | - * present, otherwise it should be assumed that the units in the EM 138 | - * square are the same as in the TTF/CFF outlines. 139 | - * 140 | - * TODO: I'm not sure what the standard says about the situation if 141 | - * `ViewBox` as well as `width`/`height` are present; however, I've 142 | - * never seen that situation in real fonts. 143 | - */ 144 | - if ( out_has_viewbox == TRUE ) 145 | - { 146 | - dimension_svg.width = (int)out_viewbox.width; /* XXX rounding? */ 147 | - dimension_svg.height = (int)out_viewbox.height; 148 | - } 149 | - else if ( out_has_width == TRUE && out_has_height == TRUE ) 150 | - { 151 | - dimension_svg.width = (int)out_width.length; /* XXX rounding? */ 152 | - dimension_svg.height = (int)out_height.length; 153 | - 154 | - /* 155 | - * librsvg 2.53+ behavior, on SVG doc without explicit width/height. 156 | - * See `rsvg_handle_get_intrinsic_dimensions` section in 157 | - * the `librsvg/rsvg.h` header file. 158 | - */ 159 | - if ( out_width.length == 1 && 160 | - out_height.length == 1 ) 161 | - { 162 | - dimension_svg.width = units_per_EM; 163 | - dimension_svg.height = units_per_EM; 164 | - } 165 | - } 166 | - else 167 | - { 168 | - /* 169 | - * If neither `ViewBox` nor `width`/`height` are present, the 170 | - * `units_per_EM` in SVG coordinates must be the same as 171 | - * `units_per_EM` of the TTF/CFF outlines. 172 | - * 173 | - * librsvg up to 2.52 behavior, on SVG doc without explicit 174 | - * width/height. 175 | - */ 176 | - dimension_svg.width = units_per_EM; 177 | - dimension_svg.height = units_per_EM; 178 | - } 179 | - 180 | - /* Scale factors from SVG coordinates to the needed output size. */ 181 | - x_svg_to_out = (double)metrics.x_ppem / dimension_svg.width; 182 | - y_svg_to_out = (double)metrics.y_ppem / dimension_svg.height; 183 | - 184 | /* 185 | * Create a cairo recording surface. This is done for two reasons. 186 | * Firstly, it is required to get the bounding box of the final drawing 187 | @@ -288,6 +215,10 @@ 188 | 189 | rec_cr = cairo_create( state->rec_surface ); 190 | 191 | + /* Scale factors from SVG coordinates to the needed output size. */ 192 | + x_svg_to_out = (double)metrics.x_ppem / units_per_EM; 193 | + y_svg_to_out = (double)metrics.y_ppem / units_per_EM; 194 | + 195 | /* 196 | * We need to take into account any transformations applied. The end 197 | * user who applied the transformation doesn't know the internal details 198 | @@ -302,9 +233,9 @@ 199 | yy = (double)document->transform.yy / ( 1 << 16 ); 200 | 201 | x0 = (double)document->delta.x / 64 * 202 | - dimension_svg.width / metrics.x_ppem; 203 | + handle->Width() / metrics.x_ppem; 204 | y0 = -(double)document->delta.y / 64 * 205 | - dimension_svg.height / metrics.y_ppem; 206 | + handle->Height() / metrics.y_ppem; 207 | 208 | /* Cairo stores both transformation and translation in one matrix. */ 209 | transform_matrix.xx = xx; 210 | @@ -320,46 +251,21 @@ 211 | /* Set up a transformation matrix. */ 212 | cairo_transform( rec_cr, &transform_matrix ); 213 | 214 | + renderer->SetCairo(rec_cr); 215 | /* If the document contains only one glyph, `start_glyph_id` and */ 216 | /* `end_glyph_id` have the same value. Otherwise `end_glyph_id` */ 217 | /* is larger. */ 218 | if ( start_glyph_id < end_glyph_id ) 219 | { 220 | /* Render only the element with its ID equal to `glyph`. */ 221 | - sprintf( str, "#glyph%u", slot->glyph_index ); 222 | + sprintf( str, "glyph%u", slot->glyph_index ); 223 | id = str; 224 | + handle->Render(id, handle->Width(), handle->Height()); 225 | } 226 | else 227 | { 228 | /* NULL = Render the whole document */ 229 | - id = NULL; 230 | - } 231 | - 232 | -#if LIBRSVG_CHECK_VERSION( 2, 52, 0 ) 233 | - { 234 | - RsvgRectangle viewport = 235 | - { 236 | - .x = 0, 237 | - .y = 0, 238 | - .width = (double)dimension_svg.width, 239 | - .height = (double)dimension_svg.height, 240 | - }; 241 | - 242 | - 243 | - ret = rsvg_handle_render_layer( handle, 244 | - rec_cr, 245 | - id, 246 | - &viewport, 247 | - NULL ); 248 | - } 249 | -#else 250 | - ret = rsvg_handle_render_cairo_sub( handle, rec_cr, id ); 251 | -#endif 252 | - 253 | - if ( ret == FALSE ) 254 | - { 255 | - error = FT_Err_Invalid_SVG_Document; 256 | - goto CleanCairo; 257 | + handle->Render(handle->Width(), handle->Height()); 258 | } 259 | 260 | /* Get the bounding box of the drawing. */ 261 | @@ -428,7 +334,6 @@ 262 | 263 | CleanLibrsvg: 264 | /* Destroy the handle. */ 265 | - g_object_unref( handle ); 266 | 267 | return error; 268 | } 269 | @@ -441,11 +346,11 @@ 270 | (SVG_Lib_Preset_Slot_Func)rsvg_port_preset_slot 271 | }; 272 | 273 | -#else /* !HAVE_LIBRSVG */ 274 | +#else /* !HAVE_ADOBESVG */ 275 | 276 | SVG_RendererHooks rsvg_hooks = { NULL, NULL, NULL, NULL }; 277 | 278 | -#endif /* !HAVE_LIBRSVG */ 279 | +#endif /* !HAVE_ADOBESVG */ 280 | 281 | 282 | /* End */ 283 | diff --git a/src/rsvg-port.h b/src/rsvg-port.h 284 | index 073fd5e..188db24 100644 285 | --- a/src/rsvg-port.h 286 | +++ b/src/rsvg-port.h 287 | @@ -22,10 +22,11 @@ 288 | #include 289 | #include 290 | 291 | -#ifdef HAVE_LIBRSVG 292 | +#ifdef HAVE_ADOBESVG 293 | 294 | #include 295 | -#include 296 | +#include "svgnative/SVGDocument.h" 297 | +#include "svgnative/ports/cairo/CairoSVGRenderer.h" 298 | #include 299 | 300 | 301 | @@ -61,7 +62,7 @@ 302 | FT_Bool cache, 303 | FT_Pointer *state ); 304 | 305 | -#endif /* HAVE_LIBRSVG */ 306 | +#endif /* HAVE_ADOBESVG */ 307 | 308 | 309 | extern SVG_RendererHooks rsvg_hooks; 310 | -------------------------------------------------------------------------------- /svg-native/ft2-demos-SkiaSVG-Adobe-SVG-Native.diff: -------------------------------------------------------------------------------- 1 | diff --git a/Makefile b/Makefile 2 | index 3ad5891..2c1c596 100644 3 | --- a/Makefile 4 | +++ b/Makefile 5 | @@ -150,7 +150,7 @@ else 6 | COMPILE = $(CC) $(ANSIFLAGS) \ 7 | $(INCLUDES:%=$I%) \ 8 | $(CFLAGS) \ 9 | - $(FT_DEMO_CFLAGS) -I$(TOP_DIR_2)/skia/ -DHAVE_SKIA 10 | + $(FT_DEMO_CFLAGS) -I$(TOP_DIR_2)/sn/svgnative/include/ -I$(TOP_DIR_2)/sn/svgnative/skia/include/core/ -I$(TOP_DIR_2)/sn/svgnative/skia/ -DHAVE_SKIA 11 | 12 | 13 | # Enable C99 for gcc to avoid warnings. 14 | @@ -189,10 +189,10 @@ else 15 | # `FT_DEMO_LDFLAGS` has been set in `unix-cc.mk`, too. 16 | override CC = $(CCraw) 17 | LINK_CMD = $(LIBTOOL) --mode=link $(CC) \ 18 | - -L$(TOP_DIR_2)/skia/out/Release/ -lsvg -lskia -lskshaper -lskunicode -lGL -lfontconfig -lharfbuzz \ 19 | + -L$(TOP_DIR_2)/sn/svgnative/build/linux/ -lSVGNativeViewerLib -L$(TOP_DIR_2)/sn/svgnative/skia/out/Release/ -lskia -lGL -lfontconfig \ 20 | $(subst /,$(COMPILER_SEP),$(LDFLAGS)) 21 | LINK_LIBS = $(subst /,$(COMPILER_SEP),$(FTLIB) $(EFENCE)) \ 22 | - -L$(TOP_DIR_2)/skia/out/Release/ -lsvg -lskia -lskshaper -lskunicode -lGL -lfontconfig -lharfbuzz \ 23 | + -L$(TOP_DIR_2)/sn/svgnative/build/linux/ -lSVGNativeViewerLib -L$(TOP_DIR_2)/sn/svgnative/skia/out/Release/ -lskia -lGL -lfontconfig \ 24 | $(FT_DEMO_LDFLAGS) 25 | else 26 | LINK_CMD = $(CC) $(subst /,$(COMPILER_SEP),$(LDFLAGS)) 27 | +++ b/README.svg-native 28 | @@ -0,0 +1,4 @@ 29 | +To run: 30 | + 31 | +LD_LIBRARY_PATH=sn/svgnative/build/linux/: ./bin/.libs/ftgrid -s TrajanColor-Concept.otf 32 | +LD_LIBRARY_PATH=sn/svgnative/build/linux/: ./bin/.libs/ftgrid -s Nabla-Regular.ttf 33 | diff --git a/src/skia-port.c b/src/skia-port.c 34 | index d1c8b7a..e818778 100644 35 | --- a/src/skia-port.c 36 | +++ b/src/skia-port.c 37 | @@ -38,9 +38,8 @@ 38 | 39 | #ifdef HAVE_SKIA 40 | 41 | -#include "modules/svg/include/SkSVGDOM.h" 42 | -#include "modules/svg/include/SkSVGNode.h" 43 | -#include "modules/svg/include/SkSVGRenderContext.h" // SkSVGPresentationContext 44 | +#include "svgnative/SVGDocument.h" 45 | +#include "svgnative/ports/skia/SkiaSVGRenderer.h" 46 | #include "include/core/SkBitmap.h" 47 | #include "include/core/SkCanvas.h" 48 | #include "include/core/SkMatrix.h" 49 | @@ -186,15 +185,10 @@ 50 | state = &state_dummy; 51 | 52 | /* Form an `sk_sp` by loading the SVG document. */ 53 | - SkMemoryStream svgmem(document->svg_document, 54 | - document->svg_document_length, false /*not copying */); 55 | - sk_sp svg = SkSVGDOM::MakeFromStream(svgmem); 56 | - 57 | - //svg->getRoot()->intrinsicSize(); 58 | - if (svg->containerSize().isEmpty()) { 59 | - SkSize size = SkSize::Make(units_per_EM, units_per_EM); 60 | - svg->setContainerSize(size); 61 | - } 62 | + auto renderer = std::make_shared(); 63 | + char *s = strndup((char *)document->svg_document, 64 | + document->svg_document_length); 65 | + auto svg = std::unique_ptr(SVGNative::SVGDocument::CreateSVGDocument(s, renderer)); 66 | // Do we care about the viewBox attribute? It is auto I think, anyway. 67 | 68 | /* 69 | @@ -245,12 +239,12 @@ 70 | /* Set up a transformation matrix. */ 71 | recordingCanvas->concat(m); 72 | 73 | + renderer->SetSkCanvas(recordingCanvas); 74 | /* If the document contains only one glyph, `start_glyph_id` and */ 75 | /* `end_glyph_id` have the same value. Otherwise `end_glyph_id` */ 76 | /* is larger. */ 77 | if ( start_glyph_id < end_glyph_id ) 78 | { 79 | - SkSVGPresentationContext pctx; 80 | char id[32]; 81 | /* Render only the element with its ID equal to `glyph`. */ 82 | sprintf( id, "glyph%u", slot->glyph_index ); 83 | @@ -262,12 +256,12 @@ 84 | * whole. In the case of OT-SVG, there is no extra 85 | * Context, so leaving it as default is fine. 86 | */ 87 | - svg->renderNode(recordingCanvas, pctx, id); 88 | + svg->Render(id, svg->Width(), svg->Height()); 89 | } 90 | else 91 | { 92 | /* Render the whole document */ 93 | - svg->render(recordingCanvas); 94 | + svg->Render(svg->Width(), svg->Height()); 95 | } 96 | 97 | /* Get the bounding box of the drawing. */ 98 | -------------------------------------------------------------------------------- /svg-native/ftgrid-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-0.png -------------------------------------------------------------------------------- /svg-native/ftgrid-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-10.png -------------------------------------------------------------------------------- /svg-native/ftgrid-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-11.png -------------------------------------------------------------------------------- /svg-native/ftgrid-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-12.png -------------------------------------------------------------------------------- /svg-native/ftgrid-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-13.png -------------------------------------------------------------------------------- /svg-native/ftgrid-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-14.png -------------------------------------------------------------------------------- /svg-native/ftgrid-190.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-190.png -------------------------------------------------------------------------------- /svg-native/ftgrid-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-2.png -------------------------------------------------------------------------------- /svg-native/ftgrid-222.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-222.png -------------------------------------------------------------------------------- /svg-native/ftgrid-238.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-238.png -------------------------------------------------------------------------------- /svg-native/ftgrid-270.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-270.png -------------------------------------------------------------------------------- /svg-native/ftgrid-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-3.png -------------------------------------------------------------------------------- /svg-native/ftgrid-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-4.png -------------------------------------------------------------------------------- /svg-native/ftgrid-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-5.png -------------------------------------------------------------------------------- /svg-native/ftgrid-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-6.png -------------------------------------------------------------------------------- /svg-native/ftgrid-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-7.png -------------------------------------------------------------------------------- /svg-native/ftgrid-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-8.png -------------------------------------------------------------------------------- /svg-native/ftgrid-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HinTak/harfbuzz-python-demos/e161074d2c86acf7c56d1f1fbb5205a49177a7b7/svg-native/ftgrid-9.png -------------------------------------------------------------------------------- /variation-selectors.md: -------------------------------------------------------------------------------- 1 | # Five different Mongolian fonts 2 | 3 | Mongolian Baiti 4 | 5 | ![mongolian](images/monbaiti-bare.png) 6 | 7 | Noto Sans Mongolian 8 | 9 | ![mongolian](images/NotoSansMongolian-Regular-bare.png) 10 | 11 | Mongolian Title 12 | 13 | ![mongolian](images/mngltitleotf-bare.png) 14 | 15 | Mongolian White 16 | 17 | ![mongolian](images/mnglwhiteotf-bare.png) 18 | 19 | Mongolian Writing 20 | 21 | ![mongolian](images/mnglwritingotf-bare.png) 22 | 23 | (all the images on this page generated with this [script](mongolian-variants.sh) ). 24 | 25 | # Free Variation Selectors 26 | 27 | ## Mongolian Baiti: 28 | 29 | ![mongolian](images/monbaiti-form0.png) 30 | 31 | Mongolian Baiti FVS1 (QA and A): 32 | 33 | ![mongolian](images/monbaiti-form1.png) 34 | 35 | ![mongolian](images/monbaiti-join1.png) 36 | 37 | ![mongolian](images/monbaiti-join2.png) 38 | 39 | ![mongolian](images/monbaiti-join3.png) 40 | 41 | ## Noto Sans Mongolian: 42 | 43 | ![mongolian](images/NotoSansMongolian-Regular-form0.png) 44 | 45 | Noto Sans Mongolian FVS1 (GA, QA, A, NA): 46 | 47 | ![mongolian](images/NotoSansMongolian-Regular-form1.png) 48 | ![mongolian](images/NotoSansMongolian-Regular-join1.png) 49 | 50 | Noto Sans Mongolian FVS2 (GA, QA; GA same as FVS1): 51 | 52 | ![mongolian](images/NotoSansMongolian-Regular-form2.png) 53 | ![mongolian](images/NotoSansMongolian-Regular-join2.png) 54 | 55 | ![mongolian](images/NotoSansMongolian-Regular-join3.png) 56 | 57 | ## Mongolian Title: 58 | 59 | ![mongolian](images/mngltitleotf-form0.png) 60 | 61 | Mongolian Title FVS1 (I, GA, QA, A, NA): 62 | 63 | ![mongolian](images/mngltitleotf-form1.png) 64 | ![mongolian](images/mngltitleotf-join1.png) 65 | 66 | ![mongolian](images/mngltitleotf-join2.png) 67 | 68 | ![mongolian](images/mngltitleotf-join3.png) 69 | 70 | ## Mongolian White: 71 | 72 | ![mongolian](images/mnglwhiteotf-form0.png) 73 | 74 | Mongolian White FVS1 (I, GA, QA, A, NA) : 75 | 76 | ![mongolian](images/mnglwhiteotf-form1.png) 77 | ![mongolian](images/mnglwhiteotf-join1.png) 78 | 79 | Mongolian White FVS2 (GA, QA): 80 | 81 | ![mongolian](images/mnglwhiteotf-form2.png) 82 | ![mongolian](images/mnglwhiteotf-join2.png) 83 | 84 | Mongolian White FVS3 (GA, QA): 85 | 86 | ![mongolian](images/mnglwhiteotf-form3.png) 87 | ![mongolian](images/mnglwhiteotf-join3.png) 88 | 89 | ## Mongolian Writing: 90 | 91 | ![mongolian](images/mnglwritingotf-form0.png) 92 | 93 | Mongolian Writing FVS1 (I, GA, QA, A, NA): 94 | 95 | ![mongolian](images/mnglwritingotf-form1.png) 96 | ![mongolian](images/mnglwritingotf-join1.png) 97 | 98 | Mongolian Writing FVS2 (GA, QA): 99 | 100 | ![mongolian](images/mnglwritingotf-form2.png) 101 | ![mongolian](images/mnglwritingotf-join2.png) 102 | 103 | Mongolian Writing FVS3 (GA, QA): 104 | 105 | ![mongolian](images/mnglwritingotf-form3.png) 106 | ![mongolian](images/mnglwritingotf-join3.png) 107 | 108 | --------------------------------------------------------------------------------