<commit_before>"""
    weasyprint.text.fonts
    ---------------------

    Interface with external libraries managing fonts installed on the system.

"""

import io
import os
import pathlib
import sys
import tempfile
import warnings

from fontTools.ttLib import TTFont, woff2

from ..logger import LOGGER
from ..urls import FILESYSTEM_ENCODING, fetch
from .constants import (
    CAPS_KEYS, EAST_ASIAN_KEYS, FONTCONFIG_STRETCH, FONTCONFIG_STYLE,
    FONTCONFIG_WEIGHT, LIGATURE_KEYS, NUMERIC_KEYS)
from .ffi import ffi, fontconfig, gobject, pangoft2

def _check_font_configuration(font_config):
    """Check whether the given font_config has fonts.

    The default fontconfig configuration file may be missing (particularly
    on Windows or macOS, where installation of fontconfig isn't as
    standardized as on Linux), resulting in "Fontconfig error: Cannot load
    default config file".

    Fontconfig tries to retrieve the system fonts as fallback, which may or
    may not work, especially on macOS, where fonts can be installed at
    various loactions. On Windows (at least since fontconfig 2.13) the
    fallback seems to work.

    If there’s no default configuration and the system fonts fallback
    fails, or if the configuration file exists but doesn’t provide fonts,
    output will be ugly.

    If you happen to have no fonts and an HTML document without a valid
    @font-face, all letters turn into rectangles.

    If you happen to have an HTML document with at least one valid
    @font-face, all text is styled with that font.

    On Windows and macOS we can cause Pango to use native font rendering
    instead of rendering fonts with FreeType. But then we must do without
    @font-face. Expect other missing features and ugly output.

    """
    # Having fonts means: fontconfig's config file returns fonts or
    # fontconfig managed to retrieve system fallback-fonts. On Windows the
    # fallback stragegy seems to work since fontconfig >= 2.13
    fonts = fontconfig.FcConfigGetFonts(font_config, fontconfig.FcSetSystem)
    # Of course, with nfont == 1 the user wont be happy, too…
    if fonts.nfont > 0:
        return

    # Find the reason why we have no fonts
    config_files = fontconfig.FcConfigGetConfigFiles(font_config)
    config_file = fontconfig.FcStrListNext(config_files)
    if config_file == ffi.NULL:
        warnings.warn(
            'FontConfig cannot load default config file. '
            'Expect ugly output.')
        return
    else:
        # Useless config file, or indeed no fonts.
        warnings.warn(
            'FontConfig: No fonts configured. Expect ugly output.')
        return

    # TODO: on Windows we could try to add the system fonts like that:
    # fontdir = os.path.join(os.environ['WINDIR'], 'Fonts')
    # fontconfig.FcConfigAppFontAddDir(
    #     font_config,
    #     # not sure which encoding fontconfig expects
    #     fontdir.encode('mbcs'))

_check_font_configuration(ffi.gc(
    fontconfig.FcInitLoadConfigAndFonts(),
    fontconfig.FcConfigDestroy))

class FontConfiguration:
    """A FreeType font configuration.

    .. versionadded:: 0.32

    Keep a list of fonts, including fonts installed on the system, fonts
    installed for the current user, and fonts referenced by cascading
    stylesheets.

    When created, an instance of this class gathers available fonts. It can
    then be given to :class:`weasyprint.HTML` methods or to
    :class:`weasyprint.CSS` to find fonts in ``@font-face`` rules.

    """

    def __init__(self):
        """Create a FreeType font configuration.

        See Behdad's blog:
        https://mces.blogspot.fr/2015/05/
                how-to-use-custom-application-fonts.html

        """
        # Load the master config file and the fonts.
        self._fontconfig_config = ffi.gc(
            fontconfig.FcInitLoadConfigAndFonts(),
            fontconfig.FcConfigDestroy)
        self.font_map = ffi.gc(
            pangoft2.pango_ft2_font_map_new(), gobject.g_object_unref)
        pangoft2.pango_fc_font_map_set_config(
            ffi.cast('PangoFcFontMap *', self.font_map),
            self._fontconfig_config)
        # pango_fc_font_map_set_config keeps a reference to config
        fontconfig.FcConfigDestroy(self._fontconfig_config)

        # On Windows the font tempfiles cannot be deleted,
        # putting them in a subfolder made my life easier.
        self._tempdir = None
        if sys.platform.startswith('win'):
            self._tempdir = os.path.join(
                tempfile.gettempdir(), 'weasyprint')
            try:
                os.mkdir(self._tempdir)
            except FileExistsError:
                pass
            except Exception:
                # Back to default.
                self._tempdir = None
        self._filenames = []

    def add_font_face(self, rule_descriptors, url_fetcher):
        if self.font_map is None:
            return
        for font_type, url in rule_descriptors['src']:
            if url is None:
                continue
            if font_type in ('external', 'local'):
                config = self._fontconfig_config
                if font_type == 'local':
                    font_name = url.encode('utf-8')
                    pattern = ffi.gc(
                        fontconfig.FcPatternCreate(),
                        fontconfig.FcPatternDestroy)
                    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 **')
                    result = ffi.new('FcResult *')
                    matching_pattern = fontconfig.FcFontMatch(
                        config, pattern, result)
                    # prevent RuntimeError, see issue #677
                    if matching_pattern == ffi.NULL:
                        LOGGER.debug(
                            'Failed to get matching local font for %r',
                            font_name.decode('utf-8'))
                        continue

                    # 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 **')
                        fontconfig.FcPatternGetString(
                            matching_pattern, b'file', 0, filename)
                        path = ffi.string(filename[0]).decode(
                            FILESYSTEM_ENCODING)
                        url = pathlib.Path(path).as_uri()
                    else:
                        LOGGER.debug(
                            'Failed to load local font "%s"',
                            font_name.decode('utf-8'))
                        continue
                try:
                    with fetch(url_fetcher, url) as result:
                        if 'string' in result:
                            font = result['string']
                        else:
                            font = result['file_obj'].read()
                    if font[:3] == b'wOF':
                        out = io.BytesIO()
                        if font[3:4] == b'F':
                            # woff font
                            ttfont = TTFont(io.BytesIO(font))
                            ttfont.flavor = ttfont.flavorData = None
                            ttfont.save(out)
                        elif font[3:4] == b'2':
                            # woff2 font
                            woff2.decompress(io.BytesIO(font), out)
                        font = out.getvalue()
                except Exception as exc:
                    LOGGER.debug(
                        'Failed to load font at %r (%s)', url, exc)
                    continue
                features = {
                    rules[0][0].replace('-', '_'): rules[0][1] for rules in
                    rule_descriptors.get('font_variant', [])}
                if 'font_feature_settings' in rule_descriptors:
                    features['font_feature_settings'] = (
                        rule_descriptors['font_feature_settings'])
                features_string = ''
                for key, value in font_features(**features).items():
                    features_string += f'<string>{key} {value}</string>'
                fd = tempfile.NamedTemporaryFile(
                    'wb', dir=self._tempdir, delete=False)
                font_filename = fd.name
                fd.write(font)
                fd.close()
                self._filenames.append(font_filename)
                fontconfig_style = FONTCONFIG_STYLE[
                    rule_descriptors.get('font_style', 'normal')]
                fontconfig_weight = FONTCONFIG_WEIGHT[
                    rule_descriptors.get('font_weight', 'normal')]
                fontconfig_stretch = FONTCONFIG_STRETCH[
                    rule_descriptors.get('font_stretch', 'normal')]
                xml = f'''<?xml version="1.0"?>
                <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
                <fontconfig>
                  <match target="scan">
                    <test name="file" compare="eq">
                      <string>{font_filename}</string>
                    </test>
                    <edit name="family" mode="assign_replace">
                      <string>{rule_descriptors['font_family']}</string>
                    </edit>
                    <edit name="slant" mode="assign_replace">
                      <const>{fontconfig_style}</const>
                    </edit>
                    <edit name="weight" mode="assign_replace">
                      <const>{fontconfig_weight}</const>
                    </edit>
                    <edit name="width" mode="assign_replace">
                      <const>{fontconfig_stretch}</const>
                    </edit>
                  </match>
                  <match target="font">
                    <test name="file" compare="eq">
                      <string>{font_filename}</string>
                    </test>
                    <edit name="fontfeatures"
                          mode="assign_replace">{features_string}</edit>
                  </match>
                </fontconfig>'''
                fd = tempfile.NamedTemporaryFile(
                    'w', dir=self._tempdir, delete=False)
                fd.write(xml)
                fd.close()
                self._filenames.append(fd.name)
                fontconfig.FcConfigParseAndLoad(
                    config, fd.name.encode(FILESYSTEM_ENCODING),
                    True)
                font_added = fontconfig.FcConfigAppFontAddFile(
                    config, font_filename.encode(FILESYSTEM_ENCODING))
                if font_added:
                    # TODO: We should mask local fonts with the same name
                    # too as explained in Behdad's blog entry.
                    pangoft2.pango_fc_font_map_config_changed(
                        ffi.cast('PangoFcFontMap *', self.font_map))
                    return font_filename
                else:
                    LOGGER.debug('Failed to load font at %r', url)
        LOGGER.warning(
            'Font-face %r cannot be loaded', rule_descriptors['font_family'])

    def __del__(self):
        """Clean a font configuration for a document."""
        # Can't cleanup the temporary font files on Windows, library has
        # still open file handles. On Unix `os.remove()` a file that is in
        # use works fine, on Windows a PermissionError is raised.
        # FcConfigAppFontClear and pango_fc_font_map_shutdown don't help.
        for filename in self._filenames:
            try:
                os.remove(filename)
            except OSError:
                continue

