Skip to content

Commit 5238b22

Browse files
committed
Fix broken line wrapping around missing glyphs
1 parent 00a1fd4 commit 5238b22

2 files changed

Lines changed: 28 additions & 30 deletions

File tree

‎CHANGES.rst‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,12 @@ Changed:
6666

6767
Fixed:
6868

69+
* Glyphs missing from a font (rendered by the fallback font) resulted in
70+
duplicated words when line wrapping
6971
* OpenType: handle optional subtables (e.g. Liberation Sans Narrow now loads)
7072
* Fix the selectors for list of tables/figures sections
71-
* Fix crash when other installed packages define stylesheet entry points
72-
* Fix infinite loop on long words in literal block (#453)
73+
* Crash when other installed packages define stylesheet entry points
74+
* Infinite loop on long words in literal block (#453)
7375
* Sphinx frontend: support the desc_sig_keyword_type, desc_sig_literal_number,
7476
desc_sig_literal_string, desc_sig_literal_char and desc_inline document nodes
7577
(#450)
@@ -238,7 +240,7 @@ Fixed:
238240
* Line-wrapped section headings without hyphenation are missing from the page
239241
header.
240242
* Sphinx frontend: inline text marked with the :menuselection: role is not
241-
styled (now mapped to the *menu cascade style*).
243+
styled (now mapped to the *menu cascade* style).
242244
* Typos in code and documentation (PR #277 by Tim Gates, PR #281 by Filipe
243245
Tavares)
244246
* Handle deprecation of importlib SelectableGroups dict interface (Python 3.10

‎src/rinoh/paragraph.py‎

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from .flowable import Flowable, FlowableStyle, FlowableState, FlowableWidth
2929
from .font import MissingGlyphException
3030
from .hyphenator import Hyphenator
31-
from .inline import InlineFlowableException
31+
from .inline import InlineFlowable, InlineFlowableException
3232
from .layout import EndOfContainer, ContainerOverflow
3333
from .number import NumberStyle, Label, format_number
3434
from .strings import StringField
@@ -427,21 +427,29 @@ def lig_kern(chars, glyph_metrics=None):
427427
return get_glyph_metrics, lig_kern
428428

429429

430+
FALLBACK_STYLE = '_fallback_'
431+
432+
430433
def handle_missing_glyphs(span, container):
434+
if isinstance(span, InlineFlowable):
435+
yield span
436+
return
431437
get_glyph, _ = create_lig_kern(span, container)
432438
string = []
433439
for char in span.text(container):
434440
try:
435-
get_glyph(char)
441+
if char not in '\n\t':
442+
get_glyph(char)
436443
string.append(char)
437444
except MissingGlyphException:
438445
if string:
439446
yield SingleStyledText(''.join(string), parent=span)
440447
string.clear()
441-
if span.parent.style == '_fallback_':
448+
if span.parent.style == FALLBACK_STYLE:
442449
yield SingleStyledText('?', parent=span)
443450
else:
444-
yield SingleStyledText(char, style='_fallback_', parent=span)
451+
fallback_span = SingleStyledText(char, style=FALLBACK_STYLE, parent=span)
452+
yield from handle_missing_glyphs(fallback_span, container)
445453
if string:
446454
yield SingleStyledText(''.join(string), parent=span)
447455

@@ -563,8 +571,10 @@ def next_word(self, container):
563571
else:
564572
if not self._words:
565573
spans = self.paragraph.text(container).wrapped_spans(container)
566-
for _ in islice(spans, 0, self.span_index): pass
567-
self._words = self._spans_to_words(spans)
574+
fixed_spans = (fixed_span for span in spans
575+
for fixed_span in handle_missing_glyphs(span, container))
576+
for _ in islice(fixed_spans, 0, self.span_index): pass
577+
self._words = self._spans_to_words(fixed_spans)
568578
word = self._words.send(container)
569579
return word
570580

@@ -574,22 +584,16 @@ def next_word(self, container):
574584
def _spans_to_words(self, spans):
575585
# FIXME: this is an incomprehensible mess; refactor (with next_word)
576586
# There's a lot going on here: missing glyphs fallback, footnotes, ...
577-
missing_glyphs_spans = None
587+
# e.g. first split spans into smaller spans if missing glyphs are encountered
578588
container = yield
579589
word = Word()
580590
self.span_index -= 1
581591
while True:
582-
if missing_glyphs_spans:
583-
try:
584-
span = next(missing_glyphs_spans)
585-
except StopIteration:
586-
missing_glyphs_spans = None
587-
if not missing_glyphs_spans:
588-
try:
589-
span = next(spans)
590-
self.span_index += 1
591-
except StopIteration:
592-
break
592+
try:
593+
span = next(spans)
594+
self.span_index += 1
595+
except StopIteration:
596+
break
593597
try:
594598
no_break_after = span.get_style('no_break_after', container)
595599
except KeyError: # InlineFlowable
@@ -626,15 +630,7 @@ def _spans_to_words(self, spans):
626630
if word and word[-1].span is span:
627631
prev_glyphs_span = word.pop()
628632
part = str(prev_glyphs_span) + part
629-
try:
630-
glyphs = [get_glyph_metrics(char) for char in part]
631-
except MissingGlyphException:
632-
# FIXME: span annotations are lost here
633-
rest = ''.join(char for _, group in groups
634-
for char in group)
635-
rest_of_span = SingleStyledText(part + rest, parent=span)
636-
missing_glyphs_spans = handle_missing_glyphs(rest_of_span, container)
637-
break
633+
glyphs = [get_glyph_metrics(char) for char in part]
638634
glyphs = lig_kern(part, glyphs)
639635
glyphs_span = GlyphsSpan(span, lig_kern, glyphs)
640636
word.append(glyphs_span)

0 commit comments

Comments
 (0)