Cascading Style Sheets with RenoirSt

Gabriel Omar Cotelli with Damien Cassou

RenoirST is a DSL enabling programmatic cascading style sheet generation for Pharo developed by Gabriel Omar Cotelli.

RenoirST aims to improve CSS integration with existing web frameworks. To do that, RenoirST generates CSS out of Pharo code. Renoir features are: common properties declaration, CSS3 selector support, important rules, font face rules and media queries support. In this tutorial we will present the key features of RenoirSt with a large set of examples. This tutorial assumes some knowledge of CSS and Pharo. For a little introduction about CSS you can read the Seaside's book CSS chapter.

1. Getting Started

To load the library in your image, evaluate:

Metacello new
   configuration: 'RenoirSt';
   githubUser: 'gcotelli' project: 'RenoirSt' commitish: 'master' path: 'source';
   load

download a ready to use image from the Pharo contribution CI Server or install it using the Catalog/Configuration Browser.

The main entry point for the library is the class CascadingStyleSheetBuilder. In a workspace or playground, inspect the result of the following expression:

CascadingStyleSheetBuilder new build

You now have an inspector on your first (empty and useless) style sheet. Real stylesheets are composed of rules (or rule-sets), where each one has a selector and a declaration group. The selector determines if the rule applies to some element in the DOM, and the declaration group specifies the style to apply.

Our first useful style sheet will simply assign a margin to every div element in the DOM.

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div ]
   with: [ :style | style margin: 2 px ];
   build

the expected result in CSS is:

div
{
   margin: 2px;
}

The message declareRuleSetFor:with: is used to configure a rule-set in the builder. The message requires two blocks: the first block defines the selector and the second defines the style to apply to elements matching the selector. The selector argument of the first block is an entry point to construct the selector (more on this later). The style argument of the second block is an entry point to declare CSS properties and values.

The properties API is mostly defined following this rules:

  • Properties without dashes in the name are directly mapped: the margin CSS property is mapped to a margin: message send.
  • Properties with one or more dashes are mapped using camel case: the margin-top CSS property is mapped to the marginTop: message send.

2. Defining the Rules

We now present how the various CSS rules can be expressed with RenoirSt. RenoirSt supports many CSS types, comments, and even functional notation.

2.1. Basic CSS Types

2.1.1. Lengths, Angles, Times and Frequencies

The library provides out-of-the-box support for the length, angle, time and frequency units in the CSS spec. There are extensions for Integer and Float classes allowing to obtain lengths.

The supported length units are:

  • em relative to font size
  • ex relative to "x" height
  • ch relative to width of the zero glyph in the element's font
  • rem relative to font size of root element
  • vw 1% of viewport's width
  • vh 1% of viewport's height
  • vmin 1% of viewport's smaller dimension
  • vmax 1% of viewport's larger dimension
  • cm centimeters
  • mm millimeteres
  • in inches
  • pc picas
  • pt points
  • px pixels (note that CSS has some special definition for pixel)

The supported angle units are:

  • deg degrees
  • grad gradians
  • rad radians
  • turn turns

The supported time units are:

  • s seconds
  • ms milliseconds

The supported frequency units are:

  • Hz Hertz
  • kHz KiloHertz

RenoirST also supports the creation of percentages: 50 percent is mapped to 50% in the resulting CSS.

Some properties require integer or floating point values. In these cases just use the standard Pharo integer and float support. For example:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div ]
   with: [ :style | style zIndex: 2 ];
   build
2.1.2. Colors

The library supports abstractions for properties requiring color values. The shared pool CssSVGColors provides easy access to colors in the SVG 1.0 list, and the abstractions CssRGBColor and CssHSLColor allow the creation of colors in the RGB and HSL spaces including alpha support.

For example,

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div ]
   with: [ :style |
      style
         backgroundColor: CssSVGColors aliceBlue;
         borderColor: (CssRGBColor red: 0 green: 128 blue: 0 alpha: 0.5) ];
         build

evaluates to:

div
{
   background-color: aliceblue;
   border-color: rgba(0,128,0,0.5);
}

In a real scenario you should avoid hard coding colors as in the examples. It is recommended to put colors in objects representing a theme or something that gives them a name related to your application.

