Skip to content

Commit 2029acc

Browse files
Onno van der Zeecscott
authored andcommitted
Validate font-family and the font compound property.
This uses the Matcher framework to handle a fairly-complex shorthand property. This patch is based on a patch set by Onno van der Zee, originally submitted as PR #164. Using the Matcher framework allowed the original implementation to be greatly simplified, but many of the test cases are from the original patch set.
1 parent b2e7858 commit 2029acc

File tree

3 files changed

+270
-9
lines changed

3 files changed

+270
-9
lines changed

src/css/Properties.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,18 +313,22 @@ var Properties = {
313313
"-ms-flex-wrap" : "nowrap | wrap | wrap-reverse",
314314
"float" : "left | right | none | inherit",
315315
"float-offset" : 1,
316-
"font" : 1,
317-
"font-family" : 1,
316+
"font" : "<font-shorthand> | caption | icon | menu | message-box | small-caption | status-bar | inherit",
317+
"font-family" : "<font-family> | inherit",
318318
"font-feature-settings" : "<feature-tag-value> | normal | inherit",
319319
"font-kerning" : "auto | normal | none | initial | inherit | unset",
320-
"font-size" : "<absolute-size> | <relative-size> | <length> | <percentage> | inherit",
320+
"font-size" : "<font-size> | inherit",
321321
"font-size-adjust" : "<number> | none | inherit",
322-
"font-stretch" : "normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit",
323-
"font-style" : "normal | italic | oblique | inherit",
324-
"font-variant" : "normal | small-caps | inherit",
325-
"font-variant-caps" : "normal | small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps",
322+
"font-stretch" : "<font-stretch> | inherit",
323+
"font-style" : "<font-style> | inherit",
324+
"font-variant" : "<font-variant> | normal | none | inherit",
325+
"font-variant-alternates" : "<font-variant-alternates> | normal | inherit",
326+
"font-variant-caps" : "<font-variant-caps> | normal | inherit",
327+
"font-variant-east-asian" : "<font-variant-east-asian> | normal | inherit",
328+
"font-variant-ligatures" : "<font-variant-ligatures> | normal | none | inherit",
329+
"font-variant-numeric" : "<font-variant-numeric> | normal | inherit",
326330
"font-variant-position" : "normal | sub | super | inherit | initial | unset",
327-
"font-weight" : "normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit",
331+
"font-weight" : "<font-weight> | inherit",
328332

329333
//G
330334
"glyph-orientation-horizontal" : "<glyph-angle> | inherit",
@@ -382,7 +386,7 @@ var Properties = {
382386
//L
383387
"left" : "<margin-width> | inherit",
384388
"letter-spacing" : "<length> | normal | inherit",
385-
"line-height" : "<number> | <length> | <percentage> | normal | inherit",
389+
"line-height" : "<line-height> | inherit",
386390
"line-break" : "auto | loose | normal | strict",
387391
"line-stacking" : 1,
388392
"line-stacking-ruby" : "exclude-ruby | include-ruby",

src/css/ValidationTypes.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ ValidationTypes = {
387387
!/^(unset|initial|inherit|will-change|auto|scroll-position|contents)$/i.test(part);
388388
},
389389

390+
"<string>": function(part){
391+
return part.type === "string";
392+
},
393+
390394
"<length>": function(part){
391395
if (part.type === "function" && /^(?:\-(?:ms|moz|o|webkit)\-)?calc/i.test(part)){
392396
return true;
@@ -507,6 +511,36 @@ ValidationTypes = {
507511
"<filter-function>": function(part){
508512
// custom() isn't actually in the spec
509513
return ValidationTypes.isLiteral(part, "blur() | brightness() | contrast() | custom() | drop-shadow() | grayscale() | hue-rotate() | invert() | opacity() | saturate() | sepia()");
514+
},
515+
516+
"<generic-family>": function(part){
517+
return ValidationTypes.isLiteral(part, "serif | sans-serif | cursive | fantasy | monospace");
518+
},
519+
520+
"<ident-not-generic-family>": function(part){
521+
return this["<ident>"](part) && !this["<generic-family>"](part);
522+
},
523+
524+
"<font-size>": function(part){
525+
var result = this["<absolute-size>"](part) || this["<relative-size>"](part) || this["<length>"](part) || this["<percentage>"](part);
526+
return result;
527+
},
528+
529+
"<font-stretch>": function(part){
530+
return ValidationTypes.isLiteral(part, "normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded");
531+
},
532+
533+
"<font-style>": function(part){
534+
return ValidationTypes.isLiteral(part, "normal | italic | oblique");
535+
},
536+
537+
"<font-weight>": function(part){
538+
return ValidationTypes.isLiteral(part, "normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900");
539+
},
540+
541+
"<line-height>": function(part){
542+
var result = this["<number>"](part) || this["<length>"](part) || this["<percentage>"](part) || ValidationTypes.isLiteral(part, "normal");
543+
return result;
510544
}
511545
},
512546

@@ -584,6 +618,73 @@ ValidationTypes = {
584618
// * inherit
585619
Matcher.alt("none", "inherit", Matcher.cast("<flex-grow>").then(Matcher.cast("<flex-shrink>").question()).oror("<flex-basis>")),
586620

621+
"<font-family>":
622+
// [ <family-name> | <generic-family> ]#
623+
Matcher.cast("<generic-family> | <family-name>").hash(),
624+
625+
"<family-name>":
626+
// <string> | <IDENT>+
627+
Matcher.alt("<string>",
628+
Matcher.seq("<ident-not-generic-family>",
629+
Matcher.cast("<ident>").star())),
630+
631+
"<font-variant-alternates>":
632+
Matcher.oror(// stylistic(<feature-value-name>)
633+
"stylistic()",
634+
"historical-forms",
635+
// styleset(<feature-value-name> #)
636+
"styleset()",
637+
// character-variant(<feature-value-name> #)
638+
"character-variant()",
639+
// swash(<feature-value-name>)
640+
"swash()",
641+
// ornaments(<feature-value-name>)
642+
"ornaments()",
643+
// annotation(<feature-value-name>)
644+
"annotation()"),
645+
646+
"<font-variant-caps>":
647+
Matcher.cast("small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps"),
648+
649+
"<font-variant-css21>":
650+
Matcher.cast("normal | small-caps"),
651+
652+
"<font-variant-ligatures>":
653+
Matcher.oror(// <common-lig-values>
654+
"common-ligatures | no-common-ligatures",
655+
// <discretionary-lig-values>
656+
"discretionary-ligatures | no-discretionary-ligatures",
657+
// <historical-lig-values>
658+
"historical-ligatures | no-historical-ligatures",
659+
// <contextual-alt-values>
660+
"contextual | no-contextual"),
661+
662+
"<font-variant-numeric>":
663+
Matcher.oror(// <numeric-figure-values>
664+
"lining-nums | oldstyle-nums",
665+
// <numeric-spacing-values>
666+
"proportional-nums | tabular-nums",
667+
// <numeric-fraction-values>
668+
"diagonal-fractions | stacked-fractions",
669+
"ordinal",
670+
"slashed-zero"),
671+
672+
"<font-variant-east-asian>":
673+
Matcher.oror(// <east-asian-variant-values>
674+
"jis78 | jis83 | jis90 | jis04 | simplified | traditional",
675+
// <east-asian-width-values>
676+
"full-width | proportional-width",
677+
"ruby"),
678+
679+
"<font-shorthand>":
680+
Matcher.seq(Matcher.oror("<font-style>",
681+
"<font-variant-css21>",
682+
"<font-weight>",
683+
"<font-stretch>").question(),
684+
"<font-size>",
685+
Matcher.seq("/", "<line-height>").question(),
686+
"<font-family>"),
687+
587688
"<text-decoration>":
588689
// none | [ underline || overline || line-through || blink ] | inherit
589690
Matcher.oror("underline", "overline", "line-through", "blink"),
@@ -593,3 +694,12 @@ ValidationTypes = {
593694
Matcher.alt("auto", Matcher.cast("<animateable-feature>").hash())
594695
}
595696
};
697+
698+
// Because this is defined relative to other complex validation types,
699+
// we need to define it *after* the rest of the types are initialized.
700+
ValidationTypes.complex["<font-variant>"] =
701+
Matcher.oror({ expand: "<font-variant-ligatures>" },
702+
{ expand: "<font-variant-alternates>" },
703+
"<font-variant-caps>",
704+
{ expand: "<font-variant-numeric>" },
705+
{ expand: "<font-variant-east-asian>" });

tests/css/Validation.js

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,154 @@
735735
}
736736
}));
737737

