diff options
author | bptato <nincsnevem662@gmail.com> | 2023-06-07 19:30:55 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-06-07 19:30:55 +0200 |
commit | 578df008d0e2e6ac2d8ee2ad84ccf640f8d07c55 (patch) | |
tree | 216e69d75300e241508194b3ed61221e98c5f1c6 | |
parent | 724f196225b5351724f0018b2fc78d744891fb17 (diff) | |
download | chawan-578df008d0e2e6ac2d8ee2ad84ccf640f8d07c55.tar.gz |
Add support for width, height media query
-rw-r--r-- | src/buffer/buffer.nim | 10 | ||||
-rw-r--r-- | src/css/cascade.nim | 58 | ||||
-rw-r--r-- | src/css/mediaquery.nim | 205 | ||||
-rw-r--r-- | src/css/values.nim | 3 | ||||
-rw-r--r-- | src/html/dom.nim | 2 | ||||
-rw-r--r-- | src/html/env.nim | 4 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 3 |
7 files changed, 234 insertions, 51 deletions
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim index 77772532..8775e0e6 100644 --- a/src/buffer/buffer.nim +++ b/src/buffer/buffer.nim @@ -558,7 +558,7 @@ proc loadResource(buffer: Buffer, document: Document, elem: HTMLLinkElement): Em let media = elem.media if media != "": let media = parseMediaQueryList(parseListOfComponentValues(newStringStream(media))) - if not media.applies(): return + if not media.applies(document.window): return return buffer.loader.fetch(newRequest(url)).then(proc(res: Response) = if res.contenttype == "text/css": elem.sheet = parseStylesheet(res.body)) @@ -652,7 +652,8 @@ proc finishLoad(buffer: Buffer): EmptyPromise = buffer.sstream.setPosition(0) buffer.available = 0 if buffer.window == nil: - buffer.window = newWindow(buffer.config.scripting, buffer.selector) + buffer.window = newWindow(buffer.config.scripting, buffer.selector, + buffer.attrs) let doc = parseHTML(buffer.sstream, charsets = buffer.charsets, window = buffer.window, url = buffer.url) buffer.document = doc @@ -745,7 +746,8 @@ proc cancel*(buffer: Buffer): int {.proxy.} = buffer.sstream.setPosition(0) buffer.available = 0 if buffer.window == nil: - buffer.window = newWindow(buffer.config.scripting, buffer.selector) + buffer.window = newWindow(buffer.config.scripting, buffer.selector, + buffer.attrs) buffer.document = parseHTML(buffer.sstream, charsets = buffer.charsets, window = buffer.window, url = buffer.url, canReinterpret = false) @@ -1195,7 +1197,7 @@ proc launchBuffer*(config: BufferConfig, source: BufferSource, buffer.srenderer = newStreamRenderer(buffer.sstream, buffer.charsets) if buffer.config.scripting: buffer.window = newWindow(buffer.config.scripting, buffer.selector, - some(buffer.loader)) + buffer.attrs, some(buffer.loader)) let socks = connectSocketStream(mainproc, false) socks.swrite(getpid()) buffer.pstream = socks 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) diff --git a/src/html/dom.nim b/src/html/dom.nim index 78184540..190dd479 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -19,6 +19,7 @@ import img/png import img/path import io/loader import io/request +import io/window import js/javascript import js/timeout import types/blob @@ -88,6 +89,7 @@ type type Window* = ref object + attrs*: WindowAttributes console* {.jsget.}: console navigator* {.jsget.}: Navigator settings*: EnvironmentSettings diff --git a/src/html/env.nim b/src/html/env.nim index 8b558b8a..ca7e497d 100644 --- a/src/html/env.nim +++ b/src/html/env.nim @@ -6,6 +6,7 @@ import html/htmlparser import io/loader import io/promise import io/request +import io/window import js/intl import js/javascript import js/timeout @@ -124,8 +125,9 @@ proc runJSJobs*(window: Window) = window.jsrt.runJSJobs(window.console.err) proc newWindow*(scripting: bool, selector: Selector[int], - loader = none(FileLoader)): Window = + attrs: WindowAttributes, loader = none(FileLoader)): Window = let window = Window( + attrs: attrs, console: console(err: newFileStream(stderr)), navigator: Navigator(), loader: loader, diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim index 93fce06f..c41a7e57 100644 --- a/src/utils/twtstr.nim +++ b/src/utils/twtstr.nim @@ -130,6 +130,9 @@ func normalizeLocale*(s: string): string = func isAscii*(r: Rune): bool = return cast[uint32](r) < 128 +func `==`*(r: Rune, c: char): bool {.inline.} = + return Rune(c) == r + func startsWithNoCase*(str, prefix: string): bool = if str.len < prefix.len: return false # prefix.len is always lower |