My goal is to place an inline box at the beginning of a non-first line. When I place it on an index of "\n" character then it will be placed at the end of the previous line. When I place it to +1 position, then it will be placed after first character.
Examples
Box at index 7
Box at index 8
Question: How to place an inline box at the beginning of the second line?
Images was generated by the following code:
use image::codecs::png::PngEncoder;
use image::{self, Pixel, Rgba, RgbaImage};
use parley::layout::{Alignment, Glyph, GlyphRun, Layout, PositionedLayoutItem};
use parley::style::{FontStack, StyleProperty};
use parley::{FontContext, InlineBox, LayoutContext};
use peniko::Color;
use std::fs::File;
use std::path::PathBuf;
use swash::scale::image::Content;
use swash::scale::{Render, ScaleContext, Scaler, Source, StrikeWith};
use swash::zeno;
use swash::FontRef;
use zeno::{Format, Vector};
fn main() {
// The text we are going to style and lay out
let text = String::from(
"Line 1\nLine 2\nLine 3",
);
// The display scale for HiDPI rendering
let display_scale = 1.0;
// Colours for rendering
let text_color = Color::rgb8(0, 0, 0);
let bg_color = Rgba([255, 255, 255, 255]);
// Padding around the output image
let padding = 20;
// Create a FontContext, LayoutContext and ScaleContext
//
// These are all intended to be constructed rarely (perhaps even once per app (or once per thread))
// and provide caches and scratch space to avoid allocations
let mut font_cx = FontContext::new();
let mut layout_cx = LayoutContext::new();
let mut scale_cx = ScaleContext::new();
// Setup some Parley text styles
let brush_style = StyleProperty::Brush(text_color);
let font_stack = FontStack::from("system-ui");
let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, display_scale);
// Set default text colour styles (set foreground text color)
builder.push_default(brush_style);
// Set default font family
builder.push_default(font_stack);
builder.push_default(StyleProperty::LineHeight(1.3));
builder.push_default(StyleProperty::FontSize(16.0));
builder.push_inline_box(InlineBox {
id: 0,
index: 8,
width: 50.0,
height: 50.0,
});
// Build the builder into a Layout
// let mut layout: Layout<Color> = builder.build(&text);
let mut layout: Layout<Color> = builder.build(&text);
// Perform layout (including bidi resolution and shaping) with start alignment
layout.break_all_lines(None);
layout.align(None, Alignment::Start);
// Create image to render into
let width = layout.width().ceil() as u32 + (padding * 2);
let height = layout.height().ceil() as u32 + (padding * 2);
let mut img = RgbaImage::from_pixel(width, height, bg_color);
// Iterate over laid out lines
for line in layout.lines() {
// Iterate over GlyphRun's within each line
for item in line.items() {
match item {
PositionedLayoutItem::GlyphRun(glyph_run) => {
render_glyph_run(&mut scale_cx, &glyph_run, &mut img, padding);
}
PositionedLayoutItem::InlineBox(inline_box) => {
for x_off in 0..(inline_box.width.floor() as u32) {
for y_off in 0..(inline_box.height.floor() as u32) {
let x = inline_box.x as u32 + x_off + padding;
let y = inline_box.y as u32 + y_off + padding;
img.put_pixel(x, y, Rgba([0, 0, 0, 255]));
}
}
}
};
}
}
// Write image to PNG file in examples/_output dir
let output_path = {
let mut path = PathBuf::new();
path.push("_output");
let _ = std::fs::create_dir(path.clone());
path.push("swash_render.png");
path
};
let output_file = File::create(output_path).unwrap();
let png_encoder = PngEncoder::new(output_file);
img.write_with_encoder(png_encoder).unwrap();
}
fn render_glyph_run(
context: &mut ScaleContext,
glyph_run: &GlyphRun<Color>,
img: &mut RgbaImage,
padding: u32,
) {
// Resolve properties of the GlyphRun
let mut run_x = glyph_run.offset();
let run_y = glyph_run.baseline();
let style = glyph_run.style();
let color = style.brush;
// Get the "Run" from the "GlyphRun"
let run = glyph_run.run();
// Resolve properties of the Run
let font = run.font();
let font_size = run.font_size();
let normalized_coords = run.normalized_coords();
// Convert from parley::Font to swash::FontRef
let font_ref = FontRef::from_index(font.data.as_ref(), font.index as usize).unwrap();
// Build a scaler. As the font properties are constant across an entire run of glyphs
// we can build one scaler for the run and reuse it for each glyph.
let mut scaler = context
.builder(font_ref)
.size(font_size)
.hint(true)
.normalized_coords(normalized_coords)
.build();
// Iterates over the glyphs in the GlyphRun
for glyph in glyph_run.glyphs() {
let glyph_x = run_x + glyph.x + (padding as f32);
let glyph_y = run_y - glyph.y + (padding as f32);
run_x += glyph.advance;
render_glyph(img, &mut scaler, color, glyph, glyph_x, glyph_y);
}
}
fn render_glyph(
img: &mut RgbaImage,
scaler: &mut Scaler,
color: Color,
glyph: Glyph,
glyph_x: f32,
glyph_y: f32,
) {
// Compute the fractional offset
// You'll likely want to quantize this in a real renderer
let offset = Vector::new(glyph_x.fract(), glyph_y.fract());
// Render the glyph using swash
let rendered_glyph = Render::new(
// Select our source order
&[
Source::ColorOutline(0),
Source::ColorBitmap(StrikeWith::BestFit),
Source::Outline,
],
)
// Select the simple alpha (non-subpixel) format
.format(Format::Alpha)
// Apply the fractional offset
.offset(offset)
// Render the image
.render(scaler, glyph.id)
.unwrap();
let glyph_width = rendered_glyph.placement.width;
let glyph_height = rendered_glyph.placement.height;
let glyph_x = (glyph_x.floor() as i32 + rendered_glyph.placement.left) as u32;
let glyph_y = (glyph_y.floor() as i32 - rendered_glyph.placement.top) as u32;
match rendered_glyph.content {
Content::Mask => {
let mut i = 0;
for pixel_y in 0..glyph_height {
for pixel_x in 0..glyph_width {
let x = glyph_x + pixel_x;
let y = glyph_y + pixel_y;
let alpha = rendered_glyph.data[i];
let color = Rgba([color.r, color.g, color.b, alpha]);
img.get_pixel_mut(x, y).blend(&color);
i += 1;
}
}
}
Content::SubpixelMask => unimplemented!(),
Content::Color => {
let row_size = glyph_width as usize * 4;
for (pixel_y, row) in rendered_glyph.data.chunks_exact(row_size).enumerate() {
for (pixel_x, pixel) in row.chunks_exact(4).enumerate() {
let x = glyph_x + pixel_x as u32;
let y = glyph_y + pixel_y as u32;
let color = Rgba(pixel.try_into().expect("Not RGBA"));
img.get_pixel_mut(x, y).blend(&color);
}
}
}
};
}
My goal is to place an inline box at the beginning of a non-first line. When I place it on an index of "\n" character then it will be placed at the end of the previous line. When I place it to +1 position, then it will be placed after first character.
Examples
Box at index 7
Box at index 8
Question: How to place an inline box at the beginning of the second line?
Images was generated by the following code: