0,'sizeTablet'=>0,'sizeMobile'=>0,'weight'=>0,'family'=>'inherit', 'lineHeight'=>0,'letterSpacing'=>0,'paragraphGap'=>0,'color'=>'','align'=>'inherit', 'alignTablet'=>'inherit','alignMobile'=>'inherit','lastLine'=>'auto','italic'=>'inherit', 'decoration'=>'inherit','transform'=>'inherit','wrap'=>'inherit','numeric'=>'normal']; return array_fill_keys(self::ROLES, $value); } public static function clean($raw): array { $out = self::defaults(); foreach ($out as $role => &$v) { $r = is_array($raw) && is_array($raw[$role] ?? null) ? $raw[$role] : []; foreach (['size','sizeTablet','sizeMobile'] as $key) { $v[$key] = Document::number($r[$key] ?? 0,0,120); } $v['weight'] = (int)Document::number($r['weight'] ?? 0,0,900); $v['lineHeight'] = Document::number($r['lineHeight'] ?? 0,0,3); $v['letterSpacing'] = Document::number($r['letterSpacing'] ?? 0,-3,12); $v['paragraphGap'] = Document::number($r['paragraphGap'] ?? 0,0,100); $v['color'] = Document::color($r['color'] ?? ''); $v['family'] = FontLibrary::clean($r['family'] ?? 'inherit'); foreach (['align','alignTablet','alignMobile'] as $key) { $v[$key] = Document::choice($r[$key] ?? '',['inherit','left','center','right','justify'],'inherit'); } $v['numeric'] = Document::choice($r['numeric']??'normal',['normal','lining-nums','oldstyle-nums','tabular-nums','proportional-nums','tabular-nums lining-nums','diagonal-fractions','ordinal','slashed-zero'],'normal'); $v['lastLine'] = Document::choice($r['lastLine'] ?? '',['auto','left','center','right','justify'],'auto'); $v['italic'] = Document::choice($r['italic'] ?? '',['inherit','normal','italic'],'inherit'); $v['decoration'] = Document::choice($r['decoration'] ?? '',['inherit','none','underline','line-through','underline line-through'],'inherit'); $v['transform'] = Document::choice($r['transform'] ?? '',['inherit','none','uppercase','lowercase','capitalize'],'inherit'); $v['wrap'] = Document::choice($r['wrap'] ?? '',['inherit','wrap','balance','pretty'],'inherit'); } unset($v); $defaults=self::defaults(); foreach($out as $role=>$values){$out[$role]=array_filter($values,static fn($value,$key)=>$value!=$defaults[$role][$key],ARRAY_FILTER_USE_BOTH);} return array_filter($out,static fn($values)=>!empty($values)); } public static function main_role(string $type): string { return in_array($type,['text','quote','button'],true) ? 'body' : 'title'; } /** Every selector stops at the current node: child cards keep their own typography. */ public static function selectors(string $type): array { switch ($type) { case 'masthead': return ['title'=>['>.dbs-masthead>div>.dbs-publication-name'],'body'=>['>.dbs-masthead>div>.dbs-publication-tagline'],'meta'=>['>.dbs-masthead>.dbs-publication-issue']]; case 'hero': return ['title'=>['>.dbs-hero>.dbs-hero-copy>.dbs-headline'],'body'=>['>.dbs-hero>.dbs-hero-copy>.dbs-standfirst'],'kicker'=>['>.dbs-hero>.dbs-hero-copy>.dbs-kicker'],'meta'=>['>.dbs-hero>.dbs-hero-copy>.dbs-byline']]; case 'story': return ['title'=>['>.dbs-story>.dbs-story-copy>h3'],'body'=>['>.dbs-story>.dbs-story-copy>.dbs-story-summary'],'kicker'=>['>.dbs-story>.dbs-story-copy>.dbs-kicker'],'meta'=>['>.dbs-story>.dbs-story-copy>.dbs-byline']]; case 'heading': case 'dual-heading': case 'animated-heading': return ['title'=>['>.dbs-heading']]; case 'text': return ['body'=>['>.dbs-prose']]; case 'bubble': return ['title'=>['>.dbs-bubble>h3'],'body'=>['>.dbs-bubble>.dbs-bubble-content']]; case 'callout': return ['title'=>['>.dbs-callout>div>h2'],'body'=>['>.dbs-callout>div>.dbs-callout-copy']]; case 'pricing': return ['title'=>['>.dbs-pricing>h3'],'body'=>['>.dbs-pricing>ul'],'meta'=>['>.dbs-pricing>small']]; case 'quote': return ['body'=>['>.dbs-quote>p'],'meta'=>['>.dbs-quote>cite']]; case 'button': return ['body'=>['>.dbs-button']]; case 'author': return ['title'=>['>.dbs-author>div>strong'],'body'=>['>.dbs-author>div>p'],'meta'=>['>.dbs-author>div>small']]; default: return []; } } public static function css(array $node, array $settings): string { $map = self::selectors($node['type']); if (!$map) { return ''; } $style=$node['style']; $parts=self::defaults();foreach(self::clean($style['typography']??[]) as $role=>$value){$parts[$role]=array_merge($parts[$role],$value);} $scope='.dbs-document #dbs-'.sanitize_html_class($node['id']); $all=''; foreach (['Desktop','Tablet','Mobile'] as $i=>$device) { $rules=''; foreach ($map as $role=>$tails) { $v=$parts[$role]; $selector=implode(',',array_map(static fn($tail)=>$scope.$tail,$tails)); $css=''; $size=$v[$i===0?'size':'size'.$device]; if (!$size && $role===self::main_role($node['type'])) { $size=$style['font'.$device]??0; } if ($size) { $css.='font-size:'.$size.'px;'; } $align=$v[$i===0?'align':'align'.$device]; if($align==='inherit') { if($i===0) { $align=!empty($style['alignExplicit'])?($node['props']['textAlign']??($style['align']??'left')):'inherit'; } else { $align=$style['align'.$device]??'inherit'; } } if($align!=='inherit') {$css.='text-align:'.$align.';';if($align==='justify'){$css.='text-justify:inter-word;text-wrap:wrap;';}} if($i===0) { if($v['numeric']!=='normal'){$css.='font-variant-numeric:'.$v['numeric'].';';} if($v['weight']){$css.='font-weight:'.$v['weight'].';';} if($v['family']!=='inherit'){$css.='font-family:'.FontLibrary::stack($v['family']).';';} if($v['lineHeight']){$css.='line-height:'.$v['lineHeight'].';';} if($v['letterSpacing']){$css.='letter-spacing:'.$v['letterSpacing'].'px;';} if($v['color']){$css.='color:'.$v['color'].($node['type']==='button'?'!important':'').';--dbs-local-ink:'.$v['color'].';';} foreach(['italic'=>'font-style','decoration'=>'text-decoration','transform'=>'text-transform','wrap'=>'text-wrap'] as $key=>$property){if($v[$key]!=='inherit'){$css.=$property.':'.$v[$key].($node['type']==='button'&&$key==='decoration'?'!important':'').';';}} if($v['lastLine']!=='auto'){$css.='text-align-last:'.$v['lastLine'].';';} } if($css){$rules.=$selector.'{'.$css.'}';} // Paragraph alignment must not be defeated by the old wrapper's copy-align class. if($role==='body') { $children=implode(',',array_map(static fn($tail)=>$scope.$tail.' :is(p,li,dd)',$tails)); if($align!=='inherit'){$rules.=$children.'{text-align:'.$align.';text-align-last:'.$v['lastLine'].';}';} if($i===0&&$v['paragraphGap']){$rules.=$children.'{margin-bottom:'.$v['paragraphGap'].'px;}';} } if($i===0&&$v['color']){$links=implode(',',array_map(static fn($tail)=>$scope.$tail.' a',$tails));$rules.=$links.'{color:inherit;}';} } if($rules){$all.=$i?'@media(max-width:'.(int)$settings[$i===1?'tabletBreakpoint':'mobileBreakpoint'].'px){'.$rules.'}':$rules;} } return Assets::inline_style($all,'card-type-'.$node['id']); } }