|
| 1 | +use crate::{ |
| 2 | + checks::googlefonts::metadata::{family_proto, DesignerInfoProto}, |
| 3 | + network_conditions::{is_designer_listed, DESIGNER_INFO_RAW_URL}, |
| 4 | +}; |
| 5 | +use fontspector_checkapi::prelude::*; |
| 6 | + |
| 7 | +const NAME_DEUNICODIZATION: [(&str, &str); 19] = [ |
| 8 | + ("á", "a"), |
| 9 | + ("é", "e"), |
| 10 | + ("í", "i"), |
| 11 | + ("ó", "o"), |
| 12 | + ("ú", "u"), |
| 13 | + ("à", "a"), |
| 14 | + ("è", "e"), |
| 15 | + ("ì", "i"), |
| 16 | + ("ò", "o"), |
| 17 | + ("ù", "u"), |
| 18 | + ("ń", "n"), |
| 19 | + ("ø", "o"), |
| 20 | + ("ř", "r"), |
| 21 | + ("ś", "s"), |
| 22 | + ("ß", "ss"), |
| 23 | + ("ł", "l"), |
| 24 | + ("ã", "a"), |
| 25 | + ("ı", "i"), |
| 26 | + ("ü", "ue"), |
| 27 | +]; |
| 28 | + |
| 29 | +fn normalize_designer_name(designer: &str) -> String { |
| 30 | + designer |
| 31 | + .chars() |
| 32 | + .filter_map(|c| { |
| 33 | + if c.is_ascii_alphanumeric() { |
| 34 | + Some(c.to_ascii_lowercase()) |
| 35 | + } else { |
| 36 | + NAME_DEUNICODIZATION |
| 37 | + .iter() |
| 38 | + .find(|(from, _)| *from == c.to_string()) |
| 39 | + .map(|(_, to)| to.chars()) |
| 40 | + .unwrap_or("".chars()) |
| 41 | + .next() |
| 42 | + } |
| 43 | + }) |
| 44 | + .collect() |
| 45 | +} |
| 46 | + |
| 47 | +#[check( |
| 48 | + id = "googlefonts/metadata/designer_profiles", |
| 49 | + rationale = " |
| 50 | + |
| 51 | + Google Fonts has a catalog of designers. |
| 52 | +
|
| 53 | + This check ensures that the online entries of the catalog can be found based |
| 54 | + on the designer names listed on the METADATA.pb file. |
| 55 | +
|
| 56 | + It also validates the URLs and file formats are all correctly set. |
| 57 | + |
| 58 | + ", |
| 59 | + proposal = "https://github.com/fonttools/fontbakery/issues/3083", |
| 60 | + title = "METADATA.pb: Designers are listed correctly on the Google Fonts catalog?", |
| 61 | + applies_to = "MDPB" |
| 62 | +)] |
| 63 | +fn designer_profiles(c: &Testable, context: &Context) -> CheckFnResult { |
| 64 | + let msg = family_proto(c).map_err(|e| { |
| 65 | + CheckError::Error(format!("METADATA.pb is not a valid FamilyProto: {:?}", e)) |
| 66 | + })?; |
| 67 | + let mut problems = vec![]; |
| 68 | + for designer in msg.designer().split(",") { |
| 69 | + let designer = normalize_designer_name(designer.trim()); |
| 70 | + if designer == "multipledesigners" { |
| 71 | + problems.push(Status::fail( |
| 72 | + "multiple-designers", |
| 73 | + &format!( |
| 74 | + "Font family {} does not explicitely mention the names of its designers on its METADATA.pb file.", |
| 75 | + msg.name() |
| 76 | + ), |
| 77 | + ), |
| 78 | + ); |
| 79 | + continue; |
| 80 | + } |
| 81 | + let listed = is_designer_listed(context, &designer).map_err(CheckError::Error)?; |
| 82 | + if let Some(profile) = listed { |
| 83 | + let designer_profile = |
| 84 | + protobuf::text_format::parse_from_str::<DesignerInfoProto>(&profile) |
| 85 | + .map_err(|e| CheckError::Error(format!("Error parsing info.pb: {}", e)))?; |
| 86 | + if normalize_designer_name(designer_profile.designer()) != designer { |
| 87 | + problems.push(Status::warn( |
| 88 | + "mismatch", |
| 89 | + &format!( |
| 90 | + "Designer name at METADATA.pb ({}) is not the same as listed on the designers catalog ({}) available at {}/{}/info.pb", |
| 91 | + designer, |
| 92 | + normalize_designer_name(designer_profile.designer()), |
| 93 | + DESIGNER_INFO_RAW_URL, designer |
| 94 | + ), |
| 95 | + ), |
| 96 | + ); |
| 97 | + } |
| 98 | + if !designer_profile.link().is_empty() { |
| 99 | + problems.push(Status::warn( |
| 100 | + "link-field", |
| 101 | + "Currently the link field is not used by the GFonts API. Designer webpage links should, for now, be placed directly on the bio.html file.", |
| 102 | + ), |
| 103 | + ); |
| 104 | + } |
| 105 | + if designer_profile.avatar.file_name().is_empty() && designer != "Google" { |
| 106 | + problems.push(Status::warn( |
| 107 | + "missing-avatar", |
| 108 | + &format!( |
| 109 | + "Designer {} still does not have an avatar image. Please provide one.", |
| 110 | + designer |
| 111 | + ), |
| 112 | + )); |
| 113 | + } else { |
| 114 | + let avatar_url = format!( |
| 115 | + "{}{}/{}", |
| 116 | + DESIGNER_INFO_RAW_URL, |
| 117 | + designer, |
| 118 | + designer_profile.avatar.file_name() |
| 119 | + ); |
| 120 | + let response = reqwest::blocking::get(&avatar_url).map_err(|e| { |
| 121 | + CheckError::Error(format!( |
| 122 | + "Error fetching avatar image from {}: {}", |
| 123 | + avatar_url, e |
| 124 | + )) |
| 125 | + })?; |
| 126 | + if !response.status().is_success() { |
| 127 | + problems.push(Status::warn( |
| 128 | + "bad-avatar-filename", |
| 129 | + &format!( |
| 130 | + "The avatar filename provided seems to be incorrect: ({})", |
| 131 | + avatar_url |
| 132 | + ), |
| 133 | + )); |
| 134 | + } |
| 135 | + } |
| 136 | + } else { |
| 137 | + problems.push(Status::warn( |
| 138 | + "profile-not-found", |
| 139 | + &format!( |
| 140 | + "It seems that {} is still not listed on the designers catalog. Please submit a photo and a link to a webpage where people can learn more about the work of this designer/typefoundry.", |
| 141 | + designer |
| 142 | + ), |
| 143 | + ), |
| 144 | + ); |
| 145 | + continue; |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + return_result(problems) |
| 150 | +} |
0 commit comments