Repository: Kozea/WeasyPrint

Issue:
Support `unicode-range`



Discussion:
I would love support for "unicode-range" in the @font-face because our text (and css) is mainly latin script with quotations in polytonic Greek or Arabic and this feature let us decide which font is used ONLY for the gliphs in the range!
I'm working on a project which would really benefit from this, would be cool to see it implemented
> I'm working on a project which would really benefit from this, would be cool to see it implemented

Yes, that would be great.

I think that it’s possible by setting the range in Fontconfig’s config file. Looks like it’s not documented in Fontconfig, but [this StackOverflow example](https://stackoverflow.com/questions/47501411/how-to-set-font-per-unicode-range-codepoint-in-fontconfig) should do the trick.

The first test that can be done is to try hard-coded values in [the configuration](https://github.com/Kozea/WeasyPrint/blob/e018bf3fbb13a05d2cfb647cb28cc4c0434558b0/weasyprint/text/fonts.py#L213) and see if it works. If it does, we’ll find a way to parse the range, validate it, and store it somewhere so that it’ll be available when we create the configuration.

Interested in getting this cool feature implemented?



Patch:
diff --git a/weasyprint/css/validation/descriptors.py b/weasyprint/css/validation/descriptors.py
index 8f2cadca9..06db3be47 100644
--- a/weasyprint/css/validation/descriptors.py
+++ b/weasyprint/css/validation/descriptors.py
@@ -208,6 +208,15 @@ def font_variant(tokens):
     return values
 
 
+@descriptor('font-face')
+@comma_separated_list
+@single_token
+def unicode_range(token):
+    """``unicode_range`` descriptor validation."""
+    if token.type == 'unicode-range':
+        return token
+
+
 @descriptor('counter-style')
 def system(tokens):
     """``system`` descriptor validation."""
diff --git a/weasyprint/text/fonts.py b/weasyprint/text/fonts.py
index efa16435d..61c46ef0c 100644
--- a/weasyprint/text/fonts.py
+++ b/weasyprint/text/fonts.py
@@ -208,14 +208,23 @@ def add_font_face(self, rule_descriptors, url_fetcher):
             match = SubElement(root, 'match', target='font')
             test = SubElement(match, 'test', name='file', compare='eq')
             SubElement(test, 'string').text = str(font_path)
-            edit = SubElement(match, 'edit', name='fontfeatures', mode=mode)
             descriptors = {
                 rules[0][0].replace('-', '_'): rules[0][1] for rules in
                 rule_descriptors.get('font_variant', [])}
             settings = rule_descriptors.get('font_feature_settings', 'normal')
             features = font_features(font_feature_settings=settings, **descriptors)
-            for key, value in features.items():
-                SubElement(edit, 'string').text = f'{key} {value}'
+            if features:
+                edit = SubElement(match, 'edit', name='fontfeatures', mode=mode)
+                for key, value in features.items():
+                    SubElement(edit, 'string').text = f'{key} {value}'
+            if unicode_ranges := rule_descriptors.get('unicode_range'):
+                edit = SubElement(match, 'edit', name='charset', mode=mode)
+                plus = SubElement(edit, 'plus')
+                for unicode_range in unicode_ranges:
+                    charset = SubElement(plus, 'charset')
+                    range_ = SubElement(charset, 'range')
+                    for value in (unicode_range.start, unicode_range.end):
+                        SubElement(range_, 'int').text = f'0x{value:x}'
             header = (
                 b'<?xml version="1.0"?>',
                 b'<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">')


Test patch:
diff --git a/tests/draw/test_text.py b/tests/draw/test_text.py
index c1be0036a..9fab6373a 100644
--- a/tests/draw/test_text.py
+++ b/tests/draw/test_text.py
@@ -1230,3 +1230,29 @@ def test_font_variant_caps_titling(assert_pixels):
       </style>
       <p>Pp</p>
     ''' % SANS_FONTS)
+
+
+def test_unicode_range(assert_pixels):
+    assert_pixels('''
+        __________
+        _RRRRRR___
+        _RRRRRRzz_
+        __________
+    ''', '''
+      <style>
+        @font-face {
+          font-family: uni;
+          src: url(weasyprint.otf);
+          unicode-range: u+41, u+043-045, u+005?;
+        }
+        @page {
+          size: 10px 4px;
+        }
+        body {
+          color: red;
+          font-family: uni;
+          font-size: 2px;
+          line-height: 0;
+          margin: 2px 1px;
+        }
+      </style>ADZB''')