Compare commits

...

8 Commits

Author SHA1 Message Date
T0mstone
f44a44b173
Merge 476096c2db5f4d539a4cb0c4cb24e4e1a36a022e into 9a6268050fb769e18c4889fa5f59d4150e8878d6 2025-07-15 13:55:52 -07:00
Laurenz
9a6268050f
HTML frame improvements (#6605) 2025-07-15 14:48:31 +00:00
T0mstone
476096c2db Fix ide and docs 2025-07-10 02:28:00 +02:00
T0mstone
fd35268a88 cleanup 2025-07-10 02:04:51 +02:00
T0mstone
3fba007c13 Fix symbol repr 2025-07-10 02:03:44 +02:00
T0mstone
7dd3523044 Improve error messages
Using "codepoint" is more accurate and lines up with what typst's standard library uses
2025-07-10 01:27:29 +02:00
T0mstone
0160bf1547 Merge branch 'main' into multi-char-symbols 2025-07-10 01:18:03 +02:00
T0mstone
4d8a9863d7 Allow multi-character symbols/variants 2025-06-27 19:27:16 +02:00
19 changed files with 165 additions and 94 deletions

2
Cargo.lock generated
View File

@ -413,7 +413,7 @@ dependencies = [
[[package]]
name = "codex"
version = "0.1.1"
source = "git+https://github.com/typst/codex?rev=9ac86f9#9ac86f96af5b89fce555e6bba8b6d1ac7b44ef00"
source = "git+https://github.com/typst/codex?rev=775d828#775d82873c3f74ce95ec2621f8541de1b48778a7"
[[package]]
name = "color-print"

View File

@ -47,7 +47,7 @@ clap = { version = "4.4", features = ["derive", "env", "wrap_help"] }
clap_complete = "4.2.1"
clap_mangen = "0.2.10"
codespan-reporting = "0.11"
codex = { git = "https://github.com/typst/codex", rev = "9ac86f9" }
codex = { git = "https://github.com/typst/codex", rev = "775d828" }
color-print = "0.3.6"
comemo = "0.4"
csv = "1"

View File

@ -123,7 +123,7 @@ impl Eval for ast::Escape<'_> {
type Output = Value;
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
Ok(Value::Symbol(Symbol::single(self.get())))
Ok(Value::Symbol(Symbol::runtime_char(self.get())))
}
}
@ -131,7 +131,7 @@ impl Eval for ast::Shorthand<'_> {
type Output = Value;
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
Ok(Value::Symbol(Symbol::single(self.get())))
Ok(Value::Symbol(Symbol::runtime_char(self.get())))
}
}

View File

@ -49,7 +49,7 @@ impl Eval for ast::MathShorthand<'_> {
type Output = Value;
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
Ok(Value::Symbol(Symbol::single(self.get())))
Ok(Value::Symbol(Symbol::runtime_char(self.get())))
}
}

View File

