///|
/// A line segment for rasterization
struct LineSegment {
x0 : Double
y0 : Double
x1 : Double
y1 : Double
} derive(Debug)
///|
pub extend LineSegment with Debug::{to_repr}
///|
priv struct CurvePoint {
cx : Double
cy : Double
curve_on : Bool
}
///|
/// Rasterizer for rendering glyph outlines to pixels
/// Implements scanline fill with anti-aliasing
struct Rasterizer {
width : Int
height : Int
pixels : FixedArray[Byte]
coverage : FixedArray[Int] // Coverage buffer for anti-aliasing
}
///|
/// Grayscale coverage mask produced by text rasterization
struct CoverageMask {
width : Int
height : Int
pixels : FixedArray[Byte]
} derive(Debug)
///|
pub extend CoverageMask with Debug::{to_repr}
///|
/// Create a coverage mask from grayscale pixels
pub fn CoverageMask::new(
width : Int,
height : Int,
pixels : FixedArray[Byte],
) -> CoverageMask {
{ width, height, pixels, }
}
///|
/// Get the width of the mask
pub fn CoverageMask::get_width(self : CoverageMask) -> Int {
self.width
}
///|
/// Get the height of the mask
pub fn CoverageMask::get_height(self : CoverageMask) -> Int {
self.height
}
///|
/// Get the grayscale coverage pixels
pub fn CoverageMask::get_pixels(self : CoverageMask) -> FixedArray[Byte] {
self.pixels
}
///|
/// Copy this coverage mask and its pixel storage.
pub fn CoverageMask::copy(self : CoverageMask) -> CoverageMask {
CoverageMask::new(self.width, self.height, copy_bytes(self.pixels))
}
///|
/// Placement of one glyph mask inside a glyph mask atlas.
struct GlyphAtlasEntry {
key : String
x : Int
y : Int
width : Int
height : Int
} derive(Debug)
///|
pub extend GlyphAtlasEntry with Debug::{to_repr}
///|
/// Result of inserting a glyph mask into an atlas with lifecycle policy.
struct GlyphMaskAtlasInsertResult {
entry : GlyphAtlasEntry
did_clear : Bool
did_reuse : Bool
} derive(Debug)
///|
pub extend GlyphMaskAtlasInsertResult with Debug::{to_repr}
///|
priv struct GlyphAtlasPlacement {
x : Int
y : Int
row_height : Int
}
///|
/// Create a glyph atlas entry.
pub fn GlyphAtlasEntry::new(
key~ : String,
x~ : Int,
y~ : Int,
width~ : Int,
height~ : Int,
) -> GlyphAtlasEntry {
{ key, x, y, width, height, }
}
///|
/// Get the caller-provided glyph key for this atlas entry.
pub fn GlyphAtlasEntry::get_key(self : GlyphAtlasEntry) -> String {
self.key
}
///|
/// Get the atlas x coordinate.
pub fn GlyphAtlasEntry::get_x(self : GlyphAtlasEntry) -> Int {
self.x
}
///|
/// Get the atlas y coordinate.
pub fn GlyphAtlasEntry::get_y(self : GlyphAtlasEntry) -> Int {
self.y
}
///|
/// Get the entry width.
pub fn GlyphAtlasEntry::get_width(self : GlyphAtlasEntry) -> Int {
self.width
}
///|
/// Get the entry height.
pub fn GlyphAtlasEntry::get_height(self : GlyphAtlasEntry) -> Int {
self.height
}
///|
/// Get the placed atlas entry.
pub fn GlyphMaskAtlasInsertResult::get_entry(
self : GlyphMaskAtlasInsertResult,
) -> GlyphAtlasEntry {
self.entry
}
///|
/// Return whether the atlas was cleared before this entry was inserted.
pub fn GlyphMaskAtlasInsertResult::did_clear(
self : GlyphMaskAtlasInsertResult,
) -> Bool {
self.did_clear
}
///|
/// Return whether this result reused an existing atlas entry.
pub fn GlyphMaskAtlasInsertResult::did_reuse(
self : GlyphMaskAtlasInsertResult,
) -> Bool {
self.did_reuse
}
///|
/// Small row-packed atlas for rendered glyph coverage masks.
struct GlyphMaskAtlas {
width : Int
height : Int
pixels : FixedArray[Byte]
mut entries : Map[String, GlyphAtlasEntry]
mut cursor_x : Int
mut cursor_y : Int
mut row_height : Int
mut used_pixel_count : Int
} derive(Debug)
///|
pub extend GlyphMaskAtlas with Debug::{to_repr}
///|
/// Create an empty glyph mask atlas.
pub fn GlyphMaskAtlas::new(width : Int, height : Int) -> GlyphMaskAtlas {
{
width,
height,
pixels: FixedArray::make(width * height, b"\x00"[0]),
entries: Map([]),
cursor_x: 0,
cursor_y: 0,
row_height: 0,
used_pixel_count: 0,
}
}
///|
/// Get the atlas width.
pub fn GlyphMaskAtlas::get_width(self : GlyphMaskAtlas) -> Int {
self.width
}
///|
/// Get the atlas height.
pub fn GlyphMaskAtlas::get_height(self : GlyphMaskAtlas) -> Int {
self.height
}
///|
/// Return the total pixel capacity of the atlas.
pub fn GlyphMaskAtlas::get_capacity(self : GlyphMaskAtlas) -> Int {
self.width * self.height
}
///|
/// Return the number of atlas pixels occupied by placed glyph masks.
pub fn GlyphMaskAtlas::get_used_pixel_count(self : GlyphMaskAtlas) -> Int {
self.used_pixel_count
}
///|
/// Return atlas capacity minus placed mask pixels.
pub fn GlyphMaskAtlas::get_free_pixel_count(self : GlyphMaskAtlas) -> Int {
self.get_capacity() - self.used_pixel_count
}
///|
/// Return placed mask pixels divided by total atlas capacity.
pub fn GlyphMaskAtlas::get_occupancy_ratio(self : GlyphMaskAtlas) -> Double {
let capacity = self.get_capacity()
if capacity <= 0 {
0.0
} else {
self.used_pixel_count.to_double() / capacity.to_double()
}
}
///|
/// Return the number of placed glyph masks.
pub fn GlyphMaskAtlas::len(self : GlyphMaskAtlas) -> Int {
self.entries.length()
}
///|
/// Return whether the atlas has no placed glyph masks.
pub fn GlyphMaskAtlas::is_empty(self : GlyphMaskAtlas) -> Bool {
self.len() is 0
}
///|
/// Look up a placed glyph mask by key.
pub fn GlyphMaskAtlas::get(
self : GlyphMaskAtlas,
key : String,
) -> GlyphAtlasEntry? {
self.entries.get(key)
}
///|
/// Snapshot the full atlas pixels as a coverage mask.
pub fn GlyphMaskAtlas::to_mask(self : GlyphMaskAtlas) -> CoverageMask {
CoverageMask::new(self.width, self.height, copy_bytes(self.pixels))
}
///|
/// Return whether this mask can be placed as a new atlas entry.
pub fn GlyphMaskAtlas::can_fit(
self : GlyphMaskAtlas,
mask : CoverageMask,
) -> Bool {
match self.next_placement(mask) {
Some(_) => true
None => false
}
}
///|
/// Insert a rendered glyph mask if there is space, or return the existing entry.
pub fn GlyphMaskAtlas::insert(
self : GlyphMaskAtlas,
key : String,
mask : CoverageMask,
) -> GlyphAtlasEntry? {
match self.get(key) {
Some(entry) => Some(entry)
None => self.place(key, mask)
}
}
///|
/// Insert a mask, clearing the atlas first if the current row-packing state is full.
/// When the result reports `did_clear`, previous atlas entries are no longer valid.
pub fn GlyphMaskAtlas::insert_or_clear(
self : GlyphMaskAtlas,
key : String,
mask : CoverageMask,
) -> GlyphMaskAtlasInsertResult? {
match self.get(key) {
Some(entry) => Some({ entry, did_clear: false, did_reuse: true, })
None =>
match self.place(key, mask) {
Some(entry) => Some({ entry, did_clear: false, did_reuse: false, })
None => {
guard self.can_fit_empty(mask) else { return None }
self.clear()
match self.place(key, mask) {
Some(entry) => Some({ entry, did_clear: true, did_reuse: false, })
None => None
}
}
}
}
}
///|
/// Remove all atlas entries and clear atlas pixels.
pub fn GlyphMaskAtlas::clear(self : GlyphMaskAtlas) -> Unit {
self.entries = Map([])
self.cursor_x = 0
self.cursor_y = 0
self.row_height = 0
self.used_pixel_count = 0
for i in 0.. GlyphAtlasPlacement? {
let mask_width = mask.get_width()
let mask_height = mask.get_height()
guard self.can_fit_empty(mask) else { return None }
let should_wrap = self.cursor_x + mask_width > self.width
let (entry_x, entry_y, current_row_height) = if should_wrap {
(0, self.cursor_y + self.row_height, 0)
} else {
(self.cursor_x, self.cursor_y, self.row_height)
}
guard entry_y + mask_height <= self.height else { return None }
Some({ x: entry_x, y: entry_y, row_height: current_row_height, })
}
///|
fn GlyphMaskAtlas::can_fit_empty(
self : GlyphMaskAtlas,
mask : CoverageMask,
) -> Bool {
let mask_width = mask.get_width()
let mask_height = mask.get_height()
mask_width > 0 &&
mask_height > 0 &&
mask.get_pixels().length() >= mask_width * mask_height &&
mask_width <= self.width &&
mask_height <= self.height
}
///|
fn GlyphMaskAtlas::place(
self : GlyphMaskAtlas,
key : String,
mask : CoverageMask,
) -> GlyphAtlasEntry? {
let mask_width = mask.get_width()
let mask_height = mask.get_height()
let placement = match self.next_placement(mask) {
Some(placement) => placement
None => return None
}
let entry = GlyphAtlasEntry::new(
key~,
x=placement.x,
y=placement.y,
width=mask_width,
height=mask_height,
)
copy_mask_into_atlas(self, entry, mask)
self.entries[key] = entry
self.cursor_x = placement.x + mask_width
self.cursor_y = placement.y
self.row_height = placement.row_height.max(mask_height)
self.used_pixel_count += mask_width * mask_height
Some(entry)
}
///|
fn copy_mask_into_atlas(
atlas : GlyphMaskAtlas,
entry : GlyphAtlasEntry,
mask : CoverageMask,
) -> Unit {
let src = mask.get_pixels()
for y in 0.. GlyphMaskCache {
{ masks: Map([]), access_order: [], capacity: 0, is_bounded: false, }
}
///|
/// Create an empty glyph mask cache with least-recently-used eviction.
pub fn GlyphMaskCache::with_capacity(capacity : Int) -> GlyphMaskCache {
{
masks: Map([]),
access_order: [],
capacity: capacity.max(1),
is_bounded: true,
}
}
///|
/// Get the resulting glyph coverage mask.
fn GlyphMaskCacheRasterizeResult::get_mask(
self : GlyphMaskCacheRasterizeResult,
) -> CoverageMask {
self.mask
}
///|
/// Return whether this lookup rasterized a new mask instead of hitting cache.
fn GlyphMaskCacheRasterizeResult::did_rasterize(
self : GlyphMaskCacheRasterizeResult,
) -> Bool {
self.did_rasterize
}
///|
/// Get the resulting face glyph coverage mask.
pub fn FaceGlyphMaskCacheResult::get_mask(
self : FaceGlyphMaskCacheResult,
) -> CoverageMask {
self.mask
}
///|
/// Get placement data for the resulting face glyph coverage mask.
pub fn FaceGlyphMaskCacheResult::get_placement(
self : FaceGlyphMaskCacheResult,
) -> FaceGlyphMaskPlacement {
self.placement
}
///|
/// Return whether this lookup rasterized a new face glyph mask.
pub fn FaceGlyphMaskCacheResult::did_rasterize(
self : FaceGlyphMaskCacheResult,
) -> Bool {
self.did_rasterize
}
///|
/// Get the mask width for this placement.
pub fn FaceGlyphMaskPlacement::get_width(self : FaceGlyphMaskPlacement) -> Int {
self.width
}
///|
/// Get the mask height for this placement.
pub fn FaceGlyphMaskPlacement::get_height(self : FaceGlyphMaskPlacement) -> Int {
self.height
}
///|
/// Get the raster origin X coordinate for this placement.
pub fn FaceGlyphMaskPlacement::get_origin_x(
self : FaceGlyphMaskPlacement,
) -> Double {
self.origin_x
}
///|
/// Get the raster origin Y coordinate for this placement.
pub fn FaceGlyphMaskPlacement::get_origin_y(
self : FaceGlyphMaskPlacement,
) -> Double {
self.origin_y
}
///|
/// Get the draw offset X coordinate for this placement.
pub fn FaceGlyphMaskPlacement::get_draw_offset_x(
self : FaceGlyphMaskPlacement,
) -> Int {
self.draw_offset_x
}
///|
/// Get the draw offset Y coordinate for this placement.
pub fn FaceGlyphMaskPlacement::get_draw_offset_y(
self : FaceGlyphMaskPlacement,
) -> Int {
self.draw_offset_y
}
///|
/// Return the number of cached glyph masks.
pub fn GlyphMaskCache::len(self : GlyphMaskCache) -> Int {
self.masks.length()
}
///|
/// Return whether the cache currently has no glyph masks.
pub fn GlyphMaskCache::is_empty(self : GlyphMaskCache) -> Bool {
self.len() is 0
}
///|
/// Return the maximum number of cached glyph masks, or 0 for an unbounded cache.
pub fn GlyphMaskCache::capacity(self : GlyphMaskCache) -> Int {
self.capacity
}
///|
/// Return whether the cache has a configured entry limit.
pub fn GlyphMaskCache::is_capacity_bounded(self : GlyphMaskCache) -> Bool {
self.is_bounded
}
///|
/// Return whether the cache has a glyph mask for this key without refreshing LRU.
pub fn GlyphMaskCache::contains(self : GlyphMaskCache, key : String) -> Bool {
self.masks.contains(key)
}
///|
/// Look up a rendered glyph mask by caller-provided key.
pub fn GlyphMaskCache::get(
self : GlyphMaskCache,
key : String,
) -> CoverageMask? {
match self.masks.get(key) {
Some(mask) => {
self.touch_key(key)
Some(mask.copy())
}
None => None
}
}
///|
/// Insert or replace a rendered glyph mask under a caller-provided key.
pub fn GlyphMaskCache::insert(
self : GlyphMaskCache,
key : String,
mask : CoverageMask,
) -> CoverageMask {
let stored = mask.copy()
self.masks[key] = stored
self.touch_key(key)
self.evict_over_capacity()
stored.copy()
}
///|
/// Rasterize a glyph on first use and report whether this lookup missed cache.
fn GlyphMaskCache::get_or_rasterize_glyph_result(
self : GlyphMaskCache,
key : String,
glyph : SimpleGlyph,
width : Int,
height : Int,
x : Double,
y : Double,
scale : Double,
) -> GlyphMaskCacheRasterizeResult {
match self.get(key) {
Some(mask) => { mask, did_rasterize: false, }
None => {
let mask = self.insert(
key,
render_glyph_mask(glyph, width, height, x, y, scale),
)
{ mask, did_rasterize: true, }
}
}
}
///|
/// Rasterize a glyph from a parsed font face on first use and report cache state.
pub fn GlyphMaskCache::get_or_rasterize_face_glyph_result(
self : GlyphMaskCache,
key : String,
face : FontFace,
glyph_id : Int,
scale : Double,
) -> FaceGlyphMaskCacheResult? {
match face.get_glyph(glyph_id) {
Simple(simple_glyph) =>
match make_face_glyph_mask_placement(simple_glyph, scale) {
Some(placement) => {
let result = self.get_or_rasterize_glyph_result(
key,
simple_glyph,
placement.width,
placement.height,
placement.origin_x,
placement.origin_y,
scale,
)
Some({
mask: result.get_mask(),
placement,
did_rasterize: result.did_rasterize(),
})
}
None => None
}
_ => None
}
}
///|
fn make_face_glyph_mask_placement(
glyph : SimpleGlyph,
scale : Double,
) -> FaceGlyphMaskPlacement? {
guard scale > 0.0 && glyph.contours.length() > 0 else { return None }
let left = floor_to_int(glyph.xmin.to_double() * scale)
let right = ceil_to_int(glyph.xmax.to_double() * scale)
let top = floor_to_int(0.0 - glyph.ymax.to_double() * scale)
let bottom = ceil_to_int(0.0 - glyph.ymin.to_double() * scale)
guard right > left && bottom > top else { return None }
Some({
width: right - left,
height: bottom - top,
origin_x: (0 - left).to_double(),
origin_y: (0 - top).to_double(),
draw_offset_x: left,
draw_offset_y: top,
})
}
///|
/// Remove a cached glyph mask by key.
pub fn GlyphMaskCache::remove(self : GlyphMaskCache, key : String) -> Unit {
self.masks.remove(key)
self.remove_access_key(key)
}
///|
/// Drop all cached glyph masks.
pub fn GlyphMaskCache::clear(self : GlyphMaskCache) -> Unit {
self.masks = Map([])
self.access_order = []
}
///|
fn GlyphMaskCache::touch_key(self : GlyphMaskCache, key : String) -> Unit {
self.remove_access_key(key)
self.access_order.push(key)
}
///|
fn GlyphMaskCache::remove_access_key(
self : GlyphMaskCache,
key : String,
) -> Unit {
let mut index = 0
while index < self.access_order.length() {
if self.access_order[index] == key {
ignore(self.access_order.remove(index))
} else {
index += 1
}
}
}
///|
fn GlyphMaskCache::evict_over_capacity(self : GlyphMaskCache) -> Unit {
guard self.is_bounded && self.capacity > 0 else { return }
while self.masks.length() > self.capacity && self.access_order.length() > 0 {
let key = self.access_order.remove(0)
self.masks.remove(key)
}
}
///|
/// Small keyed cache for rendered text coverage masks.
struct TextMaskCache {
mut masks : Map[String, CoverageMask]
mut access_order : Array[String]
capacity : Int
is_bounded : Bool
} derive(Debug)
///|
pub extend TextMaskCache with Debug::{to_repr}
///|
/// Result of reading a rendered text mask from cache or rendering it on miss.
struct TextMaskCacheRenderResult {
mask : CoverageMask
did_render : Bool
} derive(Debug)
///|
pub extend TextMaskCacheRenderResult with Debug::{to_repr}
///|
/// Create an empty text mask cache.
pub fn TextMaskCache::new() -> TextMaskCache {
{ masks: Map([]), access_order: [], capacity: 0, is_bounded: false, }
}
///|
/// Create an empty text mask cache with least-recently-used eviction.
pub fn TextMaskCache::with_capacity(capacity : Int) -> TextMaskCache {
{
masks: Map([]),
access_order: [],
capacity: capacity.max(1),
is_bounded: true,
}
}
///|
/// Get the resulting text coverage mask.
pub fn TextMaskCacheRenderResult::get_mask(
self : TextMaskCacheRenderResult,
) -> CoverageMask {
self.mask
}
///|
/// Return whether this lookup rendered a new mask instead of hitting cache.
pub fn TextMaskCacheRenderResult::did_render(
self : TextMaskCacheRenderResult,
) -> Bool {
self.did_render
}
///|
/// Return the number of cached masks.
pub fn TextMaskCache::len(self : TextMaskCache) -> Int {
self.masks.length()
}
///|
/// Return whether the cache currently has no masks.
pub fn TextMaskCache::is_empty(self : TextMaskCache) -> Bool {
self.len() is 0
}
///|
/// Return the maximum number of cached text masks, or 0 for an unbounded cache.
pub fn TextMaskCache::capacity(self : TextMaskCache) -> Int {
self.capacity
}
///|
/// Return whether the cache has a configured entry limit.
pub fn TextMaskCache::is_capacity_bounded(self : TextMaskCache) -> Bool {
self.is_bounded
}
///|
/// Return whether the cache has a text mask for this key without refreshing LRU.
pub fn TextMaskCache::contains(self : TextMaskCache, key : String) -> Bool {
self.masks.contains(key)
}
///|
/// Look up a rendered mask by caller-provided key.
pub fn TextMaskCache::get(self : TextMaskCache, key : String) -> CoverageMask? {
match self.masks.get(key) {
Some(mask) => {
self.touch_key(key)
Some(mask.copy())
}
None => None
}
}
///|
/// Insert or replace a rendered mask under a caller-provided key.
pub fn TextMaskCache::insert(
self : TextMaskCache,
key : String,
mask : CoverageMask,
) -> CoverageMask {
let stored = mask.copy()
self.masks[key] = stored
self.touch_key(key)
self.evict_over_capacity()
stored.copy()
}
///|
/// Render text from a checked face on first use and return the cached mask afterwards.
pub fn TextMaskCache::get_or_render_face(
self : TextMaskCache,
key : String,
face : FontFace,
text : String,
width : Int,
height : Int,
x : Double,
y : Double,
config : LayoutConfig,
) -> CoverageMask {
self
.get_or_render_face_result(key, face, text, width, height, x, y, config)
.get_mask()
}
///|
/// Render text from a checked face on first use and report whether cache missed.
pub fn TextMaskCache::get_or_render_face_result(
self : TextMaskCache,
key : String,
face : FontFace,
text : String,
width : Int,
height : Int,
x : Double,
y : Double,
config : LayoutConfig,
) -> TextMaskCacheRenderResult {
match self.get(key) {
Some(mask) => { mask, did_render: false, }
None => {
let mask = self.insert(
key,
render_text_mask_face(face, text, width, height, x, y, config),
)
{ mask, did_render: true, }
}
}
}
///|
/// Remove a cached mask by key.
pub fn TextMaskCache::remove(self : TextMaskCache, key : String) -> Unit {
self.masks.remove(key)
self.remove_access_key(key)
}
///|
/// Drop all cached masks.
pub fn TextMaskCache::clear(self : TextMaskCache) -> Unit {
self.masks = Map([])
self.access_order = []
}
///|
fn TextMaskCache::touch_key(self : TextMaskCache, key : String) -> Unit {
self.remove_access_key(key)
self.access_order.push(key)
}
///|
fn TextMaskCache::remove_access_key(self : TextMaskCache, key : String) -> Unit {
let mut index = 0
while index < self.access_order.length() {
if self.access_order[index] == key {
ignore(self.access_order.remove(index))
} else {
index += 1
}
}
}
///|
fn TextMaskCache::evict_over_capacity(self : TextMaskCache) -> Unit {
guard self.is_bounded && self.capacity > 0 else { return }
while self.masks.length() > self.capacity && self.access_order.length() > 0 {
let key = self.access_order.remove(0)
self.masks.remove(key)
}
}
///|
/// Create a new rasterizer
pub fn Rasterizer::new(width : Int, height : Int) -> Rasterizer {
let size = width * height
{
width,
height,
pixels: FixedArray::make(size, b"\x00"[0]),
coverage: FixedArray::make(size, 0),
}
}
///|
/// Get the pixel buffer (grayscale)
pub fn Rasterizer::get_pixels(self : Rasterizer) -> FixedArray[Byte] {
self.pixels
}
///|
/// Get the rasterizer width
pub fn Rasterizer::get_width(self : Rasterizer) -> Int {
self.width
}
///|
/// Get the rasterizer height
pub fn Rasterizer::get_height(self : Rasterizer) -> Int {
self.height
}
///|
/// Snapshot the current grayscale pixels as a coverage mask
pub fn Rasterizer::to_mask(self : Rasterizer) -> CoverageMask {
CoverageMask::new(self.width, self.height, copy_bytes(self.pixels))
}
///|
/// Clear the buffer
pub fn Rasterizer::clear(self : Rasterizer) -> Unit {
for i in 0.. Unit {
// Clear coverage buffer
for i in 0.. Unit {
let font = face.get_font()
let layout = layout_text_face(face, text, config)
let scale = config.font_size / face.get_units_per_em().to_double()
for run in layout.get_runs() {
for glyph in run.get_glyphs() {
match font.get_glyph(glyph.glyph_id) {
Simple(simple_glyph) =>
self.rasterize_glyph(
simple_glyph,
x + glyph.x,
y + run.get_baseline_y(),
scale,
)
_ => ()
}
}
}
}
///|
/// Render one glyph into a grayscale coverage mask.
fn render_glyph_mask(
glyph : SimpleGlyph,
width : Int,
height : Int,
x : Double,
y : Double,
scale : Double,
) -> CoverageMask {
let rasterizer = Rasterizer::new(width, height)
rasterizer.rasterize_glyph(glyph, x, y, scale)
rasterizer.to_mask()
}
///|
/// Render text from a parsed font face into a grayscale coverage mask.
pub fn render_text_mask_face(
face : FontFace,
text : String,
width : Int,
height : Int,
x : Double,
y : Double,
config : LayoutConfig,
) -> CoverageMask {
let rasterizer = Rasterizer::new(width, height)
rasterizer.rasterize_text_face(face, text, x, y, config)
rasterizer.to_mask()
}
///|
fn flatten_contour(
contour : GlyphContour,
x : Double,
y : Double,
scale : Double,
) -> Array[LineSegment] {
let points = contour.points
let segments = []
let len = points.length()
guard len >= 2 else { return segments }
let first = transform_point(points[0], x, y, scale)
let last = transform_point(points[len - 1], x, y, scale)
let mut current = if first.curve_on {
first
} else if last.curve_on {
last
} else {
midpoint(last, first)
}
let mut index = if first.curve_on { 1 } else { 0 }
let mut processed = 0
while processed < len {
let point = transform_point(points[index % len], x, y, scale)
if point.curve_on {
segments.push({
x0: current.cx,
y0: current.cy,
x1: point.cx,
y1: point.cy,
})
current = point
index = index + 1
processed = processed + 1
} else {
let next = transform_point(points[(index + 1) % len], x, y, scale)
if next.curve_on {
push_quadratic_segments(segments, current, point, next)
current = next
index = index + 2
processed = processed + 2
} else {
let implicit = midpoint(point, next)
push_quadratic_segments(segments, current, point, implicit)
current = implicit
index = index + 1
processed = processed + 1
}
}
}
segments
}
///|
fn transform_point(
point : GlyphPoint,
x : Double,
y : Double,
scale : Double,
) -> CurvePoint {
{
cx: x + point.x.to_double() * scale,
cy: y - point.y.to_double() * scale,
curve_on: point.on_curve,
}
}
///|
fn midpoint(a : CurvePoint, b : CurvePoint) -> CurvePoint {
{ cx: (a.cx + b.cx) / 2.0, cy: (a.cy + b.cy) / 2.0, curve_on: true, }
}
///|
fn push_quadratic_segments(
segments : Array[LineSegment],
start : CurvePoint,
control : CurvePoint,
end : CurvePoint,
) -> Unit {
let bezier_segments = flatten_quadratic_bezier(
start.cx,
start.cy,
control.cx,
control.cy,
end.cx,
end.cy,
)
for segment in bezier_segments {
segments.push(segment)
}
}
///|
/// Flatten a quadratic bezier curve into line segments
fn flatten_quadratic_bezier(
x0 : Double,
y0 : Double,
x1 : Double,
y1 : Double,
x2 : Double,
y2 : Double,
) -> Array[LineSegment] {
let segments = []
let steps = calculate_bezier_steps(x0, y0, x1, y1, x2, y2)
let mut prev_x = x0
let mut prev_y = y0
for i in 1..<=steps {
let t = i.to_double() / steps.to_double()
let mt = 1.0 - t
let px = mt * mt * x0 + 2.0 * mt * t * x1 + t * t * x2
let py = mt * mt * y0 + 2.0 * mt * t * y1 + t * t * y2
segments.push({ x0: prev_x, y0: prev_y, x1: px, y1: py, })
prev_x = px
prev_y = py
}
segments
}
///|
/// Calculate number of steps needed for bezier flattening based on curve complexity
fn calculate_bezier_steps(
x0 : Double,
y0 : Double,
x1 : Double,
y1 : Double,
x2 : Double,
y2 : Double,
) -> Int {
// Calculate deviation from straight line
let dx = x2 - x0
let dy = y2 - y0
let d = dx * dx + dy * dy
// If very short, use few steps
if d < 4.0 {
return 2
}
// Calculate control point deviation
let mid_x = (x0 + x2) / 2.0
let mid_y = (y0 + y2) / 2.0
let dev_x = x1 - mid_x
let dev_y = y1 - mid_y
let deviation = dev_x * dev_x + dev_y * dev_y
// More steps for higher deviation
if deviation > 100.0 {
32
} else if deviation > 25.0 {
16
} else if deviation > 4.0 {
8
} else {
4
}
}
///|
/// Scanline fill with non-zero winding rule and 4x4 supersampling anti-aliasing
fn scanline_fill_with_aa(
rasterizer : Rasterizer,
segments : Array[LineSegment],
) -> Unit {
guard segments.length() > 0 else { return }
// Find bounding box
let mut min_y = segments[0].y0
let mut max_y = segments[0].y0
for seg in segments {
if seg.y0 < min_y {
min_y = seg.y0
}
if seg.y1 < min_y {
min_y = seg.y1
}
if seg.y0 > max_y {
max_y = seg.y0
}
if seg.y1 > max_y {
max_y = seg.y1
}
}
let y_start = min_y.to_int().max(0)
let y_end = (max_y.to_int() + 1).min(rasterizer.height)
let x_start = min_x(segments).to_int().max(0)
let x_end = max_x(segments).to_int().min(rasterizer.width)
// 4x4 supersampling for anti-aliasing
let sample_count = 4
let sample_count_sq = sample_count * sample_count // 16
// For each pixel in the bounding box
for y in y_start.. 0 {
let idx = y * rasterizer.width + x
rasterizer.coverage[idx] = covered * 255 / sample_count_sq
}
}
}
// Apply coverage to pixels
for i in 0.. 0 {
let coverage = rasterizer.coverage[i].min(255).to_byte()
if coverage.to_int() > rasterizer.pixels[i].to_int() {
rasterizer.pixels[i] = coverage
}
}
}
}
///|
fn copy_bytes(src : FixedArray[Byte]) -> FixedArray[Byte] {
let dst = FixedArray::make(src.length(), b"\x00"[0])
for i in 0.. Int {
let truncated = value.to_int()
if truncated.to_double() > value {
truncated - 1
} else {
truncated
}
}
///|
fn ceil_to_int(value : Double) -> Int {
let truncated = value.to_int()
if truncated.to_double() < value {
truncated + 1
} else {
truncated
}
}
///|
/// Get minimum x coordinate from segments
fn min_x(segments : Array[LineSegment]) -> Double {
let mut min_val = segments[0].x0
for seg in segments {
if seg.x0 < min_val {
min_val = seg.x0
}
if seg.x1 < min_val {
min_val = seg.x1
}
}
min_val
}
///|
/// Get maximum x coordinate from segments
fn max_x(segments : Array[LineSegment]) -> Double {
let mut max_val = segments[0].x0
for seg in segments {
if seg.x0 > max_val {
max_val = seg.x0
}
if seg.x1 > max_val {
max_val = seg.x1
}
}
max_val
}
///|
/// Check if a point is inside the glyph using non-zero winding rule
fn is_point_inside_glyph(
px : Double,
py : Double,
segments : Array[LineSegment],
) -> Bool {
// Count crossings with direction
let mut winding = 0
for seg in segments {
let y0 = seg.y0
let y1 = seg.y1
// Check if segment crosses the horizontal line at py
if (y0 < py && y1 >= py) || (y1 < py && y0 >= py) {
// Calculate x intersection
let t = (py - y0) / (y1 - y0)
let x = seg.x0 + t * (seg.x1 - seg.x0)
// Only count if intersection is to the left of the point
if x < px {
// Direction: +1 if crossing upward, -1 if downward
if y0 < y1 {
winding = winding + 1
} else {
winding = winding - 1
}
}
}
}
// Non-zero rule: point is inside if winding number is non-zero
winding != 0
}
///|
/// Draw a line using Bresenham's algorithm (for outline rendering)
pub fn Rasterizer::draw_line(
self : Rasterizer,
x0 : Double,
y0 : Double,
x1 : Double,
y1 : Double,
) -> Unit {
let x_start = x0.to_int()
let y_start = y0.to_int()
let x_end = x1.to_int()
let y_end = y1.to_int()
let dx = (x_end - x_start).abs()
let dy = (y_end - y_start).abs()
let sx = if x_start < x_end { 1 } else { -1 }
let sy = if y_start < y_end { 1 } else { -1 }
let mut err = dx - dy
let mut x = x_start
let mut y = y_start
while true {
self.set_pixel(x, y, b"\xff"[0])
if x == x_end && y == y_end {
break
}
let e2 = 2 * err
if e2 > -dy {
err = err - dy
x = x + sx
}
if e2 < dx {
err = err + dx
y = y + sy
}
}
}
///|
/// Set a pixel with bounds checking
fn Rasterizer::set_pixel(
self : Rasterizer,
x : Int,
y : Int,
value : Byte,
) -> Unit {
guard x >= 0 && x < self.width && y >= 0 && y < self.height else { return }
let idx = y * self.width + x
self.pixels[idx] = value
}