Repository: Kozea/WeasyPrint
Pull request #2390 (2025-02-22 14:30:59)

Fix justify non breaking spaces
Fix issue #2269
I am not sure of results of test `test_text_align_justify_nbsp_2`. Is this the expected behaviour?



Patch:
diff --git a/weasyprint/css/html5_ua_form.css b/weasyprint/css/html5_ua_form.css
index 9c548ca6..8e353b0e 100644
--- a/weasyprint/css/html5_ua_form.css
+++ b/weasyprint/css/html5_ua_form.css
@@ -11,5 +11,5 @@ input:not([type="submit"])::before {
 }
 
 textarea {
-  text-indent: 10000%;  /* Hide text but don’t change color used by PDF form */
+  color: transparent;
 }
diff --git a/weasyprint/layout/inline.py b/weasyprint/layout/inline.py
index d67806d2..da8a4405 100644
--- a/weasyprint/layout/inline.py
+++ b/weasyprint/layout/inline.py
@@ -1119,18 +1119,17 @@ def text_align(context, line, available_width, last):
 def justify_line(context, line, extra_width):
     # TODO: We should use a better algorithm here, see
     # https://www.w3.org/TR/css-text-3/#justify-algos
-    nb_spaces = count_spaces(line)
-    if nb_spaces == 0:
-        return
-    add_word_spacing(context, line, extra_width / nb_spaces, 0)
+    if (nb_spaces := count_expandable_spaces(line)):
+        add_word_spacing(context, line, extra_width / nb_spaces, 0)
 
 
-def count_spaces(box):
+def count_expandable_spaces(box):
+    """Count expandable spaces (space and nbsp) for justification."""
     if isinstance(box, boxes.TextBox):
         # TODO: remove trailing spaces correctly
-        return box.text.count(' ')
+        return box.text.count(' ') + box.text.count('\u00a0')
     elif isinstance(box, (boxes.LineBox, boxes.InlineBox)):
-        return sum(count_spaces(child) for child in box.children)
+        return sum(count_expandable_spaces(child) for child in box.children)
     else:
         return 0
 
@@ -1139,7 +1138,7 @@ def add_word_spacing(context, box, justification_spacing, x_advance):
     if isinstance(box, boxes.TextBox):
         box.justification_spacing = justification_spacing
         box.position_x += x_advance
-        nb_spaces = count_spaces(box)
+        nb_spaces = count_expandable_spaces(box)
         if nb_spaces > 0:
             layout = create_layout(
                 box.text, box.style, context, max_width=None,
diff --git a/weasyprint/text/line_break.py b/weasyprint/text/line_break.py
index 27bf471d..c70c73ae 100644
--- a/weasyprint/text/line_break.py
+++ b/weasyprint/text/line_break.py
@@ -187,13 +187,11 @@ class Layout:
                     pango.pango_layout_set_text(self.layout, text, -1)
 
                 space_spacing = int(word_spacing * TO_UNITS + letter_spacing)
-                position = bytestring.find(b' ')
                 # Pango gives only half of word-spacing on boundaries
                 boundary_positions = (0, len(bytestring) - 1)
-                while position != -1:
-                    factor = 1 + (position in boundary_positions)
-                    add_attr(position, position + 1, factor * space_spacing)
-                    position = bytestring.find(b' ', position + 1)
+                for match in re.finditer(' |\u00a0'.encode(), bytestring):
+                    factor = 1 + (match.start() in boundary_positions)
+                    add_attr(match.start(), match.end(), factor * space_spacing)
 
             if word_breaking:
                 attr = pango.pango_attr_insert_hyphens_new(False)


Test patch:
diff --git a/tests/draw/test_text.py b/tests/draw/test_text.py
index 95440ba2..9d655f91 100644
--- a/tests/draw/test_text.py
+++ b/tests/draw/test_text.py
@@ -453,6 +453,31 @@ def test_text_align_justify(assert_pixels):
       <div>a c e</div>''')
 
 
+def test_text_align_justify_nbsp(assert_pixels):
+    assert_pixels('''
+        ___________________
+        _RR___RR___RR___RR_
+        _RR___RR___RR___RR_
+        ___________________
+    ''', '''
+      <style>
+        @page {
+          size: 19px 4px;
+        }
+        body {
+          color: red;
+          font-family: weasyprint;
+          font-size: 2px;
+        }
+        div {
+          line-height: 1;
+          margin: 1px;
+          text-align: justify-all;
+        }
+      </style>
+      <div>a b&nbsp;c&nbsp;d</div>''')
+
+
 def test_text_word_spacing(assert_pixels):
     assert_pixels('''
         ___________________