RGB-Colors also support percentage values:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div ]
   with: [ :style |
      style borderColor: (CssRGBColor red: 0 percent green: 50 percent blue: 0 percent) ];
   build

evaluates to:

div
{
   border-color: rgb(0%,50%,0%);
}

Notice the difference in the used message because there is no alpha channel specification.

2.1.3. Constants

A lot of values for CSS properties are just keyword constants. This support is provided by the classes CssConstants and CssFontConstants.

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div ]
   with: [ :style | style textAlign: CssConstants justify ];
   build

evaluates to:

div
{
   text-align: justify;
}
2.1.4. Several Property Values

Some properties support a wide range of values. For example the margin property can have 1, 2 , 3 or 4 values specified. If you only need one value, just pass it as a parameter. For more than one value, use an array:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div ]
   with: [ :style | style margin: { 2 px. 4 px } ];
   build

evaluates to:

div
{
   margin: 2px 4px;
}
2.1.5. URLs

ZnUrl instances can be used as the value for properties requiring an URI. Both relative and absolute URLs are accepted. A relative URL is considered by default relative to the site root.

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div class: 'logo' ]
   with: [ :style | style backgroundImage: 'images/logo.png' asZnUrl ];
   declareRuleSetFor: [ :selector | selector div class: 'logo' ]
   with: [ :style | style backgroundImage: 'http://www.example.com/images/logo.png' asZnUrl ];
   build

Evaluates to:

div.logo
{
   background-image: url("/images/logo.png");
}

div.logo
{
   background-image: url("http://www.example.com/images/logo.png");
}

To use a URL relative to the style sheet, send to it the message relativeToStyleSheet.

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div class: 'logo' ]
   with: [ :style | style backgroundImage: 'images/logo.png' asZnUrl relativeToStyleSheet];
   build

Evaluates to:

div.logo
{
   background-image: url("images/logo.png");
}

2.2. Comments

When declaring rule sets, the library supports attaching comments to them with the declareRuleSetFor:with:andComment: message:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div ]
   with: [ :style | style margin: 2 pc ]
   andComment: 'Two picas margin';
   build

evaluates to:

/*Two picas margin*/
div
{
   margin: 2pc;
}

RenoirST also supports defining stand-alone comments (not attached to any rule):

CascadingStyleSheetBuilder new
   comment: 'A general comment';
   build

evaluates to:

/*A general comment*/

2.3. Functional Notation

A functional notation is a type of CSS component value that can represent complex types or invoke special processing. Mathematical expressions, toggling between values, attribute references, and gradients are all supported in RenoirST.

2.3.1. Mathematical Expressions

The library provides support for math expressions using the CssMathExpression abstraction:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div ]
   with: [ :style | style margin: (CssMathExpression on: 2 pc) / 3 + 2 percent ];
   build

evaluates to:

div
{
    margin: calc(2pc / 3 + 2%);
}
2.3.2. Toggling Between Values

To let descendant elements cycle over a list of values instead of inheriting the same value, one can use the CssToggle abstraction:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector unorderedList unorderedList ]
   with: [ :style | style listStyleType: (CssToggle cyclingOver: { CssConstants disc. CssConstants circle. CssConstants square}) ];
   build

evaluates to:

ul ul
{
   list-style-type: toggle(disc, circle, square);
}
2.3.3. Attribute References

The attr() function is allowed as a component value in properties applied to an element or pseudo-element. The function returns the value of an attribute on the element. If used on a pseudo-element, it returns the value of the attribute on the pseudo-element's originating element. This function is supported using the CssAttributeReference abstraction and can be used simply providing an attribute name:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div before ]
   with: [ :style | style content: (CssAttributeReference toAttributeNamed: 'title') ];
   build

Evaluates to:

div::before
{
   content: attr(title string);
}

RenoirST allows for providing the type or unit of the attribute (if no type or unit is specified the string type is assumed):

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div  ]
   with: [ :style |
      style width: (CssAttributeReference
         toAttributeNamed: 'height'
         ofType: CssLengthUnits pixel) ];
   build

evaluates to:

div
{
   width: attr(height px);
}

Additionally, it is possible to provide a value to use when the attribute is absent:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div before ]
   with: [ :style |
      style content: (CssAttributeReference
      toStringAttributeNamed: 'title'
      withFallback: 'Missing title')
   ];
   build