def font_features(font_kerning='normal', font_variant_ligatures='normal',
                  font_variant_position='normal', font_variant_caps='normal',
                  font_variant_numeric='normal',
                  font_variant_alternates='normal',
                  font_variant_east_asian='normal',
                  font_feature_settings='normal'):
    """Get the font features from the different properties in style.

    See https://www.w3.org/TR/css-fonts-3/#feature-precedence

    """
    features = {}

    # Step 1: getting the default, we rely on Pango for this
    # Step 2: @font-face font-variant, done in fonts.add_font_face
    # Step 3: @font-face font-feature-settings, done in fonts.add_font_face

    # Step 4: font-variant and OpenType features

    if font_kerning != 'auto':
        features['kern'] = int(font_kerning == 'normal')

    if font_variant_ligatures == 'none':
        for keys in LIGATURE_KEYS.values():
            for key in keys:
                features[key] = 0
    elif font_variant_ligatures != 'normal':
        for ligature_type in font_variant_ligatures:
            value = 1
            if ligature_type.startswith('no-'):
                value = 0
                ligature_type = ligature_type[3:]
            for key in LIGATURE_KEYS[ligature_type]:
                features[key] = value

    if font_variant_position == 'sub':
        # TODO: the specification asks for additional checks
        # https://www.w3.org/TR/css-fonts-3/#font-variant-position-prop
        features['subs'] = 1
    elif font_variant_position == 'super':
        features['sups'] = 1

    if font_variant_caps != 'normal':
        # TODO: the specification asks for additional checks
        # https://www.w3.org/TR/css-fonts-3/#font-variant-caps-prop
        for key in CAPS_KEYS[font_variant_caps]:
            features[key] = 1

    if font_variant_numeric != 'normal':
        for key in font_variant_numeric:
            features[NUMERIC_KEYS[key]] = 1

    if font_variant_alternates != 'normal':
        # TODO: support other values
        # See https://www.w3.org/TR/css-fonts-3/#font-variant-caps-prop
        if font_variant_alternates == 'historical-forms':
            features['hist'] = 1

    if font_variant_east_asian != 'normal':
        for key in font_variant_east_asian:
            features[EAST_ASIAN_KEYS[key]] = 1

    # Step 5: incompatible non-OpenType features, already handled by Pango

    # Step 6: font-feature-settings

    if font_feature_settings != 'normal':
        features.update(dict(font_feature_settings))

    return features
<commit_msg>Reuse decompressed woff files based on hash<commit_after>"""
    weasyprint.text.fonts
    ---------------------

    Interface with external libraries managing fonts installed on the system.

"""

import io
import os
import pathlib
import sys
import tempfile
import warnings

from fontTools.ttLib import TTFont, woff2

from ..logger import LOGGER
from ..urls import FILESYSTEM_ENCODING, fetch
from .constants import (
    CAPS_KEYS, EAST_ASIAN_KEYS, FONTCONFIG_STRETCH, FONTCONFIG_STYLE,
    FONTCONFIG_WEIGHT, LIGATURE_KEYS, NUMERIC_KEYS)
from .ffi import ffi, fontconfig, gobject, pangoft2

def _check_font_configuration(font_config):
    """Check whether the given font_config has fonts.

    The default fontconfig configuration file may be missing (particularly
    on Windows or macOS, where installation of fontconfig isn't as
    standardized as on Linux), resulting in "Fontconfig error: Cannot load
    default config file".

    Fontconfig tries to retrieve the system fonts as fallback, which may or
    may not work, especially on macOS, where fonts can be installed at
    various loactions. On Windows (at least since fontconfig 2.13) the
    fallback seems to work.

    If there’s no default configuration and the system fonts fallback
    fails, or if the configuration file exists but doesn’t provide fonts,
    output will be ugly.

    If you happen to have no fonts and an HTML document without a valid
    @font-face, all letters turn into rectangles.

    If you happen to have an HTML document with at least one valid
    @font-face, all text is styled with that font.

    On Windows and macOS we can cause Pango to use native font rendering
    instead of rendering fonts with FreeType. But then we must do without
    @font-face. Expect other missing features and ugly output.

    """
    # Having fonts means: fontconfig's config file returns fonts or
    # fontconfig managed to retrieve system fallback-fonts. On Windows the
    # fallback stragegy seems to work since fontconfig >= 2.13
    fonts = fontconfig.FcConfigGetFonts(font_config, fontconfig.FcSetSystem)
    # Of course, with nfont == 1 the user wont be happy, too…
    if fonts.nfont > 0:
        return

    # Find the reason why we have no fonts
    config_files = fontconfig.FcConfigGetConfigFiles(font_config)
    config_file = fontconfig.FcStrListNext(config_files)
    if config_file == ffi.NULL:
        warnings.warn(
            'FontConfig cannot load default config file. '
            'Expect ugly output.')
        return
    else:
        # Useless config file, or indeed no fonts.
        warnings.warn(
            'FontConfig: No fonts configured. Expect ugly output.')
        return

    # TODO: on Windows we could try to add the system fonts like that:
    # fontdir = os.path.join(os.environ['WINDIR'], 'Fonts')
    # fontconfig.FcConfigAppFontAddDir(
    #     font_config,
    #     # not sure which encoding fontconfig expects
    #     fontdir.encode('mbcs'))

