Help language development. Donate to The Perl Foundation

CSS::Properties zef:dwarring last updated on 2022-09-06

README.md
[[Raku CSS Project]](https://css-raku.github.io)
 / [[CSS-Properties Module]](https://css-raku.github.io/CSS-Properties-raku)

<a href="https://travis-ci.org/css-raku/CSS-Properties-raku"><img src="https://travis-ci.org/css-raku/CSS-Properties-raku.svg?branch=master"></a>
 <a href="https://ci.appveyor.com/project/dwarring/CSS-Properties-raku/branch/master"><img src="https://ci.appveyor.com/api/projects/status/github/css-raku/CSS-Properties-raku?branch=master&passingText=Windows%20-%20OK&failingText=Windows%20-%20FAIL&pendingText=Windows%20-%20pending&svg=true"></a>

The CSS::Properties module is a set of related classes for parsing, manipulation and generation of CSS property sets, including inheritance, and defaults.

## Synopsis
```
use CSS::Units :pt;
use CSS::Properties;

my CSS::Properties() $css = "color:red !important; padding: 1pt";
say $css.important("color"); # True
$css.border-color = 'red';

$css.margin = [5pt, 2pt, 5pt, 2pt];
$css.margin = 5pt;  # set margin on all 4 sides

# set text alignment
$css.text-align = 'right';

say ~$css; # border-color:red; color:red!important; margin:5pt; padding:1pt; text-align:right;
```

Classes in this module
--------
  * [CSS::Properties](https://css-raku.github.io/CSS-Properties-raku/CSS/Properties) - property list manipulation class.
  * [CSS::Properties::Calculator](https://css-raku.github.io/CSS-Properties-raku/CSS/Properties/Calculator) - property calculator and measurement tool.
  * [CSS::Properties::Optimizer](https://css-raku.github.io/CSS-Properties-raku/CSS/Properties/Optimizer) - property AST optimizer delegate
  * [CSS::Properties::PropertyInfo](https://css-raku.github.io/CSS-Properties-raku/CSS/Properties/PropertyInfo) - property meta-data delegate
  * [CSS::Box](https://css-raku.github.io/CSS-Properties-raku/CSS/Box) - CSS Box model implementation.
  * [CSS::Font](https://css-raku.github.io/CSS-Properties-raku/CSS/Font) - property font manipulation
  * [CSS::Font::Descriptor](https://css-raku.github.io/CSS-Properties-raku/CSS/Font/Descriptor) - `@font-face` font descriptor objects
  * [CSS::Font::Pattern](https://css-raku.github.io/CSS-Properties-raku/CSS/Font/Pattern) - `@font-face` font patterns and matching
  * [CSS::PageBox](https://css-raku.github.io/CSS-Properties-raku/CSS/PageBox) - CSS Box model for paged media
  * [CSS::Units](https://css-raku.github.io/CSS-Properties-raku/CSS/Units) - units and post-fix operators (e.g. `12pt`)

See Also
--------

  * [CSS](https://css-raku.github.io/CSS-raku/) - Top level CSS manipulation class


## Conformance Levels

Processing defaults to CSS level 3 (class CSS::Module::CSS3). This can be configured via the :module option:

```
use CSS::Properties;
use CSS::Module;
use CSS::Module::CSS1;
use CSS::Module::CSS21;
use CSS::Module::CSS3;
use CSS::Module::SVG;

my $style = 'color:red; azimuth:left;';

my CSS::Module $module = CSS::Module::CSS1.module;
my CSS::Properties $css1 .= new: :$style, :$module;
## warnings: dropping unknown property: azimuth

$module = CSS::Module::CSS21.module;
my CSS::Properties $css21 .= new: :$style, :$module;
## (no warnings)

my CSS::Properties $css3 .= new: :$style; # CSS3 is the default
# -- or --
$module = CSS::Module::CSS3.module;
$css3 .= new: :$style, :$module;

$module = CSS::Module::SVG.module;
$style ~= "paint-order:markers;";  # add a SVG specific property
my CSS::Properties $css-svg .= new: :$style, :$module;
```

`CSS::Module::SVG` is an extension to `CSS::Module::CSS3` that includes
additional SVG specific properties.

### '@font-face' Properties

The [CSS::Font::Descriptor](https://css-raku.github.io/CSS-Properties-raku/CSS/Font/Descriptor) module is a class for managing `@font-face` declarations. The `css` method can be used to get the raw properties.

```
@font-face {
    font-family: myFirstFont;
    src: url(sansation_light.woff);
}
```

```
use CSS::Properties;
use CSS::Font::Descriptor;

my $style = "font-family: myFirstFont; src: url(sansation_light.woff)";
my CSS::Font::Descriptor $fd .= new: :$style;
my CSS::Properties $font-face-css = $fd.css;
```

## Default values

Most properties have a default value. If a property is reset to its default value it will be omitted from stringification:

    my $css = (require CSS::Properties).new;
    say $css.background-image; # none
    $css.background-image = 'url(camellia.png)';
    say ~$css; # "background-image: url(camellia.png);"
    $css.background-image = $css.info("background-image").default;
    say $css.background-image; # none
    say ~$css; # ""

## Deleting properties

Properties can be deleted via the `delete` method, or by assigning the property to `Nil`:

    my CSS::Properties $css .= new: :style("background-position:top left; border-top-color:red; border-bottom-color: green; color: blue");
    # delete background position
    $css.background-position = Nil;
    # delete all border colors
    $css.delete: "border-color";

## Inheritance

A child class can inherit from one or more parent classes. This follows CSS standards:

- not all properties are inherited by default; for example `color` is, but `margin` is not.

- the `inherit` keyword can be used in the child property to ensure inheritance.

- `initial` will reset the child property to the default value

To inherit a css object or style string:

- pass it as a `:inherit` option, when constructing the object, or

- use the `inherit` method

```
use CSS::Properties;

my $parent-style = "margin-top:5pt; margin-left: 15pt; color:rgb(0,0,255) !important";
my $style = "margin-top:25pt; margin-right: initial; margin-left: inherit; color:purple";
my CSS::Properties $css .= new: :$style, :inherit($parent-style);

say $css.color;                     # #7F007Frgb (purple)
say $css.handling("margin-left");   # inherit
say $css.margin-left;               # 15pt
```

## Optimization and Serialization

    method write(
        Bool :$optimize = True,        # consolidate properties
        Bool :$terse = True,           # single line output
        Bool :$color-names = True,     # use color names, where possible
        Bool :$keep-defaults = False,  # don't omit default values
        |c
    ) is also<Str gist> {


The `.write` (alias `.Str`, or .`gist`) method can be used to produce CSS. Properties are optimized and normalized:

- properties with default values are omitted

- multiple component properties are generally consolidated to container properties (e.g. `font-family: Courier` and `font-size: 12pt`
  are consolidated to `font: 12pt Courier`).

- rgb masks are translated to color-names, where possible

```
use CSS::Properties;
my CSS::Properties $css .= new( :style("background-repeat:repeat; border-style: groove; border-width: 2pt 2pt; color: rgb(255,0,0);") );
# - 'border-width' and 'border-style' are consolidated to the 'border' container property
# - rgb(255,0,0) is mapped to 'red'
say $css.write;  # "border:2pt groove; color: red;"
```

Notice that:

- `background-repeat` was omitted because it has the default value

- `border-style` and `border-width` have been consolidated to the `border` container property. This is possible
because all four borders have common values

- `color` has been translated from a color mask to a color

`$.write` Options include:

- `:!optimize` - turn off optimization. Don't, combine component properties into container properties (`border-style`, `border-width`, ... => `border`), or combine edges (`margin-top`, `margin-left`, ... => `margin`).

- `:!terse` - enable multi-line output

- `:!color-names` - don't translate RGB values to color-names

See also [CSS::Properties::Optimizer](https://css-raku.github.io/CSS-Properties-raku/CSS/Properties/Optimizer).

## Property Meta-data

The `info` method gives property specific meta-data, on all (component or container properties). It returns an object of type CSS::Properties::PropertyInfo:

```
use CSS::Properties;
use CSS::Properties::PropertyInfo;
my CSS::Properties $css .= new;
my CSS::Properties::PropertyInfo $margin-info = $css.info("margin");
say $margin-info.synopsis; # <margin-width>{1,4}
say $margin-info.edges;    # [margin-top margin-right margin-bottom margin-left]
say $margin-info.inherit;  # True (property is inherited)
```

## Data Introspection

The `properties` method, gives a list of current properties. Only component properties
are returned. E.g. `font-family` may be returned; but `font` never is.

```
use CSS::Properties;

my $style = "margin-top: 10%; margin-right: 5mm; margin-bottom: auto";
my CSS::Properties $css .= new: :$style;

for $css.properties -> $prop {
    my $val = $css."$prop"();
    say "$prop: $val {$val.type}";
}

```
Gives:
```
margin-top: 10 percent
margin-bottom: auto keyw
margin-right: 5 mm
```

## Lengths and Units

CSS::Units is a convenience module that provides some simple post-fix length unit definitions.

The `:ops` export overloads  `+` and `-` to perform unit
calculations. `+css` and `-css` are also available as
more explicit infix operators:

All infix operators convert to the left-hand operand's units.

```
use CSS::Units :ops, :pt, :px, :in, :mm;
my CSS::Properties $css .= new: :margin[5pt, 10px, .1in, 2mm];

# display margins in millimeters
say "%.2f mm".sprintf(.scale("mm")) for $css.margin.list;
```

The `measure` method can be used to perform contextual measurement
of lengths, which are converted to the default units.

The current font-size is used for `em`, `ex` and percentage calculations.

There are also `viewport-width` and `viewport-height` attributes
that need to be set to enable `vw` and `vh` units.

```
use CSS::Units :ops, :pt, :px, :in, :mm, :em, :vw, :vh, :percent;
use CSS::Properties;
my CSS::Properties $css .= new: :viewport-width(200);

say $css.units;         # pt
say $css.measure: 10px; # 7.5pt
say $css.measure: 1in;  # 72pt

say $css.font-size;     # 12pt
say $css.measure: 2em;  # 24pt
say $css.measure: 50%;  # 6pt
say $css.measure: .1vw; # 20pt
```

The `measure` method can also be used on specific properties. In the case
of box measurements (borders, margins and padding) a `reference-width` also needs to be set for percentage calculations.

```
use CSS::Units :px, :percent;
use CSS::Properties;
my CSS::Properties $css .= new: :margin[10%, 10px], :reference-width(120);

say $css.measure: :margin-top;       # 12pt
say $css.measure: :margin-left;      # 7.5pt
say $css.measure: :margin-left(20%); # 24pt
say $css.measure: :font-size;        # 12pt
say $css.measure: :font-size(50%);   # 6pt
```

The `units` attribute defaults to `pt` can be changed to any absolute length units:
```
use CSS::Units :px, :mm;
use CSS::Properties;
my CSS::Properties $css .= new: :margin[10mm, 10px], :units<mm>;
say $css.units;                      # mm
say $css.measure: :margin-top;       # 10mm
say $css.measure: :margin-left;      # 2.646mm
```

## Appendix : CSS3 Properties

Name | Default | Inherit | Type | Synopsis
--- | --- | --- | --- | ---
azimuth | center | Yes |  | \<angle\> \| [[ left-side \| far-left \| left \| center-left \| center \| center-right \| right \| far-right \| right-side ] \|\| behind ] \| leftwards \| rightwards
background |  |  | hash | ['background-color' \|\| 'background-image' \|\| 'background-repeat' \|\| 'background-attachment' \|\| 'background-position']
background-attachment | scroll |  |  | scroll \| fixed
background-color | transparent |  |  | \<color\> \| transparent
background-image | none |  |  | \<uri\> \| none
background-position | 0% 0% |  |  | [ [ \<percentage\> \| \<length\> \| left \| center \| right ] [ \<percentage\> \| \<length\> \| top \| center \| bottom ]? ] \| [ [ left \| center \| right ] \|\| [ top \| center \| bottom ] ]
background-repeat | repeat |  |  | repeat \| repeat-x \| repeat-y \| no-repeat
border |  |  | hash,box | [ 'border-width' \|\| 'border-style' \|\| 'border-color' ]
border-bottom |  |  | hash | [ 'border-bottom-width' \|\| 'border-bottom-style' \|\| 'border-bottom-color' ]
border-bottom-color | the value of the 'color' property |  |  | \<color\> \| transparent
border-bottom-style | none |  |  | \<border-style\>
border-bottom-width | medium |  |  | \<border-width\>
border-collapse | separate | Yes |  | collapse \| separate
border-color |  |  | box | [ \<color\> \| transparent ]{1,4}
border-left |  |  | hash | [ 'border-left-width' \|\| 'border-left-style' \|\| 'border-left-color' ]
border-left-color | the value of the 'color' property |  |  | \<color\> \| transparent
border-left-style | none |  |  | \<border-style\>
border-left-width | medium |  |  | \<border-width\>
border-right |  |  | hash | [ 'border-right-width' \|\| 'border-right-style' \|\| 'border-right-color' ]
border-right-color | the value of the 'color' property |  |  | \<color\> \| transparent
border-right-style | none |  |  | \<border-style\>
border-right-width | medium |  |  | \<border-width\>
border-spacing | 0 | Yes |  | \<length\> \<length\>?
border-style |  |  | box | \<border-style\>{1,4}
border-top |  |  | hash | [ 'border-top-width' \|\| 'border-top-style' \|\| 'border-top-color' ]
border-top-color | the value of the 'color' property |  |  | \<color\> \| transparent
border-top-style | none |  |  | \<border-style\>
border-top-width | medium |  |  | \<border-width\>
border-width |  |  | box | \<border-width\>{1,4}
bottom | auto |  |  | \<length\> \| \<percentage\> \| auto
caption-side | top | Yes |  | top \| bottom
clear | none |  |  | none \| left \| right \| both
clip | auto |  |  | \<shape\> \| auto
color | depends on user agent | Yes |  | \<color\>
content | normal |  |  | normal \| none \| [ \<string\> \| \<uri\> \| \<counter\> \| \<counters\> \| attr(\<identifier\>) \| open-quote \| close-quote \| no-open-quote \| no-close-quote ]+
counter-increment | none |  |  | none \| [ \<identifier\> \<integer\>? ]+
counter-reset | none |  |  | none \| [ \<identifier\> \<integer\>? ]+
cue |  |  | hash | [ 'cue-before' \|\| 'cue-after' ]
cue-after | none |  |  | \<uri\> \| none
cue-before | none |  |  | \<uri\> \| none
cursor | auto | Yes |  | [ [\<uri\> ,]* [ auto \| crosshair \| default \| pointer \| move \| e-resize \| ne-resize \| nw-resize \| n-resize \| se-resize \| sw-resize \| s-resize \| w-resize \| text \| wait \| help \| progress ] ]
direction | ltr | Yes |  | ltr \| rtl
display | inline |  |  | inline \| block \| list-item \| inline-block \| table \| inline-table \| table-row-group \| table-header-group \| table-footer-group \| table-row \| table-column-group \| table-column \| table-cell \| table-caption \| none
elevation | level | Yes |  | \<angle\> \| below \| level \| above \| higher \| lower
empty-cells | show | Yes |  | show \| hide
float | none |  |  | left \| right \| none
font |  | Yes | hash | [ [ \<‘font-style’\> \|\| \<font-variant-css21\> \|\| \<‘font-weight’\> \|\| \<‘font-stretch’\> ]? \<‘font-size’\> [ / \<‘line-height’\> ]? \<‘font-family’\> ] \| caption \| icon \| menu \| message-box \| small-caption \| status-bar
font-family | depends on user agent | Yes |  | [ \<generic-family\> \| \<family-name\> ]\#
font-feature-settings | normal | Yes |  | normal \| \<feature-tag-value\>\#
font-kerning | auto | Yes |  | auto \| normal \| none
font-language-override | normal | Yes |  | normal \| \<string\>
font-size | medium | Yes |  | \<absolute-size\> \| \<relative-size\> \| \<length\> \| \<percentage\>
font-size-adjust | none | Yes |  | none \| auto \| \<number\>
font-stretch | normal | Yes |  | normal \| ultra-condensed \| extra-condensed \| condensed \| semi-condensed \| semi-expanded \| expanded \| extra-expanded \| ultra-expanded
font-style | normal | Yes |  | normal \| italic \| oblique
font-synthesis | weight style | Yes |  | none \| [ weight \|\| style ]
font-variant | normal | Yes |  | normal \| none \| [ \<common-lig-values\> \|\| \<discretionary-lig-values\> \|\| \<historical-lig-values\> \|\| \<contextual-alt-values\> \|\| stylistic(\<feature-value-name\>) \|\| historical-forms \|\| styleset(\<feature-value-name\> \#) \|\| character-variant(\<feature-value-name\> \#) \|\| swash(\<feature-value-name\>) \|\| ornaments(\<feature-value-name\>) \|\| annotation(\<feature-value-name\>) \|\| [ small-caps \| all-small-caps \| petite-caps \| all-petite-caps \| unicase \| titling-caps ] \|\| \<numeric-figure-values\> \|\| \<numeric-spacing-values\> \|\| \<numeric-fraction-values\> \|\| ordinal \|\| slashed-zero \|\| \<east-asian-variant-values\> \|\| \<east-asian-width-values\> \|\| ruby ]
font-variant-alternates | normal | Yes |  | normal \| [ stylistic(\<feature-value-name\>) \|\| historical-forms \|\| styleset(\<feature-value-name\>\#) \|\| character-variant(\<feature-value-name\>\#) \|\| swash(\<feature-value-name\>) \|\| ornaments(\<feature-value-name\>) \|\| annotation(\<feature-value-name\>) ]
font-variant-caps | normal | Yes |  | normal \| small-caps \| all-small-caps \| petite-caps \| all-petite-caps \| unicase \| titling-caps
font-variant-east-asian | normal | Yes |  | normal \| [ \<east-asian-variant-values\> \|\| \<east-asian-width-values\> \|\| ruby ]
font-variant-ligatures | normal | Yes |  | normal \| none \| [ \<common-lig-values\> \|\| \<discretionary-lig-values\> \|\| \<historical-lig-values\> \|\| \<contextual-alt-values\> ]
font-variant-numeric | normal | Yes |  | normal \| [ \<numeric-figure-values\> \|\| \<numeric-spacing-values\> \|\| \<numeric-fraction-values\> \|\| ordinal \|\| slashed-zero ]
font-variant-position | normal | Yes |  | normal \| sub \| super
font-weight | normal | Yes |  | normal \| bold \| bolder \| lighter \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900
height | auto |  |  | \<length\> \| \<percentage\> \| auto
left | auto |  |  | \<length\> \| \<percentage\> \| auto
letter-spacing | normal | Yes |  | normal \| \<length\>
line-height | normal | Yes |  | normal \| \<number\> \| \<length\> \| \<percentage\>
list-style |  | Yes | hash | [ 'list-style-type' \|\| 'list-style-position' \|\| 'list-style-image' ]
list-style-image | none | Yes |  | \<uri\> \| none
list-style-position | outside | Yes |  | inside \| outside
list-style-type | disc | Yes |  | disc \| circle \| square \| decimal \| decimal-leading-zero \| lower-roman \| upper-roman \| lower-greek \| lower-latin \| upper-latin \| armenian \| georgian \| lower-alpha \| upper-alpha \| none
margin |  |  | box | \<margin-width\>{1,4}
margin-bottom | 0 |  |  | \<margin-width\>
margin-left | 0 |  |  | \<margin-width\>
margin-right | 0 |  |  | \<margin-width\>
margin-top | 0 |  |  | \<margin-width\>
max-height | none |  |  | \<length\> \| \<percentage\> \| none
max-width | none |  |  | \<length\> \| \<percentage\> \| none
min-height | 0 |  |  | \<length\> \| \<percentage\>
min-width | 0 |  |  | \<length\> \| \<percentage\>
opacity | 1.0 |  |  | \<number\>
orphans | 2 | Yes |  | \<integer\>
outline |  |  | hash | [ 'outline-color' \|\| 'outline-style' \|\| 'outline-width' ]
outline-color | invert |  |  | \<color\> \| invert
outline-style | none |  |  | [ none \| hidden \| dotted \| dashed \| solid \| double \| groove \| ridge \| inset \| outset ]
outline-width | medium |  |  | thin \| medium \| thick \| \<length\>
overflow | visible |  |  | visible \| hidden \| scroll \| auto
padding |  |  | box | \<padding-width\>{1,4}
padding-bottom | 0 |  |  | \<padding-width\>
padding-left | 0 |  |  | \<padding-width\>
padding-right | 0 |  |  | \<padding-width\>
padding-top | 0 |  |  | \<padding-width\>
page-break-after | auto |  |  | auto \| always \| avoid \| left \| right
page-break-before | auto |  |  | auto \| always \| avoid \| left \| right
page-break-inside | auto |  |  | avoid \| auto
pause |  |  |  | [ [\<time\> \| \<percentage\>]{1,2} ]
pause-after | 0 |  |  | \<time\> \| \<percentage\>
pause-before | 0 |  |  | \<time\> \| \<percentage\>
pitch | medium | Yes |  | \<frequency\> \| x-low \| low \| medium \| high \| x-high
pitch-range | 50 | Yes |  | \<number\>
play-during | auto |  |  | \<uri\> [ mix \|\| repeat ]? \| auto \| none
position | static |  |  | static \| relative \| absolute \| fixed
quotes | depends on user agent | Yes |  | [\<string\> \<string\>]+ \| none
richness | 50 | Yes |  | \<number\>
right | auto |  |  | \<length\> \| \<percentage\> \| auto
size | auto |  |  | \<length\>{1,2} \| auto \| [ \<page-size\> \|\| [ portrait \| landscape] ]
speak | normal | Yes |  | normal \| none \| spell-out
speak-header | once | Yes |  | once \| always
speak-numeral | continuous | Yes |  | digits \| continuous
speak-punctuation | none | Yes |  | code \| none
speech-rate | medium | Yes |  | \<number\> \| x-slow \| slow \| medium \| fast \| x-fast \| faster \| slower
stress | 50 | Yes |  | \<number\>
table-layout | auto |  |  | auto \| fixed
text-align | a nameless value that acts as 'left' if 'direction' is 'ltr', 'right' if 'direction' is 'rtl' | Yes |  | left \| right \| center \| justify
text-decoration | none |  |  | none \| [ underline \|\| overline \|\| line-through \|\| blink ]
text-indent | 0 | Yes |  | \<length\> \| \<percentage\>
text-transform | none | Yes |  | capitalize \| uppercase \| lowercase \| none
top | auto |  |  | \<length\> \| \<percentage\> \| auto
unicode-bidi | normal |  |  | normal \| embed \| bidi-override
vertical-align | baseline |  |  | baseline \| sub \| super \| top \| text-top \| middle \| bottom \| text-bottom \| \<percentage\> \| \<length\>
visibility | visible | Yes |  | visible \| hidden \| collapse
voice-family | depends on user agent | Yes |  | [\<generic-voice\> \| \<specific-voice\> ]\#
volume | medium | Yes |  | \<number\> \| \<percentage\> \| silent \| x-soft \| soft \| medium \| loud \| x-loud
white-space | normal | Yes |  | normal \| pre \| nowrap \| pre-wrap \| pre-line
widows | 2 | Yes |  | \<integer\>
width | auto |  |  | \<length\> \| \<percentage\> \| auto
word-spacing | normal | Yes |  | normal \| \<length\>
z-index | auto |  |  | auto \| \<integer\>


The above markdown table was produced with the following code snippet

```
use v6;
say <Name Default Inherit Type Synopsis>.join(' | ');
say ('---' xx 5).join(' | ');

my $css = (require CSS::Properties).new;

for $css.properties(:all).sort -> $name {
    with $css.info($name) {
        my @type;
        @type.push: 'hash' if .children;
        @type.push: 'box' if .box;
        my $synopsis-escaped = .synopsis.subst(/<?before <[ < | > # ]>>/, '\\', :g); 

        say ($name,
             .default // '',
             .inherit ?? 'Yes' !! '',
             @type.join(','),
             $synopsis-escaped,
            ).join(' | ');
    }
}

```