| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943 |
- import itertools
- import io
- from pathlib import Path
- import numpy as np
- import pytest
- import matplotlib as mpl
- from matplotlib import ft2font
- from matplotlib.testing.decorators import check_figures_equal
- import matplotlib.font_manager as fm
- import matplotlib.path as mpath
- import matplotlib.pyplot as plt
- def test_ft2image_draw_rect_filled():
- width = 23
- height = 42
- for x0, y0, x1, y1 in itertools.product([1, 100], [2, 200], [4, 400], [8, 800]):
- im = ft2font.FT2Image(width, height)
- im.draw_rect_filled(x0, y0, x1, y1)
- a = np.asarray(im)
- assert a.dtype == np.uint8
- assert a.shape == (height, width)
- if x0 == 100 or y0 == 200:
- # All the out-of-bounds starts should get automatically clipped.
- assert np.sum(a) == 0
- else:
- # Otherwise, ends are clipped to the dimension, but are also _inclusive_.
- filled = (min(x1 + 1, width) - x0) * (min(y1 + 1, height) - y0)
- assert np.sum(a) == 255 * filled
- def test_ft2font_dejavu_attrs():
- file = fm.findfont('DejaVu Sans')
- font = ft2font.FT2Font(file)
- assert font.fname == file
- # Names extracted from FontForge: Font Information → PS Names tab.
- assert font.postscript_name == 'DejaVuSans'
- assert font.family_name == 'DejaVu Sans'
- assert font.style_name == 'Book'
- assert font.num_faces == 1 # Single TTF.
- assert font.num_named_instances == 0 # Not a variable font.
- assert font.num_glyphs == 6241 # From compact encoding view in FontForge.
- assert font.num_fixed_sizes == 0 # All glyphs are scalable.
- assert font.num_charmaps == 5
- # Other internal flags are set, so only check the ones we're allowed to test.
- expected_flags = (ft2font.FaceFlags.SCALABLE | ft2font.FaceFlags.SFNT |
- ft2font.FaceFlags.HORIZONTAL | ft2font.FaceFlags.KERNING |
- ft2font.FaceFlags.GLYPH_NAMES)
- assert expected_flags in font.face_flags
- assert font.style_flags == ft2font.StyleFlags.NORMAL
- assert font.scalable
- # From FontForge: Font Information → General tab → entry name below.
- assert font.units_per_EM == 2048 # Em Size.
- assert font.underline_position == -175 # Underline position.
- assert font.underline_thickness == 90 # Underline height.
- # From FontForge: Font Information → OS/2 tab → Metrics tab → entry name below.
- assert font.ascender == 1901 # HHead Ascent.
- assert font.descender == -483 # HHead Descent.
- # Unconfirmed values.
- assert font.height == 2384
- assert font.max_advance_width == 3838
- assert font.max_advance_height == 2384
- assert font.bbox == (-2090, -948, 3673, 2524)
- def test_ft2font_cm_attrs():
- file = fm.findfont('cmtt10')
- font = ft2font.FT2Font(file)
- assert font.fname == file
- # Names extracted from FontForge: Font Information → PS Names tab.
- assert font.postscript_name == 'Cmtt10'
- assert font.family_name == 'cmtt10'
- assert font.style_name == 'Regular'
- assert font.num_faces == 1 # Single TTF.
- assert font.num_named_instances == 0 # Not a variable font.
- assert font.num_glyphs == 133 # From compact encoding view in FontForge.
- assert font.num_fixed_sizes == 0 # All glyphs are scalable.
- assert font.num_charmaps == 2
- # Other internal flags are set, so only check the ones we're allowed to test.
- expected_flags = (ft2font.FaceFlags.SCALABLE | ft2font.FaceFlags.SFNT |
- ft2font.FaceFlags.HORIZONTAL | ft2font.FaceFlags.GLYPH_NAMES)
- assert expected_flags in font.face_flags
- assert font.style_flags == ft2font.StyleFlags.NORMAL
- assert font.scalable
- # From FontForge: Font Information → General tab → entry name below.
- assert font.units_per_EM == 2048 # Em Size.
- assert font.underline_position == -143 # Underline position.
- assert font.underline_thickness == 20 # Underline height.
- # From FontForge: Font Information → OS/2 tab → Metrics tab → entry name below.
- assert font.ascender == 1276 # HHead Ascent.
- assert font.descender == -489 # HHead Descent.
- # Unconfirmed values.
- assert font.height == 1765
- assert font.max_advance_width == 1536
- assert font.max_advance_height == 1765
- assert font.bbox == (-12, -477, 1280, 1430)
- def test_ft2font_stix_bold_attrs():
- file = fm.findfont('STIXSizeTwoSym:bold')
- font = ft2font.FT2Font(file)
- assert font.fname == file
- # Names extracted from FontForge: Font Information → PS Names tab.
- assert font.postscript_name == 'STIXSizeTwoSym-Bold'
- assert font.family_name == 'STIXSizeTwoSym'
- assert font.style_name == 'Bold'
- assert font.num_faces == 1 # Single TTF.
- assert font.num_named_instances == 0 # Not a variable font.
- assert font.num_glyphs == 20 # From compact encoding view in FontForge.
- assert font.num_fixed_sizes == 0 # All glyphs are scalable.
- assert font.num_charmaps == 3
- # Other internal flags are set, so only check the ones we're allowed to test.
- expected_flags = (ft2font.FaceFlags.SCALABLE | ft2font.FaceFlags.SFNT |
- ft2font.FaceFlags.HORIZONTAL | ft2font.FaceFlags.GLYPH_NAMES)
- assert expected_flags in font.face_flags
- assert font.style_flags == ft2font.StyleFlags.BOLD
- assert font.scalable
- # From FontForge: Font Information → General tab → entry name below.
- assert font.units_per_EM == 1000 # Em Size.
- assert font.underline_position == -133 # Underline position.
- assert font.underline_thickness == 20 # Underline height.
- # From FontForge: Font Information → OS/2 tab → Metrics tab → entry name below.
- assert font.ascender == 2095 # HHead Ascent.
- assert font.descender == -404 # HHead Descent.
- # Unconfirmed values.
- assert font.height == 2499
- assert font.max_advance_width == 1130
- assert font.max_advance_height == 2499
- assert font.bbox == (4, -355, 1185, 2095)
- def test_ft2font_invalid_args(tmp_path):
- # filename argument.
- with pytest.raises(TypeError, match='to a font file or a binary-mode file object'):
- ft2font.FT2Font(None)
- with pytest.raises(TypeError, match='to a font file or a binary-mode file object'):
- ft2font.FT2Font(object()) # Not bytes or string, and has no read() method.
- file = tmp_path / 'invalid-font.ttf'
- file.write_text('This is not a valid font file.')
- with (pytest.raises(TypeError, match='to a font file or a binary-mode file object'),
- file.open('rt') as fd):
- ft2font.FT2Font(fd)
- with (pytest.raises(TypeError, match='to a font file or a binary-mode file object'),
- file.open('wt') as fd):
- ft2font.FT2Font(fd)
- with (pytest.raises(TypeError, match='to a font file or a binary-mode file object'),
- file.open('wb') as fd):
- ft2font.FT2Font(fd)
- file = fm.findfont('DejaVu Sans')
- # hinting_factor argument.
- with pytest.raises(TypeError, match='incompatible constructor arguments'):
- ft2font.FT2Font(file, 1.3)
- with pytest.raises(ValueError, match='hinting_factor must be greater than 0'):
- ft2font.FT2Font(file, 0)
- with pytest.raises(TypeError, match='incompatible constructor arguments'):
- # failing to be a list will fail before the 0
- ft2font.FT2Font(file, _fallback_list=(0,)) # type: ignore[arg-type]
- with pytest.raises(TypeError, match='incompatible constructor arguments'):
- ft2font.FT2Font(file, _fallback_list=[0]) # type: ignore[list-item]
- # kerning_factor argument.
- with pytest.raises(TypeError, match='incompatible constructor arguments'):
- ft2font.FT2Font(file, _kerning_factor=1.3)
- def test_ft2font_clear():
- file = fm.findfont('DejaVu Sans')
- font = ft2font.FT2Font(file)
- assert font.get_num_glyphs() == 0
- assert font.get_width_height() == (0, 0)
- assert font.get_bitmap_offset() == (0, 0)
- font.set_text('ABabCDcd')
- assert font.get_num_glyphs() == 8
- assert font.get_width_height() != (0, 0)
- assert font.get_bitmap_offset() != (0, 0)
- font.clear()
- assert font.get_num_glyphs() == 0
- assert font.get_width_height() == (0, 0)
- assert font.get_bitmap_offset() == (0, 0)
- def test_ft2font_set_size():
- file = fm.findfont('DejaVu Sans')
- # Default is 12pt @ 72 dpi.
- font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=1)
- font.set_text('ABabCDcd')
- orig = font.get_width_height()
- font.set_size(24, 72)
- font.set_text('ABabCDcd')
- assert font.get_width_height() == tuple(pytest.approx(2 * x, 1e-1) for x in orig)
- font.set_size(12, 144)
- font.set_text('ABabCDcd')
- assert font.get_width_height() == tuple(pytest.approx(2 * x, 1e-1) for x in orig)
- def test_ft2font_charmaps():
- def enc(name):
- # We don't expose the encoding enum from FreeType, but can generate it here.
- # For DejaVu, there are 5 charmaps, but only 2 have enum entries in FreeType.
- e = 0
- for x in name:
- e <<= 8
- e += ord(x)
- return e
- file = fm.findfont('DejaVu Sans')
- font = ft2font.FT2Font(file)
- assert font.num_charmaps == 5
- # Unicode.
- font.select_charmap(enc('unic'))
- unic = font.get_charmap()
- font.set_charmap(0) # Unicode platform, Unicode BMP only.
- after = font.get_charmap()
- assert len(after) <= len(unic)
- for chr, glyph in after.items():
- assert unic[chr] == glyph == font.get_char_index(chr)
- font.set_charmap(1) # Unicode platform, modern subtable.
- after = font.get_charmap()
- assert unic == after
- font.set_charmap(3) # Windows platform, Unicode BMP only.
- after = font.get_charmap()
- assert len(after) <= len(unic)
- for chr, glyph in after.items():
- assert unic[chr] == glyph == font.get_char_index(chr)
- font.set_charmap(4) # Windows platform, Unicode full repertoire, modern subtable.
- after = font.get_charmap()
- assert unic == after
- # This is just a random sample from FontForge.
- glyph_names = {
- 'non-existent-glyph-name': 0,
- 'plusminus': 115,
- 'Racute': 278,
- 'perthousand': 2834,
- 'seveneighths': 3057,
- 'triagup': 3721,
- 'uni01D3': 405,
- 'uni0417': 939,
- 'uni2A02': 4464,
- 'u1D305': 5410,
- 'u1F0A1': 5784,
- }
- for name, index in glyph_names.items():
- assert font.get_name_index(name) == index
- if name == 'non-existent-glyph-name':
- name = '.notdef'
- # This doesn't always apply, but it does for DejaVu Sans.
- assert font.get_glyph_name(index) == name
- # Apple Roman.
- font.select_charmap(enc('armn'))
- armn = font.get_charmap()
- font.set_charmap(2) # Macintosh platform, Roman.
- after = font.get_charmap()
- assert armn == after
- assert len(armn) <= 256 # 8-bit encoding.
- # The first 128 characters of Apple Roman match ASCII, which also matches Unicode.
- for o in range(1, 128):
- if o not in armn or o not in unic:
- continue
- assert unic[o] == armn[o]
- # Check a couple things outside the ASCII set that are different in each charset.
- examples = [
- # (Unicode, Macintosh)
- (0x2020, 0xA0), # Dagger.
- (0x00B0, 0xA1), # Degree symbol.
- (0x00A3, 0xA3), # Pound sign.
- (0x00A7, 0xA4), # Section sign.
- (0x00B6, 0xA6), # Pilcrow.
- (0x221E, 0xB0), # Infinity symbol.
- ]
- for u, m in examples:
- # Though the encoding is different, the glyph should be the same.
- assert unic[u] == armn[m]
- _expected_sfnt_names = {
- 'DejaVu Sans': {
- 0: 'Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved.\n'
- 'Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.\n'
- 'DejaVu changes are in public domain\n',
- 1: 'DejaVu Sans',
- 2: 'Book',
- 3: 'DejaVu Sans',
- 4: 'DejaVu Sans',
- 5: 'Version 2.35',
- 6: 'DejaVuSans',
- 8: 'DejaVu fonts team',
- 11: 'http://dejavu.sourceforge.net',
- 13: 'Fonts are (c) Bitstream (see below). '
- 'DejaVu changes are in public domain. '
- '''Glyphs imported from Arev fonts are (c) Tavmjung Bah (see below)
- Bitstream Vera Fonts Copyright
- ------------------------------
- Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
- a trademark of Bitstream, Inc.
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of the fonts accompanying this license ("Fonts") and associated
- documentation files (the "Font Software"), to reproduce and distribute the
- Font Software, including without limitation the rights to use, copy, merge,
- publish, distribute, and/or sell copies of the Font Software, and to permit
- persons to whom the Font Software is furnished to do so, subject to the
- following conditions:
- The above copyright and trademark notices and this permission notice shall
- be included in all copies of one or more of the Font Software typefaces.
- The Font Software may be modified, altered, or added to, and in particular
- the designs of glyphs or characters in the Fonts may be modified and
- additional glyphs or characters may be added to the Fonts, only if the fonts
- are renamed to names not containing either the words "Bitstream" or the word
- "Vera".
- This License becomes null and void to the extent applicable to Fonts or Font
- Software that has been modified and is distributed under the "Bitstream
- Vera" names.
- The Font Software may be sold as part of a larger software package but no
- copy of one or more of the Font Software typefaces may be sold by itself.
- THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
- TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
- FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
- ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
- THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
- FONT SOFTWARE.
- Except as contained in this notice, the names of Gnome, the Gnome
- Foundation, and Bitstream Inc., shall not be used in advertising or
- otherwise to promote the sale, use or other dealings in this Font Software
- without prior written authorization from the Gnome Foundation or Bitstream
- Inc., respectively. For further information, contact: fonts at gnome dot
- org. ''' '''
- Arev Fonts Copyright
- ------------------------------
- Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of the fonts accompanying this license ("Fonts") and
- associated documentation files (the "Font Software"), to reproduce
- and distribute the modifications to the Bitstream Vera Font Software,
- including without limitation the rights to use, copy, merge, publish,
- distribute, and/or sell copies of the Font Software, and to permit
- persons to whom the Font Software is furnished to do so, subject to
- the following conditions:
- The above copyright and trademark notices and this permission notice
- shall be included in all copies of one or more of the Font Software
- typefaces.
- The Font Software may be modified, altered, or added to, and in
- particular the designs of glyphs or characters in the Fonts may be
- modified and additional glyphs or characters may be added to the
- Fonts, only if the fonts are renamed to names not containing either
- the words "Tavmjong Bah" or the word "Arev".
- This License becomes null and void to the extent applicable to Fonts
- or Font Software that has been modified and is distributed under the ''' '''
- "Tavmjong Bah Arev" names.
- The Font Software may be sold as part of a larger software package but
- no copy of one or more of the Font Software typefaces may be sold by
- itself.
- THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
- OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
- TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
- DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
- OTHER DEALINGS IN THE FONT SOFTWARE.
- Except as contained in this notice, the name of Tavmjong Bah shall not
- be used in advertising or otherwise to promote the sale, use or other
- dealings in this Font Software without prior written authorization
- from Tavmjong Bah. For further information, contact: tavmjong @ free
- . fr.''',
- 14: 'http://dejavu.sourceforge.net/wiki/index.php/License',
- 16: 'DejaVu Sans',
- 17: 'Book',
- },
- 'cmtt10': {
- 0: 'Copyright (C) 1994, Basil K. Malyshev. All Rights Reserved.'
- '012BaKoMa Fonts Collection, Level-B.',
- 1: 'cmtt10',
- 2: 'Regular',
- 3: 'FontMonger:cmtt10',
- 4: 'cmtt10',
- 5: '1.1/12-Nov-94',
- 6: 'Cmtt10',
- },
- 'STIXSizeTwoSym:bold': {
- 0: 'Copyright (c) 2001-2010 by the STI Pub Companies, consisting of the '
- 'American Chemical Society, the American Institute of Physics, the American '
- 'Mathematical Society, the American Physical Society, Elsevier, Inc., and '
- 'The Institute of Electrical and Electronic Engineers, Inc. Portions '
- 'copyright (c) 1998-2003 by MicroPress, Inc. Portions copyright (c) 1990 by '
- 'Elsevier, Inc. All rights reserved.',
- 1: 'STIXSizeTwoSym',
- 2: 'Bold',
- 3: 'FontMaster:STIXSizeTwoSym-Bold:1.0.0',
- 4: 'STIXSizeTwoSym-Bold',
- 5: 'Version 1.0.0',
- 6: 'STIXSizeTwoSym-Bold',
- 7: 'STIX Fonts(TM) is a trademark of The Institute of Electrical and '
- 'Electronics Engineers, Inc.',
- 9: 'MicroPress Inc., with final additions and corrections provided by Coen '
- 'Hoffman, Elsevier (retired)',
- 10: 'Arie de Ruiter, who in 1995 was Head of Information Technology '
- 'Development at Elsevier Science, made a proposal to the STI Pub group, an '
- 'informal group of publishers consisting of representatives from the '
- 'American Chemical Society (ACS), American Institute of Physics (AIP), '
- 'American Mathematical Society (AMS), American Physical Society (APS), '
- 'Elsevier, and Institute of Electrical and Electronics Engineers (IEEE). '
- 'De Ruiter encouraged the members to consider development of a series of '
- 'Web fonts, which he proposed should be called the Scientific and '
- 'Technical Information eXchange, or STIX, Fonts. All STI Pub member '
- 'organizations enthusiastically endorsed this proposal, and the STI Pub '
- 'group agreed to embark on what has become a twelve-year project. The goal '
- 'of the project was to identify all alphabetic, symbolic, and other '
- 'special characters used in any facet of scientific publishing and to '
- 'create a set of Unicode-based fonts that would be distributed free to '
- 'every scientist, student, and other interested party worldwide. The fonts '
- 'would be consistent with the emerging Unicode standard, and would permit '
- 'universal representation of every character. With the release of the STIX '
- "fonts, de Ruiter's vision has been realized.",
- 11: 'http://www.stixfonts.org',
- 12: 'http://www.micropress-inc.com',
- 13: 'As a condition for receiving these fonts at no charge, each person '
- 'downloading the fonts must agree to some simple license terms. The '
- 'license is based on the SIL Open Font License '
- '<http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL>. The '
- 'SIL License is a free and open source license specifically designed for '
- 'fonts and related software. The basic terms are that the recipient will '
- 'not remove the copyright and trademark statements from the fonts and '
- 'that, if the person decides to create a derivative work based on the STIX '
- 'Fonts but incorporating some changes or enhancements, the derivative work '
- '("Modified Version") will carry a different name. The copyright and '
- 'trademark restrictions are part of the agreement between the STI Pub '
- 'companies and the typeface designer. The "renaming" restriction results '
- 'from the desire of the STI Pub companies to assure that the STIX Fonts '
- 'will continue to function in a predictable fashion for all that use them. '
- 'No copy of one or more of the individual Font typefaces that form the '
- 'STIX Fonts(TM) set may be sold by itself, but other than this one '
- 'restriction, licensees are free to sell the fonts either separately or as '
- 'part of a package that combines other software or fonts with this font '
- 'set.',
- 14: 'http://www.stixfonts.org/user_license.html',
- },
- }
- @pytest.mark.parametrize('font_name, expected', _expected_sfnt_names.items(),
- ids=_expected_sfnt_names.keys())
- def test_ft2font_get_sfnt(font_name, expected):
- file = fm.findfont(font_name)
- font = ft2font.FT2Font(file)
- sfnt = font.get_sfnt()
- for name, value in expected.items():
- # Macintosh, Unicode 1.0, English, name.
- assert sfnt.pop((1, 0, 0, name)) == value.encode('ascii')
- # Microsoft, Unicode, English United States, name.
- assert sfnt.pop((3, 1, 1033, name)) == value.encode('utf-16be')
- assert sfnt == {}
- _expected_sfnt_tables = {
- 'DejaVu Sans': {
- 'invalid': None,
- 'head': {
- 'version': (1, 0),
- 'fontRevision': (2, 22937),
- 'checkSumAdjustment': -175678572,
- 'magicNumber': 0x5F0F3CF5,
- 'flags': 31,
- 'unitsPerEm': 2048,
- 'created': (0, 3514699492), 'modified': (0, 3514699492),
- 'xMin': -2090, 'yMin': -948, 'xMax': 3673, 'yMax': 2524,
- 'macStyle': 0,
- 'lowestRecPPEM': 8,
- 'fontDirectionHint': 0,
- 'indexToLocFormat': 1,
- 'glyphDataFormat': 0,
- },
- 'maxp': {
- 'version': (1, 0),
- 'numGlyphs': 6241,
- 'maxPoints': 852, 'maxComponentPoints': 104, 'maxTwilightPoints': 16,
- 'maxContours': 43, 'maxComponentContours': 12,
- 'maxZones': 2,
- 'maxStorage': 153,
- 'maxFunctionDefs': 64,
- 'maxInstructionDefs': 0,
- 'maxStackElements': 1045,
- 'maxSizeOfInstructions': 534,
- 'maxComponentElements': 8,
- 'maxComponentDepth': 4,
- },
- 'OS/2': {
- 'version': 1,
- 'xAvgCharWidth': 1038,
- 'usWeightClass': 400, 'usWidthClass': 5,
- 'fsType': 0,
- 'ySubscriptXSize': 1331, 'ySubscriptYSize': 1433,
- 'ySubscriptXOffset': 0, 'ySubscriptYOffset': 286,
- 'ySuperscriptXSize': 1331, 'ySuperscriptYSize': 1433,
- 'ySuperscriptXOffset': 0, 'ySuperscriptYOffset': 983,
- 'yStrikeoutSize': 102, 'yStrikeoutPosition': 530,
- 'sFamilyClass': 0,
- 'panose': b'\x02\x0b\x06\x03\x03\x08\x04\x02\x02\x04',
- 'ulCharRange': (3875565311, 3523280383, 170156073, 67117068),
- 'achVendID': b'PfEd',
- 'fsSelection': 64, 'fsFirstCharIndex': 32, 'fsLastCharIndex': 65535,
- },
- 'hhea': {
- 'version': (1, 0),
- 'ascent': 1901, 'descent': -483, 'lineGap': 0,
- 'advanceWidthMax': 3838,
- 'minLeftBearing': -2090, 'minRightBearing': -1455,
- 'xMaxExtent': 3673,
- 'caretSlopeRise': 1, 'caretSlopeRun': 0, 'caretOffset': 0,
- 'metricDataFormat': 0, 'numOfLongHorMetrics': 6226,
- },
- 'vhea': None,
- 'post': {
- 'format': (2, 0),
- 'isFixedPitch': 0, 'italicAngle': (0, 0),
- 'underlinePosition': -130, 'underlineThickness': 90,
- 'minMemType42': 0, 'maxMemType42': 0,
- 'minMemType1': 0, 'maxMemType1': 0,
- },
- 'pclt': None,
- },
- 'cmtt10': {
- 'invalid': None,
- 'head': {
- 'version': (1, 0),
- 'fontRevision': (1, 0),
- 'checkSumAdjustment': 555110277,
- 'magicNumber': 0x5F0F3CF5,
- 'flags': 3,
- 'unitsPerEm': 2048,
- 'created': (0, 0), 'modified': (0, 0),
- 'xMin': -12, 'yMin': -477, 'xMax': 1280, 'yMax': 1430,
- 'macStyle': 0,
- 'lowestRecPPEM': 6,
- 'fontDirectionHint': 2,
- 'indexToLocFormat': 1,
- 'glyphDataFormat': 0,
- },
- 'maxp': {
- 'version': (1, 0),
- 'numGlyphs': 133,
- 'maxPoints': 94, 'maxComponentPoints': 0, 'maxTwilightPoints': 12,
- 'maxContours': 5, 'maxComponentContours': 0,
- 'maxZones': 2,
- 'maxStorage': 6,
- 'maxFunctionDefs': 64,
- 'maxInstructionDefs': 0,
- 'maxStackElements': 200,
- 'maxSizeOfInstructions': 100,
- 'maxComponentElements': 4,
- 'maxComponentDepth': 1,
- },
- 'OS/2': {
- 'version': 0,
- 'xAvgCharWidth': 1075,
- 'usWeightClass': 400, 'usWidthClass': 5,
- 'fsType': 0,
- 'ySubscriptXSize': 410, 'ySubscriptYSize': 369,
- 'ySubscriptXOffset': 0, 'ySubscriptYOffset': -469,
- 'ySuperscriptXSize': 410, 'ySuperscriptYSize': 369,
- 'ySuperscriptXOffset': 0, 'ySuperscriptYOffset': 1090,
- 'yStrikeoutSize': 102, 'yStrikeoutPosition': 530,
- 'sFamilyClass': 0,
- 'panose': b'\x02\x0b\x05\x00\x00\x00\x00\x00\x00\x00',
- 'ulCharRange': (0, 0, 0, 0),
- 'achVendID': b'\x00\x00\x00\x00',
- 'fsSelection': 64, 'fsFirstCharIndex': 32, 'fsLastCharIndex': 9835,
- },
- 'hhea': {
- 'version': (1, 0),
- 'ascent': 1276, 'descent': -489, 'lineGap': 0,
- 'advanceWidthMax': 1536,
- 'minLeftBearing': -12, 'minRightBearing': -29,
- 'xMaxExtent': 1280,
- 'caretSlopeRise': 1, 'caretSlopeRun': 0, 'caretOffset': 0,
- 'metricDataFormat': 0, 'numOfLongHorMetrics': 133,
- },
- 'vhea': None,
- 'post': {
- 'format': (2, 0),
- 'isFixedPitch': 0, 'italicAngle': (0, 0),
- 'underlinePosition': -133, 'underlineThickness': 20,
- 'minMemType42': 0, 'maxMemType42': 0,
- 'minMemType1': 0, 'maxMemType1': 0,
- },
- 'pclt': {
- 'version': (1, 0),
- 'fontNumber': 2147483648,
- 'pitch': 1075,
- 'xHeight': 905,
- 'style': 0,
- 'typeFamily': 0,
- 'capHeight': 1276,
- 'symbolSet': 0,
- 'typeFace': b'cmtt10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- 'characterComplement': b'\xff\xff\xff\xff7\xff\xff\xfe',
- 'strokeWeight': 0,
- 'widthType': -5,
- 'serifStyle': 64,
- },
- },
- 'STIXSizeTwoSym:bold': {
- 'invalid': None,
- 'head': {
- 'version': (1, 0),
- 'fontRevision': (1, 0),
- 'checkSumAdjustment': 1803408080,
- 'magicNumber': 0x5F0F3CF5,
- 'flags': 11,
- 'unitsPerEm': 1000,
- 'created': (0, 3359035786), 'modified': (0, 3359035786),
- 'xMin': 4, 'yMin': -355, 'xMax': 1185, 'yMax': 2095,
- 'macStyle': 1,
- 'lowestRecPPEM': 8,
- 'fontDirectionHint': 2,
- 'indexToLocFormat': 0,
- 'glyphDataFormat': 0,
- },
- 'maxp': {
- 'version': (1, 0),
- 'numGlyphs': 20,
- 'maxPoints': 37, 'maxComponentPoints': 0, 'maxTwilightPoints': 0,
- 'maxContours': 1, 'maxComponentContours': 0,
- 'maxZones': 2,
- 'maxStorage': 1,
- 'maxFunctionDefs': 64,
- 'maxInstructionDefs': 0,
- 'maxStackElements': 64,
- 'maxSizeOfInstructions': 0,
- 'maxComponentElements': 0,
- 'maxComponentDepth': 0,
- },
- 'OS/2': {
- 'version': 2,
- 'xAvgCharWidth': 598,
- 'usWeightClass': 700, 'usWidthClass': 5,
- 'fsType': 0,
- 'ySubscriptXSize': 500, 'ySubscriptYSize': 500,
- 'ySubscriptXOffset': 0, 'ySubscriptYOffset': 250,
- 'ySuperscriptXSize': 500, 'ySuperscriptYSize': 500,
- 'ySuperscriptXOffset': 0, 'ySuperscriptYOffset': 500,
- 'yStrikeoutSize': 20, 'yStrikeoutPosition': 1037,
- 'sFamilyClass': 0,
- 'panose': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
- 'ulCharRange': (3, 192, 0, 0),
- 'achVendID': b'STIX',
- 'fsSelection': 32, 'fsFirstCharIndex': 32, 'fsLastCharIndex': 10217,
- },
- 'hhea': {
- 'version': (1, 0),
- 'ascent': 2095, 'descent': -404, 'lineGap': 0,
- 'advanceWidthMax': 1130,
- 'minLeftBearing': 0, 'minRightBearing': -55,
- 'xMaxExtent': 1185,
- 'caretSlopeRise': 1, 'caretSlopeRun': 0, 'caretOffset': 0,
- 'metricDataFormat': 0, 'numOfLongHorMetrics': 19,
- },
- 'vhea': None,
- 'post': {
- 'format': (2, 0),
- 'isFixedPitch': 0, 'italicAngle': (0, 0),
- 'underlinePosition': -123, 'underlineThickness': 20,
- 'minMemType42': 0, 'maxMemType42': 0,
- 'minMemType1': 0, 'maxMemType1': 0,
- },
- 'pclt': None,
- },
- }
- @pytest.mark.parametrize('font_name', _expected_sfnt_tables.keys())
- @pytest.mark.parametrize('header', _expected_sfnt_tables['DejaVu Sans'].keys())
- def test_ft2font_get_sfnt_table(font_name, header):
- file = fm.findfont(font_name)
- font = ft2font.FT2Font(file)
- assert font.get_sfnt_table(header) == _expected_sfnt_tables[font_name][header]
- @pytest.mark.parametrize('left, right, unscaled, unfitted, default', [
- # These are all the same class.
- ('A', 'A', 57, 248, 256), ('A', 'À', 57, 248, 256), ('A', 'Á', 57, 248, 256),
- ('A', 'Â', 57, 248, 256), ('A', 'Ã', 57, 248, 256), ('A', 'Ä', 57, 248, 256),
- # And a few other random ones.
- ('D', 'A', -36, -156, -128), ('T', '.', -243, -1056, -1024),
- ('X', 'C', -149, -647, -640), ('-', 'J', 114, 495, 512),
- ])
- def test_ft2font_get_kerning(left, right, unscaled, unfitted, default):
- file = fm.findfont('DejaVu Sans')
- # With unscaled, these settings should produce exact values found in FontForge.
- font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
- font.set_size(100, 100)
- assert font.get_kerning(font.get_char_index(ord(left)),
- font.get_char_index(ord(right)),
- ft2font.Kerning.UNSCALED) == unscaled
- assert font.get_kerning(font.get_char_index(ord(left)),
- font.get_char_index(ord(right)),
- ft2font.Kerning.UNFITTED) == unfitted
- assert font.get_kerning(font.get_char_index(ord(left)),
- font.get_char_index(ord(right)),
- ft2font.Kerning.DEFAULT) == default
- with pytest.warns(mpl.MatplotlibDeprecationWarning,
- match='Use Kerning.UNSCALED instead'):
- k = ft2font.KERNING_UNSCALED
- with pytest.warns(mpl.MatplotlibDeprecationWarning,
- match='Use Kerning enum values instead'):
- assert font.get_kerning(font.get_char_index(ord(left)),
- font.get_char_index(ord(right)),
- int(k)) == unscaled
- with pytest.warns(mpl.MatplotlibDeprecationWarning,
- match='Use Kerning.UNFITTED instead'):
- k = ft2font.KERNING_UNFITTED
- with pytest.warns(mpl.MatplotlibDeprecationWarning,
- match='Use Kerning enum values instead'):
- assert font.get_kerning(font.get_char_index(ord(left)),
- font.get_char_index(ord(right)),
- int(k)) == unfitted
- with pytest.warns(mpl.MatplotlibDeprecationWarning,
- match='Use Kerning.DEFAULT instead'):
- k = ft2font.KERNING_DEFAULT
- with pytest.warns(mpl.MatplotlibDeprecationWarning,
- match='Use Kerning enum values instead'):
- assert font.get_kerning(font.get_char_index(ord(left)),
- font.get_char_index(ord(right)),
- int(k)) == default
- def test_ft2font_set_text():
- file = fm.findfont('DejaVu Sans')
- font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
- xys = font.set_text('')
- np.testing.assert_array_equal(xys, np.empty((0, 2)))
- assert font.get_width_height() == (0, 0)
- assert font.get_num_glyphs() == 0
- assert font.get_descent() == 0
- assert font.get_bitmap_offset() == (0, 0)
- # This string uses all the kerning pairs defined for test_ft2font_get_kerning.
- xys = font.set_text('AADAT.XC-J')
- np.testing.assert_array_equal(
- xys,
- [(0, 0), (512, 0), (1024, 0), (1600, 0), (2112, 0), (2496, 0), (2688, 0),
- (3200, 0), (3712, 0), (4032, 0)])
- assert font.get_width_height() == (4288, 768)
- assert font.get_num_glyphs() == 10
- assert font.get_descent() == 192
- assert font.get_bitmap_offset() == (6, 0)
- def test_ft2font_loading():
- file = fm.findfont('DejaVu Sans')
- font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
- for glyph in [font.load_char(ord('M')),
- font.load_glyph(font.get_char_index(ord('M')))]:
- assert glyph is not None
- assert glyph.width == 576
- assert glyph.height == 576
- assert glyph.horiBearingX == 0
- assert glyph.horiBearingY == 576
- assert glyph.horiAdvance == 640
- assert glyph.linearHoriAdvance == 678528
- assert glyph.vertBearingX == -384
- assert glyph.vertBearingY == 64
- assert glyph.vertAdvance == 832
- assert glyph.bbox == (54, 0, 574, 576)
- assert font.get_num_glyphs() == 2 # Both count as loaded.
- # But neither has been placed anywhere.
- assert font.get_width_height() == (0, 0)
- assert font.get_descent() == 0
- assert font.get_bitmap_offset() == (0, 0)
- def test_ft2font_drawing():
- expected_str = (
- ' ',
- '11 11 ',
- '11 11 ',
- '1 1 1 1 ',
- '1 1 1 1 ',
- '1 1 1 1 ',
- '1 11 1 ',
- '1 11 1 ',
- '1 1 ',
- '1 1 ',
- ' ',
- )
- expected = np.array([
- [int(c) for c in line.replace(' ', '0')] for line in expected_str
- ])
- expected *= 255
- file = fm.findfont('DejaVu Sans')
- font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
- font.set_text('M')
- font.draw_glyphs_to_bitmap(antialiased=False)
- image = font.get_image()
- np.testing.assert_array_equal(image, expected)
- font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
- glyph = font.load_char(ord('M'))
- image = ft2font.FT2Image(expected.shape[1], expected.shape[0])
- font.draw_glyph_to_bitmap(image, -1, 1, glyph, antialiased=False)
- np.testing.assert_array_equal(image, expected)
- def test_ft2font_get_path():
- file = fm.findfont('DejaVu Sans')
- font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0)
- vertices, codes = font.get_path()
- assert vertices.shape == (0, 2)
- assert codes.shape == (0, )
- font.load_char(ord('M'))
- vertices, codes = font.get_path()
- expected_vertices = np.array([
- (0.843750, 9.000000), (2.609375, 9.000000), # Top left.
- (4.906250, 2.875000), # Top of midpoint.
- (7.218750, 9.000000), (8.968750, 9.000000), # Top right.
- (8.968750, 0.000000), (7.843750, 0.000000), # Bottom right.
- (7.843750, 7.906250), # Point under top right.
- (5.531250, 1.734375), (4.296875, 1.734375), # Bar under midpoint.
- (1.984375, 7.906250), # Point under top left.
- (1.984375, 0.000000), (0.843750, 0.000000), # Bottom left.
- (0.843750, 9.000000), # Back to top left corner.
- (0.000000, 0.000000),
- ])
- np.testing.assert_array_equal(vertices, expected_vertices)
- expected_codes = np.full(expected_vertices.shape[0], mpath.Path.LINETO,
- dtype=mpath.Path.code_type)
- expected_codes[0] = mpath.Path.MOVETO
- expected_codes[-1] = mpath.Path.CLOSEPOLY
- np.testing.assert_array_equal(codes, expected_codes)
- @pytest.mark.parametrize('family_name, file_name',
- [("WenQuanYi Zen Hei", "wqy-zenhei.ttc"),
- ("Noto Sans CJK JP", "NotoSansCJK.ttc"),
- ("Noto Sans TC", "NotoSansTC-Regular.otf")]
- )
- def test_fallback_smoke(family_name, file_name):
- fp = fm.FontProperties(family=[family_name])
- if Path(fm.findfont(fp)).name != file_name:
- pytest.skip(f"Font {family_name} ({file_name}) is missing")
- plt.rcParams['font.size'] = 20
- fig = plt.figure(figsize=(4.75, 1.85))
- fig.text(0.05, 0.45, "There are 几个汉字 in between!",
- family=['DejaVu Sans', family_name])
- fig.text(0.05, 0.85, "There are 几个汉字 in between!",
- family=[family_name])
- # TODO enable fallback for other backends!
- for fmt in ['png', 'raw']: # ["svg", "pdf", "ps"]:
- fig.savefig(io.BytesIO(), format=fmt)
- @pytest.mark.parametrize('family_name, file_name',
- [("WenQuanYi Zen Hei", "wqy-zenhei"),
- ("Noto Sans CJK JP", "NotoSansCJK"),
- ("Noto Sans TC", "NotoSansTC-Regular.otf")]
- )
- @check_figures_equal(extensions=["png", "pdf", "eps", "svg"])
- def test_font_fallback_chinese(fig_test, fig_ref, family_name, file_name):
- fp = fm.FontProperties(family=[family_name])
- if file_name not in Path(fm.findfont(fp)).name:
- pytest.skip(f"Font {family_name} ({file_name}) is missing")
- text = ["There are", "几个汉字", "in between!"]
- plt.rcParams["font.size"] = 20
- test_fonts = [["DejaVu Sans", family_name]] * 3
- ref_fonts = [["DejaVu Sans"], [family_name], ["DejaVu Sans"]]
- for j, (txt, test_font, ref_font) in enumerate(
- zip(text, test_fonts, ref_fonts)
- ):
- fig_ref.text(0.05, .85 - 0.15*j, txt, family=ref_font)
- fig_test.text(0.05, .85 - 0.15*j, txt, family=test_font)
- @pytest.mark.parametrize("font_list",
- [['DejaVu Serif', 'DejaVu Sans'],
- ['DejaVu Sans Mono']],
- ids=["two fonts", "one font"])
- def test_fallback_missing(recwarn, font_list):
- fig = plt.figure()
- fig.text(.5, .5, "Hello 🙃 World!", family=font_list)
- fig.canvas.draw()
- assert all(isinstance(warn.message, UserWarning) for warn in recwarn)
- # not sure order is guaranteed on the font listing so
- assert recwarn[0].message.args[0].startswith(
- "Glyph 128579 (\\N{UPSIDE-DOWN FACE}) missing from font(s)")
- assert all([font in recwarn[0].message.args[0] for font in font_list])
- @pytest.mark.parametrize(
- "family_name, file_name",
- [
- ("WenQuanYi Zen Hei", "wqy-zenhei"),
- ("Noto Sans CJK JP", "NotoSansCJK"),
- ("Noto Sans TC", "NotoSansTC-Regular.otf")
- ],
- )
- def test__get_fontmap(family_name, file_name):
- fp = fm.FontProperties(family=[family_name])
- found_file_name = Path(fm.findfont(fp)).name
- if file_name not in found_file_name:
- pytest.skip(f"Font {family_name} ({file_name}) is missing")
- text = "There are 几个汉字 in between!"
- ft = fm.get_font(
- fm.fontManager._find_fonts_by_props(
- fm.FontProperties(family=["DejaVu Sans", family_name])
- )
- )
- fontmap = ft._get_fontmap(text)
- for char, font in fontmap.items():
- if ord(char) > 127:
- assert Path(font.fname).name == found_file_name
- else:
- assert Path(font.fname).name == "DejaVuSans.ttf"
|