738+
suite.add(new ValidationTestCase({
739+
property: "font",
740+
741+
valid: [
742+
"italic small-caps 300 1.3em/10% Genova, 'Comic Sans', sans-serif",
743+
"1.3em Shorties, sans-serif",
744+
"12px monospace",
745+
"caption",
746+
"status-bar",
747+
"12pt/14pt sans-serif",
748+
"80% sans-serif",
749+
"condensed 80% sans-serif",
750+
"x-large/110% \"new century schoolbook\", serif",
751+
"bold italic large Palatino, serif",
752+
"normal small-caps 120%/120% fantasy",
753+
"normal normal normal normal 12pt cursive",
754+
"normal bold small-caps italic 18px 'font'",
755+
"condensed oblique 12pt \"Helvetica Neue\", serif",
756+
"inherit",
757+
],
758+
759+
invalid: {
760+
"italic oblique bold 1.3em/10% Genova, 'Comic Sans', sans-serif" : "Expected (<font-shorthand> | caption | icon | menu | message-box | small-caption | status-bar | inherit) but found 'italic oblique bold 1.3em / 10% Genova , 'Comic Sans' , sans-serif'.",
761+
"0.9em Nirwana, 'Comic Sans', sans-serif bold" : "Expected end of value but found 'bold'.",
762+
"'Helvetica Neue', sans-serif 1.2em" : "Expected (<font-shorthand> | caption | icon | menu | message-box | small-caption | status-bar | inherit) but found ''Helvetica Neue' , sans-serif 1.2em'.",
763+
"1.3em" : "Expected (<font-shorthand> | caption | icon | menu | message-box | small-caption | status-bar | inherit) but found '1.3em'.",
764+
"cursive;" : "Expected (<font-shorthand> | caption | icon | menu | message-box | small-caption | status-bar | inherit) but found 'cursive'.",
765+
"'Dormant', sans-serif;" : "Expected (<font-shorthand> | caption | icon | menu | message-box | small-caption | status-bar | inherit) but found ''Dormant' , sans-serif'."
766+
}
767+
}));
768+
769+
suite.add(new ValidationTestCase({
770+
property: "font-family",
771+
772+
valid: [
773+
"Futura, sans-serif",
774+
"-Futura, sans-serif",
775+
'"New Century Schoolbook", serif',
776+
"'21st Century', fantasy",
777+
"serif",
778+
"sans-serif",
779+
"cursive",
780+
"fantasy",
781+
"monospace",
782+
// solve problem by quoting
783+
"'Red/Black', sans-serif",
784+
'"Lucida\\", Grande", sans-serif',
785+
"'Ahem!}', sans-serif",
786+
'"test@foo", sans-serif',
787+
"'#POUND', sans-serif",
788+
"'Hawaii 5-0', sans-serif",
789+
// solve problem by escaping
790+
"Red\\/Black, sans-serif",
791+
'\\"Lucida\\", Grande, sans-serif',
792+
"Ahem\\!, sans-serif",
793+
"test\\@foo, sans-serif",
794+
"\\#POUND, sans-serif",
795+
"Hawaii\\ 5\\-0, sans-serif",
796+
"yellowgreen"
797+
],
738798

799+
invalid: {
800+
"--Futura, sans-serif" : "Expected (<font-family> | inherit) but found '--Futura , sans-serif'.",
801+
"Red/Black, sans-serif" : "Expected end of value but found '/'.",
802+
"'Lucida' Grande, sans-serif" : "Expected end of value but found 'Grande'.",
803+
"Hawaii 5-0, sans-serif" : "Expected end of value but found '5'."
804+
},
805+
806+
error: {
807+
"47Futura, sans-serif" : "Unexpected token '47Futura' at line 1, col 20.",
808+
"-7Futura, sans-serif" : "Unexpected token '7Futura' at line 1, col 21.",
809+
"Ahem!, sans-serif" : "Expected RBRACE at line 1, col 24.",
810+
"test@foo, sans-serif" : "Expected RBRACE at line 1, col 24.",
811+
"#POUND, sans-serif" : "Expected a hex color but found '#POUND' at line 1, col 20."
812+
}
813+
}));
814+
815+
suite.add(new ValidationTestCase({
816+
property: "font-style",
817+
818+
valid: [
819+
"normal", "italic", "oblique",
820+
"inherit"
821+
]
822+
}));
823+
824+
suite.add(new ValidationTestCase({
825+
property: "font-variant",
826+
827+
valid: [
828+
"normal", "none", "small-caps", "common-ligatures small-caps",
829+
"inherit"
830+
]
831+
}));
832+
833+
suite.add(new ValidationTestCase({
834+
property: "font-variant-alternates",
835+
836+
valid: [
837+
"normal", "historical-forms",
838+
"stylistic(salt) styleset(ss01, ss02)",
839+
"character-variant(cv03, cv04, cv05) swash(swsh)",
840+
"ornaments(ornm2) annotation(nalt2)",
841+
"inherit"
842+
]
843+
}));
844+
845+
suite.add(new ValidationTestCase({
846+
property: "font-variant-caps",
847+
848+
valid: [
849+
"normal", "small-caps", "all-small-caps", "petite-caps",
850+
"all-petite-caps", "unicase", "titling-caps", "inherit"
851+
]
852+
}));
853+
854+
suite.add(new ValidationTestCase({
855+
property: "font-variant-east-asian",
856+
857+
valid: [
858+
"normal", "ruby", "jis78", "jis83", "jis90", "jis04",
859+
"simplified", "traditional", "full-width", "proportional-width",
860+
"ruby full-width jis83",
861+
"inherit"
862+
]
863+
}));
864+
865+
suite.add(new ValidationTestCase({
866+
property: "font-variant-ligatures",
867+
868+
valid: [
869+
"normal", "none",
870+
"common-ligatures discretionary-ligatures historical-ligatures contextual",
871+
"no-common-ligatures no-discretionary-ligatures no-historical-ligatures no-contextual",
872+
"inherit"
873+
]
874+
}));
875+
876+
suite.add(new ValidationTestCase({
877+
property: "font-variant-numeric",
878+
879+
valid: [
880+
"normal", "ordinal", "slashed-zero", "lining-nums",
881+
"lining-nums proportional-nums diagonal-fractions ordinal",
882+
"oldstyle-nums tabular-nums stacked-fractions slashed-zero",
883+
"inherit"
884+
]
885+
}));
739886

740887
suite.add(new ValidationTestCase({
741888
property: "min-height",

0 commit comments

Comments
 (0)