evaluates to:

div::before
{
   content: attr(title string, "Missing title");
}
2.3.4. Gradients

A gradient is an image that smoothly fades from one color to another. Gradients are commonly used for subtle shading in background images, buttons, and many other places. The gradient notations described in this section allow an author to specify such an image in a terse syntax. This notation is supported using CssLinearGradient and CssRadialGradient abstractions.

To represent a simple linear gradient from a color to another, send the fading: message to CssLinearGradient with the two colors in an array as a parameter:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div ]
   with: [ :style | style background: (CssLinearGradient
      fading: { CssSVGColors yellow. CssSVGColors blue }) ]

The above code evaluates to:

div
{
   background: linear-gradient(yellow, blue);
}

By default, a gradient's direction is from top to bottom. To specify a different direction, the author can use the to:fading: message instead:

CssLinearGradient
   to: CssConstants right
   fading: { CssSVGColors yellow. CssSVGColors blue }

The above code will result in a gradient with yellow on the left side and blue on the right side. The equivalent CSS is:

linear-gradient(to right, yellow, blue);

To specify a diagonal direction, an array must be passed as the to: argument:

CssLinearGradient
   to: { CssConstants top. CssConstants right }
   fading: { CssSVGColors yellow. CssSVGColors blue }

The above code will result in a gradient with blue in the top right corner and yellow in the bottom left one. The equivalent CSS is:

linear-gradient(to top right, yellow, blue);

Directions can also be specified as an angle by sending the rotated:fading: message:

CssLinearGradient
   rotated: 45 deg
   fading: { CssSVGColors yellow. CssSVGColors blue }

The above code maps to:

linear-gradient(45deg, yellow, blue);

Gradients can be fine-tuned by manipulating so-called color stops:

CssLinearGradient
   to: CssConstants right
   fading: {
      CssSVGColors yellow.
      CssColorStop for: CssSVGColors blue at: 30 percent }

The above code maps to:

linear-gradient(to right, yellow, blue 30%);

This results in a linear gradient from left to right with yellow at the left side and plain blue from 30% (of the horizontal line) to the right side. More than two colors can be passed as argument to fading:. This can be used to create rainbows:

CssLinearGradient
   to: CssConstants right
   fading: {
      CssSVGColors red.
      CssSVGColors orange.
      CssSVGColors yellow.
      CssSVGColors green.
      CssSVGColors blue.
      CssSVGColors indigo.
      CssSVGColors violet.
   }

This maps to:

linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet);

To create radial gradients, the author must send messages to CssRadialGradient. For example,

CssRadialGradient fading: { CssSVGColors yellow. CssSVGColors green }

maps to:

radial-gradient(yellow,green)

This results in a radial gradient with yellow at the center and green all around. Coordinates can be passed to both the first and second parameters of the elliptical:at:fading: message:

(CssRadialGradient
   elliptical: {20 px. 30 px}
   at: { 20 px. 30 px}
   fading: { CssSVGColors red. CssSVGColors yellow. CssSVGColors green })

This maps to:

   background: radial-gradient(20px 30px ellipse at 20px 30px, red, yellow, green);

To make the gradient repeatable, just send to it the message beRepeating. For Example:

(CssRadialGradient fading: { CssSVGColors yellow. CssSVGColors green }) beRepeating

renders as:

repeating-radial-gradient(yellow, green);
2.3.5. Box Shadows

Box Shadows are supported with CssBoxShadow abstraction. This abstraction simplifies the use of the box-shadow property.

CssBoxShadow
   horizontalOffset: 64 px
   verticalOffset: 64 px
   blurRadius: 12 px
   spreadDistance: 40 px
   color: (CssSVGColors black newWithAlpha: 0.4)

evaluates to:

64px 64px 12px 40px rgba(0,0,0,0.4)

Several shadows can be combined:

(CssBoxShadow horizontalOffset: 64 px verticalOffset: 64 px blurRadius: 12 px  spreadDistance: 40 px color: (CssSVGColors black newWithAlpha: 0.4)) ,
(CssBoxShadow horizontalOffset: 12 px verticalOffset: 11 px blurRadius: 0 px  spreadDistance: 8 px color: (CssSVGColors black newWithAlpha: 0.4)) beInset

