about summary refs log tree commit diff stats
path: root/src/css
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-06-07 19:30:55 +0200
committerbptato <nincsnevem662@gmail.com>2023-06-07 19:30:55 +0200
commit578df008d0e2e6ac2d8ee2ad84ccf640f8d07c55 (patch)
tree216e69d75300e241508194b3ed61221e98c5f1c6 /src/css
parent724f196225b5351724f0018b2fc78d744891fb17 (diff)
downloadchawan-578df008d0e2e6ac2d8ee2ad84ccf640f8d07c55.tar.gz
Add support for width, height media query
Diffstat (limited to 'src/css')
-rw-r--r--src/css/cascade.nim58
-rw-r--r--src/css/mediaquery.nim205
-rw-r--r--src/css/values.nim3
3 files changed, 220 insertions, 46 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index 7aac213f..acfbdcb9 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -18,7 +18,26 @@ import types/color
 type
   DeclarationList* = array[PseudoElem, seq[CSSDeclaration]]
 
-func applies(mq: MediaQuery): bool =
+func applies(feature: MediaFeature, window: Window): bool =
+  case feature.t
+  of FEATURE_COLOR:
+    return 8 in feature.range
+  of FEATURE_GRID:
+    return feature.b
+  of FEATURE_HOVER:
+    return feature.b
+  of FEATURE_PREFERS_COLOR_SCHEME:
+    return feature.b
+  of FEATURE_WIDTH:
+    let a = px(feature.lengthrange.a, window.attrs, 0)
+    let b = px(feature.lengthrange.b, window.attrs, 0)
+    return window.attrs.ppc * window.attrs.width in a .. b
+  of FEATURE_HEIGHT:
+    let a = px(feature.lengthrange.a, window.attrs, 0)
+    let b = px(feature.lengthrange.b, window.attrs, 0)
+    return window.attrs.ppl * window.attrs.height in a .. b
+
+func applies(mq: MediaQuery, window: Window): bool =
   case mq.t
   of CONDITION_MEDIA:
     case mq.media
@@ -29,25 +48,17 @@ func applies(mq: MediaQuery): bool =
     of MEDIA_TYPE_TTY: return true
     of MEDIA_TYPE_UNKNOWN: return false
   of CONDITION_NOT:
-    return not mq.n.applies()
+    return not mq.n.applies(window)
   of CONDITION_AND:
-    return mq.anda.applies() and mq.andb.applies()
+    return mq.anda.applies(window) and mq.andb.applies(window)
   of CONDITION_OR:
-    return mq.ora.applies() or mq.orb.applies()
+    return mq.ora.applies(window) or mq.orb.applies(window)
   of CONDITION_FEATURE:
-    case mq.feature.t
-    of FEATURE_COLOR:
-      return true #TODO
-    of FEATURE_GRID:
-      return mq.feature.b
-    of FEATURE_HOVER:
-      return mq.feature.b
-    of FEATURE_PREFERS_COLOR_SCHEME:
-      return mq.feature.b
-
-func applies*(mqlist: MediaQueryList): bool =
+    return mq.feature.applies(window)
+
+func applies*(mqlist: MediaQueryList, window: Window): bool =
   for mq in mqlist:
-    if mq.applies():
+    if mq.applies(window):
       return true
   return false
 
