Skip to content

Commit

Permalink
Allow multiple and local values in src descriptor
Browse files Browse the repository at this point in the history
  • Loading branch information
liZe committed Oct 1, 2016
1 parent fc5eb6c commit 38a0a1a
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 57 deletions.
27 changes: 18 additions & 9 deletions weasyprint/css/descriptors.py
Expand Up @@ -52,24 +52,33 @@ def decorator(function):


@descriptor()
def font_family(tokens):
def font_family(tokens, allow_spaces=False):
"""``font-family`` descriptor validation."""
allowed_types = ['IDENT']
if allow_spaces:
allowed_types.append('S')
if len(tokens) == 1 and tokens[0].type == 'STRING':
return tokens[0].value
elif tokens and all(token.type == 'IDENT' for token in tokens):
return ' '.join(token.value for token in tokens)
if tokens and all(token.type in allowed_types for token in tokens):
return ' '.join(
token.value for token in tokens if token.type == 'IDENT')


@descriptor(wants_base_url=True)
@comma_separated_list
def src(tokens, base_url):
"""``src`` descriptor validation."""
token = tokens.pop()
if token.type == 'URI':
if token.value.startswith('#'):
return 'internal', unquote(token.value[1:])
else:
return 'external', safe_urljoin(base_url, token.value)
if len(tokens) <= 2:
token = tokens.pop()
if token.type == 'FUNCTION' and token.function_name == 'format':
token = tokens.pop()
if token.type == 'FUNCTION' and token.function_name == 'local':
return 'local', font_family(token.content, allow_spaces=True)
if token.type == 'URI':
if token.value.startswith('#'):
return 'internal', unquote(token.value[1:])
else:
return 'external', safe_urljoin(base_url, token.value)


@descriptor()
Expand Down
157 changes: 109 additions & 48 deletions weasyprint/text.py
Expand Up @@ -15,6 +15,7 @@

import os
import re
import sys
import tempfile

import pyphen
Expand All @@ -27,6 +28,8 @@

ffi = cffi.FFI()
ffi.cdef('''
// Pango
typedef enum {
PANGO_STYLE_NORMAL,
PANGO_STYLE_OBLIQUE,
Expand Down Expand Up @@ -203,6 +206,18 @@
void pango_cairo_show_layout_line (cairo_t *cr, PangoLayoutLine *line);
// FontConfig
typedef enum _FcResult {
FcResultMatch, FcResultNoMatch, FcResultTypeMismatch, FcResultNoId,
FcResultOutOfMemory
} FcResult;
typedef enum _FcMatchKind {
FcMatchPattern, FcMatchFont, FcMatchScan
} FcMatchKind;
typedef struct _FcConfig FcConfig;
typedef struct _FcPattern FcPattern;
typedef unsigned char FcChar8;
typedef int FcBool;
typedef struct _FcConfig FcConfig;
Expand All @@ -211,6 +226,15 @@
FcConfig * FcConfigGetCurrent (void);
FcBool FcConfigParseAndLoad
(FcConfig *config, const FcChar8 *file, FcBool complain);
FcPattern * FcPatternCreate (void);
FcPattern * FcFontMatch (FcConfig *config, FcPattern *p);
FcBool FcPatternAddString
(FcPattern *p, const char *object, const FcChar8 *s);
FcBool FcConfigSubstitute
(FcConfig *config, FcPattern *p, FcMatchKind kind);
void FcDefaultSubstitute (FcPattern *pattern);
FcResult FcPatternGetString
(FcPattern *p, const char *object, int n, FcChar8 **s);
''')

Expand Down Expand Up @@ -1164,56 +1188,93 @@ def show_first_line(context, pango_layout, hinting):