Evaluates to:

64px 64px 12px 40px rgba(0,0,0,0.4), inset 12px 11px 0px 8px rgba(0,0,0,0.4)

3. Defining the selectors

So far our focus was on the style part of the rule. Let's focus now on the available selectors. Remember that a CSS selector represents a structure used to match elements in the document tree. This chapter asume some familiarity with the CSS selectors and will not go in detail about the exact meaning of each one. For more details you can take a look at CSS3 selector documentation.

3.1. Type Selectors

These selectors match a specific element type in the DOM. The library provides out-of-the-box support for HTML elements. One example is the div selector used in the previous chapter:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [ :selector | selector div ]
   with: [ :style | ... ];
   build

The following other example matches <ol> (ordered list) elements:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector orderedList ]
   with: [ :style | ... ]

evaluating to:

ol
{
  ...
}

To get a list of the supported type selectors evaluate CssSelector selectorsInProtocol: '*RenoirSt-HTML'.

3.2. Combinators

Selectors can be combined to represent complex queries. One of the most common use cases is the descendant combinator:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector div orderedList ]
   with: [:style | ... ]

evaluating to:

div ol
{
   ...
}

This only matches if an ol element is a descendant (direct or not) of a div element.

The child combinator only matches when an element is a direct child of another one:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector div > selector orderedList ]
   with: [:style |  ]

evaluates to

div > ol
{
  ...
}

Siblings combinators can be created using + and ~:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector div + selector orderedList ]
   with: [:style |  ]
   declareRuleSetFor: [:selector | selector div ~ selector orderedList ]
   with: [:style |  ]

Class selectors can be created by sending class: and id selectors can be created by sending id:. For example,

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | (selector div class: 'pastoral') id: #account5 ]
   with: [:style |  ]

evaluates to:

div.pastoral#account5
{
   ...
}

You should not hardcode the classes and ids, they should be obtained from the same object that holds them for the HTML generation. You probably have some code setting the class(es) and/or id(s) to a particular HTML element.

A comma-separated list of selectors represents the union of all elements selected by each of the individual selectors in the list. For example, in CSS when several selectors share the same declarations, they may be grouped into a comma-separated list.

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector paragraph , selector div ]
   with: [:style | ... ]

evaluates to:

p, div
{
  ...
}

3.3. Attribute Selectors

Attribute selectors are useful to match an element based on its attributes and their values.

The attribute presence selector matches an element having an attribute (without considering the value of the attribute):

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector h1 havingAttribute: 'title' ]
   with: [:style | style color: CssSVGColors blue ];
   build

evaluates to:

h1[title]
{
   color: blue;
}

The attribute value exact matching selectors matches an element having an attribute with a specific value:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector span withAttribute: 'class' equalTo: 'example' ]
   with: [:style | style color: CssSVGColors blue ];
   build

evaluates to:

span[class="example"]
{
   color: blue;
}

The attribute value inclusion selector matches an element having an attribute with a value including as a word the matching term:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector anchor attribute: 'rel' includes: 'copyright' ]
   with: [:style | style color: CssSVGColors blue ];
   build
a[rel~="copyright"]
{
   color: blue;
}

Other attribute selectors are used for substring matching:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector anchor firstValueOfAttribute: 'hreflang' beginsWith: 'en' ]
   with: [:style | style color: CssSVGColors blue ];
   build
a[hreflang|="en"]
{
   color: blue;
}
CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector anchor attribute: 'type' beginsWith: 'image/' ]
   with: [:style | style color: CssSVGColors blue ];
   build
a[type^="image/"]
{
   color: blue;
}
CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector anchor attribute: 'type' endsWith: '.html' ]
   with: [:style | style color: CssSVGColors blue ];
   build
a[type$=".html"]
{
   color: blue;
}
CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector paragraph attribute: 'title' includesSubstring: 'hello' ]
   with: [:style | style color: CssSVGColors blue ];
   build
p[title*="hello"]
{
   color: blue;
}

3.4. Pseudo-Classes

The pseudo-class concept is introduced to allow selection based on information that lies outside of the document tree or that cannot be expressed using the simpler selectors. Most pseudo-classes are supported just by sending one of the following messages link, visited, active, hover, focus, target, enabled, disabled or checked.