_check_font_configuration(ffi.gc(
    fontconfig.FcInitLoadConfigAndFonts(),
    fontconfig.FcConfigDestroy))

class FontConfiguration:
    """A FreeType font configuration.

    .. versionadded:: 0.32

    Keep a list of fonts, including fonts installed on the system, fonts
    installed for the current user, and fonts referenced by cascading
    stylesheets.

    When created, an instance of this class gathers available fonts. It can
    then be given to :class:`weasyprint.HTML` methods or to
    :class:`weasyprint.CSS` to find fonts in ``@font-face`` rules.

    """

    def __init__(self):
        """Create a FreeType font configuration.

        See Behdad's blog:
        https://mces.blogspot.fr/2015/05/
                how-to-use-custom-application-fonts.html

        """
        # Load the master config file and the fonts.
        self._fontconfig_config = ffi.gc(
            fontconfig.FcInitLoadConfigAndFonts(),
            fontconfig.FcConfigDestroy)
        self.font_map = ffi.gc(
            pangoft2.pango_ft2_font_map_new(), gobject.g_object_unref)
        pangoft2.pango_fc_font_map_set_config(
            ffi.cast('PangoFcFontMap *', self.font_map),
            self._fontconfig_config)
        # pango_fc_font_map_set_config keeps a reference to config
        fontconfig.FcConfigDestroy(self._fontconfig_config)

        # On Windows the font tempfiles cannot be deleted,
        # putting them in a subfolder made my life easier.
        self._tempdir = None
        if sys.platform.startswith('win'):
            self._tempdir = os.path.join(
                tempfile.gettempdir(), 'weasyprint')
            try:
                os.mkdir(self._tempdir)
            except FileExistsError:
                pass
            except Exception:
                # Back to default.
                self._tempdir = None
        self._filenames = []
        self._woff_cache = {}

    def add_font_face(self, rule_descriptors, url_fetcher):
        if self.font_map is None:
            return
        for font_type, url in rule_descriptors['src']:
            if url is None:
                continue
            if font_type in ('external', 'local'):
                config = self._fontconfig_config
                if font_type == 'local':
                    font_name = url.encode('utf-8')
                    pattern = ffi.gc(
                        fontconfig.FcPatternCreate(),
                        fontconfig.FcPatternDestroy)
                    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 **')
                    result = ffi.new('FcResult *')
                    matching_pattern = fontconfig.FcFontMatch(
                        config, pattern, result)
                    # prevent RuntimeError, see issue #677
                    if matching_pattern == ffi.NULL:
                        LOGGER.debug(
                            'Failed to get matching local font for %r',
                            font_name.decode('utf-8'))
                        continue

                    # 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 **')
                        fontconfig.FcPatternGetString(
                            matching_pattern, b'file', 0, filename)
                        path = ffi.string(filename[0]).decode(
                            FILESYSTEM_ENCODING)
                        url = pathlib.Path(path).as_uri()
                    else:
                        LOGGER.debug(
                            'Failed to load local font "%s"',
                            font_name.decode('utf-8'))
                        continue
                try:
                    with fetch(url_fetcher, url) as result:
                        if 'string' in result:
                            font = result['string']
                        else:
                            font = result['file_obj'].read()
                    font_is_woff = font[:3] == b'wOF'
                    if font_is_woff:
                        font_hasher = hashlib.sha256()
                        font_hasher.update(font)
                        font_digest = font_hasher.hashdigest()
                        decoded_woff_directory = self._woff_cache.get(font_digest)
                        if decoded_woff_directory is None:
                            out = io.BytesIO()
                            if font[3:4] == b'F':
                                # woff font
                                ttfont = TTFont(io.BytesIO(font))
                                ttfont.flavor = ttfont.flavorData = None
                                ttfont.save(out)
                            elif font[3:4] == b'2':
                                # woff2 font
                                woff2.decompress(io.BytesIO(font), out)
                            font = out.getvalue()
                        else:
                            fd = open('rb', decoded_woff_directory)
                            font = fd.read()
                            fd.close()
                except Exception as exc:
                    LOGGER.debug(
                        'Failed to load font at %r (%s)', url, exc)
                    continue
                features = {
                    rules[0][0].replace('-', '_'): rules[0][1] for rules in
                    rule_descriptors.get('font_variant', [])}
                if 'font_feature_settings' in rule_descriptors:
                    features['font_feature_settings'] = (
                        rule_descriptors['font_feature_settings'])
                features_string = ''
                for key, value in font_features(**features).items():
                    features_string += f'<string>{key} {value}</string>'
                fd = tempfile.NamedTemporaryFile(
                    'wb', dir=self._tempdir, delete=False)
                font_filename = fd.name
                fd.write(font)
                fd.close()
                self._filenames.append(font_filename)
                if font_is_woff and font_digest not in self._woff_cache:
                    self._woff_cache[font_digest] = font_filename
                fontconfig_style = FONTCONFIG_STYLE[
                    rule_descriptors.get('font_style', 'normal')]
                fontconfig_weight = FONTCONFIG_WEIGHT[
                    rule_descriptors.get('font_weight', 'normal')]
                fontconfig_stretch = FONTCONFIG_STRETCH[
                    rule_descriptors.get('font_stretch', 'normal')]
                xml = f'''<?xml version="1.0"?>
                <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
                <fontconfig>
                  <match target="scan">
                    <test name="file" compare="eq">
                      <string>{font_filename}</string>
                    </test>
                    <edit name="family" mode="assign_replace">
                      <string>{rule_descriptors['font_family']}</string>
                    </edit>
                    <edit name="slant" mode="assign_replace">
                      <const>{fontconfig_style}</const>
                    </edit>
                    <edit name="weight" mode="assign_replace">
                      <const>{fontconfig_weight}</const>
                    </edit>
                    <edit name="width" mode="assign_replace">
                      <const>{fontconfig_stretch}</const>
                    </edit>
                  </match>
                  <match target="font">
                    <test name="file" compare="eq">
                      <string>{font_filename}</string>
                    </test>
                    <edit name="fontfeatures"
                          mode="assign_replace">{features_string}</edit>
                  </match>
                </fontconfig>'''
                fd = tempfile.NamedTemporaryFile(
                    'w', dir=self._tempdir, delete=False)
                fd.write(xml)
                fd.close()
                self._filenames.append(fd.name)
                fontconfig.FcConfigParseAndLoad(
                    config, fd.name.encode(FILESYSTEM_ENCODING),
                    True)
                font_added = fontconfig.FcConfigAppFontAddFile(
                    config, font_filename.encode(FILESYSTEM_ENCODING))
                if font_added:
                    # TODO: We should mask local fonts with the same name
                    # too as explained in Behdad's blog entry.
                    pangoft2.pango_fc_font_map_config_changed(
                        ffi.cast('PangoFcFontMap *', self.font_map))
                    return font_filename
                else:
                    LOGGER.debug('Failed to load font at %r', url)
        LOGGER.warning(
            'Font-face %r cannot be loaded', rule_descriptors['font_family'])

    def __del__(self):
        """Clean a font configuration for a document."""
        # Can't cleanup the temporary font files on Windows, library has
        # still open file handles. On Unix `os.remove()` a file that is in
        # use works fine, on Windows a PermissionError is raised.
        # FcConfigAppFontClear and pango_fc_font_map_shutdown don't help.
        for filename in self._filenames:
            try:
                os.remove(filename)
            except OSError:
                continue