@ -109,10 +109,7 @@ fn handle(
styles.chain(&style),
Region::new(Size::splat(Abs::inf()), Axes::splat(false)),
)?;
output.push(HtmlNode::Frame(HtmlFrame {
inner: frame,
text_size: styles.resolve(TextElem::size),
}));
output.push(HtmlNode::Frame(HtmlFrame::new(frame, styles)));
} else {
engine.sink.warn(warning!(
child.span(),

View File

@ -2,10 +2,11 @@ use std::fmt::{self, Debug, Display, Formatter};
use ecow::{EcoString, EcoVec};
use typst_library::diag::{bail, HintedStrResult, StrResult};
use typst_library::foundations::{cast, Dict, Repr, Str};
use typst_library::foundations::{cast, Dict, Repr, Str, StyleChain};
use typst_library::introspection::{Introspector, Tag};
use typst_library::layout::{Abs, Frame};
use typst_library::model::DocumentInfo;
use typst_library::text::TextElem;
use typst_syntax::Span;
use typst_utils::{PicoStr, ResolvedPicoStr};
@ -279,3 +280,10 @@ pub struct HtmlFrame {
/// consistently.
pub text_size: Abs,
}
impl HtmlFrame {
/// Wraps a laid-out frame.
pub fn new(inner: Frame, styles: StyleChain) -> Self {
Self { inner, text_size: styles.resolve(TextElem::size) }
}
}

View File

@ -121,6 +121,7 @@ fn write_children(w: &mut Writer, element: &HtmlElement) -> SourceResult<()> {
let pretty_inside = allows_pretty_inside(element.tag)
&& element.children.iter().any(|node| match node {
HtmlNode::Element(child) => wants_pretty_around(child.tag),
HtmlNode::Frame(_) => true,
_ => false,
});
@ -305,14 +306,6 @@ fn write_escape(w: &mut Writer, c: char) -> StrResult<()> {
/// Encode a laid out frame into the writer.
fn write_frame(w: &mut Writer, frame: &HtmlFrame) {
// FIXME: This string replacement is obviously a hack.
let svg = typst_svg::svg_frame(&frame.inner).replace(
"<svg class",
&format!(
"<svg style=\"overflow: visible; width: {}em; height: {}em;\" class",
frame.inner.width() / frame.text_size,
frame.inner.height() / frame.text_size,
),
);
let svg = typst_svg::svg_html_frame(&frame.inner, frame.text_size);
w.buf.push_str(&svg);
}

View File

@ -98,7 +98,7 @@ pub enum CompletionKind {
/// A font family.
Font,
/// A symbol.
Symbol(char),
Symbol(EcoString),
}
/// Complete in comments. Or rather, don't!
@ -457,7 +457,7 @@ fn field_access_completions(
for modifier in symbol.modifiers() {
if let Ok(modified) = symbol.clone().modified((), modifier) {
ctx.completions.push(Completion {
kind: CompletionKind::Symbol(modified.get()),
kind: CompletionKind::Symbol(modified.get().into()),
label: modifier.into(),
apply: None,
detail: None,
@ -1392,7 +1392,7 @@ impl<'a> CompletionContext<'a> {
kind: kind.unwrap_or_else(|| match value {
Value::Func(_) => CompletionKind::Func,
Value::Type(_) => CompletionKind::Type,
Value::Symbol(s) => CompletionKind::Symbol(s.get()),
Value::Symbol(s) => CompletionKind::Symbol(s.get().into()),
_ => CompletionKind::Constant,
}),
label,

View File

@ -129,12 +129,22 @@ pub fn layout_symbol(
ctx: &mut MathContext,
styles: StyleChain,
) -> SourceResult<()> {
assert!(
elem.text.len() <= 4 && elem.text.chars().count() == 1,
"TODO: layout multi-char symbol"
);
let elem_char = elem
.text
.chars()
.next()
.expect("TODO: should an empty symbol value forbidden?");
// Switch dotless char to normal when we have the dtls OpenType feature.
// This should happen before the main styling pass.
let dtls = style_dtls();
let (unstyled_c, symbol_styles) = match try_dotless(elem.text) {
let (unstyled_c, symbol_styles) = match try_dotless(elem_char) {
Some(c) if has_dtls_feat(ctx.font) => (c, styles.chain(&dtls)),
_ => (elem.text, styles),
_ => (elem_char, styles),
};
let variant = styles.get(EquationElem::variant);

View File

@ -1,5 +1,5 @@
use std::collections::{BTreeSet, HashMap};
use std::fmt::{self, Debug, Display, Formatter, Write};
use std::fmt::{self, Debug, Display, Formatter};
use std::sync::Arc;
use codex::ModifierSet;
@ -52,7 +52,7 @@ pub struct Symbol(Repr);
#[derive(Clone, Eq, PartialEq, Hash)]
enum Repr {
/// A native symbol that has no named variant.
Single(char),
Single(&'static str),
/// A native symbol with multiple named variants.
Complex(&'static [Variant<&'static str>]),
/// A symbol with multiple named variants, where some modifiers may have
@ -61,9 +61,9 @@ enum Repr {
Modified(Arc<(List, ModifierSet<EcoString>)>),
}
/// A symbol variant, consisting of a set of modifiers, a character, and an
/// A symbol variant, consisting of a set of modifiers, the variant's value, and an
/// optional deprecation message.
type Variant<S> = (ModifierSet<S>, char, Option<S>);
type Variant<S> = (ModifierSet<S>, S, Option<S>);
/// A collection of symbols.
#[derive(Clone, Eq, PartialEq, Hash)]
@ -73,9 +73,9 @@ enum List {
}
impl Symbol {
/// Create a new symbol from a single character.
pub const fn single(c: char) -> Self {
Self(Repr::Single(c))
/// Create a new symbol from a single value.
pub const fn single(value: &'static str) -> Self {
Self(Repr::Single(value))
}
/// Create a symbol with a static variant list.
@ -85,6 +85,11 @@ impl Symbol {
Self(Repr::Complex(list))
}
/// Create a symbol from a runtime char.
pub fn runtime_char(c: char) -> Self {
Self::runtime(Box::new([(ModifierSet::default(), c.into(), None)]))
}
/// Create a symbol with a runtime variant list.
#[track_caller]
pub fn runtime(list: Box<[Variant<EcoString>]>) -> Self {
@ -92,15 +97,15 @@ impl Symbol {
Self(Repr::Modified(Arc::new((List::Runtime(list), ModifierSet::default()))))
}
/// Get the symbol's character.
pub fn get(&self) -> char {
/// Get the symbol's value.
pub fn get(&self) -> &str {
match &self.0 {
Repr::Single(c) => *c,
Repr::Single(value) => value,
Repr::Complex(_) => ModifierSet::<&'static str>::default()
.best_match_in(self.variants().map(|(m, c, _)| (m, c)))
.best_match_in(self.variants().map(|(m, v, _)| (m, v)))
.unwrap(),
Repr::Modified(arc) => {
arc.1.best_match_in(self.variants().map(|(m, c, _)| (m, c))).unwrap()
arc.1.best_match_in(self.variants().map(|(m, v, _)| (m, v))).unwrap()
}
}
}
@ -108,27 +113,27 @@ impl Symbol {
/// Try to get the function associated with the symbol, if any.
pub fn func(&self) -> StrResult<Func> {
match self.get() {
'⌈' => Ok(crate::math::ceil::func()),
'⌊' => Ok(crate::math::floor::func()),
'' => Ok(crate::math::accent::dash::func()),
'⋅' | '\u{0307}' => Ok(crate::math::accent::dot::func()),
'¨' => Ok(crate::math::accent::dot_double::func()),
'\u{20db}' => Ok(crate::math::accent::dot_triple::func()),
'\u{20dc}' => Ok(crate::math::accent::dot_quad::func()),
'' => Ok(crate::math::accent::tilde::func()),
'´' => Ok(crate::math::accent::acute::func()),
'˝' => Ok(crate::math::accent::acute_double::func()),
'˘' => Ok(crate::math::accent::breve::func()),
'ˇ' => Ok(crate::math::accent::caron::func()),
'^' => Ok(crate::math::accent::hat::func()),
'`' => Ok(crate::math::accent::grave::func()),
'¯' => Ok(crate::math::accent::macron::func()),
'○' => Ok(crate::math::accent::circle::func()),
'→' => Ok(crate::math::accent::arrow::func()),
'←' => Ok(crate::math::accent::arrow_l::func()),
'↔' => Ok(crate::math::accent::arrow_l_r::func()),
'⇀' => Ok(crate::math::accent::harpoon::func()),
'↼' => Ok(crate::math::accent::harpoon_lt::func()),
"" => Ok(crate::math::ceil::func()),
"" => Ok(crate::math::floor::func()),
"" => Ok(crate::math::accent::dash::func()),
"" | "\u{0307}" => Ok(crate::math::accent::dot::func()),
"¨" => Ok(crate::math::accent::dot_double::func()),
"\u{20db}" => Ok(crate::math::accent::dot_triple::func()),
"\u{20dc}" => Ok(crate::math::accent::dot_quad::func()),
"" => Ok(crate::math::accent::tilde::func()),
"´" => Ok(crate::math::accent::acute::func()),
"˝" => Ok(crate::math::accent::acute_double::func()),
"˘" => Ok(crate::math::accent::breve::func()),
"ˇ" => Ok(crate::math::accent::caron::func()),
"^" => Ok(crate::math::accent::hat::func()),
"`" => Ok(crate::math::accent::grave::func()),
"¯" => Ok(crate::math::accent::macron::func()),
"" => Ok(crate::math::accent::circle::func()),
"" => Ok(crate::math::accent::arrow::func()),
"" => Ok(crate::math::accent::arrow_l::func()),
"" => Ok(crate::math::accent::arrow_l_r::func()),
"" => Ok(crate::math::accent::harpoon::func()),
"" => Ok(crate::math::accent::harpoon_lt::func()),
_ => bail!("symbol {self} is not callable"),
}
}
@ -163,7 +168,7 @@ impl Symbol {
/// The characters that are covered by this symbol.
pub fn variants(&self) -> impl Iterator<Item = Variant<&str>> {
match &self.0 {
Repr::Single(c) => Variants::Single(Some(*c).into_iter()),
Repr::Single(value) => Variants::Single(std::iter::once(*value)),
Repr::Complex(list) => Variants::Static(list.iter()),
Repr::Modified(arc) => arc.0.variants(),
}
@ -279,14 +284,14 @@ impl Symbol {
impl Display for Symbol {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_char(self.get())
f.write_str(self.get())
}
}
impl Debug for Repr {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Single(c) => Debug::fmt(c, f),
Self::Single(value) => Debug::fmt(value, f),
Self::Complex(list) => list.fmt(f),
Self::Modified(lists) => lists.fmt(f),
}
@ -305,7 +310,7 @@ impl Debug for List {
impl crate::foundations::Repr for Symbol {
fn repr(&self) -> EcoString {
match &self.0 {
Repr::Single(c) => eco_format!("symbol(\"{}\")", *c),
Repr::Single(value) => eco_format!("symbol({})", value.repr()),
Repr::Complex(variants) => {
eco_format!(
"symbol{}",
@ -341,15 +346,15 @@ fn repr_variants<'a>(
// that contain all applied modifiers.
applied_modifiers.iter().all(|am| modifiers.contains(am))
})
.map(|(modifiers, c, _)| {
.map(|(modifiers, value, _)| {
let trimmed_modifiers =
modifiers.into_iter().filter(|&m| !applied_modifiers.contains(m));
if trimmed_modifiers.clone().all(|m| m.is_empty()) {
eco_format!("\"{c}\"")
value.repr()
} else {
let trimmed_modifiers =
trimmed_modifiers.collect::<Vec<_>>().join(".");
eco_format!("(\"{}\", \"{}\")", trimmed_modifiers, c)
eco_format!("({}, {})", trimmed_modifiers.repr(), value.repr())
}
})
.collect::<Vec<_>>(),
@ -362,7 +367,7 @@ impl Serialize for Symbol {
where
S: Serializer,
{
serializer.serialize_char(self.get())
serializer.serialize_str(self.get())
}
}
@ -377,11 +382,12 @@ impl List {
}
/// A value that can be cast to a symbol.
pub struct SymbolVariant(EcoString, char);
pub struct SymbolVariant(EcoString, EcoString);
cast! {
SymbolVariant,
c: char => Self(EcoString::new(), c),
c: char => Self(EcoString::new(), c.into()),
s: EcoString => Self(EcoString::new(), s),
array: Array => {
let mut iter = array.into_iter();
match (iter.next(), iter.next(), iter.next()) {
@ -393,7 +399,7 @@ cast! {
/// Iterator over variants.
enum Variants<'a> {
Single(std::option::IntoIter<char>),
Single(std::iter::Once<&'static str>),
Static(std::slice::Iter<'static, Variant<&'static str>>),
Runtime(std::slice::Iter<'a, Variant<EcoString>>),
}
@ -406,7 +412,7 @@ impl<'a> Iterator for Variants<'a> {
Self::Single(iter) => Some((ModifierSet::default(), iter.next()?, None)),
Self::Static(list) => list.next().copied(),
Self::Runtime(list) => {
list.next().map(|(m, c, d)| (m.as_deref(), *c, d.as_deref()))
list.next().map(|(m, s, d)| (m.as_deref(), s.as_str(), d.as_deref()))
}
}
}
@ -415,21 +421,21 @@ impl<'a> Iterator for Variants<'a> {
/// A single character.
#[elem(Repr, PlainText)]
pub struct SymbolElem {
/// The symbol's character.
/// The symbol's value.
#[required]
pub text: char, // This is called `text` for consistency with `TextElem`.
pub text: EcoString, // This is called `text` for consistency with `TextElem`.
}
impl SymbolElem {
/// Create a new packed symbol element.
pub fn packed(text: impl Into<char>) -> Content {
pub fn packed(text: impl Into<EcoString>) -> Content {
Self::new(text.into()).pack()
}
}
impl PlainText for Packed<SymbolElem> {
fn plain_text(&self, text: &mut EcoString) {
text.push(self.text);
text.push_str(&self.text);
}
}

View File

@ -188,7 +188,7 @@ cast! {
self => self.0.into_value(),
v: char => Self::new(v),
v: Content => match v.to_packed::<SymbolElem>() {
Some(elem) => Self::new(elem.text),
None => bail!("expected a symbol"),
Some(elem) if elem.text.chars().count() == 1 => Self::new(elem.text.chars().next().unwrap()),
_ => bail!("expected a single-codepoint symbol"),
},
}

View File

@ -274,7 +274,7 @@ cast! {
Delimiter,
self => self.0.into_value(),
_: NoneValue => Self::none(),
v: Symbol => Self::char(v.get())?,
v: Symbol => Self::char(v.get().parse::<char>().map_err(|_| "expected a single-codepoint symbol")?)?,
v: char => Self::char(v)?,
}

View File

@ -39,7 +39,7 @@ impl From<codex::Module> for Scope {
impl From<codex::Symbol> for Symbol {
fn from(symbol: codex::Symbol) -> Self {
match symbol {
codex::Symbol::Single(c) => Symbol::single(c),
codex::Symbol::Single(value) => Symbol::single(value),
codex::Symbol::Multi(list) => Symbol::list(list),
}
}

View File

@ -301,9 +301,7 @@ fn visit_kind_rules<'a>(
// textual elements via `TEXTUAL` grouping. However, in math, this is
// not desirable, so we just do it on a per-element basis.
if let Some(elem) = content.to_packed::<SymbolElem>() {
if let Some(m) =
find_regex_match_in_str(elem.text.encode_utf8(&mut [0; 4]), styles)
{
if let Some(m) = find_regex_match_in_str(elem.text.as_str(), styles) {
visit_regex_match(s, &[(content, styles)], m)?;
return Ok(true);
}
@ -324,7 +322,7 @@ fn visit_kind_rules<'a>(
// Symbols in non-math content transparently convert to `TextElem` so we
// don't have to handle them in non-math layout.
if let Some(elem) = content.to_packed::<SymbolElem>() {
let mut text = TextElem::packed(elem.text).spanned(elem.span());
let mut text = TextElem::packed(elem.text.clone()).spanned(elem.span());
if let Some(label) = elem.label() {
text.set_label(label);
}
@ -1236,7 +1234,7 @@ fn visit_regex_match<'a>(
let len = if let Some(elem) = content.to_packed::<TextElem>() {
elem.text.len()
} else if let Some(elem) = content.to_packed::<SymbolElem>() {
elem.text.len_utf8()
elem.text.len()
} else {
1 // The rest are Ascii, so just one byte.
};

View File

@ -45,6 +45,30 @@ pub fn svg_frame(frame: &Frame) -> String {
renderer.finalize()
}
/// Export a frame into an SVG suitable for embedding into HTML.
#[typst_macros::time(name = "svg html frame")]
pub fn svg_html_frame(frame: &Frame, text_size: Abs) -> String {
let mut renderer = SVGRenderer::with_options(xmlwriter::Options {
indent: xmlwriter::Indent::None,
..Default::default()
});
renderer.write_header_with_custom_attrs(frame.size(), |xml| {
xml.write_attribute("class", "typst-frame");
xml.write_attribute_fmt(
"style",
format_args!(
"overflow: visible; width: {}em; height: {}em;",
frame.width() / text_size,
frame.height() / text_size,
),
);
});
let state = State::new(frame.size(), Transform::identity());
renderer.render_frame(state, Transform::identity(), frame);
renderer.finalize()
}
/// Export a document with potentially multiple pages into a single SVG file.
///
/// The padding will be added around and between the individual frames.
@ -158,8 +182,13 @@ impl State {
impl SVGRenderer {
/// Create a new SVG renderer with empty glyph and clip path.
fn new() -> Self {
Self::with_options(Default::default())
}
/// Create a new SVG renderer with the given configuration.
fn with_options(options: xmlwriter::Options) -> Self {
SVGRenderer {
xml: XmlWriter::new(xmlwriter::Options::default()),
xml: XmlWriter::new(options),
glyphs: Deduplicator::new('g'),
clip_paths: Deduplicator::new('c'),
gradient_refs: Deduplicator::new('g'),
@ -170,11 +199,22 @@ impl SVGRenderer {
}
}
/// Write the SVG header, including the `viewBox` and `width` and `height`
/// attributes.
/// Write the default SVG header, including a `typst-doc` class, the
/// `viewBox` and `width` and `height` attributes.
fn write_header(&mut self, size: Size) {
self.write_header_with_custom_attrs(size, |xml| {
xml.write_attribute("class", "typst-doc");
});
}
/// Write the SVG header with additional attributes and standard attributes.
fn write_header_with_custom_attrs(
&mut self,
size: Size,
write_custom_attrs: impl FnOnce(&mut XmlWriter),
) {
self.xml.start_element("svg");
self.xml.write_attribute("class", "typst-doc");
write_custom_attrs(&mut self.xml);
self.xml.write_attribute_fmt(
"viewBox",
format_args!("0 0 {} {}", size.x.to_pt(), size.y.to_pt()),

View File

@ -718,9 +718,13 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
}
};
for (variant, c, deprecation) in symbol.variants() {
for (variant, value, deprecation) in symbol.variants() {
let value_char = value.parse::<char>().ok();
let shorthand = |list: &[(&'static str, char)]| {
list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s)
value_char.and_then(|c| {
list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s)
})
};
let name = complete(variant);
@ -729,9 +733,12 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
name,
markup_shorthand: shorthand(typst::syntax::ast::Shorthand::LIST),
math_shorthand: shorthand(typst::syntax::ast::MathShorthand::LIST),
math_class: typst_utils::default_math_class(c).map(math_class_name),
codepoint: c as _,
accent: typst::math::Accent::combine(c).is_some(),
math_class: value_char.and_then(|c| {
typst_utils::default_math_class(c).map(math_class_name)
}),
value: value.into(),
accent: value_char
.is_some_and(|c| typst::math::Accent::combine(c).is_some()),
alternates: symbol
.variants()
.filter(|(other, _, _)| other != &variant)

View File

@ -159,7 +159,7 @@ pub struct SymbolsModel {
#[serde(rename_all = "camelCase")]
pub struct SymbolModel {
pub name: EcoString,
pub codepoint: u32,
pub value: EcoString,
pub accent: bool,
pub alternates: Vec<EcoString>,
pub markup_shorthand: Option<&'static str>,

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<p>A rectangle:</p>
<svg class="typst-frame" style="overflow: visible; width: 4.5em; height: 3em;" viewBox="0 0 45 30" width="45pt" height="30pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:h5="http://www.w3.org/1999/xhtml"><g><g transform="translate(-0 -0)"><path class="typst-shape" fill="none" stroke="#000000" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4" d="M 0 0 L 0 30 L 45 30 L 45 0 Z "/></g></g></svg>
</body>
</html>

View File

@ -1,5 +1,6 @@
// No proper HTML tests here yet because we don't want to test SVG export just
// yet. We'll definitely add tests at some point.
--- html-frame html ---
A rectangle:
#html.frame(rect())
--- html-frame-in-layout ---
// Ensure that HTML frames are transparent in layout. This is less important for