Here is a small example that uses the pseudo-classes:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector anchor link ]
   with: [:style | style color: CssSVGColors blue ];
   declareRuleSetFor: [:selector | selector anchor visited active]
   with: [:style | style color: CssSVGColors green ];
   declareRuleSetFor: [:selector | selector anchor focus hover enabled]
   with: [:style | style color: CssSVGColors green ];
   declareRuleSetFor: [:selector | (selector paragraph class: 'note') target disabled]
   with: [:style | style color: CssSVGColors green ];
   declareRuleSetFor: [:selector | selector input checked ]
   with: [:style | style color: CssSVGColors green ];
   build

evaluates to:

a:link
{
   color: blue;
}

a:visited:active
{
   color: green;
}

a:focus:hover:enabled
{
   color: green;
}

p.note:target:disabled
{
   color: green;
}

input:checked
{
   color: green;
}
3.4.1. Language Pseudo-Class:

The :lang(C) pseudo-class can be used by sending the message lang::

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | (selector lang: 'es') > selector div ]
   with: [:style | style quotes: { '"«"'. '"»"' }  ];
   build

evaluates to:

:lang(es) > div
{
    quotes: "«" "»";
}
3.4.2. Negation Pseudo-Class:

The negation pseudo-class, :not(X), is a functional notation taking a simple selector (excluding the negation pseudo-class itself) as an argument. It represents an element that is not represented by its argument. For more information take a look at the CSS spec.

This selector is supported sending the message not:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector button not: (selector havingAttribute: 'DISABLED') ]
   with: [:style | style color: CssSVGColors blue ];
   build
button:not([DISABLED])
{
   color: blue;
}
3.4.3. Structural Pseudo-Classes

These selectors allow selection based on extra information that lies in the document tree but cannot be represented by other simpler selectors nor combinators.

The :root pseudo-class represents an element that is the root of the document. To build this kind of selector just send the message root to another selector:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector root ]
   with: [:style | style color: CssSVGColors grey ];
   build

evaluates to:

:root
{
   color: grey;
}

The :nth-child(an+b) pseudo-class notation represents an element that has an+b-1 siblings before it in the document tree, for any positive integer or zero value of n, and has a parent element. For values of a and b greater than zero, this effectively divides the element's children into groups of a elements (the last group taking the remainder), and selecting the bth element of each group. The a and b values must be integers (positive, negative, or zero). The index of the first child of an element is 1.

In addition to this, :nth-child() can take a number, odd and even as arguments. The value odd is equivalent to 2n+1, whereas even is equivalent to 2n.

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector childAt: 3 n + 1 ]
   with: [:style | style color: CssSVGColors blue ];
   declareRuleSetFor: [:selector | selector childAt: 5 ]
   with: [:style | style color: CssSVGColors blue ];
   declareRuleSetFor: [:selector | selector childAt: CssConstants even]
   with: [:style | style color: CssSVGColors blue ];
   build

evaluates to:

:nth-child(3n+1)
{
   color: blue;
}

:nth-child(5)
{
   color: blue;
}

:nth-child(even)
{
   color: blue;
}

All structural pseudo-classes can be generated using the following messages:

CSS pseudo-class RenoirST selector message
root() root
nth-child() childAt:
nth-last-child() childFromLastAt:
nth-of-type() siblingOfTypeAt:
nth-last-of-type() siblingOfTypeFromLastAt:
first-child firstChild
last-child lastChild
first-of-type firstOfType
last-of-type lastOfType
only-child onlyChild
only-of-type onlyOfType
empty empty

3.5. Pseudo-Elements

Pseudo-elements create abstractions about the document tree beyond those specified by the document language. For instance, document languages do not offer mechanisms to access the first letter or first line of an element's content. Pseudo-elements allow authors to refer to this otherwise inaccessible information. Pseudo-elements may also provide authors a way to refer to content that does not exist in the source document.

The firstLine pseudo-element describes the contents of the first formatted line of an element.

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector paragraph firstLine ]
   with: [:style | style textTransform: CssConstants uppercase ];
   build

evaluates to:

p::first-line
{
   text-transform: uppercase;
}