def font_features(font_kerning='normal', font_variant_ligatures='normal',
                  font_variant_position='normal', font_variant_caps='normal',
                  font_variant_numeric='normal',
                  font_variant_alternates='normal',
                  font_variant_east_asian='normal',
                  font_feature_settings='normal'):
    """Get the font features from the different properties in style.

    See https://www.w3.org/TR/css-fonts-3/#feature-precedence

    """
    features = {}

    # Step 1: getting the default, we rely on Pango for this
    # Step 2: @font-face font-variant, done in fonts.add_font_face
    # Step 3: @font-face font-feature-settings, done in fonts.add_font_face

    # Step 4: font-variant and OpenType features

    if font_kerning != 'auto':
        features['kern'] = int(font_kerning == 'normal')

    if font_variant_ligatures == 'none':
        for keys in LIGATURE_KEYS.values():
            for key in keys:
                features[key] = 0
    elif font_variant_ligatures != 'normal':
        for ligature_type in font_variant_ligatures:
            value = 1
            if ligature_type.startswith('no-'):
                value = 0
                ligature_type = ligature_type[3:]
            for key in LIGATURE_KEYS[ligature_type]:
                features[key] = value

    if font_variant_position == 'sub':
        # TODO: the specification asks for additional checks
        # https://www.w3.org/TR/css-fonts-3/#font-variant-position-prop
        features['subs'] = 1
    elif font_variant_position == 'super':
        features['sups'] = 1

    if font_variant_caps != 'normal':
        # TODO: the specification asks for additional checks
        # https://www.w3.org/TR/css-fonts-3/#font-variant-caps-prop
        for key in CAPS_KEYS[font_variant_caps]:
            features[key] = 1

    if font_variant_numeric != 'normal':
        for key in font_variant_numeric:
            features[NUMERIC_KEYS[key]] = 1

    if font_variant_alternates != 'normal':
        # TODO: support other values
        # See https://www.w3.org/TR/css-fonts-3/#font-variant-caps-prop
        if font_variant_alternates == 'historical-forms':
            features['hist'] = 1

    if font_variant_east_asian != 'normal':
        for key in font_variant_east_asian:
            features[EAST_ASIAN_KEYS[key]] = 1

    # Step 5: incompatible non-OpenType features, already handled by Pango

    # Step 6: font-feature-settings

    if font_feature_settings != 'normal':
        features.update(dict(font_feature_settings))

    return features
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

try:
    import configparser
except ImportError:
    import ConfigParser as configparser

import os
import sys
import logging
import tornado.options

from dp_tornado.engine.static_handler import StaticHandler

class Bootstrap(object):
    @staticmethod
    def init_template(engine_path, application_path):
        valid = True

        for e in ('config', 'controller', 'model', 'helper'):
            if os.path.isdir(os.path.join(application_path, e)):
                valid = False
                break

        if not valid:
            logging.warning('Default directory structure initialzation skipped. Directory not empty.')

        else:
            import shutil

            template_path = os.path.join(engine_path, 'engine', 'template')

            for root, dirs, files in os.walk(template_path):
                path = root[len(template_path)+1:]
                app_path = os.path.join(application_path, path)

                if path and os.path.isdir(app_path):
                    continue

                if not os.path.isdir(app_path):
                    os.mkdir(app_path)

                for file in files:
                    src = os.path.join(root, file)
                    dest = os.path.join(app_path, file)

                    if not os.path.isfile(dest):
                        shutil.copy(src, dest)

    @staticmethod
    def init_args():
        import argparse

        parser = argparse.ArgumentParser()

        parser.add_argument('--app-path', help='App Path')

        parser.add_argument('--scheduler-path', help='Scheduler Path')
        parser.add_argument('--scheduler-timeout', type=int, help='Scheduler Timeout')

        parser.add_argument('-i', '--identifier', help='Identifier')
        parser.add_argument('-p', '--port', type=int, help='Binding port')

        return parser.parse_args()

    @staticmethod
    def init_ini(application_path, ini_file):
        args = Bootstrap.init_args()

        # INI
        config = configparser.RawConfigParser()
        config.read(os.path.join(application_path, ini_file or 'config.ini'))

        def get_cfg(c, option, section='server', default=None):
            try:
                get = c.get(section, option)

                if default is True or default is False:
                    return True if get == '1' else False

                elif isinstance(default, str):
                    return str(get)

                elif isinstance(default, int):
                    return int(get)

                else:
                    return get
            except (configparser.NoSectionError, configparser.NoOptionError):
                return default

        combined_path = get_cfg(config, 'path', section='static', default='combined')
        static_prefix = get_cfg(config, 'prefix', section='static', default='/s/')
        static_minify = get_cfg(config, 'minify', section='static', default=True)

        if combined_path.find('{server_name}') != -1:
            import socket
            combined_path = combined_path.replace('{server_name}', socket.gethostname())

        if static_prefix[-1] != '/':
            static_prefix = '%s/' % static_prefix

        if combined_path[0] == '/':
            combined_path = combined_path[1:]

        if combined_path[-1] == '/':
            combined_path = combined_path[:-1]

        combined_prefix = '%s%s/' % (static_prefix, combined_path)
        combined_url = combined_path
        combined_path = os.path.join(application_path, 'static', combined_path)

        # Setup Options
        tornado.options.define('max_worker', default=get_cfg(config, 'max_worker', default=1))
        tornado.options.define('num_processes', default=get_cfg(config, 'num_processes', default=0))
        tornado.options.define('port', default=args.port or get_cfg(config, 'port', default=8080))
        tornado.options.define('debug', default=get_cfg(config, 'debug', default=False))
        tornado.options.define('gzip', default=get_cfg(config, 'gzip', default=True))
        tornado.options.define('crypto_key', default=get_cfg(config, 'key', section='crypto', default='CR$t0-$CR@T'))
        tornado.options.define('session_dsn', default=get_cfg(config, 'dsn', section='session', default=None))
        tornado.options.define('session_exp_in', default=get_cfg(config, 'expire_in', section='session', default=7200))
        tornado.options.define('max_body_size', default=get_cfg(config, 'max_body_size', default=1024*1024*10))
        tornado.options.define('application_path', application_path)
        tornado.options.define('python', sys.executable)

        m17n = get_cfg(config, 'm17n', default='').strip()
        m17n = m17n.split(',') if m17n else ['dummy']
        m17n = [e.strip() for e in m17n]

        tornado.options.define('m17n', m17n)

        # Static AWS
        tornado.options.define('static_aws_id', default=get_cfg(config, 'aws_id', section='static'))
        tornado.options.define('static_aws_secret', default=get_cfg(config, 'aws_secret', section='static'))
        tornado.options.define('static_aws_bucket', default=get_cfg(config, 'aws_bucket', section='static'))
        tornado.options.define('static_aws_region', default=get_cfg(config, 'aws_region', section='static'))
        tornado.options.define('static_aws_endpoint', default=get_cfg(config, 'aws_endpoint', section='static'))

        # Scheduler
        tornado.options.define('scheduler_timezone', default=get_cfg(config, 'timezone', section='scheduler', default=''))
        tornado.options.define('scheduler_mode', default=get_cfg(config, 'mode', section='scheduler', default='web'))

        exception_delegate = get_cfg(config, 'exception_delegate', default='', section='logging') or None
        access_logging = get_cfg(config, 'access', default=1, section='logging')
        sql_logging = get_cfg(config, 'sql', default=0, section='logging')

        if exception_delegate:
            from dp_tornado.engine.engine import Engine as dpEngine
            exception_delegate = eval('dpEngine().%s' % exception_delegate)

        tornado.options.define('exception_delegate', exception_delegate)

        # Initialize Logging
        logging.basicConfig(
            level=logging.DEBUG if access_logging else logging.WARN, format='[%(asctime)s][%(levelname)s] %(message)s')

        # SQLAlchemy logging level
        if sql_logging:
            logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)

        return {
            'template_path': os.path.join(application_path, 'view'),
            'static_path': os.path.join(application_path, 'static'),
            'static_handler_class': StaticHandler,
            'static_url_prefix': static_prefix,
            'static_minify': static_minify,
            'static_combined_url': combined_url,
            'combined_static_path': combined_path,
            'combined_static_url_prefix': combined_prefix,
            'compressors': {
                'minifier': None
            },
            'debug': tornado.options.options.debug,
            'gzip': tornado.options.options.gzip,
            'cookie_secret': get_cfg(config, 'cookie_secret', default='default_cookie_secret'),
            'ui_modules': {}
        }