@@ -209,12 +220,12 @@ proc applyDeclarations(pseudo: PseudoElem, styledParent: StyledNode, ua,
   if builder.hasValues():
     result = styledParent.newStyledElement(pseudo, builder.buildComputedValues())
 
-func applyMediaQuery(ss: CSSStylesheet): CSSStylesheet =
+func applyMediaQuery(ss: CSSStylesheet, window: Window): CSSStylesheet =
   if ss == nil: return nil
   result = ss
   for mq in ss.mq_list:
-    if mq.query.applies():
-      result.add(mq.children.applyMediaQuery())
+    if mq.query.applies(window):
+      result.add(mq.children.applyMediaQuery(window))
 
 func calcRules(styledNode: StyledNode, ua, user: CSSStylesheet, author: seq[CSSStylesheet]): tuple[uadecls, userdecls: DeclarationList, authordecls: seq[DeclarationList]] =
   result.uadecls = calcRules(styledNode, ua)
@@ -245,7 +256,7 @@ proc applyRules(document: Document, ua, user: CSSStylesheet, cachedTree: StyledN
 
   var author: seq[CSSStylesheet]
   for sheet in document.sheets():
-    author.add(sheet.applyMediaQuery())
+    author.add(sheet.applyMediaQuery(document.window))
 
   var styledStack: seq[CascadeLevel]
   styledStack.add((nil, document.html, PSEUDO_NONE, cachedTree))
@@ -393,7 +404,8 @@ proc applyRules(document: Document, ua, user: CSSStylesheet, cachedTree: StyledN
 
       stack_append styledChild, PSEUDO_BEFORE
 
-proc applyStylesheets*(document: Document, uass, userss: CSSStylesheet, previousStyled: StyledNode): StyledNode =
-  let uass = uass.applyMediaQuery()
-  let userss = userss.applyMediaQuery()
+proc applyStylesheets*(document: Document, uass, userss: CSSStylesheet,
+    previousStyled: StyledNode): StyledNode =
+  let uass = uass.applyMediaQuery(document.window)
+  let userss = userss.applyMediaQuery(document.window)
   return document.applyRules(uass, userss, previousStyled)
diff --git a/src/css/mediaquery.nim b/src/css/mediaquery.nim
index 2292a27c..8d75be2a 100644
--- a/src/css/mediaquery.nim
+++ b/src/css/mediaquery.nim
@@ -1,6 +1,9 @@
+import strutils
 import tables
 
 import css/cssparser
+import css/values
+import utils/twtstr
 
 type
   MediaQueryParser = object
@@ -16,14 +19,19 @@ type
     CONDITION_MEDIA
 
   MediaFeatureType* = enum
-    FEATURE_COLOR, FEATURE_GRID, FEATURE_HOVER, FEATURE_PREFERS_COLOR_SCHEME
+    FEATURE_COLOR, FEATURE_GRID, FEATURE_HOVER, FEATURE_PREFERS_COLOR_SCHEME,
+    FEATURE_WIDTH, FEATURE_HEIGHT
 
   MediaFeature* = object
     case t*: MediaFeatureType
     of FEATURE_COLOR:
-      color*: Slice[int]
+      range*: Slice[int]
     of FEATURE_GRID, FEATURE_HOVER, FEATURE_PREFERS_COLOR_SCHEME:
       b*: bool
+    of FEATURE_WIDTH, FEATURE_HEIGHT:
+      lengthrange*: Slice[CSSLength]
+      lengthaeq*: bool
+      lengthbeq*: bool
 
   MediaQuery* = ref object
     case t*: MediaConditionType
@@ -42,6 +50,9 @@ type
 
   MediaQueryList* = seq[MediaQuery]
 
+  MediaQueryComparison = enum
+    COMPARISON_EQ, COMPARISON_GT, COMPARISON_LT, COMPARISON_GE, COMPARISON_LE
+
 const MediaTypes = {
   "all": MEDIA_TYPE_ALL,
   "print": MEDIA_TYPE_PRINT,
@@ -50,6 +61,8 @@ const MediaTypes = {
   "tty": MEDIA_TYPE_TTY
 }.toTable()
 
+const RangeFeatures = {FEATURE_COLOR, FEATURE_WIDTH, FEATURE_HEIGHT}
+
 proc has(parser: MediaQueryParser, i = 0): bool {.inline.} =
   return parser.cvals.len > parser.at + i
 
@@ -77,7 +90,13 @@ proc getBoolFeature(feature: MediaFeatureType): MediaQuery =
   of FEATURE_GRID, FEATURE_HOVER, FEATURE_PREFERS_COLOR_SCHEME:
     result.feature = MediaFeature(t: feature, b: true)
   of FEATURE_COLOR:
-    result.feature = MediaFeature(t: feature, color: 1..high(int))
+    result.feature = MediaFeature(t: feature, range: 1..high(int))
+  else:
+    return nil
+
+template skip_has(): bool =
+  parser.skipBlanks()
+  parser.has()
 
 template get_tok(tok: untyped) =
   if not (cval of CSSToken): return nil
@@ -87,20 +106,33 @@ template get_idtok(tok: untyped) =
   get_tok(tok)
   if tok.tokenType != CSS_IDENT_TOKEN: return nil
 
-template expect_mq_int(b: bool, ifalse: int, itrue: int) =
+template consume_token(): CSSToken =
+  let cval = parser.consume()
+  if not (cval of CSSToken): return nil
+  CSSToken(cval)
+
+template skip_consume(): CSSToken =
+  parser.skipBlanks()
+  consume_token()
+
+template expect_int(i: var int) =
   let cval = parser.consume()
   if not (cval of CSSToken): return nil
   let tok = CSSToken(cval)
-  if tok.tokenType != CSS_NUMBER_TOKEN: return nil
-  let i = int(tok.nvalue)
+  if tok.tokenType == CSS_NUMBER_TOKEN and tok.tflagb == TFLAGB_INTEGER:
+    i = int(tok.nvalue)
+  else:
+    return nil
+
+template expect_mq_int(b: bool, ifalse: int, itrue: int) =
+  var i: int
+  expect_int(i)
   if i == ifalse: b = false
   elif i == itrue: b = true
   else: return nil
 
 template expect_bool(b: bool, sfalse: string, strue: string) =
-  let cval = parser.consume()
-  if not (cval of CSSToken): return nil
-  let tok = CSSToken(cval)
+  let tok = consume_token()
   if tok.tokenType != CSS_IDENT_TOKEN: return nil
   let s = tok.value
   case s
@@ -108,31 +140,154 @@ template expect_bool(b: bool, sfalse: string, strue: string) =
   of sfalse: b = false
   else: return nil
 
-proc parseFeature(parser: var MediaQueryParser, feature: MediaFeatureType): MediaQuery =
-  if not parser.has(): return getBoolFeature(feature)
+template expect_comparison(comparison: var MediaQueryComparison) =
+  let tok = consume_token()
+  if tok != CSS_DELIM_TOKEN: return nil
+  if tok.rvalue.isAscii(): return nil
+  let c = char(tok.rvalue)
+  if c notin {'=', '<', '>'}: return nil
+  block parse:
+    case char(tok.rvalue)
+    of '<':
+      if parser.has():
+        let tok = skip_consume()
+        if tok == CSS_DELIM_TOKEN and tok.rvalue == '=':
+          comparison = COMPARISON_LE
+          break parse
+        parser.reconsume()
+      comparison = COMPARISON_LT
+    of '>':
+      if parser.has():
+        let tok = skip_consume()
+        if tok == CSS_DELIM_TOKEN and tok.rvalue == '=':
+          comparison = COMPARISON_GE
+          break parse
+        parser.reconsume()
+      comparison = COMPARISON_GT
+    of '=':
+      comparison = COMPARISON_EQ
+    else: return nil
+
+template expect_int_range(range: var Slice[int], ismin, ismax: bool) =
+  if ismin:
+    expect_int(range.a)
+  elif ismax:
+    expect_int(range.b)
+  else:
+    let tok = consume_token
+    parser.reconsume()
+    if tok.tokenType == CSS_DELIM_TOKEN:
+      var comparison: MediaQueryComparison
+      expect_comparison(comparison)
+      if not skip_has: return nil
+      case comparison
+      of COMPARISON_EQ:
+        expect_int(range.a) #TODO should be >= 0 (for color at least)
+        range.b = range.a
+      of COMPARISON_GT:
+        expect_int(range.a)
+        range.b = high(int)
+      of COMPARISON_GE:
+        expect_int(range.a)
+        range.b = high(int)
+      of COMPARISON_LT:
+        expect_int(range.b)
+      of COMPARISON_LE:
+        expect_int(range.b)
+    else:
+      return nil
+
+template expect_length(length: var CSSLength) =
+  let cval = parser.consume()
+  try:
+    length = cssLength(cval)
+  except CSSValueError:
+    return nil
+
+template expect_length_range(range: var Slice[CSSLength], lengthaeq, lengthbeq:
+    var bool, ismin, ismax: bool) =
+  if ismin:
+    expect_length(range.a)
+    range.b = CSSLength(num: Inf, unit: UNIT_PX)
+    lengthaeq = true
+  elif ismax:
+    range.a = CSSLength(num: 0, unit: UNIT_PX)
+    expect_length(range.b)
+    lengthbeq = true
+  else:
+    let tok = consume_token
+    parser.reconsume()
+    if tok.tokenType == CSS_DELIM_TOKEN:
+      var comparison: MediaQueryComparison
+      expect_comparison(comparison)
+      if not skip_has: return nil
+      expect_length(range.a)
+      expect_length(range.b)
+      case comparison
+      of COMPARISON_EQ:
+        expect_length(range.a)
+        range.b = range.a
+        lengthaeq = true
+        lengthbeq = true
+      of COMPARISON_GT:
+        expect_length(range.a)
+        range.b = CSSLength(num: Inf, unit: UNIT_PX)
+      of COMPARISON_GE:
+        expect_length(range.a)
+        range.b = CSSLength(num: Inf, unit: UNIT_PX)
+        lengthaeq = true
+      of COMPARISON_LT:
+        range.a = CSSLength(num: 0, unit: UNIT_PX)
+        expect_length(range.b)
+      of COMPARISON_LE:
+        range.a = CSSLength(num: 0, unit: UNIT_PX)
+        expect_length(range.b)
+        lengthbeq = true
+    else:
+      return nil
+
+proc parseFeature(parser: var MediaQueryParser, t: MediaFeatureType,
+    ismin, ismax: bool): MediaQuery =
+  if not parser.has(): return getBoolFeature(t)
   let cval = parser.consume()
   var tok: CSSToken
   get_tok(tok)
   if tok.tokenType != CSS_COLON_TOKEN: return nil
   parser.skipBlanks()
-  case feature
+  if (ismin or ismax) and t notin RangeFeatures:
+    return nil
+  let feature = case t
   of FEATURE_GRID:
     var b: bool
     expect_mq_int(b, 0, 1)
-    result = MediaQuery(t: CONDITION_FEATURE, feature: MediaFeature(t: feature, b: b))
+    MediaFeature(t: t, b: b)
   of FEATURE_HOVER:
     var b: bool
     expect_bool(b, "none", "hover")
-    result = MediaQuery(t: CONDITION_FEATURE, feature: MediaFeature(t: feature, b: b))
+    MediaFeature(t: t, b: b)
   of FEATURE_PREFERS_COLOR_SCHEME:
     var b: bool
     expect_bool(b, "light", "dark")
-    result = MediaQuery(t: CONDITION_FEATURE, feature: MediaFeature(t: feature, b: b))
-  else: return nil
-
+    MediaFeature(t: t, b: b)
+  of FEATURE_COLOR:
+    var range: Slice[int]
+    expect_int_range(range, ismin, ismax)
+    MediaFeature(t: t, range: range)
+  of FEATURE_WIDTH, FEATURE_HEIGHT:
+    var range: Slice[CSSLength]
+    var lengthaeq: bool
+    var lengthbeq: bool
+    expect_length_range(range, lengthaeq, lengthbeq, ismin, ismax)
+    MediaFeature(
+      t: t,
+      lengthrange: range,
+      lengthaeq: lengthaeq,
+      lengthbeq: lengthbeq
+    )
   parser.skipBlanks()
   if parser.has():
     return nil
+  return MediaQuery(t: CONDITION_FEATURE, feature: feature)
 
 proc parseMediaCondition(parser: var MediaQueryParser, non = false, noor = false): MediaQuery
 
@@ -154,18 +309,24 @@ proc parseMediaInParens(parser: var MediaQueryParser): MediaQuery =
     get_tok(tok)
     fparser.skipBlanks()
     if tok.tokenType == CSS_IDENT_TOKEN:
-      let tokval = tok.value
+      var tokval = tok.value
+      let ismin = tokval.startsWith("min-")
+      let ismax = tokval.startsWith("max-")
+      if ismin or ismax:
+        tokval = tokval.substr(4)
       case tokval
       of "not":
         return fparser.parseMediaCondition(true)
       of "color":
-        return fparser.parseFeature(FEATURE_COLOR)
+        return fparser.parseFeature(FEATURE_COLOR, ismin, ismax)
+      of "width":
+        return fparser.parseFeature(FEATURE_WIDTH, ismin, ismax)
       of "grid":
-        return fparser.parseFeature(FEATURE_GRID)
+        return fparser.parseFeature(FEATURE_GRID, ismin, ismax)
       of "hover":
-        return fparser.parseFeature(FEATURE_HOVER)
+        return fparser.parseFeature(FEATURE_HOVER, ismin, ismax)
       of "prefers-color-scheme":
-        return fparser.parseFeature(FEATURE_PREFERS_COLOR_SCHEME)
+        return fparser.parseFeature(FEATURE_PREFERS_COLOR_SCHEME, ismin, ismax)
       else: discard
   return nil
 
diff --git a/src/css/values.nim b/src/css/values.nim
index dd3f2df4..2204e6ab 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -552,7 +552,8 @@ func cssColor*(val: CSSComponentValue): RGBAColor =
 
 func isToken(cval: CSSComponentValue): bool {.inline.} = cval of CSSToken
 
-func cssLength(val: CSSComponentValue, has_auto: static bool = true, allow_negative: static bool = true): CSSLength =
+func cssLength*(val: CSSComponentValue, has_auto: static bool = true,
+    allow_negative: static bool = true): CSSLength =
   block nofail:
     if val of CSSToken:
       let tok = CSSToken(val)