The firstLetter pseudo-element represents the first letter of an element, if it is not preceded by any other content (such as images or inline tables) on its line.

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector paragraph firstLetter ]
   with: [:style | style fontSize: 200 percent ];
   build

evaluates to:

p::first-letter
{
    font-size: 200%;
}

The before and after pseudo-elements can be used to describe generated content before or after an element's content. The content property, in conjunction with these pseudo-elements, specifies what is inserted.

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | (selector paragraph class: 'note') before ]
   with: [:style | style content: '"Note: "' ];
   declareRuleSetFor: [:selector | (selector paragraph class: 'note') after ]
   with: [:style | style content: '"[*]"' ];
   build

evaluates to:

p.note::before
{
   content: "Note: ";
}

p.note::after
{
   content: "[*]";
}

4. Important Rules

CSS attempts to create a balance of power between author and user style sheets. By default, rules in an author's style sheet override those in a user's style sheet. However, for balance, an !important declaration takes precedence over a normal declaration. Both author and user style sheets may contain !important declarations, and user !important rules override author !important rules. This CSS feature improves accessibility of documents by giving users with special requirements control over presentation.

RenoirSt supports this feature through the beImportantDuring: message sent to the style.

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector paragraph ]
   with: [:style |
      style beImportantDuring: [:importantStyle |
         importantStyle
            textIndent: 1 em;
            fontStyle: CssConstants italic ].
      style fontSize: 18 pt ];
   build

evaluates to:

p
{
   text-indent: 1em !important;
   font-style: italic !important;
   font-size: 18pt;
}

Note that the important properties must be created by sending the messages to the inner argument importantStyle instead of the outer argument style.

5. Media Queries

A @media rule specifies the target media types of a set of statements. The @media construct allows style sheet rules that apply to various media in the same style sheet. Style rules outside of @media rules apply to all media types that the style sheet applies to. At-rules inside @media are invalid in CSS2.1.

The most basic media rule consists of specifying just a media type:

CascadingStyleSheetBuilder new
   declare: [ :cssBuilder |
      cssBuilder
         declareRuleSetFor: [ :selector | selector div ]
         with: [ :style | ] ]
   forMediaMatching: [ :queryBuilder |
      queryBuilder type: CssMediaQueryConstants print ];
   build

evaluates to:

@media print
{
   div { }
}

To use media queries in the library just send the message declare:forMediaMatching: to the builder. The first block is evaluated with an instance of a CascadingStyleSheetBuilder and the second one with a builder of media queries.

The media query builder will match any media type by default. To specify a media type just send it the message type: with the corresponding media type. The class CssMediaQueryConstants provides easy access to the following media types: braille, embossed, handheld, print, projection, screen, speech, tty and tv.

The media query builder supports a variety of messages for additional conditions (called media features). Media features are used in expressions to describe requirements of the output device.

The following media feature messages are supported:

  • Accepting a CssMeasure with length units: width:, minWidth:, maxWidth:, height:, minHeight:, maxHeight:, deviceWidth:, minDeviceWidth:, maxDeviceWidth:, deviceHeight:, minDeviceHeight:, maxDeviceHeight:;
  • orientation: accepting CssMediaQueryConstants portrait or CssMediaQueryConstants landscape;
  • Accepting fractions as aspect ratios: aspectRatio:, minAspectRatio:, maxAspectRatio:, deviceAspectRatio:, minDeviceAspectRatio:, maxDeviceAspectRatio:;
  • Accepting integers: color: (the argument describes the number of bits per color component of the output device), minColor:, maxColor:, colorIndex: (the argument describes the number of entries in the color lookup table of the output device), minColorIndex:, maxColorIndex:, monochrome: (the argument describes the number of bits per pixel in a monochrome frame buffer), minMonochrome:, maxMonochrome:, grid: (the argument must be 1 or 0);
  • Accepting a CssMeasure with resolution units: resolution:, minResolution:, maxResolution:;
  • scan: accepting CssMediaQueryConstants progressive or CssMediaQueryConstants interlace.

New units for resolutions are added using the CssMeasure abstraction. This kind of measures can be created sending the messages dpi (dots per inch), dpcm (dots per centimeter) or dppx (dots per pixel unit) to an integer or float.

Here is a final example to better understand the media features support:

