Rand Stats

CSS::Properties

zef:dwarring

[Raku CSS Project] / [CSS-Properties Module]

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

See Also

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 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:

To inherit a css object or style string:

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:

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:

$.write Options include:

See also 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

NameDefaultInheritTypeSynopsis
azimuthcenterYes<angle> | [[ left-side | far-left | left | center-left | center | center-right | right | far-right | right-side ] || behind ] | leftwards | rightwards
backgroundhash['background-color' || 'background-image' || 'background-repeat' || 'background-attachment' || 'background-position']
background-attachmentscrollscroll | fixed
background-colortransparent<color> | transparent
background-imagenone<uri> | none
background-position0% 0%[ [ <percentage> | <length> | left | center | right ] [ <percentage> | <length> | top | center | bottom ]? ] | [ [ left | center | right ] || [ top | center | bottom ] ]
background-repeatrepeatrepeat | repeat-x | repeat-y | no-repeat
borderhash,box[ 'border-width' || 'border-style' || 'border-color' ]
border-bottomhash[ 'border-bottom-width' || 'border-bottom-style' || 'border-bottom-color' ]
border-bottom-colorthe value of the 'color' property<color> | transparent
border-bottom-stylenone<border-style>
border-bottom-widthmedium<border-width>
border-collapseseparateYescollapse | separate
border-colorbox[ <color> | transparent ]{1,4}
border-lefthash[ 'border-left-width' || 'border-left-style' || 'border-left-color' ]
border-left-colorthe value of the 'color' property<color> | transparent
border-left-stylenone<border-style>
border-left-widthmedium<border-width>
border-righthash[ 'border-right-width' || 'border-right-style' || 'border-right-color' ]
border-right-colorthe value of the 'color' property<color> | transparent
border-right-stylenone<border-style>
border-right-widthmedium<border-width>
border-spacing0Yes<length> <length>?
border-stylebox<border-style>{1,4}
border-tophash[ 'border-top-width' || 'border-top-style' || 'border-top-color' ]
border-top-colorthe value of the 'color' property<color> | transparent
border-top-stylenone<border-style>
border-top-widthmedium<border-width>
border-widthbox<border-width>{1,4}
bottomauto<length> | <percentage> | auto
caption-sidetopYestop | bottom
clearnonenone | left | right | both
clipauto<shape> | auto
colordepends on user agentYes<color>
contentnormalnormal | none | [ <string> | <uri> | <counter> | <counters> | attr(<identifier>) | open-quote | close-quote | no-open-quote | no-close-quote ]+
counter-incrementnonenone | [ <identifier> <integer>? ]+
counter-resetnonenone | [ <identifier> <integer>? ]+
cuehash[ 'cue-before' || 'cue-after' ]
cue-afternone<uri> | none
cue-beforenone<uri> | none
cursorautoYes[ [<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 ] ]
directionltrYesltr | rtl
displayinlineinline | 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
elevationlevelYes<angle> | below | level | above | higher | lower
empty-cellsshowYesshow | hide
floatnoneleft | right | none
fontYeshash[ [ <‘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-familydepends on user agentYes[ <generic-family> | <family-name> ]#
font-feature-settingsnormalYesnormal | <feature-tag-value>#
font-kerningautoYesauto | normal | none
font-language-overridenormalYesnormal | <string>
font-sizemediumYes<absolute-size> | <relative-size> | <length> | <percentage>
font-size-adjustnoneYesnone | auto | <number>
font-stretchnormalYesnormal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded
font-stylenormalYesnormal | italic | oblique
font-synthesisweight styleYesnone | [ weight || style ]
font-variantnormalYesnormal | 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-alternatesnormalYesnormal | [ 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-capsnormalYesnormal | small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps
font-variant-east-asiannormalYesnormal | [ <east-asian-variant-values> || <east-asian-width-values> || ruby ]
font-variant-ligaturesnormalYesnormal | none | [ <common-lig-values> || <discretionary-lig-values> || <historical-lig-values> || <contextual-alt-values> ]
font-variant-numericnormalYesnormal | [ <numeric-figure-values> || <numeric-spacing-values> || <numeric-fraction-values> || ordinal || slashed-zero ]
font-variant-positionnormalYesnormal | sub | super
font-weightnormalYesnormal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
heightauto<length> | <percentage> | auto
leftauto<length> | <percentage> | auto
letter-spacingnormalYesnormal | <length>
line-heightnormalYesnormal | <number> | <length> | <percentage>
list-styleYeshash[ 'list-style-type' || 'list-style-position' || 'list-style-image' ]
list-style-imagenoneYes<uri> | none
list-style-positionoutsideYesinside | outside
list-style-typediscYesdisc | circle | square | decimal | decimal-leading-zero | lower-roman | upper-roman | lower-greek | lower-latin | upper-latin | armenian | georgian | lower-alpha | upper-alpha | none
marginbox<margin-width>{1,4}
margin-bottom0<margin-width>
margin-left0<margin-width>
margin-right0<margin-width>
margin-top0<margin-width>
max-heightnone<length> | <percentage> | none
max-widthnone<length> | <percentage> | none
min-height0<length> | <percentage>
min-width0<length> | <percentage>
opacity1.0<number>
orphans2Yes<integer>
outlinehash[ 'outline-color' || 'outline-style' || 'outline-width' ]
outline-colorinvert<color> | invert
outline-stylenone[ none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset ]
outline-widthmediumthin | medium | thick | <length>
overflowvisiblevisible | hidden | scroll | auto
paddingbox<padding-width>{1,4}
padding-bottom0<padding-width>
padding-left0<padding-width>
padding-right0<padding-width>
padding-top0<padding-width>
page-break-afterautoauto | always | avoid | left | right
page-break-beforeautoauto | always | avoid | left | right
page-break-insideautoavoid | auto
pause[ [<time> | <percentage>]{1,2} ]
pause-after0<time> | <percentage>
pause-before0<time> | <percentage>
pitchmediumYes<frequency> | x-low | low | medium | high | x-high
pitch-range50Yes<number>
play-duringauto<uri> [ mix || repeat ]? | auto | none
positionstaticstatic | relative | absolute | fixed
quotesdepends on user agentYes[<string> <string>]+ | none
richness50Yes<number>
rightauto<length> | <percentage> | auto
sizeauto<length>{1,2} | auto | [ <page-size> || [ portrait | landscape] ]
speaknormalYesnormal | none | spell-out
speak-headeronceYesonce | always
speak-numeralcontinuousYesdigits | continuous
speak-punctuationnoneYescode | none
speech-ratemediumYes<number> | x-slow | slow | medium | fast | x-fast | faster | slower
stress50Yes<number>
table-layoutautoauto | fixed
text-aligna nameless value that acts as 'left' if 'direction' is 'ltr', 'right' if 'direction' is 'rtl'Yesleft | right | center | justify
text-decorationnonenone | [ underline || overline || line-through || blink ]
text-indent0Yes<length> | <percentage>
text-transformnoneYescapitalize | uppercase | lowercase | none
topauto<length> | <percentage> | auto
unicode-bidinormalnormal | embed | bidi-override
vertical-alignbaselinebaseline | sub | super | top | text-top | middle | bottom | text-bottom | <percentage> | <length>
visibilityvisibleYesvisible | hidden | collapse
voice-familydepends on user agentYes[<generic-voice> | <specific-voice> ]#
volumemediumYes<number> | <percentage> | silent | x-soft | soft | medium | loud | x-loud
white-spacenormalYesnormal | pre | nowrap | pre-wrap | pre-line
widows2Yes<integer>
widthauto<length> | <percentage> | auto
word-spacingnormalYesnormal | <length>
z-indexautoauto | <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(' | ');
    }
}