Skip to content

Commit 5e0bbd8

Browse files
committed
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 9aa1b79 commit 5e0bbd8

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
@@ -315,18 +315,22 @@ var Properties = {
315315
"-ms-flex-wrap" : "nowrap | wrap | wrap-reverse",
316316
"float" : "left | right | none | inherit",
317317
"float-offset" : 1,
318-
"font" : 1,
319-
"font-family" : 1,
318+
"font" : "<font-shorthand> | caption | icon | menu | message-box | small-caption | status-bar | inherit",
319+
"font-family" : "<font-family> | inherit",
320320
"font-feature-settings" : "<feature-tag-value> | normal | inherit",
321321
"font-kerning" : "auto | normal | none | initial | inherit | unset",
322-
"font-size" : "<absolute-size> | <relative-size> | <length> | <percentage> | inherit",
322+
"font-size" : "<font-size> | inherit",
323323
"font-size-adjust" : "<number> | none | inherit",
324-
"font-stretch" : "normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded | inherit",
325-
"font-style" : "normal | italic | oblique | inherit",
326-
"font-variant" : "normal | small-caps | inherit",
327-
"font-variant-caps" : "normal | small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps",
324+
"font-stretch" : "<font-stretch> | inherit",
325+
"font-style" : "<font-style> | inherit",
326+
"font-variant" : "<font-variant> | normal | none | inherit",
327+
"font-variant-alternates" : "<font-variant-alternates> | normal | inherit",
328+
"font-variant-caps" : "<font-variant-caps> | normal | inherit",
329+
"font-variant-east-asian" : "<font-variant-east-asian> | normal | inherit",
330+
"font-variant-ligatures" : "<font-variant-ligatures> | normal | none | inherit",
331+
"font-variant-numeric" : "<font-variant-numeric> | normal | inherit",
328332
"font-variant-position" : "normal | sub | super | inherit | initial | unset",
329-
"font-weight" : "normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit",
333+
"font-weight" : "<font-weight> | inherit",
330334

331335
//G
332336
"glyph-orientation-horizontal" : "<glyph-angle> | inherit",
@@ -384,7 +388,7 @@ var Properties = {
384388
//L
385389
"left" : "<margin-width> | inherit",
386390
"letter-spacing" : "<length> | normal | inherit",
387-
"line-height" : "<number> | <length> | <percentage> | normal | inherit",
391+
"line-height" : "<line-height> | inherit",
388392
"line-break" : "auto | loose | normal | strict",
389393
"line-stacking" : 1,
390394
"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;
@@ -539,6 +543,36 @@ ValidationTypes = {
539543
part, "blur() | brightness() | contrast() | custom() | " +
540544
"drop-shadow() | grayscale() | hue-rotate() | invert() | " +
541545
"opacity() | saturate() | sepia()");
546+
},
547+
548+
"<generic-family>": function(part){
549+
return ValidationTypes.isLiteral(part, "serif | sans-serif | cursive | fantasy | monospace");
550+
},
551+
552+
"<ident-not-generic-family>": function(part){
553+
return this["<ident>"](part) && !this["<generic-family>"](part);
554+
},
555+
556+
"<font-size>": function(part){
557+
var result = this["<absolute-size>"](part) || this["<relative-size>"](part) || this["<length>"](part) || this["<percentage>"](part);
558+
return result;
559+
},
560+
561+
"<font-stretch>": function(part){
562+
return ValidationTypes.isLiteral(part, "normal | ultra-condensed | extra-condensed | condensed | semi-condensed | semi-expanded | expanded | extra-expanded | ultra-expanded");
563+
},
564+
565+
"<font-style>": function(part){
566+
return ValidationTypes.isLiteral(part, "normal | italic | oblique");
567+
},
568+
569+
"<font-weight>": function(part){
570+
return ValidationTypes.isLiteral(part, "normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900");
571+
},
572+
573+
"<line-height>": function(part){
574+
var result = this["<number>"](part) || this["<length>"](part) || this["<percentage>"](part) || ValidationTypes.isLiteral(part, "normal");
575+
return result;
542576
}
543577
},
544578