<commit_msg>except invalid exception delegate.<commit_after># -*- coding: utf-8 -*-

try:
    import configparser
except ImportError:
    import ConfigParser as configparser

import os
import sys
import logging
import tornado.options

from dp_tornado.engine.static_handler import StaticHandler

class Bootstrap(object):
    @staticmethod
    def init_template(engine_path, application_path):
        valid = True

        for e in ('config', 'controller', 'model', 'helper'):
            if os.path.isdir(os.path.join(application_path, e)):
                valid = False
                break

        if not valid:
            logging.warning('Default directory structure initialzation skipped. Directory not empty.')

        else:
            import shutil

            template_path = os.path.join(engine_path, 'engine', 'template')

            for root, dirs, files in os.walk(template_path):
                path = root[len(template_path)+1:]
                app_path = os.path.join(application_path, path)

                if path and os.path.isdir(app_path):
                    continue

                if not os.path.isdir(app_path):
                    os.mkdir(app_path)

                for file in files:
                    src = os.path.join(root, file)
                    dest = os.path.join(app_path, file)

                    if not os.path.isfile(dest):
                        shutil.copy(src, dest)

    @staticmethod
    def init_args():
        import argparse

        parser = argparse.ArgumentParser()

        parser.add_argument('--app-path', help='App Path')

        parser.add_argument('--scheduler-path', help='Scheduler Path')
        parser.add_argument('--scheduler-timeout', type=int, help='Scheduler Timeout')

        parser.add_argument('-i', '--identifier', help='Identifier')
        parser.add_argument('-p', '--port', type=int, help='Binding port')

        return parser.parse_args()

    @staticmethod
    def init_ini(application_path, ini_file):
        args = Bootstrap.init_args()

        # INI
        config = configparser.RawConfigParser()
        config.read(os.path.join(application_path, ini_file or 'config.ini'))

        def get_cfg(c, option, section='server', default=None):
            try:
                get = c.get(section, option)

                if default is True or default is False:
                    return True if get == '1' else False

                elif isinstance(default, str):
                    return str(get)

                elif isinstance(default, int):
                    return int(get)

                else:
                    return get
            except (configparser.NoSectionError, configparser.NoOptionError):
                return default

        combined_path = get_cfg(config, 'path', section='static', default='combined')
        static_prefix = get_cfg(config, 'prefix', section='static', default='/s/')
        static_minify = get_cfg(config, 'minify', section='static', default=True)

        if combined_path.find('{server_name}') != -1:
            import socket
            combined_path = combined_path.replace('{server_name}', socket.gethostname())

        if static_prefix[-1] != '/':
            static_prefix = '%s/' % static_prefix

        if combined_path[0] == '/':
            combined_path = combined_path[1:]

        if combined_path[-1] == '/':
            combined_path = combined_path[:-1]

        combined_prefix = '%s%s/' % (static_prefix, combined_path)
        combined_url = combined_path
        combined_path = os.path.join(application_path, 'static', combined_path)

        # Setup Options
        tornado.options.define('max_worker', default=get_cfg(config, 'max_worker', default=1))
        tornado.options.define('num_processes', default=get_cfg(config, 'num_processes', default=0))
        tornado.options.define('port', default=args.port or get_cfg(config, 'port', default=8080))
        tornado.options.define('debug', default=get_cfg(config, 'debug', default=False))
        tornado.options.define('gzip', default=get_cfg(config, 'gzip', default=True))
        tornado.options.define('crypto_key', default=get_cfg(config, 'key', section='crypto', default='CR$t0-$CR@T'))
        tornado.options.define('session_dsn', default=get_cfg(config, 'dsn', section='session', default=None))
        tornado.options.define('session_exp_in', default=get_cfg(config, 'expire_in', section='session', default=7200))
        tornado.options.define('max_body_size', default=get_cfg(config, 'max_body_size', default=1024*1024*10))
        tornado.options.define('application_path', application_path)
        tornado.options.define('python', sys.executable)

        m17n = get_cfg(config, 'm17n', default='').strip()
        m17n = m17n.split(',') if m17n else ['dummy']
        m17n = [e.strip() for e in m17n]

        tornado.options.define('m17n', m17n)

        # Static AWS
        tornado.options.define('static_aws_id', default=get_cfg(config, 'aws_id', section='static'))
        tornado.options.define('static_aws_secret', default=get_cfg(config, 'aws_secret', section='static'))
        tornado.options.define('static_aws_bucket', default=get_cfg(config, 'aws_bucket', section='static'))
        tornado.options.define('static_aws_region', default=get_cfg(config, 'aws_region', section='static'))
        tornado.options.define('static_aws_endpoint', default=get_cfg(config, 'aws_endpoint', section='static'))

        # Scheduler
        tornado.options.define('scheduler_timezone', default=get_cfg(config, 'timezone', section='scheduler', default=''))
        tornado.options.define('scheduler_mode', default=get_cfg(config, 'mode', section='scheduler', default='web'))

        exception_delegate = get_cfg(config, 'exception_delegate', default='', section='logging') or None
        access_logging = get_cfg(config, 'access', default=1, section='logging')
        sql_logging = get_cfg(config, 'sql', default=0, section='logging')

        if exception_delegate:
            from dp_tornado.engine.engine import Engine as dpEngine

            try:
                exception_delegate = eval('dpEngine().%s' % exception_delegate)
            except AttributeError:
                logging.error('The specified exception delegate is invalid.')
                exception_delegate = None

        tornado.options.define('exception_delegate', exception_delegate)

        # Initialize Logging
        logging.basicConfig(
            level=logging.DEBUG if access_logging else logging.WARN, format='[%(asctime)s][%(levelname)s] %(message)s')

        # SQLAlchemy logging level
        if sql_logging:
            logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)

        return {
            'template_path': os.path.join(application_path, 'view'),
            'static_path': os.path.join(application_path, 'static'),
            'static_handler_class': StaticHandler,
            'static_url_prefix': static_prefix,
            'static_minify': static_minify,
            'static_combined_url': combined_url,
            'combined_static_path': combined_path,
            'combined_static_url_prefix': combined_prefix,
            'compressors': {
                'minifier': None
            },
            'debug': tornado.options.options.debug,
            'gzip': tornado.options.options.gzip,
            'cookie_secret': get_cfg(config, 'cookie_secret', default='default_cookie_secret'),
            'ui_modules': {}
        }
