about summary refs log tree commit diff stats
path: root/src/layout
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-12-25 19:28:38 +0100
committerbptato <nincsnevem662@gmail.com>2022-12-25 19:28:38 +0100
commit35c5fb0eca71b4a78d013dfe469d78a4a4136c41 (patch)
tree10aa995eea6aca71aae7778299a1b7a6b7b350c1 /src/layout
parenta95a40d2a2bc7e2a7030301737041e8804885ed1 (diff)
downloadchawan-35c5fb0eca71b4a78d013dfe469d78a4a4136c41.tar.gz
layout/engine: handle soft hyphens, wrap on ascii dash
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/engine.nim55
1 files changed, 42 insertions, 13 deletions
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 8ae0a59c..813400b4 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -1,5 +1,6 @@
 import math
 import options
+import strutils
 import tables
 import unicode
 
@@ -20,6 +21,8 @@ type InlineState = object
   skip: bool
   node: StyledNode
   word: InlineWord
+  wrappos: int # position of last wrapping opportunity, or -1
+  hasshy: bool
   maxwidth: int
   computed: CSSComputedValues
 
@@ -70,6 +73,8 @@ proc newWord(state: var InlineState) =
   word.vertalign = computed{"vertical-align"}
   state.ictx.format = format
   state.word = word
+  state.wrappos = -1
+  state.hasshy = false
 
 proc horizontalAlignLine(ictx: InlineContext, line: LineBox, computed: CSSComputedValues, maxwidth: int, last = false) =
   let maxwidth = if ictx.shrink:
@@ -262,6 +267,20 @@ proc addWord(state: var InlineState) =
     state.ictx.addAtom(word, state.maxwidth, state.computed)
     state.newWord()
 
+proc addWordEOL(state: var InlineState) =
+  if state.word.str != "":
+    if state.wrappos != -1:
+      let leftstr = state.word.str.substr(state.wrappos)
+      state.word.str = state.word.str.substr(0, state.wrappos - 1)
+      if state.hasshy:
+        state.word.str &= $Rune(0xAD) # soft hyphen
+        state.hasshy = false
+      state.addWord()
+      state.word.str = leftstr
+      state.word.width = leftstr.width() * state.ictx.cellwidth
+    else:
+      state.addWord()
+
 # Start a new line, even if the previous one is empty
 proc flushLine(ictx: InlineContext, computed: CSSComputedValues, maxwidth: int) =
   applyLineHeight(ictx.viewport, ictx.currentLine, computed)
@@ -271,20 +290,25 @@ proc checkWrap(state: var InlineState, r: Rune) =
   if state.computed{"white-space"} in {WHITESPACE_NOWRAP, WHITESPACE_PRE}:
     return
   let shift = state.ictx.computeShift(state.computed)
+  let rw = r.width()
   case state.computed{"word-break"}
   of WORD_BREAK_NORMAL:
-    if r.width() == 2: # break cjk
-      if state.ictx.currentLine.width + state.word.width + shift + r.width() * state.ictx.cellwidth > state.maxwidth:
-        state.addWord()
+    if rw == 2 or state.wrappos != -1: # break on cjk and wrap opportunities
+      if state.ictx.currentLine.width + state.word.width + shift + rw * state.ictx.cellwidth > state.maxwidth:
+        let l = state.ictx.currentLine
+        state.addWordEOL()
+        if l == state.ictx.currentLine: # no line wrapping occured in addAtom
+          state.ictx.finishLine(state.computed, state.maxwidth)
+          state.ictx.whitespacenum = 0
+  of WORD_BREAK_BREAK_ALL:
+    if state.ictx.currentLine.width + state.word.width + shift + rw * state.ictx.cellwidth > state.maxwidth:
+      let l = state.ictx.currentLine
+      state.addWordEOL()
+      if l == state.ictx.currentLine: # no line wrapping occured in addAtom
         state.ictx.finishLine(state.computed, state.maxwidth)
         state.ictx.whitespacenum = 0
-  of WORD_BREAK_BREAK_ALL:
-    if state.ictx.currentLine.width + state.word.width + shift + r.width() * state.ictx.cellwidth > state.maxwidth:
-      state.addWord()
-      state.ictx.finishLine(state.computed, state.maxwidth)
-      state.ictx.whitespacenum = 0
   of WORD_BREAK_KEEP_ALL:
-    if state.ictx.currentLine.width + state.word.width + shift + r.width() * state.ictx.cellwidth > state.maxwidth:
+    if state.ictx.currentLine.width + state.word.width + shift + rw * state.ictx.cellwidth > state.maxwidth:
       state.ictx.finishLine(state.computed, state.maxwidth)
       state.ictx.whitespacenum = 0
 
@@ -310,8 +334,6 @@ proc renderText*(ictx: InlineContext, str: string, maxwidth: int, computed: CSSC
   state.ictx.flushWhitespace(state.computed)
   state.newWord()
 
-  #if str.strip().len > 0:
-    #eprint "start", str.strip()
   var i = 0
   while i < str.len:
     if str[i].isWhitespace():
@@ -321,8 +343,15 @@ proc renderText*(ictx: InlineContext, str: string, maxwidth: int, computed: CSSC
       var r: Rune
       fastRuneAt(str, i, r)
       state.checkWrap(r)
-      state.word.str &= r
-      state.word.width += r.width() * state.ictx.cellwidth
+      if r == Rune(0xAD): # soft hyphen
+        state.wrappos = state.word.str.len
+        state.hasshy = true
+      else:
+        state.word.str &= r
+        state.word.width += r.width() * state.ictx.cellwidth
+        if r == Rune('-'): # ascii dash
+          state.wrappos = state.word.str.len
+          state.hasshy = false
 
   state.addWord()