feat: add Text component with per-span styling#12
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a
Textvector component that shapes a sequence of styled spans through rustybuzz and emits one filledPathper glyph through the existingVectorGraphicpipeline. Per-span styling is expressed as overrides on eachTextSpan(fill/font/size/weight), and the shaped spans can be pulled out as independently placeable graphics for per-span effects.tellur-core::text(new):Text { font, size, weight, fill, spans: Vec<TextSpan> }, withTextSpanholdingtextplusOption<Paint>/Option<Arc<Font>>/Option<f32>/Option<Weight>overrides. Spans inherit any unset field from the enclosingText. Shaping runs throughrustybuzz::shape; glyph outlines come fromttf_parser::Face::outline_glyphand are converted toPathCommands. Line metrics (ascender/descender/line_gap) come from the base font.Font: owns the font bytes viaArc<Vec<u8>>plus aface_indexfor.ttccollections. Constructors:Font::from_bytes(bytes)/Font::from_bytes_indexed(bytes, index)Font::load_file(path)Font::find_by_name(name)/Font::find_by_name_with_weight(name, weight)viafontdbFont::sans_serif()/serif()/monospace()/cursive()/fantasy()viafontconfig(the same machineryfc-matchuses, so per-language / per-script overrides on the host are honoured rather than relying on a hardcoded fallback list).SANS_SERIF/SERIF/MONOSPACE/CURSIVE/FANTASYasLazyLock<Arc<Font>>: the OS resolution runs once on first access; subsequent uses are justArc::clone.Weightnewtype (CSS-style 100..900 withTHIN/ ... /BLACKconstants) drives the OpenTypewghtaxis viarustybuzz::Face::set_variations. Has visible effect on fonts whosewghtaxis renders correctly through ttf-parser; on others (or non-VF fonts), usefind_by_name_with_weightto load the per-weight file from the family instead.Text::into_spans() -> Vec<Placed<TextSpanGraphic>>decomposes a shaped line into one independently placed component per inputTextSpan. EachTextSpanGraphicis aVectorComponentwhose layout box matches the span's own width × the line height, so callers can pattern-matchlet [a, b, c] = text.into_spans().try_into().unwrap();and apply per-span effects (rasterize → outline / drop shadow / transform) before recomposing into a layer.tellur-core::layer:VectorLayer.sizeandLayer.sizechange fromVec2toOption<Vec2>.Noneauto-fits to the children's combined paint bounds —view_box.origin(vector) /paint_rect.origin(raster) tracks the bounding rect's origin so children at negative positions are kept inside the box rather than clipped.::fit()constructors mirror::new(size). Auto-fit children are laid out underConstraints::UNBOUNDED;Fill-mode children there collapse to zero on each axis, which is the expected interaction.tellur-core::placement:impl From<Placed<C>> for Placed<dyn VectorComponent>(and the raster counterpart) forC: VectorComponent + 'static, so concrete-typedPlaced<TextSpanGraphic>etc. flow intoVec<Placed<dyn _>>via.into()without a hand-written cast. This is what letsText::into_spans()'s output drop straight into aVectorLayer.rustybuzz(shaping, re-exportsttf-parser),fontdb(system font database forfind_by_name),fontconfig(FFI binding forfind_generic— the dev shell'sflake.nixgetspkgs.fontconfigso the linker resolveslibfontconfig).tellur-renderer/examples/text_to_png.rs(new) renders "Hello world!" with the middle span in red, centered on a 1920×1080 white canvas, font resolved via theSANS_SERIFstatic.