<|endoftext|>


<commit_before>from brew.grains import Grain
from brew.hops import Hop
from brew.hops import HopAddition
from brew.recipes import Recipe

# Define Grains
pale = Grain(name='pale 2-row',
             short_name='2-row',
             hot_water_extract=0.76,
             color=2,
             percent_extract=95)
crystal = Grain(name='crystal C20',
                short_name='C20',
                hot_water_extract=0.70,
                color=20,
                percent_extract=5.0)
grain_list = [pale, crystal]

# Define Hops
centennial = Hop(name='centennial',
                 percent_alpha_acids=14.0)
cascade = Hop(name='cascade',
              percent_alpha_acids=7.0)
hop_list = [centennial, cascade]

centennial_add = HopAddition(centennial,
                             boil_time=60.0,
                             weight=0.57,
                             percent_contribution=95.0)
cascade_add = HopAddition(cascade,
                          boil_time=5.0,
                          weight=0.76,
                          percent_contribution=5.0)
hop_additions = [centennial_add, cascade_add]

# Define Recipes
recipe = Recipe(name='pale ale',
                grain_list=grain_list,
                hop_additions=hop_additions,
                percent_brew_house_yield=70.0,  # %
                final_volume=5.0,  # G
                target_sg=1.057,  # SG
                target_ibu=40.0)
<commit_msg>Add start volume<commit_after>from brew.grains import Grain
from brew.hops import Hop
from brew.hops import HopAddition
from brew.recipes import Recipe

# Define Grains
pale = Grain(name='pale 2-row',
             short_name='2-row',
             hot_water_extract=0.76,
             color=2,
             percent_extract=95)
crystal = Grain(name='crystal C20',
                short_name='C20',
                hot_water_extract=0.70,
                color=20,
                percent_extract=5.0)
grain_list = [pale, crystal]

# Define Hops
centennial = Hop(name='centennial',
                 percent_alpha_acids=14.0)
cascade = Hop(name='cascade',
              percent_alpha_acids=7.0)
hop_list = [centennial, cascade]

centennial_add = HopAddition(centennial,
                             boil_time=60.0,
                             weight=0.57,
                             percent_contribution=95.0)
cascade_add = HopAddition(cascade,
                          boil_time=5.0,
                          weight=0.76,
                          percent_contribution=5.0)
hop_additions = [centennial_add, cascade_add]

# Define Recipes
recipe = Recipe(name='pale ale',
                grain_list=grain_list,
                hop_additions=hop_additions,
                percent_brew_house_yield=70.0,  # %
                start_volume=7.0,  # G
                final_volume=5.0,  # G
                target_sg=1.057,  # SG
                target_ibu=40.0)
<|endoftext|>


<commit_before>from __future__ import division
from __future__ import absolute_import
from builtins import *
import math
#!/usr/bin/env python
#Copyright (c) 2013, Eduard Broecker
#All rights reserved.
#
#Redistribution and use in source and binary forms, with or without modification, are permitted provided that
# the following conditions are met:
#
#    Redistributions of source code must retain the above copyright notice, this list of conditions and the
#    following disclaimer.
#    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
#    following disclaimer in the documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
#WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
#PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
#DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
#OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
#DAMAGE.

#
# this script imports sym-files to a canmatrix-object
# sym-files are the can-matrix-definitions of peak tools
#
#TODO : the signalname should be unique. If same signal-name is used in different mux-ids, the signal-name should be unified. maybe this is an dbc-export-issue not an sym-import-issue
#TODO : I don't know what to do with the "/p"-Options in the signal-definition
#TODO : what is the differenze between SEND and SENDRECEIVE ?
# currently only for FormatVersion=5.0
#TODO: support for title example: Title="some title"

from .canmatrix import *
import re
import codecs

def importSym(filename):
    symImportEncoding = "latin1"
    class Mode(object):
        glob, enums, send, sendReceive = list(range(4))
    mode = Mode.glob
    valueTables = {}

    frameName = ""
    frame = None

    db = CanMatrix()
    db.addFrameDefines("GenMsgCycleTime",  'INT 0 65535')
    db.addSignalDefines("GenSigStartValue", 'FLOAT -3.4E+038 3.4E+038')
    f = open(filename, "rb")

    for line in f:
        line = line.decode(symImportEncoding).strip()
        #ignore emty line:
        if line.__len__() == 0:
            continue

        #switch mode:
        if line[0:7] == "{ENUMS}":
            mode = Mode.enums
            continue;
        if line[0:6] == "{SEND}":
            mode = Mode.send
            continue;
        if line[0:13] == "{SENDRECEIVE}":
            mode = Mode.sendReceive
            continue;

        if mode == Mode.glob:
            # just ignore headers...
            continue
        elif mode == Mode.enums:
            line = line.strip()
            if line.startswith('enum'):
                while not line[5:].strip().endswith(')'):
                    line = line.split('//')[0]
                    line += f.readline().strip()
                line = line.split('//')[0]
                tempArray = line[5:].replace(')','').split('(')
                valtabName = tempArray[0]
                tempArray =  tempArray[1].split(',')
                tempValTable = {}
                for entry in tempArray:
                    tempValTable[entry.split('=')[0].strip()] = entry.split('=')[1].replace('"','').strip()
                valueTables[valtabName] = tempValTable

        elif mode == Mode.send or mode == Mode.sendReceive:
            if line.startswith('['):
                multiplexor = None
                # found new frame:
                if frameName != line.replace('[','').replace(']','').strip():
                    frameName = line.replace('[','').replace(']','').strip()
                    if frame is not None:
                        if len(multiplexValTable) > 0:
                            frame.signalByName(frame._name + "_MUX")._values = multiplexValTable
                        db._fl.addFrame(frame)

                    frame = Frame(0,frameName,8,None)
                    multiplexValTable = {}

            #key value:
            elif line.startswith('Var') or line.startswith('Mux'):
                tmpMux = line[:3]
                line = line[4:]
                comment = ""
                indexOffset = 1
                if tmpMux == "Mux":
                    indexOffset = 0
                comment = ""
                if '//' in line:
                    comment = line.split('//')[1].strip()
                    line = line.split('//')[0]
                tempArray = line.strip().split(' ')
                sigName = tempArray[0]
                if indexOffset == 1 and tempArray[1][:8] == "unsigned":
                    valuetype = "+"
                else:
                    valuetype = "-"

                startBit = int(tempArray[indexOffset+1].split(',')[0])
                signalLength = int(tempArray[indexOffset+1].split(',')[1])
                intel = 1
                unit = ""
                factor = 1
                max = 1
                min = 0
                startValue = 0
                offset = 0
                valueTableName = None

                if tmpMux == "Mux":
                    multiplexor= tempArray[2]
                    multiplexValTable[multiplexor] = sigName
                    indexOffset = 2

                for switch in tempArray[indexOffset+2:]:
                    if switch == "-m":
                        intel = 0
                    elif switch == "-h":
                        #hexadecimal output - not supported
                        pass
                    elif switch.startswith('/'):
                        if switch[1:].split(':')[0] == 'u':
                            unit = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'f':
                            factor = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'd':
                            startValue = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'p':
                            pass
                            #TODO /p ???
                        elif switch[1:].split(':')[0] == 'o':
                            offset = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'e':
                            valueTableName = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'max':
                            max = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'min':
                            min = switch[1:].split(':')[1]