def add_font_face(rule_descriptors):
"""Add a font into the Fontconfig application."""
if not fontconfig:
LOGGER.warning('@font-face is currently supported only on Linux')
if not fontconfig or (
sys.platform.startswith('win') or
sys.platform.startswith('darwin')):
LOGGER.warning(
'@font-face is currently not supported on Windows and OSX')
return
config = fontconfig.FcConfigGetCurrent()
for font in rule_descriptors['src']:
if font[0] == 'external':
for font_type, url in rule_descriptors['src']:
config = fontconfig.FcConfigGetCurrent()
if font_type in ('external', 'local'):
if font_type == 'local':
font_name = url.encode('utf-8')
pattern = fontconfig.FcPatternCreate()
fontconfig.FcConfigSubstitute(
config, pattern, fontconfig.FcMatchFont)
fontconfig.FcDefaultSubstitute(pattern)
fontconfig.FcPatternAddString(
pattern, b'fullname', font_name)
fontconfig.FcPatternAddString(
pattern, b'postscriptname', font_name)
family = ffi.new('FcChar8 **')
postscript = ffi.new('FcChar8 **')
matching_pattern = fontconfig.FcFontMatch(config, pattern)
# TODO: do many fonts have multiple family values?
fontconfig.FcPatternGetString(
matching_pattern, b'fullname', 0, family)
fontconfig.FcPatternGetString(
matching_pattern, b'postscriptname', 0, postscript)
family = ffi.string(family[0])
postscript = ffi.string(postscript[0])
if font_name.lower() in (family.lower(), postscript.lower()):
filename = ffi.new('FcChar8 **')
matching_pattern = fontconfig.FcFontMatch(
ffi.NULL, pattern)
fontconfig.FcPatternGetString(
matching_pattern, b'file', 0, filename)
url = u'file://' + ffi.string(filename[0]).decode('utf-8')
else:
LOGGER.warning(
'Failed to load local font "%s"',
font_name.decode('utf-8'))
continue
try:
font = urlopen(url).read()
except Exception as exc:
LOGGER.warning('Failed to load font at "%s" (%s)', url, exc)
continue
_, filename = tempfile.mkstemp()
with open(filename, 'wb') as fd:
fd.write(urlopen(font[1]).read())
else:
filename = font[1]
xml = '''<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<match target="scan">
<test name="file" compare="eq"><string>%s</string></test>
<edit name="family" mode="assign_replace">
<string>%s</string>
</edit>
<edit name="slant" mode="assign_replace">
<const>%s</const>
</edit>
<edit name="weight" mode="assign_replace">
<const>%s</const>
</edit>
<edit name="width" mode="assign_replace">
<const>%s</const>
</edit>
</match>
</fontconfig>''' % (
filename,
rule_descriptors['font_family'],
FONTCONFIG_STYLE_CONSTANTS[
rule_descriptors.get('font_style', 'normal')],
FONTCONFIG_WEIGHT_CONSTANTS[
rule_descriptors.get('font_weight', 'normal')],
FONTCONFIG_STRETCH_CONSTANTS[
rule_descriptors.get('font_stretch', 'normal')],
)
_, conf_filename = tempfile.mkstemp()
with open(conf_filename, 'wb') as fd:
# TODO: encoding is OK for <test>, but what about <edit>s?
fd.write(xml.encode(FILESYSTEM_ENCODING))
fontconfig.FcConfigParseAndLoad(
config, conf_filename.encode(FILESYSTEM_ENCODING), True)
os.remove(conf_filename)
font_added = fontconfig.FcConfigAppFontAddFile(
config, filename.encode(FILESYSTEM_ENCODING))
if font_added:
# TODO: we should mask the local fonts with the same name too
return filename
fd.write(font)
xml = '''<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<match target="scan">
<test name="file" compare="eq"><string>%s</string></test>
<edit name="family" mode="assign_replace">
<string>%s</string>
</edit>
<edit name="slant" mode="assign_replace">
<const>%s</const>
</edit>
<edit name="weight" mode="assign_replace">
<const>%s</const>
</edit>
<edit name="width" mode="assign_replace">
<const>%s</const>
</edit>
</match>
</fontconfig>''' % (
filename,
rule_descriptors['font_family'],
FONTCONFIG_STYLE_CONSTANTS[
rule_descriptors.get('font_style', 'normal')],
FONTCONFIG_WEIGHT_CONSTANTS[
rule_descriptors.get('font_weight', 'normal')],
FONTCONFIG_STRETCH_CONSTANTS[
rule_descriptors.get('font_stretch', 'normal')])
_, conf_filename = tempfile.mkstemp()
with open(conf_filename, 'wb') as fd:
# TODO: encoding is OK for <test>, but what about <edit>s?
fd.write(xml.encode(FILESYSTEM_ENCODING))
fontconfig.FcConfigParseAndLoad(
config, conf_filename.encode(FILESYSTEM_ENCODING), True)
os.remove(conf_filename)
font_added = fontconfig.FcConfigAppFontAddFile(
config, filename.encode(FILESYSTEM_ENCODING))
if font_added:
# TODO: we should mask the local fonts with the same name too
return filename
LOGGER.warning(
'Font-face "%s" cannot be loaded' % rule_descriptors['font_family'])

0 comments on commit 38a0a1a

Please sign in to comment.