@@ -637,6 +671,73 @@ ValidationTypes = {
637671
// * inherit
638672
Matcher.alt("none", "inherit", Matcher.cast("<flex-grow>").then(Matcher.cast("<flex-shrink>").question()).oror("<flex-basis>")),
639673

674+
"<font-family>":
675+
// [ <family-name> | <generic-family> ]#
676+
Matcher.cast("<generic-family> | <family-name>").hash(),
677+
678+
"<family-name>":
679+
// <string> | <IDENT>+
680+
Matcher.alt("<string>",
681+
Matcher.seq("<ident-not-generic-family>",
682+
Matcher.cast("<ident>").star())),
683+
684+
"<font-variant-alternates>":
685+
Matcher.oror(// stylistic(<feature-value-name>)
686+
"stylistic()",
687+
"historical-forms",
688+
// styleset(<feature-value-name> #)
689+
"styleset()",
690+
// character-variant(<feature-value-name> #)
691+
"character-variant()",
692+
// swash(<feature-value-name>)
693+
"swash()",
694+
// ornaments(<feature-value-name>)
695+
"ornaments()",
696+
// annotation(<feature-value-name>)
697+
"annotation()"),
698+
699+
"<font-variant-caps>":
700+
Matcher.cast("small-caps | all-small-caps | petite-caps | all-petite-caps | unicase | titling-caps"),
701+
702+
"<font-variant-css21>":
703+
Matcher.cast("normal | small-caps"),
704+
705+
"<font-variant-ligatures>":
706+
Matcher.oror(// <common-lig-values>
707+
"common-ligatures | no-common-ligatures",
708+
// <discretionary-lig-values>
709+
"discretionary-ligatures | no-discretionary-ligatures",
710+
// <historical-lig-values>
711+
"historical-ligatures | no-historical-ligatures",
712+
// <contextual-alt-values>
713+
"contextual | no-contextual"),
714+
715+
"<font-variant-numeric>":
716+
Matcher.oror(// <numeric-figure-values>
717+
"lining-nums | oldstyle-nums",
718+
// <numeric-spacing-values>
719+
"proportional-nums | tabular-nums",
720+
// <numeric-fraction-values>
721+
"diagonal-fractions | stacked-fractions",
722+
"ordinal",
723+
"slashed-zero"),
724+
725+
"<font-variant-east-asian>":
726+
Matcher.oror(// <east-asian-variant-values>
727+
"jis78 | jis83 | jis90 | jis04 | simplified | traditional",
728+
// <east-asian-width-values>
729+
"full-width | proportional-width",
730+
"ruby"),
731+
732+
"<font-shorthand>":
733+
Matcher.seq(Matcher.oror("<font-style>",
734+
"<font-variant-css21>",
735+
"<font-weight>",
736+
"<font-stretch>").question(),
737+
"<font-size>",
738+
Matcher.seq("/", "<line-height>").question(),
739+
"<font-family>"),
740+
640741
"<text-decoration>":
641742
// none | [ underline || overline || line-through || blink ] | inherit
642743
Matcher.oror("underline", "overline", "line-through", "blink"),
@@ -646,3 +747,12 @@ ValidationTypes = {
646747
Matcher.alt("auto", Matcher.cast("<animateable-feature>").hash())
647748
}
648749
};
750+
751+
// Because this is defined relative to other complex validation types,
752+
// we need to define it *after* the rest of the types are initialized.
753+
ValidationTypes.complex["<font-variant>"] =
754+
Matcher.oror({ expand: "<font-variant-ligatures>" },
755+
{ expand: "<font-variant-alternates>" },
756+
"<font-variant-caps>",
757+
{ expand: "<font-variant-numeric>" },
758+
{ 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)