#                                               else:
#                                                       print switch
#                                       else:
#                                               print switch
                # ... (1 / ...) because this somehow made 59.8/0.1 be 598.0 rather than 597.9999999999999
                startValue = float(startValue) * (1 / float(factor))
                if tmpMux == "Mux":
                    signal = frame.signalByName(frameName + "_MUX")
                    if signal == None:
                        signal = Signal(frameName + "_MUX", startBit, signalLength, intel, valuetype, factor, offset, min, max, unit, "", 'Multiplexor')
                        signal.addComment(comment)
                        if intel == 0:
                            #motorola set/convert startbit
                            signal.setMsbReverseStartbit(startBit)
                        frame.addSignal(signal)

                else:
                    signal = Signal(sigName, startBit, signalLength, intel, valuetype, factor, offset, min, max, unit, "", multiplexor)
                    if intel == 0:
                        #motorola set/convert startbit
                        signal.setMsbReverseStartbit(startBit)
                    if valueTableName is not None:
                        signal._values = valueTables[valueTableName]
                    signal.addComment(comment)
                    signal.addAttribute("GenSigStartValue", str(startValue))
                    frame.addSignal(signal)
                #variable processing
            elif line.startswith('ID'):
                comment = ""
                if '//' in line:
                    comment = line.split('//')[1].strip()
                    line = line.split('//')[0]
                frame._Id = int(line.split('=')[1].strip()[:-1], 16)
                frame.addComment(comment)
            elif line.startswith('Type'):
                if line.split('=')[1][:8] == "Extended":
                    frame._extended = 1
            elif line.startswith('DLC'):
                frame._Size = int(line.split('=')[1])

            elif line.startswith('CycleTime'):
                frame.addAttribute("GenMsgCycleTime", line.split('=')[1].strip())
#                       else:
#                               print line
#               else:
#                       print "Unrecocniced line: " + l + " (%d) " % i
    if frame is not None:
        db._fl.addFrame(frame)

    return db
<commit_msg>support for charsetdecoding<commit_after>from __future__ import division
from __future__ import absolute_import
from builtins import *
import math
#!/usr/bin/env python
#Copyright (c) 2013, Eduard Broecker
#All rights reserved.
#
#Redistribution and use in source and binary forms, with or without modification, are permitted provided that
# the following conditions are met:
#
#    Redistributions of source code must retain the above copyright notice, this list of conditions and the
#    following disclaimer.
#    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
#    following disclaimer in the documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
#WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
#PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
#DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
#OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
#DAMAGE.

#
# this script imports sym-files to a canmatrix-object
# sym-files are the can-matrix-definitions of peak tools
#
#TODO : the signalname should be unique. If same signal-name is used in different mux-ids, the signal-name should be unified. maybe this is an dbc-export-issue not an sym-import-issue
#TODO : I don't know what to do with the "/p"-Options in the signal-definition
#TODO : what is the differenze between SEND and SENDRECEIVE ?
# currently only for FormatVersion=5.0
#TODO: support for title example: Title="some title"

from .canmatrix import *
import re
import codecs

def importSym(filename):
    symImportEncoding = "latin1"
    class Mode(object):
        glob, enums, send, sendReceive = list(range(4))
    mode = Mode.glob
    valueTables = {}

    frameName = ""
    frame = None

    db = CanMatrix()
    db.addFrameDefines("GenMsgCycleTime",  'INT 0 65535')
    db.addSignalDefines("GenSigStartValue", 'FLOAT -3.4E+038 3.4E+038')
    f = open(filename, "rb")

    for line in f:
        line = line.decode(symImportEncoding).strip()
        #ignore emty line:
        if line.__len__() == 0:
            continue

        #switch mode:
        if line[0:7] == "{ENUMS}":
            mode = Mode.enums
            continue;
        if line[0:6] == "{SEND}":
            mode = Mode.send
            continue;
        if line[0:13] == "{SENDRECEIVE}":
            mode = Mode.sendReceive
            continue;

        if mode == Mode.glob:
            # just ignore headers...
            continue
        elif mode == Mode.enums:
            line = line.strip()
            if line.startswith('enum'):
                while not line[5:].strip().endswith(')'):
                    line = line.split('//')[0]
                    line += f.readline().decode(symImportEncoding).strip()
                line = line.split('//')[0]
                tempArray = line[5:].replace(')','').split('(')
                valtabName = tempArray[0]
                tempArray =  tempArray[1].split(',')
                tempValTable = {}
                for entry in tempArray:
                    tempValTable[entry.split('=')[0].strip()] = entry.split('=')[1].replace('"','').strip()
                valueTables[valtabName] = tempValTable

        elif mode == Mode.send or mode == Mode.sendReceive:
            if line.startswith('['):
                multiplexor = None
                # found new frame:
                if frameName != line.replace('[','').replace(']','').strip():
                    frameName = line.replace('[','').replace(']','').strip()
                    if frame is not None:
                        if len(multiplexValTable) > 0:
                            frame.signalByName(frame._name + "_MUX")._values = multiplexValTable
                        db._fl.addFrame(frame)

                    frame = Frame(0,frameName,8,None)
                    multiplexValTable = {}

            #key value:
            elif line.startswith('Var') or line.startswith('Mux'):
                tmpMux = line[:3]
                line = line[4:]
                comment = ""
                indexOffset = 1
                if tmpMux == "Mux":
                    indexOffset = 0
                comment = ""
                if '//' in line:
                    comment = line.split('//')[1].strip()
                    line = line.split('//')[0]
                tempArray = line.strip().split(' ')
                sigName = tempArray[0]
                if indexOffset == 1 and tempArray[1][:8] == "unsigned":
                    valuetype = "+"
                else:
                    valuetype = "-"

                startBit = int(tempArray[indexOffset+1].split(',')[0])
                signalLength = int(tempArray[indexOffset+1].split(',')[1])
                intel = 1
                unit = ""
                factor = 1
                max = 1
                min = 0
                startValue = 0
                offset = 0
                valueTableName = None

                if tmpMux == "Mux":
                    multiplexor= tempArray[2]
                    multiplexValTable[multiplexor] = sigName
                    indexOffset = 2

                for switch in tempArray[indexOffset+2:]:
                    if switch == "-m":
                        intel = 0
                    elif switch == "-h":
                        #hexadecimal output - not supported
                        pass
                    elif switch.startswith('/'):
                        if switch[1:].split(':')[0] == 'u':
                            unit = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'f':
                            factor = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'd':
                            startValue = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'p':
                            pass
                            #TODO /p ???
                        elif switch[1:].split(':')[0] == 'o':
                            offset = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'e':
                            valueTableName = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'max':
                            max = switch[1:].split(':')[1]
                        elif switch[1:].split(':')[0] == 'min':
                            min = switch[1:].split(':')[1]
