fix(mtext): honor background contrast
Preserve stored dimension fills and rotate text backgrounds with their labels. Refs #774
This commit is contained in:
parent
252224f546
commit
0b5d4bd311
3 changed files with 171 additions and 72 deletions
71
src/scene/cache/block_cache.rs
vendored
71
src/scene/cache/block_cache.rs
vendored
|
|
@ -60,6 +60,9 @@ pub struct LocalWire {
|
||||||
/// equals the sub-entity's resolved colour. For colour-split MTEXT
|
/// equals the sub-entity's resolved colour. For colour-split MTEXT
|
||||||
/// (`\C`/`\c` inline overrides) each wire carries its own override colour.
|
/// (`\C`/`\c` inline overrides) each wire carries its own override colour.
|
||||||
pub color: [f32; 4],
|
pub color: [f32; 4],
|
||||||
|
pub contrast_bg: Option<[f32; 4]>,
|
||||||
|
pub preserve_color: bool,
|
||||||
|
pub canvas_color: bool,
|
||||||
pub aci: u8,
|
pub aci: u8,
|
||||||
pub pattern_length: f32,
|
pub pattern_length: f32,
|
||||||
pub pattern: [f32; 8],
|
pub pattern: [f32; 8],
|
||||||
|
|
@ -752,6 +755,23 @@ fn tessellate_sub_local(
|
||||||
.chain(wire.text_verts.iter().map(|v| v.pos)),
|
.chain(wire.text_verts.iter().map(|v| v.pos)),
|
||||||
);
|
);
|
||||||
let is_fill_only = wire.points.is_empty() && !wire.fill_tris.is_empty();
|
let is_fill_only = wire.points.is_empty() && !wire.fill_tris.is_empty();
|
||||||
|
let mtext_has_background = matches!(
|
||||||
|
sub,
|
||||||
|
EntityType::MText(text) if text.background_fill_flags & 0x03 != 0
|
||||||
|
);
|
||||||
|
let preserve_color = mtext_has_background && is_fill_only;
|
||||||
|
let canvas_color = is_fill_only
|
||||||
|
&& matches!(
|
||||||
|
sub,
|
||||||
|
EntityType::MText(text) if text.background_fill_flags & 0x02 != 0
|
||||||
|
);
|
||||||
|
let contrast_bg = if !preserve_color
|
||||||
|
&& (!wire.text_verts.is_empty() || !wire.points.is_empty())
|
||||||
|
{
|
||||||
|
tessellate::explicit_mtext_background(sub)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
// A wire whose colour differs from the entity's resolved base colour
|
// A wire whose colour differs from the entity's resolved base colour
|
||||||
// carries an explicit per-segment override (e.g. an MTEXT `\C1;` inline
|
// carries an explicit per-segment override (e.g. an MTEXT `\C1;` inline
|
||||||
// colour). ByBlock / layer-0 inheritance applies only to wires still on
|
// colour). ByBlock / layer-0 inheritance applies only to wires still on
|
||||||
|
|
@ -773,6 +793,9 @@ fn tessellate_sub_local(
|
||||||
pick_tris: wire.pick_tris,
|
pick_tris: wire.pick_tris,
|
||||||
pick_tris_low: wire.pick_tris_low,
|
pick_tris_low: wire.pick_tris_low,
|
||||||
color: wire.color,
|
color: wire.color,
|
||||||
|
contrast_bg,
|
||||||
|
preserve_color,
|
||||||
|
canvas_color,
|
||||||
aci,
|
aci,
|
||||||
pattern_length: pat_len,
|
pattern_length: pat_len,
|
||||||
pattern: pat,
|
pattern: pat,
|
||||||
|
|
@ -1316,6 +1339,9 @@ pub(crate) fn fade_toward_bg(color: [f32; 4], bg: [f32; 4]) -> [f32; 4] {
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
struct StyleKey {
|
struct StyleKey {
|
||||||
color: [u32; 4],
|
color: [u32; 4],
|
||||||
|
contrast_bg: Option<[u32; 4]>,
|
||||||
|
preserve_color: bool,
|
||||||
|
canvas_color: bool,
|
||||||
pattern_length: u32,
|
pattern_length: u32,
|
||||||
pattern: [u32; 8],
|
pattern: [u32; 8],
|
||||||
line_weight_px: u32,
|
line_weight_px: u32,
|
||||||
|
|
@ -1342,6 +1368,9 @@ struct StyleKey {
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
struct BatchEntry {
|
struct BatchEntry {
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
|
contrast_bg: Option<[f32; 4]>,
|
||||||
|
preserve_color: bool,
|
||||||
|
canvas_color: bool,
|
||||||
pattern_length: f32,
|
pattern_length: f32,
|
||||||
pattern: [f32; 8],
|
pattern: [f32; 8],
|
||||||
line_weight_px: f32,
|
line_weight_px: f32,
|
||||||
|
|
@ -1415,6 +1444,9 @@ struct Batches {
|
||||||
impl BatchEntry {
|
impl BatchEntry {
|
||||||
fn new(
|
fn new(
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
|
contrast_bg: Option<[f32; 4]>,
|
||||||
|
preserve_color: bool,
|
||||||
|
canvas_color: bool,
|
||||||
pat_len: f32,
|
pat_len: f32,
|
||||||
pat: [f32; 8],
|
pat: [f32; 8],
|
||||||
lw_px: f32,
|
lw_px: f32,
|
||||||
|
|
@ -1431,6 +1463,9 @@ impl BatchEntry {
|
||||||
// itself — the empty `points` field is enough at finalize time.
|
// itself — the empty `points` field is enough at finalize time.
|
||||||
Self {
|
Self {
|
||||||
color,
|
color,
|
||||||
|
contrast_bg,
|
||||||
|
preserve_color,
|
||||||
|
canvas_color,
|
||||||
pattern_length: pat_len,
|
pattern_length: pat_len,
|
||||||
pattern: pat,
|
pattern: pat,
|
||||||
line_weight_px: lw_px,
|
line_weight_px: lw_px,
|
||||||
|
|
@ -1454,18 +1489,28 @@ impl Batches {
|
||||||
.closed
|
.closed
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(self.by_style.into_values())
|
.chain(self.by_style.into_values())
|
||||||
.map(|b| {
|
.map(|mut b| {
|
||||||
let aabb = if b.min_x.is_infinite() {
|
let aabb = if b.min_x.is_infinite() {
|
||||||
WireModel::UNBOUNDED_AABB
|
WireModel::UNBOUNDED_AABB
|
||||||
} else {
|
} else {
|
||||||
[b.min_x, b.min_y, b.max_x, b.max_y]
|
[b.min_x, b.min_y, b.max_x, b.max_y]
|
||||||
};
|
};
|
||||||
// RAW colour came from `tessellate_sub_local` (and from
|
let contrast_bg = b.contrast_bg.unwrap_or(bg_color);
|
||||||
// `expand_defn`'s ByBlock fallbacks); apply `adapt_to_bg`
|
let color = if b.canvas_color {
|
||||||
// now so each render against a different bg gets the
|
bg_color
|
||||||
// right pure-black ↔ pure-white flip without rebuilding
|
} else if b.preserve_color {
|
||||||
// the cached defn.
|
b.color
|
||||||
let color = crate::scene::view::render::adapt_to_bg(b.color, bg_color);
|
} else {
|
||||||
|
crate::scene::view::render::adapt_to_bg(b.color, contrast_bg)
|
||||||
|
};
|
||||||
|
if !b.preserve_color {
|
||||||
|
for vertex in &mut b.text_verts {
|
||||||
|
vertex.color = crate::scene::view::render::adapt_to_bg(
|
||||||
|
vertex.color,
|
||||||
|
contrast_bg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
WireModel {
|
WireModel {
|
||||||
taper_widths: Vec::new(),
|
taper_widths: Vec::new(),
|
||||||
world_width: b.world_width,
|
world_width: b.world_width,
|
||||||
|
|
@ -1510,6 +1555,9 @@ impl Batches {
|
||||||
|
|
||||||
fn style_key(
|
fn style_key(
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
|
contrast_bg: Option<[f32; 4]>,
|
||||||
|
preserve_color: bool,
|
||||||
|
canvas_color: bool,
|
||||||
pat_len: f32,
|
pat_len: f32,
|
||||||
pat: [f32; 8],
|
pat: [f32; 8],
|
||||||
lw_px: f32,
|
lw_px: f32,
|
||||||
|
|
@ -1527,6 +1575,9 @@ fn style_key(
|
||||||
color[2].to_bits(),
|
color[2].to_bits(),
|
||||||
color[3].to_bits(),
|
color[3].to_bits(),
|
||||||
],
|
],
|
||||||
|
contrast_bg: contrast_bg.map(|color| color.map(f32::to_bits)),
|
||||||
|
preserve_color,
|
||||||
|
canvas_color,
|
||||||
pattern_length: pat_len.to_bits(),
|
pattern_length: pat_len.to_bits(),
|
||||||
pattern: [
|
pattern: [
|
||||||
pat[0].to_bits(),
|
pat[0].to_bits(),
|
||||||
|
|
@ -1943,6 +1994,9 @@ fn emit_wire(
|
||||||
|
|
||||||
let key = style_key(
|
let key = style_key(
|
||||||
final_color,
|
final_color,
|
||||||
|
lw.contrast_bg,
|
||||||
|
lw.preserve_color,
|
||||||
|
lw.canvas_color,
|
||||||
final_pat_len,
|
final_pat_len,
|
||||||
final_pat,
|
final_pat,
|
||||||
final_lw_px,
|
final_lw_px,
|
||||||
|
|
@ -1966,6 +2020,9 @@ fn emit_wire(
|
||||||
let entry = out.by_style.entry(key).or_insert_with(|| {
|
let entry = out.by_style.entry(key).or_insert_with(|| {
|
||||||
BatchEntry::new(
|
BatchEntry::new(
|
||||||
final_color,
|
final_color,
|
||||||
|
lw.contrast_bg,
|
||||||
|
lw.preserve_color,
|
||||||
|
lw.canvas_color,
|
||||||
final_pat_len,
|
final_pat_len,
|
||||||
final_pat,
|
final_pat,
|
||||||
final_lw_px,
|
final_lw_px,
|
||||||
|
|
|
||||||
|
|
@ -530,7 +530,8 @@ pub(crate) fn tessellate_entity(
|
||||||
|
|
||||||
let (entity_color, pattern_length, pattern, line_weight_px, aci) =
|
let (entity_color, pattern_length, pattern, line_weight_px, aci) =
|
||||||
view::render::render_style_for_viewport(document, e, active_viewport);
|
view::render::render_style_for_viewport(document, e, active_viewport);
|
||||||
let entity_color = view::render::adapt_to_bg(entity_color, bg_color);
|
let contrast_bg = convert::tessellate::text_contrast_background(e, bg_color);
|
||||||
|
let entity_color = view::render::adapt_to_bg(entity_color, contrast_bg);
|
||||||
let entity_color = fade_if_locked(document, e, entity_color, bg_color);
|
let entity_color = fade_if_locked(document, e, entity_color, bg_color);
|
||||||
let lt_scale = document.header.linetype_scale as f32 * e.common().linetype_scale as f32;
|
let lt_scale = document.header.linetype_scale as f32 * e.common().linetype_scale as f32;
|
||||||
let lt_name = view::render::linetype_name_for_viewport(document, e, active_viewport);
|
let lt_name = view::render::linetype_name_for_viewport(document, e, active_viewport);
|
||||||
|
|
@ -540,8 +541,7 @@ pub(crate) fn tessellate_entity(
|
||||||
let pslt_factor = 1.0_f32;
|
let pslt_factor = 1.0_f32;
|
||||||
// ── Proxy entity: draw its cached preview ───────────────────────────────
|
// ── Proxy entity: draw its cached preview ───────────────────────────────
|
||||||
//
|
//
|
||||||
// An entity from an application we have no reader for (e.g. an Autodesk
|
// An unsupported application-specific entity arrives as `Unknown`. Its data is
|
||||||
// Raster Design embedded raster image) arrives as `Unknown`. Its own data is
|
|
||||||
// a private format we cannot decode — but it usually ships a proxy-graphics
|
// a private format we cannot decode — but it usually ships a proxy-graphics
|
||||||
// blob, the vector preview its author cached for exactly this case. Draw it
|
// blob, the vector preview its author cached for exactly this case. Draw it
|
||||||
// when the object enabler is missing, so the entity
|
// when the object enabler is missing, so the entity
|
||||||
|
|
@ -738,26 +738,8 @@ pub(crate) fn tessellate_entity(
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Dimension baked-block fast path ─────────────────────────────────────
|
// Render non-annotative dimensions from their stored picture block.
|
||||||
//
|
// Rebuild geometry only when no usable block exists.
|
||||||
// A DIMENSION carries the block "that contains the entities that make up
|
|
||||||
// the dimension picture" (DXF group 2), and that block IS the picture:
|
|
||||||
// AutoCAD requires it and draws it, BricsCAD draws it when present and only
|
|
||||||
// falls back to rendering from the dimension variables when it is missing.
|
|
||||||
// OCS re-derived the picture from DIMVARS every time instead, which means a
|
|
||||||
// drawing whose style disagrees with what it actually drew comes out wrong
|
|
||||||
// — a DIMTXT stored in different units from the DIMSCALE applied to it, or
|
|
||||||
// a per-object override that is already in drawing units, and the text and
|
|
||||||
// extension lines land hundreds of times too large.
|
|
||||||
//
|
|
||||||
// Drawing the block puts OCS on the same footing as the CAD that wrote the
|
|
||||||
// file: it shows what the file says it looks like. Re-deriving stays as the
|
|
||||||
// fallback, for a dimension with no block (one OCS just created, or one
|
|
||||||
// whose block was dropped because it was edited).
|
|
||||||
//
|
|
||||||
// Annotative dimensions keep the old path: their several representations
|
|
||||||
// are separate blocks, and choosing between them is what the annotation
|
|
||||||
// machinery already does. The doctrine above assumes one picture.
|
|
||||||
if let EntityType::Dimension(dim) = e {
|
if let EntityType::Dimension(dim) = e {
|
||||||
let baked = Some(dim.base().block_name.trim())
|
let baked = Some(dim.base().block_name.trim())
|
||||||
.filter(|name| !name.is_empty())
|
.filter(|name| !name.is_empty())
|
||||||
|
|
@ -800,7 +782,18 @@ pub(crate) fn tessellate_entity(
|
||||||
let color_layer0 = !has_book_color
|
let color_layer0 = !has_book_color
|
||||||
&& view::render::is_effective_layer_zero(&sub.common().layer)
|
&& view::render::is_effective_layer_zero(&sub.common().layer)
|
||||||
&& sub.common().color == acadrust::types::Color::ByLayer;
|
&& sub.common().color == acadrust::types::Color::ByLayer;
|
||||||
|
let contrast_bg = convert::tessellate::text_contrast_background(sub, bg_color);
|
||||||
|
let sub_color = view::render::adapt_to_bg(
|
||||||
|
view::render::render_style_for_viewport(
|
||||||
|
document,
|
||||||
|
sub,
|
||||||
|
active_viewport,
|
||||||
|
)
|
||||||
|
.0,
|
||||||
|
contrast_bg,
|
||||||
|
);
|
||||||
let style = context.style_for(document, sub);
|
let style = context.style_for(document, sub);
|
||||||
|
let resolved_color = view::render::adapt_to_bg(style.0, contrast_bg);
|
||||||
let mut placed = sub.clone();
|
let mut placed = sub.clone();
|
||||||
placed.apply_transform(&context.transform);
|
placed.apply_transform(&context.transform);
|
||||||
let sub_wires = tessellate_entity(
|
let sub_wires = tessellate_entity(
|
||||||
|
|
@ -823,8 +816,8 @@ pub(crate) fn tessellate_entity(
|
||||||
if sel {
|
if sel {
|
||||||
wire.selected = true;
|
wire.selected = true;
|
||||||
wire.color = WireModel::SELECTED;
|
wire.color = WireModel::SELECTED;
|
||||||
} else if color_byblock || color_layer0 {
|
} else if (color_byblock || color_layer0) && wire.color == sub_color {
|
||||||
wire.color = view::render::adapt_to_bg(style.0, bg_color);
|
wire.color = resolved_color;
|
||||||
wire.aci = style.4;
|
wire.aci = style.4;
|
||||||
}
|
}
|
||||||
wires.push(wire);
|
wires.push(wire);
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,68 @@ fn split_ds_xyz(x: f64, y: f64, z: f64) -> ([f32; 3], [f32; 3]) {
|
||||||
([xh, yh, zh], [xl, yl, zl])
|
([xh, yh, zh], [xl, yl, zl])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn oriented_text_corners(
|
||||||
|
verts: &[crate::scene::pipeline::text_gpu::TextVertex],
|
||||||
|
origin: [f64; 2],
|
||||||
|
rotation: f64,
|
||||||
|
pad: f64,
|
||||||
|
) -> [[f64; 2]; 4] {
|
||||||
|
let (sin_r, cos_r) = rotation.sin_cos();
|
||||||
|
let mut bounds = [f64::MAX, f64::MAX, f64::MIN, f64::MIN];
|
||||||
|
for vertex in verts {
|
||||||
|
let x = vertex.pos[0] as f64 + vertex.pos_low[0] as f64 - origin[0];
|
||||||
|
let y = vertex.pos[1] as f64 + vertex.pos_low[1] as f64 - origin[1];
|
||||||
|
let local_x = x * cos_r + y * sin_r;
|
||||||
|
let local_y = -x * sin_r + y * cos_r;
|
||||||
|
bounds[0] = bounds[0].min(local_x);
|
||||||
|
bounds[1] = bounds[1].min(local_y);
|
||||||
|
bounds[2] = bounds[2].max(local_x);
|
||||||
|
bounds[3] = bounds[3].max(local_y);
|
||||||
|
}
|
||||||
|
let [left, bottom, right, top] = [
|
||||||
|
bounds[0] - pad,
|
||||||
|
bounds[1] - pad,
|
||||||
|
bounds[2] + pad,
|
||||||
|
bounds[3] + pad,
|
||||||
|
];
|
||||||
|
let to_world = |x: f64, y: f64| {
|
||||||
|
[
|
||||||
|
origin[0] + x * cos_r - y * sin_r,
|
||||||
|
origin[1] + x * sin_r + y * cos_r,
|
||||||
|
]
|
||||||
|
};
|
||||||
|
[
|
||||||
|
to_world(left, bottom),
|
||||||
|
to_world(right, bottom),
|
||||||
|
to_world(right, top),
|
||||||
|
to_world(left, top),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn explicit_mtext_background(entity: &EntityType) -> Option<[f32; 4]> {
|
||||||
|
let EntityType::MText(text) = entity else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if text.background_fill_flags & 0x01 == 0 || text.background_fill_flags & 0x02 != 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
text.background_color.rgb().map(|(r, g, b)| {
|
||||||
|
[
|
||||||
|
r as f32 / 255.0,
|
||||||
|
g as f32 / 255.0,
|
||||||
|
b as f32 / 255.0,
|
||||||
|
1.0,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn text_contrast_background(
|
||||||
|
entity: &EntityType,
|
||||||
|
canvas: [f32; 4],
|
||||||
|
) -> [f32; 4] {
|
||||||
|
explicit_mtext_background(entity).unwrap_or(canvas)
|
||||||
|
}
|
||||||
|
|
||||||
/// Split each absolute f64 source point into double-single (high, low) f32
|
/// Split each absolute f64 source point into double-single (high, low) f32
|
||||||
/// buffers in one pass — the relative-to-eye residual the GPU/CPU reconstruct
|
/// buffers in one pass — the relative-to-eye residual the GPU/CPU reconstruct
|
||||||
/// to f64 precision at UTM-scale coordinates.
|
/// to f64 precision at UTM-scale coordinates.
|
||||||
|
|
@ -440,13 +502,7 @@ pub fn tessellate(
|
||||||
let entity_zf = entity_z(entity) as f64;
|
let entity_zf = entity_z(entity) as f64;
|
||||||
let elev_v = entity_zf;
|
let elev_v = entity_zf;
|
||||||
|
|
||||||
// Annotation scaling must preserve the entity's geometric attachment point.
|
// Scale MTEXT around its attachment point.
|
||||||
//
|
|
||||||
// MTEXT's insertion_point is its attachment anchor (TopLeft, TopCenter,
|
|
||||||
// BottomRight, etc.). Scale every laid-out run around that point so the
|
|
||||||
// displayed text remains attached to the same grip at every annotation scale.
|
|
||||||
//
|
|
||||||
// Other text-like entities keep the existing first-run origin behaviour.
|
|
||||||
let ref_origin = match entity {
|
let ref_origin = match entity {
|
||||||
EntityType::MText(m) => [
|
EntityType::MText(m) => [
|
||||||
m.insertion_point.x,
|
m.insertion_point.x,
|
||||||
|
|
@ -576,6 +632,10 @@ pub fn tessellate(
|
||||||
.color
|
.color
|
||||||
.map(|c| [c[0], c[1], c[2], entity_color[3]])
|
.map(|c| [c[0], c[1], c[2], entity_color[3]])
|
||||||
.unwrap_or(entity_color);
|
.unwrap_or(entity_color);
|
||||||
|
let gcolor = crate::scene::view::render::adapt_to_bg(
|
||||||
|
gcolor,
|
||||||
|
text_contrast_background(entity, bg_color),
|
||||||
|
);
|
||||||
let quads = crate::scene::text::glyph_quads::layout_glyph_quads(
|
let quads = crate::scene::text::glyph_quads::layout_glyph_quads(
|
||||||
&mut atlas,
|
&mut atlas,
|
||||||
run.height,
|
run.height,
|
||||||
|
|
@ -606,14 +666,7 @@ pub fn tessellate(
|
||||||
.map(|[x, y, z]| [x, y, z])
|
.map(|[x, y, z]| [x, y, z])
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Pick box straight from the rendered glyph quads — the true
|
// Derive the pick box from the rendered glyph quads.
|
||||||
// text extent. entity_aabb is unreliable for MTEXT (its box sits
|
|
||||||
// beside the laid-out glyphs), so derive the AABB from
|
|
||||||
// `sdf_verts` (accumulate in f64, reconstruct high+low, then cast
|
|
||||||
// to f32 once) and stop the generic stamp clobbering it (tess.rs
|
|
||||||
// guards on `text_verts`). Computed here so both the bins-empty
|
|
||||||
// and the bins-non-empty (tolerance box + text) paths can stamp
|
|
||||||
// it on the SDF text wire.
|
|
||||||
let text_aabb = if !sdf_verts.is_empty() {
|
let text_aabb = if !sdf_verts.is_empty() {
|
||||||
let (mut nx, mut ny, mut xx, mut xy) =
|
let (mut nx, mut ny, mut xx, mut xy) =
|
||||||
(f64::MAX, f64::MAX, f64::MIN, f64::MIN);
|
(f64::MAX, f64::MAX, f64::MIN, f64::MIN);
|
||||||
|
|
@ -636,42 +689,33 @@ pub fn tessellate(
|
||||||
// also carries the glyph quads built above.
|
// also carries the glyph quads built above.
|
||||||
if bins.is_empty() {
|
if bins.is_empty() {
|
||||||
let mut wires: Vec<WireModel> = Vec::new();
|
let mut wires: Vec<WireModel> = Vec::new();
|
||||||
// MTEXT background fill / mask: an opaque rectangle behind
|
// MTEXT background and frame follow the glyph bounds.
|
||||||
// the glyphs, emitted first so it renders under the text.
|
|
||||||
// Flag 0x01 → the entity's background-fill colour; 0x02 →
|
|
||||||
// the drawing-window (canvas) colour, which masks geometry
|
|
||||||
// behind the text like a wipeout. Box = glyph bounds padded
|
|
||||||
// by (background_scale - 1) × text height.
|
|
||||||
if text_aabb != WireModel::UNBOUNDED_AABB {
|
if text_aabb != WireModel::UNBOUNDED_AABB {
|
||||||
if let EntityType::MText(m) = entity {
|
if let EntityType::MText(m) = entity {
|
||||||
let has_fill = m.background_fill_flags & 0x03 != 0;
|
let has_fill = m.background_fill_flags & 0x03 != 0;
|
||||||
let has_frame = m.background_fill_flags & 0x10 != 0;
|
let has_frame = m.background_fill_flags & 0x10 != 0;
|
||||||
if has_fill || has_frame {
|
if has_fill || has_frame {
|
||||||
// Padded box shared by the fill and the frame.
|
let text_rotation = stroke_groups
|
||||||
let th = (m.height * anno) as f32;
|
.iter()
|
||||||
let pad = ((m.background_scale as f32) - 1.0).max(0.0) * th;
|
.find_map(|group| {
|
||||||
let [bnx, bny, bxx, bxy] = text_aabb;
|
group.run.as_ref().map(|run| run.rotation as f64)
|
||||||
let (l, b, r, t) = (
|
})
|
||||||
(bnx - pad) as f64,
|
.unwrap_or(m.rotation);
|
||||||
(bny - pad) as f64,
|
let text_height = m.height * anno;
|
||||||
(bxx + pad) as f64,
|
let pad = (m.background_scale - 1.0).max(0.0) * text_height;
|
||||||
(bxy + pad) as f64,
|
let corners = oriented_text_corners(
|
||||||
|
&sdf_verts,
|
||||||
|
[m.insertion_point.x, m.insertion_point.y],
|
||||||
|
text_rotation,
|
||||||
|
pad,
|
||||||
);
|
);
|
||||||
// Fill / mask — two triangles behind the glyphs.
|
// Fill / mask — two triangles behind the glyphs.
|
||||||
if has_fill {
|
if has_fill {
|
||||||
// 0x02 (use the drawing-window colour) is a
|
|
||||||
// MASK — it wins when set, even alongside
|
|
||||||
// 0x01, so text flagged "drawing background"
|
|
||||||
// erases what's behind it (a dark box on a
|
|
||||||
// dark canvas = no visible colour), instead
|
|
||||||
// of painting the stored background_color. A
|
|
||||||
// plain 0x01 fill paints that colour.
|
|
||||||
let fill_color = if m.background_fill_flags & 0x02 != 0 {
|
let fill_color = if m.background_fill_flags & 0x02 != 0 {
|
||||||
bg_color
|
bg_color
|
||||||
} else {
|
} else {
|
||||||
color_or_inherit(&m.background_color, bg_color)
|
color_or_inherit(&m.background_color, bg_color)
|
||||||
};
|
};
|
||||||
let corners = [[l, b], [r, b], [r, t], [l, t]];
|
|
||||||
let mut ft = Vec::with_capacity(6);
|
let mut ft = Vec::with_capacity(6);
|
||||||
let mut ftl = Vec::with_capacity(6);
|
let mut ftl = Vec::with_capacity(6);
|
||||||
for &k in &[0usize, 1, 2, 0, 2, 3] {
|
for &k in &[0usize, 1, 2, 0, 2, 3] {
|
||||||
|
|
@ -713,8 +757,13 @@ pub fn tessellate(
|
||||||
// Text frame — a closed rectangle in the text
|
// Text frame — a closed rectangle in the text
|
||||||
// colour around the same box.
|
// colour around the same box.
|
||||||
if has_frame {
|
if has_frame {
|
||||||
let loop_xy =
|
let loop_xy = [
|
||||||
[[l, b], [r, b], [r, t], [l, t], [l, b]];
|
corners[0],
|
||||||
|
corners[1],
|
||||||
|
corners[2],
|
||||||
|
corners[3],
|
||||||
|
corners[0],
|
||||||
|
];
|
||||||
let mut fp = Vec::with_capacity(5);
|
let mut fp = Vec::with_capacity(5);
|
||||||
let mut fpl = Vec::with_capacity(5);
|
let mut fpl = Vec::with_capacity(5);
|
||||||
for &[x, y] in &loop_xy {
|
for &[x, y] in &loop_xy {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue