[feaLib]Escape nameid strings when writing feature files

https://github.com/fonttools/fonttools/issues/780
This commit is contained in:
Sascha Brawer 2017-02-14 10:29:57 +01:00
parent 7cf22d01ae
commit b22df7ff48
4 changed files with 94 additions and 5 deletions

View File

@ -1,6 +1,8 @@
from __future__ import print_function, division, absolute_import
from __future__ import unicode_literals
from fontTools.misc.py23 import *
from fontTools.feaLib.error import FeatureLibError
from fontTools.misc.encodingTools import getEncoding
from collections import OrderedDict
import itertools
@ -1059,10 +1061,27 @@ class NameRecord(Statement):
self.platEncID, self.langID, self.string)
def asFea(self, indent=""):
plat = simplify_name_attributes(self.platformID, self.platEncID, self.langID)
def escape(c, escape_pattern):
# Also escape U+0022 QUOTATION MARK and U+005C REVERSE SOLIDUS
if c >= 0x20 and c <= 0x7E and c not in (0x22, 0x5C):
return unichr(c)
else:
return escape_pattern % c
encoding = getEncoding(self.platformID, self.platEncID, self.langID)
if encoding is None:
raise FeatureLibError("Unsupported encoding", self.location)
s = tobytes(self.string, encoding=encoding)
if encoding == "utf_16_be":
escaped_string = "".join([
escape(byteord(s[i]) * 256 + byteord(s[i + 1]), r"\%04x")
for i in range(0, len(s), 2)])
else:
escaped_string = "".join([escape(byteord(b), r"\%02x") for b in s])
plat = simplify_name_attributes(
self.platformID, self.platEncID, self.langID)
if plat != "":
plat += " "
return "nameid {} {}\"{}\";".format(self.nameID, plat, self.string)
return "nameid {} {}\"{}\";".format(self.nameID, plat, escaped_string)
class FeatureNameStatement(NameRecord):

View File

@ -1,6 +1,4 @@
table name {
#test-fea2fea: nameid 9 "Joachim Müller-Lancé";
nameid 9 "Joachim M\00fcller-Lanc\00e9"; # Windows (Unicode)
#test-fea2fea: nameid 9 1 "Joachim Müller-Lancé";
nameid 9 1 "Joachim M\9fller-Lanc\8e"; # Macintosh (Mac Roman)
} name;

View File

@ -87,7 +87,15 @@ class LexerTest(unittest.TestCase):
[(Lexer.STRING, "foo"), (Lexer.STRING, "bar")])
self.assertEqual(lex('"foo \nbar\r baz \r\nqux\n\n "'),
[(Lexer.STRING, "foo bar baz qux ")])
self.assertRaises(FeatureLibError, lambda: lex('"foo\n bar'))
# The lexer should preserve escape sequences because they have
# different interpretations depending on context. For better
# or for worse, that is how the OpenType Feature File Syntax
# has been specified; see section 9.e (name table) for examples.
self.assertEqual(lex(r'"M\00fcller-Lanc\00e9"'), # 'nameid 9'
[(Lexer.STRING, r"M\00fcller-Lanc\00e9")])
self.assertEqual(lex(r'"M\9fller-Lanc\8e"'), # 'nameid 9 1'
[(Lexer.STRING, r"M\9fller-Lanc\8e")])
self.assertRaises(FeatureLibError, lex, '"foo\n bar')
def test_bad_character(self):
self.assertRaises(FeatureLibError, lambda: lex("123 \u0001"))

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import print_function, division, absolute_import
from __future__ import unicode_literals
from fontTools.feaLib.error import FeatureLibError
@ -901,6 +902,69 @@ class ParserTest(unittest.TestCase):
self.assertEqual(mc.glyphSet(), ("acute", "grave"))
self.assertEqual((mc.anchor.x, mc.anchor.y), (350, 3))
def test_nameid_windows_utf16(self):
doc = self.parse(
r'table name { nameid 9 "M\00fcller-Lanc\00e9"; } name;')
name = doc.statements[0].statements[0]
self.assertIsInstance(name, ast.NameRecord)
self.assertEquals(name.nameID, 9)
self.assertEquals(name.platformID, 3)
self.assertEquals(name.platEncID, 1)
self.assertEquals(name.langID, 0x0409)
self.assertEquals(name.string, "Müller-Lancé")
self.assertEquals(name.asFea(), r'nameid 9 "M\00fcller-Lanc\00e9";')
def test_nameid_windows_utf16_backslash(self):
doc = self.parse(r'table name { nameid 9 "Back\005cslash"; } name;')
name = doc.statements[0].statements[0]
self.assertEquals(name.string, r"Back\slash")
self.assertEquals(name.asFea(), r'nameid 9 "Back\005cslash";')
def test_nameid_windows_utf16_quotation_mark(self):
doc = self.parse(
r'table name { nameid 9 "Quotation \0022Mark\0022"; } name;')
name = doc.statements[0].statements[0]
self.assertEquals(name.string, 'Quotation "Mark"')
self.assertEquals(name.asFea(), r'nameid 9 "Quotation \0022Mark\0022";')
def test_nameid_windows_utf16_surroates(self):
pass
# TODO: https://github.com/fonttools/fonttools/issues/842
# doc = self.parse(r'table name { nameid 9 "Carrot \D83E\DD55"; } name;')
# name = doc.statements[0].statements[0]
# self.assertEquals(name.string, r"Carrot 🥕")
# self.assertEquals(name.asFea(), r'nameid 9 "Carrot \d83e\dd55";')
def test_nameid_mac_roman(self):
doc = self.parse(
r'table name { nameid 9 1 "Joachim M\9fller-Lanc\8e"; } name;')
name = doc.statements[0].statements[0]
self.assertIsInstance(name, ast.NameRecord)
self.assertEquals(name.nameID, 9)
self.assertEquals(name.platformID, 1)
self.assertEquals(name.platEncID, 0)
self.assertEquals(name.langID, 0)
self.assertEquals(name.string, "Joachim Müller-Lancé")
self.assertEquals(name.asFea(),
r'nameid 9 1 "Joachim M\9fller-Lanc\8e";')
def test_nameid_mac_croatian(self):
doc = self.parse(
r'table name { nameid 9 1 0 18 "Jovica Veljovi\e6"; } name;')
name = doc.statements[0].statements[0]
self.assertEquals(name.nameID, 9)
self.assertEquals(name.platformID, 1)
self.assertEquals(name.platEncID, 0)
self.assertEquals(name.langID, 18)
# TODO: https://github.com/fonttools/fonttools/issues/842
# self.assertEquals(name.string, "Jovica Veljović")
# self.assertEquals(name.asFea(), r'nameid 9 1 0 18 "Jovica Veljovi\e6";')
def test_nameid_unsupported_platform(self):
self.assertRaisesRegex(
FeatureLibError, "Expected platform id 1 or 3",
self.parse, 'table name { nameid 9 666 "Foo"; } name;')
def test_rsub_format_a(self):
doc = self.parse("feature test {rsub a [b B] c' d [e E] by C;} test;")
rsub = doc.statements[0].statements[0]