#                                               else:
#                                                       print switch
#                                       else:
#                                               print switch
                # ... (1 / ...) because this somehow made 59.8/0.1 be 598.0 rather than 597.9999999999999
                startValue = float(startValue) * (1 / float(factor))
                if tmpMux == "Mux":
                    signal = frame.signalByName(frameName + "_MUX")
                    if signal == None:
                        signal = Signal(frameName + "_MUX", startBit, signalLength, intel, valuetype, factor, offset, min, max, unit, "", 'Multiplexor')
                        signal.addComment(comment)
                        if intel == 0:
                            #motorola set/convert startbit
                            signal.setMsbReverseStartbit(startBit)
                        frame.addSignal(signal)

                else:
                    signal = Signal(sigName, startBit, signalLength, intel, valuetype, factor, offset, min, max, unit, "", multiplexor)
                    if intel == 0:
                        #motorola set/convert startbit
                        signal.setMsbReverseStartbit(startBit)
                    if valueTableName is not None:
                        signal._values = valueTables[valueTableName]
                    signal.addComment(comment)
                    signal.addAttribute("GenSigStartValue", str(startValue))
                    frame.addSignal(signal)
                #variable processing
            elif line.startswith('ID'):
                comment = ""
                if '//' in line:
                    comment = line.split('//')[1].strip()
                    line = line.split('//')[0]
                frame._Id = int(line.split('=')[1].strip()[:-1], 16)
                frame.addComment(comment)
            elif line.startswith('Type'):
                if line.split('=')[1][:8] == "Extended":
                    frame._extended = 1
            elif line.startswith('DLC'):
                frame._Size = int(line.split('=')[1])

            elif line.startswith('CycleTime'):
                frame.addAttribute("GenMsgCycleTime", line.split('=')[1].strip())
#                       else:
#                               print line
#               else:
#                       print "Unrecocniced line: " + l + " (%d) " % i
    if frame is not None:
        db._fl.addFrame(frame)

    return db
<|endoftext|>


<commit_before>import os
from system import cmd, rm, mkdir
from reporting import log
from io import read, write

#
# video (ffmpeg)
#

TRANS = "ffmpeg -y -i %s -loglevel error -stats -c:a aac -profile:v baseline -level 3.0 -movflags +faststart -f mp4 _tmp"
FAST = "ffmpeg -y -i %s -loglevel error -stats -c:a copy -profile:v baseline -level 3.0 -movflags +faststart -f mp4 _tmp"
#SEG = "ffmpeg -i %s -loglevel error -stats -map 0 -codec:v libx264 -codec:a aac -f ssegment -segment_list %s/list.m3u8 -segment_list_flags +live -segment_time 10 %s/%%03d.ts"
SEG = 'ffmpeg -i %s -loglevel error -stats -c:v libx264 -c:a copy -r 30 -x264opts "keyint=60:min-keyint=60" -forced-idr 1 -f ssegment -segment_list %s/list.m3u8 -segment_time 10 %s/%%03d.ts'

def transcode(orig, tmp=False, fast=True):
	if tmp: # orig is _data_, not _path_
		log("media.transcode > writing to tmp file", 1)
		data = orig
		orig = "_tctmp"
		write(data, orig, binary=True)
	log("media.transcode > optimizing for mobile (%s)"%(orig,), 1)
	cmd((fast and FAST or TRANS)%(orig,))
	data = read(binary=True)
	if not tmp:
		log("media.transcode > overwriting original; removing tmp file", 1)
		write(data, orig, binary=True)
	rm("_tmp")
	if tmp:
		rm(orig)
		return data

def segment(orig, p):
	log("media.segment > segmenting to %s (hls)"%(p,), 1)
	cmd(SEG%(orig, p, p))

def hlsify(blobpath, check=False):
	p = blobpath.replace("blob/", "blob/hls/")
	isd = os.path.isdir(p)
	if check:
		return isd
	if not isd:
		log("transcode > attempt with video: '%s'"%(blobpath,), important=True)
		mkdir(p, True)
		segment(blobpath, p)
		log("transcode > done!")

#
# images (PIL)
#

def crop(img, constraint):
	from PIL import Image
	w = img.size[0]
	h = img.size[1]
	smaller = min(w, h)
	if smaller == w: # clean these up...
		fromx = 0
		tox = w
		fromy = (h - w) / 2.0
		toy = fromy + w
	else:
		fromy = 0
		toy = h
		fromx = (w - h) / 2.0
		tox = fromx + h
	return img.crop((int(fromx), int(fromy), int(tox),
		int(toy))).resize((constraint, constraint), Image.ANTIALIAS)

<commit_msg>util.media.transcode(): baseline kwarg (default False)<commit_after>import os
from system import cmd, rm, mkdir
from reporting import log
from io import read, write

#
# video (ffmpeg)
#

TRANS = "ffmpeg -y -i %s -loglevel error -stats -c:a aac -movflags +faststart -f mp4 _tmp"
BASELINE = "ffmpeg -y -i %s -loglevel error -stats -c:a aac -profile:v baseline -level 3.0 -movflags +faststart -f mp4 _tmp"
FAST = "ffmpeg -y -i %s -loglevel error -stats -c:a copy +faststart -f mp4 _tmp"
#SEG = "ffmpeg -i %s -loglevel error -stats -map 0 -codec:v libx264 -codec:a aac -f ssegment -segment_list %s/list.m3u8 -segment_list_flags +live -segment_time 10 %s/%%03d.ts"
SEG = 'ffmpeg -i %s -loglevel error -stats -c:v libx264 -c:a copy -r 30 -x264opts "keyint=60:min-keyint=60" -forced-idr 1 -f ssegment -segment_list %s/list.m3u8 -segment_time 10 %s/%%03d.ts'

def transcode(orig, tmp=False, fast=True, baseline=False):
	if tmp: # orig is _data_, not _path_
		log("media.transcode > writing to tmp file", 1)
		data = orig
		orig = "_tctmp"
		write(data, orig, binary=True)
	log("media.transcode > optimizing for mobile (%s)"%(orig,), 1)
	cmd((baseline and BASELINE or fast and FAST or TRANS)%(orig,))
	data = read(binary=True)
	if not tmp:
		log("media.transcode > overwriting original; removing tmp file", 1)
		write(data, orig, binary=True)
	rm("_tmp")
	if tmp:
		rm(orig)
		return data

def segment(orig, p):
	log("media.segment > segmenting to %s (hls)"%(p,), 1)
	cmd(SEG%(orig, p, p))

def hlsify(blobpath, check=False):
	p = blobpath.replace("blob/", "blob/hls/")
	isd = os.path.isdir(p)
	if check:
		return isd
	if not isd:
		log("transcode > attempt with video: '%s'"%(blobpath,), important=True)
		mkdir(p, True)
		segment(blobpath, p)
		log("transcode > done!")

#
# images (PIL)
#

def crop(img, constraint):
	from PIL import Image
	w = img.size[0]
	h = img.size[1]
	smaller = min(w, h)
	if smaller == w: # clean these up...
		fromx = 0
		tox = w
		fromy = (h - w) / 2.0
		toy = fromy + w
	else:
		fromy = 0
		toy = h
		fromx = (w - h) / 2.0
		tox = fromx + h
	return img.crop((int(fromx), int(fromy), int(tox),
		int(toy))).resize((constraint, constraint), Image.ANTIALIAS)

<|endoftext|>