CascadingStyleSheetBuilder new
   declare: [ :cssBuilder |
      cssBuilder
         declareRuleSetFor: [ :selector | selector id: #oop ]
         with: [ :style | style color: CssSVGColors red ] ]
   forMediaMatching: [ :queryBuilder |
      queryBuilder
         orientation: CssMediaQueryConstants landscape;
         resolution: 300 dpi ];
   build

evaluates to:

@media all and (orientation: landscape) and (resolution: 300dpi)
{
   #oop
   {
      color: red;
   }
}

6. Vendor-Specific Extensions

The library doesn't provide out of the box support for non standard properties. Nevertheless, the message vendorPropertyAt:put: is available to ease the creation of this kind of properties by the end user:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector div ]
   with: [:style | style vendorPropertyAt: 'crazy-margin' put: 1 px ];
   build

evaluates to:

div
{
   crazy-margin: 1px;
   -moz-crazy-margin: 1px;
   -webkit-crazy-margin: 1px;
   -o-crazy-margin: 1px;
   -ms-crazy-margin: 1px;
}

If you really want to use a vendor specific extension, It's better to create an extension method sending the vendorPropertyAt:put: message.

7. Font Face Rules

The @font-face rule allows for linking to fonts that are automatically fetched and activated when needed. This allows authors to select a font that closely matches the design goals for a given page rather than limiting the font choice to a set of fonts available on a given platform. A set of font descriptors define the location of a font resource, either locally or externally, along with the style characteristics of an individual face.

This support is implemented in the builder:

CascadingStyleSheetBuilder new
   declareFontFaceRuleWith: [ :style |
      style
         fontFamily: 'Gentium';
         src: 'http://example.com/fonts/gentium.woff' asZnUrl ];
   build

evaluates to:

@font-face
{
   font-family: Gentium;
   src: url("http://example.com/fonts/gentium.woff");
}

This kind of rule allows for multiple src definitions specifying the resources containing the data. This resources can be external (fonts fetched from a URL) or local (available in the user system). This kind of resources are supported using CssLocalFontReference and CssExternalFontReference abstractions:

CascadingStyleSheetBuilder new
   declareFontFaceRuleWith: [ :style |
      style
         fontFamily: 'MainText';
         src: (CssExternalFontReference
            locatedAt: 'gentium.eat' asZnUrl relativeToStyleSheet);
         src: (CssLocalFontReference toFontNamed: 'Gentium'),
            (CssExternalFontReference locatedAt: 'gentium.woff' asZnUrl relativeToStyleSheet withFormat: CssFontConstants woff);
         src: (CssExternalFontReference svgFontLocatedAt: 'fonts.svg' asZnUrl relativeToStyleSheet withId: 'simple') ];
   build
@font-face
{
   font-family: MainText;
   src: url("gentium.eat");
   src: local(Gentium), url("gentium.woff") format("woff");
   src: url("fonts.svg#simple") format("svg");
}

8. Interaction with other Frameworks and Libraries

8.1. Units

The Units package (available using the ConfigurationBrowser in Pharo) includes some extensions colliding with RenoirSt. RenoirST can automatically load a compatibility package if it's loaded after the Units package. To test this integration there's a specific continuous integration job, that loads Units first and then RenoirSt.

8.2. Seaside

RenoirSt includes an optional group including some useful extensions. The Seaside framework includes its own class modeling URLs: when this group is loaded the instances of WAUrl can be used in the properties requiring an URI:

CascadingStyleSheetBuilder new
   declareRuleSetFor: [:selector | selector div class: 'logo' ]
   with: [:style |
      style backgroundImage: 'images/logo.png' seasideUrl ];
   build

evaluates to:

div.logo
{
   background-image: url("/images/logo.png");
}

This optional group also loads extensions to CssDeclarationBlock so it can be used as a JSObject in plugins requiring some style parameter or as the argument in a component style: method.

To load these extensions in an image with Seaside already loaded, you need to load the group Deployment-Seaside-Extensions, or Development-Seaside-Extensions if you want the test cases (there is also a stable-pharo-40 branch if needed):

Metacello new
   baseline: 'RenoirSt';
   repository: 'github://gcotelli/RenoirSt:stable-pharo-50/source';
   load: 'Deployment-Seaside-Extensions'